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 }