diff options
Diffstat (limited to 'src/cmd/tests/kernel')
-rw-r--r-- | src/cmd/tests/kernel/fdlimit.c | 49 | ||||
-rw-r--r-- | src/cmd/tests/kernel/fs.c | 79 | ||||
-rw-r--r-- | src/cmd/tests/kernel/misc.c | 66 | ||||
-rw-r--r-- | src/cmd/tests/kernel/miscsyscall.c | 315 | ||||
-rw-r--r-- | src/cmd/tests/kernel/path.c | 108 | ||||
-rw-r--r-- | src/cmd/tests/kernel/threads.c | 55 |
6 files changed, 672 insertions, 0 deletions
diff --git a/src/cmd/tests/kernel/fdlimit.c b/src/cmd/tests/kernel/fdlimit.c new file mode 100644 index 0000000..f332357 --- /dev/null +++ b/src/cmd/tests/kernel/fdlimit.c @@ -0,0 +1,49 @@ +#include "../tests.h" +#include <camellia/flags.h> +#include <errno.h> +#include <unistd.h> + +static void test_fdlimit_pipe(void) { + hid_t ends[2]; + hid_t h[2] = {-1, -1}; + for (;;) { + hid_t cur = _sys_open("/", 1, OPEN_READ); + if (cur == -EMFILE) break; + test(cur >= 0); + h[0] = h[1]; + h[1] = cur; + } + test(h[0] >= 0); /* we need at least two handles */ + + test(_sys_pipe(ends, 0) == -EMFILE); + test(_sys_open("/", 1, OPEN_READ) == -EMFILE); + + close(h[1]); + test(_sys_pipe(ends, 0) == -EMFILE); + test(_sys_open("/", 1, OPEN_READ) == h[1]); + test(_sys_open("/", 1, OPEN_READ) == -EMFILE); + + close(h[0]); + close(h[1]); + test(_sys_pipe(ends, 0) == 0); +} + +static void test_fdlimit_fork(void) { + for (;;) { + hid_t cur = _sys_open("/", 1, OPEN_READ); + if (cur == -EMFILE) break; + test(cur >= 0); + } + + if (!_sys_fork(0, NULL)) _sys_exit(123); + + test(_sys_fork(FORK_NEWFS, NULL) == -EMFILE); + test(_sys_await() == 123); + test(_sys_await() == ~0); +} + +void r_k_fdlimit(void) { + /* all these tests implicitly test if the vfs returns -EMFILE */ + run_test(test_fdlimit_pipe); + run_test(test_fdlimit_fork); +} diff --git a/src/cmd/tests/kernel/fs.c b/src/cmd/tests/kernel/fs.c new file mode 100644 index 0000000..b5684d7 --- /dev/null +++ b/src/cmd/tests/kernel/fs.c @@ -0,0 +1,79 @@ +#include "../tests.h" +#include <camellia/flags.h> +#include <camellia/syscalls.h> +#include <stdbool.h> +#include <string.h> + +static void test_unfinished_req(void) { + hid_t h = -1; + int ret = _sys_fork(FORK_NEWFS, &h); + test(0 <= ret); + if (ret == 0) { + // TODO make a similar test with all 0s passed to fs_wait + struct ufs_request res; + _sys_fs_wait(NULL, 0, &res); + // TODO second fs_wait + exit(0); + } else { + test(0 <= h); + test(_sys_mount(h, "/", 1) == 0); + int ret = _sys_open("/", 1, 0); + test(ret < 0); + // the handler quits while handling that call - but this syscall should return anyways + } +} + +static void test_orphaned_fs(void) { + hid_t h = -1; + int ret = _sys_fork(FORK_NEWFS, &h); + test(0 <= ret); + if (ret == 0) { + exit(0); + } else { + test(0 <= h); + test(_sys_mount(h, "/", 1) == 0); + int ret = _sys_open("/", 1, 0); + test(ret < 0); + } +} + +static void test_fs_cleanup(void) { + const char *msg = "success"; + int msglen = 8; + char buf[16]; + hid_t ends[2]; + test(_sys_pipe(ends, 0) >= 0); + if (!_sys_fork(0, NULL)) { + hid_t h = -1; + if (_sys_fork(FORK_NEWFS, &h) == 0) { + for (;;) { + struct ufs_request req; + hid_t reqh = _sys_fs_wait(buf, sizeof buf, &req); + if (reqh < 0) break; + _sys_fs_respond(reqh, NULL, 0, 0); /* success */ + } + /* this is the test: does it break out of the loop + * when it should cleanup */ + _sys_write(ends[1], msg, msglen, -1, 0); + exit(0); + } else { + test(_sys_mount(h, "/", 1) == 0); + h = _sys_open("/", 1, 0); + test(h >= 0); + _sys_close(h); + // TODO another test without the delay + _sys_sleep(0); + exit(0); + } + } else { + test(_sys_read(ends[0], buf, sizeof buf, 0) == msglen); + test(memcmp(buf, msg, msglen) == 0); + } +} + +void r_k_fs(void) { + run_test(test_unfinished_req); + run_test(test_orphaned_fs); + run_test(test_fs_cleanup); + run_test(test_fs_cleanup); +} diff --git a/src/cmd/tests/kernel/misc.c b/src/cmd/tests/kernel/misc.c new file mode 100644 index 0000000..5d6b531 --- /dev/null +++ b/src/cmd/tests/kernel/misc.c @@ -0,0 +1,66 @@ +#include "../tests.h" +#include <camellia/errno.h> +#include <camellia/flags.h> +#include <camellia/syscalls.h> +#include <string.h> +#include <unistd.h> + +static void test_fault_kill(void) { + if (!fork()) { /* invalid memory access */ + asm volatile("movb $69, 0" ::: "memory"); + test_fail(); + } + if (!fork()) { /* #GP */ + asm volatile("hlt" ::: "memory"); + test_fail(); + } + + int await_cnt = 0; + while (_sys_await() != ~0) await_cnt++; + test(await_cnt == 2); +} + +static void test_efault(void) { +#if 0 + const char *str = "o, 16 characters"; + char str2[16]; + char *invalid = (void*)0x1000; + hid_t h; + + memcpy(str2, str, 16); + + test((h = _sys_open(TMPFILEPATH, strlen(TMPFILEPATH), OPEN_CREATE | OPEN_WRITE)) >= 0); + test(_sys_write(h, str, 16, 0, 0) == 16); + test(_sys_write(h, str2, 16, 0, 0) == 16); + + test(_sys_write(h, invalid, 16, 0, 0) == -EFAULT); + + /* x64 non-canonical pointers */ + test(_sys_write(h, (void*)((uintptr_t)str ^ 0x8000000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str ^ 0x1000000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str ^ 0x0100000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str ^ 0x0010000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str ^ 0x0001000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str ^ 0x0000800000000000), 16, 0, 0) == -EFAULT); + + test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x8000000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x1000000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x0100000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x0010000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x0001000000000000), 16, 0, 0) == -EFAULT); + test(_sys_write(h, (void*)((uintptr_t)str2 ^ 0x0000800000000000), 16, 0, 0) == -EFAULT); + + test(_sys_write(h, str, 16, 0, 0) == 16); + close(h); +#endif +} + +static void test_invalid_syscall(void) { + test(_syscall(~0, 0, 0, 0, 0, 0) < 0); +} + +void r_k_misc(void) { + run_test(test_fault_kill); + run_test(test_efault); + run_test(test_invalid_syscall); +} diff --git a/src/cmd/tests/kernel/miscsyscall.c b/src/cmd/tests/kernel/miscsyscall.c new file mode 100644 index 0000000..459da2a --- /dev/null +++ b/src/cmd/tests/kernel/miscsyscall.c @@ -0,0 +1,315 @@ +#include "../tests.h" +#include <camellia/errno.h> +#include <camellia/execbuf.h> +#include <camellia/flags.h> +#include <camellia/syscalls.h> +#include <string.h> +#include <unistd.h> + + +static void test_await(void) { + /* creates 16 child processes, each returning a different value. then checks + * if await() returns every value exactly once */ + int ret; + int counts[16] = {0}; + + for (int i = 0; i < 16; i++) + if (!fork()) + exit(i); + + while ((ret = _sys_await()) != ~0) { + test(0 <= ret && ret < 16); + counts[ret]++; + } + + for (int i = 0; i < 16; i++) + test(counts[i] == 1); +} + +static void test_await2(void) { + /* hangs on failure */ + if (!fork()) { + if (!fork()) { + for (;;) _sys_sleep(1000000000); + } else { + exit(123); + } + } + test(_sys_await() == 123); + test(_sys_await() == ~0); /* don't wait for tombstone */ + _sys_filicide(); +} + +static void test_wait2_basic(void) { + struct sys_wait2 data = {0}; + int pid; + pid = fork(); + if (pid == 0) { + exit(420); + } + test(_sys_wait2(-1, 0, &data) == pid); + test(data.status == 420); + test(_sys_wait2(-1, 0, NULL) == -ECHILD); +} + +static void test_pipe(void) { + const char *pipe_msgs[2] = {"hello", "world"}; + hid_t ends[2]; + char buf[16]; + + /* test regular reads / writes */ + test(_sys_pipe(ends, 0) >= 0); + if (!fork()) { + /* those repeated asserts ensure that you can't read/write to the wrong ends */ + test(_sys_read(ends[1], buf, 16, 0) < 0); + test(_sys_write(ends[0], buf, 16, 0, 0) < 0); + + test(5 == _sys_write(ends[1], pipe_msgs[0], 5, -1, 0)); + + test(_sys_read(ends[1], buf, 16, 0) < 0); + test(_sys_write(ends[0], buf, 16, 0, 0) < 0); + + test(5 == _sys_write(ends[1], pipe_msgs[1], 5, -1, 0)); + + exit(0); + } else { + test(_sys_read(ends[1], buf, 16, 0) < 0); + test(_sys_write(ends[0], buf, 16, 0, 0) < 0); + + test(5 == _sys_read(ends[0], buf, 16, 0)); + test(!memcmp(buf, pipe_msgs[0], 5)); + + test(_sys_read(ends[1], buf, 16, 0) < 0); + test(_sys_write(ends[0], buf, 16, 0, 0) < 0); + + test(5 == _sys_read(ends[0], buf, 16, 0)); + test(!memcmp(buf, pipe_msgs[1], 5)); + + _sys_await(); + } + close(ends[0]); + close(ends[1]); + + + /* writing to pipes with one end closed */ + test(_sys_pipe(ends, 0) >= 0); + for (int i = 0; i < 16; i++) { + if (!fork()) { + close(ends[1]); + test(_sys_read(ends[0], buf, 16, 0) < 0); + exit(0); + } + } + close(ends[1]); + for (int i = 0; i < 16; i++) + _sys_await(); + close(ends[0]); + + test(_sys_pipe(ends, 0) >= 0); + for (int i = 0; i < 16; i++) { + if (!fork()) { + close(ends[0]); + test(_sys_write(ends[1], buf, 16, 0, 0) < 0); + exit(0); + } + } + close(ends[0]); + for (int i = 0; i < 16; i++) + _sys_await(); + close(ends[1]); + + + /* queueing */ + test(_sys_pipe(ends, 0) >= 0); + for (int i = 0; i < 16; i++) { + if (!fork()) { + test(_sys_write(ends[1], pipe_msgs[0], 5, -1, 0) == 5); + exit(0); + } + } + close(ends[1]); + for (int i = 0; i < 16; i++) { + test(_sys_read(ends[0], buf, sizeof buf, 0) == 5); + _sys_await(); + } + test(_sys_read(ends[0], buf, sizeof buf, 0) < 0); + close(ends[0]); + + test(_sys_pipe(ends, 0) >= 0); + for (int i = 0; i < 16; i++) { + if (!fork()) { + memset(buf, 0, sizeof buf); + test(_sys_read(ends[0], buf, 5, -1) == 5); + test(!memcmp(buf, pipe_msgs[1], 5)); + exit(0); + } + } + close(ends[0]); + for (int i = 0; i < 16; i++) { + test(_sys_write(ends[1], pipe_msgs[1], 5, -1, 0) == 5); + _sys_await(); + } + test(_sys_write(ends[1], pipe_msgs[1], 5, -1, 0) < 0); + close(ends[1]); + + + // not a to.do detect when all processes that can read are stuck on writing to the pipe and vice versa + // it seems like linux just lets the process hang endlessly. +} + +static void test_memflag(void) { + void *page = (void*)0x77777000; + _sys_memflag(page, 4096, MEMFLAG_PRESENT); // allocate page + memset(page, 77, 4096); // write to it + _sys_memflag(page, 4096, 0); // free it + + if (!fork()) { + memset(page, 11, 4096); // should segfault + exit(0); + } else { + test(_sys_await() != 0); // test if the process crashed + } + + char *pages[4]; + for (size_t i = 0; i < 4; i++) { + pages[i] = _sys_memflag(NULL, 4096, MEMFLAG_FINDFREE); + test(pages[i] == _sys_memflag(NULL, 4096, MEMFLAG_FINDFREE | MEMFLAG_PRESENT)); + if (i > 0) test(pages[i] != pages[i-1]); + *pages[i] = 0x77; + } + + for (size_t i = 0; i < 4; i++) + _sys_memflag(pages, 4096, MEMFLAG_PRESENT); +} + +static void test_dup(void) { + hid_t pipe[2]; + hid_t h1, h2; + test(_sys_pipe(pipe, 0) >= 0); + + if (!fork()) { + close(pipe[0]); + + h1 = _sys_dup(pipe[1], -1, 0); + test(h1 >= 0); + test(h1 != pipe[1]); + h2 = _sys_dup(h1, -1, 0); + test(h2 >= 0); + test(h2 != pipe[1] && h2 != h1); + + _sys_write(pipe[1], "og", 2, 0, 0); + _sys_write(h1, "h1", 2, 0, 0); + _sys_write(h2, "h2", 2, 0, 0); + + close(pipe[1]); + _sys_write(h1, "h1", 2, 0, 0); + _sys_write(h2, "h2", 2, 0, 0); + + test(_sys_dup(h1, pipe[1], 0) == pipe[1]); + test(_sys_dup(h2, pipe[1], 0) == pipe[1]); + test(_sys_dup(h1, pipe[1], 0) == pipe[1]); + test(_sys_dup(h2, pipe[1], 0) == pipe[1]); + close(h1); + close(h2); + + test(_sys_dup(pipe[1], h2, 0) == h2); + _sys_write(h2, "h2", 2, 0, 0); + close(h2); + + test(_sys_dup(pipe[1], h1, 0) == h1); + _sys_write(h1, "h1", 2, 0, 0); + close(h1); + + exit(0); + } else { + char buf[16]; + size_t count = 0; + close(pipe[1]); + while (_sys_read(pipe[0], buf, sizeof buf, 0) >= 0) + count++; + test(count == 7); + _sys_await(); + } + + + close(pipe[0]); +} + +static void test_execbuf(void) { + if (!fork()) { + uint64_t buf[] = { + EXECBUF_SYSCALL, _SYS_EXIT, 123, 0, 0, 0, 0, + }; + _sys_execbuf(buf, sizeof buf); + test_fail(); + } else { + test(_sys_await() == 123); + } +} + +static void test_sleep(void) { + hid_t reader; + FILE *writer; + if (!forkpipe(&writer, &reader)) { + if (!fork()) { + if (!fork()) { + _sys_sleep(100); + fprintf(writer, "1"); + _sys_sleep(200); + fprintf(writer, "3"); + _sys_sleep(200); + fprintf(writer, "5"); + _sys_exit(0); + } + if (!fork()) { + fprintf(writer, "0"); + _sys_sleep(200); + fprintf(writer, "2"); + _sys_sleep(200); + fprintf(writer, "4"); + /* get killed while asleep + * a peaceful death, i suppose. */ + for (;;) _sys_sleep(1000000000); + } + _sys_await(); + _sys_filicide(); + _sys_exit(0); + } + + /* this part checks if, after killing an asleep process, + * other processes can still wake up */ + _sys_sleep(600); + fprintf(writer, "6"); + exit(0); + } else { + const char *expected = "0123456"; + size_t target = strlen(expected); + size_t pos = 0; + for (;;) { + char buf[128]; + long ret = _sys_read(reader, buf, sizeof buf, 0); + if (ret < 0) break; + test(pos + ret <= target); + test(memcmp(buf, expected + pos, ret) == 0); + pos += ret; + } + test(pos == target); + } +} + +static void test_badopen(void) { + test(_sys_open(TMPFILEPATH, strlen(TMPFILEPATH), OPEN_CREATE | OPEN_WRITE) >= 0); + test(_sys_open(TMPFILEPATH, strlen(TMPFILEPATH), OPEN_CREATE) == -EINVAL); +} + +void r_k_miscsyscall(void) { + run_test(test_await); + run_test(test_await2); + run_test(test_wait2_basic); + run_test(test_pipe); + run_test(test_memflag); + run_test(test_dup); + run_test(test_execbuf); + run_test(test_sleep); + run_test(test_badopen); +} diff --git a/src/cmd/tests/kernel/path.c b/src/cmd/tests/kernel/path.c new file mode 100644 index 0000000..5a22c36 --- /dev/null +++ b/src/cmd/tests/kernel/path.c @@ -0,0 +1,108 @@ +#include "../tests.h" +#include <camellia/path.h> +#include <string.h> +#include <camellia/compat.h> +#include <camellia/fs/misc.h> + +static void test_path_simplify(void) { + const char *testcases[][2] = { + {"/", "/"}, + {"/.", "/"}, + {"//", "/"}, + {"/asdf", "/asdf"}, + {"/asdf/", "/asdf/"}, + {"/asdf//", "/asdf/"}, + {"/asdf/./", "/asdf/"}, + {"/a/./b", "/a/b"}, + {"/a/./b/", "/a/b/"}, + + // some slightly less easy cases + {"/asdf/..", "/"}, + {"/asdf/../", "/"}, + {"/asdf/.", "/asdf/"}, + {"/asdf//.", "/asdf/"}, + + {"/foo/bar/..", "/foo/"}, + {"/foo/bar/../baz", "/foo/baz"}, + {"/foo/bar/../baz/", "/foo/baz/"}, + {"/foo/bar/xyz/..", "/foo/bar/"}, + {"/foo/bar/xyz/../", "/foo/bar/"}, + + // going under the root or close to it + {"/..", NULL}, + {"/../asdf", NULL}, + {"/../asdf/", NULL}, + {"/./a/../..", NULL}, + {"/a/a/../..", "/"}, + {"/a/../a/..", "/"}, + {"/a/../../a", NULL}, + {"/../a/../a", NULL}, + {"/../../a/a", NULL}, + {"/////../..", NULL}, + {"//a//../..", NULL}, + + // relative paths aren't allowed + {"relative", NULL}, + {"some/stuff", NULL}, + {"./stuff", NULL}, + {"../stuff", NULL}, + {"", NULL}, + }; + + char buf[256]; + for (size_t i = 0; i < sizeof(testcases) / sizeof(testcases[0]); i++) { + const char *input = testcases[i][0]; + const char *expected = testcases[i][1]; + int len = path_simplify(input, buf, strlen(input)); + if (expected == NULL) { + test(len == 0); + } else { + // TODO an argument for printing info on test failure + test(len == (int)strlen(expected) && !memcmp(expected, buf, len)); + } + } +} + +static void mount_resolve_drv(const char *path) { + if (mount_at(path) != 0) return; + + struct ufs_request res; + while (!c0_fs_wait(NULL, 0, &res)) { + // TODO does the first argument of c0_fs_respond need to be non-const? + c0_fs_respond((void*)path, strlen(path), 0); + } + exit(1); +} + +static void test_mount_resolve(void) { + const char *testcases[][2] = { + {"/", "/"}, + {"/test", "/"}, + {"/dir", "/dir"}, + {"/dir/..", "/"}, + {"/dir/../dir", "/dir"}, + {"/dirry", "/"}, + {"/dir/", "/dir"}, + {"/dir/shadowed", "/dir"}, + {"/dir/shadowed/", "/dir"}, + }; + mount_resolve_drv("/"); + mount_resolve_drv("/dir/shadowed"); + mount_resolve_drv("/dir"); + + char buf[16]; + for (size_t i = 0; i < sizeof(testcases) / sizeof(testcases[0]); i++) { + const char *input = testcases[i][0]; + const char *expected = testcases[i][1]; + hid_t h = _sys_open(input, strlen(input), 0); + test(h >= 0); + int len = _sys_read(h, buf, sizeof buf, 0); + test(len == (int)strlen(expected) && !memcmp(expected, buf, len)); + _sys_close(h); + } +} + +void r_k_path(void) { + run_test(test_path_simplify); + run_test(test_mount_resolve); +} diff --git a/src/cmd/tests/kernel/threads.c b/src/cmd/tests/kernel/threads.c new file mode 100644 index 0000000..b3c1c06 --- /dev/null +++ b/src/cmd/tests/kernel/threads.c @@ -0,0 +1,55 @@ +#include "../tests.h" +#include <camellia/flags.h> +#include <camellia/syscalls.h> +#include <string.h> +#include <esemaphore.h> +#include <thread.h> + +int global_n; +static void basic_thread(void *sem) { + global_n = 10; + esem_signal(sem); +} +static void test_basic_thread(void) { + struct evil_sem *sem = esem_new(0); + global_n = 0; + thread_create(FORK_NOREAP, basic_thread, sem); + esem_wait(sem); + test(global_n == 10); +} + +hid_t global_h; +static void shared_handle(void *sem) { + hid_t ends[2]; + test(_sys_pipe(ends, 0) >= 0); + global_h = ends[0]; + esem_signal(sem); + _sys_write(ends[1], "Hello!", 7, -1, 0); +} +static void test_shared_handle(void) { + struct evil_sem *sem = esem_new(0); + char buf[16]; + global_h = -1; + thread_create(FORK_NOREAP, shared_handle, sem); + esem_wait(sem); + + test(global_h >= 0); + test(_sys_read(global_h, buf, sizeof buf, 0) == 7); + test(!strcmp("Hello!", buf)); +} + +static void many_thread(void *arg) { + *(uint64_t*)arg += 10; +} +static void test_many_threads(void) { + uint64_t n = 0; + for (int i = 0; i < 10; i++) thread_create(0, many_thread, &n); + for (int i = 0; i < 10; i++) _sys_await(); + test(n == 100); +} + +void r_k_threads(void) { + run_test(test_basic_thread); + run_test(test_shared_handle); + run_test(test_many_threads); +} |