diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 3cb4feebc35..0d57a8dce4d 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory(AK) add_subdirectory(Kernel) add_subdirectory(LibC) +add_subdirectory(LibCore) add_subdirectory(LibCompress) add_subdirectory(LibGfx) add_subdirectory(LibJS) diff --git a/Tests/LibC/CMakeLists.txt b/Tests/LibC/CMakeLists.txt index e7c5aa17d26..d18144aff43 100644 --- a/Tests/LibC/CMakeLists.txt +++ b/Tests/LibC/CMakeLists.txt @@ -5,6 +5,7 @@ set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/TestLibCMkTemp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TestLibCExec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/TestLibCDirEnt.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/TestLibCInodeWatcher.cpp ) file(GLOB CMD_SOURCES CONFIGURE_DEPENDS "*.cpp") diff --git a/Tests/LibC/TestLibCInodeWatcher.cpp b/Tests/LibC/TestLibCInodeWatcher.cpp new file mode 100644 index 00000000000..b36c3eb7ff8 --- /dev/null +++ b/Tests/LibC/TestLibCInodeWatcher.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2021, sin-ack + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +u8 buffer[MAXIMUM_EVENT_SIZE]; +InodeWatcherEvent* event = reinterpret_cast(buffer); + +static int read_event(int fd) +{ + int rc = read(fd, &buffer, MAXIMUM_EVENT_SIZE); + return rc; +} + +static String get_event_name() +{ + if (event->name_length == 0) + return String(); + + return String { event->name, event->name_length - 1 }; +} + +TEST_CASE(inode_watcher_metadata_modified_event) +{ + int fd = create_inode_watcher(0); + EXPECT_NE(fd, -1); + + int test_fd = creat("/tmp/testfile", 0777); + EXPECT_NE(test_fd, -1); + + int wd = inode_watcher_add_watch(fd, "/tmp/testfile", 13, static_cast(InodeWatcherEvent::Type::MetadataModified)); + EXPECT_NE(wd, -1); + + // "touch" the file + int rc = utime("/tmp/testfile", nullptr); + EXPECT_NE(rc, -1); + + rc = read_event(fd); + EXPECT_EQ(event->watch_descriptor, wd); + EXPECT_EQ(event->type, InodeWatcherEvent::Type::MetadataModified); + + close(fd); + close(test_fd); + unlink("/tmp/testfile"); +} + +TEST_CASE(inode_watcher_content_modified_event) +{ + int fd = create_inode_watcher(0); + EXPECT_NE(fd, -1); + + int test_fd = creat("/tmp/testfile", 0777); + EXPECT_NE(test_fd, -1); + + int wd = inode_watcher_add_watch(fd, "/tmp/testfile", 13, static_cast(InodeWatcherEvent::Type::ContentModified)); + EXPECT_NE(wd, -1); + + int rc = write(test_fd, "test", 4); + EXPECT_NE(rc, -1); + + rc = read_event(fd); + EXPECT_NE(rc, -1); + EXPECT_EQ(event->watch_descriptor, wd); + EXPECT_EQ(event->type, InodeWatcherEvent::Type::ContentModified); + + close(fd); + close(test_fd); + unlink("/tmp/testfile"); +} + +TEST_CASE(inode_watcher_deleted_event) +{ + int fd = create_inode_watcher(0); + EXPECT_NE(fd, -1); + + int test_fd = creat("/tmp/testfile", 0777); + EXPECT_NE(test_fd, -1); + + int wd = inode_watcher_add_watch(fd, "/tmp/testfile", 13, static_cast(InodeWatcherEvent::Type::Deleted)); + EXPECT_NE(wd, -1); + + int rc = unlink("/tmp/testfile"); + EXPECT_NE(rc, -1); + + rc = read_event(fd); + EXPECT_NE(rc, -1); + EXPECT_EQ(event->watch_descriptor, wd); + EXPECT_EQ(event->type, InodeWatcherEvent::Type::Deleted); + + close(fd); + close(test_fd); +} + +TEST_CASE(inode_watcher_child_events) +{ + int fd = create_inode_watcher(0); + EXPECT_NE(fd, -1); + + int wd = inode_watcher_add_watch(fd, "/tmp/", 5, static_cast(InodeWatcherEvent::Type::ChildCreated | InodeWatcherEvent::Type::ChildDeleted)); + EXPECT_NE(fd, -1); + + int rc = creat("/tmp/testfile", 0777); + EXPECT_NE(rc, -1); + + rc = read_event(fd); + EXPECT_NE(rc, -1); + EXPECT_EQ(event->watch_descriptor, wd); + EXPECT_EQ(event->type, InodeWatcherEvent::Type::ChildCreated); + VERIFY(event->name_length > 0); + EXPECT_EQ(get_event_name(), "testfile"); + + rc = unlink("/tmp/testfile"); + EXPECT_NE(rc, -1); + + rc = read_event(fd); + EXPECT_NE(rc, -1); + EXPECT_EQ(event->watch_descriptor, wd); + EXPECT_EQ(event->type, InodeWatcherEvent::Type::ChildDeleted); + VERIFY(event->name_length > 0); + EXPECT_EQ(get_event_name(), "testfile"); + + close(fd); +} + +TEST_CASE(inode_watcher_closes_children_on_close) +{ + int fd = create_inode_watcher(0); + EXPECT_NE(fd, -1); + + int test_fd = creat("/tmp/testfile", 0777); + EXPECT_NE(test_fd, -1); + int wd = inode_watcher_add_watch(fd, "/tmp/testfile", 13, static_cast(InodeWatcherEvent::Type::MetadataModified)); + EXPECT_NE(wd, -1); + + int rc = utime("/tmp/testfile", nullptr); + EXPECT_NE(rc, -1); + + close(fd); + + rc = read_event(fd); + EXPECT_EQ(rc, -1); + EXPECT_EQ(errno, EBADF); + + close(test_fd); + unlink("/tmp/testfile"); +} + +TEST_CASE(inode_watcher_nonblock) +{ + int fd = create_inode_watcher(static_cast(InodeWatcherFlags::Nonblock)); + EXPECT_NE(fd, -1); + + int rc = read_event(fd); + EXPECT_EQ(rc, -1); + EXPECT_EQ(errno, EAGAIN); + + close(fd); +} diff --git a/Tests/LibCore/CMakeLists.txt b/Tests/LibCore/CMakeLists.txt new file mode 100644 index 00000000000..1a949bfd2ff --- /dev/null +++ b/Tests/LibCore/CMakeLists.txt @@ -0,0 +1,8 @@ +set( + TEST_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/TestLibCoreFileWatcher.cpp +) + +foreach(source ${TEST_SOURCES}) + serenity_test(${source} LibCore) +endforeach() diff --git a/Tests/LibCore/TestLibCoreFileWatcher.cpp b/Tests/LibCore/TestLibCoreFileWatcher.cpp new file mode 100644 index 00000000000..6876cc59fdf --- /dev/null +++ b/Tests/LibCore/TestLibCoreFileWatcher.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include + +TEST_CASE(file_watcher_child_events) +{ + auto event_loop = Core::EventLoop(); + auto maybe_file_watcher = Core::FileWatcher::create(); + EXPECT_NE(maybe_file_watcher.is_error(), true); + + auto file_watcher = maybe_file_watcher.release_value(); + auto watch_result = file_watcher->add_watch("/tmp/", + Core::FileWatcherEvent::Type::ChildCreated + | Core::FileWatcherEvent::Type::ChildDeleted); + EXPECT_NE(watch_result.is_error(), true); + + int event_count = 0; + file_watcher->on_change = [&](Core::FileWatcherEvent const& event) { + if (event_count == 0) { + EXPECT_EQ(event.event_path, "/tmp/testfile"); + EXPECT_EQ(event.type, Core::FileWatcherEvent::Type::ChildCreated); + } else if (event_count == 1) { + EXPECT_EQ(event.event_path, "/tmp/testfile"); + EXPECT_EQ(event.type, Core::FileWatcherEvent::Type::ChildDeleted); + + event_loop.quit(0); + } + + event_count++; + }; + + auto timer1 = Core::Timer::create_single_shot(500, [&] { + int rc = creat("/tmp/testfile", 0777); + EXPECT_NE(rc, -1); + }); + timer1->start(); + + auto timer2 = Core::Timer::create_single_shot(1000, [&] { + int rc = unlink("/tmp/testfile"); + EXPECT_NE(rc, -1); + }); + timer2->start(); + + auto catchall_timer = Core::Timer::create_single_shot(2000, [&] { + VERIFY_NOT_REACHED(); + }); + catchall_timer->start(); + + event_loop.exec(); +}