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
20 * @see simpletest_script_open_browser()
23 class SimpletestResultsForm extends FormBase {
26 * Associative array of themed result images keyed by status.
30 protected $statusImageMap;
33 * The database connection service.
35 * @var \Drupal\Core\Database\Connection
42 public static function create(ContainerInterface $container) {
44 $container->get('database')
49 * Constructs a \Drupal\simpletest\Form\SimpletestResultsForm object.
51 * @param \Drupal\Core\Database\Connection $database
52 * The database connection service.
54 public function __construct(Connection $database) {
55 $this->database = $database;
59 * Builds the status image map.
61 protected static function buildStatusImageMap() {
64 '#uri' => 'core/misc/icons/73b355/check.svg',
71 '#uri' => 'core/misc/icons/e32700/error.svg',
78 '#uri' => 'core/misc/icons/e29700/warning.svg',
81 '#alt' => 'Exception',
85 '#uri' => 'core/misc/icons/e29700/warning.svg',
91 'pass' => $image_pass,
92 'fail' => $image_fail,
93 'exception' => $image_exception,
94 'debug' => $image_debug,
101 public function getFormId() {
102 return 'simpletest_results_form';
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
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]));
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());
123 $form['#action'] = $this->url('simpletest.result_form', ['test_id' => 're-run']);
125 '#type' => 'fieldset',
126 '#title' => $this->t('Actions'),
127 '#attributes' => ['class' => ['container-inline']],
131 $form['action']['filter'] = [
133 '#title' => 'Filter',
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'])]),
140 $form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all');
142 // Categorized test classes for to be used with selected filter value.
143 $form['action']['filter_pass'] = [
145 '#default_value' => implode(',', $filter['pass']),
147 $form['action']['filter_fail'] = [
149 '#default_value' => implode(',', $filter['fail']),
152 $form['action']['op'] = [
154 '#value' => $this->t('Run tests'),
157 $form['action']['return'] = [
159 '#title' => $this->t('Return to list'),
160 '#url' => Url::fromRoute('simpletest.test_form'),
163 if (is_numeric($test_id)) {
164 simpletest_clean_results_table($test_id);
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')) : [];
177 if ($form_state->getValue('filter') == 'all') {
178 $classes = array_merge($pass, $fail);
180 elseif ($form_state->getValue('filter') == 'pass') {
188 $form_state->setRedirect('simpletest.test_form');
193 $form_state_execute = new FormState();
194 foreach ($classes as $class) {
195 $form_state_execute->setValue(['tests', $class], $class);
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);
211 * Get test results for $test_id.
213 * @param int $test_id
214 * The test_id to retrieve results of.
217 * Array of results grouped by test_class.
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')
230 * Adds the result form to a $form.
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().
237 * The form to attach the results to.
238 * @param array $results
239 * The simpletest results.
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.
245 * @see simpletest_script_open_browser()
248 public static function addResultForm(array &$form, array $results) {
249 // Transform the test results to be grouped by test class.
251 foreach ($results as $result) {
252 if (!isset($test_results[$result->test_class])) {
253 $test_results[$result->test_class] = [];
255 $test_results[$result->test_class][] = $result;
258 $image_status_map = static::buildStatusImageMap();
260 // Keep track of which test cases passed or failed.
266 // Summary result widget.
268 '#type' => 'fieldset',
269 '#title' => 'Results',
270 // Because this is used in a theme-less situation need to provide a
274 $form['result']['summary'] = $summary = [
275 '#theme' => 'simpletest_result_summary',
282 \Drupal::service('test_discovery')->registerTestNamespaces();
284 // Cycle through each test group.
291 ['colspan' => 2, 'data' => 'Status']
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'],
301 '#description' => $info['description'],
303 $form['result']['results'][$group]['summary'] = $summary;
304 $group_summary =& $form['result']['results'][$group]['summary'];
306 // Create table of assertions for the group.
308 foreach ($assertions as $assertion) {
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]];
317 $class = 'simpletest-' . $assertion->status;
318 if ($assertion->message_group == 'Debug') {
319 $class = 'simpletest-debug';
321 $rows[] = ['data' => $row, 'class' => [$class]];
323 $group_summary['#' . $assertion->status]++;
324 $form['result']['summary']['#' . $assertion->status]++;
326 $form['result']['results'][$group]['table'] = [
328 '#header' => $header,
332 // Set summary information.
333 $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0;
334 $form['result']['results'][$group]['#open'] = !$group_summary['#ok'];
336 // Store test group (class) as for use in filter.
337 $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group;
340 // Overall summary status.
341 $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0;