From 4914b4aff3692222dd9f75e1c40d31999a5a981a Mon Sep 17 00:00:00 2001 From: yemkareems Date: Thu, 28 Aug 2025 15:36:44 +0530 Subject: [PATCH 1/5] fix: added test to assert auto expire list preserve's version as per max_versions_per_interval Signed-off-by: yemkareems --- .../tests/GetAutoExpireListTest.php | 226 ++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 apps/files_versions/tests/GetAutoExpireListTest.php diff --git a/apps/files_versions/tests/GetAutoExpireListTest.php b/apps/files_versions/tests/GetAutoExpireListTest.php new file mode 100644 index 0000000000000..7ca97994061fb --- /dev/null +++ b/apps/files_versions/tests/GetAutoExpireListTest.php @@ -0,0 +1,226 @@ +getMethod('getAutoExpireList'); + $method->setAccessible(true); + + return $method->invokeArgs(null, [$time, $versions]); + } + + /** + * @since 32.0.0 + * @dataProvider provideBucketKeepsLatest + */ + public function testBucketKeepsLatest(int $offset1, int $offset2, int $size1, int $size2) { + $now = time(); + + $first = $now - $offset1; + $second = $first - $offset2; + + $versions = [ + $first => ['version' => $first, 'size' => $size1, 'path' => 'f'], + $second => ['version' => $second, 'size' => $size2, 'path' => 'f'], + ]; + + [$toDelete, $size] = self::callGetAutoExpireList($now, $versions); + + $deletedKeys = array_map('intval', array_keys($toDelete)); + + $this->assertEquals([$second], $deletedKeys, 'Older version was not deleted'); + $this->assertEquals($versions[$second]['size'], $size, 'Deleted size mismatch'); + } + + /** + * Provides test cases for different bucket intervals. + * Each case is [offset1 (age of first), offset2 (extra gap for second), size1, size2]. + * @return array + */ + public static function provideBucketKeepsLatest(): array { + $DAY = 24 * 60 * 60; + $WEEK = 7 * $DAY; + + return [ + 'minute' => [ + 8, // 8s old + 1, // 9s old → both in same 2s slot + 5, + 6, + ], + 'hour' => [ + 2 * 60, // 2 minutes old + 30, // 2m30s old → both in same 1m slot + 10, + 11, + ], + 'day' => [ + 5 * 3600, // 5 hours old + 1800, // 5.5h old → both in same 1h slot + 20, + 21, + ], + 'week' => [ + 2 * $DAY, // 2 days old + 6 * 3600, // 2.25 days old → both in same 1d slot + 40, + 41, + ], + 'month' => [ + 5 * $DAY, // 5 days old + 12 * 60 * 60, // 5.5 days old → both in same 1d slot + 30, + 31, + ], + 'year' => [ + 35 * $DAY, // 35 days old + 2 * $DAY, // 37 days old → both in same 1w slot + 42, + 43, + ], + 'beyond-year' => [ + 400 * $DAY, // ~13.3 months old + 5 * $DAY, // 405 days old → same 30d slot + 50, + 51, + ], + ]; + } + + /** + * @since 32.0.0 + */ + public function testFiveDaysOfVersionsEveryTenMinutes() { + $now = time(); + $versions = []; + + // Create one version every 10 minutes for 5 days + for ($i = 0; $i < (5 * 24 * 6); $i++) { + $ts = $now - ($i * 600); + $versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f']; + } + + [$toDelete, $size] = self::callGetAutoExpireList($now, $versions); + $retained = array_diff(array_keys($versions), array_keys($toDelete)); + + // Expect ~28-33 retained due to bucket rules + $this->assertGreaterThanOrEqual(28, count($retained)); + $this->assertLessThanOrEqual(33, count($retained)); + } + + /** + * @since 32.0.0 + */ + public function testThirtyDaysOfVersionsEveryTenMinutes() { + $now = time(); + $versions = []; + + // Create one version every 10 minutes for 30 days + for ($i = 0; $i < (30 * 24 * 6); $i++) { + $ts = $now - ($i * 600); + $versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f']; + } + + [$toDelete, $size] = self::callGetAutoExpireList($now, $versions); + $retained = array_diff(array_keys($versions), array_keys($toDelete)); + + // Expect ~54-60 retained (24 hours hourly + 29 daily + bucket overlap) + $this->assertGreaterThanOrEqual(54, count($retained)); + $this->assertLessThanOrEqual(60, count($retained)); + } + + /** + * @since 32.0.0 + */ + public function testYearOfVersionsEveryTenMinutes() { + $now = time(); + $versions = []; + + // Create one version every 10 minutes for 365 days + for ($i = 0; $i < (365 * 24 * 6); $i++) { + $ts = $now - ($i * 600); + $versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f']; + } + + [$toDelete, $size] = self::callGetAutoExpireList($now, $versions); + $retained = array_diff(array_keys($versions), array_keys($toDelete)); + + // Expect ~100-140 retained due to buckets (minute, hour, day, week, month) + $this->assertGreaterThanOrEqual(100, count($retained)); + $this->assertLessThanOrEqual(140, count($retained)); + } + + /** + * @since 32.0.0 + */ + public function testMoreThanAYearOfVersionsEveryTenMinutesWithDeletion() { + $now = time(); + $versions = []; + + // Define bucket steps (same as retention logic) + $buckets = [ + 1 => ['intervalEndsAfter' => 10, 'step' => 2], + 2 => ['intervalEndsAfter' => 60, 'step' => 10], + 3 => ['intervalEndsAfter' => 3600, 'step' => 60], + 4 => ['intervalEndsAfter' => 86400, 'step' => 3600], + 5 => ['intervalEndsAfter' => 2592000, 'step' => 86400], + 6 => ['intervalEndsAfter' => -1, 'step' => 604800], + ]; + + $lastBoundary = 0; + foreach ($buckets as $bucket) { + $intervalEnd = $bucket['intervalEndsAfter'] > 0 ? $bucket['intervalEndsAfter'] : 500 * 86400; + $step = $bucket['step']; + + for ($age = $lastBoundary; $age <= $intervalEnd; $age += $step) { + // Add multiple versions per step (3 versions spaced evenly within step) + for ($i = 0; $i < 3; $i++) { + $ts = $now - ($age + $i * floor($step / 3)); + $versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f']; + } + } + + $lastBoundary = $intervalEnd; + } + + [$toDelete, $size] = self::callGetAutoExpireList($now, $versions); + $retained = array_diff(array_keys($versions), array_keys($toDelete)); + + $lastBoundary = 0; + foreach ($buckets as $bucket) { + $intervalEnd = $bucket['intervalEndsAfter'] > 0 ? $bucket['intervalEndsAfter'] : PHP_INT_MAX; + + $bucketRetained = array_filter($retained, function ($ts) use ($now, $lastBoundary, $intervalEnd) { + $age = $now - $ts; + return $age >= $lastBoundary && $age <= $intervalEnd; + }); + + $this->assertGreaterThanOrEqual( + 1, + count($bucketRetained), + "Bucket ending at $intervalEnd seconds has " . count($bucketRetained) . ' retained, expected at least 1' + ); + + $lastBoundary = $intervalEnd; + } + + } + +} From fbd99cef8f694b3fed7003c057b77f33ab17c6ab Mon Sep 17 00:00:00 2001 From: yemkareems Date: Mon, 29 Sep 2025 17:03:05 +0530 Subject: [PATCH 2/5] fix: since removed and copy right text changed Signed-off-by: yemkareems --- .../tests/GetAutoExpireListTest.php | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/apps/files_versions/tests/GetAutoExpireListTest.php b/apps/files_versions/tests/GetAutoExpireListTest.php index 7ca97994061fb..b68c799bea277 100644 --- a/apps/files_versions/tests/GetAutoExpireListTest.php +++ b/apps/files_versions/tests/GetAutoExpireListTest.php @@ -3,8 +3,7 @@ declare(strict_types=1); /** * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors - * SPDX-FileCopyrightText: 2025 ownCloud, Inc. - * SPDX-License-Identifier: AGPL-3.0-only + * SPDX-License-Identifier: AGPL-3.0-or-later */ namespace OCA\Files_Versions\Tests; @@ -16,7 +15,6 @@ class GetAutoExpireListTest extends \Test\TestCase { /** * @throws ReflectionException - * @since 32.0.0 */ protected static function callGetAutoExpireList(int $time, array $versions): array { $ref = new ReflectionClass(Storage::class); @@ -27,7 +25,6 @@ protected static function callGetAutoExpireList(int $time, array $versions): arr } /** - * @since 32.0.0 * @dataProvider provideBucketKeepsLatest */ public function testBucketKeepsLatest(int $offset1, int $offset2, int $size1, int $size2) { @@ -104,9 +101,6 @@ public static function provideBucketKeepsLatest(): array { ]; } - /** - * @since 32.0.0 - */ public function testFiveDaysOfVersionsEveryTenMinutes() { $now = time(); $versions = []; @@ -125,9 +119,6 @@ public function testFiveDaysOfVersionsEveryTenMinutes() { $this->assertLessThanOrEqual(33, count($retained)); } - /** - * @since 32.0.0 - */ public function testThirtyDaysOfVersionsEveryTenMinutes() { $now = time(); $versions = []; @@ -146,9 +137,6 @@ public function testThirtyDaysOfVersionsEveryTenMinutes() { $this->assertLessThanOrEqual(60, count($retained)); } - /** - * @since 32.0.0 - */ public function testYearOfVersionsEveryTenMinutes() { $now = time(); $versions = []; @@ -167,9 +155,6 @@ public function testYearOfVersionsEveryTenMinutes() { $this->assertLessThanOrEqual(140, count($retained)); } - /** - * @since 32.0.0 - */ public function testMoreThanAYearOfVersionsEveryTenMinutesWithDeletion() { $now = time(); $versions = []; From bbbbc4de222c2188b5b71fea12b518f1023c8759 Mon Sep 17 00:00:00 2001 From: yemkareems Date: Wed, 24 Dec 2025 11:16:43 +0530 Subject: [PATCH 3/5] fix: offsets corrected, test added to have exact count when time is frozen and variable count when time is dynamic Signed-off-by: yemkareems --- .../tests/GetAutoExpireListTest.php | 256 ++++++++++-------- 1 file changed, 143 insertions(+), 113 deletions(-) diff --git a/apps/files_versions/tests/GetAutoExpireListTest.php b/apps/files_versions/tests/GetAutoExpireListTest.php index b68c799bea277..41e9e8c1e39b2 100644 --- a/apps/files_versions/tests/GetAutoExpireListTest.php +++ b/apps/files_versions/tests/GetAutoExpireListTest.php @@ -8,33 +8,46 @@ namespace OCA\Files_Versions\Tests; use OCA\Files_Versions\Storage; -use ReflectionClass; -use ReflectionException; -class GetAutoExpireListTest extends \Test\TestCase { +class GetAutoExpireListTest extends TestCase { /** - * @throws ReflectionException + * Frozen reference time for all tests */ - protected static function callGetAutoExpireList(int $time, array $versions): array { - $ref = new ReflectionClass(Storage::class); + private const NOW = 1600000000; + + /** + * Helper to call the private retention logic + * + * @param int $now + * @param array $versions + * @return array{array, int} + */ + private static function callGetAutoExpireList(int $now, array $versions): array { + $ref = new \ReflectionClass(Storage::class); $method = $ref->getMethod('getAutoExpireList'); - $method->setAccessible(true); - return $method->invokeArgs(null, [$time, $versions]); + /** @var array{array, int} */ + return $method->invoke(null, $now, $versions); } /** * @dataProvider provideBucketKeepsLatest */ - public function testBucketKeepsLatest(int $offset1, int $offset2, int $size1, int $size2) { + public function testBucketKeepsLatest(int $age1, int $age2, int $size1, int $size2): void { $now = time(); - $first = $now - $offset1; - $second = $first - $offset2; + $first = $now - $age1; + $second = $now - $age2; + + // Ensure first is newer than second + if ($first < $second) { + [$first, $second] = [$second, $first]; + [$size1, $size2] = [$size2, $size1]; + } $versions = [ - $first => ['version' => $first, 'size' => $size1, 'path' => 'f'], + $first => ['version' => $first, 'size' => $size1, 'path' => 'f'], $second => ['version' => $second, 'size' => $size2, 'path' => 'f'], ]; @@ -46,166 +59,183 @@ public function testBucketKeepsLatest(int $offset1, int $offset2, int $size1, in $this->assertEquals($versions[$second]['size'], $size, 'Deleted size mismatch'); } - /** - * Provides test cases for different bucket intervals. - * Each case is [offset1 (age of first), offset2 (extra gap for second), size1, size2]. - * @return array - */ public static function provideBucketKeepsLatest(): array { $DAY = 24 * 60 * 60; - $WEEK = 7 * $DAY; return [ 'minute' => [ - 8, // 8s old - 1, // 9s old → both in same 2s slot + 8, // 8s old + 9, // 9s old 5, 6, ], 'hour' => [ - 2 * 60, // 2 minutes old - 30, // 2m30s old → both in same 1m slot + 120, // 2 minutes old + 150, // 2m30s old 10, 11, ], 'day' => [ - 5 * 3600, // 5 hours old - 1800, // 5.5h old → both in same 1h slot + 5 * 3600, // 5 hours old + 5 * 3600 + 1800, // 5.5h old 20, 21, ], 'week' => [ - 2 * $DAY, // 2 days old - 6 * 3600, // 2.25 days old → both in same 1d slot + 2 * $DAY, // 2 days old + 2 * $DAY + 6 * 3600, // 2.25 days old 40, 41, ], 'month' => [ - 5 * $DAY, // 5 days old - 12 * 60 * 60, // 5.5 days old → both in same 1d slot + 5 * $DAY, // 5 days old + 5 * $DAY + 12 * 3600, // 5.5 days old 30, 31, ], 'year' => [ 35 * $DAY, // 35 days old - 2 * $DAY, // 37 days old → both in same 1w slot + 37 * $DAY, // 37 days old 42, 43, ], 'beyond-year' => [ - 400 * $DAY, // ~13.3 months old - 5 * $DAY, // 405 days old → same 30d slot + 400 * $DAY, // ~13.3 months old + 405 * $DAY, // ~13.5 months old 50, 51, ], ]; } - public function testFiveDaysOfVersionsEveryTenMinutes() { + /** + * @dataProvider provideVersionRetentionRanges + */ + public function testRetentionOverTimeEveryTenMinutes( + int $days, + int $expectedMin, + int $expectedMax + ): void { $now = time(); $versions = []; - // Create one version every 10 minutes for 5 days - for ($i = 0; $i < (5 * 24 * 6); $i++) { - $ts = $now - ($i * 600); - $versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f']; + // One version every 10 minutes + $interval = 600; // 10 minutes + $total = $days * 24 * 6; + + for ($i = 0; $i < $total; $i++) { + $ts = $now - ($i * $interval); + $versions[$ts] = [ + 'version' => $ts, + 'size' => 1, + 'path' => 'f', + ]; } [$toDelete, $size] = self::callGetAutoExpireList($now, $versions); - $retained = array_diff(array_keys($versions), array_keys($toDelete)); - - // Expect ~28-33 retained due to bucket rules - $this->assertGreaterThanOrEqual(28, count($retained)); - $this->assertLessThanOrEqual(33, count($retained)); - } - - public function testThirtyDaysOfVersionsEveryTenMinutes() { - $now = time(); - $versions = []; - // Create one version every 10 minutes for 30 days - for ($i = 0; $i < (30 * 24 * 6); $i++) { - $ts = $now - ($i * 600); - $versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f']; - } - - [$toDelete, $size] = self::callGetAutoExpireList($now, $versions); $retained = array_diff(array_keys($versions), array_keys($toDelete)); + $retainedCount = count($retained); + + $this->assertGreaterThanOrEqual( + $expectedMin, + $retainedCount, + "Too few versions retained for {$days} days" + ); + + $this->assertLessThanOrEqual( + $expectedMax, + $retainedCount, + "Too many versions retained for {$days} days" + ); + } - // Expect ~54-60 retained (24 hours hourly + 29 daily + bucket overlap) - $this->assertGreaterThanOrEqual(54, count($retained)); - $this->assertLessThanOrEqual(60, count($retained)); + public static function provideVersionRetentionRanges(): array { + return [ + '5 days' => [ + 5, + 28, + 33, + ], + '30 days' => [ + 30, + 54, + 60, + ], + '1 year' => [ + 365, + 100, + 140, + ], + ]; } - public function testYearOfVersionsEveryTenMinutes() { - $now = time(); + /** + * Exact deterministic retention count for evenly spaced versions. + * + * One version per hour, offset away from bucket edges. + * + * @dataProvider provideExactRetentionCounts + */ + public function testExactRetentionCounts( + int $days, + int $expectedRetained + ): void { + $now = self::NOW; $versions = []; - // Create one version every 10 minutes for 365 days - for ($i = 0; $i < (365 * 24 * 6); $i++) { - $ts = $now - ($i * 600); + // One version per hour, safely inside bucket slots + for ($i = 0; $i < $days * 24; $i++) { + $ts = $now - ($i * 3600) - 1; $versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f']; } - [$toDelete, $size] = self::callGetAutoExpireList($now, $versions); - $retained = array_diff(array_keys($versions), array_keys($toDelete)); + [$toDelete] = self::callGetAutoExpireList($now, $versions); + $retained = array_diff_key($versions, $toDelete); - // Expect ~100-140 retained due to buckets (minute, hour, day, week, month) - $this->assertGreaterThanOrEqual(100, count($retained)); - $this->assertLessThanOrEqual(140, count($retained)); + $this->assertSame( + $expectedRetained, + count($retained), + "Exact retention count mismatch for {$days} days" + ); } - public function testMoreThanAYearOfVersionsEveryTenMinutesWithDeletion() { - $now = time(); - $versions = []; - - // Define bucket steps (same as retention logic) - $buckets = [ - 1 => ['intervalEndsAfter' => 10, 'step' => 2], - 2 => ['intervalEndsAfter' => 60, 'step' => 10], - 3 => ['intervalEndsAfter' => 3600, 'step' => 60], - 4 => ['intervalEndsAfter' => 86400, 'step' => 3600], - 5 => ['intervalEndsAfter' => 2592000, 'step' => 86400], - 6 => ['intervalEndsAfter' => -1, 'step' => 604800], + /** + * @return array + */ + public static function provideExactRetentionCounts(): array { + return [ + 'five-days' => [ + 5, + self::expectedHourlyRetention(5), + ], + 'thirty-days' => [ + 30, + self::expectedHourlyRetention(30), + ], + 'one-year' => [ + 365, + self::expectedHourlyRetention(365), + ], + 'one-year-plus' => [ + 500, + self::expectedHourlyRetention(500), + ], ]; + } - $lastBoundary = 0; - foreach ($buckets as $bucket) { - $intervalEnd = $bucket['intervalEndsAfter'] > 0 ? $bucket['intervalEndsAfter'] : 500 * 86400; - $step = $bucket['step']; - - for ($age = $lastBoundary; $age <= $intervalEnd; $age += $step) { - // Add multiple versions per step (3 versions spaced evenly within step) - for ($i = 0; $i < 3; $i++) { - $ts = $now - ($age + $i * floor($step / 3)); - $versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f']; - } - } - - $lastBoundary = $intervalEnd; - } - - [$toDelete, $size] = self::callGetAutoExpireList($now, $versions); - $retained = array_diff(array_keys($versions), array_keys($toDelete)); - - $lastBoundary = 0; - foreach ($buckets as $bucket) { - $intervalEnd = $bucket['intervalEndsAfter'] > 0 ? $bucket['intervalEndsAfter'] : PHP_INT_MAX; - - $bucketRetained = array_filter($retained, function ($ts) use ($now, $lastBoundary, $intervalEnd) { - $age = $now - $ts; - return $age >= $lastBoundary && $age <= $intervalEnd; - }); + private static function expectedHourlyRetention(int $days): int { + // Hourly for first day + $hourly = min(24, $days * 24); - $this->assertGreaterThanOrEqual( - 1, - count($bucketRetained), - "Bucket ending at $intervalEnd seconds has " . count($bucketRetained) . ' retained, expected at least 1' - ); + // Daily from day 1 to day 30 + $dailyDays = max(0, min($days, 30) - 1); + $daily = $dailyDays; - $lastBoundary = $intervalEnd; - } + // Weekly beyond 30 days + $weeklyDays = max(0, $days - 30); + $weekly = intdiv($weeklyDays, 7) + ($weeklyDays > 0 ? 1 : 0); + return $hourly + $daily + $weekly; } - } From 674381e8c149f318452b41f1108e80d886c417b8 Mon Sep 17 00:00:00 2001 From: yemkareems Date: Wed, 24 Dec 2025 11:23:19 +0530 Subject: [PATCH 4/5] fix: php cs fix run Signed-off-by: yemkareems --- apps/files_versions/tests/GetAutoExpireListTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/files_versions/tests/GetAutoExpireListTest.php b/apps/files_versions/tests/GetAutoExpireListTest.php index 41e9e8c1e39b2..9629def09c864 100644 --- a/apps/files_versions/tests/GetAutoExpireListTest.php +++ b/apps/files_versions/tests/GetAutoExpireListTest.php @@ -19,7 +19,7 @@ class GetAutoExpireListTest extends TestCase { /** * Helper to call the private retention logic * - * @param int $now + * @param int $now * @param array $versions * @return array{array, int} */ @@ -37,7 +37,7 @@ private static function callGetAutoExpireList(int $now, array $versions): array public function testBucketKeepsLatest(int $age1, int $age2, int $size1, int $size2): void { $now = time(); - $first = $now - $age1; + $first = $now - $age1; $second = $now - $age2; // Ensure first is newer than second @@ -47,7 +47,7 @@ public function testBucketKeepsLatest(int $age1, int $age2, int $size1, int $siz } $versions = [ - $first => ['version' => $first, 'size' => $size1, 'path' => 'f'], + $first => ['version' => $first, 'size' => $size1, 'path' => 'f'], $second => ['version' => $second, 'size' => $size2, 'path' => 'f'], ]; @@ -114,7 +114,7 @@ public static function provideBucketKeepsLatest(): array { public function testRetentionOverTimeEveryTenMinutes( int $days, int $expectedMin, - int $expectedMax + int $expectedMax, ): void { $now = time(); $versions = []; @@ -179,7 +179,7 @@ public static function provideVersionRetentionRanges(): array { */ public function testExactRetentionCounts( int $days, - int $expectedRetained + int $expectedRetained, ): void { $now = self::NOW; $versions = []; From 9ed9a317f5a9afed2e9d6f674011dc09e941ac95 Mon Sep 17 00:00:00 2001 From: yemkareems Date: Wed, 24 Dec 2025 11:30:31 +0530 Subject: [PATCH 5/5] fix: added Test\TestCase namespace for failing tests Signed-off-by: yemkareems --- .../tests/GetAutoExpireListTest.php | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/apps/files_versions/tests/GetAutoExpireListTest.php b/apps/files_versions/tests/GetAutoExpireListTest.php index 9629def09c864..7d33a7b464f49 100644 --- a/apps/files_versions/tests/GetAutoExpireListTest.php +++ b/apps/files_versions/tests/GetAutoExpireListTest.php @@ -8,6 +8,7 @@ namespace OCA\Files_Versions\Tests; use OCA\Files_Versions\Storage; +use Test\TestCase; class GetAutoExpireListTest extends TestCase { @@ -31,9 +32,7 @@ private static function callGetAutoExpireList(int $now, array $versions): array return $method->invoke(null, $now, $versions); } - /** - * @dataProvider provideBucketKeepsLatest - */ + #[\PHPUnit\Framework\Attributes\DataProvider('provideBucketKeepsLatest')] public function testBucketKeepsLatest(int $age1, int $age2, int $size1, int $size2): void { $now = time(); @@ -108,9 +107,7 @@ public static function provideBucketKeepsLatest(): array { ]; } - /** - * @dataProvider provideVersionRetentionRanges - */ + #[\PHPUnit\Framework\Attributes\DataProvider('provideVersionRetentionRanges')] public function testRetentionOverTimeEveryTenMinutes( int $days, int $expectedMin, @@ -170,13 +167,7 @@ public static function provideVersionRetentionRanges(): array { ]; } - /** - * Exact deterministic retention count for evenly spaced versions. - * - * One version per hour, offset away from bucket edges. - * - * @dataProvider provideExactRetentionCounts - */ + #[\PHPUnit\Framework\Attributes\DataProvider('provideExactRetentionCounts')] public function testExactRetentionCounts( int $days, int $expectedRetained,