test(linux): cover platform utility APIs

This commit is contained in:
2026-06-18 19:32:27 -05:00
parent 1b7f813094
commit 1a73875e01
9 changed files with 273 additions and 55 deletions

View File

@@ -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()

View File

@@ -0,0 +1,71 @@
#include "test_support.hpp"
#include <izo/directory_watcher.hpp>
#include <algorithm>
#include <fstream>
#include <mutex>
#include <vector>
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<izo::file_event> events;
auto watcher = izo::watch_directory(
{directory, true, std::chrono::milliseconds(10)},
[&](const izo::file_event& event) {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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);
}

29
tests/dynamic_library.cpp Normal file
View File

@@ -0,0 +1,29 @@
#include "test_support.hpp"
#include <izo/dynamic_library.hpp>
#include <utility>
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());
}

View File

@@ -0,0 +1,39 @@
#include "test_support.hpp"
#include <izo/environment.hpp>
#include <izo/paths.hpp>
#include <array>
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);
}

View File

@@ -0,0 +1,36 @@
#include "test_support.hpp"
#include <izo/clipboard.hpp>
#include <izo/dialogs.hpp>
#include <izo/environment.hpp>
#include <izo/message_box.hpp>
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());
}

29
tests/process.cpp Normal file
View File

@@ -0,0 +1,29 @@
#include "test_support.hpp"
#include <izo/process.hpp>
#include <fstream>
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);
}

View File

@@ -1,52 +0,0 @@
#include <izo/izo.hpp>
#include <atomic>
#include <cassert>
#include <fstream>
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<bool> 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);
}

View File

@@ -0,0 +1,23 @@
#include "test_support.hpp"
#include <izo/debug.hpp>
#include <izo/system.hpp>
#include <izo/time.hpp>
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);
}

31
tests/test_support.hpp Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <chrono>
#include <filesystem>
#include <iostream>
#include <string>
#include <thread>
#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 <typename Predicate>
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();
}