diff --git a/sp.h b/sp.h index ee194f7d..4d4375cd 100644 --- a/sp.h +++ b/sp.h @@ -3058,23 +3058,22 @@ typedef struct { typedef struct { sp_fs_dir_t dir; SP_ALIGNED u8 buf [SP_FS_IT_BUF_SIZE]; - sp_str_t path; + u32 len; } sp_fs_it_frame_t; typedef struct { sp_mem_t mem; sp_fs_entry_t entry; sp_da(sp_fs_it_frame_t) stack; + c8 path [SP_PATH_MAX]; bool recursive; sp_err_t err; } sp_fs_it_t; #define sp_fs_for(mem, dir, it) \ - for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_valid(&it); sp_fs_it_next(&it)) + for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_next(&it);) #define sp_fs_for_recursive(mem, dir, it) \ - for (sp_fs_it_t it = sp_fs_it_new_recursive(mem, dir); sp_fs_it_valid(&it); sp_fs_it_next(&it)) -#define sp_fs_for_it(it) \ - for (; sp_fs_it_valid(&it); sp_fs_it_next(&it)) + for (sp_fs_it_t it = sp_fs_it_new_recursive(mem, dir); sp_fs_it_next(&it);) SP_API sp_str_t sp_fs_get_name(sp_str_t path); SP_API sp_str_t sp_fs_parent_path(sp_str_t path); @@ -3121,11 +3120,7 @@ SP_API sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* o SP_API sp_err_t sp_fs_dir_close(sp_fs_dir_t* it); SP_API sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path); SP_API sp_fs_it_t sp_fs_it_new_recursive(sp_mem_t mem, sp_str_t path); -SP_API void sp_fs_it_begin(sp_fs_it_t* it, sp_str_t path); -SP_API void sp_fs_it_next(sp_fs_it_t* it); -SP_API void sp_fs_it_push(sp_fs_it_t* it, sp_str_t path); -SP_API void sp_fs_it_unwind(sp_fs_it_t* it); -SP_API bool sp_fs_it_valid(sp_fs_it_t* it); +SP_API bool sp_fs_it_next(sp_fs_it_t* it); SP_API void sp_fs_it_deinit(sp_fs_it_t* it); // literally sys+normalize, if normalize isnt needed this isn't either? @@ -20069,7 +20064,7 @@ sp_err_t sp_fs_remove_file(sp_str_t path) { sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, sp_mem_slice_t buf) { *it = sp_zero_s(sp_fs_dir_t); - if (buf.len < SP_SYS_DIR_MIN_BUF) return SP_ERR_SYS_BUG; + sp_assert(buf.len >= SP_SYS_DIR_MIN_BUF); sp_sys_fd_t dir_fd = SP_SYS_INVALID_FD; sp_try(sp_sys_open_dir_s(fd, path, &dir_fd)); @@ -20098,7 +20093,6 @@ sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out) { sp_sys_dir_entry_t entry = sp_zero; sp_try(sp_sys_dir_parse(&it->dir, &it->buf, &it->cursor, &entry)); - if (!entry.name) continue; sp_str_t name = sp_str(entry.name, entry.len); if (sp_str_equal(name, sp_str_lit(".")) || sp_str_equal(name, sp_str_lit(".."))) continue; @@ -20113,94 +20107,108 @@ sp_err_t sp_fs_dir_close(sp_fs_dir_t* it) { return sp_sys_dir_close(&it->dir); } -void sp_fs_it_unwind(sp_fs_it_t* it) { +SP_PRIVATE void sp_fs_it_unwind(sp_fs_it_t* it) { sp_da_for(it->stack, i) { sp_fs_dir_close(&it->stack[i].dir); } sp_da_clear(it->stack); } -void sp_fs_it_push(sp_fs_it_t* it, sp_str_t path) { - sp_fs_it_frame_t frame = sp_zero; - it->err = sp_fs_dir_open(&frame.dir, sp_sys_get_root(0), path, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE)); - if (it->err) return; - frame.path = sp_str_copy(it->mem, path); +SP_PRIVATE sp_err_t sp_fs_it_join(sp_fs_it_t* it, u32 prefix, sp_str_t name, u32* len) { + u32 sep = prefix && !sp_fs_is_sep(it->path[prefix - 1]); + if (prefix + sep + name.len >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; + if (sep) it->path[prefix] = '/'; + sp_str_copy_to(name, it->path + prefix + sep, name.len); + *len = prefix + sep + name.len; + return SP_OK; +} + +SP_PRIVATE sp_err_t sp_fs_it_push(sp_fs_it_t* it, u32 len) { + sp_fs_it_frame_t frame = { .len = len }; + sp_try(sp_fs_dir_open(&frame.dir, sp_sys_get_root(0), sp_str(it->path, len), sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); sp_da_push(it->stack, frame); + return SP_OK; } -void sp_fs_it_begin(sp_fs_it_t* it, sp_str_t path) { - sp_fs_it_push(it, path); - if (it->err) return; - sp_fs_it_next(it); +SP_PRIVATE sp_err_t sp_fs_it_open(sp_fs_it_t* it, sp_str_t path) { + u32 len = 0; + sp_try(sp_fs_it_join(it, 0, sp_fs_trim_path(path), &len)); + return sp_fs_it_push(it, len); } -void sp_fs_it_next(sp_fs_it_t* it) { +SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { while (!sp_da_empty(it->stack)) { sp_fs_it_frame_t* top = sp_da_back(it->stack); top->dir.buf.data = top->buf; sp_fs_dir_entry_t d = sp_zero; - it->err = sp_fs_dir_next(&top->dir, &d); - if (it->err) { - sp_fs_it_unwind(it); - return; + sp_try(sp_fs_dir_next(&top->dir, &d)); + if (!d.name.data) { + sp_da_pop(it->stack); + sp_try(sp_fs_dir_close(&top->dir)); + continue; } - if (d.name.data) { - it->entry.path = sp_fs_join_path(it->mem, top->path, d.name); - it->entry.name = sp_str_sub(it->entry.path, it->entry.path.len - d.name.len, d.name.len); - it->entry.kind = d.kind; - - // If the OS didn't give us a hint, specifically get it from metadata - if (it->entry.kind == SP_FS_KIND_NONE) { - it->entry.kind = sp_fs_get_link_kind(it->entry.path); - } + u32 len = 0; + sp_try(sp_fs_it_join(it, top->len, d.name, &len)); + it->entry = (sp_fs_entry_t) { + .path = sp_str(it->path, len), + .name = sp_str(it->path + len - d.name.len, d.name.len), + .kind = d.kind, + }; - if (it->recursive && it->entry.kind == SP_FS_KIND_DIR) { - sp_fs_it_push(it, it->entry.path); - if (it->err) sp_fs_it_unwind(it); - } - return; + // If the OS didn't give us a hint, specifically get it from metadata + if (it->entry.kind == SP_FS_KIND_NONE) { + it->entry.kind = sp_fs_get_link_kind(it->entry.path); } - it->err = sp_fs_dir_close(&top->dir); - sp_da_pop(it->stack); - if (it->err) { - sp_fs_it_unwind(it); - return; + if (it->recursive && it->entry.kind == SP_FS_KIND_DIR) { + sp_try(sp_fs_it_push(it, len)); } + return SP_OK; } + return SP_OK; } -bool sp_fs_it_valid(sp_fs_it_t* it) { +bool sp_fs_it_next(sp_fs_it_t* it) { + if (it->err) return false; + it->entry = sp_zero_s(sp_fs_entry_t); + it->err = sp_fs_it_step(it); + if (it->err) sp_fs_it_unwind(it); return !it->err && !sp_da_empty(it->stack); } void sp_fs_it_deinit(sp_fs_it_t* it) { - sp_da_for(it->stack, i) { - sp_fs_dir_close(&it->stack[i].dir); - } + sp_fs_it_unwind(it); sp_da_free(it->stack); } -sp_err_t sp_fs_collect(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out) { +SP_PRIVATE sp_fs_entry_t sp_fs_entry_copy(sp_mem_t mem, sp_fs_entry_t entry) { + sp_str_t path = sp_str_copy(mem, entry.path); + return (sp_fs_entry_t) { + .path = path, + .name = sp_str_suffix(path, (s32)entry.name.len), + .kind = entry.kind, + }; +} + +SP_PRIVATE sp_err_t sp_fs_collect_it(sp_mem_t mem, sp_fs_it_t* it, sp_da(sp_fs_entry_t)* out) { *out = sp_da_new(mem, sp_fs_entry_t); + while (sp_fs_it_next(it)) { + sp_da_push(*out, sp_fs_entry_copy(mem, it->entry)); + } + sp_fs_it_deinit(it); + return it->err; +} +sp_err_t sp_fs_collect(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out) { sp_fs_it_t it = sp_fs_it_new(mem, path); - sp_fs_for_it(it) { - sp_da_push(*out, it.entry); - } - return it.err; + return sp_fs_collect_it(mem, &it, out); } sp_err_t sp_fs_collect_recursive(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out) { - *out = sp_da_new(mem, sp_fs_entry_t); - sp_fs_it_t it = sp_fs_it_new_recursive(mem, path); - for (; sp_fs_it_valid(&it); sp_fs_it_next(&it)) { - sp_da_push(*out, it.entry); - } - return it.err; + return sp_fs_collect_it(mem, &it, out); } sp_err_t sp_fs_remove_dir(sp_str_t path) { @@ -20481,7 +20489,7 @@ sp_err_t sp_fs_copy_tree(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { if (!err && sp_fs_tree_contains(s.mem, from, to)) err = SP_ERR_SYS_INVALID; if (!err) err = sp_fs_create_dir(to); - for (; !err && sp_fs_it_valid(&it); sp_fs_it_next(&it)) { + while (!err && sp_fs_it_next(&it)) { sp_str_t rel = sp_str_sub(it.entry.path, (s32)skip, (s32)(it.entry.path.len - skip)); sp_str_t dst = sp_fs_join_path(s.mem, to, rel); switch (it.entry.kind) { @@ -20551,14 +20559,14 @@ sp_err_t sp_fs_copy_into(sp_str_t from, sp_str_t dir) { sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path) { sp_fs_it_t it = { .mem = mem }; sp_da_init(mem, it.stack); - sp_fs_it_begin(&it, path); + it.err = sp_fs_it_open(&it, path); return it; } sp_fs_it_t sp_fs_it_new_recursive(sp_mem_t mem, sp_str_t path) { sp_fs_it_t it = { .mem = mem, .recursive = true }; sp_da_init(mem, it.stack); - sp_fs_it_begin(&it, path); + it.err = sp_fs_it_open(&it, path); return it; } diff --git a/spn.toml b/spn.toml index 99ae3a9d..c5d6094f 100644 --- a/spn.toml +++ b/spn.toml @@ -22,13 +22,14 @@ source = [ "test/sys/tty/contract.c", "test/sys/tty/mode.c", "test/sys/vtable.c", "test/sys/wait/event.c", "test/sys/wait/wait.c", "test/sys/inherit.c", - "test/fs/iter/os.c", "test/fs/iter/iter.c", "test/fs/wtf8/*.c", + "test/fs/dir/*.c", "test/fs/it/*.c", "test/fs/wtf8/*.c", "test/fs/*.c", "test/fs/windows/nt_path.c", "test/msvc/*.c", "test/mem/uninitialized.c", "test/atomic.c", "test/sync/*.c", "test/sync/os/*.c" ] +include = [".", "test/fs"] [[test]] name = "meta" @@ -53,7 +54,8 @@ source = ["test/main.c", "test/atomic.c"] [[test]] name = "fs" -source = ["test/main.c", "test/fs/iter/iter.c", "test/fs/iter/os.c", "test/fs/wtf8/*.c", "test/fs/*.c", "test/fs/windows/nt_path.c"] +source = ["test/main.c", "test/fs/dir/*.c", "test/fs/it/*.c", "test/fs/wtf8/*.c", "test/fs/*.c", "test/fs/windows/nt_path.c"] +include = [".", "test/fs"] [[test]] name = "msvct" diff --git a/test/fs/collect.c b/test/fs/collect.c index 99ac8ca1..3220bf72 100644 --- a/test/fs/collect.c +++ b/test/fs/collect.c @@ -3,241 +3,81 @@ typedef struct { const c8* path; sp_fs_kind_t kind; +} entry_t; + +typedef struct { + sp_err_t err; + entry_t entries [FS_MAX_PATHS]; } expect_t; typedef struct { const c8* name; fs_setup_t setup [FS_MAX_SETUP]; bool recursive; - sp_err_t err; - expect_t expect [FS_MAX_PATHS]; + expect_t expect; } test_t; static const test_t tests [] = { { - .name = "empty_dir", - .setup = { - { "R", FS_SETUP_DIR }, - }, - }, - { - .name = "nonexistent", - .err = SP_ERR_SYS_NOT_FOUND, - }, - { - .name = "file_not_dir", - .setup = { - { "R" }, - }, - .err = SP_ERR_SYS_NOT_DIR, - }, - { - .name = "single_file", - .setup = { - { "R/A" }, - }, - .expect = { - { "A", SP_FS_KIND_FILE }, - }, - }, - { - .name = "multiple_files", - .setup = { - { "R/A" }, - { "R/B" }, - { "R/C" }, - }, - .expect = { - { "A", SP_FS_KIND_FILE }, - { "B", SP_FS_KIND_FILE }, - { "C", SP_FS_KIND_FILE }, - }, - }, - { - .name = "subdirectory", - .setup = { - { "R/A", FS_SETUP_DIR }, - }, - .expect = { - { "A", SP_FS_KIND_DIR }, - }, - }, - { - .name = "mixed_types", - .setup = { - { "R/A" }, - { "R/B", FS_SETUP_DIR }, - { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "R/A" }, - }, - .expect = { - { "A", SP_FS_KIND_FILE }, - { "B", SP_FS_KIND_DIR }, - { "L", SP_FS_KIND_SYMLINK }, - }, - }, - { - .name = "hidden_file", - .setup = { - { "R/.A" }, - { "R/B" }, - }, - .expect = { - { ".A", SP_FS_KIND_FILE }, - { "B", SP_FS_KIND_FILE }, - }, - }, - { - .name = "does_not_recurse", - .setup = { - { "R/A", FS_SETUP_DIR }, - { "R/A/B" }, - { "R/C" }, - }, - .expect = { - { "A", SP_FS_KIND_DIR }, - { "C", SP_FS_KIND_FILE }, - }, - }, - { - .name = "recursive_empty_dir", - .setup = { - { "R", FS_SETUP_DIR }, - }, - .recursive = true, - }, - { - .name = "recursive_flat_dir", + .name = "owned", .setup = { { "R/A" }, { "R/B" }, - { "R/C" }, }, - .recursive = true, .expect = { - { "A", SP_FS_KIND_FILE }, - { "B", SP_FS_KIND_FILE }, - { "C", SP_FS_KIND_FILE }, + .entries = { + { "A", SP_FS_KIND_FILE }, + { "B", SP_FS_KIND_FILE }, + }, }, }, { - .name = "recursive_nested_dirs", - .setup = { - { "R/A", FS_SETUP_DIR }, - { "R/A/B", FS_SETUP_DIR }, - { "R/C" }, - { "R/A/D" }, - { "R/A/B/E" }, - }, - .recursive = true, - .expect = { - { "C", SP_FS_KIND_FILE }, - { "A", SP_FS_KIND_DIR }, - { "A/D", SP_FS_KIND_FILE }, - { "A/B", SP_FS_KIND_DIR }, - { "A/B/E", SP_FS_KIND_FILE }, - }, - }, - { - .name = "recursive_symlink_not_followed", + .name = "owned_recursive", .setup = { { "R/A", FS_SETUP_DIR }, { "R/A/B" }, - { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "R/A" }, }, .recursive = true, .expect = { - { "A", SP_FS_KIND_DIR }, - { "A/B", SP_FS_KIND_FILE }, - { "L", SP_FS_KIND_SYMLINK }, + .entries = { + { "A", SP_FS_KIND_DIR }, + { "A/B", SP_FS_KIND_FILE }, + }, }, }, { - .name = "recursive_nonexistent", - .recursive = true, - .err = SP_ERR_SYS_NOT_FOUND, - }, - { - .name = "recursive_file_not_dir", - .setup = { - { "R" }, - }, - .recursive = true, - .err = SP_ERR_SYS_NOT_DIR, - }, - { - .name = "unicode_entries", - .setup = { - { "R/\xc3\xb1\x61\x6d\x65.txt" }, - { "R/\xc3\xbc\x6e\x69", FS_SETUP_DIR }, - }, + .name = "propagates_error", .expect = { - { "\xc3\xb1\x61\x6d\x65.txt", SP_FS_KIND_FILE }, - { "\xc3\xbc\x6e\x69", SP_FS_KIND_DIR }, + .err = SP_ERR_SYS_NOT_FOUND, }, }, -#if defined(SP_POSIX) - { - .name = "special_file", - .setup = { - { "R", FS_SETUP_DIR }, - { .path = "R/F", .kind = FS_SETUP_FIFO }, - }, - .expect = { - // a fifo is listed, with a kind sp_fs can't express - { "F" }, - }, - }, -#endif }; -static void collect(sp_test_t* t, const test_t* c, sp_str_t root, sp_str_t base) { +sp_test_each(fs, collect, test_t, tests) { sp_mem_t mem = sp_test_arena(t); - sp_da(sp_fs_entry_t) results; - sp_err_t err = c->recursive - ? sp_fs_collect_recursive(mem, base, &results) - : sp_fs_collect(mem, base, &results); - sp_expect_err_eq(t, err, c->err); - - u32 expected = 0; - sp_carr_for(c->expect, it) { - if (!c->expect[it].path) break; - expected++; - } - - sp_expect_eq(t, sp_da_size(results), expected); - - sp_da_for(results, n) { - sp_fs_entry_t entry = results[n]; - sp_expect(t, !sp_str_contains(entry.path, sp_str_lit("//"))); - sp_str_t tail = sp_str_sub(entry.path, entry.path.len - entry.name.len, entry.name.len); - sp_expect(t, sp_str_equal(tail, entry.name)); - } + sp_str_t sandbox = sp_test_dir(t); + fs_apply_setup(t, sandbox, it->setup); + sp_str_t root = sp_fs_join_path(mem, sandbox, sp_str_lit("R")); - sp_carr_for(c->expect, i) { - const expect_t* want = &c->expect[i]; + fs_match_t matches [FS_MAX_PATHS] = sp_zero; + u32 n = 0; + sp_carr_for(it->expect.entries, e) { + const entry_t* want = &it->expect.entries[e]; if (!want->path) break; - sp_str_t path = sp_fs_join_path(mem, root, sp_str_view(want->path)); - - bool found = false; - sp_da_for(results, n) { - if (!sp_str_equal(results[n].path, path)) continue; - sp_expect_eq(t, results[n].kind, want->kind); - found = true; - break; - } - sp_expect(t, found); + matches[n++] = (fs_match_t) { .key = sp_fs_join_path(mem, root, sp_cstr_as_str(want->path)), .kind = want->kind }; } -} -sp_test_each(fs, collect, test_t, tests) { - skip_if_symlinks_needed(t, it->setup); + sp_da(sp_fs_entry_t) results = sp_zero; + sp_err_t err = it->recursive + ? sp_fs_collect_recursive(mem, root, &results) + : sp_fs_collect(mem, root, &results); + sp_expect_err_eq(t, err, it->expect.err); - sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); - fs_apply_setup(t, sandbox, it->setup); - - sp_str_t root = sp_fs_join_path(mem, sandbox, sp_str_lit("R")); - collect(t, it, root, root); - collect(t, it, root, sp_str_concat(mem, root, sp_str_lit("/"))); + sp_expect_eq(t, sp_da_size(results), (u64)n); + sp_da_for(results, i) { + sp_expect_str_eq(t, results[i].name, sp_fs_get_name(results[i].path)); + fs_match(t, matches, n, results[i].path, results[i].kind); + } + fs_match_finish(t, matches, n); return SP_OK; } diff --git a/test/fs/dir/os.c b/test/fs/dir/os.c new file mode 100644 index 00000000..6647b2cd --- /dev/null +++ b/test/fs/dir/os.c @@ -0,0 +1,139 @@ +#include "fs.h" + +#define BULK_PREFIX "R" + +#define A16 "AAAAAAAAAAAAAAAA" +#define A255 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 "AAAAAAAAAAAAAAA" +#define EE "\xe2\x82\xac" +#define E5 EE EE EE EE EE +#define E25 E5 E5 E5 E5 E5 +#define E255 E25 E25 E25 E5 E5 + +typedef struct { + const c8* name; + sp_fs_kind_t kind; +} entry_t; + +typedef struct { + const c8* name; + fs_setup_t setup [FS_MAX_SETUP]; + const c8* dir; + bool relative; + u32 bulk; + entry_t expect [FS_MAX_PATHS]; +} test_t; + +static const test_t tests [] = { + { + .name = "lists_entries_with_kinds", + .setup = { + { "A" }, + { "B", FS_SETUP_DIR }, + { "C" }, + }, + .expect = { + { "A", SP_FS_KIND_FILE }, + { "B", SP_FS_KIND_DIR }, + { "C", SP_FS_KIND_FILE }, + }, + }, + { + .name = "symlink_kind", + .setup = { + { "A" }, + { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + }, + .expect = { + { "A", SP_FS_KIND_FILE }, + { "L", SP_FS_KIND_SYMLINK }, + }, + }, + { + .name = "empty_directory", + }, + { + .name = "honors_dirfd", + .setup = { + { "S", FS_SETUP_DIR }, + { "S/A" }, + }, + .dir = "S", + .relative = true, + .expect = { + { "A", SP_FS_KIND_FILE }, + }, + }, + { + .name = "max_length_name", + .setup = { + { A255 }, + }, + .expect = { + { A255, SP_FS_KIND_FILE }, + }, + }, + { + .name = "long_multibyte_name", + .setup = { + { E255 }, + }, + .expect = { + { E255, SP_FS_KIND_FILE }, + }, + }, + { + .name = "refills_across_batches", + .bulk = 96, + }, +}; + +sp_test_each(fs, dir, test_t, tests) { + skip_if_symlinks_needed(t, it->setup); + + sp_mem_t mem = sp_test_arena(t); + sp_str_t sandbox = sp_test_dir(t); + fs_apply_setup(t, sandbox, it->setup); + + sp_str_t dir = it->dir ? sp_fs_join_path(mem, sandbox, sp_cstr_as_str(it->dir)) : sandbox; + + fs_match_t* matches = sp_alloc_n(mem, fs_match_t, FS_MAX_PATHS + it->bulk); + u32 n = 0; + sp_carr_for(it->expect, e) { + if (!it->expect[e].name) break; + matches[n++] = (fs_match_t) { .key = sp_cstr_as_str(it->expect[e].name), .kind = it->expect[e].kind }; + } + sp_for(b, it->bulk) { + sp_str_t name = sp_fmt(mem, "{}{}", sp_fmt_cstr(BULK_PREFIX), sp_fmt_uint(b)).value; + sp_expect_ok(t, sp_fs_create_file(sp_fs_join_path(mem, dir, name))); + matches[n++] = (fs_match_t) { .key = name, .kind = SP_FS_KIND_FILE }; + } + + sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; + if (it->relative) { + sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &sandbox_fd)); + } + + SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; + sp_fs_dir_t iter = sp_zero; + sp_err_t open_err = it->relative + ? sp_fs_dir_open(&iter, sandbox_fd, sp_cstr_as_str(it->dir), sp_mem_slice(buf, sizeof(buf))) + : sp_fs_dir_open(&iter, sp_sys_get_root(0), dir, sp_mem_slice(buf, sizeof(buf))); + sp_expect_ok(t, open_err); + + if (!open_err) { + sp_err_t walk = SP_OK; + while (true) { + sp_fs_dir_entry_t entry = sp_zero; + walk = sp_fs_dir_next(&iter, &entry); + if (walk) break; + if (!entry.name.data) break; + fs_match(t, matches, n, entry.name, entry.kind); + } + sp_expect_ok(t, walk); + sp_expect_ok(t, sp_fs_dir_close(&iter)); + } + fs_match_finish(t, matches, n); + + if (sandbox_fd != SP_SYS_INVALID_FD) sp_sys_close(sandbox_fd); + return SP_OK; +} diff --git a/test/fs/dir/sim.c b/test/fs/dir/sim.c new file mode 100644 index 00000000..efb54b56 --- /dev/null +++ b/test/fs/dir/sim.c @@ -0,0 +1,64 @@ +#include "sp.h" +#include "sp/sp_test.h" +#include "sim.h" + +typedef struct { + sp_err_t open; + sp_err_t walk; + u32 fd_closes; +} expect_t; + +typedef struct { + const c8* name; + sim_dir_t dirs [SIM_MAX_DIRS]; + expect_t expect; +} test_t; + +static const test_t tests [] = { + { + .name = "propagates_read_error", + .dirs = { + { .path = "T", .read = SP_ERR_SYS_BAD_FD }, + }, + .expect = { + .walk = SP_ERR_SYS_BAD_FD, + }, + }, + { + .name = "closes_fd_when_from_fd_fails", + .dirs = { + { .path = "T", .from_fd = SP_ERR_SYS_IO }, + }, + .expect = { + .open = SP_ERR_SYS_IO, + .fd_closes = 1, + }, + }, +}; + +sp_test_each(fs, dir_sim, test_t, tests, .serial = true) { + sim_t s = sp_zero; + sim_begin(&s, it->dirs); + + SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; + sp_fs_dir_t dir = sp_zero; + sp_err_t open_err = sp_fs_dir_open(&dir, sp_sys_get_root(0), sp_str_lit("T"), sp_mem_slice(buf, sizeof(buf))); + sp_expect_err_eq(t, open_err, it->expect.open); + + if (!open_err) { + sp_err_t walk = SP_OK; + while (true) { + sp_fs_dir_entry_t entry = sp_zero; + walk = sp_fs_dir_next(&dir, &entry); + if (walk || !entry.name.data) break; + } + sp_expect_err_eq(t, walk, it->expect.walk); + sp_expect_ok(t, sp_fs_dir_close(&dir)); + } + + sim_end(&s); + + sp_expect_eq(t, s.count.fd_closes, it->expect.fd_closes); + sp_expect_eq(t, s.count.closes, open_err ? (u32)0 : (u32)1); + return SP_OK; +} diff --git a/test/fs/fs.h b/test/fs/fs.h index 61058ab0..32cc7dfc 100644 --- a/test/fs/fs.h +++ b/test/fs/fs.h @@ -117,6 +117,30 @@ static void fs_expect_kind(sp_test_t* t, sp_str_t path, sp_fs_kind_t actual, sp_ ); } +typedef struct { + sp_str_t key; + sp_fs_kind_t kind; + bool seen; +} fs_match_t; + +static void fs_match(sp_test_t* t, fs_match_t* matches, u32 n, sp_str_t key, sp_fs_kind_t kind) { + sp_for(it, n) { + fs_match_t* match = &matches[it]; + if (!sp_str_equal(match->key, key)) continue; + if (match->seen) sp_test_fail(t, "{} produced twice", sp_fmt_str(key)); + match->seen = true; + fs_expect_kind(t, key, kind, match->kind); + return; + } + sp_test_fail(t, "unexpected entry {}", sp_fmt_str(key)); +} + +static void fs_match_finish(sp_test_t* t, fs_match_t* matches, u32 n) { + sp_for(it, n) { + if (!matches[it].seen) sp_test_fail(t, "never produced {}", sp_fmt_str(matches[it].key)); + } +} + static void fs_apply_setup(sp_test_t* t, sp_str_t sandbox, const fs_setup_t setup [FS_MAX_SETUP]) { sp_mem_t mem = sp_test_arena(t); sp_for(it, FS_MAX_SETUP) { diff --git a/test/fs/it/os.c b/test/fs/it/os.c new file mode 100644 index 00000000..9f149aa1 --- /dev/null +++ b/test/fs/it/os.c @@ -0,0 +1,193 @@ +#include "fs.h" + +typedef struct { + const c8* path; + sp_fs_kind_t kind; +} entry_t; + +typedef struct { + sp_err_t err; + entry_t entries [FS_MAX_PATHS]; +} expect_t; + +typedef struct { + const c8* name; + fs_setup_t setup [FS_MAX_SETUP]; + const c8* root; + bool recursive; + expect_t expect; +} test_t; + +static const test_t tests [] = { + { + .name = "lists_entries_with_kinds", + .setup = { + { "R/A" }, + { "R/B", FS_SETUP_DIR }, + { "R/C" }, + }, + .expect = { + .entries = { + { "A", SP_FS_KIND_FILE }, + { "B", SP_FS_KIND_DIR }, + { "C", SP_FS_KIND_FILE }, + }, + }, + }, + { + .name = "hidden_file", + .setup = { + { "R/.A" }, + { "R/B" }, + }, + .expect = { + .entries = { + { ".A", SP_FS_KIND_FILE }, + { "B", SP_FS_KIND_FILE }, + }, + }, + }, + { + .name = "unicode_entries", + .setup = { + { "R/\xc3\xb1\x61\x6d\x65.txt" }, + { "R/\xc3\xbc\x6e\x69", FS_SETUP_DIR }, + }, + .expect = { + .entries = { + { "\xc3\xb1\x61\x6d\x65.txt", SP_FS_KIND_FILE }, + { "\xc3\xbc\x6e\x69", SP_FS_KIND_DIR }, + }, + }, + }, + { + .name = "empty_dir", + .setup = { + { "R", FS_SETUP_DIR }, + }, + }, + { + .name = "does_not_recurse", + .setup = { + { "R/A", FS_SETUP_DIR }, + { "R/A/B" }, + { "R/C" }, + }, + .expect = { + .entries = { + { "A", SP_FS_KIND_DIR }, + { "C", SP_FS_KIND_FILE }, + }, + }, + }, + { + .name = "recursive_nested_dirs", + .setup = { + { "R/A", FS_SETUP_DIR }, + { "R/A/B", FS_SETUP_DIR }, + { "R/C" }, + { "R/A/D" }, + { "R/A/B/E" }, + }, + .recursive = true, + .expect = { + .entries = { + { "C", SP_FS_KIND_FILE }, + { "A", SP_FS_KIND_DIR }, + { "A/D", SP_FS_KIND_FILE }, + { "A/B", SP_FS_KIND_DIR }, + { "A/B/E", SP_FS_KIND_FILE }, + }, + }, + }, + { + .name = "recursive_symlink_not_followed", + .setup = { + { "R/A", FS_SETUP_DIR }, + { "R/A/B" }, + { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "R/A" }, + }, + .recursive = true, + .expect = { + .entries = { + { "A", SP_FS_KIND_DIR }, + { "A/B", SP_FS_KIND_FILE }, + { "L", SP_FS_KIND_SYMLINK }, + }, + }, + }, + { + .name = "trailing_slash_root", + .setup = { + { "R/A" }, + }, + .root = "R/", + .expect = { + .entries = { + { "A", SP_FS_KIND_FILE }, + }, + }, + }, + { + .name = "nonexistent", + .expect = { + .err = SP_ERR_SYS_NOT_FOUND, + }, + }, + { + .name = "file_not_dir", + .setup = { + { "R" }, + }, + .expect = { + .err = SP_ERR_SYS_NOT_DIR, + }, + }, +#if defined(SP_POSIX) + { + .name = "special_file", + .setup = { + { "R", FS_SETUP_DIR }, + { .path = "R/F", .kind = FS_SETUP_FIFO }, + }, + .expect = { + .entries = { + // a fifo is listed, with a kind sp_fs can't express + { "F" }, + }, + }, + }, +#endif +}; + +sp_test_each(fs, it, test_t, tests) { + skip_if_symlinks_needed(t, it->setup); + + sp_mem_t mem = sp_test_arena(t); + sp_str_t sandbox = sp_test_dir(t); + fs_apply_setup(t, sandbox, it->setup); + + sp_str_t base = sp_fs_join_path(mem, sandbox, sp_str_lit("R")); + sp_str_t root = sp_fmt(mem, "{}/{}", sp_fmt_str(sandbox), sp_fmt_cstr(it->root ? it->root : "R")).value; + + fs_match_t matches [FS_MAX_PATHS] = sp_zero; + u32 n = 0; + sp_carr_for(it->expect.entries, e) { + const entry_t* want = &it->expect.entries[e]; + if (!want->path) break; + matches[n++] = (fs_match_t) { .key = sp_fs_join_path(mem, base, sp_cstr_as_str(want->path)), .kind = want->kind }; + } + + sp_fs_it_t walk = it->recursive ? sp_fs_it_new_recursive(mem, root) : sp_fs_it_new(mem, root); + while (sp_fs_it_next(&walk)) { + sp_expect_str_eq(t, walk.entry.name, sp_fs_get_name(walk.entry.path)); + fs_match(t, matches, n, walk.entry.path, walk.entry.kind); + } + sp_expect_err_eq(t, walk.err, it->expect.err); + sp_expect(t, !sp_fs_it_next(&walk)); + sp_expect_err_eq(t, walk.err, it->expect.err); + sp_fs_it_deinit(&walk); + + fs_match_finish(t, matches, n); + return SP_OK; +} diff --git a/test/fs/it/sim.c b/test/fs/it/sim.c new file mode 100644 index 00000000..74aba99e --- /dev/null +++ b/test/fs/it/sim.c @@ -0,0 +1,213 @@ +#include "sp.h" +#include "sp/sp_test.h" +#include "sim.h" + +#define MAX_WALK 4 + +#define A16 "AAAAAAAAAAAAAAAA" +#define A240 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 +#define A256 A240 A16 +#define A2044 A256 A256 A256 A256 A256 A256 A256 A240 "AAAAAAAAAAAA" +#define A2045 A2044 "A" +#define A2048 A256 A256 A256 A256 A256 A256 A256 A256 + +typedef struct { + const c8* path; + const c8* name; + sp_fs_kind_t kind; +} entry_t; + +typedef struct { + sp_err_t open; + sp_err_t walk; + entry_t entries [MAX_WALK]; + u32 opens; +} expect_t; + +typedef struct { + const c8* name; + const c8* root; + bool recursive; + u32 stop_after; + sim_dir_t dirs [SIM_MAX_DIRS]; + expect_t expect; +} test_t; + +static const test_t tests [] = { + { + .name = "resumes_parent_after_child", + .root = "T", + .recursive = true, + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR }, { "C", SP_FS_KIND_FILE } } }, + { .path = "T/B", .entries = { { "D", SP_FS_KIND_FILE } } }, + }, + .expect = { + .entries = { + { "T/B", "B", SP_FS_KIND_DIR }, + { "T/B/D", "D", SP_FS_KIND_FILE }, + { "T/C", "C", SP_FS_KIND_FILE }, + }, + .opens = 2, + }, + }, + { + .name = "descends_empty_subdir", + .root = "T", + .recursive = true, + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B" }, + }, + .expect = { + .entries = { { "T/B", "B", SP_FS_KIND_DIR } }, + .opens = 2, + }, + }, + { + .name = "root_is_sep", + .root = "/", + .dirs = { + { .path = "/", .entries = { { "A", SP_FS_KIND_DIR } } }, + }, + .expect = { + .entries = { { "/A", "A", SP_FS_KIND_DIR } }, + .opens = 1, + }, + }, + { + .name = "subdir_open_error_unwinds", + .root = "T", + .recursive = true, + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR }, { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .walk = SP_ERR_SYS_NOT_FOUND, + .opens = 2, + }, + }, + { + .name = "read_error_unwinds", + .root = "T", + .recursive = true, + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR }, { "C", SP_FS_KIND_FILE } } }, + { .path = "T/B", .read = SP_ERR_SYS_IO }, + }, + .expect = { + .walk = SP_ERR_SYS_IO, + .entries = { { "T/B", "B", SP_FS_KIND_DIR } }, + .opens = 2, + }, + }, + { + .name = "deinit_closes_open_frames", + .root = "T", + .recursive = true, + .stop_after = 1, + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B" }, + }, + .expect = { + .entries = { { "T/B", "B", SP_FS_KIND_DIR } }, + .opens = 2, + }, + }, + { + .name = "path_fits_max", + .root = "T", + .recursive = true, + .dirs = { + { .path = "T", .entries = { { A2048, SP_FS_KIND_DIR } } }, + { .path = "T/" A2048, .entries = { { A2044, SP_FS_KIND_FILE } } }, + }, + .expect = { + .entries = { + { "T/" A2048, A2048, SP_FS_KIND_DIR }, + { "T/" A2048 "/" A2044, A2044, SP_FS_KIND_FILE }, + }, + .opens = 2, + }, + }, + { + .name = "path_too_long", + .root = "T", + .recursive = true, + .dirs = { + { .path = "T", .entries = { { A2048, SP_FS_KIND_DIR } } }, + { .path = "T/" A2048, .entries = { { A2045, SP_FS_KIND_FILE } } }, + }, + .expect = { + .walk = SP_ERR_SYS_NAME_TOO_LONG, + .entries = { { "T/" A2048, A2048, SP_FS_KIND_DIR } }, + .opens = 2, + }, + }, + { + .name = "root_too_long", + .root = "T/" A2048 "/" A2045, + .expect = { + .open = SP_ERR_SYS_NAME_TOO_LONG, + .walk = SP_ERR_SYS_NAME_TOO_LONG, + }, + }, + { + .name = "kind_none_falls_back_to_stat", + .root = "T", + .recursive = true, + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_NONE, .stat = SP_FS_KIND_DIR } } }, + { .path = "T/B", .entries = { { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .entries = { + { "T/B", "B", SP_FS_KIND_DIR }, + { "T/B/C", "C", SP_FS_KIND_FILE }, + }, + .opens = 2, + }, + }, +}; + +sp_test_each(fs, it_sim, test_t, tests, .serial = true) { + sp_mem_t mem = sp_test_arena(t); + sp_str_t root = sp_cstr_as_str(it->root); + + sim_t s = sp_zero; + sim_begin(&s, it->dirs); + + sp_fs_it_t walk = it->recursive ? sp_fs_it_new_recursive(mem, root) : sp_fs_it_new(mem, root); + sp_expect_err_eq(t, walk.err, it->expect.open); + sp_expect(t, !walk.entry.path.data); + + u32 produced = 0; + while (sp_fs_it_next(&walk)) { + if (produced < MAX_WALK && it->expect.entries[produced].path) { + const entry_t* want = &it->expect.entries[produced]; + sp_expect_str_eq_c(t, walk.entry.path, want->path); + sp_expect_str_eq_c(t, walk.entry.name, want->name); + sp_expect_eq(t, (u32)walk.entry.kind, (u32)want->kind); + } + else { + sp_test_fail(t, "walker produced unexpected entry {}", sp_fmt_str(walk.entry.path)); + } + produced++; + if (produced == it->stop_after) break; + } + sp_expect_err_eq(t, walk.err, it->expect.walk); + sp_fs_it_deinit(&walk); + + sim_end(&s); + + u32 expected = 0; + sp_carr_for(it->expect.entries, e) { + if (!it->expect.entries[e].path) break; + expected++; + } + sp_expect_eq(t, produced, expected); + sp_expect_eq(t, s.count.opens, it->expect.opens); + sp_expect_eq(t, s.count.closes, s.count.dirs); + return SP_OK; +} diff --git a/test/fs/iter/iter.c b/test/fs/iter/iter.c deleted file mode 100644 index 4db930ed..00000000 --- a/test/fs/iter/iter.c +++ /dev/null @@ -1,296 +0,0 @@ -#include "sp.h" -#include "sp/sp_test.h" - -#define MAX_BATCHES 4 -#define MAX_PARSES 4 -#define MAX_ENTRIES 8 -#define FD ((sp_sys_fd_t)7777) - -typedef struct { - const c8* name; - sp_fs_kind_t kind; - sp_err_t err; - bool skip; -} parse_t; - -typedef struct { - sp_err_t err; - parse_t entries [MAX_PARSES]; -} batch_t; - -typedef struct { - const c8* name; - sp_fs_kind_t kind; -} entry_t; - -typedef struct { - sp_err_t open; - sp_err_t walk; - entry_t entries [MAX_ENTRIES]; - u32 opens; - u32 from_fds; - u32 reads; - u32 parses; - u32 fd_closes; -} expect_t; - -typedef struct { - const c8* name; - u64 cap; - sp_err_t open_err; - sp_err_t from_fd_err; - batch_t batches [MAX_BATCHES]; - expect_t expect; -} test_t; - -static const test_t tests [] = { - { - .name = "lists_entries_with_kinds", - .batches = { - { .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_DIR }, { "L", SP_FS_KIND_SYMLINK } } }, - }, - .expect = { - .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_DIR }, { "L", SP_FS_KIND_SYMLINK } }, - .opens = 1, - .from_fds = 1, - .reads = 2, - .parses = 3, - }, - }, - { - .name = "empty_read_ends_iteration", - .expect = { - .opens = 1, - .from_fds = 1, - .reads = 1, - }, - }, - { - .name = "skips_dots", - .batches = { - { .entries = { { "." }, { ".." }, { "A", SP_FS_KIND_FILE } } }, - }, - .expect = { - .entries = { { "A", SP_FS_KIND_FILE } }, - .opens = 1, - .from_fds = 1, - .reads = 2, - .parses = 3, - }, - }, - { - .name = "refills_on_empty_parse", - .batches = { - { .entries = { { .skip = true } } }, - { .entries = { { "A", SP_FS_KIND_FILE } } }, - }, - .expect = { - .entries = { { "A", SP_FS_KIND_FILE } }, - .opens = 1, - .from_fds = 1, - .reads = 3, - .parses = 2, - }, - }, - { - .name = "produces_entries_across_batches", - .batches = { - { .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_DIR } } }, - { .entries = { { "C", SP_FS_KIND_FILE } } }, - }, - .expect = { - .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_DIR }, { "C", SP_FS_KIND_FILE } }, - .opens = 1, - .from_fds = 1, - .reads = 3, - .parses = 3, - }, - }, - { - .name = "propagates_read_error", - .batches = { - { .err = SP_ERR_SYS_BAD_FD }, - }, - .expect = { - .walk = SP_ERR_SYS_BAD_FD, - .opens = 1, - .from_fds = 1, - .reads = 1, - }, - }, - { - .name = "propagates_parse_error", - .batches = { - { .entries = { { .err = SP_ERR_SYS_INVALID } } }, - }, - .expect = { - .walk = SP_ERR_SYS_INVALID, - .opens = 1, - .from_fds = 1, - .reads = 1, - .parses = 1, - }, - }, - { - .name = "propagates_open_error", - .open_err = SP_ERR_SYS_NOT_FOUND, - .expect = { - .open = SP_ERR_SYS_NOT_FOUND, - .opens = 1, - }, - }, - { - .name = "closes_fd_when_from_fd_fails", - .from_fd_err = SP_ERR_SYS_IO, - .expect = { - .open = SP_ERR_SYS_IO, - .opens = 1, - .from_fds = 1, - .fd_closes = 1, - }, - }, - { - .name = "refuses_undersized_buffer", - .cap = SP_SYS_DIR_MIN_BUF - 1, - .expect = { - .open = SP_ERR_SYS_BUG, - }, - }, -}; - -typedef struct { - u32 opens; - u32 from_fds; - u32 reads; - u32 parses; - u32 fd_closes; - u32 dir_closes; -} count_t; - -static const test_t* test; -static count_t count; - -static u32 get_batch_len(const batch_t* batch) { - u32 n = 0; - sp_carr_for(batch->entries, it) { - const parse_t* parse = &batch->entries[it]; - if (!parse->name && !parse->skip && !parse->err) break; - n++; - } - return n; -} - -static sp_err_t open_dir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out) { - count.opens++; - if (test->open_err) return test->open_err; - *out = FD; - return SP_OK; -} - -static sp_err_t close_fd(sp_sys_fd_t fd) { - if (fd == FD) count.fd_closes++; - return SP_OK; -} - -static sp_err_t dir_from_fd(sp_sys_fd_t fd, sp_sys_dir_t* out) { - count.from_fds++; - if (test->from_fd_err) return test->from_fd_err; - *out = sp_zero_s(sp_sys_dir_t); - out->handle = (s64)fd; - return SP_OK; -} - -static sp_err_t dir_read(sp_sys_dir_t* dir, sp_mem_buffer_t* buf) { - buf->len = 0; - u32 index = count.reads++; - if (index >= MAX_BATCHES) return SP_OK; - - const batch_t* batch = &test->batches[index]; - if (batch->err) return batch->err; - buf->len = get_batch_len(batch); - return SP_OK; -} - -static sp_err_t dir_parse(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { - *out = sp_zero_s(sp_sys_dir_entry_t); - count.parses++; - - const batch_t* batch = &test->batches[count.reads - 1]; - const parse_t* parse = &batch->entries[*cursor]; - *cursor += 1; - - if (parse->err) return parse->err; - if (parse->skip) return SP_OK; - - out->name = parse->name; - out->len = (u32)sp_cstr_len(parse->name); - out->kind = parse->kind; - return SP_OK; -} - -static sp_err_t dir_close(sp_sys_dir_t* dir) { - count.dir_closes++; - return SP_OK; -} - - -sp_test_each(fs, iter_shell, test_t, tests, .serial = true) { - static sp_sys_vtable_t vt; - vt = sp_sys_vtable_platform; - vt.open_dir = open_dir; - vt.close = close_fd; - vt.dir_from_fd = dir_from_fd; - vt.dir_read = dir_read; - vt.dir_parse = dir_parse; - vt.dir_close = dir_close; - - test = it; - count = sp_zero_s(count_t); - - const sp_sys_vtable_t* old = sp_sys_set_vtable(&vt); - - SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; - u64 cap = it->cap ? it->cap : sizeof(buf); - - sp_fs_dir_t iter = sp_zero; - sp_err_t open_err = sp_fs_dir_open(&iter, sp_sys_get_root(0), sp_str_lit("T"), sp_mem_slice(buf, cap)); - sp_expect_err_eq(t, open_err, it->expect.open); - - u32 produced = 0; - if (!open_err) { - sp_err_t walk = SP_OK; - while (true) { - sp_fs_dir_entry_t entry = sp_zero; - walk = sp_fs_dir_next(&iter, &entry); - if (walk) break; - if (!entry.name.data) break; - - if (produced < MAX_ENTRIES && it->expect.entries[produced].name) { - sp_expect_str_eq_c(t, entry.name, it->expect.entries[produced].name); - sp_expect_eq(t, (u32)entry.kind, (u32)it->expect.entries[produced].kind); - } - else { - sp_test_fail(t, "iterator produced unexpected entry {}", sp_fmt_str(entry.name)); - } - produced++; - } - sp_expect_err_eq(t, walk, it->expect.walk); - sp_expect_ok(t, sp_fs_dir_close(&iter)); - } - - sp_sys_set_vtable(old); - - u32 expected = 0; - sp_carr_for(it->expect.entries, e) { - if (!it->expect.entries[e].name) break; - expected++; - } - sp_expect_eq(t, produced, expected); - sp_expect_eq(t, count.opens, it->expect.opens); - sp_expect_eq(t, count.from_fds, it->expect.from_fds); - sp_expect_eq(t, count.reads, it->expect.reads); - sp_expect_eq(t, count.parses, it->expect.parses); - sp_expect_eq(t, count.fd_closes, it->expect.fd_closes); - sp_expect_eq(t, count.dir_closes, open_err ? (u32)0 : (u32)1); - return SP_OK; -} diff --git a/test/fs/iter/os.c b/test/fs/iter/os.c deleted file mode 100644 index 4089d421..00000000 --- a/test/fs/iter/os.c +++ /dev/null @@ -1,247 +0,0 @@ -#include "sp.h" -#include "sp/sp_test.h" - -#define MAX_SETUP 8 -#define MAX_ENTRIES 8 -#define BULK_PREFIX "R" - -#define A16 "AAAAAAAAAAAAAAAA" -#define A255 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 A16 "AAAAAAAAAAAAAAA" -#define EE "\xe2\x82\xac" -#define E5 EE EE EE EE EE -#define E25 E5 E5 E5 E5 E5 -#define E255 E25 E25 E25 E5 E5 - -typedef struct { - const c8* path; - sp_fs_kind_t kind; - const c8* target; -} setup_t; - -typedef struct { - const c8* name; - sp_fs_kind_t kind; - bool seen; -} entry_t; - -typedef struct { - entry_t entries [MAX_ENTRIES]; -} expect_t; - -typedef struct { - const c8* name; - setup_t setup [MAX_SETUP]; - const c8* dir; - bool relative; - u32 bulk; - expect_t expect; -} test_t; - -static const test_t tests [] = { - { - .name = "lists_entries_with_kinds", - .setup = { - { "A", SP_FS_KIND_FILE }, - { "B", SP_FS_KIND_DIR }, - { "C", SP_FS_KIND_FILE }, - }, - .expect = { - .entries = { - { "A", SP_FS_KIND_FILE }, - { "B", SP_FS_KIND_DIR }, - { "C", SP_FS_KIND_FILE }, - }, - }, - }, - { - .name = "symlink_kind", - .setup = { - { "A", SP_FS_KIND_FILE }, - { .path = "L", .kind = SP_FS_KIND_SYMLINK, .target = "A" }, - }, - .expect = { - .entries = { - { "A", SP_FS_KIND_FILE }, - { "L", SP_FS_KIND_SYMLINK }, - }, - }, - }, - { - .name = "empty_directory", - }, - { - .name = "honors_dirfd", - .setup = { - { "S", SP_FS_KIND_DIR }, - { "S/A", SP_FS_KIND_FILE }, - }, - .dir = "S", - .relative = true, - .expect = { - .entries = { - { "A", SP_FS_KIND_FILE }, - }, - }, - }, - { - .name = "max_length_name", - .setup = { - { A255, SP_FS_KIND_FILE }, - }, - .expect = { - .entries = { - { A255, SP_FS_KIND_FILE }, - }, - }, - }, - { - .name = "long_multibyte_name", - .setup = { - { E255, SP_FS_KIND_FILE }, - }, - .expect = { - .entries = { - { E255, SP_FS_KIND_FILE }, - }, - }, - }, - { - .name = "refills_across_batches", - .bulk = 96, - }, -}; - -static sp_err_t probe_symlinks(void* user) { - sp_str_t dir = *(sp_str_t*)user; - - sp_mem_arena_marker_t scratch = sp_mem_begin_scratch(); - sp_str_t target = sp_fs_join_path(scratch.mem, dir, sp_str_lit("probe_target")); - sp_str_t link = sp_fs_join_path(scratch.mem, dir, sp_str_lit("probe_link")); - - sp_fs_create_file(target); - sp_err_t err = sp_fs_create_sym_link(target, link); - if (!err) sp_fs_remove_file(link); - sp_fs_remove_file(target); - - sp_mem_end_scratch(scratch); - return err; -} - -static bool is_symlink_needed(const test_t* c) { - sp_carr_for(c->setup, it) { - if (!c->setup[it].path) break; - if (c->setup[it].kind == SP_FS_KIND_SYMLINK) return true; - } - return false; -} - -sp_test_each(fs, iter, test_t, tests) { - sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); - - static sp_test_once_t probe = sp_zero; - if (is_symlink_needed(it) && sp_test_once(&probe, probe_symlinks, &sandbox)) { - return sp_test_skip(t, "symlinks not available"); - } - - sp_carr_for(it->setup, s) { - const setup_t* entry = &it->setup[s]; - if (!entry->path) break; - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(entry->path)); - - switch (entry->kind) { - case SP_FS_KIND_FILE: { - sp_expect_ok(t, sp_fs_create_file(path)); - break; - } - case SP_FS_KIND_DIR: { - sp_expect_ok(t, sp_fs_create_dir(path)); - break; - } - case SP_FS_KIND_SYMLINK: { - sp_str_t target = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(entry->target)); - sp_expect_ok(t, sp_fs_create_sym_link(target, path)); - break; - } - case SP_FS_KIND_NONE: { - break; - } - } - } - - sp_str_t dir = it->dir ? sp_fs_join_path(mem, sandbox, sp_cstr_as_str(it->dir)) : sandbox; - - sp_str_t* bulk_names = sp_alloc_n(mem, sp_str_t, it->bulk ? it->bulk : 1); - bool* bulk_seen = sp_alloc_n(mem, bool, it->bulk ? it->bulk : 1); - sp_for(b, it->bulk) { - bulk_names[b] = sp_fmt(mem, "{}{}", sp_fmt_cstr(BULK_PREFIX), sp_fmt_uint(b)).value; - sp_expect_ok(t, sp_fs_create_file(sp_fs_join_path(mem, dir, bulk_names[b]))); - } - - sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; - if (it->relative) { - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &sandbox_fd)); - } - - SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; - sp_fs_dir_t iter = sp_zero; - sp_err_t open_err = it->relative - ? sp_fs_dir_open(&iter, sandbox_fd, sp_cstr_as_str(it->dir), sp_mem_slice(buf, sizeof(buf))) - : sp_fs_dir_open(&iter, sp_sys_get_root(0), dir, sp_mem_slice(buf, sizeof(buf))); - sp_expect_ok(t, open_err); - - sp_err_t walk = SP_OK; - if (!open_err) { - while (true) { - sp_fs_dir_entry_t entry = sp_zero; - walk = sp_fs_dir_next(&iter, &entry); - if (walk) break; - if (!entry.name.data) break; - - if (sp_str_equal(entry.name, sp_str_lit(".")) || sp_str_equal(entry.name, sp_str_lit(".."))) { - sp_test_fail(t, "iterator produced dot entry {}", sp_fmt_str(entry.name)); - continue; - } - - bool matched = false; - sp_carr_for(it->expect.entries, e) { - entry_t* want = &it->expect.entries[e]; - if (!want->name) break; - if (!sp_str_equal_cstr(entry.name, want->name)) continue; - matched = true; - if (want->seen) sp_test_fail(t, "iterator produced {} twice", sp_fmt_str(entry.name)); - want->seen = true; - sp_expect_eq(t, (u32)entry.kind, (u32)want->kind); - break; - } - sp_for(b, it->bulk) { - if (matched) break; - if (!sp_str_equal(entry.name, bulk_names[b])) continue; - matched = true; - if (bulk_seen[b]) sp_test_fail(t, "iterator produced {} twice", sp_fmt_str(entry.name)); - bulk_seen[b] = true; - sp_expect_eq(t, (u32)entry.kind, (u32)SP_FS_KIND_FILE); - } - if (!matched) { - sp_test_fail(t, "iterator produced unexpected entry {}", sp_fmt_str(entry.name)); - } - } - sp_expect_ok(t, walk); - sp_expect_ok(t, sp_fs_dir_close(&iter)); - } - - sp_carr_for(it->expect.entries, e) { - if (!it->expect.entries[e].name) break; - if (!it->expect.entries[e].seen) { - sp_test_fail(t, "iterator never produced {}", sp_fmt_cstr(it->expect.entries[e].name)); - } - } - sp_for(b, it->bulk) { - if (!bulk_seen[b]) { - sp_test_fail(t, "iterator never produced {}", sp_fmt_str(bulk_names[b])); - } - } - - if (sandbox_fd != SP_SYS_INVALID_FD) sp_sys_close(sandbox_fd); - return SP_OK; -} diff --git a/test/fs/sim.c b/test/fs/sim.c new file mode 100644 index 00000000..87e59508 --- /dev/null +++ b/test/fs/sim.c @@ -0,0 +1,110 @@ +#include "sim.h" + +#define FD_BASE 7777 + +static sim_t* active; + +static const sim_dir_t* find_dir(sp_str_t path) { + sp_for(it, SIM_MAX_DIRS) { + const sim_dir_t* dir = &active->dirs[it]; + if (!dir->path) break; + if (sp_str_equal_cstr(path, dir->path)) return dir; + } + return SP_NULLPTR; +} + +static const sim_entry_t* find_entry(sp_str_t path) { + const sim_dir_t* dir = find_dir(sp_fs_parent_path(path)); + if (!dir) return SP_NULLPTR; + + sp_str_t name = sp_fs_get_name(path); + sp_carr_for(dir->entries, it) { + const sim_entry_t* entry = &dir->entries[it]; + if (!entry->name) break; + if (sp_str_equal_cstr(name, entry->name)) return entry; + } + return SP_NULLPTR; +} + +static u32 dir_len(const sim_dir_t* dir) { + u32 n = 0; + sp_carr_for(dir->entries, it) { + if (!dir->entries[it].name) break; + n++; + } + return n; +} + +static sp_err_t open_dir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out) { + active->count.opens++; + const sim_dir_t* dir = find_dir(sp_str(path, len)); + if (!dir) return SP_ERR_SYS_NOT_FOUND; + sp_assert(active->count.dirs < SIM_MAX_OPENS); + active->opened[active->count.dirs] = (sim_open_t) { .dir = dir }; + *out = (sp_sys_fd_t)(FD_BASE + active->count.dirs); + active->count.dirs++; + return SP_OK; +} + +static sp_err_t close_fd(sp_sys_fd_t fd) { + active->count.fd_closes++; + return SP_OK; +} + +static sp_err_t dir_from_fd(sp_sys_fd_t fd, sp_sys_dir_t* out) { + s64 handle = (s64)fd - FD_BASE; + if (active->opened[handle].dir->from_fd) return active->opened[handle].dir->from_fd; + *out = sp_zero_s(sp_sys_dir_t); + out->handle = handle; + return SP_OK; +} + +static sp_err_t dir_read(sp_sys_dir_t* dir, sp_mem_buffer_t* buf) { + sim_open_t* slot = &active->opened[dir->handle]; + if (slot->dir->read) return slot->dir->read; + buf->len = slot->served ? 0 : dir_len(slot->dir); + slot->served = true; + return SP_OK; +} + +static sp_err_t dir_parse(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { + const sim_entry_t* entry = &active->opened[dir->handle].dir->entries[*cursor]; + *cursor += 1; + + *out = sp_zero_s(sp_sys_dir_entry_t); + out->name = entry->name; + out->len = (u32)sp_cstr_len(entry->name); + out->kind = entry->kind; + return SP_OK; +} + +static sp_err_t dir_close(sp_sys_dir_t* dir) { + active->count.closes++; + return SP_OK; +} + +static sp_err_t get_link_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st) { + const sim_entry_t* entry = find_entry(sp_str(path, len)); + if (!entry) return SP_ERR_SYS_NOT_FOUND; + *st = sp_zero_s(sp_sys_file_meta_t); + st->kind = entry->stat; + return SP_OK; +} + +void sim_begin(sim_t* sim, const sim_dir_t* dirs) { + *sim = (sim_t) { .dirs = dirs, .vt = sp_sys_vtable_platform }; + sim->vt.open_dir = open_dir; + sim->vt.close = close_fd; + sim->vt.dir_from_fd = dir_from_fd; + sim->vt.dir_read = dir_read; + sim->vt.dir_parse = dir_parse; + sim->vt.dir_close = dir_close; + sim->vt.get_link_metadata = get_link_metadata; + sim->saved = sp_sys_set_vtable(&sim->vt); + active = sim; +} + +void sim_end(sim_t* sim) { + sp_sys_set_vtable(sim->saved); + active = SP_NULLPTR; +} diff --git a/test/fs/sim.h b/test/fs/sim.h new file mode 100644 index 00000000..07404c1c --- /dev/null +++ b/test/fs/sim.h @@ -0,0 +1,46 @@ +#ifndef FS_SIM_H +#define FS_SIM_H + +#include "sp.h" + +#define SIM_MAX_DIRS 4 +#define SIM_MAX_ENTRIES 4 +#define SIM_MAX_OPENS 4 + +typedef struct { + const c8* name; + sp_fs_kind_t kind; + sp_fs_kind_t stat; +} sim_entry_t; + +typedef struct { + const c8* path; + sp_err_t from_fd; + sp_err_t read; + sim_entry_t entries [SIM_MAX_ENTRIES]; +} sim_dir_t; + +typedef struct { + const sim_dir_t* dir; + bool served; +} sim_open_t; + +typedef struct { + u32 opens; + u32 dirs; + u32 closes; + u32 fd_closes; +} sim_count_t; + +typedef struct { + const sim_dir_t* dirs; + sim_count_t count; + sim_open_t opened [SIM_MAX_OPENS]; + sp_sys_vtable_t vt; + const sp_sys_vtable_t* saved; +} sim_t; + +void sim_begin(sim_t* sim, const sim_dir_t* dirs); +void sim_end(sim_t* sim); + +#endif // FS_SIM_H diff --git a/tools/windows/sp/spn/amalgamation.vcxproj b/tools/windows/sp/spn/amalgamation.vcxproj index 9d801ae5..376ef584 100644 --- a/tools/windows/sp/spn/amalgamation.vcxproj +++ b/tools/windows/sp/spn/amalgamation.vcxproj @@ -18,12 +18,12 @@ amalgamation 10.0 amalgamation - ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\resolve.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\iter.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\vtable.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\iter\os.c;..\..\..\..\test\fs\iter\iter.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c + ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\resolve.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\iter.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\vtable.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c ..\..\..\..\sp.h;..\..\..\..\sp\sp_test.h false - + $(MSBuildProjectDirectory)\..\..\..\..\test\fs diff --git a/tools/windows/sp/spn/fs.vcxproj b/tools/windows/sp/spn/fs.vcxproj index 1b68e9c1..1d92a2fa 100644 --- a/tools/windows/sp/spn/fs.vcxproj +++ b/tools/windows/sp/spn/fs.vcxproj @@ -18,12 +18,12 @@ fs 10.0 fs - ..\..\..\..\test\main.c;..\..\..\..\test\fs\iter\iter.c;..\..\..\..\test\fs\iter\os.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c + ..\..\..\..\test\main.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c ..\..\..\..\sp.h;..\..\..\..\sp\sp_test.h false - + $(MSBuildProjectDirectory)\..\..\..\..\test\fs