Security update for Core, with self-updated composer
[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  * @see simpletest_script_open_browser()
21  * @see run-tests.sh
22  */
23 class SimpletestResultsForm extends FormBase {
24
25   /**
26    * Associative array of themed result images keyed by status.
27    *
28    * @var array
29    */
30   protected $statusImageMap;
31
32   /**
33    * The database connection service.
34    *
35    * @var \Drupal\Core\Database\Connection
36    */
37   protected $database;
38
39   /**
40    * {@inheritdoc}
41    */
42   public static function create(ContainerInterface $container) {
43     return new static(
44       $container->get('database')
45     );
46   }
47
48   /**
49    * Constructs a \Drupal\simpletest\Form\SimpletestResultsForm object.
50    *
51    * @param \Drupal\Core\Database\Connection $database
52    *   The database connection service.
53    */
54   public function __construct(Connection $database) {
55     $this->database = $database;
56   }
57
58   /**
59    * Builds the status image map.
60    */
61   protected static function buildStatusImageMap() {
62     $image_pass = [
63       '#theme' => 'image',
64       '#uri' => 'core/misc/icons/73b355/check.svg',
65       '#width' => 18,
66       '#height' => 18,
67       '#alt' => 'Pass',
68     ];
69     $image_fail = [
70       '#theme' => 'image',
71       '#uri' => 'core/misc/icons/e32700/error.svg',
72       '#width' => 18,
73       '#height' => 18,
74       '#alt' => 'Fail',
75     ];
76     $image_exception = [
77       '#theme' => 'image',
78       '#uri' => 'core/misc/icons/e29700/warning.svg',
79       '#width' => 18,
80       '#height' => 18,
81       '#alt' => 'Exception',
82     ];
83     $image_debug = [
84       '#theme' => 'image',
85       '#uri' => 'core/misc/icons/e29700/warning.svg',
86       '#width' => 18,
87       '#height' => 18,
88       '#alt' => 'Debug',
89     ];
90     return [
91       'pass' => $image_pass,
92       'fail' => $image_fail,
93       'exception' => $image_exception,
94       'debug' => $image_debug,
95     ];
96   }
97
98   /**
99    * {@inheritdoc}
100    */
101   public function getFormId() {
102     return 'simpletest_results_form';
103   }
104
105   /**
106    * {@inheritdoc}
107    */
108   public function buildForm(array $form, FormStateInterface $form_state, $test_id = NULL) {
109     // Make sure there are test results to display and a re-run is not being
110     // performed.
111     $results = [];
112     if (is_numeric($test_id) && !$results = $this->getResults($test_id)) {
113       drupal_set_message($this->t('No test results to display.'), 'error');
114       return new RedirectResponse($this->url('simpletest.test_form', [], ['absolute' => TRUE]));
115     }
116
117     // Load all classes and include CSS.
118     $form['#attached']['library'][] = 'simpletest/drupal.simpletest';
119     // Add the results form.
120     $filter = static::addResultForm($form, $results, $this->getStringTranslation());
121
122     // Actions.
123     $form['#action'] = $this->url('simpletest.result_form', ['test_id' => 're-run']);
124     $form['action'] = [
125       '#type' => 'fieldset',
126       '#title' => $this->t('Actions'),
127       '#attributes' => ['class' => ['container-inline']],
128       '#weight' => -11,
129     ];
130
131     $form['action']['filter'] = [
132       '#type' => 'select',
133       '#title' => 'Filter',
134       '#options' => [
135         'all' => $this->t('All (@count)', ['@count' => count($filter['pass']) + count($filter['fail'])]),
136         'pass' => $this->t('Pass (@count)', ['@count' => count($filter['pass'])]),
137         'fail' => $this->t('Fail (@count)', ['@count' => count($filter['fail'])]),
138       ],
139     ];
140     $form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all');
141
142     // Categorized test classes for to be used with selected filter value.
143     $form['action']['filter_pass'] = [
144       '#type' => 'hidden',
145       '#default_value' => implode(',', $filter['pass']),
146     ];
147     $form['action']['filter_fail'] = [
148       '#type' => 'hidden',
149       '#default_value' => implode(',', $filter['fail']),
150     ];
151
152     $form['action']['op'] = [
153       '#type' => 'submit',
154       '#value' => $this->t('Run tests'),
155     ];
156
157     $form['action']['return'] = [
158       '#type' => 'link',
159       '#title' => $this->t('Return to list'),
160       '#url' => Url::fromRoute('simpletest.test_form'),
161     ];
162
163     if (is_numeric($test_id)) {
164       simpletest_clean_results_table($test_id);
165     }
166
167     return $form;
168   }
169
170   /**
171    * {@inheritdoc}
172    */
173   public function submitForm(array &$form, FormStateInterface $form_state) {
174     $pass = $form_state->getValue('filter_pass') ? explode(',', $form_state->getValue('filter_pass')) : [];
175     $fail = $form_state->getValue('filter_fail') ? explode(',', $form_state->getValue('filter_fail')) : [];
176
177     if ($form_state->getValue('filter') == 'all') {
178       $classes = array_merge($pass, $fail);
179     }
180     elseif ($form_state->getValue('filter') == 'pass') {
181       $classes = $pass;
182     }
183     else {
184       $classes = $fail;
185     }
186
187     if (!$classes) {
188       $form_state->setRedirect('simpletest.test_form');
189       return;
190     }
191
192     $form_execute = [];
193     $form_state_execute = new FormState();
194     foreach ($classes as $class) {
195       $form_state_execute->setValue(['tests', $class], $class);
196     }
197
198     // Submit the simpletest test form to rerun the tests.
199     // Under normal circumstances, a form object's submitForm() should never be
200     // called directly, FormBuilder::submitForm() should be called instead.
201     // However, it calls $form_state->setProgrammed(), which disables the Batch API.
202     $simpletest_test_form = SimpletestTestForm::create(\Drupal::getContainer());
203     $simpletest_test_form->buildForm($form_execute, $form_state_execute);
204     $simpletest_test_form->submitForm($form_execute, $form_state_execute);
205     if ($redirect = $form_state_execute->getRedirect()) {
206       $form_state->setRedirectUrl($redirect);
207     }
208   }
209
210   /**
211    * Get test results for $test_id.
212    *
213    * @param int $test_id
214    *   The test_id to retrieve results of.
215    *
216    * @return array
217    *   Array of results grouped by test_class.
218    */
219   protected function getResults($test_id) {
220     return $this->database->select('simpletest')
221       ->fields('simpletest')
222       ->condition('test_id', $test_id)
223       ->orderBy('test_class')
224       ->orderBy('message_id')
225       ->execute()
226       ->fetchAll();
227   }
228
229   /**
230    * Adds the result form to a $form.
231    *
232    * This is a static method so that run-tests.sh can use it to generate a
233    * results page completely external to Drupal. This is why the UI strings are
234    * not wrapped in t().
235    *
236    * @param array $form
237    *   The form to attach the results to.
238    * @param array $results
239    *   The simpletest results.
240    *
241    * @return array
242    *   A list of tests the passed and failed. The array has two keys, 'pass' and
243    *   'fail'. Each contains a list of test classes.
244    *
245    * @see simpletest_script_open_browser()
246    * @see run-tests.sh
247    */
248   public static function addResultForm(array &$form, array $results) {
249     // Transform the test results to be grouped by test class.
250     $test_results = [];
251     foreach ($results as $result) {
252       if (!isset($test_results[$result->test_class])) {
253         $test_results[$result->test_class] = [];
254       }
255       $test_results[$result->test_class][] = $result;
256     }
257
258     $image_status_map = static::buildStatusImageMap();
259
260     // Keep track of which test cases passed or failed.
261     $filter = [
262       'pass' => [],
263       'fail' => [],
264     ];
265
266     // Summary result widget.
267     $form['result'] = [
268       '#type' => 'fieldset',
269       '#title' => 'Results',
270       // Because this is used in a theme-less situation need to provide a
271       // default.
272       '#attributes' => [],
273     ];
274     $form['result']['summary'] = $summary = [
275       '#theme' => 'simpletest_result_summary',
276       '#pass' => 0,
277       '#fail' => 0,
278       '#exception' => 0,
279       '#debug' => 0,
280     ];
281
282     \Drupal::service('test_discovery')->registerTestNamespaces();
283
284     // Cycle through each test group.
285     $header = [
286       'Message',
287       'Group',
288       'Filename',
289       'Line',
290       'Function',
291       ['colspan' => 2, 'data' => 'Status']
292     ];
293     $form['result']['results'] = [];
294     foreach ($test_results as $group => $assertions) {
295       // Create group details with summary information.
296       $info = TestDiscovery::getTestInfo($group);
297       $form['result']['results'][$group] = [
298         '#type' => 'details',
299         '#title' => $info['name'],
300         '#open' => TRUE,
301         '#description' => $info['description'],
302       ];
303       $form['result']['results'][$group]['summary'] = $summary;
304       $group_summary =& $form['result']['results'][$group]['summary'];
305
306       // Create table of assertions for the group.
307       $rows = [];
308       foreach ($assertions as $assertion) {
309         $row = [];
310         $row[] = ['data' => ['#markup' => $assertion->message]];
311         $row[] = $assertion->message_group;
312         $row[] = \Drupal::service('file_system')->basename(($assertion->file));
313         $row[] = $assertion->line;
314         $row[] = $assertion->function;
315         $row[] = ['data' => $image_status_map[$assertion->status]];
316
317         $class = 'simpletest-' . $assertion->status;
318         if ($assertion->message_group == 'Debug') {
319           $class = 'simpletest-debug';
320         }
321         $rows[] = ['data' => $row, 'class' => [$class]];
322
323         $group_summary['#' . $assertion->status]++;
324         $form['result']['summary']['#' . $assertion->status]++;
325       }
326       $form['result']['results'][$group]['table'] = [
327         '#type' => 'table',
328         '#header' => $header,
329         '#rows' => $rows,
330       ];
331
332       // Set summary information.
333       $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0;
334       $form['result']['results'][$group]['#open'] = !$group_summary['#ok'];
335
336       // Store test group (class) as for use in filter.
337       $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group;
338     }
339
340     // Overall summary status.
341     $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0;
342
343     return $filter;
344   }
345
346 }