diff options
Diffstat (limited to 'na/test/test_pipe_devfs.cc')
| -rw-r--r-- | na/test/test_pipe_devfs.cc | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/na/test/test_pipe_devfs.cc b/na/test/test_pipe_devfs.cc new file mode 100644 index 0000000..ed375ed --- /dev/null +++ b/na/test/test_pipe_devfs.cc @@ -0,0 +1,420 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * test_pipe_devfs.cc — Self-tests for pipe, /dev/null, /dev/zero, fcntl + */ + +#include <lume/selftest.h> +#include <lume/errno.h> +#include <lume/file.h> +#include <lume/fs.h> +#include <lume/fs_types.h> +#include <lume/task.h> +#include <lume/fdtable.h> +#include <lume/pipe.h> + +/* Syscall entry points */ +int64 sys_openat(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_close(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_read(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_write(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_pipe2(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_fcntl(uint64, uint64, uint64, uint64, uint64, uint64); +int64 sys_fstat_wrapper(uint64, uint64, uint64, uint64, uint64, uint64); + +/* ================================================================== */ +/* Helpers */ +/* ================================================================== */ + +static int64 open_dev(const char* path, int flags) +{ + return sys_openat( + static_cast<uint64>(-100), /* AT_FDCWD */ + reinterpret_cast<uint64>(path), + static_cast<uint64>(flags), + 0, 0, 0); +} + +static int64 do_close(int64 fd) +{ + return sys_close(static_cast<uint64>(fd), 0, 0, 0, 0, 0); +} + +static int64 do_read(int64 fd, void* buf, uint64 count) +{ + return sys_read(static_cast<uint64>(fd), + reinterpret_cast<uint64>(buf), count, 0, 0, 0); +} + +static int64 do_write(int64 fd, const void* buf, uint64 count) +{ + return sys_write(static_cast<uint64>(fd), + reinterpret_cast<uint64>(buf), count, 0, 0, 0); +} + +/* fcntl command constants — must match sys_fs.cc */ +inline constexpr int F_DUPFD = 0; +inline constexpr int F_GETFD = 1; +inline constexpr int F_SETFD = 2; +inline constexpr int F_GETFL = 3; +inline constexpr int F_SETFL = 4; +inline constexpr int F_DUPFD_CLOEXEC = 1030; +inline constexpr int FD_CLOEXEC = 1; + +/* ================================================================== */ +/* /dev/null tests */ +/* ================================================================== */ + +void selftest_devfs() +{ + /* ---- /dev/null: read returns EOF ---- */ + st_begin("devfs: /dev/null read returns 0 (EOF)"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + char buf[16]; + int64 n = do_read(fd, buf, sizeof(buf)); + ST_ASSERT_EQ(n, (int64)0); + do_close(fd); + } + st_pass(); + + /* ---- /dev/null: write discards data ---- */ + st_begin("devfs: /dev/null write discards data"); + { + int64 fd = open_dev("/dev/null", O_WRONLY); + ST_ASSERT(fd >= 0); + + const char* msg = "discarded"; + int64 n = do_write(fd, msg, 9); + ST_ASSERT_EQ(n, (int64)9); + do_close(fd); + } + st_pass(); + + /* ---- /dev/null: fstat reports S_IFCHR ---- */ + st_begin("devfs: /dev/null fstat is S_IFCHR"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + kstat st{}; + int64 ret = sys_fstat_wrapper( + static_cast<uint64>(fd), + reinterpret_cast<uint64>(&st), 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + ST_ASSERT(S_ISCHR(st.st_mode)); + do_close(fd); + } + st_pass(); + + /* ---- /dev/zero: read returns zeros ---- */ + st_begin("devfs: /dev/zero read returns zeros"); + { + int64 fd = open_dev("/dev/zero", O_RDONLY); + ST_ASSERT(fd >= 0); + + char buf[32]; + /* Pre-fill with non-zero */ + for (int i = 0; i < 32; i++) buf[i] = 0xFF; + + int64 n = do_read(fd, buf, 32); + ST_ASSERT_EQ(n, (int64)32); + + bool all_zero = true; + for (int i = 0; i < 32; i++) { + if (buf[i] != 0) { all_zero = false; break; } + } + ST_ASSERT(all_zero); + do_close(fd); + } + st_pass(); + + /* ---- /dev/zero: write discards data ---- */ + st_begin("devfs: /dev/zero write discards data"); + { + int64 fd = open_dev("/dev/zero", O_WRONLY); + ST_ASSERT(fd >= 0); + + char buf[16] = {1, 2, 3}; + int64 n = do_write(fd, buf, 16); + ST_ASSERT_EQ(n, (int64)16); + do_close(fd); + } + st_pass(); +} + +/* ================================================================== */ +/* Pipe tests */ +/* ================================================================== */ + +void selftest_pipe() +{ + /* ---- pipe2: basic creation ---- */ + st_begin("pipe: pipe2 creates two valid fds"); + { + int fds[2] = {-1, -1}; + int64 ret = sys_pipe2( + reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + ST_ASSERT(fds[0] >= 0); + ST_ASSERT(fds[1] >= 0); + ST_ASSERT_NE(fds[0], fds[1]); + + do_close(fds[0]); + do_close(fds[1]); + } + st_pass(); + + /* ---- pipe: write then read ---- */ + st_begin("pipe: write then read returns correct data"); + { + int fds[2]; + sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0); + + const char* msg = "pipe_test"; + int64 w = do_write(fds[1], msg, 9); + ST_ASSERT_EQ(w, (int64)9); + + char buf[16] = {}; + int64 r = do_read(fds[0], buf, 16); + ST_ASSERT_EQ(r, (int64)9); + + bool match = true; + for (int i = 0; i < 9; i++) { + if (buf[i] != msg[i]) { match = false; break; } + } + ST_ASSERT(match); + + do_close(fds[0]); + do_close(fds[1]); + } + st_pass(); + + /* ---- pipe: read from closed write end returns EOF ---- */ + st_begin("pipe: read after writer close returns EOF"); + { + int fds[2]; + sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0); + + /* Write some data, then close write end */ + const char* msg = "eof"; + do_write(fds[1], msg, 3); + do_close(fds[1]); + + /* Read the data */ + char buf[16]; + int64 r1 = do_read(fds[0], buf, 16); + ST_ASSERT_EQ(r1, (int64)3); + + /* Next read should return 0 (EOF) */ + int64 r2 = do_read(fds[0], buf, 16); + ST_ASSERT_EQ(r2, (int64)0); + + do_close(fds[0]); + } + st_pass(); + + /* ---- pipe: write to closed read end returns -EPIPE ---- */ + st_begin("pipe: write after reader close returns -EPIPE"); + { + int fds[2]; + sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0); + + /* Close read end */ + do_close(fds[0]); + + /* Write should fail */ + const char* msg = "broken"; + int64 w = do_write(fds[1], msg, 6); + ST_ASSERT_EQ(w, (int64)-EPIPE); + + do_close(fds[1]); + } + st_pass(); + + /* ---- pipe: multiple writes accumulate ---- */ + st_begin("pipe: multiple writes accumulate in buffer"); + { + int fds[2]; + sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0); + + do_write(fds[1], "AAA", 3); + do_write(fds[1], "BBB", 3); + + char buf[16] = {}; + int64 r = do_read(fds[0], buf, 16); + ST_ASSERT_EQ(r, (int64)6); + ST_ASSERT_EQ(buf[0], 'A'); + ST_ASSERT_EQ(buf[3], 'B'); + + do_close(fds[0]); + do_close(fds[1]); + } + st_pass(); + + /* ---- pipe: read partial ---- */ + st_begin("pipe: partial read leaves remainder"); + { + int fds[2]; + sys_pipe2(reinterpret_cast<uint64>(fds), 0, 0, 0, 0, 0); + + do_write(fds[1], "ABCDEF", 6); + + char buf[3] = {}; + int64 r1 = do_read(fds[0], buf, 3); + ST_ASSERT_EQ(r1, (int64)3); + ST_ASSERT_EQ(buf[0], 'A'); + ST_ASSERT_EQ(buf[2], 'C'); + + int64 r2 = do_read(fds[0], buf, 3); + ST_ASSERT_EQ(r2, (int64)3); + ST_ASSERT_EQ(buf[0], 'D'); + ST_ASSERT_EQ(buf[2], 'F'); + + do_close(fds[0]); + do_close(fds[1]); + } + st_pass(); +} + +/* ================================================================== */ +/* fcntl tests */ +/* ================================================================== */ + +void selftest_fcntl() +{ + /* ---- fcntl F_DUPFD ---- */ + st_begin("fcntl: F_DUPFD duplicates fd"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 fd2 = sys_fcntl(static_cast<uint64>(fd), F_DUPFD, 0, 0, 0, 0); + ST_ASSERT(fd2 >= 0); + ST_ASSERT_NE(fd, fd2); + + /* Both should be valid reads */ + char buf[4]; + int64 r = do_read(fd2, buf, 4); + ST_ASSERT_EQ(r, (int64)0); /* /dev/null → EOF */ + + do_close(fd); + do_close(fd2); + } + st_pass(); + + /* ---- fcntl F_DUPFD with min_fd ---- */ + st_begin("fcntl: F_DUPFD respects min_fd"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 fd2 = sys_fcntl(static_cast<uint64>(fd), F_DUPFD, 100, 0, 0, 0); + ST_ASSERT(fd2 >= 100); + + do_close(fd); + do_close(fd2); + } + st_pass(); + + /* ---- fcntl F_GETFD / F_SETFD ---- */ + st_begin("fcntl: F_GETFD/F_SETFD cloexec"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + /* Initially no CLOEXEC */ + int64 flags = sys_fcntl(static_cast<uint64>(fd), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)0); + + /* Set CLOEXEC */ + int64 ret = sys_fcntl(static_cast<uint64>(fd), F_SETFD, FD_CLOEXEC, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)0); + + /* Verify it was set */ + flags = sys_fcntl(static_cast<uint64>(fd), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)FD_CLOEXEC); + + /* Clear it */ + sys_fcntl(static_cast<uint64>(fd), F_SETFD, 0, 0, 0, 0); + flags = sys_fcntl(static_cast<uint64>(fd), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)0); + + do_close(fd); + } + st_pass(); + + /* ---- fcntl F_GETFL ---- */ + st_begin("fcntl: F_GETFL returns open flags"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 fl = sys_fcntl(static_cast<uint64>(fd), F_GETFL, 0, 0, 0, 0); + /* O_RDONLY == 0, so result should have no write bits */ + ST_ASSERT_EQ(fl & O_WRONLY, (int64)0); + + do_close(fd); + } + st_pass(); + + /* ---- fcntl F_SETFL O_NONBLOCK ---- */ + st_begin("fcntl: F_SETFL toggles O_NONBLOCK"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + /* Set O_NONBLOCK */ + sys_fcntl(static_cast<uint64>(fd), F_SETFL, O_NONBLOCK, 0, 0, 0); + int64 fl = sys_fcntl(static_cast<uint64>(fd), F_GETFL, 0, 0, 0, 0); + ST_ASSERT(fl & O_NONBLOCK); + + /* Clear it */ + sys_fcntl(static_cast<uint64>(fd), F_SETFL, 0, 0, 0, 0); + fl = sys_fcntl(static_cast<uint64>(fd), F_GETFL, 0, 0, 0, 0); + ST_ASSERT_EQ(fl & O_NONBLOCK, (int64)0); + + do_close(fd); + } + st_pass(); + + /* ---- fcntl F_DUPFD_CLOEXEC ---- */ + st_begin("fcntl: F_DUPFD_CLOEXEC sets cloexec"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 fd2 = sys_fcntl(static_cast<uint64>(fd), F_DUPFD_CLOEXEC, 0, 0, 0, 0); + ST_ASSERT(fd2 >= 0); + + int64 flags = sys_fcntl(static_cast<uint64>(fd2), F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(flags, (int64)FD_CLOEXEC); + + do_close(fd); + do_close(fd2); + } + st_pass(); + + /* ---- fcntl bad fd ---- */ + st_begin("fcntl: bad fd returns -EBADF"); + { + int64 ret = sys_fcntl(999, F_GETFD, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EBADF); + } + st_pass(); + + /* ---- fcntl unknown cmd ---- */ + st_begin("fcntl: unknown cmd returns -EINVAL"); + { + int64 fd = open_dev("/dev/null", O_RDONLY); + ST_ASSERT(fd >= 0); + + int64 ret = sys_fcntl(static_cast<uint64>(fd), 9999, 0, 0, 0, 0); + ST_ASSERT_EQ(ret, (int64)-EINVAL); + do_close(fd); + } + st_pass(); +} |
