[!!!][TASK] Extract testing framework for TYPO3 25/50125/18
authorSusanne Moog <susanne.moog@typo3.com>
Sun, 18 Dec 2016 12:12:41 +0000 (13:12 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Thu, 22 Dec 2016 13:51:23 +0000 (14:51 +0100)
Add testing framework component for independent usage

Resolves: #79025
Releases: master
Change-Id: I23cfd2ed42108d7d80ec776e778a1ac2d5293e55
Reviewed-on: https://review.typo3.org/50125
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
Tested-by: TYPO3com <no-reply@typo3.com>
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
651 files changed:
.travis.yml
components/testing_framework/core/Acceptance/Fixtures/be_groups.xml [new file with mode: 0644]
components/testing_framework/core/Acceptance/Fixtures/be_sessions.xml [new file with mode: 0644]
components/testing_framework/core/Acceptance/Fixtures/be_users.xml [new file with mode: 0644]
components/testing_framework/core/Acceptance/Fixtures/sys_category.xml [new file with mode: 0644]
components/testing_framework/core/Acceptance/Fixtures/tx_extensionmanager_domain_model_extension.xml [new file with mode: 0644]
components/testing_framework/core/Acceptance/Fixtures/tx_extensionmanager_domain_model_repository.xml [new file with mode: 0644]
components/testing_framework/core/Acceptance/Step/Backend/Admin.php [new file with mode: 0644]
components/testing_framework/core/Acceptance/Step/Backend/Editor.php [new file with mode: 0644]
components/testing_framework/core/Acceptance/Support/Helper/FormHandlerElementTestDataObject.php [new file with mode: 0644]
components/testing_framework/core/Acceptance/Support/Helper/Formhandler.php [new file with mode: 0644]
components/testing_framework/core/Acceptance/Support/Helper/ModalDialog.php [new file with mode: 0644]
components/testing_framework/core/Acceptance/Support/Helper/Topbar.php [new file with mode: 0644]
components/testing_framework/core/Acceptance/Support/Page/PageTree.php [new file with mode: 0644]
components/testing_framework/core/AccessibleObjectInterface.php [new file with mode: 0644]
components/testing_framework/core/BaseTestCase.php [new file with mode: 0644]
components/testing_framework/core/Build/AcceptanceTests.yml [new file with mode: 0644]
components/testing_framework/core/Build/Configuration/Acceptance/Support/AcceptanceTester.php [new file with mode: 0644]
components/testing_framework/core/Build/Configuration/Acceptance/Support/Helper/Acceptance.php [new file with mode: 0644]
components/testing_framework/core/Build/Configuration/JSUnit/Bootstrap.js [new file with mode: 0644]
components/testing_framework/core/Build/Configuration/JSUnit/Helper.js [new file with mode: 0644]
components/testing_framework/core/Build/Configuration/JSUnit/karma.conf.js [new file with mode: 0644]
components/testing_framework/core/Build/FunctionalTests.xml [new file with mode: 0644]
components/testing_framework/core/Build/FunctionalTestsBootstrap.php [new file with mode: 0644]
components/testing_framework/core/Build/Scripts/splitFunctionalTests.sh [new file with mode: 0755]
components/testing_framework/core/Build/UnitTests.xml [new file with mode: 0644]
components/testing_framework/core/Build/UnitTestsBootstrap.php [new file with mode: 0644]
components/testing_framework/core/Exception.php [new file with mode: 0644]
components/testing_framework/core/FileStreamWrapper.php [new file with mode: 0644]
components/testing_framework/core/Functional/Fixtures/be_users.xml [new file with mode: 0644]
components/testing_framework/core/Functional/Fixtures/pages.xml [new file with mode: 0644]
components/testing_framework/core/Functional/Fixtures/sys_file_storage.xml [new file with mode: 0644]
components/testing_framework/core/Functional/Fixtures/sys_language.xml [new file with mode: 0644]
components/testing_framework/core/Functional/Fixtures/tt_content.xml [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/AbstractRecordConstraint.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/AbstractStructureRecordConstraint.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/DoesNotHaveRecordConstraint.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/HasRecordConstraint.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/StructureDoesNotHaveRecordConstraint.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/StructureHasRecordConstraint.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Frontend/Collector.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Frontend/Hook/BackendUserHandler.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Frontend/Hook/FrontendUserHandler.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Frontend/Parser.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Frontend/Renderer.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Frontend/RequestBootstrap.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Frontend/Response.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Frontend/ResponseContent.php [new file with mode: 0644]
components/testing_framework/core/Functional/Framework/Frontend/ResponseSection.php [new file with mode: 0644]
components/testing_framework/core/FunctionalTestCase.php [new file with mode: 0644]
components/testing_framework/core/Testbase.php [new file with mode: 0644]
components/testing_framework/core/UnitTestCase.php [new file with mode: 0644]
composer.json
composer.lock
typo3/sysext/backend/Tests/Functional/Controller/Page/LocalizationControllerTest.php
typo3/sysext/backend/Tests/Unit/Configuration/TypoScript/ConditionMatching/ConditionMatcherTest.php
typo3/sysext/backend/Tests/Unit/Controller/EditDocumentControllerTest.php
typo3/sysext/backend/Tests/Unit/Controller/File/FileControllerTest.php
typo3/sysext/backend/Tests/Unit/Controller/FormInlineAjaxControllerTest.php
typo3/sysext/backend/Tests/Unit/Controller/LoginControllerTest.php
typo3/sysext/backend/Tests/Unit/Controller/PageLayoutControllerTest.php
typo3/sysext/backend/Tests/Unit/Controller/SelectTreeControllerTest.php
typo3/sysext/backend/Tests/Unit/Controller/Wizard/SuggestWizardControllerTest.php
typo3/sysext/backend/Tests/Unit/Form/Element/GroupElementTest.php
typo3/sysext/backend/Tests/Unit/Form/Element/InputHiddenElementTest.php
typo3/sysext/backend/Tests/Unit/Form/Element/InputTextElementTest.php
typo3/sysext/backend/Tests/Unit/Form/Element/NoneElementTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataCompilerTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/FlexFormSegmentTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/InlineParentRecordTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/OnTheFlyTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/TcaDatabaseRecordTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataGroup/TcaInputPlaceholderRecordTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseEditRowTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseEffectivePidTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseLanguageRowsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabasePageLanguageOverlayRowsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseParentPageRowTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordOverrideValuesTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRecordTypeValueTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowDateTimeFieldsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowDefaultValuesTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseRowInitializeNewTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseSystemLanguageRowsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseUniqueUidNewRowTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseUserPermissionCheckTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/EvaluateDisplayConditionsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/InitializeProcessedTcaTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/InlineOverrrideChildTcaTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/PageTsConfigMergedTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/ParentPageTcaTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaCheckboxItemsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaColumnsOverridesTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaColumnsProcessCommonTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaColumnsProcessFieldLabelsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaColumnsProcessPlaceholdersTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaColumnsProcessRecordTitleTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaColumnsProcessShowitemTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaColumnsRemoveUnusedTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaFlexPrepareTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaFlexProcessTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaGroupTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaInlineConfigurationTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaInlineExpandCollapseStateTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaInlineIsOnSymmetricSideTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaInlineTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaInputPlaceholdersTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaRadioItemsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaRecordTitleTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectItemsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaSelectTreeItemsTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/TcaTypesShowitemTest.php
typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/UserTsConfigTest.php
typo3/sysext/backend/Tests/Unit/Form/InlineStackProcessorTest.php
typo3/sysext/backend/Tests/Unit/Form/NodeFactoryTest.php
typo3/sysext/backend/Tests/Unit/Form/Utility/FormEngineUtilityTest.php
typo3/sysext/backend/Tests/Unit/Form/Wizard/SuggestWizardTest.php
typo3/sysext/backend/Tests/Unit/Module/ModuleLoaderTest.php
typo3/sysext/backend/Tests/Unit/Template/Components/Button/FullyRenderedButtonTest.php
typo3/sysext/backend/Tests/Unit/Template/Components/Button/InputButtonTest.php
typo3/sysext/backend/Tests/Unit/Template/Components/Button/LinkButtonTest.php
typo3/sysext/backend/Tests/Unit/Template/Components/Button/SplitButtonTest.php
typo3/sysext/backend/Tests/Unit/Template/Components/Menu/MenuItemTest.php
typo3/sysext/backend/Tests/Unit/Template/Components/MenuTest.php
typo3/sysext/backend/Tests/Unit/Tree/Pagetree/DataProviderTest.php
typo3/sysext/backend/Tests/Unit/Tree/SortedTreeNodeCollectionTest.php
typo3/sysext/backend/Tests/Unit/Tree/TreeNodeCollectionTest.php
typo3/sysext/backend/Tests/Unit/Tree/TreeNodeTest.php
typo3/sysext/backend/Tests/Unit/Utility/BackendUtilityTest.php
typo3/sysext/backend/Tests/Unit/View/BackendLayout/BackendLayoutCollectionTest.php
typo3/sysext/backend/Tests/Unit/View/BackendLayout/BackendLayoutTest.php
typo3/sysext/backend/Tests/Unit/View/BackendLayout/DataProviderCollectionTest.php
typo3/sysext/backend/Tests/Unit/View/BackendLayoutViewTest.php
typo3/sysext/belog/Tests/Unit/Domain/Model/ConstraintTest.php
typo3/sysext/belog/Tests/Unit/Domain/Model/LogEntryTest.php
typo3/sysext/belog/Tests/Unit/Domain/Repository/HistoryEntryRepositoryTest.php
typo3/sysext/belog/Tests/Unit/Domain/Repository/LogEntryRepositoryTest.php
typo3/sysext/belog/Tests/Unit/Domain/Repository/WorkspaceRepositoryTest.php
typo3/sysext/beuser/Tests/Unit/Domain/Model/BackendUserTest.php
typo3/sysext/beuser/Tests/Unit/Domain/Model/DemandTest.php
typo3/sysext/beuser/Tests/Unit/Domain/Repository/BackendUserRepositoryTest.php
typo3/sysext/beuser/Tests/Unit/Domain/Repository/BackendUserSessionRepositoryTest.php
typo3/sysext/beuser/Tests/Unit/Service/ModuleDataStorageServiceTest.php
typo3/sysext/core/Build/AcceptanceTests.yml [deleted file]
typo3/sysext/core/Build/Configuration/Acceptance/Support/AcceptanceTester.php [deleted file]
typo3/sysext/core/Build/Configuration/Acceptance/Support/Helper/Acceptance.php [deleted file]
typo3/sysext/core/Build/Configuration/JSUnit/Bootstrap.js [deleted file]
typo3/sysext/core/Build/Configuration/JSUnit/Helper.js [deleted file]
typo3/sysext/core/Build/Configuration/JSUnit/karma.conf.js [deleted file]
typo3/sysext/core/Build/FunctionalTests.xml [deleted file]
typo3/sysext/core/Build/FunctionalTestsBootstrap.php [deleted file]
typo3/sysext/core/Build/Scripts/splitFunctionalTests.sh [deleted file]
typo3/sysext/core/Build/UnitTests.xml [deleted file]
typo3/sysext/core/Build/UnitTestsBootstrap.php [deleted file]
typo3/sysext/core/Classes/Tests/AccessibleObjectInterface.php [deleted file]
typo3/sysext/core/Classes/Tests/BaseTestCase.php [deleted file]
typo3/sysext/core/Classes/Tests/Exception.php [deleted file]
typo3/sysext/core/Classes/Tests/FileStreamWrapper.php [deleted file]
typo3/sysext/core/Classes/Tests/FunctionalTestCase.php [deleted file]
typo3/sysext/core/Classes/Tests/Testbase.php [deleted file]
typo3/sysext/core/Classes/Tests/UnitTestCase.php [deleted file]
typo3/sysext/core/Documentation/Changelog/master/Breaking-79025-ExtractTestingFrameworkForTYPO3.rst [new file with mode: 0644]
typo3/sysext/core/Tests/Acceptance/Fixtures/be_groups.xml [deleted file]
typo3/sysext/core/Tests/Acceptance/Fixtures/be_sessions.xml [deleted file]
typo3/sysext/core/Tests/Acceptance/Fixtures/be_users.xml [deleted file]
typo3/sysext/core/Tests/Acceptance/Fixtures/sys_category.xml [deleted file]
typo3/sysext/core/Tests/Acceptance/Fixtures/tx_extensionmanager_domain_model_extension.xml [deleted file]
typo3/sysext/core/Tests/Acceptance/Fixtures/tx_extensionmanager_domain_model_repository.xml [deleted file]
typo3/sysext/core/Tests/Acceptance/Step/Backend/Admin.php [deleted file]
typo3/sysext/core/Tests/Acceptance/Step/Backend/Editor.php [deleted file]
typo3/sysext/core/Tests/Acceptance/Support/Helper/FormHandlerElementTestDataObject.php [deleted file]
typo3/sysext/core/Tests/Acceptance/Support/Helper/Formhandler.php [deleted file]
typo3/sysext/core/Tests/Acceptance/Support/Helper/ModalDialog.php [deleted file]
typo3/sysext/core/Tests/Acceptance/Support/Helper/Topbar.php [deleted file]
typo3/sysext/core/Tests/Acceptance/Support/Page/PageTree.php [deleted file]
typo3/sysext/core/Tests/AcceptanceCoreEnvironment.php
typo3/sysext/core/Tests/Functional/Cache/Backend/Typo3DatabaseBackendTest.php
typo3/sysext/core/Tests/Functional/Category/Collection/CategoryCollectionTest.php
typo3/sysext/core/Tests/Functional/Collection/RecordCollectionRepositoryTest.php
typo3/sysext/core/Tests/Functional/DataHandling/AbstractDataHandlerActionTestCase.php
typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/GetUniqueTest.php
typo3/sysext/core/Tests/Functional/DataHandling/FAL/AbstractActionTestCase.php
typo3/sysext/core/Tests/Functional/DataHandling/Framework/ActionService.php
typo3/sysext/core/Tests/Functional/Database/DatabaseConnectionTest.php
typo3/sysext/core/Tests/Functional/Database/PreparedStatementTest.php
typo3/sysext/core/Tests/Functional/Database/Schema/SchemaMigratorTest.php
typo3/sysext/core/Tests/Functional/Fixtures/Frontend/AdditionalConfiguration.php
typo3/sysext/core/Tests/Functional/Fixtures/Frontend/ExtbaseJsonRenderer.ts
typo3/sysext/core/Tests/Functional/Fixtures/Frontend/JsonRenderer.ts
typo3/sysext/core/Tests/Functional/Fixtures/Frontend/request.tpl
typo3/sysext/core/Tests/Functional/Fixtures/be_users.xml [deleted file]
typo3/sysext/core/Tests/Functional/Fixtures/pages.xml [deleted file]
typo3/sysext/core/Tests/Functional/Fixtures/sys_file_storage.xml [deleted file]
typo3/sysext/core/Tests/Functional/Fixtures/sys_language.xml [deleted file]
typo3/sysext/core/Tests/Functional/Fixtures/tt_content.xml [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Constraint/RequestSection/AbstractRecordConstraint.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Constraint/RequestSection/AbstractStructureRecordConstraint.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Constraint/RequestSection/DoesNotHaveRecordConstraint.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Constraint/RequestSection/HasRecordConstraint.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Constraint/RequestSection/StructureDoesNotHaveRecordConstraint.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Constraint/RequestSection/StructureHasRecordConstraint.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Frontend/Collector.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/BackendUserHandler.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Frontend/Hook/FrontendUserHandler.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Frontend/Parser.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Frontend/Renderer.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Frontend/RequestBootstrap.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Frontend/Response.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseContent.php [deleted file]
typo3/sysext/core/Tests/Functional/Framework/Frontend/ResponseSection.php [deleted file]
typo3/sysext/core/Tests/Functional/Log/Writer/DatabaseWriterTest.php
typo3/sysext/core/Tests/Functional/Messaging/FlashMessageQueueTest.php
typo3/sysext/core/Tests/Functional/Page/PageRendererTest.php
typo3/sysext/core/Tests/Functional/RegistryTest.php
typo3/sysext/core/Tests/Functional/Resource/ResourceStorageTest.php
typo3/sysext/core/Tests/Functional/Tca/BackendGroupsVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/BackendUsersVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/CategoryVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/CollectionVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/FileCollectionVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/FileMetadataVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/FileStorageVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/FilemountsVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/LanguageVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/NewsVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/Tca/PagesVisibleFieldsTest.php
typo3/sysext/core/Tests/Functional/TypoScript/Parser/TypoScriptParserTest.php
typo3/sysext/core/Tests/Integrity/IntegrityTest.php
typo3/sysext/core/Tests/Legacy/typo3/contrib/class.removexssTest.php
typo3/sysext/core/Tests/Unit/Authentication/AbstractUserAuthenticationTest.php
typo3/sysext/core/Tests/Unit/Authentication/BackendUserAuthenticationTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/AbstractBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/ApcBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/ApcuBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/FileBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/MemcachedBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/PdoBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/RedisBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/TransientMemoryBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/Typo3DatabaseBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/Backend/WincacheBackendTest.php
typo3/sysext/core/Tests/Unit/Cache/CacheFactoryTest.php
typo3/sysext/core/Tests/Unit/Cache/CacheManagerTest.php
typo3/sysext/core/Tests/Unit/Cache/Frontend/AbstractFrontendTest.php
typo3/sysext/core/Tests/Unit/Cache/Frontend/PhpFrontendTest.php
typo3/sysext/core/Tests/Unit/Cache/Frontend/StringFrontendTest.php
typo3/sysext/core/Tests/Unit/Cache/Frontend/VariableFrontendTest.php
typo3/sysext/core/Tests/Unit/Category/CategoryRegistryTest.php
typo3/sysext/core/Tests/Unit/Category/Collection/CategoryCollectionTest.php
typo3/sysext/core/Tests/Unit/Charset/CharsetConverterTest.php
typo3/sysext/core/Tests/Unit/Configuration/ConfigurationManagerTest.php
typo3/sysext/core/Tests/Unit/Configuration/FlexForm/FlexFormToolsTest.php
typo3/sysext/core/Tests/Unit/Configuration/TypoScript/ConditionMatching/AbstractConditionMatcherTest.php
typo3/sysext/core/Tests/Unit/Core/ApplicationContextTest.php
typo3/sysext/core/Tests/Unit/Core/ClassLoadingInformationGeneratorTest.php
typo3/sysext/core/Tests/Unit/Core/SystemEnvironmentBuilderTest.php
typo3/sysext/core/Tests/Unit/Crypto/RandomTest.php
typo3/sysext/core/Tests/Unit/DataHandling/DataHandlerTest.php
typo3/sysext/core/Tests/Unit/Database/ConnectionPoolTest.php
typo3/sysext/core/Tests/Unit/Database/ConnectionTest.php
typo3/sysext/core/Tests/Unit/Database/DatabaseConnectionTest.php
typo3/sysext/core/Tests/Unit/Database/PreparedStatementTest.php
typo3/sysext/core/Tests/Unit/Database/Query/BulkInsertTest.php
typo3/sysext/core/Tests/Unit/Database/Query/Expression/ExpressionBuilderTest.php
typo3/sysext/core/Tests/Unit/Database/Query/QueryBuilderTest.php
typo3/sysext/core/Tests/Unit/Database/Query/QueryHelperTest.php
typo3/sysext/core/Tests/Unit/Database/Query/Restriction/AbstractRestrictionTestCase.php
typo3/sysext/core/Tests/Unit/Database/RelationHandlerTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/ConnectionMigratorTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/EventListener/SchemaColumnDefinitionListenerTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Parser/AbstractDataTypeBaseTestCase.php
typo3/sysext/core/Tests/Unit/Database/Schema/Parser/ColumnDefinitionAttributesTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Parser/ColumnDefinitionItemTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Parser/CreateTableFragmentTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Parser/ForeignKeyDefinitionTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Parser/IndexDefinitionTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Parser/ReferenceDefinitionTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Parser/TableBuilderTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Parser/TableOptionsTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/SqlReaderTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Types/EnumTypeTest.php
typo3/sysext/core/Tests/Unit/Database/Schema/Types/SetTypeTest.php
typo3/sysext/core/Tests/Unit/Encoder/JavaScriptEncoderTest.php
typo3/sysext/core/Tests/Unit/Error/DebugExceptionHandlerTest.php
typo3/sysext/core/Tests/Unit/Error/ProductionExceptionHandlerTest.php
typo3/sysext/core/Tests/Unit/FileStreamWrapperTest.php
typo3/sysext/core/Tests/Unit/FormProtection/AbstractFormProtectionTest.php
typo3/sysext/core/Tests/Unit/FormProtection/BackendFormProtectionTest.php
typo3/sysext/core/Tests/Unit/FormProtection/FormProtectionFactoryTest.php
typo3/sysext/core/Tests/Unit/FormProtection/InstallToolFormProtectionTest.php
typo3/sysext/core/Tests/Unit/Html/HtmlParserTest.php
typo3/sysext/core/Tests/Unit/Html/RteHtmlParserTest.php
typo3/sysext/core/Tests/Unit/Http/MessageTest.php
typo3/sysext/core/Tests/Unit/Http/RequestTest.php
typo3/sysext/core/Tests/Unit/Http/ResponseTest.php
typo3/sysext/core/Tests/Unit/Http/ServerRequestFactoryTest.php
typo3/sysext/core/Tests/Unit/Http/ServerRequestTest.php
typo3/sysext/core/Tests/Unit/Http/StreamTest.php
typo3/sysext/core/Tests/Unit/Http/UploadedFileTest.php
typo3/sysext/core/Tests/Unit/Http/UriTest.php
typo3/sysext/core/Tests/Unit/Imaging/DimensionTest.php
typo3/sysext/core/Tests/Unit/Imaging/GraphicalFunctionsTest.php
typo3/sysext/core/Tests/Unit/Imaging/IconFactoryTest.php
typo3/sysext/core/Tests/Unit/Imaging/IconProvider/BitmapIconProviderTest.php
typo3/sysext/core/Tests/Unit/Imaging/IconProvider/FontawesomeIconProviderTest.php
typo3/sysext/core/Tests/Unit/Imaging/IconProvider/SvgIconProviderTest.php
typo3/sysext/core/Tests/Unit/Imaging/IconRegistryTest.php
typo3/sysext/core/Tests/Unit/Imaging/IconTest.php
typo3/sysext/core/Tests/Unit/LinkHandling/EmailLinkHandlerTest.php
typo3/sysext/core/Tests/Unit/LinkHandling/FileLinkHandlerTest.php
typo3/sysext/core/Tests/Unit/LinkHandling/FolderLinkHandlerTest.php
typo3/sysext/core/Tests/Unit/LinkHandling/LegacyLinkNotationConverterTest.php
typo3/sysext/core/Tests/Unit/LinkHandling/LinkServiceTest.php
typo3/sysext/core/Tests/Unit/LinkHandling/PageLinkHandlerTest.php
typo3/sysext/core/Tests/Unit/LinkHandling/UrlLinkHandlerTest.php
typo3/sysext/core/Tests/Unit/Localization/LocalesTest.php
typo3/sysext/core/Tests/Unit/Localization/LocalizationFactoryTest.php
typo3/sysext/core/Tests/Unit/Localization/Parser/LocallangXmlParserTest.php
typo3/sysext/core/Tests/Unit/Localization/Parser/XliffParserTest.php
typo3/sysext/core/Tests/Unit/Locking/FileLockStrategyTest.php
typo3/sysext/core/Tests/Unit/Locking/LockFactoryTest.php
typo3/sysext/core/Tests/Unit/Locking/SemaphoreLockStrategyTest.php
typo3/sysext/core/Tests/Unit/Locking/SimpleLockStrategyTest.php
typo3/sysext/core/Tests/Unit/Log/LogLevelTest.php
typo3/sysext/core/Tests/Unit/Log/LogManagerTest.php
typo3/sysext/core/Tests/Unit/Log/LogRecordTest.php
typo3/sysext/core/Tests/Unit/Log/LoggerTest.php
typo3/sysext/core/Tests/Unit/Log/Processor/AbstractMemoryTest.php
typo3/sysext/core/Tests/Unit/Log/Processor/AbstractProcessorTest.php
typo3/sysext/core/Tests/Unit/Log/Processor/IntrospectionProcessorTest.php
typo3/sysext/core/Tests/Unit/Log/Processor/MemoryPeakUsageProcessorTest.php
typo3/sysext/core/Tests/Unit/Log/Processor/MemoryUsageProcessorTest.php
typo3/sysext/core/Tests/Unit/Log/Processor/WebProcessorTest.php
typo3/sysext/core/Tests/Unit/Log/Writer/AbstractWriterTest.php
typo3/sysext/core/Tests/Unit/Log/Writer/DatabaseWriterTest.php
typo3/sysext/core/Tests/Unit/Log/Writer/FileWriterTest.php
typo3/sysext/core/Tests/Unit/Mail/MailMessageTest.php
typo3/sysext/core/Tests/Unit/Mail/MailerTest.php
typo3/sysext/core/Tests/Unit/Messaging/FlashMessageServiceTest.php
typo3/sysext/core/Tests/Unit/Migrations/TcaMigrationTest.php
typo3/sysext/core/Tests/Unit/Package/DependencyResolverTest.php
typo3/sysext/core/Tests/Unit/Package/PackageManagerTest.php
typo3/sysext/core/Tests/Unit/Package/PackageTest.php
typo3/sysext/core/Tests/Unit/Page/PageRendererTest.php
typo3/sysext/core/Tests/Unit/RegistryTest.php
typo3/sysext/core/Tests/Unit/Resource/AbstractFileTest.php
typo3/sysext/core/Tests/Unit/Resource/BaseTestCase.php
typo3/sysext/core/Tests/Unit/Resource/Driver/AbstractHierarchicalFilesystemDriverTest.php
typo3/sysext/core/Tests/Unit/Resource/Driver/DriverRegistryTest.php
typo3/sysext/core/Tests/Unit/Resource/Driver/LocalDriverTest.php
typo3/sysext/core/Tests/Unit/Resource/FileReferenceTest.php
typo3/sysext/core/Tests/Unit/Resource/FileTest.php
typo3/sysext/core/Tests/Unit/Resource/Filter/FileNameFilterTest.php
typo3/sysext/core/Tests/Unit/Resource/FolderTest.php
typo3/sysext/core/Tests/Unit/Resource/Index/ExtractorRegistryTest.php
typo3/sysext/core/Tests/Unit/Resource/Index/IndexerTest.php
typo3/sysext/core/Tests/Unit/Resource/ProcessedFileTest.php
typo3/sysext/core/Tests/Unit/Resource/Processing/LocalPreviewHelperTest.php
typo3/sysext/core/Tests/Unit/Resource/Rendering/AudioTagRendererTest.php
typo3/sysext/core/Tests/Unit/Resource/Rendering/RendererRegistryTest.php
typo3/sysext/core/Tests/Unit/Resource/Rendering/VideoTagRendererTest.php
typo3/sysext/core/Tests/Unit/Resource/Rendering/VimeoRendererTest.php
typo3/sysext/core/Tests/Unit/Resource/Rendering/YouTubeRendererTest.php
typo3/sysext/core/Tests/Unit/Resource/Repository/AbstractRepositoryTest.php
typo3/sysext/core/Tests/Unit/Resource/ResourceCompressorTest.php
typo3/sysext/core/Tests/Unit/Resource/ResourceFactoryTest.php
typo3/sysext/core/Tests/Unit/Resource/ResourceStorageTest.php
typo3/sysext/core/Tests/Unit/Resource/TextExtraction/PlainTextExtractorTest.php
typo3/sysext/core/Tests/Unit/Resource/TextExtraction/TextExtractorRegistryTest.php
typo3/sysext/core/Tests/Unit/Resource/Utility/FileExtensionFilterTest.php
typo3/sysext/core/Tests/Unit/Service/DependencyOrderingServiceTest.php
typo3/sysext/core/Tests/Unit/Service/MarkerBasedTemplateServiceTest.php
typo3/sysext/core/Tests/Unit/Tree/TableConfiguration/DatabaseTreeDataProviderTest.php
typo3/sysext/core/Tests/Unit/Tree/TableConfiguration/TreeDataProviderFactoryTest.php
typo3/sysext/core/Tests/Unit/Type/EnumerationTest.php
typo3/sysext/core/Tests/Unit/Type/File/FileInfoTest.php
typo3/sysext/core/Tests/Unit/Type/File/ImageInfoTest.php
typo3/sysext/core/Tests/Unit/TypoScript/Parser/TypoScriptParserTest.php
typo3/sysext/core/Tests/Unit/TypoScript/TemplateServiceTest.php
typo3/sysext/core/Tests/Unit/Utility/ArrayUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/ClassNamingUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/ClientUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/CommandUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/CsvUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/DebugUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/ExtensionManagementUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/File/ExtendedFileUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/GeneralUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/HttpUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/MailUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/MathUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/PathUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/ResourceUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/RootlineUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/StringUtilityTest.php
typo3/sysext/core/Tests/Unit/Utility/VersionNumberUtilityTest.php
typo3/sysext/css_styled_content/Tests/Functional/Tca/ContentVisibleFieldsTest.php
typo3/sysext/documentation/Tests/Unit/Domain/Model/DocumentFormatTest.php
typo3/sysext/documentation/Tests/Unit/Domain/Model/DocumentTest.php
typo3/sysext/documentation/Tests/Unit/Domain/Model/DocumentTranslationTest.php
typo3/sysext/extbase/Tests/Functional/Configuration/BackendConfigurationManagerTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/AddTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/CountTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/EnableFieldsTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/Fixtures/Frontend/JsonRenderer.ts
typo3/sysext/extbase/Tests/Functional/Persistence/Generic/Mapper/DataMapperTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/InTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/OperatorTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/QueryParserTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/RelationTest.php
typo3/sysext/extbase/Tests/Functional/Persistence/TranslationTest.php
typo3/sysext/extbase/Tests/Unit/Configuration/AbstractConfigurationManagerTest.php
typo3/sysext/extbase/Tests/Unit/Configuration/BackendConfigurationManagerTest.php
typo3/sysext/extbase/Tests/Unit/Configuration/FrontendConfigurationManagerTest.php
typo3/sysext/extbase/Tests/Unit/Core/BootstrapTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Model/BackendUserGroupTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Model/BackendUserTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Model/CategoryTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Model/FileMountTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Model/FrontendUserGroupTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Model/FrontendUserTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Repository/BackendUserGroupRepositoryTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Repository/BackendUserRepositoryTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Repository/CategoryRepositoryTest.php
typo3/sysext/extbase/Tests/Unit/Domain/Repository/FileMountRepositoryTest.php
typo3/sysext/extbase/Tests/Unit/DomainObject/AbstractEntityTest.php
typo3/sysext/extbase/Tests/Unit/Error/ErrorTest.php
typo3/sysext/extbase/Tests/Unit/Error/MessageTest.php
typo3/sysext/extbase/Tests/Unit/Error/ResultTest.php
typo3/sysext/extbase/Tests/Unit/Hook/DataHandler/CheckFlexFormValueTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Cli/CommandManagerTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Cli/CommandTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Cli/RequestBuilderTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Cli/RequestTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Controller/AbstractControllerTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Controller/ActionControllerTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Controller/ArgumentTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Controller/ArgumentsTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Controller/CommandControllerTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Controller/MvcPropertyMappingConfigurationServiceTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/RequestTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/ResponseTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/View/JsonViewTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Web/CacheHashEnforcerTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Web/RequestBuilderTest.php
typo3/sysext/extbase/Tests/Unit/Mvc/Web/Routing/UriBuilderTest.php
typo3/sysext/extbase/Tests/Unit/Object/Container/ClassInfoFactoryTest.php
typo3/sysext/extbase/Tests/Unit/Object/Container/ContainerTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/BackendTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/DataMapFactoryTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Mapper/DataMapperTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/PersistenceManagerTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/QueryFactoryTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/QueryResultTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/QueryTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/SessionTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbBackendTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Storage/Typo3DbQueryParserTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/Generic/Typo3QuerySettingsTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/ObjectStorageTest.php
typo3/sysext/extbase/Tests/Unit/Persistence/RepositoryTest.php
typo3/sysext/extbase/Tests/Unit/Property/PropertyMapperTest.php
typo3/sysext/extbase/Tests/Unit/Property/PropertyMappingConfigurationBuilderTest.php
typo3/sysext/extbase/Tests/Unit/Property/PropertyMappingConfigurationTest.php
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/ArrayConverterTest.php
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/BooleanConverterTest.php
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/DateTimeConverterTest.php
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/FloatConverterTest.php
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/IntegerConverterTest.php
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/ObjectConverterTest.php
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/PersistentObjectConverterTest.php
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/StringConverterTest.php
typo3/sysext/extbase/Tests/Unit/Reflection/ClassSchemaTest.php
typo3/sysext/extbase/Tests/Unit/Reflection/DocCommentParserTest.php
typo3/sysext/extbase/Tests/Unit/Reflection/ObjectAccessTest.php
typo3/sysext/extbase/Tests/Unit/Reflection/ReflectionServiceTest.php
typo3/sysext/extbase/Tests/Unit/Scheduler/FieldProviderTest.php
typo3/sysext/extbase/Tests/Unit/Scheduler/TaskTest.php
typo3/sysext/extbase/Tests/Unit/Security/Cryptography/HashServiceTest.php
typo3/sysext/extbase/Tests/Unit/Service/CacheServiceTest.php
typo3/sysext/extbase/Tests/Unit/Service/ExtensionServiceTest.php
typo3/sysext/extbase/Tests/Unit/Service/FlexFormServiceTest.php
typo3/sysext/extbase/Tests/Unit/Service/ImageScriptServiceTest.php
typo3/sysext/extbase/Tests/Unit/Service/TypoScriptServiceTest.php
typo3/sysext/extbase/Tests/Unit/SignalSlot/DispatcherTest.php
typo3/sysext/extbase/Tests/Unit/Utility/ArrayUtilityTest.php
typo3/sysext/extbase/Tests/Unit/Utility/DebuggerUtilityTest.php
typo3/sysext/extbase/Tests/Unit/Utility/ExtensionUtilityTest.php
typo3/sysext/extbase/Tests/Unit/Utility/LocalizationUtilityTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/AbstractCompositeValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/AbstractValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/AbstractValidatorTestcase.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/AlphanumericValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/CollectionValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/ConjunctionValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/DisjunctionValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/EmailAddressValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/NumberRangeValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/NumberValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/RegularExpressionValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/Validator/StringLengthValidatorTest.php
typo3/sysext/extbase/Tests/Unit/Validation/ValidatorResolverTest.php
typo3/sysext/extensionmanager/Tests/Unit/Controller/DownloadControllerTest.php
typo3/sysext/extensionmanager/Tests/Unit/Controller/UpdateFromTerControllerTest.php
typo3/sysext/extensionmanager/Tests/Unit/Controller/UploadExtensionFileControllerTest.php
typo3/sysext/extensionmanager/Tests/Unit/Domain/Model/DownloadQueueTest.php
typo3/sysext/extensionmanager/Tests/Unit/Domain/Model/ExtensionTest.php
typo3/sysext/extensionmanager/Tests/Unit/Domain/Repository/ConfigurationItemRepositoryTest.php
typo3/sysext/extensionmanager/Tests/Unit/Domain/Repository/RepositoryRepositoryTest.php
typo3/sysext/extensionmanager/Tests/Unit/Report/ExtensionStatusTest.php
typo3/sysext/extensionmanager/Tests/Unit/Service/ExtensionManagementServiceTest.php
typo3/sysext/extensionmanager/Tests/Unit/Task/UpdateExtensionListTaskTest.php
typo3/sysext/extensionmanager/Tests/Unit/Utility/ConfigurationUtilityTest.php
typo3/sysext/extensionmanager/Tests/Unit/Utility/DependencyUtilityTest.php
typo3/sysext/extensionmanager/Tests/Unit/Utility/EmConfUtilityTest.php
typo3/sysext/extensionmanager/Tests/Unit/Utility/ExtensionModelUtilityTest.php
typo3/sysext/extensionmanager/Tests/Unit/Utility/FileHandlingUtilityTest.php
typo3/sysext/extensionmanager/Tests/Unit/Utility/InstallUtilityTest.php
typo3/sysext/extensionmanager/Tests/Unit/Utility/ListUtilityTest.php
typo3/sysext/felogin/Tests/Functional/Tca/ContentVisibleFieldsTest.php
typo3/sysext/felogin/Tests/Unit/Controller/FrontendLoginControllerTest.php
typo3/sysext/filemetadata/Tests/Functional/Tca/FileMetadataVisibleFieldsTest.php
typo3/sysext/fluid/Classes/Tests/Unit/ViewHelpers/ViewHelperBaseTestcase.php
typo3/sysext/fluid/Tests/Functional/EscapeChildrenRenderingStandaloneTest.php
typo3/sysext/fluid/Tests/Functional/EscapeChildrenRenderingTest.php
typo3/sysext/fluid/Tests/Unit/Core/Cache/FluidTemplateCacheTest.php
typo3/sysext/fluid/Tests/Unit/Core/Parser/PreProcessor/XmlnsNamespaceTemplatePreProcessorTest.php
typo3/sysext/fluid/Tests/Unit/Core/Parser/SyntaxTree/LegacyNamespaceExpressionNodeTest.php
typo3/sysext/fluid/Tests/Unit/Core/Rendering/RenderingContextTest.php
typo3/sysext/fluid/Tests/Unit/Core/Variables/CmsVariableProviderTest.php
typo3/sysext/fluid/Tests/Unit/Core/ViewHelper/AbstractViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/Core/ViewHelper/ArgumentDefinitionTest.php
typo3/sysext/fluid/Tests/Unit/Core/ViewHelper/ViewHelperResolverTest.php
typo3/sysext/fluid/Tests/Unit/Core/Widget/AbstractWidgetControllerTest.php
typo3/sysext/fluid/Tests/Unit/Core/Widget/AbstractWidgetViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/Core/Widget/AjaxWidgetContextHolderTest.php
typo3/sysext/fluid/Tests/Unit/Core/Widget/WidgetContextTest.php
typo3/sysext/fluid/Tests/Unit/Core/Widget/WidgetRequestBuilderTest.php
typo3/sysext/fluid/Tests/Unit/Core/Widget/WidgetRequestHandlerTest.php
typo3/sysext/fluid/Tests/Unit/Core/Widget/WidgetRequestTest.php
typo3/sysext/fluid/Tests/Unit/View/AbstractTemplateViewTest.php
typo3/sysext/fluid/Tests/Unit/View/StandaloneViewTest.php
typo3/sysext/fluid/Tests/Unit/View/TemplatePathsTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/DateViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/ImageViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/TypolinkViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Uri/TypolinkViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Widget/Controller/PaginateControllerTest.php
typo3/sysext/fluid_styled_content/Tests/Functional/Tca/ContentVisibleFieldsTest.php
typo3/sysext/form/Tests/Unit/Controller/AbstractBackendControllerTest.php
typo3/sysext/form/Tests/Unit/Controller/FormEditorControllerTest.php
typo3/sysext/form/Tests/Unit/Controller/FormFrontendControllerTest.php
typo3/sysext/form/Tests/Unit/Controller/FormManagerControllerTest.php
typo3/sysext/form/Tests/Unit/Domain/Configuration/ConfigurationServiceTest.php
typo3/sysext/form/Tests/Unit/Hooks/DataStructureIdentifierHookTest.php
typo3/sysext/form/Tests/Unit/Mvc/Configuration/InheritancesResolverServiceTest.php
typo3/sysext/form/Tests/Unit/Mvc/Configuration/TypoScriptServiceTest.php
typo3/sysext/form/Tests/Unit/Mvc/Configuration/YamlSourceTest.php
typo3/sysext/form/Tests/Unit/Mvc/Persistence/FormPersistenceManagerTest.php
typo3/sysext/form/Tests/Unit/Mvc/ProcessingRuleTest.php
typo3/sysext/form/Tests/Unit/Mvc/Validation/CountValidatorTest.php
typo3/sysext/form/Tests/Unit/Mvc/Validation/EmptyValidatorTest.php
typo3/sysext/form/Tests/Unit/Mvc/Validation/MimeTypeValidatorTest.php
typo3/sysext/form/Tests/Unit/ViewHelpers/Form/DatePickerViewHelperTest.php
typo3/sysext/frontend/Tests/Functional/ContentObject/ContentObjectRendererTest.php
typo3/sysext/frontend/Tests/Functional/ContentObject/FluidTemplateContentObjectTest.php
typo3/sysext/frontend/Tests/Functional/Controller/TypoScriptFrontendControllerTest.php
typo3/sysext/frontend/Tests/Functional/Page/PageRepositoryTest.php
typo3/sysext/frontend/Tests/Functional/Tca/BackendLayoutVisibleFieldsTest.php
typo3/sysext/frontend/Tests/Functional/Tca/ContentVisibleFieldsTest.php
typo3/sysext/frontend/Tests/Functional/Tca/DomainVisibleFieldsTest.php
typo3/sysext/frontend/Tests/Functional/Tca/FrontendGroupsVisibleFieldsTest.php
typo3/sysext/frontend/Tests/Functional/Tca/FrontendUsersVisibleFieldsTest.php
typo3/sysext/frontend/Tests/Functional/Tca/PagesLanguageOverlayVisibleFieldsTest.php
typo3/sysext/frontend/Tests/Functional/Tca/TemplateVisibleFieldsTest.php
typo3/sysext/frontend/Tests/Unit/Configuration/TypoScript/ConditionMatching/ConditionMatcherTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/CaseContentObjectTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/ContentDataProcessorTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/ContentObjectRendererTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/FilesContentObjectTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/FluidTemplateContentObjectTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/AbstractMenuContentObjectTest.php
typo3/sysext/frontend/Tests/Unit/ContentObject/Menu/MenuContentObjectFactoryTest.php
typo3/sysext/frontend/Tests/Unit/Controller/TypoScriptFrontendControllerTest.php
typo3/sysext/frontend/Tests/Unit/Page/CacheHashCalculatorTest.php
typo3/sysext/frontend/Tests/Unit/Page/PageGeneratorTest.php
typo3/sysext/frontend/Tests/Unit/Page/PageRepositoryTest.php
typo3/sysext/frontend/Tests/Unit/Plugin/AbstractPluginTest.php
typo3/sysext/frontend/Tests/Unit/Processor/GalleryProcessorTest.php
typo3/sysext/frontend/Tests/Unit/Service/TypoLinkCodecServiceTest.php
typo3/sysext/frontend/Tests/Unit/View/AdminPanelViewTest.php
typo3/sysext/impexp/Tests/Functional/Export/AbstractExportTestCase.php
typo3/sysext/indexed_search/Tests/Functional/Tca/IndexConfigVisibleFieldsTest.php
typo3/sysext/indexed_search/Tests/Functional/Utility/LikeWildcardTest.php
typo3/sysext/indexed_search/Tests/Unit/IndexerTest.php
typo3/sysext/install/Tests/Functional/SqlSchemaMigrationServiceTest.php
typo3/sysext/install/Tests/Unit/Controller/Action/Ajax/ExtensionCompatibilityTesterTest.php
typo3/sysext/install/Tests/Unit/FolderStructure/AbstractNodeTest.php
typo3/sysext/install/Tests/Unit/FolderStructure/DefaultFactoryTest.php
typo3/sysext/install/Tests/Unit/FolderStructure/DirectoryNodeTest.php
typo3/sysext/install/Tests/Unit/FolderStructure/FileNodeTest.php
typo3/sysext/install/Tests/Unit/FolderStructure/LinkNodeTest.php
typo3/sysext/install/Tests/Unit/FolderStructure/RootNodeTest.php
typo3/sysext/install/Tests/Unit/FolderStructure/StructureFacadeTest.php
typo3/sysext/install/Tests/Unit/FolderStructureTestCase.php
typo3/sysext/install/Tests/Unit/Service/CoreUpdateServiceTest.php
typo3/sysext/install/Tests/Unit/Service/CoreVersionServiceTest.php
typo3/sysext/install/Tests/Unit/Service/EnableFileServiceTest.php
typo3/sysext/install/Tests/Unit/Service/SilentConfigurationUpgradeServiceTest.php
typo3/sysext/install/Tests/Unit/Service/SqlSchemaMigrationServiceTest.php
typo3/sysext/install/Tests/Unit/Status/StatusUtilityTest.php
typo3/sysext/install/Tests/Unit/UpgradeAnalysis/DocumentationFileTest.php
typo3/sysext/install/Tests/Unit/View/JsonViewTest.php
typo3/sysext/lang/Tests/Unit/Domain/Model/ExtensionTest.php
typo3/sysext/lang/Tests/Unit/Domain/Model/LanguageTest.php
typo3/sysext/lowlevel/Tests/Unit/Utility/ArrayBrowserTest.php
typo3/sysext/recordlist/Tests/Unit/RecordList/AbstractDatabaseRecordListTest.php
typo3/sysext/recycler/Tests/Functional/Recycle/AbstractRecycleTestCase.php
typo3/sysext/recycler/Tests/Unit/Task/CleanerFieldProviderTest.php
typo3/sysext/recycler/Tests/Unit/Task/CleanerTaskTest.php
typo3/sysext/reports/Tests/Unit/Report/Status/Typo3StatusTest.php
typo3/sysext/rsaauth/Tests/Functional/Storage/SplitStorageTest.php
typo3/sysext/rsaauth/Tests/Unit/Backend/CommandLineBackendTest.php
typo3/sysext/rsaauth/Tests/Unit/Backend/PhpBackendTest.php
typo3/sysext/rsaauth/Tests/Unit/KeypairTest.php
typo3/sysext/rtehtmlarea/Tests/Functional/Tca/AcronymVisibleFieldsTest.php
typo3/sysext/saltedpasswords/Tests/Functional/SaltedPasswordServiceTest.php
typo3/sysext/saltedpasswords/Tests/Functional/Task/BulkUpdateTaskTest.php
typo3/sysext/saltedpasswords/Tests/Functional/Utility/SaltedPasswordsUtilityTest.php
typo3/sysext/saltedpasswords/Tests/Unit/Evaluation/EvaluatorTest.php
typo3/sysext/saltedpasswords/Tests/Unit/Salt/BlowfishSaltTest.php
typo3/sysext/saltedpasswords/Tests/Unit/Salt/Md5SaltTest.php
typo3/sysext/saltedpasswords/Tests/Unit/Salt/Pbkdf2SaltTest.php
typo3/sysext/saltedpasswords/Tests/Unit/Salt/PhpassSaltTest.php
typo3/sysext/saltedpasswords/Tests/Unit/Salt/SaltFactoryTest.php
typo3/sysext/saltedpasswords/Tests/Unit/Utility/SaltedPasswordsUtilityTest.php
typo3/sysext/scheduler/Tests/Functional/Tca/TaskGroupVisibleFieldsTest.php
typo3/sysext/scheduler/Tests/Unit/CronCommand/CronCommandTest.php
typo3/sysext/scheduler/Tests/Unit/CronCommand/NormalizeCommandTest.php
typo3/sysext/scheduler/Tests/Unit/Task/CachingFrameworkGarbageCollectionTest.php
typo3/sysext/setup/Tests/Unit/SetupModuleControllerTest.php
typo3/sysext/sv/Tests/Functional/AuthenticationServiceTest.php
typo3/sysext/sv/Tests/Unit/AuthenticationServiceTest.php
typo3/sysext/sv/Tests/Unit/Report/ServicesListReportTest.php
typo3/sysext/sys_action/Tests/Functional/Tca/ActionVisibleFieldsTest.php
typo3/sysext/sys_note/Tests/Functional/Tca/NoteVisibleFieldsTest.php
typo3/sysext/workspaces/Tests/Functional/ActionHandler/ActionHandlerTest.php
typo3/sysext/workspaces/Tests/Functional/Service/WorkspaceServiceTest.php
typo3/sysext/workspaces/Tests/Functional/Tca/WorkspaceStageVisibleFieldsTest.php
typo3/sysext/workspaces/Tests/Functional/Tca/WorkspaceVisibleFieldsTest.php
typo3/sysext/workspaces/Tests/Unit/Controller/Remote/RemoteServerTest.php

index 241f9b2..5bf9688 100644 (file)
@@ -57,19 +57,19 @@ before_script:
 script:
   - >
     if [[ "$UNIT_TESTS" == "yes" ]]; then
-      ./bin/phpunit -c typo3/sysext/core/Build/UnitTests.xml
+      ./bin/phpunit -c components/testing_framework/core/Build/UnitTests.xml
     fi
 
   - >
     if [[ "$FUNCTIONAL_TESTS" == "yes" ]]; then
-      ./typo3/sysext/core/Build/Scripts/splitFunctionalTests.sh 14
-      parallel --jobs 4 -a <(seq 0 13) --gnu './bin/phpunit -c typo3/sysext/core/Build/FunctionalTests-Job-{}.xml'
+      ./components/testing_framework/core/Build/Scripts/splitFunctionalTests.sh 14
+      parallel --jobs 4 -a <(seq 0 13) --gnu './bin/phpunit -c components/testing_framework/core/Build/FunctionalTests-Job-{}.xml'
     fi
 
   - >
     if [[ "$JSUNIT_TESTS" == "yes" ]]; then
       cd Build && npm update -g npm && npm install && cd ..
-      ./Build/node_modules/karma/bin/karma start typo3/sysext/core/Build/Configuration/JSUnit/karma.conf.js --single-run
+      ./Build/node_modules/karma/bin/karma start components/testing_framework/core/Build/Configuration/JSUnit/karma.conf.js --single-run
     fi
 
   - >
diff --git a/components/testing_framework/core/Acceptance/Fixtures/be_groups.xml b/components/testing_framework/core/Acceptance/Fixtures/be_groups.xml
new file mode 100644 (file)
index 0000000..7394d26
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <be_groups>
+               <uid>1</uid>
+               <pid>0</pid>
+               <tstamp>1452959228</tstamp>
+               <title>editor-group</title>
+               <tables_modify>pages</tables_modify>
+               <crdate>1452959228</crdate>
+               <cruser_id>1</cruser_id>
+       </be_groups>
+</dataset>
diff --git a/components/testing_framework/core/Acceptance/Fixtures/be_sessions.xml b/components/testing_framework/core/Acceptance/Fixtures/be_sessions.xml
new file mode 100644 (file)
index 0000000..f4f3e73
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <be_sessions>
+               <ses_id>886526ce72b86870739cc41991144ec1</ses_id>
+               <ses_name>be_typo_user</ses_name>
+               <ses_iplock>[DISABLED]</ses_iplock>
+               <ses_hashlock>222419149</ses_hashlock>
+               <ses_userid>1</ses_userid>
+               <ses_tstamp>1777777777</ses_tstamp>
+               <ses_data></ses_data>
+               <ses_backuserid>0</ses_backuserid>
+       </be_sessions>
+       <be_sessions>
+               <ses_id>ff83dfd81e20b34c27d3e97771a4525a</ses_id>
+               <ses_name>be_typo_user</ses_name>
+               <ses_iplock>[DISABLED]</ses_iplock>
+               <ses_hashlock>222419149</ses_hashlock>
+               <ses_userid>2</ses_userid>
+               <ses_tstamp>1777777777</ses_tstamp>
+               <ses_data></ses_data>
+               <ses_backuserid>0</ses_backuserid>
+       </be_sessions>
+</dataset>
diff --git a/components/testing_framework/core/Acceptance/Fixtures/be_users.xml b/components/testing_framework/core/Acceptance/Fixtures/be_users.xml
new file mode 100644 (file)
index 0000000..c386c25
--- /dev/null
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <be_users>
+               <uid>1</uid>
+               <pid>0</pid>
+               <tstamp>1366642540</tstamp>
+               <username>admin</username>
+               <password>$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1</password> <!-- password -->
+               <admin>1</admin>
+               <disable>0</disable>
+               <starttime>0</starttime>
+               <endtime>0</endtime>
+               <options>0</options>
+               <crdate>1366642540</crdate>
+               <cruser_id>0</cruser_id>
+               <workspace_perms>1</workspace_perms>
+               <disableIPlock>1</disableIPlock>
+               <deleted>0</deleted>
+               <TSconfig>NULL</TSconfig>
+               <lastlogin>1371033743</lastlogin>
+               <createdByAction>0</createdByAction>
+               <workspace_id>0</workspace_id>
+               <workspace_preview>1</workspace_preview>
+       </be_users>
+       <be_users>
+               <uid>2</uid>
+               <pid>0</pid>
+               <tstamp>1452944912</tstamp>
+               <username>editor</username>
+               <password>$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1</password> <!-- password -->
+               <admin>0</admin>
+               <disable>0</disable>
+               <starttime>0</starttime>
+               <endtime>0</endtime>
+               <options>0</options>
+               <crdate>1452944912</crdate>
+               <cruser_id>1</cruser_id>
+               <workspace_perms>1</workspace_perms>
+               <disableIPlock>1</disableIPlock>
+               <deleted>0</deleted>
+               <TSconfig>NULL</TSconfig>
+               <lastlogin>1452944915</lastlogin>
+               <createdByAction>0</createdByAction>
+               <workspace_id>0</workspace_id>
+               <workspace_preview>1</workspace_preview>
+               <db_mountpoints>1</db_mountpoints>
+               <usergroup>1</usergroup>
+       </be_users>
+</dataset>
diff --git a/components/testing_framework/core/Acceptance/Fixtures/sys_category.xml b/components/testing_framework/core/Acceptance/Fixtures/sys_category.xml
new file mode 100644 (file)
index 0000000..2c2e37d
--- /dev/null
@@ -0,0 +1,120 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+    <sys_category>
+        <uid>1</uid>
+        <pid>0</pid>
+        <title>level-1-1</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>2</uid>
+        <parent>1</parent>
+        <pid>0</pid>
+        <title>level-1-1-1</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>3</uid>
+        <parent>2</parent>
+        <pid>0</pid>
+        <title>level-1-1-1-1</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>4</uid>
+        <parent>2</parent>
+        <pid>0</pid>
+        <title>level-1-1-1-2</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>5</uid>
+        <parent>2</parent>
+        <pid>0</pid>
+        <title>level-1-1-1-3</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>6</uid>
+        <parent>3</parent>
+        <pid>0</pid>
+        <title>level-1-1-1-1-1</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>7</uid>
+        <parent>3</parent>
+        <pid>0</pid>
+        <title>level-1-1-1-1-2</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>8</uid>
+        <pid>0</pid>
+        <title>level-1-2</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>9</uid>
+        <parent>8</parent>
+        <pid>0</pid>
+        <title>level-1-2-1</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>10</uid>
+        <parent>8</parent>
+        <pid>0</pid>
+        <title>level-1-2-2</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>11</uid>
+        <parent>8</parent>
+        <pid>0</pid>
+        <title>level-1-2-3</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>12</uid>
+        <parent>8</parent>
+        <pid>0</pid>
+        <title>level-1-2-4</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>13</uid>
+        <parent>8</parent>
+        <pid>0</pid>
+        <title>level-1-2-5</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>14</uid>
+        <parent>8</parent>
+        <pid>0</pid>
+        <title>level-1-2-6</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+    <sys_category>
+        <uid>15</uid>
+        <pid>0</pid>
+        <title>level-1-3</title>
+        <deleted>0</deleted>
+        <l10n_diffsource/>
+    </sys_category>
+</dataset>
diff --git a/components/testing_framework/core/Acceptance/Fixtures/tx_extensionmanager_domain_model_extension.xml b/components/testing_framework/core/Acceptance/Fixtures/tx_extensionmanager_domain_model_extension.xml
new file mode 100644 (file)
index 0000000..fe10af6
--- /dev/null
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+    <tx_extensionmanager_domain_model_extension>
+        <uid>1</uid>
+        <pid>0</pid>
+        <extension_key>superext</extension_key>
+        <repository>1</repository>
+        <version>1.0.0</version>
+        <alldownloadcounter>1</alldownloadcounter>
+        <downloadcounter>0</downloadcounter>
+        <title>Super extension</title>
+        <description>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam</description>
+        <state>2</state>
+        <review_state>0</review_state>
+        <category>2</category>
+        <last_updated>1474625908</last_updated>
+        <serialized_dependencies>a:1:{s:7:&quot;depends&quot;;a:1:{s:5:&quot;typo3&quot;;s:12:&quot;7.6.0-8.9.99&quot;;}}</serialized_dependencies>
+        <author_name>John Doe</author_name>
+        <author_email>john@doe.com</author_email>
+        <ownerusername>jdoe</ownerusername>
+        <md5hash>fa061138fc220bdfe5c631ba019f9f84</md5hash>
+        <update_comment> Update comment</update_comment>
+        <authorcompany></authorcompany>
+        <integer_version>1000000</integer_version>
+        <current_version>1</current_version>
+        <lastreviewedversion>0</lastreviewedversion>
+    </tx_extensionmanager_domain_model_extension>
+    <tx_extensionmanager_domain_model_extension>
+        <uid>2</uid>
+        <pid>0</pid>
+        <extension_key>neededext</extension_key>
+        <repository>1</repository>
+        <version>2.0.0</version>
+        <alldownloadcounter>2</alldownloadcounter>
+        <downloadcounter>0</downloadcounter>
+        <title>Needed Extension</title>
+        <description>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut</description>
+        <state>2</state>
+        <review_state>0</review_state>
+        <category>5</category>
+        <last_updated>1474977787</last_updated>
+        <serialized_dependencies>a:1:{s:7:&quot;depends&quot;;a:3:{s:5:&quot;typo3&quot;;s:13:&quot;6.2.0-8.4.999&quot;;s:3:&quot;php&quot;;s:13:&quot;5.4.0-7.0.999&quot;;s:9:&quot;scheduler&quot;;s:13:&quot;6.2.0-8.4.999&quot;;}}</serialized_dependencies>
+        <author_name>Dmitry Dulepov</author_name>
+        <author_email>dmitry.dulepov@gmail.com</author_email>
+        <ownerusername>dmitry</ownerusername>
+        <md5hash>b28075aa867d17cc97c7acfe187a71d1</md5hash>
+        <update_comment>no comment</update_comment>
+        <authorcompany></authorcompany>
+        <integer_version>2000000</integer_version>
+        <current_version>1</current_version>
+        <lastreviewedversion>0</lastreviewedversion>
+    </tx_extensionmanager_domain_model_extension>
+
+</dataset>
diff --git a/components/testing_framework/core/Acceptance/Fixtures/tx_extensionmanager_domain_model_repository.xml b/components/testing_framework/core/Acceptance/Fixtures/tx_extensionmanager_domain_model_repository.xml
new file mode 100644 (file)
index 0000000..07e8491
--- /dev/null
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+    <tx_extensionmanager_domain_model_repository>
+        <pid>0</pid>
+        <title>TYPO3.org Main Repository</title>
+        <description>Main repository on typo3.org. This repository has some mirrors configured which are available with
+            the mirror url.
+        </description>
+        <wsdl_url>https://typo3.org/wsdl/tx_ter_wsdl.php</wsdl_url>
+        <mirror_list_url>https://repositories.typo3.org/mirrors.xml.gz</mirror_list_url>
+        <last_update>1477500928</last_update>
+        <extension_count>2</extension_count>
+    </tx_extensionmanager_domain_model_repository>
+</dataset>
diff --git a/components/testing_framework/core/Acceptance/Step/Backend/Admin.php b/components/testing_framework/core/Acceptance/Step/Backend/Admin.php
new file mode 100644 (file)
index 0000000..df1cc64
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Acceptance\Step\Backend;
+
+/*
+ * 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!
+ */
+
+/**
+ * A backend user with admin access
+ */
+class Admin extends \AcceptanceTester
+{
+    /**
+     * The session cookie that is used if the session is injected.
+     * This session must exist in the database fixture to get a logged in state.
+     *
+     * @var string
+     */
+    protected $sessionCookie = '886526ce72b86870739cc41991144ec1';
+}
diff --git a/components/testing_framework/core/Acceptance/Step/Backend/Editor.php b/components/testing_framework/core/Acceptance/Step/Backend/Editor.php
new file mode 100644 (file)
index 0000000..711db53
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Acceptance\Step\Backend;
+
+/*
+ * 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!
+ */
+
+/**
+ * A backend editor
+ */
+class Editor extends \AcceptanceTester
+{
+    /**
+     * The session cookie that is used if the session is injected.
+     * This session must exist in the database fixture to get a logged in state.
+     *
+     * @var string
+     */
+    protected $sessionCookie = 'ff83dfd81e20b34c27d3e97771a4525a';
+}
diff --git a/components/testing_framework/core/Acceptance/Support/Helper/FormHandlerElementTestDataObject.php b/components/testing_framework/core/Acceptance/Support/Helper/FormHandlerElementTestDataObject.php
new file mode 100644 (file)
index 0000000..55b79e4
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+declare(strict_types=1);
+
+/*
+ * 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!
+ */
+
+namespace TYPO3\CMS\Components\TestingFramework\Core\Acceptance\Support\Helper;
+
+/**
+ * Class FormHandlerElementTestDataObject.
+ */
+class FormHandlerElementTestDataObject
+{
+    /**
+     * The test value.
+     *
+     * @var string
+     */
+    public $inputValue;
+
+    /**
+     * The expected value as seen by the user.
+     *
+     * @var string
+     */
+    public $expectedValue;
+
+    /**
+     * The expected value in the value attribute of the hidden field
+     * (full ISO date for example).
+     *
+     * @var string
+     */
+    public $expectedInternalValue;
+
+    /**
+     * Expected value in hidden field after saving the data.
+     *
+     * @var string
+     */
+    public $expectedValueAfterSave;
+
+    /**
+     * Does this test data set result in a modal window (e.g. for errors)?
+     *
+     * @var bool
+     */
+    public $notificationExpected;
+
+    /**
+     * Comment echoed in test log.
+     *
+     * @var string
+     */
+    public $comment;
+
+    /**
+     * FormHandlerElementTestDataObject constructor.
+     *
+     * @param string $inputValue
+     * @param string $expectedValue
+     * @param string $expectedInternalValue
+     * @param string $expectedValueAfterSave
+     * @param bool   $notificationExpected
+     * @param string $comment
+     */
+    public function __construct(
+        string $inputValue,
+        string $expectedValue,
+        string $expectedInternalValue = '',
+        string $expectedValueAfterSave = '',
+        bool $notificationExpected = false,
+        string $comment = ''
+    ) {
+        $this->inputValue = $inputValue;
+        $this->expectedValue = $expectedValue;
+        $this->expectedInternalValue = $expectedInternalValue;
+        $this->expectedValueAfterSave = $expectedValueAfterSave;
+        $this->notificationExpected = $notificationExpected;
+        $this->comment = $comment;
+    }
+}
diff --git a/components/testing_framework/core/Acceptance/Support/Helper/Formhandler.php b/components/testing_framework/core/Acceptance/Support/Helper/Formhandler.php
new file mode 100644 (file)
index 0000000..e36c6df
--- /dev/null
@@ -0,0 +1,232 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Acceptance\Support\Helper;
+
+/*
+ * 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 Facebook\WebDriver\Remote\RemoteWebElement;
+use Facebook\WebDriver\WebDriverKeys;
+
+/**
+ * Helper to interact with formhandler fields
+ */
+class Formhandler
+{
+    /**
+     * Selector to select one formengine section
+     *
+     * @var string
+     */
+    public static $selectorFormSection = '.form-section';
+    protected $visibleFieldPath = './/*/input[@data-formengine-input-name]';
+    protected $initializedInputFieldXpath;
+    protected $internalInputFieldXpath;
+    protected $internalInputFieldXpath1;
+    protected $internalFieldXpath;
+    /**
+     * @var \AcceptanceTester
+     */
+    protected $tester;
+
+    /**
+     * @param \AcceptanceTester $I
+     */
+    public function __construct(\AcceptanceTester $I)
+    {
+        $this->tester = $I;
+    }
+
+    /**
+     * @param string $fieldLabel
+     * @param FormHandlerElementTestDataObject[] $testData An array of Objects that contains the data to validate.
+     */
+    public function fillSeeSaveAndClearInputField($fieldLabel, array $testData)
+    {
+        $I = $this->tester;
+        $I->wantTo('Fill field, check the fieldvalue after evaluation and delete the value.');
+
+        $this->initializeFieldSelectors($fieldLabel);
+
+        foreach ($testData as $data) {
+            list($inputField, $internalInputField) = $this->getInputFields($fieldLabel);
+            $fieldContext = $this->getContextForFormhandlerField($fieldLabel);
+
+            $expectedInternal = $data->expectedInternalValue !== '' ? $data->expectedInternalValue : $data->expectedValue;
+            $expectedInternalAfterSave = $data->expectedValueAfterSave !== '' ? $data->expectedValueAfterSave : $expectedInternal;
+            $this->addComment($data->comment);
+
+            $I->comment('Fill the field and switch focus to trigger validation.');
+            $I->fillField($inputField, $data->inputValue);
+            // change the focus to trigger validation
+            $inputField->sendKeys(WebDriverKeys::TAB);
+            // click on the div so that any opened popup (potentially from the field below) is closed
+            $fieldContext->click();
+
+            $this->testFieldValues($inputField, $data->expectedValue, $internalInputField, $expectedInternal);
+
+            if ($data->notificationExpected) {
+                $this->save();
+                $this->closeNotification();
+                return;
+            } else {
+                $this->save();
+            }
+
+            // wait for the save to be completed
+            $this->waitForSaveToBeCompleted();
+
+            // find the fields again (after reload of iframe)
+            list($inputField, $internalInputField) = $this->getInputFields($fieldLabel);
+
+            // validate that the save was successful
+            $this->testFieldValues($inputField, $data->expectedValue, $internalInputField, $expectedInternalAfterSave);
+        }
+
+        list($inputField) = $this->getInputFields($fieldLabel);
+
+        // clear the field
+        $this->clearField($inputField);
+        $this->save();
+        $this->waitForSaveToBeCompleted();
+    }
+
+    /**
+     * @param $comment
+     */
+    protected function addComment($comment)
+    {
+        if ($comment !== null) {
+            $this->tester->comment($comment);
+        }
+    }
+
+    protected function clearField($inputField)
+    {
+        $I = $this->tester;
+        $I->comment('Clear the field');
+        $I->waitForElementVisible($this->initializedInputFieldXpath);
+        $I->fillField($inputField, '');
+    }
+
+    protected function closeNotification()
+    {
+        $I = $this->tester;
+        $I->switchToWindow();
+        $notificationCloseXpath = '//*[@class="modal-title"][contains(text(),"Alert")]/parent::*/button[@class="close"]';
+        $I->waitForElement($notificationCloseXpath, 30);
+        $I->click($notificationCloseXpath);
+    }
+
+    /**
+     * @param string $fieldName
+     * @return RemoteWebElement
+     */
+    protected function getContextForFormhandlerField(string $fieldName)
+    {
+        $I = $this->tester;
+        $I->comment('Get context for field "' . $fieldName . '"');
+
+        return $I->executeInSelenium(
+            function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) use ($fieldName) {
+                return $webdriver->findElement(
+                    \WebDriverBy::xpath(
+                        '(//label[contains(text(),"' .
+                        $fieldName .
+                        '")])[1]/ancestor::fieldset[@class="form-section"][1]'
+                    )
+                );
+            }
+        );
+    }
+
+    /**
+     * @param $fieldLabel
+     * @return array
+     */
+    protected function getInputFields($fieldLabel)
+    {
+        $I = $this->tester;
+        $I->comment('get input fields');
+        $I->waitForElement($this->initializedInputFieldXpath, 30);
+        $fieldContext = $this->getContextForFormhandlerField($fieldLabel);
+        $inputField = $fieldContext->findElement(\WebDriverBy::xpath($this->visibleFieldPath));
+        $internalInputFieldXpath = '(//label[contains(text(),"' .
+                                   $fieldLabel .
+                                   '")])[1]/parent::*//*/input[@name="' .
+                                   $inputField->getAttribute('data-formengine-input-name') .
+                                   '"]';
+
+        $I->waitForElement($internalInputFieldXpath, 30);
+
+        $this->internalFieldXpath = './/*/input[@name="' .
+                                    $inputField->getAttribute('data-formengine-input-name') .
+                                    '"]';
+        $internalInputField = $fieldContext->findElement(\WebDriverBy::xpath($this->internalFieldXpath));
+
+        $this->internalInputFieldXpath = $internalInputFieldXpath;
+        return [$inputField, $internalInputField];
+    }
+
+    /**
+     * @param $fieldLabel
+     * @return array
+     */
+    protected function initializeFieldSelectors($fieldLabel)
+    {
+        $this->initializedInputFieldXpath = '(//label[contains(text(),"' .
+                                            $fieldLabel .
+                                            '")])[1]/parent::*//*/input[@data-formengine-input-name]' .
+                                            '[@data-formengine-input-initialized]';
+    }
+
+    /**
+     */
+    protected function save()
+    {
+        $I = $this->tester;
+        $I->comment('Save the form');
+        $saveButtonLink = '//*/button[@name="_savedok"][1]';
+        $I->waitForElement($saveButtonLink, 30);
+        $I->click($saveButtonLink);
+    }
+
+    /**
+     * @param $inputField
+     * @param $expectedAfterValidation
+     * @param $internalInputField
+     * @param $expectedInternal
+     */
+    protected function testFieldValues(
+        $inputField,
+        $expectedAfterValidation,
+        $internalInputField,
+        $expectedInternal
+    ) {
+        $I = $this->tester;
+        $I->comment('Test value of "visible" field');
+        $I->canSeeInField($inputField, $expectedAfterValidation);
+        $I->comment('Test value of the internal field');
+        $I->canSeeInField($internalInputField, $expectedInternal);
+    }
+
+    /**
+     */
+    protected function waitForSaveToBeCompleted()
+    {
+        $I = $this->tester;
+        $I->comment('wait for save to be completed');
+        $I->waitForElement('//*/button[@name="_savedok"][not(@disabled)][1]', 30);
+        $I->waitForElement($this->initializedInputFieldXpath, 30);
+        $I->waitForElement($this->internalInputFieldXpath, 30);
+    }
+}
diff --git a/components/testing_framework/core/Acceptance/Support/Helper/ModalDialog.php b/components/testing_framework/core/Acceptance/Support/Helper/ModalDialog.php
new file mode 100644 (file)
index 0000000..7e31592
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Acceptance\Support\Helper;
+
+/*
+ * 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 AcceptanceTester;
+
+/**
+ * Helper to interact with modal dialogs that appear for example when
+ * you delete a record or have to confirm something.
+ *
+ *  --------------------------------
+ * | Would you like to continue?    |
+ * |                                |
+ * |            [no] [maybe] [yeah] |
+ *  --------------------------------
+ */
+class ModalDialog
+{
+    /**
+     * Selector for a visible modal window
+     *
+     * @var string
+     */
+    public static $openedModalSelector = '.t3-modal.in';
+
+    /**
+     * Selector for the container in the modal where the buttons are located
+     *
+     * @var string
+     */
+    public static $openedModalButtonContainerSelector = '.t3-modal.in .modal-footer';
+
+    /**
+     * @var AcceptanceTester
+     */
+    protected $tester;
+
+    /**
+     * @param AcceptanceTester $I
+     */
+    public function __construct(\AcceptanceTester $I)
+    {
+        $this->tester = $I;
+    }
+
+    /**
+     * Perform a click on a link or a button, given by a locator.
+     *
+     * @param string $buttonLinkLocator the button title
+     * @see \Codeception\Module\WebDriver::click()
+     */
+    public function clickButtonInDialog(string $buttonLinkLocator)
+    {
+        $I = $this->tester;
+        $this->canSeeDialog();
+        $I->click($buttonLinkLocator, self::$openedModalButtonContainerSelector);
+        $I->waitForElementNotVisible(self::$openedModalSelector);
+    }
+
+    /**
+     * Check if modal dialog is visible in top frame
+     */
+    public function canSeeDialog()
+    {
+        $I = $this->tester;
+        $I->switchToIFrame();
+        $I->waitForElement(self::$openedModalSelector);
+    }
+}
diff --git a/components/testing_framework/core/Acceptance/Support/Helper/Topbar.php b/components/testing_framework/core/Acceptance/Support/Helper/Topbar.php
new file mode 100644 (file)
index 0000000..50534b9
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Acceptance\Support\Helper;
+
+/*
+ * 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!
+ */
+
+/**
+ * Helper to interact with the Topbar
+ */
+class Topbar
+{
+    /**
+     * Selector for the topbar container
+     *
+     * @var string
+     */
+    public static $containerSelector = '.t3js-scaffold-toolbar';
+
+    /**
+     * Selector for the dropdown container
+     *
+     * @var string
+     */
+    public static $dropdownListSelector = '.dropdown-menu';
+
+    /**
+     * Selector for the dropdown toggle
+     *
+     * @var string
+     */
+    public static $dropdownToggleSelector = '.dropdown-toggle';
+}
diff --git a/components/testing_framework/core/Acceptance/Support/Page/PageTree.php b/components/testing_framework/core/Acceptance/Support/Page/PageTree.php
new file mode 100644 (file)
index 0000000..bc68d11
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Acceptance\Support\Page;
+
+/*
+ * 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 AcceptanceTester;
+use Facebook\WebDriver\Remote\RemoteWebElement;
+use Facebook\WebDriver\WebDriverBy;
+
+/**
+ * Helper class to interact with the page tree
+ */
+class PageTree
+{
+    // Selectors
+    public static $pageTreeFrameSelector = '#typo3-pagetree';
+    public static $pageTreeSelector = '#typo3-pagetree-treeContainer';
+    public static $treeItemSelector = '.x-tree-node-ct > .x-tree-node';
+    public static $treeItemAnchorSelector = '.x-tree-node-anchor';
+
+    /**
+     * @var AcceptanceTester
+     */
+    protected $tester;
+
+    /**
+     * @param AcceptanceTester $I
+     */
+    public function __construct(\AcceptanceTester $I)
+    {
+        $this->tester = $I;
+    }
+
+    /**
+     * Open the given hierarchical path in the pagetree and click the last page.
+     *
+     * Example to open "styleuide -> elements basic" page:
+     * [
+     *    'styleguide TCA demo',
+     *    'elements basic',
+     * ]
+     *
+     * @param string[] $path
+     */
+    public function openPath(array $path)
+    {
+        $context = $this->getPageTreeElement();
+        foreach ($path as $pageName) {
+            $context = $this->ensureTreeNodeIsOpen($pageName, $context);
+        }
+        $context->findElement(\WebDriverBy::cssSelector(self::$treeItemAnchorSelector))->click();
+    }
+
+    /**
+     * Check if the pagetree is visible end return the web element object
+     *
+     * @return RemoteWebElement
+     */
+    public function getPageTreeElement()
+    {
+        $I = $this->tester;
+        $I->switchToIFrame();
+        return $I->executeInSelenium(function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {
+            return $webdriver->findElement(\WebDriverBy::cssSelector(self::$pageTreeSelector));
+        });
+    }
+
+    /**
+     * Search for an element with the given link text in the provided context.
+     *
+     * @param string $nodeText
+     * @param RemoteWebElement $context
+     * @return RemoteWebElement
+     */
+    protected function ensureTreeNodeIsOpen(string $nodeText, RemoteWebElement $context)
+    {
+        $I = $this->tester;
+        $I->see($nodeText, self::$treeItemSelector);
+
+        /** @var RemoteWebElement $context */
+        $context = $I->executeInSelenium(function () use ($nodeText, $context
+        ) {
+            return $context->findElement(\WebDriverBy::linkText($nodeText))->findElement(
+                WebDriverBy::xpath('ancestor::li[@class="x-tree-node"][1]')
+            );
+        });
+
+        try {
+            $context->findElement(\WebDriverBy::cssSelector('.x-tree-elbow-end-plus'))->click();
+        } catch (\Facebook\WebDriver\Exception\NoSuchElementException $e) {
+            // element not found so it may be already opened...
+        }
+
+        return $context;
+    }
+}
diff --git a/components/testing_framework/core/AccessibleObjectInterface.php b/components/testing_framework/core/AccessibleObjectInterface.php
new file mode 100644 (file)
index 0000000..992d7cb
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core;
+
+/*
+ * 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\Components\TestingFramework\Core\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/components/testing_framework/core/BaseTestCase.php b/components/testing_framework/core/BaseTestCase.php
new file mode 100644 (file)
index 0000000..1410a58
--- /dev/null
@@ -0,0 +1,300 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core;
+
+/*
+ * 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\Components\TestingFramework\Core\AccessibleObjectInterface
+     *         a mock of $originalClassName with access methods added
+     *
+     * @throws \InvalidArgumentException
+     */
+    protected function getAccessibleMock(
+        $originalClassName, $methods = [], array $arguments = [], $mockClassName = '',
+        $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true
+    ) {
+        if ($originalClassName === '') {
+            throw new \InvalidArgumentException('$originalClassName must not be empty.', 1334701880);
+        }
+
+        $mockBuilder = $this->getMockBuilder($this->buildAccessibleProxy($originalClassName))
+            ->setMethods($methods)
+            ->setConstructorArgs($arguments)
+            ->setMockClassName($mockClassName);
+
+        if (!$callOriginalConstructor) {
+            $mockBuilder->disableOriginalConstructor();
+        }
+
+        if (!$callOriginalClone) {
+            $mockBuilder->disableOriginalClone();
+        }
+
+        if (!$callAutoload) {
+            $mockBuilder->disableAutoload();
+        }
+
+        return $mockBuilder->getMock();
+    }
+
+    /**
+     * 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\Components\TestingFramework\Core\AccessibleObjectInterface
+     *
+     */
+    protected function getAccessibleMockForAbstractClass(
+        $originalClassName, array $arguments = [], $mockClassName = '',
+        $callOriginalConstructor = true, $callOriginalClone = true, $callAutoload = true, $mockedMethods = []
+    ) {
+        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\Components\TestingFramework\Core\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.\', 1476049901' .
+                                ');' .
+                        '}' .
+                        '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.', 1476107338);
+        }
+
+        $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),
+                1476107339
+            );
+        }
+    }
+
+    /**
+     * 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/components/testing_framework/core/Build/AcceptanceTests.yml b/components/testing_framework/core/Build/AcceptanceTests.yml
new file mode 100644 (file)
index 0000000..a52860d
--- /dev/null
@@ -0,0 +1,15 @@
+actor: Tester
+paths:
+  tests: ../Tests
+  log: ../../../../typo3temp/var/tests
+  data: Configuration/Acceptance/Data
+  support: Configuration/Acceptance/Support
+  envs: Configuration/Acceptance/Envs
+settings:
+  colors: true
+  memory_limit: 1024M
+extensions:
+  enabled:
+    - Codeception\Extension\RunFailed
+    - Codeception\Extension\Recorder
+    - TYPO3\CMS\Core\Tests\AcceptanceCoreEnvironment
\ No newline at end of file
diff --git a/components/testing_framework/core/Build/Configuration/Acceptance/Support/AcceptanceTester.php b/components/testing_framework/core/Build/Configuration/Acceptance/Support/AcceptanceTester.php
new file mode 100644 (file)
index 0000000..54392ce
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+
+
+/**
+ * Inherited Methods
+ * @method void wantToTest($text)
+ * @method void wantTo($text)
+ * @method void execute($callable)
+ * @method void expectTo($prediction)
+ * @method void expect($prediction)
+ * @method void amGoingTo($argumentation)
+ * @method void am($role)
+ * @method void lookForwardTo($achieveValue)
+ * @method void comment($description)
+ * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = null)
+ *
+ * @SuppressWarnings(PHPMD)
+*/
+class AcceptanceTester extends \Codeception\Actor
+{
+    use _generated\AcceptanceTesterActions;
+
+    /**
+     * The session cookie that is used if the session is injected.
+     * This session must exist in the database fixture to get a logged in state.
+     *
+     * @var string
+     */
+    protected $sessionCookie = '';
+
+    /**
+     * Use the existing database session from the fixture by setting the backend user cookie
+     */
+    public function useExistingSession()
+    {
+        $I = $this;
+        $I->amOnPage('/typo3/index.php');
+
+        // @todo: There is a bug in PhantomJS / firefox (?) where adding a cookie fails.
+        // This bug will be fixed in the next PhantomJS version but i also found
+        // this workaround. First reset / delete the cookie and than set it and catch
+        // the webdriver exception as the cookie has been set successful.
+        try {
+            $I->resetCookie('be_typo_user');
+            $I->setCookie('be_typo_user', $this->sessionCookie);
+        } catch (\Facebook\WebDriver\Exception\UnableToSetCookieException $e) {
+        }
+        try {
+            $I->resetCookie('be_lastLoginProvider');
+            $I->setCookie('be_lastLoginProvider', '1433416747');
+        } catch (\Facebook\WebDriver\Exception\UnableToSetCookieException $e) {
+        }
+
+        // reload the page to have a logged in backend
+        $I->amOnPage('/typo3/index.php');
+    }
+}
diff --git a/components/testing_framework/core/Build/Configuration/Acceptance/Support/Helper/Acceptance.php b/components/testing_framework/core/Build/Configuration/Acceptance/Support/Helper/Acceptance.php
new file mode 100644 (file)
index 0000000..bde9b7b
--- /dev/null
@@ -0,0 +1,6 @@
+<?php
+namespace Helper;
+
+class Acceptance extends \Codeception\Module
+{
+}
diff --git a/components/testing_framework/core/Build/Configuration/JSUnit/Bootstrap.js b/components/testing_framework/core/Build/Configuration/JSUnit/Bootstrap.js
new file mode 100644 (file)
index 0000000..c7d38a8
--- /dev/null
@@ -0,0 +1,86 @@
+'use strict';
+
+var tests = [];
+var paths = {
+       'jquery-ui': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/jquery-ui',
+       'datatables': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/jquery.dataTables',
+       'matchheight': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/jquery.matchHeight-min',
+       'nprogress': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/nprogress',
+       'moment': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/moment',
+       'cropper': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/cropper.min',
+       'imagesloaded': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/imagesloaded.pkgd.min',
+       'bootstrap': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/bootstrap/bootstrap',
+       'twbs/bootstrap-datetimepicker': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/bootstrap-datetimepicker',
+       'autosize': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/autosize',
+       'taboverride': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/taboverride.min',
+       'twbs/bootstrap-slider': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/bootstrap-slider.min',
+       'jquery/autocomplete': '/base/typo3/sysext/core/Resources/Public/JavaScript/Contrib/jquery.autocomplete'
+};
+
+/**
+ * Collect test files and define namespace mapping for RequireJS config
+ */
+for (var file in window.__karma__.files) {
+       if (window.__karma__.files.hasOwnProperty(file)) {
+               // Add dynamic path mapping for requirejs config
+               if (/Resources\/Public\/JavaScript\//.test(file)) {
+                       var parts = file.split('/');
+                       var extkey = parts[4];
+                       var extname = extkey.replace(/_([a-z])/g, function(g) {
+                               return g[1].toUpperCase();
+                       });
+                       extname = extname.charAt(0).toUpperCase() + extname.slice(1);
+                       var namespace = 'TYPO3/CMS/' + extname;
+                       if (typeof paths[namespace] === 'undefined') {
+                               paths[namespace] = '/base/typo3/sysext/' + extkey + '/Resources/Public/JavaScript';
+                       }
+               }
+               // Find all test files
+               var testFilePattern = /Tests\/JavaScript\/(.*)Test\.js$/gi;
+               if (testFilePattern.test(file)) {
+                       tests.push(file);
+               }
+       }
+}
+
+/**
+ * Define environment
+ * Set global objects and variables
+ * @TODO: hopefully we can cleanup the following lines in future
+ */
+if (typeof TYPO3 === 'undefined') {
+       var TYPO3 = TYPO3 || {};
+       TYPO3.jQuery = jQuery.noConflict(true);
+       TYPO3.settings = {
+               'FormEngine': {
+                       'formName': 'Test'
+               },
+               'DateTimePicker': {
+                       'DateFormat': 'd.m.Y'
+               },
+               'ajaxUrls': {
+               }
+       };
+       TYPO3.lang = {};
+}
+
+top.TYPO3 = TYPO3;
+var TBE_EDITOR = {};
+
+/**
+ * RequireJS setup
+ */
+requirejs.config({
+       // Karma serves files from '/base'
+       baseUrl: '/base',
+
+       paths: paths,
+
+       shim: {},
+
+       // ask Require.js to load these files (all our tests)
+       deps: tests,
+
+       // start test run, once Require.js is done
+       callback: window.__karma__.start
+});
diff --git a/components/testing_framework/core/Build/Configuration/JSUnit/Helper.js b/components/testing_framework/core/Build/Configuration/JSUnit/Helper.js
new file mode 100644 (file)
index 0000000..d8f7685
--- /dev/null
@@ -0,0 +1,34 @@
+'use strict';
+
+/**
+ * Helper function to implement DataProvider
+ * @param {Function|Array|Object} values
+ * @param {Function} func
+ */
+function using(values, func) {
+       if (values instanceof Function) {
+               values = values();
+       }
+
+       if (values instanceof Array) {
+               values.forEach(function(value) {
+                       if (!(value instanceof Array)) {
+                               value = [value];
+                       }
+
+                       func.apply(this, value);
+               });
+       } else {
+               var objectKeys = Object.keys(values);
+
+               objectKeys.forEach(function(key) {
+                       if (!(values[key] instanceof Array)) {
+                               values[key] = [values[key]];
+                       }
+
+                       values[key].push(key);
+
+                       func.apply(this, values[key]);
+               });
+       }
+}
diff --git a/components/testing_framework/core/Build/Configuration/JSUnit/karma.conf.js b/components/testing_framework/core/Build/Configuration/JSUnit/karma.conf.js
new file mode 100644 (file)
index 0000000..9f82f99
--- /dev/null
@@ -0,0 +1,78 @@
+'use strict';
+
+/**
+ * Karma configuration
+ */
+
+module.exports = function(config) {
+       config.set({
+               // base path that will be used to resolve all patterns (eg. files, exclude)
+               basePath: '../../../../../../',
+
+               // frameworks to use
+               // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+               frameworks: ['jasmine', 'requirejs'],
+
+               // list of files / patterns to load in the browser
+               files: [
+                       { pattern: 'typo3/sysext/core/Resources/Public/JavaScript/Contrib/jquery/jquery-3.1.1.js', included: true },
+                       { pattern: 'typo3/sysext/**/Resources/Public/JavaScript/**/*.js', included: false },
+                       { pattern: 'typo3/sysext/**/Tests/JavaScript/**/*.js', included: false },
+                       'components/testing_framework/core/Build/Configuration/JSUnit/Helper.js',
+                       'components/testing_framework/core/Build/Configuration/JSUnit/Bootstrap.js'
+               ],
+
+               // list of files to exclude
+               exclude: [
+               ],
+
+               // preprocess matching files before serving them to the browser
+               // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+               preprocessors: {
+                       'typo3/sysext/**/Resources/Public/JavaScript/**/*.js': ['coverage']
+               },
+
+               // test results reporter to use
+               // possible values: 'dots', 'progress', 'coverage', 'junit'
+               // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+               reporters: ['progress', 'junit', 'coverage'],
+
+               junitReporter: {
+                       outputDir: 'typo3temp/var/tests/',
+                       useBrowserName: false,
+                       outputFile: 'karma.junit.xml'
+               },
+
+               coverageReporter: {
+                       reporters: [
+                               {type: 'clover', dir: 'typo3temp', subdir: 'var/tests', file: 'karma.clover.xml'}
+                       ]
+               },
+
+               // web server port
+               port: 9876,
+
+               // enable / disable colors in the output (reporters and logs)
+               colors: true,
+
+               // level of logging
+               // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+               logLevel: config.LOG_INFO,
+
+               // enable / disable watching file and executing tests whenever any file changes
+               autoWatch: true,
+
+               // start these browsers
+               // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+               // browsers: ['Firefox', 'Chrome', 'Safari', 'PhantomJS', 'Opera', 'IE'],
+               browsers: ['PhantomJS'],
+
+               // Continuous Integration mode
+               // if true, Karma captures browsers, runs the tests and exits
+               singleRun: false,
+
+               // Concurrency level
+               // how many browser should be started simultaneous
+               concurrency: Infinity
+       })
+};
diff --git a/components/testing_framework/core/Build/FunctionalTests.xml b/components/testing_framework/core/Build/FunctionalTests.xml
new file mode 100644 (file)
index 0000000..7c91c73
--- /dev/null
@@ -0,0 +1,30 @@
+<!--
+       Functional test suites setup
+
+       Functional tests should extend from \TYPO3\CMS\Core\Tests\FunctionalTestCase,
+       take a look at this class for further documentation on how to run the suite.
+
+       TYPO3 CMS functional test suite also needs phpunit bootstrap code, the
+       file is located next to this .xml as FunctionalTestsBootstrap.php
+-->
+<phpunit
+       backupGlobals="true"
+       backupStaticAttributes="false"
+       bootstrap="FunctionalTestsBootstrap.php"
+       colors="true"
+       convertErrorsToExceptions="true"
+       convertWarningsToExceptions="true"
+       forceCoversAnnotation="false"
+       processIsolation="true"
+       stopOnError="false"
+       stopOnFailure="false"
+       stopOnIncomplete="false"
+       stopOnSkipped="false"
+       verbose="false"
+>
+       <testsuites>
+               <testsuite name="Core tests">
+                       <directory>../../../../typo3/sysext/*/Tests/Functional/</directory>
+               </testsuite>
+       </testsuites>
+</phpunit>
diff --git a/components/testing_framework/core/Build/FunctionalTestsBootstrap.php b/components/testing_framework/core/Build/FunctionalTestsBootstrap.php
new file mode 100644 (file)
index 0000000..483091c
--- /dev/null
@@ -0,0 +1,27 @@
+<?php
+/*
+ * 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 file is defined in FunctionalTests.xml and called by phpunit
+ * before instantiating the test suites, it must also be included
+ * with phpunit parameter --bootstrap if executing single test case classes.
+ */
+call_user_func(function () {
+    $testbase = new \TYPO3\CMS\Components\TestingFramework\Core\Testbase();
+    $testbase->enableDisplayErrors();
+    $testbase->defineBaseConstants();
+    $testbase->defineOriginalRootPath();
+    $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests');
+    $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient');
+});
diff --git a/components/testing_framework/core/Build/Scripts/splitFunctionalTests.sh b/components/testing_framework/core/Build/Scripts/splitFunctionalTests.sh
new file mode 100755 (executable)
index 0000000..49a54fd
--- /dev/null
@@ -0,0 +1,117 @@
+#!/bin/bash
+
+#########################
+#
+# This file is typically executed by travis and / or bamboo.
+# It expects to be run from the core root.
+#
+# ./components/testing_framework/core/Build/Scripts/splitFunctionalTests.sh <numberOfConfigs>
+#
+# The scripts finds all functional tests and creates <numberOfConfigs> number
+# of phpunit .xml configuration files where each configuration lists a weighted
+# number of single functional tests.
+#
+# components/testing_framework/core/Build/FunctionalTests-Job-<counter>.xml
+#
+#########################
+
+numberOfFunctionalTestJobs=${1}
+numberOfFunctionalTestJobsMinusOne=$(( numberOfFunctionalTestJobs - 1 ))
+
+# Have a dir for temp files and clean up possibly existing stuff
+if [ ! -d buildTemp ]; then
+       mkdir buildTemp || exit 1
+fi
+if [ -f buildTemp/testFiles.txt ]; then
+       rm buildTemp/testFiles.txt
+fi
+if [ -f buildTemp/testFilesWithNumberOfTestFiles.txt ]; then
+       rm buildTemp/testFilesWithNumberOfTestFiles.txt
+fi
+if [ -f buildTemp/testFilesWeighted.txt ]; then
+       rm buildTemp/testFilesWeighted.txt
+fi
+
+# A list of all functional test files
+find . -name \*Test.php -path \*typo3/sysext/*/Tests/Functional* > buildTemp/testFiles.txt
+
+# File with test files of format "42 ./path/to/file"
+while read testFile; do
+       numberOfTestsInTestFile=`grep "@test" ${testFile} | wc -l`
+       echo "${numberOfTestsInTestFile} ${testFile}" >> buildTemp/testFilesWithNumberOfTestFiles.txt
+done < buildTemp/testFiles.txt
+
+# Sort list of files numeric
+cat buildTemp/testFilesWithNumberOfTestFiles.txt | sort -n -r > buildTemp/testFilesWeighted.txt
+
+# Config file boilerplate per job
+for (( i=0; i<${numberOfFunctionalTestJobs}; i++)); do
+       if [ -f components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml ]; then
+               rm components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       fi
+       echo '<phpunit' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  backupGlobals="true"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  backupStaticAttributes="false"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  bootstrap="FunctionalTestsBootstrap.php"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  colors="true"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  convertErrorsToExceptions="true"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  convertWarningsToExceptions="true"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  forceCoversAnnotation="false"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  processIsolation="true"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  stopOnError="false"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  stopOnFailure="false"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  stopOnIncomplete="false"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  stopOnSkipped="false"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  verbose="false"' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '>' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  <testsuites>' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '          <testsuite name="Core tests">' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+done
+
+counter=0
+direction=ascending
+while read testFileWeighted; do
+       # test file only, without leading ./
+       testFile=`echo ${testFileWeighted} | cut -f2 -d" " | cut -f2-40 -d"/"`
+
+       # Goal: with 3 jobs, have:
+       # file #0 to job #0 (asc)
+       # file #1 to job #1 (asc)
+       # file #2 to job #2 (asc)
+       # file #3 to job #2 (desc)
+       # file #4 to job #1 (desc)
+       # file #5 to job #0 (desc)
+       # file #6 to job #0 (asc)
+       # ...
+       testFileModuleNumberOfJobs=$(( counter % numberOfFunctionalTestJobs ))
+       if [[ ${direction} == "descending" ]]; then
+               targetJobNumberForFile=$(( numberOfFunctionalTestJobs - testFileModuleNumberOfJobs))
+       else
+               targetJobNumberForFile=${testFileModuleNumberOfJobs}
+       fi
+       if [ ${testFileModuleNumberOfJobs} -eq ${numberOfFunctionalTestJobs} ]; then
+               if [[ ${direction} == "descending" ]]; then
+                       direction=ascending
+               else
+                       direction=descending
+               fi
+       fi
+
+       echo '                  <directory>' >> components/testing_framework/core/Build/FunctionalTests-Job-${targetJobNumberForFile}.xml
+       echo "                          ../../../../${testFile}" >> components/testing_framework/core/Build/FunctionalTests-Job-${targetJobNumberForFile}.xml
+       echo '                  </directory>' >> components/testing_framework/core/Build/FunctionalTests-Job-${targetJobNumberForFile}.xml
+       (( counter ++ ))
+done < buildTemp/testFilesWeighted.txt
+
+# Final part of config file
+for (( i=0; i<${numberOfFunctionalTestJobs}; i++)); do
+       echo '          </testsuite>' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '  </testsuites>' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+       echo '</phpunit>' >> components/testing_framework/core/Build/FunctionalTests-Job-${i}.xml
+done
+
+# Clean up
+rm buildTemp/testFiles.txt
+rm buildTemp/testFilesWeighted.txt
+rm buildTemp/testFilesWithNumberOfTestFiles.txt
+rmdir buildTemp
diff --git a/components/testing_framework/core/Build/UnitTests.xml b/components/testing_framework/core/Build/UnitTests.xml
new file mode 100644 (file)
index 0000000..0078936
--- /dev/null
@@ -0,0 +1,27 @@
+<phpunit
+       backupGlobals="true"
+       backupStaticAttributes="false"
+       bootstrap="UnitTestsBootstrap.php"
+       colors="true"
+       convertErrorsToExceptions="true"
+       convertWarningsToExceptions="true"
+       forceCoversAnnotation="false"
+       processIsolation="false"
+       stopOnError="false"
+       stopOnFailure="false"
+       stopOnIncomplete="false"
+       stopOnSkipped="false"
+       verbose="false"
+>
+       <testsuites>
+               <testsuite name="Core tests">
+                       <directory>../../../../typo3/sysext/*/Tests/Unit/</directory>
+               </testsuite>
+               <testsuite name="Core legacy tests">
+                       <directory>../../../../typo3/sysext/core/Tests/Legacy/</directory>
+               </testsuite>
+               <testsuite name="Suite integrity tests">
+                       <directory>../../../../typo3/sysext/core/Tests/Integrity/</directory>
+               </testsuite>
+       </testsuites>
+</phpunit>
diff --git a/components/testing_framework/core/Build/UnitTestsBootstrap.php b/components/testing_framework/core/Build/UnitTestsBootstrap.php
new file mode 100644 (file)
index 0000000..fbd246d
--- /dev/null
@@ -0,0 +1,70 @@
+<?php
+/*
+ * 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 file is defined in UnitTests.xml and called by phpunit
+ * before instantiating the test suites, it must also be included
+ * with phpunit parameter --bootstrap if executing single test case classes.
+ *
+ * Run whole core unit test suite, example:
+ * - cd /var/www/t3master/foo  # Document root of TYPO3 CMS instance (location of index.php)
+ * - typo3/../bin/phpunit -c components/testing_framework/core/Build/UnitTests.xml
+ *
+ * Run single test case, example:
+ * - cd /var/www/t3master/foo  # Document root of TYPO3 CMS instance (location of index.php)
+ * - typo3/../bin/phpunit -c components/testing_framework/core/Build/UnitTests.xml
+ *     typo3/sysext/core/Tests/Unit/DataHandling/DataHandlerTest.php
+ */
+call_user_func(function () {
+    $testbase = new \TYPO3\CMS\Components\TestingFramework\Core\Testbase();
+    $testbase->enableDisplayErrors();
+    $testbase->defineBaseConstants();
+    $testbase->defineSitePath();
+    $testbase->defineTypo3ModeBe();
+    $testbase->setTypo3TestingContext();
+    $testbase->createDirectory(PATH_site . 'typo3conf/ext');
+    $testbase->createDirectory(PATH_site . 'typo3temp/assets');
+    $testbase->createDirectory(PATH_site . 'typo3temp/var/tests');
+    $testbase->createDirectory(PATH_site . 'typo3temp/var/transient');
+    $testbase->createDirectory(PATH_site . 'uploads');
+
+    // Retrieve an instance of class loader and inject to core bootstrap
+    $classLoaderFilepath = __DIR__ . '/../../../../vendor/autoload.php';
+    if (!file_exists($classLoaderFilepath)) {
+        die('ClassLoader can\'t be loaded. Please check your path or set an environment variable \'TYPO3_PATH_WEB\' to your root path.');
+    }
+    $classLoader = require $classLoaderFilepath;
+    \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
+        ->initializeClassLoader($classLoader)
+        ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
+        ->baseSetup();
+
+    // Initialize default TYPO3_CONF_VARS
+    $configurationManager = new \TYPO3\CMS\Core\Configuration\ConfigurationManager();
+    $GLOBALS['TYPO3_CONF_VARS'] = $configurationManager->getDefaultConfiguration();
+    // Avoid failing tests that rely on HTTP_HOST retrieval
+    $GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = '.*';
+
+    \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
+        ->disableCoreCache()
+        ->initializeCachingFramework()
+        // Set all packages to active
+        ->initializePackageManagement(\TYPO3\CMS\Core\Package\UnitTestPackageManager::class);
+
+    if (!\TYPO3\CMS\Core\Core\Bootstrap::usesComposerClassLoading()) {
+        // Dump autoload info if in non composer mode
+        \TYPO3\CMS\Core\Core\ClassLoadingInformation::dumpClassLoadingInformation();
+        \TYPO3\CMS\Core\Core\ClassLoadingInformation::registerClassLoadingInformation();
+    }
+});
diff --git a/components/testing_framework/core/Exception.php b/components/testing_framework/core/Exception.php
new file mode 100644 (file)
index 0000000..fead5c2
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core;
+
+/*
+ * 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/components/testing_framework/core/FileStreamWrapper.php b/components/testing_framework/core/FileStreamWrapper.php
new file mode 100644 (file)
index 0000000..ee49ee4
--- /dev/null
@@ -0,0 +1,610 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core;
+
+/*
+ * 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 = [];
+
+    /**
+     * 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 = [];
+        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/components/testing_framework/core/Functional/Fixtures/be_users.xml b/components/testing_framework/core/Functional/Fixtures/be_users.xml
new file mode 100644 (file)
index 0000000..9611be2
--- /dev/null
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <be_users>
+               <uid>1</uid>
+               <pid>0</pid>
+               <tstamp>1366642540</tstamp>
+               <username>admin</username>
+               <password>$1$tCrlLajZ$C0sikFQQ3SWaFAZ1Me0Z/1</password> <!-- password -->
+               <admin>1</admin>
+               <disable>0</disable>
+               <starttime>0</starttime>
+               <endtime>0</endtime>
+               <options>0</options>
+               <crdate>1366642540</crdate>
+               <cruser_id>0</cruser_id>
+               <workspace_perms>1</workspace_perms>
+               <disableIPlock>1</disableIPlock>
+               <deleted>0</deleted>
+               <TSconfig>NULL</TSconfig>
+               <lastlogin>1371033743</lastlogin>
+               <createdByAction>0</createdByAction>
+               <workspace_id>0</workspace_id>
+               <workspace_preview>1</workspace_preview>
+       </be_users>
+</dataset>
\ No newline at end of file
diff --git a/components/testing_framework/core/Functional/Fixtures/pages.xml b/components/testing_framework/core/Functional/Fixtures/pages.xml
new file mode 100644 (file)
index 0000000..8766471
--- /dev/null
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <pages>
+               <uid>1</uid>
+               <pid>0</pid>
+               <title>Root</title>
+               <deleted>0</deleted>
+               <perms_everybody>15</perms_everybody>
+       </pages>
+       <pages>
+               <uid>2</uid>
+               <pid>1</pid>
+               <title>Dummy 1-2</title>
+               <deleted>0</deleted>
+               <perms_everybody>15</perms_everybody>
+       </pages>
+       <pages>
+               <uid>3</uid>
+               <pid>2</pid>
+               <title>Dummy 1-2-3</title>
+               <deleted>0</deleted>
+               <perms_everybody>15</perms_everybody>
+       </pages>
+       <pages>
+               <uid>4</uid>
+               <pid>3</pid>
+               <title>Dummy 1-2-3-4</title>
+               <deleted>0</deleted>
+               <perms_everybody>15</perms_everybody>
+       </pages>
+       <pages>
+               <uid>5</uid>
+               <pid>1</pid>
+               <title>Dummy 1-5</title>
+               <deleted>0</deleted>
+               <perms_everybody>15</perms_everybody>
+       </pages>
+       <pages>
+               <uid>6</uid>
+               <pid>5</pid>
+               <title>Dummy 1-5-6</title>
+               <deleted>0</deleted>
+               <perms_everybody>15</perms_everybody>
+       </pages>
+       <pages>
+               <uid>7</uid>
+               <pid>0</pid>
+               <title>Root 2</title>
+               <deleted>0</deleted>
+               <perms_everybody>15</perms_everybody>
+       </pages>
+</dataset>
\ No newline at end of file
diff --git a/components/testing_framework/core/Functional/Fixtures/sys_file_storage.xml b/components/testing_framework/core/Functional/Fixtures/sys_file_storage.xml
new file mode 100644 (file)
index 0000000..68d3bb4
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <sys_file_storage>
+               <uid>1</uid>
+               <pid>0</pid>
+               <name>fileadmin/ (auto-created)</name>
+               <processingfolder>typo3temp/assets/_processed_/</processingfolder>
+               <driver>Local</driver>
+               <configuration><![CDATA[<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3FlexForms>
+       <data>
+               <sheet index="sDEF">
+                       <language index="lDEF">
+                               <field index="basePath">
+                                       <value index="vDEF">fileadmin/</value>
+                               </field>
+                               <field index="pathType">
+                                       <value index="vDEF">relative</value>
+                               </field>
+                               <field index="caseSensitive">
+                                       <value index="vDEF">1</value>
+                               </field>
+                       </language>
+               </sheet>
+       </data>
+</T3FlexForms>]]></configuration>
+               <is_browsable>1</is_browsable>
+               <is_public>1</is_public>
+               <is_writable>1</is_writable>
+               <is_online>1</is_online>
+       </sys_file_storage>
+</dataset>
\ No newline at end of file
diff --git a/components/testing_framework/core/Functional/Fixtures/sys_language.xml b/components/testing_framework/core/Functional/Fixtures/sys_language.xml
new file mode 100644 (file)
index 0000000..d28a551
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+    <sys_language>
+        <uid>1</uid>
+        <pid>0</pid>
+        <tstamp>1277119475</tstamp>
+        <hidden>0</hidden>
+        <title>Dansk</title>
+        <flag>dk</flag>
+        <static_lang_isocode>0</static_lang_isocode>
+    </sys_language>
+    <sys_language>
+        <uid>2</uid>
+        <pid>0</pid>
+        <tstamp>1277119475</tstamp>
+        <hidden>0</hidden>
+        <title>Deutsch</title>
+        <flag>de</flag>
+        <static_lang_isocode>0</static_lang_isocode>
+    </sys_language>
+</dataset>
\ No newline at end of file
diff --git a/components/testing_framework/core/Functional/Fixtures/tt_content.xml b/components/testing_framework/core/Functional/Fixtures/tt_content.xml
new file mode 100644 (file)
index 0000000..49ceb10
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+    <tt_content>
+        <uid>1</uid>
+        <pid>1</pid>
+        <header>Test content</header>
+        <deleted>0</deleted>
+        <t3ver_oid>0</t3ver_oid>
+        <t3ver_wsid>0</t3ver_wsid>
+    </tt_content>
+</dataset>
\ No newline at end of file
diff --git a/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/AbstractRecordConstraint.php b/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/AbstractRecordConstraint.php
new file mode 100644 (file)
index 0000000..a37e498
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Constraint\RequestSection;
+
+/*
+ * 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\Components\TestingFramework\Core\Functional\Framework\Frontend\ResponseSection;
+
+/**
+ * Model of frontend response
+ */
+abstract class AbstractRecordConstraint extends \PHPUnit_Framework_Constraint
+{
+    /**
+     * @var array
+     */
+    protected $sectionFailures = [];
+
+    /**
+     * @var string
+     */
+    protected $table;
+
+    /**
+     * @var string
+     */
+    protected $field;
+
+    /**
+     * @var bool
+     */
+    protected $strict = false;
+
+    /**
+     * @var array
+     */
+    protected $values;
+
+    public function setTable($table)
+    {
+        $this->table = $table;
+        return $this;
+    }
+
+    public function setField($field)
+    {
+        $this->field = $field;
+        return $this;
+    }
+
+    public function setValues(...$values)
+    {
+        $this->values = $values;
+        return $this;
+    }
+
+    public function setStrict($strict)
+    {
+        $this->strict = (bool)$strict;
+        return $this;
+    }
+
+    /**
+     * Evaluates the constraint for parameter $other. Returns true if the
+     * constraint is met, false otherwise.
+     *
+     * @param array|ResponseSection|ResponseSection[] $other ResponseSections to evaluate
+     * @return bool
+     */
+    protected function matches($other)
+    {
+        if (is_array($other)) {
+            $success = null;
+            foreach ($other as $item) {
+                $currentSuccess = $this->matchesSection($item);
+                $success = ($success === null ? $currentSuccess : $success || $currentSuccess);
+            }
+            return !empty($success);
+        } else {
+            return $this->matchesSection($other);
+        }
+    }
+
+    /**
+     * @param ResponseSection $responseSection
+     * @return bool
+     */
+    abstract protected function matchesSection(ResponseSection $responseSection);
+
+    /**
+     * @param array $records
+     * @return array
+     */
+    protected function getNonMatchingValues(array $records)
+    {
+        $values = $this->values;
+
+        foreach ($records as $recordIdentifier => $recordData) {
+            if (strpos($recordIdentifier, $this->table . ':') !== 0) {
+                continue;
+            }
+
+            if (($foundValueIndex = array_search($recordData[$this->field], $values)) !== false) {
+                unset($values[$foundValueIndex]);
+            }
+        }
+
+        return $values;
+    }
+
+    /**
+     * @param array $records
+     * @return array
+     */
+    protected function getRemainingRecords(array $records)
+    {
+        $values = $this->values;
+
+        foreach ($records as $recordIdentifier => $recordData) {
+            if (strpos($recordIdentifier, $this->table . ':') !== 0) {
+                unset($records[$recordIdentifier]);
+                continue;
+            }
+
+            if (($foundValueIndex = array_search($recordData[$this->field], $values)) !== false) {
+                unset($values[$foundValueIndex]);
+                unset($records[$recordIdentifier]);
+            }
+        }
+
+        return $records;
+    }
+
+    /**
+     * Returns the description of the failure
+     *
+     * The beginning of failure messages is "Failed asserting that" in most
+     * cases. This method should return the second part of that sentence.
+     *
+     * @param mixed $other Evaluated value or object.
+     * @return string
+     */
+    protected function failureDescription($other)
+    {
+        return $this->toString();
+    }
+
+    /**
+     * Return additional failure description where needed
+     *
+     * The function can be overridden to provide additional failure
+     * information like a diff
+     *
+     * @param mixed $other Evaluated value or object.
+     * @return string
+     */
+    protected function additionalFailureDescription($other)
+    {
+        $failureDescription = '';
+        foreach ($this->sectionFailures as $sectionIdentifier => $sectionFailure) {
+            $failureDescription .= '* Section "' . $sectionIdentifier . '": ' . $sectionFailure . LF;
+        }
+        return $failureDescription;
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/AbstractStructureRecordConstraint.php b/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/AbstractStructureRecordConstraint.php
new file mode 100644 (file)
index 0000000..16f5983
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Constraint\RequestSection;
+
+/*
+ * 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!
+ */
+
+/**
+ * Model of frontend response
+ */
+abstract class AbstractStructureRecordConstraint extends AbstractRecordConstraint
+{
+    /**
+     * @var string
+     */
+    protected $recordIdentifier;
+
+    /**
+     * @var string
+     */
+    protected $recordField;
+
+    public function setRecordIdentifier($recordIdentifier)
+    {
+        $this->recordIdentifier = $recordIdentifier;
+        return $this;
+    }
+
+    public function setRecordField($recordField)
+    {
+        $this->recordField = $recordField;
+        return $this;
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/DoesNotHaveRecordConstraint.php b/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/DoesNotHaveRecordConstraint.php
new file mode 100644 (file)
index 0000000..ee26093
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Constraint\RequestSection;
+
+/*
+ * 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\Components\TestingFramework\Core\Functional\Framework\Frontend\ResponseSection;
+
+/**
+ * Model of frontend response
+ */
+class DoesNotHaveRecordConstraint extends AbstractRecordConstraint
+{
+    /**
+     * @param ResponseSection $responseSection
+     * @return bool
+     */
+    protected function matchesSection(ResponseSection $responseSection)
+    {
+        $records = $responseSection->getRecords();
+
+        if (empty($records) || !is_array($records)) {
+            $this->sectionFailures[$responseSection->getIdentifier()] = 'No records found.';
+            return false;
+        }
+
+        $nonMatchingValues = $this->getNonMatchingValues($records);
+        $matchingValues = array_diff($this->values, $nonMatchingValues);
+
+        if (!empty($matchingValues)) {
+            $this->sectionFailures[$responseSection->getIdentifier()] = 'Could not assert not having values for "' . $this->table . '.' . $this->field . '": ' . implode(', ', $matchingValues);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns a string representation of the constraint.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'response does not have record';
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/HasRecordConstraint.php b/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/HasRecordConstraint.php
new file mode 100644 (file)
index 0000000..f8c5b42
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Constraint\RequestSection;
+
+/*
+ * 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\Components\TestingFramework\Core\Functional\Framework\Frontend\ResponseSection;
+
+/**
+ * Model of frontend response
+ */
+class HasRecordConstraint extends AbstractRecordConstraint
+{
+    /**
+     * @param ResponseSection $responseSection
+     * @return bool
+     */
+    protected function matchesSection(ResponseSection $responseSection)
+    {
+        $records = $responseSection->getRecords();
+
+        if (empty($records) || !is_array($records)) {
+            $this->sectionFailures[$responseSection->getIdentifier()] = 'No records found.';
+            return false;
+        }
+
+        $nonMatchingValues = $this->getNonMatchingValues($records);
+
+        if (!empty($nonMatchingValues)) {
+            $this->sectionFailures[$responseSection->getIdentifier()] = 'Could not assert all values for "' . $this->table . '.' . $this->field . '": ' . implode(', ', $nonMatchingValues);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns a string representation of the constraint.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'response has records';
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/StructureDoesNotHaveRecordConstraint.php b/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/StructureDoesNotHaveRecordConstraint.php
new file mode 100644 (file)
index 0000000..ba2e156
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Constraint\RequestSection;
+
+/*
+ * 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\Components\TestingFramework\Core\Functional\Framework\Frontend\ResponseSection;
+
+/**
+ * Model of frontend response
+ */
+class StructureDoesNotHaveRecordConstraint extends AbstractStructureRecordConstraint
+{
+    /**
+     * @param ResponseSection $responseSection
+     * @return bool
+     */
+    protected function matchesSection(ResponseSection $responseSection)
+    {
+        $matchingVariants = [];
+
+        foreach ($responseSection->findStructures($this->recordIdentifier, $this->recordField) as $path => $structure) {
+            if (empty($structure) || !is_array($structure)) {
+                $this->sectionFailures[$responseSection->getIdentifier()] = 'No records found in "' . $path . '"';
+                return false;
+            }
+
+            $nonMatchingValues = $this->getNonMatchingValues($structure);
+            $matchingValues = array_diff($this->values, $nonMatchingValues);
+
+            if (!empty($matchingValues)) {
+                $matchingVariants[$path] = $matchingValues;
+            }
+        }
+
+        if (empty($matchingVariants)) {
+            return true;
+        }
+
+        $matchingMessage = '';
+        foreach ($matchingVariants as $path => $matchingValues) {
+            $matchingMessage .= '  * Found in "' . $path . '": ' . implode(', ', $matchingValues);
+        }
+
+        $this->sectionFailures[$responseSection->getIdentifier()] = 'Could not assert not having values for "' . $this->table . '.' . $this->field . '"' . LF . $matchingMessage;
+        return false;
+    }
+
+    /**
+     * Returns a string representation of the constraint.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'structure does not have record';
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/StructureHasRecordConstraint.php b/components/testing_framework/core/Functional/Framework/Constraint/RequestSection/StructureHasRecordConstraint.php
new file mode 100644 (file)
index 0000000..a62fe9c
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Constraint\RequestSection;
+
+/*
+ * 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\Components\TestingFramework\Core\Functional\Framework\Frontend\ResponseSection;
+
+/**
+ * Model of frontend response
+ */
+class StructureHasRecordConstraint extends AbstractStructureRecordConstraint
+{
+    /**
+     * @param ResponseSection $responseSection
+     * @return bool
+     */
+    protected function matchesSection(ResponseSection $responseSection)
+    {
+        $nonMatchingVariants = [];
+        $remainingRecordVariants = [];
+
+        foreach ($responseSection->findStructures($this->recordIdentifier, $this->recordField) as $path => $structure) {
+            if (empty($structure) || !is_array($structure)) {
+                $this->sectionFailures[$responseSection->getIdentifier()] = 'No records found in "' . $path . '"';
+                return false;
+            }
+
+            $remainingRecords = [];
+            $nonMatchingValues = $this->getNonMatchingValues($structure);
+
+            if ($this->strict) {
+                $remainingRecords = $this->getRemainingRecords($structure);
+            }
+
+            if (empty($nonMatchingValues) && (!$this->strict || empty($remainingRecords))) {
+                return true;
+            }
+
+            if (!empty($nonMatchingValues)) {
+                $nonMatchingVariants[$path] = $nonMatchingValues;
+            }
+            if ($this->strict && !empty($remainingRecords)) {
+                $remainingRecordVariants[$path] = $remainingRecords;
+            }
+        }
+
+        $failureMessage = '';
+
+        if (!empty($nonMatchingVariants)) {
+            $failureMessage .= 'Could not assert all values for "' . $this->table . '.' . $this->field . '"' . LF;
+            foreach ($nonMatchingVariants as $path => $nonMatchingValues) {
+                $failureMessage .= '  * Not found in "' . $path . '": ' . implode(', ', $nonMatchingValues) . LF;
+            }
+        }
+
+        if (!empty($remainingRecordVariants)) {
+            $failureMessage .= 'Found remaining records for "' . $this->table . '.' . $this->field . '"' . LF;
+            foreach ($remainingRecordVariants as $path => $remainingRecords) {
+                $failureMessage .= '  * Found in "' . $path . '": ' . implode(', ', array_keys($remainingRecords)) . LF;
+            }
+        }
+
+        $this->sectionFailures[$responseSection->getIdentifier()] = $failureMessage;
+        return false;
+    }
+
+    /**
+     * Returns a string representation of the constraint.
+     *
+     * @return string
+     */
+    public function toString()
+    {
+        return 'structure has record';
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Frontend/Collector.php b/components/testing_framework/core/Functional/Framework/Frontend/Collector.php
new file mode 100644 (file)
index 0000000..c6a8172
--- /dev/null
@@ -0,0 +1,201 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend;
+
+/*
+ * 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!
+ */
+
+/**
+ * Model of frontend response
+ */
+class Collector implements \TYPO3\CMS\Core\SingletonInterface
+{
+    /**
+     * @var array
+     */
+    protected $tableFields;
+
+    /**
+     * @var array
+     */
+    protected $structure = [];
+
+    /**
+     * @var array
+     */
+    protected $structurePaths = [];
+
+    /**
+     * @var array
+     */
+    protected $records = [];
+
+    /**
+     * @var \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
+     */
+    public $cObj;
+
+    public function addRecordData($content, array $configuration = null)
+    {
+        $recordIdentifier = $this->cObj->currentRecord;
+        list($tableName) = explode(':', $recordIdentifier);
+        $currentWatcherValue = $this->getCurrentWatcherValue();
+        $position = strpos($currentWatcherValue, '/' . $recordIdentifier);
+
+        $recordData = $this->filterFields($tableName, $this->cObj->data);
+        $this->records[$recordIdentifier] = $recordData;
+
+        if ($currentWatcherValue === $recordIdentifier) {
+            $this->structure[$recordIdentifier] = $recordData;
+            $this->structurePaths[$recordIdentifier] = [[]];
+        } elseif (!empty($position)) {
+            $levelIdentifier = substr($currentWatcherValue, 0, $position);
+            $this->addToStructure($levelIdentifier, $recordIdentifier, $recordData);
+        }
+    }
+
+    public function addFileData($content, array $configuration = null)
+    {
+        $currentFile = $this->cObj->getCurrentFile();
+
+        if ($currentFile instanceof \TYPO3\CMS\Core\Resource\File) {
+            $tableName = 'sys_file';
+        } elseif ($currentFile instanceof \TYPO3\CMS\Core\Resource\FileReference) {
+            $tableName = 'sys_file_reference';
+        } else {
+            return;
+        }
+
+        $recordData = $this->filterFields($tableName, $currentFile->getProperties());
+        $recordIdentifier = $tableName . ':' . $currentFile->getUid();
+        $this->records[$recordIdentifier] = $recordData;
+
+        $currentWatcherValue = $this->getCurrentWatcherValue();
+        $levelIdentifier = rtrim($currentWatcherValue, '/');
+        $this->addToStructure($levelIdentifier, $recordIdentifier, $recordData);
+    }
+
+    /**
+     * @param string $tableName
+     * @param array $recordData
+     * @return array
+     */
+    protected function filterFields($tableName, array $recordData)
+    {
+        $recordData = array_intersect_key(
+            $recordData,
+            array_flip($this->getTableFields($tableName))
+        );
+        return $recordData;
+    }
+
+    protected function addToStructure($levelIdentifier, $recordIdentifier, array $recordData)
+    {
+        $steps = explode('/', $levelIdentifier);
+        $structurePaths = [];
+        $structure = &$this->structure;
+
+        foreach ($steps as $step) {
+            list($identifier, $fieldName) = explode('.', $step);
+            $structurePaths[] = $identifier;
+            $structurePaths[] = $fieldName;
+            if (!isset($structure[$identifier])) {
+                return;
+            }
+            $structure = &$structure[$identifier];
+            if (!isset($structure[$fieldName]) || !is_array($structure[$fieldName])) {
+                $structure[$fieldName] = [];
+            }
+            $structure = &$structure[$fieldName];
+        }
+
+        $structure[$recordIdentifier] = $recordData;
+        $this->structurePaths[$recordIdentifier][] = $structurePaths;
+    }
+
+    /**
+     * @param string $content
+     * @param NULL|array $configuration
+     * @return void
+     */
+    public function attachSection($content, array $configuration = null)
+    {
+        $section = [
+            'structure' => $this->structure,
+            'structurePaths' => $this->structurePaths,
+            'records' => $this->records,
+        ];
+
+        $as = (!empty($configuration['as']) ? $configuration['as'] : null);
+        $this->getRenderer()->addSection($section, $as);
+        $this->reset();
+    }
+
+    /**
+     * @param string $tableName
+     * @return array
+     */
+    protected function getTableFields($tableName)
+    {
+        if (!isset($this->tableFields) && !empty($this->getFrontendController()->tmpl->setup['config.']['watcher.']['tableFields.'])) {
+            $this->tableFields = $this->getFrontendController()->tmpl->setup['config.']['watcher.']['tableFields.'];
+            foreach ($this->tableFields as &$fieldList) {
+                $fieldList = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $fieldList, true);
+            }
+            unset($fieldList);
+        }
+
+        return !empty($this->tableFields[$tableName]) ? $this->tableFields[$tableName] : [];
+    }
+
+    /**
+     * @return string
+     */
+    protected function getCurrentWatcherValue()
+    {
+        $watcherValue = null;
+        if (isset($this->getFrontendController()->register['watcher'])) {
+            $watcherValue = $this->getFrontendController()->register['watcher'];
+        }
+        return $watcherValue;
+    }
+
+    /**
+     * @return Renderer
+     */
+    protected function getRenderer()
+    {
+        return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
+            \TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend\Renderer::class
+        );
+    }
+
+    /**
+     * @return \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController
+     */
+    protected function getFrontendController()
+    {
+        return $GLOBALS['TSFE'];
+    }
+
+    /**
+     * Collector needs to be reset after attaching a section, otherwise records will pile up.
+     *
+     * @return void
+     */
+    protected function reset()
+    {
+        $this->structure = [];
+        $this->structurePaths = [];
+        $this->records = [];
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Frontend/Hook/BackendUserHandler.php b/components/testing_framework/core/Functional/Framework/Frontend/Hook/BackendUserHandler.php
new file mode 100644 (file)
index 0000000..3dd8e03
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend\Hook;
+
+/*
+ * 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\Database\ConnectionPool;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Handler for backend user
+ */
+class BackendUserHandler implements \TYPO3\CMS\Core\SingletonInterface
+{
+    /**
+     * @param array $parameters
+     * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController
+     */
+    public function initialize(array $parameters, \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController)
+    {
+        $backendUserId = (int)GeneralUtility::_GP('backendUserId');
+        $workspaceId = (int)GeneralUtility::_GP('workspaceId');
+
+        if (empty($backendUserId) || empty($workspaceId)) {
+            return;
+        }
+
+        $backendUser = $this->createBackendUser();
+        $backendUser->user = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getConnectionForTable('be_users')
+            ->select(['*'], 'be_users', ['uid' => $backendUserId])
+            ->fetch();
+        $backendUser->setTemporaryWorkspace($workspaceId);
+        $frontendController->beUserLogin = true;
+
+        $parameters['BE_USER'] = $backendUser;
+        $GLOBALS['BE_USER'] = $backendUser;
+    }
+
+    /**
+     * @return \TYPO3\CMS\Backend\FrontendBackendUserAuthentication
+     */
+    protected function createBackendUser()
+    {
+        return GeneralUtility::makeInstance(
+            \TYPO3\CMS\Backend\FrontendBackendUserAuthentication::class
+        );
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Frontend/Hook/FrontendUserHandler.php b/components/testing_framework/core/Functional/Framework/Frontend/Hook/FrontendUserHandler.php
new file mode 100644 (file)
index 0000000..9055ea4
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend\Hook;
+
+/*
+ * 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\Database\ConnectionPool;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * Handler for frontend user
+ */
+class FrontendUserHandler implements \TYPO3\CMS\Core\SingletonInterface
+{
+    /**
+     * Initialize
+     *
+     * @param array $parameters
+     * @param \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController
+     */
+    public function initialize(array $parameters, \TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController $frontendController)
+    {
+        $frontendUserId = (int)GeneralUtility::_GP('frontendUserId');
+        $frontendController->fe_user->checkPid = 0;
+
+        $frontendUser = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getConnectionForTable('fe_users')
+            ->select(['*'], 'fe_users', ['uid' => $frontendUserId])
+            ->fetch();
+        if (is_array($frontendUser)) {
+            $frontendController->loginUser = 1;
+            $frontendController->fe_user->createUserSession($frontendUser);
+            $frontendController->fe_user->user = $GLOBALS['TSFE']->fe_user->fetchUserSession();
+            $frontendController->initUserGroups();
+        }
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Frontend/Parser.php b/components/testing_framework/core/Functional/Framework/Frontend/Parser.php
new file mode 100644 (file)
index 0000000..2e14d7b
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend;
+
+/*
+ * 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!
+ */
+
+/**
+ * Model of frontend response
+ */
+class Parser implements \TYPO3\CMS\Core\SingletonInterface
+{
+    /**
+     * @var array
+     */
+    protected $paths = [];
+
+    /**
+     * @var array
+     */
+    protected $records = [];
+
+    /**
+     * @return array
+     */
+    public function getPaths()
+    {
+        return $this->paths;
+    }
+
+    /**
+     * @return array
+     */
+    public function getRecords()
+    {
+        return $this->records;
+    }
+
+    /**
+     * @param array $structure
+     * @param array $path
+     */
+    public function parse(array $structure, array $path = [])
+    {
+        $this->process($structure);
+    }
+
+    /**
+     * @param array $iterator
+     * @param array $path
+     */
+    protected function process(array $iterator, array $path = [])
+    {
+        foreach ($iterator as $identifier => $properties) {
+            $this->addRecord($identifier, $properties);
+            $this->addPath($identifier, $path);
+            foreach ($properties as $propertyName => $propertyValue) {
+                if (!is_array($propertyValue)) {
+                    continue;
+                }
+                $nestedPath = array_merge($path, [$identifier, $propertyName]);
+                $this->process($propertyValue, $nestedPath);
+            }
+        }
+    }
+
+    /**
+     * @param string $identifier
+     * @param array $properties
+     */
+    protected function addRecord($identifier, array $properties)
+    {
+        if (isset($this->records[$identifier])) {
+            return;
+        }
+
+        foreach ($properties as $propertyName => $propertyValue) {
+            if (is_array($propertyValue)) {
+                unset($properties[$propertyName]);
+            }
+        }
+
+        $this->records[$identifier] = $properties;
+    }
+
+    /**
+     * @param string $identifier
+     * @param array $path
+     */
+    protected function addPath($identifier, array $path)
+    {
+        if (!isset($this->paths[$identifier])) {
+            $this->paths[$identifier] = [];
+        }
+
+        $this->paths[$identifier][] = $path;
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Frontend/Renderer.php b/components/testing_framework/core/Functional/Framework/Frontend/Renderer.php
new file mode 100644 (file)
index 0000000..3d39434
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend;
+
+/*
+ * 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!
+ */
+
+/**
+ * Model of frontend response
+ */
+class Renderer implements \TYPO3\CMS\Core\SingletonInterface
+{
+    /**
+     * @var array
+     */
+    protected $sections = [];
+
+    /**
+     * @param string $content
+     * @param NULL|array $configuration
+     * @return void
+     */
+    public function parseValues($content, array $configuration = null)
+    {
+        if (empty($content)) {
+            return;
+        }
+
+        $values = json_decode($content, true);
+
+        if (empty($values) || !is_array($values)) {
+            return;
+        }
+
+        $asPrefix = (!empty($configuration['as']) ? $configuration['as'] . ':' : null);
+        foreach ($values as $identifier => $structure) {
+            $parser = $this->createParser();
+            $parser->parse($structure);
+
+            $section = [
+                'structure' => $structure,
+                'structurePaths' => $parser->getPaths(),
+                'records' => $parser->getRecords(),
+            ];
+
+            $this->addSection($section, $asPrefix . $identifier);
+        }
+    }
+
+    /**
+     * @param array $section
+     * @param NULL|string $as
+     */
+    public function addSection(array $section, $as = null)
+    {
+        if (!empty($as)) {
+            $this->sections[$as] = $section;
+        } else {
+            $this->sections[] = $section;
+        }
+    }
+
+    /**
+     * @param string $content
+     * @param NULL|array $configuration
+     * @return string
+     */
+    public function renderSections($content, array $configuration = null)
+    {
+        $content = json_encode($this->sections);
+        return $content;
+    }
+
+    /**
+     * @return Parser
+     */
+    protected function createParser()
+    {
+        return \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(
+            \TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend\Parser::class
+        );
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Frontend/RequestBootstrap.php b/components/testing_framework/core/Functional/Framework/Frontend/RequestBootstrap.php
new file mode 100644 (file)
index 0000000..53d9bf0
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend;
+
+/*
+ * 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!
+ */
+
+/**
+ * Bootstrap for direct CLI Request
+ */
+class RequestBootstrap
+{
+    /**
+     * @return void
+     */
+    public static function setGlobalVariables(array $requestArguments = null)
+    {
+        if (empty($requestArguments)) {
+            die('No JSON encoded arguments given');
+        }
+
+        if (empty($requestArguments['documentRoot'])) {
+            die('No documentRoot given');
+        }
+
+        if (empty($requestArguments['requestUrl']) || ($requestUrlParts = parse_url($requestArguments['requestUrl'])) === false) {
+            die('No valid request URL given');
+        }
+
+        // Populating $_GET and $_REQUEST is query part is set:
+        if (isset($requestUrlParts['query'])) {
+            parse_str($requestUrlParts['query'], $_GET);
+            parse_str($requestUrlParts['query'], $_REQUEST);
+        }
+
+        // Populating $_POST
+        $_POST = [];
+        // Populating $_COOKIE
+        $_COOKIE = [];
+
+        // Setting up the server environment
+        $_SERVER = [];
+        $_SERVER['DOCUMENT_ROOT'] = $requestArguments['documentRoot'];
+        $_SERVER['HTTP_USER_AGENT'] = 'TYPO3 Functional Test Request';
+        $_SERVER['HTTP_HOST'] = $_SERVER['SERVER_NAME'] = isset($requestUrlParts['host']) ? $requestUrlParts['host'] : 'localhost';
+        $_SERVER['SERVER_ADDR'] = $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+        $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'] = '/index.php';
+        $_SERVER['SCRIPT_FILENAME'] = $_SERVER['_'] = $_SERVER['PATH_TRANSLATED'] = $requestArguments['documentRoot'] . '/index.php';
+        $_SERVER['QUERY_STRING'] = (isset($requestUrlParts['query']) ? $requestUrlParts['query'] : '');
+        $_SERVER['REQUEST_URI'] = $requestUrlParts['path'] . (isset($requestUrlParts['query']) ? '?' . $requestUrlParts['query'] : '');
+        $_SERVER['REQUEST_METHOD'] = 'GET';
+
+        // Define HTTPS and server port:
+        if (isset($requestUrlParts['scheme'])) {
+            if ($requestUrlParts['scheme'] === 'https') {
+                $_SERVER['HTTPS'] = 'on';
+                $_SERVER['SERVER_PORT'] = '443';
+            } else {
+                $_SERVER['SERVER_PORT'] = '80';
+            }
+        }
+
+        // Define a port if used in the URL:
+        if (isset($requestUrlParts['port'])) {
+            $_SERVER['SERVER_PORT'] = $requestUrlParts['port'];
+        }
+
+        if (!is_dir($_SERVER['DOCUMENT_ROOT'])) {
+            die('Document root directory "' . $_SERVER['DOCUMENT_ROOT'] . '" does not exist');
+        }
+
+        if (!is_file($_SERVER['SCRIPT_FILENAME'])) {
+            die('Script file "' . $_SERVER['SCRIPT_FILENAME'] . '" does not exist');
+        }
+    }
+
+    /**
+     * @return void
+     */
+    public static function executeAndOutput()
+    {
+        global $TT, $TSFE, $TYPO3_CONF_VARS, $BE_USER, $TYPO3_MISC;
+
+        $result = ['status' => 'failure', 'content' => null, 'error' => null];
+
+        ob_start();
+        try {
+            chdir($_SERVER['DOCUMENT_ROOT']);
+            include($_SERVER['SCRIPT_FILENAME']);
+            $result['status'] = 'success';
+            $result['content'] = ob_get_contents();
+        } catch (\Exception $exception) {
+            $result['error'] = $exception->__toString();
+        }
+        ob_end_clean();
+
+        echo json_encode($result);
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Frontend/Response.php b/components/testing_framework/core/Functional/Framework/Frontend/Response.php
new file mode 100644 (file)
index 0000000..979fb1f
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend;
+
+/*
+ * 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!
+ */
+
+/**
+ * Model of frontend response
+ */
+class Response
+{
+    const STATUS_Success = 'success';
+    const STATUS_Failure = 'failure';
+
+    /**
+     * @var string
+     */
+    protected $status;
+
+    /**
+     * @var NULL|string|array
+     */
+    protected $content;
+
+    /**
+     * @var string
+     */
+    protected $error;
+
+    /**
+     * @var ResponseContent
+     */
+    protected $responseSection;
+
+    /**
+     * @param string $status
+     * @param string $content
+     * @param string $error
+     */
+    public function __construct($status, $content, $error)
+    {
+        $this->status = $status;
+        $this->content = $content;
+        $this->error = $error;
+    }
+
+    /**
+     * @return string
+     */
+    public function getStatus()
+    {
+        return $this->status;
+    }
+
+    /**
+     * @return array|NULL|string
+     */
+    public function getContent()
+    {
+        return $this->content;
+    }
+
+    /**
+     * @return string
+     */
+    public function getError()
+    {
+        return $this->error;
+    }
+
+    /**
+     * @return ResponseContent
+     */
+    public function getResponseContent()
+    {
+        if (!isset($this->responseContent)) {
+            $this->responseContent = new ResponseContent($this);
+        }
+        return $this->responseContent;
+    }
+
+    /**
+     * @param mixed $sectionIdentifiers
+     * @return NULL|array|ResponseSection[]
+     */
+    public function getResponseSections(...$sectionIdentifiers)
+    {
+        if (empty($sectionIdentifiers)) {
+            $sectionIdentifiers = ['Default'];
+        }
+
+        $sections = [];
+        foreach ($sectionIdentifiers as $sectionIdentifier) {
+            $sections[] = $this->getResponseContent()->getSection($sectionIdentifier);
+        }
+
+        return $sections;
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Frontend/ResponseContent.php b/components/testing_framework/core/Functional/Framework/Frontend/ResponseContent.php
new file mode 100644 (file)
index 0000000..dffc057
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend;
+
+/*
+ * 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!
+ */
+
+/**
+ * Model of frontend response content
+ */
+class ResponseContent
+{
+    /**
+     * @var array|ResponseSection[]
+     */
+    protected $sections;
+
+    /**
+     * @var array
+     */
+    protected $structure;
+
+    /**
+     * @var array
+     */
+    protected $structurePaths;
+
+    /**
+     * @var array
+     */
+    protected $records;
+
+    /**
+     * @var array
+     */
+    protected $queries;
+
+    /**
+     * @param Response $response
+     */
+    public function __construct(Response $response)
+    {
+        $content = json_decode($response->getContent(), true);
+
+        if ($content !== null && is_array($content)) {
+            foreach ($content as $sectionIdentifier => $sectionData) {
+                $section = new ResponseSection($sectionIdentifier, $sectionData);
+                $this->sections[$sectionIdentifier] = $section;
+            }
+        }
+    }
+
+    /**
+     * @param string $sectionIdentifier
+     * @return NULL|ResponseSection
+     * @throws \RuntimeException
+     */
+    public function getSection($sectionIdentifier)
+    {
+        if (isset($this->sections[$sectionIdentifier])) {
+            return $this->sections[$sectionIdentifier];
+        }
+
+        throw new \RuntimeException('ResponseSection "' . $sectionIdentifier . '" does not exist', 1476122151);
+    }
+}
diff --git a/components/testing_framework/core/Functional/Framework/Frontend/ResponseSection.php b/components/testing_framework/core/Functional/Framework/Frontend/ResponseSection.php
new file mode 100644 (file)
index 0000000..fa7accb
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core\Functional\Framework\Frontend;
+
+/*
+ * 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!
+ */
+
+/**
+ * Model of frontend response content
+ */
+class ResponseSection
+{
+    /**
+     * @var string
+     */
+    protected $identifier;
+
+    /**
+     * @var array
+     */
+    protected $structure;
+
+    /**
+     * @var array
+     */
+    protected $structurePaths;
+
+    /**
+     * @var array
+     */
+    protected $records;
+
+    /**
+     * @var array
+     */
+    protected $queries;
+
+    /**
+     * @param string $identifier
+     * @param array $data
+     */
+    public function __construct($identifier, array $data)
+    {
+        $this->identifier = (string)$identifier;
+        $this->structure = $data['structure'];
+        $this->structurePaths = $data['structurePaths'];
+        $this->records = $data['records'];
+
+        if (!empty($data['queries'])) {
+            $this->queries = $data['queries'];
+        }
+    }
+
+    /**
+     * @return string
+     */
+    public function getIdentifier()
+    {
+        return $this->identifier;
+    }
+
+    /**
+     * @return array
+     */
+    public function getStructure()
+    {
+        return $this->structure;
+    }
+
+    /**
+     * @return array
+     */
+    public function getStructurePaths()
+    {
+        return $this->structurePaths;
+    }
+
+    /**
+     * @return array
+     */
+    public function getRecords()
+    {
+        return $this->records;
+    }
+
+    /**
+     * @return array
+     */
+    public function getQueries()
+    {
+        return $this->queries;
+    }
+
+    /**
+     * @param string $recordIdentifier
+     * @param string $fieldName
+     * @return array
+     */
+    public function findStructures($recordIdentifier, $fieldName = '')
+    {
+        $structures = [];
+
+        if (empty($this->structurePaths[$recordIdentifier])) {
+            return $structures;
+        }
+
+        foreach ($this->structurePaths[$recordIdentifier] as $steps) {
+            $structure = $this->structure;
+            $steps[] = $recordIdentifier;
+
+            if (!empty($fieldName)) {
+                $steps[] = $fieldName;
+            }
+
+            foreach ($steps as $step) {
+                if (!isset($structure[$step])) {
+                    $structure = null;
+                    break;
+                }
+                $structure = $structure[$step];
+            }
+
+            if (!empty($structure)) {
+                $structures[implode('/', $steps)] = $structure;
+            }
+        }
+
+        return $structures;
+    }
+}
diff --git a/components/testing_framework/core/FunctionalTestCase.php b/components/testing_framework/core/FunctionalTestCase.php
new file mode 100644 (file)
index 0000000..e8618b3
--- /dev/null
@@ -0,0 +1,429 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core;
+
+/*
+ * 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\Components\TestingFramework\Core\Functional\Framework\Frontend\Response;
+use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Cache\Backend\NullBackend;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+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 components/testing_framework/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 components/testing_framework/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 = 'components/testing_framework/core/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['DB'] = $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'] = '.*';
+            // @todo: This should be moved over to DB/Connections/Default/initCommands
+            $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\';';
+            $localConfiguration['SYS']['caching']['cacheConfigurations']['extbase_object']['backend'] = NullBackend::class;
+            $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
+     * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
+     */
+    protected function getDatabaseConnection()
+    {
+        GeneralUtility::logDeprecatedFunction();
+        return $GLOBALS['TYPO3_DB'];
+    }
+
+    /**
+     * @return ConnectionPool
+     */
+    protected function getConnectionPool()
+    {
+        return GeneralUtility::makeInstance(ConnectionPool::class);
+    }
+
+    /**
+     * 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);
+
+        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
+        $queryBuilder->getRestrictions()->removeAll();
+
+        $userRow = $queryBuilder->select('*')
+            ->from('be_users')
+            ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($userUid, \PDO::PARAM_INT)))
+            ->execute()
+            ->fetch();
+
+        /** @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 = [])
+    {
+        $pageId = (int)$pageId;
+
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');
+        $page = $connection->select(['*'], 'pages', ['uid' => $pageId])->fetch();
+
+        if (empty($page)) {
+            $this->fail('Cannot set up frontend root page "' . $pageId . '"');
+        }
+
+        $connection->update(
+            'pages',
+            ['is_siteroot' => 1],
+            ['uid' => $pageId]
+        );
+
+        $templateFields = [
+            'pid' => $pageId,
+            'title' => '',
+            'config' => '',
+            'clear' => 3,
+            'root' => 1,
+        ];
+
+        foreach ($typoScriptFiles as $typoScriptFile) {
+            $templateFields['config'] .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . LF;
+        }
+
+        GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_template')->insert(
+            '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 = [
+            '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(
+            [
+                '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/components/testing_framework/core/Testbase.php b/components/testing_framework/core/Testbase.php
new file mode 100644 (file)
index 0000000..6a56ac6
--- /dev/null
@@ -0,0 +1,693 @@
+<?php
+namespace TYPO3\CMS\Components\TestingFramework\Core;
+
+/*
+ * 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 Doctrine\DBAL\DBALException;
+use Doctrine\DBAL\DriverManager;
+use TYPO3\CMS\Core\Core\Bootstrap;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Schema\SchemaMigrator;
+use TYPO3\CMS\Core\Database\Schema\SqlReader;
+use TYPO3\CMS\Core\Utility\ArrayUtility;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
+/**
+ * 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 = [
+            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) {
+    &n