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