SimpletestResultsForm.php

Namespace

Drupal\simpletest\Form

File

core/modules/simpletest/src/Form/SimpletestResultsForm.php

View source
<?php

namespace Drupal\simpletest\Form;

use Drupal\Core\Database\Connection;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Test\EnvironmentCleanerInterface;
use Drupal\Core\Url;
use Drupal\simpletest\TestDiscovery;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Test results form for $test_id.
 *
 * Note that the UI strings are not translated because this form is also used
 * from run-tests.sh.
 *
 * @internal
 *
 * @see simpletest_script_open_browser()
 * @see run-tests.sh
 */
class SimpletestResultsForm extends FormBase {
    
    /**
     * Associative array of themed result images keyed by status.
     *
     * @var array
     */
    protected $statusImageMap;
    
    /**
     * The database connection service.
     *
     * @var \Drupal\Core\Database\Connection
     */
    protected $database;
    
    /**
     * The environment cleaner service.
     *
     * @var \Drupal\Core\Test\EnvironmentCleanerInterface
     */
    protected $cleaner;
    
    /**
     * {@inheritdoc}
     */
    public static function create(ContainerInterface $container) {
        return new static($container->get('database'), $container->get('environment_cleaner'));
    }
    
    /**
     * Constructs a \Drupal\simpletest\Form\SimpletestResultsForm object.
     *
     * @param \Drupal\Core\Database\Connection $database
     *   The database connection service.
     */
    public function __construct(Connection $database, EnvironmentCleanerInterface $cleaner) {
        $this->database = $database;
        $this->cleaner = $cleaner;
    }
    
    /**
     * 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))) {
            $this->messenger()
                ->addError($this->t('No test results to display.'));
            return $this->redirect('simpletest.test_form');
        }
        // 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'] = Url::fromRoute('simpletest.result_form', [
            'test_id' => 're-run',
        ])->toString();
        $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)) {
            $this->cleaner
                ->cleanResultsTable($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;
    }

}

Classes

Title Deprecated Summary
SimpletestResultsForm Test results form for $test_id.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.