[TASK] Acceptance tests in controlled environment 27/46727/8
authorChristian Kuhn <lolli@schwarzbu.ch>
Tue, 16 Feb 2016 19:19:26 +0000 (20:19 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Wed, 24 Feb 2016 11:58:11 +0000 (12:58 +0100)
To successfully run acceptance tests we need to create
a "controlled environment" that is separated from any
existing core environment. A standalone instance is
created that has its own set of loaded extensions,
an own database and an own web path.

The patch refactors and simplifies the existing set up
code of unit and functional test environments to be
better reusable to achieve this.

Change-Id: I6c4a818fa82d2723075888ad8a60b30acb5ed30e
Resolves: #68113
Releases: master
Reviewed-on: https://review.typo3.org/46727
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Frank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Naegler <frank.naegler@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
15 files changed:
.travis.yml
typo3/sysext/core/Build/AcceptanceTests.yml
typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Functional.php [deleted file]
typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Unit.php [deleted file]
typo3/sysext/core/Build/Configuration/Acceptance/TravisLocalConfiguration.php [deleted file]
typo3/sysext/core/Build/Configuration/Acceptance/TravisPackageStates.php [deleted file]
typo3/sysext/core/Build/Configuration/Acceptance/acceptance.suite.yml
typo3/sysext/core/Build/Configuration/FunctionalTestsConfiguration.php [deleted file]
typo3/sysext/core/Build/FunctionalTestsBootstrap.php
typo3/sysext/core/Build/UnitTestsBootstrap.php
typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php
typo3/sysext/core/Tests/AcceptanceCoreEnvironment.php [new file with mode: 0644]
typo3/sysext/core/Tests/FunctionalTestCase.php
typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php [deleted file]
typo3/sysext/core/Tests/Testbase.php [new file with mode: 0644]

index 6992161..a3a354c 100644 (file)
@@ -71,11 +71,10 @@ script:
         ./bin/selenium-server-standalone &
         php -S 0.0.0.0:8000 &
         sleep 3;
-        mysql -e 'create database acceptance';
-        mysql acceptance < typo3/sysext/core/ext_tables.sql;
-        mkdir -p typo3temp/var/logs/codeception/ typo3conf/ext;
-        cp typo3/sysext/core/Build/Configuration/Acceptance/TravisLocalConfiguration.php typo3conf/LocalConfiguration.php;
-        cp typo3/sysext/core/Build/Configuration/Acceptance/TravisPackageStates.php typo3conf/PackageStates.php;
+        export typo3DatabaseName="typo3";
+        export typo3DatabaseHost="localhost";
+        export typo3DatabaseUsername="root";
+        export typo3DatabasePassword="";
         ./bin/codecept run acceptance -c typo3/sysext/core/Build/AcceptanceTests.yml typo3/sysext/core/Tests/Acceptance
     fi
   - >
index 99c1af1..6de4260 100644 (file)
@@ -1,13 +1,14 @@
 actor: Tester
 paths:
-    tests: Configuration/Acceptance
-    log: ../../../../typo3temp/var/logs/codeception
-    data: Configuration/Acceptance/Data
-    support: Configuration/Acceptance/Support
-    envs: Configuration/Acceptance/Envs
+  tests: Configuration/Acceptance
+  log: ../../../../typo3temp/var/tests
+  data: Configuration/Acceptance/Data
+  support: Configuration/Acceptance/Support
+  envs: Configuration/Acceptance/Envs
 settings:
-    colors: true
-    memory_limit: 1024M
+  colors: true
+  memory_limit: 1024M
 extensions:
-    enabled:
-        - Codeception\Extension\RunFailed
\ No newline at end of file
+  enabled:
+    - Codeception\Extension\RunFailed
+    - TYPO3\CMS\Core\Tests\AcceptanceCoreEnvironment
\ No newline at end of file
diff --git a/typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Functional.php b/typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Functional.php
deleted file mode 100644 (file)
index 4183cb0..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-namespace Helper;
-
-// here you can define custom actions
-// all public methods declared in helper class will be available in $I
-
-class Functional extends \Codeception\Module
-{
-
-}
diff --git a/typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Unit.php b/typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Unit.php
deleted file mode 100644 (file)
index 6064d37..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-namespace Helper;
-
-// here you can define custom actions
-// all public methods declared in helper class will be available in $I
-
-class Unit extends \Codeception\Module
-{
-
-}
diff --git a/typo3/sysext/core/Build/Configuration/Acceptance/TravisLocalConfiguration.php b/typo3/sysext/core/Build/Configuration/Acceptance/TravisLocalConfiguration.php
deleted file mode 100644 (file)
index 8274f88..0000000
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-return [
-    'BE' => [
-        'debug' => true,
-        'explicitADmode' => 'explicitAllow',
-        'explicitConfirmationOfTranslation' => true,
-        'fileadminDir' => 'fileadmin/',
-        'installToolPassword' => '$P$notnotnotnotnotnot.validvalidva',
-        'loginSecurityLevel' => 'rsa',
-        'sessionTimeout' => '200000',
-        'versionNumberInFilename' => '0',
-    ],
-    'DB' => [
-        'database' => 'acceptance',
-        'extTablesDefinitionScript' => 'extTables.php',
-        'host' => 'localhost',
-        'socket' => '',
-        'username' => 'root',
-    ],
-    'EXT' => [
-        'extConf' => [
-            'rsaauth' => 'a:1:{s:18:"temporaryDirectory";s:0:"";}',
-            'saltedpasswords' => 'a:2:{s:3:"BE.";a:4:{s:21:"saltedPWHashingMethod";s:41:"TYPO3\\CMS\\Saltedpasswords\\Salt\\PhpassSalt";s:11:"forceSalted";i:0;s:15:"onlyAuthService";i:0;s:12:"updatePasswd";i:1;}s:3:"FE.";a:5:{s:7:"enabled";i:1;s:21:"saltedPWHashingMethod";s:41:"TYPO3\\CMS\\Saltedpasswords\\Salt\\PhpassSalt";s:11:"forceSalted";i:0;s:15:"onlyAuthService";i:0;s:12:"updatePasswd";i:1;}}',
-        ],
-    ],
-    'EXTCONF' => [
-    ],
-    'FE' => [
-        'debug' => true,
-        'loginSecurityLevel' => 'rsa',
-    ],
-    'GFX' => [
-        'jpg_quality' => '80',
-        'processor' => 'GraphicsMagick',
-        'processor_allowTemporaryMasksAsPng' => false,
-        'processor_colorspace' => 'RGB',
-        'processor_effects' => -1,
-        'processor_enabled' => 1,
-        'processor_path' => '/usr/bin/',
-        'processor_path_lzw' => '/usr/bin/',
-    ],
-    'INSTALL' => [
-        'wizardDone' => [
-        ],
-    ],
-    'SYS' => [
-        'caching' => [
-            'cacheConfigurations' => [
-                'extbase_object' => [
-                    'backend' => 'TYPO3\\CMS\\Core\\Cache\\Backend\\Typo3DatabaseBackend',
-                    'frontend' => 'TYPO3\\CMS\\Core\\Cache\\Frontend\\VariableFrontend',
-                    'groups' => [
-                        'system',
-                    ],
-                    'options' => [
-                        'defaultLifetime' => 0,
-                    ],
-                ],
-            ],
-        ],
-        'clearCacheSystem' => true,
-        'devIPmask' => '*',
-        'displayErrors' => 1,
-        'enableDeprecationLog' => 'file',
-        'encryptionKey' => 'invalidinvalidinvalidinvalidinvalidinvalidinvalidinvalidinvalidinvalidinvalidinvalidinvalidinval',
-        'exceptionalErrors' => 28674,
-        'isInitialInstallationInProgress' => false,
-        'sitename' => 't3',
-        'sqlDebug' => 1,
-        'systemLogLevel' => 0,
-        'trustedHostsPattern' => '.*',
-    ],
-];
\ No newline at end of file
diff --git a/typo3/sysext/core/Build/Configuration/Acceptance/TravisPackageStates.php b/typo3/sysext/core/Build/Configuration/Acceptance/TravisPackageStates.php
deleted file mode 100644 (file)
index 31057b2..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-<?php
-# PackageStates.php
-
-# This file is maintained by TYPO3's package management. Although you can edit it
-# manually, you should rather use the extension manager for maintaining packages.
-# This file will be regenerated automatically if it doesn't exist. Deleting this file
-# should, however, never become necessary if you use the package commands.
-
-return [
-    'packages' => [
-        'core' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-core',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/core/',
-            'classesPath' => 'Classes/',
-        ],
-        'extbase' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-extbase',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/extbase/',
-            'classesPath' => 'Classes/',
-        ],
-        'fluid' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-fluid',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/fluid/',
-            'classesPath' => 'Classes/',
-        ],
-        'extensionmanager' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-extensionmanager',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/extensionmanager/',
-            'classesPath' => 'Classes/',
-        ],
-        'lang' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-lang',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/lang/',
-            'classesPath' => 'Classes/',
-        ],
-        'setup' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-setup',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/setup/',
-            'classesPath' => 'Classes/',
-        ],
-        'rsaauth' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-rsaauth',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/rsaauth/',
-            'classesPath' => 'Classes/',
-        ],
-        'saltedpasswords' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-saltedpasswords',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/saltedpasswords/',
-            'classesPath' => 'Classes/',
-        ],
-        'backend' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-backend',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/backend/',
-            'classesPath' => 'Classes/',
-        ],
-        'belog' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-belog',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/belog/',
-            'classesPath' => 'Classes/',
-        ],
-        'install' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-install',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/install/',
-            'classesPath' => 'Classes/',
-        ],
-        't3skin' => [
-            'manifestPath' => '',
-            'composerName' => 'typo3/cms-t3skin',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/t3skin/',
-            'classesPath' => 'Classes/',
-        ],
-        'frontend' => [
-            'composerName' => 'typo3/cms-frontend',
-            'state' => 'active',
-            'packagePath' => 'typo3/sysext/frontend/',
-            'classesPath' => 'Classes/',
-        ],
-    ],
-    'version' => 4,
-];
\ No newline at end of file
index 168a286..4602ac3 100644 (file)
@@ -1,22 +1,18 @@
 class_name: AcceptanceTester
 modules:
-    enabled:
-        - WebDriver
-        - \Helper\Acceptance
-        - Asserts
-        - \Helper\Unit
-    config:
-      WebDriver:
-        url: http://localhost:8000
-        browser: phantomjs
-        http_proxy: direct
-
+  enabled:
+    - WebDriver
+    - \Helper\Acceptance
+    - Asserts
+  config:
+    WebDriver:
+      url: http://localhost:8000/typo3temp/var/tests/acceptance
+      browser: phantomjs
+      http_proxy: direct
 env:
-    firefox:
-        modules:
-            config:
-                WebDriver:
-                    browser: 'firefox'
-                    window_size: 1024x768
-    phantom:
-        # nothing changed
\ No newline at end of file
+  firefox:
+    modules:
+      config:
+        WebDriver:
+          browser: 'firefox'
+          window_size: 1024x768
\ No newline at end of file
diff --git a/typo3/sysext/core/Build/Configuration/FunctionalTestsConfiguration.php b/typo3/sysext/core/Build/Configuration/FunctionalTestsConfiguration.php
deleted file mode 100644 (file)
index 135c46a..0000000
+++ /dev/null
@@ -1,9 +0,0 @@
-<?php
-return array(
-    'SYS' => array(
-        'displayErrors' => '1',
-        'debugExceptionHandler' => '',
-        'trustedHostsPattern' => '.*',
-        'setDBinit' => 'SET SESSION sql_mode = \'STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_VALUE_ON_ZERO,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ONLY_FULL_GROUP_BY\';',
-    )
-);
index d52ada0..afa8245 100644 (file)
@@ -1,6 +1,4 @@
 <?php
-namespace TYPO3\CMS\Core\Build;
-
 /*
  * This file is part of the TYPO3 CMS project.
  *
@@ -14,134 +12,22 @@ namespace TYPO3\CMS\Core\Build;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Tests\BaseTestCase;
-
 /**
  * This file is defined in FunctionalTests.xml and called by phpunit
  * before instantiating the test suites, it must also be included
  * with phpunit parameter --bootstrap if executing single test case classes.
  */
-class FunctionalTestsBootstrap
-{
-    /**
-     * Bootstraps the system for unit tests.
-     *
-     * @return void
-     */
-    public function bootstrapSystem()
-    {
-        $this->enableDisplayErrors()
-            ->loadClassFiles()
-            ->defineOriginalRootPath()
-            ->createNecessaryDirectoriesInDocumentRoot();
-    }
-
-    /**
-     * Makes sure error messages during the tests get displayed no matter what is set in php.ini.
-     *
-     * @return FunctionalTestsBootstrap fluent interface
-     */
-    protected function enableDisplayErrors()
-    {
-        @ini_set('display_errors', 1);
-        return $this;
-    }
-
-    /**
-     * Requires classes the functional test classes extend from or use for further bootstrap.
-     * Only files required for "new TestCaseClass" are required here and a general exception
-     * that is thrown by setUp() code.
-     *
-     * @return FunctionalTestsBootstrap fluent interface
-     */
-    protected function loadClassFiles()
-    {
-        if (!class_exists('PHPUnit_Framework_TestCase')) {
-            die('PHPUnit wasn\'t found. Please check your settings and command.');
-        }
-        if (!class_exists(BaseTestCase::class)) {
-            // PHPUnit is invoked globally, so we need to include the project autoload file
-            require_once __DIR__ . '/../../../../vendor/autoload.php';
-        }
-
-        return $this;
-    }
-
-    /**
-     * Defines the constant ORIGINAL_ROOT for the path to the original TYPO3 document root.
-     *
-     * If ORIGINAL_ROOT already is defined, this method is a no-op.
-     *
-     * @return FunctionalTestsBootstrap fluent interface
-     */
-    protected function defineOriginalRootPath()
-    {
-        if (!defined('ORIGINAL_ROOT')) {
-            /** @var string */
-            define('ORIGINAL_ROOT', $this->getWebRoot());
-        }
-
-        if (!file_exists(ORIGINAL_ROOT . 'typo3/cli_dispatch.phpsh')) {
-            die('Unable to determine path to entry script. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
-        }
-
-        return $this;
-    }
-
-    /**
-     * Creates the following directories in the TYPO3 core:
-     * - typo3temp
-     *
-     * @return FunctionalTestsBootstrap fluent interface
-     */
-    protected function createNecessaryDirectoriesInDocumentRoot()
-    {
-        $this->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests');
-        $this->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient');
-
-        return $this;
-    }
-
-    /**
-     * Creates the directory $directory (recursively if required).
-     *
-     * If $directory already exists, this method is a no-op.
-     *
-     * @param string $directory absolute path of the directory to be created
-     * @return void
-     * @throws \RuntimeException
-     */
-    protected function createDirectory($directory)
-    {
-        if (is_dir($directory)) {
-            return;
-        }
-        @mkdir($directory, 0777, true);
-        clearstatcache();
-        if (!is_dir($directory)) {
-            throw new \RuntimeException('Directory "' . $directory . '" could not be created', 1404038665);
-        }
-    }
 
-    /**
-     * Returns the absolute path the TYPO3 document root.
-     *
-     * @return string the TYPO3 document root using Unix path separators
-     */
-    protected function getWebRoot()
-    {
-        if (getenv('TYPO3_PATH_WEB')) {
-            $webRoot = getenv('TYPO3_PATH_WEB');
-        } else {
-            $webRoot = getcwd();
-        }
-        return rtrim(strtr($webRoot, '\\', '/'), '/') . '/';
+call_user_func(function () {
+    // Ensure cli only as security measure
+    if (PHP_SAPI !== 'cli') {
+        die('This script supports command line usage only. Please check your command.');
     }
-}
 
-if (PHP_SAPI !== 'cli') {
-    die('This script supports command line usage only. Please check your command.');
-}
-$bootstrap = new FunctionalTestsBootstrap();
-$bootstrap->bootstrapSystem();
-unset($bootstrap);
+    $testbase = new \TYPO3\CMS\Core\Tests\Testbase();
+    $testbase->enableDisplayErrors();
+    $testbase->defineBaseConstants();
+    $testbase->defineOriginalRootPath();
+    $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests');
+    $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient');
+});
\ No newline at end of file
index 32bdf12..e2fb5fa 100644 (file)
@@ -1,6 +1,4 @@
 <?php
-namespace TYPO3\CMS\Core\Build;
-
 /*
  * This file is part of the TYPO3 CMS project.
  *
@@ -14,225 +12,61 @@ namespace TYPO3\CMS\Core\Build;
  * The TYPO3 project - inspiring people to share!
  */
 
-use TYPO3\CMS\Core\Core\Bootstrap;
-
 /**
  * This file is defined in UnitTests.xml and called by phpunit
  * before instantiating the test suites, it must also be included
  * with phpunit parameter --bootstrap if executing single test case classes.
  *
- * For easy access to the PHPUnit and VFS framework, it is recommended to install the phpunit TYPO3 Extension
- * It does not need to be activated, nor a cli user needs to be present.
- * But it is also possible to use other installations of PHPUnit and VFS
- *
- *  * Call whole unit test suite, example:
- * - cd /var/www/t3master/foo  # Document root of TYPO3 CMS sources (location of index.php)
+ * Run whole core unit test suite, example:
+ * - cd /var/www/t3master/foo  # Document root of TYPO3 CMS instance (location of index.php)
  * - typo3/../bin/phpunit -c typo3/sysext/core/Build/UnitTests.xml
  *
- * Call single test case, example:
+ * Run single test case, example:
  * - cd /var/www/t3master/foo  # Document root of TYPO3 CMS instance (location of index.php)
- * - typo3/../bin/phpunit \
- *     --bootstrap typo3/sysext/core/Build/UnitTestsBootstrap.php \
- *     typo3/sysext/core/Tests/Uinit/DataHandling/DataHandlerTest.php
+ * - typo3/../bin/phpunit -c typo3/sysext/core/Build/UnitTests.xml
+ *     typo3/sysext/core/Tests/Unit/DataHandling/DataHandlerTest.php
  */
-class UnitTestsBootstrap
-{
-    /**
-     * Bootstraps the system for unit tests.
-     *
-     * @return void
-     */
-    public function bootstrapSystem()
-    {
-        $this->enableDisplayErrors()
-            ->checkForCliDispatch()
-            ->defineSitePath()
-            ->setTypo3Context()
-            ->createNecessaryDirectoriesInDocumentRoot()
-            ->includeAndStartCoreBootstrap()
-            ->initializeConfiguration()
-            ->finishCoreBootstrap();
-    }
 
-    /**
-     * Makes sure error messages during the tests get displayed no matter what is set in php.ini.
-     *
-     * @return UnitTestsBootstrap fluent interface
-     */
-    protected function enableDisplayErrors()
-    {
-        @ini_set('display_errors', 1);
-        return $this;
+call_user_func(function () {
+    // Ensure cli only as security measure
+    if (PHP_SAPI !== 'cli') {
+        die('This script supports command line usage only. Please check your command.');
     }
 
-    /**
-     * Checks whether the tests are run using the CLI dispatcher. If so, echos a helpful message and exits with
-     * an error code 1.
-     *
-     * @return UnitTestsBootstrap fluent interface
-     */
-    protected function checkForCliDispatch()
-    {
-        if (!defined('TYPO3_MODE')) {
-            return $this;
-        }
-
-        array_shift($_SERVER['argv']);
-        $flatArguments = implode(' ', $_SERVER['argv']);
-        echo 'Please run the unit tests using the following command:' . chr(10) .
-            sprintf('typo3/../bin/phpunit %s', $flatArguments) . chr(10) .
-            chr(10);
-
-        exit(1);
-    }
-
-    /**
-     * Defines the PATH_site and PATH_thisScript constant and sets $_SERVER['SCRIPT_NAME'].
-     *
-     * @return UnitTestsBootstrap fluent interface
-     */
-    protected function defineSitePath()
-    {
-        /** @var string */
-        define('PATH_site', $this->getWebRoot());
-        /** @var string */
-        define('PATH_thisScript', PATH_site . 'typo3/cli_dispatch.phpsh');
-        $_SERVER['SCRIPT_NAME'] = PATH_thisScript;
-
-        if (!file_exists(PATH_thisScript)) {
-            die('Unable to determine path to entry script. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
-        }
-
-        return $this;
-    }
-
-    /**
-     * Returns the absolute path the TYPO3 document root.
-     *
-     * @return string the TYPO3 document root using Unix path separators
-     */
-    protected function getWebRoot()
-    {
-        if (getenv('TYPO3_PATH_WEB')) {
-            $webRoot = getenv('TYPO3_PATH_WEB');
-        } else {
-            $webRoot = getcwd();
-        }
-        return rtrim(strtr($webRoot, '\\', '/'), '/') . '/';
-    }
-
-    /**
-     * Defines TYPO3_MODE and sets the environment variable TYPO3_CONTEXT.
-     *
-     * @return UnitTestsBootstrap fluent interface
-     */
-    protected function setTypo3Context()
-    {
-        /** @var string */
-        define('TYPO3_MODE', 'BE');
-        /** @var string */
-        putenv('TYPO3_CONTEXT=Testing');
-
-        return $this;
-    }
-
-    /**
-     * Creates the following directories in the TYPO3 document root:
-     * - typo3conf
-     * - typo3conf/ext
-     * - typo3temp
-     * - uploads
-     *
-     * @return UnitTestsBootstrap fluent interface
-     */
-    protected function createNecessaryDirectoriesInDocumentRoot()
-    {
-        $this->createDirectory(PATH_site . 'uploads');
-        $this->createDirectory(PATH_site . 'typo3temp/var/tests');
-        $this->createDirectory(PATH_site . 'typo3temp/var/transient');
-        $this->createDirectory(PATH_site . 'typo3conf/ext');
-
-        return $this;
-    }
-
-    /**
-     * Creates the directory $directory (recursively if required).
-     *
-     * If $directory already exists, this method is a no-op.
-     *
-     * @param string $directory absolute path of the directory to be created
-     * @return void
-     * @throws \RuntimeException
-     */
-    protected function createDirectory($directory)
-    {
-        if (is_dir($directory)) {
-            return;
-        }
-        @mkdir($directory, 0777, true);
-        clearstatcache();
-        if (!is_dir($directory)) {
-            throw new \RuntimeException('Directory "' . $directory . '" could not be created', 1423043755);
-        }
-    }
-
-    /**
-     * Includes the Core Bootstrap class and calls its first few functions.
-     *
-     * @return UnitTestsBootstrap fluent interface
-     */
-    protected function includeAndStartCoreBootstrap()
-    {
-        $classLoaderFilepath = __DIR__ . '/../../../../vendor/autoload.php';
-        if (!file_exists($classLoaderFilepath)) {
-            die('ClassLoader can\'t be loaded. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
-        }
-        $classLoader = require $classLoaderFilepath;
-
-        Bootstrap::getInstance()
-            ->initializeClassLoader($classLoader)
-            ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
-            ->baseSetup();
-
-        return $this;
-    }
-
-    /**
-     * Provides the default configuration in $GLOBALS['TYPO3_CONF_VARS'].
-     *
-     * @return UnitTestsBootstrap fluent interface
-     */
-    protected function initializeConfiguration()
-    {
-        $configurationManager = new \TYPO3\CMS\Core\Configuration\ConfigurationManager();
-        $GLOBALS['TYPO3_CONF_VARS'] = $configurationManager->getDefaultConfiguration();
-
-        // avoid failing tests that rely on HTTP_HOST retrieval
-        $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = '.*';
-
-        return $this;
-    }
-
-    /**
-     * Finishes the last steps of the Core Bootstrap.
-     *
-     * @return UnitTestsBootstrap fluent interface
-     */
-    protected function finishCoreBootstrap()
-    {
-        Bootstrap::getInstance()
-            ->disableCoreCache()
-            ->initializeCachingFramework()
-            ->initializePackageManagement(\TYPO3\CMS\Core\Package\UnitTestPackageManager::class)
-            ->ensureClassLoadingInformationExists();
-
-        return $this;
+    $testbase = new \TYPO3\CMS\Core\Tests\Testbase();
+    $testbase->enableDisplayErrors();
+    $testbase->defineBaseConstants();
+    $testbase->defineSitePath();
+    $testbase->defineTypo3ModeBe();
+    $testbase->setTypo3TestingContext();
+    $testbase->createDirectory(PATH_site . 'uploads');
+    $testbase->createDirectory(PATH_site . 'typo3temp/var/tests');
+    $testbase->createDirectory(PATH_site . 'typo3temp/var/transient');
+    $testbase->createDirectory(PATH_site . 'typo3conf/ext');
+
+    // Retrieve an instance of class loader and inject to core bootstrap
+    $classLoaderFilepath = __DIR__ . '/../../../../vendor/autoload.php';
+    if (!file_exists($classLoaderFilepath)) {
+        die('ClassLoader can\'t be loaded. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
     }
-}
+    $classLoader = require $classLoaderFilepath;
+    \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
+        ->initializeClassLoader($classLoader)
+        ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
+        ->baseSetup();
+
+    // Initialize default TYPO3_CONF_VARS
+    $configurationManager = new \TYPO3\CMS\Core\Configuration\ConfigurationManager();
+    $GLOBALS['TYPO3_CONF_VARS'] = $configurationManager->getDefaultConfiguration();
+    // Avoid failing tests that rely on HTTP_HOST retrieval
+    $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = '.*';
+
+    \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
+        ->disableCoreCache()
+        ->initializeCachingFramework()
+        // Set all packages to active
+        ->initializePackageManagement(\TYPO3\CMS\Core\Package\UnitTestPackageManager::class)
+        // Find all class names
+        ->ensureClassLoadingInformationExists();
+});
 
-if (PHP_SAPI !== 'cli') {
-    die('This script supports command line usage only. Please check your command.');
-}
-$bootstrap = new UnitTestsBootstrap();
-$bootstrap->bootstrapSystem();
-unset($bootstrap);
index fed1a39..82964a0 100644 (file)
@@ -106,12 +106,12 @@ class SystemEnvironmentBuilder
         define('TYPO3_URL_WIKI_OPCODECACHE', 'https://wiki.typo3.org/Opcode_Cache');
 
         // A null, a tabulator, a linefeed, a carriage return, a substitution, a CR-LF combination
-        define('NUL', chr(0));
-        define('TAB', chr(9));
-        define('LF', chr(10));
-        define('CR', chr(13));
-        define('SUB', chr(26));
-        define('CRLF', CR . LF);
+        defined('NUL') ?: define('NUL', chr(0));
+        defined('TAB') ?: define('TAB', chr(9));
+        defined('LF') ?: define('LF', chr(10));
+        defined('CR') ?: define('CR', chr(13));
+        defined('SUB') ?: define('SUB', chr(26));
+        defined('CRLF') ?: define('CRLF', CR . LF);
 
         // Security related constant: Default value of fileDenyPattern
         define('FILE_DENY_PATTERN_DEFAULT', '\\.(php[3-7]?|phpsh|phtml)(\\..*)?$|^\\.htaccess$');
@@ -120,7 +120,7 @@ class SystemEnvironmentBuilder
 
         // Operating system identifier
         // Either "WIN" or empty string
-        define('TYPO3_OS', self::getTypo3Os());
+        defined('TYPO3_OS') ?: define('TYPO3_OS', self::getTypo3Os());
 
         // Service error constants
         // General error - something went wrong
diff --git a/typo3/sysext/core/Tests/AcceptanceCoreEnvironment.php b/typo3/sysext/core/Tests/AcceptanceCoreEnvironment.php
new file mode 100644 (file)
index 0000000..d3f28b2
--- /dev/null
@@ -0,0 +1,215 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/*
+ * 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 Codeception\Events;
+use Codeception\Event\SuiteEvent;
+use Codeception\Extension;
+use TYPO3\CMS\Core\Cache\Backend\NullBackend;
+
+/**
+ * This codeception extension creates a full TYPO3 instance within
+ * typo3temp. Own acceptance test suites may extend from this class
+ * and change the properties. This can be used to not copy the whole
+ * bootstrapTypo3Environment() method but reuse it instead.
+ */
+class AcceptanceCoreEnvironment extends Extension
+{
+
+    /**
+     * Additional core extensions to load.
+     *
+     * To be used in own acceptance test suites.
+     *
+     * If a test suite needs additional core extensions, for instance as a dependency of
+     * an extension that is tested, those core extension names can be noted here and will
+     * be loaded.
+     *
+     * @var array
+     */
+    protected $coreExtensionsToLoad = [];
+
+    /**
+     * Array of test/fixture extensions paths that should be loaded for a test.
+     *
+     * To be used in own acceptance test suites.
+     *
+     * Given path is expected to be relative to your document root, example:
+     *
+     * array(
+     *   'typo3conf/ext/some_extension/Tests/Functional/Fixtures/Extensions/test_extension',
+     *   'typo3conf/ext/base_extension',
+     * );
+     *
+     * Extensions in this array are linked to the test instance, loaded
+     * and their ext_tables.sql will be applied.
+     *
+     * @var array
+     */
+    protected $testExtensionsToLoad = [];
+
+    /**
+     * Array of test/fixture folder or file paths that should be linked for a test.
+     *
+     * To be used in own acceptance test suites.
+     *
+     * array(
+     *   'link-source' => 'link-destination'
+     * );
+     *
+     * Given paths are expected to be relative to the test instance root.
+     * The array keys are the source paths and the array values are the destination
+     * paths, example:
+     *
+     * array(
+     *   'typo3/sysext/impext/Tests/Functional/Fixtures/Folders/fileadmin/user_upload' =>
+     *   'fileadmin/user_upload',
+     *   'typo3conf/ext/my_own_ext/Tests/Functional/Fixtures/Folders/uploads/tx_myownext' =>
+     *   'uploads/tx_myownext'
+     * );
+     *
+     * To be able to link from my_own_ext the extension path needs also to be registered in
+     * property $testExtensionsToLoad
+     *
+     * @var array
+     */
+    protected $pathsToLinkInTestInstance = [];
+
+    /**
+     * This configuration array is merged with TYPO3_CONF_VARS
+     * that are set in default configuration and factory configuration
+     *
+     * To be used in own acceptance test suites.
+     *
+     * @var array
+     */
+    protected $configurationToUseInTestInstance = [];
+
+    /**
+     * Array of folders that should be created inside the test instance document root.
+     *
+     * To be used in own acceptance test suites.
+     *
+     * Per default the following folder are created
+     * /fileadmin
+     * /typo3temp
+     * /typo3conf
+     * /typo3conf/ext
+     * /uploads
+     *
+     * To create additional folders add the paths to this array. Given paths are expected to be
+     * relative to the test instance root and have to begin with a slash. Example:
+     *
+     * array(
+     *   'fileadmin/user_upload'
+     * );
+     *
+     * @var array
+     */
+    protected $additionalFoldersToCreate = [];
+
+    /**
+     * Events to listen to
+     */
+    public static $events = [
+        Events::SUITE_BEFORE => 'bootstrapTypo3Environment',
+    ];
+
+    /**
+     * Handle SUITE_BEFORE event.
+     *
+     * Create a full standalone TYPO3 instance within typo3temp/var/tests/acceptance,
+     * create a database and create database schema.
+     *
+     * @param SuiteEvent $suiteEvent
+     * @throws Exception
+     */
+    public function bootstrapTypo3Environment(SuiteEvent $suiteEvent) {
+        $testbase = new Testbase();
+        $testbase->enableDisplayErrors();
+        $testbase->defineBaseConstants();
+        $testbase->defineOriginalRootPath();
+        $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance');
+        $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient');
+
+        $instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance';
+
+        $testbase = new Testbase();
+        $testbase->defineTypo3ModeBe();
+        $testbase->setTypo3TestingContext();
+        $testbase->removeOldInstanceIfExists($instancePath);
+        // Basic instance directory structure
+        $testbase->createDirectory($instancePath . '/fileadmin');
+        $testbase->createDirectory($instancePath . '/typo3temp/var/transient');
+        $testbase->createDirectory($instancePath . '/typo3temp/assets');
+        $testbase->createDirectory($instancePath . '/typo3conf/ext');
+        $testbase->createDirectory($instancePath . '/uploads');
+        // Additionally requested directories
+        foreach ($this->additionalFoldersToCreate as $directory) {
+            $testbase->createDirectory($instancePath . '/' . $directory);
+        }
+        $testbase->createLastRunTextfile($instancePath);
+        $testbase->setUpInstanceCoreLinks($instancePath);
+        $testbase->linkTestExtensionsToInstance($instancePath, $this->testExtensionsToLoad);
+        $testbase->linkPathsInTestInstance($instancePath, $this->pathsToLinkInTestInstance);
+        $localConfiguration = $testbase->getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration();
+        $originalDatabaseName = $localConfiguration['DB']['database'];
+        // Append the unique identifier to the base database name to end up with a single database per test case
+        $localConfiguration['DB']['database'] = $originalDatabaseName . '_at';
+        $testbase->testDatabaseNameIsNotTooLong($originalDatabaseName, $localConfiguration);
+        // Set some hard coded base settings for the instance. Those could be overruled by
+        // $this->configurationToUseInTestInstance if needed again.
+        $localConfiguration['BE']['debug'] = true;
+        $localConfiguration['BE']['installToolPassword'] = '$P$notnotnotnotnotnot.validvalidva';
+        $localConfiguration['SYS']['isInitialInstallationInProgress'] = false;
+        $localConfiguration['SYS']['isInitialDatabaseImportDone'] = true;
+        $localConfiguration['SYS']['displayErrors'] = '1';
+        $localConfiguration['SYS']['debugExceptionHandler'] = '';
+        $localConfiguration['SYS']['trustedHostsPattern'] = '.*';
+        $localConfiguration['SYS']['encryptionKey'] = 'iAmInvalid';
+        $localConfiguration['SYS']['setDBinit'] = 'SET SESSION sql_mode = \'STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_VALUE_ON_ZERO,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ONLY_FULL_GROUP_BY\';';
+        $localConfiguration['SYS']['caching']['cacheConfigurations']['extbase_object']['backend'] = NullBackend::class;
+        $testbase->setUpLocalConfiguration($instancePath, $localConfiguration, $this->configurationToUseInTestInstance);
+        $defaultCoreExtensionsToLoad = [
+            'core',
+            'extbase',
+            'fluid',
+            'extensionmanager',
+            'lang',
+            'setup',
+            'rsaauth',
+            'saltedpasswords',
+            'backend',
+            'belog',
+            'install',
+            't3skin',
+            'frontend',
+            'recordlist',
+            'sv',
+        ];
+        $testbase->setUpPackageStates($instancePath, $defaultCoreExtensionsToLoad, $this->coreExtensionsToLoad, $this->testExtensionsToLoad);
+        $testbase->setUpBasicTypo3Bootstrap($instancePath);
+        $testbase->setUpTestDatabase($localConfiguration['DB']['database'], $originalDatabaseName);
+        $testbase->loadExtensionTables();
+        $testbase->createDatabaseStructure();
+
+        // Unset a closure or phpunit kicks in with a 'serialization of \Closure is not allowed'
+        // Alternative solution:
+        // unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys']['extbase']);
+        $suite = $suiteEvent->getSuite();
+        $suite->setBackupGlobals(FALSE);
+    }
+
+}
index b5a26ec..16cb328 100644 (file)
@@ -14,7 +14,10 @@ namespace TYPO3\CMS\Core\Tests;
  * The TYPO3 project - inspiring people to share!
  */
 
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
 
 /**
  * Base test case class for functional tests, all TYPO3 CMS
@@ -48,6 +51,23 @@ use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response;
  */
 abstract class FunctionalTestCase extends BaseTestCase
 {
+
+    /**
+     * An unique identifier for this test case. Location of the test
+     * instance and database name depend on this. Calculated early in setUp()
+     *
+     * @var string
+     */
+    protected $identifier;
+
+    /**
+     * Absolute path to test instance document root. Depends on $identifier.
+     * Calculated early in setUp()
+     *
+     * @var string
+     */
+    protected $instancePath;
+
     /**
      * Core extensions to load.
      *
@@ -65,7 +85,7 @@ abstract class FunctionalTestCase extends BaseTestCase
      * @see FunctionalTestCaseUtility $defaultActivatedCoreExtensions
      * @var array
      */
-    protected $coreExtensionsToLoad = array();
+    protected $coreExtensionsToLoad = [];
 
     /**
      * Array of test/fixture extensions paths that should be loaded for a test.
@@ -87,7 +107,7 @@ abstract class FunctionalTestCase extends BaseTestCase
      *
      * @var array
      */
-    protected $testExtensionsToLoad = array();
+    protected $testExtensionsToLoad = [];
 
     /**
      * Array of test/fixture folder or file paths that should be linked for a test.
@@ -117,7 +137,7 @@ abstract class FunctionalTestCase extends BaseTestCase
      *
      * @var array
      */
-    protected $pathsToLinkInTestInstance = array();
+    protected $pathsToLinkInTestInstance = [];
 
     /**
      * This configuration array is merged with TYPO3_CONF_VARS
@@ -125,7 +145,7 @@ abstract class FunctionalTestCase extends BaseTestCase
      *
      * @var array
      */
-    protected $configurationToUseInTestInstance = array();
+    protected $configurationToUseInTestInstance = [];
 
     /**
      * Array of folders that should be created inside the test instance document root.
@@ -151,7 +171,7 @@ abstract class FunctionalTestCase extends BaseTestCase
      *
      * @var array
      */
-    protected $additionalFoldersToCreate = array();
+    protected $additionalFoldersToCreate = [];
 
     /**
      * The fixture which is used when initializing a backend user
@@ -161,34 +181,6 @@ abstract class FunctionalTestCase extends BaseTestCase
     protected $backendUserFixture = 'typo3/sysext/core/Tests/Functional/Fixtures/be_users.xml';
 
     /**
-     * Private utility class used in setUp() and tearDown(). Do NOT use in test cases!
-     *
-     * @var \TYPO3\CMS\Core\Tests\FunctionalTestCaseBootstrapUtility
-     */
-    private $bootstrapUtility = null;
-
-    /**
-     * Calculate a "unique" identifier for the test database and the
-     * instance patch based on the given test case class name.
-     *
-     * @return string
-     */
-    protected function getInstanceIdentifier()
-    {
-        return FunctionalTestCaseBootstrapUtility::getInstanceIdentifier(get_class($this));
-    }
-
-    /**
-     * Calculates path to TYPO3 CMS test installation for this test case.
-     *
-     * @return string
-     */
-    protected function getInstancePath()
-    {
-        return FunctionalTestCaseBootstrapUtility::getInstancePath(get_class($this));
-    }
-
-    /**
      * Set up creates a test instance and database.
      *
      * This method should be called with parent::setUp() in your test cases!
@@ -200,15 +192,64 @@ abstract class FunctionalTestCase extends BaseTestCase
         if (!defined('ORIGINAL_ROOT')) {
             $this->markTestSkipped('Functional tests must be called through phpunit on CLI');
         }
-        $this->bootstrapUtility = new FunctionalTestCaseBootstrapUtility();
-        $this->bootstrapUtility->setUp(
-            get_class($this),
-            $this->coreExtensionsToLoad,
-            $this->testExtensionsToLoad,
-            $this->pathsToLinkInTestInstance,
-            $this->configurationToUseInTestInstance,
-            $this->additionalFoldersToCreate
-        );
+
+        // Use a 7 char long hash of class name as identifier
+        $this->identifier = substr(sha1(get_class($this)), 0, 7);
+        $this->instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/functional-' . $this->identifier;
+
+        $testbase = new Testbase();
+        $testbase->defineTypo3ModeBe();
+        $testbase->setTypo3TestingContext();
+        if ($testbase->recentTestInstanceExists($this->instancePath)) {
+            // Reusing an existing instance. This typically happens for the second, third, ... test
+            // in a test case, so environment is set up only once per test case.
+            $testbase->setUpBasicTypo3Bootstrap($this->instancePath);
+            $testbase->initializeTestDatabaseAndTruncateTables();
+            $testbase->loadExtensionTables();
+        } else {
+            $testbase->removeOldInstanceIfExists($this->instancePath);
+            // Basic instance directory structure
+            $testbase->createDirectory($this->instancePath . '/fileadmin');
+            $testbase->createDirectory($this->instancePath . '/typo3temp/var/transient');
+            $testbase->createDirectory($this->instancePath . '/typo3temp/assets');
+            $testbase->createDirectory($this->instancePath . '/typo3conf/ext');
+            $testbase->createDirectory($this->instancePath . '/uploads');
+            // Additionally requested directories
+            foreach ($this->additionalFoldersToCreate as $directory) {
+                $testbase->createDirectory($this->instancePath . '/' . $directory);
+            }
+            $testbase->createLastRunTextfile($this->instancePath);
+            $testbase->setUpInstanceCoreLinks($this->instancePath);
+            $testbase->linkTestExtensionsToInstance($this->instancePath, $this->testExtensionsToLoad);
+            $testbase->linkPathsInTestInstance($this->instancePath, $this->pathsToLinkInTestInstance);
+            $localConfiguration = $testbase->getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration();
+            $originalDatabaseName = $localConfiguration['DB']['database'];
+            // Append the unique identifier to the base database name to end up with a single database per test case
+            $localConfiguration['DB']['database'] = $originalDatabaseName . '_ft' . $this->identifier;
+            $testbase->testDatabaseNameIsNotTooLong($originalDatabaseName, $localConfiguration);
+            // Set some hard coded base settings for the instance. Those could be overruled by
+            // $this->configurationToUseInTestInstance if needed again.
+            $localConfiguration['SYS']['isInitialInstallationInProgress'] = false;
+            $localConfiguration['SYS']['isInitialDatabaseImportDone'] = true;
+            $localConfiguration['SYS']['displayErrors'] = '1';
+            $localConfiguration['SYS']['debugExceptionHandler'] = '';
+            $localConfiguration['SYS']['trustedHostsPattern'] = '.*';
+            $localConfiguration['SYS']['setDBinit'] = 'SET SESSION sql_mode = \'STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_VALUE_ON_ZERO,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ONLY_FULL_GROUP_BY\';';
+            $testbase->setUpLocalConfiguration($this->instancePath, $localConfiguration, $this->configurationToUseInTestInstance);
+            $defaultCoreExtensionsToLoad = [
+                'core',
+                'backend',
+                'frontend',
+                'lang',
+                'extbase',
+                'install',
+            ];
+            $testbase->setUpPackageStates($this->instancePath, $defaultCoreExtensionsToLoad, $this->coreExtensionsToLoad, $this->testExtensionsToLoad);
+            $testbase->setUpBasicTypo3Bootstrap($this->instancePath);
+            $testbase->setUpTestDatabase($localConfiguration['DB']['database'], $originalDatabaseName);
+            $testbase->loadExtensionTables();
+            $testbase->createDatabaseStructure();
+        }
     }
 
     /**
@@ -228,7 +269,7 @@ abstract class FunctionalTestCase extends BaseTestCase
      * Initialize backend user
      *
      * @param int $userUid uid of the user we want to initialize. This user must exist in the fixture file
-     * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
+     * @return BackendUserAuthentication
      * @throws Exception
      */
     protected function setUpBackendUserFromFixture($userUid)
@@ -237,8 +278,8 @@ abstract class FunctionalTestCase extends BaseTestCase
         $database = $this->getDatabaseConnection();
         $userRow = $database->exec_SELECTgetSingleRow('*', 'be_users', 'uid = ' . (int)$userUid);
 
-        /** @var $backendUser \TYPO3\CMS\Core\Authentication\BackendUserAuthentication */
-        $backendUser = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Authentication\BackendUserAuthentication::class);
+        /** @var $backendUser BackendUserAuthentication */
+        $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
         $sessionId = $backendUser->createSessionId();
         $_COOKIE['be_typo_user'] = $sessionId;
         $backendUser->id = $sessionId;
@@ -381,7 +422,7 @@ abstract class FunctionalTestCase extends BaseTestCase
         }
 
         $arguments = array(
-            'documentRoot' => $this->getInstancePath(),
+            'documentRoot' => $this->instancePath,
             'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter,
         );
 
@@ -408,4 +449,5 @@ abstract class FunctionalTestCase extends BaseTestCase
         $response = new Response($result['status'], $result['content'], $result['error']);
         return $response;
     }
+
 }
diff --git a/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php b/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php
deleted file mode 100644 (file)
index e885024..0000000
+++ /dev/null
@@ -1,790 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests;
-
-/*
- * 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!
- */
-
-/**
- * Utility class to set up and bootstrap TYPO3 CMS for functional tests
- */
-class FunctionalTestCaseBootstrapUtility
-{
-    /**
-     * @var string Identifier calculated from test case class
-     */
-    protected $identifier;
-
-    /**
-     * @var string Absolute path to test instance document root
-     */
-    protected $instancePath;
-
-    /**
-     * @var string Name of test database
-     */
-    protected $databaseName;
-
-    /**
-     * @var string Name of original database
-     */
-    protected $originalDatabaseName;
-
-    /**
-     * @var array These extensions are always loaded
-     */
-    protected $defaultActivatedCoreExtensions = array(
-        'core',
-        'backend',
-        'frontend',
-        'lang',
-        'extbase',
-        'install',
-    );
-
-    /**
-     * @var array These folder are always created
-     */
-    protected $defaultFoldersToCreate = array(
-        '',
-        '/fileadmin',
-        '/typo3temp',
-        '/typo3temp/var',
-        '/typo3temp/var/transient',
-        '/typo3temp/assets',
-        '/typo3conf',
-        '/typo3conf/ext',
-        '/uploads'
-    );
-
-    /**
-     * Calculate a "unique" identifier for the test database and the
-     * instance patch based on the given test case class name.
-     *
-     * @param string $testCaseClassName Name of test case class
-     * @return string
-     */
-    public static function getInstanceIdentifier($testCaseClassName)
-    {
-        // 7 characters of sha1 should be enough for a unique identification
-        return substr(sha1($testCaseClassName), 0, 7);
-    }
-
-    /**
-     * Calculates path to TYPO3 CMS test installation for this test case.
-     *
-     * @param string $testCaseClassName Name of test case class
-     * @return string
-     */
-    public static function getInstancePath($testCaseClassName)
-    {
-        return ORIGINAL_ROOT . 'typo3temp/var/tests/functional-' . static::getInstanceIdentifier($testCaseClassName);
-    }
-
-    /**
-     * Set up creates a test instance and database.
-     *
-     * @param string $testCaseClassName Name of test case class
-     * @param array $coreExtensionsToLoad Array of core extensions to load
-     * @param array $testExtensionsToLoad Array of test extensions to load
-     * @param array $pathsToLinkInTestInstance Array of source => destination path pairs to be linked
-     * @param array $configurationToUse Array of TYPO3_CONF_VARS that need to be overridden
-     * @param array $additionalFoldersToCreate Array of folder paths to be created
-     * @return string Path to TYPO3 CMS test installation for this test case
-     */
-    public function setUp(
-        $testCaseClassName,
-        array $coreExtensionsToLoad,
-        array $testExtensionsToLoad,
-        array $pathsToLinkInTestInstance,
-        array $configurationToUse,
-        array $additionalFoldersToCreate
-    ) {
-        $this->setUpIdentifier($testCaseClassName);
-        $this->setUpInstancePath($testCaseClassName);
-        if ($this->recentTestInstanceExists()) {
-            $this->setUpBasicTypo3Bootstrap();
-            $this->initializeTestDatabase();
-            \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables();
-        } else {
-            $this->removeOldInstanceIfExists();
-            $this->setUpInstanceDirectories($additionalFoldersToCreate);
-            $this->setUpInstanceCoreLinks();
-            $this->linkTestExtensionsToInstance($testExtensionsToLoad);
-            $this->linkPathsInTestInstance($pathsToLinkInTestInstance);
-            $this->setUpLocalConfiguration($configurationToUse);
-            $this->setUpPackageStates($coreExtensionsToLoad, $testExtensionsToLoad);
-            $this->setUpBasicTypo3Bootstrap();
-            $this->setUpTestDatabase();
-            \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables();
-            $this->createDatabaseStructure();
-        }
-
-        return $this->instancePath;
-    }
-
-    /**
-     * Checks whether the current test instance exists and is younger than
-     * some minutes.
-     *
-     * @return bool
-     */
-    protected function recentTestInstanceExists()
-    {
-        if (@file_get_contents($this->instancePath . '/last_run.txt') <= (time() - 300)) {
-            return false;
-        } else {
-            // Test instance exists and is pretty young -> re-use
-            return true;
-        }
-    }
-
-    /**
-     * Calculate a "unique" identifier for the test database and the
-     * instance patch based on the given test case class name.
-     *
-     * As a result, the database name will be identical between different
-     * test runs, but different between each test case.
-     *
-     * @param string $testCaseClassName Name of test case class
-     * @return void
-     */
-    protected function setUpIdentifier($testCaseClassName)
-    {
-        $this->identifier = static::getInstanceIdentifier($testCaseClassName);
-    }
-
-    /**
-     * Calculates path to TYPO3 CMS test installation for this test case.
-     *
-     * @param string $testCaseClassName Name of test case class
-     * @return void
-     */
-    protected function setUpInstancePath($testCaseClassName)
-    {
-        $this->instancePath = static::getInstancePath($testCaseClassName);
-    }
-
-    /**
-     * Remove test instance folder structure in setUp() if it exists.
-     * This may happen if a functional test before threw a fatal.
-     *
-     * @return void
-     */
-    protected function removeOldInstanceIfExists()
-    {
-        if (is_dir($this->instancePath)) {
-            $this->removeInstance();
-        }
-    }
-
-    /**
-     * Create folder structure of test instance.
-     *
-     * @param array $additionalFoldersToCreate Array of additional folders to be created
-     * @throws Exception
-     * @return void
-     */
-    protected function setUpInstanceDirectories(array $additionalFoldersToCreate = array())
-    {
-        $foldersToCreate = array_merge($this->defaultFoldersToCreate, $additionalFoldersToCreate);
-        foreach ($foldersToCreate as $folder) {
-            $success = mkdir($this->instancePath . $folder);
-            if (!$success) {
-                throw new Exception(
-                    'Creating directory failed: ' . $this->instancePath . $folder,
-                    1376657189
-                );
-            }
-        }
-
-        // Store the time we created this directory
-        file_put_contents($this->instancePath . '/last_run.txt', time());
-    }
-
-    /**
-     * Link TYPO3 CMS core from "parent" instance.
-     *
-     * @throws Exception
-     * @return void
-     */
-    protected function setUpInstanceCoreLinks()
-    {
-        $linksToSet = array(
-            ORIGINAL_ROOT . 'typo3' => $this->instancePath . '/typo3',
-            ORIGINAL_ROOT . 'index.php' => $this->instancePath . '/index.php'
-        );
-        foreach ($linksToSet as $from => $to) {
-            $success = symlink($from, $to);
-            if (!$success) {
-                throw new Exception(
-                    'Creating link failed: from ' . $from . ' to: ' . $to,
-                    1376657199
-                );
-            }
-        }
-    }
-
-    /**
-     * Link test extensions to the typo3conf/ext folder of the instance.
-     *
-     * @param array $extensionPaths Contains paths to extensions relative to document root
-     * @throws Exception
-     * @return void
-     */
-    protected function linkTestExtensionsToInstance(array $extensionPaths)
-    {
-        foreach ($extensionPaths as $extensionPath) {
-            $absoluteExtensionPath = ORIGINAL_ROOT . $extensionPath;
-            if (!is_dir($absoluteExtensionPath)) {
-                throw new Exception(
-                    'Test extension path ' . $absoluteExtensionPath . ' not found',
-                    1376745645
-                );
-            }
-            $destinationPath = $this->instancePath . '/typo3conf/ext/' . basename($absoluteExtensionPath);
-            $success = symlink($absoluteExtensionPath, $destinationPath);
-            if (!$success) {
-                throw new Exception(
-                    'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
-                    1376657142
-                );
-            }
-        }
-    }
-
-    /**
-     * Link paths inside the test instance, e.g. from a fixture fileadmin subfolder to the
-     * test instance fileadmin folder
-     *
-     * @param array $pathsToLinkInTestInstance Contains paths as array of source => destination in key => value pairs of folders relative to test instance root
-     * @throws \TYPO3\CMS\Core\Tests\Exception if a source path could not be found
-     * @throws \TYPO3\CMS\Core\Tests\Exception on failing creating the symlink
-     * @return void
-     * @see \TYPO3\CMS\Core\Tests\FunctionalTestCase::$pathsToLinkInTestInstance
-     */
-    protected function linkPathsInTestInstance(array $pathsToLinkInTestInstance)
-    {
-        foreach ($pathsToLinkInTestInstance as $sourcePathToLinkInTestInstance => $destinationPathToLinkInTestInstance) {
-            $sourcePath = $this->instancePath . '/' . ltrim($sourcePathToLinkInTestInstance, '/');
-            if (!file_exists($sourcePath)) {
-                throw new Exception(
-                    'Path ' . $sourcePath . ' not found',
-                    1376745645
-                );
-            }
-            $destinationPath = $this->instancePath . '/' . ltrim($destinationPathToLinkInTestInstance, '/');
-            $success = symlink($sourcePath, $destinationPath);
-            if (!$success) {
-                throw new Exception(
-                    'Can not link the path ' . $sourcePath . ' to ' . $destinationPath,
-                    1389969623
-                );
-            }
-        }
-    }
-
-    /**
-     * Create LocalConfiguration.php file in the test instance
-     *
-     * @param array $configurationToMerge
-     * @throws Exception
-     * @return void
-     */
-    protected function setUpLocalConfiguration(array $configurationToMerge)
-    {
-        $databaseName = trim(getenv('typo3DatabaseName'));
-        $databaseHost = trim(getenv('typo3DatabaseHost'));
-        $databaseUsername = trim(getenv('typo3DatabaseUsername'));
-        $databasePassword = trim(getenv('typo3DatabasePassword'));
-        $databasePort = trim(getenv('typo3DatabasePort'));
-        $databaseSocket = trim(getenv('typo3DatabaseSocket'));
-        if ($databaseName || $databaseHost || $databaseUsername || $databasePassword || $databasePort || $databaseSocket) {
-            // Try to get database credentials from environment variables first
-            $originalConfigurationArray = array(
-                'DB' => array(),
-            );
-            if ($databaseName) {
-                $originalConfigurationArray['DB']['database'] = $databaseName;
-            }
-            if ($databaseHost) {
-                $originalConfigurationArray['DB']['host'] = $databaseHost;
-            }
-            if ($databaseUsername) {
-                $originalConfigurationArray['DB']['username'] = $databaseUsername;
-            }
-            if ($databasePassword) {
-                $originalConfigurationArray['DB']['password'] = $databasePassword;
-            }
-            if ($databasePort) {
-                $originalConfigurationArray['DB']['port'] = $databasePort;
-            }
-            if ($databaseSocket) {
-                $originalConfigurationArray['DB']['socket'] = $databaseSocket;
-            }
-        } elseif (file_exists(ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php')) {
-            // See if a LocalConfiguration file exists in "parent" instance to get db credentials from
-            $originalConfigurationArray = require ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php';
-        } else {
-            throw new Exception(
-                'Database credentials for functional tests are neither set through environment'
-                . ' variables, and can not be found in an existing LocalConfiguration file',
-                1397406356
-            );
-        }
-
-        // Base of final LocalConfiguration is core factory configuration
-        $finalConfigurationArray = require ORIGINAL_ROOT . 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
-
-        $this->mergeRecursiveWithOverrule($finalConfigurationArray, require ORIGINAL_ROOT . 'typo3/sysext/core/Build/Configuration/FunctionalTestsConfiguration.php');
-        $this->mergeRecursiveWithOverrule($finalConfigurationArray, $configurationToMerge);
-        $finalConfigurationArray['DB'] = $originalConfigurationArray['DB'];
-        // Calculate and set new database name
-        $this->originalDatabaseName = $originalConfigurationArray['DB']['database'];
-        $this->databaseName = $this->originalDatabaseName . '_ft' . $this->identifier;
-
-        // Maximum database name length for mysql is 64 characters
-        if (strlen($this->databaseName) > 64) {
-            $maximumOriginalDatabaseName = 64 - strlen('_ft' . $this->identifier);
-            throw new Exception(
-                'The name of the database that is used for the functional test (' . $this->databaseName . ')' .
-                ' exceeds the maximum length of 64 character allowed by MySQL. You have to shorten your' .
-                ' original database name to ' . $maximumOriginalDatabaseName . ' characters',
-                1377600104
-            );
-        }
-
-        $finalConfigurationArray['DB']['database'] = $this->databaseName;
-
-        $result = $this->writeFile(
-            $this->instancePath . '/typo3conf/LocalConfiguration.php',
-            '<?php' . chr(10) .
-            'return ' .
-            $this->arrayExport(
-                $finalConfigurationArray
-            ) .
-            ';' . chr(10) .
-            '?>'
-        );
-        if (!$result) {
-            throw new Exception('Can not write local configuration', 1376657277);
-        }
-    }
-
-    /**
-     * Compile typo3conf/PackageStates.php containing default packages like core,
-     * a functional test specific list of additional core extensions, and a list of
-     * test extensions.
-     *
-     * @param array $coreExtensionsToLoad Additional core extensions to load
-     * @param array $testExtensionPaths Paths to extensions relative to document root
-     * @throws Exception
-     * @TODO Figure out what the intention of the upper arguments is
-     */
-    protected function setUpPackageStates(array $coreExtensionsToLoad, array $testExtensionPaths)
-    {
-        $packageStates = array(
-            'packages' => array(),
-            'version' => 4,
-        );
-
-        // Register default list of extensions and set active
-        foreach ($this->defaultActivatedCoreExtensions as $extensionName) {
-            $packageStates['packages'][$extensionName] = array(
-                'state' => 'active',
-                'packagePath' => 'typo3/sysext/' . $extensionName . '/',
-                'classesPath' => 'Classes/',
-            );
-        }
-
-        // Register additional core extensions and set active
-        foreach ($coreExtensionsToLoad as $extensionName) {
-            if (isset($packageSates['packages'][$extensionName])) {
-                throw new Exception(
-                    $extensionName . ' is already registered as default core extension to load, no need to load it explicitly',
-                    1390913893
-                );
-            }
-            $packageStates['packages'][$extensionName] = array(
-                'state' => 'active',
-                'packagePath' => 'typo3/sysext/' . $extensionName . '/',
-                'classesPath' => 'Classes/',
-            );
-        }
-
-        // Activate test extensions that have been symlinked before
-        foreach ($testExtensionPaths as $extensionPath) {
-            $extensionName = basename($extensionPath);
-            if (isset($packageSates['packages'][$extensionName])) {
-                throw new Exception(
-                    $extensionName . ' is already registered as extension to load, no need to load it explicitly',
-                    1390913894
-                );
-            }
-            $packageStates['packages'][$extensionName] = array(
-                'state' => 'active',
-                'packagePath' => 'typo3conf/ext/' . $extensionName . '/',
-                'classesPath' => 'Classes/',
-            );
-        }
-
-        $result = $this->writeFile(
-            $this->instancePath . '/typo3conf/PackageStates.php',
-            '<?php' . chr(10) .
-            'return ' .
-            $this->arrayExport(
-                $packageStates
-            ) .
-            ';' . chr(10) .
-            '?>'
-        );
-        if (!$result) {
-            throw new Exception('Can not write PackageStates', 1381612729);
-        }
-    }
-
-    /**
-     * Bootstrap basic TYPO3
-     *
-     * @return void
-     */
-    protected function setUpBasicTypo3Bootstrap()
-    {
-        $_SERVER['PWD'] = $this->instancePath;
-        $_SERVER['argv'][0] = 'index.php';
-
-        define('TYPO3_MODE', 'BE');
-
-        $classLoader = require rtrim(realpath($this->instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php';
-        \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
-            ->initializeClassLoader($classLoader)
-            ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
-            ->baseSetup('')
-            ->loadConfigurationAndInitialize(true)
-            ->loadTypo3LoadedExtAndExtLocalconf(true)
-            ->setFinalCachingFrameworkCacheConfiguration()
-            ->defineLoggingAndExceptionConstants()
-            ->unsetReservedGlobalVariables();
-    }
-
-    /**
-     * Populate $GLOBALS['TYPO3_DB'] and create test database
-     *
-     * @throws \TYPO3\CMS\Core\Tests\Exception
-     * @return void
-     */
-    protected function setUpTestDatabase()
-    {
-        \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->initializeTypo3DbGlobal();
-        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
-        $database = $GLOBALS['TYPO3_DB'];
-        if (!$database->sql_pconnect()) {
-            throw new Exception(
-                'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
-                . ' connection to the database was attempted to be established!',
-                1377620117
-            );
-        }
-
-        // Drop database in case a previous test had a fatal and did not clean up properly
-        $database->admin_query('DROP DATABASE IF EXISTS `' . $this->databaseName . '`');
-        $createDatabaseResult = $database->admin_query('CREATE DATABASE `' . $this->databaseName . '` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci');
-        if (!$createDatabaseResult) {
-            $user = $GLOBALS['TYPO3_CONF_VARS']['DB']['username'];
-            $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['host'];
-            throw new Exception(
-                'Unable to create database with name ' . $this->databaseName . '. This is probably a permission problem.'
-                . ' For this instance this could be fixed executing'
-                . ' "GRANT ALL ON `' . $this->originalDatabaseName . '_ft%`.* TO `' . $user . '`@`' . $host . '`;"',
-                1376579070
-            );
-        }
-        $database->setDatabaseName($this->databaseName);
-        // On windows, this still works, but throws a warning, which we need to discard.
-        @$database->sql_select_db();
-    }
-
-    /**
-     * Populate $GLOBALS['TYPO3_DB'] reusing an existing database with
-     * all tables truncated.
-     *
-     * @throws \TYPO3\CMS\Core\Tests\Exception
-     * @return void
-     */
-    protected function initializeTestDatabase()
-    {
-        \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->initializeTypo3DbGlobal();
-        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
-        $database = $GLOBALS['TYPO3_DB'];
-        if (!$database->sql_pconnect()) {
-            throw new Exception(
-                'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
-                . ' connection to the database was attempted to be established!',
-                1377620117
-            );
-        }
-        $this->databaseName = $GLOBALS['TYPO3_CONF_VARS']['DB']['database'];
-        $database->setDatabaseName($this->databaseName);
-        $database->sql_select_db();
-        foreach ($database->admin_get_tables() as $table) {
-            $database->admin_query('TRUNCATE ' . $table['Name'] . ';');
-        }
-    }
-
-    /**
-     * Create tables and import static rows
-     *
-     * @return void
-     */
-    protected function createDatabaseStructure()
-    {
-        /** @var \TYPO3\CMS\Install\Service\SqlSchemaMigrationService $schemaMigrationService */
-        $schemaMigrationService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\SqlSchemaMigrationService::class);
-        /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
-        $objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
-        /** @var \TYPO3\CMS\Install\Service\SqlExpectedSchemaService $expectedSchemaService */
-        $expectedSchemaService = $objectManager->get(\TYPO3\CMS\Install\Service\SqlExpectedSchemaService::class);
-
-        // Raw concatenated ext_tables.sql and friends string
-        $expectedSchemaString = $expectedSchemaService->getTablesDefinitionString(true);
-        $statements = $schemaMigrationService->getStatementArray($expectedSchemaString, true);
-        list($_, $insertCount) = $schemaMigrationService->getCreateTables($statements, true);
-
-        $fieldDefinitionsFile = $schemaMigrationService->getFieldDefinitions_fileContent($expectedSchemaString);
-        $fieldDefinitionsDatabase = $schemaMigrationService->getFieldDefinitions_database();
-        $difference = $schemaMigrationService->getDatabaseExtra($fieldDefinitionsFile, $fieldDefinitionsDatabase);
-        $updateStatements = $schemaMigrationService->getUpdateSuggestions($difference);
-
-        $schemaMigrationService->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
-        $schemaMigrationService->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
-        $schemaMigrationService->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
-
-        foreach ($insertCount as $table => $count) {
-            $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
-            foreach ($insertStatements as $insertQuery) {
-                $insertQuery = rtrim($insertQuery, ';');
-                /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
-                $database = $GLOBALS['TYPO3_DB'];
-                $database->admin_query($insertQuery);
-            }
-        }
-    }
-
-    /**
-     * Drop test database.
-     *
-     * @throws \TYPO3\CMS\Core\Tests\Exception
-     * @return void
-     */
-    protected function tearDownTestDatabase()
-    {
-        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
-        $database = $GLOBALS['TYPO3_DB'];
-        $result = $database->admin_query('DROP DATABASE `' . $this->databaseName . '`');
-        if (!$result) {
-            throw new Exception(
-                'Dropping test database ' . $this->databaseName . ' failed',
-                1376583188
-            );
-        }
-    }
-
-    /**
-     * Removes instance directories and files
-     *
-     * @throws \TYPO3\CMS\Core\Tests\Exception
-     * @return void
-     */
-    protected function removeInstance()
-    {
-        $success = $this->rmdir($this->instancePath, true);
-        if (!$success) {
-            throw new Exception(
-                'Can not remove folder: ' . $this->instancePath,
-                1376657210
-            );
-        }
-    }
-
-    /**
-     * COPIED FROM GeneralUtility
-     *
-     * Wrapper function for rmdir, allowing recursive deletion of folders and files
-     *
-     * @param string $path Absolute path to folder, see PHP rmdir() function. Removes trailing slash internally.
-     * @param bool $removeNonEmpty Allow deletion of non-empty directories
-     * @return bool TRUE if @rmdir went well!
-     */
-    protected function rmdir($path, $removeNonEmpty = false)
-    {
-        $OK = false;
-        // Remove trailing slash
-        $path = preg_replace('|/$|', '', $path);
-        if (file_exists($path)) {
-            $OK = true;
-            if (!is_link($path) && is_dir($path)) {
-                if ($removeNonEmpty == true && ($handle = opendir($path))) {
-                    while ($OK && false !== ($file = readdir($handle))) {
-                        if ($file == '.' || $file == '..') {
-                            continue;
-                        }
-                        $OK = $this->rmdir($path . '/' . $file, $removeNonEmpty);
-                    }
-                    closedir($handle);
-                }
-                if ($OK) {
-                    $OK = @rmdir($path);
-                }
-            } else {
-                // If $path is a symlink to a folder we need rmdir() on Windows systems
-                if (!stristr(PHP_OS, 'darwin') && stristr(PHP_OS, 'win') && is_link($path) && is_dir($path . '/')) {
-                    $OK = rmdir($path);
-                } else {
-                    $OK = unlink($path);
-                }
-            }
-            clearstatcache();
-        } elseif (is_link($path)) {
-            $OK = unlink($path);
-            clearstatcache();
-        }
-        return $OK;
-    }
-
-    /**
-     * COPIED FROM GeneralUtility
-     *
-     * Writes $content to the file $file
-     *
-     * @param string $file Filepath to write to
-     * @param string $content Content to write
-     * @return bool TRUE if the file was successfully opened and written to.
-     */
-    protected function writeFile($file, $content)
-    {
-        if ($fd = fopen($file, 'wb')) {
-            $res = fwrite($fd, $content);
-            fclose($fd);
-            if ($res === false) {
-                return false;
-            }
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * COPIED FROM ArrayUtility
-     *
-     * Exports an array as string.
-     * Similar to var_export(), but representation follows the TYPO3 core CGL.
-     *
-     * See unit tests for detailed examples
-     *
-     * @param array $array Array to export
-     * @param int $level Internal level used for recursion, do *not* set from outside!
-     * @return string String representation of array
-     * @throws \RuntimeException
-     */
-    protected function arrayExport(array $array = array(), $level = 0)
-    {
-        $lines = 'array(' . chr(10);
-        $level++;
-        $writeKeyIndex = false;
-        $expectedKeyIndex = 0;
-        foreach ($array as $key => $value) {
-            if ($key === $expectedKeyIndex) {
-                $expectedKeyIndex++;
-            } else {
-                // Found a non integer or non consecutive key, so we can break here
-                $writeKeyIndex = true;
-                break;
-            }
-        }
-        foreach ($array as $key => $value) {
-            // Indention
-            $lines .= str_repeat(chr(9), $level);
-            if ($writeKeyIndex) {
-                // Numeric / string keys
-                $lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => ';
-            }
-            if (is_array($value)) {
-                if (!empty($value)) {
-                    $lines .= $this->arrayExport($value, $level);
-                } else {
-                    $lines .= 'array(),' . chr(10);
-                }
-            } elseif (is_int($value) || is_float($value)) {
-                $lines .= $value . ',' . chr(10);
-            } elseif (is_null($value)) {
-                $lines .= 'NULL' . ',' . chr(10);
-            } elseif (is_bool($value)) {
-                $lines .= $value ? 'TRUE' : 'FALSE';
-                $lines .= ',' . chr(10);
-            } elseif (is_string($value)) {
-                // Quote \ to \\
-                $stringContent = str_replace('\\', '\\\\', $value);
-                // Quote ' to \'
-                $stringContent = str_replace('\'', '\\\'', $stringContent);
-                $lines .= '\'' . $stringContent . '\'' . ',' . chr(10);
-            } else {
-                throw new \RuntimeException('Objects are not supported', 1342294986);
-            }
-        }
-        $lines .= str_repeat(chr(9), ($level - 1)) . ')' . ($level - 1 == 0 ? '' : ',' . chr(10));
-        return $lines;
-    }
-
-    /**
-     * COPIED FROM ArrayUtility
-     *
-     * Merges two arrays recursively and "binary safe" (integer keys are
-     * overridden as well), overruling similar values in the original array
-     * with the values of the overrule array.
-     * In case of identical keys, ie. keeping the values of the overrule array.
-     *
-     * This method takes the original array by reference for speed optimization with large arrays
-     *
-     * The differences to the existing PHP function array_merge_recursive() are:
-     *  * Keys of the original array can be unset via the overrule array. ($enableUnsetFeature)
-     *  * Much more control over what is actually merged. ($addKeys, $includeEmptyValues)
-     *  * Elements or the original array get overwritten if the same key is present in the overrule array.
-     *
-     * @param array $original Original array. It will be *modified* by this method and contains the result afterwards!
-     * @param array $overrule Overrule array, overruling the original array
-     * @param bool $addKeys If set to FALSE, keys that are NOT found in $original will not be set. Thus only existing value can/will be overruled from overrule array.
-     * @param bool $includeEmptyValues If set, values from $overrule will overrule if they are empty or zero.
-     * @param bool $enableUnsetFeature If set, special values "__UNSET" can be used in the overrule array in order to unset array keys in the original array.
-     * @return void
-     */
-    protected function mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true)
-    {
-        foreach ($overrule as $key => $_) {
-            if ($enableUnsetFeature && $overrule[$key] === '__UNSET') {
-                unset($original[$key]);
-                continue;
-            }
-            if (isset($original[$key]) && is_array($original[$key])) {
-                if (is_array($overrule[$key])) {
-                    self::mergeRecursiveWithOverrule($original[$key], $overrule[$key], $addKeys, $includeEmptyValues, $enableUnsetFeature);
-                }
-            } elseif (
-                ($addKeys || isset($original[$key])) &&
-                ($includeEmptyValues || $overrule[$key])
-            ) {
-                $original[$key] = $overrule[$key];
-            }
-        }
-        // This line is kept for backward compatibility reasons.
-        reset($original);
-    }
-}
diff --git a/typo3/sysext/core/Tests/Testbase.php b/typo3/sysext/core/Tests/Testbase.php
new file mode 100644 (file)
index 0000000..626b198
--- /dev/null
@@ -0,0 +1,654 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/*
+ * 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\Core\Bootstrap;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+use TYPO3\CMS\Install\Service\SqlExpectedSchemaService;
+use TYPO3\CMS\Install\Service\SqlSchemaMigrationService;
+
+/**
+ * This is a helper class used by unit, functional and acceptance test
+ * environment builders.
+ * It contains methods to create test environments.
+ *
+ * This class is for internal use only and may change wihtout further notice.
+ *
+ * Use the classes "UnitTestCase", "FunctionalTestCase" or "AcceptanceCoreEnvironment"
+ * to indirectly benefit from this class in own extensions.
+ */
+class Testbase {
+
+    /**
+     * Makes sure error messages during the tests get displayed no matter what is set in php.ini.
+     *
+     * @return void
+     */
+    public function enableDisplayErrors()
+    {
+        @ini_set('display_errors', 1);
+    }
+
+    /**
+     * Defines a list of basic constants that are used by GeneralUtility and other
+     * helpers during tests setup. Those are sanitized in SystemEnvironmentBuilder
+     * to be not defined again.
+     *
+     * @return void
+     * @see SystemEnvironmentBuilder::defineBaseConstants()
+     */
+    public function defineBaseConstants()
+    {
+        // A null, a tabulator, a linefeed, a carriage return, a substitution, a CR-LF combination
+        defined('NUL') ?: define('NUL', chr(0));
+        defined('TAB') ?: define('TAB', chr(9));
+        defined('LF') ?: define('LF', chr(10));
+        defined('CR') ?: define('CR', chr(13));
+        defined('SUB') ?: define('SUB', chr(26));
+        defined('CRLF') ?: define('CRLF', CR . LF);
+
+        if (!defined('TYPO3_OS')) {
+            // Operating system identifier
+            // Either "WIN" or empty string
+            $typoOs = '';
+            if (!stristr(PHP_OS, 'darwin') && !stristr(PHP_OS, 'cygwin') && stristr(PHP_OS, 'win')) {
+                $typoOs = 'WIN';
+            }
+            define('TYPO3_OS', $typoOs);
+        }
+    }
+
+    /**
+     * Defines the PATH_site and PATH_thisScript constant and sets $_SERVER['SCRIPT_NAME'].
+     * For unit tests only
+     *
+     * @return void
+     */
+    public function defineSitePath()
+    {
+        /** @var string */
+        define('PATH_site', $this->getWebRoot());
+        /** @var string */
+        define('PATH_thisScript', PATH_site . 'typo3/cli_dispatch.phpsh');
+        $_SERVER['SCRIPT_NAME'] = PATH_thisScript;
+
+        if (!file_exists(PATH_thisScript)) {
+            die('Unable to determine path to entry script. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
+        }
+    }
+
+    /**
+     * Defines the constant ORIGINAL_ROOT for the path to the original TYPO3 document root.
+     * For functional / acceptance tests only
+     * If ORIGINAL_ROOT already is defined, this method is a no-op.
+     *
+     * @return void
+     */
+    public function defineOriginalRootPath()
+    {
+        if (!defined('ORIGINAL_ROOT')) {
+            /** @var string */
+            define('ORIGINAL_ROOT', $this->getWebRoot());
+        }
+
+        if (!file_exists(ORIGINAL_ROOT . 'typo3/cli_dispatch.phpsh')) {
+            die('Unable to determine path to entry script. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
+        }
+    }
+
+    /**
+     * Define TYPO3_MODE to BE
+     *
+     * @return void
+     */
+    public function defineTypo3ModeBe()
+    {
+        /** @var string */
+        define('TYPO3_MODE', 'BE');
+    }
+
+    /**
+     * Sets the environment variable TYPO3_CONTEXT to testing.
+     *
+     * @return void
+     */
+    public function setTypo3TestingContext()
+    {
+        /** @var string */
+        putenv('TYPO3_CONTEXT=Testing');
+    }
+
+    /**
+     * Creates directories, recursively if required.
+     *
+     * @param string $directory Absolute path to directories to create
+     * @return void
+     * @throws Exception
+     */
+    public function createDirectory($directory)
+    {
+        if (is_dir($directory)) {
+            return;
+        }
+        @mkdir($directory, 0777, true);
+        clearstatcache();
+        if (!is_dir($directory)) {
+            throw new Exception('Directory "' . $directory . '" could not be created', 1404038665);
+        }
+    }
+
+    /**
+     * Checks whether given test instance exists in path and is younger than some minutes.
+     * Used in functional tests
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @return bool
+     */
+    public function recentTestInstanceExists($instancePath)
+    {
+        if (@file_get_contents($instancePath . '/last_run.txt') <= (time() - 300)) {
+            return false;
+        } else {
+            // Test instance exists and is pretty young -> re-use
+            return true;
+        }
+    }
+
+    /**
+     * Remove test instance folder structure if it exists.
+     * This may happen if a functional test before threw a fatal or is too old
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @return void
+     * @throws Exception
+     */
+    public function removeOldInstanceIfExists($instancePath)
+    {
+        if (is_dir($instancePath)) {
+            $success = GeneralUtility::rmdir($instancePath, true);
+            if (!$success) {
+                throw new Exception(
+                    'Can not remove folder: ' . $instancePath,
+                    1376657210
+                );
+            }
+        }
+    }
+
+    /**
+     * Create last_run.txt file within instance path containing timestamp of "now".
+     * Used in functional tests to reuse an instance for multiple tests in one test case.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @return void
+     */
+    public function createLastRunTextfile($instancePath)
+    {
+        // Store the time instance was created
+        file_put_contents($instancePath . '/last_run.txt', time());
+    }
+
+    /**
+     * Link TYPO3 CMS core from "parent" instance.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @throws Exception
+     * @return void
+     */
+    public function setUpInstanceCoreLinks($instancePath)
+    {
+        $linksToSet = array(
+            ORIGINAL_ROOT . 'typo3' => $instancePath . '/typo3',
+            ORIGINAL_ROOT . 'index.php' => $instancePath . '/index.php'
+        );
+        foreach ($linksToSet as $from => $to) {
+            $success = symlink($from, $to);
+            if (!$success) {
+                throw new Exception(
+                    'Creating link failed: from ' . $from . ' to: ' . $to,
+                    1376657199
+                );
+            }
+        }
+    }
+
+    /**
+     * Link test extensions to the typo3conf/ext folder of the instance.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $extensionPaths Contains paths to extensions relative to document root
+     * @throws Exception
+     * @return void
+     */
+    public function linkTestExtensionsToInstance($instancePath, array $extensionPaths)
+    {
+        foreach ($extensionPaths as $extensionPath) {
+            $absoluteExtensionPath = ORIGINAL_ROOT . $extensionPath;
+            if (!is_dir($absoluteExtensionPath)) {
+                throw new Exception(
+                    'Test extension path ' . $absoluteExtensionPath . ' not found',
+                    1376745645
+                );
+            }
+            $destinationPath = $instancePath . '/typo3conf/ext/' . basename($absoluteExtensionPath);
+            $success = symlink($absoluteExtensionPath, $destinationPath);
+            if (!$success) {
+                throw new Exception(
+                    'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
+                    1376657142
+                );
+            }
+        }
+    }
+
+    /**
+     * Link paths inside the test instance, e.g. from a fixture fileadmin subfolder to the
+     * test instance fileadmin folder.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $pathsToLinkInTestInstance Contains paths as array of source => destination in key => value pairs of folders relative to test instance root
+     * @throws Exception if a source path could not be found and on failing creating the symlink
+     * @return void
+     */
+    public function linkPathsInTestInstance($instancePath, array $pathsToLinkInTestInstance)
+    {
+        foreach ($pathsToLinkInTestInstance as $sourcePathToLinkInTestInstance => $destinationPathToLinkInTestInstance) {
+            $sourcePath = $instancePath . '/' . ltrim($sourcePathToLinkInTestInstance, '/');
+            if (!file_exists($sourcePath)) {
+                throw new Exception(
+                    'Path ' . $sourcePath . ' not found',
+                    1376745645
+                );
+            }
+            $destinationPath = $instancePath . '/' . ltrim($destinationPathToLinkInTestInstance, '/');
+            $success = symlink($sourcePath, $destinationPath);
+            if (!$success) {
+                throw new Exception(
+                    'Can not link the path ' . $sourcePath . ' to ' . $destinationPath,
+                    1389969623
+                );
+            }
+        }
+    }
+
+    /**
+     * Database settings for functional and acceptance tests can be either set by
+     * environment variables (recommended), or from an existing LocalConfiguration as fallback.
+     * The method fetches these.
+     *
+     * An unique name will be added to the database name later.
+     *
+     * @throws Exception
+     * @return array [DB][host], [DB][username], ...
+     */
+    public function getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration()
+    {
+        $databaseName = trim(getenv('typo3DatabaseName'));
+        $databaseHost = trim(getenv('typo3DatabaseHost'));
+        $databaseUsername = trim(getenv('typo3DatabaseUsername'));
+        $databasePassword = trim(getenv('typo3DatabasePassword'));
+        $databasePort = trim(getenv('typo3DatabasePort'));
+        $databaseSocket = trim(getenv('typo3DatabaseSocket'));
+        if ($databaseName || $databaseHost || $databaseUsername || $databasePassword || $databasePort || $databaseSocket) {
+            // Try to get database credentials from environment variables first
+            $originalConfigurationArray = array(
+                'DB' => array(),
+            );
+            if ($databaseName) {
+                $originalConfigurationArray['DB']['database'] = $databaseName;
+            }
+            if ($databaseHost) {
+                $originalConfigurationArray['DB']['host'] = $databaseHost;
+            }
+            if ($databaseUsername) {
+                $originalConfigurationArray['DB']['username'] = $databaseUsername;
+            }
+            if ($databasePassword) {
+                $originalConfigurationArray['DB']['password'] = $databasePassword;
+            }
+            if ($databasePort) {
+                $originalConfigurationArray['DB']['port'] = $databasePort;
+            }
+            if ($databaseSocket) {
+                $originalConfigurationArray['DB']['socket'] = $databaseSocket;
+            }
+        } elseif (file_exists(ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php')) {
+            // See if a LocalConfiguration file exists in "parent" instance to get db credentials from
+            $originalConfigurationArray = require ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php';
+        } else {
+            throw new Exception(
+                'Database credentials for tests are neither set through environment'
+                . ' variables, and can not be found in an existing LocalConfiguration file',
+                1397406356
+            );
+        }
+        return $originalConfigurationArray;
+    }
+
+    /**
+     * Maximum length of database names is 64 chars in mysql. Test this is not exceeded
+     * after a suffix has been added.
+     *
+     * @param string $originalDatabaseName Base name of the database
+     * @param array $configuration "LocalConfiguration" array with DB settings
+     * @throws Exception
+     */
+    public function testDatabaseNameIsNotTooLong($originalDatabaseName, array $configuration)
+    {
+        // Maximum database name length for mysql is 64 characters
+        if (strlen($configuration['DB']['database']) > 64) {
+            $suffixLength = strlen($configuration['DB']['database']) - strlen($originalDatabaseName);
+            $maximumOriginalDatabaseName = 64 - $suffixLength;
+            throw new Exception(
+                'The name of the database that is used for the functional test (' . $originalDatabaseName . ')' .
+                ' exceeds the maximum length of 64 character allowed by MySQL. You have to shorten your' .
+                ' original database name to ' . $maximumOriginalDatabaseName . ' characters',
+                1377600104
+            );
+        }
+    }
+
+    /**
+     * Create LocalConfiguration.php file of the test instance.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $configuration Base configuration array
+     * @param array $overruleConfiguration Overrule factory and base configuration
+     * @throws Exception
+     * @return void
+     */
+    public function setUpLocalConfiguration($instancePath, array $configuration, array $overruleConfiguration)
+    {
+        // Base of final LocalConfiguration is core factory configuration
+        $finalConfigurationArray = require ORIGINAL_ROOT . 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
+        ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $configuration);
+        ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $overruleConfiguration);
+        $result = $this->writeFile(
+            $instancePath . '/typo3conf/LocalConfiguration.php',
+            '<?php' . chr(10) .
+            'return ' .
+            ArrayUtility::arrayExport(
+                $finalConfigurationArray
+            ) .
+            ';'
+        );
+        if (!$result) {
+            throw new Exception('Can not write local configuration', 1376657277);
+        }
+    }
+
+    /**
+     * Compile typo3conf/PackageStates.php containing default packages like core,
+     * a test specific list of additional core extensions, and a list of
+     * test extensions.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $defaultCoreExtensionsToLoad Default list of core extensions to load
+     * @param array $additionalCoreExtensionsToLoad Additional core extensions to load
+     * @param array $testExtensionPaths Paths to extensions relative to document root
+     * @throws Exception
+     */
+    public function setUpPackageStates(
+        $instancePath,
+        array $defaultCoreExtensionsToLoad,
+        array $additionalCoreExtensionsToLoad,
+        array $testExtensionPaths
+    ) {
+        $packageStates = array(
+            'packages' => array(),
+            'version' => 4,
+        );
+
+        // Register default list of extensions and set active
+        foreach ($defaultCoreExtensionsToLoad as $extensionName) {
+            $packageStates['packages'][$extensionName] = array(
+                'state' => 'active',
+                'packagePath' => 'typo3/sysext/' . $extensionName . '/',
+                'classesPath' => 'Classes/',
+            );
+        }
+
+        // Register additional core extensions and set active
+        foreach ($additionalCoreExtensionsToLoad as $extensionName) {
+            if (isset($packageSates['packages'][$extensionName])) {
+                throw new Exception(
+                    $extensionName . ' is already registered as default core extension to load, no need to load it explicitly',
+                    1390913893
+                );
+            }
+            $packageStates['packages'][$extensionName] = array(
+                'state' => 'active',
+                'packagePath' => 'typo3/sysext/' . $extensionName . '/',
+                'classesPath' => 'Classes/',
+            );
+        }
+
+        // Activate test extensions that have been symlinked before
+        foreach ($testExtensionPaths as $extensionPath) {
+            $extensionName = basename($extensionPath);
+            if (isset($packageSates['packages'][$extensionName])) {
+                throw new Exception(
+                    $extensionName . ' is already registered as extension to load, no need to load it explicitly',
+                    1390913894
+                );
+            }
+            $packageStates['packages'][$extensionName] = array(
+                'state' => 'active',
+                'packagePath' => 'typo3conf/ext/' . $extensionName . '/',
+                'classesPath' => 'Classes/',
+            );
+        }
+
+        $result = $this->writeFile(
+            $instancePath . '/typo3conf/PackageStates.php',
+            '<?php' . chr(10) .
+            'return ' .
+            ArrayUtility::arrayExport(
+                $packageStates
+            ) .
+            ';'
+        );
+
+        if (!$result) {
+            throw new Exception('Can not write PackageStates', 1381612729);
+        }
+    }
+
+    /**
+     * Populate $GLOBALS['TYPO3_DB'] and create test database
+     * For functional and acceptance tests
+     *
+     * @param string $databaseName Database name of this test instance
+     * @param string $originalDatabaseName Original database name before suffix was added
+     * @throws \TYPO3\CMS\Core\Tests\Exception
+     * @return void
+     */
+    public function setUpTestDatabase($databaseName, $originalDatabaseName)
+    {
+        Bootstrap::getInstance()->initializeTypo3DbGlobal();
+        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
+        $database = $GLOBALS['TYPO3_DB'];
+        if (!$database->sql_pconnect()) {
+            throw new Exception(
+                'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
+                . ' connection to the database was attempted to be established!',
+                1377620117
+            );
+        }
+
+        // Drop database if exists
+        $database->admin_query('DROP DATABASE IF EXISTS `' . $databaseName . '`');
+        $createDatabaseResult = $database->admin_query('CREATE DATABASE `' . $databaseName . '` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci');
+        if (!$createDatabaseResult) {
+            $user = $GLOBALS['TYPO3_CONF_VARS']['DB']['username'];
+            $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['host'];
+            throw new Exception(
+                'Unable to create database with name ' . $databaseName . '. This is probably a permission problem.'
+                . ' For this instance this could be fixed executing'
+                . ' "GRANT ALL ON `' . $originalDatabaseName . '_ft%`.* TO `' . $user . '`@`' . $host . '`;"',
+                1376579070
+            );
+        }
+        $database->setDatabaseName($databaseName);
+
+       // On windows, this still works, but throws a warning, which we need to discard.
+        @$database->sql_select_db();
+    }
+
+    /**
+     * Bootstrap basic TYPO3. This bootstraps TYPO3 far enough to initialize database afterwards.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @return void
+     */
+    public function setUpBasicTypo3Bootstrap($instancePath)
+    {
+        $_SERVER['PWD'] = $instancePath;
+        $_SERVER['argv'][0] = 'index.php';
+
+        $classLoader = require rtrim(realpath($instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php';
+        Bootstrap::getInstance()
+            ->initializeClassLoader($classLoader)
+            ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
+            ->baseSetup('')
+            ->loadConfigurationAndInitialize(true)
+            ->loadTypo3LoadedExtAndExtLocalconf(true)
+            ->setFinalCachingFrameworkCacheConfiguration()
+            ->defineLoggingAndExceptionConstants()
+            ->unsetReservedGlobalVariables();
+    }
+
+    /**
+     * Populate $GLOBALS['TYPO3_DB'] and truncate all tables.
+     * For functional and acceptance tests.
+     *
+     * @throws Exception
+     * @return void
+     */
+    public function initializeTestDatabaseAndTruncateTables()
+    {
+        Bootstrap::getInstance()->initializeTypo3DbGlobal();
+        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
+        $database = $GLOBALS['TYPO3_DB'];
+        if (!$database->sql_pconnect()) {
+            throw new Exception(
+                'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
+                . ' connection to the database was attempted to be established!',
+                1377620117
+            );
+        }
+        $database->setDatabaseName($GLOBALS['TYPO3_CONF_VARS']['DB']['database']);
+        $database->sql_select_db();
+        foreach ($database->admin_get_tables() as $table) {
+            $database->admin_query('TRUNCATE ' . $table['Name'] . ';');
+        }
+    }
+
+    /**
+     * Load ext_tables.php files.
+     * For functional and acceptance tests.
+     *
+     * @return void
+     */
+    public function loadExtensionTables()
+    {
+        Bootstrap::getInstance()->loadExtensionTables();
+    }
+
+    /**
+     * Create tables and import static rows.
+     * For functional and acceptance tests.
+     *
+     * @return void
+     */
+    public function createDatabaseStructure()
+    {
+        /** @var SqlSchemaMigrationService $schemaMigrationService */
+        $schemaMigrationService = GeneralUtility::makeInstance(SqlSchemaMigrationService::class);
+        /** @var ObjectManager $objectManager */
+        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
+        /** @var SqlExpectedSchemaService $expectedSchemaService */
+        $expectedSchemaService = $objectManager->get(SqlExpectedSchemaService::class);
+
+        // Raw concatenated ext_tables.sql and friends string
+        $expectedSchemaString = $expectedSchemaService->getTablesDefinitionString(true);
+        $statements = $schemaMigrationService->getStatementArray($expectedSchemaString, true);
+        list($_, $insertCount) = $schemaMigrationService->getCreateTables($statements, true);
+
+        $fieldDefinitionsFile = $schemaMigrationService->getFieldDefinitions_fileContent($expectedSchemaString);
+        $fieldDefinitionsDatabase = $schemaMigrationService->getFieldDefinitions_database();
+        $difference = $schemaMigrationService->getDatabaseExtra($fieldDefinitionsFile, $fieldDefinitionsDatabase);
+        $updateStatements = $schemaMigrationService->getUpdateSuggestions($difference);
+
+        $schemaMigrationService->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
+        $schemaMigrationService->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
+        $schemaMigrationService->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
+
+        foreach ($insertCount as $table => $count) {
+            $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
+            foreach ($insertStatements as $insertQuery) {
+                $insertQuery = rtrim($insertQuery, ';');
+                /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
+                $database = $GLOBALS['TYPO3_DB'];
+                $database->admin_query($insertQuery);
+            }
+        }
+    }
+
+    /**
+     * Returns the absolute path the TYPO3 document root.
+     * This is the "original" document root, not the "instance" root for functional / acceptance tests.
+     *
+     * @return string the TYPO3 document root using Unix path separators
+     */
+    protected function getWebRoot()
+    {
+        if (getenv('TYPO3_PATH_WEB')) {
+            $webRoot = getenv('TYPO3_PATH_WEB');
+        } else {
+            $webRoot = getcwd();
+        }
+        return rtrim(strtr($webRoot, '\\', '/'), '/') . '/';
+    }
+
+    /**
+     * Writes $content to the file $file. This is a simplified version
+     * of GeneralUtility::writeFile that does not fix permissions.
+     *
+     * @param string $file Filepath to write to
+     * @param string $content Content to write
+     * @return bool TRUE if the file was successfully opened and written to.
+     */
+    protected function writeFile($file, $content)
+    {
+        if ($fd = fopen($file, 'wb')) {
+            $res = fwrite($fd, $content);
+            fclose($fd);
+            if ($res === false) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+}
\ No newline at end of file