Skip to content

Commit bbbbc4d

Browse files
committed
fix: offsets corrected, test added to have exact count when time is frozen and variable count when time is dynamic
Signed-off-by: yemkareems <[email protected]>
1 parent 2ba3766 commit bbbbc4d

File tree

1 file changed

+143
-113
lines changed

1 file changed

+143
-113
lines changed

apps/files_versions/tests/GetAutoExpireListTest.php

Lines changed: 143 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,46 @@
88
namespace OCA\Files_Versions\Tests;
99

1010
use OCA\Files_Versions\Storage;
11-
use ReflectionClass;
12-
use ReflectionException;
1311

14-
class GetAutoExpireListTest extends \Test\TestCase {
12+
class GetAutoExpireListTest extends TestCase {
1513

1614
/**
17-
* @throws ReflectionException
15+
* Frozen reference time for all tests
1816
*/
19-
protected static function callGetAutoExpireList(int $time, array $versions): array {
20-
$ref = new ReflectionClass(Storage::class);
17+
private const NOW = 1600000000;
18+
19+
/**
20+
* Helper to call the private retention logic
21+
*
22+
* @param int $now
23+
* @param array $versions
24+
* @return array{array<int,array>, int}
25+
*/
26+
private static function callGetAutoExpireList(int $now, array $versions): array {
27+
$ref = new \ReflectionClass(Storage::class);
2128
$method = $ref->getMethod('getAutoExpireList');
22-
$method->setAccessible(true);
2329

24-
return $method->invokeArgs(null, [$time, $versions]);
30+
/** @var array{array<int,array>, int} */
31+
return $method->invoke(null, $now, $versions);
2532
}
2633

2734
/**
2835
* @dataProvider provideBucketKeepsLatest
2936
*/
30-
public function testBucketKeepsLatest(int $offset1, int $offset2, int $size1, int $size2) {
37+
public function testBucketKeepsLatest(int $age1, int $age2, int $size1, int $size2): void {
3138
$now = time();
3239

33-
$first = $now - $offset1;
34-
$second = $first - $offset2;
40+
$first = $now - $age1;
41+
$second = $now - $age2;
42+
43+
// Ensure first is newer than second
44+
if ($first < $second) {
45+
[$first, $second] = [$second, $first];
46+
[$size1, $size2] = [$size2, $size1];
47+
}
3548

3649
$versions = [
37-
$first => ['version' => $first, 'size' => $size1, 'path' => 'f'],
50+
$first => ['version' => $first, 'size' => $size1, 'path' => 'f'],
3851
$second => ['version' => $second, 'size' => $size2, 'path' => 'f'],
3952
];
4053

@@ -46,166 +59,183 @@ public function testBucketKeepsLatest(int $offset1, int $offset2, int $size1, in
4659
$this->assertEquals($versions[$second]['size'], $size, 'Deleted size mismatch');
4760
}
4861

49-
/**
50-
* Provides test cases for different bucket intervals.
51-
* Each case is [offset1 (age of first), offset2 (extra gap for second), size1, size2].
52-
* @return array<string, array{int,int,int,int}>
53-
*/
5462
public static function provideBucketKeepsLatest(): array {
5563
$DAY = 24 * 60 * 60;
56-
$WEEK = 7 * $DAY;
5764

5865
return [
5966
'minute' => [
60-
8, // 8s old
61-
1, // 9s old → both in same 2s slot
67+
8, // 8s old
68+
9, // 9s old
6269
5,
6370
6,
6471
],
6572
'hour' => [
66-
2 * 60, // 2 minutes old
67-
30, // 2m30s old → both in same 1m slot
73+
120, // 2 minutes old
74+
150, // 2m30s old
6875
10,
6976
11,
7077
],
7178
'day' => [
72-
5 * 3600, // 5 hours old
73-
1800, // 5.5h old → both in same 1h slot
79+
5 * 3600, // 5 hours old
80+
5 * 3600 + 1800, // 5.5h old
7481
20,
7582
21,
7683
],
7784
'week' => [
78-
2 * $DAY, // 2 days old
79-
6 * 3600, // 2.25 days old → both in same 1d slot
85+
2 * $DAY, // 2 days old
86+
2 * $DAY + 6 * 3600, // 2.25 days old
8087
40,
8188
41,
8289
],
8390
'month' => [
84-
5 * $DAY, // 5 days old
85-
12 * 60 * 60, // 5.5 days old → both in same 1d slot
91+
5 * $DAY, // 5 days old
92+
5 * $DAY + 12 * 3600, // 5.5 days old
8693
30,
8794
31,
8895
],
8996
'year' => [
9097
35 * $DAY, // 35 days old
91-
2 * $DAY, // 37 days old → both in same 1w slot
98+
37 * $DAY, // 37 days old
9299
42,
93100
43,
94101
],
95102
'beyond-year' => [
96-
400 * $DAY, // ~13.3 months old
97-
5 * $DAY, // 405 days old → same 30d slot
103+
400 * $DAY, // ~13.3 months old
104+
405 * $DAY, // ~13.5 months old
98105
50,
99106
51,
100107
],
101108
];
102109
}
103110

104-
public function testFiveDaysOfVersionsEveryTenMinutes() {
111+
/**
112+
* @dataProvider provideVersionRetentionRanges
113+
*/
114+
public function testRetentionOverTimeEveryTenMinutes(
115+
int $days,
116+
int $expectedMin,
117+
int $expectedMax
118+
): void {
105119
$now = time();
106120
$versions = [];
107121

108-
// Create one version every 10 minutes for 5 days
109-
for ($i = 0; $i < (5 * 24 * 6); $i++) {
110-
$ts = $now - ($i * 600);
111-
$versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f'];
122+
// One version every 10 minutes
123+
$interval = 600; // 10 minutes
124+
$total = $days * 24 * 6;
125+
126+
for ($i = 0; $i < $total; $i++) {
127+
$ts = $now - ($i * $interval);
128+
$versions[$ts] = [
129+
'version' => $ts,
130+
'size' => 1,
131+
'path' => 'f',
132+
];
112133
}
113134

114135
[$toDelete, $size] = self::callGetAutoExpireList($now, $versions);
115-
$retained = array_diff(array_keys($versions), array_keys($toDelete));
116-
117-
// Expect ~28-33 retained due to bucket rules
118-
$this->assertGreaterThanOrEqual(28, count($retained));
119-
$this->assertLessThanOrEqual(33, count($retained));
120-
}
121-
122-
public function testThirtyDaysOfVersionsEveryTenMinutes() {
123-
$now = time();
124-
$versions = [];
125136

126-
// Create one version every 10 minutes for 30 days
127-
for ($i = 0; $i < (30 * 24 * 6); $i++) {
128-
$ts = $now - ($i * 600);
129-
$versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f'];
130-
}
131-
132-
[$toDelete, $size] = self::callGetAutoExpireList($now, $versions);
133137
$retained = array_diff(array_keys($versions), array_keys($toDelete));
138+
$retainedCount = count($retained);
139+
140+
$this->assertGreaterThanOrEqual(
141+
$expectedMin,
142+
$retainedCount,
143+
"Too few versions retained for {$days} days"
144+
);
145+
146+
$this->assertLessThanOrEqual(
147+
$expectedMax,
148+
$retainedCount,
149+
"Too many versions retained for {$days} days"
150+
);
151+
}
134152

135-
// Expect ~54-60 retained (24 hours hourly + 29 daily + bucket overlap)
136-
$this->assertGreaterThanOrEqual(54, count($retained));
137-
$this->assertLessThanOrEqual(60, count($retained));
153+
public static function provideVersionRetentionRanges(): array {
154+
return [
155+
'5 days' => [
156+
5,
157+
28,
158+
33,
159+
],
160+
'30 days' => [
161+
30,
162+
54,
163+
60,
164+
],
165+
'1 year' => [
166+
365,
167+
100,
168+
140,
169+
],
170+
];
138171
}
139172

140-
public function testYearOfVersionsEveryTenMinutes() {
141-
$now = time();
173+
/**
174+
* Exact deterministic retention count for evenly spaced versions.
175+
*
176+
* One version per hour, offset away from bucket edges.
177+
*
178+
* @dataProvider provideExactRetentionCounts
179+
*/
180+
public function testExactRetentionCounts(
181+
int $days,
182+
int $expectedRetained
183+
): void {
184+
$now = self::NOW;
142185
$versions = [];
143186

144-
// Create one version every 10 minutes for 365 days
145-
for ($i = 0; $i < (365 * 24 * 6); $i++) {
146-
$ts = $now - ($i * 600);
187+
// One version per hour, safely inside bucket slots
188+
for ($i = 0; $i < $days * 24; $i++) {
189+
$ts = $now - ($i * 3600) - 1;
147190
$versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f'];
148191
}
149192

150-
[$toDelete, $size] = self::callGetAutoExpireList($now, $versions);
151-
$retained = array_diff(array_keys($versions), array_keys($toDelete));
193+
[$toDelete] = self::callGetAutoExpireList($now, $versions);
194+
$retained = array_diff_key($versions, $toDelete);
152195

153-
// Expect ~100-140 retained due to buckets (minute, hour, day, week, month)
154-
$this->assertGreaterThanOrEqual(100, count($retained));
155-
$this->assertLessThanOrEqual(140, count($retained));
196+
$this->assertSame(
197+
$expectedRetained,
198+
count($retained),
199+
"Exact retention count mismatch for {$days} days"
200+
);
156201
}
157202

158-
public function testMoreThanAYearOfVersionsEveryTenMinutesWithDeletion() {
159-
$now = time();
160-
$versions = [];
161-
162-
// Define bucket steps (same as retention logic)
163-
$buckets = [
164-
1 => ['intervalEndsAfter' => 10, 'step' => 2],
165-
2 => ['intervalEndsAfter' => 60, 'step' => 10],
166-
3 => ['intervalEndsAfter' => 3600, 'step' => 60],
167-
4 => ['intervalEndsAfter' => 86400, 'step' => 3600],
168-
5 => ['intervalEndsAfter' => 2592000, 'step' => 86400],
169-
6 => ['intervalEndsAfter' => -1, 'step' => 604800],
203+
/**
204+
* @return array<string, array{int,int}>
205+
*/
206+
public static function provideExactRetentionCounts(): array {
207+
return [
208+
'five-days' => [
209+
5,
210+
self::expectedHourlyRetention(5),
211+
],
212+
'thirty-days' => [
213+
30,
214+
self::expectedHourlyRetention(30),
215+
],
216+
'one-year' => [
217+
365,
218+
self::expectedHourlyRetention(365),
219+
],
220+
'one-year-plus' => [
221+
500,
222+
self::expectedHourlyRetention(500),
223+
],
170224
];
225+
}
171226

172-
$lastBoundary = 0;
173-
foreach ($buckets as $bucket) {
174-
$intervalEnd = $bucket['intervalEndsAfter'] > 0 ? $bucket['intervalEndsAfter'] : 500 * 86400;
175-
$step = $bucket['step'];
176-
177-
for ($age = $lastBoundary; $age <= $intervalEnd; $age += $step) {
178-
// Add multiple versions per step (3 versions spaced evenly within step)
179-
for ($i = 0; $i < 3; $i++) {
180-
$ts = $now - ($age + $i * floor($step / 3));
181-
$versions[$ts] = ['version' => $ts, 'size' => 1, 'path' => 'f'];
182-
}
183-
}
184-
185-
$lastBoundary = $intervalEnd;
186-
}
187-
188-
[$toDelete, $size] = self::callGetAutoExpireList($now, $versions);
189-
$retained = array_diff(array_keys($versions), array_keys($toDelete));
190-
191-
$lastBoundary = 0;
192-
foreach ($buckets as $bucket) {
193-
$intervalEnd = $bucket['intervalEndsAfter'] > 0 ? $bucket['intervalEndsAfter'] : PHP_INT_MAX;
194-
195-
$bucketRetained = array_filter($retained, function ($ts) use ($now, $lastBoundary, $intervalEnd) {
196-
$age = $now - $ts;
197-
return $age >= $lastBoundary && $age <= $intervalEnd;
198-
});
227+
private static function expectedHourlyRetention(int $days): int {
228+
// Hourly for first day
229+
$hourly = min(24, $days * 24);
199230

200-
$this->assertGreaterThanOrEqual(
201-
1,
202-
count($bucketRetained),
203-
"Bucket ending at $intervalEnd seconds has " . count($bucketRetained) . ' retained, expected at least 1'
204-
);
231+
// Daily from day 1 to day 30
232+
$dailyDays = max(0, min($days, 30) - 1);
233+
$daily = $dailyDays;
205234

206-
$lastBoundary = $intervalEnd;
207-
}
235+
// Weekly beyond 30 days
236+
$weeklyDays = max(0, $days - 30);
237+
$weekly = intdiv($weeklyDays, 7) + ($weeklyDays > 0 ? 1 : 0);
208238

239+
return $hourly + $daily + $weekly;
209240
}
210-
211241
}

0 commit comments

Comments
 (0)