get('database') ); } /** * Constructs a \Drupal\simpletest\Form\SimpletestResultsForm object. * * @param \Drupal\Core\Database\Connection $database * The database connection service. */ public function __construct(Connection $database) { $this->database = $database; } /** * 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, ]; } /** * {@inheritdoc} */ public function getFormId() { return 'simpletest_results_form'; } /** * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state, $test_id = NULL) { // Make sure there are test results to display and a re-run is not being // performed. $results = []; if (is_numeric($test_id) && !$results = $this->getResults($test_id)) { drupal_set_message($this->t('No test results to display.'), 'error'); return new RedirectResponse($this->url('simpletest.test_form', [], ['absolute' => TRUE])); } // Load all classes and include CSS. $form['#attached']['library'][] = 'simpletest/drupal.simpletest'; // Add the results form. $filter = static::addResultForm($form, $results, $this->getStringTranslation()); // Actions. $form['#action'] = $this->url('simpletest.result_form', ['test_id' => 're-run']); $form['action'] = [ '#type' => 'fieldset', '#title' => $this->t('Actions'), '#attributes' => ['class' => ['container-inline']], '#weight' => -11, ]; $form['action']['filter'] = [ '#type' => 'select', '#title' => 'Filter', '#options' => [ 'all' => $this->t('All (@count)', ['@count' => count($filter['pass']) + count($filter['fail'])]), 'pass' => $this->t('Pass (@count)', ['@count' => count($filter['pass'])]), 'fail' => $this->t('Fail (@count)', ['@count' => count($filter['fail'])]), ], ]; $form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all'); // Categorized test classes for to be used with selected filter value. $form['action']['filter_pass'] = [ '#type' => 'hidden', '#default_value' => implode(',', $filter['pass']), ]; $form['action']['filter_fail'] = [ '#type' => 'hidden', '#default_value' => implode(',', $filter['fail']), ]; $form['action']['op'] = [ '#type' => 'submit', '#value' => $this->t('Run tests'), ]; $form['action']['return'] = [ '#type' => 'link', '#title' => $this->t('Return to list'), '#url' => Url::fromRoute('simpletest.test_form'), ]; if (is_numeric($test_id)) { simpletest_clean_results_table($test_id); } return $form; } /** * {@inheritdoc} */ public function submitForm(array &$form, FormStateInterface $form_state) { $pass = $form_state->getValue('filter_pass') ? explode(',', $form_state->getValue('filter_pass')) : []; $fail = $form_state->getValue('filter_fail') ? explode(',', $form_state->getValue('filter_fail')) : []; if ($form_state->getValue('filter') == 'all') { $classes = array_merge($pass, $fail); } elseif ($form_state->getValue('filter') == 'pass') { $classes = $pass; } else { $classes = $fail; } if (!$classes) { $form_state->setRedirect('simpletest.test_form'); return; } $form_execute = []; $form_state_execute = new FormState(); foreach ($classes as $class) { $form_state_execute->setValue(['tests', $class], $class); } // Submit the simpletest test form to rerun the tests. // Under normal circumstances, a form object's submitForm() should never be // called directly, FormBuilder::submitForm() should be called instead. // However, it calls $form_state->setProgrammed(), which disables the Batch API. $simpletest_test_form = SimpletestTestForm::create(\Drupal::getContainer()); $simpletest_test_form->buildForm($form_execute, $form_state_execute); $simpletest_test_form->submitForm($form_execute, $form_state_execute); if ($redirect = $form_state_execute->getRedirect()) { $form_state->setRedirectUrl($redirect); } } /** * Get test results for $test_id. * * @param int $test_id * The test_id to retrieve results of. * * @return array * Array of results grouped by test_class. */ protected function getResults($test_id) { return $this->database->select('simpletest') ->fields('simpletest') ->condition('test_id', $test_id) ->orderBy('test_class') ->orderBy('message_id') ->execute() ->fetchAll(); } /** * 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 */ 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; } }