From 1a73875e01fa69a1df7dfd0f9a5bed37314f52ec Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Thu, 18 Jun 2026 19:32:27 -0500 Subject: [PATCH] test(linux): cover platform utility APIs --- CMakeLists.txt | 18 +++++++-- tests/directory_watcher.cpp | 71 ++++++++++++++++++++++++++++++++++ tests/dynamic_library.cpp | 29 ++++++++++++++ tests/environment_paths.cpp | 39 +++++++++++++++++++ tests/headless_interaction.cpp | 36 +++++++++++++++++ tests/process.cpp | 29 ++++++++++++++ tests/smoke.cpp | 52 ------------------------- tests/system_time_debug.cpp | 23 +++++++++++ tests/test_support.hpp | 31 +++++++++++++++ 9 files changed, 273 insertions(+), 55 deletions(-) create mode 100644 tests/directory_watcher.cpp create mode 100644 tests/dynamic_library.cpp create mode 100644 tests/environment_paths.cpp create mode 100644 tests/headless_interaction.cpp create mode 100644 tests/process.cpp delete mode 100644 tests/smoke.cpp create mode 100644 tests/system_time_debug.cpp create mode 100644 tests/test_support.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 382eea3..b744fee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,8 +78,20 @@ endif() option(IZO_BUILD_TESTS "Build the iZo tests" OFF) if(IZO_BUILD_TESTS) + if(NOT UNIX OR APPLE) + message(FATAL_ERROR "iZo unit tests currently require Linux") + endif() enable_testing() - add_executable(izo_smoke_test tests/smoke.cpp) - target_link_libraries(izo_smoke_test PRIVATE iZo::izo) - add_test(NAME izo_smoke COMMAND izo_smoke_test) + function(izo_add_test name) + add_executable(izo_test_${name} tests/${name}.cpp) + target_link_libraries(izo_test_${name} PRIVATE iZo::izo) + add_test(NAME ${name} COMMAND izo_test_${name}) + set_tests_properties(${name} PROPERTIES TIMEOUT 10) + endfunction() + izo_add_test(environment_paths) + izo_add_test(system_time_debug) + izo_add_test(dynamic_library) + izo_add_test(process) + izo_add_test(directory_watcher) + izo_add_test(headless_interaction) endif() diff --git a/tests/directory_watcher.cpp b/tests/directory_watcher.cpp new file mode 100644 index 0000000..b920f72 --- /dev/null +++ b/tests/directory_watcher.cpp @@ -0,0 +1,71 @@ +#include "test_support.hpp" + +#include + +#include +#include +#include +#include + +int main() { + std::string error; + CHECK(!izo::watch_directory({"/izo/missing", false}, [](const auto&) {}, &error)); + CHECK(!error.empty()); + error.clear(); + + const auto directory = test_directory("watcher"); + CHECK(!izo::watch_directory({directory, false}, {}, &error)); + CHECK(!error.empty()); + error.clear(); + CHECK(!izo::watch_directory({directory, false, std::chrono::milliseconds(0)}, + [](const auto&) {}, &error)); + + std::mutex mutex; + std::vector events; + auto watcher = izo::watch_directory( + {directory, true, std::chrono::milliseconds(10)}, + [&](const izo::file_event& event) { + std::lock_guard lock(mutex); + events.push_back(event); + }, &error); + CHECK(watcher); + std::this_thread::sleep_for(std::chrono::milliseconds(50)); + + const auto original = directory / "original.txt"; + const auto renamed = directory / "renamed.txt"; + { std::ofstream file(original); file << "a"; } + CHECK(wait_until([&] { + std::lock_guard lock(mutex); + return std::any_of(events.begin(), events.end(), [&](const auto& event) { + return event.change == izo::file_change::added && event.path == original; + }); + })); + + { std::ofstream file(original, std::ios::app); file << "more"; } + CHECK(wait_until([&] { + std::lock_guard lock(mutex); + return std::any_of(events.begin(), events.end(), [&](const auto& event) { + return event.change == izo::file_change::modified && event.path == original; + }); + })); + + std::filesystem::rename(original, renamed); + CHECK(wait_until([&] { + std::lock_guard lock(mutex); + return std::any_of(events.begin(), events.end(), [&](const auto& event) { + return event.change == izo::file_change::renamed && event.path == renamed && + event.previous_path == original; + }); + })); + + std::filesystem::remove(renamed); + CHECK(wait_until([&] { + std::lock_guard lock(mutex); + return std::any_of(events.begin(), events.end(), [&](const auto& event) { + return event.change == izo::file_change::removed && event.path == renamed; + }); + })); + watcher.stop(); + CHECK(!watcher); + std::filesystem::remove_all(directory); +} diff --git a/tests/dynamic_library.cpp b/tests/dynamic_library.cpp new file mode 100644 index 0000000..7e56238 --- /dev/null +++ b/tests/dynamic_library.cpp @@ -0,0 +1,29 @@ +#include "test_support.hpp" + +#include + +#include + +int main() { + std::string error; + auto missing = izo::load_dynamic_library("/izo/does/not/exist.so", &error); + CHECK(!missing); + CHECK(!error.empty()); + + error.clear(); + auto library = izo::load_dynamic_library("libc.so.6", &error); + CHECK(library); + CHECK(library.symbol("getpid", &error) != nullptr); + error.clear(); + CHECK(library.symbol("izo_missing_symbol", &error) == nullptr); + CHECK(!error.empty()); + + auto moved = std::move(library); + CHECK(!library); + CHECK(moved); + moved.reset(); + CHECK(!moved); + error.clear(); + CHECK(moved.symbol("getpid", &error) == nullptr); + CHECK(!error.empty()); +} diff --git a/tests/environment_paths.cpp b/tests/environment_paths.cpp new file mode 100644 index 0000000..3eac081 --- /dev/null +++ b/tests/environment_paths.cpp @@ -0,0 +1,39 @@ +#include "test_support.hpp" + +#include +#include + +#include + +int main() { + std::string error; + CHECK(izo::set_environment_variable("IZO_TEST_VALUE", "hello-utf8-å", &error)); + CHECK(izo::get_environment_variable("IZO_TEST_VALUE") == "hello-utf8-å"); + CHECK(izo::set_environment_variable("IZO_TEST_VALUE", "", &error)); + CHECK(izo::get_environment_variable("IZO_TEST_VALUE") == ""); + CHECK(izo::unset_environment_variable("IZO_TEST_VALUE", &error)); + CHECK(!izo::get_environment_variable("IZO_TEST_VALUE")); + + const auto root = test_directory("paths"); + CHECK(izo::set_environment_variable("HOME", root.string(), &error)); + CHECK(izo::unset_environment_variable("XDG_DATA_HOME", &error)); + CHECK(izo::unset_environment_variable("XDG_CONFIG_HOME", &error)); + CHECK(izo::unset_environment_variable("XDG_CACHE_HOME", &error)); + CHECK(izo::unset_environment_variable("TMPDIR", &error)); + + CHECK(izo::get_known_path(izo::known_path::app_data) == root / ".local/share"); + CHECK(izo::get_known_path(izo::known_path::local_app_data) == root / ".local/share"); + CHECK(izo::get_known_path(izo::known_path::config) == root / ".config"); + CHECK(izo::get_known_path(izo::known_path::cache) == root / ".cache"); + CHECK(izo::get_known_path(izo::known_path::documents) == root / "Documents"); + CHECK(izo::get_known_path(izo::known_path::downloads) == root / "Downloads"); + CHECK(izo::get_known_path(izo::known_path::desktop) == root / "Desktop"); + CHECK(izo::get_known_path(izo::known_path::temporary) == "/tmp"); + CHECK(!izo::get_known_path(izo::known_path::executable_directory, &error).empty()); + CHECK(izo::get_known_path(izo::known_path::current_directory, &error) == + std::filesystem::current_path()); + + CHECK(izo::set_environment_variable("XDG_CONFIG_HOME", (root / "custom-config").string(), &error)); + CHECK(izo::get_known_path(izo::known_path::config) == root / "custom-config"); + std::filesystem::remove_all(root); +} diff --git a/tests/headless_interaction.cpp b/tests/headless_interaction.cpp new file mode 100644 index 0000000..6ffb5fe --- /dev/null +++ b/tests/headless_interaction.cpp @@ -0,0 +1,36 @@ +#include "test_support.hpp" + +#include +#include +#include +#include + +int main() { + std::string error; + CHECK(izo::set_environment_variable("PATH", "", &error)); + + auto dialog = izo::open_file(); + CHECK(dialog.status == izo::dialog_status::error); + CHECK(!dialog.error_message.empty()); + dialog = izo::save_file(); + CHECK(dialog.status == izo::dialog_status::error); + dialog = izo::pick_folder(); + CHECK(dialog.status == izo::dialog_status::error); + + error.clear(); + CHECK(!izo::open_path("/tmp", &error)); + CHECK(!error.empty()); + error.clear(); + CHECK(!izo::reveal_in_file_manager("/tmp/file", &error)); + CHECK(!error.empty()); + + error.clear(); + CHECK(izo::show_message_box({"title", "message"}, &error) == izo::message_response::error); + CHECK(!error.empty()); + error.clear(); + CHECK(!izo::set_clipboard_text({}, &error)); + CHECK(!error.empty()); + error.clear(); + CHECK(!izo::get_clipboard_text(&error)); + CHECK(!error.empty()); +} diff --git a/tests/process.cpp b/tests/process.cpp new file mode 100644 index 0000000..288a038 --- /dev/null +++ b/tests/process.cpp @@ -0,0 +1,29 @@ +#include "test_support.hpp" + +#include + +#include + +int main() { + std::string marker; + auto result = izo::launch_process({}); + CHECK(!result); + CHECK(!result.error_message.empty()); + + result = izo::launch_process({"/izo/missing-executable", {}, {}, false}); + CHECK(!result); + CHECK(!result.error_message.empty()); + + const auto directory = test_directory("process"); + result = izo::launch_process({"/bin/sh", {"-c", "printf launched > marker.txt"}, directory, false}); + CHECK(result); + CHECK(result.process_id > 0); + CHECK(wait_until([&] { return std::filesystem::exists(directory / "marker.txt"); })); + std::ifstream(directory / "marker.txt") >> marker; + CHECK(marker == "launched"); + + result = izo::launch_process({"/bin/true", {}, directory / "missing", false}); + CHECK(!result); + CHECK(!result.error_message.empty()); + std::filesystem::remove_all(directory); +} diff --git a/tests/smoke.cpp b/tests/smoke.cpp deleted file mode 100644 index 96fc034..0000000 --- a/tests/smoke.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include - -#include -#include -#include - -int main() { - std::string error; - - assert(izo::set_environment_variable("IZO_SMOKE_TEST", "utf8-value", &error)); - const auto variable = izo::get_environment_variable("IZO_SMOKE_TEST"); - assert(variable && *variable == "utf8-value"); - assert(izo::unset_environment_variable("IZO_SMOKE_TEST", &error)); - - assert(!izo::get_known_path(izo::known_path::temporary, &error).empty()); - assert(!izo::get_known_path(izo::known_path::executable_directory, &error).empty()); - - const auto system = izo::get_system_info(); - assert(system.logical_cpu_count > 0); - assert(system.total_memory_bytes > 0); - assert(!system.os_name.empty()); - - const auto before = izo::monotonic_now(); - izo::sleep_for(std::chrono::milliseconds(2)); - assert(izo::monotonic_now() > before); - -#ifdef _WIN32 - auto library = izo::load_dynamic_library("kernel32.dll", &error); - assert(library && library.symbol("GetCurrentProcessId", &error)); -#else - auto library = izo::load_dynamic_library("libc.so.6", &error); - assert(library && library.symbol("getpid", &error)); -#endif - - const auto directory = std::filesystem::temp_directory_path() / "izo-smoke-watch"; - std::filesystem::remove_all(directory); - std::filesystem::create_directory(directory); - std::atomic observed{false}; - auto watcher = izo::watch_directory( - {directory, false, std::chrono::milliseconds(10)}, - [&](const izo::file_event& event) { - if (event.change == izo::file_change::added && event.path.filename() == "created.txt") - observed = true; - }, &error); - assert(watcher); - izo::sleep_for(std::chrono::milliseconds(30)); - { std::ofstream file(directory / "created.txt"); file << "test"; } - for (int i = 0; i < 100 && !observed; ++i) izo::sleep_for(std::chrono::milliseconds(10)); - assert(observed); - watcher.stop(); - std::filesystem::remove_all(directory); -} diff --git a/tests/system_time_debug.cpp b/tests/system_time_debug.cpp new file mode 100644 index 0000000..62f4939 --- /dev/null +++ b/tests/system_time_debug.cpp @@ -0,0 +1,23 @@ +#include "test_support.hpp" + +#include +#include +#include + +int main() { + const auto info = izo::get_system_info(); + CHECK(info.logical_cpu_count > 0); + CHECK(info.total_memory_bytes > 0); + CHECK(info.available_memory_bytes <= info.total_memory_bytes); + CHECK(info.os_name == "Linux"); + CHECK(!info.os_version.empty()); + + const auto before = izo::monotonic_now(); + izo::sleep_for(std::chrono::milliseconds(5)); + CHECK(izo::monotonic_now() > before); + izo::sleep_for(std::chrono::nanoseconds(-1)); + + (void)izo::is_debugger_attached(); + izo::debug_output("iZo debug output test\n"); + izo::install_crash_handler(nullptr); +} diff --git a/tests/test_support.hpp b/tests/test_support.hpp new file mode 100644 index 0000000..3847fb8 --- /dev/null +++ b/tests/test_support.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define CHECK(expression) \ + do { \ + if (!(expression)) { \ + std::cerr << __FILE__ << ':' << __LINE__ << ": check failed: " #expression << '\n'; \ + return 1; \ + } \ + } while (false) + +inline std::filesystem::path test_directory(const char* name) { + auto path = std::filesystem::temp_directory_path() / + (std::string("izo-") + name + '-' + + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count())); + std::filesystem::create_directories(path); + return path; +} + +template +bool wait_until(Predicate predicate, std::chrono::milliseconds timeout = std::chrono::seconds(3)) { + const auto deadline = std::chrono::steady_clock::now() + timeout; + while (!predicate() && std::chrono::steady_clock::now() < deadline) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + return predicate(); +}