2
// $Id: run-tests.sh,v 1.1.2.5 2009/09/05 13:34:10 boombatower Exp $
3
// Core: Id: run-tests.sh,v 1.35 2009/08/17 19:14:41 webchick Exp
7
* Backport of Drupal 7 run-tests.sh with modifications, see BACKPORT.txt.
8
* This file must be placed in the Drupal scripts folder in order for it to
11
* Copyright 2008-2009 by Jimmy Berry ("boombatower", http://drupal.org/user/214218)
14
define('SIMPLETEST_SCRIPT_COLOR_PASS', 32); // Green.
15
define('SIMPLETEST_SCRIPT_COLOR_FAIL', 31); // Red.
16
define('SIMPLETEST_SCRIPT_COLOR_EXCEPTION', 33); // Brown.
18
// Set defaults and get overrides.
19
list($args, $count) = simpletest_script_parse_args();
21
if ($args['help'] || $count == 0) {
22
simpletest_script_help();
26
if ($args['execute-batch']) {
27
// Masquerade as Apache for running tests.
28
simpletest_script_init("Apache");
29
simpletest_script_execute_batch();
32
// Run administrative functions as CLI.
33
simpletest_script_init("PHP CLI");
36
// Bootstrap to perform initial validation or other operations.
37
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
38
if (!module_exists('simpletest')) {
39
simpletest_script_print_error("The simpletest module must be enabled before this script can run.");
44
// Clean up left-over times and directories.
45
simpletest_clean_environment();
46
echo "\nEnvironment cleaned.\n";
48
// Get the status messages and print them.
49
$messages = array_pop(drupal_get_messages('status'));
50
foreach($messages as $text) {
51
echo " - " . $text . "\n";
56
// Load SimpleTest files.
57
$groups = simpletest_test_get_all();
59
foreach ($groups as $group => $tests) {
60
$all_tests = array_merge($all_tests, array_keys($tests));
65
// Display all available tests.
66
echo "\nAvailable test groups & classes\n";
67
echo "-------------------------------\n\n";
68
foreach ($groups as $group => $tests) {
70
foreach ($tests as $class => $info) {
71
echo " - " . $info['name'] . ' (' . $class . ')' . "\n";
77
$test_list = simpletest_script_get_test_list();
79
// Try to allocate unlimited time to run the tests.
80
//drupal_set_time_limit(0);
81
if (!ini_get('safe_mode')) {
85
simpletest_script_reporter_init();
87
// Setup database for test results.
88
//$test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
89
db_query('INSERT INTO {simpletest_test_id} VALUES (default)');
90
$test_id = db_last_insert_id('simpletest_test_id', 'test_id');
93
simpletest_script_command($args['concurrency'], $test_id, implode(",", $test_list));
95
// Retrieve the last database prefix used for testing and the last test class
96
// that was run from. Use the information to read the lgo file in case any
97
// fatal errors caused the test to crash.
98
list($last_prefix, $last_test_class) = simpletest_last_test_get($test_id);
99
simpletest_log_read($test_id, $last_prefix, $last_test_class);
101
// Display results before database is cleared.
102
simpletest_script_reporter_display_results();
104
// Cleanup our test results.
105
simpletest_clean_results_table($test_id);
110
function simpletest_script_help() {
115
Run Drupal tests from the shell.
117
Usage: {$args['script']} [OPTIONS] <tests>
118
Example: {$args['script']} Profile
120
All arguments are long options.
122
--help Print this page.
124
--list Display all available test groups.
126
--clean Cleans up database tables or directories from previous, failed,
127
tests and then exits (no tests are run).
129
--url Immediately preceeds a URL to set the host and path. You will
130
need this parameter if Drupal is in a subdirectory on your
131
localhost and you have not set \$base_url in settings.php.
133
--php The absolute path to the PHP executable. Usually not needed.
137
Run tests in parallel, up to [num] tests at a time. This requires
138
the Process Control Extension (PCNTL) to be compiled in PHP, not
139
supported under Windows.
141
--all Run all available tests.
143
--class Run tests identified by specific class names, instead of group names.
145
--file Run tests identified by specific file names, instead of group names.
146
Specify the path and the extension (i.e. 'modules/user/user.test').
148
--color Output the results with color highlighting.
150
--verbose Output detailed assertion messages in addition to summary.
152
<test1>[,<test2>[,<test3> ...]]
154
One or more tests to be run. By default, these are interpreted
155
as the names of test groups as shown at
156
?q=admin/build/testing.
157
These group names typically correspond to module names like "User"
158
or "Profile" or "System", but there is also a group "XML-RPC".
159
If --class is specified then these are interpreted as the names of
160
specific test classes whose test methods will be run. Tests must
161
be separated by commas. Ignored if --all is specified.
163
To run this script you will normally invoke it from the root directory of your
164
Drupal installation as the webserver user (differs per configuration), or root:
166
sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
167
--url http://example.com/ --all
168
sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
169
--url http://example.com/ --class UploadTestCase
175
* Parse execution argument and ensure that all are valid.
177
* @return The list of arguments.
179
function simpletest_script_parse_args() {
180
// Set default values.
194
'test_names' => array(),
197
'execute-batch' => FALSE
200
// Override with set values.
201
$args['script'] = basename(array_shift($_SERVER['argv']));
204
while ($arg = array_shift($_SERVER['argv'])) {
205
if (preg_match('/--(\S+)/', $arg, $matches)) {
207
if (array_key_exists($matches[1], $args)) {
208
// Argument found in list.
209
$previous_arg = $matches[1];
210
if (is_bool($args[$previous_arg])) {
211
$args[$matches[1]] = TRUE;
214
$args[$matches[1]] = array_shift($_SERVER['argv']);
216
// Clear extraneous values.
217
$args['test_names'] = array();
221
// Argument not found in list.
222
simpletest_script_print_error("Unknown argument '$arg'.");
227
// Values found without an argument should be test names.
228
$args['test_names'] += explode(',', $arg);
233
// Validate the concurrency argument
234
if (!is_numeric($args['concurrency']) || $args['concurrency'] <= 0) {
235
simpletest_script_print_error("--concurrency must be a strictly positive integer.");
238
elseif ($args['concurrency'] > 1 && !function_exists('pcntl_fork')) {
239
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.");
243
return array($args, $count);
247
* Initialize script variables and perform general setup requirements.
249
function simpletest_script_init($server_software) {
254
// Determine location of php command automatically, unless a command line argument is supplied.
255
if (!empty($args['php'])) {
258
elseif (!empty($_ENV['_'])) {
259
// '_' is an environment variable set by the shell. It contains the command that was executed.
262
elseif (!empty($_ENV['SUDO_COMMAND'])) {
263
// 'SUDO_COMMAND' is an environment variable set by the sudo program.
264
// Extract only the PHP interpreter, not the rest of the command.
265
list($php, ) = explode(' ', $_ENV['SUDO_COMMAND'], 2);
268
simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Please supply the --php command line argument.');
269
simpletest_script_help();
273
// Get url from arguments.
274
if (!empty($args['url'])) {
275
$parsed_url = parse_url($args['url']);
276
$host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
277
$path = $parsed_url['path'];
280
$_SERVER['HTTP_HOST'] = $host;
281
$_SERVER['REMOTE_ADDR'] = '127.0.0.1';
282
$_SERVER['SERVER_ADDR'] = '127.0.0.1';
283
$_SERVER['SERVER_SOFTWARE'] = $server_software;
284
$_SERVER['SERVER_NAME'] = 'localhost';
285
$_SERVER['REQUEST_URI'] = $path .'/';
286
$_SERVER['REQUEST_METHOD'] = 'GET';
287
$_SERVER['SCRIPT_NAME'] = $path .'/index.php';
288
$_SERVER['PHP_SELF'] = $path .'/index.php';
289
$_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
291
chdir(realpath(dirname(__FILE__) . '/..'));
292
define('DRUPAL_ROOT', getcwd());
293
require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
297
* Execute a batch of tests.
299
function simpletest_script_execute_batch() {
302
if (is_null($args['test-id'])) {
303
simpletest_script_print_error("--execute-batch should not be called interactively.");
306
if ($args['concurrency'] == 1) {
307
// Fallback to mono-threaded execution.
308
if (count($args['test_names']) > 1) {
309
foreach ($args['test_names'] as $test_class) {
310
// Execute each test in its separate Drupal environment.
311
simpletest_script_command(1, $args['test-id'], $test_class);
316
// Execute an individual test.
317
$test_class = array_shift($args['test_names']);
318
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
319
simpletest_script_run_one_test($args['test-id'], $test_class);
324
// Multi-threaded execution.
326
while (!empty($args['test_names']) || !empty($children)) {
327
// Fork children safely since Drupal is not bootstrapped yet.
328
while (count($children) < $args['concurrency']) {
329
if (empty($args['test_names'])) break;
332
$child['test_class'] = $test_class = array_shift($args['test_names']);
333
$child['pid'] = pcntl_fork();
334
if (!$child['pid']) {
335
// This is the child process, bootstrap and execute the test.
336
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
337
simpletest_script_run_one_test($args['test-id'], $test_class);
341
// Register our new child.
342
$children[] = $child;
346
// Wait for children every 200ms.
349
// Check if some children finished.
350
foreach ($children as $cid => $child) {
351
if (pcntl_waitpid($child['pid'], $status, WUNTRACED | WNOHANG)) {
352
// This particular child exited.
353
unset($children[$cid]);
362
* Run a single test (assume a Drupal bootstrapped environment).
364
function simpletest_script_run_one_test($test_id, $test_class) {
366
require_once drupal_get_path('module', 'simpletest') . '/drupal_web_test_case.php';
367
$classes = simpletest_test_get_all_classes();
368
require_once $classes[$test_class]['file'];
370
$test = new $test_class($test_id);
372
$info = $test->getInfo();
374
$status = ((isset($test->results['#fail']) && $test->results['#fail'] > 0)
375
|| (isset($test->results['#exception']) && $test->results['#exception'] > 0) ? 'fail' : 'pass');
376
simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
380
* Execute a command to run batch of tests in separate process.
382
function simpletest_script_command($concurrency, $test_id, $tests) {
385
$command = "$php ./scripts/{$args['script']} --url {$args['url']}";
386
if ($args['color']) {
387
$command .= ' --color';
389
$command .= " --php " . escapeshellarg($php) . " --concurrency $concurrency --test-id $test_id --execute-batch $tests";
394
* Get list of tests based on arguments. If --all specified then
395
* returns all available tests, otherwise reads list of tests.
397
* Will print error and exit if no valid tests were found.
399
* @return List of tests.
401
function simpletest_script_get_test_list() {
402
global $args, $all_tests, $groups;
404
$test_list = array();
406
$test_list = $all_tests;
409
if ($args['class']) {
410
// Check for valid class names.
411
foreach ($args['test_names'] as $class_name) {
412
if (in_array($class_name, $all_tests)) {
413
$test_list[] = $class_name;
417
elseif ($args['file']) {
419
foreach ($args['test_names'] as $file) {
420
// $files[drupal_realpath($file)] = 1;
421
$files[realpath($file)] = 1;
424
// Check for valid class names.
425
foreach ($all_tests as $class_name) {
426
$refclass = new ReflectionClass($class_name);
427
$file = $refclass->getFileName();
428
if (isset($files[$file])) {
429
$test_list[] = $class_name;
434
// Check for valid group names and get all valid classes in group.
435
foreach ($args['test_names'] as $group_name) {
436
if (isset($groups[$group_name])) {
437
foreach($groups[$group_name] as $class_name => $info) {
438
$test_list[] = $class_name;
445
if (empty($test_list)) {
446
simpletest_script_print_error('No valid tests were specified.');
453
* Initialize the reporter.
455
function simpletest_script_reporter_init() {
456
global $args, $all_tests, $test_list;
459
echo "Drupal test run\n";
460
echo "---------------\n";
463
// Tell the user about what tests are to be run.
465
echo "All tests will run.\n\n";
468
echo "Tests to be run:\n";
469
foreach ($test_list as $class_name) {
470
$info = call_user_func(array($class_name, 'getInfo'));
471
echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
476
echo "Test run started: " . format_date($_SERVER['REQUEST_TIME'], 'long') . "\n";
477
timer_start('run-tests');
480
echo "Test summary:\n";
481
echo "-------------\n";
486
* Display test results.
488
function simpletest_script_reporter_display_results() {
489
global $args, $test_id, $results_map;
492
$end = timer_stop('run-tests');
493
echo "Test run duration: " . format_interval($end['time'] / 1000);
496
if ($args['verbose']) {
498
echo "Detailed test results:\n";
499
echo "----------------------\n";
502
$results_map = array(
505
'exception' => 'Exception'
508
// $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
509
$results = db_query("SELECT * FROM {simpletest} WHERE test_id = %d ORDER BY test_class, message_id", $test_id);
512
// foreach ($results as $result) {
513
while ($result = db_fetch_object($results)) {
514
if (isset($results_map[$result->status])) {
515
if ($result->test_class != $test_class) {
516
// Display test class every time results are for new test class.
517
echo "\n\n---- $result->test_class ----\n\n\n";
518
$test_class = $result->test_class;
521
simpletest_script_format_result($result);
528
* Format the result so that it fits within the default 80 character
531
* @param $result The result object to format.
533
function simpletest_script_format_result($result) {
534
global $results_map, $color;
536
$summary = sprintf("%-10.10s %-10.10s %-30.30s %-5.5s %-20.20s\n",
537
$results_map[$result->status], $result->message_group, basename($result->file), $result->line, $result->caller);
539
simpletest_script_print($summary, simpletest_script_color_code($result->status));
541
$lines = explode("\n", wordwrap(trim(strip_tags($result->message)), 76));
542
foreach ($lines as $line) {
548
* Print error message prefixed with " ERROR: " and displayed in fail color
549
* if color output is enabled.
551
* @param $message The message to print.
553
function simpletest_script_print_error($message) {
554
simpletest_script_print(" ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
558
* Print a message to the console, if color is enabled then the specified
559
* color code will be used.
561
* @param $message The message to print.
562
* @param $color_code The color code to use for coloring.
564
function simpletest_script_print($message, $color_code) {
566
if ($args['color']) {
567
echo "\033[" . $color_code . "m" . $message . "\033[0m";
575
* Get the color code associated with the specified status.
577
* @param $status The status string to get code for.
578
* @return Color code.
580
function simpletest_script_color_code($status) {
583
return SIMPLETEST_SCRIPT_COLOR_PASS;
585
return SIMPLETEST_SCRIPT_COLOR_FAIL;
587
return SIMPLETEST_SCRIPT_COLOR_EXCEPTION;
589
return 0; // Default formatting.