summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorIoannis Tzavaras <tzavaras.jannis@gmail.com>2026-04-05 16:47:58 +0300
committerGitHub <noreply@github.com>2026-04-05 14:47:58 +0100
commitd7edc50eb441b0f90198c431f76030ebb90cc04d (patch)
treec6b13075662278f06cc84a2805195e0784607e1a /tests
parent3af9b108909ee1e138cb8b3b6659563ab763e038 (diff)
tests: add unit tests for CDamageRing (#13995)
Adds GTest unit tests for CDamageRing in src/helpers/DamageRing.hpp. Covers: - setSize: initial full damage, same-size no-op, resize re-damage, zero-size - damage: in-bounds accept, empty/out-of-bounds reject, partial clip - damageEntire: normal path and zero-size no-op - rotate: clears current, preserves as previous, wraps beyond ring length - getBufferDamage: age<=0 and age>ring return full screen, age=1 current only, age=2 accumulates previous, full ring accumulation, coalescing fallback, empty ring returns empty - hasChanged: false initially, true after damage/damageEntire, false after rotate, false for out-of-bounds damage 27 new test cases, all passing.
Diffstat (limited to 'tests')
-rw-r--r--tests/helpers/DamageRing.cpp313
1 files changed, 313 insertions, 0 deletions
diff --git a/tests/helpers/DamageRing.cpp b/tests/helpers/DamageRing.cpp
new file mode 100644
index 00000000..c9f1653f
--- /dev/null
+++ b/tests/helpers/DamageRing.cpp
@@ -0,0 +1,313 @@
+#include <helpers/DamageRing.hpp>
+
+#include <gtest/gtest.h>
+
+// --- setSize ---
+
+TEST(DamageRing, setSizeMarksDamageEntire) {
+ CDamageRing ring;
+ ring.setSize({100, 200});
+
+ // After setSize the entire screen should be damaged
+ EXPECT_TRUE(ring.hasChanged());
+}
+
+TEST(DamageRing, setSizeSameSizeNoChange) {
+ CDamageRing ring;
+ ring.setSize({100, 200});
+ ring.rotate(); // clear current damage
+
+ // Setting same size again must not re-damage
+ ring.setSize({100, 200});
+ EXPECT_FALSE(ring.hasChanged());
+}
+
+TEST(DamageRing, setSizeNewSizeReDamages) {
+ CDamageRing ring;
+ ring.setSize({100, 200});
+ ring.rotate(); // clear current damage
+
+ ring.setSize({300, 400});
+ EXPECT_TRUE(ring.hasChanged());
+}
+
+TEST(DamageRing, setSizeZeroSize) {
+ CDamageRing ring;
+ ring.setSize({0, 0});
+
+ // Zero-size screen: damageEntire covers a 0x0 box, which is empty
+ EXPECT_FALSE(ring.hasChanged());
+}
+
+// --- damage ---
+
+TEST(DamageRing, damageReturnsTrueForRegionInsideSize) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+ ring.rotate(); // clear current damage
+
+ CRegion rg(10, 10, 50, 50);
+ EXPECT_TRUE(ring.damage(rg));
+}
+
+TEST(DamageRing, damageReturnsFalseForEmptyRegion) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+ ring.rotate();
+
+ CRegion empty;
+ EXPECT_FALSE(ring.damage(empty));
+}
+
+TEST(DamageRing, damageReturnsFalseForRegionOutsideSize) {
+ CDamageRing ring;
+ ring.setSize({100, 100});
+ ring.rotate();
+
+ // Region entirely outside the screen bounds
+ CRegion outside(200, 200, 50, 50);
+ EXPECT_FALSE(ring.damage(outside));
+}
+
+TEST(DamageRing, damageClipsRegionToSize) {
+ CDamageRing ring;
+ ring.setSize({100, 100});
+ ring.rotate();
+
+ // Region that partially overlaps the screen
+ CRegion partial(50, 50, 200, 200);
+ EXPECT_TRUE(ring.damage(partial));
+ EXPECT_TRUE(ring.hasChanged());
+}
+
+TEST(DamageRing, damageSetsHasChanged) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+ ring.rotate();
+
+ EXPECT_FALSE(ring.hasChanged());
+
+ CRegion rg(0, 0, 10, 10);
+ ring.damage(rg);
+ EXPECT_TRUE(ring.hasChanged());
+}
+
+// --- damageEntire ---
+
+TEST(DamageRing, damageEntireSetsHasChanged) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+ ring.rotate();
+
+ EXPECT_FALSE(ring.hasChanged());
+ ring.damageEntire();
+ EXPECT_TRUE(ring.hasChanged());
+}
+
+TEST(DamageRing, damageEntireOnZeroSizeDoesNotSetHasChanged) {
+ CDamageRing ring;
+ ring.setSize({0, 0});
+ ring.rotate();
+
+ ring.damageEntire();
+ EXPECT_FALSE(ring.hasChanged());
+}
+
+// --- rotate ---
+
+TEST(DamageRing, rotateClearsCurrentDamage) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+ // setSize already damaged; rotate should clear it
+ ring.rotate();
+ EXPECT_FALSE(ring.hasChanged());
+}
+
+TEST(DamageRing, rotatePreservesCurrentAsPreviousForNextFrame) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+ ring.rotate(); // clear initial damage
+
+ CRegion rg(0, 0, 50, 50);
+ ring.damage(rg);
+ ring.rotate(); // the damage just applied becomes "previous[0]"
+
+ // With age=2 we should see that previous damage accumulated
+ CRegion bufDamage = ring.getBufferDamage(2);
+ EXPECT_FALSE(bufDamage.empty());
+}
+
+TEST(DamageRing, rotateMultipleTimes) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+
+ // Rotate more times than the ring length — must not crash
+ for (int i = 0; i < DAMAGE_RING_PREVIOUS_LEN + 5; ++i) {
+ ring.damageEntire();
+ ring.rotate();
+ }
+ EXPECT_FALSE(ring.hasChanged());
+}
+
+// --- getBufferDamage ---
+
+TEST(DamageRing, getBufferDamageAge0ReturnsFullScreen) {
+ CDamageRing ring;
+ ring.setSize({100, 100});
+ ring.rotate();
+
+ // age <= 0 returns full screen
+ CRegion full = ring.getBufferDamage(0);
+ EXPECT_FALSE(full.empty());
+}
+
+TEST(DamageRing, getBufferDamageNegativeAgeReturnsFullScreen) {
+ CDamageRing ring;
+ ring.setSize({100, 100});
+ ring.rotate();
+
+ CRegion full = ring.getBufferDamage(-1);
+ EXPECT_FALSE(full.empty());
+}
+
+TEST(DamageRing, getBufferDamageExceedsRingLengthReturnsFullScreen) {
+ CDamageRing ring;
+ ring.setSize({100, 100});
+ ring.rotate();
+
+ // age > DAMAGE_RING_PREVIOUS_LEN + 1 returns full screen
+ CRegion full = ring.getBufferDamage(DAMAGE_RING_PREVIOUS_LEN + 2);
+ EXPECT_FALSE(full.empty());
+}
+
+TEST(DamageRing, getBufferDamageAge1ReturnCurrentOnly) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+ ring.rotate();
+
+ EXPECT_TRUE(ring.getBufferDamage(1).empty());
+
+ CRegion rg(10, 10, 20, 20);
+ ring.damage(rg);
+ EXPECT_FALSE(ring.getBufferDamage(1).empty());
+}
+
+TEST(DamageRing, getBufferDamageAge2AccumulatesOnePreviousFrame) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+ ring.rotate();
+
+ // Frame N: damage a region, then rotate
+ CRegion rg(0, 0, 30, 30);
+ ring.damage(rg);
+ ring.rotate();
+
+ // Frame N+1: no new damage
+ // age=2 should include previous frame's damage
+ CRegion buf = ring.getBufferDamage(2);
+ EXPECT_FALSE(buf.empty());
+}
+
+TEST(DamageRing, getBufferDamageAccumulatesUpToRingLength) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+ ring.rotate();
+
+ // Fill the ring with damage then check full accumulation
+ for (int i = 0; i < DAMAGE_RING_PREVIOUS_LEN; ++i) {
+ CRegion rg(i * 10, 0, 5, 5);
+ ring.damage(rg);
+ ring.rotate();
+ }
+
+ // age = DAMAGE_RING_PREVIOUS_LEN + 1 accumulates all stored frames
+ CRegion buf = ring.getBufferDamage(DAMAGE_RING_PREVIOUS_LEN + 1);
+ EXPECT_FALSE(buf.empty());
+}
+
+TEST(DamageRing, getBufferDamageCoalescesWhenTooManyRects) {
+ CDamageRing ring;
+ ring.setSize({500, 500});
+ ring.rotate();
+
+ // Add many non-overlapping rects across several frames so that
+ // accumulation produces more than 8 rectangles, triggering the
+ // getExtents() fallback path.
+ int x = 0;
+ for (int frame = 0; frame < DAMAGE_RING_PREVIOUS_LEN; ++frame) {
+ for (int r = 0; r < 4; ++r) {
+ ring.damage(CRegion(x, 0, 5, 5));
+ x += 10;
+ }
+ ring.rotate();
+ }
+
+ // Current frame damage
+ ring.damage(CRegion(x, 0, 5, 5));
+
+ // age = DAMAGE_RING_PREVIOUS_LEN + 1 accumulates all frames
+ CRegion buf = ring.getBufferDamage(DAMAGE_RING_PREVIOUS_LEN + 1);
+ EXPECT_FALSE(buf.empty());
+
+ // The result should be coalesced into a single extents rect (<=1 rect)
+ EXPECT_LE(buf.getRects().size(), 1);
+}
+
+TEST(DamageRing, getBufferDamageEmptyRingReturnsEmptyForValidAge) {
+ CDamageRing ring;
+ ring.setSize({200, 200});
+
+ // Rotate enough times to clear all previous slots
+ for (int i = 0; i < DAMAGE_RING_PREVIOUS_LEN + 1; ++i)
+ ring.rotate();
+
+ // No damage in any slot
+ CRegion buf = ring.getBufferDamage(1);
+ EXPECT_TRUE(buf.empty());
+}
+
+// --- hasChanged ---
+
+TEST(DamageRing, hasChangedFalseInitially) {
+ CDamageRing ring;
+ // No size set, no damage applied
+ EXPECT_FALSE(ring.hasChanged());
+}
+
+TEST(DamageRing, hasChangedTrueAfterDamage) {
+ CDamageRing ring;
+ ring.setSize({100, 100});
+ ring.rotate();
+
+ CRegion rg(0, 0, 10, 10);
+ ring.damage(rg);
+ EXPECT_TRUE(ring.hasChanged());
+}
+
+TEST(DamageRing, hasChangedFalseAfterRotate) {
+ CDamageRing ring;
+ ring.setSize({100, 100});
+ // setSize caused damage; rotate clears it
+ ring.rotate();
+ EXPECT_FALSE(ring.hasChanged());
+}
+
+TEST(DamageRing, hasChangedTrueAfterDamageEntire) {
+ CDamageRing ring;
+ ring.setSize({100, 100});
+ ring.rotate();
+
+ ring.damageEntire();
+ EXPECT_TRUE(ring.hasChanged());
+}
+
+TEST(DamageRing, hasChangedFalseAfterDamageOutsideBounds) {
+ CDamageRing ring;
+ ring.setSize({100, 100});
+ ring.rotate();
+
+ // Damage entirely outside the screen must not change state
+ CRegion outside(500, 500, 10, 10);
+ ring.damage(outside);
+ EXPECT_FALSE(ring.hasChanged());
+}