3 namespace Drupal\simpletest\Form;
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Form\FormBase;
7 use Drupal\Core\Form\FormState;
8 use Drupal\Core\Form\FormStateInterface;
10 use Drupal\simpletest\TestDiscovery;
11 use Symfony\Component\DependencyInjection\ContainerInterface;
12 use Symfony\Component\HttpFoundation\RedirectResponse;
15 * Test results form for $test_id.
17 * Note that the UI strings are not translated because this form is also used
22 * @see simpletest_script_open_browser()
25 class SimpletestResultsForm extends FormBase {
28 * Associative array of themed result images keyed by status.
32 protected $statusImageMap;
35 * The database connection service.
37 * @var \Drupal\Core\Database\Connection
44 public static function create(ContainerInterface $container) {
46 $container->get('database')
51 * Constructs a \Drupal\simpletest\Form\SimpletestResultsForm object.
53 * @param \Drupal\Core\Database\Connection $database
54 * The database connection service.
56 public function __construct(Connection $database) {
57 $this->database = $database;
61 * Builds the status image map.
63 protected static function buildStatusImageMap() {
66 '#uri' => 'core/misc/icons/73b355/check.svg',
73 '#uri' => 'core/misc/icons/e32700/error.svg',
80 '#uri' => 'core/misc/icons/e29700/warning.svg',
83 '#alt' => 'Exception',
87 '#uri' => 'core/misc/icons/e29700/warning.svg',
93 'pass' => $image_pass,
94 'fail' => $image_fail,
95 'exception' => $image_exception,
96 'debug' => $image_debug,
103 public function getFormId() {
104 return 'simpletest_results_form';
110 public function buildForm(array $form, FormStateInterface $form_state, $test_id = NULL) {
111 // Make sure there are test results to display and a re-run is not being
114 if (is_numeric($test_id) && !$results = $this->getResults($test_id)) {
115 $this->messenger()->addError($this->t('No test results to display.'));
116 return new RedirectResponse($this->url('simpletest.test_form', [], ['absolute' => TRUE]));
119 // Load all classes and include CSS.
120 $form['#attached']['library'][] = 'simpletest/drupal.simpletest';
121 // Add the results form.
122 $filter = static::addResultForm($form, $results, $this->getStringTranslation());
125 $form['#action'] = $this->url('simpletest.result_form', ['test_id' => 're-run']);
127 '#type' => 'fieldset',
128 '#title' => $this->t('Actions'),
129 '#attributes' => ['class' => ['container-inline']],
133 $form['action']['filter'] = [
135 '#title' => 'Filter',
137 'all' => $this->t('All (@count)', ['@count' => count($filter['pass']) + count($filter['fail'])]),
138 'pass' => $this->t('Pass (@count)', ['@count' => count($filter['pass'])]),
139 'fail' => $this->t('Fail (@count)', ['@count' => count($filter['fail'])]),
142 $form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all');
144 // Categorized test classes for to be used with selected filter value.
145 $form['action']['filter_pass'] = [
147 '#default_value' => implode(',', $filter['pass']),
149 $form['action']['filter_fail'] = [
151 '#default_value' => implode(',', $filter['fail']),
154 $form['action']['op'] = [
156 '#value' => $this->t('Run tests'),
159 $form['action']['return'] = [
161 '#title' => $this->t('Return to list'),
162 '#url' => Url::fromRoute('simpletest.test_form'),
165 if (is_numeric($test_id)) {
166 simpletest_clean_results_table($test_id);
175 public function submitForm(array &$form, FormStateInterface $form_state) {
176 $pass = $form_state->getValue('filter_pass') ? explode(',', $form_state->getValue('filter_pass')) : [];
177 $fail = $form_state->getValue('filter_fail') ? explode(',', $form_state->getValue('filter_fail')) : [];
179 if ($form_state->getValue('filter') == 'all') {
180 $classes = array_merge($pass, $fail);
182 elseif ($form_state->getValue('filter') == 'pass') {
190 $form_state->setRedirect('simpletest.test_form');
195 $form_state_execute = new FormState();
196 foreach ($classes as $class) {
197 $form_state_execute->setValue(['tests', $class], $class);
200 // Submit the simpletest test form to rerun the tests.
201 // Under normal circumstances, a form object's submitForm() should never be
202 // called directly, FormBuilder::submitForm() should be called instead.
203 // However, it calls $form_state->setProgrammed(), which disables the Batch API.
204 $simpletest_test_form = SimpletestTestForm::create(\Drupal::getContainer());
205 $simpletest_test_form->buildForm($form_execute, $form_state_execute);
206 $simpletest_test_form->submitForm($form_execute, $form_state_execute);
207 if ($redirect = $form_state_execute->getRedirect()) {
208 $form_state->setRedirectUrl($redirect);
213 * Get test results for $test_id.
215 * @param int $test_id
216 * The test_id to retrieve results of.
219 * Array of results grouped by test_class.
221 protected function getResults($test_id) {
222 return $this->database->select('simpletest')
223 ->fields('simpletest')
224 ->condition('test_id', $test_id)
225 ->orderBy('test_class')
226 ->orderBy('message_id')
232 * Adds the result form to a $form.
234 * This is a static method so that run-tests.sh can use it to generate a
235 * results page completely external to Drupal. This is why the UI strings are
236 * not wrapped in t().
239 * The form to attach the results to.
240 * @param array $results
241 * The simpletest results.
244 * A list of tests the passed and failed. The array has two keys, 'pass' and
245 * 'fail'. Each contains a list of test classes.
247 * @see simpletest_script_open_browser()
250 public static function addResultForm(array &$form, array $results) {
251 // Transform the test results to be grouped by test class.
253 foreach ($results as $result) {
254 if (!isset($test_results[$result->test_class])) {
255 $test_results[$result->test_class] = [];
257 $test_results[$result->test_class][] = $result;
260 $image_status_map = static::buildStatusImageMap();
262 // Keep track of which test cases passed or failed.
268 // Summary result widget.
270 '#type' => 'fieldset',
271 '#title' => 'Results',
272 // Because this is used in a theme-less situation need to provide a
276 $form['result']['summary'] = $summary = [
277 '#theme' => 'simpletest_result_summary',
284 \Drupal::service('test_discovery')->registerTestNamespaces();
286 // Cycle through each test group.
293 ['colspan' => 2, 'data' => 'Status'],
295 $form['result']['results'] = [];
296 foreach ($test_results as $group => $assertions) {
297 // Create group details with summary information.
298 $info = TestDiscovery::getTestInfo($group);
299 $form['result']['results'][$group] = [
300 '#type' => 'details',
301 '#title' => $info['name'],
303 '#description' => $info['description'],
305 $form['result']['results'][$group]['summary'] = $summary;
306 $group_summary =& $form['result']['results'][$group]['summary'];
308 // Create table of assertions for the group.
310 foreach ($assertions as $assertion) {
312 $row[] = ['data' => ['#markup' => $assertion->message]];
313 $row[] = $assertion->message_group;
314 $row[] = \Drupal::service('file_system')->basename(($assertion->file));
315 $row[] = $assertion->line;
316 $row[] = $assertion->function;
317 $row[] = ['data' => $image_status_map[$assertion->status]];
319 $class = 'simpletest-' . $assertion->status;
320 if ($assertion->message_group == 'Debug') {
321 $class = 'simpletest-debug';
323 $rows[] = ['data' => $row, 'class' => [$class]];
325 $group_summary['#' . $assertion->status]++;
326 $form['result']['summary']['#' . $assertion->status]++;
328 $form['result']['results'][$group]['table'] = [
330 '#header' => $header,
334 // Set summary information.
335 $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0;
336 $form['result']['results'][$group]['#open'] = !$group_summary['#ok'];
338 // Store test group (class) as for use in filter.
339 $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group;
342 // Overall summary status.
343 $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0;