[TASK] Run tests on new bamboo infrastructure 05/57605/5
authorChristian Kuhn <lolli@schwarzbu.ch>
Sun, 15 Jul 2018 21:23:57 +0000 (23:23 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Sun, 15 Jul 2018 22:55:00 +0000 (00:55 +0200)
A new bamboo agent infrastructure has been deployed that
significantly changes how tests are executed: The agent
docker containers are now "stupid" and no longer bundle
specific php versions or daemons. Instead, they can run
own containers to start needed daemons for specific jobs
and execute needed php commands in ad-hoc containers that
provide the required php version.
Daemons needed for single jobs are defined in a
docker-compose.yml file provided by core itself.
This docker-compose.yml file can not be used directly for
local test execution since it has to fiddle quite a bit
with docker volume mounts, networks and executing users
that is specific to the bamboo environment.
However, another yml file can be added later to ease local
test execution in a similar way.
The patch rewrites the bamboo plan pre-merge and nightly
specs of core master to use the new infrastructure and brings
a couple of minor changes to tests that rely on a running
memcached or redis to retrieve the daemon host from an
environment variable.

Patch for core v7.

Change-Id: I65777eeee6e28fca5b3d3d979498293cc91a77af
Resolves: #85563
Resolves: #36934
Releases: 7.6
Reviewed-on: https://review.typo3.org/57605
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
Build/bamboo/pom.xml
Build/bamboo/src/main/java/core/AbstractCoreSpec.java
Build/bamboo/src/main/java/core/PreMergeSpec.java
Build/testing-docker/bamboo/docker-compose.yml [new file with mode: 0644]
typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/RedisBackendTest.php

index 8a8e054..757de5d 100644 (file)
@@ -5,7 +5,7 @@
   <parent>
     <groupId>com.atlassian.bamboo</groupId>
     <artifactId>bamboo-specs-parent</artifactId>
-    <version>6.2.2</version>
+    <version>6.3.1</version>
     <relativePath/>
   </parent>
 
index 535ca6e..c2bad07 100644 (file)
@@ -45,13 +45,6 @@ abstract public class AbstractCoreSpec {
 
     protected String testingFrameworkBuildPath = "typo3/sysext/core/Build/";
 
-    protected String credentialsMysql =
-        "typo3DatabaseName=\"func\"" +
-        " typo3DatabaseUsername=\"funcu\"" +
-        " typo3DatabasePassword=\"funcp\"" +
-        " typo3DatabaseHost=\"localhost\"" +
-        " typo3InstallToolPassword=\"klaus\"";
-
     /**
      * Default permissions on core plans
      *
@@ -125,17 +118,27 @@ abstract public class AbstractCoreSpec {
 
     /**
      * Job composer validate
+     *
+     * @param String requirementIdentifier
      */
-    protected Job getJobComposerValidate() {
+    protected Job getJobComposerValidate(String requirementIdentifier) {
         return new Job("Validate composer.json", new BambooKey("VC"))
         .description("Validate composer.json before actual tests are executed")
         .pluginConfigurations(this.getDefaultJobPluginConfiguration())
         .tasks(
             this.getTaskGitCloneRepository(),
             this.getTaskGitCherryPick(),
-            new CommandTask()
+            new ScriptTask()
                 .description("composer validate")
-                .executable("composer").argument("validate")
+                .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
+                .inlineBody(
+                    this.getScriptTaskBashInlineBody() +
+                    this.getScriptTaskComposer(requirementIdentifier) +
+                    "composer validate"
+                )
+        )
+        .requirements(
+            this.getRequirementDocker10()
         )
         .cleanWorkingDirectory(true);
     }
@@ -144,10 +147,9 @@ abstract public class AbstractCoreSpec {
      * Jobs for mysql based functional tests
      *
      * @param int numberOfChunks
-     * @param Requirement requirement
      * @param String requirementIdentifier
      */
-    protected ArrayList<Job> getJobsFunctionalTestsMysql(int numberOfChunks, Requirement requirement, String requirementIdentifier) {
+    protected ArrayList<Job> getJobsFunctionalTestsMysql(int numberOfChunks, String requirementIdentifier) {
         ArrayList<Job> jobs = new ArrayList<Job>();
 
         for (int i=0; i<numberOfChunks; i++) {
@@ -157,24 +159,42 @@ abstract public class AbstractCoreSpec {
                 .tasks(
                     this.getTaskGitCloneRepository(),
                     this.getTaskGitCherryPick(),
-                    this.getTaskComposerInstall(),
+                    this.getTaskComposerInstall(requirementIdentifier),
+                    this.getTaskDockerDependenciesFunctionalMariadb10(),
                     this.getTaskSplitFunctionalJobs(numberOfChunks),
                     new ScriptTask()
                         .description("Run phpunit with functional chunk 0" + i)
                         .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
                         .inlineBody(
                             this.getScriptTaskBashInlineBody() +
-                            "./bin/phpunit --log-junit test-reports/phpunit.xml -c " + this.testingFrameworkBuildPath + "FunctionalTests-Job-" + i + ".xml"
+                            "function phpunit() {\n" +
+                            "    docker run \\\n" +
+                            "        -u ${HOST_UID} \\\n" +
+                            "        -v /etc/passwd:/etc/passwd \\\n" +
+                            "        -v ${BAMBOO_COMPOSE_PROJECT_NAME}_bamboo-data:/srv/bamboo/xml-data/build-dir/ \\\n" +
+                            "        -e typo3DatabaseName=func_test \\\n" +
+                            "        -e typo3DatabaseUsername=root \\\n" +
+                            "        -e typo3DatabasePassword=funcp \\\n" +
+                            "        -e typo3DatabaseHost=mariadb10 \\\n" +
+                            "        -e typo3TestingRedisHost=${BAMBOO_COMPOSE_PROJECT_NAME}sib_redis4_1 \\\n" +
+                            "        -e typo3TestingMemcachedHost=${BAMBOO_COMPOSE_PROJECT_NAME}sib_memcached1-5_1 \\\n" +
+                            "        --name ${BAMBOO_COMPOSE_PROJECT_NAME}sib_adhoc \\\n" +
+                            "        --network ${BAMBOO_COMPOSE_PROJECT_NAME}_test \\\n" +
+                            "        --rm \\\n" +
+                            "        typo3gmbh/" + requirementIdentifier.toLowerCase() + ":latest \\\n" +
+                            "        bin/bash -c \"cd ${PWD}; ./bin/phpunit $*\"\n" +
+                            "}\n" +
+                            "\n" +
+                            "phpunit --log-junit test-reports/phpunit.xml -c " + this.testingFrameworkBuildPath + "FunctionalTests-Job-" + i + ".xml"
                         )
-                        .environmentVariables(this.credentialsMysql)
                 )
                 .finalTasks(
-                    this.getTaskDeleteMysqlDatabases(),
+                    this.getTaskStopDockerDependencies(),
                     new TestParserTask(TestParserTaskProperties.TestType.JUNIT)
                         .resultDirectories("test-reports/phpunit.xml")
                 )
                 .requirements(
-                    requirement
+                    this.getRequirementDocker10()
                 )
                 .cleanWorkingDirectory(true)
             );
@@ -185,8 +205,10 @@ abstract public class AbstractCoreSpec {
 
     /**
      * Job with various smaller script tests
+     *
+     * @param String requirementIdentifier
      */
-    protected Job getJobIntegrationVarious() {
+    protected Job getJobIntegrationVarious(String requirementIdentifier) {
         // Exception code checker, xlf, permissions, rst file check
         return new Job("Integration various", new BambooKey("CDECC"))
             .description("Checks duplicate exceptions, git submodules, xlf files, permissions, rst")
@@ -212,16 +234,18 @@ abstract public class AbstractCoreSpec {
                         "./typo3/sysext/core/Build/Scripts/xlfcheck.sh"
                     )
             )
+            .requirements(
+                this.getRequirementDocker10()
+            )
             .cleanWorkingDirectory(true);
     }
 
     /**
      * Job for PHP lint
      *
-     * @param Requirement requirement
      * @param String requirementIdentfier
      */
-    protected Job getJobLintPhp(Requirement requirement, String requirementIdentifier) {
+    protected Job getJobLintPhp(String requirementIdentifier) {
         return new Job("Lint " + requirementIdentifier, new BambooKey("L" + requirementIdentifier))
             .description("Run php -l on source files for linting " + requirementIdentifier)
             .pluginConfigurations(this.getDefaultJobPluginConfiguration())
@@ -233,11 +257,23 @@ abstract public class AbstractCoreSpec {
                     .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
                     .inlineBody(
                         this.getScriptTaskBashInlineBody() +
-                        "find . -name \\*.php -print0 | xargs -0 -n1 -P2 php -l >/dev/null\n"
+                        "function runLint() {\n" +
+                        "    docker run \\\n" +
+                        "        -u ${HOST_UID} \\\n" +
+                        "        -v /etc/passwd:/etc/passwd \\\n" +
+                        "        -v ${BAMBOO_COMPOSE_PROJECT_NAME}_bamboo-data:/srv/bamboo/xml-data/build-dir/ \\\n" +
+                        "        -e HOME=${HOME} \\\n" +
+                        "        --name ${BAMBOO_COMPOSE_PROJECT_NAME}sib_adhoc \\\n" +
+                        "        --rm \\\n" +
+                        "        typo3gmbh/" + requirementIdentifier.toLowerCase() + ":latest \\\n" +
+                        "        bin/bash -c \"cd ${PWD}; find . -name \\*.php -print0 | xargs -0 -n1 -P2 php -l >/dev/null\"\n" +
+                        "}\n" +
+                        "\n" +
+                        "runLint"
                     )
             )
             .requirements(
-                requirement
+                this.getRequirementDocker10()
             )
             .cleanWorkingDirectory(true);
     }
@@ -245,32 +281,46 @@ abstract public class AbstractCoreSpec {
     /**
      * Job for unit testing PHP
      *
-     * @param Requirement requirement
      * @param String requirementIdentfier
      */
-    protected Job getJobUnitPhp(Requirement requirement, String requirementIdentifier) {
+    protected Job getJobUnitPhp(String requirementIdentifier) {
         return new Job("Unit " + requirementIdentifier, new BambooKey("UT" + requirementIdentifier))
             .description("Run unit tests " + requirementIdentifier)
             .pluginConfigurations(this.getDefaultJobPluginConfiguration())
             .tasks(
                 this.getTaskGitCloneRepository(),
                 this.getTaskGitCherryPick(),
-                this.getTaskComposerInstall(),
+                this.getTaskComposerInstall(requirementIdentifier),
+                this.getTaskDockerDependenciesUnit(),
                 new ScriptTask()
                     .description("Run phpunit")
                     .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
                     .inlineBody(
                         this.getScriptTaskBashInlineBody() +
-                        this.getScriptTaskBashPhpNoXdebug() +
-                        "php_no_xdebug bin/phpunit --log-junit test-reports/phpunit.xml -c " + this.testingFrameworkBuildPath + "UnitTests.xml"
+                        "function phpunit() {\n" +
+                        "    docker run \\\n" +
+                        "        -u ${HOST_UID} \\\n" +
+                        "        -v /etc/passwd:/etc/passwd \\\n" +
+                        "        -v ${BAMBOO_COMPOSE_PROJECT_NAME}_bamboo-data:/srv/bamboo/xml-data/build-dir/ \\\n" +
+                        "        -e typo3TestingRedisHost=redis4 \\\n" +
+                        "        -e typo3TestingMemcachedHost=memcached1-5 \\\n" +
+                        "        --name ${BAMBOO_COMPOSE_PROJECT_NAME}sib_adhoc \\\n" +
+                        "        --network ${BAMBOO_COMPOSE_PROJECT_NAME}_test \\\n" +
+                        "        --rm \\\n" +
+                        "        typo3gmbh/" + requirementIdentifier.toLowerCase() + ":latest \\\n" +
+                        "        bin/bash -c \"cd ${PWD}; php bin/phpunit $*\"\n" +
+                        "}\n" +
+                        "\n" +
+                        "phpunit --log-junit test-reports/phpunit.xml -c " + this.testingFrameworkBuildPath + "UnitTests.xml"
                     )
             )
             .finalTasks(
+                this.getTaskStopDockerDependencies(),
                 new TestParserTask(TestParserTaskProperties.TestType.JUNIT)
                     .resultDirectories("test-reports/phpunit.xml")
             )
             .requirements(
-                requirement
+                this.getRequirementDocker10()
             )
             .cleanWorkingDirectory(true);
     }
@@ -281,8 +331,7 @@ abstract public class AbstractCoreSpec {
     protected Task getTaskGitCloneRepository() {
         return new VcsCheckoutTask()
             .description("Checkout git core")
-            .checkoutItems(new CheckoutItem().defaultRepository())
-            .cleanCheckout(true);
+            .checkoutItems(new CheckoutItem().defaultRepository());
     }
 
     /**
@@ -306,93 +355,83 @@ abstract public class AbstractCoreSpec {
 
     /**
      * Task definition to execute composer install
+     *
+     * @param String requirementIdentifier
      */
-    protected Task getTaskComposerInstall() {
-        return new CommandTask()
+    protected Task getTaskComposerInstall(String requirementIdentifier) {
+        return new ScriptTask()
             .description("composer install")
-            .executable("composer")
-            .argument("install -n");
+            .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
+            .inlineBody(
+                this.getScriptTaskBashInlineBody() +
+                this.getScriptTaskComposer(requirementIdentifier) +
+                "composer install -n"
+            );
     }
 
     /**
-     * Task to delete any created mysql test databases, used as final task
+     * Start docker sibling containers to execute functional tests on mariadb
      */
-    protected Task getTaskDeleteMysqlDatabases() {
+    protected Task getTaskDockerDependenciesFunctionalMariadb10() {
         return new ScriptTask()
-            .description("Delete mysql test dbs")
+            .description("Start docker siblings for functional tests on mariadb")
             .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
             .inlineBody(
                 this.getScriptTaskBashInlineBody() +
-                "DB_STARTS_WITH=\"func_\"\n" +
-                "MUSER=\"funcu\"\n" +
-                "MPWD=\"funcp\"\n" +
-                "MYSQL=\"mysql\"\n" +
-                "DBS=\"$($MYSQL -u $MUSER -p\"$MPWD\" -Bse 'show databases')\"\n" +
-                "\n" +
-                "for db in $DBS; do\n" +
-                "    if [[ \"$db\" == $DB_STARTS_WITH* ]]; then\n" +
-                "        echo \"Deleting $db\"\n" +
-                "        $MYSQL -u $MUSER -p\"$MPWD\" -Bse \"drop database $db\"\n" +
-                "    fi\n" +
-                "done\n"
+                "cd Build/testing-docker/bamboo\n" +
+                "echo COMPOSE_PROJECT_NAME=${BAMBOO_COMPOSE_PROJECT_NAME}sib > .env\n" +
+                "docker-compose run start_dependencies_functional_mariadb10"
             );
     }
 
     /**
-     * Task to split functional jobs into chunks
+     * Start docker sibling containers to execute unit tests
      */
-    protected Task getTaskSplitFunctionalJobs(int numberOfJobs) {
+    protected Task getTaskDockerDependenciesUnit() {
         return new ScriptTask()
-            .description("Create list of test files to execute per job")
+            .description("Start docker siblings for unit tests")
             .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
             .inlineBody(
                 this.getScriptTaskBashInlineBody() +
-                "./" + this.testingFrameworkBuildPath + "Scripts/splitFunctionalTests.sh " + numberOfJobs
+                "cd Build/testing-docker/bamboo\n" +
+                "echo COMPOSE_PROJECT_NAME=${BAMBOO_COMPOSE_PROJECT_NAME}sib > .env\n" +
+                "docker-compose run start_dependencies_unit"
             );
     }
 
     /**
-     * Requirement for php 5.5
-     */
-    protected Requirement getRequirementPhpVersion55() {
-        return new Requirement("system.phpVersion")
-            .matchValue("5.5")
-            .matchType(Requirement.MatchType.EQUALS);
-    }
-
-    /**
-     * Requirement for php 5.6
+     * Stop started docker containers
      */
-    protected Requirement getRequirementPhpVersion56() {
-        return new Requirement("system.phpVersion")
-            .matchValue("5.6")
-            .matchType(Requirement.MatchType.EQUALS);
-    }
-
-    /**
-     * Requirement for php 7.0
-     */
-    protected Requirement getRequirementPhpVersion70() {
-        return new Requirement("system.phpVersion")
-            .matchValue("7.0")
-            .matchType(Requirement.MatchType.EQUALS);
+    protected Task getTaskStopDockerDependencies() {
+        return new ScriptTask()
+            .description("Stop docker siblings")
+            .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
+            .inlineBody(
+                this.getScriptTaskBashInlineBody() +
+                "cd Build/testing-docker/bamboo\n" +
+                "docker-compose down -v"
+            );
     }
 
     /**
-     * Requirement for php 7.1
+     * Task to split functional jobs into chunks
      */
-    protected Requirement getRequirementPhpVersion71() {
-        return new Requirement("system.phpVersion")
-            .matchValue("7.1")
-            .matchType(Requirement.MatchType.EQUALS);
+    protected Task getTaskSplitFunctionalJobs(int numberOfJobs) {
+        return new ScriptTask()
+            .description("Create list of test files to execute per job")
+            .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
+            .inlineBody(
+                this.getScriptTaskBashInlineBody() +
+                "./" + this.testingFrameworkBuildPath + "Scripts/splitFunctionalTests.sh " + numberOfJobs
+            );
     }
 
     /**
-     * Requirement for php 7.2
+     * Requirement for docker 1.0 set by bamboo-agents
      */
-    protected Requirement getRequirementPhpVersion72() {
-        return new Requirement("system.phpVersion")
-            .matchValue("7.2")
+    protected Requirement getRequirementDocker10() {
+        return new Requirement("system.hasDocker")
+            .matchValue("1.0")
             .matchType(Requirement.MatchType.EQUALS);
     }
 
@@ -407,6 +446,29 @@ abstract public class AbstractCoreSpec {
             "    bash \"$0\" \"$@\"\n" +
             "    exit \"$?\"\n" +
             "fi\n" +
+            "\n" +
+            "set -x\n" +
+            "\n";
+    }
+
+    /**
+     * A bash function aliasing 'composer' as docker command
+     *
+     * @param String requirementIdentifier
+     */
+    protected String getScriptTaskComposer(String requirementIdentifier) {
+        return
+            "function composer() {\n" +
+            "    docker run \\\n" +
+            "        -u ${HOST_UID} \\\n" +
+            "        -v /etc/passwd:/etc/passwd \\\n" +
+            "        -v ${BAMBOO_COMPOSE_PROJECT_NAME}_bamboo-data:/srv/bamboo/xml-data/build-dir/ \\\n" +
+            "        -e HOME=${HOME} \\\n" +
+            "        --name ${BAMBOO_COMPOSE_PROJECT_NAME}sib_adhoc \\\n" +
+            "        --rm \\\n" +
+            "        typo3gmbh/" + requirementIdentifier.toLowerCase() + ":latest \\\n" +
+            "        bin/bash -c \"cd ${PWD}; composer $*\"\n" +
+            "}\n" +
             "\n";
     }
 
index 08e80b9..f6f8f6b 100644 (file)
@@ -70,36 +70,38 @@ public class PreMergeSpec extends AbstractCoreSpec {
     Plan createPlan() {
         // PREPARATION stage
         ArrayList<Job> jobsPreparationStage = new ArrayList<Job>();
-
         jobsPreparationStage.add(this.getJobBuildLabels());
-
-        jobsPreparationStage.add(this.getJobComposerValidate());
-
         Stage stagePreparation = new Stage("Preparation")
             .jobs(jobsPreparationStage.toArray(new Job[jobsPreparationStage.size()]));
 
+        // EARLY stage
+        ArrayList<Job> jobsEarlyStage = new ArrayList<Job>();
+        jobsEarlyStage.add(this.getJobComposerValidate("PHP72"));
+        Stage stageEarly = new Stage("Early")
+            .jobs(jobsEarlyStage.toArray(new Job[jobsEarlyStage.size()]));
+
         // MAIN stage
         ArrayList<Job> jobsMainStage = new ArrayList<Job>();
 
-        jobsMainStage.add(this.getJobIntegrationVarious());
+        jobsMainStage.add(this.getJobIntegrationVarious("PHP72"));
 
-        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, this.getRequirementPhpVersion55(), "PHP55"));
-        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, this.getRequirementPhpVersion56(), "PHP56"));
-        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, this.getRequirementPhpVersion70(), "PHP70"));
-        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, this.getRequirementPhpVersion71(), "PHP71"));
-        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, this.getRequirementPhpVersion72(), "PHP72"));
+        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, "PHP55"));
+        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, "PHP56"));
+        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, "PHP70"));
+        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, "PHP71"));
+        jobsMainStage.addAll(this.getJobsFunctionalTestsMysql(this.numberOfFunctionalMysqlJobs, "PHP72"));
 
-        jobsMainStage.add(this.getJobLintPhp(this.getRequirementPhpVersion55(), "PHP55"));
-        jobsMainStage.add(this.getJobLintPhp(this.getRequirementPhpVersion56(), "PHP56"));
-        jobsMainStage.add(this.getJobLintPhp(this.getRequirementPhpVersion70(), "PHP70"));
-        jobsMainStage.add(this.getJobLintPhp(this.getRequirementPhpVersion71(), "PHP71"));
-        jobsMainStage.add(this.getJobLintPhp(this.getRequirementPhpVersion72(), "PHP72"));
+        jobsMainStage.add(this.getJobLintPhp("PHP55"));
+        jobsMainStage.add(this.getJobLintPhp("PHP56"));
+        jobsMainStage.add(this.getJobLintPhp("PHP70"));
+        jobsMainStage.add(this.getJobLintPhp("PHP71"));
+        jobsMainStage.add(this.getJobLintPhp("PHP72"));
 
-        jobsMainStage.add(this.getJobUnitPhp(this.getRequirementPhpVersion55(), "PHP55"));
-        jobsMainStage.add(this.getJobUnitPhp(this.getRequirementPhpVersion56(), "PHP56"));
-        jobsMainStage.add(this.getJobUnitPhp(this.getRequirementPhpVersion70(), "PHP70"));
-        jobsMainStage.add(this.getJobUnitPhp(this.getRequirementPhpVersion71(), "PHP71"));
-        jobsMainStage.add(this.getJobUnitPhp(this.getRequirementPhpVersion72(), "PHP72"));
+        jobsMainStage.add(this.getJobUnitPhp("PHP55"));
+        jobsMainStage.add(this.getJobUnitPhp("PHP56"));
+        jobsMainStage.add(this.getJobUnitPhp("PHP70"));
+        jobsMainStage.add(this.getJobUnitPhp("PHP71"));
+        jobsMainStage.add(this.getJobUnitPhp("PHP72"));
 
         Stage stageMainStage = new Stage("Main stage")
             .jobs(jobsMainStage.toArray(new Job[jobsMainStage.size()]));
@@ -110,6 +112,7 @@ public class PreMergeSpec extends AbstractCoreSpec {
             .pluginConfigurations(this.getDefaultPlanPluginConfiguration())
             .stages(
                 stagePreparation,
+                stageEarly,
                 stageMainStage
             )
             .linkedRepositories("git.typo3.org Core 7.6")
@@ -170,6 +173,9 @@ public class PreMergeSpec extends AbstractCoreSpec {
                     .interpreter(ScriptTaskProperties.Interpreter.BINSH_OR_CMDEXE)
                     .inlineBody("echo \"I'm just here for the labels!\"")
             )
+            .requirements(
+                this.getRequirementDocker10()
+            )
             .cleanWorkingDirectory(true);
     }
 }
diff --git a/Build/testing-docker/bamboo/docker-compose.yml b/Build/testing-docker/bamboo/docker-compose.yml
new file mode 100644 (file)
index 0000000..879981a
--- /dev/null
@@ -0,0 +1,69 @@
+version: '2.3'
+services:
+  chrome:
+    image: selenium/standalone-chrome:3.12
+    networks:
+      - test
+  mariadb10:
+    image: mariadb:10.1
+    environment:
+      MYSQL_ROOT_PASSWORD: funcp
+    tmpfs:
+      - /var/lib/mysql/:rw,noexec,nosuid
+    networks:
+      - test
+  redis4:
+    image: redis:4-alpine
+    networks:
+      - test
+  memcached1-5:
+    image: memcached:1.5-alpine
+    networks:
+      - test
+  web:
+    image: typo3gmbh/php72:latest
+    user: ${HOST_UID}
+    stop_grace_period: 1s
+    networks:
+      - test
+    volumes:
+      - bamboo-data:/srv/bamboo/xml-data/build-dir
+    command: php -n -c /etc/php/cli-no-xdebug/php.ini -S web:8000 -t /srv/bamboo/xml-data/build-dir/${bamboo_buildKey}
+
+  start_dependencies_functional_mariadb10:
+    image: alpine:3.8
+    links:
+      - mariadb10
+      - redis4
+      - memcached1-5
+    networks:
+      - test
+    command: >
+      /bin/sh -c "
+        echo Waiting for db start...;
+        while ! nc -z mariadb10 3306;
+        do
+          sleep 1;
+        done;
+        echo Connected!;
+      "
+
+  start_dependencies_unit:
+    image: alpine:3.8
+    links:
+      - redis4
+      - memcached1-5
+    command: >
+      /bin/sh -c "
+        sleep 1;
+      "
+
+networks:
+  test:
+    external:
+      name: ${BAMBOO_COMPOSE_PROJECT_NAME}_test
+
+volumes:
+  bamboo-data:
+    external:
+      name: ${BAMBOO_COMPOSE_PROJECT_NAME}_bamboo-data
\ No newline at end of file
index e74d167..c72462f 100644 (file)
@@ -15,13 +15,14 @@ namespace TYPO3\CMS\Core\Tests\Unit\Cache\Backend;
  */
 
 use TYPO3\CMS\Core\Cache\Backend\MemcachedBackend;
+use TYPO3\CMS\Core\Tests\UnitTestCase;
 
 /**
  * Testcase for the cache to memcached backend
  *
  * This file is a backport from FLOW3
  */
-class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+class MemcachedBackendTest extends UnitTestCase
 {
     /**
      * Sets up this testcase
@@ -33,13 +34,32 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         if (!extension_loaded('memcache') && !extension_loaded('memcached')) {
             $this->markTestSkipped('Neither "memcache" nor "memcached" extension is available');
         }
-        try {
-            if (!@fsockopen('localhost', 11211)) {
-                $this->markTestSkipped('memcached not reachable');
-            }
-        } catch (\Exception $e) {
-            $this->markTestSkipped('memcached not reachable');
+        if (!getenv('typo3TestingMemcachedHost')) {
+            $this->markTestSkipped('environment variable "typo3TestingMemcachedHost" must be set to run this test');
         }
+        // Note we assume that if that typo3TestingMemcachedHost 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.
+    }
+
+    /**
+     * Initialize MemcacheBackend ($subject)
+     */
+    protected function initializeSubject()
+    {
+        // We know this env is set, otherwise setUp() would skip the tests
+        $memcachedHost = getenv('typo3TestingMemcachedHost');
+        // If typo3TestingMemcachedPort env is set, use it, otherwise fall back to standard port
+        $env = getenv('typo3TestingMemcachedPort');
+        $memcachedPort = is_string($env) ? (int)$env : 11211;
+
+        /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
+        $cache = $this->getMock(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface::class, [], [], '', false);
+
+        $subject = new MemcachedBackend('Testing', [ 'servers' => [$memcachedHost . ':' . $memcachedPort] ]);
+        $subject->setCache($cache);
+        $subject->initializeObject();
+        return $subject;
     }
 
     /**
@@ -48,8 +68,13 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function setThrowsExceptionIfNoFrontEndHasBeenSet()
     {
-        $backendOptions = ['servers' => ['localhost:11211']];
-        $backend = new MemcachedBackend('Testing', $backendOptions);
+        // We know this env is set, otherwise setUp() would skip the tests
+        $memcachedHost = getenv('typo3TestingMemcachedHost');
+        // If typo3TestingMemcachedPort env is set, use it, otherwise fall back to standard port
+        $env = getenv('typo3TestingMemcachedPort');
+        $memcachedPort = is_string($env) ? (int)$env : 11211;
+
+        $backend = new MemcachedBackend('Testing', [ 'servers' => [$memcachedHost . ':' . $memcachedPort] ]);
         $backend->initializeObject();
         $data = 'Some data';
         $identifier = $this->getUniqueId('MyIdentifier');
@@ -71,7 +96,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function itIsPossibleToSetAndCheckExistenceInCache()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $data = 'Some data';
         $identifier = $this->getUniqueId('MyIdentifier');
         $backend->set($identifier, $data);
@@ -84,7 +109,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function itIsPossibleToSetAndGetEntry()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $data = 'Some data';
         $identifier = $this->getUniqueId('MyIdentifier');
         $backend->set($identifier, $data);
@@ -97,7 +122,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function itIsPossibleToRemoveEntryFromCache()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $data = 'Some data';
         $identifier = $this->getUniqueId('MyIdentifier');
         $backend->set($identifier, $data);
@@ -111,7 +136,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function itIsPossibleToOverwriteAnEntryInTheCache()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $data = 'Some data';
         $identifier = $this->getUniqueId('MyIdentifier');
         $backend->set($identifier, $data);
@@ -126,7 +151,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function findIdentifiersByTagFindsCacheEntriesWithSpecifiedTag()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $data = 'Some data';
         $identifier = $this->getUniqueId('MyIdentifier');
         $backend->set($identifier, $data, ['UnitTestTag%tag1', 'UnitTestTag%tag2']);
@@ -141,7 +166,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function setRemovesTagsFromPreviousSet()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $data = 'Some data';
         $identifier = $this->getUniqueId('MyIdentifier');
         $backend->set($identifier, $data, ['UnitTestTag%tag1', 'UnitTestTag%tag2']);
@@ -155,7 +180,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function hasReturnsFalseIfTheEntryDoesntExist()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $identifier = $this->getUniqueId('NonExistingIdentifier');
         $inCache = $backend->has($identifier);
         $this->assertFalse($inCache, '"has" did not return FALSE when checking on non existing identifier');
@@ -166,7 +191,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function removeReturnsFalseIfTheEntryDoesntExist()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $identifier = $this->getUniqueId('NonExistingIdentifier');
         $inCache = $backend->remove($identifier);
         $this->assertFalse($inCache, '"remove" did not return FALSE when checking on non existing identifier');
@@ -177,7 +202,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function flushByTagRemovesCacheEntriesWithSpecifiedTag()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $data = 'some data' . microtime();
         $backend->set('BackendMemcacheTest1', $data, ['UnitTestTag%test', 'UnitTestTag%boring']);
         $backend->set('BackendMemcacheTest2', $data, ['UnitTestTag%test', 'UnitTestTag%special']);
@@ -193,7 +218,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function flushRemovesAllCacheEntries()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $data = 'some data' . microtime();
         $backend->set('BackendMemcacheTest1', $data);
         $backend->set('BackendMemcacheTest2', $data);
@@ -209,18 +234,23 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function flushRemovesOnlyOwnEntries()
     {
-        $backendOptions = ['servers' => ['localhost:11211']];
+         // We know this env is set, otherwise setUp() would skip the tests
+        $memcachedHost = getenv('typo3TestingMemcachedHost');
+        // If typo3TestingMemcachedPort env is set, use it, otherwise fall back to standard port
+        $env = getenv('typo3TestingMemcachedPort');
+        $memcachedPort = is_string($env) ? (int)$env : 11211;
+
         /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $thisCache */
         $thisCache = $this->getMock(\TYPO3\CMS\Core\Cache\Frontend\AbstractFrontend::class, [], [], '', false);
         $thisCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('thisCache'));
-        $thisBackend = new MemcachedBackend('Testing', $backendOptions);
+        $thisBackend = new MemcachedBackend('Testing', [ 'servers' => [$memcachedHost . ':' . $memcachedPort] ]);
         $thisBackend->setCache($thisCache);
         $thisBackend->initializeObject();
 
         /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $thatCache */
         $thatCache = $this->getMock(\TYPO3\CMS\Core\Cache\Frontend\AbstractFrontend::class, [], [], '', false);
         $thatCache->expects($this->any())->method('getIdentifier')->will($this->returnValue('thatCache'));
-        $thatBackend = new MemcachedBackend('Testing', $backendOptions);
+        $thatBackend = new MemcachedBackend('Testing', [ 'servers' => [$memcachedHost . ':' . $memcachedPort] ]);
         $thatBackend->setCache($thatCache);
         $thatBackend->initializeObject();
         $thisBackend->set('thisEntry', 'Hello');
@@ -238,7 +268,7 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function largeDataIsStored()
     {
-        $backend = $this->setUpBackend();
+        $backend = $this->initializeSubject();
         $data = str_repeat('abcde', 1024 * 1024);
         $backend->set('tooLargeData', $data);
         $this->assertTrue($backend->has('tooLargeData'));
@@ -250,11 +280,23 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     public function setTagsOnlyOnceToIdentifier()
     {
-        $backendOptions = ['servers' => ['localhost:11211']];
+        // We know this env is set, otherwise setUp() would skip the tests
+        $memcachedHost = getenv('typo3TestingMemcachedHost');
+        // If typo3TestingMemcachedPort env is set, use it, otherwise fall back to standard port
+        $env = getenv('typo3TestingMemcachedPort');
+        $memcachedPort = is_string($env) ? (int)$env : 11211;
+
+        $accessibleClassName = $this->buildAccessibleProxy(MemcachedBackend::class);
+        $backend = new $accessibleClassName('Testing', [ 'servers' => [$memcachedHost . ':' . $memcachedPort] ]);
+
+        /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
+        $cache = $this->getMock(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface::class, [], [], '', false);
+        $backend->setCache($cache);
+        $backend->initializeObject();
+
         $identifier = $this->getUniqueId('MyIdentifier');
         $tags = ['UnitTestTag%test', 'UnitTestTag%boring'];
 
-        $backend = $this->setUpBackend($backendOptions, true);
         $backend->_call('addIdentifierToTags', $identifier, $tags);
         $this->assertSame(
             $tags,
@@ -267,29 +309,4 @@ class MemcachedBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
             $backend->_call('findTagsByIdentifier', $identifier)
         );
     }
-
-    /**
-     * Sets up the memcached backend used for testing
-     *
-     * @param array $backendOptions Options for the memcache backend
-     * @param bool $accessible TRUE if backend should be encapsulated in accessible proxy otherwise FALSE.
-     * @return \TYPO3\CMS\Core\Tests\AccessibleObjectInterface|MemcachedBackend
-     */
-    protected function setUpBackend(array $backendOptions = [], $accessible = false)
-    {
-        /** @var \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface $cache */
-        $cache = $this->getMock(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface::class, [], [], '', false);
-        if ($backendOptions == []) {
-            $backendOptions = ['servers' => ['localhost:11211']];
-        }
-        if ($accessible) {
-            $accessibleClassName = $this->buildAccessibleProxy(MemcachedBackend::class);
-            $backend = new $accessibleClassName('Testing', $backendOptions);
-        } else {
-            $backend = new MemcachedBackend('Testing', $backendOptions);
-        }
-        $backend->setCache($cache);
-        $backend->initializeObject();
-        return $backend;
-    }
 }
index 667d70a..da0e035 100644 (file)
@@ -14,6 +14,8 @@ namespace TYPO3\CMS\Core\Tests\Unit\Cache\Backend;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Tests\UnitTestCase;
+
 /**
  * Testcase for the cache to redis backend
  *
@@ -25,9 +27,10 @@ namespace TYPO3\CMS\Core\Tests\Unit\Cache\Backend;
  * to the internal data structure are done.
  *
  * Warning:
- * The unit tests use and flush redis database numbers 0 and 1!
+ * The unit tests use and flush redis database numbers 0 and 1 on the
+ * redis host specified by environment variable typo3RedisHost
  */
-class RedisBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
+class RedisBackendTest extends UnitTestCase
 {
     /**
      * If set, the tearDown() method will flush the cache used by this unit test.
@@ -39,7 +42,7 @@ class RedisBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
     /**
      * Own redis instance used in implementation tests
      *
-     * @var Redis
+     * @var \Redis
      */
     protected $redis = null;
 
@@ -51,13 +54,12 @@ class RedisBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
         if (!extension_loaded('redis')) {
             $this->markTestSkipped('redis extension was not available');
         }
-        try {
-            if (!@fsockopen('127.0.0.1', 6379)) {
-                $this->markTestSkipped('redis server not reachable');
-            }
-        } catch (\Exception $e) {
-            $this->markTestSkipped('redis server not reachable');
+        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.
     }
 
     /**
@@ -69,6 +71,11 @@ class RedisBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
     {
         $mockCache = $this->getMock(\TYPO3\CMS\Core\Cache\Frontend\FrontendInterface::class, [], [], '', false);
         $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();
@@ -79,8 +86,14 @@ class RedisBackendTest extends \TYPO3\CMS\Core\Tests\UnitTestCase
      */
     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('127.0.0.1', 6379);
+        $this->redis->connect($redisHost, $redisPort);
     }
 
     /**