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