summaryrefslogtreecommitdiff
path: root/scripts/run-tests.sh
diff options
context:
space:
mode:
authorDries Buytaert <dries@buytaert.net>2008-07-07 06:22:18 +0000
committerDries Buytaert <dries@buytaert.net>2008-07-07 06:22:18 +0000
commita4883506e3064c1acf554d118ccf8a2ef09ad8d6 (patch)
tree7971c83a22cc812d07dd69621e4fae83f697a990 /scripts/run-tests.sh
parentf5645d67e795085b4b347beed496564f0f950b5c (diff)
downloadbrdo-a4883506e3064c1acf554d118ccf8a2ef09ad8d6.tar.gz
brdo-a4883506e3064c1acf554d118ccf8a2ef09ad8d6.tar.bz2
- Patch #254166 by boombatower: improve and clean-up the test script. Added color coding and allow people to control the verbosity.
Diffstat (limited to 'scripts/run-tests.sh')
-rwxr-xr-xscripts/run-tests.sh591
1 files changed, 380 insertions, 211 deletions
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index db7600b20..22615c1a9 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -1,28 +1,103 @@
<?php
// $Id$
-
/**
* @file
* This script runs Drupal tests from command line.
- * You can provide groups or class names of the tests you wish to run.
- * For example: php scripts/run-functional-tests.sh Profile
- * If no arguments are provided, the help text will print.
*/
-$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";
+define('SIMPLETEST_SCRIPT_COLOR_PASS', 32); // Green.
+define('SIMPLETEST_SCRIPT_COLOR_FAIL', 31); // Red.
+define('SIMPLETEST_SCRIPT_COLOR_EXCEPTION', 33); // Brown.
+
+// Set defaults and get overrides.
+list($args, $count) = simpletest_script_parse_args();
+
+simpletest_script_init();
+
+if ($args['help'] || $count == 0) {
+ simpletest_script_help();
+ exit;
+}
+
+if ($args['execute-batch']) {
+ simpletest_script_execute_batch();
+}
+
+// Bootstrap to perform initial validation or other opperations.
+drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
+if (!module_exists('simpletest')) {
+ simpletest_script_print_error("The simpletest module must be enabled before this script can run.");
+ exit;
+}
+
+if ($args['clean']) {
+ // Clean up left-over times and directories.
+ simpletest_clean_environment();
+ echo "\nEnvironment cleaned.\n";
+
+ // Get the status messages and print them.
+ $messages = array_pop(drupal_get_messages('status'));
+ foreach($messages as $text) {
+ echo " - " . $text . "\n";
+ }
+ exit;
+}
+
+// Load SimpleTest files.
+$all_tests = simpletest_get_all_tests();
+$groups = simpletest_categorize_tests($all_tests);
+$test_list = array();
+
+if ($args['list']) {
+ // Display all availabe tests.
+ echo "\nAvailable test groups & classes\n";
+ echo "-------------------------------\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;
+}
+
+$test_list = simpletest_script_get_test_list();
+
+// If not in 'safe mode', increase the maximum execution time.
+if (!ini_get('safe_mode')) {
+ set_time_limit(0);
+}
+
+simpletest_script_reporter_init();
+
+// Setup database for test results.
+db_query('INSERT INTO {simpletest_test_id} VALUES (default)');
+$test_id = db_last_insert_id('simpletest_test_id', 'test_id');
+
+// Execute tests.
+simpletest_script_command($args['concurrency'], $test_id, implode(",", $test_list));
+
+// Display results before database is cleared.
+simpletest_script_reporter_display_results();
+
+// Cleanup our test results.
+db_query("DELETE FROM {simpletest} WHERE test_id = %d", $test_id);
+
+
+
+/**
+ * Print help text.
+ */
+function simpletest_script_help() {
+ global $args;
-if (in_array('--help', $_SERVER['argv']) || empty($_SERVER['argv'])) {
echo <<<EOF
Run Drupal tests from the shell.
-Usage: {$script} [OPTIONS] <tests>
-Example: {$script} Profile
+Usage: {$args['script']} [OPTIONS] <tests>
+Example: {$args['script']} Profile
All arguments are long options.
@@ -46,6 +121,10 @@ All arguments are long options.
--class Run tests identified by specific class names, instead of group names.
+ --color Output the rusults with color highlighting.
+
+ --verbose Output detailed assertion messages in addition to summary.
+
<test1>[,<test2>[,<test3> ...]]
One or more tests to be run. By default, these are interpreted
@@ -57,124 +136,159 @@ All arguments are long options.
be separated by commas. Ignored if --all is specified.
To run this script you will normally invoke it from the root directory of your
-Drupal installation as
+Drupal installation as the webserver user, or root, with
-php ./scripts/{$script}
+php ./scripts/{$args['script']}
\n
EOF;
- exit;
}
-$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) {
- case '--list':
- $list = TRUE;
- break;
- case '--url':
- $url = array_shift($_SERVER['argv']);
- $parsed = parse_url($url);
- $host = $parsed['host'];
- $path = $parsed['path'];
- break;
- case '--all':
- $all = TRUE;
- break;
- case '--class':
- $class_names = TRUE;
- break;
- case '--clean':
- $clean = TRUE;
- break;
- 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);
- break;
+/**
+ * Parse execution argument and ensure that all are valid.
+ *
+ * @return The list of arguments.
+ */
+function simpletest_script_parse_args() {
+ // Set default values.
+ $args = array(
+ 'script' => '',
+ 'help' => FALSE,
+ 'list' => FALSE,
+ 'clean' => FALSE,
+ 'url' => '',
+ 'concurrency' => 1,
+ 'all' => FALSE,
+ 'class' => FALSE,
+ 'color' => FALSE,
+ 'verbose' => FALSE,
+ 'test_names' => array(),
+ // Used internally.
+ 'test-id' => NULL,
+ 'execute-batch' => FALSE
+ );
+
+ // Override with set values.
+ $args['script'] = basename(array_shift($_SERVER['argv']));
+
+ $count = 0;
+ while ($arg = array_shift($_SERVER['argv'])) {
+ if (preg_match('/--(\S+)/', $arg, $matches)) {
+ // Argument found.
+ if (array_key_exists($matches[1], $args)) {
+ // Argument found in list.
+ $previous_arg = $matches[1];
+ if (is_bool($args[$previous_arg])) {
+ $args[$matches[1]] = TRUE;
+ }
+ else {
+ $args[$matches[1]] = array_shift($_SERVER['argv']);
+ }
+ // Clear an extrenious values.
+ $args['test_names'] = array();
+ $count++;
+ }
+ else {
+ // Argument not found in list.
+ simpletest_script_print_error("Unknown argument '$arg'.");
+ exit;
+ }
+ }
+ else {
+ // Values found without an argument should be test names.
+ $args['test_names'] += explode(',', $arg);
+ }
}
+ return array($args, $count);
}
-$_SERVER['HTTP_HOST'] = $host;
-$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
-$_SERVER['SERVER_ADDR'] = '127.0.0.1';
-$_SERVER['SERVER_SOFTWARE'] = 'Apache';
-$_SERVER['SERVER_NAME'] = 'localhost';
-$_SERVER['REQUEST_URI'] = $path .'/';
-$_SERVER['SCRIPT_NAME'] = $path .'/index.php';
-$_SERVER['PHP_SELF'] = $path .'/index.php';
-$_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
-
-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";
+/**
+ * Initialize script variables and perform general setup requirements.
+ */
+function simpletest_script_init() {
+ global $args, $php;
+
+ $host = 'localhost';
+ $path = '';
+ $php = "/usr/bin/php"; // TODO Get dynamically if possible.
+
+ // Get url from arguments.
+ if (!empty($args['url'])) {
+ $parsed_url = parse_url($args['url']);
+ $host = $parsed_url['host'];
+ $path = $parsed_url['path'];
+ }
+
+ $_SERVER['HTTP_HOST'] = $host;
+ $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+ $_SERVER['SERVER_ADDR'] = '127.0.0.1';
+ $_SERVER['SERVER_SOFTWARE'] = 'Apache';
+ $_SERVER['SERVER_NAME'] = 'localhost';
+ $_SERVER['REQUEST_URI'] = $path .'/';
+ $_SERVER['SCRIPT_NAME'] = $path .'/index.php';
+ $_SERVER['PHP_SELF'] = $path .'/index.php';
+ $_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
+
+ chdir(realpath(dirname(__FILE__) . '/..'));
+ require_once './includes/bootstrap.inc';
+}
+
+/**
+ * Execute a batch of tests.
+ */
+function simpletest_script_execute_batch() {
+ global $args;
+
+ if (is_null($args['test-id'])) {
+ simpletest_script_print_error("--execute-batch should not be called interactively.");
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");
+ if ($args['concurrency'] == 1 || !function_exists('pcntl_fork')) {
+ // Fallback to mono-threaded execution.
+ if (count($args['test_names']) > 1) {
+ foreach ($args['test_names'] as $test_class) {
+ // Execute each test in its separate Drupal environment.
+ simpletest_script_command(1, $args['test-id'], $test_class);
}
exit;
}
else {
- // Execute an individual test
- $test_class = array_shift($test_names);
+ // Execute an individual test.
+ $test_class = array_shift($args['test_names']);
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
- simpletest_run_one_test($test_id, $test_class);
+ simpletest_script_run_one_test($args['test-id'], $test_class);
exit;
}
}
else {
- // Multi-threaded execution
+ // 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;
+ while (!empty($args['test_names']) || !empty($children)) {
+ // Fork children safely since Drupal is not bootstrapped yet.
+ while (count($children) < $concurrency) {
+ if (empty($args['test_names'])) break;
$child = array();
- $child['test_class'] = $test_class = array_shift($test_names);
+ $child['test_class'] = $test_class = array_shift($args['test_names']);
$child['pid'] = pcntl_fork();
if (!$child['pid']) {
- // This is the child process, bootstrap and execute the test
+ // This is the child process, bootstrap and execute the test.
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
- simpletest_run_one_test($test_id, $test_class);
+ simpletest_script_run_one_test($test_id, $test_class);
exit;
}
else {
- // Register our new child
+ // Register our new child.
$children[] = $child;
}
}
- // Wait for children every 200ms
+ // Wait for children every 200ms.
usleep(200000);
- // Check if some children finished
- foreach($children as $cid => $child) {
+ // Check if some children finished.
+ foreach ($children as $cid => $child) {
if (pcntl_waitpid($child['pid'], $status, WUNTRACED | WNOHANG)) {
- // This particular child exited
+ // This particular child exited.
unset($children[$cid]);
}
}
@@ -183,153 +297,208 @@ if ($execute_batch) {
}
}
-drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
-
-if (!module_exists('simpletest')) {
- echo("ERROR: The simpletest module must be enabled before this script can run.\n");
- exit;
-}
+/**
+ * Run a single test (assume a Drupal bootstrapped environnement).
+ */
+function simpletest_script_run_one_test($test_id, $test_class) {
+ simpletest_get_all_tests();
+ $test = new $test_class($test_id);
+ $test->run();
+ $info = $test->getInfo();
-if ($clean) {
- // Clean up left-over times and directories.
- simpletest_clean_environment();
- // Get the status messages and print them.
- $messages = array_pop(drupal_get_messages('status'));
- foreach($messages as $text) {
- echo(" - " . $text . "\n");
- }
- exit;
+ $status = ((isset($test->_results['#fail']) && $test->_results['#fail'] > 0)
+ || (isset($test->_results['#exception']) && $test->_results['#exception'] > 0) ? 'fail' : 'pass');
+ simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->_results) . "\n", simpletest_script_color_code($status));
}
-// Run tests as user #1.
-$GLOBALS['user'] = user_load(1);
-
-// Load simpletest files
-$all_tests = simpletest_get_all_tests();
-$groups = simpletest_categorize_tests($all_tests);
-$test_list = array();
+/**
+ * Execute a command to run batch of tests in separate process.
+ */
+function simpletest_script_command($concurrency, $test_id, $tests) {
+ global $args, $php;
-if ($list) {
- // Display all availabe tests.
- 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";
- }
+ $command = "$php ./scripts/{$args['script']} --url {$args['url']}";
+ if ($args['color']) {
+ $command .= ' --color';
}
- exit;
+ $command .= " --concurrency $concurrency --test-id $test_id --execute-batch $tests";
+ passthru($command);
}
-if ($all) {
- $test_list = array_keys($all_tests);
-}
-else {
- if ($class_names) {
- // Use only valid class names
- foreach ($test_names as $class_name) {
- if (isset($all_tests[$class_name])) {
- $test_list[] = $class_name;
- }
- }
+/**
+ * Get list of tests based on arguments. If --all specfied then
+ * returns all available tests, otherwise reads list of tests.
+ *
+ * Will print error and exit if no valid tests were found.
+ *
+ * @return List of tests.
+ */
+function simpletest_script_get_test_list() {
+ global $args, $all_tests, $groups;
+
+ $test_list = array();
+ if ($args['all']) {
+ $test_list = array_keys($all_tests);
}
else {
- // Resolve group names
- foreach ($test_names as $group_name) {
- if (isset($groups[$group_name])) {
- foreach($groups[$group_name] as $class_name => $instance) {
+ if ($args['class_names']) {
+ // Check for valid class names.
+ foreach ($args['test_names'] as $class_name) {
+ if (isset($all_tests[$class_name])) {
$test_list[] = $class_name;
}
}
}
+ else {
+ // Check for valid group names and get all valid classes in group.
+ foreach ($args['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 (empty($test_list)) {
+ simpletest_script_print_error('No valid tests were specified.');
+ exit;
+ }
+ return $test_list;
}
-// If not in 'safe mode', increase the maximum execution time:
-if (!ini_get('safe_mode')) {
- set_time_limit(0);
-}
+/**
+ * Initialize the reporter.
+ */
+function simpletest_script_reporter_init() {
+ global $args, $all_tests, $test_list;
-echo "\n";
-echo "Drupal test run\n";
-echo "---------------\n";
-echo "\n";
+ echo "\n";
+ echo "Drupal test run\n";
+ echo "---------------\n";
+ echo "\n";
-// Tell the user about what tests are to be run.
-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";
+ // Tell the user about what tests are to be run.
+ if ($args['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 "Test run started: " . format_date(time(), 'long') . "\n";
+ echo "\n";
+
+ echo "Test summary:\n";
+ echo "-------------\n";
echo "\n";
}
-echo "Test run started: " . format_date(time(), 'long') . "\n";
-echo "\n";
+/**
+ * Display test results.
+ */
+function simpletest_script_reporter_display_results() {
+ global $args, $test_id, $results_map;
-db_query('INSERT INTO {simpletest_test_id} VALUES (default)');
-$test_id = db_last_insert_id('simpletest_test_id', 'test_id');
+ echo "\n";
+ echo "Test run ended: " . format_date(time(), 'long') . "\n";
+ echo "\n";
-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,
+ if ($args['verbose']) {
+ // Report results.
+ echo "Detailed test results:\n";
+ echo "----------------------\n";
+ echo "\n";
+
+ $results_map = array(
+ 'pass' => 'Pass',
+ 'fail' => 'Fail',
+ 'exception' => 'Exception'
);
- echo implode("\t", $data) . "\n";
+
+ $results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $test_id);
+ $test_class = '';
+ while($result = db_fetch_object($results)) {
+ if (isset($results_map[$result->status])) {
+ if ($result->test_class != $test_class) {
+ // Display test class every time results are for new test class.
+ echo "\n\n---- $result->test_class ----\n\n\n";
+ $test_class = $result->test_class;
+ }
+
+ simpletest_script_format_result($result);
+ }
+ }
}
}
-// Cleanup our test results
-db_query("DELETE FROM {simpletest} WHERE test_id = %d", $test_id);
+/**
+ * Format the result so that it fits within the default 80 character
+ * terminal size.
+ *
+ * @param $result The result object to format.
+ */
+function simpletest_script_format_result($result) {
+ global $results_map, $color;
+
+ $summary = sprintf("%-10.10s %-10.10s %-30.30s %-5.5s %-20.20s\n",
+ $results_map[$result->status], $result->message_group, basename($result->file), $result->line, $result->caller);
+
+ simpletest_script_print($summary, simpletest_script_color_code($result->status));
-// Support function:
+ $lines = explode("\n", wordwrap(trim(strip_tags($result->message)), 76));
+ foreach ($lines as $line) {
+ echo " $line\n";
+ }
+}
/**
- * Run a single test (assume a Drupal bootstrapped environnement).
+ * Print error message prefixed with " ERROR: " and displayed in fail color
+ * if color output is enabled.
+ *
+ * @param $message The message to print.
*/
-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";
+function simpletest_script_print_error($message) {
+ simpletest_script_print(" ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
}
+/**
+ * Print a message to the console, if color is enabled then the specified
+ * color code will be used.
+ *
+ * @param $message The message to print.
+ * @param $color_code The color code to use for coloring.
+ */
+function simpletest_script_print($message, $color_code) {
+ global $args;
+ if ($args['color']) {
+ echo "\033[" . $color_code . "m" . $message . "\033[0m";
+ }
+ else {
+ echo $message;
+ }
+}
+
+/**
+ * Get the color code associated with the specified status.
+ *
+ * @param $status The status string to get code for.
+ * @return Color code.
+ */
+function simpletest_script_color_code($status) {
+ switch ($status) {
+ case 'pass':
+ return SIMPLETEST_SCRIPT_COLOR_PASS;
+ case 'fail':
+ return SIMPLETEST_SCRIPT_COLOR_FAIL;
+ case 'exception':
+ return SIMPLETEST_SCRIPT_COLOR_EXCEPTION;
+ }
+ return 0; // Default formatting.
+}