2 namespace TYPO3\CMS\Core\Tests
;
5 * This file is part of the TYPO3 CMS project.
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
14 * The TYPO3 project - inspiring people to share!
17 use TYPO3\CMS\Core\Core\Bootstrap
;
18 use TYPO3\CMS\Core\Utility\ArrayUtility
;
19 use TYPO3\CMS\Core\Utility\GeneralUtility
;
20 use TYPO3\CMS\Extbase\
Object\ObjectManager
;
21 use TYPO3\CMS\Install\Service\SqlExpectedSchemaService
;
22 use TYPO3\CMS\Install\Service\SqlSchemaMigrationService
;
25 * This is a helper class used by unit, functional and acceptance test
26 * environment builders.
27 * It contains methods to create test environments.
29 * This class is for internal use only and may change wihtout further notice.
31 * Use the classes "UnitTestCase", "FunctionalTestCase" or "AcceptanceCoreEnvironment"
32 * to indirectly benefit from this class in own extensions.
37 * Makes sure error messages during the tests get displayed no matter what is set in php.ini.
41 public function enableDisplayErrors()
43 @ini_set
('display_errors', 1);
47 * Defines a list of basic constants that are used by GeneralUtility and other
48 * helpers during tests setup. Those are sanitized in SystemEnvironmentBuilder
49 * to be not defined again.
52 * @see SystemEnvironmentBuilder::defineBaseConstants()
54 public function defineBaseConstants()
56 // A null, a tabulator, a linefeed, a carriage return, a substitution, a CR-LF combination
57 defined('NUL') ?
: define('NUL', chr(0));
58 defined('TAB') ?
: define('TAB', chr(9));
59 defined('LF') ?
: define('LF', chr(10));
60 defined('CR') ?
: define('CR', chr(13));
61 defined('SUB') ?
: define('SUB', chr(26));
62 defined('CRLF') ?
: define('CRLF', CR
. LF
);
64 if (!defined('TYPO3_OS')) {
65 // Operating system identifier
66 // Either "WIN" or empty string
68 if (!stristr(PHP_OS
, 'darwin') && !stristr(PHP_OS
, 'cygwin') && stristr(PHP_OS
, 'win')) {
71 define('TYPO3_OS', $typoOs);
76 * Defines the PATH_site and PATH_thisScript constant and sets $_SERVER['SCRIPT_NAME'].
81 public function defineSitePath()
84 define('PATH_site', $this->getWebRoot());
86 define('PATH_thisScript', PATH_site
. 'typo3/cli_dispatch.phpsh');
87 $_SERVER['SCRIPT_NAME'] = PATH_thisScript
;
89 if (!file_exists(PATH_thisScript
)) {
90 die('Unable to determine path to entry script. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
95 * Defines the constant ORIGINAL_ROOT for the path to the original TYPO3 document root.
96 * For functional / acceptance tests only
97 * If ORIGINAL_ROOT already is defined, this method is a no-op.
101 public function defineOriginalRootPath()
103 if (!defined('ORIGINAL_ROOT')) {
105 define('ORIGINAL_ROOT', $this->getWebRoot());
108 if (!file_exists(ORIGINAL_ROOT
. 'typo3/cli_dispatch.phpsh')) {
109 die('Unable to determine path to entry script. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
114 * Define TYPO3_MODE to BE
118 public function defineTypo3ModeBe()
121 define('TYPO3_MODE', 'BE');
125 * Sets the environment variable TYPO3_CONTEXT to testing.
129 public function setTypo3TestingContext()
132 putenv('TYPO3_CONTEXT=Testing');
136 * Creates directories, recursively if required.
138 * @param string $directory Absolute path to directories to create
142 public function createDirectory($directory)
144 if (is_dir($directory)) {
147 @mkdir
($directory, 0777, true);
149 if (!is_dir($directory)) {
150 throw new Exception('Directory "' . $directory . '" could not be created', 1404038665);
155 * Checks whether given test instance exists in path and is younger than some minutes.
156 * Used in functional tests
158 * @param string $instancePath Absolute path to test instance
161 public function recentTestInstanceExists($instancePath)
163 if (@file_get_contents
($instancePath . '/last_run.txt') <= (time() - 300)) {
166 // Test instance exists and is pretty young -> re-use
172 * Remove test instance folder structure if it exists.
173 * This may happen if a functional test before threw a fatal or is too old
175 * @param string $instancePath Absolute path to test instance
179 public function removeOldInstanceIfExists($instancePath)
181 if (is_dir($instancePath)) {
182 $success = GeneralUtility
::rmdir($instancePath, true);
185 'Can not remove folder: ' . $instancePath,
193 * Create last_run.txt file within instance path containing timestamp of "now".
194 * Used in functional tests to reuse an instance for multiple tests in one test case.
196 * @param string $instancePath Absolute path to test instance
199 public function createLastRunTextfile($instancePath)
201 // Store the time instance was created
202 file_put_contents($instancePath . '/last_run.txt', time());
206 * Link TYPO3 CMS core from "parent" instance.
207 * For functional and acceptance tests.
209 * @param string $instancePath Absolute path to test instance
213 public function setUpInstanceCoreLinks($instancePath)
216 ORIGINAL_ROOT
. 'typo3' => $instancePath . '/typo3',
217 ORIGINAL_ROOT
. 'index.php' => $instancePath . '/index.php'
219 foreach ($linksToSet as $from => $to) {
220 $success = symlink($from, $to);
223 'Creating link failed: from ' . $from . ' to: ' . $to,
231 * Link test extensions to the typo3conf/ext folder of the instance.
232 * For functional and acceptance tests.
234 * @param string $instancePath Absolute path to test instance
235 * @param array $extensionPaths Contains paths to extensions relative to document root
239 public function linkTestExtensionsToInstance($instancePath, array $extensionPaths)
241 foreach ($extensionPaths as $extensionPath) {
242 $absoluteExtensionPath = ORIGINAL_ROOT
. $extensionPath;
243 if (!is_dir($absoluteExtensionPath)) {
245 'Test extension path ' . $absoluteExtensionPath . ' not found',
249 $destinationPath = $instancePath . '/typo3conf/ext/' . basename($absoluteExtensionPath);
250 $success = symlink($absoluteExtensionPath, $destinationPath);
253 'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
261 * Link paths inside the test instance, e.g. from a fixture fileadmin subfolder to the
262 * test instance fileadmin folder.
263 * For functional and acceptance tests.
265 * @param string $instancePath Absolute path to test instance
266 * @param array $pathsToLinkInTestInstance Contains paths as array of source => destination in key => value pairs of folders relative to test instance root
267 * @throws Exception if a source path could not be found and on failing creating the symlink
270 public function linkPathsInTestInstance($instancePath, array $pathsToLinkInTestInstance)
272 foreach ($pathsToLinkInTestInstance as $sourcePathToLinkInTestInstance => $destinationPathToLinkInTestInstance) {
273 $sourcePath = $instancePath . '/' . ltrim($sourcePathToLinkInTestInstance, '/');
274 if (!file_exists($sourcePath)) {
276 'Path ' . $sourcePath . ' not found',
280 $destinationPath = $instancePath . '/' . ltrim($destinationPathToLinkInTestInstance, '/');
281 $success = symlink($sourcePath, $destinationPath);
284 'Can not link the path ' . $sourcePath . ' to ' . $destinationPath,
292 * Database settings for functional and acceptance tests can be either set by
293 * environment variables (recommended), or from an existing LocalConfiguration as fallback.
294 * The method fetches these.
296 * An unique name will be added to the database name later.
299 * @return array [DB][host], [DB][username], ...
301 public function getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration()
303 $databaseName = trim(getenv('typo3DatabaseName'));
304 $databaseHost = trim(getenv('typo3DatabaseHost'));
305 $databaseUsername = trim(getenv('typo3DatabaseUsername'));
306 $databasePassword = trim(getenv('typo3DatabasePassword'));
307 $databasePort = trim(getenv('typo3DatabasePort'));
308 $databaseSocket = trim(getenv('typo3DatabaseSocket'));
309 if ($databaseName ||
$databaseHost ||
$databaseUsername ||
$databasePassword ||
$databasePort ||
$databaseSocket) {
310 // Try to get database credentials from environment variables first
311 $originalConfigurationArray = array(
315 $originalConfigurationArray['DB']['database'] = $databaseName;
318 $originalConfigurationArray['DB']['host'] = $databaseHost;
320 if ($databaseUsername) {
321 $originalConfigurationArray['DB']['username'] = $databaseUsername;
323 if ($databasePassword) {
324 $originalConfigurationArray['DB']['password'] = $databasePassword;
327 $originalConfigurationArray['DB']['port'] = $databasePort;
329 if ($databaseSocket) {
330 $originalConfigurationArray['DB']['socket'] = $databaseSocket;
332 } elseif (file_exists(ORIGINAL_ROOT
. 'typo3conf/LocalConfiguration.php')) {
333 // See if a LocalConfiguration file exists in "parent" instance to get db credentials from
334 $originalConfigurationArray = require ORIGINAL_ROOT
. 'typo3conf/LocalConfiguration.php';
337 'Database credentials for tests are neither set through environment'
338 . ' variables, and can not be found in an existing LocalConfiguration file',
342 return $originalConfigurationArray;
346 * Maximum length of database names is 64 chars in mysql. Test this is not exceeded
347 * after a suffix has been added.
349 * @param string $originalDatabaseName Base name of the database
350 * @param array $configuration "LocalConfiguration" array with DB settings
353 public function testDatabaseNameIsNotTooLong($originalDatabaseName, array $configuration)
355 // Maximum database name length for mysql is 64 characters
356 if (strlen($configuration['DB']['database']) > 64) {
357 $suffixLength = strlen($configuration['DB']['database']) - strlen($originalDatabaseName);
358 $maximumOriginalDatabaseName = 64 - $suffixLength;
360 'The name of the database that is used for the functional test (' . $originalDatabaseName . ')' .
361 ' exceeds the maximum length of 64 character allowed by MySQL. You have to shorten your' .
362 ' original database name to ' . $maximumOriginalDatabaseName . ' characters',
369 * Create LocalConfiguration.php file of the test instance.
370 * For functional and acceptance tests.
372 * @param string $instancePath Absolute path to test instance
373 * @param array $configuration Base configuration array
374 * @param array $overruleConfiguration Overrule factory and base configuration
378 public function setUpLocalConfiguration($instancePath, array $configuration, array $overruleConfiguration)
380 // Base of final LocalConfiguration is core factory configuration
381 $finalConfigurationArray = require ORIGINAL_ROOT
. 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
382 ArrayUtility
::mergeRecursiveWithOverrule($finalConfigurationArray, $configuration);
383 ArrayUtility
::mergeRecursiveWithOverrule($finalConfigurationArray, $overruleConfiguration);
384 $result = $this->writeFile(
385 $instancePath . '/typo3conf/LocalConfiguration.php',
388 ArrayUtility
::arrayExport(
389 $finalConfigurationArray
394 throw new Exception('Can not write local configuration', 1376657277);
399 * Compile typo3conf/PackageStates.php containing default packages like core,
400 * a test specific list of additional core extensions, and a list of
402 * For functional and acceptance tests.
404 * @param string $instancePath Absolute path to test instance
405 * @param array $defaultCoreExtensionsToLoad Default list of core extensions to load
406 * @param array $additionalCoreExtensionsToLoad Additional core extensions to load
407 * @param array $testExtensionPaths Paths to extensions relative to document root
410 public function setUpPackageStates(
412 array $defaultCoreExtensionsToLoad,
413 array $additionalCoreExtensionsToLoad,
414 array $testExtensionPaths
416 $packageStates = array(
417 'packages' => array(),
421 // Register default list of extensions and set active
422 foreach ($defaultCoreExtensionsToLoad as $extensionName) {
423 $packageStates['packages'][$extensionName] = array(
425 'packagePath' => 'typo3/sysext/' . $extensionName . '/',
426 'classesPath' => 'Classes/',
430 // Register additional core extensions and set active
431 foreach ($additionalCoreExtensionsToLoad as $extensionName) {
432 if (isset($packageSates['packages'][$extensionName])) {
434 $extensionName . ' is already registered as default core extension to load, no need to load it explicitly',
438 $packageStates['packages'][$extensionName] = array(
440 'packagePath' => 'typo3/sysext/' . $extensionName . '/',
441 'classesPath' => 'Classes/',
445 // Activate test extensions that have been symlinked before
446 foreach ($testExtensionPaths as $extensionPath) {
447 $extensionName = basename($extensionPath);
448 if (isset($packageSates['packages'][$extensionName])) {
450 $extensionName . ' is already registered as extension to load, no need to load it explicitly',
454 $packageStates['packages'][$extensionName] = array(
456 'packagePath' => 'typo3conf/ext/' . $extensionName . '/',
457 'classesPath' => 'Classes/',
461 $result = $this->writeFile(
462 $instancePath . '/typo3conf/PackageStates.php',
465 ArrayUtility
::arrayExport(
472 throw new Exception('Can not write PackageStates', 1381612729);
477 * Populate $GLOBALS['TYPO3_DB'] and create test database
478 * For functional and acceptance tests
480 * @param string $databaseName Database name of this test instance
481 * @param string $originalDatabaseName Original database name before suffix was added
482 * @throws \TYPO3\CMS\Core\Tests\Exception
485 public function setUpTestDatabase($databaseName, $originalDatabaseName)
487 Bootstrap
::getInstance()->initializeTypo3DbGlobal();
488 /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
489 $database = $GLOBALS['TYPO3_DB'];
490 if (!$database->sql_pconnect()) {
492 'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
493 . ' connection to the database was attempted to be established!',
498 // Drop database if exists
499 $database->admin_query('DROP DATABASE IF EXISTS `' . $databaseName . '`');
500 $createDatabaseResult = $database->admin_query('CREATE DATABASE `' . $databaseName . '` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci');
501 if (!$createDatabaseResult) {
502 $user = $GLOBALS['TYPO3_CONF_VARS']['DB']['username'];
503 $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['host'];
505 'Unable to create database with name ' . $databaseName . '. This is probably a permission problem.'
506 . ' For this instance this could be fixed executing'
507 . ' "GRANT ALL ON `' . $originalDatabaseName . '_ft%`.* TO `' . $user . '`@`' . $host . '`;"',
511 $database->setDatabaseName($databaseName);
513 // On windows, this still works, but throws a warning, which we need to discard.
514 @$database->sql_select_db();
518 * Bootstrap basic TYPO3. This bootstraps TYPO3 far enough to initialize database afterwards.
519 * For functional and acceptance tests.
521 * @param string $instancePath Absolute path to test instance
524 public function setUpBasicTypo3Bootstrap($instancePath)
526 $_SERVER['PWD'] = $instancePath;
527 $_SERVER['argv'][0] = 'index.php';
529 $classLoader = require rtrim(realpath($instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php';
530 Bootstrap
::getInstance()
531 ->initializeClassLoader($classLoader)
532 ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI
)
534 ->loadConfigurationAndInitialize(true)
535 ->loadTypo3LoadedExtAndExtLocalconf(true)
536 ->setFinalCachingFrameworkCacheConfiguration()
537 ->defineLoggingAndExceptionConstants()
538 ->unsetReservedGlobalVariables();
542 * Populate $GLOBALS['TYPO3_DB'] and truncate all tables.
543 * For functional and acceptance tests.
548 public function initializeTestDatabaseAndTruncateTables()
550 Bootstrap
::getInstance()->initializeTypo3DbGlobal();
551 /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
552 $database = $GLOBALS['TYPO3_DB'];
553 if (!$database->sql_pconnect()) {
555 'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
556 . ' connection to the database was attempted to be established!',
560 $database->setDatabaseName($GLOBALS['TYPO3_CONF_VARS']['DB']['database']);
561 $database->sql_select_db();
562 foreach ($database->admin_get_tables() as $table) {
563 $database->admin_query('TRUNCATE ' . $table['Name'] . ';');
568 * Load ext_tables.php files.
569 * For functional and acceptance tests.
573 public function loadExtensionTables()
575 Bootstrap
::getInstance()->loadExtensionTables();
579 * Create tables and import static rows.
580 * For functional and acceptance tests.
584 public function createDatabaseStructure()
586 /** @var SqlSchemaMigrationService $schemaMigrationService */
587 $schemaMigrationService = GeneralUtility
::makeInstance(SqlSchemaMigrationService
::class);
588 /** @var ObjectManager $objectManager */
589 $objectManager = GeneralUtility
::makeInstance(ObjectManager
::class);
590 /** @var SqlExpectedSchemaService $expectedSchemaService */
591 $expectedSchemaService = $objectManager->get(SqlExpectedSchemaService
::class);
593 // Raw concatenated ext_tables.sql and friends string
594 $expectedSchemaString = $expectedSchemaService->getTablesDefinitionString(true);
595 $statements = $schemaMigrationService->getStatementArray($expectedSchemaString, true);
596 list($_, $insertCount) = $schemaMigrationService->getCreateTables($statements, true);
598 $fieldDefinitionsFile = $schemaMigrationService->getFieldDefinitions_fileContent($expectedSchemaString);
599 $fieldDefinitionsDatabase = $schemaMigrationService->getFieldDefinitions_database();
600 $difference = $schemaMigrationService->getDatabaseExtra($fieldDefinitionsFile, $fieldDefinitionsDatabase);
601 $updateStatements = $schemaMigrationService->getUpdateSuggestions($difference);
603 $schemaMigrationService->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
604 $schemaMigrationService->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
605 $schemaMigrationService->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
607 foreach ($insertCount as $table => $count) {
608 $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
609 foreach ($insertStatements as $insertQuery) {
610 $insertQuery = rtrim($insertQuery, ';');
611 /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
612 $database = $GLOBALS['TYPO3_DB'];
613 $database->admin_query($insertQuery);
619 * Returns the absolute path the TYPO3 document root.
620 * This is the "original" document root, not the "instance" root for functional / acceptance tests.
622 * @return string the TYPO3 document root using Unix path separators
624 protected function getWebRoot()
626 if (getenv('TYPO3_PATH_WEB')) {
627 $webRoot = getenv('TYPO3_PATH_WEB');
631 return rtrim(strtr($webRoot, '\\', '/'), '/') . '/';
635 * Writes $content to the file $file. This is a simplified version
636 * of GeneralUtility::writeFile that does not fix permissions.
638 * @param string $file Filepath to write to
639 * @param string $content Content to write
640 * @return bool TRUE if the file was successfully opened and written to.
642 protected function writeFile($file, $content)
644 if ($fd = fopen($file, 'wb')) {
645 $res = fwrite($fd, $content);
647 if ($res === false) {