// TEST DATA DO NOT REMOVE THIS LINE #include "tests.h" #include "crucible/chatter.h" #include "crucible/error.h" #include "crucible/fd.h" #include #include #include #include #include #include #include #include #include #include using namespace crucible; static void test_default_constructor_and_destructor() { Fd f; } static void test_basic_read() { Fd f = open_or_die("fd.cc"); const char test_string[] = "// TEST DATA DO NOT REMOVE THIS LINE"; const int test_string_len = sizeof(test_string) - 1; char read_buf[test_string_len]; read_or_die(f, read_buf); assert(!strncmp(read_buf, test_string, test_string_len)); f = Fd(); } static void test_create_read_write() { Fd f = open_or_die("tmp/fd-read-write", O_CREAT | O_RDWR | O_TRUNC); struct test_str_out { int i; float f; } tso = { .i = 5, .f = 3.14159, }, tsi = { .i = 0, .f = 0, }; size_t bytes_read = 0; read_partial_or_die(f, tsi, bytes_read); assert(bytes_read == 0); assert(tsi.i == 0); assert(tsi.f == 0); pwrite_or_die(f, tso, 1024); pread_or_die(f, tsi, 1024); assert(!memcmp(&tsi, &tso, sizeof(tsi))); } static void test_flags() { #define FLAG_TEST(x) cerr << #x << ": " << flush; cerr << x << endl; FLAG_TEST(o_flags_ntoa(O_RDONLY)); FLAG_TEST(o_flags_ntoa(O_WRONLY)); FLAG_TEST(o_flags_ntoa(O_RDWR)); FLAG_TEST(o_flags_ntoa(O_CREAT|O_WRONLY|O_TRUNC)); FLAG_TEST(o_mode_ntoa(0001)); FLAG_TEST(o_mode_ntoa(0002)); FLAG_TEST(o_mode_ntoa(0004)); FLAG_TEST(o_mode_ntoa(0010)); FLAG_TEST(o_mode_ntoa(0020)); FLAG_TEST(o_mode_ntoa(0040)); FLAG_TEST(o_mode_ntoa(0100)); FLAG_TEST(o_mode_ntoa(0200)); FLAG_TEST(o_mode_ntoa(0400)); FLAG_TEST(o_mode_ntoa(01000)); FLAG_TEST(o_mode_ntoa(02000)); FLAG_TEST(o_mode_ntoa(04000)); FLAG_TEST(o_mode_ntoa(010000)); FLAG_TEST(o_mode_ntoa(020000)); FLAG_TEST(o_mode_ntoa(040000)); FLAG_TEST(o_mode_ntoa(0777)); FLAG_TEST(o_mode_ntoa(02775)); FLAG_TEST(o_mode_ntoa(01777)); FLAG_TEST(o_mode_ntoa(022)); FLAG_TEST(o_mode_ntoa(077)); } // Test code namespace crucible { extern bool assert_no_leaked_fds(); }; struct FdChecker { ~FdChecker() { assert_no_leaked_fds(); } }; static FdChecker fd_destructor_check; static inline void assert_is_closed(int i, bool closed = true) { pid_t self_pid = getpid(); char buf[1024]; snprintf(buf, sizeof(buf), "/proc/%d/fd/%d", self_pid, i); assert(access(buf, F_OK) ? closed : !closed); } static void test_construct_destroy() { int i; { Fd fd(open("fd.cc", O_RDONLY)); i = fd; } assert_is_closed(i); } static void test_construct_copy() { int i; { Fd fd(open("fd.cc", O_RDONLY)); i = fd; Fd fd2(fd); int j = fd2; assert(i == j); } assert_is_closed(i); } static void test_construct_default_assign() { int i; { i = open("fd.cc", O_RDONLY); Fd fd; fd = i; Fd fd2; fd2 = fd; int j = fd2; assert(i == j); } assert_is_closed(i); } static void test_assign_int() { int i; { i = open("fd.cc", O_RDONLY); Fd fd; fd = i; Fd fd2; fd2 = i; int j = fd2; assert(i == j); } assert_is_closed(i); } static void test_assign_int_survives_scope() { int i, j; { Fd fd2; { i = open("fd.cc", O_RDONLY); Fd fd; fd = i; fd2 = i; j = fd2; assert(i == j); } assert_is_closed(i, false); } assert_is_closed(i, true); } static void test_assign_int_close() { int i; { Fd fd(open("fd.cc", O_RDONLY)); i = fd; assert_is_closed(i, false); fd = -1; assert_is_closed(i, true); int j = fd; assert(j == -1); // Bonus conversion operator tests assert(fd == -1); // Chasing a closed ref no longer triggers an exception assert(fd->get_fd() == -1); } assert_is_closed(i, true); } static void test_assign_int_close_2() { int i; { Fd fd(open("fd.cc", O_RDONLY)); i = fd; assert_is_closed(i, false); // -2 is null... fd = -2; assert_is_closed(i, true); int j = fd; // ...but it will come back as -1 assert(j == -1); // Bonus conversion operator tests assert(fd == -1); // Chasing a closed ref no longer triggers an exception assert(fd->get_fd() == -1); } assert_is_closed(i, true); } static void test_map() { int a, b, c; map fds; { Fd fd_dot_cc = open("fd.cc", O_RDONLY); a = fd_dot_cc; assert_is_closed(a, false); Fd fd_tests_h = open("tests.h", O_RDONLY); b = fd_tests_h; assert_is_closed(b, false); Fd fd_makefile = open("Makefile", O_RDONLY); c = fd_makefile; assert_is_closed(c, false); fds["fd.cc"] = fd_dot_cc; fds.insert(make_pair("tests.h", fd_tests_h)); int j = fds["Makefile"]; assert(j == -1); fds["Makefile"] = fd_makefile; assert_is_closed(a, false); assert_is_closed(b, false); assert_is_closed(c, false); } assert_is_closed(a, false); assert_is_closed(b, false); assert_is_closed(c, false); } static void test_close() { Fd fd = open("fd.cc", O_RDONLY); int i = fd; assert_is_closed(i, false); fd = Fd(); assert_is_closed(i, true); } static void test_shared_close() { Fd fd = open("fd.cc", O_RDONLY); int i = fd; Fd fd2 = fd; assert_is_closed(i, false); assert_is_closed(fd2, false); fd2 = Fd(); assert_is_closed(i, false); assert_is_closed(fd, false); assert_is_closed(fd2, true); fd = Fd(); assert_is_closed(i, true); assert_is_closed(fd, true); assert_is_closed(fd2, true); } struct DerivedFdResource : public Fd::resource_type { string m_name; DerivedFdResource(string name) : Fd::resource_type(open(name.c_str(), O_RDONLY)), m_name(name) { assert_is_closed(this->get_fd(), false); } const string &name() const { return m_name; } }; template shared_ptr cast(const Fd &fd) { auto dp = dynamic_pointer_cast(fd.operator->()); if (!dp) { cerr << "expect bad cast exception: " << flush; throw bad_cast(); } return dp; } struct DerivedFd : public Fd { using resource_type = DerivedFdResource; DerivedFd(string name) { shared_ptr ptr = make_shared(name); Fd::operator=(static_pointer_cast(ptr)); } shared_ptr operator->() const { shared_ptr rv = cast(*this); THROW_CHECK1(out_of_range, rv, rv); return rv; } private: DerivedFd() = default; }; static void test_derived_resource_type() { DerivedFd fd("fd.cc"); assert_is_closed(fd, false); assert(fd->name() == "fd.cc"); DerivedFd fd3(fd); assert_is_closed(fd, false); assert_is_closed(fd3, false); Fd fd2(fd3); assert_is_closed(fd, false); assert_is_closed(fd2, false); assert_is_closed(fd3, false); } static void test_derived_cast() { DerivedFd fd("fd.cc"); assert_is_closed(fd, false); Fd fd2(fd); Fd fd3 = open("fd.cc", O_RDONLY); assert(fd->name() == "fd.cc"); assert(cast(fd)); assert(cast(fd)); assert(cast(fd2)); assert(cast(fd2)); assert(cast(fd3)); assert(catch_all([&](){ assert(!cast(fd3)); } )); } static void test_derived_map() { int a, b, c; map fds; { DerivedFd fd_dot_cc("fd.cc"); a = fd_dot_cc; assert_is_closed(a, false); Fd fd_tests_h = open("tests.h", O_RDONLY); b = fd_tests_h; assert_is_closed(b, false); DerivedFd fd_makefile("Makefile"); c = fd_makefile; assert_is_closed(c, false); fds["fd.cc"] = fd_dot_cc; fds.insert(make_pair("tests.h", fd_tests_h)); int j = fds["Makefile"]; assert(j == -1); fds["Makefile"] = fd_makefile; assert_is_closed(a, false); assert_is_closed(b, false); assert_is_closed(c, false); } assert_is_closed(a, false); assert_is_closed(b, false); assert_is_closed(c, false); } int main(int, const char **) { RUN_A_TEST(test_default_constructor_and_destructor()); RUN_A_TEST(test_basic_read()); RUN_A_TEST(test_create_read_write()); RUN_A_TEST(test_flags()); RUN_A_TEST(test_construct_destroy()); RUN_A_TEST(test_construct_copy()); RUN_A_TEST(test_construct_default_assign()); RUN_A_TEST(test_assign_int()); RUN_A_TEST(test_assign_int_survives_scope()); RUN_A_TEST(test_assign_int_close()); RUN_A_TEST(test_assign_int_close_2()); RUN_A_TEST(test_map()); RUN_A_TEST(test_close()); RUN_A_TEST(test_shared_close()); RUN_A_TEST(test_derived_resource_type()); RUN_A_TEST(test_derived_map()); RUN_A_TEST(test_derived_cast()); assert_no_leaked_fds(); return 0; }