diff --git a/src/chunks/chunks.odin b/src/chunks/chunks.odin index a5412ae..2a5ccd1 100644 --- a/src/chunks/chunks.odin +++ b/src/chunks/chunks.odin @@ -47,7 +47,7 @@ Error :: union #shared_nil { // ----------------------------------------- @(require_results) -format_error :: proc(ferr: Error) -> string { +error_string :: proc(ferr: Error) -> string { if ferr == nil do return "" switch e in ferr { diff --git a/src/chunks/read_assets.odin b/src/chunks/read_assets.odin index 63a42f2..bd46b6c 100644 --- a/src/chunks/read_assets.odin +++ b/src/chunks/read_assets.odin @@ -47,7 +47,6 @@ read_asset :: proc( found, i := asset_exists(r, clean_path) or_return if !found { - fmt.eprintln("asset: ", clean_path) return nil, .Asset_Not_Exist } @@ -79,7 +78,6 @@ read_asset_from_chunk :: proc( err: Error, ) { if cast(u32)len(r.indices) < index { - fmt.eprintf("asset[{}]\n", index) return nil, .Asset_Index_Out_Of_Bounds } diff --git a/src/chunks/write_metadata.odin b/src/chunks/write_metadata.odin index fd8410b..683e819 100644 --- a/src/chunks/write_metadata.odin +++ b/src/chunks/write_metadata.odin @@ -27,9 +27,21 @@ write_metadata :: proc( chunk_file := os.open(chunk_path, {.Read, .Write}) or_return defer os.close(chunk_file) - write_header(w, chunk_file, number_of_assets, size_per_chunk, compression) + write_header( + w, + chunk_file, + number_of_assets, + size_per_chunk, + compression, + ) or_return - write_asset_index(w, chunk_file, entries, number_of_assets, allocator) + write_asset_index( + w, + chunk_file, + entries, + number_of_assets, + allocator, + ) or_return return nil } @@ -89,7 +101,6 @@ write_asset_index :: proc( // TODO: Move to cli validation so it happens sooner path_length := len(f.relpath) if path_length > 512 { - fmt.eprintf("path: {} ({})\n", f.relpath, path_length) return .Path_Too_Large } diff --git a/src/file/file.odin b/src/file/file.odin index a54ba1f..42975d2 100644 --- a/src/file/file.odin +++ b/src/file/file.odin @@ -40,7 +40,7 @@ get_working_dir :: proc(allocator := context.allocator) -> (string, Error) { } @(require_results) -format_error :: proc(ferr: Error) -> string { +error_string :: proc(ferr: Error) -> string { if ferr == nil do return "" switch e in ferr { diff --git a/src/main.odin b/src/main.odin index 46ffa26..885f8bd 100644 --- a/src/main.odin +++ b/src/main.odin @@ -31,7 +31,7 @@ main :: proc() { entries, ld_err := file.list_dir_recursive(opts.input, working_dir) if ld_err != nil { - fmt.eprintln("error:", file.format_error(ld_err)) + fmt.eprintln("error:", file.error_string(ld_err)) os.exit(LIST_DIR_ERR) } defer file.delete_entries(&entries) @@ -47,7 +47,7 @@ main :: proc() { opts.compression, ) if wa_err != nil { - fmt.eprintln("error:", chunks.format_error(wa_err)) + fmt.eprintln("error:", chunks.error_string(wa_err)) os.exit(WRITE_ASSETS_ERR) } @@ -59,7 +59,7 @@ main :: proc() { opts.compression, ) if wm_err != nil { - fmt.eprintln("error:", chunks.format_error(wm_err)) + fmt.eprintln("error:", chunks.error_string(wm_err)) os.exit(WRITE_METADATA_ERR) } } diff --git a/tests/helpers.odin b/tests/helpers.odin index c766461..2c94ad5 100644 --- a/tests/helpers.odin +++ b/tests/helpers.odin @@ -13,8 +13,9 @@ TEST_OUTPUT_DIR :: "./build/tests/out" // ----------------------------------------- Test_State :: struct { - input_dir: string, - output_dir: string, + input_dir: string, + output_dir: string, + working_dir: string, } // ----------------------------------------- @@ -36,9 +37,13 @@ test_state_init :: proc( temp_output_path, err2 := os.mkdir_temp(TEST_OUTPUT_DIR, name, allocator) testing.expect(t, err2 == nil, os.error_string(err2)) + working_dir, err3 := os.get_working_directory(context.allocator) + testing.expect(t, err3 == nil, os.error_string(err3)) + return Test_State { input_dir = temp_input_path, output_dir = temp_output_path, + working_dir = working_dir, } } @@ -72,9 +77,13 @@ create_file :: proc( t: ^testing.T, path: string, content: string, + is_output_file := false, allocator := context.allocator, ) { - relpath, err := os.join_path({ts.input_dir, path}, allocator) + relpath, err := os.join_path( + {!is_output_file ? ts.input_dir : ts.output_dir, path}, + allocator, + ) testing.expect(t, err == nil, os.error_string(err)) defer delete(relpath, allocator) @@ -106,27 +115,48 @@ delete_path :: proc( } } -pack :: proc(ts: ^Test_State, t: ^testing.T, size: u64) { - wd, err := os.get_working_directory(context.allocator) - testing.expect(t, err == nil, os.error_string(err)) - entries, err2 := file.list_dir_recursive_by_path(ts.input_dir, wd) - testing.expect(t, err2 == nil, file.format_error(err2)) +pack :: proc( + ts: ^Test_State, + t: ^testing.T, + size: u64, + compression: u16 = 0, +) -> ( + wa_err: chunks.Error, + wm_err: chunks.Error, +) { + entries, err := file.list_dir_recursive_by_path( + ts.input_dir, + ts.working_dir, + ) + testing.expect(t, err == nil, file.error_string(err)) w := chunks.writer_init(cast(u64)len(entries)) defer chunks.writer_destroy(&w) - output_file, err3 := os.open(ts.output_dir, {.Read}) - testing.expect(t, err3 == nil, os.error_string(err3)) + output_file, err2 := os.open(ts.output_dir, {.Read}) + testing.expect(t, err2 == nil, os.error_string(err2)) defer os.close(output_file) - err4 := chunks.write_assets(&w, output_file, entries[:], size, 0) - testing.expect(t, err4 == nil, chunks.format_error(err4)) + wa_err = chunks.write_assets( + &w, + output_file, + entries[:], + size, + compression, + ) + if wa_err != nil do return wa_err, nil - err5 := chunks.write_metadata(&w, output_file, entries[:], size, 0) - testing.expect(t, err5 == nil, chunks.format_error(err5)) + wm_err = chunks.write_metadata( + &w, + output_file, + entries[:], + size, + compression, + ) + return wa_err, wm_err } -read :: proc( +read_asset :: proc( ts: ^Test_State, t: ^testing.T, path: string, @@ -139,22 +169,58 @@ read :: proc( testing.expect(t, err == nil, os.error_string(err)) defer delete(relpath, allocator) - file, err2 := os.open(ts.output_dir) + file, err2 := os.open(ts.output_dir, {.Read}) testing.expect(t, err2 == nil, os.error_string(err2)) defer os.close(file) r, err3 := chunks.reader_init(file) - testing.expect(t, err3 == nil, chunks.format_error(err3)) + testing.expect(t, err3 == nil, chunks.error_string(err3)) defer chunks.reader_destroy(&r) exists, i, err4 := chunks.asset_exists(&r, relpath) - testing.expect(t, err4 == nil, chunks.format_error(err4)) + testing.expect(t, err4 == nil, chunks.error_string(err4)) if exists { bytes, err5 := chunks.read_asset(&r, relpath) - testing.expect(t, err5 == nil, chunks.format_error(err5)) + testing.expect(t, err5 == nil, chunks.error_string(err5)) return true, string(bytes) } return false, {} } + +read_asset_raw :: proc( + ts: ^Test_State, + t: ^testing.T, + path: string, + allocator := context.allocator, +) -> ( + bytes: []u8, + err: chunks.Error, +) { + relpath, err2 := os.join_path({ts.input_dir, path}, allocator) + testing.expect(t, err2 == nil, os.error_string(err2)) + defer delete(relpath, allocator) + + output_file, err3 := os.open(ts.output_dir, {.Read}) + testing.expect(t, err3 == nil, os.error_string(err3)) + defer os.close(output_file) + + r, err4 := chunks.reader_init(output_file) + testing.expect(t, err4 == nil, chunks.error_string(err4)) + defer chunks.reader_destroy(&r) + + bytes, err = chunks.read_asset(&r, relpath) + return bytes, err +} + +reader_init_raw :: proc(ts: ^Test_State, t: ^testing.T) -> chunks.Error { + output_file, err2 := os.open(ts.output_dir, {.Read}) + testing.expect(t, err2 == nil, os.error_string(err2)) + defer os.close(output_file) + + r, err := chunks.reader_init(output_file) + defer chunks.reader_destroy(&r) + + return err +} diff --git a/tests/tests.odin b/tests/tests.odin index 2893d7e..c080a7e 100644 --- a/tests/tests.odin +++ b/tests/tests.odin @@ -3,6 +3,11 @@ package tests import "core:strings" import "core:testing" +import "src:chunks" + +// ----------------------------------------- +// Single file tests + @(test) single_asset_single_chunk_read_from_path :: proc(t: ^testing.T) { // Arrange @@ -11,14 +16,14 @@ single_asset_single_chunk_read_from_path :: proc(t: ^testing.T) { defer test_state_destroy(&ts, t) delete_path(&ts, t, "") - create_file(&ts, t, "single_tiny_file", "hello world!") + create_file(&ts, t, "single_asset", "hello world!") pack(&ts, t, 750) delete_path(&ts, t, "") - create_file(&ts, t, "single_tiny_file", "hello world from path!") + create_file(&ts, t, "single_asset", "hello world from path!") // Act - _, read := read(&ts, t, "single_tiny_file") + _, read := read_asset(&ts, t, "single_asset") // Assert @@ -33,13 +38,13 @@ single_asset_single_chunk_read_from_chunk :: proc(t: ^testing.T) { defer test_state_destroy(&ts, t) delete_path(&ts, t, "") - create_file(&ts, t, "single_tiny_file", "hello world!") + create_file(&ts, t, "single_asset", "hello world!") pack(&ts, t, 750) delete_path(&ts, t, "") // Act - _, read := read(&ts, t, "single_tiny_file") + _, read := read_asset(&ts, t, "single_asset") // Assert @@ -56,13 +61,13 @@ single_asset_multiple_chunks_read_from_chunk :: proc(t: ^testing.T) { delete_path(&ts, t, "") content := strings.repeat("abcde", 400) defer delete(content) - create_file(&ts, t, "multi_chunk_file", content) + create_file(&ts, t, "single_asset", content) pack(&ts, t, 750) delete_path(&ts, t, "") // Act - _, read := read(&ts, t, "multi_chunk_file") + _, read := read_asset(&ts, t, "single_asset") // Assert @@ -70,6 +75,7 @@ single_asset_multiple_chunks_read_from_chunk :: proc(t: ^testing.T) { } // ----------------------------------------- +// Multi file tests @(test) multiple_assets_single_chunk_read_from_path :: proc(t: ^testing.T) { @@ -90,9 +96,9 @@ multiple_assets_single_chunk_read_from_path :: proc(t: ^testing.T) { // Act - _, a := read(&ts, t, "a") - _, b := read(&ts, t, "b") - _, c := read(&ts, t, "c") + _, a := read_asset(&ts, t, "a") + _, b := read_asset(&ts, t, "b") + _, c := read_asset(&ts, t, "c") // Assert @@ -117,9 +123,9 @@ multiple_assets_single_chunk_read_from_chunk :: proc(t: ^testing.T) { // Act - _, a := read(&ts, t, "a") - _, b := read(&ts, t, "b") - _, c := read(&ts, t, "c") + _, a := read_asset(&ts, t, "a") + _, b := read_asset(&ts, t, "b") + _, c := read_asset(&ts, t, "c") // Assert @@ -150,9 +156,9 @@ multiple_assets_multiple_chunks_read_from_chunk :: proc(t: ^testing.T) { // Act - _, a := read(&ts, t, "a") - _, b := read(&ts, t, "b") - _, c := read(&ts, t, "c") + _, a := read_asset(&ts, t, "a") + _, b := read_asset(&ts, t, "b") + _, c := read_asset(&ts, t, "c") // Assert @@ -162,6 +168,7 @@ multiple_assets_multiple_chunks_read_from_chunk :: proc(t: ^testing.T) { } // ----------------------------------------- +// Nested path tests @(test) single_asset_single_chunk_nested_path_read_from_path :: proc(t: ^testing.T) { @@ -171,14 +178,14 @@ single_asset_single_chunk_nested_path_read_from_path :: proc(t: ^testing.T) { defer test_state_destroy(&ts, t) delete_path(&ts, t, "") - create_file(&ts, t, "subdir/nested_file", "hello nested!") + create_file(&ts, t, "subdir/nested_asset", "hello nested!") pack(&ts, t, 750) delete_path(&ts, t, "") - create_file(&ts, t, "subdir/nested_file", "hello nested from path!") + create_file(&ts, t, "subdir/nested_asset", "hello nested from path!") // Act - _, read := read(&ts, t, "subdir/nested_file") + _, read := read_asset(&ts, t, "subdir/nested_asset") // Assert @@ -193,13 +200,13 @@ single_asset_single_chunk_nested_path_read_from_chunk :: proc(t: ^testing.T) { defer test_state_destroy(&ts, t) delete_path(&ts, t, "") - create_file(&ts, t, "subdir/nested_file", "hello nested!") + create_file(&ts, t, "subdir/nested_asset", "hello nested!") pack(&ts, t, 750) delete_path(&ts, t, "") // Act - _, read := read(&ts, t, "subdir/nested_file") + _, read := read_asset(&ts, t, "subdir/nested_asset") // Assert @@ -218,13 +225,13 @@ single_asset_multiple_chunks_nested_path_read_from_chunk :: proc( delete_path(&ts, t, "") content := strings.repeat("abcde", 50) defer delete(content) - create_file(&ts, t, "subdir/nested_file", content) + create_file(&ts, t, "subdir/nested_asset", content) pack(&ts, t, 750) delete_path(&ts, t, "") // Act - _, read := read(&ts, t, "subdir/nested_file") + _, read := read_asset(&ts, t, "subdir/nested_asset") // Assert @@ -232,6 +239,7 @@ single_asset_multiple_chunks_nested_path_read_from_chunk :: proc( } // ----------------------------------------- +// Asset exists tests @(test) asset_in_path_not_in_chunk_read_matching_file :: proc(t: ^testing.T) { @@ -248,7 +256,7 @@ asset_in_path_not_in_chunk_read_matching_file :: proc(t: ^testing.T) { // Act - _, read := read(&ts, t, "target") + _, read := read_asset(&ts, t, "target") // Assert @@ -269,7 +277,7 @@ asset_in_chunk_not_in_path_read_matching_file :: proc(t: ^testing.T) { // Act - _, read := read(&ts, t, "target") + _, read := read_asset(&ts, t, "target") // Assert @@ -291,7 +299,7 @@ asset_in_chunk_and_in_path_read_matching_file :: proc(t: ^testing.T) { // Act - _, read := read(&ts, t, "target") + _, read := read_asset(&ts, t, "target") // Assert @@ -305,13 +313,14 @@ asset_in_path_not_in_chunk_read_non_matching_file :: proc(t: ^testing.T) { ts := test_state_init(t, #procedure) defer test_state_destroy(&ts, t) + // Empty asset dir pack(&ts, t, 750) delete_path(&ts, t, "") create_file(&ts, t, "target", "hello from path!") // Act - exists, _ := read(&ts, t, "non_matching") + exists, _ := read_asset(&ts, t, "non_matching") // Assert @@ -332,7 +341,7 @@ asset_in_chunk_not_in_path_read_non_matching_file :: proc(t: ^testing.T) { // Act - exists, _ := read(&ts, t, "non_matching") + exists, _ := read_asset(&ts, t, "non_matching") // Assert @@ -354,9 +363,159 @@ asset_in_chunk_and_in_path_read_non_matching_file :: proc(t: ^testing.T) { // Act - exists, _ := read(&ts, t, "non_matching") + exists, _ := read_asset(&ts, t, "non_matching") // Assert testing.expect(t, !exists) } + +// ----------------------------------------- +// Error return tests + +@(test) +chunk_size_too_small :: proc(t: ^testing.T) { + // Arrange + + ts := test_state_init(t, #procedure) + defer test_state_destroy(&ts, t) + + delete_path(&ts, t, "") + create_file(&ts, t, "chunk_too_small", "hello world!") + + // Act + + wa_err, _ := pack(&ts, t, 100, 0) + + // Assert + + testing.expect( + t, + wa_err == chunks.Write_Error.Chunk_Size_Too_Small, + chunks.error_string(wa_err), + ) +} + +@(test) +non_chunk_file_read :: proc(t: ^testing.T) { + // Arrange + + ts := test_state_init(t, #procedure) + defer test_state_destroy(&ts, t) + + create_file( + &ts, + t, + "CHUNK0", + "this is definitely not a chunk file, nope", + true, + ) + + // Act + + err := reader_init_raw(&ts, t) + + // Assert + + testing.expect( + t, + err == chunks.Read_Error.File_Not_A_Chunk, + chunks.error_string(err), + ) +} + +@(test) +compression_unimplemented :: proc(t: ^testing.T) { + // Arrange + + ts := test_state_init(t, #procedure) + defer test_state_destroy(&ts, t) + + delete_path(&ts, t, "") + create_file(&ts, t, "tiny_file", "hello world!") + + wa_err, wm_err := pack(&ts, t, 750, 1) + testing.expect(t, wa_err == nil, chunks.error_string(wa_err)) + testing.expect(t, wm_err == nil, chunks.error_string(wm_err)) + + // Act + + err := reader_init_raw(&ts, t) + + // Assert + + testing.expect( + t, + err == chunks.Read_Error.Unimplemented, + chunks.error_string(err), + ) +} + +@(test) +read_non_existent_asset :: proc(t: ^testing.T) { + // Arrange + + ts := test_state_init(t, #procedure) + defer test_state_destroy(&ts, t) + + delete_path(&ts, t, "") + create_file(&ts, t, "target", "hello from chunk!") + pack(&ts, t, 750) + delete_path(&ts, t, "") + + // Act + + _, err := read_asset_raw(&ts, t, "non_matching") + + // Assert + + testing.expect( + t, + err == chunks.Read_Error.Asset_Not_Exist, + chunks.error_string(err), + ) +} + +@(test) +path_too_large :: proc(t: ^testing.T) { + // Arrange + + ts := test_state_init(t, #procedure) + defer test_state_destroy(&ts, t) + + delete_path(&ts, t, "") + dir_segment := strings.repeat("a", 100) + defer delete(dir_segment) + + // Nested to avoid OS filename limit of 255 + nested_path := strings.concatenate( + { + dir_segment, + "/", + dir_segment, + "/", + dir_segment, + "/", + dir_segment, + "/", + dir_segment, + "/", + "path_too_large", + }, + ) + defer delete(nested_path) + + create_file(&ts, t, nested_path, "hello nested!") + + // Act + + _, wm_err := pack(&ts, t, 750, 0) + + // Assert + + testing.expect( + t, + wm_err == chunks.Write_Error.Path_Too_Large, + chunks.error_string(wm_err), + ) +}