diff --git a/core/lib/Drupal/Core/Test/RunTests/RunTestsResultsBuilder.php b/core/lib/Drupal/Core/Test/RunTests/RunTestsResultsBuilder.php new file mode 100644 index 0000000000..b5685edae5 --- /dev/null +++ b/core/lib/Drupal/Core/Test/RunTests/RunTestsResultsBuilder.php @@ -0,0 +1,245 @@ + + {{ label }} {{ items|join(', ') }} + +TWIG; + + /** + * Builds the status image map. + * + * @return string[] + * Render array of icons for status displays. + */ + public static function buildStatusImageMap() { + $image_pass = [ + '#theme' => 'image', + '#uri' => 'core/misc/icons/73b355/check.svg', + '#width' => 18, + '#height' => 18, + '#alt' => 'Pass', + ]; + $image_fail = [ + '#theme' => 'image', + '#uri' => 'core/misc/icons/e32700/error.svg', + '#width' => 18, + '#height' => 18, + '#alt' => 'Fail', + ]; + $image_exception = [ + '#theme' => 'image', + '#uri' => 'core/misc/icons/e29700/warning.svg', + '#width' => 18, + '#height' => 18, + '#alt' => 'Exception', + ]; + $image_debug = [ + '#theme' => 'image', + '#uri' => 'core/misc/icons/e29700/warning.svg', + '#width' => 18, + '#height' => 18, + '#alt' => 'Debug', + ]; + return [ + 'pass' => $image_pass, + 'fail' => $image_fail, + 'exception' => $image_exception, + 'debug' => $image_debug, + ]; + } + + /** + * Adds the result form to a $form. + * + * This is a static method so that run-tests.sh can use it to generate a + * results page completely external to Drupal. This is why the UI strings are + * not wrapped in t(). + * + * @param array $form + * The form to attach the results to. + * @param array $results + * The simpletest results. + * + * @return array + * A list of tests the passed and failed. The array has two keys, 'pass' and + * 'fail'. Each contains a list of test classes. + * + * @see simpletest_script_open_browser() + * @see run-tests.sh + * @see \Drupal\simpletest\Form\SimpletestResultsForm + */ + public static function addResultForm(array &$form, array $results) { + // Transform the test results to be grouped by test class. + $test_results = []; + foreach ($results as $result) { + if (!isset($test_results[$result->test_class])) { + $test_results[$result->test_class] = []; + } + $test_results[$result->test_class][] = $result; + } + + $image_status_map = static::buildStatusImageMap(); + + // Keep track of which test cases passed or failed. + $filter = [ + 'pass' => [], + 'fail' => [], + ]; + + // Summary result widget. + $form['result'] = [ + '#type' => 'fieldset', + '#title' => 'Results', + // Because this is used in a theme-less situation need to provide a + // default. + '#attributes' => [], + ]; + + // Add summary line. + $form['result']['summary'] = $summary = [ + '#type' => 'inline_template', + '#template' => static::$summaryTemplate, + // Fill in the context for the template after we've tallied results in + // #pass, #fail, etc. + '#context' => [], + '#pass' => 0, + '#fail' => 0, + '#exception' => 0, + '#debug' => 0, + ]; + + \Drupal::service('test_discovery')->registerTestNamespaces(); + + // Cycle through each test group. + $header = [ + 'Message', + 'Group', + 'Filename', + 'Line', + 'Function', + ['colspan' => 2, 'data' => 'Status'], + ]; + $form['result']['results'] = []; + foreach ($test_results as $group => $assertions) { + // Create group details with summary information. + $info = TestDiscovery::getTestInfo($group); + $form['result']['results'][$group] = [ + '#type' => 'details', + '#title' => $info['name'], + '#open' => TRUE, + '#description' => $info['description'], + ]; + $form['result']['results'][$group]['summary'] = $summary; + $group_summary = & $form['result']['results'][$group]['summary']; + + // Create table of assertions for the group. + $rows = []; + foreach ($assertions as $assertion) { + $row = []; + $row[] = ['data' => ['#markup' => $assertion->message]]; + $row[] = $assertion->message_group; + $row[] = \Drupal::service('file_system')->basename(($assertion->file)); + $row[] = $assertion->line; + $row[] = $assertion->function; + $row[] = ['data' => $image_status_map[$assertion->status]]; + + $class = 'simpletest-' . $assertion->status; + if ($assertion->message_group == 'Debug') { + $class = 'simpletest-debug'; + } + $rows[] = ['data' => $row, 'class' => [$class]]; + + $group_summary['#' . $assertion->status]++; + $form['result']['summary']['#' . $assertion->status]++; + } + $form['result']['results'][$group]['table'] = [ + '#type' => 'table', + '#header' => $header, + '#rows' => $rows, + ]; + + $group_summary['#context'] = static::buildSummaryContext( + $group_summary['#pass'], + $group_summary['#fail'], + $group_summary['#exception'], + $group_summary['#debug'] + ); + + // Set summary information. + $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0; + $form['result']['results'][$group]['#open'] = !$group_summary['#ok']; + + // Store test group (class) as for use in filter. + $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group; + } + + // Now that we've tallied all the results, we can compute the context for + // the results summary template. + + $form['result']['summary']['#context'] = static::buildSummaryContext( + $form['result']['summary']['#pass'], + $form['result']['summary']['#fail'], + $form['result']['summary']['#exception'], + $form['result']['summary']['#debug'] + ); + + // Overall summary status. + $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0; + + return $filter; + } + + /** + * Assemble the context for the twig template in static::$summaryTemplate. + * + * @param int $pass + * The count of passing tests. + * @param int $fail + * The count of failing tests. + * @param int $exception + * The count of tests that threw an exception. + * @param int $debug + * The count of debug messages present. + * @param string $label + * An optional label for the summary line. + * + * @return string[] + * The context for the twig template in static::$summaryTemplate. + */ + protected static function buildSummaryContext($pass, $fail, $exception, $debug, $label = NULL) { + $context = []; + $translation = \Drupal::translation(); + + $context['label'] = $label; + $context['pass'] = $pass; + $context['fail'] = $fail; + $context['exception'] = $exception; + $context['debug'] = $debug; + + $context['items']['pass'] = $translation->formatPlural($pass, '1 pass', '@count passes'); + $context['items']['fail'] = $translation->formatPlural($fail, '1 fail', '@count fails'); + $context['items']['exception'] = $translation->formatPlural($exception, '1 exception', '@count exceptions'); + if ($debug) { + $context['items']['debug'] = $translation->formatPlural($debug, '1 debug message', '@count debug messages'); + } + return $context; + } + +} diff --git a/core/modules/simpletest/simpletest.libraries.yml b/core/modules/simpletest/simpletest.libraries.yml index 7bcbc07de5..b84366e2d5 100644 --- a/core/modules/simpletest/simpletest.libraries.yml +++ b/core/modules/simpletest/simpletest.libraries.yml @@ -2,9 +2,6 @@ drupal.simpletest: version: VERSION js: simpletest.js: {} - css: - component: - css/simpletest.module.css: {} dependencies: - core/jquery - core/drupal @@ -12,3 +9,4 @@ drupal.simpletest: - core/jquery.once - core/drupal.tableselect - core/drupal.debounce + - system/runtests diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index 5775c1d83d..3c6f0feeb6 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -41,6 +41,10 @@ function simpletest_help($route_name, RouteMatchInterface $route_match) { /** * Implements hook_theme(). + * + * @todo Replace this theming system with + * Drupal\Core\Test\RunTests\RunTestsResultsBuilder in + * https://www.drupal.org/project/drupal/issues/3075490 */ function simpletest_theme() { return [ @@ -100,20 +104,6 @@ function _simpletest_build_summary_line($summary) { return $items; } -/** - * Formats test result summaries into a comma separated string for run-tests.sh. - * - * @param array $summary - * A summary of the test results. - * - * @return string - * A concatenated string of the formatted test results. - */ -function _simpletest_format_summary_line($summary) { - $parts = _simpletest_build_summary_line($summary); - return implode(', ', $parts); -} - /** * Runs tests. * diff --git a/core/modules/simpletest/src/Form/SimpletestResultsForm.php b/core/modules/simpletest/src/Form/SimpletestResultsForm.php index adc1d4a5bb..5de37fcbbe 100644 --- a/core/modules/simpletest/src/Form/SimpletestResultsForm.php +++ b/core/modules/simpletest/src/Form/SimpletestResultsForm.php @@ -7,8 +7,8 @@ use Drupal\Core\Form\FormState; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Test\EnvironmentCleanerInterface; +use Drupal\Core\Test\RunTests\RunTestsResultsBuilder; use Drupal\Core\Url; -use Drupal\simpletest\TestDiscovery; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -70,40 +70,7 @@ public function __construct(Connection $database, EnvironmentCleanerInterface $c * Builds the status image map. */ protected static function buildStatusImageMap() { - $image_pass = [ - '#theme' => 'image', - '#uri' => 'core/misc/icons/73b355/check.svg', - '#width' => 18, - '#height' => 18, - '#alt' => 'Pass', - ]; - $image_fail = [ - '#theme' => 'image', - '#uri' => 'core/misc/icons/e32700/error.svg', - '#width' => 18, - '#height' => 18, - '#alt' => 'Fail', - ]; - $image_exception = [ - '#theme' => 'image', - '#uri' => 'core/misc/icons/e29700/warning.svg', - '#width' => 18, - '#height' => 18, - '#alt' => 'Exception', - ]; - $image_debug = [ - '#theme' => 'image', - '#uri' => 'core/misc/icons/e29700/warning.svg', - '#width' => 18, - '#height' => 18, - '#alt' => 'Debug', - ]; - return [ - 'pass' => $image_pass, - 'fail' => $image_fail, - 'exception' => $image_exception, - 'debug' => $image_debug, - ]; + return RunTestsResultsBuilder::buildStatusImageMap(); } /** @@ -257,101 +224,7 @@ protected function getResults($test_id) { * @see run-tests.sh */ public static function addResultForm(array &$form, array $results) { - // Transform the test results to be grouped by test class. - $test_results = []; - foreach ($results as $result) { - if (!isset($test_results[$result->test_class])) { - $test_results[$result->test_class] = []; - } - $test_results[$result->test_class][] = $result; - } - - $image_status_map = static::buildStatusImageMap(); - - // Keep track of which test cases passed or failed. - $filter = [ - 'pass' => [], - 'fail' => [], - ]; - - // Summary result widget. - $form['result'] = [ - '#type' => 'fieldset', - '#title' => 'Results', - // Because this is used in a theme-less situation need to provide a - // default. - '#attributes' => [], - ]; - $form['result']['summary'] = $summary = [ - '#theme' => 'simpletest_result_summary', - '#pass' => 0, - '#fail' => 0, - '#exception' => 0, - '#debug' => 0, - ]; - - \Drupal::service('test_discovery')->registerTestNamespaces(); - - // Cycle through each test group. - $header = [ - 'Message', - 'Group', - 'Filename', - 'Line', - 'Function', - ['colspan' => 2, 'data' => 'Status'], - ]; - $form['result']['results'] = []; - foreach ($test_results as $group => $assertions) { - // Create group details with summary information. - $info = TestDiscovery::getTestInfo($group); - $form['result']['results'][$group] = [ - '#type' => 'details', - '#title' => $info['name'], - '#open' => TRUE, - '#description' => $info['description'], - ]; - $form['result']['results'][$group]['summary'] = $summary; - $group_summary =& $form['result']['results'][$group]['summary']; - - // Create table of assertions for the group. - $rows = []; - foreach ($assertions as $assertion) { - $row = []; - $row[] = ['data' => ['#markup' => $assertion->message]]; - $row[] = $assertion->message_group; - $row[] = \Drupal::service('file_system')->basename(($assertion->file)); - $row[] = $assertion->line; - $row[] = $assertion->function; - $row[] = ['data' => $image_status_map[$assertion->status]]; - - $class = 'simpletest-' . $assertion->status; - if ($assertion->message_group == 'Debug') { - $class = 'simpletest-debug'; - } - $rows[] = ['data' => $row, 'class' => [$class]]; - - $group_summary['#' . $assertion->status]++; - $form['result']['summary']['#' . $assertion->status]++; - } - $form['result']['results'][$group]['table'] = [ - '#type' => 'table', - '#header' => $header, - '#rows' => $rows, - ]; - - // Set summary information. - $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0; - $form['result']['results'][$group]['#open'] = !$group_summary['#ok']; - - // Store test group (class) as for use in filter. - $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group; - } - - // Overall summary status. - $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0; - - return $filter; + return RunTestsResultsBuilder::addResultForm($form, $results); } } diff --git a/core/modules/simpletest/css/simpletest.module.css b/core/modules/system/css/system.runtests.css similarity index 100% rename from core/modules/simpletest/css/simpletest.module.css rename to core/modules/system/css/system.runtests.css diff --git a/core/modules/system/system.libraries.yml b/core/modules/system/system.libraries.yml index 98eb283919..d47b4fae9f 100644 --- a/core/modules/system/system.libraries.yml +++ b/core/modules/system/system.libraries.yml @@ -43,6 +43,13 @@ maintenance: - system/base - system/admin +# Styling for the run-tests.sh --browser option. +runtests: + version: VERSION + css: + theme: + css/system.runtests.css: {} + drupal.system: version: VERSION js: diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index 9d3f3894eb..f7651696e7 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -16,10 +16,10 @@ use Drupal\Core\StreamWrapper\PublicStream; use Drupal\Core\Test\EnvironmentCleaner; use Drupal\Core\Test\PhpUnitTestRunner; +use Drupal\Core\Test\RunTests\RunTestsResultsBuilder; use Drupal\Core\Test\RunTests\TestFileParser; use Drupal\Core\Test\TestDatabase; use Drupal\Core\Test\TestRunnerKernel; -use Drupal\simpletest\Form\SimpletestResultsForm; use Drupal\Core\Test\TestDiscovery; use PHPUnit\Framework\TestCase; use Symfony\Component\Console\Output\ConsoleOutput; @@ -319,7 +319,8 @@ function simpletest_script_help() { --browser Opens the results in the browser. This enforces --keep-results and if you want to also view any pages rendered in the simpletest - browser you need to add --verbose to the command line. + browser you need to add --verbose to the command line. See: + https://www.drupal.org/project/drupal/issues/3084354 --non-html Removes escaping from output. Useful for reading results on the CLI. @@ -815,6 +816,9 @@ function simpletest_script_run_one_test($test_id, $test_class) { global $args; try { + // Default to status = success. This could mean that we didn't discover any + // tests and that none ran. + $status = SIMPLETEST_SCRIPT_EXIT_SUCCESS; if (strpos($test_class, '::') > 0) { list($class_name, $method) = explode('::', $test_class, 2); $methods = [$method]; @@ -831,7 +835,10 @@ function simpletest_script_run_one_test($test_id, $test_class) { if (is_subclass_of($test_class, TestCase::class)) { $status = simpletest_script_run_phpunit($test_id, $test_class); } - else { + // If we aren't running a PHPUnit-based test, then we might have a + // Simpletest-based one. Ensure that: 1) The simpletest framework exists, + // and 2) That our test belongs to that framework. + elseif (class_exists('\Drupal\simpletest\TestBase') && is_subclass_of($test_class, '\Drupal\simpletest\TestBase')) { $test->dieOnFail = (bool) $args['die-on-fail']; $test->verbose = (bool) $args['verbose']; $test->run($methods); @@ -1515,7 +1522,7 @@ function simpletest_script_open_browser() { // Get the results form. $form = []; - SimpletestResultsForm::addResultForm($form, $results); + RunTestsResultsBuilder::addResultForm($form, $results); // Get the assets to make the details element collapsible and theme the result // form. @@ -1523,7 +1530,7 @@ function simpletest_script_open_browser() { $assets->setLibraries([ 'core/drupal.collapse', 'system/admin', - 'simpletest/drupal.simpletest', + 'system/runtests', ]); $resolver = \Drupal::service('asset.resolver'); list($js_assets_header, $js_assets_footer) = $resolver->getJsAssets($assets, FALSE); diff --git a/core/tests/Drupal/KernelTests/Core/Theme/StableLibraryOverrideTest.php b/core/tests/Drupal/KernelTests/Core/Theme/StableLibraryOverrideTest.php index 795ea33466..41d74cfc67 100644 --- a/core/tests/Drupal/KernelTests/Core/Theme/StableLibraryOverrideTest.php +++ b/core/tests/Drupal/KernelTests/Core/Theme/StableLibraryOverrideTest.php @@ -44,7 +44,13 @@ class StableLibraryOverrideTest extends KernelTestBase { * * @var string[] */ - protected $librariesToSkip = []; + protected $librariesToSkip = [ + // The system/runtests library supports the run-tests.sh --verbose option, + // which does not use a theme. This library is also a dependency of + // drupal.simpletest which will be deprecated. + // @see https://www.drupal.org/project/drupal/issues/3057420 + 'system/runtests', + ]; /** * {@inheritdoc}