Further Drupal 8.6.4 changes. Some core files were not committed before a commit...
[yaffs-website] / web / core / modules / simpletest / src / Form / SimpletestResultsForm.php
1 <?php
2
3 namespace Drupal\simpletest\Form;
4
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Form\FormBase;
7 use Drupal\Core\Form\FormState;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\Core\Url;
10 use Drupal\simpletest\TestDiscovery;
11 use Symfony\Component\DependencyInjection\ContainerInterface;
12 use Symfony\Component\HttpFoundation\RedirectResponse;
13
14 /**
15  * Test results form for $test_id.
16  *
17  * Note that the UI strings are not translated because this form is also used
18  * from run-tests.sh.
19  *
20  * @internal
21  *
22  * @see simpletest_script_open_browser()
23  * @see run-tests.sh
24  */
25 class SimpletestResultsForm extends FormBase {
26
27   /**
28    * Associative array of themed result images keyed by status.
29    *
30    * @var array
31    */
32   protected $statusImageMap;
33
34   /**
35    * The database connection service.
36    *
37    * @var \Drupal\Core\Database\Connection
38    */
39   protected $database;
40
41   /**
42    * {@inheritdoc}
43    */
44   public static function create(ContainerInterface $container) {
45     return new static(
46       $container->get('database')
47     );
48   }
49
50   /**
51    * Constructs a \Drupal\simpletest\Form\SimpletestResultsForm object.
52    *
53    * @param \Drupal\Core\Database\Connection $database
54    *   The database connection service.
55    */
56   public function __construct(Connection $database) {
57     $this->database = $database;
58   }
59
60   /**
61    * Builds the status image map.
62    */
63   protected static function buildStatusImageMap() {
64     $image_pass = [
65       '#theme' => 'image',
66       '#uri' => 'core/misc/icons/73b355/check.svg',
67       '#width' => 18,
68       '#height' => 18,
69       '#alt' => 'Pass',
70     ];
71     $image_fail = [
72       '#theme' => 'image',
73       '#uri' => 'core/misc/icons/e32700/error.svg',
74       '#width' => 18,
75       '#height' => 18,
76       '#alt' => 'Fail',
77     ];
78     $image_exception = [
79       '#theme' => 'image',
80       '#uri' => 'core/misc/icons/e29700/warning.svg',
81       '#width' => 18,
82       '#height' => 18,
83       '#alt' => 'Exception',
84     ];
85     $image_debug = [
86       '#theme' => 'image',
87       '#uri' => 'core/misc/icons/e29700/warning.svg',
88       '#width' => 18,
89       '#height' => 18,
90       '#alt' => 'Debug',
91     ];
92     return [
93       'pass' => $image_pass,
94       'fail' => $image_fail,
95       'exception' => $image_exception,
96       'debug' => $image_debug,
97     ];
98   }
99
100   /**
101    * {@inheritdoc}
102    */
103   public function getFormId() {
104     return 'simpletest_results_form';
105   }
106
107   /**
108    * {@inheritdoc}
109    */
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
112     // performed.
113     $results = [];
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]));
117     }
118
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());
123
124     // Actions.
125     $form['#action'] = $this->url('simpletest.result_form', ['test_id' => 're-run']);
126     $form['action'] = [
127       '#type' => 'fieldset',
128       '#title' => $this->t('Actions'),
129       '#attributes' => ['class' => ['container-inline']],
130       '#weight' => -11,
131     ];
132
133     $form['action']['filter'] = [
134       '#type' => 'select',
135       '#title' => 'Filter',
136       '#options' => [
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'])]),
140       ],
141     ];
142     $form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all');
143
144     // Categorized test classes for to be used with selected filter value.
145     $form['action']['filter_pass'] = [
146       '#type' => 'hidden',
147       '#default_value' => implode(',', $filter['pass']),
148     ];
149     $form['action']['filter_fail'] = [
150       '#type' => 'hidden',
151       '#default_value' => implode(',', $filter['fail']),
152     ];
153
154     $form['action']['op'] = [
155       '#type' => 'submit',
156       '#value' => $this->t('Run tests'),
157     ];
158
159     $form['action']['return'] = [
160       '#type' => 'link',
161       '#title' => $this->t('Return to list'),
162       '#url' => Url::fromRoute('simpletest.test_form'),
163     ];
164
165     if (is_numeric($test_id)) {
166       simpletest_clean_results_table($test_id);
167     }
168
169     return $form;
170   }
171
172   /**
173    * {@inheritdoc}
174    */
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')) : [];
178
179     if ($form_state->getValue('filter') == 'all') {
180       $classes = array_merge($pass, $fail);
181     }
182     elseif ($form_state->getValue('filter') == 'pass') {
183       $classes = $pass;
184     }
185     else {
186       $classes = $fail;
187     }
188
189     if (!$classes) {
190       $form_state->setRedirect('simpletest.test_form');
191       return;
192     }
193
194     $form_execute = [];
195     $form_state_execute = new FormState();
196     foreach ($classes as $class) {
197       $form_state_execute->setValue(['tests', $class], $class);
198     }
199
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);
209     }
210   }
211
212   /**
213    * Get test results for $test_id.
214    *
215    * @param int $test_id
216    *   The test_id to retrieve results of.
217    *
218    * @return array
219    *   Array of results grouped by test_class.
220    */
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')
227       ->execute()
228       ->fetchAll();
229   }
230
231   /**
232    * Adds the result form to a $form.
233    *
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().
237    *
238    * @param array $form
239    *   The form to attach the results to.
240    * @param array $results
241    *   The simpletest results.
242    *
243    * @return array
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.
246    *
247    * @see simpletest_script_open_browser()
248    * @see run-tests.sh
249    */
250   public static function addResultForm(array &$form, array $results) {
251     // Transform the test results to be grouped by test class.
252     $test_results = [];
253     foreach ($results as $result) {
254       if (!isset($test_results[$result->test_class])) {
255         $test_results[$result->test_class] = [];
256       }
257       $test_results[$result->test_class][] = $result;
258     }
259
260     $image_status_map = static::buildStatusImageMap();
261
262     // Keep track of which test cases passed or failed.
263     $filter = [
264       'pass' => [],
265       'fail' => [],
266     ];
267
268     // Summary result widget.
269     $form['result'] = [
270       '#type' => 'fieldset',
271       '#title' => 'Results',
272       // Because this is used in a theme-less situation need to provide a
273       // default.
274       '#attributes' => [],
275     ];
276     $form['result']['summary'] = $summary = [
277       '#theme' => 'simpletest_result_summary',
278       '#pass' => 0,
279       '#fail' => 0,
280       '#exception' => 0,
281       '#debug' => 0,
282     ];
283
284     \Drupal::service('test_discovery')->registerTestNamespaces();
285
286     // Cycle through each test group.
287     $header = [
288       'Message',
289       'Group',
290       'Filename',
291       'Line',
292       'Function',
293       ['colspan' => 2, 'data' => 'Status'],
294     ];
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'],
302         '#open' => TRUE,
303         '#description' => $info['description'],
304       ];
305       $form['result']['results'][$group]['summary'] = $summary;
306       $group_summary =& $form['result']['results'][$group]['summary'];
307
308       // Create table of assertions for the group.
309       $rows = [];
310       foreach ($assertions as $assertion) {
311         $row = [];
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]];
318
319         $class = 'simpletest-' . $assertion->status;
320         if ($assertion->message_group == 'Debug') {
321           $class = 'simpletest-debug';
322         }
323         $rows[] = ['data' => $row, 'class' => [$class]];
324
325         $group_summary['#' . $assertion->status]++;
326         $form['result']['summary']['#' . $assertion->status]++;
327       }
328       $form['result']['results'][$group]['table'] = [
329         '#type' => 'table',
330         '#header' => $header,
331         '#rows' => $rows,
332       ];
333
334       // Set summary information.
335       $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0;
336       $form['result']['results'][$group]['#open'] = !$group_summary['#ok'];
337
338       // Store test group (class) as for use in filter.
339       $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group;
340     }
341
342     // Overall summary status.
343     $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0;
344
345     return $filter;
346   }
347
348 }