Completed
Push — master ( 9fa596...e14f7e )
by Marco
02:55
created

CacheTest::testDeleteMulti()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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