Code coverage for /20080809/modules/simpletest/simpletest.module

Line #Times calledCode
1
<?php
2
// $Id: simpletest.module,v 1.8 2008/08/09 12:41:22 dries Exp $
3
4
/**
5
 * Implementation of hook_help().
6
 */
713
function simpletest_help($path, $arg) {
8
  switch ($path) {
99
    case 'admin/help#simpletest':
100
      $output  = '<p>' . t('The SimpleTest module is a framework for
running automated unit tests in Drupal. It can be used to verify a working
state of Drupal before and after any code changes, or as a means for
developers to write and execute tests for their modules.') .'</p>';
110
      $output .= '<p>' . t('Visit <a href="@admin-simpletest">Administer >>
Site building >> SimpleTest</a> to display a list of available tests. For
comprehensive testing, select <em>all</em> tests, or individually select
tests for more targeted testing. Note that it might take several minutes
for all tests to complete.)', array('@admin-simpletest' =>
url('admin/build/testing'))) .'</p>';
120
      $output .= '<p>' . t('After the tests have run, a message will be
displayed next to each test group indicating whether tests within it
passed, failed, or had exceptions. A pass means that a test returned the
expected results, while fail means that it did not. An exception normally
indicates an error outside of the test, such as a PHP warning or notice. If
there were fails or exceptions, the results are expanded, and the tests
that had issues will be indicated in red or pink rows. Use these results to
refine your code and tests until all tests return a pass.') .'</p>';
130
      $output .= '<p>' . t('For more information on creating and modifying
your own tests, see the <a href="@simpletest-api">SimpleTest API
Documentation</a> in the Drupal handbook.', array('@simpletest-api' =>
'http://drupal.org/simpletest')) .'</p>';
140
      $output .= '<p>' . t('For more information, see the online handbook
entry for <a href="@simpletest">SimpleTest module</a>.',
array('@simpletest' => 'http://drupal.org/handbook/modules/simpletest'))
.'</p>';
150
      return $output;
160
  }
179
}
18
19
/**
20
 * Implementation of hook_menu().
21
 */
2213
function simpletest_menu() {
231
  $items['admin/build/testing'] = array(
241
    'title' => 'Testing',
251
    'page callback' => 'drupal_get_form',
261
    'page arguments' => array('simpletest_test_form'),
271
    'description' => 'Run tests against Drupal core and your active
modules. These tests help assure that your site code is working as
designed.',
281
    'access arguments' => array('administer unit tests'),
29
  );
301
  return $items;
310
}
32
33
/**
34
 * Implementation of hook_perm().
35
 */
3613
function simpletest_perm() {
37
  return array(
381
    'administer unit tests' => t('Manage and run automated testing.
%warning', array('%warning' => t('Warning: Give to trusted roles only; this
permission has security implications.'))),
391
  );
400
}
41
42
/**
43
 * Implemenation of hook_theme().
44
 */
4513
function simpletest_theme() {
46
  return array(
47
    'simpletest_test_form' => array(
482
      'arguments' => array('form' => NULL)
492
    ),
50
    'simpletest_result_summary' => array(
512
      'arguments' => array('form' => NULL)
522
    ),
532
  );
540
}
55
56
/**
57
 * Menu callback for both running tests and listing possible tests
58
 */
5913
function simpletest_test_form() {
604
  global $db_prefix, $db_prefix_original;
614
  $form = array();
624
  $uncategorized_tests = simpletest_get_all_tests();
634
  $tests = simpletest_categorize_tests($uncategorized_tests);
644
  if (isset($_SESSION['test_id'])) {
651
    $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d
ORDER BY test_class, message_id", $_SESSION['test_id']);
661
    unset($_SESSION['test_id']);
67
    $summary = array(
681
      '#theme' => 'simpletest_result_summary',
691
      '#pass' => 0,
701
      '#fail' => 0,
711
      '#exception' => 0,
721
      '#weight' => -10,
731
    );
741
    $form['summary'] = $summary;
751
    $form['results'] = array();
761
    $group_summary = array();
77
    $map = array(
781
      'pass' => theme('image', 'misc/watchdog-ok.png'),
791
      'fail' => theme('image', 'misc/watchdog-error.png'),
801
      'exception' => theme('image', 'misc/watchdog-warning.png'),
811
    );
821
    $header = array(t('Message'), t('Group'), t('Filename'), t('Line'),
t('Function'), array('colspan' => 2, 'data' => t('Status')));
831
    while ($result = db_fetch_object($results)) {
841
      $class = $result->test_class;
851
      $info = $uncategorized_tests[$class]->getInfo();
861
      $group = $info['group'];
871
      if (!isset($group_summary[$group])) {
881
        $group_summary[$group] = $summary;
891
      }
901
      $element = &$form['results'][$group][$class];
911
      if (!isset($element)) {
921
        $element['summary'] = $summary;
931
      }
941
      $status = $result->status;
95
      // This reporter can only handle pass, fail and exception.
961
      if (isset($map[$status])) {
971
        $element['#title'] = $info['name'];
981
        $status_index = '#'. $status;
991
        $form['summary'][$status_index]++;
1001
        $group_summary[$group][$status_index]++;
1011
        $element['summary'][$status_index]++;
1021
        $element['result_table']['#rows'][] = array(
103
          'data' => array(
1041
            $result->message,
1051
            $result->message_group,
1061
            basename($result->file),
1071
            $result->line,
1081
            $result->caller,
1091
            $map[$status],
1101
          ),
1111
          'class' => "simpletest-$status",
112
        );
1131
      }
1141
      unset($element);
1151
    }
1161
    $all_ok = TRUE;
1171
    foreach ($form['results'] as $group => &$elements) {
1181
      $group_ok = TRUE;
1191
      foreach ($elements as $class => &$element) {
1201
        $info = $uncategorized_tests[$class]->getInfo();
1211
        $ok = $element['summary']['#fail'] +
$element['summary']['#exception'] == 0;
122
        $element += array(
1231
          '#type' => 'fieldset',
1241
          '#collapsible' => TRUE,
1251
          '#collapsed' => $ok,
1261
          '#description' => $info['description'],
1270
        );
1281
        $element['result_table']['#markup'] = theme('table', $header,
$element['result_table']['#rows']);
1291
        $element['summary']['#ok'] = $ok;
1301
        $group_ok = $group_ok && $ok;
1311
      }
132
      $elements += array(
1331
        '#type' => 'fieldset',
1341
        '#title' => $group,
1351
        '#collapsible' => TRUE,
1361
        '#collapsed' => $group_ok,
1371
        'summary' => $group_summary[$group],
1380
      );
1391
      $elements['summary']['#ok'] = $group_ok;
1401
      $all_ok = $group_ok && $all_ok;
1411
    }
1421
    $form['summary']['#ok'] = $all_ok;
1431
  }
1444
  foreach ($tests as $group_name => $test_group) {
1454
    foreach ($test_group as $test) {
1464
      $test_info = $test->getInfo();
1474
      $test_class = get_class($test);
1484
      $form['tests'][$group_name][$test_class] = array(
1494
        '#type' => 'checkbox',
1504
        '#title' => $test_info['name'],
1514
        '#default_value' => 0,
1524
        '#description' => $test_info['description'],
153
      );
1544
    }
1554
  }
156
1574
  $form['run'] = array(
1584
    '#type' => 'fieldset',
1594
    '#collapsible' => FALSE,
1604
    '#collapsed' => FALSE,
1614
    '#title' => t('Run tests'),
162
  );
1634
  $form['run']['running_options'] = array(
1644
    '#type' => 'radios',
1654
    '#default_value' => 'selected_tests',
166
    '#options' => array(
1674
      'all_tests' => t('Run all tests (WARNING, this may take a long
time)'),
1684
      'selected_tests' => t('Run selected tests'),
1694
    ),
170
  );
1714
  $form['run']['op'] = array(
1724
    '#type' => 'submit',
1734
    '#value' => t('Run tests'),
174
  );
1754
  $form['reset'] = array(
1764
    '#type' => 'fieldset',
1774
    '#collapsible' => FALSE,
1784
    '#collapsed' => FALSE,
1794
    '#title' => t('Clean test environment'),
1804
    '#description' => t('Remove tables with the prefix "simpletest" and
temporary directories that are left over from tests that crashed.')
1814
  );
1824
  $form['reset']['op'] = array(
1834
    '#type' => 'submit',
1844
    '#value' => t('Clean environment'),
1854
    '#submit' => array('simpletest_clean_environment')
1864
  );
1874
  return $form;
1880
}
189
190
/**
191
 * Theme the SimpleTest form that provides test selection.
192
 *
193
 * @ingroup themeable
194
 */
19513
function theme_simpletest_test_form($form) {
1962
  drupal_add_css(drupal_get_path('module', 'simpletest')
.'/simpletest.css', 'module');
1972
  drupal_add_js(drupal_get_path('module', 'simpletest') .'/simpletest.js',
'module');
198
  $header = array(
1992
    array('data' => t('Run'), 'class' => 'simpletest_run checkbox'),
2002
    array('data' => t('Test'), 'class' => 'simpletest_test'),
2012
    array('data' => t('Description'), 'class' =>
'simpletest_description'),
2022
  );
203
  $js = array(
204
    'images' => array(
2052
      theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand'),
2062
      theme('image', 'misc/menu-expanded.png', 'Collapsed', 'Collapsed'),
2072
    ),
2082
  );
209
210
  // Go through each test group and create a row:
2112
  $rows = array();
2122
  foreach (element_children($form['tests']) as $key) {
2132
    $element = &$form['tests'][$key];
2142
    $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
2152
    $row = array();
2162
    $row[] = array('id' => $test_class, 'class' =>
'simpletest-select-all');
2172
    $row[] = array(
2182
      'data' =>  '<div class="simpletest-image"
id="simpletest-test-group-'. $test_class .'">'. $js['images'][0]
.'</div>&nbsp;<label for="'. $test_class .'-select-all"
class="simpletest-group-label">'. $key .'</label>',
219
      'style' => 'font-weight: bold;'
2202
    );
2212
    $row[] = isset($element['#description']) ? $element['#description'] :
'&nbsp;';
2222
    $rows[] = array('data' => $row, 'class' => 'simpletest-group');
223
2242
    $current_js = array('testClass' => $test_class .'-test', 'testNames' =>
array(), 'imageDirection' => 0, 'clickActive' => FALSE);
2252
    foreach (element_children($element) as $test_name) {
2262
      $current_js['testNames'][] = 'edit-'. $test_name;
2272
      $test = $element[$test_name];
2282
      foreach (array('title', 'description') as $key) {
2292
        $$key = $test['#'. $key];
2302
        unset($test['#'. $key]);
2312
      }
2322
      $test['#name'] = $test_name;
2332
      $themed_test = drupal_render($test);
2342
      $row = array();
2352
      $row[] = $themed_test;
2362
      $row[] = theme('indentation', 1) .'<label for="edit-'. $test_name
.'">'. $title .'</label>';
2372
      $row[] = '<div class="description">'. $description .'</div>';
2382
      $rows[] = array('data' => $row, 'style' => 'display: none;', 'class'
=> $test_class .'-test');
2392
    }
2402
    $js['simpletest-test-group-'. $test_class] = $current_js;
2412
  }
2422
  unset($form['tests']);
2432
  drupal_add_js(array('simpleTest' => $js), 'setting');
244
  // Output test groups:
2452
  $output = '';
2462
  if (isset($form['results'])) {
2471
    $output .= drupal_render($form['summary']);
2481
    $output .= drupal_render($form['results']);
2491
  }
2502
  if (count($rows)) {
2512
    $output .= theme('table', $header, $rows, array('id' =>
'simpletest-form-table'));
2522
  }
253
  // Output the rest of the form, excluded test groups which have been
removed:
2542
  $output .= drupal_render($form);
255
2562
  return $output;
2570
}
258
25913
function theme_simpletest_result_summary($form, $text = NULL) {
2601
  return '<div class="simpletest-'. ($form['#ok'] ? 'pass' : 'fail') .'">'
. _simpletest_format_summary_line($form) . '</div>';
2610
}
262
26313
function _simpletest_format_summary_line($summary) {
2641
  return t('@pass, @fail, @exception', array(
2651
    '@pass' => format_plural(isset($summary['#pass']) ? $summary['#pass'] :
0, '1 pass', '@count passes'),
2661
    '@fail' => format_plural(isset($summary['#fail']) ? $summary['#fail'] :
0, '1 fail', '@count fails'),
2671
    '@exception' => format_plural(isset($summary['#exception']) ?
$summary['#exception'] : 0, '1 exception', '@count exceptions'),
2681
  ));
2690
}
270
271
/**
272
 * Run selected tests.
273
 */
27413
function simpletest_test_form_submit($form, &$form_state) {
2752
  $output = '';
2762
  $batch_mode = !preg_match("/^simpletest\d+$/",
$_SERVER['HTTP_USER_AGENT']);
2772
  $tests_list = array();
2782
  $run_all_tests = $form_state['values']['running_options'] ==
'all_tests';
2792
  simpletest_get_all_tests();
2802
  foreach ($form_state['values'] as $class_name => $value) {
2812
    if (class_exists($class_name) && ($value === 1 || $run_all_tests)) {
2822
      $tests_list[] = $class_name;
2832
    }
2842
  }
2852
  if (count($tests_list) > 0 ) {
2862
    simpletest_run_tests($tests_list, 'drupal', $batch_mode);
2871
  }
288
  else {
2890
    drupal_set_message(t('No test has been selected.'), 'error');
290
  }
2911
}
292
293
/**
294
 * Actually runs tests
295
 * @param $test_list
296
 *   List of tests to run.
297
 * @param $reporter
298
 *   Which reporter to use. Allowed values are: text, xml, html and
drupal,
299
 *   drupal being the default.
300
 * @param $batch_mode
301
 *   Whether to use the batch API or not.
302
 */
30313
function simpletest_run_tests($test_list, $reporter = 'drupal', $batch_mode
= FALSE) {
3042
  global $db_prefix, $db_prefix_original;
3052
  cache_clear_all();
3062
  db_query('INSERT INTO {simpletest_test_id} VALUES (default)');
3072
  $test_id = db_last_insert_id('simpletest_test_id', 'test_id');
308
3092
  if ($batch_mode) {
310
    $batch = array(
3110
      'title' => t('Running SimpleTests'),
312
      'operations' => array(
3130
        array('_simpletest_batch_operation', array($test_list, $test_id)),
3140
      ),
3150
      'finished' => '_simpletest_batch_finished',
3160
      'redirect' => 'admin/build/testing',
3170
      'progress_message' => t('Processing tests.'),
3180
      'css' => array(drupal_get_path('module', 'simpletest')
.'/simpletest.css'),
3190
      'init_message' => t('SimpleTest is initializing...') . ' ' .
format_plural(count($test_list), "one test case will run.", "@count test
cases will run."),
3200
    );
3210
    batch_set($batch);
3220
  }
323
  else {
3242
    simpletest_get_all_tests();
3252
    foreach ($test_list as $test_class) {
3262
      $test = new $test_class($test_id);
3272
      $test->run();
3281
    }
3291
    $_SESSION['test_id'] = $test_id;
330
  }
3311
}
332
333
/**
334
 * Batch operation callback.
335
 */
33613
function _simpletest_batch_operation($test_list_init, $test_id, &$context)
{
337
  // Ensure that all classes are loaded before we unserialize some
instances.
3380
  simpletest_get_all_tests();
339
340
  // Get working values.
3410
  if (!isset($context['sandbox']['max'])) {
342
    // First iteration: initialize working values.
3430
    $test_list = $test_list_init;
3440
    $context['sandbox']['max'] = count($test_list);
3450
    $test_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0);
3460
  }
347
  else {
348
    // Nth iteration: get the current values where we last stored them.
3490
    $test_list = $context['sandbox']['tests'];
3500
    $test_results = $context['sandbox']['test_results'];
351
  }
3520
  $max = $context['sandbox']['max'];
353
354
  // Perform the next test.
3550
  $test_class = array_shift($test_list);
3560
  $test = new $test_class($test_id);
35760
  $test->run();
3580
  $size = count($test_list);
3590
  $info = $test->getInfo();
360
361
  // Gather results and compose the report.
3620
  $test_results[$test_class] = $test->_results;
3630
  foreach ($test_results[$test_class] as $key => $value) {
3640
    $test_results[$key] += $value;
3650
  }
3660
  $test_results[$test_class]['#name'] = $info['name'];
3670
  $items = array();
3680
  foreach (element_children($test_results) as $class) {
3690
    $items[] = '<div class="simpletest-' . ($test_results[$class]['#fail']
+ $test_results[$class]['#exception'] ? 'fail' : 'pass') . '">' . t('@name:
@summary', array('@name' => $test_results[$class]['#name'], '@summary' =>
_simpletest_format_summary_line($test_results[$class]))) . '</div>';
3700
  }
3710
  $message = t('Processed test @num of @max - %test.', array('%test' =>
$info['name'], '@num' => $max - $size, '@max' => $max));
3720
  $message .= theme('item_list', $items);
3730
  $context['message'] = $message;
374
  // TODO: Do we want a summary of all?
375
376
  // Save working values for the next iteration.
3770
  $context['sandbox']['tests'] = $test_list;
3780
  $context['sandbox']['test_results'] = $test_results;
379
  // The test_id is the only thing we need to save for the report page.
3800
  $context['results']['test_id'] = $test_id;
381
382
  // Multistep processing: report progress.
3830
  $context['finished'] = 1 - $size / $max;
3840
}
385
38613
function _simpletest_batch_finished($success, $results, $operations) {
3870
  $_SESSION['test_id'] = $results['test_id'];
3880
  if ($success) {
3890
    drupal_set_message(t('The tests have finished running.'));
3900
  }
391
  else {
3920
    drupal_set_message(t('The tests did not successfully finish.'),
'error');
393
  }
3940
}
395
396
/**
397
 * Get a list of all of the tests.
398
 *
399
 * @return
400
 *   An array of tests, with the class name as the keys and the
instantiated
401
 *   versions of the classes as the values.
402
 */
40313
function simpletest_get_all_tests() {
4044
  static $formatted_classes;
4054
  if (!isset($formatted_classes)) {
4064
    require_once drupal_get_path('module', 'simpletest') .
'/drupal_web_test_case.php';
4074
    $files = array();
4084
    foreach (array_keys(module_rebuild_cache()) as $module) {
4094
      $module_path = drupal_get_path('module', $module);
4104
      $test = $module_path . "/$module.test";
4114
      if (file_exists($test)) {
4124
        $files[] = $test;
4134
      }
414
4154
      $tests_directory = $module_path . '/tests';
4164
      if (is_dir($tests_directory)) {
4174
        foreach (file_scan_directory($tests_directory, '\.test$') as $file)
{
4184
          $files[] = $file->filename;
4194
        }
4204
      }
4214
    }
422
4234
    $existing_classes = get_declared_classes();
4244
    foreach ($files as $file) {
4254
      include_once($file);
4264
    }
4274
    $classes = array_values(array_diff(get_declared_classes(),
$existing_classes));
4284
    $formatted_classes = array();
4294
    foreach ($classes as $key => $class) {
4304
      if (method_exists($class, 'getInfo')) {
4314
        $formatted_classes[$class] = new $class;
4324
      }
4334
    }
4344
  }
4354
  if (count($formatted_classes) == 0) {
4360
    drupal_set_message('No test cases found.', 'error');
4370
    return FALSE;
4380
  }
4394
  return $formatted_classes;
4400
}
441
442
/**
443
 * Categorize the tests into groups.
444
 *
445
 * @param $tests
446
 *   A list of tests from simpletest_get_all_tests.
447
 * @see simpletest_get_all_tests.
448
 */
44913
function simpletest_categorize_tests($tests) {
4504
  $groups = array();
4514
  foreach ($tests as $test => $instance) {
4524
    $info = $instance->getInfo();
4534
    $groups[$info['group']][$test] = $instance;
4544
  }
4554
  uksort($groups, 'strnatcasecmp');
4564
  return $groups;
4570
}
458
459
/**
460
 * Remove all temporary database tables and directories.
461
 */
46213
function simpletest_clean_environment() {
4630
  simpletest_clean_database();
4640
  simpletest_clean_temporary_directories();
4650
}
466
467
/**
468
 * Removed prefixed talbes from the database that are left over from
crashed tests.
469
 */
47013
function simpletest_clean_database() {
4710
  $tables = simpletest_get_like_tables();
472
4730
  $ret = array();
4740
  foreach ($tables as $table) {
4750
    db_drop_table($ret, $table);
4760
  }
477
4780
  if (count($ret) > 0) {
4790
    drupal_set_message(t('Removed @count left over tables.', array('@count'
=> count($ret))));
4800
  }
481
  else {
4820
    drupal_set_message(t('No left over tables to remove.'));
483
  }
4840
}
485
486
/**
487
 * Find all tables that are like the specified base table name.
488
 *
489
 * @param string $base_table Base table name.
490
 * @param boolean $count Return the table count instead of list of tables.
491
 * @return mixed Array of matching tables or count of tables.
492
 */
49313
function simpletest_get_like_tables($base_table = 'simpletest', $count =
FALSE) {
4941
  global $db_url, $db_prefix;
4951
  $url = parse_url($db_url);
4961
  $database = substr($url['path'], 1);
4971
  $select = $count ? 'COUNT(table_name)' : 'table_name';
4981
  $result = db_query("SELECT $select FROM information_schema.tables WHERE
table_schema = '$database' AND table_name LIKE '$db_prefix$base_table%'");
4991
  $schema = drupal_get_schema_unprocessed('simpletest');
500
5011
  if ($count) {
5021
    return db_result($result);
5030
  }
5040
  $tables = array();
5050
  while ($table = db_result($result)) {
5060
    if (!isset($schema[$table])) {
5070
      $tables[] = $table;
5080
    }
5090
  }
5100
  return $tables;
5110
}
512
513
/**
514
 * Find all left over temporary directories and remove them.
515
 */
51613
function simpletest_clean_temporary_directories() {
5170
  $files = scandir(file_directory_path());
5180
  $count = 0;
5190
  foreach ($files as $file) {
5200
    $path = file_directory_path() . '/' . $file;
5210
    if (is_dir($path) && preg_match('/^simpletest\d+/', $file)) {
5220
      simpletest_clean_temporary_directory($path);
5230
      $count++;
5240
    }
5250
  }
526
5270
  if ($count > 0) {
5280
    drupal_set_message(t('Removed @count temporary directories.',
array('@count' => $count)));
5290
  }
530
  else {
5310
    drupal_set_message(t('No temporary directories to remove.'));
532
  }
5330
}
534
535
/**
536
 * Remove all files from specified firectory and then remove directory.
537
 *
538
 * @param string $path Directory path.
539
 */
54013
function simpletest_clean_temporary_directory($path) {
54162
  $files = scandir($path);
54262
  foreach ($files as $file) {
54362
    if ($file != '.' && $file != '..') {
54462
      $file_path = "$path/$file";
54562
      if (is_dir($file_path)) {
5461
        simpletest_clean_temporary_directory($file_path);
5471
      }
548
      else {
54962
        file_delete($file_path);
550
      }
55162
    }
55262
  }
55362
  rmdir($path);
55462
}
55513