[TASK] Testing Framework Extraction - Next Steps 49/51449/12
authorSusanne Moog <susanne.moog@typo3.com>
Sat, 28 Jan 2017 09:24:15 +0000 (10:24 +0100)
committerChristian Kuhn <lolli@schwarzbu.ch>
Fri, 3 Feb 2017 15:33:43 +0000 (16:33 +0100)
- Removal of the CMS part from the namespace
- Distinguish Classes / Resources

For background information see

https://decisions.typo3.org/t/testing-framework-extraction-next-steps/50

Change-Id: I3f73baeb16bbef82c9626add063c9edde41b47a6
Resolves: #79518
Releases: master
Reviewed-on: https://review.typo3.org/51449
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Christian Kuhn <lolli@schwarzbu.ch>
Tested-by: Christian Kuhn <lolli@schwarzbu.ch>
731 files changed:
components/testing_framework/Classes/Core/Acceptance/AcceptanceCoreEnvironment.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Acceptance/Step/Backend/Admin.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Acceptance/Step/Backend/Editor.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Acceptance/Support/Helper/FormHandlerElementTestDataObject.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Acceptance/Support/Helper/Formhandler.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Acceptance/Support/Helper/ModalDialog.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Acceptance/Support/Helper/Topbar.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Acceptance/Support/Page/PageTree.php [new file with mode: 0644]
components/testing_framework/Classes/Core/AccessibleObjectInterface.php [new file with mode: 0644]
components/testing_framework/Classes/Core/BaseTestCase.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Exception.php [new file with mode: 0644]
components/testing_framework/Classes/Core/FileStreamWrapper.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/AbstractRecordConstraint.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/AbstractStructureRecordConstraint.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/DoesNotHaveRecordConstraint.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/HasRecordConstraint.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/StructureDoesNotHaveRecordConstraint.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/StructureHasRecordConstraint.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Frontend/Collector.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Frontend/Hook/BackendUserHandler.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Frontend/Hook/FrontendUserHandler.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Frontend/Parser.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Frontend/Renderer.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Frontend/Response.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Frontend/ResponseContent.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/Framework/Frontend/ResponseSection.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Functional/FunctionalTestCase.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Testbase.php [new file with mode: 0644]
components/testing_framework/Classes/Core/Unit/UnitTestCase.php [new file with mode: 0644]
components/testing_framework/Classes/Fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php [new file with mode: 0644]
components/testing_framework/Resources/Core/Acceptance/Fixtures/be_groups.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Acceptance/Fixtures/be_sessions.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Acceptance/Fixtures/be_users.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Acceptance/Fixtures/sys_category.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Acceptance/Fixtures/tx_extensionmanager_domain_model_extension.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Acceptance/Fixtures/tx_extensionmanager_domain_model_repository.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/AcceptanceTests.yml [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/Configuration/Acceptance/Support/AcceptanceTester.php [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/Configuration/Acceptance/Support/Helper/Acceptance.php [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/Configuration/JSUnit/Bootstrap.js [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/Configuration/JSUnit/Helper.js [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/Configuration/JSUnit/karma.conf.js [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/FunctionalTests.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/FunctionalTestsBootstrap.php [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/Scripts/splitFunctionalTests.sh [new file with mode: 0755]
components/testing_framework/Resources/Core/Build/UnitTests.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Build/UnitTestsBootstrap.php [new file with mode: 0644]
components/testing_framework/Resources/Core/Functional/Fixtures/be_users.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Functional/Fixtures/pages.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Functional/Fixtures/sys_file_storage.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Functional/Fixtures/sys_language.xml [new file with mode: 0644]
components/testing_framework/Resources/Core/Functional/Fixtures/tt_content.xml [new file with mode: 0644]
components/testing_framework/compat/core/AccessibleObjectInterface.php
components/testing_framework/compat/core/BaseTestCase.php
components/testing_framework/compat/core/Exception.php
components/testing_framework/compat/core/FileStreamWrapper.php
components/testing_framework/compat/core/FunctionalTestCase.php
components/testing_framework/compat/core/Testbase.php
components/testing_framework/compat/core/UnitTestCase.php
components/testing_framework/compat/fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php
components/testing_framework/core/Acceptance/Fixtures/be_groups.xml [deleted file]
components/testing_framework/core/Acceptance/Fixtures/be_sessions.xml [deleted file]
components/testing_framework/core/Acceptance/Fixtures/be_users.xml [deleted file]
components/testing_framework/core/Acceptance/Fixtures/sys_category.xml [deleted file]
components/testing_framework/core/Acceptance/Fixtures/tx_extensionmanager_domain_model_extension.xml [deleted file]
components/testing_framework/core/Acceptance/Fixtures/tx_extensionmanager_domain_model_repository.xml [deleted file]
components/testing_framework/core/Acceptance/Step/Backend/Admin.php [deleted file]
components/testing_framework/core/Acceptance/Step/Backend/Editor.php [deleted file]
components/testing_framework/core/Acceptance/Support/Helper/FormHandlerElementTestDataObject.php [deleted file]
components/testing_framework/core/Acceptance/Support/Helper/Formhandler.php [deleted file]
components/testing_framework/core/Acceptance/Support/Helper/ModalDialog.php [deleted file]
components/testing_framework/core/Acceptance/Support/Helper/Topbar.php [deleted file]
components/testing_framework/core/Acceptance/Support/Page/PageTree.php [deleted file]
components/testing_framework/core/AcceptanceCoreEnvironment.php [deleted file]
components/testing_framework/core/AccessibleObjectInterface.php [deleted file]
components/testing_framework/core/BaseTestCase.php [deleted file]
components/testing_framework/core/Build/AcceptanceTests.yml [deleted file]
components/testing_framework/core/Build/Configuration/Acceptance/Support/AcceptanceTester.php [deleted file]
components/testing_framework/core/Build/Configuration/Acceptance/Support/Helper/Acceptance.php [deleted file]
components/testing_framework/core/Build/Configuration/JSUnit/Bootstrap.js [deleted file]
components/testing_framework/core/Build/Configuration/JSUnit/Helper.js [deleted file]
components/testing_framework/core/Build/Configuration/JSUnit/karma.conf.js [deleted file]
components/testing_framework/core/Build/FunctionalTests.xml [deleted file]
components/testing_framework/core/Build/FunctionalTestsBootstrap.php [deleted file]
components/testing_framework/core/Build/Scripts/splitFunctionalTests.sh [deleted file]
components/testing_framework/core/Build/UnitTests.xml [deleted file]
components/testing_framework/core/Build/UnitTestsBootstrap.php [deleted file]
components/testing_framework/core/Exception.php [deleted file]
components/testing_framework/core/FileStreamWrapper.php [deleted file]
components/testing_framework/core/Functional/Fixtures/be_users.xml [deleted file]
components/testing_framework/core/Functional/Fixtures/pages.xml [deleted file]
components/testing_framework/core/Functional/Fixtures/sys_file_storage.xml [deleted file]
components/testing_framework/core/Functional/Fixtures/sys_language.xml [deleted file]
components/testing_framework/core/Functional/Fixtures/tt_content.xml [deleted file]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/AbstractRecordConstraint.php [deleted file]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/AbstractStructureRecordConstraint.php [deleted file]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/DoesNotHaveRecordConstraint.php [deleted file]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/HasRecordConstraint.php [deleted file]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/StructureDoesNotHaveRecordConstraint.php [deleted file]
components/testing_framework/core/Functional/Framework/Constraint/RequestSection/StructureHasRecordConstraint.php [deleted file]
components/testing_framework/core/Functional/Framework/Frontend/Collector.php [deleted file]
components/testing_framework/core/Functional/Framework/Frontend/Hook/BackendUserHandler.php [deleted file]
components/testing_framework/core/Functional/Framework/Frontend/Hook/FrontendUserHandler.php [deleted file]
components/testing_framework/core/Functional/Framework/Frontend/Parser.php [deleted file]
components/testing_framework/core/Functional/Framework/Frontend/Renderer.php [deleted file]
components/testing_framework/core/Functional/Framework/Frontend/RequestBootstrap.php [deleted file]
components/testing_framework/core/Functional/Framework/Frontend/Response.php [deleted file]
components/testing_framework/core/Functional/Framework/Frontend/ResponseContent.php [deleted file]
components/testing_framework/core/Functional/Framework/Frontend/ResponseSection.php [deleted file]
components/testing_framework/core/FunctionalTestCase.php [deleted file]
components/testing_framework/core/Testbase.php [deleted file]
components/testing_framework/core/UnitTestCase.php [deleted file]
components/testing_framework/fluid/Unit/ViewHelpers/ViewHelperBaseTestcase.php [deleted file]
composer.json
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/FormSelectTreeAjaxControllerTest.php
typo3/sysext/backend/Tests/Unit/Controller/LoginControllerTest.php
typo3/sysext/backend/Tests/Unit/Controller/PageLayoutControllerTest.php
typo3/sysext/backend/Tests/Unit/Controller/Wizard/SuggestWizardControllerTest.php
typo3/sysext/backend/Tests/Unit/Form/Element/AbstractFormElementTest.php
typo3/sysext/backend/Tests/Unit/Form/Element/InputDateTimeElementTest.php
typo3/sysext/backend/Tests/Unit/Form/Element/InputHiddenElementTest.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/OrderedProviderListTest.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/TcaTextTest.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/Documentation/Changelog/master/Breaking-79025-ExtractTestingFrameworkForTYPO3.rst
typo3/sysext/core/Tests/Acceptance/Backend/BackendUser/ListUserCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Extensionmanager/GetExtensionsCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Extensionmanager/InstalledExtensionsCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Formhandler/CategoryTreeCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Formhandler/ElementsBasicCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Formhandler/ElementsGroupCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Formhandler/Inline1nCest.php
typo3/sysext/core/Tests/Acceptance/Backend/General/ModuleMenuCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Install/InstallModuleCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Language/LanguageCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Login/BackendLoginCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Page/AddPageInPageModuleCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Page/PageModuleCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Scheduler/TasksCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Template/TemplateCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Topbar/BookmarkCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Topbar/FlushCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Topbar/HelpCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Topbar/LogoCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Topbar/ModuleMenuButtonCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Topbar/NavigationComponentTreeCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Topbar/SearchCest.php
typo3/sysext/core/Tests/Acceptance/Backend/Topbar/UsernameOnAvatarCest.php
typo3/sysext/core/Tests/Functional/Cache/Backend/Typo3DatabaseBackendTest.php
typo3/sysext/core/Tests/Functional/Cache/Frontend/VariableFrontendTest.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/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/Loader/YamlFileLoaderTest.php
typo3/sysext/core/Tests/Unit/Configuration/RichtextTest.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/FlashMessageRendererResolverTest.php
typo3/sysext/core/Tests/Unit/Messaging/FlashMessageServiceTest.php
typo3/sysext/core/Tests/Unit/Messaging/Renderer/BootstrapRendererTest.php
typo3/sysext/core/Tests/Unit/Messaging/Renderer/ListRendererTest.php
typo3/sysext/core/Tests/Unit/Messaging/Renderer/PlaintextRendererTest.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/core/Tests/Unit/ViewHelpers/IconForRecordViewHelperTest.php
typo3/sysext/core/Tests/Unit/ViewHelpers/IconViewHelperTest.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/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/BaseViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Be/Security/IfAuthenticatedViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Be/Security/IfHasRoleViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/CObjectViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/FlashMessagesViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Form/AbstractFormViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Form/ButtonViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Form/CheckboxViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Form/FormFieldViewHelperBaseTestcase.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Form/RadioViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Form/SelectViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Form/TextfieldViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Form/UploadViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/FormViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/BytesViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/CaseViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/CropViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/CurrencyViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/DateViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/HtmlentitiesDecodeViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/HtmlentitiesViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/Nl2brViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/NumberViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/PaddingViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/StripTagsViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Format/UrlencodeViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/ImageViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/EmailViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/ExternalViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/PageViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Link/TypolinkViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Security/IfAuthenticatedViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Security/IfHasRoleViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/TranslateViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Uri/EmailViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Uri/ExternalViewHelperTest.php
typo3/sysext/fluid/Tests/Unit/ViewHelpers/Uri/PageViewHelperTest.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/Domain/Finishers/AbstractFinisherTest.php
typo3/sysext/form/Tests/Unit/Domain/FormElements/AbstractSectionTest.php
typo3/sysext/form/Tests/Unit/Domain/Runtime/FormRuntimeTest.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/Service/TranslationServiceTest.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/install/Tests/Unit/ViewHelpers/Format/PhpErrorCodeViewHelperTest.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

diff --git a/components/testing_framework/Classes/Core/Acceptance/AcceptanceCoreEnvironment.php b/components/testing_framework/Classes/Core/Acceptance/AcceptanceCoreEnvironment.php
new file mode 100644 (file)
index 0000000..d0e40e2
--- /dev/null
@@ -0,0 +1,299 @@
+<?php
+declare(strict_types=1);
+namespace TYPO3\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 Codeception\Event\SuiteEvent;
+use Codeception\Events;
+use Codeception\Extension;
+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;
+use TYPO3\CMS\Styleguide\TcaDataGenerator\Generator;
+
+/**
+ * This codeception extension creates a full TYPO3 instance within
+ * typo3temp. Own acceptance test suites may extend from this class
+ * and change the properties. This can be used to not copy the whole
+ * bootstrapTypo3Environment() method but reuse it instead.
+ */
+class AcceptanceCoreEnvironment extends Extension
+{
+    /**
+     * Additional core extensions to load.
+     *
+     * To be used in own acceptance test suites.
+     *
+     * If a test suite needs additional core extensions, for instance as a dependency of
+     * an extension that is tested, those core extension names can be noted here and will
+     * be loaded.
+     *
+     * @var array
+     */
+    protected $coreExtensionsToLoad = [];
+
+    /**
+     * Array of test/fixture extensions paths that should be loaded for a test.
+     *
+     * To be used in own acceptance test suites.
+     *
+     * 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.
+     *
+     * To be used in own acceptance test suites.
+     *
+     * 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
+     *
+     * To be used in own acceptance test suites.
+     *
+     * @var array
+     */
+    protected $configurationToUseInTestInstance = [];
+
+    /**
+     * Array of folders that should be created inside the test instance document root.
+     *
+     * To be used in own acceptance test suites.
+     *
+     * 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 = [];
+
+    /**
+     * XML database fixtures to be loaded into database.
+     *
+     * Given paths are expected to be relative to your document root.
+     *
+     * @var array
+     */
+    protected $xmlDatabaseFixtures = [
+        'components/testing_framework/Resources/Core/Acceptance/Fixtures/be_users.xml',
+        'components/testing_framework/Resources/Core/Acceptance/Fixtures/be_sessions.xml',
+        'components/testing_framework/Resources/Core/Acceptance/Fixtures/be_groups.xml',
+        'components/testing_framework/Resources/Core/Acceptance/Fixtures/sys_category.xml',
+        'components/testing_framework/Resources/Core/Acceptance/Fixtures/tx_extensionmanager_domain_model_extension.xml',
+        'components/testing_framework/Resources/Core/Acceptance/Fixtures/tx_extensionmanager_domain_model_repository.xml',
+    ];
+
+    /**
+     * Events to listen to
+     */
+    public static $events = [
+        Events::SUITE_BEFORE => 'bootstrapTypo3Environment',
+        Events::TEST_AFTER => 'cleanupTypo3Environment'
+    ];
+
+    /**
+     * Handle SUITE_BEFORE event.
+     *
+     * Create a full standalone TYPO3 instance within typo3temp/var/tests/acceptance,
+     * create a database and create database schema.
+     *
+     * @param SuiteEvent $suiteEvent
+     * @throws Exception
+     */
+    public function bootstrapTypo3Environment(SuiteEvent $suiteEvent)
+    {
+        $testbase = new Testbase();
+        $testbase->enableDisplayErrors();
+        $testbase->defineBaseConstants();
+        $testbase->defineOriginalRootPath();
+        $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance');
+        $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient');
+
+        $instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/acceptance';
+
+        $testbase->defineTypo3ModeBe();
+        $testbase->setTypo3TestingContext();
+        $testbase->removeOldInstanceIfExists($instancePath);
+        // Basic instance directory structure
+        $testbase->createDirectory($instancePath . '/fileadmin');
+        $testbase->createDirectory($instancePath . '/typo3temp/var/transient');
+        $testbase->createDirectory($instancePath . '/typo3temp/assets');
+        $testbase->createDirectory($instancePath . '/typo3conf/ext');
+        $testbase->createDirectory($instancePath . '/uploads');
+        // Additionally requested directories
+        foreach ($this->additionalFoldersToCreate as $directory) {
+            $testbase->createDirectory($instancePath . '/' . $directory);
+        }
+        $testbase->createLastRunTextfile($instancePath);
+        $testbase->setUpInstanceCoreLinks($instancePath);
+        // ext:styleguide is always loaded
+        $testExtensionsToLoad = array_merge(
+            [ 'typo3conf/ext/styleguide' ],
+            $this->testExtensionsToLoad
+        );
+        $testbase->linkTestExtensionsToInstance($instancePath, $testExtensionsToLoad);
+        $testbase->linkPathsInTestInstance($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 . '_at';
+        $testbase->testDatabaseNameIsNotTooLong($originalDatabaseName, $localConfiguration);
+        // Set some hard coded base settings for the instance. Those could be overruled by
+        // $this->configurationToUseInTestInstance if needed again.
+        $localConfiguration['BE']['debug'] = true;
+        $localConfiguration['BE']['lockHashKeyWords'] = '';
+        $localConfiguration['BE']['installToolPassword'] = $this->getInstallToolPassword();
+        $localConfiguration['BE']['loginSecurityLevel'] = 'rsa';
+        $localConfiguration['SYS']['isInitialInstallationInProgress'] = false;
+        $localConfiguration['SYS']['isInitialDatabaseImportDone'] = true;
+        $localConfiguration['SYS']['displayErrors'] = false;
+        $localConfiguration['SYS']['debugExceptionHandler'] = '';
+        $localConfiguration['SYS']['trustedHostsPattern'] = 'localhost:8000';
+        $localConfiguration['SYS']['encryptionKey'] = 'iAmInvalid';
+        // @todo: This sql_mode should be enabled as soon as styleguide and dataHandler can cope with it
+        //$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($instancePath, $localConfiguration, $this->configurationToUseInTestInstance);
+        $defaultCoreExtensionsToLoad = [
+            'core',
+            'beuser',
+            'extbase',
+            'fluid',
+            'filelist',
+            'extensionmanager',
+            'lang',
+            'setup',
+            'rsaauth',
+            'saltedpasswords',
+            'backend',
+            'about',
+            'belog',
+            'install',
+            't3skin',
+            'frontend',
+            'recordlist',
+            'reports',
+            'sv',
+            'scheduler',
+            'tstemplate',
+        ];
+        $testbase->setUpPackageStates($instancePath, $defaultCoreExtensionsToLoad, $this->coreExtensionsToLoad, $testExtensionsToLoad);
+        $testbase->setUpBasicTypo3Bootstrap($instancePath);
+        $testbase->setUpTestDatabase($localConfiguration['DB']['Connections']['Default']['dbname'], $originalDatabaseName);
+        $testbase->loadExtensionTables();
+        $testbase->createDatabaseStructure();
+
+        // Unset a closure or phpunit kicks in with a 'serialization of \Closure is not allowed'
+        // Alternative solution:
+        // unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys']['extbase']);
+        $suite = $suiteEvent->getSuite();
+        $suite->setBackupGlobals(false);
+
+        foreach ($this->xmlDatabaseFixtures as $fixture) {
+            $testbase->importXmlDatabaseFixture(ORIGINAL_ROOT . $fixture);
+        }
+
+        // styleguide generator uses DataHandler for some parts. DataHandler needs an initialized BE user
+        // with admin right and the live workspace.
+        Bootstrap::getInstance()->initializeBackendUser();
+        $GLOBALS['BE_USER']->user['admin'] = 1;
+        $GLOBALS['BE_USER']->user['uid'] = 1;
+        $GLOBALS['BE_USER']->workspace = 0;
+        Bootstrap::getInstance()->initializeLanguageObject();
+
+        $styleguideGenerator = new Generator();
+        $styleguideGenerator->create();
+
+        // @todo: Find out why that is needed to execute the first test successfully
+        $this->cleanupTypo3Environment();
+    }
+
+    /**
+     * Method executed after each test
+     *
+     * @return void
+     */
+    public function cleanupTypo3Environment()
+    {
+        // Reset uc db field of be_user "admin" to null to reduce
+        // possible side effects between single tests.
+        GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getConnectionForTable('be_users')
+            ->update('be_users', ['uc' => null], ['uid' => 1]);
+    }
+
+    /**
+     * Set install tool password. This is either a salted password
+     * of a given typo3InstallToolPassword environment variable, or
+     * a hardcoded value that does not allow login.
+     *
+     * @return string
+     */
+    protected function getInstallToolPassword(): string
+    {
+        $password = getenv('typo3InstallToolPassword');
+        if (!empty($password)) {
+            $saltFactory = \TYPO3\CMS\Saltedpasswords\Salt\SaltFactory::getSaltingInstance(null, 'BE');
+            return $saltFactory->getHashedPassword($password);
+        } else {
+            return '$P$notnotnotnotnotnot.validvalidva';
+        }
+    }
+}
diff --git a/components/testing_framework/Classes/Core/Acceptance/Step/Backend/Admin.php b/components/testing_framework/Classes/Core/Acceptance/Step/Backend/Admin.php
new file mode 100644 (file)
index 0000000..6b92f75
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace TYPO3\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/Classes/Core/Acceptance/Step/Backend/Editor.php b/components/testing_framework/Classes/Core/Acceptance/Step/Backend/Editor.php
new file mode 100644 (file)
index 0000000..fb368c3
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace TYPO3\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/Classes/Core/Acceptance/Support/Helper/FormHandlerElementTestDataObject.php b/components/testing_framework/Classes/Core/Acceptance/Support/Helper/FormHandlerElementTestDataObject.php
new file mode 100644 (file)
index 0000000..642d751
--- /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\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/Classes/Core/Acceptance/Support/Helper/Formhandler.php b/components/testing_framework/Classes/Core/Acceptance/Support/Helper/Formhandler.php
new file mode 100644 (file)
index 0000000..4314439
--- /dev/null
@@ -0,0 +1,232 @@
+<?php
+namespace TYPO3\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/Classes/Core/Acceptance/Support/Helper/ModalDialog.php b/components/testing_framework/Classes/Core/Acceptance/Support/Helper/ModalDialog.php
new file mode 100644 (file)
index 0000000..0c59c37
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+namespace TYPO3\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/Classes/Core/Acceptance/Support/Helper/Topbar.php b/components/testing_framework/Classes/Core/Acceptance/Support/Helper/Topbar.php
new file mode 100644 (file)
index 0000000..3e8c6c3
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+namespace TYPO3\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/Classes/Core/Acceptance/Support/Page/PageTree.php b/components/testing_framework/Classes/Core/Acceptance/Support/Page/PageTree.php
new file mode 100644 (file)
index 0000000..0a16be7
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace TYPO3\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/Classes/Core/AccessibleObjectInterface.php b/components/testing_framework/Classes/Core/AccessibleObjectInterface.php
new file mode 100644 (file)
index 0000000..c030c6f
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+namespace TYPO3\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\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/Classes/Core/BaseTestCase.php b/components/testing_framework/Classes/Core/BaseTestCase.php
new file mode 100644 (file)
index 0000000..86299d0
--- /dev/null
@@ -0,0 +1,300 @@
+<?php
+namespace TYPO3\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\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\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\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/Classes/Core/Exception.php b/components/testing_framework/Classes/Core/Exception.php
new file mode 100644 (file)
index 0000000..b7b8698
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace TYPO3\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/Classes/Core/FileStreamWrapper.php b/components/testing_framework/Classes/Core/FileStreamWrapper.php
new file mode 100644 (file)
index 0000000..34cf739
--- /dev/null
@@ -0,0 +1,610 @@
+<?php
+namespace TYPO3\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/Classes/Core/Functional/Framework/Constraint/RequestSection/AbstractRecordConstraint.php b/components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/AbstractRecordConstraint.php
new file mode 100644 (file)
index 0000000..332c54c
--- /dev/null
@@ -0,0 +1,175 @@
+<?php
+namespace TYPO3\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\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/Classes/Core/Functional/Framework/Constraint/RequestSection/AbstractStructureRecordConstraint.php b/components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/AbstractStructureRecordConstraint.php
new file mode 100644 (file)
index 0000000..a98e763
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace TYPO3\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/Classes/Core/Functional/Framework/Constraint/RequestSection/DoesNotHaveRecordConstraint.php b/components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/DoesNotHaveRecordConstraint.php
new file mode 100644 (file)
index 0000000..c11103d
--- /dev/null
@@ -0,0 +1,57 @@
+<?php
+namespace TYPO3\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\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/Classes/Core/Functional/Framework/Constraint/RequestSection/HasRecordConstraint.php b/components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/HasRecordConstraint.php
new file mode 100644 (file)
index 0000000..6773f45
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+namespace TYPO3\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\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/Classes/Core/Functional/Framework/Constraint/RequestSection/StructureDoesNotHaveRecordConstraint.php b/components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/StructureDoesNotHaveRecordConstraint.php
new file mode 100644 (file)
index 0000000..16352a3
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+namespace TYPO3\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\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/Classes/Core/Functional/Framework/Constraint/RequestSection/StructureHasRecordConstraint.php b/components/testing_framework/Classes/Core/Functional/Framework/Constraint/RequestSection/StructureHasRecordConstraint.php
new file mode 100644 (file)
index 0000000..e7e4057
--- /dev/null
@@ -0,0 +1,87 @@
+<?php
+namespace TYPO3\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\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/Classes/Core/Functional/Framework/Frontend/Collector.php b/components/testing_framework/Classes/Core/Functional/Framework/Frontend/Collector.php
new file mode 100644 (file)
index 0000000..21d29ed
--- /dev/null
@@ -0,0 +1,201 @@
+<?php
+namespace TYPO3\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\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/Classes/Core/Functional/Framework/Frontend/Hook/BackendUserHandler.php b/components/testing_framework/Classes/Core/Functional/Framework/Frontend/Hook/BackendUserHandler.php
new file mode 100644 (file)
index 0000000..cfb177d
--- /dev/null
@@ -0,0 +1,59 @@
+<?php
+namespace TYPO3\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/Classes/Core/Functional/Framework/Frontend/Hook/FrontendUserHandler.php b/components/testing_framework/Classes/Core/Functional/Framework/Frontend/Hook/FrontendUserHandler.php
new file mode 100644 (file)
index 0000000..4a312c7
--- /dev/null
@@ -0,0 +1,47 @@
+<?php
+namespace TYPO3\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/Classes/Core/Functional/Framework/Frontend/Parser.php b/components/testing_framework/Classes/Core/Functional/Framework/Frontend/Parser.php
new file mode 100644 (file)
index 0000000..e235e15
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+namespace TYPO3\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/Classes/Core/Functional/Framework/Frontend/Renderer.php b/components/testing_framework/Classes/Core/Functional/Framework/Frontend/Renderer.php
new file mode 100644 (file)
index 0000000..7dc3a3f
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+namespace TYPO3\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\Components\TestingFramework\Core\Functional\Framework\Frontend\Parser::class
+        );
+    }
+}
diff --git a/components/testing_framework/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php b/components/testing_framework/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php
new file mode 100644 (file)
index 0000000..e2b0eea
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+namespace TYPO3\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/Classes/Core/Functional/Framework/Frontend/Response.php b/components/testing_framework/Classes/Core/Functional/Framework/Frontend/Response.php
new file mode 100644 (file)
index 0000000..e6d4e1a
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+namespace TYPO3\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/Classes/Core/Functional/Framework/Frontend/ResponseContent.php b/components/testing_framework/Classes/Core/Functional/Framework/Frontend/ResponseContent.php
new file mode 100644 (file)
index 0000000..44edf23
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+namespace TYPO3\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/Classes/Core/Functional/Framework/Frontend/ResponseSection.php b/components/testing_framework/Classes/Core/Functional/Framework/Frontend/ResponseSection.php
new file mode 100644 (file)
index 0000000..4a9e921
--- /dev/null
@@ -0,0 +1,139 @@
+<?php
+namespace TYPO3\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/Classes/Core/Functional/FunctionalTestCase.php b/components/testing_framework/Classes/Core/Functional/FunctionalTestCase.php
new file mode 100644 (file)
index 0000000..8bcf126
--- /dev/null
@@ -0,0 +1,430 @@
+<?php
+namespace TYPO3\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\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/Resources/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;
+        }
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_template');
+        $connection->delete('sys_template', ['pid' => $pageId]);
+        $connection->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/Classes/Core/Testbase.php b/components/testing_framework/Classes/Core/Testbase.php
new file mode 100644 (file)
index 0000000..97d7d62
--- /dev/null
@@ -0,0 +1,697 @@
+<?php
+namespace TYPO3\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) {
+            $absoluteExtensionPath = ORIGINAL_ROOT . $extensionPath;
+            if (!is_dir($absoluteExtensionPath)) {
+                throw new Exception(
+                    'Test extension path ' . $absoluteExtensionPath . ' not found',
+                    1376745645
+                );
+            }
+            $destinationPath = $instancePath . '/typo3conf/ext/' . basename($absoluteExtensionPath);
+            $success = symlink($absoluteExtensionPath, $destinationPath);
+            if (!$success) {
+                throw new Exception(
+                    'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
+                    1376657142
+                );
+            }
+        }
+    }
+
+    /**
+     * Link paths inside the test instance, e.g. from a fixture fileadmin subfolder to the
+     * test instance fileadmin folder.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $pathsToLinkInTestInstance Contains paths as array of source => destination in key => value pairs of folders relative to test instance root
+     * @throws Exception if a source path could not be found and on failing creating the symlink
+     * @return void
+     */
+    public function linkPathsInTestInstance($instancePath, array $pathsToLinkInTestInstance)
+    {
+        foreach ($pathsToLinkInTestInstance as $sourcePathToLinkInTestInstance => $destinationPathToLinkInTestInstance) {
+            $sourcePath = $instancePath . '/' . ltrim($sourcePathToLinkInTestInstance, '/');
+            if (!file_exists($sourcePath)) {
+                throw new Exception(
+                    'Path ' . $sourcePath . ' not found',
+                    1476109221
+                );
+            }
+            $destinationPath = $instancePath . '/' . ltrim($destinationPathToLinkInTestInstance, '/');
+            $success = symlink($sourcePath, $destinationPath);
+            if (!$success) {
+                throw new Exception(
+                    'Can not link the path ' . $sourcePath . ' to ' . $destinationPath,
+                    1389969623
+                );
+            }
+        }
+    }
+
+    /**
+     * Database settings for functional and acceptance tests can be either set by
+     * environment variables (recommended), or from an existing LocalConfiguration as fallback.
+     * The method fetches these.
+     *
+     * An unique name will be added to the database name later.
+     *
+     * @throws Exception
+     * @return array [DB][host], [DB][username], ...
+     */
+    public function getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration()
+    {
+        $databaseName = trim(getenv('typo3DatabaseName'));
+        $databaseHost = trim(getenv('typo3DatabaseHost'));
+        $databaseUsername = trim(getenv('typo3DatabaseUsername'));
+        $databasePassword = getenv('typo3DatabasePassword');
+        $databasePasswordTrimmed = trim($databasePassword);
+        $databasePort = trim(getenv('typo3DatabasePort'));
+        $databaseSocket = trim(getenv('typo3DatabaseSocket'));
+        $databaseDriver = trim(getenv('typo3DatabaseDriver'));
+        if ($databaseName || $databaseHost || $databaseUsername || $databasePassword || $databasePort || $databaseSocket) {
+            // Try to get database credentials from environment variables first
+            $originalConfigurationArray = [
+                'DB' => [
+                    'Connections' => [
+                        'Default' => [
+                            'driver' => 'mysqli'
+                        ],
+                    ],
+                ],
+            ];
+            if ($databaseName) {
+                $originalConfigurationArray['DB']['Connections']['Default']['dbname'] = $databaseName;
+            }
+            if ($databaseHost) {
+                $originalConfigurationArray['DB']['Connections']['Default']['host'] = $databaseHost;
+            }
+            if ($databaseUsername) {
+                $originalConfigurationArray['DB']['Connections']['Default']['user'] = $databaseUsername;
+            }
+            if ($databasePassword !== false) {
+                $originalConfigurationArray['DB']['Connections']['Default']['password'] = $databasePasswordTrimmed;
+            }
+            if ($databasePort) {
+                $originalConfigurationArray['DB']['Connections']['Default']['port'] = $databasePort;
+            }
+            if ($databaseSocket) {
+                $originalConfigurationArray['DB']['Connections']['Default']['unix_socket'] = $databaseSocket;
+            }
+            if ($databaseDriver) {
+                $originalConfigurationArray['DB']['Connections']['Default']['driver'] = $databaseDriver;
+            }
+        } elseif (file_exists(ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php')) {
+            // See if a LocalConfiguration file exists in "parent" instance to get db credentials from
+            $originalConfigurationArray = require ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php';
+        } else {
+            throw new Exception(
+                'Database credentials for tests are neither set through environment'
+                . ' variables, and can not be found in an existing LocalConfiguration file',
+                1397406356
+            );
+        }
+        return $originalConfigurationArray['DB'];
+    }
+
+    /**
+     * Maximum length of database names is 64 chars in mysql. Test this is not exceeded
+     * after a suffix has been added.
+     *
+     * @param string $originalDatabaseName Base name of the database
+     * @param array $configuration "LocalConfiguration" array with DB settings
+     * @throws Exception
+     */
+    public function testDatabaseNameIsNotTooLong($originalDatabaseName, array $configuration)
+    {
+        // Maximum database name length for mysql is 64 characters
+        if (strlen($configuration['DB']['Connections']['Default']['dbname']) > 64) {
+            $suffixLength = strlen($configuration['DB']['Connections']['Default']['dbname']) - strlen($originalDatabaseName);
+            $maximumOriginalDatabaseName = 64 - $suffixLength;
+            throw new Exception(
+                'The name of the database that is used for the functional test (' . $originalDatabaseName . ')' .
+                ' exceeds the maximum length of 64 character allowed by MySQL. You have to shorten your' .
+                ' original database name to ' . $maximumOriginalDatabaseName . ' characters',
+                1377600104
+            );
+        }
+    }
+
+    /**
+     * Create LocalConfiguration.php file of the test instance.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $configuration Base configuration array
+     * @param array $overruleConfiguration Overrule factory and base configuration
+     * @throws Exception
+     * @return void
+     */
+    public function setUpLocalConfiguration($instancePath, array $configuration, array $overruleConfiguration)
+    {
+        // Base of final LocalConfiguration is core factory configuration
+        $finalConfigurationArray = require ORIGINAL_ROOT . 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
+        ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $configuration);
+        ArrayUtility::mergeRecursiveWithOverrule($finalConfigurationArray, $overruleConfiguration);
+        $result = $this->writeFile(
+            $instancePath . '/typo3conf/LocalConfiguration.php',
+            '<?php' . chr(10) .
+            'return ' .
+            ArrayUtility::arrayExport(
+                $finalConfigurationArray
+            ) .
+            ';'
+        );
+        if (!$result) {
+            throw new Exception('Can not write local configuration', 1376657277);
+        }
+    }
+
+    /**
+     * Compile typo3conf/PackageStates.php containing default packages like core,
+     * a test specific list of additional core extensions, and a list of
+     * test extensions.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @param array $defaultCoreExtensionsToLoad Default list of core extensions to load
+     * @param array $additionalCoreExtensionsToLoad Additional core extensions to load
+     * @param array $testExtensionPaths Paths to extensions relative to document root
+     * @throws Exception
+     */
+    public function setUpPackageStates(
+        $instancePath,
+        array $defaultCoreExtensionsToLoad,
+        array $additionalCoreExtensionsToLoad,
+        array $testExtensionPaths
+    ) {
+        $packageStates = [
+            'packages' => [],
+            'version' => 5,
+        ];
+
+        // Register default list of extensions and set active
+        foreach ($defaultCoreExtensionsToLoad as $extensionName) {
+            $packageStates['packages'][$extensionName] = [
+                'packagePath' => 'typo3/sysext/' . $extensionName . '/'
+            ];
+        }
+
+        // Register additional core extensions and set active
+        foreach ($additionalCoreExtensionsToLoad as $extensionName) {
+            $packageStates['packages'][$extensionName] = [
+                'packagePath' => 'typo3/sysext/' . $extensionName . '/'
+            ];
+        }
+
+        // Activate test extensions that have been symlinked before
+        foreach ($testExtensionPaths as $extensionPath) {
+            $extensionName = basename($extensionPath);
+            $packageStates['packages'][$extensionName] = [
+                'packagePath' => 'typo3conf/ext/' . $extensionName . '/'
+            ];
+        }
+
+        $result = $this->writeFile(
+            $instancePath . '/typo3conf/PackageStates.php',
+            '<?php' . chr(10) .
+            'return ' .
+            ArrayUtility::arrayExport(
+                $packageStates
+            ) .
+            ';'
+        );
+
+        if (!$result) {
+            throw new Exception('Can not write PackageStates', 1381612729);
+        }
+    }
+
+    /**
+     * Create a low level connection to dbms, without selecting the target database.
+     * Drop existing database if it exists and create a new one.
+     *
+     * @param string $databaseName Database name of this test instance
+     * @param string $originalDatabaseName Original database name before suffix was added
+     * @throws \TYPO3\Components\TestingFramework\Core\Exception
+     * @return void
+     */
+    public function setUpTestDatabase($databaseName, $originalDatabaseName)
+    {
+        Bootstrap::getInstance()->initializeTypo3DbGlobal();
+
+        // Drop database if exists. Directly using the Doctrine DriverManager to
+        // work around connection caching in ConnectionPool
+        $connectionParameters = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default'];
+        unset($connectionParameters['dbname']);
+        $schemaManager = DriverManager::getConnection($connectionParameters)->getSchemaManager();
+
+        if (in_array($databaseName, $schemaManager->listDatabases(), true)) {
+            $schemaManager->dropDatabase($databaseName);
+        }
+
+        try {
+            $schemaManager->createDatabase($databaseName);
+        } catch (DBALException $e) {
+            $user = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['user'];
+            $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['Connections']['Default']['host'];
+            throw new Exception(
+                'Unable to create database with name ' . $databaseName . '. This is probably a permission problem.'
+                . ' For this instance this could be fixed executing:'
+                . ' GRANT ALL ON `' . $originalDatabaseName . '_%`.* TO `' . $user . '`@`' . $host . '`;'
+                . ' Original message thrown by database layer: ' . $e->getMessage(),
+                1376579070
+            );
+        }
+    }
+
+    /**
+     * Bootstrap basic TYPO3. This bootstraps TYPO3 far enough to initialize database afterwards.
+     * For functional and acceptance tests.
+     *
+     * @param string $instancePath Absolute path to test instance
+     * @return void
+     */
+    public function setUpBasicTypo3Bootstrap($instancePath)
+    {
+        $_SERVER['PWD'] = $instancePath;
+        $_SERVER['argv'][0] = 'index.php';
+
+        $classLoader = require rtrim(realpath($instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php';
+        Bootstrap::getInstance()
+            ->initializeClassLoader($classLoader)
+            ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
+            ->baseSetup()
+            ->loadConfigurationAndInitialize(true)
+            ->loadTypo3LoadedExtAndExtLocalconf(true)
+            ->setFinalCachingFrameworkCacheConfiguration()
+            ->defineLoggingAndExceptionConstants()
+            ->unsetReservedGlobalVariables();
+    }
+
+    /**
+     * Truncate all tables.
+     * For functional and acceptance tests.
+     *
+     * @throws Exception
+     * @return void
+     */
+    public function initializeTestDatabaseAndTruncateTables()
+    {
+        Bootstrap::getInstance()->initializeTypo3DbGlobal();
+
+        $connection = GeneralUtility::makeInstance(ConnectionPool::class)
+            ->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
+        $schemaManager = $connection->getSchemaManager();
+
+        foreach ($schemaManager->listTables() as $table) {
+            $connection->truncate($table->getName());
+        }
+    }
+
+    /**
+     * Load ext_tables.php files.
+     * For functional and acceptance tests.
+     *
+     * @return void
+     */
+    public function loadExtensionTables()
+    {
+        Bootstrap::getInstance()->loadExtensionTables();
+    }
+
+    /**
+     * Create tables and import static rows.
+     * For functional and acceptance tests.
+     *
+     * @return void
+     */
+    public function createDatabaseStructure()
+    {
+        $schemaMigrationService = GeneralUtility::makeInstance(SchemaMigrator::class);
+        $sqlReader = GeneralUtility::makeInstance(SqlReader::class);
+        $sqlCode = $sqlReader->getTablesDefinitionString(true);
+
+        $createTableStatements = $sqlReader->getCreateTableStatementArray($sqlCode);
+
+        $schemaMigrationService->install($createTableStatements);
+
+        $insertStatements = $sqlReader->getInsertStatementArray($sqlCode);
+        $schemaMigrationService->importStaticData($insertStatements);
+    }
+
+    /**
+     * Imports a data set represented as XML into the test database,
+     *
+     * @param string $path Absolute path to the XML file containing the data set to load
+     * @return void
+     * @throws Exception
+     */
+    public function importXmlDatabaseFixture($path)
+    {
+        if (!is_file($path)) {
+            throw new Exception(
+                'Fixture file ' . $path . ' not found',
+                1376746261
+            );
+        }
+
+        $fileContent = file_get_contents($path);
+        // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
+        $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
+        $xml = simplexml_load_string($fileContent);
+        libxml_disable_entity_loader($previousValueOfEntityLoader);
+        $foreignKeys = [];
+
+        /** @var $table \SimpleXMLElement */
+        foreach ($xml->children() as $table) {
+            $insertArray = [];
+
+            /** @var $column \SimpleXMLElement */
+            foreach ($table->children() as $column) {
+                $columnName = $column->getName();
+                $columnValue = null;
+
+                if (isset($column['ref'])) {
+                    list($tableName, $elementId) = explode('#', $column['ref']);
+                    $columnValue = $foreignKeys[$tableName][$elementId];
+                } elseif (isset($column['is-NULL']) && ($column['is-NULL'] === 'yes')) {
+                   &nb