diff --git a/test/GlibTestsFixture.hpp b/test/GlibTestsFixture.hpp new file mode 100644 index 00000000..ffe6fd31 --- /dev/null +++ b/test/GlibTestsFixture.hpp @@ -0,0 +1,19 @@ +#pragma once +#include +/** + * Minimal Glib application to be used for tests that require Glib main loop + */ +class GlibTestsFixture : public sigc::trackable { + public: + GlibTestsFixture() : main_loop_{Glib::MainLoop::create()} {} + + void run(std::function fn) { + Glib::signal_idle().connect_once(fn); + main_loop_->run(); + } + + void quit() { main_loop_->quit(); } + + protected: + Glib::RefPtr main_loop_; +}; diff --git a/test/SafeSignal.cpp b/test/SafeSignal.cpp new file mode 100644 index 00000000..b07e9cab --- /dev/null +++ b/test/SafeSignal.cpp @@ -0,0 +1,63 @@ +#define CATCH_CONFIG_RUNNER +#include "util/SafeSignal.hpp" + +#include + +#include +#include + +#include "GlibTestsFixture.hpp" + +using namespace waybar; +/** + * Basic sanity test for SafeSignal: + * check that type deduction works, events are delivered and the order is right + * Running this with -fsanitize=thread should not fail + */ +TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal basic functionality", "[signal][thread][util]") { + const int NUM_EVENTS = 100; + int count = 0; + int last_value = 0; + + SafeSignal test_signal; + + const auto main_tid = std::this_thread::get_id(); + std::thread producer; + + // timeout the test in 500ms + Glib::signal_timeout().connect_once([]() { throw std::runtime_error("Test timed out"); }, 500); + + test_signal.connect([&](auto val, auto str) { + static_assert(std::is_same::value); + static_assert(std::is_same::value); + // check that we're in the same thread as the main loop + REQUIRE(std::this_thread::get_id() == main_tid); + // check event order + REQUIRE(val == last_value + 1); + + last_value = val; + if (++count >= NUM_EVENTS) { + this->quit(); + }; + }); + + run([&]() { + // check that events from the same thread are delivered and processed synchronously + test_signal.emit(1, "test"); + REQUIRE(count == 1); + + // start another thread and generate events + producer = std::thread([&]() { + for (auto i = 2; i <= NUM_EVENTS; ++i) { + test_signal.emit(i, "test"); + } + }); + }); + producer.join(); + REQUIRE(count == NUM_EVENTS); +} + +int main(int argc, char* argv[]) { + Glib::init(); + return Catch::Session().run(argc, argv); +} diff --git a/test/meson.build b/test/meson.build index 85b9771f..bbef21e7 100644 --- a/test/meson.build +++ b/test/meson.build @@ -2,6 +2,7 @@ test_inc = include_directories('../include') test_dep = [ catch2, fmt, + gtkmm, jsoncpp, spdlog, ] @@ -14,8 +15,21 @@ config_test = executable( include_directories: test_inc, ) +safesignal_test = executable( + 'safesignal_test', + 'SafeSignal.cpp', + dependencies: test_dep, + include_directories: test_inc, +) + test( 'Configuration test', config_test, workdir: meson.source_root(), ) + +test( + 'SafeSignal test', + safesignal_test, + workdir: meson.source_root(), +)