Passed
Push — master ( 58f408...206b63 )
by Marco
12:28
created

testValueThatIsFalseBooleanIsProperlyRetrieved()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 1
eloc 6
nc 1
nop 0
1
<?php
2
3
namespace Doctrine\Tests\Common\Cache;
4
5
use Doctrine\Common\Cache\Cache;
6
use ArrayObject;
7
use Doctrine\Common\Cache\CacheProvider;
8
9
abstract class CacheTest extends \Doctrine\Tests\DoctrineTestCase
10
{
11
    /**
12
     * @dataProvider provideDataToCache
13
     */
14
    public function testSetContainsFetchDelete($value) : void
15
    {
16
        $cache = $this->_getCacheDriver();
17
18
        // Test saving a value, checking if it exists, and fetching it back
19
        $this->assertTrue($cache->save('key', $value));
20
        $this->assertTrue($cache->contains('key'));
21
        if (is_object($value)) {
22
            $this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference');
23
        } else {
24
            $this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type');
25
        }
26
27
        // Test deleting a value
28
        $this->assertTrue($cache->delete('key'));
29
        $this->assertFalse($cache->contains('key'));
30
        $this->assertFalse($cache->fetch('key'));
31
    }
32
33
    /**
34
     * @dataProvider provideDataToCache
35
     */
36
    public function testUpdateExistingEntry($value) : void
37
    {
38
        $cache = $this->_getCacheDriver();
39
40
        $this->assertTrue($cache->save('key', 'old-value'));
41
        $this->assertTrue($cache->contains('key'));
42
43
        $this->assertTrue($cache->save('key', $value));
44
        $this->assertTrue($cache->contains('key'));
45
        if (is_object($value)) {
46
            $this->assertEquals($value, $cache->fetch('key'), 'Objects retrieved from the cache must be equal but not necessarily the same reference');
47
        } else {
48
            $this->assertSame($value, $cache->fetch('key'), 'Scalar and array data retrieved from the cache must be the same as the original, e.g. same type');
49
        }
50
    }
51
52
    public function testCacheKeyIsCaseSensitive() : void
53
    {
54
        $cache = $this->_getCacheDriver();
55
56
        $this->assertTrue($cache->save('key', 'value'));
57
        $this->assertTrue($cache->contains('key'));
58
        $this->assertSame('value', $cache->fetch('key'));
59
60
        $this->assertFalse($cache->contains('KEY'));
61
        $this->assertFalse($cache->fetch('KEY'));
62
63
        $cache->delete('KEY');
64
        $this->assertTrue($cache->contains('key'), 'Deleting cache item with different case must not affect other cache item');
65
    }
66
67
    public function testFetchMultiple() : void
68
    {
69
        $cache  = $this->_getCacheDriver();
70
        $values = $this->provideDataToCache();
71
        $saved  = [];
72
73
        foreach ($values as $key => $value) {
74
            $cache->save($key, $value[0]);
75
76
            $saved[$key] = $value[0];
77
        }
78
79
        $keys = array_keys($saved);
80
81
        $this->assertEquals(
82
            $saved,
83
            $cache->fetchMultiple($keys),
84
            'Testing fetchMultiple with different data types'
85
        );
86
        $this->assertEquals(
87
            array_slice($saved, 0, 1),
88
            $cache->fetchMultiple(array_slice($keys, 0, 1)),
89
            'Testing fetchMultiple with a single key'
90
        );
91
92
        $keysWithNonExisting = [];
93
        $keysWithNonExisting[] = 'non_existing1';
94
        $keysWithNonExisting[] = $keys[0];
95
        $keysWithNonExisting[] = 'non_existing2';
96
        $keysWithNonExisting[] = $keys[1];
97
        $keysWithNonExisting[] = 'non_existing3';
98
99
        $this->assertEquals(
100
            array_slice($saved, 0, 2),
101
            $cache->fetchMultiple($keysWithNonExisting),
102
            'Testing fetchMultiple with a subset of keys and mixed with non-existing ones'
103
        );
104
    }
105
106
    public function testFetchMultipleWithNoKeys() : void
107
    {
108
        $cache = $this->_getCacheDriver();
109
110
        $this->assertSame([], $cache->fetchMultiple([]));
111
    }
112
113
    public function testSaveMultiple() : void
114
    {
115
        $cache = $this->_getCacheDriver();
116
        $cache->deleteAll();
117
118
        $data = array_map(function ($value) {
119
            return $value[0];
120
        }, $this->provideDataToCache());
121
122
        $this->assertTrue($cache->saveMultiple($data));
123
124
        $keys = array_keys($data);
125
126
        $this->assertEquals($data, $cache->fetchMultiple($keys));
127
    }
128
129
    public function provideDataToCache() : array
130
    {
131
        $obj = new \stdClass();
132
        $obj->foo = 'bar';
133
        $obj2 = new \stdClass();
134
        $obj2->bar = 'foo';
135
        $obj2->obj = $obj;
136
        $obj->obj2 = $obj2;
137
138
        return [
139
            'array' => [['one', 2, 3.01]],
140
            'string' => ['value'],
141
            'string_invalid_utf8' => ["\xc3\x28"],
142
            'string_null_byte' => ['with'."\0".'null char'],
143
            'integer' => [1],
144
            'float' => [1.5],
145
            'object' => [new ArrayObject(['one', 2, 3.01])],
146
            'object_recursive' => [$obj],
147
            'true' => [true],
148
            // the following are considered FALSE in boolean context, but caches should still recognize their existence
149
            'null' => [null],
150
            'false' => [false],
151
            'array_empty' => [[]],
152
            'string_zero' => ['0'],
153
            'integer_zero' => [0],
154
            'float_zero' => [0.0],
155
            'string_empty' => [''],
156
        ];
157
    }
158
159
    public function testDeleteIsSuccessfulWhenKeyDoesNotExist() : void
160
    {
161
        $cache = $this->_getCacheDriver();
162
163
        $cache->delete('key');
164
        $this->assertFalse($cache->contains('key'));
165
        $this->assertTrue($cache->delete('key'));
166
    }
167
168
    public function testDeleteAll() : void
169
    {
170
        $cache = $this->_getCacheDriver();
171
172
        $this->assertTrue($cache->save('key1', 1));
173
        $this->assertTrue($cache->save('key2', 2));
174
        $this->assertTrue($cache->deleteAll());
175
        $this->assertFalse($cache->contains('key1'));
176
        $this->assertFalse($cache->contains('key2'));
177
    }
178
179
    public function testDeleteMulti() : void
180
    {
181
        $cache = $this->_getCacheDriver();
182
183
        $this->assertTrue($cache->save('key1', 1));
184
        $this->assertTrue($cache->save('key2', 1));
185
        $this->assertTrue($cache->deleteMultiple(['key1', 'key2', 'key3']));
186
        $this->assertFalse($cache->contains('key1'));
187
        $this->assertFalse($cache->contains('key2'));
188
        $this->assertFalse($cache->contains('key3'));
189
    }
190
191
    /**
192
     * @dataProvider provideCacheIds
193
     */
194
    public function testCanHandleSpecialCacheIds($id) : void
195
    {
196
        $cache = $this->_getCacheDriver();
197
198
        $this->assertTrue($cache->save($id, 'value'));
199
        $this->assertTrue($cache->contains($id));
200
        $this->assertEquals('value', $cache->fetch($id));
201
202
        $this->assertTrue($cache->delete($id));
203
        $this->assertFalse($cache->contains($id));
204
        $this->assertFalse($cache->fetch($id));
205
    }
206
207
    public function testNoCacheIdCollisions() : void
208
    {
209
        $cache = $this->_getCacheDriver();
210
211
        $ids = $this->provideCacheIds();
212
213
        // fill cache with each id having a different value
214
        foreach ($ids as $index => $id) {
215
            $cache->save($id[0], $index);
216
        }
217
218
        // then check value of each cache id
219
        foreach ($ids as $index => $id) {
220
            $value = $cache->fetch($id[0]);
221
            $this->assertNotFalse($value, sprintf('Failed to retrieve data for cache id "%s".', $id[0]));
222
            if ($index !== $value) {
223
                $this->fail(sprintf('Cache id "%s" collides with id "%s".', $id[0], $ids[$value][0]));
224
            }
225
        }
226
    }
227
228
    /**
229
     * Returns cache ids with special characters that should still work.
230
     *
231
     * For example, the characters :\/<>"*?| are not valid in Windows filenames. So they must be encoded properly.
232
     * Each cache id should be considered different from the others.
233
     */
234
    public function provideCacheIds() : array
235
    {
236
        return [
237
            [':'],
238
            ['\\'],
239
            ['/'],
240
            ['<'],
241
            ['>'],
242
            ['"'],
243
            ['*'],
244
            ['?'],
245
            ['|'],
246
            ['['],
247
            [']'],
248
            ['ä'],
249
            ['a'],
250
            ['é'],
251
            ['e'],
252
            ['.'], // directory traversal
253
            ['..'], // directory traversal
254
            ['-'],
255
            ['_'],
256
            ['$'],
257
            ['%'],
258
            [' '],
259
            ["\0"],
260
            [''],
261
            [str_repeat('a', 300)], // long key
262
            [str_repeat('a', 113)],
263
        ];
264
    }
265
266
    public function testLifetime() : void
267
    {
268
        $cache = $this->_getCacheDriver();
269
        $cache->save('expire', 'value', 1);
270
        $this->assertTrue($cache->contains('expire'), 'Data should not be expired yet');
271
        // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
272
        sleep(2);
273
        $this->assertFalse($cache->contains('expire'), 'Data should be expired');
274
    }
275
276
    public function testNoExpire() : void
277
    {
278
        $cache = $this->_getCacheDriver();
279
        $cache->save('noexpire', 'value', 0);
280
        // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
281
        sleep(1);
282
        $this->assertTrue($cache->contains('noexpire'), 'Data with lifetime of zero should not expire');
283
    }
284
285
    public function testLongLifetime() : void
286
    {
287
        $cache = $this->_getCacheDriver();
288
        $cache->save('longlifetime', 'value', 30 * 24 * 3600 + 1);
289
        $this->assertTrue($cache->contains('longlifetime'), 'Data with lifetime > 30 days should be accepted');
290
    }
291
292
    public function testDeleteAllAndNamespaceVersioningBetweenCaches() : void
293
    {
294
        if ( ! $this->isSharedStorage()) {
295
            $this->markTestSkipped('The cache storage needs to be shared.');
296
        }
297
298
        $cache1 = $this->_getCacheDriver();
299
        $cache2 = $this->_getCacheDriver();
300
301
        $this->assertTrue($cache1->save('key1', 1));
302
        $this->assertTrue($cache2->save('key2', 2));
303
304
        /* Both providers are initialized with the same namespace version, so
305
         * they can see entries set by each other.
306
         */
307
        $this->assertTrue($cache1->contains('key1'));
308
        $this->assertTrue($cache1->contains('key2'));
309
        $this->assertTrue($cache2->contains('key1'));
310
        $this->assertTrue($cache2->contains('key2'));
311
312
        /* Deleting all entries through one provider will only increment the
313
         * namespace version on that object (and in the cache itself, which new
314
         * instances will use to initialize). The second provider will retain
315
         * its original version and still see stale data.
316
         */
317
        $this->assertTrue($cache1->deleteAll());
318
        $this->assertFalse($cache1->contains('key1'));
319
        $this->assertFalse($cache1->contains('key2'));
320
        $this->assertTrue($cache2->contains('key1'));
321
        $this->assertTrue($cache2->contains('key2'));
322
323
        /* A new cache provider should not see the deleted entries, since its
324
         * namespace version will be initialized.
325
         */
326
        $cache3 = $this->_getCacheDriver();
327
        $this->assertFalse($cache3->contains('key1'));
328
        $this->assertFalse($cache3->contains('key2'));
329
    }
330
331
    public function testFlushAll() : void
332
    {
333
        $cache = $this->_getCacheDriver();
334
335
        $this->assertTrue($cache->save('key1', 1));
336
        $this->assertTrue($cache->save('key2', 2));
337
        $this->assertTrue($cache->flushAll());
338
        $this->assertFalse($cache->contains('key1'));
339
        $this->assertFalse($cache->contains('key2'));
340
    }
341
342
    public function testFlushAllAndNamespaceVersioningBetweenCaches() : void
343
    {
344
        if ( ! $this->isSharedStorage()) {
345
            $this->markTestSkipped('The cache storage needs to be shared.');
346
        }
347
348
        $cache1 = $this->_getCacheDriver();
349
        $cache2 = $this->_getCacheDriver();
350
351
        /* Deleting all elements from the first provider should increment its
352
         * namespace version before saving the first entry.
353
         */
354
        $cache1->deleteAll();
355
        $this->assertTrue($cache1->save('key1', 1));
356
357
        /* The second provider will be initialized with the same namespace
358
         * version upon its first save operation.
359
         */
360
        $this->assertTrue($cache2->save('key2', 2));
361
362
        /* Both providers have the same namespace version and can see entries
363
         * set by each other.
364
         */
365
        $this->assertTrue($cache1->contains('key1'));
366
        $this->assertTrue($cache1->contains('key2'));
367
        $this->assertTrue($cache2->contains('key1'));
368
        $this->assertTrue($cache2->contains('key2'));
369
370
        /* Flushing all entries through one cache will remove all entries from
371
         * the cache but leave their namespace version as-is.
372
         */
373
        $this->assertTrue($cache1->flushAll());
374
        $this->assertFalse($cache1->contains('key1'));
375
        $this->assertFalse($cache1->contains('key2'));
376
        $this->assertFalse($cache2->contains('key1'));
377
        $this->assertFalse($cache2->contains('key2'));
378
379
        /* Inserting a new entry will use the same, incremented namespace
380
         * version, and it will be visible to both providers.
381
         */
382
        $this->assertTrue($cache1->save('key1', 1));
383
        $this->assertTrue($cache1->contains('key1'));
384
        $this->assertTrue($cache2->contains('key1'));
385
386
        /* A new cache provider will be initialized with the original namespace
387
         * version and not share any visibility with the first two providers.
388
         */
389
        $cache3 = $this->_getCacheDriver();
390
        $this->assertFalse($cache3->contains('key1'));
391
        $this->assertFalse($cache3->contains('key2'));
392
        $this->assertTrue($cache3->save('key3', 3));
393
        $this->assertTrue($cache3->contains('key3'));
394
    }
395
396
    public function testNamespace() : void
397
    {
398
        $cache = $this->_getCacheDriver();
399
400
        $cache->setNamespace('ns1_');
401
402
        $this->assertTrue($cache->save('key1', 1));
403
        $this->assertTrue($cache->contains('key1'));
404
405
        $cache->setNamespace('ns2_');
406
407
        $this->assertFalse($cache->contains('key1'));
408
    }
409
410
    public function testDeleteAllNamespace() : void
411
    {
412
        $cache = $this->_getCacheDriver();
413
414
        $cache->setNamespace('ns1');
415
        $this->assertFalse($cache->contains('key1'));
416
        $cache->save('key1', 'test');
417
        $this->assertTrue($cache->contains('key1'));
418
419
        $cache->setNamespace('ns2');
420
        $this->assertFalse($cache->contains('key1'));
421
        $cache->save('key1', 'test');
422
        $this->assertTrue($cache->contains('key1'));
423
424
        $cache->setNamespace('ns1');
425
        $this->assertTrue($cache->contains('key1'));
426
        $cache->deleteAll();
427
        $this->assertFalse($cache->contains('key1'));
428
429
        $cache->setNamespace('ns2');
430
        $this->assertTrue($cache->contains('key1'));
431
        $cache->deleteAll();
432
        $this->assertFalse($cache->contains('key1'));
433
    }
434
435
    /**
436
     * @group DCOM-43
437
     */
438
    public function testGetStats() : void
439
    {
440
        $cache = $this->_getCacheDriver();
441
        $stats = $cache->getStats();
442
443
        $this->assertArrayHasKey(Cache::STATS_HITS, $stats);
444
        $this->assertArrayHasKey(Cache::STATS_MISSES, $stats);
445
        $this->assertArrayHasKey(Cache::STATS_UPTIME, $stats);
446
        $this->assertArrayHasKey(Cache::STATS_MEMORY_USAGE, $stats);
447
        $this->assertArrayHasKey(Cache::STATS_MEMORY_AVAILABLE, $stats);
448
    }
449
450
    public function testSaveReturnsTrueWithAndWithoutTTlSet() : void
451
    {
452
        $cache = $this->_getCacheDriver();
453
        $cache->deleteAll();
454
        $this->assertTrue($cache->save('without_ttl', 'without_ttl'));
455
        $this->assertTrue($cache->save('with_ttl', 'with_ttl', 3600));
456
    }
457
458
    public function testValueThatIsFalseBooleanIsProperlyRetrieved()
459
    {
460
        $cache = $this->_getCacheDriver();
461
        $cache->deleteAll();
462
463
        $this->assertTrue($cache->save('key1', false));
464
        $this->assertTrue($cache->contains('key1'));
465
        $this->assertFalse($cache->fetch('key1'));
466
    }
467
468
    /**
469
     * @group 147
470
     * @group 152
471
     */
472
    public function testFetchingANonExistingKeyShouldNeverCauseANoticeOrWarning() : void
473
    {
474
        $cache = $this->_getCacheDriver();
475
476
        $errorHandler = function () {
477
            restore_error_handler();
478
479
            $this->fail('include failure captured');
480
        };
481
482
        set_error_handler($errorHandler);
483
484
        $cache->fetch('key');
485
486
        self::assertSame(
487
            $errorHandler,
488
            set_error_handler(function () {
489
            }),
490
            'The error handler is the one set by this test, and wasn\'t replaced'
491
        );
492
493
        restore_error_handler();
494
        restore_error_handler();
495
    }
496
497
    /**
498
     * Return whether multiple cache providers share the same storage.
499
     *
500
     * This is used for skipping certain tests for shared storage behavior.
501
     */
502
    protected function isSharedStorage() : bool
503
    {
504
        return true;
505
    }
506
507
    abstract protected function _getCacheDriver() : CacheProvider;
508
}
509