[TASK] Make base test classes always available 28/48428/6
authorHelmut Hummel <info@helhum.io>
Wed, 1 Jun 2016 17:03:13 +0000 (19:03 +0200)
committerChristian Kuhn <lolli@schwarzbu.ch>
Fri, 3 Jun 2016 12:56:51 +0000 (14:56 +0200)
To ease the usage of the core with extensions,
we move the base test classes into a different place
to be able to make them available when typo3/cms is pulled in
as dependency, e.g. for testing extensions or in projects.

This is a non breaking change, as the classes will be available
in any case after that.

By doing so, we can nicely cleanup the .gitattributes file

Resolves: #76425
Releases: master
Change-Id: I639f08a4e3e5dd73aa47570d326b72c64452acaf
Reviewed-on: https://review.typo3.org/48428
Reviewed-by: Mathias Brodala <mbrodala@pagemachine.de>
Tested-by: Mathias Brodala <mbrodala@pagemachine.de>
Reviewed-by: Elmar Hinz <t3elmar@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
15 files changed:
.gitattributes
typo3/sysext/core/Classes/Tests/AccessibleObjectInterface.php [new file with mode: 0644]
typo3/sysext/core/Classes/Tests/BaseTestCase.php [new file with mode: 0644]
typo3/sysext/core/Classes/Tests/Exception.php [new file with mode: 0644]
typo3/sysext/core/Classes/Tests/FileStreamWrapper.php [new file with mode: 0644]
typo3/sysext/core/Classes/Tests/FunctionalTestCase.php [new file with mode: 0644]
typo3/sysext/core/Classes/Tests/Testbase.php [new file with mode: 0644]
typo3/sysext/core/Classes/Tests/UnitTestCase.php [new file with mode: 0644]
typo3/sysext/core/Tests/AccessibleObjectInterface.php [deleted file]
typo3/sysext/core/Tests/BaseTestCase.php [deleted file]
typo3/sysext/core/Tests/Exception.php [deleted file]
typo3/sysext/core/Tests/FileStreamWrapper.php [deleted file]
typo3/sysext/core/Tests/FunctionalTestCase.php [deleted file]
typo3/sysext/core/Tests/Testbase.php [deleted file]
typo3/sysext/core/Tests/UnitTestCase.php [deleted file]

index a5918fe..298a76f 100644 (file)
@@ -4,33 +4,4 @@
 /.travis.yml export-ignore
 /Build/ export-ignore
 /dynamicReturnTypeMeta.json export-ignore
-/typo3/sysext/backend/Tests/ export-ignore
-/typo3/sysext/belog/Tests/ export-ignore
-/typo3/sysext/beuser/Tests/ export-ignore
-/typo3/sysext/core/Tests/Acceptance export-ignore
-/typo3/sysext/core/Tests/Functional export-ignore
-/typo3/sysext/core/Tests/Integrity export-ignore
-/typo3/sysext/core/Tests/Legacy export-ignore
-/typo3/sysext/core/Tests/Unit export-ignore
-/typo3/sysext/dbal/Tests/ export-ignore
-/typo3/sysext/documentation/Tests/ export-ignore
-/typo3/sysext/extbase/Tests/ export-ignore
-/typo3/sysext/extensionmanager/Tests/ export-ignore
-/typo3/sysext/felogin/Tests/ export-ignore
-/typo3/sysext/fluid/Tests/ export-ignore
-/typo3/sysext/form/Tests/ export-ignore
-/typo3/sysext/frontend/Tests/ export-ignore
-/typo3/sysext/impexp/Tests/ export-ignore
-/typo3/sysext/indexed_search/Tests/ export-ignore
-/typo3/sysext/install/Tests/ export-ignore
-/typo3/sysext/lang/Tests/ export-ignore
-/typo3/sysext/lowlevel/Tests/ export-ignore
-/typo3/sysext/recordlist/Tests/ export-ignore
-/typo3/sysext/recycler/Tests/ export-ignore
-/typo3/sysext/reports/Tests/ export-ignore
-/typo3/sysext/rsaauth/Tests/ export-ignore
-/typo3/sysext/saltedpasswords/Tests/ export-ignore
-/typo3/sysext/scheduler/Tests/ export-ignore
-/typo3/sysext/setup/Tests/ export-ignore
-/typo3/sysext/sv/Tests/ export-ignore
-/typo3/sysext/workspaces/Tests/ export-ignore
+/typo3/sysext/*/Tests/ export-ignore
diff --git a/typo3/sysext/core/Classes/Tests/AccessibleObjectInterface.php b/typo3/sysext/core/Classes/Tests/AccessibleObjectInterface.php
new file mode 100644 (file)
index 0000000..b655c72
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * This interface defines the methods provided by TYPO3\CMS\Core\Tests\TestCase::getAccessibleMock.::
+ */
+interface AccessibleObjectInterface
+{
+    /**
+     * Calls the method $method using call_user_func* and returns its return value.
+     *
+     * @param string $methodName name of method to call, must not be empty
+     *
+     * @return mixed the return value from the method $methodName
+     */
+    public function _call($methodName);
+
+    /**
+     * Calls the method $method without using call_user_func* and returns its return value.
+     *
+     * @param string $methodName name of method to call, must not be empty
+     * @param mixed &$arg1 first argument given to method $methodName
+     * @param mixed &$arg2 second argument given to method $methodName
+     * @param mixed &$arg3 third argument given to method $methodName
+     * @param mixed &$arg4 fourth argument given to method $methodName
+     * @param mixed &$arg5 fifth argument given to method $methodName
+     * @param mixed &$arg6 sixth argument given to method $methodName
+     * @param mixed &$arg7 seventh argument given to method $methodName
+     * @param mixed &$arg8 eighth argument given to method $methodName
+     * @param mixed &$arg9 ninth argument given to method $methodName
+     *
+     * @return mixed the return value from the method $methodName
+     */
+    public function _callRef(
+        $methodName, &$arg1 = null, &$arg2 = null, &$arg3 = null, &$arg4 = null, &$arg5= null, &$arg6 = null, &$arg7 = null,
+        &$arg8 = null, &$arg9 = null
+    );
+
+    /**
+     * Sets the value of a property.
+     *
+     * @param string $propertyName name of property to set value for, must not be empty
+     * @param mixed $value the new value for the property defined in $propertyName
+     *
+     * @return void
+     */
+    public function _set($propertyName, $value);
+
+    /**
+     * Sets the value of a property by reference.
+     *
+     * @param string $propertyName name of property to set value for, must not be empty
+     * @param mixed &$value the new value for the property defined in $propertyName
+     *
+     * @return void
+     */
+    public function _setRef($propertyName, &$value);
+
+    /**
+     * Sets the value of a static property.
+     *
+     * @param string $propertyName name of property to set value for, must not be empty
+     * @param mixed $value the new value for the property defined in $propertyName
+     *
+     * @return void
+     */
+    public function _setStatic($propertyName, $value);
+
+    /**
+     * Gets the value of the given property.
+     *
+     * @param string $propertyName name of property to return value of, must not be empty
+     *
+     * @return mixed the value of the property $propertyName
+     */
+    public function _get($propertyName);
+
+    /**
+     * Gets the value of the given static property.
+     *
+     * @param string $propertyName name of property to return value of, must not be empty
+     *
+     * @return mixed the value of the static property $propertyName
+     */
+    public function _getStatic($propertyName);
+}
diff --git a/typo3/sysext/core/Classes/Tests/BaseTestCase.php b/typo3/sysext/core/Classes/Tests/BaseTestCase.php
new file mode 100644 (file)
index 0000000..9f0f535
--- /dev/null
@@ -0,0 +1,288 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\StringUtility;
+
+/**
+ * The mother of all test cases.
+ *
+ * Don't sub class this test case but rather choose a more specialized base test case,
+ * such as UnitTestCase or FunctionalTestCase
+ *
+ */
+abstract class BaseTestCase extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * Whether global variables should be backed up
+     *
+     * @var bool
+     */
+    protected $backupGlobals = true;
+
+    /**
+     * Whether static attributes should be backed up
+     *
+     * @var bool
+     */
+    protected $backupStaticAttributes = false;
+
+    /**
+     * 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
+     * @param string[]|null $methods name of the methods to mock, null for "mock no methods"
+     * @param array $arguments arguments to pass to constructor
+     * @param string $mockClassName the class name to use for the mock class
+     * @param bool $callOriginalConstructor whether to call the constructor
+     * @param bool $callOriginalClone whether to call the __clone method
+     * @param bool $callAutoload whether to call any autoload function
+     *
+     * @return \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
+     *         a mock of $originalClassName with access methods added
+     *
+     * @throws \InvalidArgumentException
+     */
+    protected function getAccessibleMock(
+        $originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '',
+        $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true
+    ) {
+        if ($originalClassName === '') {
+            throw new \InvalidArgumentException('$originalClassName must not be empty.', 1334701880);
+        }
+
+        return $this->getMock(
+            $this->buildAccessibleProxy($originalClassName),
+            $methods,
+            $arguments,
+            $mockClassName,
+            $callOriginalConstructor,
+            $callOriginalClone,
+            $callAutoload
+        );
+    }
+
+    /**
+     * Returns a mock object which allows for calling protected methods and access
+     * of protected properties. Concrete methods to mock can be specified with
+     * the last parameter
+     *
+     * @param string $originalClassName Full qualified name of the original class
+     * @param array $arguments
+     * @param string $mockClassName
+     * @param bool $callOriginalConstructor
+     * @param bool $callOriginalClone
+     * @param bool $callAutoload
+     * @param array $mockedMethods
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
+     *
+     */
+    protected function getAccessibleMockForAbstractClass(
+        $originalClassName, array $arguments = array(), $mockClassName = '',
+        $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array()
+    ) {
+        if ($originalClassName === '') {
+            throw new \InvalidArgumentException('$originalClassName must not be empty.', 1384268260);
+        }
+
+        return $this->getMockForAbstractClass(
+            $this->buildAccessibleProxy($originalClassName),
+            $arguments,
+            $mockClassName,
+            $callOriginalConstructor,
+            $callOriginalClone,
+            $callAutoload,
+            $mockedMethods
+        );
+    }
+
+    /**
+     * Creates a proxy class of the specified class which allows
+     * for calling even protected methods and access of protected properties.
+     *
+     * @param string $className Name of class to make available, must not be empty
+     * @return string Fully qualified name of the built class, will not be empty
+     */
+    protected function buildAccessibleProxy($className)
+    {
+        $accessibleClassName = $this->getUniqueId('Tx_Phpunit_AccessibleProxy');
+        $class = new \ReflectionClass($className);
+        $abstractModifier = $class->isAbstract() ? 'abstract ' : '';
+
+        eval(
+            $abstractModifier . 'class ' . $accessibleClassName .
+                ' extends ' . $className . ' implements ' . \TYPO3\CMS\Core\Tests\AccessibleObjectInterface::class . ' {' .
+                    'public function _call($methodName) {' .
+                        'if ($methodName === \'\') {' .
+                            'throw new \InvalidArgumentException(\'$methodName must not be empty.\', 1334663993);' .
+                        '}' .
+                        '$args = func_get_args();' .
+                        'return call_user_func_array(array($this, $methodName), array_slice($args, 1));' .
+                    '}' .
+                    'public function _callRef(' .
+                        '$methodName, &$arg1 = NULL, &$arg2 = NULL, &$arg3 = NULL, &$arg4 = NULL, &$arg5= NULL, &$arg6 = NULL, ' .
+                        '&$arg7 = NULL, &$arg8 = NULL, &$arg9 = NULL' .
+                    ') {' .
+                        'if ($methodName === \'\') {' .
+                            'throw new \InvalidArgumentException(\'$methodName must not be empty.\', 1334664210);' .
+                        '}' .
+                        'switch (func_num_args()) {' .
+                            'case 0:' .
+                                'throw new RuntimeException(\'The case of 0 arguments is not supposed to happen.\', 1334703124);' .
+                                'break;' .
+                            'case 1:' .
+                                '$returnValue = $this->$methodName();' .
+                                'break;' .
+                            'case 2:' .
+                                '$returnValue = $this->$methodName($arg1);' .
+                                'break;' .
+                            'case 3:' .
+                                '$returnValue = $this->$methodName($arg1, $arg2);' .
+                                'break;' .
+                            'case 4:' .
+                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3);' .
+                                'break;' .
+                            'case 5:' .
+                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4);' .
+                                'break;' .
+                            'case 6:' .
+                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5);' .
+                                'break;' .
+                            'case 7:' .
+                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6);' .
+                                'break;' .
+                            'case 8:' .
+                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7);' .
+                                'break;' .
+                            'case 9:' .
+                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8);' .
+                                'break;' .
+                            'case 10:' .
+                                '$returnValue = $this->$methodName(' .
+                                    '$arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8, $arg9' .
+                                ');' .
+                                'break;' .
+                            'default:' .
+                                'throw new \InvalidArgumentException(' .
+                                    '\'_callRef currently only allows calls to methods with no more than 9 parameters.\'' .
+                                ');' .
+                        '}' .
+                        'return $returnValue;' .
+                    '}' .
+                    'public function _set($propertyName, $value) {' .
+                        'if ($propertyName === \'\') {' .
+                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664355);' .
+                        '}' .
+                        '$this->$propertyName = $value;' .
+                    '}' .
+                    'public function _setRef($propertyName, &$value) {' .
+                        'if ($propertyName === \'\') {' .
+                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664545);' .
+                        '}' .
+                        '$this->$propertyName = $value;' .
+                    '}' .
+                    'public function _setStatic($propertyName, $value) {' .
+                        'if ($propertyName === \'\') {' .
+                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1344242602);' .
+                        '}' .
+                        'self::$$propertyName = $value;' .
+                    '}' .
+                    'public function _get($propertyName) {' .
+                        'if ($propertyName === \'\') {' .
+                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664967);' .
+                        '}' .
+                        'return $this->$propertyName;' .
+                    '}' .
+                    'public function _getStatic($propertyName) {' .
+                        'if ($propertyName === \'\') {' .
+                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1344242603);' .
+                        '}' .
+                        'return self::$$propertyName;' .
+                    '}' .
+            '}'
+        );
+
+        return $accessibleClassName;
+    }
+
+    /**
+     * Helper function to call protected or private methods
+     *
+     * @param object $object The object to be invoked
+     * @param string $name the name of the method to call
+     * @param mixed $arguments
+     * @return mixed
+     */
+    protected function callInaccessibleMethod($object, $name, ...$arguments)
+    {
+        $reflectionObject = new \ReflectionObject($object);
+        $reflectionMethod = $reflectionObject->getMethod($name);
+        $reflectionMethod->setAccessible(true);
+        return $reflectionMethod->invokeArgs($object, $arguments);
+    }
+
+    /**
+     * Injects $dependency into property $name of $target
+     *
+     * This is a convenience method for setting a protected or private property in
+     * a test subject for the purpose of injecting a dependency.
+     *
+     * @param object $target The instance which needs the dependency
+     * @param string $name Name of the property to be injected
+     * @param mixed $dependency The dependency to inject – usually an object but can also be any other type
+     * @return void
+     * @throws \RuntimeException
+     * @throws \InvalidArgumentException
+     */
+    protected function inject($target, $name, $dependency)
+    {
+        if (!is_object($target)) {
+            throw new \InvalidArgumentException('Wrong type for argument $target, must be object.');
+        }
+
+        $objectReflection = new \ReflectionObject($target);
+        $methodNamePart = strtoupper($name[0]) . substr($name, 1);
+        if ($objectReflection->hasMethod('set' . $methodNamePart)) {
+            $methodName = 'set' . $methodNamePart;
+            $target->$methodName($dependency);
+        } elseif ($objectReflection->hasMethod('inject' . $methodNamePart)) {
+            $methodName = 'inject' . $methodNamePart;
+            $target->$methodName($dependency);
+        } elseif ($objectReflection->hasProperty($name)) {
+            $property = $objectReflection->getProperty($name);
+            $property->setAccessible(true);
+            $property->setValue($target, $dependency);
+        } else {
+            throw new \RuntimeException('Could not inject ' . $name . ' into object of type ' . get_class($target));
+        }
+    }
+
+    /**
+     * Create and return a unique id optionally prepended by a given string
+     *
+     * This function is used because on windows and in cygwin environments uniqid() has a resolution of one second which
+     * results in identical ids if simply uniqid('Foo'); is called.
+     *
+     * @param string $prefix
+     * @return string
+     */
+    protected function getUniqueId($prefix = '')
+    {
+        return $prefix . StringUtility::getUniqueId(mt_rand());
+    }
+}
diff --git a/typo3/sysext/core/Classes/Tests/Exception.php b/typo3/sysext/core/Classes/Tests/Exception.php
new file mode 100644 (file)
index 0000000..dd97aba
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * An exception - Thrown in abstract test cases to mark
+ * a test configuration or setup error.
+ */
+class Exception extends \Exception
+{
+}
diff --git a/typo3/sysext/core/Classes/Tests/FileStreamWrapper.php b/typo3/sysext/core/Classes/Tests/FileStreamWrapper.php
new file mode 100644 (file)
index 0000000..01d96a8
--- /dev/null
@@ -0,0 +1,610 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Stream wrapper for the file:// protocol
+ *
+ * Implementation details:
+ * Due to the nature of PHP, it is not possible to switch to the default handler
+ * other then restoring the default handler for file:// and registering it again
+ * around each call.
+ * It is important that the default handler is restored to allow autoloading (including)
+ * of files during the test run.
+ * For each method allowed to pass paths, the passed path is checked against the
+ * the list of paths to overlay and rewritten if needed.
+ *
+ * = Usage =
+ * <code title="Add use statements">
+ * use org\bovigo\vfs\vfsStream;
+ * use org\bovigo\vfs\visitor\vfsStreamStructureVisitor;
+ * </code>
+ *
+ * <code title="Usage in test">
+ * $root = \org\bovigo\vfs\vfsStream::setup('root');
+ * $subfolder = \org\bovigo\vfs\vfsStream::newDirectory('fileadmin');
+ * $root->addChild($subfolder);
+ * // Load fixture files and folders from disk
+ * \org\bovigo\vfs\vfsStream::copyFromFileSystem(__DIR__ . '/Fixture/Files', $subfolder, 1024*1024);
+ * FileStreamWrapper::init(PATH_site);
+ * FileStreamWrapper::registerOverlayPath('fileadmin', 'vfs://root/fileadmin');
+ *
+ * // Use file functions as usual
+ * mkdir(PATH_site . 'fileadmin/test/');
+ * $file = PATH_site . 'fileadmin/test/Foo.bar';
+ * file_put_contents($file, 'Baz');
+ * $content = file_get_contents($file);
+ * $this->assertSame('Baz', $content);
+ *
+ * $this->assertEqual(**array(file system structure as array**), vfsStream::inspect(new vfsStreamStructureVisitor())->getStructure());
+ *
+ * FileStreamWrapper::destroy();
+ * </code>
+ *
+ * @see http://www.php.net/manual/en/class.streamwrapper.php
+ */
+class FileStreamWrapper
+{
+    /**
+     * @var resource
+     */
+    protected $dirHandle = null;
+
+    /**
+     * @var resource
+     */
+    protected $fileHandle = null;
+
+    /**
+     * Switch whether class has already been registered as stream wrapper or not
+     *
+     * @type bool
+     */
+    protected static $registered = false;
+
+    /**
+     * Array of paths to overlay
+     *
+     * @var array
+     */
+    protected static $overlayPaths = array();
+
+    /**
+     * The first part of each (absolute) path that shall be ignored
+     *
+     * @var string
+     */
+    protected static $rootPath = '';
+
+    /**
+     * Initialize the stream wrapper with a root path and register itself
+     *
+     * @param $rootPath
+     * @return void
+     */
+    public static function init($rootPath)
+    {
+        self::$rootPath = rtrim(str_replace('\\', '/', $rootPath), '/') . '/';
+        self::register();
+    }
+
+    /**
+     * Unregister the stream wrapper and reset all static members to their default values
+     * @return void
+     */
+    public static function destroy()
+    {
+        self::$overlayPaths = array();
+        self::$rootPath = '';
+        if (self::$registered) {
+            self::restore();
+        }
+    }
+
+    /**
+     * Register a path relative to the root path (set in init) to be overlaid
+     *
+     * @param string $overlay Relative path to the root folder
+     * @param string $replace The path that should replace the overlay path
+     * @param bool $createFolder TRUE of the folder should be created (mkdir)
+     * @return void
+     */
+    public static function registerOverlayPath($overlay, $replace, $createFolder = true)
+    {
+        $overlay = trim(str_replace('\\', '/', $overlay), '/') . '/';
+        $replace = rtrim(str_replace('\\', '/', $replace), '/') . '/';
+        self::$overlayPaths[$overlay] = $replace;
+        if ($createFolder) {
+            mkdir($replace);
+        }
+    }
+
+    /**
+     * Checks and overlays a path
+     *
+     * @param string $path The path to check
+     * @return string The potentially overlaid path
+     */
+    protected static function overlayPath($path)
+    {
+        $path = str_replace('\\', '/', $path);
+        $hasOverlay = false;
+        if (strpos($path, self::$rootPath) !== 0) {
+            // Path is not below root path, ignore it
+            return $path;
+        }
+
+        $newPath = ltrim(substr($path, strlen(self::$rootPath)), '/');
+        foreach (self::$overlayPaths as $overlay => $replace) {
+            if (strpos($newPath, $overlay) === 0) {
+                $newPath = $replace . substr($newPath, strlen($overlay));
+                $hasOverlay = true;
+                break;
+            }
+        }
+        return $hasOverlay ? $newPath : $path;
+    }
+
+    /**
+     * Method to register the stream wrapper
+     *
+     * If the stream is already registered the method returns silently. If there
+     * is already another stream wrapper registered for the scheme used by
+     * file:// scheme a \BadFunctionCallException will be thrown.
+     *
+     * @throws \BadFunctionCallException
+     * @return void
+     */
+    protected static function register()
+    {
+        if (self::$registered) {
+            return;
+        }
+
+        if (@stream_wrapper_unregister('file') === false) {
+            throw new \BadFunctionCallException('Cannot unregister file:// stream wrapper.', 1396340331);
+        }
+        if (@stream_wrapper_register('file', __CLASS__) === false) {
+            throw new \BadFunctionCallException('A handler has already been registered for the file:// scheme.', 1396340332);
+        }
+
+        self::$registered = true;
+    }
+
+    /**
+     * Restore the file handler
+     *
+     * @return void
+     */
+    protected static function restore()
+    {
+        if (!self::$registered) {
+            return;
+        }
+        if (@stream_wrapper_restore('file') === false) {
+            throw new \BadFunctionCallException('Cannot restore the default file:// stream handler.', 1396340333);
+        }
+        self::$registered = false;
+    }
+
+    /*
+     * The following list of functions is implemented as of
+     * @see http://www.php.net/manual/en/streamwrapper.dir-closedir.php
+     */
+
+    /**
+     * Close the directory
+     *
+     * @return bool
+     */
+    public function dir_closedir()
+    {
+        if ($this->dirHandle === null) {
+            return false;
+        } else {
+            self::restore();
+            closedir($this->dirHandle);
+            self::register();
+            $this->dirHandle = null;
+            return true;
+        }
+    }
+
+    /**
+     * Opens a directory for reading
+     *
+     * @param string $path
+     * @param int $options
+     * @return bool
+     */
+    public function dir_opendir($path, $options = 0)
+    {
+        if ($this->dirHandle !== null) {
+            return false;
+        }
+        self::restore();
+        $path = self::overlayPath($path);
+        $this->dirHandle = opendir($path);
+        self::register();
+        return $this->dirHandle !== false;
+    }
+
+    /**
+     * Read a single filename of a directory
+     *
+     * @return string|bool
+     */
+    public function dir_readdir()
+    {
+        if ($this->dirHandle === null) {
+            return false;
+        }
+        self::restore();
+        $success = readdir($this->dirHandle);
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Reset directory name pointer
+     *
+     * @return bool
+     */
+    public function dir_rewinddir()
+    {
+        if ($this->dirHandle === null) {
+            return false;
+        }
+        self::restore();
+        rewinddir($this->dirHandle);
+        self::register();
+        return true;
+    }
+
+    /**
+     * Create a directory
+     *
+     * @param string $path
+     * @param int $mode
+     * @param int $options
+     * @return bool
+     */
+    public function mkdir($path, $mode, $options = 0)
+    {
+        self::restore();
+        $path = self::overlayPath($path);
+        $success = mkdir($path, $mode, (bool)($options & STREAM_MKDIR_RECURSIVE));
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Rename a file
+     *
+     * @param string $pathFrom
+     * @param string $pathTo
+     * @return bool
+     */
+    public function rename($pathFrom, $pathTo)
+    {
+        self::restore();
+        $pathFrom = self::overlayPath($pathFrom);
+        $pathTo = self::overlayPath($pathTo);
+        $success = rename($pathFrom, $pathTo);
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Remove a directory
+     *
+     * @param string $path
+     * @return bool
+     */
+    public function rmdir($path)
+    {
+        self::restore();
+        $path = self::overlayPath($path);
+        $success = rmdir($path);
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Retrieve the underlying resource
+     *
+     * @param int $castAs Can be STREAM_CAST_FOR_SELECT when stream_select()
+     * is calling stream_cast() or STREAM_CAST_AS_STREAM when stream_cast()
+     * is called for other uses.
+     * @return resource|bool
+     */
+    public function stream_cast($castAs)
+    {
+        if ($this->fileHandle !== null && $castAs & STREAM_CAST_AS_STREAM) {
+            return $this->fileHandle;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Close a file
+     *
+     */
+    public function stream_close()
+    {
+        self::restore();
+        if ($this->fileHandle !== null) {
+            fclose($this->fileHandle);
+            $this->fileHandle = null;
+        }
+        self::register();
+    }
+
+    /**
+     * Test for end-of-file on a file pointer
+     *
+     * @return bool
+     */
+    public function stream_eof()
+    {
+        if ($this->fileHandle === null) {
+            return false;
+        }
+        self::restore();
+        $success = feof($this->fileHandle);
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Flush the output
+     *
+     * @return bool
+     */
+    public function stream_flush()
+    {
+        if ($this->fileHandle === null) {
+            return false;
+        }
+        self::restore();
+        $success = fflush($this->fileHandle);
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Advisory file locking
+     *
+     * @param int $operation
+     * @return bool
+     */
+    public function stream_lock($operation)
+    {
+        if ($this->fileHandle === null) {
+            return false;
+        }
+        self::restore();
+        $success = flock($this->fileHandle, $operation);
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Change file options
+     *
+     * @param string $path
+     * @param int $options
+     * @param mixed $value
+     * @return bool
+     */
+    public function stream_metadata($path, $options, $value)
+    {
+        self::restore();
+        $path = self::overlayPath($path);
+        switch ($options) {
+            case STREAM_META_TOUCH:
+                $success = touch($path, $value[0], $value[1]);
+                break;
+            case STREAM_META_OWNER_NAME:
+                // Fall through
+            case STREAM_META_OWNER:
+                $success = chown($path, $value);
+                break;
+            case STREAM_META_GROUP_NAME:
+                // Fall through
+            case STREAM_META_GROUP:
+                $success = chgrp($path, $value);
+                break;
+            case STREAM_META_ACCESS:
+                $success = chmod($path, $value);
+                break;
+            default:
+                $success = false;
+        }
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Open a file
+     *
+     * @param string $path
+     * @param string $mode
+     * @param int $options
+     * @param string &$opened_path
+     * @return bool
+     */
+    public function stream_open($path, $mode, $options, &$opened_path)
+    {
+        if ($this->fileHandle !== null) {
+            return false;
+        }
+        self::restore();
+        $path = self::overlayPath($path);
+        $this->fileHandle = fopen($path, $mode, (bool)($options & STREAM_USE_PATH));
+        self::register();
+        return $this->fileHandle !== false;
+    }
+
+    /**
+     * Read from a file
+     *
+     * @param int $length
+     * @return string
+     */
+    public function stream_read($length)
+    {
+        if ($this->fileHandle === null) {
+            return false;
+        }
+        self::restore();
+        $content = fread($this->fileHandle, $length);
+        self::register();
+        return $content;
+    }
+
+    /**
+     * Seek to specific location in a stream
+     *
+     * @param int $offset
+     * @param int $whence = SEEK_SET
+     * @return bool
+     */
+    public function stream_seek($offset, $whence = SEEK_SET)
+    {
+        if ($this->fileHandle === null) {
+            return false;
+        }
+        self::restore();
+        $success = fseek($this->fileHandle, $offset, $whence);
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Change stream options (not implemented)
+     *
+     * @param int $option
+     * @param int $arg1
+     * @param int $arg2
+     * @return bool
+     */
+    public function stream_set_option($option, $arg1, $arg2)
+    {
+        return false;
+    }
+
+    /**
+     * Retrieve information about a file resource
+     *
+     * @return array
+     */
+    public function stream_stat()
+    {
+        if ($this->fileHandle === null) {
+            return false;
+        }
+        self::restore();
+        $stats = fstat($this->fileHandle);
+        self::register();
+        return $stats;
+    }
+
+    /**
+     * Retrieve the current position of a stream
+     *
+     * @return int
+     */
+    public function stream_tell()
+    {
+        if ($this->fileHandle === null) {
+            return false;
+        }
+        self::restore();
+        $position = ftell($this->fileHandle);
+        self::register();
+        return $position;
+    }
+
+    /**
+     * Truncates a file to the given size
+     *
+     * @param int $size Truncate to this size
+     * @return bool
+     */
+    public function stream_truncate($size)
+    {
+        if ($this->fileHandle === null) {
+            return false;
+        }
+        self::restore();
+        $success = ftruncate($this->fileHandle, $size);
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Write to stream
+     *
+     * @param string $data
+     * @return int
+     */
+    public function stream_write($data)
+    {
+        if ($this->fileHandle === null) {
+            return false;
+        }
+        self::restore();
+        $length = fwrite($this->fileHandle, $data);
+        self::register();
+        return $length;
+    }
+
+    /**
+     * Unlink a file
+     *
+     * @param string $path
+     * @return bool
+     */
+    public function unlink($path)
+    {
+        self::restore();
+        $path = self::overlayPath($path);
+        $success = unlink($path);
+        self::register();
+        return $success;
+    }
+
+    /**
+     * Retrieve information about a file
+     *
+     * @param string $path
+     * @param int $flags
+     * @return array
+     */
+    public function url_stat($path, $flags)
+    {
+        self::restore();
+        $path = self::overlayPath($path);
+        if ($flags & STREAM_URL_STAT_LINK) {
+            $information = @lstat($path);
+        } else {
+            $information = @stat($path);
+        }
+        self::register();
+        return $information;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Tests/FunctionalTestCase.php b/typo3/sysext/core/Classes/Tests/FunctionalTestCase.php
new file mode 100644 (file)
index 0000000..9e60acd
--- /dev/null
@@ -0,0 +1,404 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Base test case class for functional tests, all TYPO3 CMS
+ * functional tests should extend from this class!
+ *
+ * If functional tests need additional setUp() and tearDown() code,
+ * they *must* call parent::setUp() and parent::tearDown() to properly
+ * set up and destroy the test system.
+ *
+ * The functional test system creates a full new TYPO3 CMS instance
+ * within typo3temp/ of the base system and the bootstraps this TYPO3 instance.
+ * This abstract class takes care of creating this instance with its
+ * folder structure and a LocalConfiguration, creates an own database
+ * for each test run and imports tables of loaded extensions.
+ *
+ * Functional tests must be run standalone (calling native phpunit
+ * directly) and can not be executed by eg. the ext:phpunit backend module.
+ * Additionally, the script must be called from the document root
+ * of the instance, otherwise path calculation is not successfully.
+ *
+ * Call whole functional test suite, example:
+ * - cd /var/www/t3master/foo  # Document root of CMS instance, here is index.php of frontend
+ * - typo3/../bin/phpunit -c typo3/sysext/core/Build/FunctionalTests.xml
+ *
+ * Call single test case, example:
+ * - cd /var/www/t3master/foo  # Document root of CMS instance, here is index.php of frontend
+ * - typo3/../bin/phpunit \
+ *     --process-isolation \
+ *     --bootstrap typo3/sysext/core/Build/FunctionalTestsBootstrap.php \
+ *     typo3/sysext/core/Tests/Functional/DataHandling/DataHandlerTest.php
+ */
+abstract class FunctionalTestCase extends BaseTestCase
+{
+
+    /**
+     * An unique identifier for this test case. Location of the test
+     * instance and database name depend on this. Calculated early in setUp()
+     *
+     * @var string
+     */
+    protected $identifier;
+
+    /**
+     * Absolute path to test instance document root. Depends on $identifier.
+     * Calculated early in setUp()
+     *
+     * @var string
+     */
+    protected $instancePath;
+
+    /**
+     * Core extensions to load.
+     *
+     * 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.
+     *
+     * This property will stay empty in this abstract, so it is possible
+     * to just overwrite it in extending classes. Extensions noted here will
+     * be loaded for every test of a test case and it is not possible to change
+     * the list of loaded extensions between single tests of a test case.
+     *
+     * A default list of core extensions is always loaded.
+     *
+     * @see FunctionalTestCaseUtility $defaultActivatedCoreExtensions
+     * @var array
+     */
+    protected $coreExtensionsToLoad = [];
+
+    /**
+     * Array of test/fixture extensions paths that should be loaded for a test.
+     *
+     * This property will stay empty in this abstract, so it is possible
+     * to just overwrite it in extending classes. Extensions noted here will
+     * be loaded for every test of a test case and it is not possible to change
+     * the list of loaded extensions between single tests of a test case.
+     *
+     * Given path is expected to be relative to your document root, example:
+     *
+     * 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
+     */
+    protected $testExtensionsToLoad = [];
+
+    /**
+     * Array of test/fixture folder or file paths that should be linked for a test.
+     *
+     * This property will stay empty in this abstract, so it is possible
+     * to just overwrite it in extending classes. Path noted here will
+     * be linked for every test of a test case and it is not possible to change
+     * the list of folders between single tests of a test case.
+     *
+     * array(
+     *   'link-source' => 'link-destination'
+     * );
+     *
+     * Given paths are expected to be relative to the test instance root.
+     * The array keys are the source paths and the array values are the destination
+     * paths, example:
+     *
+     * array(
+     *   'typo3/sysext/impext/Tests/Functional/Fixtures/Folders/fileadmin/user_upload' =>
+     *   'fileadmin/user_upload',
+     *   'typo3conf/ext/my_own_ext/Tests/Functional/Fixtures/Folders/uploads/tx_myownext' =>
+     *   'uploads/tx_myownext'
+     * );
+     *
+     * To be able to link from my_own_ext the extension path needs also to be registered in
+     * property $testExtensionsToLoad
+     *
+     * @var array
+     */
+    protected $pathsToLinkInTestInstance = [];
+
+    /**
+     * This configuration array is merged with TYPO3_CONF_VARS
+     * that are set in default configuration and factory configuration
+     *
+     * @var array
+     */
+    protected $configurationToUseInTestInstance = [];
+
+    /**
+     * Array of folders that should be created inside the test instance document root.
+     *
+     * This property will stay empty in this abstract, so it is possible
+     * to just overwrite it in extending classes. Path noted here will
+     * be linked for every test of a test case and it is not possible to change
+     * the list of folders between single tests of a test case.
+     *
+     * Per default the following folder are created
+     * /fileadmin
+     * /typo3temp
+     * /typo3conf
+     * /typo3conf/ext
+     * /uploads
+     *
+     * To create additional folders add the paths to this array. Given paths are expected to be
+     * relative to the test instance root and have to begin with a slash. Example:
+     *
+     * array(
+     *   'fileadmin/user_upload'
+     * );
+     *
+     * @var array
+     */
+    protected $additionalFoldersToCreate = [];
+
+    /**
+     * The fixture which is used when initializing a backend user
+     *
+     * @var string
+     */
+    protected $backendUserFixture = 'typo3/sysext/core/Tests/Functional/Fixtures/be_users.xml';
+
+    /**
+     * Set up creates a test instance and database.
+     *
+     * This method should be called with parent::setUp() in your test cases!
+     *
+     * @return void
+     */
+    protected function setUp()
+    {
+        if (!defined('ORIGINAL_ROOT')) {
+            $this->markTestSkipped('Functional tests must be called through phpunit on CLI');
+        }
+
+        // Use a 7 char long hash of class name as identifier
+        $this->identifier = substr(sha1(get_class($this)), 0, 7);
+        $this->instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/functional-' . $this->identifier;
+
+        $testbase = new Testbase();
+        $testbase->defineTypo3ModeBe();
+        $testbase->setTypo3TestingContext();
+        if ($testbase->recentTestInstanceExists($this->instancePath)) {
+            // Reusing an existing instance. This typically happens for the second, third, ... test
+            // in a test case, so environment is set up only once per test case.
+            $testbase->setUpBasicTypo3Bootstrap($this->instancePath);
+            $testbase->initializeTestDatabaseAndTruncateTables();
+            $testbase->loadExtensionTables();
+        } else {
+            $testbase->removeOldInstanceIfExists($this->instancePath);
+            // Basic instance directory structure
+            $testbase->createDirectory($this->instancePath . '/fileadmin');
+            $testbase->createDirectory($this->instancePath . '/typo3temp/var/transient');
+            $testbase->createDirectory($this->instancePath . '/typo3temp/assets');
+            $testbase->createDirectory($this->instancePath . '/typo3conf/ext');
+            $testbase->createDirectory($this->instancePath . '/uploads');
+            // Additionally requested directories
+            foreach ($this->additionalFoldersToCreate as $directory) {
+                $testbase->createDirectory($this->instancePath . '/' . $directory);
+            }
+            $testbase->createLastRunTextfile($this->instancePath);
+            $testbase->setUpInstanceCoreLinks($this->instancePath);
+            $testbase->linkTestExtensionsToInstance($this->instancePath, $this->testExtensionsToLoad);
+            $testbase->linkPathsInTestInstance($this->instancePath, $this->pathsToLinkInTestInstance);
+            $localConfiguration = $testbase->getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration();
+            $originalDatabaseName = $localConfiguration['DB']['Connections']['Default']['dbname'];
+            // Append the unique identifier to the base database name to end up with a single database per test case
+            $localConfiguration['DB']['Connections']['Default']['dbname'] = $originalDatabaseName . '_ft' . $this->identifier;
+            $testbase->testDatabaseNameIsNotTooLong($originalDatabaseName, $localConfiguration);
+            // Set some hard coded base settings for the instance. Those could be overruled by
+            // $this->configurationToUseInTestInstance if needed again.
+            $localConfiguration['SYS']['isInitialInstallationInProgress'] = false;
+            $localConfiguration['SYS']['isInitialDatabaseImportDone'] = true;
+            $localConfiguration['SYS']['displayErrors'] = '1';
+            $localConfiguration['SYS']['debugExceptionHandler'] = '';
+            $localConfiguration['SYS']['trustedHostsPattern'] = '.*';
+            $localConfiguration['SYS']['setDBinit'] = 'SET SESSION sql_mode = \'STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_VALUE_ON_ZERO,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ONLY_FULL_GROUP_BY\';';
+            $testbase->setUpLocalConfiguration($this->instancePath, $localConfiguration, $this->configurationToUseInTestInstance);
+            $defaultCoreExtensionsToLoad = [
+                'core',
+                'backend',
+                'frontend',
+                'lang',
+                'extbase',
+                'install',
+            ];
+            $testbase->setUpPackageStates($this->instancePath, $defaultCoreExtensionsToLoad, $this->coreExtensionsToLoad, $this->testExtensionsToLoad);
+            $testbase->setUpBasicTypo3Bootstrap($this->instancePath);
+            $testbase->setUpTestDatabase($localConfiguration['DB']['Connections']['Default']['dbname'], $originalDatabaseName);
+            $testbase->loadExtensionTables();
+            $testbase->createDatabaseStructure();
+        }
+    }
+
+    /**
+     * Get DatabaseConnection instance - $GLOBALS['TYPO3_DB']
+     *
+     * This method should be used instead of direct access to
+     * $GLOBALS['TYPO3_DB'] for easy IDE auto completion.
+     *
+     * @return \TYPO3\CMS\Core\Database\DatabaseConnection
+     */
+    protected function getDatabaseConnection()
+    {
+        return $GLOBALS['TYPO3_DB'];
+    }
+
+    /**
+     * Initialize backend user
+     *
+     * @param int $userUid uid of the user we want to initialize. This user must exist in the fixture file
+     * @return BackendUserAuthentication
+     * @throws Exception
+     */
+    protected function setUpBackendUserFromFixture($userUid)
+    {
+        $this->importDataSet(ORIGINAL_ROOT . $this->backendUserFixture);
+        $database = $this->getDatabaseConnection();
+        $userRow = $database->exec_SELECTgetSingleRow('*', 'be_users', 'uid = ' . (int)$userUid);
+
+        /** @var $backendUser BackendUserAuthentication */
+        $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
+        $sessionId = $backendUser->createSessionId();
+        $_COOKIE['be_typo_user'] = $sessionId;
+        $backendUser->id = $sessionId;
+        $backendUser->sendNoCacheHeaders = false;
+        $backendUser->dontSetCookie = true;
+        $backendUser->createUserSession($userRow);
+
+        $GLOBALS['BE_USER'] = $backendUser;
+        $GLOBALS['BE_USER']->start();
+        if (!is_array($GLOBALS['BE_USER']->user) || !$GLOBALS['BE_USER']->user['uid']) {
+            throw new Exception(
+                'Can not initialize backend user',
+                1377095807
+            );
+        }
+        $GLOBALS['BE_USER']->backendCheckLogin();
+
+        return $backendUser;
+    }
+
+    /**
+     * 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
+     * @throws Exception
+     */
+    protected function importDataSet($path)
+    {
+        $testbase = new Testbase();
+        $testbase->importXmlDatabaseFixture($path);
+    }
+
+    /**
+     * @param int $pageId
+     * @param array $typoScriptFiles
+     */
+    protected function setUpFrontendRootPage($pageId, array $typoScriptFiles = array())
+    {
+        $pageId = (int)$pageId;
+        $page = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'pages', 'uid=' . $pageId);
+
+        if (empty($page)) {
+            $this->fail('Cannot set up frontend root page "' . $pageId . '"');
+        }
+
+        $pagesFields = array(
+            'is_siteroot' => 1
+        );
+
+        $this->getDatabaseConnection()->exec_UPDATEquery('pages', 'uid=' . $pageId, $pagesFields);
+
+        $templateFields = array(
+            'pid' => $pageId,
+            'title' => '',
+            'config' => '',
+            'clear' => 3,
+            'root' => 1,
+        );
+
+        foreach ($typoScriptFiles as $typoScriptFile) {
+            $templateFields['config'] .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . LF;
+        }
+
+        $this->getDatabaseConnection()->exec_INSERTquery('sys_template', $templateFields);
+    }
+
+    /**
+     * @param int $pageId
+     * @param int $languageId
+     * @param int $backendUserId
+     * @param int $workspaceId
+     * @param bool $failOnFailure
+     * @param int $frontendUserId
+     * @return Response
+     */
+    protected function getFrontendResponse($pageId, $languageId = 0, $backendUserId = 0, $workspaceId = 0, $failOnFailure = true, $frontendUserId = 0)
+    {
+        $pageId = (int)$pageId;
+        $languageId = (int)$languageId;
+
+        $additionalParameter = '';
+
+        if (!empty($frontendUserId)) {
+            $additionalParameter .= '&frontendUserId=' . (int)$frontendUserId;
+        }
+        if (!empty($backendUserId)) {
+            $additionalParameter .= '&backendUserId=' . (int)$backendUserId;
+        }
+        if (!empty($workspaceId)) {
+            $additionalParameter .= '&workspaceId=' . (int)$workspaceId;
+        }
+
+        $arguments = array(
+            'documentRoot' => $this->instancePath,
+            'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter,
+        );
+
+        $template = new \Text_Template(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/request.tpl');
+        $template->setVar(
+            array(
+                'arguments' => var_export($arguments, true),
+                'originalRoot' => ORIGINAL_ROOT,
+            )
+        );
+
+        $php = \PHPUnit_Util_PHP::factory();
+        $response = $php->runJob($template->render());
+        $result = json_decode($response['stdout'], true);
+
+        if ($result === null) {
+            $this->fail('Frontend Response is empty');
+        }
+
+        if ($failOnFailure && $result['status'] === Response::STATUS_Failure) {
+            $this->fail('Frontend Response has failure:' . LF . $result['error']);
+        }
+
+        $response = new Response($result['status'], $result['content'], $result['error']);
+        return $response;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Tests/Testbase.php b/typo3/sysext/core/Classes/Tests/Testbase.php
new file mode 100644 (file)
index 0000000..199c478
--- /dev/null
@@ -0,0 +1,723 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Object\ObjectManager;
+use TYPO3\CMS\Install\Service\SqlExpectedSchemaService;
+use TYPO3\CMS\Install\Service\SqlSchemaMigrationService;
+
+/**
+ * This is a helper class used by unit, functional and acceptance test
+ * environment builders.
+ * It contains methods to create test environments.
+ *
+ * This class is for internal use only and may change wihtout further notice.
+ *
+ * Use the classes "UnitTestCase", "FunctionalTestCase" or "AcceptanceCoreEnvironment"
+ * to indirectly benefit from this class in own extensions.
+ */
+class Testbase
+{
+    /**
+     * This class must be called in CLI environment as a security measure
+     * against path disclosures and other stuff. Check this within
+     * constructor to make sure this check can't be circumvented.
+     */
+    public function __construct()
+    {
+        // Ensure cli only as security measure
+        if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
+            die('This script supports command line usage only. Please check your command.');
+        }
+    }
+
+    /**
+     * Makes sure error messages during the tests get displayed no matter what is set in php.ini.
+     *
+     * @return void
+     */
+    public function enableDisplayErrors()
+    {
+        @ini_set('display_errors', 1);
+    }
+
+    /**
+     * Defines a list of basic constants that are used by GeneralUtility and other
+     * helpers during tests setup. Those are sanitized in SystemEnvironmentBuilder
+     * to be not defined again.
+     *
+     * @return void
+     * @see SystemEnvironmentBuilder::defineBaseConstants()
+     */
+    public function defineBaseConstants()
+    {
+        // A null, a tabulator, a linefeed, a carriage return, a substitution, a CR-LF combination
+        defined('NUL') ?: define('NUL', chr(0));
+        defined('TAB') ?: define('TAB', chr(9));
+        defined('LF') ?: define('LF', chr(10));
+        defined('CR') ?: define('CR', chr(13));
+        defined('SUB') ?: define('SUB', chr(26));
+        defined('CRLF') ?: define('CRLF', CR . LF);
+
+        if (!defined('TYPO3_OS')) {
+            // Operating system identifier
+            // Either "WIN" or empty string
+            $typoOs = '';
+            if (!stristr(PHP_OS, 'darwin') && !stristr(PHP_OS, 'cygwin') && stristr(PHP_OS, 'win')) {
+                $typoOs = 'WIN';
+            }
+            define('TYPO3_OS', $typoOs);
+        }
+    }
+
+    /**
+     * Defines the PATH_site and PATH_thisScript constant and sets $_SERVER['SCRIPT_NAME'].
+     * For unit tests only
+     *
+     * @return void
+     */
+    public function defineSitePath()
+    {
+        define('PATH_site', $this->getWebRoot());
+        define('PATH_thisScript', PATH_site . 'typo3/cli_dispatch.phpsh');
+        $_SERVER['SCRIPT_NAME'] = PATH_thisScript;
+
+        if (!file_exists(PATH_thisScript)) {
+            $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.');
+        }
+    }
+
+    /**
+     * Defines the constant ORIGINAL_ROOT for the path to the original TYPO3 document root.
+     * For functional / acceptance tests only
+     * If ORIGINAL_ROOT already is defined, this method is a no-op.
+     *
+     * @return void
+     */
+    public function defineOriginalRootPath()
+    {
+        if (!defined('ORIGINAL_ROOT')) {
+            define('ORIGINAL_ROOT', $this->getWebRoot());
+        }
+
+        if (!file_exists(ORIGINAL_ROOT . 'typo3/cli_dispatch.phpsh')) {
+            $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.');
+        }
+    }
+
+    /**
+     * Define TYPO3_MODE to BE
+     *
+     * @return void
+     */
+    public function defineTypo3ModeBe()
+    {
+        define('TYPO3_MODE', 'BE');
+    }
+
+    /**
+     * Sets the environment variable TYPO3_CONTEXT to testing.
+     *
+     * @return void
+     */
+    public function setTypo3TestingContext()
+    {
+        putenv('TYPO3_CONTEXT=Testing');
+    }
+
+    /**
+     * Creates directories, recursively if required.
+     *
+     * @param string $directory Absolute path to directories to create
+     * @return void
+     * @throws Exception
+     */
+    public function createDirectory($directory)
+    {
+        if (is_dir($directory)) {
+            return;
+        }
+        @mkdir($directory, 0777, true);
+        clearstatcache();
+        if (!is_dir($directory)) {
+            throw new Exception('Directory "' . $directory . '" could not be created', 1404038665);
+        }
+    }
+
+    /**
+     * Checks whether given test instance exists in path and is younger than some minutes.
+     * Used in functional tests
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @return bool
+     */
+    public function recentTestInstanceExists($instancePath)
+    {
+        if (@file_get_contents($instancePath . '/last_run.txt') <= (time() - 300)) {
+            return false;
+        } else {
+            // Test instance exists and is pretty young -> re-use
+            return true;
+        }
+    }
+
+    /**
+     * Remove test instance folder structure if it exists.
+     * This may happen if a functional test before threw a fatal or is too old
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @return void
+     * @throws Exception
+     */
+    public function removeOldInstanceIfExists($instancePath)
+    {
+        if (is_dir($instancePath)) {
+            $success = GeneralUtility::rmdir($instancePath, true);
+            if (!$success) {
+                throw new Exception(
+                    'Can not remove folder: ' . $instancePath,
+                    1376657210
+                );
+            }
+        }
+    }
+
+    /**
+     * Create last_run.txt file within instance path containing timestamp of "now".
+     * Used in functional tests to reuse an instance for multiple tests in one test case.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @return void
+     */
+    public function createLastRunTextfile($instancePath)
+    {
+        // Store the time instance was created
+        file_put_contents($instancePath . '/last_run.txt', time());
+    }
+
+    /**
+     * Link TYPO3 CMS core from "parent" instance.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @throws Exception
+     * @return void
+     */
+    public function setUpInstanceCoreLinks($instancePath)
+    {
+        $linksToSet = array(
+            ORIGINAL_ROOT . 'typo3' => $instancePath . '/typo3',
+            ORIGINAL_ROOT . 'index.php' => $instancePath . '/index.php'
+        );
+        foreach ($linksToSet as $from => $to) {
+            $success = symlink($from, $to);
+            if (!$success) {
+                throw new Exception(
+                    'Creating link failed: from ' . $from . ' to: ' . $to,
+                    1376657199
+                );
+            }
+        }
+    }
+
+    /**
+     * Link test extensions to the typo3conf/ext folder of the instance.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $extensionPaths Contains paths to extensions relative to document root
+     * @throws Exception
+     * @return void
+     */
+    public function linkTestExtensionsToInstance($instancePath, array $extensionPaths)
+    {
+        foreach ($extensionPaths as $extensionPath) {
+            $absoluteExtensionPath = ORIGINAL_ROOT . $extensionPath;
+            if (!is_dir($absoluteExtensionPath)) {
+                throw new Exception(
+                    'Test extension path ' . $absoluteExtensionPath . ' not found',
+                    1376745645
+                );
+            }
+            $destinationPath = $instancePath . '/typo3conf/ext/' . basename($absoluteExtensionPath);
+            $success = symlink($absoluteExtensionPath, $destinationPath);
+            if (!$success) {
+                throw new Exception(
+                    'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
+                    1376657142
+                );
+            }
+        }
+    }
+
+    /**
+     * Link paths inside the test instance, e.g. from a fixture fileadmin subfolder to the
+     * test instance fileadmin folder.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $pathsToLinkInTestInstance Contains paths as array of source => destination in key => value pairs of folders relative to test instance root
+     * @throws Exception if a source path could not be found and on failing creating the symlink
+     * @return void
+     */
+    public function linkPathsInTestInstance($instancePath, array $pathsToLinkInTestInstance)
+    {
+        foreach ($pathsToLinkInTestInstance as $sourcePathToLinkInTestInstance => $destinationPathToLinkInTestInstance) {
+            $sourcePath = $instancePath . '/' . ltrim($sourcePathToLinkInTestInstance, '/');
+            if (!file_exists($sourcePath)) {
+                throw new Exception(
+                    'Path ' . $sourcePath . ' not found',
+                    1376745645
+                );
+            }
+            $destinationPath = $instancePath . '/' . ltrim($destinationPathToLinkInTestInstance, '/');
+            $success = symlink($sourcePath, $destinationPath);
+            if (!$success) {
+                throw new Exception(
+                    'Can not link the path ' . $sourcePath . ' to ' . $destinationPath,
+                    1389969623
+                );
+            }
+        }
+    }
+
+    /**
+     * Database settings for functional and acceptance tests can be either set by
+     * environment variables (recommended), or from an existing LocalConfiguration as fallback.
+     * The method fetches these.
+     *
+     * An unique name will be added to the database name later.
+     *
+     * @throws Exception
+     * @return array [DB][host], [DB][username], ...
+     */
+    public function getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration()
+    {
+        $databaseName = trim(getenv('typo3DatabaseName'));
+        $databaseHost = trim(getenv('typo3DatabaseHost'));
+        $databaseUsername = trim(getenv('typo3DatabaseUsername'));
+        $databasePassword = trim(getenv('typo3DatabasePassword'));
+        $databasePort = trim(getenv('typo3DatabasePort'));
+        $databaseSocket = trim(getenv('typo3DatabaseSocket'));
+        if ($databaseName || $databaseHost || $databaseUsername || $databasePassword || $databasePort || $databaseSocket) {
+            // Try to get database credentials from environment variables first
+            $originalConfigurationArray = [
+                'DB' => [
+                    'Connections' => [
+                        'Default' => [
+                            'driver' => 'mysqli'
+                        ],
+                    ],
+                ],
+            ];
+            if ($databaseName) {
+                $originalConfigurationArray['DB']['Connections']['Default']['dbname'] = $databaseName;
+            }
+            if ($databaseHost) {
+                $originalConfigurationArray['DB']['Connections']['Default']['host'] = $databaseHost;
+            }
+            if ($databaseUsername) {
+                $originalConfigurationArray['DB']['Connections']['Default']['user'] = $databaseUsername;
+            }
+            if ($databasePassword) {
+                $originalConfigurationArray['DB']['Connections']['Default']['password'] = $databasePassword;
+            }
+            if ($databasePort) {
+                $originalConfigurationArray['DB']['Connections']['Default']['port'] = $databasePort;
+            }
+            if ($databaseSocket) {
+                $originalConfigurationArray['DB']['Connections']['Default']['unix_socket'] = $databaseSocket;
+            }
+        } elseif (file_exists(ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php')) {
+            // See if a LocalConfiguration file exists in "parent" instance to get db credentials from
+            $originalConfigurationArray = require ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php';
+        } else {
+            throw new Exception(
+                'Database credentials for tests are neither set through environment'
+                . ' variables, and can not be found in an existing LocalConfiguration file',
+                1397406356
+            );
+        }
+        return $originalConfigurationArray;
+    }
+
+    /**
+     * Maximum length of database names is 64 chars in mysql. Test this is not exceeded
+     * after a suffix has been added.
+     *
+     * @param string $originalDatabaseName Base name of the database
+     * @param array $configuration "LocalConfiguration" array with DB settings
+     * @throws Exception
+     */
+    public function testDatabaseNameIsNotTooLong($originalDatabaseName, array $configuration)
+    {
+        // Maximum database name length for mysql is 64 characters
+        if (strlen($configuration['DB']['Connections']['Default']['dbname']) > 64) {
+            $suffixLength = strlen($configuration['DB']['Connections']['Default']['dbname']) - strlen($originalDatabaseName);
+            $maximumOriginalDatabaseName = 64 - $suffixLength;
+            throw new Exception(
+                'The name of the database that is used for the functional test (' . $originalDatabaseName . ')' .
+                ' exceeds the maximum length of 64 character allowed by MySQL. You have to shorten your' .
+                ' original database name to ' . $maximumOriginalDatabaseName . ' characters',
+                1377600104
+            );
+        }
+    }
+
+    /**
+     * Create LocalConfiguration.php file of the test instance.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $configuration Base configuration array
+     * @param array $overruleConfiguration Overrule factory and base configuration
+     * @throws Exception
+     * @return void
+     */
+    public function setUpLocalConfiguration($instancePath, array $configuration, array $overruleConfiguration)
+    {
+        // Base of final LocalConfiguration is core factory configuration
+        $finalConfigurationArray = require ORIGINAL_ROOT . 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
+        ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $configuration);
+        ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $overruleConfiguration);
+        $result = $this->writeFile(
+            $instancePath . '/typo3conf/LocalConfiguration.php',
+            '<?php' . chr(10) .
+            'return ' .
+            ArrayUtility::arrayExport(
+                $finalConfigurationArray
+            ) .
+            ';'
+        );
+        if (!$result) {
+            throw new Exception('Can not write local configuration', 1376657277);
+        }
+    }
+
+    /**
+     * Compile typo3conf/PackageStates.php containing default packages like core,
+     * a test specific list of additional core extensions, and a list of
+     * test extensions.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $defaultCoreExtensionsToLoad Default list of core extensions to load
+     * @param array $additionalCoreExtensionsToLoad Additional core extensions to load
+     * @param array $testExtensionPaths Paths to extensions relative to document root
+     * @throws Exception
+     */
+    public function setUpPackageStates(
+        $instancePath,
+        array $defaultCoreExtensionsToLoad,
+        array $additionalCoreExtensionsToLoad,
+        array $testExtensionPaths
+    ) {
+        $packageStates = array(
+            'packages' => array(),
+            'version' => 5,
+        );
+
+        // Register default list of extensions and set active
+        foreach ($defaultCoreExtensionsToLoad as $extensionName) {
+            $packageStates['packages'][$extensionName] = array(
+                'packagePath' => 'typo3/sysext/' . $extensionName . '/'
+            );
+        }
+
+        // Register additional core extensions and set active
+        foreach ($additionalCoreExtensionsToLoad as $extensionName) {
+            $packageStates['packages'][$extensionName] = array(
+                'packagePath' => 'typo3/sysext/' . $extensionName . '/'
+            );
+        }
+
+        // Activate test extensions that have been symlinked before
+        foreach ($testExtensionPaths as $extensionPath) {
+            $extensionName = basename($extensionPath);
+            $packageStates['packages'][$extensionName] = array(
+                'packagePath' => 'typo3conf/ext/' . $extensionName . '/'
+            );
+        }
+
+        $result = $this->writeFile(
+            $instancePath . '/typo3conf/PackageStates.php',
+            '<?php' . chr(10) .
+            'return ' .
+            ArrayUtility::arrayExport(
+                $packageStates
+            ) .
+            ';'
+        );
+
+        if (!$result) {
+            throw new Exception('Can not write PackageStates', 1381612729);
+        }
+    }
+
+    /**
+     * Populate $GLOBALS['TYPO3_DB'] and create test database
+     * For functional and acceptance tests
+     *
+     * @param string $databaseName Database name of this test instance
+     * @param string $originalDatabaseName Original database name before suffix was added
+     * @throws \TYPO3\CMS\Core\Tests\Exception
+     * @return void
+     */
+    public function setUpTestDatabase($databaseName, $originalDatabaseName)
+    {
+        Bootstrap::getInstance()->initializeTypo3DbGlobal();
+        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
+        $database = $GLOBALS['TYPO3_DB'];
+        if (!$database->sql_pconnect()) {
+            throw new Exception(
+                'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
+                . ' connection to the database was attempted to be established!',
+                1377620117
+            );
+        }
+
+        // Drop database if exists
+        $database->admin_query('DROP DATABASE IF EXISTS `' . $databaseName . '`');
+        $createDatabaseResult = $database->admin_query('CREATE DATABASE `' . $databaseName . '` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci');
+        if (!$createDatabaseResult) {
+            $user = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'];
+            $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'];
+            throw new Exception(
+                'Unable to create database with name ' . $databaseName . '. This is probably a permission problem.'
+                . ' For this instance this could be fixed executing:'
+                . ' GRANT ALL ON `' . $originalDatabaseName . '_%`.* TO `' . $user . '`@`' . $host . '`;',
+                1376579070
+            );
+        }
+        $database->setDatabaseName($databaseName);
+
+       // On windows, this still works, but throws a warning, which we need to discard.
+        @$database->sql_select_db();
+    }
+
+    /**
+     * Bootstrap basic TYPO3. This bootstraps TYPO3 far enough to initialize database afterwards.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @return void
+     */
+    public function setUpBasicTypo3Bootstrap($instancePath)
+    {
+        $_SERVER['PWD'] = $instancePath;
+        $_SERVER['argv'][0] = 'index.php';
+
+        $classLoader = require rtrim(realpath($instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php';
+        Bootstrap::getInstance()
+            ->initializeClassLoader($classLoader)
+            ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
+            ->baseSetup()
+            ->loadConfigurationAndInitialize(true)
+            ->loadTypo3LoadedExtAndExtLocalconf(true)
+            ->setFinalCachingFrameworkCacheConfiguration()
+            ->defineLoggingAndExceptionConstants()
+            ->unsetReservedGlobalVariables();
+    }
+
+    /**
+     * Populate $GLOBALS['TYPO3_DB'] and truncate all tables.
+     * For functional and acceptance tests.
+     *
+     * @throws Exception
+     * @return void
+     */
+    public function initializeTestDatabaseAndTruncateTables()
+    {
+        Bootstrap::getInstance()->initializeTypo3DbGlobal();
+        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
+        $database = $GLOBALS['TYPO3_DB'];
+        if (!$database->sql_pconnect()) {
+            throw new Exception(
+                'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
+                . ' connection to the database was attempted to be established!',
+                1377620117
+            );
+        }
+        $database->setDatabaseName($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname']);
+        $database->sql_select_db();
+        foreach ($database->admin_get_tables() as $table) {
+            $database->admin_query('TRUNCATE ' . $table['Name'] . ';');
+        }
+    }
+
+    /**
+     * Load ext_tables.php files.
+     * For functional and acceptance tests.
+     *
+     * @return void
+     */
+    public function loadExtensionTables()
+    {
+        Bootstrap::getInstance()->loadExtensionTables();
+    }
+
+    /**
+     * Create tables and import static rows.
+     * For functional and acceptance tests.
+     *
+     * @return void
+     */
+    public function createDatabaseStructure()
+    {
+        /** @var SqlSchemaMigrationService $schemaMigrationService */
+        $schemaMigrationService = GeneralUtility::makeInstance(SqlSchemaMigrationService::class);
+        /** @var ObjectManager $objectManager */
+        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
+        /** @var SqlExpectedSchemaService $expectedSchemaService */
+        $expectedSchemaService = $objectManager->get(SqlExpectedSchemaService::class);
+
+        // Raw concatenated ext_tables.sql and friends string
+        $expectedSchemaString = $expectedSchemaService->getTablesDefinitionString(true);
+        $statements = $schemaMigrationService->getStatementArray($expectedSchemaString, true);
+        list($_, $insertCount) = $schemaMigrationService->getCreateTables($statements, true);
+
+        $fieldDefinitionsFile = $schemaMigrationService->getFieldDefinitions_fileContent($expectedSchemaString);
+        $fieldDefinitionsDatabase = $schemaMigrationService->getFieldDefinitions_database();
+        $difference = $schemaMigrationService->getDatabaseExtra($fieldDefinitionsFile, $fieldDefinitionsDatabase);
+        $updateStatements = $schemaMigrationService->getUpdateSuggestions($difference);
+
+        $schemaMigrationService->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
+        $schemaMigrationService->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
+        $schemaMigrationService->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
+
+        foreach ($insertCount as $table => $count) {
+            $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
+            foreach ($insertStatements as $insertQuery) {
+                $insertQuery = rtrim($insertQuery, ';');
+                /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
+                $database = $GLOBALS['TYPO3_DB'];
+                $database->admin_query($insertQuery);
+            }
+        }
+    }
+
+    /**
+     * 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
+     * @throws Exception
+     */
+    public function importXmlDatabaseFixture($path)
+    {
+        if (!is_file($path)) {
+            throw new Exception(
+                'Fixture file ' . $path . ' not found',
+                1376746261
+            );
+        }
+
+        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
+        $database = $GLOBALS['TYPO3_DB'];
+
+        $fileContent = file_get_contents($path);
+        // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
+        $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
+        $xml = simplexml_load_string($fileContent);
+        libxml_disable_entity_loader($previousValueOfEntityLoader);
+        $foreignKeys = array();
+
+        /** @var $table \SimpleXMLElement */
+        foreach ($xml->children() as $table) {
+            $insertArray = array();
+
+            /** @var $column \SimpleXMLElement */
+            foreach ($table->children() as $column) {
+                $columnName = $column->getName();
+                $columnValue = null;
+
+                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 = (string)$table->$columnName;
+                }
+
+                $insertArray[$columnName] = $columnValue;
+            }
+
+            $tableName = $table->getName();
+            $result = $database->exec_INSERTquery($tableName, $insertArray);
+            if ($result === false) {
+                throw new Exception(
+                    'Error when processing fixture file: ' . $path . ' Can not insert data to table ' . $tableName . ': ' . $database->sql_error(),
+                    1376746262
+                );
+            }
+            if (isset($table['id'])) {
+                $elementId = (string)$table['id'];
+                $foreignKeys[$tableName][$elementId] = $database->sql_insert_id();
+            }
+        }
+    }
+
+    /**
+     * Returns the absolute path the TYPO3 document root.
+     * This is the "original" document root, not the "instance" root for functional / acceptance tests.
+     *
+     * @return string the TYPO3 document root using Unix path separators
+     */
+    protected function getWebRoot()
+    {
+        if (getenv('TYPO3_PATH_WEB')) {
+            $webRoot = getenv('TYPO3_PATH_WEB');
+        } else {
+            $webRoot = getcwd();
+        }
+        return rtrim(strtr($webRoot, '\\', '/'), '/') . '/';
+    }
+
+    /**
+     * Send http headers, echo out a text message and exit with error code
+     *
+     * @param string $message
+     */
+    protected function exitWithMessage($message)
+    {
+        echo $message . LF;
+        exit(1);
+    }
+
+    /**
+     * Writes $content to the file $file. This is a simplified version
+     * of GeneralUtility::writeFile that does not fix permissions.
+     *
+     * @param string $file Filepath to write to
+     * @param string $content Content to write
+     * @return bool TRUE if the file was successfully opened and written to.
+     */
+    protected function writeFile($file, $content)
+    {
+        if ($fd = fopen($file, 'wb')) {
+            $res = fwrite($fd, $content);
+            fclose($fd);
+            if ($res === false) {
+                return false;
+            }
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/typo3/sysext/core/Classes/Tests/UnitTestCase.php b/typo3/sysext/core/Classes/Tests/UnitTestCase.php
new file mode 100644 (file)
index 0000000..f33be44
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+namespace TYPO3\CMS\Core\Tests;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+use TYPO3\CMS\Core\Utility\StringUtility;
+
+/**
+ * Base test case for unit tests.
+ *
+ * This class currently only inherits the base test case. However, it is recommended
+ * to extend this class for unit test cases instead of the base test case because if,
+ * at some point, specific behavior needs to be implemented for unit tests, your test cases
+ * will profit from it automatically.
+ *
+ */
+abstract class UnitTestCase extends BaseTestCase
+{
+    /**
+     * @todo make LoadedExtensionsArray serializable instead
+     *
+     * @var array
+     */
+    protected $backupGlobalsBlacklist = array('TYPO3_LOADED_EXT');
+
+    /**
+     * Absolute path to files that should be removed after a test.
+     * Handled in tearDown. Tests can register here to get any files
+     * within typo3temp/ or typo3conf/ext cleaned up again.
+     *
+     * @var array
+     */
+    protected $testFilesToDelete = array();
+
+    /**
+     * Unset all additional properties of test classes to help PHP
+     * garbage collection. This reduces memory footprint with lots
+     * of tests.
+     *
+     * If overwriting tearDown() in test classes, please call
+     * parent::tearDown() at the end. Unsetting of own properties
+     * is not needed this way.
+     *
+     * @throws \RuntimeException
+     * @return void
+     */
+    protected function tearDown()
+    {
+        // Unset properties of test classes to safe memory
+        $reflection = new \ReflectionObject($this);
+        foreach ($reflection->getProperties() as $property) {
+            $declaringClass = $property->getDeclaringClass()->getName();
+            if (
+                !$property->isStatic()
+                && $declaringClass !== \TYPO3\CMS\Core\Tests\UnitTestCase::class
+                && $declaringClass !== \TYPO3\CMS\Core\Tests\BaseTestCase::class
+                && strpos($property->getDeclaringClass()->getName(), 'PHPUnit_') !== 0
+            ) {
+                $propertyName = $property->getName();
+                unset($this->$propertyName);
+            }
+        }
+        unset($reflection);
+
+        // Delete registered test files and directories
+        foreach ($this->testFilesToDelete as $absoluteFileName) {
+            $absoluteFileName = GeneralUtility::fixWindowsFilePath(PathUtility::getCanonicalPath($absoluteFileName));
+            if (!GeneralUtility::validPathStr($absoluteFileName)) {
+                throw new \RuntimeException('tearDown() cleanup: Filename contains illegal characters', 1410633087);
+            }
+            if (!StringUtility::beginsWith($absoluteFileName, PATH_site . 'typo3temp/var/')) {
+                throw new \RuntimeException(
+                    'tearDown() cleanup:  Files to delete must be within typo3temp/var/',
+                    1410633412
+                );
+            }
+            // file_exists returns false for links pointing to not existing targets, so handle links before next check.
+            if (@is_link($absoluteFileName) || @is_file($absoluteFileName)) {
+                unlink($absoluteFileName);
+            } elseif (@is_dir($absoluteFileName)) {
+                GeneralUtility::rmdir($absoluteFileName, true);
+            } else {
+                throw new \RuntimeException('tearDown() cleanup: File, link or directory does not exist', 1410633510);
+            }
+        }
+        $this->testFilesToDelete = array();
+    }
+}
diff --git a/typo3/sysext/core/Tests/AccessibleObjectInterface.php b/typo3/sysext/core/Tests/AccessibleObjectInterface.php
deleted file mode 100644 (file)
index b655c72..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-/**
- * This interface defines the methods provided by TYPO3\CMS\Core\Tests\TestCase::getAccessibleMock.::
- */
-interface AccessibleObjectInterface
-{
-    /**
-     * Calls the method $method using call_user_func* and returns its return value.
-     *
-     * @param string $methodName name of method to call, must not be empty
-     *
-     * @return mixed the return value from the method $methodName
-     */
-    public function _call($methodName);
-
-    /**
-     * Calls the method $method without using call_user_func* and returns its return value.
-     *
-     * @param string $methodName name of method to call, must not be empty
-     * @param mixed &$arg1 first argument given to method $methodName
-     * @param mixed &$arg2 second argument given to method $methodName
-     * @param mixed &$arg3 third argument given to method $methodName
-     * @param mixed &$arg4 fourth argument given to method $methodName
-     * @param mixed &$arg5 fifth argument given to method $methodName
-     * @param mixed &$arg6 sixth argument given to method $methodName
-     * @param mixed &$arg7 seventh argument given to method $methodName
-     * @param mixed &$arg8 eighth argument given to method $methodName
-     * @param mixed &$arg9 ninth argument given to method $methodName
-     *
-     * @return mixed the return value from the method $methodName
-     */
-    public function _callRef(
-        $methodName, &$arg1 = null, &$arg2 = null, &$arg3 = null, &$arg4 = null, &$arg5= null, &$arg6 = null, &$arg7 = null,
-        &$arg8 = null, &$arg9 = null
-    );
-
-    /**
-     * Sets the value of a property.
-     *
-     * @param string $propertyName name of property to set value for, must not be empty
-     * @param mixed $value the new value for the property defined in $propertyName
-     *
-     * @return void
-     */
-    public function _set($propertyName, $value);
-
-    /**
-     * Sets the value of a property by reference.
-     *
-     * @param string $propertyName name of property to set value for, must not be empty
-     * @param mixed &$value the new value for the property defined in $propertyName
-     *
-     * @return void
-     */
-    public function _setRef($propertyName, &$value);
-
-    /**
-     * Sets the value of a static property.
-     *
-     * @param string $propertyName name of property to set value for, must not be empty
-     * @param mixed $value the new value for the property defined in $propertyName
-     *
-     * @return void
-     */
-    public function _setStatic($propertyName, $value);
-
-    /**
-     * Gets the value of the given property.
-     *
-     * @param string $propertyName name of property to return value of, must not be empty
-     *
-     * @return mixed the value of the property $propertyName
-     */
-    public function _get($propertyName);
-
-    /**
-     * Gets the value of the given static property.
-     *
-     * @param string $propertyName name of property to return value of, must not be empty
-     *
-     * @return mixed the value of the static property $propertyName
-     */
-    public function _getStatic($propertyName);
-}
diff --git a/typo3/sysext/core/Tests/BaseTestCase.php b/typo3/sysext/core/Tests/BaseTestCase.php
deleted file mode 100644 (file)
index 9f0f535..0000000
+++ /dev/null
@@ -1,288 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Utility\StringUtility;
-
-/**
- * The mother of all test cases.
- *
- * Don't sub class this test case but rather choose a more specialized base test case,
- * such as UnitTestCase or FunctionalTestCase
- *
- */
-abstract class BaseTestCase extends \PHPUnit_Framework_TestCase
-{
-    /**
-     * Whether global variables should be backed up
-     *
-     * @var bool
-     */
-    protected $backupGlobals = true;
-
-    /**
-     * Whether static attributes should be backed up
-     *
-     * @var bool
-     */
-    protected $backupStaticAttributes = false;
-
-    /**
-     * 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
-     * @param string[]|null $methods name of the methods to mock, null for "mock no methods"
-     * @param array $arguments arguments to pass to constructor
-     * @param string $mockClassName the class name to use for the mock class
-     * @param bool $callOriginalConstructor whether to call the constructor
-     * @param bool $callOriginalClone whether to call the __clone method
-     * @param bool $callAutoload whether to call any autoload function
-     *
-     * @return \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
-     *         a mock of $originalClassName with access methods added
-     *
-     * @throws \InvalidArgumentException
-     */
-    protected function getAccessibleMock(
-        $originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '',
-        $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true
-    ) {
-        if ($originalClassName === '') {
-            throw new \InvalidArgumentException('$originalClassName must not be empty.', 1334701880);
-        }
-
-        return $this->getMock(
-            $this->buildAccessibleProxy($originalClassName),
-            $methods,
-            $arguments,
-            $mockClassName,
-            $callOriginalConstructor,
-            $callOriginalClone,
-            $callAutoload
-        );
-    }
-
-    /**
-     * Returns a mock object which allows for calling protected methods and access
-     * of protected properties. Concrete methods to mock can be specified with
-     * the last parameter
-     *
-     * @param string $originalClassName Full qualified name of the original class
-     * @param array $arguments
-     * @param string $mockClassName
-     * @param bool $callOriginalConstructor
-     * @param bool $callOriginalClone
-     * @param bool $callAutoload
-     * @param array $mockedMethods
-     *
-     * @throws \InvalidArgumentException
-     *
-     * @return \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Core\Tests\AccessibleObjectInterface
-     *
-     */
-    protected function getAccessibleMockForAbstractClass(
-        $originalClassName, array $arguments = array(), $mockClassName = '',
-        $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = array()
-    ) {
-        if ($originalClassName === '') {
-            throw new \InvalidArgumentException('$originalClassName must not be empty.', 1384268260);
-        }
-
-        return $this->getMockForAbstractClass(
-            $this->buildAccessibleProxy($originalClassName),
-            $arguments,
-            $mockClassName,
-            $callOriginalConstructor,
-            $callOriginalClone,
-            $callAutoload,
-            $mockedMethods
-        );
-    }
-
-    /**
-     * Creates a proxy class of the specified class which allows
-     * for calling even protected methods and access of protected properties.
-     *
-     * @param string $className Name of class to make available, must not be empty
-     * @return string Fully qualified name of the built class, will not be empty
-     */
-    protected function buildAccessibleProxy($className)
-    {
-        $accessibleClassName = $this->getUniqueId('Tx_Phpunit_AccessibleProxy');
-        $class = new \ReflectionClass($className);
-        $abstractModifier = $class->isAbstract() ? 'abstract ' : '';
-
-        eval(
-            $abstractModifier . 'class ' . $accessibleClassName .
-                ' extends ' . $className . ' implements ' . \TYPO3\CMS\Core\Tests\AccessibleObjectInterface::class . ' {' .
-                    'public function _call($methodName) {' .
-                        'if ($methodName === \'\') {' .
-                            'throw new \InvalidArgumentException(\'$methodName must not be empty.\', 1334663993);' .
-                        '}' .
-                        '$args = func_get_args();' .
-                        'return call_user_func_array(array($this, $methodName), array_slice($args, 1));' .
-                    '}' .
-                    'public function _callRef(' .
-                        '$methodName, &$arg1 = NULL, &$arg2 = NULL, &$arg3 = NULL, &$arg4 = NULL, &$arg5= NULL, &$arg6 = NULL, ' .
-                        '&$arg7 = NULL, &$arg8 = NULL, &$arg9 = NULL' .
-                    ') {' .
-                        'if ($methodName === \'\') {' .
-                            'throw new \InvalidArgumentException(\'$methodName must not be empty.\', 1334664210);' .
-                        '}' .
-                        'switch (func_num_args()) {' .
-                            'case 0:' .
-                                'throw new RuntimeException(\'The case of 0 arguments is not supposed to happen.\', 1334703124);' .
-                                'break;' .
-                            'case 1:' .
-                                '$returnValue = $this->$methodName();' .
-                                'break;' .
-                            'case 2:' .
-                                '$returnValue = $this->$methodName($arg1);' .
-                                'break;' .
-                            'case 3:' .
-                                '$returnValue = $this->$methodName($arg1, $arg2);' .
-                                'break;' .
-                            'case 4:' .
-                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3);' .
-                                'break;' .
-                            'case 5:' .
-                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4);' .
-                                'break;' .
-                            'case 6:' .
-                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5);' .
-                                'break;' .
-                            'case 7:' .
-                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6);' .
-                                'break;' .
-                            'case 8:' .
-                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7);' .
-                                'break;' .
-                            'case 9:' .
-                                '$returnValue = $this->$methodName($arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8);' .
-                                'break;' .
-                            'case 10:' .
-                                '$returnValue = $this->$methodName(' .
-                                    '$arg1, $arg2, $arg3, $arg4, $arg5, $arg6, $arg7, $arg8, $arg9' .
-                                ');' .
-                                'break;' .
-                            'default:' .
-                                'throw new \InvalidArgumentException(' .
-                                    '\'_callRef currently only allows calls to methods with no more than 9 parameters.\'' .
-                                ');' .
-                        '}' .
-                        'return $returnValue;' .
-                    '}' .
-                    'public function _set($propertyName, $value) {' .
-                        'if ($propertyName === \'\') {' .
-                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664355);' .
-                        '}' .
-                        '$this->$propertyName = $value;' .
-                    '}' .
-                    'public function _setRef($propertyName, &$value) {' .
-                        'if ($propertyName === \'\') {' .
-                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664545);' .
-                        '}' .
-                        '$this->$propertyName = $value;' .
-                    '}' .
-                    'public function _setStatic($propertyName, $value) {' .
-                        'if ($propertyName === \'\') {' .
-                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1344242602);' .
-                        '}' .
-                        'self::$$propertyName = $value;' .
-                    '}' .
-                    'public function _get($propertyName) {' .
-                        'if ($propertyName === \'\') {' .
-                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1334664967);' .
-                        '}' .
-                        'return $this->$propertyName;' .
-                    '}' .
-                    'public function _getStatic($propertyName) {' .
-                        'if ($propertyName === \'\') {' .
-                            'throw new \InvalidArgumentException(\'$propertyName must not be empty.\', 1344242603);' .
-                        '}' .
-                        'return self::$$propertyName;' .
-                    '}' .
-            '}'
-        );
-
-        return $accessibleClassName;
-    }
-
-    /**
-     * Helper function to call protected or private methods
-     *
-     * @param object $object The object to be invoked
-     * @param string $name the name of the method to call
-     * @param mixed $arguments
-     * @return mixed
-     */
-    protected function callInaccessibleMethod($object, $name, ...$arguments)
-    {
-        $reflectionObject = new \ReflectionObject($object);
-        $reflectionMethod = $reflectionObject->getMethod($name);
-        $reflectionMethod->setAccessible(true);
-        return $reflectionMethod->invokeArgs($object, $arguments);
-    }
-
-    /**
-     * Injects $dependency into property $name of $target
-     *
-     * This is a convenience method for setting a protected or private property in
-     * a test subject for the purpose of injecting a dependency.
-     *
-     * @param object $target The instance which needs the dependency
-     * @param string $name Name of the property to be injected
-     * @param mixed $dependency The dependency to inject – usually an object but can also be any other type
-     * @return void
-     * @throws \RuntimeException
-     * @throws \InvalidArgumentException
-     */
-    protected function inject($target, $name, $dependency)
-    {
-        if (!is_object($target)) {
-            throw new \InvalidArgumentException('Wrong type for argument $target, must be object.');
-        }
-
-        $objectReflection = new \ReflectionObject($target);
-        $methodNamePart = strtoupper($name[0]) . substr($name, 1);
-        if ($objectReflection->hasMethod('set' . $methodNamePart)) {
-            $methodName = 'set' . $methodNamePart;
-            $target->$methodName($dependency);
-        } elseif ($objectReflection->hasMethod('inject' . $methodNamePart)) {
-            $methodName = 'inject' . $methodNamePart;
-            $target->$methodName($dependency);
-        } elseif ($objectReflection->hasProperty($name)) {
-            $property = $objectReflection->getProperty($name);
-            $property->setAccessible(true);
-            $property->setValue($target, $dependency);
-        } else {
-            throw new \RuntimeException('Could not inject ' . $name . ' into object of type ' . get_class($target));
-        }
-    }
-
-    /**
-     * Create and return a unique id optionally prepended by a given string
-     *
-     * This function is used because on windows and in cygwin environments uniqid() has a resolution of one second which
-     * results in identical ids if simply uniqid('Foo'); is called.
-     *
-     * @param string $prefix
-     * @return string
-     */
-    protected function getUniqueId($prefix = '')
-    {
-        return $prefix . StringUtility::getUniqueId(mt_rand());
-    }
-}
diff --git a/typo3/sysext/core/Tests/Exception.php b/typo3/sysext/core/Tests/Exception.php
deleted file mode 100644 (file)
index dd97aba..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-/**
- * An exception - Thrown in abstract test cases to mark
- * a test configuration or setup error.
- */
-class Exception extends \Exception
-{
-}
diff --git a/typo3/sysext/core/Tests/FileStreamWrapper.php b/typo3/sysext/core/Tests/FileStreamWrapper.php
deleted file mode 100644 (file)
index 01d96a8..0000000
+++ /dev/null
@@ -1,610 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-/**
- * Stream wrapper for the file:// protocol
- *
- * Implementation details:
- * Due to the nature of PHP, it is not possible to switch to the default handler
- * other then restoring the default handler for file:// and registering it again
- * around each call.
- * It is important that the default handler is restored to allow autoloading (including)
- * of files during the test run.
- * For each method allowed to pass paths, the passed path is checked against the
- * the list of paths to overlay and rewritten if needed.
- *
- * = Usage =
- * <code title="Add use statements">
- * use org\bovigo\vfs\vfsStream;
- * use org\bovigo\vfs\visitor\vfsStreamStructureVisitor;
- * </code>
- *
- * <code title="Usage in test">
- * $root = \org\bovigo\vfs\vfsStream::setup('root');
- * $subfolder = \org\bovigo\vfs\vfsStream::newDirectory('fileadmin');
- * $root->addChild($subfolder);
- * // Load fixture files and folders from disk
- * \org\bovigo\vfs\vfsStream::copyFromFileSystem(__DIR__ . '/Fixture/Files', $subfolder, 1024*1024);
- * FileStreamWrapper::init(PATH_site);
- * FileStreamWrapper::registerOverlayPath('fileadmin', 'vfs://root/fileadmin');
- *
- * // Use file functions as usual
- * mkdir(PATH_site . 'fileadmin/test/');
- * $file = PATH_site . 'fileadmin/test/Foo.bar';
- * file_put_contents($file, 'Baz');
- * $content = file_get_contents($file);
- * $this->assertSame('Baz', $content);
- *
- * $this->assertEqual(**array(file system structure as array**), vfsStream::inspect(new vfsStreamStructureVisitor())->getStructure());
- *
- * FileStreamWrapper::destroy();
- * </code>
- *
- * @see http://www.php.net/manual/en/class.streamwrapper.php
- */
-class FileStreamWrapper
-{
-    /**
-     * @var resource
-     */
-    protected $dirHandle = null;
-
-    /**
-     * @var resource
-     */
-    protected $fileHandle = null;
-
-    /**
-     * Switch whether class has already been registered as stream wrapper or not
-     *
-     * @type bool
-     */
-    protected static $registered = false;
-
-    /**
-     * Array of paths to overlay
-     *
-     * @var array
-     */
-    protected static $overlayPaths = array();
-
-    /**
-     * The first part of each (absolute) path that shall be ignored
-     *
-     * @var string
-     */
-    protected static $rootPath = '';
-
-    /**
-     * Initialize the stream wrapper with a root path and register itself
-     *
-     * @param $rootPath
-     * @return void
-     */
-    public static function init($rootPath)
-    {
-        self::$rootPath = rtrim(str_replace('\\', '/', $rootPath), '/') . '/';
-        self::register();
-    }
-
-    /**
-     * Unregister the stream wrapper and reset all static members to their default values
-     * @return void
-     */
-    public static function destroy()
-    {
-        self::$overlayPaths = array();
-        self::$rootPath = '';
-        if (self::$registered) {
-            self::restore();
-        }
-    }
-
-    /**
-     * Register a path relative to the root path (set in init) to be overlaid
-     *
-     * @param string $overlay Relative path to the root folder
-     * @param string $replace The path that should replace the overlay path
-     * @param bool $createFolder TRUE of the folder should be created (mkdir)
-     * @return void
-     */
-    public static function registerOverlayPath($overlay, $replace, $createFolder = true)
-    {
-        $overlay = trim(str_replace('\\', '/', $overlay), '/') . '/';
-        $replace = rtrim(str_replace('\\', '/', $replace), '/') . '/';
-        self::$overlayPaths[$overlay] = $replace;
-        if ($createFolder) {
-            mkdir($replace);
-        }
-    }
-
-    /**
-     * Checks and overlays a path
-     *
-     * @param string $path The path to check
-     * @return string The potentially overlaid path
-     */
-    protected static function overlayPath($path)
-    {
-        $path = str_replace('\\', '/', $path);
-        $hasOverlay = false;
-        if (strpos($path, self::$rootPath) !== 0) {
-            // Path is not below root path, ignore it
-            return $path;
-        }
-
-        $newPath = ltrim(substr($path, strlen(self::$rootPath)), '/');
-        foreach (self::$overlayPaths as $overlay => $replace) {
-            if (strpos($newPath, $overlay) === 0) {
-                $newPath = $replace . substr($newPath, strlen($overlay));
-                $hasOverlay = true;
-                break;
-            }
-        }
-        return $hasOverlay ? $newPath : $path;
-    }
-
-    /**
-     * Method to register the stream wrapper
-     *
-     * If the stream is already registered the method returns silently. If there
-     * is already another stream wrapper registered for the scheme used by
-     * file:// scheme a \BadFunctionCallException will be thrown.
-     *
-     * @throws \BadFunctionCallException
-     * @return void
-     */
-    protected static function register()
-    {
-        if (self::$registered) {
-            return;
-        }
-
-        if (@stream_wrapper_unregister('file') === false) {
-            throw new \BadFunctionCallException('Cannot unregister file:// stream wrapper.', 1396340331);
-        }
-        if (@stream_wrapper_register('file', __CLASS__) === false) {
-            throw new \BadFunctionCallException('A handler has already been registered for the file:// scheme.', 1396340332);
-        }
-
-        self::$registered = true;
-    }
-
-    /**
-     * Restore the file handler
-     *
-     * @return void
-     */
-    protected static function restore()
-    {
-        if (!self::$registered) {
-            return;
-        }
-        if (@stream_wrapper_restore('file') === false) {
-            throw new \BadFunctionCallException('Cannot restore the default file:// stream handler.', 1396340333);
-        }
-        self::$registered = false;
-    }
-
-    /*
-     * The following list of functions is implemented as of
-     * @see http://www.php.net/manual/en/streamwrapper.dir-closedir.php
-     */
-
-    /**
-     * Close the directory
-     *
-     * @return bool
-     */
-    public function dir_closedir()
-    {
-        if ($this->dirHandle === null) {
-            return false;
-        } else {
-            self::restore();
-            closedir($this->dirHandle);
-            self::register();
-            $this->dirHandle = null;
-            return true;
-        }
-    }
-
-    /**
-     * Opens a directory for reading
-     *
-     * @param string $path
-     * @param int $options
-     * @return bool
-     */
-    public function dir_opendir($path, $options = 0)
-    {
-        if ($this->dirHandle !== null) {
-            return false;
-        }
-        self::restore();
-        $path = self::overlayPath($path);
-        $this->dirHandle = opendir($path);
-        self::register();
-        return $this->dirHandle !== false;
-    }
-
-    /**
-     * Read a single filename of a directory
-     *
-     * @return string|bool
-     */
-    public function dir_readdir()
-    {
-        if ($this->dirHandle === null) {
-            return false;
-        }
-        self::restore();
-        $success = readdir($this->dirHandle);
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Reset directory name pointer
-     *
-     * @return bool
-     */
-    public function dir_rewinddir()
-    {
-        if ($this->dirHandle === null) {
-            return false;
-        }
-        self::restore();
-        rewinddir($this->dirHandle);
-        self::register();
-        return true;
-    }
-
-    /**
-     * Create a directory
-     *
-     * @param string $path
-     * @param int $mode
-     * @param int $options
-     * @return bool
-     */
-    public function mkdir($path, $mode, $options = 0)
-    {
-        self::restore();
-        $path = self::overlayPath($path);
-        $success = mkdir($path, $mode, (bool)($options & STREAM_MKDIR_RECURSIVE));
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Rename a file
-     *
-     * @param string $pathFrom
-     * @param string $pathTo
-     * @return bool
-     */
-    public function rename($pathFrom, $pathTo)
-    {
-        self::restore();
-        $pathFrom = self::overlayPath($pathFrom);
-        $pathTo = self::overlayPath($pathTo);
-        $success = rename($pathFrom, $pathTo);
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Remove a directory
-     *
-     * @param string $path
-     * @return bool
-     */
-    public function rmdir($path)
-    {
-        self::restore();
-        $path = self::overlayPath($path);
-        $success = rmdir($path);
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Retrieve the underlying resource
-     *
-     * @param int $castAs Can be STREAM_CAST_FOR_SELECT when stream_select()
-     * is calling stream_cast() or STREAM_CAST_AS_STREAM when stream_cast()
-     * is called for other uses.
-     * @return resource|bool
-     */
-    public function stream_cast($castAs)
-    {
-        if ($this->fileHandle !== null && $castAs & STREAM_CAST_AS_STREAM) {
-            return $this->fileHandle;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Close a file
-     *
-     */
-    public function stream_close()
-    {
-        self::restore();
-        if ($this->fileHandle !== null) {
-            fclose($this->fileHandle);
-            $this->fileHandle = null;
-        }
-        self::register();
-    }
-
-    /**
-     * Test for end-of-file on a file pointer
-     *
-     * @return bool
-     */
-    public function stream_eof()
-    {
-        if ($this->fileHandle === null) {
-            return false;
-        }
-        self::restore();
-        $success = feof($this->fileHandle);
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Flush the output
-     *
-     * @return bool
-     */
-    public function stream_flush()
-    {
-        if ($this->fileHandle === null) {
-            return false;
-        }
-        self::restore();
-        $success = fflush($this->fileHandle);
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Advisory file locking
-     *
-     * @param int $operation
-     * @return bool
-     */
-    public function stream_lock($operation)
-    {
-        if ($this->fileHandle === null) {
-            return false;
-        }
-        self::restore();
-        $success = flock($this->fileHandle, $operation);
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Change file options
-     *
-     * @param string $path
-     * @param int $options
-     * @param mixed $value
-     * @return bool
-     */
-    public function stream_metadata($path, $options, $value)
-    {
-        self::restore();
-        $path = self::overlayPath($path);
-        switch ($options) {
-            case STREAM_META_TOUCH:
-                $success = touch($path, $value[0], $value[1]);
-                break;
-            case STREAM_META_OWNER_NAME:
-                // Fall through
-            case STREAM_META_OWNER:
-                $success = chown($path, $value);
-                break;
-            case STREAM_META_GROUP_NAME:
-                // Fall through
-            case STREAM_META_GROUP:
-                $success = chgrp($path, $value);
-                break;
-            case STREAM_META_ACCESS:
-                $success = chmod($path, $value);
-                break;
-            default:
-                $success = false;
-        }
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Open a file
-     *
-     * @param string $path
-     * @param string $mode
-     * @param int $options
-     * @param string &$opened_path
-     * @return bool
-     */
-    public function stream_open($path, $mode, $options, &$opened_path)
-    {
-        if ($this->fileHandle !== null) {
-            return false;
-        }
-        self::restore();
-        $path = self::overlayPath($path);
-        $this->fileHandle = fopen($path, $mode, (bool)($options & STREAM_USE_PATH));
-        self::register();
-        return $this->fileHandle !== false;
-    }
-
-    /**
-     * Read from a file
-     *
-     * @param int $length
-     * @return string
-     */
-    public function stream_read($length)
-    {
-        if ($this->fileHandle === null) {
-            return false;
-        }
-        self::restore();
-        $content = fread($this->fileHandle, $length);
-        self::register();
-        return $content;
-    }
-
-    /**
-     * Seek to specific location in a stream
-     *
-     * @param int $offset
-     * @param int $whence = SEEK_SET
-     * @return bool
-     */
-    public function stream_seek($offset, $whence = SEEK_SET)
-    {
-        if ($this->fileHandle === null) {
-            return false;
-        }
-        self::restore();
-        $success = fseek($this->fileHandle, $offset, $whence);
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Change stream options (not implemented)
-     *
-     * @param int $option
-     * @param int $arg1
-     * @param int $arg2
-     * @return bool
-     */
-    public function stream_set_option($option, $arg1, $arg2)
-    {
-        return false;
-    }
-
-    /**
-     * Retrieve information about a file resource
-     *
-     * @return array
-     */
-    public function stream_stat()
-    {
-        if ($this->fileHandle === null) {
-            return false;
-        }
-        self::restore();
-        $stats = fstat($this->fileHandle);
-        self::register();
-        return $stats;
-    }
-
-    /**
-     * Retrieve the current position of a stream
-     *
-     * @return int
-     */
-    public function stream_tell()
-    {
-        if ($this->fileHandle === null) {
-            return false;
-        }
-        self::restore();
-        $position = ftell($this->fileHandle);
-        self::register();
-        return $position;
-    }
-
-    /**
-     * Truncates a file to the given size
-     *
-     * @param int $size Truncate to this size
-     * @return bool
-     */
-    public function stream_truncate($size)
-    {
-        if ($this->fileHandle === null) {
-            return false;
-        }
-        self::restore();
-        $success = ftruncate($this->fileHandle, $size);
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Write to stream
-     *
-     * @param string $data
-     * @return int
-     */
-    public function stream_write($data)
-    {
-        if ($this->fileHandle === null) {
-            return false;
-        }
-        self::restore();
-        $length = fwrite($this->fileHandle, $data);
-        self::register();
-        return $length;
-    }
-
-    /**
-     * Unlink a file
-     *
-     * @param string $path
-     * @return bool
-     */
-    public function unlink($path)
-    {
-        self::restore();
-        $path = self::overlayPath($path);
-        $success = unlink($path);
-        self::register();
-        return $success;
-    }
-
-    /**
-     * Retrieve information about a file
-     *
-     * @param string $path
-     * @param int $flags
-     * @return array
-     */
-    public function url_stat($path, $flags)
-    {
-        self::restore();
-        $path = self::overlayPath($path);
-        if ($flags & STREAM_URL_STAT_LINK) {
-            $information = @lstat($path);
-        } else {
-            $information = @stat($path);
-        }
-        self::register();
-        return $information;
-    }
-}
diff --git a/typo3/sysext/core/Tests/FunctionalTestCase.php b/typo3/sysext/core/Tests/FunctionalTestCase.php
deleted file mode 100644 (file)
index 9e60acd..0000000
+++ /dev/null
@@ -1,404 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
-use TYPO3\CMS\Core\Core\Bootstrap;
-use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
-/**
- * Base test case class for functional tests, all TYPO3 CMS
- * functional tests should extend from this class!
- *
- * If functional tests need additional setUp() and tearDown() code,
- * they *must* call parent::setUp() and parent::tearDown() to properly
- * set up and destroy the test system.
- *
- * The functional test system creates a full new TYPO3 CMS instance
- * within typo3temp/ of the base system and the bootstraps this TYPO3 instance.
- * This abstract class takes care of creating this instance with its
- * folder structure and a LocalConfiguration, creates an own database
- * for each test run and imports tables of loaded extensions.
- *
- * Functional tests must be run standalone (calling native phpunit
- * directly) and can not be executed by eg. the ext:phpunit backend module.
- * Additionally, the script must be called from the document root
- * of the instance, otherwise path calculation is not successfully.
- *
- * Call whole functional test suite, example:
- * - cd /var/www/t3master/foo  # Document root of CMS instance, here is index.php of frontend
- * - typo3/../bin/phpunit -c typo3/sysext/core/Build/FunctionalTests.xml
- *
- * Call single test case, example:
- * - cd /var/www/t3master/foo  # Document root of CMS instance, here is index.php of frontend
- * - typo3/../bin/phpunit \
- *     --process-isolation \
- *     --bootstrap typo3/sysext/core/Build/FunctionalTestsBootstrap.php \
- *     typo3/sysext/core/Tests/Functional/DataHandling/DataHandlerTest.php
- */
-abstract class FunctionalTestCase extends BaseTestCase
-{
-
-    /**
-     * An unique identifier for this test case. Location of the test
-     * instance and database name depend on this. Calculated early in setUp()
-     *
-     * @var string
-     */
-    protected $identifier;
-
-    /**
-     * Absolute path to test instance document root. Depends on $identifier.
-     * Calculated early in setUp()
-     *
-     * @var string
-     */
-    protected $instancePath;
-
-    /**
-     * Core extensions to load.
-     *
-     * 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.
-     *
-     * This property will stay empty in this abstract, so it is possible
-     * to just overwrite it in extending classes. Extensions noted here will
-     * be loaded for every test of a test case and it is not possible to change
-     * the list of loaded extensions between single tests of a test case.
-     *
-     * A default list of core extensions is always loaded.
-     *
-     * @see FunctionalTestCaseUtility $defaultActivatedCoreExtensions
-     * @var array
-     */
-    protected $coreExtensionsToLoad = [];
-
-    /**
-     * Array of test/fixture extensions paths that should be loaded for a test.
-     *
-     * This property will stay empty in this abstract, so it is possible
-     * to just overwrite it in extending classes. Extensions noted here will
-     * be loaded for every test of a test case and it is not possible to change
-     * the list of loaded extensions between single tests of a test case.
-     *
-     * Given path is expected to be relative to your document root, example:
-     *
-     * 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
-     */
-    protected $testExtensionsToLoad = [];
-
-    /**
-     * Array of test/fixture folder or file paths that should be linked for a test.
-     *
-     * This property will stay empty in this abstract, so it is possible
-     * to just overwrite it in extending classes. Path noted here will
-     * be linked for every test of a test case and it is not possible to change
-     * the list of folders between single tests of a test case.
-     *
-     * array(
-     *   'link-source' => 'link-destination'
-     * );
-     *
-     * Given paths are expected to be relative to the test instance root.
-     * The array keys are the source paths and the array values are the destination
-     * paths, example:
-     *
-     * array(
-     *   'typo3/sysext/impext/Tests/Functional/Fixtures/Folders/fileadmin/user_upload' =>
-     *   'fileadmin/user_upload',
-     *   'typo3conf/ext/my_own_ext/Tests/Functional/Fixtures/Folders/uploads/tx_myownext' =>
-     *   'uploads/tx_myownext'
-     * );
-     *
-     * To be able to link from my_own_ext the extension path needs also to be registered in
-     * property $testExtensionsToLoad
-     *
-     * @var array
-     */
-    protected $pathsToLinkInTestInstance = [];
-
-    /**
-     * This configuration array is merged with TYPO3_CONF_VARS
-     * that are set in default configuration and factory configuration
-     *
-     * @var array
-     */
-    protected $configurationToUseInTestInstance = [];
-
-    /**
-     * Array of folders that should be created inside the test instance document root.
-     *
-     * This property will stay empty in this abstract, so it is possible
-     * to just overwrite it in extending classes. Path noted here will
-     * be linked for every test of a test case and it is not possible to change
-     * the list of folders between single tests of a test case.
-     *
-     * Per default the following folder are created
-     * /fileadmin
-     * /typo3temp
-     * /typo3conf
-     * /typo3conf/ext
-     * /uploads
-     *
-     * To create additional folders add the paths to this array. Given paths are expected to be
-     * relative to the test instance root and have to begin with a slash. Example:
-     *
-     * array(
-     *   'fileadmin/user_upload'
-     * );
-     *
-     * @var array
-     */
-    protected $additionalFoldersToCreate = [];
-
-    /**
-     * The fixture which is used when initializing a backend user
-     *
-     * @var string
-     */
-    protected $backendUserFixture = 'typo3/sysext/core/Tests/Functional/Fixtures/be_users.xml';
-
-    /**
-     * Set up creates a test instance and database.
-     *
-     * This method should be called with parent::setUp() in your test cases!
-     *
-     * @return void
-     */
-    protected function setUp()
-    {
-        if (!defined('ORIGINAL_ROOT')) {
-            $this->markTestSkipped('Functional tests must be called through phpunit on CLI');
-        }
-
-        // Use a 7 char long hash of class name as identifier
-        $this->identifier = substr(sha1(get_class($this)), 0, 7);
-        $this->instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/functional-' . $this->identifier;
-
-        $testbase = new Testbase();
-        $testbase->defineTypo3ModeBe();
-        $testbase->setTypo3TestingContext();
-        if ($testbase->recentTestInstanceExists($this->instancePath)) {
-            // Reusing an existing instance. This typically happens for the second, third, ... test
-            // in a test case, so environment is set up only once per test case.
-            $testbase->setUpBasicTypo3Bootstrap($this->instancePath);
-            $testbase->initializeTestDatabaseAndTruncateTables();
-            $testbase->loadExtensionTables();
-        } else {
-            $testbase->removeOldInstanceIfExists($this->instancePath);
-            // Basic instance directory structure
-            $testbase->createDirectory($this->instancePath . '/fileadmin');
-            $testbase->createDirectory($this->instancePath . '/typo3temp/var/transient');
-            $testbase->createDirectory($this->instancePath . '/typo3temp/assets');
-            $testbase->createDirectory($this->instancePath . '/typo3conf/ext');
-            $testbase->createDirectory($this->instancePath . '/uploads');
-            // Additionally requested directories
-            foreach ($this->additionalFoldersToCreate as $directory) {
-                $testbase->createDirectory($this->instancePath . '/' . $directory);
-            }
-            $testbase->createLastRunTextfile($this->instancePath);
-            $testbase->setUpInstanceCoreLinks($this->instancePath);
-            $testbase->linkTestExtensionsToInstance($this->instancePath, $this->testExtensionsToLoad);
-            $testbase->linkPathsInTestInstance($this->instancePath, $this->pathsToLinkInTestInstance);
-            $localConfiguration = $testbase->getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration();
-            $originalDatabaseName = $localConfiguration['DB']['Connections']['Default']['dbname'];
-            // Append the unique identifier to the base database name to end up with a single database per test case
-            $localConfiguration['DB']['Connections']['Default']['dbname'] = $originalDatabaseName . '_ft' . $this->identifier;
-            $testbase->testDatabaseNameIsNotTooLong($originalDatabaseName, $localConfiguration);
-            // Set some hard coded base settings for the instance. Those could be overruled by
-            // $this->configurationToUseInTestInstance if needed again.
-            $localConfiguration['SYS']['isInitialInstallationInProgress'] = false;
-            $localConfiguration['SYS']['isInitialDatabaseImportDone'] = true;
-            $localConfiguration['SYS']['displayErrors'] = '1';
-            $localConfiguration['SYS']['debugExceptionHandler'] = '';
-            $localConfiguration['SYS']['trustedHostsPattern'] = '.*';
-            $localConfiguration['SYS']['setDBinit'] = 'SET SESSION sql_mode = \'STRICT_ALL_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_VALUE_ON_ZERO,NO_ENGINE_SUBSTITUTION,NO_ZERO_DATE,NO_ZERO_IN_DATE,ONLY_FULL_GROUP_BY\';';
-            $testbase->setUpLocalConfiguration($this->instancePath, $localConfiguration, $this->configurationToUseInTestInstance);
-            $defaultCoreExtensionsToLoad = [
-                'core',
-                'backend',
-                'frontend',
-                'lang',
-                'extbase',
-                'install',
-            ];
-            $testbase->setUpPackageStates($this->instancePath, $defaultCoreExtensionsToLoad, $this->coreExtensionsToLoad, $this->testExtensionsToLoad);
-            $testbase->setUpBasicTypo3Bootstrap($this->instancePath);
-            $testbase->setUpTestDatabase($localConfiguration['DB']['Connections']['Default']['dbname'], $originalDatabaseName);
-            $testbase->loadExtensionTables();
-            $testbase->createDatabaseStructure();
-        }
-    }
-
-    /**
-     * Get DatabaseConnection instance - $GLOBALS['TYPO3_DB']
-     *
-     * This method should be used instead of direct access to
-     * $GLOBALS['TYPO3_DB'] for easy IDE auto completion.
-     *
-     * @return \TYPO3\CMS\Core\Database\DatabaseConnection
-     */
-    protected function getDatabaseConnection()
-    {
-        return $GLOBALS['TYPO3_DB'];
-    }
-
-    /**
-     * Initialize backend user
-     *
-     * @param int $userUid uid of the user we want to initialize. This user must exist in the fixture file
-     * @return BackendUserAuthentication
-     * @throws Exception
-     */
-    protected function setUpBackendUserFromFixture($userUid)
-    {
-        $this->importDataSet(ORIGINAL_ROOT . $this->backendUserFixture);
-        $database = $this->getDatabaseConnection();
-        $userRow = $database->exec_SELECTgetSingleRow('*', 'be_users', 'uid = ' . (int)$userUid);
-
-        /** @var $backendUser BackendUserAuthentication */
-        $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
-        $sessionId = $backendUser->createSessionId();
-        $_COOKIE['be_typo_user'] = $sessionId;
-        $backendUser->id = $sessionId;
-        $backendUser->sendNoCacheHeaders = false;
-        $backendUser->dontSetCookie = true;
-        $backendUser->createUserSession($userRow);
-
-        $GLOBALS['BE_USER'] = $backendUser;
-        $GLOBALS['BE_USER']->start();
-        if (!is_array($GLOBALS['BE_USER']->user) || !$GLOBALS['BE_USER']->user['uid']) {
-            throw new Exception(
-                'Can not initialize backend user',
-                1377095807
-            );
-        }
-        $GLOBALS['BE_USER']->backendCheckLogin();
-
-        return $backendUser;
-    }
-
-    /**
-     * 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
-     * @throws Exception
-     */
-    protected function importDataSet($path)
-    {
-        $testbase = new Testbase();
-        $testbase->importXmlDatabaseFixture($path);
-    }
-
-    /**
-     * @param int $pageId
-     * @param array $typoScriptFiles
-     */
-    protected function setUpFrontendRootPage($pageId, array $typoScriptFiles = array())
-    {
-        $pageId = (int)$pageId;
-        $page = $this->getDatabaseConnection()->exec_SELECTgetSingleRow('*', 'pages', 'uid=' . $pageId);
-
-        if (empty($page)) {
-            $this->fail('Cannot set up frontend root page "' . $pageId . '"');
-        }
-
-        $pagesFields = array(
-            'is_siteroot' => 1
-        );
-
-        $this->getDatabaseConnection()->exec_UPDATEquery('pages', 'uid=' . $pageId, $pagesFields);
-
-        $templateFields = array(
-            'pid' => $pageId,
-            'title' => '',
-            'config' => '',
-            'clear' => 3,
-            'root' => 1,
-        );
-
-        foreach ($typoScriptFiles as $typoScriptFile) {
-            $templateFields['config'] .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . LF;
-        }
-
-        $this->getDatabaseConnection()->exec_INSERTquery('sys_template', $templateFields);
-    }
-
-    /**
-     * @param int $pageId
-     * @param int $languageId
-     * @param int $backendUserId
-     * @param int $workspaceId
-     * @param bool $failOnFailure
-     * @param int $frontendUserId
-     * @return Response
-     */
-    protected function getFrontendResponse($pageId, $languageId = 0, $backendUserId = 0, $workspaceId = 0, $failOnFailure = true, $frontendUserId = 0)
-    {
-        $pageId = (int)$pageId;
-        $languageId = (int)$languageId;
-
-        $additionalParameter = '';
-
-        if (!empty($frontendUserId)) {
-            $additionalParameter .= '&frontendUserId=' . (int)$frontendUserId;
-        }
-        if (!empty($backendUserId)) {
-            $additionalParameter .= '&backendUserId=' . (int)$backendUserId;
-        }
-        if (!empty($workspaceId)) {
-            $additionalParameter .= '&workspaceId=' . (int)$workspaceId;
-        }
-
-        $arguments = array(
-            'documentRoot' => $this->instancePath,
-            'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter,
-        );
-
-        $template = new \Text_Template(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/request.tpl');
-        $template->setVar(
-            array(
-                'arguments' => var_export($arguments, true),
-                'originalRoot' => ORIGINAL_ROOT,
-            )
-        );
-
-        $php = \PHPUnit_Util_PHP::factory();
-        $response = $php->runJob($template->render());
-        $result = json_decode($response['stdout'], true);
-
-        if ($result === null) {
-            $this->fail('Frontend Response is empty');
-        }
-
-        if ($failOnFailure && $result['status'] === Response::STATUS_Failure) {
-            $this->fail('Frontend Response has failure:' . LF . $result['error']);
-        }
-
-        $response = new Response($result['status'], $result['content'], $result['error']);
-        return $response;
-    }
-}
diff --git a/typo3/sysext/core/Tests/Testbase.php b/typo3/sysext/core/Tests/Testbase.php
deleted file mode 100644 (file)
index 199c478..0000000
+++ /dev/null
@@ -1,723 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Core\Bootstrap;
-use TYPO3\CMS\Core\Utility\ArrayUtility;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Extbase\Object\ObjectManager;
-use TYPO3\CMS\Install\Service\SqlExpectedSchemaService;
-use TYPO3\CMS\Install\Service\SqlSchemaMigrationService;
-
-/**
- * This is a helper class used by unit, functional and acceptance test
- * environment builders.
- * It contains methods to create test environments.
- *
- * This class is for internal use only and may change wihtout further notice.
- *
- * Use the classes "UnitTestCase", "FunctionalTestCase" or "AcceptanceCoreEnvironment"
- * to indirectly benefit from this class in own extensions.
- */
-class Testbase
-{
-    /**
-     * This class must be called in CLI environment as a security measure
-     * against path disclosures and other stuff. Check this within
-     * constructor to make sure this check can't be circumvented.
-     */
-    public function __construct()
-    {
-        // Ensure cli only as security measure
-        if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
-            die('This script supports command line usage only. Please check your command.');
-        }
-    }
-
-    /**
-     * Makes sure error messages during the tests get displayed no matter what is set in php.ini.
-     *
-     * @return void
-     */
-    public function enableDisplayErrors()
-    {
-        @ini_set('display_errors', 1);
-    }
-
-    /**
-     * Defines a list of basic constants that are used by GeneralUtility and other
-     * helpers during tests setup. Those are sanitized in SystemEnvironmentBuilder
-     * to be not defined again.
-     *
-     * @return void
-     * @see SystemEnvironmentBuilder::defineBaseConstants()
-     */
-    public function defineBaseConstants()
-    {
-        // A null, a tabulator, a linefeed, a carriage return, a substitution, a CR-LF combination
-        defined('NUL') ?: define('NUL', chr(0));
-        defined('TAB') ?: define('TAB', chr(9));
-        defined('LF') ?: define('LF', chr(10));
-        defined('CR') ?: define('CR', chr(13));
-        defined('SUB') ?: define('SUB', chr(26));
-        defined('CRLF') ?: define('CRLF', CR . LF);
-
-        if (!defined('TYPO3_OS')) {
-            // Operating system identifier
-            // Either "WIN" or empty string
-            $typoOs = '';
-            if (!stristr(PHP_OS, 'darwin') && !stristr(PHP_OS, 'cygwin') && stristr(PHP_OS, 'win')) {
-                $typoOs = 'WIN';
-            }
-            define('TYPO3_OS', $typoOs);
-        }
-    }
-
-    /**
-     * Defines the PATH_site and PATH_thisScript constant and sets $_SERVER['SCRIPT_NAME'].
-     * For unit tests only
-     *
-     * @return void
-     */
-    public function defineSitePath()
-    {
-        define('PATH_site', $this->getWebRoot());
-        define('PATH_thisScript', PATH_site . 'typo3/cli_dispatch.phpsh');
-        $_SERVER['SCRIPT_NAME'] = PATH_thisScript;
-
-        if (!file_exists(PATH_thisScript)) {
-            $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.');
-        }
-    }
-
-    /**
-     * Defines the constant ORIGINAL_ROOT for the path to the original TYPO3 document root.
-     * For functional / acceptance tests only
-     * If ORIGINAL_ROOT already is defined, this method is a no-op.
-     *
-     * @return void
-     */
-    public function defineOriginalRootPath()
-    {
-        if (!defined('ORIGINAL_ROOT')) {
-            define('ORIGINAL_ROOT', $this->getWebRoot());
-        }
-
-        if (!file_exists(ORIGINAL_ROOT . 'typo3/cli_dispatch.phpsh')) {
-            $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.');
-        }
-    }
-
-    /**
-     * Define TYPO3_MODE to BE
-     *
-     * @return void
-     */
-    public function defineTypo3ModeBe()
-    {
-        define('TYPO3_MODE', 'BE');
-    }
-
-    /**
-     * Sets the environment variable TYPO3_CONTEXT to testing.
-     *
-     * @return void
-     */
-    public function setTypo3TestingContext()
-    {
-        putenv('TYPO3_CONTEXT=Testing');
-    }
-
-    /**
-     * Creates directories, recursively if required.
-     *
-     * @param string $directory Absolute path to directories to create
-     * @return void
-     * @throws Exception
-     */
-    public function createDirectory($directory)
-    {
-        if (is_dir($directory)) {
-            return;
-        }
-        @mkdir($directory, 0777, true);
-        clearstatcache();
-        if (!is_dir($directory)) {
-            throw new Exception('Directory "' . $directory . '" could not be created', 1404038665);
-        }
-    }
-
-    /**
-     * Checks whether given test instance exists in path and is younger than some minutes.
-     * Used in functional tests
-     *
-     * @param string $instancePath Absolute path to test instance
-     * @return bool
-     */
-    public function recentTestInstanceExists($instancePath)
-    {
-        if (@file_get_contents($instancePath . '/last_run.txt') <= (time() - 300)) {
-            return false;
-        } else {
-            // Test instance exists and is pretty young -> re-use
-            return true;
-        }
-    }
-
-    /**
-     * Remove test instance folder structure if it exists.
-     * This may happen if a functional test before threw a fatal or is too old
-     *
-     * @param string $instancePath Absolute path to test instance
-     * @return void
-     * @throws Exception
-     */
-    public function removeOldInstanceIfExists($instancePath)
-    {
-        if (is_dir($instancePath)) {
-            $success = GeneralUtility::rmdir($instancePath, true);
-            if (!$success) {
-                throw new Exception(
-                    'Can not remove folder: ' . $instancePath,
-                    1376657210
-                );
-            }
-        }
-    }
-
-    /**
-     * Create last_run.txt file within instance path containing timestamp of "now".
-     * Used in functional tests to reuse an instance for multiple tests in one test case.
-     *
-     * @param string $instancePath Absolute path to test instance
-     * @return void
-     */
-    public function createLastRunTextfile($instancePath)
-    {
-        // Store the time instance was created
-        file_put_contents($instancePath . '/last_run.txt', time());
-    }
-
-    /**
-     * Link TYPO3 CMS core from "parent" instance.
-     * For functional and acceptance tests.
-     *
-     * @param string $instancePath Absolute path to test instance
-     * @throws Exception
-     * @return void
-     */
-    public function setUpInstanceCoreLinks($instancePath)
-    {
-        $linksToSet = array(
-            ORIGINAL_ROOT . 'typo3' => $instancePath . '/typo3',
-            ORIGINAL_ROOT . 'index.php' => $instancePath . '/index.php'
-        );
-        foreach ($linksToSet as $from => $to) {
-            $success = symlink($from, $to);
-            if (!$success) {
-                throw new Exception(
-                    'Creating link failed: from ' . $from . ' to: ' . $to,
-                    1376657199
-                );
-            }
-        }
-    }
-
-    /**
-     * Link test extensions to the typo3conf/ext folder of the instance.
-     * For functional and acceptance tests.
-     *
-     * @param string $instancePath Absolute path to test instance
-     * @param array $extensionPaths Contains paths to extensions relative to document root
-     * @throws Exception
-     * @return void
-     */
-    public function linkTestExtensionsToInstance($instancePath, array $extensionPaths)
-    {
-        foreach ($extensionPaths as $extensionPath) {
-            $absoluteExtensionPath = ORIGINAL_ROOT . $extensionPath;
-            if (!is_dir($absoluteExtensionPath)) {
-                throw new Exception(
-                    'Test extension path ' . $absoluteExtensionPath . ' not found',
-                    1376745645
-                );
-            }
-            $destinationPath = $instancePath . '/typo3conf/ext/' . basename($absoluteExtensionPath);
-            $success = symlink($absoluteExtensionPath, $destinationPath);
-            if (!$success) {
-                throw new Exception(
-                    'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
-                    1376657142
-                );
-            }
-        }
-    }
-
-    /**
-     * Link paths inside the test instance, e.g. from a fixture fileadmin subfolder to the
-     * test instance fileadmin folder.
-     * For functional and acceptance tests.
-     *
-     * @param string $instancePath Absolute path to test instance
-     * @param array $pathsToLinkInTestInstance Contains paths as array of source => destination in key => value pairs of folders relative to test instance root
-     * @throws Exception if a source path could not be found and on failing creating the symlink
-     * @return void
-     */
-    public function linkPathsInTestInstance($instancePath, array $pathsToLinkInTestInstance)
-    {
-        foreach ($pathsToLinkInTestInstance as $sourcePathToLinkInTestInstance => $destinationPathToLinkInTestInstance) {
-            $sourcePath = $instancePath . '/' . ltrim($sourcePathToLinkInTestInstance, '/');
-            if (!file_exists($sourcePath)) {
-                throw new Exception(
-                    'Path ' . $sourcePath . ' not found',
-                    1376745645
-                );
-            }
-            $destinationPath = $instancePath . '/' . ltrim($destinationPathToLinkInTestInstance, '/');
-            $success = symlink($sourcePath, $destinationPath);
-            if (!$success) {
-                throw new Exception(
-                    'Can not link the path ' . $sourcePath . ' to ' . $destinationPath,
-                    1389969623
-                );
-            }
-        }
-    }
-
-    /**
-     * Database settings for functional and acceptance tests can be either set by
-     * environment variables (recommended), or from an existing LocalConfiguration as fallback.
-     * The method fetches these.
-     *
-     * An unique name will be added to the database name later.
-     *
-     * @throws Exception
-     * @return array [DB][host], [DB][username], ...
-     */
-    public function getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration()
-    {
-        $databaseName = trim(getenv('typo3DatabaseName'));
-        $databaseHost = trim(getenv('typo3DatabaseHost'));
-        $databaseUsername = trim(getenv('typo3DatabaseUsername'));
-        $databasePassword = trim(getenv('typo3DatabasePassword'));
-        $databasePort = trim(getenv('typo3DatabasePort'));
-        $databaseSocket = trim(getenv('typo3DatabaseSocket'));
-        if ($databaseName || $databaseHost || $databaseUsername || $databasePassword || $databasePort || $databaseSocket) {
-            // Try to get database credentials from environment variables first
-            $originalConfigurationArray = [
-                'DB' => [
-                    'Connections' => [
-                        'Default' => [
-                            'driver' => 'mysqli'
-                        ],
-                    ],
-                ],
-            ];
-            if ($databaseName) {
-                $originalConfigurationArray['DB']['Connections']['Default']['dbname'] = $databaseName;
-            }
-            if ($databaseHost) {
-                $originalConfigurationArray['DB']['Connections']['Default']['host'] = $databaseHost;
-            }
-            if ($databaseUsername) {
-                $originalConfigurationArray['DB']['Connections']['Default']['user'] = $databaseUsername;
-            }
-            if ($databasePassword) {
-                $originalConfigurationArray['DB']['Connections']['Default']['password'] = $databasePassword;
-            }
-            if ($databasePort) {
-                $originalConfigurationArray['DB']['Connections']['Default']['port'] = $databasePort;
-            }
-            if ($databaseSocket) {
-                $originalConfigurationArray['DB']['Connections']['Default']['unix_socket'] = $databaseSocket;
-            }
-        } elseif (file_exists(ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php')) {
-            // See if a LocalConfiguration file exists in "parent" instance to get db credentials from
-            $originalConfigurationArray = require ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php';
-        } else {
-            throw new Exception(
-                'Database credentials for tests are neither set through environment'
-                . ' variables, and can not be found in an existing LocalConfiguration file',
-                1397406356
-            );
-        }
-        return $originalConfigurationArray;
-    }
-
-    /**
-     * Maximum length of database names is 64 chars in mysql. Test this is not exceeded
-     * after a suffix has been added.
-     *
-     * @param string $originalDatabaseName Base name of the database
-     * @param array $configuration "LocalConfiguration" array with DB settings
-     * @throws Exception
-     */
-    public function testDatabaseNameIsNotTooLong($originalDatabaseName, array $configuration)
-    {
-        // Maximum database name length for mysql is 64 characters
-        if (strlen($configuration['DB']['Connections']['Default']['dbname']) > 64) {
-            $suffixLength = strlen($configuration['DB']['Connections']['Default']['dbname']) - strlen($originalDatabaseName);
-            $maximumOriginalDatabaseName = 64 - $suffixLength;
-            throw new Exception(
-                'The name of the database that is used for the functional test (' . $originalDatabaseName . ')' .
-                ' exceeds the maximum length of 64 character allowed by MySQL. You have to shorten your' .
-                ' original database name to ' . $maximumOriginalDatabaseName . ' characters',
-                1377600104
-            );
-        }
-    }
-
-    /**
-     * Create LocalConfiguration.php file of the test instance.
-     * For functional and acceptance tests.
-     *
-     * @param string $instancePath Absolute path to test instance
-     * @param array $configuration Base configuration array
-     * @param array $overruleConfiguration Overrule factory and base configuration
-     * @throws Exception
-     * @return void
-     */
-    public function setUpLocalConfiguration($instancePath, array $configuration, array $overruleConfiguration)
-    {
-        // Base of final LocalConfiguration is core factory configuration
-        $finalConfigurationArray = require ORIGINAL_ROOT . 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
-        ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $configuration);
-        ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $overruleConfiguration);
-        $result = $this->writeFile(
-            $instancePath . '/typo3conf/LocalConfiguration.php',
-            '<?php' . chr(10) .
-            'return ' .
-            ArrayUtility::arrayExport(
-                $finalConfigurationArray
-            ) .
-            ';'
-        );
-        if (!$result) {
-            throw new Exception('Can not write local configuration', 1376657277);
-        }
-    }
-
-    /**
-     * Compile typo3conf/PackageStates.php containing default packages like core,
-     * a test specific list of additional core extensions, and a list of
-     * test extensions.
-     * For functional and acceptance tests.
-     *
-     * @param string $instancePath Absolute path to test instance
-     * @param array $defaultCoreExtensionsToLoad Default list of core extensions to load
-     * @param array $additionalCoreExtensionsToLoad Additional core extensions to load
-     * @param array $testExtensionPaths Paths to extensions relative to document root
-     * @throws Exception
-     */
-    public function setUpPackageStates(
-        $instancePath,
-        array $defaultCoreExtensionsToLoad,
-        array $additionalCoreExtensionsToLoad,
-        array $testExtensionPaths
-    ) {
-        $packageStates = array(
-            'packages' => array(),
-            'version' => 5,
-        );
-
-        // Register default list of extensions and set active
-        foreach ($defaultCoreExtensionsToLoad as $extensionName) {
-            $packageStates['packages'][$extensionName] = array(
-                'packagePath' => 'typo3/sysext/' . $extensionName . '/'
-            );
-        }
-
-        // Register additional core extensions and set active
-        foreach ($additionalCoreExtensionsToLoad as $extensionName) {
-            $packageStates['packages'][$extensionName] = array(
-                'packagePath' => 'typo3/sysext/' . $extensionName . '/'
-            );
-        }
-
-        // Activate test extensions that have been symlinked before
-        foreach ($testExtensionPaths as $extensionPath) {
-            $extensionName = basename($extensionPath);
-            $packageStates['packages'][$extensionName] = array(
-                'packagePath' => 'typo3conf/ext/' . $extensionName . '/'
-            );
-        }
-
-        $result = $this->writeFile(
-            $instancePath . '/typo3conf/PackageStates.php',
-            '<?php' . chr(10) .
-            'return ' .
-            ArrayUtility::arrayExport(
-                $packageStates
-            ) .
-            ';'
-        );
-
-        if (!$result) {
-            throw new Exception('Can not write PackageStates', 1381612729);
-        }
-    }
-
-    /**
-     * Populate $GLOBALS['TYPO3_DB'] and create test database
-     * For functional and acceptance tests
-     *
-     * @param string $databaseName Database name of this test instance
-     * @param string $originalDatabaseName Original database name before suffix was added
-     * @throws \TYPO3\CMS\Core\Tests\Exception
-     * @return void
-     */
-    public function setUpTestDatabase($databaseName, $originalDatabaseName)
-    {
-        Bootstrap::getInstance()->initializeTypo3DbGlobal();
-        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
-        $database = $GLOBALS['TYPO3_DB'];
-        if (!$database->sql_pconnect()) {
-            throw new Exception(
-                'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
-                . ' connection to the database was attempted to be established!',
-                1377620117
-            );
-        }
-
-        // Drop database if exists
-        $database->admin_query('DROP DATABASE IF EXISTS `' . $databaseName . '`');
-        $createDatabaseResult = $database->admin_query('CREATE DATABASE `' . $databaseName . '` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci');
-        if (!$createDatabaseResult) {
-            $user = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'];
-            $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'];
-            throw new Exception(
-                'Unable to create database with name ' . $databaseName . '. This is probably a permission problem.'
-                . ' For this instance this could be fixed executing:'
-                . ' GRANT ALL ON `' . $originalDatabaseName . '_%`.* TO `' . $user . '`@`' . $host . '`;',
-                1376579070
-            );
-        }
-        $database->setDatabaseName($databaseName);
-
-       // On windows, this still works, but throws a warning, which we need to discard.
-        @$database->sql_select_db();
-    }
-
-    /**
-     * Bootstrap basic TYPO3. This bootstraps TYPO3 far enough to initialize database afterwards.
-     * For functional and acceptance tests.
-     *
-     * @param string $instancePath Absolute path to test instance
-     * @return void
-     */
-    public function setUpBasicTypo3Bootstrap($instancePath)
-    {
-        $_SERVER['PWD'] = $instancePath;
-        $_SERVER['argv'][0] = 'index.php';
-
-        $classLoader = require rtrim(realpath($instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php';
-        Bootstrap::getInstance()
-            ->initializeClassLoader($classLoader)
-            ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
-            ->baseSetup()
-            ->loadConfigurationAndInitialize(true)
-            ->loadTypo3LoadedExtAndExtLocalconf(true)
-            ->setFinalCachingFrameworkCacheConfiguration()
-            ->defineLoggingAndExceptionConstants()
-            ->unsetReservedGlobalVariables();
-    }
-
-    /**
-     * Populate $GLOBALS['TYPO3_DB'] and truncate all tables.
-     * For functional and acceptance tests.
-     *
-     * @throws Exception
-     * @return void
-     */
-    public function initializeTestDatabaseAndTruncateTables()
-    {
-        Bootstrap::getInstance()->initializeTypo3DbGlobal();
-        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
-        $database = $GLOBALS['TYPO3_DB'];
-        if (!$database->sql_pconnect()) {
-            throw new Exception(
-                'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
-                . ' connection to the database was attempted to be established!',
-                1377620117
-            );
-        }
-        $database->setDatabaseName($GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['dbname']);
-        $database->sql_select_db();
-        foreach ($database->admin_get_tables() as $table) {
-            $database->admin_query('TRUNCATE ' . $table['Name'] . ';');
-        }
-    }
-
-    /**
-     * Load ext_tables.php files.
-     * For functional and acceptance tests.
-     *
-     * @return void
-     */
-    public function loadExtensionTables()
-    {
-        Bootstrap::getInstance()->loadExtensionTables();
-    }
-
-    /**
-     * Create tables and import static rows.
-     * For functional and acceptance tests.
-     *
-     * @return void
-     */
-    public function createDatabaseStructure()
-    {
-        /** @var SqlSchemaMigrationService $schemaMigrationService */
-        $schemaMigrationService = GeneralUtility::makeInstance(SqlSchemaMigrationService::class);
-        /** @var ObjectManager $objectManager */
-        $objectManager = GeneralUtility::makeInstance(ObjectManager::class);
-        /** @var SqlExpectedSchemaService $expectedSchemaService */
-        $expectedSchemaService = $objectManager->get(SqlExpectedSchemaService::class);
-
-        // Raw concatenated ext_tables.sql and friends string
-        $expectedSchemaString = $expectedSchemaService->getTablesDefinitionString(true);
-        $statements = $schemaMigrationService->getStatementArray($expectedSchemaString, true);
-        list($_, $insertCount) = $schemaMigrationService->getCreateTables($statements, true);
-
-        $fieldDefinitionsFile = $schemaMigrationService->getFieldDefinitions_fileContent($expectedSchemaString);
-        $fieldDefinitionsDatabase = $schemaMigrationService->getFieldDefinitions_database();
-        $difference = $schemaMigrationService->getDatabaseExtra($fieldDefinitionsFile, $fieldDefinitionsDatabase);
-        $updateStatements = $schemaMigrationService->getUpdateSuggestions($difference);
-
-        $schemaMigrationService->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
-        $schemaMigrationService->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
-        $schemaMigrationService->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
-
-        foreach ($insertCount as $table => $count) {
-            $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
-            foreach ($insertStatements as $insertQuery) {
-                $insertQuery = rtrim($insertQuery, ';');
-                /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
-                $database = $GLOBALS['TYPO3_DB'];
-                $database->admin_query($insertQuery);
-            }
-        }
-    }
-
-    /**
-     * 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
-     * @throws Exception
-     */
-    public function importXmlDatabaseFixture($path)
-    {
-        if (!is_file($path)) {
-            throw new Exception(
-                'Fixture file ' . $path . ' not found',
-                1376746261
-            );
-        }
-
-        /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
-        $database = $GLOBALS['TYPO3_DB'];
-
-        $fileContent = file_get_contents($path);
-        // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
-        $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
-        $xml = simplexml_load_string($fileContent);
-        libxml_disable_entity_loader($previousValueOfEntityLoader);
-        $foreignKeys = array();
-
-        /** @var $table \SimpleXMLElement */
-        foreach ($xml->children() as $table) {
-            $insertArray = array();
-
-            /** @var $column \SimpleXMLElement */
-            foreach ($table->children() as $column) {
-                $columnName = $column->getName();
-                $columnValue = null;
-
-                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 = (string)$table->$columnName;
-                }
-
-                $insertArray[$columnName] = $columnValue;
-            }
-
-            $tableName = $table->getName();
-            $result = $database->exec_INSERTquery($tableName, $insertArray);
-            if ($result === false) {
-                throw new Exception(
-                    'Error when processing fixture file: ' . $path . ' Can not insert data to table ' . $tableName . ': ' . $database->sql_error(),
-                    1376746262
-                );
-            }
-            if (isset($table['id'])) {
-                $elementId = (string)$table['id'];
-                $foreignKeys[$tableName][$elementId] = $database->sql_insert_id();
-            }
-        }
-    }
-
-    /**
-     * Returns the absolute path the TYPO3 document root.
-     * This is the "original" document root, not the "instance" root for functional / acceptance tests.
-     *
-     * @return string the TYPO3 document root using Unix path separators
-     */
-    protected function getWebRoot()
-    {
-        if (getenv('TYPO3_PATH_WEB')) {
-            $webRoot = getenv('TYPO3_PATH_WEB');
-        } else {
-            $webRoot = getcwd();
-        }
-        return rtrim(strtr($webRoot, '\\', '/'), '/') . '/';
-    }
-
-    /**
-     * Send http headers, echo out a text message and exit with error code
-     *
-     * @param string $message
-     */
-    protected function exitWithMessage($message)
-    {
-        echo $message . LF;
-        exit(1);
-    }
-
-    /**
-     * Writes $content to the file $file. This is a simplified version
-     * of GeneralUtility::writeFile that does not fix permissions.
-     *
-     * @param string $file Filepath to write to
-     * @param string $content Content to write
-     * @return bool TRUE if the file was successfully opened and written to.
-     */
-    protected function writeFile($file, $content)
-    {
-        if ($fd = fopen($file, 'wb')) {
-            $res = fwrite($fd, $content);
-            fclose($fd);
-            if ($res === false) {
-                return false;
-            }
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/typo3/sysext/core/Tests/UnitTestCase.php b/typo3/sysext/core/Tests/UnitTestCase.php
deleted file mode 100644 (file)
index f33be44..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-<?php
-namespace TYPO3\CMS\Core\Tests;
-
-/*
- * This file is part of the TYPO3 CMS project.
- *
- * It is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License, either version 2
- * of the License, or any later version.
- *
- * For the full copyright and license information, please read the
- * LICENSE.txt file that was distributed with this source code.
- *
- * The TYPO3 project - inspiring people to share!
- */
-
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-use TYPO3\CMS\Core\Utility\PathUtility;
-use TYPO3\CMS\Core\Utility\StringUtility;
-
-/**
- * Base test case for unit tests.
- *
- * This class currently only inherits the base test case. However, it is recommended
- * to extend this class for unit test cases instead of the base test case because if,
- * at some point, specific behavior needs to be implemented for unit tests, your test cases
- * will profit from it automatically.
- *
- */
-abstract class UnitTestCase extends BaseTestCase
-{
-    /**
-     * @todo make LoadedExtensionsArray serializable instead
-     *
-     * @var array
-     */
-    protected $backupGlobalsBlacklist = array('TYPO3_LOADED_EXT');
-
-    /**
-     * Absolute path to files that should be removed after a test.
-     * Handled in tearDown. Tests can register here to get any files
-     * within typo3temp/ or typo3conf/ext cleaned up again.
-     *
-     * @var array
-     */
-    protected $testFilesToDelete = array();
-
-    /**
-     * Unset all additional properties of test classes to help PHP
-     * garbage collection. This reduces memory footprint with lots
-     * of tests.
-     *
-     * If overwriting tearDown() in test classes, please call
-     * parent::tearDown() at the end. Unsetting of own properties
-     * is not needed this way.
-     *
-     * @throws \RuntimeException
-     * @return void
-     */
-    protected function tearDown()
-    {
-        // Unset properties of test classes to safe memory
-        $reflection = new \ReflectionObject($this);
-        foreach ($reflection->getProperties() as $property) {
-            $declaringClass = $property->getDeclaringClass()->getName();
-            if (
-                !$property->isStatic()
-                && $declaringClass !== \TYPO3\CMS\Core\Tests\UnitTestCase::class
-                && $declaringClass !== \TYPO3\CMS\Core\Tests\BaseTestCase::class
-                && strpos($property->getDeclaringClass()->getName(), 'PHPUnit_') !== 0
-            ) {
-                $propertyName = $property->getName();
-                unset($this->$propertyName);
-            }
-        }
-        unset($reflection);
-
-        // Delete registered test files and directories
-        foreach ($this->testFilesToDelete as $absoluteFileName) {
-            $absoluteFileName = GeneralUtility::fixWindowsFilePath(PathUtility::getCanonicalPath($absoluteFileName));
-            if (!GeneralUtility::validPathStr($absoluteFileName)) {
-                throw new \RuntimeException('tearDown() cleanup: Filename contains illegal characters', 1410633087);
-            }
-            if (!StringUtility::beginsWith($absoluteFileName, PATH_site . 'typo3temp/var/')) {
-                throw new \RuntimeException(
-                    'tearDown() cleanup:  Files to delete must be within typo3temp/var/',
-                    1410633412
-                );
-            }
-            // file_exists returns false for links pointing to not existing targets, so handle links before next check.
-            if (@is_link($absoluteFileName) || @is_file($absoluteFileName)) {
-                unlink($absoluteFileName);
-            } elseif (@is_dir($absoluteFileName)) {
-                GeneralUtility::rmdir($absoluteFileName, true);
-            } else {
-                throw new \RuntimeException('tearDown() cleanup: File, link or directory does not exist', 1410633510);
-            }
-        }
-        $this->testFilesToDelete = array();
-    }
-}