92bf2f541d0b08981edb972d6367572622bb41d6
[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;
39
40 /**
41 * Own redis instance used in implementation tests
42 *
43 * @var Redis
44 */
45 protected $redis;
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 $result = $this->redis->exists('identData:' . $identifier);
244 if (is_int($result)) {
245 // Since 3.1.4 of phpredis/phpredis the return types has been changed
246 $result = (bool)$result;
247 }
248 $this->assertTrue($result);
249 }
250
251 /**
252 * @test Implementation
253 */
254 public function setSavesStringDataTypeForIdentifierToDataEntry()
255 {
256 $this->setUpBackend();
257 $this->setUpRedis();
258 $identifier = $this->getUniqueId('identifier');
259 $this->backend->set($identifier, 'data');
260 $this->assertSame(\Redis::REDIS_STRING, $this->redis->type('identData:' . $identifier));
261 }
262
263 /**
264 * @test Implementation
265 */
266 public function setSavesEntryWithDefaultLifeTime()
267 {
268 $this->setUpBackend();
269 $this->setUpRedis();
270 $identifier = $this->getUniqueId('identifier');
271 $defaultLifetime = 42;
272 $this->backend->setDefaultLifetime($defaultLifetime);
273 $this->backend->set($identifier, 'data');
274 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
275 $this->assertSame($defaultLifetime, $lifetimeRegisteredInBackend);
276 }
277
278 /**
279 * @test Implementation
280 */
281 public function setSavesEntryWithSpecifiedLifeTime()
282 {
283 $this->setUpBackend();
284 $this->setUpRedis();
285 $identifier = $this->getUniqueId('identifier');
286 $lifetime = 43;
287 $this->backend->set($identifier, 'data', [], $lifetime);
288 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
289 $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
290 }
291
292 /**
293 * @test Implementation
294 */
295 public function setSavesEntryWithUnlimitedLifeTime()
296 {
297 $this->setUpBackend();
298 $this->setUpRedis();
299 $identifier = $this->getUniqueId('identifier');
300 $this->backend->set($identifier, 'data', [], 0);
301 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
302 $this->assertSame(31536000, $lifetimeRegisteredInBackend);
303 }
304
305 /**
306 * @test Functional
307 */
308 public function setOverwritesExistingEntryWithNewData()
309 {
310 $this->setUpBackend();
311 $data = 'data 1';
312 $identifier = $this->getUniqueId('identifier');
313 $this->backend->set($identifier, $data);
314 $otherData = 'data 2';
315 $this->backend->set($identifier, $otherData);
316 $fetchedData = $this->backend->get($identifier);
317 $this->assertSame($otherData, $fetchedData);
318 }
319
320 /**
321 * @test Implementation
322 */
323 public function setOverwritesExistingEntryWithSpecifiedLifetime()
324 {
325 $this->setUpBackend();
326 $this->setUpRedis();
327 $data = 'data';
328 $identifier = $this->getUniqueId('identifier');
329 $this->backend->set($identifier, $data);
330 $lifetime = 42;
331 $this->backend->set($identifier, $data, [], $lifetime);
332 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
333 $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
334 }
335
336 /**
337 * @test Implementation
338 */
339 public function setOverwritesExistingEntryWithNewDefaultLifetime()
340 {
341 $this->setUpBackend();
342 $this->setUpRedis();
343 $data = 'data';
344 $identifier = $this->getUniqueId('identifier');
345 $lifetime = 42;
346 $this->backend->set($identifier, $data, [], $lifetime);
347 $newDefaultLifetime = 43;
348 $this->backend->setDefaultLifetime($newDefaultLifetime);
349 $this->backend->set($identifier, $data, [], $newDefaultLifetime);
350 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
351 $this->assertSame($newDefaultLifetime, $lifetimeRegisteredInBackend);
352 }
353
354 /**
355 * @test Implementation
356 */
357 public function setOverwritesExistingEntryWithNewUnlimitedLifetime()
358 {
359 $this->setUpBackend();
360 $this->setUpRedis();
361 $data = 'data';
362 $identifier = $this->getUniqueId('identifier');
363 $lifetime = 42;
364 $this->backend->set($identifier, $data, [], $lifetime);
365 $this->backend->set($identifier, $data, [], 0);
366 $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
367 $this->assertSame(31536000, $lifetimeRegisteredInBackend);
368 }
369
370 /**
371 * @test Implementation
372 */
373 public function setSavesSetDataTypeForIdentifierToTagsSet()
374 {
375 $this->setUpBackend();
376 $this->setUpRedis();
377 $identifier = $this->getUniqueId('identifier');
378 $this->backend->set($identifier, 'data', ['tag']);
379 $this->assertSame(\Redis::REDIS_SET, $this->redis->type('identTags:' . $identifier));
380 }
381
382 /**
383 * @test Implementation
384 */
385 public function setSavesSpecifiedTagsInIdentifierToTagsSet()
386 {
387 $this->setUpBackend();
388 $this->setUpRedis();
389 $identifier = $this->getUniqueId('identifier');
390 $tags = ['thatTag', 'thisTag'];
391 $this->backend->set($identifier, 'data', $tags);
392 $savedTags = $this->redis->sMembers('identTags:' . $identifier);
393 sort($savedTags);
394 $this->assertSame($tags, $savedTags);
395 }
396
397 /**
398 * @test Implementation
399 */
400 public function setRemovesAllPreviouslySetTagsFromIdentifierToTagsSet()
401 {
402 $this->setUpBackend();
403 $this->setUpRedis();
404 $identifier = $this->getUniqueId('identifier');
405 $tags = ['fooTag', 'barTag'];
406 $this->backend->set($identifier, 'data', $tags);
407 $this->backend->set($identifier, 'data', []);
408 $this->assertSame([], $this->redis->sMembers('identTags:' . $identifier));
409 }
410
411 /**
412 * @test Implementation
413 */
414 public function setRemovesMultiplePreviouslySetTagsFromIdentifierToTagsSet()
415 {
416 $this->setUpBackend();
417 $this->setUpRedis();
418 $identifier = $this->getUniqueId('identifier');
419 $firstTagSet = ['tag1', 'tag2', 'tag3', 'tag4'];
420 $this->backend->set($identifier, 'data', $firstTagSet);
421 $secondTagSet = ['tag1', 'tag3'];
422 $this->backend->set($identifier, 'data', $secondTagSet);
423 $actualTagSet = $this->redis->sMembers('identTags:' . $identifier);
424 sort($actualTagSet);
425 $this->assertSame($secondTagSet, $actualTagSet);
426 }
427
428 /**
429 * @test Implementation
430 */
431 public function setSavesSetDataTypeForTagToIdentifiersSet()
432 {
433 $this->setUpBackend();
434 $this->setUpRedis();
435 $identifier = $this->getUniqueId('identifier');
436 $tag = 'tag';
437 $this->backend->set($identifier, 'data', [$tag]);
438 $this->assertSame(\Redis::REDIS_SET, $this->redis->type('tagIdents:' . $tag));
439 }
440
441 /**
442 * @test Implementation
443 */
444 public function setSavesIdentifierInTagToIdentifiersSetOfSpecifiedTag()
445 {
446 $this->setUpBackend();
447 $this->setUpRedis();
448 $identifier = $this->getUniqueId('identifier');
449 $tag = 'thisTag';
450 $this->backend->set($identifier, 'data', [$tag]);
451 $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
452 $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
453 }
454
455 /**
456 * @test Implementation
457 */
458 public function setAppendsSecondIdentifierInTagToIdentifiersEntry()
459 {
460 $this->setUpBackend();
461 $this->setUpRedis();
462 $firstIdentifier = $this->getUniqueId('identifier1-');
463 $tag = 'thisTag';
464 $this->backend->set($firstIdentifier, 'data', [$tag]);
465 $secondIdentifier = $this->getUniqueId('identifier2-');
466 $this->backend->set($secondIdentifier, 'data', [$tag]);
467 $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
468 sort($savedTagToIdentifiersMemberArray);
469 $identifierArray = [$firstIdentifier, $secondIdentifier];
470 sort($identifierArray);
471 $this->assertSame([$firstIdentifier, $secondIdentifier], $savedTagToIdentifiersMemberArray);
472 }
473
474 /**
475 * @test Implementation
476 */
477 public function setRemovesIdentifierFromTagToIdentifiersEntryIfTagIsOmittedOnConsecutiveSet()
478 {
479 $this->setUpBackend();
480 $this->setUpRedis();
481 $identifier = $this->getUniqueId('identifier');
482 $tag = 'thisTag';
483 $this->backend->set($identifier, 'data', [$tag]);
484 $this->backend->set($identifier, 'data', []);
485 $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
486 $this->assertSame([], $savedTagToIdentifiersMemberArray);
487 }
488
489 /**
490 * @test Implementation
491 */
492 public function setAddsIdentifierInTagToIdentifiersEntryIfTagIsAddedOnConsecutiveSet()
493 {
494 $this->setUpBackend();
495 $this->setUpRedis();
496 $identifier = $this->getUniqueId('identifier');
497 $this->backend->set($identifier, 'data');
498 $tag = 'thisTag';
499 $this->backend->set($identifier, 'data', [$tag]);
500 $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
501 $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
502 }
503
504 /**
505 * @test Implementation
506 */
507 public function setSavesCompressedDataWithEnabledCompression()
508 {
509 $this->setUpBackend([
510 'compression' => true
511 ]);
512 $this->setUpRedis();
513 $identifier = $this->getUniqueId('identifier');
514 $data = 'some data ' . microtime();
515 $this->backend->set($identifier, $data);
516 $uncompresedStoredData = '';
517 try {
518 $uncompresedStoredData = @gzuncompress($this->redis->get('identData:' . $identifier));
519 } catch (\Exception $e) {
520 }
521 $this->assertEquals($data, $uncompresedStoredData, 'Original and compressed data don\'t match');
522 }
523
524 /**
525 * @test Implementation
526 */
527 public function setSavesPlaintextDataWithEnabledCompressionAndCompressionLevel0()
528 {
529 $this->setUpBackend([
530 'compression' => true,
531 'compressionLevel' => 0
532 ]);
533 $this->setUpRedis();
534 $identifier = $this->getUniqueId('identifier');
535 $data = 'some data ' . microtime();
536 $this->backend->set($identifier, $data);
537 $this->assertGreaterThan(0, substr_count($this->redis->get('identData:' . $identifier), $data), 'Plaintext data not found');
538 }
539
540 /**
541 * @test Functional
542 */
543 public function hasThrowsExceptionIfIdentifierIsNotAString()
544 {
545 $this->expectException(\InvalidArgumentException::class);
546 $this->expectExceptionCode(1377006653);
547
548 $this->setUpBackend();
549 $this->backend->has([]);
550 }
551
552 /**
553 * @test Functional
554 */
555 public function hasReturnsFalseForNotExistingEntry()
556 {
557 $this->setUpBackend();
558 $identifier = $this->getUniqueId('identifier');
559 $this->assertFalse($this->backend->has($identifier));
560 }
561
562 /**
563 * @test Functional
564 */
565 public function hasReturnsTrueForPreviouslySetEntry()
566 {
567 $this->setUpBackend();
568 $identifier = $this->getUniqueId('identifier');
569 $this->backend->set($identifier, 'data');
570 $this->assertTrue($this->backend->has($identifier));
571 }
572
573 /**
574 * @test Functional
575 */
576 public function getThrowsExceptionIfIdentifierIsNotAString()
577 {
578 $this->expectException(\InvalidArgumentException::class);
579 //@todo Add exception code with redis extension
580
581 $this->setUpBackend();
582 $this->backend->get([]);
583 }
584
585 /**
586 * @test Functional
587 */
588 public function getReturnsPreviouslyCompressedSetEntry()
589 {
590 $this->setUpBackend([
591 'compression' => true
592 ]);
593 $data = 'data';
594 $identifier = $this->getUniqueId('identifier');
595 $this->backend->set($identifier, $data);
596 $fetchedData = $this->backend->get($identifier);
597 $this->assertSame($data, $fetchedData);
598 }
599
600 /**
601 * @test Functional
602 */
603 public function getReturnsPreviouslySetEntry()
604 {
605 $this->setUpBackend();
606 $data = 'data';
607 $identifier = $this->getUniqueId('identifier');
608 $this->backend->set($identifier, $data);
609 $fetchedData = $this->backend->get($identifier);
610 $this->assertSame($data, $fetchedData);
611 }
612
613 /**
614 * @test Functional
615 */
616 public function removeThrowsExceptionIfIdentifierIsNotAString()
617 {
618 $this->expectException(\InvalidArgumentException::class);
619 $this->expectExceptionCode(1377006654);
620
621 $this->setUpBackend();
622 $this->backend->remove([]);
623 }
624
625 /**
626 * @test Functional
627 */
628 public function removeReturnsFalseIfNoEntryWasDeleted()
629 {
630 $this->setUpBackend();
631 $this->assertFalse($this->backend->remove($this->getUniqueId('identifier')));
632 }
633
634 /**
635 * @test Functional
636 */
637 public function removeReturnsTrueIfAnEntryWasDeleted()
638 {
639 $this->setUpBackend();
640 $identifier = $this->getUniqueId('identifier');
641 $this->backend->set($identifier, 'data');
642 $this->assertTrue($this->backend->remove($identifier));
643 }
644
645 /**
646 * @test Functional
647 */
648 public function removeDeletesEntryFromCache()
649 {
650 $this->setUpBackend();
651 $identifier = $this->getUniqueId('identifier');
652 $this->backend->set($identifier, 'data');
653 $this->backend->remove($identifier);
654 $this->assertFalse($this->backend->has($identifier));
655 }
656
657 /**
658 * @test Implementation
659 */
660 public function removeDeletesIdentifierToTagEntry()
661 {
662 $this->setUpBackend();
663 $this->setUpRedis();
664 $identifier = $this->getUniqueId('identifier');
665 $tag = 'thisTag';
666 $this->backend->set($identifier, 'data', [$tag]);
667 $this->backend->remove($identifier);
668 $result = $this->redis->exists('identTags:' . $identifier);
669 if (is_int($result)) {
670 // Since 3.1.4 of phpredis/phpredis the return types has been changed
671 $result = (bool)$result;
672 }
673 $this->assertFalse($result);
674 }
675
676 /**
677 * @test Implementation
678 */
679 public function removeDeletesIdentifierFromTagToIdentifiersSet()
680 {
681 $this->setUpBackend();
682 $this->setUpRedis();
683 $identifier = $this->getUniqueId('identifier');
684 $tag = 'thisTag';
685 $this->backend->set($identifier, 'data', [$tag]);
686 $this->backend->remove($identifier);
687 $tagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
688 $this->assertSame([], $tagToIdentifiersMemberArray);
689 }
690
691 /**
692 * @test Implementation
693 */
694 public function removeDeletesIdentifierFromTagToIdentifiersSetWithMultipleEntries()
695 {
696 $this->setUpBackend();
697 $this->setUpRedis();
698 $firstIdentifier = $this->getUniqueId('identifier');
699 $secondIdentifier = $this->getUniqueId('identifier');
700 $tag = 'thisTag';
701 $this->backend->set($firstIdentifier, 'data', [$tag]);
702 $this->backend->set($secondIdentifier, 'data', [$tag]);
703 $this->backend->remove($firstIdentifier);
704 $tagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
705 $this->assertSame([$secondIdentifier], $tagToIdentifiersMemberArray);
706 }
707
708 /**
709 * @test Functional
710 */
711 public function findIdentifiersByTagThrowsExceptionIfTagIsNotAString()
712 {
713 $this->expectException(\InvalidArgumentException::class);
714 $this->expectExceptionCode(1377006655);
715
716 $this->setUpBackend();
717 $this->backend->findIdentifiersByTag([]);
718 }
719
720 /**
721 * @test Functional
722 */
723 public function findIdentifiersByTagReturnsEmptyArrayForNotExistingTag()
724 {
725 $this->setUpBackend();
726 $this->assertSame([], $this->backend->findIdentifiersByTag('thisTag'));
727 }
728
729 /**
730 * @test Functional
731 */
732 public function findIdentifiersByTagReturnsAllIdentifiersTagedWithSpecifiedTag()
733 {
734 $this->setUpBackend();
735 $firstIdentifier = $this->getUniqueId('identifier1-');
736 $secondIdentifier = $this->getUniqueId('identifier2-');
737 $thirdIdentifier = $this->getUniqueId('identifier3-');
738 $tagsForFirstIdentifier = ['thisTag'];
739 $tagsForSecondIdentifier = ['thatTag'];
740 $tagsForThirdIdentifier = ['thisTag', 'thatTag'];
741 $this->backend->set($firstIdentifier, 'data', $tagsForFirstIdentifier);
742 $this->backend->set($secondIdentifier, 'data', $tagsForSecondIdentifier);
743 $this->backend->set($thirdIdentifier, 'data', $tagsForThirdIdentifier);
744 $expectedResult = [$firstIdentifier, $thirdIdentifier];
745 $actualResult = $this->backend->findIdentifiersByTag('thisTag');
746 sort($actualResult);
747 $this->assertSame($expectedResult, $actualResult);
748 }
749
750 /**
751 * @test Implementation
752 */
753 public function flushRemovesAllEntriesFromCache()
754 {
755 $this->setUpBackend();
756 $this->setUpRedis();
757 $identifier = $this->getUniqueId('identifier');
758 $this->backend->set($identifier, 'data');
759 $this->backend->flush();
760 $this->assertSame([], $this->redis->getKeys('*'));
761 }
762
763 /**
764 * @test Functional
765 */
766 public function flushByTagThrowsExceptionIfTagIsNotAString()
767 {
768 $this->expectException(\InvalidArgumentException::class);
769 $this->expectExceptionCode(1377006656);
770
771 $this->setUpBackend();
772 $this->backend->flushByTag([]);
773 }
774
775 /**
776 * @test Functional
777 */
778 public function flushByTagRemovesEntriesTaggedWithSpecifiedTag()
779 {
780 $this->setUpBackend();
781 $identifier = $this->getUniqueId('identifier');
782 $this->backend->set($identifier . 'A', 'data', ['tag1']);
783 $this->backend->set($identifier . 'B', 'data', ['tag2']);
784 $this->backend->set($identifier . 'C', 'data', ['tag1', 'tag2']);
785 $this->backend->flushByTag('tag1');
786 $expectedResult = [false, true, false];
787 $actualResult = [
788 $this->backend->has($identifier . 'A'),
789 $this->backend->has($identifier . 'B'),
790 $this->backend->has($identifier . 'C')
791 ];
792 $this->assertSame($expectedResult, $actualResult);
793 }
794
795 /**
796 * @test Functional
797 */
798 public function flushByTagsRemovesEntriesTaggedWithSpecifiedTags()
799 {
800 $this->setUpBackend();
801 $identifier = $this->getUniqueId('identifier');
802 $this->backend->set($identifier . 'A', 'data', ['tag1']);
803 $this->backend->set($identifier . 'B', 'data', ['tag2']);
804 $this->backend->set($identifier . 'C', 'data', ['tag1', 'tag2']);
805 $this->backend->set($identifier . 'D', 'data', ['tag3']);
806 $this->backend->flushByTags(['tag1', 'tag2']);
807 $expectedResult = [false, false, false, true];
808 $actualResult = [
809 $this->backend->has($identifier . 'A'),
810 $this->backend->has($identifier . 'B'),
811 $this->backend->has($identifier . 'C'),
812 $this->backend->has($identifier . 'D')
813 ];
814 $this->assertSame($expectedResult, $actualResult);
815 }
816
817 /**
818 * @test Implementation
819 */
820 public function flushByTagRemovesTemporarySet()
821 {
822 $this->setUpBackend();
823 $this->setUpRedis();
824 $identifier = $this->getUniqueId('identifier');
825 $this->backend->set($identifier . 'A', 'data', ['tag1']);
826 $this->backend->set($identifier . 'C', 'data', ['tag1', 'tag2']);
827 $this->backend->flushByTag('tag1');
828 $this->assertSame([], $this->redis->getKeys('temp*'));
829 }
830
831 /**
832 * @test Implementation
833 */
834 public function flushByTagRemovesIdentifierToTagsSetOfEntryTaggedWithGivenTag()
835 {
836 $this->setUpBackend();
837 $this->setUpRedis();
838 $identifier = $this->getUniqueId('identifier');
839 $tag = 'tag1';
840 $this->backend->set($identifier, 'data', [$tag]);
841 $this->backend->flushByTag($tag);
842 $result = $this->redis->exists('identTags:' . $identifier);
843 if (is_int($result)) {
844 // Since 3.1.4 of phpredis/phpredis the return types has been changed
845 $result = (bool)$result;
846 }
847 $this->assertFalse($result);
848 }
849
850 /**
851 * @test Implementation
852 */
853 public function flushByTagDoesNotRemoveIdentifierToTagsSetOfUnrelatedEntry()
854 {
855 $this->setUpBackend();
856 $this->setUpRedis();
857 $identifierToBeRemoved = $this->getUniqueId('identifier');
858 $tagToRemove = 'tag1';
859 $this->backend->set($identifierToBeRemoved, 'data', [$tagToRemove]);
860 $identifierNotToBeRemoved = $this->getUniqueId('identifier');
861 $tagNotToRemove = 'tag2';
862 $this->backend->set($identifierNotToBeRemoved, 'data', [$tagNotToRemove]);
863 $this->backend->flushByTag($tagToRemove);
864 $this->assertSame([$tagNotToRemove], $this->redis->sMembers('identTags:' . $identifierNotToBeRemoved));
865 }
866
867 /**
868 * @test Implementation
869 */
870 public function flushByTagRemovesTagToIdentifiersSetOfGivenTag()
871 {
872 $this->setUpBackend();
873 $this->setUpRedis();
874 $identifier = $this->getUniqueId('identifier');
875 $tag = 'tag1';
876 $this->backend->set($identifier, 'data', [$tag]);
877 $this->backend->flushByTag($tag);
878 $result = $this->redis->exists('tagIdents:' . $tag);
879 if (is_int($result)) {
880 // Since 3.1.4 of phpredis/phpredis the return types has been changed
881 $result = (bool)$result;
882 }
883 $this->assertFalse($result);
884 }
885
886 /**
887 * @test Implementation
888 */
889 public function flushByTagRemovesIdentifiersTaggedWithGivenTagFromTagToIdentifiersSets()
890 {
891 $this->setUpBackend();
892 $this->setUpRedis();
893 $identifier = $this->getUniqueId('identifier');
894 $this->backend->set($identifier . 'A', 'data', ['tag1', 'tag2']);
895 $this->backend->set($identifier . 'B', 'data', ['tag1', 'tag2']);
896 $this->backend->set($identifier . 'C', 'data', ['tag2']);
897 $this->backend->flushByTag('tag1');
898 $this->assertSame([$identifier . 'C'], $this->redis->sMembers('tagIdents:tag2'));
899 }
900
901 /**
902 * @test Implementation
903 */
904 public function collectGarbageDoesNotRemoveNotExpiredIdentifierToDataEntry()
905 {
906 $this->setUpBackend();
907 $this->setUpRedis();
908 $identifier = $this->getUniqueId('identifier');
909 $this->backend->set($identifier . 'A', 'data', ['tag']);
910 $this->backend->set($identifier . 'B', 'data', ['tag']);
911 $this->redis->delete('identData:' . $identifier . 'A');
912 $this->backend->collectGarbage();
913 $result = $this->redis->exists('identData:' . $identifier . 'B');
914 if (is_int($result)) {
915 // Since 3.1.4 of phpredis/phpredis the return types has been changed
916 $result = (bool)$result;
917 }
918 $this->assertTrue($result);
919 }
920
921 /**
922 * @test Implementation
923 */
924 public function collectGarbageRemovesLeftOverIdentifierToTagsSet()
925 {
926 $this->setUpBackend();
927 $this->setUpRedis();
928 $identifier = $this->getUniqueId('identifier');
929 $this->backend->set($identifier . 'A', 'data', ['tag']);
930 $this->backend->set($identifier . 'B', 'data', ['tag']);
931 $this->redis->delete('identData:' . $identifier . 'A');
932 $this->backend->collectGarbage();
933 $expectedResult = [false, true];
934 $resultA = $this->redis->exists('identTags:' . $identifier . 'A');
935 $resultB = $this->redis->exists('identTags:' . $identifier . 'B');
936 if (is_int($resultA)) {
937 // Since 3.1.4 of phpredis/phpredis the return types has been changed
938 $resultA = (bool)$resultA;
939 }
940 if (is_int($resultB)) {
941 // Since 3.1.4 of phpredis/phpredis the return types has been changed
942 $resultB = (bool)$resultB;
943 }
944 $actualResult = [
945 $resultA,
946 $resultB
947 ];
948 $this->assertSame($expectedResult, $actualResult);
949 }
950
951 /**
952 * @test Implementation
953 */
954 public function collectGarbageRemovesExpiredIdentifierFromTagsToIdentifierSet()
955 {
956 $this->setUpBackend();
957 $this->setUpRedis();
958 $identifier = $this->getUniqueId('identifier');
959 $this->backend->set($identifier . 'A', 'data', ['tag1', 'tag2']);
960 $this->backend->set($identifier . 'B', 'data', ['tag2']);
961 $this->redis->delete('identData:' . $identifier . 'A');
962 $this->backend->collectGarbage();
963 $expectedResult = [
964 [],
965 [$identifier . 'B']
966 ];
967 $actualResult = [
968 $this->redis->sMembers('tagIdents:tag1'),
969 $this->redis->sMembers('tagIdents:tag2')
970 ];
971 $this->assertSame($expectedResult, $actualResult);
972 }
973 }