1ec56154b0404f7a7561af9ea852091fccc1b19e
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Unit / Cache / Backend / RedisBackendTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Unit\Cache\Backend;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16 use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
17
18 /**
19 * Testcase for the cache to redis backend
20 *
21 * This class has functional tests as well as implementation tests:
22 * - The functional tests make API calls to the backend and check expected behaviour
23 * - The implementation tests make additional calls with an own redis instance to
24 * check stored data structures in the redis server, which can not be checked
25 * by functional tests alone. Those tests will fail if any changes
26 * to the internal data structure are done.
27 *
28 * Warning:
29 * The unit tests use and flush redis database numbers 0 and 1!
30 */
31 class RedisBackendTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
32 {
33 /**
34 * If set, the tearDown() method will flush the cache used by this unit test.
35 *
36 * @var \TYPO3\CMS\Core\Cache\Backend\RedisBackend
37 */
38 protected $backend = null;
39
40 /**
41 * Own redis instance used in implementation tests
42 *
43 * @var Redis
44 */
45 protected $redis = null;
46
47 /**
48 * Set up this testcase
49 */
50 protected function setUp()
51 {
52 if (!extension_loaded('redis')) {
53 $this->markTestSkipped('redis extension was not available');
54 }
55 try {
56 if (!@fsockopen('127.0.0.1', 6379)) {
57 $this->markTestSkipped('redis server not reachable');
58 }
59 } catch (\Exception $e) {
60 $this->markTestSkipped('redis server not reachable');
61 }
62 }
63
64 /**
65 * Sets up the redis backend used for testing
66 */
67 protected function setUpBackend(array $backendOptions = [])
68 {
69 $mockCache = $this->createMock(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface::class);
70 $mockCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('TestCache'));
71 $this->backend = new \TYPO3\CMS\Core\Cache\Backend\RedisBackend('Testing', $backendOptions);
72 $this->backend->setCache($mockCache);
73 $this->backend->initializeObject();
74 }
75
76 /**
77 * Sets up an own redis instance for implementation tests
78 */
79 protected function setUpRedis()
80 {
81 $this->redis = new \Redis();
82 $this->redis->connect('127.0.0.1', 6379);
83 }
84
85 /**
86 * Tear down this testcase
87 */
88 protected function tearDown()
89 {
90 if ($this->backend instanceof \TYPO3\CMS\Core\Cache\Backend\RedisBackend) {
91 $this->backend->flush();
92 }
93 parent::tearDown();
94 }
95
96 /**
97 * @test Functional
98 */
99 public function initializeObjectThrowsNoExceptionIfGivenDatabaseWasSuccessfullySelected()
100 {
101 try {
102 $this->setUpBackend(['database' => 1]);
103 } catch (Exception $e) {
104 $this->assertTrue();
105 }
106 }
107
108 /**
109 * @test Functional
110 */
111 public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNotAnInteger()
112 {
113 $this->expectException(\InvalidArgumentException::class);
114 $this->expectExceptionCode(1279763057);
115
116 $this->setUpBackend(['database' => 'foo']);
117 }
118
119 /**
120 * @test Functional
121 */
122 public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNegative()
123 {
124 $this->expectException(\InvalidArgumentException::class);
125 $this->expectExceptionCode(1279763534);
126
127 $this->setUpBackend(['database' => -1]);
128 }
129
130 /**
131 * @test Functional
132 */
133 public function setCompressionThrowsExceptionIfCompressionParameterIsNotOfTypeBoolean()
134 {
135 $this->expectException(\InvalidArgumentException::class);
136 $this->expectExceptionCode(1289679153);
137
138 $this->setUpBackend(['compression' => 'foo']);
139 }
140
141 /**
142 * @test Functional
143 */
144 public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotInteger()
145 {
146 $this->expectException(\InvalidArgumentException::class);
147 $this->expectExceptionCode(1289679154);
148
149 $this->setUpBackend(['compressionLevel' => 'foo']);
150 }
151
152 /**
153 * @test Functional
154 */
155 public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotBetweenMinusOneAndNine()
156 {
157 $this->expectException(\InvalidArgumentException::class);
158 $this->expectExceptionCode(1289679155);
159
160 $this->setUpBackend(['compressionLevel' => 11]);
161 }
162
163 /**
164 * @test Functional
165 */
166 public function setConnectionTimeoutThrowsExceptionIfConnectionTimeoutIsNotInteger()
167 {
168 $this->expectException(\InvalidArgumentException::class);
169 $this->expectExceptionCode(1487849315);
170
171 $this->setUpBackend(['connectionTimeout' => 'foo']);
172 }
173
174 /**
175 * @test Functional
176 */
177 public function setConnectionTimeoutThrowsExceptionIfConnectionTimeoutIsNegative()
178 {
179 $this->expectException(\InvalidArgumentException::class);
180 $this->expectExceptionCode(1487849326);
181
182 $this->setUpBackend(['connectionTimeout' => -1]);
183 }
184
185 /**
186 * @test Functional
187 */
188 public function setThrowsExceptionIfIdentifierIsNotAString()
189 {
190 $this->expectException(\InvalidArgumentException::class);
191 $this->expectExceptionCode(1377006651);
192
193 $this->setUpBackend();
194 $this->backend->set([], 'data');
195 }
196
197 /**
198 * @test Functional
199 */
200 public function setThrowsExceptionIfDataIsNotAString()
201 {
202 $this->expectException(InvalidDataException::class);
203 $this->expectExceptionCode(1279469941);
204
205 $this->setUpBackend();
206 $this->backend->set($this->getUniqueId('identifier'), []);
207 }
208
209 /**
210 * @test Functional
211 */
212 public function setThrowsExceptionIfLifetimeIsNegative()
213 {
214 $this->expectException(\InvalidArgumentException::class);
215 $this->expectExceptionCode(1279487573);
216
217 $this->setUpBackend();
218 $this->backend->set($this->getUniqueId('identifier'), 'data', [], -42);
219 }
220
221 /**
222 * @test Functional
223 */
224 public function setThrowsExceptionIfLifetimeIsNotNullOrAnInteger()
225 {
226 $this->expectException(\InvalidArgumentException::class);
227 $this->expectExceptionCode(1279488008);
228
229 $this->setUpBackend();
230 $this->backend->set($this->getUniqueId('identifier'), 'data', [], []);
231 }
232
233 /**
234 * @test Implementation
235 */
236 public function setStoresEntriesInSelectedDatabase()
237 {
238 $this->setUpRedis();
239 $this->redis->select(1);
240 $this->setUpBackend(['database' => 1]);
241 $identifier = $this->getUniqueId('identifier');
242 $this->backend->set($identifier, 'data');
243 $this->assertTrue($this->redis->exists('identData:' . $identifier));
244 }
245
246 /**
247 * @test Implementation
248 */
249 public function setSavesStringDataTypeForIdentifierToDataEntry()
250 {
251 $this->setUpBackend();
252 $this->setUpRedis();
253 $identifier = $this->getUniqueId('identifier');
254 $this->backend->set($identifier, 'data');
255 $this->assertSame(\Redis::REDIS_STRING, $this->redis->type('identData:' . $identifier));
256 }
257
258 /**
259 * @test Implementation
260 */
261 public function setSavesEntryWithDefaultLifeTime()
262 {
263 $this->setUpBackend();
264 $this->setUpRedis();
265 $identifier = $this->getUniqueId('identifier');
266 $defaultLifetime = 42;
267 $this->backend->setDefaultLifetime($defaultLifetime);
268 $this->backend->set($identifier, 'data');
269 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
270 $this->assertSame($defaultLifetime, $lifetimeRegisteredInBackend);
271 }
272
273 /**
274 * @test Implementation
275 */
276 public function setSavesEntryWithSpecifiedLifeTime()
277 {
278 $this->setUpBackend();
279 $this->setUpRedis();
280 $identifier = $this->getUniqueId('identifier');
281 $lifetime = 43;
282 $this->backend->set($identifier, 'data', [], $lifetime);
283 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
284 $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
285 }
286
287 /**
288 * @test Implementation
289 */
290 public function setSavesEntryWithUnlimitedLifeTime()
291 {
292 $this->setUpBackend();
293 $this->setUpRedis();
294 $identifier = $this->getUniqueId('identifier');
295 $this->backend->set($identifier, 'data', [], 0);
296 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
297 $this->assertSame(31536000, $lifetimeRegisteredInBackend);
298 }
299
300 /**
301 * @test Functional
302 */
303 public function setOverwritesExistingEntryWithNewData()
304 {
305 $this->setUpBackend();
306 $data = 'data 1';
307 $identifier = $this->getUniqueId('identifier');
308 $this->backend->set($identifier, $data);
309 $otherData = 'data 2';
310 $this->backend->set($identifier, $otherData);
311 $fetchedData = $this->backend->get($identifier);
312 $this->assertSame($otherData, $fetchedData);
313 }
314
315 /**
316 * @test Implementation
317 */
318 public function setOverwritesExistingEntryWithSpecifiedLifetime()
319 {
320 $this->setUpBackend();
321 $this->setUpRedis();
322 $data = 'data';
323 $identifier = $this->getUniqueId('identifier');
324 $this->backend->set($identifier, $data);
325 $lifetime = 42;
326 $this->backend->set($identifier, $data, [], $lifetime);
327 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
328 $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
329 }
330
331 /**
332 * @test Implementation
333 */
334 public function setOverwritesExistingEntryWithNewDefaultLifetime()
335 {
336 $this->setUpBackend();
337 $this->setUpRedis();
338 $data = 'data';
339 $identifier = $this->getUniqueId('identifier');
340 $lifetime = 42;
341 $this->backend->set($identifier, $data, [], $lifetime);
342 $newDefaultLifetime = 43;
343 $this->backend->setDefaultLifetime($newDefaultLifetime);
344 $this->backend->set($identifier, $data, [], $newDefaultLifetime);
345 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
346 $this->assertSame($newDefaultLifetime, $lifetimeRegisteredInBackend);
347 }
348
349 /**
350 * @test Implementation
351 */
352 public function setOverwritesExistingEntryWithNewUnlimitedLifetime()
353 {
354 $this->setUpBackend();
355 $this->setUpRedis();
356 $data = 'data';
357 $identifier = $this->getUniqueId('identifier');
358 $lifetime = 42;
359 $this->backend->set($identifier, $data, [], $lifetime);
360 $this->backend->set($identifier, $data, [], 0);
361 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
362 $this->assertSame(31536000, $lifetimeRegisteredInBackend);
363 }
364
365 /**
366 * @test Implementation
367 */
368 public function setSavesSetDataTypeForIdentifierToTagsSet()
369 {
370 $this->setUpBackend();
371 $this->setUpRedis();
372 $identifier = $this->getUniqueId('identifier');
373 $this->backend->set($identifier, 'data', ['tag']);
374 $this->assertSame(\Redis::REDIS_SET, $this->redis->type('identTags:' . $identifier));
375 }
376
377 /**
378 * @test Implementation
379 */
380 public function setSavesSpecifiedTagsInIdentifierToTagsSet()
381 {
382 $this->setUpBackend();
383 $this->setUpRedis();
384 $identifier = $this->getUniqueId('identifier');
385 $tags = ['thatTag', 'thisTag'];
386 $this->backend->set($identifier, 'data', $tags);
387 $savedTags = $this->redis->sMembers('identTags:' . $identifier);
388 sort($savedTags);
389 $this->assertSame($tags, $savedTags);
390 }
391
392 /**
393 * @test Implementation
394 */
395 public function setRemovesAllPreviouslySetTagsFromIdentifierToTagsSet()
396 {
397 $this->setUpBackend();
398 $this->setUpRedis();
399 $identifier = $this->getUniqueId('identifier');
400 $tags = ['fooTag', 'barTag'];
401 $this->backend->set($identifier, 'data', $tags);
402 $this->backend->set($identifier, 'data', []);
403 $this->assertSame([], $this->redis->sMembers('identTags:' . $identifier));
404 }
405
406 /**
407 * @test Implementation
408 */
409 public function setRemovesMultiplePreviouslySetTagsFromIdentifierToTagsSet()
410 {
411 $this->setUpBackend();
412 $this->setUpRedis();
413 $identifier = $this->getUniqueId('identifier');
414 $firstTagSet = ['tag1', 'tag2', 'tag3', 'tag4'];
415 $this->backend->set($identifier, 'data', $firstTagSet);
416 $secondTagSet = ['tag1', 'tag3'];
417 $this->backend->set($identifier, 'data', $secondTagSet);
418 $actualTagSet = $this->redis->sMembers('identTags:' . $identifier);
419 sort($actualTagSet);
420 $this->assertSame($secondTagSet, $actualTagSet);
421 }
422
423 /**
424 * @test Implementation
425 */
426 public function setSavesSetDataTypeForTagToIdentifiersSet()
427 {
428 $this->setUpBackend();
429 $this->setUpRedis();
430 $identifier = $this->getUniqueId('identifier');
431 $tag = 'tag';
432 $this->backend->set($identifier, 'data', [$tag]);
433 $this->assertSame(\Redis::REDIS_SET, $this->redis->type('tagIdents:' . $tag));
434 }
435
436 /**
437 * @test Implementation
438 */
439 public function setSavesIdentifierInTagToIdentifiersSetOfSpecifiedTag()
440 {
441 $this->setUpBackend();
442 $this->setUpRedis();
443 $identifier = $this->getUniqueId('identifier');
444 $tag = 'thisTag';
445 $this->backend->set($identifier, 'data', [$tag]);
446 $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
447 $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
448 }
449
450 /**
451 * @test Implementation
452 */
453 public function setAppendsSecondIdentifierInTagToIdentifiersEntry()
454 {
455 $this->setUpBackend();
456 $this->setUpRedis();
457 $firstIdentifier = $this->getUniqueId('identifier1-');
458 $tag = 'thisTag';
459 $this->backend->set($firstIdentifier, 'data', [$tag]);
460 $secondIdentifier = $this->getUniqueId('identifier2-');
461 $this->backend->set($secondIdentifier, 'data', [$tag]);
462 $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
463 sort($savedTagToIdentifiersMemberArray);
464 $identifierArray = [$firstIdentifier, $secondIdentifier];
465 sort($identifierArray);
466 $this->assertSame([$firstIdentifier, $secondIdentifier], $savedTagToIdentifiersMemberArray);
467 }
468
469 /**
470 * @test Implementation
471 */
472 public function setRemovesIdentifierFromTagToIdentifiersEntryIfTagIsOmittedOnConsecutiveSet()
473 {
474 $this->setUpBackend();
475 $this->setUpRedis();
476 $identifier = $this->getUniqueId('identifier');
477 $tag = 'thisTag';
478 $this->backend->set($identifier, 'data', [$tag]);
479 $this->backend->set($identifier, 'data', []);
480 $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
481 $this->assertSame([], $savedTagToIdentifiersMemberArray);
482 }
483
484 /**
485 * @test Implementation
486 */
487 public function setAddsIdentifierInTagToIdentifiersEntryIfTagIsAddedOnConsecutiveSet()
488 {
489 $this->setUpBackend();
490 $this->setUpRedis();
491 $identifier = $this->getUniqueId('identifier');
492 $this->backend->set($identifier, 'data');
493 $tag = 'thisTag';
494 $this->backend->set($identifier, 'data', [$tag]);
495 $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
496 $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
497 }
498
499 /**
500 * @test Implementation
501 */
502 public function setSavesCompressedDataWithEnabledCompression()
503 {
504 $this->setUpBackend([
505 'compression' => true
506 ]);
507 $this->setUpRedis();
508 $identifier = $this->getUniqueId('identifier');
509 $data = 'some data ' . microtime();
510 $this->backend->set($identifier, $data);
511 $uncompresedStoredData = '';
512 try {
513 $uncompresedStoredData = @gzuncompress($this->redis->get(('identData:' . $identifier)));
514 } catch (\Exception $e) {
515 }
516 $this->assertEquals($data, $uncompresedStoredData, 'Original and compressed data don\'t match');
517 }
518
519 /**
520 * @test Implementation
521 */
522 public function setSavesPlaintextDataWithEnabledCompressionAndCompressionLevel0()
523 {
524 $this->setUpBackend([
525 'compression' => true,
526 'compressionLevel' => 0
527 ]);
528 $this->setUpRedis();
529 $identifier = $this->getUniqueId('identifier');
530 $data = 'some data ' . microtime();
531 $this->backend->set($identifier, $data);
532 $this->assertGreaterThan(0, substr_count($this->redis->get('identData:' . $identifier), $data), 'Plaintext data not found');
533 }
534
535 /**
536 * @test Functional
537 */
538 public function hasThrowsExceptionIfIdentifierIsNotAString()
539 {
540 $this->expectException(\InvalidArgumentException::class);
541 $this->expectExceptionCode(1377006653);
542
543 $this->setUpBackend();
544 $this->backend->has([]);
545 }
546
547 /**
548 * @test Functional
549 */
550 public function hasReturnsFalseForNotExistingEntry()
551 {
552 $this->setUpBackend();
553 $identifier = $this->getUniqueId('identifier');
554 $this->assertFalse($this->backend->has($identifier));
555 }
556
557 /**
558 * @test Functional
559 */
560 public function hasReturnsTrueForPreviouslySetEntry()
561 {
562 $this->setUpBackend();
563 $identifier = $this->getUniqueId('identifier');
564 $this->backend->set($identifier, 'data');
565 $this->assertTrue($this->backend->has($identifier));
566 }
567
568 /**
569 * @test Functional
570 */
571 public function getThrowsExceptionIfIdentifierIsNotAString()
572 {
573 $this->expectException(\InvalidArgumentException::class);
574 //@todo Add exception code with redis extension
575
576 $this->setUpBackend();
577 $this->backend->get([]);
578 }
579
580 /**
581 * @test Functional
582 */
583 public function getReturnsPreviouslyCompressedSetEntry()
584 {
585 $this->setUpBackend([
586 'compression' => true
587 ]);
588 $data = 'data';
589 $identifier = $this->getUniqueId('identifier');
590 $this->backend->set($identifier, $data);
591 $fetchedData = $this->backend->get($identifier);
592 $this->assertSame($data, $fetchedData);
593 }
594
595 /**
596 * @test Functional
597 */
598 public function getReturnsPreviouslySetEntry()
599 {
600 $this->setUpBackend();
601 $data = 'data';
602 $identifier = $this->getUniqueId('identifier');
603 $this->backend->set($identifier, $data);
604 $fetchedData = $this->backend->get($identifier);
605 $this->assertSame($data, $fetchedData);
606 }
607
608 /**
609 * @test Functional
610 */
611 public function removeThrowsExceptionIfIdentifierIsNotAString()
612 {
613 $this->expectException(\InvalidArgumentException::class);
614 $this->expectExceptionCode(1377006654);
615
616 $this->setUpBackend();
617 $this->backend->remove([]);
618 }
619
620 /**
621 * @test Functional
622 */
623 public function removeReturnsFalseIfNoEntryWasDeleted()
624 {
625 $this->setUpBackend();
626 $this->assertFalse($this->backend->remove($this->getUniqueId('identifier')));
627 }
628
629 /**
630 * @test Functional
631 */
632 public function removeReturnsTrueIfAnEntryWasDeleted()
633 {
634 $this->setUpBackend();
635 $identifier = $this->getUniqueId('identifier');
636 $this->backend->set($identifier, 'data');
637 $this->assertTrue($this->backend->remove($identifier));
638 }
639
640 /**
641 * @test Functional
642 */
643 public function removeDeletesEntryFromCache()
644 {
645 $this->setUpBackend();
646 $identifier = $this->getUniqueId('identifier');
647 $this->backend->set($identifier, 'data');
648 $this->backend->remove($identifier);
649 $this->assertFalse($this->backend->has($identifier));
650 }
651
652 /**
653 * @test Implementation
654 */
655 public function removeDeletesIdentifierToTagEntry()
656 {
657 $this->setUpBackend();
658 $this->setUpRedis();
659 $identifier = $this->getUniqueId('identifier');
660 $tag = 'thisTag';
661 $this->backend->set($identifier, 'data', [$tag]);
662 $this->backend->remove($identifier);
663 $this->assertFalse($this->redis->exists('identTags:' . $identifier));
664 }
665
666 /**
667 * @test Implementation
668 */
669 public function removeDeletesIdentifierFromTagToIdentifiersSet()
670 {
671 $this->setUpBackend();
672 $this->setUpRedis();
673 $identifier = $this->getUniqueId('identifier');
674 $tag = 'thisTag';
675 $this->backend->set($identifier, 'data', [$tag]);
676 $this->backend->remove($identifier);
677 $tagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
678 $this->assertSame([], $tagToIdentifiersMemberArray);
679 }
680
681 /**
682 * @test Implementation
683 */
684 public function removeDeletesIdentifierFromTagToIdentifiersSetWithMultipleEntries()
685 {
686 $this->setUpBackend();
687 $this->setUpRedis();
688 $firstIdentifier = $this->getUniqueId('identifier');
689 $secondIdentifier = $this->getUniqueId('identifier');
690 $tag = 'thisTag';
691 $this->backend->set($firstIdentifier, 'data', [$tag]);
692 $this->backend->set($secondIdentifier, 'data', [$tag]);
693 $this->backend->remove($firstIdentifier);
694 $tagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
695 $this->assertSame([$secondIdentifier], $tagToIdentifiersMemberArray);
696 }
697
698 /**
699 * @test Functional
700 */
701 public function findIdentifiersByTagThrowsExceptionIfTagIsNotAString()
702 {
703 $this->expectException(\InvalidArgumentException::class);
704 $this->expectExceptionCode(1377006655);
705
706 $this->setUpBackend();
707 $this->backend->findIdentifiersByTag([]);
708 }
709
710 /**
711 * @test Functional
712 */
713 public function findIdentifiersByTagReturnsEmptyArrayForNotExistingTag()
714 {
715 $this->setUpBackend();
716 $this->assertSame([], $this->backend->findIdentifiersByTag('thisTag'));
717 }
718
719 /**
720 * @test Functional
721 */
722 public function findIdentifiersByTagReturnsAllIdentifiersTagedWithSpecifiedTag()
723 {
724 $this->setUpBackend();
725 $firstIdentifier = $this->getUniqueId('identifier1-');
726 $secondIdentifier = $this->getUniqueId('identifier2-');
727 $thirdIdentifier = $this->getUniqueId('identifier3-');
728 $tagsForFirstIdentifier = ['thisTag'];
729 $tagsForSecondIdentifier = ['thatTag'];
730 $tagsForThirdIdentifier = ['thisTag', 'thatTag'];
731 $this->backend->set($firstIdentifier, 'data', $tagsForFirstIdentifier);
732 $this->backend->set($secondIdentifier, 'data', $tagsForSecondIdentifier);
733 $this->backend->set($thirdIdentifier, 'data', $tagsForThirdIdentifier);
734 $expectedResult = [$firstIdentifier, $thirdIdentifier];
735 $actualResult = $this->backend->findIdentifiersByTag('thisTag');
736 sort($actualResult);
737 $this->assertSame($expectedResult, $actualResult);
738 }
739
740 /**
741 * @test Implementation
742 */
743 public function flushRemovesAllEntriesFromCache()
744 {
745 $this->setUpBackend();
746 $this->setUpRedis();
747 $identifier = $this->getUniqueId('identifier');
748 $this->backend->set($identifier, 'data');
749 $this->backend->flush();
750 $this->assertSame([], $this->redis->getKeys('*'));
751 }
752
753 /**
754 * @test Functional
755 */
756 public function flushByTagThrowsExceptionIfTagIsNotAString()
757 {
758 $this->expectException(\InvalidArgumentException::class);
759 $this->expectExceptionCode(1377006656);
760
761 $this->setUpBackend();
762 $this->backend->flushByTag([]);
763 }
764
765 /**
766 * @test Functional
767 */
768 public function flushByTagRemovesEntriesTaggedWithSpecifiedTag()
769 {
770 $this->setUpBackend();
771 $identifier = $this->getUniqueId('identifier');
772 $this->backend->set($identifier . 'A', 'data', ['tag1']);
773 $this->backend->set($identifier . 'B', 'data', ['tag2']);
774 $this->backend->set($identifier . 'C', 'data', ['tag1', 'tag2']);
775 $this->backend->flushByTag('tag1');
776 $expectedResult = [false, true, false];
777 $actualResult = [
778 $this->backend->has($identifier . 'A'),
779 $this->backend->has($identifier . 'B'),
780 $this->backend->has($identifier . 'C')
781 ];
782 $this->assertSame($expectedResult, $actualResult);
783 }
784
785 /**
786 * @test Functional
787 */
788 public function flushByTagsRemovesEntriesTaggedWithSpecifiedTags()
789 {
790 $this->setUpBackend();
791 $identifier = $this->getUniqueId('identifier');
792 $this->backend->set($identifier . 'A', 'data', ['tag1']);
793 $this->backend->set($identifier . 'B', 'data', ['tag2']);
794 $this->backend->set($identifier . 'C', 'data', ['tag1', 'tag2']);
795 $this->backend->set($identifier . 'D', 'data', ['tag3']);
796 $this->backend->flushByTags(['tag1', 'tag2']);
797 $expectedResult = [false, false, false, true];
798 $actualResult = [
799 $this->backend->has($identifier . 'A'),
800 $this->backend->has($identifier . 'B'),
801 $this->backend->has($identifier . 'C'),
802 $this->backend->has($identifier . 'D')
803 ];
804 $this->assertSame($expectedResult, $actualResult);
805 }
806
807 /**
808 * @test Implementation
809 */
810 public function flushByTagRemovesTemporarySet()
811 {
812 $this->setUpBackend();
813 $this->setUpRedis();
814 $identifier = $this->getUniqueId('identifier');
815 $this->backend->set($identifier . 'A', 'data', ['tag1']);
816 $this->backend->set($identifier . 'C', 'data', ['tag1', 'tag2']);
817 $this->backend->flushByTag('tag1');
818 $this->assertSame([], $this->redis->getKeys('temp*'));
819 }
820
821 /**
822 * @test Implementation
823 */
824 public function flushByTagRemovesIdentifierToTagsSetOfEntryTaggedWithGivenTag()
825 {
826 $this->setUpBackend();
827 $this->setUpRedis();
828 $identifier = $this->getUniqueId('identifier');
829 $tag = 'tag1';
830 $this->backend->set($identifier, 'data', [$tag]);
831 $this->backend->flushByTag($tag);
832 $this->assertFalse($this->redis->exists('identTags:' . $identifier));
833 }
834
835 /**
836 * @test Implementation
837 */
838 public function flushByTagDoesNotRemoveIdentifierToTagsSetOfUnrelatedEntry()
839 {
840 $this->setUpBackend();
841 $this->setUpRedis();
842 $identifierToBeRemoved = $this->getUniqueId('identifier');
843 $tagToRemove = 'tag1';
844 $this->backend->set($identifierToBeRemoved, 'data', [$tagToRemove]);
845 $identifierNotToBeRemoved = $this->getUniqueId('identifier');
846 $tagNotToRemove = 'tag2';
847 $this->backend->set($identifierNotToBeRemoved, 'data', [$tagNotToRemove]);
848 $this->backend->flushByTag($tagToRemove);
849 $this->assertSame([$tagNotToRemove], $this->redis->sMembers('identTags:' . $identifierNotToBeRemoved));
850 }
851
852 /**
853 * @test Implementation
854 */
855 public function flushByTagRemovesTagToIdentifiersSetOfGivenTag()
856 {
857 $this->setUpBackend();
858 $this->setUpRedis();
859 $identifier = $this->getUniqueId('identifier');
860 $tag = 'tag1';
861 $this->backend->set($identifier, 'data', [$tag]);
862 $this->backend->flushByTag($tag);
863 $this->assertFalse($this->redis->exists('tagIdents:' . $tag));
864 }
865
866 /**
867 * @test Implementation
868 */
869 public function flushByTagRemovesIdentifiersTaggedWithGivenTagFromTagToIdentifiersSets()
870 {
871 $this->setUpBackend();
872 $this->setUpRedis();
873 $identifier = $this->getUniqueId('identifier');
874 $this->backend->set($identifier . 'A', 'data', ['tag1', 'tag2']);
875 $this->backend->set($identifier . 'B', 'data', ['tag1', 'tag2']);
876 $this->backend->set($identifier . 'C', 'data', ['tag2']);
877 $this->backend->flushByTag('tag1');
878 $this->assertSame([$identifier . 'C'], $this->redis->sMembers('tagIdents:tag2'));
879 }
880
881 /**
882 * @test Implementation
883 */
884 public function collectGarbageDoesNotRemoveNotExpiredIdentifierToDataEntry()
885 {
886 $this->setUpBackend();
887 $this->setUpRedis();
888 $identifier = $this->getUniqueId('identifier');
889 $this->backend->set($identifier . 'A', 'data', ['tag']);
890 $this->backend->set($identifier . 'B', 'data', ['tag']);
891 $this->redis->delete('identData:' . $identifier . 'A');
892 $this->backend->collectGarbage();
893 $this->assertTrue($this->redis->exists('identData:' . $identifier . 'B'));
894 }
895
896 /**
897 * @test Implementation
898 */
899 public function collectGarbageRemovesLeftOverIdentifierToTagsSet()
900 {
901 $this->setUpBackend();
902 $this->setUpRedis();
903 $identifier = $this->getUniqueId('identifier');
904 $this->backend->set($identifier . 'A', 'data', ['tag']);
905 $this->backend->set($identifier . 'B', 'data', ['tag']);
906 $this->redis->delete('identData:' . $identifier . 'A');
907 $this->backend->collectGarbage();
908 $expectedResult = [false, true];
909 $actualResult = [
910 $this->redis->exists('identTags:' . $identifier . 'A'),
911 $this->redis->exists('identTags:' . $identifier . 'B')
912 ];
913 $this->assertSame($expectedResult, $actualResult);
914 }
915
916 /**
917 * @test Implementation
918 */
919 public function collectGarbageRemovesExpiredIdentifierFromTagsToIdentifierSet()
920 {
921 $this->setUpBackend();
922 $this->setUpRedis();
923 $identifier = $this->getUniqueId('identifier');
924 $this->backend->set($identifier . 'A', 'data', ['tag1', 'tag2']);
925 $this->backend->set($identifier . 'B', 'data', ['tag2']);
926 $this->redis->delete('identData:' . $identifier . 'A');
927 $this->backend->collectGarbage();
928 $expectedResult = [
929 [],
930 [$identifier . 'B']
931 ];
932 $actualResult = [
933 $this->redis->sMembers('tagIdents:tag1'),
934 $this->redis->sMembers('tagIdents:tag2')
935 ];
936 $this->assertSame($expectedResult, $actualResult);
937 }
938 }