tests: move tests out of source tree (#88)

This commit is contained in:
Vaxry 2025-11-21 16:27:29 +00:00 committed by GitHub
parent 671792bcfe
commit 2698ac1194
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 1283 additions and 1309 deletions

View file

@ -57,17 +57,17 @@ 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)
file(GLOB_RECURSE TESTFILES CONFIGURE_DEPENDS "tests/*.cpp")
add_executable(hyprutils_tests ${TESTFILES})
target_compile_options(hyprutils_tests PRIVATE --coverage)
target_link_options(hyprutils_tests PRIVATE --coverage)
target_include_directories(
hyprutils_inline_tests
hyprutils_tests
PUBLIC "./include"
PRIVATE "./src" "./src/include" "./protocols" "${CMAKE_BINARY_DIR}")
target_link_libraries(hyprutils_inline_tests PRIVATE GTest::gtest_main
target_link_libraries(hyprutils_tests PRIVATE hyprutils GTest::gtest_main
PkgConfig::deps)
gtest_discover_tests(hyprutils_inline_tests)
gtest_discover_tests(hyprutils_tests)
endif()
# Installation

View file

@ -99,404 +99,3 @@ const std::unordered_map<std::string, SP<CBezierCurve>>& CAnimationManager::getA
CWeakPointer<CAnimationManager::SAnimationManagerSignals> CAnimationManager::getSignals() const {
return m_events;
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
#include <hyprutils/animation/AnimationConfig.hpp>
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.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 <typename VarType>
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
template <typename VarType>
using PANIMVAR = SP<CAnimatedVariable<VarType>>;
template <typename VarType>
using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
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<CAnimatedVariable<int>*>(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<CAnimatedVariable<SomeTestType>*>(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 <typename VarType>
void createAnimation(const VarType& v, PANIMVAR<VarType>& av, const std::string& animationConfigName) {
constexpr const eAVTypes EAVTYPE = std::is_same_v<VarType, int> ? eAVTypes::INT : eAVTypes::TEST;
const auto PAV = makeShared<CGenericAnimatedVariable<VarType, EmtpyContext>>();
PAV->create(EAVTYPE, sc<CAnimationManager*>(this), PAV, v);
PAV->setConfig(animationTree.getConfig(animationConfigName));
av = std::move(PAV);
}
virtual void scheduleTick() {
;
}
virtual void onTicked() {
;
}
};
UP<CMyAnimationManager> 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<int> m_iA;
PANIMVAR<int> m_iB;
PANIMVAR<SomeTestType> m_iC;
};
static int config() {
pAnimationManager = makeUnique<CMyAnimationManager>();
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<CBaseAnimatedVariable> pav) { beginCallbackRan++; });
s.m_iA->setUpdateCallback([&updateCallbackRan](WP<CBaseAnimatedVariable> pav) { updateCallbackRan++; });
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> 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<PANIMVAR<int>> 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<CBaseAnimatedVariable> 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<CBaseAnimatedVariable> v) {
endCallbackRan++;
const auto PAV = dc<CAnimatedVariable<int>*>(v.lock().get());
*PAV = 10;
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> 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<int> 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<int> 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

View file

@ -105,84 +105,3 @@ float CBezierCurve::getYForPoint(float const& x) const {
const std::vector<Hyprutils::Math::Vector2D>& CBezierCurve::getControlPoints() const {
return m_vPoints;
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
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<Vector2D, 4> 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<float>::max())), true);
EXPECT_EQ(std::isfinite(curve.getYForPoint(-std::numeric_limits<float>::max())), true);
}
static void test_adjacent_baked_x_equal() {
// Curve with flat tail (X=1, Y=1)
CBezierCurve curve;
std::array<Vector2D, 4> 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<Vector2D, 4> 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

View file

@ -163,81 +163,3 @@ CI18nLocale CI18nEngine::getSystemLocale() {
return CI18nLocale(std::locale("").name());
} catch (...) { return CI18nLocale("en_US.UTF-8"); }
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
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");
engine.registerEntry("ts_TST", TXT_KEY_FALLBACK, "Hello {var1} world {var2}");
engine.registerEntry("am", TXT_KEY_FALLBACK, "Amongus!");
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!");
EXPECT_EQ(engine.localizeEntry("am_AM", TXT_KEY_FALLBACK, {}), "Amongus!");
// test weird translations
engine.registerEntry("ts", TXT_KEY_HELLO, "count}");
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "count}");
engine.registerEntry("ts", TXT_KEY_HELLO, "{count");
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "{count");
EXPECT_EQ(engine.localizeEntry("ts", 42069 /* invalid key */, {{"count", "1"}}), "");
EXPECT_EQ(engine.localizeEntry("ts_TST", TXT_KEY_FALLBACK, {{"var1", "hi"}, {"var2", "!"}}), "Hello hi world !");
}
#endif

View file

@ -13,4 +13,6 @@ namespace Hyprutils::I18n {
std::unordered_map<std::string, std::vector<SI18nTranslationEntry>> entries;
std::string fallbackLocale = "en_US";
};
std::string extractLocale(std::string locale);
};

View file

@ -1,8 +1,8 @@
#include <hyprutils/i18n/I18nEngine.hpp>
#include "I18nEngine.hpp"
using namespace Hyprutils::I18n;
static std::string extractLocale(std::string locale) {
std::string Hyprutils::I18n::extractLocale(std::string locale) {
// localeStr is very arbitrary... from my testing, it can be:
// en_US.UTF-8
// LC_CTYPE=en_US
@ -42,19 +42,3 @@ std::string CI18nLocale::stem() {
std::string CI18nLocale::full() {
return m_rawFullLocale;
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
#include <hyprutils/i18n/I18nEngine.hpp>
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

View file

@ -235,78 +235,3 @@ 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 <gtest/gtest.h>
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

View file

@ -153,26 +153,3 @@ 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 <gtest/gtest.h>
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<float, 9>{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

View file

@ -220,22 +220,3 @@ Vector2D Hyprutils::Math::CRegion::closestPoint(const Vector2D& vec) const {
return leader;
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
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

View file

@ -57,23 +57,3 @@ Vector2D Hyprutils::Math::Vector2D::transform(eTransform transform, const Vector
default: return *this;
}
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
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

View file

@ -84,49 +84,3 @@ bool CFileDescriptor::isReadable(int fd) {
return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN);
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
#include <sys/mman.h>
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

View file

@ -283,32 +283,3 @@ void Hyprutils::OS::CProcess::setStdoutFD(int fd) {
void Hyprutils::OS::CProcess::setStderrFD(int fd) {
m_impl->stderrFD = fd;
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
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

View file

@ -58,376 +58,3 @@ void Hyprutils::Signal::CSignalBase::registerStaticListenerInternal(std::functio
void Hyprutils::Signal::CSignal::emit(std::any data) {
CSignalT::emit(data);
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
#include <any>
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/signal/Listener.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <memory>
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<int>(d); });
listener->emit(1); // not a typo
EXPECT_EQ(data, 1);
}
static void legacyListeners() {
int data = 0;
CSignalT<> signal0;
CSignalT<int> signal1;
auto listener0 = signal0.registerListener([&](std::any d) { data += 1; });
auto listener1 = signal1.registerListener([&](std::any d) { data += std::any_cast<int>(d); });
signal0.registerStaticListener([&](void* o, std::any d) { data += 10; }, nullptr);
signal1.registerStaticListener([&](void* o, std::any d) { data += std::any_cast<int>(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<int> signal;
auto listener = signal.listen([&](int newData) { data = newData; });
signal.emit(1);
EXPECT_EQ(data, 1);
}
static void ignoreParams() {
int data = 0;
CSignalT<int> 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<int, int, int> 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<int&> signal;
auto l1 = signal.listen([&](int& v) { v += 1; });
auto l2 = signal.listen([&](int v) { count += v; });
signal.emit(data);
CSignalT<const int&> 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<int&, const int&> 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<CCopyCounter> 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<int> sig;
CSignalT<int> 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<CSignalT<>>();
// 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<CSignalT<>>();
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<CSignalT<>>();
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<int> signal;
signal.listenStatic([&](int newData) { data = newData; });
signal.emit(1);
EXPECT_EQ(data, 1);
}
static void staticListenerDestroy() {
int count = 0;
auto signal = makeShared<CSignalT<>>();
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

View file

@ -36,19 +36,3 @@ std::string CConstVarList::join(const std::string& joiner, size_t from, size_t t
return rolling;
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
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

View file

@ -88,38 +88,3 @@ void Hyprutils::String::replaceInString(std::string& string, const std::string&
pos += to.length();
}
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
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

View file

@ -36,15 +36,3 @@ std::string Hyprutils::String::CVarList::join(const std::string& joiner, size_t
return rolling;
}
#ifdef HU_UNIT_TESTS
#include <gtest/gtest.h>
TEST(String, varlist) {
CVarList list("hello world!", 0, 's', true);
EXPECT_EQ(list[0], "hello");
EXPECT_EQ(list[1], "world!");
}
#endif

View file

@ -111,71 +111,3 @@ 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 <gtest/gtest.h>
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

View file

@ -0,0 +1,397 @@
#include <gtest/gtest.h>
#include <hyprutils/animation/AnimationConfig.hpp>
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/UniquePtr.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 <typename VarType>
using CAnimatedVariable = CGenericAnimatedVariable<VarType, EmtpyContext>;
template <typename VarType>
using PANIMVAR = SP<CAnimatedVariable<VarType>>;
template <typename VarType>
using PANIMVARREF = WP<CAnimatedVariable<VarType>>;
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<CAnimatedVariable<int>*>(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<CAnimatedVariable<SomeTestType>*>(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 <typename VarType>
void createAnimation(const VarType& v, PANIMVAR<VarType>& av, const std::string& animationConfigName) {
constexpr const eAVTypes EAVTYPE = std::is_same_v<VarType, int> ? eAVTypes::INT : eAVTypes::TEST;
const auto PAV = makeShared<CGenericAnimatedVariable<VarType, EmtpyContext>>();
PAV->create(EAVTYPE, sc<CAnimationManager*>(this), PAV, v);
PAV->setConfig(animationTree.getConfig(animationConfigName));
av = std::move(PAV);
}
virtual void scheduleTick() {
;
}
virtual void onTicked() {
;
}
};
UP<CMyAnimationManager> 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<int> m_iA;
PANIMVAR<int> m_iB;
PANIMVAR<SomeTestType> m_iC;
};
static int config() {
pAnimationManager = makeUnique<CMyAnimationManager>();
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<CBaseAnimatedVariable> pav) { beginCallbackRan++; });
s.m_iA->setUpdateCallback([&updateCallbackRan](WP<CBaseAnimatedVariable> pav) { updateCallbackRan++; });
s.m_iA->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> 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<PANIMVAR<int>> 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<CBaseAnimatedVariable> 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<CBaseAnimatedVariable> v) {
endCallbackRan++;
const auto PAV = dc<CAnimatedVariable<int>*>(v.lock().get());
*PAV = 10;
PAV->setCallbackOnEnd([&endCallbackRan](WP<CBaseAnimatedVariable> 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<int> 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<int> 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);
}

View file

@ -0,0 +1,79 @@
#include <cmath>
#include <hyprutils/animation/BezierCurve.hpp>
#include <gtest/gtest.h>
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<Vector2D, 4> 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<float>::max())), true);
EXPECT_EQ(std::isfinite(curve.getYForPoint(-std::numeric_limits<float>::max())), true);
}
static void test_adjacent_baked_x_equal() {
// Curve with flat tail (X=1, Y=1)
CBezierCurve curve;
std::array<Vector2D, 4> 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<Vector2D, 4> 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();
}

78
tests/i18n/Engine.cpp Normal file
View file

@ -0,0 +1,78 @@
#include <i18n/I18nEngine.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::I18n;
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");
engine.registerEntry("ts_TST", TXT_KEY_FALLBACK, "Hello {var1} world {var2}");
engine.registerEntry("am", TXT_KEY_FALLBACK, "Amongus!");
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!");
EXPECT_EQ(engine.localizeEntry("am_AM", TXT_KEY_FALLBACK, {}), "Amongus!");
// test weird translations
engine.registerEntry("ts", TXT_KEY_HELLO, "count}");
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "count}");
engine.registerEntry("ts", TXT_KEY_HELLO, "{count");
EXPECT_EQ(engine.localizeEntry("ts", TXT_KEY_HELLO, {{"count", "1"}}), "{count");
EXPECT_EQ(engine.localizeEntry("ts", 42069 /* invalid key */, {{"count", "1"}}), "");
EXPECT_EQ(engine.localizeEntry("ts_TST", TXT_KEY_FALLBACK, {{"var1", "hi"}, {"var2", "!"}}), "Hello hi world !");
}

11
tests/i18n/Locale.cpp Normal file
View file

@ -0,0 +1,11 @@
#include <gtest/gtest.h>
#include <i18n/I18nEngine.hpp>
using namespace Hyprutils::I18n;
TEST(I18n, Locale) {
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");
}

73
tests/math/Box.cpp Normal file
View file

@ -0,0 +1,73 @@
#include <hyprutils/math/Box.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
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);
}
}

23
tests/math/Mat3x3.cpp Normal file
View file

@ -0,0 +1,23 @@
#include <hyprutils/math/Mat3x3.hpp>
#include <hyprutils/math/Box.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
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<float, 9>{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);
}

18
tests/math/Region.cpp Normal file
View file

@ -0,0 +1,18 @@
#include <hyprutils/math/Region.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
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));
}

19
tests/math/Vector2D.cpp Normal file
View file

@ -0,0 +1,19 @@
#include <hyprutils/math/Vector2D.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::Math;
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));
}

View file

@ -1,16 +1,15 @@
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprutils/memory/Atomic.hpp>
#include <hyprutils/memory/Casts.hpp>
using namespace Hyprutils::Memory;
#ifdef HU_UNIT_TESTS
#include <hyprutils/memory/SharedPtr.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <gtest/gtest.h>
#include <chrono>
#include <thread>
#include <vector>
using namespace Hyprutils::Memory;
#define SP CSharedPointer
#define WP CWeakPointer
#define UP CUniquePointer
@ -178,5 +177,3 @@ TEST(Memory, memory) {
testAtomicImpl();
}
#endif

45
tests/os/Fd.cpp Normal file
View file

@ -0,0 +1,45 @@
#include <hyprutils/os/FileDescriptor.hpp>
#include <gtest/gtest.h>
#include <sys/mman.h>
using namespace Hyprutils::OS;
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());
}

28
tests/os/Process.cpp Normal file
View file

@ -0,0 +1,28 @@
#include <hyprutils/os/Process.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::OS;
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);
}

369
tests/signal/Signal.cpp Normal file
View file

@ -0,0 +1,369 @@
#include <gtest/gtest.h>
#include <any>
#include <hyprutils/signal/Signal.hpp>
#include <hyprutils/signal/Listener.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#include <memory>
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<int>(d); });
listener->emit(1); // not a typo
EXPECT_EQ(data, 1);
}
static void legacyListeners() {
int data = 0;
CSignalT<> signal0;
CSignalT<int> signal1;
auto listener0 = signal0.registerListener([&](std::any d) { data += 1; });
auto listener1 = signal1.registerListener([&](std::any d) { data += std::any_cast<int>(d); });
signal0.registerStaticListener([&](void* o, std::any d) { data += 10; }, nullptr);
signal1.registerStaticListener([&](void* o, std::any d) { data += std::any_cast<int>(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<int> signal;
auto listener = signal.listen([&](int newData) { data = newData; });
signal.emit(1);
EXPECT_EQ(data, 1);
}
static void ignoreParams() {
int data = 0;
CSignalT<int> 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<int, int, int> 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<int&> signal;
auto l1 = signal.listen([&](int& v) { v += 1; });
auto l2 = signal.listen([&](int v) { count += v; });
signal.emit(data);
CSignalT<const int&> 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<int&, const int&> 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<CCopyCounter> 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<int> sig;
CSignalT<int> 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<CSignalT<>>();
// 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<CSignalT<>>();
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<CSignalT<>>();
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<int> signal;
signal.listenStatic([&](int newData) { data = newData; });
signal.emit(1);
EXPECT_EQ(data, 1);
}
static void staticListenerDestroy() {
int count = 0;
auto signal = makeShared<CSignalT<>>();
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();
}

View file

@ -0,0 +1,15 @@
#include <hyprutils/string/ConstVarList.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
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");
}

34
tests/string/String.cpp Normal file
View file

@ -0,0 +1,34 @@
#include <hyprutils/string/String.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
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);
}

11
tests/string/VarList.cpp Normal file
View file

@ -0,0 +1,11 @@
#include <hyprutils/string/VarList.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
TEST(String, varlist) {
CVarList list("hello world!", 0, 's', true);
EXPECT_EQ(list[0], "hello");
EXPECT_EQ(list[1], "world!");
}

67
tests/string/VarList2.cpp Normal file
View file

@ -0,0 +1,67 @@
#include <hyprutils/string/VarList2.hpp>
#include <gtest/gtest.h>
using namespace Hyprutils::String;
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");
}