[TASK] Refactor functional base test class 54/23154/7
authorChristian Kuhn <lolli@schwarzbu.ch>
Sat, 17 Aug 2013 14:10:03 +0000 (16:10 +0200)
committerHelmut Hummel <helmut.hummel@typo3.org>
Sat, 17 Aug 2013 15:19:11 +0000 (17:19 +0200)
Extract setUp() and tearDown() code to an own class
and start documenting the whole sytem.

Resolves: #51144
Releases: 6.2

Change-Id: Id71e9f9111f62ee04e5f8ebcec121e5e01b958b3
Reviewed-on: https://review.typo3.org/23154
Reviewed-by: Tymoteusz Motylewski
Tested-by: Tymoteusz Motylewski
Reviewed-by: Anja Leichsenring
Tested-by: Anja Leichsenring
Reviewed-by: Helmut Hummel
Tested-by: Helmut Hummel
typo3/sysext/core/Build/FunctionalTests.xml
typo3/sysext/core/Build/FunctionalTestsBootstrap.php
typo3/sysext/core/Tests/BaseTestCase.php
typo3/sysext/core/Tests/FunctionalTestCase.php
typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php [new file with mode: 0644]

index 7b7dfc0..99a22f1 100644 (file)
@@ -1,3 +1,12 @@
+<!--
+       Functional test suites setup
+
+       Functional tests should extend from \TYPO3\CMS\Core\Tests\FunctionalTestCase,
+       take a look at this class for further documentation on how to run the suite.
+
+       TYPO3 CMS functional test suite also needs phpunit bootstrap code, the
+       file is located next to this .xml as FunctionalTestsBootstrap.php
+-->
 <phpunit
        backupGlobals="true"
        backupStaticAttributes="false"
index 838fdcf..1bc6d0a 100644 (file)
  ***************************************************************/
 
 /**
- * Boostrap code for functional tests
+ * This file is defined in FuntionalTests.xml and called by phpunit
+ * before instantiating the test suites, it must also be included
+ * with phpunit parameter --bootstrap if executing single test case classes.
+ */
+
+/**
+ * Require 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.
  */
 require_once(__DIR__ . '/../Tests/BaseTestCase.php');
 require_once(__DIR__ . '/../Tests/FunctionalTestCase.php');
+require_once(__DIR__ . '/../Tests/FunctionalTestCaseBootstrapUtility.php');
 require_once(__DIR__ . '/../Tests/Exception.php');
+
+/**
+ * Define a constant to specify the document root since this calculation would
+ * be way more complicated if done within the test class.
+ *
+ * It is required that phpunit binary is called from the document root of the instance,
+ * otherwise path calculation of the created TYPO3 CMS instance will fail.
+ */
 if (!defined('ORIGINAL_ROOT')) {
-       define('ORIGINAL_ROOT', $_SERVER['PWD']);
+       define('ORIGINAL_ROOT', $_SERVER['PWD'] . '/');
 }
 ?>
\ No newline at end of file
index 6925173..134afdf 100644 (file)
@@ -48,6 +48,23 @@ abstract class BaseTestCase extends \PHPUnit_Framework_TestCase {
        protected $backupStaticAttributes = FALSE;
 
        /**
+        * Returns absolute path to the Fixture folder if called with empty
+        * $relativeFixturePath, returns path to a fixture file otherwise.
+        *
+        * Fixtures are expected to be located in a sub-folder called "Fixtures"
+        * below the test case PHP file.
+        *
+        * @param string $relativeFixtureFile
+        * @return string Absolute path with trailing slash
+        */
+       protected function getFixturePath($relativeFixtureFile = '') {
+               $reflectionClass = new \ReflectionClass(get_class($this));
+               $fileLocationOfTestClass = $reflectionClass->getFileName();
+               $path = dirname($fileLocationOfTestClass) . '/Fixtures/' . $relativeFixtureFile;
+               return $path;
+       }
+
+       /**
         * Creates a mock object which allows for calling protected methods and access of protected properties.
         *
         * @param string $originalClassName name of class to create the mock object of, must not be empty
index d11ff9a..008f9b9 100644 (file)
@@ -4,7 +4,7 @@ namespace TYPO3\CMS\Core\Tests;
 /***************************************************************
  * Copyright notice
  *
- * (c) 2005-2013 Robert Lemke (robert@typo3.org)
+ * (c) 2013 Christian Kuhn <lolli@schwarzbu.ch>
  * All rights reserved
  *
  * This script is part of the TYPO3 project. The TYPO3 project is
@@ -24,41 +24,46 @@ namespace TYPO3\CMS\Core\Tests;
  * This copyright notice MUST APPEAR in all copies of the script!
  ***************************************************************/
 
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
 /**
- * Base test case for functional tests.
+ * Base test case class for functional tests, all TYPO3 CMS
+ * functional tests should extend from this class!
  *
- * Functional tests should extend this class. It provides methods to create
- * a new database with base data and methods to fiddle with test data.
+ * # cd /var/www/t3master/foo  # Document root of CMS instance, fileadmin/ directory and frontend index.php are here
+ * #  ./typo3conf/ext/phpunit/Composer/vendor/bin/phpunit -c typo3/sysext/core/Build/FunctionalTests.xml # Call functional tests
  */
 abstract class FunctionalTestCase extends BaseTestCase {
 
        /**
-        * @var string Name of test database - Private since test cases must not fiddle with this!
-        */
-       private $testDatabaseName;
-
-       /**
-        * Array of core extension names this test depends on
+        * Core extensions to load.
         *
-        * @var array
-        */
-       protected $requiredExtensions = array();
-
-       /**
-        * Array of test/fixture extension names this test depends on
+        * If the test case needs additional core extensions as requirement,
+        * they can be noted here and will be added to LocalConfiguration
+        * extension list and ext_tables.sql of those extensions will be applied.
+        *
+        * Required core extensions like core, cms, extbase and so on are loaded
+        * automatically, so there is no need to add them here. See constant
+        * REQUIRED_EXTENSIONS for a list of automatically loaded extensions.
         *
         * @var array
         */
-       protected $requiredTestExtensions = array();
+       protected $coreExtensionsToLoad = array();
 
        /**
-        * Absolute path to the test installation root folder
+        * Array of test/fixture extensions paths that should be loaded for a test.
+        *
+        * Given path is expected to be relative to your document root, example:
         *
-        * @var string
+        * array(
+        *   'typo3conf/ext/some_extension/Tests/Functional/Fixtures/Extensions/test_extension',
+        *   'typo3conf/ext/base_extension',
+        * );
+        *
+        * Extensions in this array are linked to the test instance, loaded
+        * and their ext_tables.sql will be applied.
+        *
+        * @var array
         */
-       private $testInstallationPath;
+       protected $testExtensionsToLoad = array();
 
        /**
         * Set up creates a test database and fills with data.
@@ -68,426 +73,75 @@ abstract class FunctionalTestCase extends BaseTestCase {
         * @return void
         */
        public function setUp() {
-               $this->calculateTestInstallationPath();
-               $this->setUpTestInstallationFolderStructure();
-               $this->copyMultipleTestExtensionsToExtFolder($this->requiredTestExtensions);
-               $this->setUpLocalConfiguration();
-               $this->setUpBasicTypo3Bootstrap();
-               $this->setUpTestDatabaseConnection();
-               $this->createDatabaseStructure();
-               \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables(TRUE);
+               if (!defined('ORIGINAL_ROOT')) {
+                       $this->markTestSkipped('Functional tests must be called through phpunit on CLI');
+               }
+               $bootstrapUtility = new FunctionalTestCaseBootstrapUtility();
+               $bootstrapUtility->setUp(get_class($this), $this->coreExtensionsToLoad, $this->testExtensionsToLoad);
        }
 
        /**
         * Tear down.
         *
-        * This method should be called with parent::setUp() in your test cases!
+        * This method should be called with parent::tearDown() in your test cases!
         *
-        * @throws \TYPO3\CMS\Core\Tests\Exception
         * @return void
         */
        public function tearDown() {
-               if (empty($this->testDatabaseName)) {
-                       throw new Exception(
-                               'Test database name not set. parent::setUp called?',
-                               1376579421
-                       );
-               }
-               $this->tearDownTestDatabase();
-               $this->tearDownTestInstallationFolder();
+               $bootstrapUtility = new FunctionalTestCaseBootstrapUtility();
+               $bootstrapUtility->tearDown();
        }
 
        /**
-        * Calculates path to the test TYPO3 installation
+        * Imports a data set represented as XML into the test database,
         *
+        * @param string $path Absolute path to the XML file containing the data set to load
         * @return void
-        */
-       private function calculateTestInstallationPath() {
-               // @TODO: same id for filesystem & database name
-               $this->testInstallationPath = ORIGINAL_ROOT . '/typo3temp/'. uniqid('functional');
-       }
-
-       /**
-        * Calculates test database name based on original database name
-        *
-        * @param string $originalDatabaseName Name of original database
-        * @return void
-        */
-       private function calculateTestDatabaseName($originalDatabaseName) {
-               // @TODO: same id for filesystem & database name
-               $this->testDatabaseName = uniqid(strtolower($originalDatabaseName . '_test_'));
-       }
-
-       /**
-        * Creates folder structure of the test installation and link TYPO3 core
-        *
-        * @throws Exception
-        * @return void
-        */
-       private function setUpTestInstallationFolderStructure() {
-               $neededFolders = array(
-                       '',
-                       '/fileadmin',
-                       '/typo3temp',
-                       '/typo3conf',
-                       '/typo3conf/ext',
-                       '/uploads'
-               );
-               foreach ($neededFolders as $folder) {
-                       $success = mkdir($this->testInstallationPath . $folder);
-                       if (!$success) {
-                               throw new Exception('Can not create directory: ' . $this->testInstallationPath . $folder, 1376657189);
-                       }
-               }
-
-               $neededLinks = array(
-                       '/typo3' => '/typo3',
-                       '/index.php' => '/index.php'
-               );
-               foreach ($neededLinks as $from => $to) {
-                       $success = symlink(ORIGINAL_ROOT . $from, $this->testInstallationPath . $to);
-                       if (!$success) {
-                               throw new Exception('Can not link file : ' . ORIGINAL_ROOT . $from . ' to: ' . $this->testInstallationPath . $to, 1376657199);
-                       }
-               }
-       }
-
-       /**
-        * Create new $GLOBALS['TYPO3_DB'] on test database
-        *
-        * @throws \TYPO3\CMS\Core\Tests\Exception
-        * @return void
-        */
-       private function setUpTestDatabaseConnection() {
-               \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->initializeTypo3DbGlobal();
-               $GLOBALS['TYPO3_DB']->sql_pconnect();
-               $createDatabaseResult = $GLOBALS['TYPO3_DB']->admin_query('CREATE DATABASE `' . $this->testDatabaseName . '`');
-               if (!$createDatabaseResult) {
-                       throw new Exception(
-                               'Unable to create database with name ' . $this->testDatabaseName . ' permission problem?',
-                               1376579070
-                       );
-               }
-               $GLOBALS['TYPO3_DB']->setDatabaseName($this->testDatabaseName);
-               $GLOBALS['TYPO3_DB']->sql_select_db($this->testDatabaseName);
-       }
-
-       /**
-        * Creates LocalConfiguration.php file in the test installation
-        *
-        * @return void
-        */
-       private function setUpLocalConfiguration() {
-               $localConfigurationFile = $this->testInstallationPath . '/typo3conf/LocalConfiguration.php';
-               $originalConfigurationArray = require ORIGINAL_ROOT . '/typo3conf/LocalConfiguration.php';
-               $localConfigurationArray = require ORIGINAL_ROOT .'/typo3/sysext/core/Configuration/FactoryConfiguration.php';
-
-
-               $additionalConfiguration = array('DB' => $originalConfigurationArray['DB']);
-               $this->calculateTestDatabaseName($additionalConfiguration['DB']['database']);
-               $additionalConfiguration['DB']['database'] = $this->testDatabaseName;
-               $localConfigurationArray['DB'] = $additionalConfiguration['DB'];
-
-               $extensions = array_merge($this->requiredExtensions, $this->requiredTestExtensions);
-               $localConfigurationArray['EXT']['extListArray'] = $extensions;
-
-               $result = $this->writeFile(
-                       $localConfigurationFile,
-                       '<?php' . chr(10) .
-                       'return ' .
-                       $this->arrayExport(
-                               $localConfigurationArray
-                       ) .
-                       ';' . chr(10) .
-                       '?>'
-               );
-               if (!$result) {
-                       throw new Exception('Can not write local configuration', 1376657277);
-               }
-       }
-
-       /**
-        * Bootstrap basic TYPO3
-        *
-        * @return void
-        */
-       private function setUpBasicTypo3Bootstrap() {
-               $_SERVER['PWD'] = $this->testInstallationPath;
-               $_SERVER['argv'][0] = 'index.php';
-
-               define('TYPO3_MODE', 'BE');
-               define('TYPO3_cliMode', TRUE);
-
-               require $this->testInstallationPath . '/typo3/sysext/core/Classes/Core/CliBootstrap.php';
-               \TYPO3\CMS\Core\Core\CliBootstrap::checkEnvironmentOrDie();
-
-               require $this->testInstallationPath . '/typo3/sysext/core/Classes/Core/Bootstrap.php';
-               \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
-                       ->baseSetup('')
-                       ->loadConfigurationAndInitialize(FALSE)
-                       ->loadTypo3LoadedExtAndExtLocalconf(FALSE)
-                       ->applyAdditionalConfigurationSettings();
-       }
-
-       /**
-        * Drop the test database.
-        *
         * @throws \TYPO3\CMS\Core\Tests\Exception
-        * @return void
         */
-       private function tearDownTestDatabase() {
-               $result = $GLOBALS['TYPO3_DB']->admin_query('DROP DATABASE `' . $this->testDatabaseName . '`');
-               if (!$result) {
+       protected function importDataSet($path) {
+               if (!is_file($path)) {
                        throw new Exception(
-                               'Dropping test database ' . $this->testDatabaseName . ' failed',
-                               1376583188
+                               'Fixture file ' . $path . ' not found',
+                               1376746261
                        );
                }
-       }
-
-       /**
-        * Removes test installation folder
-        *
-        * @throws \TYPO3\CMS\Core\Tests\Exception
-        * @return void
-        */
-       private function tearDownTestInstallationFolder() {
-               $success = $this->rmdir($this->testInstallationPath, TRUE);
-               if (!$success) {
-                       throw new Exception('Can not remove folder: ' . $this->testInstallationPath, 1376657210);
-               }
-       }
 
-       /**
-        * Create tables and import static rows
-        *
-        * @return void
-        */
-       private function createDatabaseStructure() {
-               /** @var \TYPO3\CMS\Install\Service\SqlSchemaMigrationService $schemaMigrationService */
-               $schemaMigrationService = GeneralUtility::makeInstance('TYPO3\\CMS\\Install\\Service\\SqlSchemaMigrationService');
-               /** @var \TYPO3\CMS\Install\Service\SqlExpectedSchemaService $expectedSchemaService */
-               $expectedSchemaService = GeneralUtility::makeInstance('TYPO3\\CMS\\Install\\Service\\SqlExpectedSchemaService');
+               /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
+               $database = $GLOBALS['TYPO3_DB'];
 
-               // Raw concatenated ext_tables.sql and friends string
-               $expectedSchemaString = $expectedSchemaService->getTablesDefinitionString(TRUE);
-               $statements = $schemaMigrationService->getStatementArray($expectedSchemaString, TRUE);
-               list($_, $insertCount) = $schemaMigrationService->getCreateTables($statements, TRUE);
+               $xml = simplexml_load_file($path);
+               $foreignKeys = array();
 
-               $fieldDefinitionsFile = $schemaMigrationService->getFieldDefinitions_fileContent($expectedSchemaString);
-               $fieldDefinitionsDatabase = $schemaMigrationService->getFieldDefinitions_database();
-               $difference = $schemaMigrationService->getDatabaseExtra($fieldDefinitionsFile, $fieldDefinitionsDatabase);
-               $updateStatements = $schemaMigrationService->getUpdateSuggestions($difference);
+               /** @var $table \SimpleXMLElement */
+               foreach ($xml->children() as $table) {
+                       $insertArray = array();
 
-               $schemaMigrationService->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
-               $schemaMigrationService->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
-               $schemaMigrationService->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
+                       /** @var $column \SimpleXMLElement */
+                       foreach ($table->children() as $column) {
+                               $columnName = $column->getName();
+                               $columnValue = NULL;
 
-               foreach ($insertCount as $table => $count) {
-                       $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
-                       foreach ($insertStatements as $insertQuery) {
-                               $insertQuery = rtrim($insertQuery, ';');
-                               $GLOBALS['TYPO3_DB']->admin_query($insertQuery);
-                       }
-               }
-       }
-
-       /**
-        * Copy all needed test extensions to the typo3conf/ext folder of the test installation
-        *
-        * @param array $extensionNames array containing extension names (name should be the same as a folder name)
-        * @return void
-        */
-       private function copyMultipleTestExtensionsToExtFolder(array $extensionNames) {
-               foreach ($extensionNames as $extensionName) {
-                       $extensionPath = $this->getFixtureExtensionPath($extensionName);
-                       $this->copyTestExtensionToExtFolder($extensionPath);
-               }
-       }
-
-       /**
-        * Copy single single test extension to the typo3conf/ext folder of the test installation
-        *
-        * @param string $sourceFolderPath absolute path to extension
-        * @throws \TYPO3\CMS\Core\Tests\Exception
-        * @return void
-        */
-       private function copyTestExtensionToExtFolder($sourceFolderPath) {
-               if (!stristr(PHP_OS, 'darwin') && stristr(PHP_OS, 'win')) {
-                       // Windows
-                       $sourceFolderPath = rtrim($sourceFolderPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
-                       $files = GeneralUtility::getAllFilesAndFoldersInPath(array(), $sourceFolderPath, '', TRUE);
-                       $files = GeneralUtility::removePrefixPathFromList($files, $sourceFolderPath);
-
-                       foreach ($files as $fileName) {
-                               $destinationPath = $this->testInstallationPath . DIRECTORY_SEPARATOR . 'typo3conf' . DIRECTORY_SEPARATOR . 'ext'. DIRECTORY_SEPARATOR . $fileName;
-                               $success = copy($sourceFolderPath . $fileName, $destinationPath);
-                               if (!$success) {
-                                       throw new Exception('Can not copy file: ' . $fileName . ' to ' . $destinationPath, 1376657187);
-                               }
-                       }
-               } else {
-                       //linux
-                       $destinationPath = $this->testInstallationPath . DIRECTORY_SEPARATOR . 'typo3conf' . DIRECTORY_SEPARATOR . 'ext'. DIRECTORY_SEPARATOR. basename($sourceFolderPath);
-                       $success = symlink($sourceFolderPath, $destinationPath);
-                       if (!$success) {
-                               throw new Exception('Can not link folder: ' . $sourceFolderPath . ' to ' . $destinationPath, 1376657187);
-                       }
-               }
-       }
-
-       /**
-        * Returns absolute path to the fixture
-        * if called with empty $relativeFixturePath, returns path to the base folder for fixtures
-        *
-        * @param string $relativeFixturePath
-        * @return string absolute path with trailing slash
-        * @TODO: Figure out if this is useful
-        */
-       protected function getFixturePath($relativeFixturePath = '') {
-               $relativeFixturePath = !empty($relativeFixturePath) ? $relativeFixturePath . DIRECTORY_SEPARATOR : '';
-               $path = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'fixtures' . DIRECTORY_SEPARATOR . $relativeFixturePath;
-               return $path;
-       }
-
-       /**
-        * Returns absolute path to the fixture extension
-        * if called with empty name, returns path to the base folder for test extensions
-        *
-        * @param string $name
-        * @return string absolute path with trailing slash
-        * @TODO: Figure out if this is useful
-        */
-       protected function getFixtureExtensionPath($name = '') {
-               $name = !empty($name) ? $name . DIRECTORY_SEPARATOR : '';
-               $path = $this->getFixturePath() . 'extensions' . DIRECTORY_SEPARATOR . $name;
-               return $path;
-       }
-
-       /**
-        * METHODS COPIED FROM GeneralUtility
-        */
-
-       /**
-        * 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 boolean $removeNonEmpty Allow deletion of non-empty directories
-        * @return boolean TRUE if @rmdir went well!
-        */
-       private 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);
+                               if (isset($column['ref'])) {
+                                       list($tableName, $elementId) = explode('#', $column['ref']);
+                                       $columnValue = $foreignKeys[$tableName][$elementId];
+                               } elseif (isset($column['is-NULL']) && ($column['is-NULL'] === 'yes')) {
+                                       $columnValue = NULL;
+                               } else {
+                                       $columnValue = $table->$columnName;
                                }
-                       } else {
-                               // If $path is a file, simply remove it
-                               $OK = unlink($path);
-                       }
-                       clearstatcache();
-               } elseif (is_link($path)) {
-                       $OK = unlink($path);
-                       clearstatcache();
-               }
-               return $OK;
-       }
 
-       /**
-        * Writes $content to the file $file
-        *
-        * @param string $file Filepath to write to
-        * @param string $content Content to write
-        * @return boolean TRUE if the file was successfully opened and written to.
-        */
-       private function writeFile($file, $content) {
-               if ($fd = fopen($file, 'wb')) {
-                       $res = fwrite($fd, $content);
-                       fclose($fd);
-                       if ($res === FALSE) {
-                               return FALSE;
+                               $insertArray[$columnName] = $columnValue;
                        }
-                       return TRUE;
-               }
-               return FALSE;
-       }
 
-       /**
-        * METHODS COPIED FROM ArrayUtility
-        */
+                       $tableName = $table->getName();
+                       $database->exec_INSERTquery($tableName, $insertArray);
 
-       /**
-        * 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 integer $level Internal level used for recursion, do *not* set from outside!
-        * @return string String representation of array
-        * @throws \RuntimeException
-        */
-       private 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 (count($value) > 0) {
-                                       $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);
+                       if (isset($table['id'])) {
+                               $elementId = (string) $table['id'];
+                               $foreignKeys[$tableName][$elementId] = $database->sql_insert_id();
                        }
                }
-               $lines .= str_repeat(chr(9), ($level - 1)) . ')' . ($level - 1 == 0 ? '' : ',' . chr(10));
-               return $lines;
        }
 }
 ?>
\ No newline at end of file
diff --git a/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php b/typo3/sysext/core/Tests/FunctionalTestCaseBootstrapUtility.php
new file mode 100644 (file)
index 0000000..ecd6069
--- /dev/null
@@ -0,0 +1,479 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/***************************************************************
+ * Copyright notice
+ *
+ * (c) 2013 Christian Kuhn <lolli@schwarzbu.ch>
+ * All rights reserved
+ *
+ * This script is part of the TYPO3 project. The TYPO3 project is
+ * free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The GNU General Public License can be found at
+ * http://www.gnu.org/copyleft/gpl.html.
+ *
+ * This script is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+/**
+ * Utility class to set up and bootstrap TYPO3 CMS for functional tests
+ */
+class FunctionalTestCaseBootstrapUtility {
+
+       /**
+        * @var string Identifier calculated from test case class
+        */
+       static protected $identifier;
+
+       /**
+        * @var string Absolute path to test instance document root
+        */
+       static protected $instancePath;
+
+       /**
+        * @var string Name of test database
+        */
+       static protected $databaseName;
+
+       /**
+        * Set up creates a test database and fills with data.
+        *
+        * This method should be called with parent::setUp() in your test cases!
+        *
+        * @param string $testCaseClassName Name of test case class
+        * @param array $coreExtensionsToLoad Array of core extensions to load
+        * @param array $testExtensionsToLoad Array of test extensions to load
+        * @return void
+        */
+       public function setUp(
+               $testCaseClassName,
+               array $coreExtensionsToLoad,
+               array $testExtensionsToLoad
+       ) {
+               $this->setUpIdentifier($testCaseClassName);
+               $this->setUpInstancePath();
+               $this->removeOldInstanceIfExists();
+               $this->setUpInstanceDirectories();
+               $this->setUpInstanceCoreLinks();
+               $this->linkTestExtensionsToInstance($testExtensionsToLoad);
+               $this->setUpLocalConfiguration($coreExtensionsToLoad, $testExtensionsToLoad);
+               $this->setUpBasicTypo3Bootstrap();
+               $this->setUpTestDatabase();
+               $this->createDatabaseStructure();
+               \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables(TRUE);
+       }
+
+       /**
+        * Tear down.
+        *
+        * This method should be called with parent::setUp() in your test cases!
+        *
+        * @throws Exception
+        * @return void
+        */
+       public function tearDown() {
+               if (empty(static::$identifier)) {
+                       throw new Exception(
+                               'Test identifier not set. Is parent::setUp() called in setUp()?',
+                               1376739702
+                       );
+               }
+               $this->tearDownTestDatabase();
+               $this->removeInstance();
+       }
+
+       /**
+        * 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.
+        */
+       protected function setUpIdentifier($testCaseClassName) {
+               // 7 characters of sha1 should be enough for a unique identification
+               static::$identifier = substr(sha1($testCaseClassName), 0, 7);
+       }
+
+       /**
+        * Calculates path to TYPO3 CMS test installation for this test case.
+        *
+        * @return void
+        */
+       protected function setUpInstancePath() {
+               static::$instancePath = ORIGINAL_ROOT . 'typo3temp/functional-' . static::$identifier;
+       }
+
+       /**
+        * 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(static::$instancePath)) {
+                       $this->removeInstance();
+               }
+       }
+
+       /**
+        * Create folder structure of test instance.
+        *
+        * @throws Exception
+        * @return void
+        */
+       protected function setUpInstanceDirectories() {
+               $foldersToCreate = array(
+                       '',
+                       '/fileadmin',
+                       '/typo3temp',
+                       '/typo3conf',
+                       '/typo3conf/ext',
+                       '/uploads'
+               );
+               foreach ($foldersToCreate as $folder) {
+                       $success = mkdir(static::$instancePath . $folder);
+                       if (!$success) {
+                               throw new Exception(
+                                       'Creating directory failed: ' . static::$instancePath . $folder,
+                                       1376657189
+                               );
+                       }
+               }
+       }
+
+       /**
+        * Link TYPO3 CMS core from "parent" instance.
+        *
+        * @throws Exception
+        * @return void
+        */
+       protected function setUpInstanceCoreLinks() {
+               $linksToSet = array(
+                       ORIGINAL_ROOT . 'typo3' => static::$instancePath . '/typo3',
+                       ORIGINAL_ROOT . 'index.php' => static::$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) {
+                       if (!is_dir($extensionPath)) {
+                               throw new Exception(
+                                       'Test extension path ' . $extensionPath . ' not found',
+                                       1376745645
+                               );
+                       }
+                       $absoluteExtensionPath = ORIGINAL_ROOT . $extensionPath;
+                       $destinationPath = static::$instancePath . '/typo3conf/ext/'. basename($absoluteExtensionPath);
+                       $success = symlink($absoluteExtensionPath, $destinationPath);
+                       if (!$success) {
+                               throw new Exception(
+                                       'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
+                                       1376657142
+                               );
+                       }
+               }
+       }
+
+       /**
+        * Create LocalConfiguration.php file in the test instance
+        *
+        * @param array $coreExtensionsToLoad Additional core extensions to load
+        * @param array $testExtensionPaths Paths to extensions relative to document root
+        * @throws Exception
+        * @return void
+        */
+       protected function setUpLocalConfiguration(array $coreExtensionsToLoad, array $testExtensionPaths) {
+               $originalConfigurationArray = require ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php';
+               // Base of final LocalConfiguration is core factory configuration
+               $finalConfigurationArray = require ORIGINAL_ROOT .'typo3/sysext/core/Configuration/FactoryConfiguration.php';
+
+               $finalConfigurationArray['DB'] = $originalConfigurationArray['DB'];
+               // Calculate and set new database name
+               static::$databaseName = $originalConfigurationArray['DB']['database'] . '_test_' . static::$identifier;
+               $finalConfigurationArray['DB']['database'] = static::$databaseName;
+
+               // Determine list of additional extensions to load
+               $extensionNamesOfTestExtensions = array();
+               foreach ($testExtensionPaths as $path) {
+                       $extensionNamesOfTestExtensions[] = basename($path);
+               }
+               $extensionsToLoad = array_merge($coreExtensionsToLoad, $extensionNamesOfTestExtensions);
+               $finalConfigurationArray['EXT']['extListArray'] = $extensionsToLoad;
+
+               $result = $this->writeFile(
+                       static::$instancePath . '/typo3conf/LocalConfiguration.php',
+                       '<?php' . chr(10) .
+                       'return ' .
+                       $this->arrayExport(
+                               $finalConfigurationArray
+                       ) .
+                       ';' . chr(10) .
+                       '?>'
+               );
+               if (!$result) {
+                       throw new Exception('Can not write local configuration', 1376657277);
+               }
+       }
+
+       /**
+        * Bootstrap basic TYPO3
+        *
+        * @return void
+        */
+       protected function setUpBasicTypo3Bootstrap() {
+               $_SERVER['PWD'] = static::$instancePath;
+               $_SERVER['argv'][0] = 'index.php';
+
+               define('TYPO3_MODE', 'BE');
+               define('TYPO3_cliMode', TRUE);
+
+               require static::$instancePath . '/typo3/sysext/core/Classes/Core/CliBootstrap.php';
+               \TYPO3\CMS\Core\Core\CliBootstrap::checkEnvironmentOrDie();
+
+               require static::$instancePath . '/typo3/sysext/core/Classes/Core/Bootstrap.php';
+               \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
+                       ->baseSetup('')
+                       ->loadConfigurationAndInitialize(FALSE)
+                       ->loadTypo3LoadedExtAndExtLocalconf(FALSE)
+                       ->applyAdditionalConfigurationSettings();
+       }
+
+       /**
+        * 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'];
+               $database->sql_pconnect();
+               // @TODO: Test if database exists already and drop if so (eg. because of a fatal)
+               $createDatabaseResult = $database->admin_query('CREATE DATABASE `' . static::$databaseName . '`');
+               if (!$createDatabaseResult) {
+                       throw new Exception(
+                               'Unable to create database with name ' . static::$databaseName . ' permission problem?',
+                               1376579070
+                       );
+               }
+               $database->setDatabaseName(static::$databaseName);
+               $database->sql_select_db(static::$databaseName);
+       }
+
+       /**
+        * 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');
+               /** @var \TYPO3\CMS\Install\Service\SqlExpectedSchemaService $expectedSchemaService */
+               $expectedSchemaService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Install\\Service\\SqlExpectedSchemaService');
+
+               // 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() {
+               $result = $GLOBALS['TYPO3_DB']->admin_query('DROP DATABASE `' . static::$databaseName . '`');
+               if (!$result) {
+                       throw new Exception(
+                               'Dropping test database ' . static::$databaseName . ' failed',
+                               1376583188
+                       );
+               }
+       }
+
+       /**
+        * Removes instance directories and files
+        *
+        * @throws \TYPO3\CMS\Core\Tests\Exception
+        * @return void
+        */
+       protected function removeInstance() {
+               $success = $this->rmdir(static::$instancePath, TRUE);
+               if (!$success) {
+                       throw new Exception(
+                               'Can not remove folder: ' . static::$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 boolean $removeNonEmpty Allow deletion of non-empty directories
+        * @return boolean 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 file, simply remove it
+                               $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 boolean 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 integer $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 (count($value) > 0) {
+                                       $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;
+       }
+}
+?>
\ No newline at end of file