Completed
Push — master ( a8b991...dda2b8 )
by Marco
06:05 queued 02:56
created

CacheTest   A

Complexity

Total Complexity 31

Size/Duplication

Total Lines 456
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2
Metric Value
wmc 31
lcom 1
cbo 2
dl 0
loc 456
rs 9.8

24 Methods

Rating   Name   Duplication   Size   Complexity  
A testSetContainsFetchDelete() 0 18 2
A testUpdateExistingEntry() 0 15 2
A testCacheKeyIsCaseSensitive() 0 14 1
B testFetchMultiple() 0 38 2
A testFetchMultipleWithNoKeys() 0 6 1
A testSaveMultiple() 0 15 1
B provideDataToCache() 0 29 1
A testDeleteIsSuccessfulWhenKeyDoesNotExist() 0 8 1
A testDeleteAll() 0 10 1
A testCanHandleSpecialCacheIds() 0 12 1
A testNoCacheIdCollisions() 0 20 4
B provideCacheIds() 0 31 1
A testLifetime() 0 9 1
A testNoExpire() 0 8 1
A testLongLifetime() 0 6 1
B testDeleteAllAndNamespaceVersioningBetweenCaches() 0 38 2
A testFlushAll() 0 10 1
A testFlushAllAndNamespaceVersioningBetweenCaches() 0 53 2
A testNamespace() 0 13 1
B testDeleteAllNamespace() 0 24 1
A testGetStats() 0 11 1
A testSaveReturnsTrueWithAndWithoutTTlSet() 0 7 1
A isSharedStorage() 0 4 1
_getCacheDriver() 0 1 ?
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'));
0 ignored issues
show
Unused Code introduced by
The call to CacheProvider::contains() has too many arguments starting with 'Deleting cache item wit...ffect other cache item'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
64
    }
65
66
    public function testFetchMultiple()
67
    {
68
        $cache  = $this->_getCacheDriver();
69
        $values = $this->provideDataToCache();
70
        $saved  = array();
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 = array();
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(array(), $cache->fetchMultiple(array()));
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 array(
138
            'array' => array(array('one', 2, 3.01)),
139
            'string' => array('value'),
140
            'string_invalid_utf8' => array("\xc3\x28"),
141
            'string_null_byte' => array('with'."\0".'null char'),
142
            'integer' => array(1),
143
            'float' => array(1.5),
144
            'object' => array(new ArrayObject(array('one', 2, 3.01))),
145
            'object_recursive' => array($obj),
146
            'true' => array(true),
147
            // the following are considered FALSE in boolean context, but caches should still recognize their existence
148
            'null' => array(null),
149
            'false' => array(false),
150
            'array_empty' => array(array()),
151
            'string_zero' => array('0'),
152
            'integer_zero' => array(0),
153
            'float_zero' => array(0.0),
154
            'string_empty' => array(''),
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
    /**
179
     * @dataProvider provideCacheIds
180
     */
181
    public function testCanHandleSpecialCacheIds($id)
182
    {
183
        $cache = $this->_getCacheDriver();
184
185
        $this->assertTrue($cache->save($id, 'value'));
186
        $this->assertTrue($cache->contains($id));
187
        $this->assertEquals('value', $cache->fetch($id));
188
189
        $this->assertTrue($cache->delete($id));
190
        $this->assertFalse($cache->contains($id));
191
        $this->assertFalse($cache->fetch($id));
192
    }
193
194
    public function testNoCacheIdCollisions()
195
    {
196
        $cache = $this->_getCacheDriver();
197
198
        $ids = $this->provideCacheIds();
199
200
        // fill cache with each id having a different value
201
        foreach ($ids as $index => $id) {
202
            $cache->save($id[0], $index);
203
        }
204
205
        // then check value of each cache id
206
        foreach ($ids as $index => $id) {
207
            $value = $cache->fetch($id[0]);
208
            $this->assertNotFalse($value, sprintf('Failed to retrieve data for cache id "%s".', $id[0]));
209
            if ($index !== $value) {
210
                $this->fail(sprintf('Cache id "%s" collides with id "%s".', $id[0], $ids[$value][0]));
211
            }
212
        }
213
    }
214
215
    /**
216
     * Returns cache ids with special characters that should still work.
217
     *
218
     * For example, the characters :\/<>"*?| are not valid in Windows filenames. So they must be encoded properly.
219
     * Each cache id should be considered different from the others.
220
     *
221
     * @return array
222
     */
223
    public function provideCacheIds()
224
    {
225
        return array(
226
            array(':'),
227
            array('\\'),
228
            array('/'),
229
            array('<'),
230
            array('>'),
231
            array('"'),
232
            array('*'),
233
            array('?'),
234
            array('|'),
235
            array('['),
236
            array(']'),
237
            array('ä'),
238
            array('a'),
239
            array('é'),
240
            array('e'),
241
            array('.'), // directory traversal
242
            array('..'), // directory traversal
243
            array('-'),
244
            array('_'),
245
            array('$'),
246
            array('%'),
247
            array(' '),
248
            array("\0"),
249
            array(''),
250
            array(str_repeat('a', 300)), // long key
251
            array(str_repeat('a', 113)),
252
        );
253
    }
254
255
    public function testLifetime()
256
    {
257
        $cache = $this->_getCacheDriver();
258
        $cache->save('expire', 'value', 1);
259
        $this->assertTrue($cache->contains('expire'), 'Data should not be expired yet');
260
        // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
261
        sleep(2);
262
        $this->assertFalse($cache->contains('expire'), 'Data should be expired');
263
    }
264
265
    public function testNoExpire()
266
    {
267
        $cache = $this->_getCacheDriver();
268
        $cache->save('noexpire', 'value', 0);
269
        // @TODO should more TTL-based tests pop up, so then we should mock the `time` API instead
270
        sleep(1);
271
        $this->assertTrue($cache->contains('noexpire'), 'Data with lifetime of zero should not expire');
272
    }
273
274
    public function testLongLifetime()
275
    {
276
        $cache = $this->_getCacheDriver();
277
        $cache->save('longlifetime', 'value', 30 * 24 * 3600 + 1);
278
        $this->assertTrue($cache->contains('longlifetime'), 'Data with lifetime > 30 days should be accepted');
279
    }
280
281
    public function testDeleteAllAndNamespaceVersioningBetweenCaches()
282
    {
283
        if ( ! $this->isSharedStorage()) {
284
            $this->markTestSkipped('The cache storage needs to be shared.');
285
        }
286
287
        $cache1 = $this->_getCacheDriver();
288
        $cache2 = $this->_getCacheDriver();
289
290
        $this->assertTrue($cache1->save('key1', 1));
291
        $this->assertTrue($cache2->save('key2', 2));
292
293
        /* Both providers are initialized with the same namespace version, so
294
         * they can see entries set by each other.
295
         */
296
        $this->assertTrue($cache1->contains('key1'));
297
        $this->assertTrue($cache1->contains('key2'));
298
        $this->assertTrue($cache2->contains('key1'));
299
        $this->assertTrue($cache2->contains('key2'));
300
301
        /* Deleting all entries through one provider will only increment the
302
         * namespace version on that object (and in the cache itself, which new
303
         * instances will use to initialize). The second provider will retain
304
         * its original version and still see stale data.
305
         */
306
        $this->assertTrue($cache1->deleteAll());
307
        $this->assertFalse($cache1->contains('key1'));
308
        $this->assertFalse($cache1->contains('key2'));
309
        $this->assertTrue($cache2->contains('key1'));
310
        $this->assertTrue($cache2->contains('key2'));
311
312
        /* A new cache provider should not see the deleted entries, since its
313
         * namespace version will be initialized.
314
         */
315
        $cache3 = $this->_getCacheDriver();
316
        $this->assertFalse($cache3->contains('key1'));
317
        $this->assertFalse($cache3->contains('key2'));
318
    }
319
320
    public function testFlushAll()
321
    {
322
        $cache = $this->_getCacheDriver();
323
324
        $this->assertTrue($cache->save('key1', 1));
325
        $this->assertTrue($cache->save('key2', 2));
326
        $this->assertTrue($cache->flushAll());
327
        $this->assertFalse($cache->contains('key1'));
328
        $this->assertFalse($cache->contains('key2'));
329
    }
330
331
    public function testFlushAllAndNamespaceVersioningBetweenCaches()
332
    {
333
        if ( ! $this->isSharedStorage()) {
334
            $this->markTestSkipped('The cache storage needs to be shared.');
335
        }
336
337
        $cache1 = $this->_getCacheDriver();
338
        $cache2 = $this->_getCacheDriver();
339
340
        /* Deleting all elements from the first provider should increment its
341
         * namespace version before saving the first entry.
342
         */
343
        $cache1->deleteAll();
344
        $this->assertTrue($cache1->save('key1', 1));
345
346
        /* The second provider will be initialized with the same namespace
347
         * version upon its first save operation.
348
         */
349
        $this->assertTrue($cache2->save('key2', 2));
350
351
        /* Both providers have the same namespace version and can see entries
352
         * set by each other.
353
         */
354
        $this->assertTrue($cache1->contains('key1'));
355
        $this->assertTrue($cache1->contains('key2'));
356
        $this->assertTrue($cache2->contains('key1'));
357
        $this->assertTrue($cache2->contains('key2'));
358
359
        /* Flushing all entries through one cache will remove all entries from
360
         * the cache but leave their namespace version as-is.
361
         */
362
        $this->assertTrue($cache1->flushAll());
363
        $this->assertFalse($cache1->contains('key1'));
364
        $this->assertFalse($cache1->contains('key2'));
365
        $this->assertFalse($cache2->contains('key1'));
366
        $this->assertFalse($cache2->contains('key2'));
367
368
        /* Inserting a new entry will use the same, incremented namespace
369
         * version, and it will be visible to both providers.
370
         */
371
        $this->assertTrue($cache1->save('key1', 1));
372
        $this->assertTrue($cache1->contains('key1'));
373
        $this->assertTrue($cache2->contains('key1'));
374
375
        /* A new cache provider will be initialized with the original namespace
376
         * version and not share any visibility with the first two providers.
377
         */
378
        $cache3 = $this->_getCacheDriver();
379
        $this->assertFalse($cache3->contains('key1'));
380
        $this->assertFalse($cache3->contains('key2'));
381
        $this->assertTrue($cache3->save('key3', 3));
382
        $this->assertTrue($cache3->contains('key3'));
383
    }
384
385
    public function testNamespace()
386
    {
387
        $cache = $this->_getCacheDriver();
388
389
        $cache->setNamespace('ns1_');
390
391
        $this->assertTrue($cache->save('key1', 1));
392
        $this->assertTrue($cache->contains('key1'));
393
394
        $cache->setNamespace('ns2_');
395
396
        $this->assertFalse($cache->contains('key1'));
397
    }
398
399
    public function testDeleteAllNamespace()
400
    {
401
        $cache = $this->_getCacheDriver();
402
403
        $cache->setNamespace('ns1');
404
        $this->assertFalse($cache->contains('key1'));
405
        $cache->save('key1', 'test');
406
        $this->assertTrue($cache->contains('key1'));
407
408
        $cache->setNamespace('ns2');
409
        $this->assertFalse($cache->contains('key1'));
410
        $cache->save('key1', 'test');
411
        $this->assertTrue($cache->contains('key1'));
412
413
        $cache->setNamespace('ns1');
414
        $this->assertTrue($cache->contains('key1'));
415
        $cache->deleteAll();
416
        $this->assertFalse($cache->contains('key1'));
417
418
        $cache->setNamespace('ns2');
419
        $this->assertTrue($cache->contains('key1'));
420
        $cache->deleteAll();
421
        $this->assertFalse($cache->contains('key1'));
422
    }
423
424
    /**
425
     * @group DCOM-43
426
     */
427
    public function testGetStats()
428
    {
429
        $cache = $this->_getCacheDriver();
430
        $stats = $cache->getStats();
431
432
        $this->assertArrayHasKey(Cache::STATS_HITS, $stats);
433
        $this->assertArrayHasKey(Cache::STATS_MISSES, $stats);
434
        $this->assertArrayHasKey(Cache::STATS_UPTIME, $stats);
435
        $this->assertArrayHasKey(Cache::STATS_MEMORY_USAGE, $stats);
436
        $this->assertArrayHasKey(Cache::STATS_MEMORY_AVAILABLE, $stats);
437
    }
438
439
    public function testSaveReturnsTrueWithAndWithoutTTlSet()
440
    {
441
        $cache = $this->_getCacheDriver();
442
        $cache->deleteAll();
443
        $this->assertTrue($cache->save('without_ttl', 'without_ttl'));
444
        $this->assertTrue($cache->save('with_ttl', 'with_ttl', 3600));
445
    }
446
447
    /**
448
     * Return whether multiple cache providers share the same storage.
449
     *
450
     * This is used for skipping certain tests for shared storage behavior.
451
     *
452
     * @return bool
453
     */
454
    protected function isSharedStorage()
455
    {
456
        return true;
457
    }
458
459
    /**
460
     * @return \Doctrine\Common\Cache\CacheProvider
461
     */
462
    abstract protected function _getCacheDriver();
463
}
464