TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
Testbase.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Tests;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
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.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
17 use Doctrine\DBAL\DBALException;
18 use Doctrine\DBAL\DriverManager;
25 
36 class Testbase
37 {
43  public function __construct()
44  {
45  // Ensure cli only as security measure
46  if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
47  die('This script supports command line usage only. Please check your command.');
48  }
49  }
50 
56  public function enableDisplayErrors()
57  {
58  @ini_set('display_errors', 1);
59  }
60 
69  public function defineBaseConstants()
70  {
71  // A null, a tabulator, a linefeed, a carriage return, a substitution, a CR-LF combination
72  defined('NUL') ?: define('NUL', chr(0));
73  defined('TAB') ?: define('TAB', chr(9));
74  defined('LF') ?: define('LF', chr(10));
75  defined('CR') ?: define('CR', chr(13));
76  defined('SUB') ?: define('SUB', chr(26));
77  defined('CRLF') ?: define('CRLF', CR . LF);
78 
79  if (!defined('TYPO3_OS')) {
80  // Operating system identifier
81  // Either "WIN" or empty string
82  $typoOs = '';
83  if (!stristr(PHP_OS, 'darwin') && !stristr(PHP_OS, 'cygwin') && stristr(PHP_OS, 'win')) {
84  $typoOs = 'WIN';
85  }
86  define('TYPO3_OS', $typoOs);
87  }
88  }
89 
96  public function defineSitePath()
97  {
98  define('PATH_site', $this->getWebRoot());
99  define('PATH_thisScript', PATH_site . 'typo3/cli_dispatch.phpsh');
100  $_SERVER['SCRIPT_NAME'] = PATH_thisScript;
101 
102  if (!file_exists(PATH_thisScript)) {
103  $this->exitWithMessage('Unable to determine path to entry script. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
104  }
105  }
106 
114  public function defineOriginalRootPath()
115  {
116  if (!defined('ORIGINAL_ROOT')) {
117  define('ORIGINAL_ROOT', $this->getWebRoot());
118  }
119 
120  if (!file_exists(ORIGINAL_ROOT . 'typo3/cli_dispatch.phpsh')) {
121  $this->exitWithMessage('Unable to determine path to entry script. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
122  }
123  }
124 
130  public function defineTypo3ModeBe()
131  {
132  define('TYPO3_MODE', 'BE');
133  }
134 
140  public function setTypo3TestingContext()
141  {
142  putenv('TYPO3_CONTEXT=Testing');
143  }
144 
152  public function createDirectory($directory)
153  {
154  if (is_dir($directory)) {
155  return;
156  }
157  @mkdir($directory, 0777, true);
158  clearstatcache();
159  if (!is_dir($directory)) {
160  throw new Exception('Directory "' . $directory . '" could not be created', 1404038665);
161  }
162  }
163 
171  public function recentTestInstanceExists($instancePath)
172  {
173  if (@file_get_contents($instancePath . '/last_run.txt') <= (time() - 300)) {
174  return false;
175  } else {
176  // Test instance exists and is pretty young -> re-use
177  return true;
178  }
179  }
180 
189  public function removeOldInstanceIfExists($instancePath)
190  {
191  if (is_dir($instancePath)) {
192  $success = GeneralUtility::rmdir($instancePath, true);
193  if (!$success) {
194  throw new Exception(
195  'Can not remove folder: ' . $instancePath,
196  1376657210
197  );
198  }
199  }
200  }
201 
209  public function createLastRunTextfile($instancePath)
210  {
211  // Store the time instance was created
212  file_put_contents($instancePath . '/last_run.txt', time());
213  }
214 
223  public function setUpInstanceCoreLinks($instancePath)
224  {
225  $linksToSet = [
226  ORIGINAL_ROOT . 'typo3' => $instancePath . '/typo3',
227  ORIGINAL_ROOT . 'index.php' => $instancePath . '/index.php'
228  ];
229  foreach ($linksToSet as $from => $to) {
230  $success = symlink($from, $to);
231  if (!$success) {
232  throw new Exception(
233  'Creating link failed: from ' . $from . ' to: ' . $to,
234  1376657199
235  );
236  }
237  }
238  }
239 
249  public function linkTestExtensionsToInstance($instancePath, array $extensionPaths)
250  {
251  foreach ($extensionPaths as $extensionPath) {
252  $absoluteExtensionPath = ORIGINAL_ROOT . $extensionPath;
253  if (!is_dir($absoluteExtensionPath)) {
254  throw new Exception(
255  'Test extension path ' . $absoluteExtensionPath . ' not found',
256  1376745645
257  );
258  }
259  $destinationPath = $instancePath . '/typo3conf/ext/' . basename($absoluteExtensionPath);
260  $success = symlink($absoluteExtensionPath, $destinationPath);
261  if (!$success) {
262  throw new Exception(
263  'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
264  1376657142
265  );
266  }
267  }
268  }
269 
280  public function linkPathsInTestInstance($instancePath, array $pathsToLinkInTestInstance)
281  {
282  foreach ($pathsToLinkInTestInstance as $sourcePathToLinkInTestInstance => $destinationPathToLinkInTestInstance) {
283  $sourcePath = $instancePath . '/' . ltrim($sourcePathToLinkInTestInstance, '/');
284  if (!file_exists($sourcePath)) {
285  throw new Exception(
286  'Path ' . $sourcePath . ' not found',
287  1476109221
288  );
289  }
290  $destinationPath = $instancePath . '/' . ltrim($destinationPathToLinkInTestInstance, '/');
291  $success = symlink($sourcePath, $destinationPath);
292  if (!$success) {
293  throw new Exception(
294  'Can not link the path ' . $sourcePath . ' to ' . $destinationPath,
295  1389969623
296  );
297  }
298  }
299  }
300 
312  {
313  $databaseName = trim(getenv('typo3DatabaseName'));
314  $databaseHost = trim(getenv('typo3DatabaseHost'));
315  $databaseUsername = trim(getenv('typo3DatabaseUsername'));
316  $databasePassword = getenv('typo3DatabasePassword');
317  $databasePasswordTrimmed = trim($databasePassword);
318  $databasePort = trim(getenv('typo3DatabasePort'));
319  $databaseSocket = trim(getenv('typo3DatabaseSocket'));
320  if ($databaseName || $databaseHost || $databaseUsername || $databasePassword || $databasePort || $databaseSocket) {
321  // Try to get database credentials from environment variables first
322  $originalConfigurationArray = [
323  'DB' => [
324  'Connections' => [
325  'Default' => [
326  'driver' => 'mysqli'
327  ],
328  ],
329  ],
330  ];
331  if ($databaseName) {
332  $originalConfigurationArray['DB']['Connections']['Default']['dbname'] = $databaseName;
333  }
334  if ($databaseHost) {
335  $originalConfigurationArray['DB']['Connections']['Default']['host'] = $databaseHost;
336  }
337  if ($databaseUsername) {
338  $originalConfigurationArray['DB']['Connections']['Default']['user'] = $databaseUsername;
339  }
340  if ($databasePassword !== false) {
341  $originalConfigurationArray['DB']['Connections']['Default']['password'] = $databasePasswordTrimmed;
342  }
343  if ($databasePort) {
344  $originalConfigurationArray['DB']['Connections']['Default']['port'] = $databasePort;
345  }
346  if ($databaseSocket) {
347  $originalConfigurationArray['DB']['Connections']['Default']['unix_socket'] = $databaseSocket;
348  }
349  } elseif (file_exists(ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php')) {
350  // See if a LocalConfiguration file exists in "parent" instance to get db credentials from
351  $originalConfigurationArray = require ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php';
352  } else {
353  throw new Exception(
354  'Database credentials for tests are neither set through environment'
355  . ' variables, and can not be found in an existing LocalConfiguration file',
356  1397406356
357  );
358  }
359  return $originalConfigurationArray['DB'];
360  }
361 
370  public function testDatabaseNameIsNotTooLong($originalDatabaseName, array $configuration)
371  {
372  // Maximum database name length for mysql is 64 characters
373  if (strlen($configuration['DB']['Connections']['Default']['dbname']) > 64) {
374  $suffixLength = strlen($configuration['DB']['Connections']['Default']['dbname']) - strlen($originalDatabaseName);
375  $maximumOriginalDatabaseName = 64 - $suffixLength;
376  throw new Exception(
377  'The name of the database that is used for the functional test (' . $originalDatabaseName . ')' .
378  ' exceeds the maximum length of 64 character allowed by MySQL. You have to shorten your' .
379  ' original database name to ' . $maximumOriginalDatabaseName . ' characters',
380  1377600104
381  );
382  }
383  }
384 
395  public function setUpLocalConfiguration($instancePath, array $configuration, array $overruleConfiguration)
396  {
397  // Base of final LocalConfiguration is core factory configuration
398  $finalConfigurationArray = require ORIGINAL_ROOT . 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
399  ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $configuration);
400  ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $overruleConfiguration);
401  $result = $this->writeFile(
402  $instancePath . '/typo3conf/LocalConfiguration.php',
403  '<?php' . chr(10) .
404  'return ' .
406  $finalConfigurationArray
407  ) .
408  ';'
409  );
410  if (!$result) {
411  throw new Exception('Can not write local configuration', 1376657277);
412  }
413  }
414 
427  public function setUpPackageStates(
428  $instancePath,
429  array $defaultCoreExtensionsToLoad,
430  array $additionalCoreExtensionsToLoad,
431  array $testExtensionPaths
432  ) {
433  $packageStates = [
434  'packages' => [],
435  'version' => 5,
436  ];
437 
438  // Register default list of extensions and set active
439  foreach ($defaultCoreExtensionsToLoad as $extensionName) {
440  $packageStates['packages'][$extensionName] = [
441  'packagePath' => 'typo3/sysext/' . $extensionName . '/'
442  ];
443  }
444 
445  // Register additional core extensions and set active
446  foreach ($additionalCoreExtensionsToLoad as $extensionName) {
447  $packageStates['packages'][$extensionName] = [
448  'packagePath' => 'typo3/sysext/' . $extensionName . '/'
449  ];
450  }
451 
452  // Activate test extensions that have been symlinked before
453  foreach ($testExtensionPaths as $extensionPath) {
454  $extensionName = basename($extensionPath);
455  $packageStates['packages'][$extensionName] = [
456  'packagePath' => 'typo3conf/ext/' . $extensionName . '/'
457  ];
458  }
459 
460  $result = $this->writeFile(
461  $instancePath . '/typo3conf/PackageStates.php',
462  '<?php' . chr(10) .
463  'return ' .
465  $packageStates
466  ) .
467  ';'
468  );
469 
470  if (!$result) {
471  throw new Exception('Can not write PackageStates', 1381612729);
472  }
473  }
474 
484  public function setUpTestDatabase($databaseName, $originalDatabaseName)
485  {
486  Bootstrap::getInstance()->initializeTypo3DbGlobal();
487 
488  // Drop database if exists. Directly using the Doctrine DriverManager to
489  // work around connection caching in ConnectionPool
490  $connectionParameters = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default'];
491  unset($connectionParameters['dbname']);
492  $schemaManager = DriverManager::getConnection($connectionParameters)->getSchemaManager();
493 
494  if (in_array($databaseName, $schemaManager->listDatabases(), true)) {
495  $schemaManager->dropDatabase($databaseName);
496  }
497 
498  try {
499  $schemaManager->createDatabase($databaseName);
500  } catch (DBALException $e) {
501  $user = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'];
502  $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'];
503  throw new Exception(
504  'Unable to create database with name ' . $databaseName . '. This is probably a permission problem.'
505  . ' For this instance this could be fixed executing:'
506  . ' GRANT ALL ON `' . $originalDatabaseName . '_%`.* TO `' . $user . '`@`' . $host . '`;'
507  . ' Original message thrown by database layer: ' . $e->getMessage(),
508  1376579070
509  );
510  }
511  }
512 
520  public function setUpBasicTypo3Bootstrap($instancePath)
521  {
522  $_SERVER['PWD'] = $instancePath;
523  $_SERVER['argv'][0] = 'index.php';
524 
525  $classLoader = require rtrim(realpath($instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php';
527  ->initializeClassLoader($classLoader)
528  ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
529  ->baseSetup()
530  ->loadConfigurationAndInitialize(true)
531  ->loadTypo3LoadedExtAndExtLocalconf(true)
532  ->setFinalCachingFrameworkCacheConfiguration()
533  ->defineLoggingAndExceptionConstants()
534  ->unsetReservedGlobalVariables();
535  }
536 
545  {
546  Bootstrap::getInstance()->initializeTypo3DbGlobal();
547 
548  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
549  ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
550  $schemaManager = $connection->getSchemaManager();
551 
552  foreach ($schemaManager->listTables() as $table) {
553  $connection->truncate($table->getName());
554  }
555  }
556 
563  public function loadExtensionTables()
564  {
565  Bootstrap::getInstance()->loadExtensionTables();
566  }
567 
574  public function createDatabaseStructure()
575  {
576  $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
577  $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
578  $sqlCode = $sqlReader->getTablesDefinitionString(true);
579 
580  $createTableStatements = $sqlReader->getCreateTableStatementArray($sqlCode);
581 
582  $schemaMigrationService->install($createTableStatements);
583 
584  $insertStatements = $sqlReader->getInsertStatementArray($sqlCode);
585  $schemaMigrationService->importStaticData($insertStatements);
586  }
587 
595  public function importXmlDatabaseFixture($path)
596  {
597  if (!is_file($path)) {
598  throw new Exception(
599  'Fixture file ' . $path . ' not found',
600  1376746261
601  );
602  }
603 
604  $fileContent = file_get_contents($path);
605  // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
606  $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
607  $xml = simplexml_load_string($fileContent);
608  libxml_disable_entity_loader($previousValueOfEntityLoader);
609  $foreignKeys = [];
610 
612  foreach ($xml->children() as $table) {
613  $insertArray = [];
614 
616  foreach ($table->children() as $column) {
617  $columnName = $column->getName();
618  $columnValue = null;
619 
620  if (isset($column['ref'])) {
621  list($tableName, $elementId) = explode('#', $column['ref']);
622  $columnValue = $foreignKeys[$tableName][$elementId];
623  } elseif (isset($column['is-NULL']) && ($column['is-NULL'] === 'yes')) {
624  $columnValue = null;
625  } else {
626  $columnValue = (string)$table->$columnName;
627  }
628 
629  $insertArray[$columnName] = $columnValue;
630  }
631 
632  $tableName = $table->getName();
633  $connection = GeneralUtility::makeInstance(ConnectionPool::class)
634  ->getConnectionForTable($tableName);
635  $connection->insert(
636  $tableName,
637  $insertArray
638  );
639  if (isset($table['id'])) {
640  $elementId = (string)$table['id'];
641  $foreignKeys[$tableName][$elementId] = $connection->lastInsertId($tableName);
642  }
643  }
644  }
645 
652  protected function getWebRoot()
653  {
654  if (getenv('TYPO3_PATH_WEB')) {
655  $webRoot = getenv('TYPO3_PATH_WEB');
656  } else {
657  $webRoot = getcwd();
658  }
659  return rtrim(strtr($webRoot, '\\', '/'), '/') . '/';
660  }
661 
667  protected function exitWithMessage($message)
668  {
669  echo $message . LF;
670  exit(1);
671  }
672 
681  protected function writeFile($file, $content)
682  {
683  if ($fd = fopen($file, 'wb')) {
684  $res = fwrite($fd, $content);
685  fclose($fd);
686  if ($res === false) {
687  return false;
688  }
689  return true;
690  }
691  return false;
692  }
693 }
setUpInstanceCoreLinks($instancePath)
Definition: Testbase.php:223
static rmdir($path, $removeNonEmpty=false)
setUpLocalConfiguration($instancePath, array $configuration, array $overruleConfiguration)
Definition: Testbase.php:395
writeFile($file, $content)
Definition: Testbase.php:681
testDatabaseNameIsNotTooLong($originalDatabaseName, array $configuration)
Definition: Testbase.php:370
setUpTestDatabase($databaseName, $originalDatabaseName)
Definition: Testbase.php:484
getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration()
Definition: Testbase.php:311
static mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=true, $includeEmptyValues=true, $enableUnsetFeature=true)
setUpBasicTypo3Bootstrap($instancePath)
Definition: Testbase.php:520
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
createLastRunTextfile($instancePath)
Definition: Testbase.php:209
linkTestExtensionsToInstance($instancePath, array $extensionPaths)
Definition: Testbase.php:249
setUpPackageStates($instancePath, array $defaultCoreExtensionsToLoad, array $additionalCoreExtensionsToLoad, array $testExtensionPaths)
Definition: Testbase.php:427
linkPathsInTestInstance($instancePath, array $pathsToLinkInTestInstance)
Definition: Testbase.php:280
removeOldInstanceIfExists($instancePath)
Definition: Testbase.php:189
recentTestInstanceExists($instancePath)
Definition: Testbase.php:171