From 4c6ec4df4d381bb5f8aef3f9b55acd3cefe1735c Mon Sep 17 00:00:00 2001 From: Christian Kuhn Date: Tue, 16 Feb 2016 20:19:26 +0100 Subject: [PATCH 1/1] [TASK] Acceptance tests in controlled environment 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 Reviewed-by: Frank Naegler Tested-by: Frank Naegler Reviewed-by: Christian Kuhn Tested-by: Christian Kuhn --- .travis.yml | 9 +- typo3/sysext/core/Build/AcceptanceTests.yml | 19 +- .../Acceptance/Support/Helper/Functional.php | 10 - .../Acceptance/Support/Helper/Unit.php | 10 - .../Acceptance/TravisLocalConfiguration.php | 73 -- .../Acceptance/TravisPackageStates.php | 103 --- .../Acceptance/acceptance.suite.yml | 34 +- .../FunctionalTestsConfiguration.php | 9 - .../core/Build/FunctionalTestsBootstrap.php | 136 +-- .../sysext/core/Build/UnitTestsBootstrap.php | 254 +----- .../Classes/Core/SystemEnvironmentBuilder.php | 14 +- .../core/Tests/AcceptanceCoreEnvironment.php | 215 +++++ .../sysext/core/Tests/FunctionalTestCase.php | 134 ++- .../FunctionalTestCaseBootstrapUtility.php | 790 ------------------ typo3/sysext/core/Tests/Testbase.php | 654 +++++++++++++++ 15 files changed, 1048 insertions(+), 1416 deletions(-) delete mode 100644 typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Functional.php delete mode 100644 typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Unit.php delete mode 100644 typo3/sysext/core/Build/Configuration/Acceptance/TravisLocalConfiguration.php delete mode 100644 typo3/sysext/core/Build/Configuration/Acceptance/TravisPackageStates.php delete mode 100644 typo3/sysext/core/Build/Configuration/FunctionalTestsConfiguration.php create mode 100644 typo3/sysext/core/Tests/AcceptanceCoreEnvironment.php delete mode 100644 typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php create mode 100644 typo3/sysext/core/Tests/Testbase.php diff --git a/.travis.yml b/.travis.yml index 69921610449c..a3a354c27618 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 - > diff --git a/typo3/sysext/core/Build/AcceptanceTests.yml b/typo3/sysext/core/Build/AcceptanceTests.yml index 99c1af1bd3f7..6de4260e66cb 100644 --- a/typo3/sysext/core/Build/AcceptanceTests.yml +++ b/typo3/sysext/core/Build/AcceptanceTests.yml @@ -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 index 4183cb0a5525..000000000000 --- a/typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Functional.php +++ /dev/null @@ -1,10 +0,0 @@ - [ - '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 index 31057b2605c9..000000000000 --- a/typo3/sysext/core/Build/Configuration/Acceptance/TravisPackageStates.php +++ /dev/null @@ -1,103 +0,0 @@ - [ - '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 diff --git a/typo3/sysext/core/Build/Configuration/Acceptance/acceptance.suite.yml b/typo3/sysext/core/Build/Configuration/Acceptance/acceptance.suite.yml index 168a286c2787..4602ac3d784f 100644 --- a/typo3/sysext/core/Build/Configuration/Acceptance/acceptance.suite.yml +++ b/typo3/sysext/core/Build/Configuration/Acceptance/acceptance.suite.yml @@ -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 index 135c46ad6de6..000000000000 --- a/typo3/sysext/core/Build/Configuration/FunctionalTestsConfiguration.php +++ /dev/null @@ -1,9 +0,0 @@ - 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\';', - ) -); diff --git a/typo3/sysext/core/Build/FunctionalTestsBootstrap.php b/typo3/sysext/core/Build/FunctionalTestsBootstrap.php index d52ada0da6a5..afa82459edd5 100644 --- a/typo3/sysext/core/Build/FunctionalTestsBootstrap.php +++ b/typo3/sysext/core/Build/FunctionalTestsBootstrap.php @@ -1,6 +1,4 @@ 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 diff --git a/typo3/sysext/core/Build/UnitTestsBootstrap.php b/typo3/sysext/core/Build/UnitTestsBootstrap.php index 32bdf127a353..e2fb5fae2287 100644 --- a/typo3/sysext/core/Build/UnitTestsBootstrap.php +++ b/typo3/sysext/core/Build/UnitTestsBootstrap.php @@ -1,6 +1,4 @@ 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); diff --git a/typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php b/typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php index fed1a395ac68..82964a035919 100644 --- a/typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php +++ b/typo3/sysext/core/Classes/Core/SystemEnvironmentBuilder.php @@ -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 index 000000000000..d3f28b27d91d --- /dev/null +++ b/typo3/sysext/core/Tests/AcceptanceCoreEnvironment.php @@ -0,0 +1,215 @@ + '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); + } + +} diff --git a/typo3/sysext/core/Tests/FunctionalTestCase.php b/typo3/sysext/core/Tests/FunctionalTestCase.php index b5a26ec15e23..16cb328fab98 100644 --- a/typo3/sysext/core/Tests/FunctionalTestCase.php +++ b/typo3/sysext/core/Tests/FunctionalTestCase.php @@ -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 @@ -160,34 +180,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. * @@ -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 index e88502409d0f..000000000000 --- a/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php +++ /dev/null @@ -1,790 +0,0 @@ - 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', - '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', - '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 index 000000000000..626b1983bb23 --- /dev/null +++ b/typo3/sysext/core/Tests/Testbase.php @@ -0,0 +1,654 @@ +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', + ' 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', + '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 -- 2.20.1