Passed
Push — master ( 4b1455...e368f6 )
by Alexander
07:30
created

FileCacheTest.php$0 ➔ testDirectoryLevel()   A

Complexity

Conditions 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 13
rs 9.8333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Cache\File\Tests;
6
7
require_once __DIR__ . '/MockHelper.php';
8
9
use DateInterval;
10
use phpmock\phpunit\PHPMock;
11
use Psr\SimpleCache\CacheInterface;
12
use Psr\SimpleCache\InvalidArgumentException;
13
use ReflectionException;
14
use Yiisoft\Cache\File\FileCache;
15
use Yiisoft\Cache\File\MockHelper;
16
17
class FileCacheTest extends TestCase
18
{
19
    use PHPMock;
20
21
    protected const CACHE_DIRECTORY = __DIR__ . '/runtime/cache';
22
23
    protected function tearDown(): void
24
    {
25
        MockHelper::$time = null;
26
    }
27
28
    protected function createCacheInstance(): CacheInterface
29
    {
30
        return new FileCache(static::CACHE_DIRECTORY);
31
    }
32
33
    /**
34
     * @dataProvider dataProvider
35
     * @param $key
36
     * @param $value
37
     * @throws InvalidArgumentException
38
     */
39
    public function testSet($key, $value): void
40
    {
41
        $cache = $this->createCacheInstance();
42
        $cache->clear();
43
44
        for ($i = 0; $i < 2; $i++) {
45
            $this->assertTrue($cache->set($key, $value));
46
        }
47
    }
48
49
    /**
50
     * @dataProvider dataProvider
51
     * @param $key
52
     * @param $value
53
     * @throws InvalidArgumentException
54
     */
55
    public function testGet($key, $value): void
56
    {
57
        $cache = $this->createCacheInstance();
58
        $cache->clear();
59
60
        $cache->set($key, $value);
61
        $valueFromCache = $cache->get($key, 'default');
62
63
        $this->assertSameExceptObject($value, $valueFromCache);
64
    }
65
66
    /**
67
     * @dataProvider dataProvider
68
     * @param $key
69
     * @param $value
70
     * @throws InvalidArgumentException
71
     */
72
    public function testValueInCacheCannotBeChanged($key, $value): void
73
    {
74
        $cache = $this->createCacheInstance();
75
        $cache->clear();
76
77
        $cache->set($key, $value);
78
        $valueFromCache = $cache->get($key, 'default');
79
80
        $this->assertSameExceptObject($value, $valueFromCache);
81
82
        if (is_object($value)) {
83
            $originalValue = clone $value;
84
            $valueFromCache->test_field = 'changed';
85
            $value->test_field = 'changed';
86
            $valueFromCacheNew = $cache->get($key, 'default');
87
            $this->assertSameExceptObject($originalValue, $valueFromCacheNew);
88
        }
89
    }
90
91
    /**
92
     * @dataProvider dataProvider
93
     * @param $key
94
     * @param $value
95
     * @throws InvalidArgumentException
96
     */
97
    public function testHas($key, $value): void
98
    {
99
        $cache = $this->createCacheInstance();
100
        $cache->clear();
101
102
        $cache->set($key, $value);
103
104
        $this->assertTrue($cache->has($key));
105
        // check whether exists affects the value
106
        $this->assertSameExceptObject($value, $cache->get($key));
107
108
        $this->assertTrue($cache->has($key));
109
        $this->assertFalse($cache->has('not_exists'));
110
    }
111
112
    public function testGetNonExistent(): void
113
    {
114
        $cache = $this->createCacheInstance();
115
        $cache->clear();
116
117
        $this->assertNull($cache->get('non_existent_key'));
118
    }
119
120
    /**
121
     * @dataProvider dataProvider
122
     * @param $key
123
     * @param $value
124
     * @throws InvalidArgumentException
125
     */
126
    public function testDelete($key, $value): void
127
    {
128
        $cache = $this->createCacheInstance();
129
        $cache->clear();
130
131
        $cache->set($key, $value);
132
133
        $this->assertSameExceptObject($value, $cache->get($key));
134
        $this->assertTrue($cache->delete($key));
135
        $this->assertNull($cache->get($key));
136
    }
137
138
    /**
139
     * @dataProvider dataProvider
140
     * @param $key
141
     * @param $value
142
     * @throws InvalidArgumentException
143
     */
144
    public function testClear($key, $value): void
145
    {
146
        $cache = $this->createCacheInstance();
147
        $cache = $this->prepare($cache);
148
149
        $this->assertTrue($cache->clear());
150
        $this->assertNull($cache->get($key));
151
    }
152
153
    /**
154
     * @dataProvider dataProviderSetMultiple
155
     * @param int|null $ttl
156
     * @throws InvalidArgumentException
157
     */
158
    public function testSetMultiple(?int $ttl): void
159
    {
160
        $cache = $this->createCacheInstance();
161
        $cache->clear();
162
163
        $data = $this->getDataProviderData();
164
165
        $cache->setMultiple($data, $ttl);
166
167
        foreach ($data as $key => $value) {
168
            $this->assertSameExceptObject($value, $cache->get((string)$key));
169
        }
170
    }
171
172
    /**
173
     * @return array testing multiSet with and without expiry
174
     */
175
    public function dataProviderSetMultiple(): array
176
    {
177
        return [
178
            [null],
179
            [2],
180
        ];
181
    }
182
183
    public function testGetMultiple(): void
184
    {
185
        $cache = $this->createCacheInstance();
186
        $cache->clear();
187
188
        $data = $this->getDataProviderData();
189
        $keys = array_map('strval', array_keys($data));
190
191
        $cache->setMultiple($data);
192
193
        $this->assertSameExceptObject($data, $cache->getMultiple($keys));
194
    }
195
196
    public function testDeleteMultiple(): void
197
    {
198
        $cache = $this->createCacheInstance();
199
        $cache->clear();
200
201
        $data = $this->getDataProviderData();
202
        $keys = array_map('strval', array_keys($data));
203
204
        $cache->setMultiple($data);
205
206
        $this->assertSameExceptObject($data, $cache->getMultiple($keys));
207
208
        $cache->deleteMultiple($keys);
209
210
        $emptyData = array_map(static function ($v) {
211
            return null;
212
        }, $data);
213
214
        $this->assertSameExceptObject($emptyData, $cache->getMultiple($keys));
215
    }
216
217
    public function testZeroAndNegativeTtl(): void
218
    {
219
        $cache = $this->createCacheInstance();
220
        $cache->clear();
221
        $cache->setMultiple([
222
            'a' => 1,
223
            'b' => 2,
224
        ]);
225
226
        $this->assertTrue($cache->has('a'));
227
        $this->assertTrue($cache->has('b'));
228
229
        $cache->set('a', 11, -1);
230
231
        $this->assertFalse($cache->has('a'));
232
233
        $cache->set('b', 22, 0);
234
235
        $this->assertFalse($cache->has('b'));
236
    }
237
238
    /**
239
     * @dataProvider dataProviderNormalizeTtl
240
     * @param mixed $ttl
241
     * @param mixed $expectedResult
242
     * @throws ReflectionException
243
     */
244
    public function testNormalizeTtl($ttl, $expectedResult): void
245
    {
246
        $cache = new FileCache(static::CACHE_DIRECTORY);
247
        $this->assertSameExceptObject($expectedResult, $this->invokeMethod($cache, 'normalizeTtl', [$ttl]));
248
    }
249
250
    /**
251
     * Data provider for {@see testNormalizeTtl()}
252
     * @return array test data
253
     *
254
     * @throws \Exception
255
     */
256
    public function dataProviderNormalizeTtl(): array
257
    {
258
        return [
259
            [123, 123],
260
            ['123', 123],
261
            [null, null],
262
            [0, 0],
263
            [new DateInterval('PT6H8M'), 6 * 3600 + 8 * 60],
264
            [new DateInterval('P2Y4D'), 2 * 365 * 24 * 3600 + 4 * 24 * 3600],
265
        ];
266
    }
267
268
    /**
269
     * @dataProvider ttlToExpirationProvider
270
     * @param mixed $ttl
271
     * @param mixed $expected
272
     * @throws ReflectionException
273
     */
274
    public function testTtlToExpiration($ttl, $expected): void
275
    {
276
        if ($expected === 'calculate_expiration') {
277
            MockHelper::$time = \time();
278
            $expected = MockHelper::$time + $ttl;
279
        }
280
        if ($expected === 'calculate_max_expiration') {
281
            MockHelper::$time = \time();
282
            $expected = MockHelper::$time + 31536000;
283
        }
284
        $cache = new FileCache(static::CACHE_DIRECTORY);
285
        $this->assertSameExceptObject($expected, $this->invokeMethod($cache, 'ttlToExpiration', [$ttl]));
286
    }
287
288
    public function ttlToExpirationProvider(): array
289
    {
290
        return [
291
            [3, 'calculate_expiration'],
292
            [null, 'calculate_max_expiration'],
293
            [-5, -1],
294
        ];
295
    }
296
297
    /**
298
     * @dataProvider iterableProvider
299
     * @param array $array
300
     * @param iterable $iterable
301
     * @throws InvalidArgumentException
302
     */
303
    public function testValuesAsIterable(array $array, iterable $iterable): void
304
    {
305
        $cache = $this->createCacheInstance();
306
        $cache->clear();
307
308
        $cache->setMultiple($iterable);
309
310
        $this->assertSameExceptObject($array, $cache->getMultiple(array_keys($array)));
311
    }
312
313
    public function iterableProvider(): array
314
    {
315
        return [
316
            'array' => [
317
                ['a' => 1, 'b' => 2,],
318
                ['a' => 1, 'b' => 2,],
319
            ],
320
            'ArrayIterator' => [
321
                ['a' => 1, 'b' => 2,],
322
                new \ArrayIterator(['a' => 1, 'b' => 2,]),
323
            ],
324
            'IteratorAggregate' => [
325
                ['a' => 1, 'b' => 2,],
326
                new class() implements \IteratorAggregate {
327
                    public function getIterator()
328
                    {
329
                        return new \ArrayIterator(['a' => 1, 'b' => 2,]);
330
                    }
331
                }
332
            ],
333
            'generator' => [
334
                ['a' => 1, 'b' => 2,],
335
                (static function () {
336
                    yield 'a' => 1;
337
                    yield 'b' => 2;
338
                })()
339
            ]
340
        ];
341
    }
342
343
    public function testExpire(): void
344
    {
345
        $cache = $this->createCacheInstance();
346
        $cache->clear();
347
348
        MockHelper::$time = \time();
349
        $this->assertTrue($cache->set('expire_test', 'expire_test', 2));
350
        MockHelper::$time++;
351
        $this->assertEquals('expire_test', $cache->get('expire_test'));
352
        MockHelper::$time++;
353
        $this->assertNull($cache->get('expire_test'));
354
    }
355
356
    /**
357
     * We have to on separate process because of PHPMock not being able to mock a function that
358
     * was already called.
359
     * @runInSeparateProcess
360
     */
361
    public function testCacheRenewalOnDifferentOwnership(): void
362
    {
363
        if (!function_exists('posix_geteuid')) {
364
            $this->markTestSkipped('Can not test without posix extension installed.');
365
        }
366
367
        $cache = $this->createCacheInstance();
368
        $cache->clear();
369
370
        $cacheValue = uniqid('value_', false);
371
        $cacheKey = uniqid('key_', false);
372
373
        MockHelper::$time = \time();
374
        $this->assertTrue($cache->set($cacheKey, $cacheValue, 2));
375
        $this->assertSame($cacheValue, $cache->get($cacheKey));
376
377
        // Override fileowner method so it always returns something not equal to the current user
378
        $notCurrentEuid = posix_geteuid() + 15;
379
        $this->getFunctionMock('Yiisoft\Cache\File', 'fileowner')->expects($this->any())->willReturn($notCurrentEuid);
380
        $this->getFunctionMock('Yiisoft\Cache\File', 'unlink')->expects($this->once());
381
382
        $this->assertTrue($cache->set($cacheKey, uniqid('value_2_', false), 2), 'Cannot rebuild cache on different file ownership');
383
    }
384
385
    public function testSetWithDateIntervalTtl(): void
386
    {
387
        $cache = $this->createCacheInstance();
388
        $cache->clear();
389
390
        $cache->set('a', 1, new DateInterval('PT1H'));
391
        $this->assertSameExceptObject(1, $cache->get('a'));
392
393
        $cache->setMultiple(['b' => 2]);
394
        $this->assertSameExceptObject(['b' => 2], $cache->getMultiple(['b']));
395
    }
396
397
    public function testCacheFileSuffix(): void
398
    {
399
        /** @var FileCache $cache */
400
        $cache = $this->createCacheInstance();
401
        $cache->clear();
402
        $cache->setCacheFileSuffix('.test');
403
404
        $cache->set('a', 1);
405
        $this->assertSameExceptObject(1, $cache->get('a'));
406
407
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', ['a']);
408
409
        $this->assertEquals('.test', substr($cacheFile, -5));
410
    }
411
412
    public function testDirectoryLevel(): void
413
    {
414
        /** @var FileCache $cache */
415
        $cache = $this->createCacheInstance();
416
        $cache->clear();
417
        $cache->setDirectoryLevel(0);
418
419
        $cache->set('a', 1);
420
        $this->assertSameExceptObject(1, $cache->get('a'));
421
422
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', ['a']);
423
424
        $this->assertPathEquals(__DIR__ . '/runtime/cache/a.bin', $cacheFile);
425
    }
426
427
    public function testFileMode(): void
428
    {
429
        if ($this->isWindows()) {
430
            $this->markTestSkipped('Can not test permissions on Windows');
431
        }
432
433
        $cache = new FileCache('/tmp/test_file_cache');
434
        $cache->clear();
435
        $cache->setFileMode(0755);
436
437
        $cache->set('a', 1);
438
        $this->assertSameExceptObject(1, $cache->get('a'));
439
440
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', ['a']);
441
442
        $permissions = substr(sprintf('%o', fileperms($cacheFile)), -4);
443
444
        $this->assertEquals('0755', $permissions);
445
    }
446
447
    public function testDirMode(): void
448
    {
449
        if ($this->isWindows()) {
450
            $this->markTestSkipped('Can not test permissions on Windows');
451
        }
452
453
        $cache = new FileCache('/tmp/test_file_cache');
454
        $cache->clear();
455
        $cache->setDirMode(0755);
456
457
        $cache->set('a', 1);
458
        $this->assertSameExceptObject(1, $cache->get('a'));
459
460
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', ['a']);
461
462
        $permissions = substr(sprintf('%o', fileperms(dirname($cacheFile))), -4);
463
464
        $this->assertEquals('0755', $permissions);
465
    }
466
467
    public function testGcProbability(): void
468
    {
469
        /** @var FileCache $cache */
470
        $cache = $this->createCacheInstance();
471
        $cache->clear();
472
        $cache->setGcProbability(1000000);
473
474
        $key = 'gc_probability_test';
475
476
        MockHelper::$time = \time();
477
478
        $cache->set($key, 1, 1);
479
480
        $this->assertSameExceptObject(1, $cache->get($key));
481
482
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', [$key]);
483
484
        $this->assertFileExists($cacheFile);
485
486
        MockHelper::$time++;
487
        MockHelper::$time++;
488
489
        $cache->set('b', 2);
490
491
        $this->assertFileDoesNotExist($cacheFile);
492
    }
493
494
    public function testGetInvalidKey(): void
495
    {
496
        $this->expectException(InvalidArgumentException::class);
497
        $cache = $this->createCacheInstance();
498
        $cache->get(1);
499
    }
500
501
    public function testSetInvalidKey(): void
502
    {
503
        $this->expectException(InvalidArgumentException::class);
504
        $cache = $this->createCacheInstance();
505
        $cache->set(1, 1);
506
    }
507
508
    public function testDeleteInvalidKey(): void
509
    {
510
        $this->expectException(InvalidArgumentException::class);
511
        $cache = $this->createCacheInstance();
512
        $cache->delete(1);
513
    }
514
515
    public function testGetMultipleInvalidKeys(): void
516
    {
517
        $this->expectException(InvalidArgumentException::class);
518
        $cache = $this->createCacheInstance();
519
        $cache->getMultiple([true]);
520
    }
521
522
    public function testGetMultipleInvalidKeysNotIterable(): void
523
    {
524
        $this->expectException(InvalidArgumentException::class);
525
        $cache = $this->createCacheInstance();
526
        $cache->getMultiple(1);
527
    }
528
529
    public function testSetMultipleInvalidKeysNotIterable(): void
530
    {
531
        $this->expectException(InvalidArgumentException::class);
532
        $cache = $this->createCacheInstance();
533
        $cache->setMultiple(1);
534
    }
535
536
    public function testDeleteMultipleInvalidKeys(): void
537
    {
538
        $this->expectException(InvalidArgumentException::class);
539
        $cache = $this->createCacheInstance();
540
        $cache->deleteMultiple([true]);
541
    }
542
543
    public function testDeleteMultipleInvalidKeysNotIterable(): void
544
    {
545
        $this->expectException(InvalidArgumentException::class);
546
        $cache = $this->createCacheInstance();
547
        $cache->deleteMultiple(1);
548
    }
549
550
    public function testHasInvalidKey(): void
551
    {
552
        $this->expectException(InvalidArgumentException::class);
553
        $cache = $this->createCacheInstance();
554
        $cache->has(1);
555
    }
556
557
    private function isWindows(): bool
558
    {
559
        return DIRECTORY_SEPARATOR === '\\';
560
    }
561
562
    private function assertPathEquals($expected, $actual, string $message = ''): void
563
    {
564
        $expected = $this->normalizePath($expected);
565
        $actual = $this->normalizePath($actual);
566
        $this->assertSame($expected, $actual, $message);
567
    }
568
569
    private function normalizePath(string $path): string
570
    {
571
        return str_replace('\\', '/', $path);
572
    }
573
}
574