diff --git a/.github/workflows/arch.yml b/.github/workflows/arch.yml index 80556e4..d505bc7 100644 --- a/.github/workflows/arch.yml +++ b/.github/workflows/arch.yml @@ -17,7 +17,7 @@ jobs: run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman + pacman --noconfirm --noprogressbar -Sy gcc gtest base-devel cmake clang libc++ pixman - name: Build hyprutils with gcc run: | @@ -44,7 +44,7 @@ jobs: run: | sed -i 's/SigLevel = Required DatabaseOptional/SigLevel = Optional TrustAll/' /etc/pacman.conf pacman --noconfirm --noprogressbar -Syyu - pacman --noconfirm --noprogressbar -Sy gcc base-devel cmake clang libc++ pixman + pacman --noconfirm --noprogressbar -Sy gcc gtest base-devel cmake clang libc++ pixman - name: Build hyprutils with clang run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f6b620..5f21be7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,81 +53,22 @@ set_target_properties(hyprutils PROPERTIES VERSION ${hyprutils_VERSION} SOVERSION 9) target_link_libraries(hyprutils PkgConfig::deps) -# tests -add_custom_target(tests) - -add_executable(hyprutils_memory "tests/memory.cpp") -target_link_libraries(hyprutils_memory PRIVATE hyprutils PkgConfig::deps) -add_test( - NAME "Memory" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - COMMAND hyprutils_memory "memory") -add_dependencies(tests hyprutils_memory) - -add_executable(hyprutils_string "tests/string.cpp") -target_link_libraries(hyprutils_string PRIVATE hyprutils PkgConfig::deps) -add_test( - NAME "String" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - COMMAND hyprutils_string "string") -add_dependencies(tests hyprutils_string) - -add_executable(hyprutils_signal "tests/signal.cpp") -target_link_libraries(hyprutils_signal PRIVATE hyprutils PkgConfig::deps) -add_test( - NAME "Signal" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - COMMAND hyprutils_signal "signal") -add_dependencies(tests hyprutils_signal) - -add_executable(hyprutils_math "tests/math.cpp") -target_link_libraries(hyprutils_math PRIVATE hyprutils PkgConfig::deps) -add_test( - NAME "Math" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - COMMAND hyprutils_math "math") -add_dependencies(tests hyprutils_math) - -add_executable(hyprutils_os "tests/os.cpp") -target_link_libraries(hyprutils_os PRIVATE hyprutils PkgConfig::deps) -add_test( - NAME "OS" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - COMMAND hyprutils_os "os") -add_dependencies(tests hyprutils_os) - -add_executable(hyprutils_filedescriptor "tests/filedescriptor.cpp") -target_link_libraries(hyprutils_filedescriptor PRIVATE hyprutils PkgConfig::deps) -add_test( - NAME "Filedescriptor" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - COMMAND hyprutils_filedescriptor "filedescriptor") -add_dependencies(tests hyprutils_filedescriptor) - -add_executable(hyprutils_animation "tests/animation.cpp") -target_link_libraries(hyprutils_animation PRIVATE hyprutils PkgConfig::deps) -add_test( - NAME "Animation" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - COMMAND hyprutils_animation "utils") -add_dependencies(tests hyprutils_animation) - -add_executable(hyprutils_beziercurve "tests/beziercurve.cpp") -target_link_libraries(hyprutils_beziercurve PRIVATE hyprutils PkgConfig::deps) -add_test( - NAME "BezierCurve" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - COMMAND hyprutils_beziercurve "beziercurve") -add_dependencies(tests hyprutils_beziercurve) - -add_executable(hyprutils_i18n "tests/i18n.cpp") -target_link_libraries(hyprutils_i18n PRIVATE hyprutils PkgConfig::deps) -add_test( - NAME "I18n" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests - COMMAND hyprutils_i18n "i18n") -add_dependencies(tests hyprutils_i18n) - +if(BUILD_TESTING) + # GTest + find_package(GTest CONFIG REQUIRED) + include(GoogleTest) + add_executable(hyprutils_inline_tests ${SRCFILES}) + target_compile_definitions(hyprutils_inline_tests PRIVATE HU_UNIT_TESTS=1) + target_compile_options(hyprutils_inline_tests PRIVATE --coverage) + target_link_options(hyprutils_inline_tests PRIVATE --coverage) + target_include_directories( + hyprutils_inline_tests + PUBLIC "./include" + PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}") + target_link_libraries(hyprutils_inline_tests PRIVATE GTest::gtest_main + PkgConfig::deps) + gtest_discover_tests(hyprutils_inline_tests) +endif() # Installation install(TARGETS hyprutils) diff --git a/nix/default.nix b/nix/default.nix index 0db0819..093cf00 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -4,6 +4,7 @@ stdenvAdapters, cmake, pkg-config, + gtest, pixman, version ? "git", doCheck ? false, @@ -11,10 +12,12 @@ # whether to use the mold linker # disable this for older machines without SSE4_2 and AVX2 support withMold ? true, -}: let +}: +let inherit (builtins) foldl'; - inherit (lib.lists) flatten; - inherit (lib.strings) optionalString; + inherit (lib.attrsets) mapAttrsToList; + inherit (lib.lists) flatten optional; + inherit (lib.strings) cmakeBool optionalString; adapters = flatten [ (lib.optional withMold stdenvAdapters.useMoldLinker) @@ -23,31 +26,36 @@ customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters; in - customStdenv.mkDerivation { - pname = "hyprutils" + optionalString debug "-debug"; - inherit version doCheck; - src = ../.; +customStdenv.mkDerivation { + pname = "hyprutils" + optionalString debug "-debug"; + inherit version doCheck; + src = ../.; - nativeBuildInputs = [ - cmake - pkg-config - ]; + nativeBuildInputs = [ + cmake + pkg-config + ]; - buildInputs = [ - pixman - ]; + buildInputs = flatten [ + (optional doCheck gtest) + pixman + ]; - outputs = ["out" "dev"]; + outputs = [ + "out" + "dev" + ]; - cmakeBuildType = - if debug - then "Debug" - else "RelWithDebInfo"; + cmakeBuildType = if debug then "Debug" else "RelWithDebInfo"; - meta = with lib; { - homepage = "https://github.com/hyprwm/hyprutils"; - description = "Small C++ library for utilities used across the Hypr* ecosystem"; - license = licenses.bsd3; - platforms = platforms.linux; - }; - } + cmakeFlags = mapAttrsToList cmakeBool { + "DISABLE_TESTING" = !doCheck; + }; + + meta = with lib; { + homepage = "https://github.com/hyprwm/hyprutils"; + description = "Small C++ library for utilities used across the Hypr* ecosystem"; + license = licenses.bsd3; + platforms = platforms.linux; + }; +} diff --git a/src/animation/AnimationManager.cpp b/src/animation/AnimationManager.cpp index 446b2d9..6ab72ae 100644 --- a/src/animation/AnimationManager.cpp +++ b/src/animation/AnimationManager.cpp @@ -99,3 +99,404 @@ const std::unordered_map>& CAnimationManager::getA CWeakPointer CAnimationManager::getSignals() const { return m_events; } + +#ifdef HU_UNIT_TESTS + +#include + +#include +#include +#include +#include +#include + +#define SP CSharedPointer +#define WP CWeakPointer +#define UP CUniquePointer + +using namespace Hyprutils::Animation; +using namespace Hyprutils::Math; +using namespace Hyprutils::Memory; + +class EmtpyContext {}; + +template +using CAnimatedVariable = CGenericAnimatedVariable; + +template +using PANIMVAR = SP>; + +template +using PANIMVARREF = WP>; + +enum eAVTypes { + INT = 1, + TEST, +}; + +struct SomeTestType { + bool done = false; + bool operator==(const SomeTestType& other) const { + return done == other.done; + } + SomeTestType& operator=(const SomeTestType& other) { + done = other.done; + return *this; + } +}; + +CAnimationConfigTree animationTree; + +class CMyAnimationManager : public CAnimationManager { + public: + void tick() { + for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) { + const auto PAV = m_vActiveAnimatedVariables[i].lock(); + if (!PAV || !PAV->ok() || !PAV->isBeingAnimated()) + continue; + + const auto SPENT = PAV->getPercent(); + const auto PBEZIER = getBezier(PAV->getBezierName()); + + if (SPENT >= 1.f || !PAV->enabled()) { + PAV->warp(true, false); + continue; + } + + const auto POINTY = PBEZIER->getYForPoint(SPENT); + + switch (PAV->m_Type) { + case eAVTypes::INT: { + auto avInt = dc*>(PAV.get()); + if (!avInt) + std::cout << "Dynamic cast upcast failed\n"; + + const auto DELTA = avInt->goal() - avInt->value(); + avInt->value() = avInt->begun() + (DELTA * POINTY); + } break; + case eAVTypes::TEST: { + auto avCustom = dc*>(PAV.get()); + if (!avCustom) + std::cout << "Dynamic cast upcast failed\n"; + + if (SPENT >= 1.f) + avCustom->value().done = true; + } break; + default: { + std::cout << "What are we even doing?\n"; + } break; + } + + PAV->onUpdate(); + } + + tickDone(); + } + + template + void createAnimation(const VarType& v, PANIMVAR& av, const std::string& animationConfigName) { + constexpr const eAVTypes EAVTYPE = std::is_same_v ? eAVTypes::INT : eAVTypes::TEST; + const auto PAV = makeShared>(); + + PAV->create(EAVTYPE, sc(this), PAV, v); + PAV->setConfig(animationTree.getConfig(animationConfigName)); + av = std::move(PAV); + } + + virtual void scheduleTick() { + ; + } + + virtual void onTicked() { + ; + } +}; + +UP pAnimationManager; + +class Subject { + public: + Subject(const int& a, const int& b) { + pAnimationManager->createAnimation(a, m_iA, "default"); + pAnimationManager->createAnimation(b, m_iB, "internal"); + pAnimationManager->createAnimation({}, m_iC, "default"); + } + PANIMVAR m_iA; + PANIMVAR m_iB; + PANIMVAR m_iC; +}; + +static int config() { + pAnimationManager = makeUnique(); + + int ret = 0; + + animationTree.createNode("global"); + animationTree.createNode("internal"); + + animationTree.createNode("foo", "internal"); + animationTree.createNode("default", "global"); + animationTree.createNode("bar", "default"); + + /* + internal + ↳ foo + global + ↳ default + ↳ bar + */ + + auto barCfg = animationTree.getConfig("bar"); + auto internalCfg = animationTree.getConfig("internal"); + + // internal is a root node and should point to itself + EXPECT_EQ(internalCfg->pParentAnimation.get(), internalCfg.get()); + EXPECT_EQ(internalCfg->pValues.get(), internalCfg.get()); + + animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf"); + + EXPECT_EQ(barCfg->internalEnabled, -1); + { + const auto PVALUES = barCfg->pValues.lock(); + EXPECT_EQ(PVALUES->internalEnabled, 1); + EXPECT_EQ(PVALUES->internalBezier, "default"); + EXPECT_EQ(PVALUES->internalStyle, "asdf"); + EXPECT_EQ(PVALUES->internalSpeed, 4.0); + } + EXPECT_EQ(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get()); + + // Overwrite our own values + animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer"); + + { + const auto PVALUES = barCfg->pValues.lock(); + EXPECT_EQ(PVALUES->internalEnabled, 1); + EXPECT_EQ(PVALUES->internalBezier, "test"); + EXPECT_EQ(PVALUES->internalStyle, "qwer"); + EXPECT_EQ(PVALUES->internalSpeed, 4.2f); + } + + // Now overwrite the parent + animationTree.setConfigForNode("default", 0, 0.0, "zxcv", "foo"); + + { + // Expecting no change + const auto PVALUES = barCfg->pValues.lock(); + EXPECT_EQ(PVALUES->internalEnabled, 1); + EXPECT_EQ(PVALUES->internalBezier, "test"); + EXPECT_EQ(PVALUES->internalStyle, "qwer"); + EXPECT_EQ(PVALUES->internalSpeed, 4.2f); + } + + return ret; +} + +TEST(Animation, animation) { + config(); + + animationTree.createNode("global"); + animationTree.createNode("internal"); + + animationTree.createNode("default", "global"); + animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf"); + + Subject s(0, 0); + + EXPECT_EQ(s.m_iA->value(), 0); + EXPECT_EQ(s.m_iB->value(), 0); + + // Test destruction of a CAnimatedVariable + { + Subject s2(10, 10); + // Adds them to active + *s2.m_iA = 1; + *s2.m_iB = 2; + // We deliberately do not tick here, to make sure the destructor removes active animated variables + } + + EXPECT_EQ(pAnimationManager->shouldTickForNext(), false); + EXPECT_EQ(s.m_iC->value().done, false); + + *s.m_iA = 10; + *s.m_iB = 100; + *s.m_iC = SomeTestType(true); + + EXPECT_EQ(s.m_iC->value().done, false); + + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT_EQ(s.m_iA->value(), 10); + EXPECT_EQ(s.m_iB->value(), 100); + EXPECT_EQ(s.m_iC->value().done, true); + + s.m_iA->setValue(0); + s.m_iB->setValue(0); + + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT_EQ(s.m_iA->value(), 10); + EXPECT_EQ(s.m_iB->value(), 100); + + // Test config stuff + EXPECT_EQ(s.m_iA->getBezierName(), "default"); + EXPECT_EQ(s.m_iA->getStyle(), "asdf"); + EXPECT_EQ(s.m_iA->enabled(), true); + + animationTree.getConfig("global")->internalEnabled = 0; + + EXPECT_EQ(s.m_iA->enabled(), false); + + *s.m_iA = 50; + pAnimationManager->tick(); // Expecting a warp + EXPECT_EQ(s.m_iA->value(), 50); + + // Test missing pValues + animationTree.getConfig("global")->internalEnabled = 0; + animationTree.getConfig("default")->pValues.reset(); + + EXPECT_EQ(s.m_iA->enabled(), false); + EXPECT_EQ(s.m_iA->getBezierName(), "default"); + EXPECT_EQ(s.m_iA->getStyle(), ""); + EXPECT_EQ(s.m_iA->getPercent(), 1.f); + + // Reset + animationTree.setConfigForNode("default", 1, 1, "default"); + + // + // Test callbacks + // + int beginCallbackRan = 0; + int updateCallbackRan = 0; + int endCallbackRan = 0; + s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP pav) { beginCallbackRan++; }); + s.m_iA->setUpdateCallback([&updateCallbackRan](WP pav) { updateCallbackRan++; }); + s.m_iA->setCallbackOnEnd([&endCallbackRan](WP pav) { endCallbackRan++; }, false); + + s.m_iA->setValueAndWarp(42); + + EXPECT_EQ(beginCallbackRan, 0); + EXPECT_EQ(updateCallbackRan, 1); + EXPECT_EQ(endCallbackRan, 2); // first called when setting the callback, then when warping. + + *s.m_iA = 1337; + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT_EQ(beginCallbackRan, 1); + EXPECT_EQ(updateCallbackRan > 2, true); + EXPECT_EQ(endCallbackRan, 3); + + std::vector> vars; + for (int i = 0; i < 10; i++) { + vars.resize(vars.size() + 1); + pAnimationManager->createAnimation(1, vars.back(), "default"); + *vars.back() = 1337; + } + + // test adding / removing vars during a tick + s.m_iA->resetAllCallbacks(); + s.m_iA->setUpdateCallback([&vars](WP v) { + if (v.lock() != vars.back()) + vars.back()->warp(); + }); + s.m_iA->setCallbackOnEnd([&s, &vars](auto) { + vars.resize(vars.size() + 1); + pAnimationManager->createAnimation(1, vars.back(), "default"); + *vars.back() = 1337; + }); + + *s.m_iA = 1000000; + + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT_EQ(s.m_iA->value(), 1000000); + // all vars should be set to 1337 + EXPECT_EQ(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true); + + // test one-time callbacks + s.m_iA->resetAllCallbacks(); + s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true); + + EXPECT_EQ(endCallbackRan, 4); + + s.m_iA->setValueAndWarp(10); + + EXPECT_EQ(endCallbackRan, 4); + EXPECT_EQ(s.m_iA->value(), 10); + + // test warp + *s.m_iA = 3; + s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false); + + s.m_iA->warp(false); + EXPECT_EQ(endCallbackRan, 4); + + *s.m_iA = 4; + s.m_iA->warp(true); + EXPECT_EQ(endCallbackRan, 5); + + // test getCurveValue + *s.m_iA = 0; + EXPECT_EQ(s.m_iA->getCurveValue(), 0.f); + s.m_iA->warp(); + EXPECT_EQ(s.m_iA->getCurveValue(), 1.f); + EXPECT_EQ(endCallbackRan, 6); + + // test end callback readding the var + *s.m_iA = 5; + s.m_iA->setCallbackOnEnd([&endCallbackRan](WP v) { + endCallbackRan++; + const auto PAV = dc*>(v.lock().get()); + + *PAV = 10; + PAV->setCallbackOnEnd([&endCallbackRan](WP v) { endCallbackRan++; }); + }); + + while (pAnimationManager->shouldTickForNext()) { + pAnimationManager->tick(); + } + + EXPECT_EQ(endCallbackRan, 8); + EXPECT_EQ(s.m_iA->value(), 10); + + // Test duplicate active anim vars are not allowed + { + EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); + PANIMVAR a; + pAnimationManager->createAnimation(1, a, "default"); + EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); + *a = 10; + EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); + *a = 20; + EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); + a->warp(); + EXPECT_EQ(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); + EXPECT_EQ(a->value(), 20); + } + + // Test no crash when animation manager gets destroyed + { + PANIMVAR a; + pAnimationManager->createAnimation(1, a, "default"); + *a = 10; + pAnimationManager.reset(); + EXPECT_EQ(a->isAnimationManagerDead(), true); + a->setValueAndWarp(11); + EXPECT_EQ(a->value(), 11); + *a = 12; + a->warp(); + EXPECT_EQ(a->value(), 12); + *a = 13; + } // a gets destroyed + + EXPECT_EQ(pAnimationManager.get(), nullptr); +} + +#endif diff --git a/src/animation/BezierCurve.cpp b/src/animation/BezierCurve.cpp index 463f0a3..d54d9f5 100644 --- a/src/animation/BezierCurve.cpp +++ b/src/animation/BezierCurve.cpp @@ -105,3 +105,84 @@ float CBezierCurve::getYForPoint(float const& x) const { const std::vector& CBezierCurve::getControlPoints() const { return m_vPoints; } + +#ifdef HU_UNIT_TESTS + +#include + +using Hyprutils::Animation::CBezierCurve; +using Hyprutils::Math::Vector2D; + +static void test_nonmonotonic4_clamps_out_of_range() { + // Non-monotonic curve in X + // This used to drive the step-halving search to OOB. It should now clamp + CBezierCurve curve; + std::array pts = { + Vector2D{0.5f, 1.0f}, // P0 + Vector2D{1.0f, 1.0f}, // P1 + Vector2D{0.0f, 0.0f}, // P2 + Vector2D{0.5f, 0.0f} // P3 + }; + curve.setup4(pts); + + // x > last baked x + EXPECT_EQ(std::isfinite(curve.getYForPoint(0.6f)), true); + // Far beyond range + EXPECT_EQ(std::isfinite(curve.getYForPoint(std::numeric_limits::max())), true); + EXPECT_EQ(std::isfinite(curve.getYForPoint(-std::numeric_limits::max())), true); +} + +static void test_adjacent_baked_x_equal() { + // Curve with flat tail (X=1, Y=1) + CBezierCurve curve; + std::array pts = { + Vector2D{0.0f, 0.0f}, // P0 + Vector2D{0.2f, 0.2f}, // P1 + Vector2D{1.0f, 1.0f}, // P2 + Vector2D{1.0f, 1.0f} // P3 + }; + curve.setup4(pts); + + // Exactly at last baked X + const float y_at_end = curve.getYForPoint(1.0f); + // Slightly beyond last baked X + const float y_past_end = curve.getYForPoint(1.0001f); + + EXPECT_EQ(y_at_end, 1.0f); + EXPECT_EQ(y_past_end, y_at_end); +} + +static void test_all_baked_x_equal() { + // Extreme case: X is constant along the whole curve + CBezierCurve curve; + std::array pts = { + Vector2D{0.0f, 0.0f}, // P0 + Vector2D{0.0f, 0.3f}, // P1 + Vector2D{0.0f, 0.7f}, // P2 + Vector2D{0.0f, 1.0f} // P3 + }; + curve.setup4(pts); + + // Below any baked X + const float y_lo = curve.getYForPoint(-100.0f); + const float y_0 = curve.getYForPoint(0.0f); + // Above any baked X + const float y_hi = curve.getYForPoint(100.0f); + + EXPECT_EQ(std::isfinite(y_lo), true); + EXPECT_EQ(std::isfinite(y_0), true); + EXPECT_EQ(std::isfinite(y_hi), true); + + // For this curve Y should stay within [0,1] + EXPECT_EQ((y_lo >= 0.0f && y_lo <= 1.0f), true); + EXPECT_EQ((y_0 >= 0.0f && y_0 <= 1.0f), true); + EXPECT_EQ((y_hi >= 0.0f && y_hi <= 1.0f), true); +} + +TEST(Animation, beziercurve) { + test_nonmonotonic4_clamps_out_of_range(); + test_adjacent_baked_x_equal(); + test_all_baked_x_equal(); +} + +#endif diff --git a/src/i18n/I18nEngine.cpp b/src/i18n/I18nEngine.cpp index ba82840..3c6d48e 100644 --- a/src/i18n/I18nEngine.cpp +++ b/src/i18n/I18nEngine.cpp @@ -106,5 +106,69 @@ std::string CI18nEngine::localizeEntry(const std::string& locale, uint64_t key, } CI18nLocale CI18nEngine::getSystemLocale() { - return CI18nLocale(std::locale("").name()); + try { + return CI18nLocale(std::locale("").name()); + } catch (...) { return CI18nLocale("en_US.UTF-8"); } } + +#ifdef HU_UNIT_TESTS + +#include + +enum eTxtKeys : uint64_t { + TXT_KEY_HELLO, + TXT_KEY_I_HAVE_APPLES, + TXT_KEY_FALLBACK, +}; + +TEST(I18n, Engine) { + CI18nEngine engine; + + engine.setFallbackLocale("en_US"); + + engine.registerEntry("en_US", TXT_KEY_HELLO, "Hello World!"); + engine.registerEntry("en_US", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) { + if (std::stoi(m.at("count")) == 1) + return "I have {count} apple."; + else + return "I have {count} apples."; + }); + engine.registerEntry("en_US", TXT_KEY_FALLBACK, "Fallback string!"); + + engine.registerEntry("pl_PL", TXT_KEY_HELLO, "Witaj świecie!"); + engine.registerEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) { + const auto COUNT = std::stoi(m.at("count")); + if (COUNT == 1) + return "Mam {count} jabłko."; + else if (COUNT < 5) + return "Mam {count} jabłka."; + else + return "Mam {count} jabłek."; + }); + + engine.registerEntry("es_XX", TXT_KEY_FALLBACK, "I don't speak spanish"); + engine.registerEntry("es_ES", TXT_KEY_FALLBACK, "I don't speak spanish here either"); + + EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_HELLO, {}), "Hello World!"); + EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_HELLO, {}), "Witaj świecie!"); + EXPECT_EQ(engine.localizeEntry("de_DE", TXT_KEY_HELLO, {}), "Hello World!"); + + EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "I have 1 apple."); + EXPECT_EQ(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples."); + + EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "Mam 1 jabłko."); + EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "Mam 2 jabłka."); + EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek."); + + EXPECT_EQ(engine.localizeEntry("pl", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek."); + + EXPECT_EQ(engine.localizeEntry("pl_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek."); + EXPECT_EQ(engine.localizeEntry("en_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples."); + + EXPECT_EQ(engine.localizeEntry("es_YY", TXT_KEY_FALLBACK, {}), "I don't speak spanish here either"); + EXPECT_EQ(engine.localizeEntry("es_XX", TXT_KEY_FALLBACK, {}), "I don't speak spanish"); + + EXPECT_EQ(engine.localizeEntry("pl_PL", TXT_KEY_FALLBACK, {}), "Fallback string!"); +} + +#endif diff --git a/src/i18n/I18nLocale.cpp b/src/i18n/I18nLocale.cpp index 260b386..2d77a29 100644 --- a/src/i18n/I18nLocale.cpp +++ b/src/i18n/I18nLocale.cpp @@ -42,3 +42,19 @@ std::string CI18nLocale::stem() { std::string CI18nLocale::full() { return m_rawFullLocale; } + +#ifdef HU_UNIT_TESTS + +#include +#include + +TEST(I18n, Locale) { + CI18nEngine engine; + + EXPECT_EQ(extractLocale("pl_PL.UTF-8"), "pl_PL"); + EXPECT_EQ(extractLocale("POSIX"), "en_US"); + EXPECT_EQ(extractLocale("*"), "en_US"); + EXPECT_EQ(extractLocale("LC_CTYPE=pl_PL.UTF-8"), "pl_PL"); +} + +#endif diff --git a/src/math/Box.cpp b/src/math/Box.cpp index 16fadfb..a4c6775 100644 --- a/src/math/Box.cpp +++ b/src/math/Box.cpp @@ -235,3 +235,78 @@ Vector2D Hyprutils::Math::CBox::closestPoint(const Vector2D& vec) const { SBoxExtents Hyprutils::Math::CBox::extentsFrom(const CBox& small) { return {.topLeft = {small.x - x, small.y - y}, .bottomRight = {w - small.w - (small.x - x), h - small.h - (small.y - y)}}; } + +#ifdef HU_UNIT_TESTS + +#include + +TEST(Math, box) { + // Test default constructor and accessors + { + CBox box1; + EXPECT_EQ(box1.x, 0); + EXPECT_EQ(box1.y, 0); + EXPECT_EQ(box1.width, 0); + EXPECT_EQ(box1.height, 0); + + // Test parameterized constructor and accessors + CBox box2(10, 20, 30, 40); + EXPECT_EQ(box2.x, 10); + EXPECT_EQ(box2.y, 20); + EXPECT_EQ(box2.width, 30); + EXPECT_EQ(box2.height, 40); + + // Test setters and getters + box2.translate(Vector2D(5, -5)); + EXPECT_EQ(box2.pos(), Vector2D(15, 15)); + } + + //Test Scaling and Transformation + { + CBox box(10, 10, 20, 30); + + // Test scaling + box.scale(2.0); + EXPECT_EQ(box.size(), Vector2D(40, 60)); + EXPECT_EQ(box.pos(), Vector2D(20, 20)); + + // Test scaling from center + box.scaleFromCenter(0.5); + EXPECT_EQ(box.size(), Vector2D(20, 30)); + EXPECT_EQ(box.pos(), Vector2D(30, 35)); + + // Test transformation + box.transform(HYPRUTILS_TRANSFORM_90, 100, 200); + EXPECT_EQ(box.pos(), Vector2D(135, 30)); + EXPECT_EQ(box.size(), Vector2D(30, 20)); + + // Test Intersection and Extents + } + + { + CBox box1(0, 0, 100, 100); + CBox box2(50, 50, 100, 100); + + CBox intersection = box1.intersection(box2); + EXPECT_EQ(intersection.pos(), Vector2D(50, 50)); + EXPECT_EQ(intersection.size(), Vector2D(50, 50)); + + SBoxExtents extents = box1.extentsFrom(box2); + EXPECT_EQ(extents.topLeft, Vector2D(50, 50)); + EXPECT_EQ(extents.bottomRight, Vector2D(-50, -50)); + } + + // Test Boundary Conditions and Special Cases + { + CBox box(0, 0, 50, 50); + + EXPECT_EQ(box.empty(), false); + + EXPECT_EQ(box.containsPoint(Vector2D(25, 25)), true); + EXPECT_EQ(box.containsPoint(Vector2D(60, 60)), false); + EXPECT_EQ(box.overlaps(CBox(25, 25, 50, 50)), true); + EXPECT_EQ(box.inside(CBox(0, 0, 100, 100)), false); + } +} + +#endif diff --git a/src/math/Mat3x3.cpp b/src/math/Mat3x3.cpp index b91cf1e..d37a991 100644 --- a/src/math/Mat3x3.cpp +++ b/src/math/Mat3x3.cpp @@ -153,3 +153,26 @@ std::string Mat3x3::toString() const { return std::format("[mat3x3: {}, {}, {}, {}, {}, {}, {}, {}, {}]", matrix.at(0), matrix.at(1), matrix.at(2), matrix.at(3), matrix.at(4), matrix.at(5), matrix.at(6), matrix.at(7), matrix.at(8)); } + +#ifdef HU_UNIT_TESTS + +#include + +TEST(Math, mat3x3) { + Mat3x3 jeremy = Mat3x3::outputProjection({1920, 1080}, HYPRUTILS_TRANSFORM_FLIPPED_90); + Mat3x3 matrixBox = jeremy.projectBox(CBox{10, 10, 200, 200}, HYPRUTILS_TRANSFORM_NORMAL).translate({100, 100}).scale({1.25F, 1.5F}).transpose(); + + Mat3x3 expected = std::array{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1}; + // we need to do this to avoid precision errors on 32-bit archs + EXPECT_EQ(std::abs(expected.getMatrix().at(0) - matrixBox.getMatrix().at(0)) < 0.1, true); + EXPECT_EQ(std::abs(expected.getMatrix().at(1) - matrixBox.getMatrix().at(1)) < 0.1, true); + EXPECT_EQ(std::abs(expected.getMatrix().at(2) - matrixBox.getMatrix().at(2)) < 0.1, true); + EXPECT_EQ(std::abs(expected.getMatrix().at(3) - matrixBox.getMatrix().at(3)) < 0.1, true); + EXPECT_EQ(std::abs(expected.getMatrix().at(4) - matrixBox.getMatrix().at(4)) < 0.1, true); + EXPECT_EQ(std::abs(expected.getMatrix().at(5) - matrixBox.getMatrix().at(5)) < 0.1, true); + EXPECT_EQ(std::abs(expected.getMatrix().at(6) - matrixBox.getMatrix().at(6)) < 0.1, true); + EXPECT_EQ(std::abs(expected.getMatrix().at(7) - matrixBox.getMatrix().at(7)) < 0.1, true); + EXPECT_EQ(std::abs(expected.getMatrix().at(8) - matrixBox.getMatrix().at(8)) < 0.1, true); +} + +#endif diff --git a/src/math/Region.cpp b/src/math/Region.cpp index a75bf32..0d8c969 100644 --- a/src/math/Region.cpp +++ b/src/math/Region.cpp @@ -220,3 +220,22 @@ Vector2D Hyprutils::Math::CRegion::closestPoint(const Vector2D& vec) const { return leader; } + +#ifdef HU_UNIT_TESTS + +#include + +TEST(Math, region) { + CRegion rg(CBox{{20, 20}, {40, 40}}); + + auto extents = rg.getExtents(); + EXPECT_EQ(extents.pos(), Vector2D(20, 20)); + EXPECT_EQ(extents.size(), Vector2D(40, 40)); + + rg.scale(2); + extents = rg.getExtents(); + EXPECT_EQ(extents.pos(), Vector2D(40, 40)); + EXPECT_EQ(extents.size(), Vector2D(80, 80)); +} + +#endif diff --git a/src/math/Vector2D.cpp b/src/math/Vector2D.cpp index b253a54..cb5fdf1 100644 --- a/src/math/Vector2D.cpp +++ b/src/math/Vector2D.cpp @@ -57,3 +57,23 @@ Vector2D Hyprutils::Math::Vector2D::transform(eTransform transform, const Vector default: return *this; } } + +#ifdef HU_UNIT_TESTS + +#include + +TEST(Math, vector2d) { + Vector2D original(30, 40); + Vector2D monitorSize(100, 200); + + EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_NORMAL, monitorSize), Vector2D(30, 40)); + EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_90, monitorSize), Vector2D(40, 200 - 30)); + EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_180, monitorSize), Vector2D(100 - 30, 200 - 40)); + EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_270, monitorSize), Vector2D(100 - 40, 30)); + EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED, monitorSize), Vector2D(100 - 30, 40)); + EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_90, monitorSize), Vector2D(40, 30)); + EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_180, monitorSize), Vector2D(30, 200 - 40)); + EXPECT_EQ(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_270, monitorSize), Vector2D(100 - 40, 200 - 30)); +} + +#endif diff --git a/tests/memory.cpp b/src/memory/Memory.cpp similarity index 69% rename from tests/memory.cpp rename to src/memory/Memory.cpp index fa27c01..567f6f7 100644 --- a/tests/memory.cpp +++ b/src/memory/Memory.cpp @@ -1,15 +1,13 @@ -#include -#include #include #include -#include "shared.hpp" -#include -#include -#include -#include +#include using namespace Hyprutils::Memory; +#ifdef HU_UNIT_TESTS + +#include + #define SP CSharedPointer #define WP CWeakPointer #define UP CUniquePointer @@ -19,9 +17,7 @@ using namespace Hyprutils::Memory; #define NTHREADS 8 #define ITERATIONS 10000 -static int testAtomicImpl() { - int ret = 0; - +static void testAtomicImpl() { { // Using makeShared here could lead to invalid refcounts. ASP shared = makeAtomicShared(0); @@ -45,7 +41,7 @@ static int testAtomicImpl() { // Actual count is not incremented in a thread-safe manner here, so we can't check it. // We just want to check that the concurent refcounting doesn't cause any memory corruption. shared.reset(); - EXPECT(shared, false); + EXPECT_EQ(shared, false); } { @@ -71,15 +67,15 @@ static int testAtomicImpl() { thread.join(); } - EXPECT(shared.strongRef(), 0); - EXPECT(weak.valid(), false); + EXPECT_EQ(shared.strongRef(), 0); + EXPECT_EQ(weak.valid(), false); auto shared2 = weak.lock(); - EXPECT(shared, false); - EXPECT(shared2.get(), nullptr); - EXPECT(shared.strongRef(), 0); - EXPECT(weak.valid(), false); - EXPECT(weak.expired(), true); + EXPECT_EQ(shared, false); + EXPECT_EQ(shared2.get(), nullptr); + EXPECT_EQ(shared.strongRef(), 0); + EXPECT_EQ(weak.valid(), false); + EXPECT_EQ(weak.expired(), true); } { // This tests recursive deletion. When foo will be deleted, bar will be deleted within the foo dtor. @@ -120,38 +116,34 @@ static int testAtomicImpl() { genericNormal = derivedNormal; } - EXPECT(!!genericAtomic, true); - EXPECT(!!genericNormal, true); + EXPECT_EQ(!!genericAtomic, true); + EXPECT_EQ(!!genericNormal, true); } - - return ret; } -int main(int argc, char** argv, char** envp) { +TEST(Memory, memory) { SP intPtr = makeShared(10); SP intPtr2 = makeShared(-1337); UP intUnique = makeUnique(420); - int ret = 0; - - EXPECT(*intPtr, 10); - EXPECT(intPtr.strongRef(), 1); - EXPECT(*intUnique, 420); + EXPECT_EQ(*intPtr, 10); + EXPECT_EQ(intPtr.strongRef(), 1); + EXPECT_EQ(*intUnique, 420); WP weak = intPtr; WP weakUnique = intUnique; - EXPECT(*intPtr, 10); - EXPECT(intPtr.strongRef(), 1); - EXPECT(*weak, 10); - EXPECT(weak.expired(), false); - EXPECT(!!weak.lock(), true); - EXPECT(*weakUnique, 420); - EXPECT(weakUnique.expired(), false); - EXPECT(intUnique.impl_->wref(), 1); + EXPECT_EQ(*intPtr, 10); + EXPECT_EQ(intPtr.strongRef(), 1); + EXPECT_EQ(*weak, 10); + EXPECT_EQ(weak.expired(), false); + EXPECT_EQ(!!weak.lock(), true); + EXPECT_EQ(*weakUnique, 420); + EXPECT_EQ(weakUnique.expired(), false); + EXPECT_EQ(intUnique.impl_->wref(), 1); SP sharedFromUnique = weakUnique.lock(); - EXPECT(sharedFromUnique, nullptr); + EXPECT_EQ(sharedFromUnique, nullptr); std::vector> sps; sps.push_back(intPtr); @@ -163,24 +155,25 @@ int main(int argc, char** argv, char** envp) { intPtr.reset(); intUnique.reset(); - EXPECT(weak.impl_->ref(), 0); - EXPECT(weakUnique.impl_->ref(), 0); - EXPECT(weakUnique.impl_->wref(), 1); - EXPECT(intPtr2.strongRef(), 3); + EXPECT_EQ(weak.impl_->ref(), 0); + EXPECT_EQ(weakUnique.impl_->ref(), 0); + EXPECT_EQ(weakUnique.impl_->wref(), 1); + EXPECT_EQ(intPtr2.strongRef(), 3); - EXPECT(weak.expired(), true); - EXPECT(weakUnique.expired(), true); + EXPECT_EQ(weak.expired(), true); + EXPECT_EQ(weakUnique.expired(), true); auto intPtr2AsUint = reinterpretPointerCast(intPtr2); - EXPECT(intPtr2.strongRef(), 4); - EXPECT(intPtr2AsUint.strongRef(), 4); + EXPECT_EQ(intPtr2.strongRef(), 4); + EXPECT_EQ(intPtr2AsUint.strongRef(), 4); - EXPECT(*intPtr2AsUint > 0, true); - EXPECT(*intPtr2AsUint, (unsigned int)(int)-1337); + EXPECT_EQ(*intPtr2AsUint > 0, true); + EXPECT_EQ(*intPtr2AsUint, (unsigned int)(int)-1337); *intPtr2AsUint = 10; - EXPECT(*intPtr2AsUint, 10); - EXPECT(*intPtr2, 10); + EXPECT_EQ(*intPtr2AsUint, 10); + EXPECT_EQ(*intPtr2, 10); - EXPECT(testAtomicImpl(), 0); - return ret; + testAtomicImpl(); } + +#endif \ No newline at end of file diff --git a/src/os/FileDescriptor.cpp b/src/os/FileDescriptor.cpp index f13a3ee..5327564 100644 --- a/src/os/FileDescriptor.cpp +++ b/src/os/FileDescriptor.cpp @@ -84,3 +84,49 @@ bool CFileDescriptor::isReadable(int fd) { return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN); } + +#ifdef HU_UNIT_TESTS + +#include +#include + +TEST(OS, fd) { + std::string name = "/test_filedescriptors"; + CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600)); + + EXPECT_EQ(fd.isValid(), true); + EXPECT_EQ(fd.isReadable(), true); + + int flags = fd.getFlags(); + EXPECT_EQ(fd.getFlags(), FD_CLOEXEC); + flags &= ~FD_CLOEXEC; + fd.setFlags(flags); + EXPECT_EQ(fd.getFlags(), !FD_CLOEXEC); + + CFileDescriptor fd2 = fd.duplicate(); + EXPECT_EQ(fd.isValid(), true); + EXPECT_EQ(fd.isReadable(), true); + EXPECT_EQ(fd2.isValid(), true); + EXPECT_EQ(fd2.isReadable(), true); + + CFileDescriptor fd3(fd2.take()); + EXPECT_EQ(fd.isValid(), true); + EXPECT_EQ(fd.isReadable(), true); + EXPECT_EQ(fd2.isValid(), false); + EXPECT_EQ(fd2.isReadable(), false); + + // .duplicate default flags is FD_CLOEXEC + EXPECT_EQ(fd3.getFlags(), FD_CLOEXEC); + + fd.reset(); + fd2.reset(); + fd3.reset(); + + EXPECT_EQ(fd.isReadable(), false); + EXPECT_EQ(fd2.isReadable(), false); + EXPECT_EQ(fd3.isReadable(), false); + + shm_unlink(name.c_str()); +} + +#endif diff --git a/src/os/Process.cpp b/src/os/Process.cpp index 335d6b1..d51c758 100644 --- a/src/os/Process.cpp +++ b/src/os/Process.cpp @@ -283,3 +283,32 @@ void Hyprutils::OS::CProcess::setStdoutFD(int fd) { void Hyprutils::OS::CProcess::setStderrFD(int fd) { m_impl->stderrFD = fd; } + +#ifdef HU_UNIT_TESTS + +#include + +TEST(OS, process) { + CProcess process("sh", {"-c", "echo \"Hello $WORLD!\""}); + process.addEnv("WORLD", "World"); + + EXPECT_EQ(process.runAsync(), true); + EXPECT_EQ(process.runSync(), true); + + EXPECT_EQ(process.stdOut(), std::string{"Hello World!\n"}); + EXPECT_EQ(process.stdErr(), std::string{""}); + EXPECT_EQ(process.exitCode(), 0); + + CProcess process2("sh", {"-c", "while true; do sleep 1; done;"}); + + EXPECT_EQ(process2.runAsync(), true); + EXPECT_EQ(getpgid(process2.pid()) >= 0, true); + + kill(process2.pid(), SIGKILL); + + CProcess process3("sh", {"-c", "cat /geryueruggbuergheruger/reugiheruygyuerghuryeghyer/eruihgyuerguyerghyuerghuyergerguyer/NON_EXISTENT"}); + EXPECT_EQ(process3.runSync(), true); + EXPECT_EQ(process3.exitCode(), 1); +} + +#endif diff --git a/src/signal/Signal.cpp b/src/signal/Signal.cpp index befb51c..61647df 100644 --- a/src/signal/Signal.cpp +++ b/src/signal/Signal.cpp @@ -58,3 +58,376 @@ void Hyprutils::Signal::CSignalBase::registerStaticListenerInternal(std::functio void Hyprutils::Signal::CSignal::emit(std::any data) { CSignalT::emit(data); } + +#ifdef HU_UNIT_TESTS + +#include + +#include +#include +#include +#include +#include + +using namespace Hyprutils::Signal; +using namespace Hyprutils::Memory; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +// + +static void legacy() { + CSignal signal; + int data = 0; + auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; }); + + signal.emit(); + + EXPECT_EQ(data, 1); + + data = 0; + + listener.reset(); + + signal.emit(); + + EXPECT_EQ(data, 0); +} + +static void legacyListenerEmit() { + int data = 0; + CSignal signal; + auto listener = signal.registerListener([&](std::any d) { data = std::any_cast(d); }); + + listener->emit(1); // not a typo + EXPECT_EQ(data, 1); +} + +static void legacyListeners() { + 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_EQ(data, 33); +} + +#pragma GCC diagnostic pop +// + +static void empty() { + int data = 0; + + CSignalT<> signal; + auto listener = signal.listen([&] { data = 1; }); + + signal.emit(); + EXPECT_EQ(data, 1); + + data = 0; + listener.reset(); + signal.emit(); + EXPECT_EQ(data, 0); +} + +static void typed() { + int data = 0; + + CSignalT signal; + auto listener = signal.listen([&](int newData) { data = newData; }); + + signal.emit(1); + EXPECT_EQ(data, 1); +} + +static void ignoreParams() { + int data = 0; + + CSignalT signal; + auto listener = signal.listen([&] { data += 1; }); + + signal.listenStatic([&] { data += 1; }); + + signal.emit(2); + EXPECT_EQ(data, 2); +} + +static void typedMany() { + int data1 = 0; + int data2 = 0; + int data3 = 0; + + CSignalT signal; + auto listener = signal.listen([&](int d1, int d2, int d3) { + data1 = d1; + data2 = d2; + data3 = d3; + }); + + signal.emit(1, 2, 3); + EXPECT_EQ(data1, 1); + EXPECT_EQ(data2, 2); + EXPECT_EQ(data3, 3); +} + +static void ref() { + 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_EQ(data, 1); + EXPECT_EQ(count, 3); +} + +static void refMany() { + 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_EQ(data1, 1); + EXPECT_EQ(count, 11); +} + +static void autoRefTypes() { + class CCopyCounter { + public: + CCopyCounter(int& createCount, int& destroyCount) : createCount(createCount), destroyCount(destroyCount) { + createCount += 1; + } + + CCopyCounter(CCopyCounter&& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {} + CCopyCounter(const CCopyCounter& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {} + + ~CCopyCounter() { + destroyCount += 1; + } + + private: + int& createCount; + int& destroyCount; + }; + + auto createCount = 0; + auto destroyCount = 0; + + CSignalT signal; + auto listener = signal.listen([](const CCopyCounter& counter) {}); + + signal.emit(CCopyCounter(createCount, destroyCount)); + EXPECT_EQ(createCount, 1); + EXPECT_EQ(destroyCount, 1); +} + +static void forward() { + int count = 0; + + CSignalT sig; + CSignalT connected1; + CSignalT<> connected2; + + auto conn1 = sig.forward(connected1); + auto conn2 = sig.forward(connected2); + + auto listener1 = connected1.listen([&](int v) { count += v; }); + auto listener2 = connected2.listen([&] { count += 1; }); + + sig.emit(2); + + EXPECT_EQ(count, 3); +} + +static void listenerAdded() { + int count = 0; + + CSignalT<> signal; + CHyprSignalListener secondListener; + + auto listener = signal.listen([&] { + count += 1; + + if (!secondListener) + secondListener = signal.listen([&] { count += 1; }); + }); + + signal.emit(); + EXPECT_EQ(count, 1); // second should NOT be invoked as it was registed during emit + + signal.emit(); + EXPECT_EQ(count, 3); // second should be invoked +} + +static void lastListenerSwapped() { + 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_EQ(count, 0); // neither the removed nor added listeners should fire + + signal.emit(); + EXPECT_EQ(count, 2); // only the new listener should fire +} + +static void signalDestroyed() { + 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_EQ(count, 2); // all listeners should fire regardless of signal deletion +} + +// purely an asan test +static void signalDestroyedBeforeListener() { + CHyprSignalListener listener1; + CHyprSignalListener listener2; + + CSignalT<> signal; + + listener1 = signal.listen([] {}); + listener2 = signal.listen([] {}); +} + +static void signalDestroyedWithAddedListener() { + int count = 0; + + auto signal = std::make_unique>(); + CHyprSignalListener shouldNotRun; + + auto listener = signal->listen([&] { + shouldNotRun = signal->listen([&] { count += 2; }); + signal.reset(); + }); + + signal->emit(); + EXPECT_EQ(count, 0); +} + +static void signalDestroyedWithRemovedAndAddedListener() { + 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_EQ(count, 0); +} + +static void staticListener() { + int data = 0; + + CSignalT signal; + signal.listenStatic([&](int newData) { data = newData; }); + + signal.emit(1); + EXPECT_EQ(data, 1); +} + +static void staticListenerDestroy() { + 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_EQ(count, 2); +} + +// purely an asan test +static void listenerDestroysSelf() { + CSignalT<> signal; + + CHyprSignalListener listener; + listener = signal.listen([&] { listener.reset(); }); + + // the static signal case is taken care of above + + signal.emit(); +} + +TEST(Signal, signal) { + legacy(); + legacyListenerEmit(); + legacyListeners(); + empty(); + typed(); + ignoreParams(); + typedMany(); + ref(); + refMany(); + autoRefTypes(); + forward(); + listenerAdded(); + lastListenerSwapped(); + signalDestroyed(); + signalDestroyedBeforeListener(); + signalDestroyedWithAddedListener(); + signalDestroyedWithRemovedAndAddedListener(); + staticListener(); + staticListenerDestroy(); + signalDestroyed(); + listenerDestroysSelf(); +} + +#endif diff --git a/src/string/ConstVarList.cpp b/src/string/ConstVarList.cpp index 76a0928..1eabe11 100644 --- a/src/string/ConstVarList.cpp +++ b/src/string/ConstVarList.cpp @@ -36,3 +36,19 @@ std::string CConstVarList::join(const std::string& joiner, size_t from, size_t t return rolling; } + +#ifdef HU_UNIT_TESTS + +#include + +TEST(String, constvarlist) { + CConstVarList listConst("hello world!", 0, 's', true); + EXPECT_EQ(listConst[0], "hello"); + EXPECT_EQ(listConst[1], "world!"); + + CConstVarList listConst2("0 set", 2, ' '); + EXPECT_EQ(listConst2[0], "0"); + EXPECT_EQ(listConst2[1], "set"); +} + +#endif diff --git a/src/string/String.cpp b/src/string/String.cpp index db8dcc1..5ca94c7 100644 --- a/src/string/String.cpp +++ b/src/string/String.cpp @@ -88,3 +88,38 @@ void Hyprutils::String::replaceInString(std::string& string, const std::string& pos += to.length(); } } + +#ifdef HU_UNIT_TESTS + +#include + +TEST(String, string) { + EXPECT_EQ(trim(" a "), "a"); + EXPECT_EQ(trim(" a a "), "a a"); + EXPECT_EQ(trim("a"), "a"); + EXPECT_EQ(trim(" "), ""); + + EXPECT_EQ(isNumber("99214123434"), true); + EXPECT_EQ(isNumber("-35252345234"), true); + EXPECT_EQ(isNumber("---3423--432"), false); + EXPECT_EQ(isNumber("s---3423--432"), false); + EXPECT_EQ(isNumber("---3423--432s"), false); + EXPECT_EQ(isNumber("1s"), false); + EXPECT_EQ(isNumber(""), false); + EXPECT_EQ(isNumber("-"), false); + EXPECT_EQ(isNumber("--0"), false); + EXPECT_EQ(isNumber("abc"), false); + EXPECT_EQ(isNumber("0.0", true), true); + EXPECT_EQ(isNumber("0.2", true), true); + EXPECT_EQ(isNumber("0.", true), false); + EXPECT_EQ(isNumber(".0", true), false); + EXPECT_EQ(isNumber("", true), false); + EXPECT_EQ(isNumber("vvss", true), false); + EXPECT_EQ(isNumber("0.9999s", true), false); + EXPECT_EQ(isNumber("s0.9999", true), false); + EXPECT_EQ(isNumber("-1.0", true), true); + EXPECT_EQ(isNumber("-1..0", true), false); + EXPECT_EQ(isNumber("-10.0000000001", true), true); +} + +#endif \ No newline at end of file diff --git a/src/string/VarList.cpp b/src/string/VarList.cpp index 48508ba..e8745a3 100644 --- a/src/string/VarList.cpp +++ b/src/string/VarList.cpp @@ -36,3 +36,15 @@ std::string Hyprutils::String::CVarList::join(const std::string& joiner, size_t return rolling; } + +#ifdef HU_UNIT_TESTS + +#include + +TEST(String, varlist) { + CVarList list("hello world!", 0, 's', true); + EXPECT_EQ(list[0], "hello"); + EXPECT_EQ(list[1], "world!"); +} + +#endif diff --git a/src/string/VarList2.cpp b/src/string/VarList2.cpp index 751f212..bc67aa0 100644 --- a/src/string/VarList2.cpp +++ b/src/string/VarList2.cpp @@ -6,7 +6,7 @@ using namespace Hyprutils::String; CVarList2::CVarList2(std::string&& in, const size_t lastArgNo, const char delim, const bool removeEmpty, const bool allowEscape) : m_inString(std::move(in)) { - if (!removeEmpty && m_inString.empty()) + if (m_inString.empty()) return; auto isDelimiter = [&delim](const char& c) { return delim == 's' ? std::isspace(c) : delim == c; }; @@ -21,28 +21,29 @@ CVarList2::CVarList2(std::string&& in, const size_t lastArgNo, const char delim, if (allowEscape) { // we allow escape, so this might be escaped. Check first - if (i - argBegin == 0) - continue; // can't be - const char& previousC = m_inString[i - 1]; - if (i - argBegin == 1) { - if (previousC == '\\') { - escapedIndices.emplace_back(i - argBegin - 1); - continue; // escaped - } - // fall to breaking, not escaped - } else { - const char& prevPreviousC = m_inString[i - 2]; - if (previousC == '\\') { - // whether or not escaped, pop char - escapedIndices.emplace_back(i - argBegin - 1); - - if (prevPreviousC != '\\') { - // escaped - continue; + if (i - argBegin != 0) { + const char& previousC = m_inString[i - 1]; + if (i - argBegin == 1) { + if (previousC == '\\') { + escapedIndices.emplace_back(i - argBegin - 1); + continue; // escaped } - } + // fall to breaking, not escaped + } else { + const char& prevPreviousC = m_inString[i - 2]; + if (previousC == '\\') { + // whether or not escaped, pop char + escapedIndices.emplace_back(i - argBegin - 1); - // fall to breaking, not escaped, but mark the \\ to be popped + if (prevPreviousC != '\\') { + // escaped + continue; + } + } + + // fall to breaking, not escaped, but mark the \\ to be popped + } + // fall to breaking, couldn't be escaped } } @@ -91,12 +92,12 @@ CVarList2::CVarList2(std::string&& in, const size_t lastArgNo, const char delim, std::string CVarList2::join(const std::string& joiner, size_t from, size_t to) const { if (to == 0 || to <= from) - return ""; + to = m_args.size(); std::string roll; - for (size_t i = from; i < to; ++i) { + for (size_t i = from; i < to && i < m_args.size(); ++i) { roll += m_args[i]; - if (i + 1 < to) + if (i + 1 < to && i + 1 < m_args.size()) roll += joiner; } return roll; @@ -110,3 +111,71 @@ void CVarList2::append(std::string&& arg) { bool CVarList2::contains(const std::string& el) { return std::ranges::any_of(m_args, [&el](const auto& e) { return e == el; }); } + +#ifdef HU_UNIT_TESTS + +#include + +TEST(String, varlist2) { + CVarList2 varList2("0 set", 2, ' '); + EXPECT_EQ(varList2[0], "0"); + EXPECT_EQ(varList2[1], "set"); + + varList2.append("Hello"); + + EXPECT_EQ(varList2[1], "set"); + EXPECT_EQ(varList2[2], "Hello"); + EXPECT_EQ(varList2[3], ""); + EXPECT_EQ(varList2.contains("set"), true); + EXPECT_EQ(varList2.contains("sett"), false); + EXPECT_EQ(varList2.contains(""), false); + EXPECT_EQ(varList2.size(), 3); + + CVarList2 varList2B("hello, world\\, ok?", 0, ',', true, true); + EXPECT_EQ(varList2B[0], "hello"); + EXPECT_EQ(varList2B[1], "world, ok?"); + + CVarList2 varList2C("hello, , ok?", 0, ',', true, true); + EXPECT_EQ(varList2C[0], "hello"); + EXPECT_EQ(varList2C[1], "ok?"); + + CVarList2 varList2D("\\\\, , ok?", 0, ',', true, true); + EXPECT_EQ(varList2D[0], "\\"); + EXPECT_EQ(varList2D[1], "ok?"); + + CVarList2 varList2E("\\, , ok?", 0, ',', true, true); + EXPECT_EQ(varList2E[0], ","); + EXPECT_EQ(varList2E[1], "ok?"); + + CVarList2 varList2F("Hello, world\\\\, ok?", 0, ',', true, true); + EXPECT_EQ(varList2F[0], "Hello"); + EXPECT_EQ(varList2F[1], "world\\"); + EXPECT_EQ(varList2F[2], "ok?"); + + CVarList2 varList2G("Hello,\\, ok?", 0, ',', true, true); + EXPECT_EQ(varList2G[0], "Hello"); + EXPECT_EQ(varList2G[1], ", ok?"); + + CVarList2 varList2H("Hello,\\\\, ok?", 0, ',', true, true); + EXPECT_EQ(varList2H[0], "Hello"); + EXPECT_EQ(varList2H[1], "\\"); + EXPECT_EQ(varList2H[2], "ok?"); + + CVarList2 varList2I("Hello,\\, ok?", 0, ',', true, false); + EXPECT_EQ(varList2I[0], "Hello"); + EXPECT_EQ(varList2I[1], "\\"); + EXPECT_EQ(varList2I[2], "ok?"); + + CVarList2 varList2J("", 0, ',', true, false); + EXPECT_EQ(varList2J[0], ""); + + CVarList2 varList2K(",\\, ok?", 0, ',', true); + EXPECT_EQ(varList2K[0], ", ok?"); + + CVarList2 varList2L("Hello, world", 0, ',', true); + EXPECT_EQ(varList2L.join(" "), "Hello world"); + EXPECT_EQ(varList2L.join(" ", 0, 1000), "Hello world"); + EXPECT_EQ(varList2L.join(" ", 0, 1), "Hello"); +} + +#endif diff --git a/tests/animation.cpp b/tests/animation.cpp deleted file mode 100644 index 9dbcf84..0000000 --- a/tests/animation.cpp +++ /dev/null @@ -1,397 +0,0 @@ -#include -#include -#include -#include -#include -#include "shared.hpp" - -#define SP CSharedPointer -#define WP CWeakPointer -#define UP CUniquePointer - -using namespace Hyprutils::Animation; -using namespace Hyprutils::Math; -using namespace Hyprutils::Memory; - -class EmtpyContext {}; - -template -using CAnimatedVariable = CGenericAnimatedVariable; - -template -using PANIMVAR = SP>; - -template -using PANIMVARREF = WP>; - -enum eAVTypes { - INT = 1, - TEST, -}; - -struct SomeTestType { - bool done = false; - bool operator==(const SomeTestType& other) const { - return done == other.done; - } - SomeTestType& operator=(const SomeTestType& other) { - done = other.done; - return *this; - } -}; - -CAnimationConfigTree animationTree; - -class CMyAnimationManager : public CAnimationManager { - public: - void tick() { - for (size_t i = 0; i < m_vActiveAnimatedVariables.size(); i++) { - const auto PAV = m_vActiveAnimatedVariables[i].lock(); - if (!PAV || !PAV->ok() || !PAV->isBeingAnimated()) - continue; - - const auto SPENT = PAV->getPercent(); - const auto PBEZIER = getBezier(PAV->getBezierName()); - - if (SPENT >= 1.f || !PAV->enabled()) { - PAV->warp(true, false); - continue; - } - - const auto POINTY = PBEZIER->getYForPoint(SPENT); - - switch (PAV->m_Type) { - case eAVTypes::INT: { - auto avInt = dc*>(PAV.get()); - if (!avInt) - std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET; - - const auto DELTA = avInt->goal() - avInt->value(); - avInt->value() = avInt->begun() + (DELTA * POINTY); - } break; - case eAVTypes::TEST: { - auto avCustom = dc*>(PAV.get()); - if (!avCustom) - std::cout << Colors::RED << "Dynamic cast upcast failed" << Colors::RESET; - - if (SPENT >= 1.f) - avCustom->value().done = true; - } break; - default: { - std::cout << Colors::RED << "What are we even doing?" << Colors::RESET; - } break; - } - - PAV->onUpdate(); - } - - tickDone(); - } - - template - void createAnimation(const VarType& v, PANIMVAR& av, const std::string& animationConfigName) { - constexpr const eAVTypes EAVTYPE = std::is_same_v ? eAVTypes::INT : eAVTypes::TEST; - const auto PAV = makeShared>(); - - PAV->create(EAVTYPE, sc(this), PAV, v); - PAV->setConfig(animationTree.getConfig(animationConfigName)); - av = std::move(PAV); - } - - virtual void scheduleTick() { - ; - } - - virtual void onTicked() { - ; - } -}; - -UP pAnimationManager; - -class Subject { - public: - Subject(const int& a, const int& b) { - pAnimationManager->createAnimation(a, m_iA, "default"); - pAnimationManager->createAnimation(b, m_iB, "internal"); - pAnimationManager->createAnimation({}, m_iC, "default"); - } - PANIMVAR m_iA; - PANIMVAR m_iB; - PANIMVAR m_iC; -}; - -int config() { - pAnimationManager = makeUnique(); - - int ret = 0; - - animationTree.createNode("global"); - animationTree.createNode("internal"); - - animationTree.createNode("foo", "internal"); - animationTree.createNode("default", "global"); - animationTree.createNode("bar", "default"); - - /* - internal - ↳ foo - global - ↳ default - ↳ bar - */ - - auto barCfg = animationTree.getConfig("bar"); - auto internalCfg = animationTree.getConfig("internal"); - - // internal is a root node and should point to itself - EXPECT(internalCfg->pParentAnimation.get(), internalCfg.get()); - EXPECT(internalCfg->pValues.get(), internalCfg.get()); - - animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf"); - - EXPECT(barCfg->internalEnabled, -1); - { - const auto PVALUES = barCfg->pValues.lock(); - EXPECT(PVALUES->internalEnabled, 1); - EXPECT(PVALUES->internalBezier, "default"); - EXPECT(PVALUES->internalStyle, "asdf"); - EXPECT(PVALUES->internalSpeed, 4.0); - } - EXPECT(barCfg->pParentAnimation.get(), animationTree.getConfig("default").get()); - - // Overwrite our own values - animationTree.setConfigForNode("bar", 1, 4.2, "test", "qwer"); - - { - const auto PVALUES = barCfg->pValues.lock(); - EXPECT(PVALUES->internalEnabled, 1); - EXPECT(PVALUES->internalBezier, "test"); - EXPECT(PVALUES->internalStyle, "qwer"); - EXPECT(PVALUES->internalSpeed, 4.2f); - } - - // Now overwrite the parent - animationTree.setConfigForNode("default", 0, 0.0, "zxcv", "foo"); - - { - // Expecting no change - const auto PVALUES = barCfg->pValues.lock(); - EXPECT(PVALUES->internalEnabled, 1); - EXPECT(PVALUES->internalBezier, "test"); - EXPECT(PVALUES->internalStyle, "qwer"); - EXPECT(PVALUES->internalSpeed, 4.2f); - } - - return ret; -} - -int main(int argc, char** argv, char** envp) { - int ret = config(); - - animationTree.createNode("global"); - animationTree.createNode("internal"); - - animationTree.createNode("default", "global"); - animationTree.setConfigForNode("global", 1, 4.0, "default", "asdf"); - - Subject s(0, 0); - - EXPECT(s.m_iA->value(), 0); - EXPECT(s.m_iB->value(), 0); - - // Test destruction of a CAnimatedVariable - { - Subject s2(10, 10); - // Adds them to active - *s2.m_iA = 1; - *s2.m_iB = 2; - // We deliberately do not tick here, to make sure the destructor removes active animated variables - } - - EXPECT(pAnimationManager->shouldTickForNext(), false); - EXPECT(s.m_iC->value().done, false); - - *s.m_iA = 10; - *s.m_iB = 100; - *s.m_iC = SomeTestType(true); - - EXPECT(s.m_iC->value().done, false); - - while (pAnimationManager->shouldTickForNext()) { - pAnimationManager->tick(); - } - - EXPECT(s.m_iA->value(), 10); - EXPECT(s.m_iB->value(), 100); - EXPECT(s.m_iC->value().done, true); - - s.m_iA->setValue(0); - s.m_iB->setValue(0); - - while (pAnimationManager->shouldTickForNext()) { - pAnimationManager->tick(); - } - - EXPECT(s.m_iA->value(), 10); - EXPECT(s.m_iB->value(), 100); - - // Test config stuff - EXPECT(s.m_iA->getBezierName(), "default"); - EXPECT(s.m_iA->getStyle(), "asdf"); - EXPECT(s.m_iA->enabled(), true); - - animationTree.getConfig("global")->internalEnabled = 0; - - EXPECT(s.m_iA->enabled(), false); - - *s.m_iA = 50; - pAnimationManager->tick(); // Expecting a warp - EXPECT(s.m_iA->value(), 50); - - // Test missing pValues - animationTree.getConfig("global")->internalEnabled = 0; - animationTree.getConfig("default")->pValues.reset(); - - EXPECT(s.m_iA->enabled(), false); - EXPECT(s.m_iA->getBezierName(), "default"); - EXPECT(s.m_iA->getStyle(), ""); - EXPECT(s.m_iA->getPercent(), 1.f); - - // Reset - animationTree.setConfigForNode("default", 1, 1, "default"); - - // - // Test callbacks - // - int beginCallbackRan = 0; - int updateCallbackRan = 0; - int endCallbackRan = 0; - s.m_iA->setCallbackOnBegin([&beginCallbackRan](WP pav) { beginCallbackRan++; }); - s.m_iA->setUpdateCallback([&updateCallbackRan](WP pav) { updateCallbackRan++; }); - s.m_iA->setCallbackOnEnd([&endCallbackRan](WP pav) { endCallbackRan++; }, false); - - s.m_iA->setValueAndWarp(42); - - EXPECT(beginCallbackRan, 0); - EXPECT(updateCallbackRan, 1); - EXPECT(endCallbackRan, 2); // first called when setting the callback, then when warping. - - *s.m_iA = 1337; - while (pAnimationManager->shouldTickForNext()) { - pAnimationManager->tick(); - } - - EXPECT(beginCallbackRan, 1); - EXPECT(updateCallbackRan > 2, true); - EXPECT(endCallbackRan, 3); - - std::vector> vars; - for (int i = 0; i < 10; i++) { - vars.resize(vars.size() + 1); - pAnimationManager->createAnimation(1, vars.back(), "default"); - *vars.back() = 1337; - } - - // test adding / removing vars during a tick - s.m_iA->resetAllCallbacks(); - s.m_iA->setUpdateCallback([&vars](WP v) { - if (v.lock() != vars.back()) - vars.back()->warp(); - }); - s.m_iA->setCallbackOnEnd([&s, &vars](auto) { - vars.resize(vars.size() + 1); - pAnimationManager->createAnimation(1, vars.back(), "default"); - *vars.back() = 1337; - }); - - *s.m_iA = 1000000; - - while (pAnimationManager->shouldTickForNext()) { - pAnimationManager->tick(); - } - - EXPECT(s.m_iA->value(), 1000000); - // all vars should be set to 1337 - EXPECT(std::find_if(vars.begin(), vars.end(), [](const auto& v) { return v->value() != 1337; }) == vars.end(), true); - - // test one-time callbacks - s.m_iA->resetAllCallbacks(); - s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, true); - - EXPECT(endCallbackRan, 4); - - s.m_iA->setValueAndWarp(10); - - EXPECT(endCallbackRan, 4); - EXPECT(s.m_iA->value(), 10); - - // test warp - *s.m_iA = 3; - s.m_iA->setCallbackOnEnd([&endCallbackRan](auto) { endCallbackRan++; }, false); - - s.m_iA->warp(false); - EXPECT(endCallbackRan, 4); - - *s.m_iA = 4; - s.m_iA->warp(true); - EXPECT(endCallbackRan, 5); - - // test getCurveValue - *s.m_iA = 0; - EXPECT(s.m_iA->getCurveValue(), 0.f); - s.m_iA->warp(); - EXPECT(s.m_iA->getCurveValue(), 1.f); - EXPECT(endCallbackRan, 6); - - // test end callback readding the var - *s.m_iA = 5; - s.m_iA->setCallbackOnEnd([&endCallbackRan](WP v) { - endCallbackRan++; - const auto PAV = dc*>(v.lock().get()); - - *PAV = 10; - PAV->setCallbackOnEnd([&endCallbackRan](WP v) { endCallbackRan++; }); - }); - - while (pAnimationManager->shouldTickForNext()) { - pAnimationManager->tick(); - } - - EXPECT(endCallbackRan, 8); - EXPECT(s.m_iA->value(), 10); - - // Test duplicate active anim vars are not allowed - { - EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); - PANIMVAR a; - pAnimationManager->createAnimation(1, a, "default"); - EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); - *a = 10; - EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); - *a = 20; - EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 1); - a->warp(); - EXPECT(pAnimationManager->m_vActiveAnimatedVariables.size(), 0); - EXPECT(a->value(), 20); - } - - // Test no crash when animation manager gets destroyed - { - PANIMVAR a; - pAnimationManager->createAnimation(1, a, "default"); - *a = 10; - pAnimationManager.reset(); - EXPECT(a->isAnimationManagerDead(), true); - a->setValueAndWarp(11); - EXPECT(a->value(), 11); - *a = 12; - a->warp(); - EXPECT(a->value(), 12); - *a = 13; - } // a gets destroyed - - EXPECT(pAnimationManager.get(), nullptr); - - return ret; -} diff --git a/tests/beziercurve.cpp b/tests/beziercurve.cpp deleted file mode 100644 index 1026bc5..0000000 --- a/tests/beziercurve.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include -#include -#include -#include "shared.hpp" - -using Hyprutils::Animation::CBezierCurve; -using Hyprutils::Math::Vector2D; - -static void test_nonmonotonic4_clamps_out_of_range(int& ret) { - // Non-monotonic curve in X - // This used to drive the step-halving search to OOB. It should now clamp - CBezierCurve curve; - std::array pts = { - Vector2D{0.5f, 1.0f}, // P0 - Vector2D{1.0f, 1.0f}, // P1 - Vector2D{0.0f, 0.0f}, // P2 - Vector2D{0.5f, 0.0f} // P3 - }; - curve.setup4(pts); - - // x > last baked x - EXPECT(std::isfinite(curve.getYForPoint(0.6f)), true); - // Far beyond range - EXPECT(std::isfinite(curve.getYForPoint(std::numeric_limits::max())), true); - EXPECT(std::isfinite(curve.getYForPoint(-std::numeric_limits::max())), true); -} - -static void test_adjacent_baked_x_equal(int& ret) { - // Curve with flat tail (X=1, Y=1) - CBezierCurve curve; - std::array pts = { - Vector2D{0.0f, 0.0f}, // P0 - Vector2D{0.2f, 0.2f}, // P1 - Vector2D{1.0f, 1.0f}, // P2 - Vector2D{1.0f, 1.0f} // P3 - }; - curve.setup4(pts); - - // Exactly at last baked X - const float y_at_end = curve.getYForPoint(1.0f); - // Slightly beyond last baked X - const float y_past_end = curve.getYForPoint(1.0001f); - - EXPECT(y_at_end, 1.0f); - EXPECT(y_past_end, y_at_end); -} - -static void test_all_baked_x_equal(int& ret) { - // Extreme case: X is constant along the whole curve - CBezierCurve curve; - std::array pts = { - Vector2D{0.0f, 0.0f}, // P0 - Vector2D{0.0f, 0.3f}, // P1 - Vector2D{0.0f, 0.7f}, // P2 - Vector2D{0.0f, 1.0f} // P3 - }; - curve.setup4(pts); - - // Below any baked X - const float y_lo = curve.getYForPoint(-100.0f); - const float y_0 = curve.getYForPoint(0.0f); - // Above any baked X - const float y_hi = curve.getYForPoint(100.0f); - - EXPECT(std::isfinite(y_lo), true); - EXPECT(std::isfinite(y_0), true); - EXPECT(std::isfinite(y_hi), true); - - // For this curve Y should stay within [0,1] - EXPECT((y_lo >= 0.0f && y_lo <= 1.0f), true); - EXPECT((y_0 >= 0.0f && y_0 <= 1.0f), true); - EXPECT((y_hi >= 0.0f && y_hi <= 1.0f), true); -} - -int main() { - int ret = 0; - - test_nonmonotonic4_clamps_out_of_range(ret); - test_adjacent_baked_x_equal(ret); - test_all_baked_x_equal(ret); - - return ret; -} diff --git a/tests/filedescriptor.cpp b/tests/filedescriptor.cpp deleted file mode 100644 index c2a2c79..0000000 --- a/tests/filedescriptor.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include -#include "shared.hpp" -#include -#include -#include - -using namespace Hyprutils::OS; - -int main(int argc, char** argv, char** envp) { - std::string name = "/test_filedescriptors"; - CFileDescriptor fd(shm_open(name.c_str(), O_RDWR | O_CREAT | O_EXCL, 0600)); - - int ret = 0; - EXPECT(fd.isValid(), true); - EXPECT(fd.isReadable(), true); - - int flags = fd.getFlags(); - EXPECT(fd.getFlags(), FD_CLOEXEC); - flags &= ~FD_CLOEXEC; - fd.setFlags(flags); - EXPECT(fd.getFlags(), !FD_CLOEXEC); - - CFileDescriptor fd2 = fd.duplicate(); - EXPECT(fd.isValid(), true); - EXPECT(fd.isReadable(), true); - EXPECT(fd2.isValid(), true); - EXPECT(fd2.isReadable(), true); - - CFileDescriptor fd3(fd2.take()); - EXPECT(fd.isValid(), true); - EXPECT(fd.isReadable(), true); - EXPECT(fd2.isValid(), false); - EXPECT(fd2.isReadable(), false); - - // .duplicate default flags is FD_CLOEXEC - EXPECT(fd3.getFlags(), FD_CLOEXEC); - - fd.reset(); - fd2.reset(); - fd3.reset(); - - EXPECT(fd.isReadable(), false); - EXPECT(fd2.isReadable(), false); - EXPECT(fd3.isReadable(), false); - - shm_unlink(name.c_str()); - - return ret; -} diff --git a/tests/i18n.cpp b/tests/i18n.cpp deleted file mode 100644 index 0cb8703..0000000 --- a/tests/i18n.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include "shared.hpp" -#include - -using namespace Hyprutils::I18n; - -enum eTxtKeys : uint64_t { - TXT_KEY_HELLO, - TXT_KEY_I_HAVE_APPLES, - TXT_KEY_FALLBACK, -}; - -int main(int argc, char** argv, char** envp) { - int ret = 0; - - CI18nEngine engine; - - std::println("System locale: {}, stem: {}", engine.getSystemLocale().locale(), engine.getSystemLocale().stem()); - - engine.setFallbackLocale("en_US"); - - engine.registerEntry("en_US", TXT_KEY_HELLO, "Hello World!"); - engine.registerEntry("en_US", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) { - if (std::stoi(m.at("count")) == 1) - return "I have {count} apple."; - else - return "I have {count} apples."; - }); - engine.registerEntry("en_US", TXT_KEY_FALLBACK, "Fallback string!"); - - engine.registerEntry("pl_PL", TXT_KEY_HELLO, "Witaj świecie!"); - engine.registerEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, [](const translationVarMap& m) { - const auto COUNT = std::stoi(m.at("count")); - if (COUNT == 1) - return "Mam {count} jabłko."; - else if (COUNT < 5) - return "Mam {count} jabłka."; - else - return "Mam {count} jabłek."; - }); - - engine.registerEntry("es_XX", TXT_KEY_FALLBACK, "I don't speak spanish"); - engine.registerEntry("es_ES", TXT_KEY_FALLBACK, "I don't speak spanish here either"); - - EXPECT(engine.localizeEntry("en_US", TXT_KEY_HELLO, {}), "Hello World!"); - EXPECT(engine.localizeEntry("pl_PL", TXT_KEY_HELLO, {}), "Witaj świecie!"); - EXPECT(engine.localizeEntry("de_DE", TXT_KEY_HELLO, {}), "Hello World!"); - - EXPECT(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "I have 1 apple."); - EXPECT(engine.localizeEntry("en_US", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples."); - - EXPECT(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "1"}}), "Mam 1 jabłko."); - EXPECT(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "Mam 2 jabłka."); - EXPECT(engine.localizeEntry("pl_PL", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek."); - - EXPECT(engine.localizeEntry("pl", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek."); - - EXPECT(engine.localizeEntry("pl_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "5"}}), "Mam 5 jabłek."); - EXPECT(engine.localizeEntry("en_XX", TXT_KEY_I_HAVE_APPLES, {{"count", "2"}}), "I have 2 apples."); - - EXPECT(engine.localizeEntry("es_YY", TXT_KEY_FALLBACK, {}), "I don't speak spanish here either"); - EXPECT(engine.localizeEntry("es_XX", TXT_KEY_FALLBACK, {}), "I don't speak spanish"); - - EXPECT(engine.localizeEntry("pl_PL", TXT_KEY_FALLBACK, {}), "Fallback string!"); - - return ret; -} \ No newline at end of file diff --git a/tests/math.cpp b/tests/math.cpp deleted file mode 100644 index 974061a..0000000 --- a/tests/math.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include -#include "shared.hpp" - -using namespace Hyprutils::Math; - -int main(int argc, char** argv, char** envp) { - CRegion rg = {0, 0, 100, 100}; - rg.add(CBox{{}, {20, 200}}); - - int ret = 0; - - EXPECT(rg.getExtents().height, 200); - EXPECT(rg.getExtents().width, 100); - - rg.intersect(CBox{10, 10, 300, 300}); - - EXPECT(rg.getExtents().width, 90); - EXPECT(rg.getExtents().height, 190); - - /*Box.cpp test cases*/ - // Test default constructor and accessors - { - CBox box1; - EXPECT(box1.x, 0); - EXPECT(box1.y, 0); - EXPECT(box1.width, 0); - EXPECT(box1.height, 0); - - // Test parameterized constructor and accessors - CBox box2(10, 20, 30, 40); - EXPECT(box2.x, 10); - EXPECT(box2.y, 20); - EXPECT(box2.width, 30); - EXPECT(box2.height, 40); - - // Test setters and getters - box2.translate(Vector2D(5, -5)); - EXPECT_VECTOR2D(box2.pos(), Vector2D(15, 15)); - } - - //Test Scaling and Transformation - { - CBox box(10, 10, 20, 30); - - // Test scaling - box.scale(2.0); - EXPECT_VECTOR2D(box.size(), Vector2D(40, 60)); - EXPECT_VECTOR2D(box.pos(), Vector2D(20, 20)); - - // Test scaling from center - box.scaleFromCenter(0.5); - EXPECT_VECTOR2D(box.size(), Vector2D(20, 30)); - EXPECT_VECTOR2D(box.pos(), Vector2D(30, 35)); - - // Test transformation - box.transform(HYPRUTILS_TRANSFORM_90, 100, 200); - EXPECT_VECTOR2D(box.pos(), Vector2D(135, 30)); - EXPECT_VECTOR2D(box.size(), Vector2D(30, 20)); - - // Test Intersection and Extents - } - - { - CBox box1(0, 0, 100, 100); - CBox box2(50, 50, 100, 100); - - CBox intersection = box1.intersection(box2); - EXPECT_VECTOR2D(intersection.pos(), Vector2D(50, 50)); - EXPECT_VECTOR2D(intersection.size(), Vector2D(50, 50)); - - SBoxExtents extents = box1.extentsFrom(box2); - EXPECT_VECTOR2D(extents.topLeft, Vector2D(50, 50)); - EXPECT_VECTOR2D(extents.bottomRight, Vector2D(-50, -50)); - } - - // Test Boundary Conditions and Special Cases - { - CBox box(0, 0, 50, 50); - - EXPECT(box.empty(), false); - - EXPECT(box.containsPoint(Vector2D(25, 25)), true); - EXPECT(box.containsPoint(Vector2D(60, 60)), false); - EXPECT(box.overlaps(CBox(25, 25, 50, 50)), true); - EXPECT(box.inside(CBox(0, 0, 100, 100)), false); - } - - // Test matrices - { - Mat3x3 jeremy = Mat3x3::outputProjection({1920, 1080}, HYPRUTILS_TRANSFORM_FLIPPED_90); - Mat3x3 matrixBox = jeremy.projectBox(CBox{10, 10, 200, 200}, HYPRUTILS_TRANSFORM_NORMAL).translate({100, 100}).scale({1.25F, 1.5F}).transpose(); - - Mat3x3 expected = std::array{0, 0.46296296, 0, 0.3125, 0, 0, 19.84375, 36.055557, 1}; - // we need to do this to avoid precision errors on 32-bit archs - EXPECT(std::abs(expected.getMatrix().at(0) - matrixBox.getMatrix().at(0)) < 0.1, true); - EXPECT(std::abs(expected.getMatrix().at(1) - matrixBox.getMatrix().at(1)) < 0.1, true); - EXPECT(std::abs(expected.getMatrix().at(2) - matrixBox.getMatrix().at(2)) < 0.1, true); - EXPECT(std::abs(expected.getMatrix().at(3) - matrixBox.getMatrix().at(3)) < 0.1, true); - EXPECT(std::abs(expected.getMatrix().at(4) - matrixBox.getMatrix().at(4)) < 0.1, true); - EXPECT(std::abs(expected.getMatrix().at(5) - matrixBox.getMatrix().at(5)) < 0.1, true); - EXPECT(std::abs(expected.getMatrix().at(6) - matrixBox.getMatrix().at(6)) < 0.1, true); - EXPECT(std::abs(expected.getMatrix().at(7) - matrixBox.getMatrix().at(7)) < 0.1, true); - EXPECT(std::abs(expected.getMatrix().at(8) - matrixBox.getMatrix().at(8)) < 0.1, true); - } - - // Test Region Scaling - { - CRegion rg(CBox{{20, 20}, {40, 40}}); - - auto extents = rg.getExtents(); - EXPECT_VECTOR2D(extents.pos(), Vector2D(20, 20)); - EXPECT_VECTOR2D(extents.size(), Vector2D(40, 40)); - - rg.scale(2); - extents = rg.getExtents(); - EXPECT_VECTOR2D(extents.pos(), Vector2D(40, 40)); - EXPECT_VECTOR2D(extents.size(), Vector2D(80, 80)); - } - - { - Vector2D original(30, 40); - Vector2D monitorSize(100, 200); - - EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_NORMAL, monitorSize), Vector2D(30, 40 )); - EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_90, monitorSize), Vector2D(40, 200 - 30)); - EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_180, monitorSize), Vector2D(100 - 30, 200 - 40)); - EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_270, monitorSize), Vector2D(100 - 40, 30 )); - EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED, monitorSize), Vector2D(100 - 30, 40 )); - EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_90, monitorSize), Vector2D(40, 30 )); - EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_180, monitorSize), Vector2D(30, 200 - 40)); - EXPECT_VECTOR2D(original.transform(HYPRUTILS_TRANSFORM_FLIPPED_270, monitorSize), Vector2D(100 - 40, 200 - 30)); - } - - return ret; -} diff --git a/tests/os.cpp b/tests/os.cpp deleted file mode 100644 index 661cc77..0000000 --- a/tests/os.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include -#include -#include -#include -#include -#include "shared.hpp" - -using namespace Hyprutils::OS; - -int main(int argc, char** argv, char** envp) { - int ret = 0; - - CProcess process("sh", {"-c", "echo \"Hello $WORLD!\""}); - process.addEnv("WORLD", "World"); - - EXPECT(process.runAsync(), true); - EXPECT(process.runSync(), true); - - EXPECT(process.stdOut(), std::string{"Hello World!\n"}); - EXPECT(process.stdErr(), std::string{""}); - EXPECT(process.exitCode(), 0); - - CProcess process2("sh", {"-c", "while true; do sleep 1; done;"}); - - EXPECT(process2.runAsync(), true); - EXPECT(getpgid(process2.pid()) >= 0, true); - - kill(process2.pid(), SIGKILL); - - CProcess process3("sh", {"-c", "cat /geryueruggbuergheruger/reugiheruygyuerghuryeghyer/eruihgyuerguyerghyuerghuyergerguyer/NON_EXISTENT"}); - EXPECT(process3.runSync(), true); - EXPECT(process3.exitCode(), 1); - - return ret; -} \ No newline at end of file diff --git a/tests/shared.hpp b/tests/shared.hpp deleted file mode 100644 index 4b9aa32..0000000 --- a/tests/shared.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include - -namespace Colors { - constexpr const char* RED = "\x1b[31m"; - constexpr const char* GREEN = "\x1b[32m"; - constexpr const char* YELLOW = "\x1b[33m"; - constexpr const char* BLUE = "\x1b[34m"; - constexpr const char* MAGENTA = "\x1b[35m"; - constexpr const char* CYAN = "\x1b[36m"; - constexpr const char* RESET = "\x1b[0m"; -}; - -#define EXPECT(expr, val) \ - if (const auto RESULT = expr; RESULT != (val)) { \ - std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected " << val << " but got " << RESULT << "\n"; \ - ret = 1; \ - } else { \ - std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got " << val << "\n"; \ - } - -#define EXPECT_VECTOR2D(expr, val) \ - do { \ - const auto& RESULT = expr; \ - const auto& EXPECTED = val; \ - if (!(std::abs(RESULT.x - EXPECTED.x) < 1e-6 && std::abs(RESULT.y - EXPECTED.y) < 1e-6)) { \ - std::cout << Colors::RED << "Failed: " << Colors::RESET << #expr << ", expected (" << EXPECTED.x << ", " << EXPECTED.y << ") but got (" << RESULT.x << ", " \ - << RESULT.y << ")\n"; \ - ret = 1; \ - } else { \ - std::cout << Colors::GREEN << "Passed " << Colors::RESET << #expr << ". Got (" << RESULT.x << ", " << RESULT.y << ")\n"; \ - } \ - } while (0) - -#define EXPECT_NEAR(actual, expected, tolerance) \ - do { \ - auto _a = (actual); \ - auto _e = (expected); \ - auto _t = (tolerance); \ - if (!(std::fabs((_a) - (_e)) <= (_t))) { \ - std::cout << Colors::RED << "Failed: " << Colors::RESET << " EXPECT_NEAR(" #actual ", " #expected ", " #tolerance ") got=" << _a << " expected=" << _e << " ± " << _t \ - << "\n"; \ - ret = 1; \ - } else { \ - std::cout << Colors::GREEN << "Passed " << Colors::RESET << " |" #actual " - " #expected "| <= " #tolerance "\n"; \ - } \ - } while (0) diff --git a/tests/signal.cpp b/tests/signal.cpp deleted file mode 100644 index 99a8274..0000000 --- a/tests/signal.cpp +++ /dev/null @@ -1,370 +0,0 @@ -#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; - auto listener = signal.registerListener([&]([[maybe_unused]] std::any d) { data = 1; }); - - signal.emit(); - - EXPECT(data, 1); - - data = 0; - - listener.reset(); - - signal.emit(); - - EXPECT(data, 0); -} - -void legacyListenerEmit(int& ret) { - int data = 0; - CSignal signal; - auto listener = signal.registerListener([&](std::any d) { data = std::any_cast(d); }); - - listener->emit(1); // not a typo - 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; - - CSignalT<> signal; - auto listener = signal.listen([&] { data = 1; }); - - signal.emit(); - EXPECT(data, 1); - - data = 0; - listener.reset(); - signal.emit(); - EXPECT(data, 0); -} - -void typed(int& ret) { - int data = 0; - - CSignalT signal; - auto listener = signal.listen([&](int newData) { data = newData; }); - - signal.emit(1); - EXPECT(data, 1); -} - -void ignoreParams(int& ret) { - int data = 0; - - CSignalT signal; - auto listener = signal.listen([&] { data += 1; }); - - signal.listenStatic([&] { data += 1; }); - - signal.emit(2); - EXPECT(data, 2); -} - -void typedMany(int& ret) { - int data1 = 0; - int data2 = 0; - int data3 = 0; - - CSignalT signal; - auto listener = signal.listen([&](int d1, int d2, int d3) { - data1 = d1; - data2 = d2; - data3 = d3; - }); - - signal.emit(1, 2, 3); - EXPECT(data1, 1); - EXPECT(data2, 2); - 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 autoRefTypes(int& ret) { - class CCopyCounter { - public: - CCopyCounter(int& createCount, int& destroyCount) : createCount(createCount), destroyCount(destroyCount) { - createCount += 1; - } - - CCopyCounter(CCopyCounter&& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {} - CCopyCounter(const CCopyCounter& other) noexcept : CCopyCounter(other.createCount, other.destroyCount) {} - - ~CCopyCounter() { - destroyCount += 1; - } - - private: - int& createCount; - int& destroyCount; - }; - - auto createCount = 0; - auto destroyCount = 0; - - CSignalT signal; - auto listener = signal.listen([](const CCopyCounter& counter) {}); - - signal.emit(CCopyCounter(createCount, destroyCount)); - EXPECT(createCount, 1); - EXPECT(destroyCount, 1); -} - -void forward(int& ret) { - int count = 0; - - CSignalT sig; - CSignalT connected1; - CSignalT<> connected2; - - auto conn1 = sig.forward(connected1); - auto conn2 = sig.forward(connected2); - - auto listener1 = connected1.listen([&](int v) { count += v; }); - auto listener2 = connected2.listen([&] { count += 1; }); - - sig.emit(2); - - EXPECT(count, 3); -} - -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; - - CSignalT signal; - signal.listenStatic([&](int newData) { data = newData; }); - - signal.emit(1); - 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); - ignoreParams(ret); - typedMany(ret); - ref(ret); - refMany(ret); - autoRefTypes(ret); - forward(ret); - listenerAdded(ret); - lastListenerSwapped(ret); - signalDestroyed(ret); - signalDestroyedBeforeListener(); - signalDestroyedWithAddedListener(ret); - signalDestroyedWithRemovedAndAddedListener(ret); - staticListener(ret); - staticListenerDestroy(ret); - signalDestroyed(ret); - listenerDestroysSelf(); - return ret; -} diff --git a/tests/string.cpp b/tests/string.cpp deleted file mode 100644 index 9cca024..0000000 --- a/tests/string.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include -#include -#include -#include -#include "shared.hpp" - -using namespace Hyprutils::String; - -int main(int argc, char** argv, char** envp) { - int ret = 0; - - EXPECT(trim(" a "), "a"); - EXPECT(trim(" a a "), "a a"); - EXPECT(trim("a"), "a"); - EXPECT(trim(" "), ""); - - EXPECT(isNumber("99214123434"), true); - EXPECT(isNumber("-35252345234"), true); - EXPECT(isNumber("---3423--432"), false); - EXPECT(isNumber("s---3423--432"), false); - EXPECT(isNumber("---3423--432s"), false); - EXPECT(isNumber("1s"), false); - EXPECT(isNumber(""), false); - EXPECT(isNumber("-"), false); - EXPECT(isNumber("--0"), false); - EXPECT(isNumber("abc"), false); - EXPECT(isNumber("0.0", true), true); - EXPECT(isNumber("0.2", true), true); - EXPECT(isNumber("0.", true), false); - EXPECT(isNumber(".0", true), false); - EXPECT(isNumber("", true), false); - EXPECT(isNumber("vvss", true), false); - EXPECT(isNumber("0.9999s", true), false); - EXPECT(isNumber("s0.9999", true), false); - EXPECT(isNumber("-1.0", true), true); - EXPECT(isNumber("-1..0", true), false); - EXPECT(isNumber("-10.0000000001", true), true); - - CVarList list("hello world!", 0, 's', true); - EXPECT(list[0], "hello"); - EXPECT(list[1], "world!"); - - CConstVarList listConst("hello world!", 0, 's', true); - EXPECT(listConst[0], "hello"); - EXPECT(listConst[1], "world!"); - - CConstVarList listConst2("0 set", 2, ' '); - EXPECT(listConst2[0], "0"); - EXPECT(listConst2[1], "set"); - - CVarList2 varList2("0 set", 2, ' '); - EXPECT(varList2[0], "0"); - EXPECT(varList2[1], "set"); - - varList2.append("Hello"); - - EXPECT(varList2[1], "set"); - EXPECT(varList2[2], "Hello"); - - CVarList2 varList2B("hello, world\\, ok?", 0, ',', true, true); - EXPECT(varList2B[0], "hello"); - EXPECT(varList2B[1], "world, ok?"); - - CVarList2 varList2C("hello, , ok?", 0, ',', true, true); - EXPECT(varList2C[0], "hello"); - EXPECT(varList2C[1], "ok?"); - - CVarList2 varList2D("\\\\, , ok?", 0, ',', true, true); - EXPECT(varList2D[0], "\\"); - EXPECT(varList2D[1], "ok?"); - - CVarList2 varList2E("\\, , ok?", 0, ',', true, true); - EXPECT(varList2E[0], ","); - EXPECT(varList2E[1], "ok?"); - - CVarList2 varList2F("Hello, world\\\\, ok?", 0, ',', true, true); - EXPECT(varList2F[0], "Hello"); - EXPECT(varList2F[1], "world\\"); - EXPECT(varList2F[2], "ok?"); - - CVarList2 varList2G("Hello,\\, ok?", 0, ',', true, true); - EXPECT(varList2G[0], "Hello"); - EXPECT(varList2G[1], ", ok?"); - - CVarList2 varList2H("Hello,\\\\, ok?", 0, ',', true, true); - EXPECT(varList2H[0], "Hello"); - EXPECT(varList2H[1], "\\"); - EXPECT(varList2H[2], "ok?"); - - CVarList2 varList2I("Hello,\\, ok?", 0, ',', true, false); - EXPECT(varList2I[0], "Hello"); - EXPECT(varList2I[1], "\\"); - EXPECT(varList2I[2], "ok?"); - - std::string hello = "hello world!"; - replaceInString(hello, "hello", "hi"); - EXPECT(hello, "hi world!"); - - return ret; -} \ No newline at end of file