[BUGFIX] Show loading label while scanning broken extensions
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Tests / FunctionalTestCase.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
18 use TYPO3\CMS\Core\Cache\Backend\NullBackend;
19 use TYPO3\CMS\Core\Core\Bootstrap;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Tests\Functional\Framework\Frontend\Response;
22 use TYPO3\CMS\Core\Utility\GeneralUtility;
23
24 /**
25 * Base test case class for functional tests, all TYPO3 CMS
26 * functional tests should extend from this class!
27 *
28 * If functional tests need additional setUp() and tearDown() code,
29 * they *must* call parent::setUp() and parent::tearDown() to properly
30 * set up and destroy the test system.
31 *
32 * The functional test system creates a full new TYPO3 CMS instance
33 * within typo3temp/ of the base system and the bootstraps this TYPO3 instance.
34 * This abstract class takes care of creating this instance with its
35 * folder structure and a LocalConfiguration, creates an own database
36 * for each test run and imports tables of loaded extensions.
37 *
38 * Functional tests must be run standalone (calling native phpunit
39 * directly) and can not be executed by eg. the ext:phpunit backend module.
40 * Additionally, the script must be called from the document root
41 * of the instance, otherwise path calculation is not successfully.
42 *
43 * Call whole functional test suite, example:
44 * - cd /var/www/t3master/foo # Document root of CMS instance, here is index.php of frontend
45 * - typo3/../bin/phpunit -c typo3/sysext/core/Build/FunctionalTests.xml
46 *
47 * Call single test case, example:
48 * - cd /var/www/t3master/foo # Document root of CMS instance, here is index.php of frontend
49 * - typo3/../bin/phpunit \
50 * --process-isolation \
51 * --bootstrap typo3/sysext/core/Build/FunctionalTestsBootstrap.php \
52 * typo3/sysext/core/Tests/Functional/DataHandling/DataHandlerTest.php
53 */
54 abstract class FunctionalTestCase extends BaseTestCase
55 {
56 /**
57 * An unique identifier for this test case. Location of the test
58 * instance and database name depend on this. Calculated early in setUp()
59 *
60 * @var string
61 */
62 protected $identifier;
63
64 /**
65 * Absolute path to test instance document root. Depends on $identifier.
66 * Calculated early in setUp()
67 *
68 * @var string
69 */
70 protected $instancePath;
71
72 /**
73 * Core extensions to load.
74 *
75 * If the test case needs additional core extensions as requirement,
76 * they can be noted here and will be added to LocalConfiguration
77 * extension list and ext_tables.sql of those extensions will be applied.
78 *
79 * This property will stay empty in this abstract, so it is possible
80 * to just overwrite it in extending classes. Extensions noted here will
81 * be loaded for every test of a test case and it is not possible to change
82 * the list of loaded extensions between single tests of a test case.
83 *
84 * A default list of core extensions is always loaded.
85 *
86 * @see FunctionalTestCaseUtility $defaultActivatedCoreExtensions
87 * @var array
88 */
89 protected $coreExtensionsToLoad = [];
90
91 /**
92 * Array of test/fixture extensions paths that should be loaded for a test.
93 *
94 * This property will stay empty in this abstract, so it is possible
95 * to just overwrite it in extending classes. Extensions noted here will
96 * be loaded for every test of a test case and it is not possible to change
97 * the list of loaded extensions between single tests of a test case.
98 *
99 * Given path is expected to be relative to your document root, example:
100 *
101 * array(
102 * 'typo3conf/ext/some_extension/Tests/Functional/Fixtures/Extensions/test_extension',
103 * 'typo3conf/ext/base_extension',
104 * );
105 *
106 * Extensions in this array are linked to the test instance, loaded
107 * and their ext_tables.sql will be applied.
108 *
109 * @var array
110 */
111 protected $testExtensionsToLoad = [];
112
113 /**
114 * Array of test/fixture folder or file paths that should be linked for a test.
115 *
116 * This property will stay empty in this abstract, so it is possible
117 * to just overwrite it in extending classes. Path noted here will
118 * be linked for every test of a test case and it is not possible to change
119 * the list of folders between single tests of a test case.
120 *
121 * array(
122 * 'link-source' => 'link-destination'
123 * );
124 *
125 * Given paths are expected to be relative to the test instance root.
126 * The array keys are the source paths and the array values are the destination
127 * paths, example:
128 *
129 * array(
130 * 'typo3/sysext/impext/Tests/Functional/Fixtures/Folders/fileadmin/user_upload' =>
131 * 'fileadmin/user_upload',
132 * 'typo3conf/ext/my_own_ext/Tests/Functional/Fixtures/Folders/uploads/tx_myownext' =>
133 * 'uploads/tx_myownext'
134 * );
135 *
136 * To be able to link from my_own_ext the extension path needs also to be registered in
137 * property $testExtensionsToLoad
138 *
139 * @var array
140 */
141 protected $pathsToLinkInTestInstance = [];
142
143 /**
144 * This configuration array is merged with TYPO3_CONF_VARS
145 * that are set in default configuration and factory configuration
146 *
147 * @var array
148 */
149 protected $configurationToUseInTestInstance = [];
150
151 /**
152 * Array of folders that should be created inside the test instance document root.
153 *
154 * This property will stay empty in this abstract, so it is possible
155 * to just overwrite it in extending classes. Path noted here will
156 * be linked for every test of a test case and it is not possible to change
157 * the list of folders between single tests of a test case.
158 *
159 * Per default the following folder are created
160 * /fileadmin
161 * /typo3temp
162 * /typo3conf
163 * /typo3conf/ext
164 * /uploads
165 *
166 * To create additional folders add the paths to this array. Given paths are expected to be
167 * relative to the test instance root and have to begin with a slash. Example:
168 *
169 * array(
170 * 'fileadmin/user_upload'
171 * );
172 *
173 * @var array
174 */
175 protected $additionalFoldersToCreate = [];
176
177 /**
178 * The fixture which is used when initializing a backend user
179 *
180 * @var string
181 */
182 protected $backendUserFixture = 'typo3/sysext/core/Tests/Functional/Fixtures/be_users.xml';
183
184 /**
185 * Set up creates a test instance and database.
186 *
187 * This method should be called with parent::setUp() in your test cases!
188 *
189 * @return void
190 */
191 protected function setUp()
192 {
193 if (!defined('ORIGINAL_ROOT')) {
194 $this->markTestSkipped('Functional tests must be called through phpunit on CLI');
195 }
196
197 // Use a 7 char long hash of class name as identifier
198 $this->identifier = substr(sha1(get_class($this)), 0, 7);
199 $this->instancePath = ORIGINAL_ROOT . 'typo3temp/var/tests/functional-' . $this->identifier;
200
201 $testbase = new Testbase();
202 $testbase->defineTypo3ModeBe();
203 $testbase->setTypo3TestingContext();
204 if ($testbase->recentTestInstanceExists($this->instancePath)) {
205 // Reusing an existing instance. This typically happens for the second, third, ... test
206 // in a test case, so environment is set up only once per test case.
207 $testbase->setUpBasicTypo3Bootstrap($this->instancePath);
208 $testbase->initializeTestDatabaseAndTruncateTables();
209 $testbase->loadExtensionTables();
210 } else {
211 $testbase->removeOldInstanceIfExists($this->instancePath);
212 // Basic instance directory structure
213 $testbase->createDirectory($this->instancePath . '/fileadmin');
214 $testbase->createDirectory($this->instancePath . '/typo3temp/var/transient');
215 $testbase->createDirectory($this->instancePath . '/typo3temp/assets');
216 $testbase->createDirectory($this->instancePath . '/typo3conf/ext');
217 $testbase->createDirectory($this->instancePath . '/uploads');
218 // Additionally requested directories
219 foreach ($this->additionalFoldersToCreate as $directory) {
220 $testbase->createDirectory($this->instancePath . '/' . $directory);
221 }
222 $testbase->createLastRunTextfile($this->instancePath);
223 $testbase->setUpInstanceCoreLinks($this->instancePath);
224 $testbase->linkTestExtensionsToInstance($this->instancePath, $this->testExtensionsToLoad);
225 $testbase->linkPathsInTestInstance($this->instancePath, $this->pathsToLinkInTestInstance);
226 $localConfiguration['DB'] = $testbase->getOriginalDatabaseSettingsFromEnvironmentOrLocalConfiguration();
227 $originalDatabaseName = $localConfiguration['DB']['Connections']['Default']['dbname'];
228 // Append the unique identifier to the base database name to end up with a single database per test case
229 $localConfiguration['DB']['Connections']['Default']['dbname'] = $originalDatabaseName . '_ft' . $this->identifier;
230 $testbase->testDatabaseNameIsNotTooLong($originalDatabaseName, $localConfiguration);
231 // Set some hard coded base settings for the instance. Those could be overruled by
232 // $this->configurationToUseInTestInstance if needed again.
233 $localConfiguration['SYS']['isInitialInstallationInProgress'] = false;
234 $localConfiguration['SYS']['isInitialDatabaseImportDone'] = true;
235 $localConfiguration['SYS']['displayErrors'] = '1';
236 $localConfiguration['SYS']['debugExceptionHandler'] = '';
237 $localConfiguration['SYS']['trustedHostsPattern'] = '.*';
238 // @todo: This should be moved over to DB/Connections/Default/initCommands
239 $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\';';
240 $localConfiguration['SYS']['caching']['cacheConfigurations']['extbase_object']['backend'] = NullBackend::class;
241 $testbase->setUpLocalConfiguration($this->instancePath, $localConfiguration, $this->configurationToUseInTestInstance);
242 $defaultCoreExtensionsToLoad = [
243 'core',
244 'backend',
245 'frontend',
246 'lang',
247 'extbase',
248 'install',
249 ];
250 $testbase->setUpPackageStates($this->instancePath, $defaultCoreExtensionsToLoad, $this->coreExtensionsToLoad, $this->testExtensionsToLoad);
251 $testbase->setUpBasicTypo3Bootstrap($this->instancePath);
252 $testbase->setUpTestDatabase($localConfiguration['DB']['Connections']['Default']['dbname'], $originalDatabaseName);
253 $testbase->loadExtensionTables();
254 $testbase->createDatabaseStructure();
255 }
256 }
257
258 /**
259 * Get DatabaseConnection instance - $GLOBALS['TYPO3_DB']
260 *
261 * This method should be used instead of direct access to
262 * $GLOBALS['TYPO3_DB'] for easy IDE auto completion.
263 *
264 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
265 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
266 */
267 protected function getDatabaseConnection()
268 {
269 GeneralUtility::logDeprecatedFunction();
270 return $GLOBALS['TYPO3_DB'];
271 }
272
273 /**
274 * @return ConnectionPool
275 */
276 protected function getConnectionPool()
277 {
278 return GeneralUtility::makeInstance(ConnectionPool::class);
279 }
280
281 /**
282 * Initialize backend user
283 *
284 * @param int $userUid uid of the user we want to initialize. This user must exist in the fixture file
285 * @return BackendUserAuthentication
286 * @throws Exception
287 */
288 protected function setUpBackendUserFromFixture($userUid)
289 {
290 $this->importDataSet(ORIGINAL_ROOT . $this->backendUserFixture);
291
292 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('be_users');
293 $queryBuilder->getRestrictions()->removeAll();
294
295 $userRow = $queryBuilder->select('*')
296 ->from('be_users')
297 ->where($queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($userUid, \PDO::PARAM_INT)))
298 ->execute()
299 ->fetch();
300
301 /** @var $backendUser BackendUserAuthentication */
302 $backendUser = GeneralUtility::makeInstance(BackendUserAuthentication::class);
303 $sessionId = $backendUser->createSessionId();
304 $_COOKIE['be_typo_user'] = $sessionId;
305 $backendUser->id = $sessionId;
306 $backendUser->sendNoCacheHeaders = false;
307 $backendUser->dontSetCookie = true;
308 $backendUser->createUserSession($userRow);
309
310 $GLOBALS['BE_USER'] = $backendUser;
311 $GLOBALS['BE_USER']->start();
312 if (!is_array($GLOBALS['BE_USER']->user) || !$GLOBALS['BE_USER']->user['uid']) {
313 throw new Exception(
314 'Can not initialize backend user',
315 1377095807
316 );
317 }
318 $GLOBALS['BE_USER']->backendCheckLogin();
319
320 return $backendUser;
321 }
322
323 /**
324 * Imports a data set represented as XML into the test database,
325 *
326 * @param string $path Absolute path to the XML file containing the data set to load
327 * @return void
328 * @throws Exception
329 */
330 protected function importDataSet($path)
331 {
332 $testbase = new Testbase();
333 $testbase->importXmlDatabaseFixture($path);
334 }
335
336 /**
337 * @param int $pageId
338 * @param array $typoScriptFiles
339 */
340 protected function setUpFrontendRootPage($pageId, array $typoScriptFiles = [])
341 {
342 $pageId = (int)$pageId;
343
344 $connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('pages');
345 $page = $connection->select(['*'], 'pages', ['uid' => $pageId])->fetch();
346
347 if (empty($page)) {
348 $this->fail('Cannot set up frontend root page "' . $pageId . '"');
349 }
350
351 $connection->update(
352 'pages',
353 ['is_siteroot' => 1],
354 ['uid' => $pageId]
355 );
356
357 $templateFields = [
358 'pid' => $pageId,
359 'title' => '',
360 'config' => '',
361 'clear' => 3,
362 'root' => 1,
363 ];
364
365 foreach ($typoScriptFiles as $typoScriptFile) {
366 $templateFields['config'] .= '<INCLUDE_TYPOSCRIPT: source="FILE:' . $typoScriptFile . '">' . LF;
367 }
368
369 GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('sys_template')->insert(
370 'sys_template',
371 $templateFields
372 );
373 }
374
375 /**
376 * @param int $pageId
377 * @param int $languageId
378 * @param int $backendUserId
379 * @param int $workspaceId
380 * @param bool $failOnFailure
381 * @param int $frontendUserId
382 * @return Response
383 */
384 protected function getFrontendResponse($pageId, $languageId = 0, $backendUserId = 0, $workspaceId = 0, $failOnFailure = true, $frontendUserId = 0)
385 {
386 $pageId = (int)$pageId;
387 $languageId = (int)$languageId;
388
389 $additionalParameter = '';
390
391 if (!empty($frontendUserId)) {
392 $additionalParameter .= '&frontendUserId=' . (int)$frontendUserId;
393 }
394 if (!empty($backendUserId)) {
395 $additionalParameter .= '&backendUserId=' . (int)$backendUserId;
396 }
397 if (!empty($workspaceId)) {
398 $additionalParameter .= '&workspaceId=' . (int)$workspaceId;
399 }
400
401 $arguments = [
402 'documentRoot' => $this->instancePath,
403 'requestUrl' => 'http://localhost/?id=' . $pageId . '&L=' . $languageId . $additionalParameter,
404 ];
405
406 $template = new \Text_Template(ORIGINAL_ROOT . 'typo3/sysext/core/Tests/Functional/Fixtures/Frontend/request.tpl');
407 $template->setVar(
408 [
409 'arguments' => var_export($arguments, true),
410 'originalRoot' => ORIGINAL_ROOT,
411 ]
412 );
413
414 $php = \PHPUnit_Util_PHP::factory();
415 $response = $php->runJob($template->render());
416 $result = json_decode($response['stdout'], true);
417
418 if ($result === null) {
419 $this->fail('Frontend Response is empty');
420 }
421
422 if ($failOnFailure && $result['status'] === Response::STATUS_Failure) {
423 $this->fail('Frontend Response has failure:' . LF . $result['error']);
424 }
425
426 $response = new Response($result['status'], $result['content'], $result['error']);
427 return $response;
428 }
429 }