Simpletest Coverage - modules/simpletest/simpletest.pages.inc

1 <?php
2 // $Id: simpletest.pages.inc,v 1.14 2009/08/15 17:52:53 webchick Exp $
3
4 /**
5 * @file
6 * Page callbacks for simpletest module.
7 */
8
9 /**
10 * List tests arranged in groups that can be selected and run.
11 */
12 function simpletest_test_form() {
13 $form = array();
14
15 $form['tests'] = array(
16 '#type' => 'fieldset',
17 '#title' => t('Tests'),
18 '#description' => t('Select the test(s) or test group(s) you would like to run, and click <em>Run tests</em>.'),
19 );
20
21 $form['tests']['table'] = array(
22 '#theme' => 'simpletest_test_table',
23 );
24
25 // Generate the list of tests arranged by group.
26 $groups = simpletest_test_get_all();
27 foreach ($groups as $group => $tests) {
28 $form['tests']['table'][$group] = array(
29 '#collapsed' => TRUE,
30 );
31
32 foreach ($tests as $class => $info) {
33 $form['tests']['table'][$group][$class] = array(
34 '#type' => 'checkbox',
35 '#title' => $info['name'],
36 '#description' => $info['description'],
37 );
38 }
39 }
40
41 // Operation buttons.
42 $form['tests']['op'] = array(
43 '#type' => 'submit',
44 '#value' => t('Run tests'),
45 );
46 $form['clean'] = array(
47 '#type' => 'fieldset',
48 '#collapsible' => FALSE,
49 '#collapsed' => FALSE,
50 '#title' => t('Clean test environment'),
51 '#description' => t('Remove tables with the prefix "simpletest" and temporary directories that are left over from tests that crashed. This is intended for developers when creating tests.'),
52 );
53 $form['clean']['op'] = array(
54 '#type' => 'submit',
55 '#value' => t('Clean environment'),
56 '#submit' => array('simpletest_clean_environment'),
57 );
58
59 return $form;
60 }
61
62 /**
63 * Theme the test list generated by simpletest_test_form() into a table.
64 *
65 * @param $table Form array that represent a table.
66 * @return HTML output.
67 */
68 function theme_simpletest_test_table($table) {
69 drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
70 drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js');
71
72 // Create header for test selection table.
73 $header = array(
74 theme('table_select_header_cell'),
75 array('data' => t('Test'), 'class' => 'simpletest_test'),
76 array('data' => t('Description'), 'class' => 'simpletest_description'),
77 );
78
79 // Define the images used to expand/collapse the test groups.
80 $js = array(
81 'images' => array(
82 theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand'),
83 theme('image', 'misc/menu-expanded.png', 'Collapsed', 'Collapsed'),
84 ),
85 );
86
87 // Cycle through each test group and create a row.
88 $rows = array();
89 foreach (element_children($table) as $key) {
90 $element = &$table[$key];
91 $row = array();
92
93 // Make the class name safe for output on the page by replacing all
94 // non-word/decimal characters with a dash (-).
95 $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
96
97 // Select the right "expand"/"collapse" image, depending on whether the
98 // category is expanded (at least one test selected) or not.
99 $collapsed = !empty($element['#collapsed']);
100 $image_index = $collapsed ? 0 : 1;
101
102 // Place-holder for checkboxes to select group of tests.
103 $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all');
104
105 // Expand/collapse image and group title.
106 $row[] = array(
107 'data' => '<div class="simpletest-image" id="simpletest-test-group-' . $test_class . '"></div>&nbsp;' .
108 '<label for="' . $test_class . '-select-all" class="simpletest-group-label">' . $key . '</label>',
109 'style' => 'font-weight: bold;'
110 );
111
112 $row[] = '&nbsp;';
113
114 $rows[] = array('data' => $row, 'class' => 'simpletest-group');
115
116 // Add individual tests to group.
117 $current_js = array(
118 'testClass' => $test_class . '-test',
119 'testNames' => array(),
120 'imageDirection' => $image_index,
121 'clickActive' => FALSE,
122 );
123
124 // Sorting $element by children's #title attribute instead of by class name.
125 uasort($element, '_simpletest_sort_by_title');
126
127 // Cycle through each test within the current group.
128 foreach (element_children($element) as $test_name) {
129 $test = $element[$test_name];
130 $row = array();
131
132 $current_js['testNames'][] = 'edit-' . $test_name;
133
134 // Store test title and description so that checkbox won't render them.
135 $title = $test['#title'];
136 $description = $test['#description'];
137
138 unset($test['#title']);
139 unset($test['#description']);
140
141 // Test name is used to determine what tests to run.
142 $test['#name'] = $test_name;
143
144 $row[] = drupal_render($test);
145 $row[] = theme('indentation', 1) . '<label for="edit-' . $test_name . '">' . $title . '</label>';
146 $row[] = '<div class="description">' . $description . '</div>';
147
148 $rows[] = array('data' => $row, 'class' => $test_class . '-test' . ($collapsed ? ' js-hide' : ''));
149 }
150 $js['simpletest-test-group-' . $test_class] = $current_js;
151 unset($table[$key]);
152 }
153
154 // Add js array of settings.
155 drupal_add_js(array('simpleTest' => $js), 'setting');
156
157 if (empty($rows)) {
158 return '<strong>' . t('No tests to display.') . '</strong>';
159 }
160 else {
161 return theme('table', $header, $rows, array('id' => 'simpletest-form-table'));
162 }
163 }
164
165 /**
166 * Sort element by title instead of by class name.
167 */
168 function _simpletest_sort_by_title($a, $b) {
169 // This is for parts of $element that are not an array.
170 if (!isset($a['#title']) || !isset($b['#title'])) {
171 return 1;
172 }
173
174 return strcasecmp($a['#title'], $b['#title']);
175 }
176
177 /**
178 * Run selected tests.
179 */
180 function simpletest_test_form_submit($form, &$form_state) {
181 // Get list of tests.
182 $tests_list = array();
183 foreach ($form_state['values'] as $class_name => $value) {
184 if (class_exists($class_name) && $value === 1) {
185 $tests_list[] = $class_name;
186 }
187 }
188 if (count($tests_list) > 0 ) {
189 simpletest_run_tests($tests_list, 'drupal');
190 }
191 else {
192 drupal_set_message(t('No test(s) selected.'), 'error');
193 }
194 }
195
196 /**
197 * Test results form for $test_id.
198 */
199 function simpletest_result_form(&$form_state, $test_id) {
200 $form = array();
201
202 // Make sure there are test results to display and a re-run is not being performed.
203 $results = array();
204 if (is_numeric($test_id) && !$results = simpletest_result_get($test_id)) {
205 drupal_set_message(t('No test results to display.'), 'error');
206 drupal_goto('admin/config/development/testing');
207 return $form;
208 }
209
210 // Load all classes and include CSS.
211 drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
212
213 // Keep track of which test cases passed or failed.
214 $filter = array(
215 'pass' => array(),
216 'fail' => array(),
217 );
218
219 // Summary result fieldset.
220 $form['result'] = array(
221 '#type' => 'fieldset',
222 '#title' => t('Results'),
223 );
224 $form['result']['summary'] = $summary = array(
225 '#theme' => 'simpletest_result_summary',
226 '#pass' => 0,
227 '#fail' => 0,
228 '#exception' => 0,
229 '#debug' => 0,
230 );
231
232 // Cycle through each test group.
233 $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
234 $form['result']['results'] = array();
235 foreach ($results as $group => $assertions) {
236 // Create group fieldset with summary information.
237 $info = call_user_func(array($group, 'getInfo'));
238 $form['result']['results'][$group] = array(
239 '#type' => 'fieldset',
240 '#title' => $info['name'],
241 '#description' => $info['description'],
242 '#collapsible' => TRUE,
243 );
244 $form['result']['results'][$group]['summary'] = $summary;
245 $group_summary = &$form['result']['results'][$group]['summary'];
246
247 // Create table of assertions for the group.
248 $rows = array();
249 foreach ($assertions as $assertion) {
250 $row = array();
251 $row[] = $assertion->message;
252 $row[] = $assertion->message_group;
253 $row[] = basename($assertion->file);
254 $row[] = $assertion->line;
255 $row[] = $assertion->function;
256 $row[] = simpletest_result_status_image($assertion->status);
257
258 $class = 'simpletest-' . $assertion->status;
259 if ($assertion->message_group == 'Debug') {
260 $class = 'simpletest-debug';
261 }
262 $rows[] = array('data' => $row, 'class' => $class);
263
264 $group_summary['#' . $assertion->status]++;
265 $form['result']['summary']['#' . $assertion->status]++;
266 }
267 $form['result']['results'][$group]['table'] = array(
268 '#theme' => 'table',
269 '#header' => $header,
270 '#rows' => $rows,
271 );
272
273 // Set summary information.
274 $group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0;
275 $form['result']['results'][$group]['#collapsed'] = $group_summary['#ok'] && !$group_summary['#debug'];
276
277 // Store test group (class) as for use in filter.
278 $filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group;
279 }
280
281 // Overal summary status.
282 $form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0;
283
284 // Actions.
285 $form['#action'] = url('admin/config/development/testing/results/re-run');
286 $form['action'] = array(
287 '#type' => 'fieldset',
288 '#title' => t('Actions'),
289 '#attributes' => array('class' => 'container-inline'),
290 '#weight' => -11,
291 );
292
293 $form['action']['filter'] = array(
294 '#type' => 'select',
295 '#title' => 'Filter',
296 '#options' => array(
297 'all' => t('All (@count)', array('@count' => count($filter['pass']) + count($filter['fail']))),
298 'pass' => t('Pass (@count)', array('@count' => count($filter['pass']))),
299 'fail' => t('Fail (@count)', array('@count' => count($filter['fail']))),
300 ),
301 );
302 $form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all');
303
304 // Catagorized test classes for to be used with selected filter value.
305 $form['action']['filter_pass'] = array(
306 '#type' => 'hidden',
307 '#default_value' => implode(',', $filter['pass']),
308 );
309 $form['action']['filter_fail'] = array(
310 '#type' => 'hidden',
311 '#default_value' => implode(',', $filter['fail']),
312 );
313
314 $form['action']['op'] = array(
315 '#type' => 'submit',
316 '#value' => t('Run tests'),
317 );
318
319 $form['action']['return'] = array(
320 '#markup' => l(t('Return to list'), 'admin/config/development/testing'),
321 );
322
323 if (is_numeric($test_id)) {
324 simpletest_clean_results_table($test_id);
325 }
326
327 return $form;
328 }
329
330 /**
331 * Re-run the tests that match the filter.
332 */
333 function simpletest_result_form_submit($form, &$form_state) {
334 $pass = $form_state['values']['filter_pass'] ? explode(',', $form_state['values']['filter_pass']) : array();
335 $fail = $form_state['values']['filter_fail'] ? explode(',', $form_state['values']['filter_fail']) : array();
336
337 if ($form_state['values']['filter'] == 'all') {
338 $classes = array_merge($pass, $fail);
339 }
340 else if ($form_state['values']['filter'] == 'pass') {
341 $classes = $pass;
342 }
343 else {
344 $classes = $fail;
345 }
346
347 if (!$classes) {
348 $form_state['redirect'] = 'admin/config/development/testing';
349 return;
350 }
351
352 $form_state_execute = array('values' => array());
353 foreach ($classes as $class) {
354 $form_state_execute['values'][$class] = 1;
355 }
356
357 simpletest_test_form_submit(array(), $form_state_execute);
358 }
359
360 /**
361 * Add wrapper div with class based on summary status.
362 *
363 * @return HTML output.
364 */
365 function theme_simpletest_result_summary($form) {
366 return '<div class="simpletest-' . ($form['#ok'] ? 'pass' : 'fail') . '">' . _simpletest_format_summary_line($form) . '</div>';
367 }
368
369 /**
370 * Get test results for $test_id.
371 *
372 * @param $test_id The test_id to retrieve results of.
373 * @return Array of results grouped by test_class.
374 */
375 function simpletest_result_get($test_id) {
376 $results = db_select('simpletest')
377 ->fields('simpletest')
378 ->condition('test_id', $test_id)
379 ->orderBy('test_class')
380 ->orderBy('message_id')
381 ->execute();
382
383 $test_results = array();
384 foreach ($results as $result) {
385 if (!isset($test_results[$result->test_class])) {
386 $test_results[$result->test_class] = array();
387 }
388 $test_results[$result->test_class][] = $result;
389 }
390 return $test_results;
391 }
392
393 /**
394 * Get the appropriate image for the status.
395 *
396 * @param $status Status string, either: pass, fail, exception.
397 * @return HTML image or false.
398 */
399 function simpletest_result_status_image($status) {
400 // $map does not use drupal_static() as its value never changes.
401 static $map;
402
403 if (!isset($map)) {
404 $map = array(
405 'pass' => theme('image', 'misc/watchdog-ok.png'),
406 'fail' => theme('image', 'misc/watchdog-error.png'),
407 'exception' => theme('image', 'misc/watchdog-warning.png'),
408 'debug' => theme('image', 'misc/watchdog-warning.png'),
409 );
410 }
411 if (isset($map[$status])) {
412 return $map[$status];
413 }
414 return FALSE;
415 }
416
417 /**
418 * Provides settings form for SimpleTest variables.
419 */
420 function simpletest_settings_form(&$form_state) {
421 $form = array();
422
423 $form['general'] = array(
424 '#type' => 'fieldset',
425 '#title' => t('General'),
426 );
427 $form['general']['simpletest_clear_results'] = array(
428 '#type' => 'checkbox',
429 '#title' => t('Clear results after each complete test suite run'),
430 '#description' => t('By default SimpleTest will clear the results after they have been viewed on the results page, but in some cases it may be useful to leave the results in the database. The results can then be viewed at <em>admin/config/development/testing/[test_id]</em>. The test ID can be found in the database, simpletest table, or kept track of when viewing the results the first time. Additionally, some modules may provide more analaysis or features that require this setting to be disabled.'),
431 '#default_value' => variable_get('simpletest_clear_results', TRUE),
432 );
433 $form['general']['simpletest_verbose'] = array(
434 '#type' => 'checkbox',
435 '#title' => t('Provide verbose information when running tests'),
436 '#description' => t('The verbose data will be printed along with the standard assertions and is useful for debugging. The verbose data will be erased between each test suite run. The verbose data output is very detailed and should only be used when debugging.'),
437 '#default_value' => variable_get('simpletest_verbose', FALSE),
438 );
439
440 $form['httpauth'] = array(
441 '#type' => 'fieldset',
442 '#title' => t('HTTP authentication credentials'),
443 '#description' => t('HTTP auth settings to be used by the SimpleTest browser during testing. Useful when the site requires basic HTTP authentication.'),
444 );
445 $form['httpauth']['simpletest_username'] = array(
446 '#type' => 'textfield',
447 '#title' => t('Username'),
448 '#default_value' => variable_get('simpletest_username', ''),
449 );
450 $form['httpauth']['simpletest_password'] = array(
451 '#type' => 'textfield',
452 '#title' => t('Password'),
453 '#default_value' => variable_get('simpletest_password', ''),
454 );
455
456 return system_settings_form($form);
457 }
458

Legend

Missed
lines code that were not excersized during program execution.
Covered
lines code were excersized during program execution.
Comment/non executable
Comment or non-executable line of code.
Dead
lines of code that according to xdebug could not be executed. This is counted as coverage code because in almost all cases it is code that runnable.