From a3a470a0cde4b34b3b81c93450630bb5395a8764 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Wed, 25 Jun 2025 20:26:56 -0700 Subject: [PATCH] signals: add a lot of tests --- tests/signal.cpp | 209 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) diff --git a/tests/signal.cpp b/tests/signal.cpp index 586a692..79d4bab 100644 --- a/tests/signal.cpp +++ b/tests/signal.cpp @@ -1,11 +1,18 @@ #include #include #include +#include +#include "hyprutils/memory/SharedPtr.hpp" +#include "hyprutils/signal/Listener.hpp" #include "shared.hpp" using namespace Hyprutils::Signal; using namespace Hyprutils::Memory; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +// + void legacy(int& ret) { CSignal signal; int data = 0; @@ -33,6 +40,27 @@ void legacyListenerEmit(int& ret) { EXPECT(data, 1); } +void legacyListeners(int& ret) { + int data = 0; + + CSignalT<> signal0; + CSignalT signal1; + + auto listener0 = signal0.registerListener([&](std::any d) { data += 1; }); + auto listener1 = signal1.registerListener([&](std::any d) { data += std::any_cast(d); }); + + signal0.registerStaticListener([&](void* o, std::any d) { data += 10; }, nullptr); + signal1.registerStaticListener([&](void* o, std::any d) { data += std::any_cast(d) * 10; }, nullptr); + + signal0.emit(); + signal1.emit(2); + + EXPECT(data, 33); +} + +#pragma GCC diagnostic pop +// + void empty(int& ret) { int data = 0; @@ -76,6 +104,144 @@ void typedMany(int& ret) { EXPECT(data3, 3); } +void ref(int& ret) { + int count = 0; + int data = 0; + + CSignalT signal; + auto l1 = signal.listen([&](int& v) { v += 1; }); + auto l2 = signal.listen([&](int v) { count += v; }); + signal.emit(data); + + CSignalT constSignal; + auto l3 = constSignal.listen([&](const int& v) { count += v; }); + auto l4 = constSignal.listen([&](int v) { count += v; }); + constSignal.emit(data); + + EXPECT(data, 1); + EXPECT(count, 3); +} + +void refMany(int& ret) { + int count = 0; + int data1 = 0; + int data2 = 10; + + CSignalT signal; + auto l1 = signal.listen([&](int& v, const int&) { v += 1; }); + auto l2 = signal.listen([&](int v1, int v2) { count += v1 + v2; }); + + signal.emit(data1, data2); + EXPECT(data1, 1); + EXPECT(count, 11); +} + +void listenerAdded(int& ret) { + int count = 0; + + CSignalT<> signal; + CHyprSignalListener secondListener; + + auto listener = signal.listen([&] { + count += 1; + + if (!secondListener) + secondListener = signal.listen([&] { count += 1; }); + }); + + signal.emit(); + EXPECT(count, 1); // second should NOT be invoked as it was registed during emit + + signal.emit(); + EXPECT(count, 3); // second should be invoked +} + +void lastListenerSwapped(int& ret) { + int count = 0; + + CSignalT<> signal; + CHyprSignalListener removedListener; + CHyprSignalListener addedListener; + + auto firstListener = signal.listen([&] { + removedListener.reset(); // dropped and should NOT be invoked + + if (!addedListener) + addedListener = signal.listen([&] { count += 2; }); + }); + + removedListener = signal.listen([&] { count += 1; }); + + signal.emit(); + EXPECT(count, 0); // neither the removed nor added listeners should fire + + signal.emit(); + EXPECT(count, 2); // only the new listener should fire +} + +void signalDestroyed(int& ret) { + int count = 0; + + auto signal = std::make_unique>(); + + // This ensures a destructor of a listener called before signal reset is safe. + auto preListener = signal->listen([&] { count += 1; }); + + auto listener = signal->listen([&] { signal.reset(); }); + + // This ensures a destructor of a listener called after signal reset is safe + // and gets called. + auto postListener = signal->listen([&] { count += 1; }); + + signal->emit(); + EXPECT(count, 2); // all listeners should fire regardless of signal deletion +} + +// purely an asan test +void signalDestroyedBeforeListener() { + CHyprSignalListener listener1; + CHyprSignalListener listener2; + + CSignalT<> signal; + + listener1 = signal.listen([] {}); + listener2 = signal.listen([] {}); +} + +void signalDestroyedWithAddedListener(int& ret) { + int count = 0; + + auto signal = std::make_unique>(); + CHyprSignalListener shouldNotRun; + + auto listener = signal->listen([&] { + shouldNotRun = signal->listen([&] { count += 2; }); + signal.reset(); + }); + + signal->emit(); + EXPECT(count, 0); +} + +void signalDestroyedWithRemovedAndAddedListener(int& ret) { + int count = 0; + + auto signal = std::make_unique>(); + CHyprSignalListener removed; + CHyprSignalListener shouldNotRun; + + auto listener = signal->listen([&] { + removed.reset(); + shouldNotRun = signal->listen([&] { count += 2; }); + signal.reset(); + }); + + removed = signal->listen([&] { count += 1; }); + + signal->emit(); + EXPECT(count, 0); +} + void staticListener(int& ret) { int data = 0; @@ -86,13 +252,56 @@ void staticListener(int& ret) { EXPECT(data, 1); } +void staticListenerDestroy(int& ret) { + int count = 0; + + auto signal = makeShared>(); + signal->listenStatic([&] { count += 1; }); + + signal->listenStatic([&] { + // should not fire but SHOULD be freed + signal->listenStatic([&] { count += 3; }); + + signal.reset(); + }); + + signal->listenStatic([&] { count += 1; }); + + signal->emit(); + EXPECT(count, 2); +} + +// purely an asan test +void listenerDestroysSelf() { + CSignalT<> signal; + + CHyprSignalListener listener; + listener = signal.listen([&] { listener.reset(); }); + + // the static signal case is taken care of above + + signal.emit(); +} + int main(int argc, char** argv, char** envp) { int ret = 0; legacy(ret); legacyListenerEmit(ret); + legacyListeners(ret); empty(ret); typed(ret); typedMany(ret); + ref(ret); + refMany(ret); + listenerAdded(ret); + lastListenerSwapped(ret); + signalDestroyed(ret); + signalDestroyedBeforeListener(); + signalDestroyedWithAddedListener(ret); + signalDestroyedWithRemovedAndAddedListener(ret); staticListener(ret); + staticListenerDestroy(ret); + signalDestroyed(ret); + listenerDestroysSelf(); return ret; }