00001 <?php
00002 
00003 
00013 define('SIMPLETEST_SCRIPT_COLOR_PASS', 32); 
00014 define('SIMPLETEST_SCRIPT_COLOR_FAIL', 31); 
00015 define('SIMPLETEST_SCRIPT_COLOR_EXCEPTION', 33); 
00016 
00017 
00018 list($args, $count) = simpletest_script_parse_args();
00019 
00020 simpletest_script_init();
00021 
00022 if ($args['help'] || $count == 0) {
00023   simpletest_script_help();
00024   exit;
00025 }
00026 
00027 if ($args['execute-batch']) {
00028   simpletest_script_execute_batch();
00029 }
00030 
00031 
00032 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
00033 if (!module_exists('simpletest')) {
00034   simpletest_script_print_error("The simpletest module must be enabled before this script can run.");
00035   exit;
00036 }
00037 
00038 if ($args['clean']) {
00039   
00040   simpletest_clean_environment();
00041   echo "\nEnvironment cleaned.\n";
00042 
00043   
00044   $messages = array_pop(drupal_get_messages('status'));
00045   foreach($messages as $text) {
00046     echo " - " . $text . "\n";
00047   }
00048   exit;
00049 }
00050 
00051 
00052 $all_tests = simpletest_get_all_tests();
00053 $groups = simpletest_categorize_tests($all_tests);
00054 $test_list = array();
00055 
00056 if ($args['list']) {
00057   
00058   echo "\nAvailable test groups & classes\n";
00059   echo   "-------------------------------\n\n";
00060   foreach ($groups as $group => $tests) {
00061     echo $group . "\n";
00062     foreach ($tests as $class_name => $info) {
00063       echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
00064     }
00065   }
00066   exit;
00067 }
00068 
00069 $test_list = simpletest_script_get_test_list();
00070 
00071 
00072 if (!ini_get('safe_mode')) {
00073   set_time_limit(0);
00074 }
00075 
00076 simpletest_script_reporter_init();
00077 
00078 
00079 
00080 db_query('INSERT INTO {simpletest_test_id} VALUES (default)');
00081 $test_id = db_last_insert_id('simpletest_test_id', 'test_id');
00082 
00083 
00084 simpletest_script_command($args['concurrency'], $test_id, implode(",", $test_list));
00085 
00086 
00087 simpletest_script_reporter_display_results();
00088 
00089 
00090 simpletest_clean_results_table($test_id);
00091 
00095 function simpletest_script_help() {
00096   global $args;
00097 
00098   echo <<<EOF
00099 
00100 Run Drupal tests from the shell.
00101 
00102 Usage:        {$args['script']} [OPTIONS] <tests>
00103 Example:      {$args['script']} Profile
00104 
00105 All arguments are long options.
00106 
00107   --help      Print this page.
00108 
00109   --list      Display all available test groups.
00110 
00111   --clean     Cleans up database tables or directories from previous, failed,
00112               tests and then exits (no tests are run).
00113 
00114   --url       Immediately preceeds a URL to set the host and path. You will
00115               need this parameter if Drupal is in a subdirectory on your
00116               localhost and you have not set \$base_url in settings.php.
00117 
00118   --php       The absolute path to the PHP executable. Usually not needed.
00119 
00120   --concurrency [num]
00121 
00122               Run tests in parallel, up to [num] tests at a time. This requires
00123               the Process Control Extension (PCNTL) to be compiled in PHP, not
00124               supported under Windows.
00125 
00126   --all       Run all available tests.
00127 
00128   --class     Run tests identified by specific class names, instead of group names.
00129 
00130   --file      Run tests identified by specific file names, instead of group names.
00131               Specify the path and the extension (i.e. 'modules/user/user.test').
00132 
00133   --color     Output the results with color highlighting.
00134 
00135   --verbose   Output detailed assertion messages in addition to summary.
00136 
00137   <test1>[,<test2>[,<test3> ...]]
00138 
00139               One or more tests to be run. By default, these are interpreted
00140               as the names of test groups as shown at ?q=admin/build/testing.
00141               These group names typically correspond to module names like "User"
00142               or "Profile" or "System", but there is also a group "XML-RPC".
00143               If --class is specified then these are interpreted as the names of
00144               specific test classes whose test methods will be run. Tests must
00145               be separated by commas. Ignored if --all is specified.
00146 
00147 To run this script you will normally invoke it from the root directory of your
00148 Drupal installation as the webserver user (differs per configuration), or root:
00149 
00150 sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
00151   --url http:
00152 sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
00153   --url http:
00154 \n
00155 EOF;
00156 }
00157 
00163 function simpletest_script_parse_args() {
00164   
00165   $args = array(
00166     'script' => '',
00167     'help' => FALSE,
00168     'list' => FALSE,
00169     'clean' => FALSE,
00170     'url' => '',
00171     'php' => '',
00172     'concurrency' => 1,
00173     'all' => FALSE,
00174     'class' => FALSE,
00175     'file' => FALSE,
00176     'color' => FALSE,
00177     'verbose' => FALSE,
00178     'test_names' => array(),
00179     
00180     'test-id' => NULL,
00181     'execute-batch' => FALSE
00182   );
00183 
00184   
00185   $args['script'] = basename(array_shift($_SERVER['argv']));
00186 
00187   $count = 0;
00188   while ($arg = array_shift($_SERVER['argv'])) {
00189     if (preg_match('/--(\S+)/', $arg, $matches)) {
00190       
00191       if (array_key_exists($matches[1], $args)) {
00192         
00193         $previous_arg = $matches[1];
00194         if (is_bool($args[$previous_arg])) {
00195           $args[$matches[1]] = TRUE;
00196         }
00197         else {
00198           $args[$matches[1]] = array_shift($_SERVER['argv']);
00199         }
00200         
00201         $args['test_names'] = array();
00202         $count++;
00203       }
00204       else {
00205         
00206         simpletest_script_print_error("Unknown argument '$arg'.");
00207         exit;
00208       }
00209     }
00210     else {
00211       
00212       $args['test_names'] += explode(',', $arg);
00213       $count++;
00214     }
00215   }
00216 
00217   
00218   if (!is_numeric($args['concurrency']) || $args['concurrency'] <= 0) {
00219     simpletest_script_print_error("--concurrency must be a strictly positive integer.");
00220     exit;
00221   }
00222   elseif ($args['concurrency'] > 1 && !function_exists('pcntl_fork')) {
00223     simpletest_script_print_error("Parallel test execution requires the Process Control extension to be compiled in PHP. Please see http://php.net/manual/en/intro.pcntl.php for more information.");
00224     exit;
00225   }
00226 
00227   return array($args, $count);
00228 }
00229 
00233 function simpletest_script_init() {
00234   global $args, $php;
00235 
00236   $host = 'localhost';
00237   $path = '';
00238   
00239   if (!empty($args['php'])) {
00240     $php = $args['php'];
00241   }
00242   elseif (!empty($_ENV['_'])) {
00243     
00244     $php = $_ENV['_'];
00245   }
00246   elseif (!empty($_ENV['SUDO_COMMAND'])) {
00247     
00248     
00249     list($php, ) = explode(' ', $_ENV['SUDO_COMMAND'], 2);
00250   }
00251   else {
00252     simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Please supply the --php command line argument.');
00253     simpletest_script_help();
00254     exit();
00255   }
00256 
00257   
00258   if (!empty($args['url'])) {
00259     $parsed_url = parse_url($args['url']);
00260     $host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
00261     $path = $parsed_url['path'];
00262   }
00263 
00264   $_SERVER['HTTP_HOST'] = $host;
00265   $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
00266   $_SERVER['SERVER_ADDR'] = '127.0.0.1';
00267   $_SERVER['SERVER_SOFTWARE'] = 'Apache';
00268   $_SERVER['SERVER_NAME'] = 'localhost';
00269   $_SERVER['REQUEST_URI'] = $path .'/';
00270   $_SERVER['REQUEST_METHOD'] = 'GET';
00271   $_SERVER['SCRIPT_NAME'] = $path .'/index.php';
00272   $_SERVER['PHP_SELF'] = $path .'/index.php';
00273   $_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
00274 
00275   chdir(realpath(dirname(__FILE__) . '/..'));
00276   define('DRUPAL_ROOT', getcwd());
00277   require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
00278 }
00279 
00283 function simpletest_script_execute_batch() {
00284   global $args;
00285 
00286   if (is_null($args['test-id'])) {
00287     simpletest_script_print_error("--execute-batch should not be called interactively.");
00288     exit;
00289   }
00290   if ($args['concurrency'] == 1) {
00291     
00292     if (count($args['test_names']) > 1) {
00293       foreach ($args['test_names'] as $test_class) {
00294         
00295         simpletest_script_command(1, $args['test-id'], $test_class);
00296       }
00297       exit;
00298     }
00299     else {
00300       
00301       $test_class = array_shift($args['test_names']);
00302       drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
00303       simpletest_script_run_one_test($args['test-id'], $test_class);
00304       exit;
00305     }
00306   }
00307   else {
00308     
00309     $children = array();
00310     while (!empty($args['test_names']) || !empty($children)) {
00311       
00312       while (count($children) < $args['concurrency']) {
00313         if (empty($args['test_names'])) break;
00314 
00315         $child = array();
00316         $child['test_class'] = $test_class = array_shift($args['test_names']);
00317         $child['pid'] = pcntl_fork();
00318         if (!$child['pid']) {
00319           
00320           drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
00321           simpletest_script_run_one_test($args['test-id'], $test_class);
00322           exit;
00323         }
00324         else {
00325           
00326           $children[] = $child;
00327         }
00328       }
00329 
00330       
00331       usleep(200000);
00332 
00333       
00334       foreach ($children as $cid => $child) {
00335         if (pcntl_waitpid($child['pid'], $status, WUNTRACED | WNOHANG)) {
00336           
00337           unset($children[$cid]);
00338         }
00339       }
00340     }
00341     exit;
00342   }
00343 }
00344 
00348 function simpletest_script_run_one_test($test_id, $test_class) {
00349   simpletest_get_all_tests();
00350   $test = new $test_class($test_id);
00351   $test->run();
00352   $info = $test->getInfo();
00353 
00354   $status = ((isset($test->results['#fail']) && $test->results['#fail'] > 0)
00355            || (isset($test->results['#exception']) && $test->results['#exception'] > 0) ? 'fail' : 'pass');
00356   simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
00357 }
00358 
00362 function simpletest_script_command($concurrency, $test_id, $tests) {
00363   global $args, $php;
00364 
00365   $command = "$php ./scripts/{$args['script']} --url {$args['url']}";
00366   if ($args['color']) {
00367     $command .= ' --color';
00368   }
00369   $command .= " --php " . escapeshellarg($php) . " --concurrency $concurrency --test-id $test_id --execute-batch $tests";
00370   passthru($command);
00371 }
00372 
00381 function simpletest_script_get_test_list() {
00382   global $args, $all_tests, $groups;
00383 
00384   $test_list = array();
00385   if ($args['all']) {
00386     $test_list = $all_tests;
00387   }
00388   else {
00389     if ($args['class']) {
00390       
00391       foreach ($args['test_names'] as $class_name) {
00392         if (in_array($class_name, $all_tests)) {
00393           $test_list[] = $class_name;
00394         }
00395       }
00396     }
00397     elseif ($args['file']) {
00398       $files = array();
00399       foreach ($args['test_names'] as $file) {
00400         $files[realpath($file)] = 1;
00401       }
00402 
00403       
00404       foreach ($all_tests as $class_name => $info) {
00405         $refclass = new ReflectionClass($class_name);
00406         $file = $refclass->getFileName();
00407         if (isset($files[$file])) {
00408           $test_list[] = $class_name;
00409         }
00410       }
00411     }
00412     else {
00413       
00414       foreach ($args['test_names'] as $group_name) {
00415         if (isset($groups[$group_name])) {
00416           foreach($groups[$group_name] as $class_name => $info) {
00417             $test_list[] = $class_name;
00418           }
00419         }
00420       }
00421     }
00422   }
00423 
00424   if (empty($test_list)) {
00425     simpletest_script_print_error('No valid tests were specified.');
00426     exit;
00427   }
00428   return $test_list;
00429 }
00430 
00434 function simpletest_script_reporter_init() {
00435   global $args, $all_tests, $test_list;
00436 
00437   echo "\n";
00438   echo "Drupal test run\n";
00439   echo "---------------\n";
00440   echo "\n";
00441 
00442   
00443   if ($args['all']) {
00444     echo "All tests will run.\n\n";
00445   }
00446   else {
00447     echo "Tests to be run:\n";
00448     foreach ($test_list as $class_name) {
00449       $info = call_user_func(array($class_name, 'getInfo'));
00450       echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
00451     }
00452     echo "\n";
00453   }
00454 
00455   echo "Test run started: " . format_date($_SERVER['REQUEST_TIME'], 'long') . "\n";
00456   timer_start('run-tests');
00457   echo "\n";
00458 
00459   echo "Test summary:\n";
00460   echo "-------------\n";
00461   echo "\n";
00462 }
00463 
00467 function simpletest_script_reporter_display_results() {
00468   global $args, $test_id, $results_map;
00469 
00470   echo "\n";
00471   $end = timer_stop('run-tests');
00472   echo "Test run duration: " . format_interval($end['time'] / 1000);
00473   echo "\n";
00474 
00475   if ($args['verbose']) {
00476     
00477     echo "Detailed test results:\n";
00478     echo "----------------------\n";
00479     echo "\n";
00480 
00481     $results_map = array(
00482       'pass' => 'Pass',
00483       'fail' => 'Fail',
00484       'exception' => 'Exception'
00485     );
00486 
00487 
00488     $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $test_id);
00489 
00490     $test_class = '';
00491 
00492     while ($result = db_fetch_object($results)) {
00493       if (isset($results_map[$result->status])) {
00494         if ($result->test_class != $test_class) {
00495           
00496           echo "\n\n---- $result->test_class ----\n\n\n";
00497           $test_class = $result->test_class;
00498         }
00499 
00500         simpletest_script_format_result($result);
00501       }
00502     }
00503   }
00504 }
00505 
00512 function simpletest_script_format_result($result) {
00513   global $results_map, $color;
00514 
00515   $summary = sprintf("%-10.10s %-10.10s %-30.30s %-5.5s %-20.20s\n",
00516     $results_map[$result->status], $result->message_group, basename($result->file), $result->line, $result->caller);
00517 
00518   simpletest_script_print($summary, simpletest_script_color_code($result->status));
00519 
00520   $lines = explode("\n", wordwrap(trim(strip_tags($result->message)), 76));
00521   foreach ($lines as $line) {
00522     echo "    $line\n";
00523   }
00524 }
00525 
00532 function simpletest_script_print_error($message) {
00533   simpletest_script_print("  ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
00534 }
00535 
00543 function simpletest_script_print($message, $color_code) {
00544   global $args;
00545   if ($args['color']) {
00546     echo "\033[" . $color_code . "m" . $message . "\033[0m";
00547   }
00548   else {
00549     echo $message;
00550   }
00551 }
00552 
00559 function simpletest_script_color_code($status) {
00560   switch ($status) {
00561     case 'pass':
00562       return SIMPLETEST_SCRIPT_COLOR_PASS;
00563     case 'fail':
00564       return SIMPLETEST_SCRIPT_COLOR_FAIL;
00565     case 'exception':
00566       return SIMPLETEST_SCRIPT_COLOR_EXCEPTION;
00567   }
00568   return 0; 
00569 }