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