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