1<?php
2
3/**
4 * @file
5 * Provides testing functionality.
6 */
7
8use Drupal\Component\Uuid\Php;
9use Drupal\Core\Asset\AttachedAssets;
10use Drupal\Core\Asset\AttachedAssetsInterface;
11use Drupal\Core\Database\Database;
12use Drupal\Core\File\Exception\FileException;
13use Drupal\Core\File\FileSystemInterface;
14use Drupal\Core\Render\Element;
15use Drupal\Core\Routing\RouteMatchInterface;
16use Drupal\Core\StreamWrapper\PublicStream;
17use Drupal\Core\Test\JUnitConverter;
18use Drupal\Core\Test\PhpUnitTestRunner;
19use Drupal\Core\Test\TestDatabase;
20use Drupal\Core\Url;
21use Drupal\simpletest\Form\SimpletestResultsForm;
22use Drupal\simpletest\TestDiscovery;
23use PHPUnit\Framework\TestCase;
24
25/**
26 * Implements hook_help().
27 */
28function simpletest_help($route_name, RouteMatchInterface $route_match) {
29  switch ($route_name) {
30    case 'help.page.simpletest':
31      $output = '';
32      $output .= '<h3>' . t('About') . '</h3>';
33      $output .= '<p>' . t('The Testing module provides a framework for running automated tests. It can be used to verify a working state of Drupal before and after any code changes, or as a means for developers to write and execute tests for their modules. For more information, see the <a href=":simpletest">online documentation for the Testing module</a>.', [':simpletest' => 'https://www.drupal.org/documentation/modules/simpletest']) . '</p>';
34      $output .= '<h3>' . t('Uses') . '</h3>';
35      $output .= '<dl>';
36      $output .= '<dt>' . t('Running tests') . '</dt>';
37      $output .= '<dd><p>' . t('Visit the <a href=":admin-simpletest">Testing page</a> to display a list of available tests. For comprehensive testing, select <em>all</em> tests, or individually select tests for more targeted testing. Note that it might take several minutes for all tests to complete.', [':admin-simpletest' => Url::fromRoute('simpletest.test_form')->toString()]) . '</p>';
38      $output .= '<p>' . t('After the tests run, a message will be displayed next to each test group indicating whether tests within it passed, failed, or had exceptions. A pass means that the test returned the expected results, while fail means that it did not. An exception normally indicates an error outside of the test, such as a PHP warning or notice. If there were failures or exceptions, the results will be expanded to show details, and the tests that had failures or exceptions will be indicated in red or pink rows. You can then use these results to refine your code and tests, until all tests pass.') . '</p></dd>';
39      $output .= '</dl>';
40      return $output;
41
42    case 'simpletest.test_form':
43      $output = t('Select the test(s) or test group(s) you would like to run, and click <em>Run tests</em>.');
44      return $output;
45  }
46}
47
48/**
49 * Implements hook_theme().
50 */
51function simpletest_theme() {
52  return [
53    'simpletest_result_summary' => [
54      'variables' => ['label' => NULL, 'items' => [], 'pass' => 0, 'fail' => 0, 'exception' => 0, 'debug' => 0],
55    ],
56  ];
57}
58
59/**
60 * Implements hook_js_alter().
61 */
62function simpletest_js_alter(&$javascript, AttachedAssetsInterface $assets) {
63  // Since SimpleTest is a special use case for the table select, stick the
64  // SimpleTest JavaScript above the table select.
65  $simpletest = drupal_get_path('module', 'simpletest') . '/simpletest.js';
66  if (array_key_exists($simpletest, $javascript) && array_key_exists('core/misc/tableselect.js', $javascript)) {
67    $javascript[$simpletest]['weight'] = $javascript['core/misc/tableselect.js']['weight'] - 1;
68  }
69}
70
71/**
72 * Prepares variables for simpletest result summary templates.
73 *
74 * Default template: simpletest-result-summary.html.twig.
75 *
76 * @param array $variables
77 *   An associative array containing:
78 *   - label: An optional label to be rendered before the results.
79 *   - ok: The overall group result pass or fail.
80 *   - pass: The number of passes.
81 *   - fail: The number of fails.
82 *   - exception: The number of exceptions.
83 *   - debug: The number of debug messages.
84 */
85function template_preprocess_simpletest_result_summary(&$variables) {
86  $variables['items'] = _simpletest_build_summary_line($variables);
87}
88
89/**
90 * Formats each test result type pluralized summary.
91 *
92 * @param array $summary
93 *   A summary of the test results.
94 *
95 * @return array
96 *   The pluralized test summary items.
97 */
98function _simpletest_build_summary_line($summary) {
99  $translation = \Drupal::translation();
100  $items['pass'] = $translation->formatPlural($summary['pass'], '1 pass', '@count passes');
101  $items['fail'] = $translation->formatPlural($summary['fail'], '1 fail', '@count fails');
102  $items['exception'] = $translation->formatPlural($summary['exception'], '1 exception', '@count exceptions');
103  if ($summary['debug']) {
104    $items['debug'] = $translation->formatPlural($summary['debug'], '1 debug message', '@count debug messages');
105  }
106  return $items;
107}
108
109/**
110 * Formats test result summaries into a comma separated string for run-tests.sh.
111 *
112 * @param array $summary
113 *   A summary of the test results.
114 *
115 * @return string
116 *   A concatenated string of the formatted test results.
117 */
118function _simpletest_format_summary_line($summary) {
119  $parts = _simpletest_build_summary_line($summary);
120  return implode(', ', $parts);
121}
122
123/**
124 * Runs tests.
125 *
126 * @param array[] $test_list
127 *   List of tests to run. The top level is keyed by type of test, either
128 *   'simpletest' or 'phpunit'. Under that is an array of class names to run.
129 *
130 * @return string
131 *   The test ID.
132 */
133function simpletest_run_tests($test_list) {
134  // We used to separate PHPUnit and Simpletest tests for a performance
135  // optimization. In order to support backwards compatibility check if these
136  // keys are set and create a single test list.
137  // @todo https://www.drupal.org/node/2748967 Remove BC support in Drupal 9.
138  if (isset($test_list['simpletest'])) {
139    $test_list = array_merge($test_list, $test_list['simpletest']);
140    unset($test_list['simpletest']);
141  }
142  if (isset($test_list['phpunit'])) {
143    $test_list = array_merge($test_list, $test_list['phpunit']);
144    unset($test_list['phpunit']);
145  }
146
147  $test_id = \Drupal::database()->insert('simpletest_test_id')
148    ->useDefaults(['test_id'])
149    ->execute();
150
151  // Clear out the previous verbose files.
152  try {
153    \Drupal::service('file_system')->deleteRecursive('public://simpletest/verbose');
154  }
155  catch (FileException $e) {
156    // Ignore failed deletes.
157  }
158
159  // Get the info for the first test being run.
160  $first_test = reset($test_list);
161  $info = TestDiscovery::getTestInfo($first_test);
162
163  $batch = [
164    'title' => t('Running tests'),
165    'operations' => [
166      ['_simpletest_batch_operation', [$test_list, $test_id]],
167    ],
168    'finished' => '_simpletest_batch_finished',
169    'progress_message' => '',
170    'library' => ['simpletest/drupal.simpletest'],
171    'init_message' => t('Processing test @num of @max - %test.', ['%test' => $info['name'], '@num' => '1', '@max' => count($test_list)]),
172  ];
173  batch_set($batch);
174
175  \Drupal::moduleHandler()->invokeAllDeprecated('Convert your test to a PHPUnit-based one and implement test listeners. See https://www.drupal.org/node/2934242', 'test_group_started');
176
177  return $test_id;
178}
179
180/**
181 * Executes PHPUnit tests and returns the results of the run.
182 *
183 * @param $test_id
184 *   The current test ID.
185 * @param $unescaped_test_classnames
186 *   An array of test class names, including full namespaces, to be passed as
187 *   a regular expression to PHPUnit's --filter option.
188 * @param int $status
189 *   (optional) The exit status code of the PHPUnit process will be assigned to
190 *   this variable.
191 *
192 * @return array
193 *   The parsed results of PHPUnit's JUnit XML output, in the format of
194 *   {simpletest}'s schema.
195 *
196 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
197 *   \Drupal\Core\Test\PhpUnitTestRunner::runTests() instead.
198 *
199 * @see https://www.drupal.org/node/2948547
200 */
201function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames, &$status = NULL) {
202  $runner = PhpUnitTestRunner::create(\Drupal::getContainer());
203  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::runTests() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED);
204  return $runner->runTests($test_id, $unescaped_test_classnames, $status);
205}
206
207/**
208 * Inserts the parsed PHPUnit results into {simpletest}.
209 *
210 * @param array[] $phpunit_results
211 *   An array of test results returned from simpletest_phpunit_xml_to_rows().
212 *
213 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
214 *   \Drupal\Core\Test\TestDatabase::processPhpUnitResults() instead.
215 *
216 * @see https://www.drupal.org/node/3075252
217 */
218function simpletest_process_phpunit_results($phpunit_results) {
219  @trigger_error(__FUNCTION__ . '() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDatabase::processPhpUnitResults() instead. See https://www.drupal.org/node/3075252', E_USER_DEPRECATED);
220  TestDatabase::processPhpUnitResults($phpunit_results);
221}
222
223/**
224 * Maps phpunit results to a data structure for batch messages and run-tests.sh.
225 *
226 * @param array $results
227 *   The output from simpletest_run_phpunit_tests().
228 *
229 * @return array
230 *   The test result summary. A row per test class.
231 *
232 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
233 *   \Drupal\Core\Test\PhpUnitTestRunner::summarizeResults() instead.
234 *
235 * @see https://www.drupal.org/node/2948547
236 */
237function simpletest_summarize_phpunit_result($results) {
238  $runner = PhpUnitTestRunner::create(\Drupal::getContainer());
239  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::summarizeResults() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED);
240  return $runner->summarizeResults($results);
241}
242
243/**
244 * Returns the path to use for PHPUnit's --log-junit option.
245 *
246 * @param $test_id
247 *   The current test ID.
248 *
249 * @return string
250 *   Path to the PHPUnit XML file to use for the current $test_id.
251 *
252 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
253 *   \Drupal\Core\Test\PhpUnitTestRunner::xmlLogFilepath() instead.
254 *
255 * @see https://www.drupal.org/node/2948547
256 */
257function simpletest_phpunit_xml_filepath($test_id) {
258  $runner = PhpUnitTestRunner::create(\Drupal::getContainer());
259  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::xmlLogFilepath() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED);
260  return $runner->xmlLogFilePath($test_id);
261}
262
263/**
264 * Returns the path to core's phpunit.xml.dist configuration file.
265 *
266 * @return string
267 *   The path to core's phpunit.xml.dist configuration file.
268 *
269 * @deprecated in drupal:8.4.0 and is removed from drupal:9.0.0. PHPUnit test
270 *   runners should change directory into core/ and then run the phpunit tool.
271 *   See simpletest_phpunit_run_command() for an example.
272 *
273 * @see simpletest_phpunit_run_command()
274 */
275function simpletest_phpunit_configuration_filepath() {
276  @trigger_error('The ' . __FUNCTION__ . ' function is deprecated since version 8.4.x and will be removed in 9.0.0.', E_USER_DEPRECATED);
277  return \Drupal::root() . '/core/phpunit.xml.dist';
278}
279
280/**
281 * Executes the PHPUnit command.
282 *
283 * @param array $unescaped_test_classnames
284 *   An array of test class names, including full namespaces, to be passed as
285 *   a regular expression to PHPUnit's --filter option.
286 * @param string $phpunit_file
287 *   A filepath to use for PHPUnit's --log-junit option.
288 * @param int $status
289 *   (optional) The exit status code of the PHPUnit process will be assigned to
290 *   this variable.
291 * @param string $output
292 *   (optional) The output by running the phpunit command.
293 *
294 * @return string
295 *   The results as returned by exec().
296 *
297 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
298 *   \Drupal\Core\Test\PhpUnitTestRunner::runCommand() instead.
299 *
300 * @see https://www.drupal.org/node/2948547
301 */
302function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpunit_file, &$status = NULL, &$output = NULL) {
303  $runner = PhpUnitTestRunner::create(\Drupal::getContainer());
304  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::runCommand() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED);
305  return $runner->runCommand($unescaped_test_classnames, $phpunit_file, $status, $output);
306}
307
308/**
309 * Returns the command to run PHPUnit.
310 *
311 * @return string
312 *   The command that can be run through exec().
313 *
314 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
315 *   \Drupal\Core\Test\PhpUnitTestRunner::phpUnitCommand() instead.
316 *
317 * @see https://www.drupal.org/node/2948547
318 */
319function simpletest_phpunit_command() {
320  $runner = PhpUnitTestRunner::create(\Drupal::getContainer());
321  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\PhpUnitTestRunner::phpUnitCommand() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED);
322  return $runner->phpUnitCommand();
323}
324
325/**
326 * Implements callback_batch_operation().
327 */
328function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
329  \Drupal::service('test_discovery')->registerTestNamespaces();
330  // Get working values.
331  if (!isset($context['sandbox']['max'])) {
332    // First iteration: initialize working values.
333    $test_list = $test_list_init;
334    $context['sandbox']['max'] = count($test_list);
335    $test_results = ['#pass' => 0, '#fail' => 0, '#exception' => 0, '#debug' => 0];
336  }
337  else {
338    // Nth iteration: get the current values where we last stored them.
339    $test_list = $context['sandbox']['tests'];
340    $test_results = $context['sandbox']['test_results'];
341  }
342  $max = $context['sandbox']['max'];
343
344  // Perform the next test.
345  $test_class = array_shift($test_list);
346  if (is_subclass_of($test_class, TestCase::class)) {
347    $runner = PhpUnitTestRunner::create(\Drupal::getContainer());
348    $phpunit_results = $runner->runTests($test_id, [$test_class]);
349    TestDatabase::processPhpUnitResults($phpunit_results);
350    $test_results[$test_class] = simpletest_summarize_phpunit_result($phpunit_results)[$test_class];
351  }
352  else {
353    $test = new $test_class($test_id);
354    $test->run();
355    \Drupal::moduleHandler()->invokeAllDeprecated('Convert your test to a PHPUnit-based one and implement test listeners. See https://www.drupal.org/node/2934242', 'test_finished', [$test->results]);
356    $test_results[$test_class] = $test->results;
357  }
358  $size = count($test_list);
359  $info = TestDiscovery::getTestInfo($test_class);
360
361  // Gather results and compose the report.
362  foreach ($test_results[$test_class] as $key => $value) {
363    $test_results[$key] += $value;
364  }
365  $test_results[$test_class]['#name'] = $info['name'];
366  $items = [];
367  foreach (Element::children($test_results) as $class) {
368    $class_test_result = $test_results[$class] + [
369      '#theme' => 'simpletest_result_summary',
370      '#label' => t($test_results[$class]['#name'] . ':'),
371    ];
372    array_unshift($items, \Drupal::service('renderer')->render($class_test_result));
373  }
374  $context['message'] = t('Processed test @num of @max - %test.', ['%test' => $info['name'], '@num' => $max - $size, '@max' => $max]);
375  $overall_results = $test_results + [
376    '#theme' => 'simpletest_result_summary',
377    '#label' => t('Overall results:'),
378  ];
379  $context['message'] .= \Drupal::service('renderer')->render($overall_results);
380
381  $item_list = [
382    '#theme' => 'item_list',
383    '#items' => $items,
384  ];
385  $context['message'] .= \Drupal::service('renderer')->render($item_list);
386
387  // Save working values for the next iteration.
388  $context['sandbox']['tests'] = $test_list;
389  $context['sandbox']['test_results'] = $test_results;
390  // The test_id is the only thing we need to save for the report page.
391  $context['results']['test_id'] = $test_id;
392
393  // Multistep processing: report progress.
394  $context['finished'] = 1 - $size / $max;
395}
396
397/**
398 * Implements callback_batch_finished().
399 */
400function _simpletest_batch_finished($success, $results, $operations, $elapsed) {
401  if ($success) {
402    \Drupal::messenger()->addStatus(t('The test run finished in @elapsed.', ['@elapsed' => $elapsed]));
403  }
404  else {
405    // Use the test_id passed as a parameter to _simpletest_batch_operation().
406    $test_id = $operations[0][1][1];
407
408    // Retrieve the last database prefix used for testing and the last test
409    // class that was run from. Use the information to read the lgo file
410    // in case any fatal errors caused the test to crash.
411    $last_test = TestDatabase::lastTestGet($test_id);
412    (new TestDatabase($last_test['last_prefix']))->logRead($test_id, $last_test['test_class']);
413
414    \Drupal::messenger()->addError(t('The test run did not successfully finish.'));
415    \Drupal::messenger()->addWarning(t('Use the <em>Clean environment</em> button to clean-up temporary files and tables.'));
416  }
417  \Drupal::moduleHandler()->invokeAllDeprecated('Convert your test to a PHPUnit-based one and implement test listeners. See https://www.drupal.org/node/2934242', 'test_group_finished');
418}
419
420/**
421 * Get information about the last test that ran given a test ID.
422 *
423 * @param $test_id
424 *   The test ID to get the last test from.
425 *
426 * @return array
427 *   Array containing the last database prefix used and the last test class
428 *   that ran.
429 *
430 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
431 *   \Drupal\Core\Test\TestDatabase::lastTestGet() instead.
432 *
433 * @see https://www.drupal.org/node/3075252
434 */
435function simpletest_last_test_get($test_id) {
436  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDatabase::lastTestGet() instead. See https://www.drupal.org/node/3075252', E_USER_DEPRECATED);
437  return array_values(TestDatabase::lastTestGet($test_id));
438}
439
440/**
441 * Reads the error log and reports any errors as assertion failures.
442 *
443 * The errors in the log should only be fatal errors since any other errors
444 * will have been recorded by the error handler.
445 *
446 * @param $test_id
447 *   The test ID to which the log relates.
448 * @param $database_prefix
449 *   The database prefix to which the log relates.
450 * @param $test_class
451 *   The test class to which the log relates.
452 *
453 * @return bool
454 *   Whether any fatal errors were found.
455 *
456 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
457 *   \Drupal\Core\Test\TestDatabase::logRead() instead.
458 *
459 * @see https://www.drupal.org/node/3075252
460 */
461function simpletest_log_read($test_id, $database_prefix, $test_class) {
462  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDatabase::logRead() instead. See https://www.drupal.org/node/3075252', E_USER_DEPRECATED);
463  $test_db = new TestDatabase($database_prefix);
464  return $test_db->logRead($test_id, $test_class);
465}
466
467/**
468 * Store an assertion from outside the testing context.
469 *
470 * This is useful for inserting assertions that can only be recorded after
471 * the test case has been destroyed, such as PHP fatal errors. The caller
472 * information is not automatically gathered since the caller is most likely
473 * inserting the assertion on behalf of other code. In all other respects
474 * the method behaves just like \Drupal\simpletest\TestBase::assert() in terms
475 * of storing the assertion.
476 *
477 * @param string $test_id
478 *   The test ID to which the assertion relates.
479 * @param string $test_class
480 *   The test class to store an assertion for.
481 * @param bool|string $status
482 *   A boolean or a string of 'pass' or 'fail'. TRUE means 'pass'.
483 * @param string $message
484 *   The assertion message.
485 * @param string $group
486 *   The assertion message group.
487 * @param array $caller
488 *   The an array containing the keys 'file' and 'line' that represent the file
489 *   and line number of that file that is responsible for the assertion.
490 *
491 * @return
492 *   Message ID of the stored assertion.
493 *
494 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
495 *   \Drupal\Core\Test\TestDatabase::insertAssert() instead.
496 *
497 * @see https://www.drupal.org/node/3075252
498 */
499function simpletest_insert_assert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = []) {
500  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\TestDatabase::insertAssert() instead. See https://www.drupal.org/node/3075252', E_USER_DEPRECATED);
501  TestDatabase::insertAssert($test_id, $test_class, $status, $message, $group, $caller);
502}
503
504/**
505 * Gets a list of all of the tests provided by the system.
506 *
507 * The list of test classes is loaded by searching the designated directory for
508 * each module for files matching the PSR-4 standard. Once loaded the test list
509 * is cached and stored in a static variable.
510 *
511 * @param string $extension
512 *   (optional) The name of an extension to limit discovery to; e.g., 'node'.
513 * @param string[] $types
514 *   An array of included test types.
515 *
516 * @return array[]
517 *   An array of tests keyed with the groups, and then keyed by test classes.
518 *   For example:
519 *   @code
520 *     $groups['Block'] => array(
521 *       'BlockTestCase' => array(
522 *         'name' => 'Block functionality',
523 *         'description' => 'Add, edit and delete custom block.',
524 *         'group' => 'Block',
525 *       ),
526 *     );
527 *   @endcode
528 *
529 * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use
530 *   \Drupal::service('test_discovery')->getTestClasses($extension, $types)
531 *   instead.
532 */
533function simpletest_test_get_all($extension = NULL, array $types = []) {
534  @trigger_error('The ' . __FUNCTION__ . ' function is deprecated in version 8.3.x and will be removed in 9.0.0. Use \Drupal::service(\'test_discovery\')->getTestClasses($extension, $types) instead.', E_USER_DEPRECATED);
535  return \Drupal::service('test_discovery')->getTestClasses($extension, $types);
536}
537
538/**
539 * Registers test namespaces of all extensions and core test classes.
540 *
541 * @deprecated in drupal:8.3.0 and is removed from drupal:9.0.0. Use
542 *   \Drupal::service('test_discovery')->registerTestNamespaces() instead.
543 */
544function simpletest_classloader_register() {
545  @trigger_error('The ' . __FUNCTION__ . ' function is deprecated in version 8.3.x and will be removed in 9.0.0. Use \Drupal::service(\'test_discovery\')->registerTestNamespaces() instead.', E_USER_DEPRECATED);
546  \Drupal::service('test_discovery')->registerTestNamespaces();
547}
548
549/**
550 * Generates a test file.
551 *
552 * @param string $filename
553 *   The name of the file, including the path. The suffix '.txt' is appended to
554 *   the supplied file name and the file is put into the public:// files
555 *   directory.
556 * @param int $width
557 *   The number of characters on one line.
558 * @param int $lines
559 *   The number of lines in the file.
560 * @param string $type
561 *   (optional) The type, one of:
562 *   - text: The generated file contains random ASCII characters.
563 *   - binary: The generated file contains random characters whose codes are in
564 *     the range of 0 to 31.
565 *   - binary-text: The generated file contains random sequence of '0' and '1'
566 *     values.
567 *
568 * @return string
569 *   The name of the file, including the path.
570 *
571 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
572 *   \Drupal\Tests\TestFileCreationTrait::generateFile() instead.
573 *
574 * @see https://www.drupal.org/node/3077768
575 */
576function simpletest_generate_file($filename, $width, $lines, $type = 'binary-text') {
577  @trigger_error(__FUNCTION__ . '() is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Tests\TestFileCreationTrait::generateFile() instead. See https://www.drupal.org/node/3077768', E_USER_DEPRECATED);
578  $text = '';
579  for ($i = 0; $i < $lines; $i++) {
580    // Generate $width - 1 characters to leave space for the "\n" character.
581    for ($j = 0; $j < $width - 1; $j++) {
582      switch ($type) {
583        case 'text':
584          $text .= chr(rand(32, 126));
585          break;
586        case 'binary':
587          $text .= chr(rand(0, 31));
588          break;
589        case 'binary-text':
590        default:
591          $text .= rand(0, 1);
592          break;
593      }
594    }
595    $text .= "\n";
596  }
597
598  // Create filename.
599  file_put_contents('public://' . $filename . '.txt', $text);
600  return $filename;
601}
602
603/**
604 * Removes all temporary database tables and directories.
605 *
606 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the
607 *   environment_cleaner service and call its cleanEnvironment() method, or use
608 *   \Drupal\Core\Test\EnvironmentCleaner::cleanEnvironment() instead.
609 *
610 * @see https://www.drupal.org/node/3076634
611 */
612function simpletest_clean_environment() {
613  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanEnvironment() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanEnvironment() instead.. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED);
614  /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */
615  $cleaner = \Drupal::service('environment_cleaner');
616  $cleaner->cleanEnvironment();
617}
618
619/**
620 * Removes prefixed tables from the database from crashed tests.
621 *
622 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the
623 *   environment_cleaner service and call its cleanDatabase() method, or use
624 *   \Drupal\Core\Test\EnvironmentCleaner::cleanDatabase() instead.
625 *
626 * @see https://www.drupal.org/node/3076634
627 */
628function simpletest_clean_database() {
629  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanDatabase() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanDatabase() instead. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED);
630  /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */
631  $cleaner = \Drupal::service('environment_cleaner');
632  $cleaner->cleanDatabase();
633}
634
635/**
636 * Finds all leftover temporary directories and removes them.
637 *
638 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the
639 *   environment_cleaner service and call its cleanTemporaryDirectories()
640 *   method, or use
641 *   \Drupal\Core\Test\EnvironmentCleaner::cleanTemporaryDirectories() instead.
642 *
643 * @see https://www.drupal.org/node/3076634
644 */
645function simpletest_clean_temporary_directories() {
646  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanTemporaryDirectories() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanTemporaryDirectories() instead. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED);
647  /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */
648  $cleaner = \Drupal::service('environment_cleaner');
649  $cleaner->cleanTemporaryDirectories();
650}
651
652/**
653 * Clears the test result tables.
654 *
655 * @param $test_id
656 *   Test ID to remove results for, or NULL to remove all results.
657 *
658 * @return int
659 *   The number of results that were removed.
660 *
661 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the
662 *   environment_cleaner service and call its cleanResultsTable() method, or use
663 *   \Drupal\Core\Test\EnvironmentCleaner::cleanResultsTable() instead.
664 *
665 * @see https://www.drupal.org/node/3076634
666 */
667function simpletest_clean_results_table($test_id = NULL) {
668  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Access the environment_cleaner service and call its cleanResultsTable() method, or use \Drupal\Core\Test\EnvironmentCleaner::cleanResultsTable() instead. See https://www.drupal.org/node/3076634', E_USER_DEPRECATED);
669  $count = 0;
670  if (\Drupal::config('simpletest.settings')->get('clear_results')) {
671    /* @var $cleaner \Drupal\simpletest\EnvironmentCleanerService */
672    $cleaner = \Drupal::service('environment_cleaner');
673    $count = $cleaner->cleanResultsTable($test_id);
674  }
675  return $count;
676}
677
678/**
679 * Converts PHPUnit's JUnit XML output to an array.
680 *
681 * @param $test_id
682 *   The current test ID.
683 * @param $phpunit_xml_file
684 *   Path to the PHPUnit XML file.
685 *
686 * @return array[]|null
687 *   The results as array of rows in a format that can be inserted into
688 *   {simpletest}. If the phpunit_xml_file does not have any contents then the
689 *   function will return NULL.
690 *
691 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
692 *   \Drupal\Core\Test\JUnitConverter::xmlToRows() instead.
693 *
694 * @see https://www.drupal.org/node/2948547
695 */
696function simpletest_phpunit_xml_to_rows($test_id, $phpunit_xml_file) {
697  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\JUnitConverter::xmlToRows() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED);
698  return JUnitConverter::xmlToRows($test_id, $phpunit_xml_file) ?: NULL;
699}
700
701/**
702 * Finds all test cases recursively from a test suite list.
703 *
704 * @param \SimpleXMLElement $element
705 *   The PHPUnit xml to search for test cases.
706 * @param \SimpleXMLElement $parent
707 *   (Optional) The parent of the current element. Defaults to NULL.
708 *
709 * @return array
710 *   A list of all test cases.
711 *
712 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
713 *   \Drupal\Core\Test\JUnitConverter::findTestCases() instead.
714 *
715 * @see https://www.drupal.org/node/2948547
716 */
717function simpletest_phpunit_find_testcases(\SimpleXMLElement $element, \SimpleXMLElement $parent = NULL) {
718  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\JUnitConverter::findTestCases() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED);
719  return JUnitConverter::findTestCases($element, $parent);
720}
721
722/**
723 * Converts a PHPUnit test case result to a {simpletest} result row.
724 *
725 * @param int $test_id
726 *   The current test ID.
727 * @param \SimpleXMLElement $test_case
728 *   The PHPUnit test case represented as XML element.
729 *
730 * @return array
731 *   An array containing the {simpletest} result row.
732 *
733 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use
734 *  \Drupal\Core\Test\JUnitConverter::convertTestCaseToSimpletestRow() instead.
735 *
736 * @see https://www.drupal.org/node/2948547
737 */
738function simpletest_phpunit_testcase_to_row($test_id, \SimpleXMLElement $test_case) {
739  @trigger_error(__FUNCTION__ . ' is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use \Drupal\Core\Test\JUnitConverter::convertTestCaseToSimpletestRow() instead. See https://www.drupal.org/node/2948547', E_USER_DEPRECATED);
740  return JUnitConverter::convertTestCaseToSimpletestRow($test_id, $test_case);
741}
742
743/**
744 * Display test results from run-tests.sh in a browser.
745 *
746 * @internal
747 *   This function is only used by run-tests.sh
748 *
749 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. This function
750 *   supports the --browser option in this script. Use the --verbose option
751 *   instead.
752 *
753 * @see https://www.drupal.org/node/3083549
754 */
755function _simpletest_run_tests_script_open_browser() {
756  global $test_ids;
757
758  try {
759    $connection = Database::getConnection('default', 'test-runner');
760    $results = $connection->select('simpletest')
761      ->fields('simpletest')
762      ->condition('test_id', $test_ids, 'IN')
763      ->orderBy('test_class')
764      ->orderBy('message_id')
765      ->execute()
766      ->fetchAll();
767  }
768  catch (Exception $e) {
769    echo (string) $e;
770    exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
771  }
772
773  // Get the results form.
774  $form = [];
775  SimpletestResultsForm::addResultForm($form, $results);
776
777  // Get the assets to make the details element collapsible and theme the result
778  // form.
779  $assets = new AttachedAssets();
780  $assets->setLibraries([
781    'core/drupal.collapse',
782    'system/admin',
783    'simpletest/drupal.simpletest',
784  ]);
785  $resolver = \Drupal::service('asset.resolver');
786  list($js_assets_header, $js_assets_footer) = $resolver->getJsAssets($assets, FALSE);
787  $js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
788  $js_assets_header = $js_collection_renderer->render($js_assets_header);
789  $js_assets_footer = $js_collection_renderer->render($js_assets_footer);
790  $css_assets = \Drupal::service('asset.css.collection_renderer')->render($resolver->getCssAssets($assets, FALSE));
791
792  // Make the html page to write to disk.
793  $render_service = \Drupal::service('renderer');
794  $html = '<head>' . $render_service->renderPlain($js_assets_header) . $render_service->renderPlain($css_assets) . '</head><body>' . $render_service->renderPlain($form) . $render_service->renderPlain($js_assets_footer) . '</body>';
795
796  // Ensure we have assets verbose directory - tests with no verbose output will
797  // not have created one.
798  $directory = PublicStream::basePath() . '/simpletest/verbose';
799  \Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
800  $php = new Php();
801  $uuid = $php->generate();
802  $filename = $directory . '/results-' . $uuid . '.html';
803  $base_url = getenv('SIMPLETEST_BASE_URL');
804  if (empty($base_url)) {
805    simpletest_script_print_error("--browser needs argument --url.");
806  }
807  $url = $base_url . '/' . PublicStream::basePath() . '/simpletest/verbose/results-' . $uuid . '.html';
808  file_put_contents($filename, $html);
809
810  // See if we can find an OS helper to open URLs in default browser.
811  $browser = FALSE;
812  if (shell_exec('which xdg-open')) {
813    $browser = 'xdg-open';
814  }
815  elseif (shell_exec('which open')) {
816    $browser = 'open';
817  }
818  elseif (substr(PHP_OS, 0, 3) == 'WIN') {
819    $browser = 'start';
820  }
821
822  if ($browser) {
823    shell_exec($browser . ' ' . escapeshellarg($url));
824  }
825  else {
826    // Can't find assets valid browser.
827    print 'Open file://' . realpath($filename) . ' in your browser to see the verbose output.';
828  }
829}
830