00001 <?php
00002
00003
00014 function simpletest_help($path, $arg) {
00015 switch ($path) {
00016 case 'admin/help#simpletest':
00017 $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>';
00018 $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>';
00019 $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>';
00020 $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>';
00021 $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>';
00022 return $output;
00023 }
00024 }
00025
00029 function simpletest_menu() {
00030 $items['admin/build/testing'] = array(
00031 'title' => 'Testing',
00032 'page callback' => 'drupal_get_form',
00033 'page arguments' => array('simpletest_test_form'),
00034 'description' => 'Run tests against Drupal core and your active modules. These tests help assure that your site code is working as designed.',
00035 'access arguments' => array('administer unit tests'),
00036 );
00037 return $items;
00038 }
00039
00043 function simpletest_perm() {
00044
00045
00046
00047
00048
00049
00050 return array('administer unit tests');
00051 }
00052
00056 function simpletest_theme() {
00057 return array(
00058 'simpletest_test_table' => array(
00059 'arguments' => array('table' => NULL)
00060 ),
00061 'simpletest_result_summary' => array(
00062 'arguments' => array('form' => NULL)
00063 ),
00064 );
00065 }
00066
00070 function simpletest_test_form() {
00071 $form = array();
00072
00073
00074 $uncategorized_tests = simpletest_get_all_tests();
00075 $tests = simpletest_categorize_tests($uncategorized_tests);
00076 $selected_tests = array();
00077
00078 if (isset($_SESSION['test_id'])) {
00079
00080 $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $_SESSION['test_id']);
00081
00082 $summary = array(
00083 '#theme' => 'simpletest_result_summary',
00084 '#pass' => 0,
00085 '#fail' => 0,
00086 '#exception' => 0,
00087 '#weight' => -10,
00088 );
00089 $form['summary'] = $summary;
00090 $form['results'] = array();
00091 $group_summary = array();
00092 $map = array(
00093 'pass' => theme('image', 'misc/watchdog-ok.png'),
00094 'fail' => theme('image', 'misc/watchdog-error.png'),
00095 'exception' => theme('image', 'misc/watchdog-warning.png'),
00096 );
00097 $header = array(t('Message'), t('Group'), t('Filename'), t('Line'), t('Function'), array('colspan' => 2, 'data' => t('Status')));
00098 while ($result = db_fetch_object($results)) {
00099 $class = $result->test_class;
00100 $info = call_user_func(array($class, 'getInfo'));
00101 $group = $info['group'];
00102 $selected_tests[$group][$class] = TRUE;
00103 if (!isset($group_summary[$group])) {
00104 $group_summary[$group] = $summary;
00105 }
00106 $element = &$form['results'][$group][$class];
00107 if (!isset($element)) {
00108 $element['summary'] = $summary;
00109 }
00110 $status = $result->status;
00111
00112 if (isset($map[$status])) {
00113 $element['#title'] = $info['name'];
00114 $status_index = '#'. $status;
00115 $form['summary'][$status_index]++;
00116 $group_summary[$group][$status_index]++;
00117 $element['summary'][$status_index]++;
00118 $element['result_table']['#rows'][] = array(
00119 'data' => array(
00120 $result->message,
00121 $result->message_group,
00122 basename($result->file),
00123 $result->line,
00124 $result->function,
00125 $map[$status],
00126 ),
00127 'class' => "simpletest-$status",
00128 );
00129 }
00130 unset($element);
00131 }
00132
00133
00134 simpletest_clean_results_table($_SESSION['test_id']);
00135 unset($_SESSION['test_id']);
00136
00137 $all_ok = TRUE;
00138 foreach ($form['results'] as $group => &$elements) {
00139 $group_ok = TRUE;
00140 foreach ($elements as $class => &$element) {
00141 $info = call_user_func(array($class, 'getInfo'));
00142 $ok = $element['summary']['#fail'] + $element['summary']['#exception'] == 0;
00143 $element += array(
00144 '#type' => 'fieldset',
00145 '#collapsible' => TRUE,
00146 '#collapsed' => $ok,
00147 '#description' => $info['description'],
00148 );
00149
00150 $element['result_table']['#value'] = theme('table', $header, $element['result_table']['#rows']);
00151 $element['summary']['#ok'] = $ok;
00152 $group_ok = $group_ok && $ok;
00153 }
00154 $elements += array(
00155 '#type' => 'fieldset',
00156 '#title' => $group,
00157 '#collapsible' => TRUE,
00158 '#collapsed' => $group_ok,
00159 'summary' => $group_summary[$group],
00160 );
00161 $elements['summary']['#ok'] = $group_ok;
00162 $all_ok = $group_ok && $all_ok;
00163 }
00164 $form['summary']['#ok'] = $all_ok;
00165 }
00166 $form['tests'] = array(
00167 '#type' => 'fieldset',
00168 '#title' => t('Tests'),
00169 '#description' => t('Select the tests you would like to run, and click Run tests.'),
00170 );
00171 $form['tests']['table'] = array(
00172 '#theme' => 'simpletest_test_table'
00173 );
00174 foreach ($tests as $group_name => $test_group) {
00175 $form['tests']['table'][$group_name] = array(
00176 '#collapsed' => TRUE,
00177 );
00178 foreach ($test_group as $class => $info) {
00179 $is_selected = isset($selected_tests[$group_name][$class]);
00180 $form['tests']['table'][$group_name][$class] = array(
00181 '#type' => 'checkbox',
00182 '#title' => $info['name'],
00183 '#default_value' => $is_selected,
00184 '#description' => $info['description'],
00185 );
00186 if ($is_selected) {
00187 $form['tests']['table'][$group_name]['#collapsed'] = FALSE;
00188 }
00189 }
00190 }
00191
00192
00193 $form['tests']['op'] = array(
00194 '#type' => 'submit',
00195 '#value' => t('Run tests'),
00196 );
00197 $form['reset'] = array(
00198 '#type' => 'fieldset',
00199 '#collapsible' => FALSE,
00200 '#collapsed' => FALSE,
00201 '#title' => t('Clean test environment'),
00202 '#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.'),
00203 );
00204 $form['reset']['op'] = array(
00205 '#type' => 'submit',
00206 '#value' => t('Clean environment'),
00207 '#submit' => array('simpletest_clean_environment'),
00208 );
00209
00210 return $form;
00211 }
00212
00213 function theme_simpletest_test_table($table) {
00214 drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');
00215 drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js');
00216
00217
00218 $header = array(
00219 theme('table_select_header_cell'),
00220 array('data' => t('Test'), 'class' => 'simpletest_test'),
00221 array('data' => t('Description'), 'class' => 'simpletest_description'),
00222 );
00223
00224
00225 $js = array(
00226 'images' => array(
00227 theme('image', 'misc/menu-collapsed.png', 'Expand', 'Expand'),
00228 theme('image', 'misc/menu-expanded.png', 'Collapsed', 'Collapsed'),
00229 ),
00230 );
00231
00232
00233 $rows = array();
00234 foreach (element_children($table) as $key) {
00235 $element = &$table[$key];
00236 $row = array();
00237
00238
00239
00240 $test_class = strtolower(trim(preg_replace("/[^\w\d]/", "-", $key)));
00241
00242
00243
00244 $collapsed = !empty($element['#collapsed']);
00245 $image_index = $collapsed ? 0 : 1;
00246
00247
00248 $row[] = array('id' => $test_class, 'class' => 'simpletest-select-all');
00249
00250
00251 $row[] = array(
00252 'data' => '<div class="simpletest-image" id="simpletest-test-group-' . $test_class . '"></div> ' .
00253 '<label for="' . $test_class . '-select-all" class="simpletest-group-label">' . $key . '</label>',
00254 'style' => 'font-weight: bold;'
00255 );
00256
00257 $row[] = isset($element['#description']) ? $element['#description'] : ' ';
00258 $rows[] = array('data' => $row, 'class' => 'simpletest-group');
00259
00260
00261 $current_js = array(
00262 'testClass' => $test_class . '-test',
00263 'testNames' => array(),
00264 'imageDirection' => $image_index,
00265 'clickActive' => FALSE,
00266 );
00267 foreach (element_children($element) as $test_name) {
00268 $test = $element[$test_name];
00269 $row = array();
00270
00271 $current_js['testNames'][] = 'edit-' . $test_name;
00272
00273
00274 $title = $test['#title'];
00275 $description = $test['#description'];
00276
00277 unset($test['#title']);
00278 unset($test['#description']);
00279
00280
00281 $test['#name'] = $test_name;
00282
00283 $row[] = drupal_render($test);
00284 $row[] = theme('indentation', 1) . '<label for="edit-' . $test_name . '">' . $title . '</label>';
00285 $row[] = '<div class="description">' . $description . '</div>';
00286 $rows[] = array('data' => $row, 'class' => $test_class . '-test' . ($collapsed ? ' js-hide' : ''));
00287 }
00288 $js['simpletest-test-group-'. $test_class] = $current_js;
00289 unset($table[$key]);
00290 }
00291
00292
00293 drupal_add_js(array('simpleTest' => $js), 'setting');
00294
00295 if (empty($rows)) {
00296 return '<strong>' . t('No tests to display.') . '</strong>';
00297 }
00298 else {
00299 return theme('table', $header, $rows, array('id' => 'simpletest-form-table'));
00300 }
00301 }
00302
00306 function simpletest_js_alter(&$javascript) {
00307
00308
00309 $simpletest = drupal_get_path('module', 'simpletest') . '/simpletest.js';
00310 if (array_key_exists($simpletest, $javascript) && array_key_exists('misc/tableselect.js', $javascript)) {
00311 $javascript[$simpletest]['weight'] = $javascript['misc/tableselect.js']['weight'] - 1;
00312 }
00313 }
00314
00315 function theme_simpletest_result_summary($form, $text = NULL) {
00316 return '<div class="simpletest-'. ($form['#ok'] ? 'pass' : 'fail') .'">' . _simpletest_format_summary_line($form) . '</div>';
00317 }
00318
00319 function _simpletest_format_summary_line($summary) {
00320 return t('@pass, @fail, and @exception', array(
00321 '@pass' => format_plural(isset($summary['#pass']) ? $summary['#pass'] : 0, '1 pass', '@count passes'),
00322 '@fail' => format_plural(isset($summary['#fail']) ? $summary['#fail'] : 0, '1 fail', '@count fails'),
00323 '@exception' => format_plural(isset($summary['#exception']) ? $summary['#exception'] : 0, '1 exception', '@count exceptions'),
00324 ));
00325 }
00326
00330 function simpletest_test_form_submit($form, &$form_state) {
00331
00332 simpletest_get_all_tests();
00333
00334
00335 $tests_list = array();
00336 foreach ($form_state['values'] as $class_name => $value) {
00337 if (class_exists($class_name) && $value === 1) {
00338 $tests_list[] = $class_name;
00339 }
00340 }
00341 if (count($tests_list) > 0 ) {
00342 simpletest_run_tests($tests_list, 'drupal');
00343 }
00344 else {
00345 drupal_set_message(t('No test(s) selected.'), 'error');
00346 }
00347 }
00348
00358 function simpletest_run_tests($test_list, $reporter = 'drupal') {
00359 cache_clear_all();
00360
00361 db_query('INSERT INTO {simpletest_test_id} VALUES (default)');
00362 $test_id = db_last_insert_id('simpletest_test_id', 'test_id');
00363
00364
00365 $first_test = array_shift($test_list);
00366 $first_instance = new $first_test();
00367 array_unshift($test_list, $first_test);
00368 $info = $first_instance->getInfo();
00369
00370 $batch = array(
00371 'title' => t('Running SimpleTests'),
00372 'operations' => array(
00373 array('_simpletest_batch_operation', array($test_list, $test_id)),
00374 ),
00375 'finished' => '_simpletest_batch_finished',
00376 'redirect' => 'admin/build/testing',
00377 'progress_message' => '',
00378 'css' => array(drupal_get_path('module', 'simpletest') . '/simpletest.css'),
00379 'init_message' => t('Processing test @num of @max - %test.', array('%test' => $info['name'], '@num' => '1', '@max' => count($test_list))),
00380 );
00381 batch_set($batch);
00382
00383
00384
00385
00386
00387
00388 batch_process();
00389 }
00390
00394 function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
00395
00396 simpletest_get_all_tests();
00397
00398
00399 if (!isset($context['sandbox']['max'])) {
00400
00401 $test_list = $test_list_init;
00402 $context['sandbox']['max'] = count($test_list);
00403 $test_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0);
00404 }
00405 else {
00406
00407 $test_list = $context['sandbox']['tests'];
00408 $test_results = $context['sandbox']['test_results'];
00409 }
00410 $max = $context['sandbox']['max'];
00411
00412
00413 $test_class = array_shift($test_list);
00414 $test = new $test_class($test_id);
00415 $test->run();
00416 $size = count($test_list);
00417 $info = $test->getInfo();
00418
00419
00420 $test_results[$test_class] = $test->results;
00421 foreach ($test_results[$test_class] as $key => $value) {
00422 $test_results[$key] += $value;
00423 }
00424 $test_results[$test_class]['#name'] = $info['name'];
00425 $items = array();
00426 foreach (element_children($test_results) as $class) {
00427 array_unshift($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>');
00428 }
00429 $context['message'] = t('Processed test @num of @max - %test.', array('%test' => $info['name'], '@num' => $max - $size, '@max' => $max));
00430 $context['message'] .= '<div class="simpletest-' . ($test_results['#fail'] + $test_results['#exception'] ? 'fail' : 'pass') . '">Overall results: ' . _simpletest_format_summary_line($test_results) . '</div>';
00431 $context['message'] .= theme('item_list', $items);
00432
00433
00434 $context['sandbox']['tests'] = $test_list;
00435 $context['sandbox']['test_results'] = $test_results;
00436
00437 $context['results']['test_id'] = $test_id;
00438
00439
00440 $context['finished'] = 1 - $size / $max;
00441 }
00442
00443
00444 function _simpletest_batch_finished($success, $results, $operations) {
00445 if (isset($results['test_id'])) {
00446
00447 $_SESSION['test_id'] = $results['test_id'];
00448 }
00449 if ($success) {
00450
00451 drupal_set_message(t('The tests finished.'));
00452 }
00453 else {
00454 drupal_set_message(t('The tests did not successfully finish.'), 'error');
00455 }
00456 }
00457
00465 function simpletest_get_all_tests() {
00466 static $formatted_classes;
00467 if (!isset($formatted_classes)) {
00468
00469 require_once drupal_get_path('module', 'simpletest') . '/drupal_web_test_case.php';
00470 $files = array();
00471 foreach (array_keys(module_rebuild_cache()) as $module) {
00472 $module_path = drupal_get_path('module', $module);
00473 $test = $module_path . "/$module.test";
00474 if (file_exists($test)) {
00475 $files[] = $test;
00476 }
00477
00478 $tests_directory = $module_path . '/tests';
00479 if (is_dir($tests_directory)) {
00480
00481 foreach (file_scan_directory($tests_directory, '\.test$') as $file) {
00482
00483 if (!preg_match('/class\s+.*?\s+extends\s+DrupalTestCase/', file_get_contents($file->filename))) {
00484
00485 $files[] = $file->filename;
00486 }
00487 }
00488 }
00489 }
00490
00491 $existing_classes = get_declared_classes();
00492 foreach ($files as $file) {
00493
00494 include_once $file;
00495 }
00496 $classes = array_values(array_diff(get_declared_classes(), $existing_classes));
00497 $formatted_classes = array();
00498 foreach ($classes as $key => $class) {
00499 if (!method_exists($class, 'getInfo')) {
00500 unset($classes[$key]);
00501 }
00502 }
00503 }
00504 if (count($classes) == 0) {
00505 drupal_set_message('No test cases found.', 'error');
00506 return FALSE;
00507 }
00508 return $classes;
00509 }
00510
00518 function simpletest_categorize_tests($tests) {
00519 $groups = array();
00520 foreach ($tests as $test) {
00521 $info = call_user_func(array($test, 'getInfo'));
00522 $groups[$info['group']][$test] = $info;
00523 }
00524 uksort($groups, 'strnatcasecmp');
00525 return $groups;
00526 }
00527
00531 function simpletest_clean_environment() {
00532 simpletest_clean_database();
00533 simpletest_clean_temporary_directories();
00534 $count = simpletest_clean_results_table();
00535 drupal_set_message(t('Removed @count test results.', array('@count' => $count)));
00536 }
00537
00541 function simpletest_clean_database() {
00542
00543 $tables = simpletest_get_like_tables();
00544 $schema = drupal_get_schema_unprocessed('simpletest');
00545 $ret = array();
00546 foreach (array_diff_key($tables, $schema) as $table) {
00547
00548
00549 if (preg_match('/simpletest\d+.*/', $table, $matches)) {
00550 db_drop_table($ret, $matches[0]);
00551 }
00552 }
00553
00554 if (count($ret) > 0) {
00555 drupal_set_message(t('Removed @count left over tables.', array('@count' => count($ret))));
00556 }
00557 else {
00558 drupal_set_message(t('No left over tables to remove.'));
00559 }
00560 }
00561
00569 function simpletest_get_like_tables($base_table = 'simpletest', $count = FALSE) {
00570 global $db_url, $db_prefix;
00571 $url = parse_url(is_array($db_url) ? $db_url['default'] : $db_url);
00572 $database = substr($url['path'], 1);
00573 $result = db_query("SELECT table_name FROM information_schema.tables WHERE table_schema = '$database' AND table_name LIKE '$db_prefix$base_table%'");
00574 $schema = drupal_get_schema_unprocessed('simpletest');
00575
00576 $tables = array();
00577 while ($table = db_result($result)) {
00578 if (!isset($schema[$table])) {
00579 $tables[] = $table;
00580 }
00581 }
00582 return ($count) ? count($tables) : $tables;
00583 }
00584
00588 function simpletest_clean_temporary_directories() {
00589 $files = scandir(file_directory_path());
00590 $count = 0;
00591 foreach ($files as $file) {
00592 $path = file_directory_path() . '/' . $file;
00593 if (is_dir($path) && preg_match('/^simpletest\d+/', $file)) {
00594 simpletest_clean_temporary_directory($path);
00595 $count++;
00596 }
00597 }
00598
00599 if ($count > 0) {
00600 drupal_set_message(t('Removed @count temporary directories.', array('@count' => $count)));
00601 }
00602 else {
00603 drupal_set_message(t('No temporary directories to remove.'));
00604 }
00605 }
00606
00612 function simpletest_clean_temporary_directory($path) {
00613 $files = scandir($path);
00614 foreach ($files as $file) {
00615 if ($file != '.' && $file != '..') {
00616 $file_path = "$path/$file";
00617 if (is_dir($file_path)) {
00618 simpletest_clean_temporary_directory($file_path);
00619 }
00620 else {
00621
00622 unlink($file_path);
00623 }
00624 }
00625 }
00626 rmdir($path);
00627 }
00628
00637 function simpletest_clean_results_table($test_id = NULL) {
00638 if (variable_get('simpletest_clear_results', TRUE)) {
00639 if ($test_id) {
00640
00641 $count = db_result(db_query('SELECT COUNT(test_id) FROM {simpletest_test_id} WHERE test_id = %d', $test_id));
00642
00643
00644
00645
00646
00647
00648
00649 db_query("DELETE FROM {simpletest} WHERE test_id = %d", $test_id);
00650 db_query("DELETE FROM {simpletest_test_id} WHERE test_id = %d", $test_id);
00651 }
00652 else {
00653 $count = db_result(db_query('SELECT COUNT(test_id) FROM {simpletest_test_id}'));
00654
00655
00656
00657
00658 db_query('DELETE FROM {simpletest}');
00659 db_query('DELETE FROM {simpletest_test_id}');
00660 }
00661
00662 return $count;
00663 }
00664 return FALSE;
00665 }
00666
00672 function simpletest_form_system_modules_alter(&$form, $form_state) {
00673 foreach ($form['validation_modules']['#value'] as $filename => $file) {
00674 if (!empty($file->info['hidden'])) {
00675 unset($form['name'][$filename]);
00676 unset($form['version'][$filename]);
00677 unset($form['description'][$filename]);
00678 unset($form['status']['#options'][$filename]);
00679 unset($form['throttle']['#options'][$filename]);
00680 unset($form['validation_modules']['#value'][$filename]);
00681 }
00682 }
00683 }
00684