[TASK] Turn redis cache backend unit into functional tests 07/57607/4
authorChristian Kuhn <lolli@schwarzbu.ch>
Mon, 16 Jul 2018 00:08:31 +0000 (02:08 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Mon, 16 Jul 2018 00:45:39 +0000 (02:45 +0200)
Our unit tests should not rely on running daemons, there is
a functional test environment to take care of this. The patch
turns redis cache backend unit tests into funcitional tests
and adapts the unit test setup to no longer start dependencies.

Resolves: #85565
Releases: master
Change-Id: I8d81209ed339d35af11bb06fb7cb191845b6b587
Reviewed-on: https://review.typo3.org/57607
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Build/bamboo/src/main/java/core/AbstractCoreSpec.java
Build/testing-docker/bamboo/docker-compose.yml
typo3/sysext/core/Tests/Functional/Cache/Backend/RedisBackendTest.php [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Cache/Backend/RedisBackendTest.php [deleted file]

index 209720c..53013e8 100644 (file)
@@ -522,8 +522,8 @@ abstract public class AbstractCoreSpec {
                     this.getTaskGitCloneRepository(),
                     this.getTaskGitCherryPick(),
                     this.getTaskComposerInstall(requirementIdentifier),
-                    this.getTaskSplitFunctionalJobs(numberOfChunks, requirementIdentifier),
                     this.getTaskDockerDependenciesFunctionalPostgres10(),
+                    this.getTaskSplitFunctionalJobs(numberOfChunks, requirementIdentifier),
                     new ScriptTask()
                         .description("Run phpunit with functional chunk " + formattedI)
                         .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
@@ -999,7 +999,6 @@ abstract public class AbstractCoreSpec {
                 this.getTaskGitCloneRepository(),
                 this.getTaskGitCherryPick(),
                 this.getTaskComposerInstall(requirementIdentifier),
-                this.getTaskDockerDependenciesUnit(),
                 new ScriptTask()
                     .description("Run phpunit")
                     .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
@@ -1021,7 +1020,6 @@ abstract public class AbstractCoreSpec {
                     )
             )
             .finalTasks(
-                this.getTaskStopDockerDependencies(),
                 new TestParserTask(TestParserTaskProperties.TestType.JUNIT)
                     .resultDirectories("test-reports/phpunit.xml")
             )
@@ -1044,7 +1042,6 @@ abstract public class AbstractCoreSpec {
                 this.getTaskGitCloneRepository(),
                 this.getTaskGitCherryPick(),
                 this.getTaskComposerInstall(requirementIdentifier),
-                this.getTaskDockerDependenciesUnit(),
                 new ScriptTask()
                     .description("Run phpunit")
                     .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
@@ -1066,7 +1063,6 @@ abstract public class AbstractCoreSpec {
                     )
             )
             .finalTasks(
-                this.getTaskStopDockerDependencies(),
                 new TestParserTask(TestParserTaskProperties.TestType.JUNIT)
                     .resultDirectories("test-reports/phpunit.xml")
             )
@@ -1093,7 +1089,6 @@ abstract public class AbstractCoreSpec {
                     this.getTaskGitCloneRepository(),
                     this.getTaskGitCherryPick(),
                     this.getTaskComposerInstall(requirementIdentifier),
-                    this.getTaskDockerDependenciesUnit(),
                     new ScriptTask()
                         .description("Run phpunit-randomizer")
                         .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
@@ -1115,7 +1110,6 @@ abstract public class AbstractCoreSpec {
                         )
                 )
                 .finalTasks(
-                    this.getTaskStopDockerDependencies(),
                     new TestParserTask(TestParserTaskProperties.TestType.JUNIT)
                         .resultDirectories("test-reports/phpunit.xml")
                 )
@@ -1278,21 +1272,6 @@ abstract public class AbstractCoreSpec {
     }
 
     /**
-     * Start docker sibling containers to execute unit tests
-     */
-    protected Task getTaskDockerDependenciesUnit() {
-        return new ScriptTask()
-            .description("Start docker siblings for unit tests")
-            .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
-            .inlineBody(
-                this.getScriptTaskBashInlineBody() +
-                "cd Build/testing-docker/bamboo\n" +
-                "echo COMPOSE_PROJECT_NAME=${BAMBOO_COMPOSE_PROJECT_NAME}sib > .env\n" +
-                "docker-compose run start_dependencies_unit"
-            );
-    }
-
-    /**
      * Stop started docker containers
      */
     protected Task getTaskStopDockerDependencies() {
index 3cb4b56..5df4af2 100644 (file)
@@ -134,15 +134,6 @@ services:
         sleep 1;
       "
 
-  start_dependencies_unit:
-    image: alpine:3.8
-    links:
-      - redis4
-    command: >
-      /bin/sh -c "
-        sleep 1;
-      "
-
 networks:
   test:
     external:
diff --git a/typo3/sysext/core/Tests/Functional/Cache/Backend/RedisBackendTest.php b/typo3/sysext/core/Tests/Functional/Cache/Backend/RedisBackendTest.php
new file mode 100644 (file)
index 0000000..7e96009
--- /dev/null
@@ -0,0 +1,948 @@
+<?php
+namespace TYPO3\CMS\Core\Tests\Functional\Cache\Backend;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+use TYPO3\CMS\Core\Cache\Backend\RedisBackend;
+use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
+use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+/**
+ * Test case for the cache to redis backend
+ *
+ * Warning:
+ * These functional tests use and flush redis database numbers 0 and 1 on the
+ * redis host specified by environment variable typo3RedisHost
+ */
+class RedisBackendTest extends FunctionalTestCase
+{
+    /**
+     * Set up
+     */
+    protected function setUp()
+    {
+        if (!extension_loaded('redis')) {
+            $this->markTestSkipped('redis extension was not available');
+        }
+        if (!getenv('typo3TestingRedisHost')) {
+            $this->markTestSkipped('environment variable "typo3TestingRedisHost" must be set to run this test');
+        }
+        // Note we assume that if that typo3TestingRedisHost env is set, we can use that for testing,
+        // there is no test to see if the daemon is actually up and running. Tests will fail if env
+        // is set but daemon is down.
+    }
+
+    /**
+     * Sets up the redis cache backend used for testing
+     */
+    protected function setUpSubject(array $backendOptions = []): RedisBackend
+    {
+        // We know this env is set, otherwise setUp() would skip the tests
+        $backendOptions['hostname'] = getenv('typo3TestingRedisHost');
+        // If typo3TestingRedisPort env is set, use it, otherwise fall back to standard port
+        $env = getenv('typo3TestingRedisPort');
+        $backendOptions['port'] = is_string($env) ? (int)$env : 6379;
+
+        $frontendProphecy = $this->prophesize(FrontendInterface::class);
+        $frontendProphecy->getIdentifier()->willReturn('cache_pages');
+
+        $subject = new RedisBackend('Testing', $backendOptions);
+        $subject->setCache($frontendProphecy->reveal());
+        $subject->initializeObject();
+        $subject->flush();
+        return $subject;
+    }
+
+    /**
+     * Sets up a test-internal redis connection to check implementation details
+     */
+    protected function setUpRedis(): \Redis
+    {
+        // We know this env is set, otherwise setUp() would skip the tests
+        $redisHost = getenv('typo3TestingRedisHost');
+        // If typo3TestingRedisPort env is set, use it, otherwise fall back to standard port
+        $env = getenv('typo3TestingRedisPort');
+        $redisPort = is_string($env) ? (int)$env : 6379;
+
+        $redis = new \Redis();
+        $redis->connect($redisHost, $redisPort);
+        return $redis;
+    }
+
+    /**
+     * @test
+     */
+    public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNotAnInteger()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1279763057);
+
+        $this->setUpSubject(['database' => 'foo']);
+    }
+
+    /**
+     * @test
+     */
+    public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNegative()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1279763534);
+
+        $this->setUpSubject(['database' => -1]);
+    }
+
+    /**
+     * @test
+     */
+    public function setCompressionThrowsExceptionIfCompressionParameterIsNotOfTypeBoolean()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1289679153);
+
+        $this->setUpSubject(['compression' => 'foo']);
+    }
+
+    /**
+     * @test
+     */
+    public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotInteger()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1289679154);
+
+        $this->setUpSubject(['compressionLevel' => 'foo']);
+    }
+
+    /**
+     * @test
+     */
+    public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotBetweenMinusOneAndNine()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1289679155);
+
+        $this->setUpSubject(['compressionLevel' => 11]);
+    }
+
+    /**
+     * @test
+     */
+    public function setConnectionTimeoutThrowsExceptionIfConnectionTimeoutIsNotInteger()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1487849315);
+
+        $this->setUpSubject(['connectionTimeout' => 'foo']);
+    }
+
+    /**
+     * @test
+     */
+    public function setConnectionTimeoutThrowsExceptionIfConnectionTimeoutIsNegative()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1487849326);
+
+        $this->setUpSubject(['connectionTimeout' => -1]);
+    }
+
+    /**
+     * @test
+     */
+    public function setThrowsExceptionIfIdentifierIsNotAString()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1377006651);
+
+        $subject = $this->setUpSubject();
+        $subject->set([], 'data');
+    }
+
+    /**
+     * @test
+     */
+    public function setThrowsExceptionIfDataIsNotAString()
+    {
+        $this->expectException(InvalidDataException::class);
+        $this->expectExceptionCode(1279469941);
+
+        $subject = $this->setUpSubject();
+        $subject->set($this->getUniqueId('identifier'), []);
+    }
+
+    /**
+     * @test
+     */
+    public function setThrowsExceptionIfLifetimeIsNegative()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1279487573);
+
+        $subject = $this->setUpSubject();
+        $subject->set($this->getUniqueId('identifier'), 'data', [], -42);
+    }
+
+    /**
+     * @test
+     */
+    public function setThrowsExceptionIfLifetimeIsNotNullOrAnInteger()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1279488008);
+
+        $subject = $this->setUpSubject();
+        $subject->set($this->getUniqueId('identifier'), 'data', [], []);
+    }
+
+    /**
+     * @test
+     */
+    public function setStoresEntriesInSelectedDatabase()
+    {
+        $redis = $this->setUpRedis();
+        $redis->select(1);
+        $subject = $this->setUpSubject(['database' => 1]);
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, 'data');
+        $result = $redis->exists('identData:' . $identifier);
+        if (is_int($result)) {
+            // Since 3.1.4 of phpredis/phpredis the return types has been changed
+            $result = (bool)$result;
+        }
+        $this->assertTrue($result);
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesStringDataTypeForIdentifierToDataEntry()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, 'data');
+        $this->assertSame(\Redis::REDIS_STRING, $redis->type('identData:' . $identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesEntryWithDefaultLifeTime()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $defaultLifetime = 42;
+        $subject->setDefaultLifetime($defaultLifetime);
+        $subject->set($identifier, 'data');
+        $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
+        $this->assertSame($defaultLifetime, $lifetimeRegisteredInBackend);
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesEntryWithSpecifiedLifeTime()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $lifetime = 43;
+        $subject->set($identifier, 'data', [], $lifetime);
+        $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
+        $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesEntryWithUnlimitedLifeTime()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, 'data', [], 0);
+        $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
+        $this->assertSame(31536000, $lifetimeRegisteredInBackend);
+    }
+
+    /**
+     * @test
+     */
+    public function setOverwritesExistingEntryWithNewData()
+    {
+        $subject = $this->setUpSubject();
+        $data = 'data 1';
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, $data);
+        $otherData = 'data 2';
+        $subject->set($identifier, $otherData);
+        $fetchedData = $subject->get($identifier);
+        $this->assertSame($otherData, $fetchedData);
+    }
+
+    /**
+     * @test
+     */
+    public function setOverwritesExistingEntryWithSpecifiedLifetime()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $data = 'data';
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, $data);
+        $lifetime = 42;
+        $subject->set($identifier, $data, [], $lifetime);
+        $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
+        $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
+    }
+
+    /**
+     * @test
+     */
+    public function setOverwritesExistingEntryWithNewDefaultLifetime()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $data = 'data';
+        $identifier = $this->getUniqueId('identifier');
+        $lifetime = 42;
+        $subject->set($identifier, $data, [], $lifetime);
+        $newDefaultLifetime = 43;
+        $subject->setDefaultLifetime($newDefaultLifetime);
+        $subject->set($identifier, $data, [], $newDefaultLifetime);
+        $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
+        $this->assertSame($newDefaultLifetime, $lifetimeRegisteredInBackend);
+    }
+
+    /**
+     * @test
+     */
+    public function setOverwritesExistingEntryWithNewUnlimitedLifetime()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $data = 'data';
+        $identifier = $this->getUniqueId('identifier');
+        $lifetime = 42;
+        $subject->set($identifier, $data, [], $lifetime);
+        $subject->set($identifier, $data, [], 0);
+        $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
+        $this->assertSame(31536000, $lifetimeRegisteredInBackend);
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesSetDataTypeForIdentifierToTagsSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, 'data', ['tag']);
+        $this->assertSame(\Redis::REDIS_SET, $redis->type('identTags:' . $identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesSpecifiedTagsInIdentifierToTagsSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $tags = ['thatTag', 'thisTag'];
+        $subject->set($identifier, 'data', $tags);
+        $savedTags = $redis->sMembers('identTags:' . $identifier);
+        sort($savedTags);
+        $this->assertSame($tags, $savedTags);
+    }
+
+    /**
+     * @test
+     */
+    public function setRemovesAllPreviouslySetTagsFromIdentifierToTagsSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $tags = ['fooTag', 'barTag'];
+        $subject->set($identifier, 'data', $tags);
+        $subject->set($identifier, 'data', []);
+        $this->assertSame([], $redis->sMembers('identTags:' . $identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function setRemovesMultiplePreviouslySetTagsFromIdentifierToTagsSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $firstTagSet = ['tag1', 'tag2', 'tag3', 'tag4'];
+        $subject->set($identifier, 'data', $firstTagSet);
+        $secondTagSet = ['tag1', 'tag3'];
+        $subject->set($identifier, 'data', $secondTagSet);
+        $actualTagSet = $redis->sMembers('identTags:' . $identifier);
+        sort($actualTagSet);
+        $this->assertSame($secondTagSet, $actualTagSet);
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesSetDataTypeForTagToIdentifiersSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $tag = 'tag';
+        $subject->set($identifier, 'data', [$tag]);
+        $this->assertSame(\Redis::REDIS_SET, $redis->type('tagIdents:' . $tag));
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesIdentifierInTagToIdentifiersSetOfSpecifiedTag()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $tag = 'thisTag';
+        $subject->set($identifier, 'data', [$tag]);
+        $savedTagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
+        $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
+    }
+
+    /**
+     * @test
+     */
+    public function setAppendsSecondIdentifierInTagToIdentifiersEntry()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $firstIdentifier = $this->getUniqueId('identifier1-');
+        $tag = 'thisTag';
+        $subject->set($firstIdentifier, 'data', [$tag]);
+        $secondIdentifier = $this->getUniqueId('identifier2-');
+        $subject->set($secondIdentifier, 'data', [$tag]);
+        $savedTagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
+        sort($savedTagToIdentifiersMemberArray);
+        $identifierArray = [$firstIdentifier, $secondIdentifier];
+        sort($identifierArray);
+        $this->assertSame([$firstIdentifier, $secondIdentifier], $savedTagToIdentifiersMemberArray);
+    }
+
+    /**
+     * @test
+     */
+    public function setRemovesIdentifierFromTagToIdentifiersEntryIfTagIsOmittedOnConsecutiveSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $tag = 'thisTag';
+        $subject->set($identifier, 'data', [$tag]);
+        $subject->set($identifier, 'data', []);
+        $savedTagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
+        $this->assertSame([], $savedTagToIdentifiersMemberArray);
+    }
+
+    /**
+     * @test
+     */
+    public function setAddsIdentifierInTagToIdentifiersEntryIfTagIsAddedOnConsecutiveSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, 'data');
+        $tag = 'thisTag';
+        $subject->set($identifier, 'data', [$tag]);
+        $savedTagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
+        $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesCompressedDataWithEnabledCompression()
+    {
+        $subject = $this->setUpSubject([
+            'compression' => true
+        ]);
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $data = 'some data ' . microtime();
+        $subject->set($identifier, $data);
+        $uncompresedStoredData = '';
+        try {
+            $uncompresedStoredData = @gzuncompress($redis->get('identData:' . $identifier));
+        } catch (\Exception $e) {
+        }
+        $this->assertEquals($data, $uncompresedStoredData, 'Original and compressed data don\'t match');
+    }
+
+    /**
+     * @test
+     */
+    public function setSavesPlaintextDataWithEnabledCompressionAndCompressionLevel0()
+    {
+        $subject = $this->setUpSubject([
+            'compression' => true,
+            'compressionLevel' => 0
+        ]);
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $data = 'some data ' . microtime();
+        $subject->set($identifier, $data);
+        $this->assertGreaterThan(0, substr_count($redis->get('identData:' . $identifier), $data), 'Plaintext data not found');
+    }
+
+    /**
+     * @test
+     */
+    public function hasThrowsExceptionIfIdentifierIsNotAString()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1377006653);
+
+        $subject = $this->setUpSubject();
+        $subject->has([]);
+    }
+
+    /**
+     * @test
+     */
+    public function hasReturnsFalseForNotExistingEntry()
+    {
+        $subject = $this->setUpSubject();
+        $identifier = $this->getUniqueId('identifier');
+        $this->assertFalse($subject->has($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function hasReturnsTrueForPreviouslySetEntry()
+    {
+        $subject = $this->setUpSubject();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, 'data');
+        $this->assertTrue($subject->has($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function getThrowsExceptionIfIdentifierIsNotAString()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        //@todo Add exception code with redis extension
+
+        $subject = $this->setUpSubject();
+        $subject->get([]);
+    }
+
+    /**
+     * @test
+     */
+    public function getReturnsPreviouslyCompressedSetEntry()
+    {
+        $subject = $this->setUpSubject([
+            'compression' => true
+        ]);
+        $data = 'data';
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, $data);
+        $fetchedData = $subject->get($identifier);
+        $this->assertSame($data, $fetchedData);
+    }
+
+    /**
+     * @test
+     */
+    public function getReturnsPreviouslySetEntry()
+    {
+        $subject = $this->setUpSubject();
+        $data = 'data';
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, $data);
+        $fetchedData = $subject->get($identifier);
+        $this->assertSame($data, $fetchedData);
+    }
+
+    /**
+     * @test
+     */
+    public function removeThrowsExceptionIfIdentifierIsNotAString()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1377006654);
+
+        $subject = $this->setUpSubject();
+        $subject->remove([]);
+    }
+
+    /**
+     * @test
+     */
+    public function removeReturnsFalseIfNoEntryWasDeleted()
+    {
+        $subject = $this->setUpSubject();
+        $this->assertFalse($subject->remove($this->getUniqueId('identifier')));
+    }
+
+    /**
+     * @test
+     */
+    public function removeReturnsTrueIfAnEntryWasDeleted()
+    {
+        $subject = $this->setUpSubject();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, 'data');
+        $this->assertTrue($subject->remove($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function removeDeletesEntryFromCache()
+    {
+        $subject = $this->setUpSubject();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, 'data');
+        $subject->remove($identifier);
+        $this->assertFalse($subject->has($identifier));
+    }
+
+    /**
+     * @test
+     */
+    public function removeDeletesIdentifierToTagEntry()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $tag = 'thisTag';
+        $subject->set($identifier, 'data', [$tag]);
+        $subject->remove($identifier);
+        $result = $redis->exists('identTags:' . $identifier);
+        if (is_int($result)) {
+            // Since 3.1.4 of phpredis/phpredis the return types has been changed
+            $result = (bool)$result;
+        }
+        $this->assertFalse($result);
+    }
+
+    /**
+     * @test
+     */
+    public function removeDeletesIdentifierFromTagToIdentifiersSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $tag = 'thisTag';
+        $subject->set($identifier, 'data', [$tag]);
+        $subject->remove($identifier);
+        $tagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
+        $this->assertSame([], $tagToIdentifiersMemberArray);
+    }
+
+    /**
+     * @test
+     */
+    public function removeDeletesIdentifierFromTagToIdentifiersSetWithMultipleEntries()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $firstIdentifier = $this->getUniqueId('identifier');
+        $secondIdentifier = $this->getUniqueId('identifier');
+        $tag = 'thisTag';
+        $subject->set($firstIdentifier, 'data', [$tag]);
+        $subject->set($secondIdentifier, 'data', [$tag]);
+        $subject->remove($firstIdentifier);
+        $tagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
+        $this->assertSame([$secondIdentifier], $tagToIdentifiersMemberArray);
+    }
+
+    /**
+     * @test
+     */
+    public function findIdentifiersByTagThrowsExceptionIfTagIsNotAString()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1377006655);
+
+        $subject = $this->setUpSubject();
+        $subject->findIdentifiersByTag([]);
+    }
+
+    /**
+     * @test
+     */
+    public function findIdentifiersByTagReturnsEmptyArrayForNotExistingTag()
+    {
+        $subject = $this->setUpSubject();
+        $this->assertSame([], $subject->findIdentifiersByTag('thisTag'));
+    }
+
+    /**
+     * @test
+     */
+    public function findIdentifiersByTagReturnsAllIdentifiersTagedWithSpecifiedTag()
+    {
+        $subject = $this->setUpSubject();
+        $firstIdentifier = $this->getUniqueId('identifier1-');
+        $secondIdentifier = $this->getUniqueId('identifier2-');
+        $thirdIdentifier = $this->getUniqueId('identifier3-');
+        $tagsForFirstIdentifier = ['thisTag'];
+        $tagsForSecondIdentifier = ['thatTag'];
+        $tagsForThirdIdentifier = ['thisTag', 'thatTag'];
+        $subject->set($firstIdentifier, 'data', $tagsForFirstIdentifier);
+        $subject->set($secondIdentifier, 'data', $tagsForSecondIdentifier);
+        $subject->set($thirdIdentifier, 'data', $tagsForThirdIdentifier);
+        $expectedResult = [$firstIdentifier, $thirdIdentifier];
+        $actualResult = $subject->findIdentifiersByTag('thisTag');
+        sort($actualResult);
+        $this->assertSame($expectedResult, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function flushRemovesAllEntriesFromCache()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier, 'data');
+        $subject->flush();
+        $this->assertSame([], $redis->getKeys('*'));
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagThrowsExceptionIfTagIsNotAString()
+    {
+        $this->expectException(\InvalidArgumentException::class);
+        $this->expectExceptionCode(1377006656);
+
+        $subject = $this->setUpSubject();
+        $subject->flushByTag([]);
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagRemovesEntriesTaggedWithSpecifiedTag()
+    {
+        $subject = $this->setUpSubject();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier . 'A', 'data', ['tag1']);
+        $subject->set($identifier . 'B', 'data', ['tag2']);
+        $subject->set($identifier . 'C', 'data', ['tag1', 'tag2']);
+        $subject->flushByTag('tag1');
+        $expectedResult = [false, true, false];
+        $actualResult = [
+            $subject->has($identifier . 'A'),
+            $subject->has($identifier . 'B'),
+            $subject->has($identifier . 'C')
+        ];
+        $this->assertSame($expectedResult, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagsRemovesEntriesTaggedWithSpecifiedTags()
+    {
+        $subject = $this->setUpSubject();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier . 'A', 'data', ['tag1']);
+        $subject->set($identifier . 'B', 'data', ['tag2']);
+        $subject->set($identifier . 'C', 'data', ['tag1', 'tag2']);
+        $subject->set($identifier . 'D', 'data', ['tag3']);
+        $subject->flushByTags(['tag1', 'tag2']);
+        $expectedResult = [false, false, false, true];
+        $actualResult = [
+            $subject->has($identifier . 'A'),
+            $subject->has($identifier . 'B'),
+            $subject->has($identifier . 'C'),
+            $subject->has($identifier . 'D')
+        ];
+        $this->assertSame($expectedResult, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagRemovesTemporarySet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier . 'A', 'data', ['tag1']);
+        $subject->set($identifier . 'C', 'data', ['tag1', 'tag2']);
+        $subject->flushByTag('tag1');
+        $this->assertSame([], $redis->getKeys('temp*'));
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagRemovesIdentifierToTagsSetOfEntryTaggedWithGivenTag()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $tag = 'tag1';
+        $subject->set($identifier, 'data', [$tag]);
+        $subject->flushByTag($tag);
+        $result = $redis->exists('identTags:' . $identifier);
+        if (is_int($result)) {
+            // Since 3.1.4 of phpredis/phpredis the return types has been changed
+            $result = (bool)$result;
+        }
+        $this->assertFalse($result);
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagDoesNotRemoveIdentifierToTagsSetOfUnrelatedEntry()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifierToBeRemoved = $this->getUniqueId('identifier');
+        $tagToRemove = 'tag1';
+        $subject->set($identifierToBeRemoved, 'data', [$tagToRemove]);
+        $identifierNotToBeRemoved = $this->getUniqueId('identifier');
+        $tagNotToRemove = 'tag2';
+        $subject->set($identifierNotToBeRemoved, 'data', [$tagNotToRemove]);
+        $subject->flushByTag($tagToRemove);
+        $this->assertSame([$tagNotToRemove], $redis->sMembers('identTags:' . $identifierNotToBeRemoved));
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagRemovesTagToIdentifiersSetOfGivenTag()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $tag = 'tag1';
+        $subject->set($identifier, 'data', [$tag]);
+        $subject->flushByTag($tag);
+        $result = $redis->exists('tagIdents:' . $tag);
+        if (is_int($result)) {
+            // Since 3.1.4 of phpredis/phpredis the return types has been changed
+            $result = (bool)$result;
+        }
+        $this->assertFalse($result);
+    }
+
+    /**
+     * @test
+     */
+    public function flushByTagRemovesIdentifiersTaggedWithGivenTagFromTagToIdentifiersSets()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier . 'A', 'data', ['tag1', 'tag2']);
+        $subject->set($identifier . 'B', 'data', ['tag1', 'tag2']);
+        $subject->set($identifier . 'C', 'data', ['tag2']);
+        $subject->flushByTag('tag1');
+        $this->assertSame([$identifier . 'C'], $redis->sMembers('tagIdents:tag2'));
+    }
+
+    /**
+     * @test
+     */
+    public function collectGarbageDoesNotRemoveNotExpiredIdentifierToDataEntry()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier . 'A', 'data', ['tag']);
+        $subject->set($identifier . 'B', 'data', ['tag']);
+        $redis->delete('identData:' . $identifier . 'A');
+        $subject->collectGarbage();
+        $result = $redis->exists('identData:' . $identifier . 'B');
+        if (is_int($result)) {
+            // Since 3.1.4 of phpredis/phpredis the return types has been changed
+            $result = (bool)$result;
+        }
+        $this->assertTrue($result);
+    }
+
+    /**
+     * @test
+     */
+    public function collectGarbageRemovesLeftOverIdentifierToTagsSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier . 'A', 'data', ['tag']);
+        $subject->set($identifier . 'B', 'data', ['tag']);
+        $redis->delete('identData:' . $identifier . 'A');
+        $subject->collectGarbage();
+        $expectedResult = [false, true];
+        $resultA = $redis->exists('identTags:' . $identifier . 'A');
+        $resultB = $redis->exists('identTags:' . $identifier . 'B');
+        if (is_int($resultA)) {
+            // Since 3.1.4 of phpredis/phpredis the return types has been changed
+            $resultA = (bool)$resultA;
+        }
+        if (is_int($resultB)) {
+            // Since 3.1.4 of phpredis/phpredis the return types has been changed
+            $resultB = (bool)$resultB;
+        }
+        $actualResult = [
+            $resultA,
+            $resultB
+        ];
+        $this->assertSame($expectedResult, $actualResult);
+    }
+
+    /**
+     * @test
+     */
+    public function collectGarbageRemovesExpiredIdentifierFromTagsToIdentifierSet()
+    {
+        $subject = $this->setUpSubject();
+        $redis = $this->setUpRedis();
+        $identifier = $this->getUniqueId('identifier');
+        $subject->set($identifier . 'A', 'data', ['tag1', 'tag2']);
+        $subject->set($identifier . 'B', 'data', ['tag2']);
+        $redis->delete('identData:' . $identifier . 'A');
+        $subject->collectGarbage();
+        $expectedResult = [
+            [],
+            [$identifier . 'B']
+        ];
+        $actualResult = [
+            $redis->sMembers('tagIdents:tag1'),
+            $redis->sMembers('tagIdents:tag2')
+        ];
+        $this->assertSame($expectedResult, $actualResult);
+    }
+}
diff --git a/typo3/sysext/core/Tests/Unit/Cache/Backend/RedisBackendTest.php b/typo3/sysext/core/Tests/Unit/Cache/Backend/RedisBackendTest.php
deleted file mode 100644 (file)
index 523fbee..0000000
+++ /dev/null
@@ -1,985 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests\Unit\Cache\Backend;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
-use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
-
-/**
- * Testcase for the cache to redis backend
- *
- * This class has functional tests as well as implementation tests:
- * - The functional tests make API calls to the backend and check expected behaviour
- * - The implementation tests make additional calls with an own redis instance to
- * check stored data structures in the redis server, which can not be checked
- * by functional tests alone. Those tests will fail if any changes
- * to the internal data structure are done.
- *
- * Warning:
- * The unit tests use and flush redis database numbers 0 and 1 on the
- * redis host specified by environment variable typo3RedisHost
- */
-class RedisBackendTest extends UnitTestCase
-{
-    /**
-     * If set, the tearDown() method will flush the cache used by this unit test.
-     *
-     * @var \TYPO3\CMS\Core\Cache\Backend\RedisBackend
-     */
-    protected $backend;
-
-    /**
-     * Own redis instance used in implementation tests
-     *
-     * @var \Redis
-     */
-    protected $redis;
-
-    /**
-     * Set up this testcase
-     */
-    protected function setUp()
-    {
-        if (!extension_loaded('redis')) {
-            $this->markTestSkipped('redis extension was not available');
-        }
-        if (!getenv('typo3TestingRedisHost')) {
-            $this->markTestSkipped('environment variable "typo3TestingRedisHost" must be set to run this test');
-        }
-        // Note we assume that if that typo3TestingRedisHost env is set, we can use that for testing,
-        // there is no test to see if the daemon is actually up and running. Tests will fail if env
-        // is set but daemon is down.
-    }
-
-    /**
-     * Sets up the redis backend used for testing
-     */
-    protected function setUpBackend(array $backendOptions = [])
-    {
-        $mockCache = $this->createMock(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface::class);
-        $mockCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('TestCache'));
-        // We know this env is set, otherwise setUp() would skip the tests
-        $backendOptions['hostname'] = getenv('typo3TestingRedisHost');
-        // If typo3TestingRedisPort env is set, use it, otherwise fall back to standard port
-        $env = getenv('typo3TestingRedisPort');
-        $backendOptions['port'] = is_string($env) ? (int)$env : 6379;
-        $this->backend = new \TYPO3\CMS\Core\Cache\Backend\RedisBackend('Testing', $backendOptions);
-        $this->backend->setCache($mockCache);
-        $this->backend->initializeObject();
-    }
-
-    /**
-     * Sets up an own redis instance for implementation tests
-     */
-    protected function setUpRedis()
-    {
-        // We know this env is set, otherwise setUp() would skip the tests
-        $redisHost = getenv('typo3TestingRedisHost');
-        // If typo3TestingRedisPort env is set, use it, otherwise fall back to standard port
-        $env = getenv('typo3TestingRedisPort');
-        $redisPort = is_string($env) ? (int)$env : 6379;
-
-        $this->redis = new \Redis();
-        $this->redis->connect($redisHost, $redisPort);
-    }
-
-    /**
-     * Tear down this testcase
-     */
-    protected function tearDown()
-    {
-        if ($this->backend instanceof \TYPO3\CMS\Core\Cache\Backend\RedisBackend) {
-            $this->backend->flush();
-        }
-        parent::tearDown();
-    }
-
-    /**
-     * @test Functional
-     */
-    public function initializeObjectThrowsNoExceptionIfGivenDatabaseWasSuccessfullySelected()
-    {
-        try {
-            $this->setUpBackend(['database' => 1]);
-        } catch (Exception $e) {
-            $this->assertTrue();
-        }
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNotAnInteger()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1279763057);
-
-        $this->setUpBackend(['database' => 'foo']);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNegative()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1279763534);
-
-        $this->setUpBackend(['database' => -1]);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setCompressionThrowsExceptionIfCompressionParameterIsNotOfTypeBoolean()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1289679153);
-
-        $this->setUpBackend(['compression' => 'foo']);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotInteger()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1289679154);
-
-        $this->setUpBackend(['compressionLevel' => 'foo']);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotBetweenMinusOneAndNine()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1289679155);
-
-        $this->setUpBackend(['compressionLevel' => 11]);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setConnectionTimeoutThrowsExceptionIfConnectionTimeoutIsNotInteger()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1487849315);
-
-        $this->setUpBackend(['connectionTimeout' => 'foo']);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setConnectionTimeoutThrowsExceptionIfConnectionTimeoutIsNegative()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1487849326);
-
-        $this->setUpBackend(['connectionTimeout' => -1]);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setThrowsExceptionIfIdentifierIsNotAString()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1377006651);
-
-        $this->setUpBackend();
-        $this->backend->set([], 'data');
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setThrowsExceptionIfDataIsNotAString()
-    {
-        $this->expectException(InvalidDataException::class);
-        $this->expectExceptionCode(1279469941);
-
-        $this->setUpBackend();
-        $this->backend->set($this->getUniqueId('identifier'), []);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setThrowsExceptionIfLifetimeIsNegative()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1279487573);
-
-        $this->setUpBackend();
-        $this->backend->set($this->getUniqueId('identifier'), 'data', [], -42);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setThrowsExceptionIfLifetimeIsNotNullOrAnInteger()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1279488008);
-
-        $this->setUpBackend();
-        $this->backend->set($this->getUniqueId('identifier'), 'data', [], []);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setStoresEntriesInSelectedDatabase()
-    {
-        $this->setUpRedis();
-        $this->redis->select(1);
-        $this->setUpBackend(['database' => 1]);
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, 'data');
-        $result = $this->redis->exists('identData:' . $identifier);
-        if (is_int($result)) {
-            // Since 3.1.4 of phpredis/phpredis the return types has been changed
-            $result = (bool)$result;
-        }
-        $this->assertTrue($result);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesStringDataTypeForIdentifierToDataEntry()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, 'data');
-        $this->assertSame(\Redis::REDIS_STRING, $this->redis->type('identData:' . $identifier));
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesEntryWithDefaultLifeTime()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $defaultLifetime = 42;
-        $this->backend->setDefaultLifetime($defaultLifetime);
-        $this->backend->set($identifier, 'data');
-        $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
-        $this->assertSame($defaultLifetime, $lifetimeRegisteredInBackend);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesEntryWithSpecifiedLifeTime()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $lifetime = 43;
-        $this->backend->set($identifier, 'data', [], $lifetime);
-        $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
-        $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesEntryWithUnlimitedLifeTime()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, 'data', [], 0);
-        $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
-        $this->assertSame(31536000, $lifetimeRegisteredInBackend);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function setOverwritesExistingEntryWithNewData()
-    {
-        $this->setUpBackend();
-        $data = 'data 1';
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, $data);
-        $otherData = 'data 2';
-        $this->backend->set($identifier, $otherData);
-        $fetchedData = $this->backend->get($identifier);
-        $this->assertSame($otherData, $fetchedData);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setOverwritesExistingEntryWithSpecifiedLifetime()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $data = 'data';
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, $data);
-        $lifetime = 42;
-        $this->backend->set($identifier, $data, [], $lifetime);
-        $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
-        $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setOverwritesExistingEntryWithNewDefaultLifetime()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $data = 'data';
-        $identifier = $this->getUniqueId('identifier');
-        $lifetime = 42;
-        $this->backend->set($identifier, $data, [], $lifetime);
-        $newDefaultLifetime = 43;
-        $this->backend->setDefaultLifetime($newDefaultLifetime);
-        $this->backend->set($identifier, $data, [], $newDefaultLifetime);
-        $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
-        $this->assertSame($newDefaultLifetime, $lifetimeRegisteredInBackend);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setOverwritesExistingEntryWithNewUnlimitedLifetime()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $data = 'data';
-        $identifier = $this->getUniqueId('identifier');
-        $lifetime = 42;
-        $this->backend->set($identifier, $data, [], $lifetime);
-        $this->backend->set($identifier, $data, [], 0);
-        $lifetimeRegisteredInBackend = $this->redis->ttl('identData:' . $identifier);
-        $this->assertSame(31536000, $lifetimeRegisteredInBackend);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesSetDataTypeForIdentifierToTagsSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, 'data', ['tag']);
-        $this->assertSame(\Redis::REDIS_SET, $this->redis->type('identTags:' . $identifier));
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesSpecifiedTagsInIdentifierToTagsSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $tags = ['thatTag', 'thisTag'];
-        $this->backend->set($identifier, 'data', $tags);
-        $savedTags = $this->redis->sMembers('identTags:' . $identifier);
-        sort($savedTags);
-        $this->assertSame($tags, $savedTags);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setRemovesAllPreviouslySetTagsFromIdentifierToTagsSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $tags = ['fooTag', 'barTag'];
-        $this->backend->set($identifier, 'data', $tags);
-        $this->backend->set($identifier, 'data', []);
-        $this->assertSame([], $this->redis->sMembers('identTags:' . $identifier));
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setRemovesMultiplePreviouslySetTagsFromIdentifierToTagsSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $firstTagSet = ['tag1', 'tag2', 'tag3', 'tag4'];
-        $this->backend->set($identifier, 'data', $firstTagSet);
-        $secondTagSet = ['tag1', 'tag3'];
-        $this->backend->set($identifier, 'data', $secondTagSet);
-        $actualTagSet = $this->redis->sMembers('identTags:' . $identifier);
-        sort($actualTagSet);
-        $this->assertSame($secondTagSet, $actualTagSet);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesSetDataTypeForTagToIdentifiersSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $tag = 'tag';
-        $this->backend->set($identifier, 'data', [$tag]);
-        $this->assertSame(\Redis::REDIS_SET, $this->redis->type('tagIdents:' . $tag));
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesIdentifierInTagToIdentifiersSetOfSpecifiedTag()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $tag = 'thisTag';
-        $this->backend->set($identifier, 'data', [$tag]);
-        $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
-        $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setAppendsSecondIdentifierInTagToIdentifiersEntry()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $firstIdentifier = $this->getUniqueId('identifier1-');
-        $tag = 'thisTag';
-        $this->backend->set($firstIdentifier, 'data', [$tag]);
-        $secondIdentifier = $this->getUniqueId('identifier2-');
-        $this->backend->set($secondIdentifier, 'data', [$tag]);
-        $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
-        sort($savedTagToIdentifiersMemberArray);
-        $identifierArray = [$firstIdentifier, $secondIdentifier];
-        sort($identifierArray);
-        $this->assertSame([$firstIdentifier, $secondIdentifier], $savedTagToIdentifiersMemberArray);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setRemovesIdentifierFromTagToIdentifiersEntryIfTagIsOmittedOnConsecutiveSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $tag = 'thisTag';
-        $this->backend->set($identifier, 'data', [$tag]);
-        $this->backend->set($identifier, 'data', []);
-        $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
-        $this->assertSame([], $savedTagToIdentifiersMemberArray);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setAddsIdentifierInTagToIdentifiersEntryIfTagIsAddedOnConsecutiveSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, 'data');
-        $tag = 'thisTag';
-        $this->backend->set($identifier, 'data', [$tag]);
-        $savedTagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
-        $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesCompressedDataWithEnabledCompression()
-    {
-        $this->setUpBackend([
-            'compression' => true
-        ]);
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $data = 'some data ' . microtime();
-        $this->backend->set($identifier, $data);
-        $uncompresedStoredData = '';
-        try {
-            $uncompresedStoredData = @gzuncompress($this->redis->get('identData:' . $identifier));
-        } catch (\Exception $e) {
-        }
-        $this->assertEquals($data, $uncompresedStoredData, 'Original and compressed data don\'t match');
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function setSavesPlaintextDataWithEnabledCompressionAndCompressionLevel0()
-    {
-        $this->setUpBackend([
-            'compression' => true,
-            'compressionLevel' => 0
-        ]);
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $data = 'some data ' . microtime();
-        $this->backend->set($identifier, $data);
-        $this->assertGreaterThan(0, substr_count($this->redis->get('identData:' . $identifier), $data), 'Plaintext data not found');
-    }
-
-    /**
-     * @test Functional
-     */
-    public function hasThrowsExceptionIfIdentifierIsNotAString()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1377006653);
-
-        $this->setUpBackend();
-        $this->backend->has([]);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function hasReturnsFalseForNotExistingEntry()
-    {
-        $this->setUpBackend();
-        $identifier = $this->getUniqueId('identifier');
-        $this->assertFalse($this->backend->has($identifier));
-    }
-
-    /**
-     * @test Functional
-     */
-    public function hasReturnsTrueForPreviouslySetEntry()
-    {
-        $this->setUpBackend();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, 'data');
-        $this->assertTrue($this->backend->has($identifier));
-    }
-
-    /**
-     * @test Functional
-     */
-    public function getThrowsExceptionIfIdentifierIsNotAString()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        //@todo Add exception code with redis extension
-
-        $this->setUpBackend();
-        $this->backend->get([]);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function getReturnsPreviouslyCompressedSetEntry()
-    {
-        $this->setUpBackend([
-            'compression' => true
-        ]);
-        $data = 'data';
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, $data);
-        $fetchedData = $this->backend->get($identifier);
-        $this->assertSame($data, $fetchedData);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function getReturnsPreviouslySetEntry()
-    {
-        $this->setUpBackend();
-        $data = 'data';
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, $data);
-        $fetchedData = $this->backend->get($identifier);
-        $this->assertSame($data, $fetchedData);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function removeThrowsExceptionIfIdentifierIsNotAString()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1377006654);
-
-        $this->setUpBackend();
-        $this->backend->remove([]);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function removeReturnsFalseIfNoEntryWasDeleted()
-    {
-        $this->setUpBackend();
-        $this->assertFalse($this->backend->remove($this->getUniqueId('identifier')));
-    }
-
-    /**
-     * @test Functional
-     */
-    public function removeReturnsTrueIfAnEntryWasDeleted()
-    {
-        $this->setUpBackend();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, 'data');
-        $this->assertTrue($this->backend->remove($identifier));
-    }
-
-    /**
-     * @test Functional
-     */
-    public function removeDeletesEntryFromCache()
-    {
-        $this->setUpBackend();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, 'data');
-        $this->backend->remove($identifier);
-        $this->assertFalse($this->backend->has($identifier));
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function removeDeletesIdentifierToTagEntry()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $tag = 'thisTag';
-        $this->backend->set($identifier, 'data', [$tag]);
-        $this->backend->remove($identifier);
-        $result = $this->redis->exists('identTags:' . $identifier);
-        if (is_int($result)) {
-            // Since 3.1.4 of phpredis/phpredis the return types has been changed
-            $result = (bool)$result;
-        }
-        $this->assertFalse($result);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function removeDeletesIdentifierFromTagToIdentifiersSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $tag = 'thisTag';
-        $this->backend->set($identifier, 'data', [$tag]);
-        $this->backend->remove($identifier);
-        $tagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
-        $this->assertSame([], $tagToIdentifiersMemberArray);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function removeDeletesIdentifierFromTagToIdentifiersSetWithMultipleEntries()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $firstIdentifier = $this->getUniqueId('identifier');
-        $secondIdentifier = $this->getUniqueId('identifier');
-        $tag = 'thisTag';
-        $this->backend->set($firstIdentifier, 'data', [$tag]);
-        $this->backend->set($secondIdentifier, 'data', [$tag]);
-        $this->backend->remove($firstIdentifier);
-        $tagToIdentifiersMemberArray = $this->redis->sMembers('tagIdents:' . $tag);
-        $this->assertSame([$secondIdentifier], $tagToIdentifiersMemberArray);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function findIdentifiersByTagThrowsExceptionIfTagIsNotAString()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1377006655);
-
-        $this->setUpBackend();
-        $this->backend->findIdentifiersByTag([]);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function findIdentifiersByTagReturnsEmptyArrayForNotExistingTag()
-    {
-        $this->setUpBackend();
-        $this->assertSame([], $this->backend->findIdentifiersByTag('thisTag'));
-    }
-
-    /**
-     * @test Functional
-     */
-    public function findIdentifiersByTagReturnsAllIdentifiersTagedWithSpecifiedTag()
-    {
-        $this->setUpBackend();
-        $firstIdentifier = $this->getUniqueId('identifier1-');
-        $secondIdentifier = $this->getUniqueId('identifier2-');
-        $thirdIdentifier = $this->getUniqueId('identifier3-');
-        $tagsForFirstIdentifier = ['thisTag'];
-        $tagsForSecondIdentifier = ['thatTag'];
-        $tagsForThirdIdentifier = ['thisTag', 'thatTag'];
-        $this->backend->set($firstIdentifier, 'data', $tagsForFirstIdentifier);
-        $this->backend->set($secondIdentifier, 'data', $tagsForSecondIdentifier);
-        $this->backend->set($thirdIdentifier, 'data', $tagsForThirdIdentifier);
-        $expectedResult = [$firstIdentifier, $thirdIdentifier];
-        $actualResult = $this->backend->findIdentifiersByTag('thisTag');
-        sort($actualResult);
-        $this->assertSame($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function flushRemovesAllEntriesFromCache()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier, 'data');
-        $this->backend->flush();
-        $this->assertSame([], $this->redis->getKeys('*'));
-    }
-
-    /**
-     * @test Functional
-     */
-    public function flushByTagThrowsExceptionIfTagIsNotAString()
-    {
-        $this->expectException(\InvalidArgumentException::class);
-        $this->expectExceptionCode(1377006656);
-
-        $this->setUpBackend();
-        $this->backend->flushByTag([]);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function flushByTagRemovesEntriesTaggedWithSpecifiedTag()
-    {
-        $this->setUpBackend();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier . 'A', 'data', ['tag1']);
-        $this->backend->set($identifier . 'B', 'data', ['tag2']);
-        $this->backend->set($identifier . 'C', 'data', ['tag1', 'tag2']);
-        $this->backend->flushByTag('tag1');
-        $expectedResult = [false, true, false];
-        $actualResult = [
-            $this->backend->has($identifier . 'A'),
-            $this->backend->has($identifier . 'B'),
-            $this->backend->has($identifier . 'C')
-        ];
-        $this->assertSame($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test Functional
-     */
-    public function flushByTagsRemovesEntriesTaggedWithSpecifiedTags()
-    {
-        $this->setUpBackend();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier . 'A', 'data', ['tag1']);
-        $this->backend->set($identifier . 'B', 'data', ['tag2']);
-        $this->backend->set($identifier . 'C', 'data', ['tag1', 'tag2']);
-        $this->backend->set($identifier . 'D', 'data', ['tag3']);
-        $this->backend->flushByTags(['tag1', 'tag2']);
-        $expectedResult = [false, false, false, true];
-        $actualResult = [
-            $this->backend->has($identifier . 'A'),
-            $this->backend->has($identifier . 'B'),
-            $this->backend->has($identifier . 'C'),
-            $this->backend->has($identifier . 'D')
-        ];
-        $this->assertSame($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function flushByTagRemovesTemporarySet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier . 'A', 'data', ['tag1']);
-        $this->backend->set($identifier . 'C', 'data', ['tag1', 'tag2']);
-        $this->backend->flushByTag('tag1');
-        $this->assertSame([], $this->redis->getKeys('temp*'));
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function flushByTagRemovesIdentifierToTagsSetOfEntryTaggedWithGivenTag()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $tag = 'tag1';
-        $this->backend->set($identifier, 'data', [$tag]);
-        $this->backend->flushByTag($tag);
-        $result = $this->redis->exists('identTags:' . $identifier);
-        if (is_int($result)) {
-            // Since 3.1.4 of phpredis/phpredis the return types has been changed
-            $result = (bool)$result;
-        }
-        $this->assertFalse($result);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function flushByTagDoesNotRemoveIdentifierToTagsSetOfUnrelatedEntry()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifierToBeRemoved = $this->getUniqueId('identifier');
-        $tagToRemove = 'tag1';
-        $this->backend->set($identifierToBeRemoved, 'data', [$tagToRemove]);
-        $identifierNotToBeRemoved = $this->getUniqueId('identifier');
-        $tagNotToRemove = 'tag2';
-        $this->backend->set($identifierNotToBeRemoved, 'data', [$tagNotToRemove]);
-        $this->backend->flushByTag($tagToRemove);
-        $this->assertSame([$tagNotToRemove], $this->redis->sMembers('identTags:' . $identifierNotToBeRemoved));
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function flushByTagRemovesTagToIdentifiersSetOfGivenTag()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $tag = 'tag1';
-        $this->backend->set($identifier, 'data', [$tag]);
-        $this->backend->flushByTag($tag);
-        $result = $this->redis->exists('tagIdents:' . $tag);
-        if (is_int($result)) {
-            // Since 3.1.4 of phpredis/phpredis the return types has been changed
-            $result = (bool)$result;
-        }
-        $this->assertFalse($result);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function flushByTagRemovesIdentifiersTaggedWithGivenTagFromTagToIdentifiersSets()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier . 'A', 'data', ['tag1', 'tag2']);
-        $this->backend->set($identifier . 'B', 'data', ['tag1', 'tag2']);
-        $this->backend->set($identifier . 'C', 'data', ['tag2']);
-        $this->backend->flushByTag('tag1');
-        $this->assertSame([$identifier . 'C'], $this->redis->sMembers('tagIdents:tag2'));
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function collectGarbageDoesNotRemoveNotExpiredIdentifierToDataEntry()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier . 'A', 'data', ['tag']);
-        $this->backend->set($identifier . 'B', 'data', ['tag']);
-        $this->redis->delete('identData:' . $identifier . 'A');
-        $this->backend->collectGarbage();
-        $result = $this->redis->exists('identData:' . $identifier . 'B');
-        if (is_int($result)) {
-            // Since 3.1.4 of phpredis/phpredis the return types has been changed
-            $result = (bool)$result;
-        }
-        $this->assertTrue($result);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function collectGarbageRemovesLeftOverIdentifierToTagsSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier . 'A', 'data', ['tag']);
-        $this->backend->set($identifier . 'B', 'data', ['tag']);
-        $this->redis->delete('identData:' . $identifier . 'A');
-        $this->backend->collectGarbage();
-        $expectedResult = [false, true];
-        $resultA = $this->redis->exists('identTags:' . $identifier . 'A');
-        $resultB = $this->redis->exists('identTags:' . $identifier . 'B');
-        if (is_int($resultA)) {
-            // Since 3.1.4 of phpredis/phpredis the return types has been changed
-            $resultA = (bool)$resultA;
-        }
-        if (is_int($resultB)) {
-            // Since 3.1.4 of phpredis/phpredis the return types has been changed
-            $resultB = (bool)$resultB;
-        }
-        $actualResult = [
-            $resultA,
-            $resultB
-        ];
-        $this->assertSame($expectedResult, $actualResult);
-    }
-
-    /**
-     * @test Implementation
-     */
-    public function collectGarbageRemovesExpiredIdentifierFromTagsToIdentifierSet()
-    {
-        $this->setUpBackend();
-        $this->setUpRedis();
-        $identifier = $this->getUniqueId('identifier');
-        $this->backend->set($identifier . 'A', 'data', ['tag1', 'tag2']);
-        $this->backend->set($identifier . 'B', 'data', ['tag2']);
-        $this->redis->delete('identData:' . $identifier . 'A');
-        $this->backend->collectGarbage();
-        $expectedResult = [
-            [],
-            [$identifier . 'B']
-        ];
-        $actualResult = [
-            $this->redis->sMembers('tagIdents:tag1'),
-            $this->redis->sMembers('tagIdents:tag2')
-        ];
-        $this->assertSame($expectedResult, $actualResult);
-    }
-}