#define CATCH_CONFIG_RUNNER #include "util/SafeSignal.hpp" #include #include #include #include #include "GlibTestsFixture.hpp" using namespace waybar; template using remove_cvref_t = typename std::remove_cv::type>::type; /** * 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 setTimeout(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); } template struct TestObject { T value; unsigned copied = 0; unsigned moved = 0; TestObject(const T& v) : value(v){}; ~TestObject() = default; TestObject(const TestObject& other) : value(other.value), copied(other.copied + 1), moved(other.moved) {} TestObject(TestObject&& other) noexcept : value(std::move(other.value)), copied(std::exchange(other.copied, 0)), moved(std::exchange(other.moved, 0) + 1) {} TestObject& operator=(const TestObject& other) { value = other.value; copied = other.copied + 1; moved = other.moved; return *this; } TestObject& operator=(TestObject&& other) noexcept { value = std::move(other.value); copied = std::exchange(other.copied, 0); moved = std::exchange(other.moved, 0) + 1; return *this; } bool operator==(T other) const { return value == other; } operator T() const { return value; } }; /* * Check the number of copies/moves performed on the object passed through SafeSignal */ TEST_CASE_METHOD(GlibTestsFixture, "SafeSignal copy/move counter", "[signal][thread][util]") { const int NUM_EVENTS = 3; int count = 0; SafeSignal> test_signal; std::thread producer; // timeout the test in 500ms setTimeout(500); test_signal.connect([&](auto& val) { static_assert(std::is_same, remove_cvref_t>::value); /* explicit move in the producer thread */ REQUIRE(val.moved <= 1); /* copy within the SafeSignal queuing code */ REQUIRE(val.copied <= 1); if (++count >= NUM_EVENTS) { this->quit(); }; }); run([&]() { test_signal.emit(1); REQUIRE(count == 1); producer = std::thread([&]() { for (auto i = 2; i <= NUM_EVENTS; ++i) { TestObject t{i}; // check that signal.emit accepts moved objects test_signal.emit(std::move(t)); } }); }); producer.join(); REQUIRE(count == NUM_EVENTS); } int main(int argc, char* argv[]) { Glib::init(); return Catch::Session().run(argc, argv); }