diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh index 9b9786a33bfd18cc7609309c7b4f12df2e9dbde8..db7600b2073b2c6e34454e27f1665139d5ec6d44 100755 --- a/scripts/run-tests.sh +++ b/scripts/run-tests.sh @@ -1,4 +1,3 @@ -#!/Applications/MAMP/bin/php5/bin/php <?php // $Id$ @@ -10,11 +9,12 @@ * If no arguments are provided, the help text will print. */ -$reporter = 'text'; $test_names = array(); $host = 'localhost'; $path = ''; $script = basename(array_shift($_SERVER['argv'])); +// XXX: is there a way to get the interpreter path dynamically? +$php = "/usr/bin/php"; if (in_array('--help', $_SERVER['argv']) || empty($_SERVER['argv'])) { echo <<<EOF @@ -37,12 +37,14 @@ need this parameter if Drupal is in a subdirectory on your localhost and you have not set \$base_url in settings.php. - --reporter Immediatly preceeds the name of the output reporter to use. This - Defaults to "text", while other options include "xml" and "html". + --concurrency [num] + + Run tests in parallel, up to [num] tests at a time. + This is not supported under Windows. --all Run all available tests. - --class Run tests identified by speficic class names. + --class Run tests identified by specific class names, instead of group names. <test1>[,<test2>[,<test3> ...]] @@ -66,8 +68,11 @@ $list = FALSE; $clean = FALSE; $all = FALSE; +$concurrency = 1; $class_names = FALSE; $test_names = array(); +$execute_batch = FALSE; +$test_id = NULL; while ($param = array_shift($_SERVER['argv'])) { switch ($param) { @@ -89,11 +94,14 @@ case '--clean': $clean = TRUE; break; - case '--reporter': - $reporter = array_shift($_SERVER['argv']); - if (!in_array($reporter, array("text", "xml", "html"))) { - $reporter = "text"; - } + case '--concurrency': + $concurrency = array_shift($_SERVER['argv']); + break; + case '--test-id': + $test_id = array_shift($_SERVER['argv']); + break; + case '--execute-batch': + $execute_batch = TRUE; break; default: $test_names += explode(',', $param); @@ -113,6 +121,68 @@ chdir(realpath(dirname(__FILE__) . '/..')); require_once './includes/bootstrap.inc'; + +if ($execute_batch) { + if (is_null($test_id)) { + echo "ERROR: --execute-batch should not be called interactively.\n"; + exit; + } + if ($concurrency == 1 || !function_exists('pcntl_fork')) { + // Fallback to mono-threaded execution + if (count($test_names) > 1) { + foreach($test_names as $test_class) { + // Note: we still need to execute each test in its separate Drupal environment + passthru($php . " ./scripts/run-tests.sh --url $url --concurrency 1 --test-id $test_id --execute-batch $test_class"); + } + exit; + } + else { + // Execute an individual test + $test_class = array_shift($test_names); + drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); + simpletest_run_one_test($test_id, $test_class); + exit; + } + } + else { + // Multi-threaded execution + $children = array(); + while (!empty($test_names) || !empty($children)) { + // Fork children + // Note: we can safely fork here, because Drupal is not bootstrapped yet + while(count($children) < $concurrency) { + if (empty($test_names)) break; + + $child = array(); + $child['test_class'] = $test_class = array_shift($test_names); + $child['pid'] = pcntl_fork(); + if (!$child['pid']) { + // This is the child process, bootstrap and execute the test + drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); + simpletest_run_one_test($test_id, $test_class); + exit; + } + else { + // Register our new child + $children[] = $child; + } + } + + // Wait for children every 200ms + usleep(200000); + + // Check if some children finished + foreach($children as $cid => $child) { + if (pcntl_waitpid($child['pid'], $status, WUNTRACED | WNOHANG)) { + // This particular child exited + unset($children[$cid]); + } + } + } + exit; + } +} + drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); if (!module_exists('simpletest')) { @@ -126,7 +196,7 @@ // Get the status messages and print them. $messages = array_pop(drupal_get_messages('status')); foreach($messages as $text) { - echo("- " . $text . "\n"); + echo(" - " . $text . "\n"); } exit; } @@ -134,92 +204,132 @@ // Run tests as user #1. $GLOBALS['user'] = user_load(1); -//Load simpletest files -$total_test = &simpletest_get_total_test(); - -$test_instances = $total_test->getTestInstances(); +// Load simpletest files +$all_tests = simpletest_get_all_tests(); +$groups = simpletest_categorize_tests($all_tests); +$test_list = array(); if ($list) { // Display all availabe tests. - echo("Available test groups:\n----------------------\n"); - foreach ($test_instances as $group_test) { - echo($group_test->getLabel() . "\n"); + echo("\nAvailable test groups\n---------------------\n\n"); + foreach ($groups as $group => $tests) { + echo($group . "\n"); + foreach ($tests as $class_name => $instance) { + $info = $instance->getInfo(); + echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n"; + } } exit; } if ($all) { - $test_list = NULL; + $test_list = array_keys($all_tests); } else { if ($class_names) { - $test_list = _run_tests_check_classes($test_names, $test_instances); + // Use only valid class names + foreach ($test_names as $class_name) { + if (isset($all_tests[$class_name])) { + $test_list[] = $class_name; + } + } } else { - $test_list = _run_tests_find_classes($test_names, $test_instances); + // Resolve group names + foreach ($test_names as $group_name) { + if (isset($groups[$group_name])) { + foreach($groups[$group_name] as $class_name => $instance) { + $test_list[] = $class_name; + } + } + } } } + if (empty($test_list) && !$all) { echo("ERROR: No valid tests were specified.\n"); exit; } - // If not in 'safe mode', increase the maximum execution time: if (!ini_get('safe_mode')) { - set_time_limit(360); + set_time_limit(0); } +echo "\n"; +echo "Drupal test run\n"; +echo "---------------\n"; +echo "\n"; + // Tell the user about what tests are to be run. -if (!$all && $reporter == 'text') { - echo("Tests to be run:\n"); - foreach ($test_list as $name) { - echo("- " . $name . "\n"); +if ($all) { + echo "All tests will run.\n\n"; +} +else { + echo "Tests to be run:\n"; + foreach ($test_list as $class_name) { + $info = $all_tests[$class_name]->getInfo(); + echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n"; } - echo("\n"); + echo "\n"; } -simpletest_run_tests(array_keys($test_list), $reporter); - -// Utility functions: -/** - * Check that each class name exists as a test, return the list of valid ones. - */ -function _run_tests_check_classes($test_names, $test_instances) { - $test_list = array(); - $test_names = array_flip($test_names); - - foreach ($test_instances as $group_test) { - $tests = $group_test->getTestInstances(); - foreach ($tests as $test) { - $class = get_class($test); - $info = $test->getInfo(); - if (isset($test_names[$class])) { - $test_list[$class] = $info['name']; - } - } +echo "Test run started: " . format_date(time(), 'long') . "\n"; +echo "\n"; + +db_query('INSERT INTO {simpletest_test_id} VALUES (default)'); +$test_id = db_last_insert_id('simpletest_test_id', 'test_id'); + +echo "Test summary:\n"; +echo "-------------\n"; +echo "\n"; + +// Now execute tests +passthru($php . " ./scripts/run-tests.sh --url $url --test-id $test_id --concurrency $concurrency --execute-batch " . implode(",", $test_list)); + +echo "\n"; +echo "Test run ended: " . format_date(time(), 'long') . "\n"; +echo "\n"; + +// Report results +echo "Detailed test results:\n"; +echo "----------------------\n"; +echo "\n"; + +$results_map = array( + 'pass' => 'Pass', + 'fail' => 'Fail', + 'exception' => 'Exception' +); + +$results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $test_id); +while($result = db_fetch_object($results)) { + if (isset($results_map[$result->status])) { + $data = array( + '[' . $results_map[$result->status] . ']', + $result->message, + $result->message_group, + basename($result->file), + $result->line, + $result->caller, + ); + echo implode("\t", $data) . "\n"; } - return $test_list; } +// Cleanup our test results +db_query("DELETE FROM {simpletest} WHERE test_id = %d", $test_id); + +// Support function: + /** - * Check that each group name exists, return the list of class in valid groups. + * Run a single test (assume a Drupal bootstrapped environnement). */ -function _run_tests_find_classes($test_names, &$test_instances) { - $test_list = array(); - $test_names = array_flip($test_names); - - uasort($test_instances, 'simpletest_compare_instances'); - foreach ($test_instances as $group_test) { - $group = $group_test->getLabel(); - if (isset($test_names[$group])) { - $tests = $group_test->getTestInstances(); - foreach ($tests as $test) { - $info = $test->getInfo(); - $test_list[get_class($test)] = $info['name']; - } - } - } - return $test_list; +function simpletest_run_one_test($test_id, $test_class) { + simpletest_get_all_tests(); + $test = new $test_class($test_id); + $test->run(); + $info = $test->getInfo(); + echo $info['name'] . ' ' . _simpletest_format_summary_line($test->_results) . "\n"; }