Passed
Push — master ( e9a803...cec0c9 )
by Alexander
01:56
created

FileCacheTest.php$0 ➔ isWindows()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Yiisoft\Cache\File\Tests;
4
5
require_once __DIR__ . '/MockHelper.php';
6
7
use DateInterval;
8
use phpmock\phpunit\PHPMock;
9
use Psr\SimpleCache\CacheInterface;
10
use Psr\SimpleCache\InvalidArgumentException;
11
use ReflectionException;
12
use Yiisoft\Cache\File\FileCache;
13
use Yiisoft\Cache\File\MockHelper;
14
15
class FileCacheTest extends TestCase
16
{
17
    use PHPMock;
18
19
    protected const CACHE_DIRECTORY = __DIR__ . '/runtime/cache';
20
21
    protected function tearDown(): void
22
    {
23
        MockHelper::$time = null;
24
    }
25
26
    protected function createCacheInstance(): CacheInterface
27
    {
28
        return new FileCache(static::CACHE_DIRECTORY);
29
    }
30
31
    /**
32
     * @dataProvider dataProvider
33
     * @param $key
34
     * @param $value
35
     * @throws InvalidArgumentException
36
     */
37
    public function testSet($key, $value): void
38
    {
39
        $cache = $this->createCacheInstance();
40
        $cache->clear();
41
42
        for ($i = 0; $i < 2; $i++) {
43
            $this->assertTrue($cache->set($key, $value));
44
        }
45
    }
46
47
    /**
48
     * @dataProvider dataProvider
49
     * @param $key
50
     * @param $value
51
     * @throws InvalidArgumentException
52
     */
53
    public function testGet($key, $value): void
54
    {
55
        $cache = $this->createCacheInstance();
56
        $cache->clear();
57
58
        $cache->set($key, $value);
59
        $valueFromCache = $cache->get($key, 'default');
60
61
        $this->assertSameExceptObject($value, $valueFromCache);
62
    }
63
64
    /**
65
     * @dataProvider dataProvider
66
     * @param $key
67
     * @param $value
68
     * @throws InvalidArgumentException
69
     */
70
    public function testValueInCacheCannotBeChanged($key, $value): void
71
    {
72
        $cache = $this->createCacheInstance();
73
        $cache->clear();
74
75
        $cache->set($key, $value);
76
        $valueFromCache = $cache->get($key, 'default');
77
78
        $this->assertSameExceptObject($value, $valueFromCache);
79
80
        if (is_object($value)) {
81
            $originalValue = clone $value;
82
            $valueFromCache->test_field = 'changed';
83
            $value->test_field = 'changed';
84
            $valueFromCacheNew = $cache->get($key, 'default');
85
            $this->assertSameExceptObject($originalValue, $valueFromCacheNew);
86
        }
87
    }
88
89
    /**
90
     * @dataProvider dataProvider
91
     * @param $key
92
     * @param $value
93
     * @throws InvalidArgumentException
94
     */
95
    public function testHas($key, $value): void
96
    {
97
        $cache = $this->createCacheInstance();
98
        $cache->clear();
99
100
        $cache->set($key, $value);
101
102
        $this->assertTrue($cache->has($key));
103
        // check whether exists affects the value
104
        $this->assertSameExceptObject($value, $cache->get($key));
105
106
        $this->assertTrue($cache->has($key));
107
        $this->assertFalse($cache->has('not_exists'));
108
    }
109
110
    public function testGetNonExistent(): void
111
    {
112
        $cache = $this->createCacheInstance();
113
        $cache->clear();
114
115
        $this->assertNull($cache->get('non_existent_key'));
116
    }
117
118
    /**
119
     * @dataProvider dataProvider
120
     * @param $key
121
     * @param $value
122
     * @throws InvalidArgumentException
123
     */
124
    public function testDelete($key, $value): void
125
    {
126
        $cache = $this->createCacheInstance();
127
        $cache->clear();
128
129
        $cache->set($key, $value);
130
131
        $this->assertSameExceptObject($value, $cache->get($key));
132
        $this->assertTrue($cache->delete($key));
133
        $this->assertNull($cache->get($key));
134
    }
135
136
    /**
137
     * @dataProvider dataProvider
138
     * @param $key
139
     * @param $value
140
     * @throws InvalidArgumentException
141
     */
142
    public function testClear($key, $value): void
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

142
    public function testClear($key, /** @scrutinizer ignore-unused */ $value): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
143
    {
144
        $cache = $this->createCacheInstance();
145
        $cache = $this->prepare($cache);
146
147
        $this->assertTrue($cache->clear());
148
        $this->assertNull($cache->get($key));
149
    }
150
151
    /**
152
     * @dataProvider dataProviderSetMultiple
153
     * @param int|null $ttl
154
     * @throws InvalidArgumentException
155
     */
156
    public function testSetMultiple(?int $ttl): void
157
    {
158
        $cache = $this->createCacheInstance();
159
        $cache->clear();
160
161
        $data = $this->getDataProviderData();
162
163
        $cache->setMultiple($data, $ttl);
164
165
        foreach ($data as $key => $value) {
166
            $this->assertSameExceptObject($value, $cache->get((string)$key));
167
        }
168
    }
169
170
    /**
171
     * @return array testing multiSet with and without expiry
172
     */
173
    public function dataProviderSetMultiple(): array
174
    {
175
        return [
176
            [null],
177
            [2],
178
        ];
179
    }
180
181
    public function testGetMultiple(): void
182
    {
183
        $cache = $this->createCacheInstance();
184
        $cache->clear();
185
186
        $data = $this->getDataProviderData();
187
        $keys = array_map('strval', array_keys($data));
188
189
        $cache->setMultiple($data);
190
191
        $this->assertSameExceptObject($data, $cache->getMultiple($keys));
192
    }
193
194
    public function testDeleteMultiple(): void
195
    {
196
        $cache = $this->createCacheInstance();
197
        $cache->clear();
198
199
        $data = $this->getDataProviderData();
200
        $keys = array_map('strval', array_keys($data));
201
202
        $cache->setMultiple($data);
203
204
        $this->assertSameExceptObject($data, $cache->getMultiple($keys));
205
206
        $cache->deleteMultiple($keys);
207
208
        $emptyData = array_map(static function ($v) {
0 ignored issues
show
Unused Code introduced by
The parameter $v is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

208
        $emptyData = array_map(static function (/** @scrutinizer ignore-unused */ $v) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
209
            return null;
210
        }, $data);
211
212
        $this->assertSameExceptObject($emptyData, $cache->getMultiple($keys));
213
    }
214
215
    public function testZeroAndNegativeTtl(): void
216
    {
217
        $cache = $this->createCacheInstance();
218
        $cache->clear();
219
        $cache->setMultiple([
220
            'a' => 1,
221
            'b' => 2,
222
        ]);
223
224
        $this->assertTrue($cache->has('a'));
225
        $this->assertTrue($cache->has('b'));
226
227
        $cache->set('a', 11, -1);
228
229
        $this->assertFalse($cache->has('a'));
230
231
        $cache->set('b', 22, 0);
232
233
        $this->assertFalse($cache->has('b'));
234
    }
235
236
    /**
237
     * @dataProvider dataProviderNormalizeTtl
238
     * @param mixed $ttl
239
     * @param mixed $expectedResult
240
     * @throws ReflectionException
241
     */
242
    public function testNormalizeTtl($ttl, $expectedResult): void
243
    {
244
        $cache = new FileCache(static::CACHE_DIRECTORY);
245
        $this->assertSameExceptObject($expectedResult, $this->invokeMethod($cache, 'normalizeTtl', [$ttl]));
246
    }
247
248
    /**
249
     * Data provider for {@see testNormalizeTtl()}
250
     * @return array test data
251
     *
252
     * @throws \Exception
253
     */
254
    public function dataProviderNormalizeTtl(): array
255
    {
256
        return [
257
            [123, 123],
258
            ['123', 123],
259
            [null, null],
260
            [0, 0],
261
            [new DateInterval('PT6H8M'), 6 * 3600 + 8 * 60],
262
            [new DateInterval('P2Y4D'), 2 * 365 * 24 * 3600 + 4 * 24 * 3600],
263
        ];
264
    }
265
266
    /**
267
     * @dataProvider ttlToExpirationProvider
268
     * @param mixed $ttl
269
     * @param mixed $expected
270
     * @throws ReflectionException
271
     */
272
    public function testTtlToExpiration($ttl, $expected): void
273
    {
274
        if ($expected === 'calculate_expiration') {
275
            MockHelper::$time = \time();
276
            $expected = MockHelper::$time + $ttl;
277
        }
278
        if ($expected === 'calculate_max_expiration') {
279
            MockHelper::$time = \time();
280
            $expected = MockHelper::$time + 31536000;
281
        }
282
        $cache = new FileCache(static::CACHE_DIRECTORY);
283
        $this->assertSameExceptObject($expected, $this->invokeMethod($cache, 'ttlToExpiration', [$ttl]));
284
    }
285
286
    public function ttlToExpirationProvider(): array
287
    {
288
        return [
289
            [3, 'calculate_expiration'],
290
            [null, 'calculate_max_expiration'],
291
            [-5, -1],
292
        ];
293
    }
294
295
    /**
296
     * @dataProvider iterableProvider
297
     * @param array $array
298
     * @param iterable $iterable
299
     * @throws InvalidArgumentException
300
     */
301
    public function testValuesAsIterable(array $array, iterable $iterable): void
302
    {
303
        $cache = $this->createCacheInstance();
304
        $cache->clear();
305
306
        $cache->setMultiple($iterable);
307
308
        $this->assertSameExceptObject($array, $cache->getMultiple(array_keys($array)));
309
    }
310
311
    public function iterableProvider(): array
312
    {
313
        return [
314
            'array' => [
315
                ['a' => 1, 'b' => 2,],
316
                ['a' => 1, 'b' => 2,],
317
            ],
318
            'ArrayIterator' => [
319
                ['a' => 1, 'b' => 2,],
320
                new \ArrayIterator(['a' => 1, 'b' => 2,]),
321
            ],
322
            'IteratorAggregate' => [
323
                ['a' => 1, 'b' => 2,],
324
                new class() implements \IteratorAggregate {
325
                    public function getIterator()
326
                    {
327
                        return new \ArrayIterator(['a' => 1, 'b' => 2,]);
328
                    }
329
                }
330
            ],
331
            'generator' => [
332
                ['a' => 1, 'b' => 2,],
333
                (static function () {
334
                    yield 'a' => 1;
335
                    yield 'b' => 2;
336
                })()
337
            ]
338
        ];
339
    }
340
341
    public function testExpire(): void
342
    {
343
        $cache = $this->createCacheInstance();
344
        $cache->clear();
345
346
        MockHelper::$time = \time();
347
        $this->assertTrue($cache->set('expire_test', 'expire_test', 2));
348
        MockHelper::$time++;
349
        $this->assertEquals('expire_test', $cache->get('expire_test'));
350
        MockHelper::$time++;
351
        $this->assertNull($cache->get('expire_test'));
352
    }
353
354
    /**
355
     * We have to on separate process because of PHPMock not being able to mock a function that
356
     * was already called.
357
     * @runInSeparateProcess
358
     */
359
    public function testCacheRenewalOnDifferentOwnership(): void
360
    {
361
        if (!function_exists('posix_geteuid')) {
362
            $this->markTestSkipped('Can not test without posix extension installed.');
363
        }
364
365
        $cache = $this->createCacheInstance();
366
        $cache->clear();
367
368
        $cacheValue = uniqid('value_', false);
369
        $cacheKey = uniqid('key_', false);
370
371
        MockHelper::$time = \time();
372
        $this->assertTrue($cache->set($cacheKey, $cacheValue, 2));
373
        $this->assertSame($cacheValue, $cache->get($cacheKey));
374
375
        // Override fileowner method so it always returns something not equal to the current user
376
        $notCurrentEuid = posix_geteuid() + 15;
377
        $this->getFunctionMock('Yiisoft\Cache\File', 'fileowner')->expects($this->any())->willReturn($notCurrentEuid);
378
        $this->getFunctionMock('Yiisoft\Cache\File', 'unlink')->expects($this->once());
379
380
        $this->assertTrue($cache->set($cacheKey, uniqid('value_2_', false), 2), 'Cannot rebuild cache on different file ownership');
381
    }
382
383
    public function testSetWithDateIntervalTtl(): void
384
    {
385
        $cache = $this->createCacheInstance();
386
        $cache->clear();
387
388
        $cache->set('a', 1, new DateInterval('PT1H'));
389
        $this->assertSameExceptObject(1, $cache->get('a'));
390
391
        $cache->setMultiple(['b' => 2]);
392
        $this->assertSameExceptObject(['b' => 2], $cache->getMultiple(['b']));
393
    }
394
395
    public function testCacheFileSuffix(): void
396
    {
397
        /** @var FileCache $cache */
398
        $cache = $this->createCacheInstance();
399
        $cache->clear();
400
        $cache->setCacheFileSuffix('.test');
401
402
        $cache->set('a', 1);
403
        $this->assertSameExceptObject(1, $cache->get('a'));
404
405
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', ['a']);
406
407
        $this->assertEquals('.test', substr($cacheFile, -5));
408
    }
409
410
    public function testDirectoryLevel(): void
411
    {
412
        /** @var FileCache $cache */
413
        $cache = $this->createCacheInstance();
414
        $cache->clear();
415
        $cache->setDirectoryLevel(0);
416
417
        $cache->set('a', 1);
418
        $this->assertSameExceptObject(1, $cache->get('a'));
419
420
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', ['a']);
421
422
        $this->assertPathEquals(__DIR__ . '/runtime/cache/a.bin', $cacheFile);
423
    }
424
425
    public function testFileMode(): void
426
    {
427
        if ($this->isWindows()) {
428
            $this->markTestSkipped('Can not test permissions on Windows');
429
        }
430
431
        $cache = new FileCache('/tmp/test_file_cache');
432
        $cache->clear();
433
        $cache->setFileMode(0755);
434
435
        $cache->set('a', 1);
436
        $this->assertSameExceptObject(1, $cache->get('a'));
437
438
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', ['a']);
439
440
        $permissions = substr(sprintf('%o', fileperms($cacheFile)), -4);
441
442
        $this->assertEquals('0755', $permissions);
443
    }
444
445
    public function testDirMode(): void
446
    {
447
        if ($this->isWindows()) {
448
            $this->markTestSkipped('Can not test permissions on Windows');
449
        }
450
451
        $cache = new FileCache('/tmp/test_file_cache');
452
        $cache->clear();
453
        $cache->setDirMode(0755);
454
455
        $cache->set('a', 1);
456
        $this->assertSameExceptObject(1, $cache->get('a'));
457
458
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', ['a']);
459
460
        $permissions = substr(sprintf('%o', fileperms(dirname($cacheFile))), -4);
461
462
        $this->assertEquals('0755', $permissions);
463
    }
464
465
    public function testGcProbability(): void
466
    {
467
        /** @var FileCache $cache */
468
        $cache = $this->createCacheInstance();
469
        $cache->clear();
470
        $cache->setGcProbability(1000000);
471
472
        $key = 'gc_probability_test';
473
474
        MockHelper::$time = \time();
475
476
        $cache->set($key, 1, 1);
477
478
        $this->assertSameExceptObject(1, $cache->get($key));
479
480
        $cacheFile = $this->invokeMethod($cache, 'getCacheFile', [$key]);
481
482
        $this->assertFileExists($cacheFile);
483
484
        MockHelper::$time++;
485
        MockHelper::$time++;
486
487
        $cache->set('b', 2);
488
489
        $this->assertFileNotExists($cacheFile);
490
    }
491
492
    public function testGetInvalidKey(): void
493
    {
494
        $this->expectException(InvalidArgumentException::class);
495
        $cache = $this->createCacheInstance();
496
        $cache->get(1);
497
    }
498
499
    public function testSetInvalidKey(): void
500
    {
501
        $this->expectException(InvalidArgumentException::class);
502
        $cache = $this->createCacheInstance();
503
        $cache->set(1, 1);
504
    }
505
506
    public function testDeleteInvalidKey(): void
507
    {
508
        $this->expectException(InvalidArgumentException::class);
509
        $cache = $this->createCacheInstance();
510
        $cache->delete(1);
511
    }
512
513
    public function testGetMultipleInvalidKeys(): void
514
    {
515
        $this->expectException(InvalidArgumentException::class);
516
        $cache = $this->createCacheInstance();
517
        $cache->getMultiple([true]);
518
    }
519
520
    public function testGetMultipleInvalidKeysNotIterable(): void
521
    {
522
        $this->expectException(InvalidArgumentException::class);
523
        $cache = $this->createCacheInstance();
524
        $cache->getMultiple(1);
525
    }
526
527
    public function testSetMultipleInvalidKeysNotIterable(): void
528
    {
529
        $this->expectException(InvalidArgumentException::class);
530
        $cache = $this->createCacheInstance();
531
        $cache->setMultiple(1);
532
    }
533
534
    public function testDeleteMultipleInvalidKeys(): void
535
    {
536
        $this->expectException(InvalidArgumentException::class);
537
        $cache = $this->createCacheInstance();
538
        $cache->deleteMultiple([true]);
539
    }
540
541
    public function testDeleteMultipleInvalidKeysNotIterable(): void
542
    {
543
        $this->expectException(InvalidArgumentException::class);
544
        $cache = $this->createCacheInstance();
545
        $cache->deleteMultiple(1);
546
    }
547
548
    public function testHasInvalidKey(): void
549
    {
550
        $this->expectException(InvalidArgumentException::class);
551
        $cache = $this->createCacheInstance();
552
        $cache->has(1);
553
    }
554
555
    private function isWindows(): bool
556
    {
557
        return DIRECTORY_SEPARATOR === '\\';
558
    }
559
560
    private function assertPathEquals($expected, $actual, string $message = ''): void
561
    {
562
        $expected = $this->normalizePath($expected);
563
        $actual = $this->normalizePath($actual);
564
        $this->assertSame($expected, $actual, $message);
565
    }
566
567
    private function normalizePath(string $path): string
568
    {
569
        return str_replace('\\', '/', $path);
570
    }
571
}
572