TYPO3 CMS  TYPO3_6-2
FunctionalTestCaseBootstrapUtility.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Core\Tests;
3 
21 
25  protected $identifier;
26 
30  protected $instancePath;
31 
35  protected $databaseName;
36 
41 
46  'core',
47  'backend',
48  'frontend',
49  'cms',
50  'lang',
51  'extbase',
52  'install',
53  );
54 
58  protected $defaultFoldersToCreate = array(
59  '',
60  '/fileadmin',
61  '/typo3temp',
62  '/typo3conf',
63  '/typo3conf/ext',
64  '/uploads'
65  );
66 
74  static public function getInstanceIdentifier($testCaseClassName) {
75  // 7 characters of sha1 should be enough for a unique identification
76  return substr(sha1($testCaseClassName), 0, 7);
77  }
78 
85  static public function getInstancePath($testCaseClassName) {
86  return ORIGINAL_ROOT . 'typo3temp/functional-' . static::getInstanceIdentifier($testCaseClassName);
87  }
88 
100  public function setUp(
101  $testCaseClassName,
102  array $coreExtensionsToLoad,
103  array $testExtensionsToLoad,
104  array $pathsToLinkInTestInstance,
105  array $configurationToUse,
106  array $additionalFoldersToCreate
107  ) {
108  $this->setUpIdentifier($testCaseClassName);
109  $this->setUpInstancePath($testCaseClassName);
110  if ($this->recentTestInstanceExists()) {
111  $this->setUpBasicTypo3Bootstrap();
112  $this->initializeTestDatabase();
113  \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables(TRUE);
114  } else {
115  $this->removeOldInstanceIfExists();
116  $this->setUpInstanceDirectories($additionalFoldersToCreate);
117  $this->setUpInstanceCoreLinks();
118  $this->linkTestExtensionsToInstance($testExtensionsToLoad);
119  $this->linkPathsInTestInstance($pathsToLinkInTestInstance);
120  $this->setUpLocalConfiguration($configurationToUse);
121  $this->setUpPackageStates($coreExtensionsToLoad, $testExtensionsToLoad);
122  $this->setUpBasicTypo3Bootstrap();
123  $this->setUpTestDatabase();
124  \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables(TRUE);
125  $this->createDatabaseStructure();
126  }
127 
128  return $this->instancePath;
129  }
130 
137  protected function recentTestInstanceExists() {
138  if (@file_get_contents($this->instancePath . '/last_run.txt') <= (time() - 300)) {
139  return FALSE;
140  } else {
141  // Test instance exists and is pretty young -> re-use
142  return TRUE;
143  }
144  }
145 
156  protected function setUpIdentifier($testCaseClassName) {
157  $this->identifier = static::getInstanceIdentifier($testCaseClassName);
158  }
159 
166  protected function setUpInstancePath($testCaseClassName) {
167  $this->instancePath = static::getInstancePath($testCaseClassName);
168  }
169 
176  protected function removeOldInstanceIfExists() {
177  if (is_dir($this->instancePath)) {
178  $this->removeInstance();
179  }
180  }
181 
189  protected function setUpInstanceDirectories(array $additionalFoldersToCreate = array()) {
190  $foldersToCreate = array_merge($this->defaultFoldersToCreate, $additionalFoldersToCreate);
191  foreach ($foldersToCreate as $folder) {
192  $success = mkdir($this->instancePath . $folder);
193  if (!$success) {
194  throw new Exception(
195  'Creating directory failed: ' . $this->instancePath . $folder,
196  1376657189
197  );
198  }
199  }
200 
201  // Store the time we created this directory
202  file_put_contents($this->instancePath . '/last_run.txt', time());
203  }
204 
211  protected function setUpInstanceCoreLinks() {
212  $linksToSet = array(
213  ORIGINAL_ROOT . 'typo3' => $this->instancePath . '/typo3',
214  ORIGINAL_ROOT . 'index.php' => $this->instancePath . '/index.php'
215  );
216  foreach ($linksToSet as $from => $to) {
217  $success = symlink($from, $to);
218  if (!$success) {
219  throw new Exception(
220  'Creating link failed: from ' . $from . ' to: ' . $to,
221  1376657199
222  );
223  }
224  }
225  }
226 
234  protected function linkTestExtensionsToInstance(array $extensionPaths) {
235  foreach ($extensionPaths as $extensionPath) {
236  $absoluteExtensionPath = ORIGINAL_ROOT . $extensionPath;
237  if (!is_dir($absoluteExtensionPath)) {
238  throw new Exception(
239  'Test extension path ' . $absoluteExtensionPath . ' not found',
240  1376745645
241  );
242  }
243  $destinationPath = $this->instancePath . '/typo3conf/ext/'. basename($absoluteExtensionPath);
244  $success = symlink($absoluteExtensionPath, $destinationPath);
245  if (!$success) {
246  throw new Exception(
247  'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
248  1376657142
249  );
250  }
251  }
252  }
253 
264  protected function linkPathsInTestInstance(array $pathsToLinkInTestInstance) {
265  foreach ($pathsToLinkInTestInstance as $sourcePathToLinkInTestInstance => $destinationPathToLinkInTestInstance) {
266  $sourcePath = $this->instancePath . '/' . ltrim($sourcePathToLinkInTestInstance, '/');
267  if (!file_exists($sourcePath)) {
268  throw new Exception(
269  'Path ' . $sourcePath . ' not found',
270  1376745645
271  );
272  }
273  $destinationPath = $this->instancePath . '/' . ltrim($destinationPathToLinkInTestInstance, '/');
274  $success = symlink($sourcePath, $destinationPath);
275  if (!$success) {
276  throw new Exception(
277  'Can not link the path ' . $sourcePath . ' to ' . $destinationPath,
278  1389969623
279  );
280  }
281  }
282  }
283 
291  protected function setUpLocalConfiguration(array $configurationToMerge) {
292  $databaseName = trim(getenv('typo3DatabaseName'));
293  $databaseHost = trim(getenv('typo3DatabaseHost'));
294  $databaseUsername = trim(getenv('typo3DatabaseUsername'));
295  $databasePassword = trim(getenv('typo3DatabasePassword'));
296  $databasePort = trim(getenv('typo3DatabasePort'));
297  $databaseSocket = trim(getenv('typo3DatabaseSocket'));
298  if ($databaseName || $databaseHost || $databaseUsername || $databasePassword || $databasePort || $databaseSocket) {
299  // Try to get database credentials from environment variables first
300  $originalConfigurationArray = array(
301  'DB' => array(),
302  );
303  if ($databaseName) {
304  $originalConfigurationArray['DB']['database'] = $databaseName;
305  }
306  if ($databaseHost) {
307  $originalConfigurationArray['DB']['host'] = $databaseHost;
308  }
309  if ($databaseUsername) {
310  $originalConfigurationArray['DB']['username'] = $databaseUsername;
311  }
312  if ($databasePassword) {
313  $originalConfigurationArray['DB']['password'] = $databasePassword;
314  }
315  if ($databasePort) {
316  $originalConfigurationArray['DB']['port'] = $databasePort;
317  }
318  if ($databaseSocket) {
319  $originalConfigurationArray['DB']['socket'] = $databaseSocket;
320  }
321  } elseif (file_exists(ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php')) {
322  // See if a LocalConfiguration file exists in "parent" instance to get db credentials from
323  $originalConfigurationArray = require ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php';
324  } else {
325  throw new Exception(
326  'Database credentials for functional tests are neither set through environment'
327  . ' variables, and can not be found in an existing LocalConfiguration file',
328  1397406356
329  );
330  }
331 
332  // Base of final LocalConfiguration is core factory configuration
333  $finalConfigurationArray = require ORIGINAL_ROOT .'typo3/sysext/core/Configuration/FactoryConfiguration.php';
334 
335  $this->mergeRecursiveWithOverrule($finalConfigurationArray, require ORIGINAL_ROOT .'typo3/sysext/core/Build/Configuration/FunctionalTestsConfiguration.php');
336  $this->mergeRecursiveWithOverrule($finalConfigurationArray, $configurationToMerge);
337  $finalConfigurationArray['DB'] = $originalConfigurationArray['DB'];
338  // Calculate and set new database name
339  $this->originalDatabaseName = $originalConfigurationArray['DB']['database'];
340  $this->databaseName = $this->originalDatabaseName . '_ft' . $this->identifier;
341 
342  // Maximum database name length for mysql is 64 characters
343  if (strlen($this->databaseName) > 64) {
344  $maximumOriginalDatabaseName = 64 - strlen('_ft' . $this->identifier);
345  throw new Exception(
346  'The name of the database that is used for the functional test (' . $this->databaseName . ')' .
347  ' exceeds the maximum length of 64 character allowed by MySQL. You have to shorten your' .
348  ' original database name to ' . $maximumOriginalDatabaseName . ' characters',
349  1377600104
350  );
351  }
352 
353  $finalConfigurationArray['DB']['database'] = $this->databaseName;
354 
355  $result = $this->writeFile(
356  $this->instancePath . '/typo3conf/LocalConfiguration.php',
357  '<?php' . chr(10) .
358  'return ' .
359  $this->arrayExport(
360  $finalConfigurationArray
361  ) .
362  ';' . chr(10) .
363  '?>'
364  );
365  if (!$result) {
366  throw new Exception('Can not write local configuration', 1376657277);
367  }
368  }
369 
380  protected function setUpPackageStates(array $coreExtensionsToLoad, array $testExtensionPaths) {
381  $packageStates = array(
382  'packages' => array(),
383  'version' => 4,
384  );
385 
386  // Register default list of extensions and set active
387  foreach ($this->defaultActivatedCoreExtensions as $extensionName) {
388  $packageStates['packages'][$extensionName] = array(
389  'state' => 'active',
390  'packagePath' => 'typo3/sysext/' . $extensionName . '/',
391  'classesPath' => 'Classes/',
392  );
393  }
394 
395  // Register additional core extensions and set active
396  foreach ($coreExtensionsToLoad as $extensionName) {
397  if (isset($packageSates['packages'][$extensionName])) {
398  throw new Exception(
399  $extensionName . ' is already registered as default core extension to load, no need to load it explicitly',
400  1390913893
401  );
402  }
403  $packageStates['packages'][$extensionName] = array(
404  'state' => 'active',
405  'packagePath' => 'typo3/sysext/' . $extensionName . '/',
406  'classesPath' => 'Classes/',
407  );
408  }
409 
410  // Activate test extensions that have been symlinked before
411  foreach ($testExtensionPaths as $extensionPath) {
412  $extensionName = basename($extensionPath);
413  if (isset($packageSates['packages'][$extensionName])) {
414  throw new Exception(
415  $extensionName . ' is already registered as extension to load, no need to load it explicitly',
416  1390913894
417  );
418  }
419  $packageStates['packages'][$extensionName] = array(
420  'state' => 'active',
421  'packagePath' => 'typo3conf/ext/' . $extensionName . '/',
422  'classesPath' => 'Classes/',
423  );
424  }
425 
426  $result = $this->writeFile(
427  $this->instancePath . '/typo3conf/PackageStates.php',
428  '<?php' . chr(10) .
429  'return ' .
430  $this->arrayExport(
431  $packageStates
432  ) .
433  ';' . chr(10) .
434  '?>'
435  );
436  if (!$result) {
437  throw new Exception('Can not write PackageStates', 1381612729);
438  }
439  }
440 
446  protected function setUpBasicTypo3Bootstrap() {
447  $_SERVER['PWD'] = $this->instancePath;
448  $_SERVER['argv'][0] = 'index.php';
449 
450  define('TYPO3_MODE', 'BE');
451  define('TYPO3_cliMode', TRUE);
452 
453  require_once $this->instancePath . '/typo3/sysext/core/Classes/Core/CliBootstrap.php';
455 
456  require_once $this->instancePath . '/typo3/sysext/core/Classes/Core/Bootstrap.php';
458  ->baseSetup('')
459  ->loadConfigurationAndInitialize(TRUE)
460  ->loadTypo3LoadedExtAndExtLocalconf(TRUE)
461  ->applyAdditionalConfigurationSettings();
462  }
463 
470  protected function setUpTestDatabase() {
471  \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->initializeTypo3DbGlobal();
473  $database = $GLOBALS['TYPO3_DB'];
474  if(!$database->sql_pconnect()) {
475  throw new Exception(
476  'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
477  . ' connection to the database was attempted to be established!',
478  1377620117
479  );
480  }
481 
482  // Drop database in case a previous test had a fatal and did not clean up properly
483  $database->admin_query('DROP DATABASE IF EXISTS `' . $this->databaseName . '`');
484  $createDatabaseResult = $database->admin_query('CREATE DATABASE `' . $this->databaseName . '`');
485  if (!$createDatabaseResult) {
486  $user = $GLOBALS['TYPO3_CONF_VARS']['DB']['username'];
487  $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['host'];
488  throw new Exception(
489  'Unable to create database with name ' . $this->databaseName . '. This is probably a permission problem.'
490  . ' For this instance this could be fixed executing'
491  . ' "GRANT ALL ON `' . $this->originalDatabaseName . '_ft%`.* TO `' . $user . '`@`' . $host . '`;"',
492  1376579070
493  );
494  }
495  $database->setDatabaseName($this->databaseName);
496  $database->sql_select_db($this->databaseName);
497  }
498 
506  protected function initializeTestDatabase() {
507  \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->initializeTypo3DbGlobal();
509  $database = $GLOBALS['TYPO3_DB'];
510  if (!$database->sql_pconnect()) {
511  throw new Exception(
512  'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
513  . ' connection to the database was attempted to be established!',
514  1377620117
515  );
516  }
517  $this->databaseName = $GLOBALS['TYPO3_CONF_VARS']['DB']['database'];
518  $database->setDatabaseName($this->databaseName);
519  // On windows, this still works, but throws a warning, which we need to discard.
520  @$database->sql_select_db($this->databaseName);
521  foreach ($database->admin_get_tables() as $table) {
522  $database->admin_query('TRUNCATE ' . $table['Name'] . ';');
523  }
524  }
525 
531  protected function createDatabaseStructure() {
533  $schemaMigrationService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Install\\Service\\SqlSchemaMigrationService');
535  $objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Object\\ObjectManager');
537  $expectedSchemaService = $objectManager->get('TYPO3\\CMS\\Install\\Service\\SqlExpectedSchemaService');
538 
539  // Raw concatenated ext_tables.sql and friends string
540  $expectedSchemaString = $expectedSchemaService->getTablesDefinitionString(TRUE);
541  $statements = $schemaMigrationService->getStatementArray($expectedSchemaString, TRUE);
542  list($_, $insertCount) = $schemaMigrationService->getCreateTables($statements, TRUE);
543 
544  $fieldDefinitionsFile = $schemaMigrationService->getFieldDefinitions_fileContent($expectedSchemaString);
545  $fieldDefinitionsDatabase = $schemaMigrationService->getFieldDefinitions_database();
546  $difference = $schemaMigrationService->getDatabaseExtra($fieldDefinitionsFile, $fieldDefinitionsDatabase);
547  $updateStatements = $schemaMigrationService->getUpdateSuggestions($difference);
548 
549  $schemaMigrationService->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
550  $schemaMigrationService->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
551  $schemaMigrationService->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
552 
553  foreach ($insertCount as $table => $count) {
554  $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
555  foreach ($insertStatements as $insertQuery) {
556  $insertQuery = rtrim($insertQuery, ';');
558  $database = $GLOBALS['TYPO3_DB'];
559  $database->admin_query($insertQuery);
560  }
561  }
562  }
563 
570  protected function tearDownTestDatabase() {
572  $database = $GLOBALS['TYPO3_DB'];
573  $result = $database->admin_query('DROP DATABASE `' . $this->databaseName . '`');
574  if (!$result) {
575  throw new Exception(
576  'Dropping test database ' . $this->databaseName . ' failed',
577  1376583188
578  );
579  }
580  }
581 
588  protected function removeInstance() {
589  $success = $this->rmdir($this->instancePath, TRUE);
590  if (!$success) {
591  throw new Exception(
592  'Can not remove folder: ' . $this->instancePath,
593  1376657210
594  );
595  }
596  }
597 
607  protected function rmdir($path, $removeNonEmpty = FALSE) {
608  $OK = FALSE;
609  // Remove trailing slash
610  $path = preg_replace('|/$|', '', $path);
611  if (file_exists($path)) {
612  $OK = TRUE;
613  if (!is_link($path) && is_dir($path)) {
614  if ($removeNonEmpty == TRUE && ($handle = opendir($path))) {
615  while ($OK && FALSE !== ($file = readdir($handle))) {
616  if ($file == '.' || $file == '..') {
617  continue;
618  }
619  $OK = $this->rmdir($path . '/' . $file, $removeNonEmpty);
620  }
621  closedir($handle);
622  }
623  if ($OK) {
624  $OK = @rmdir($path);
625  }
626  } else {
627  // If $path is a symlink to a folder we need rmdir() on Windows systems
628  if (!stristr(PHP_OS, 'darwin') && stristr(PHP_OS, 'win') && is_link($path) && is_dir($path . '/')) {
629  $OK = rmdir($path);
630  } else {
631  $OK = unlink($path);
632  }
633  }
634  clearstatcache();
635  } elseif (is_link($path)) {
636  $OK = unlink($path);
637  clearstatcache();
638  }
639  return $OK;
640  }
641 
651  protected function writeFile($file, $content) {
652  if ($fd = fopen($file, 'wb')) {
653  $res = fwrite($fd, $content);
654  fclose($fd);
655  if ($res === FALSE) {
656  return FALSE;
657  }
658  return TRUE;
659  }
660  return FALSE;
661  }
662 
676  protected function arrayExport(array $array = array(), $level = 0) {
677  $lines = 'array(' . chr(10);
678  $level++;
679  $writeKeyIndex = FALSE;
680  $expectedKeyIndex = 0;
681  foreach ($array as $key => $value) {
682  if ($key === $expectedKeyIndex) {
683  $expectedKeyIndex++;
684  } else {
685  // Found a non integer or non consecutive key, so we can break here
686  $writeKeyIndex = TRUE;
687  break;
688  }
689  }
690  foreach ($array as $key => $value) {
691  // Indention
692  $lines .= str_repeat(chr(9), $level);
693  if ($writeKeyIndex) {
694  // Numeric / string keys
695  $lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => ';
696  }
697  if (is_array($value)) {
698  if (count($value) > 0) {
699  $lines .= $this->arrayExport($value, $level);
700  } else {
701  $lines .= 'array(),' . chr(10);
702  }
703  } elseif (is_int($value) || is_float($value)) {
704  $lines .= $value . ',' . chr(10);
705  } elseif (is_null($value)) {
706  $lines .= 'NULL' . ',' . chr(10);
707  } elseif (is_bool($value)) {
708  $lines .= $value ? 'TRUE' : 'FALSE';
709  $lines .= ',' . chr(10);
710  } elseif (is_string($value)) {
711  // Quote \ to \\
712  $stringContent = str_replace('\\', '\\\\', $value);
713  // Quote ' to \'
714  $stringContent = str_replace('\'', '\\\'', $stringContent);
715  $lines .= '\'' . $stringContent . '\'' . ',' . chr(10);
716  } else {
717  throw new \RuntimeException('Objects are not supported', 1342294986);
718  }
719  }
720  $lines .= str_repeat(chr(9), ($level - 1)) . ')' . ($level - 1 == 0 ? '' : ',' . chr(10));
721  return $lines;
722  }
723 
746  protected function mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys = TRUE, $includeEmptyValues = TRUE, $enableUnsetFeature = TRUE) {
747  foreach ($overrule as $key => $_) {
748  if ($enableUnsetFeature && $overrule[$key] === '__UNSET') {
749  unset($original[$key]);
750  continue;
751  }
752  if (isset($original[$key]) && is_array($original[$key])) {
753  if (is_array($overrule[$key])) {
754  self::mergeRecursiveWithOverrule($original[$key], $overrule[$key], $addKeys, $includeEmptyValues, $enableUnsetFeature);
755  }
756  } elseif (
757  ($addKeys || isset($original[$key])) &&
758  ($includeEmptyValues || $overrule[$key])
759  ) {
760  $original[$key] = $overrule[$key];
761  }
762  }
763  // This line is kept for backward compatibility reasons.
764  reset($original);
765  }
766 }
setUp( $testCaseClassName, array $coreExtensionsToLoad, array $testExtensionsToLoad, array $pathsToLinkInTestInstance, array $configurationToUse, array $additionalFoldersToCreate)
mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys=TRUE, $includeEmptyValues=TRUE, $enableUnsetFeature=TRUE)
if($list_of_literals) if(!empty($literals)) if(!empty($literals)) $result
Analyse literals to prepend the N char to them if their contents aren&#39;t numeric.
$host
Definition: server.php:35
$database
Definition: server.php:38
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
setUpPackageStates(array $coreExtensionsToLoad, array $testExtensionPaths)