cms/drupal/scripts/run-tests.sh
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 /**
       
     3  * @file
       
     4  * This script runs Drupal tests from command line.
       
     5  */
       
     6 
       
     7 define('SIMPLETEST_SCRIPT_COLOR_PASS', 32); // Green.
       
     8 define('SIMPLETEST_SCRIPT_COLOR_FAIL', 31); // Red.
       
     9 define('SIMPLETEST_SCRIPT_COLOR_EXCEPTION', 33); // Brown.
       
    10 
       
    11 define('SIMPLETEST_SCRIPT_EXIT_SUCCESS', 0);
       
    12 define('SIMPLETEST_SCRIPT_EXIT_FAILURE', 1);
       
    13 define('SIMPLETEST_SCRIPT_EXIT_EXCEPTION', 2);
       
    14 
       
    15 // Set defaults and get overrides.
       
    16 list($args, $count) = simpletest_script_parse_args();
       
    17 
       
    18 if ($args['help'] || $count == 0) {
       
    19   simpletest_script_help();
       
    20   exit(($count == 0) ? SIMPLETEST_SCRIPT_EXIT_FAILURE : SIMPLETEST_SCRIPT_EXIT_SUCCESS);
       
    21 }
       
    22 
       
    23 if ($args['execute-test']) {
       
    24   // Masquerade as Apache for running tests.
       
    25   simpletest_script_init("Apache");
       
    26   simpletest_script_run_one_test($args['test-id'], $args['execute-test']);
       
    27 }
       
    28 else {
       
    29   // Run administrative functions as CLI.
       
    30   simpletest_script_init(NULL);
       
    31 }
       
    32 
       
    33 // Bootstrap to perform initial validation or other operations.
       
    34 drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
       
    35 if (!module_exists('simpletest')) {
       
    36   simpletest_script_print_error("The simpletest module must be enabled before this script can run.");
       
    37   exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
       
    38 }
       
    39 
       
    40 if ($args['clean']) {
       
    41   // Clean up left-over times and directories.
       
    42   simpletest_clean_environment();
       
    43   echo "\nEnvironment cleaned.\n";
       
    44 
       
    45   // Get the status messages and print them.
       
    46   $messages = array_pop(drupal_get_messages('status'));
       
    47   foreach ($messages as $text) {
       
    48     echo " - " . $text . "\n";
       
    49   }
       
    50   exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
       
    51 }
       
    52 
       
    53 // Load SimpleTest files.
       
    54 $groups = simpletest_test_get_all();
       
    55 $all_tests = array();
       
    56 foreach ($groups as $group => $tests) {
       
    57   $all_tests = array_merge($all_tests, array_keys($tests));
       
    58 }
       
    59 $test_list = array();
       
    60 
       
    61 if ($args['list']) {
       
    62   // Display all available tests.
       
    63   echo "\nAvailable test groups & classes\n";
       
    64   echo   "-------------------------------\n\n";
       
    65   foreach ($groups as $group => $tests) {
       
    66     echo $group . "\n";
       
    67     foreach ($tests as $class => $info) {
       
    68       echo " - " . $info['name'] . ' (' . $class . ')' . "\n";
       
    69     }
       
    70   }
       
    71   exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
       
    72 }
       
    73 
       
    74 $test_list = simpletest_script_get_test_list();
       
    75 
       
    76 // Try to allocate unlimited time to run the tests.
       
    77 drupal_set_time_limit(0);
       
    78 
       
    79 simpletest_script_reporter_init();
       
    80 
       
    81 // Setup database for test results.
       
    82 $test_id = db_insert('simpletest_test_id')->useDefaults(array('test_id'))->execute();
       
    83 
       
    84 // Execute tests.
       
    85 $status = simpletest_script_execute_batch($test_id, simpletest_script_get_test_list());
       
    86 
       
    87 // Retrieve the last database prefix used for testing and the last test class
       
    88 // that was run from. Use the information to read the lgo file in case any
       
    89 // fatal errors caused the test to crash.
       
    90 list($last_prefix, $last_test_class) = simpletest_last_test_get($test_id);
       
    91 simpletest_log_read($test_id, $last_prefix, $last_test_class);
       
    92 
       
    93 // Stop the timer.
       
    94 simpletest_script_reporter_timer_stop();
       
    95 
       
    96 // Display results before database is cleared.
       
    97 simpletest_script_reporter_display_results();
       
    98 
       
    99 if ($args['xml']) {
       
   100   simpletest_script_reporter_write_xml_results();
       
   101 }
       
   102 
       
   103 // Cleanup our test results.
       
   104 simpletest_clean_results_table($test_id);
       
   105 
       
   106 // Test complete, exit.
       
   107 exit($status);
       
   108 
       
   109 /**
       
   110  * Print help text.
       
   111  */
       
   112 function simpletest_script_help() {
       
   113   global $args;
       
   114 
       
   115   echo <<<EOF
       
   116 
       
   117 Run Drupal tests from the shell.
       
   118 
       
   119 Usage:        {$args['script']} [OPTIONS] <tests>
       
   120 Example:      {$args['script']} Profile
       
   121 
       
   122 All arguments are long options.
       
   123 
       
   124   --help      Print this page.
       
   125 
       
   126   --list      Display all available test groups.
       
   127 
       
   128   --clean     Cleans up database tables or directories from previous, failed,
       
   129               tests and then exits (no tests are run).
       
   130 
       
   131   --url       Immediately precedes a URL to set the host and path. You will
       
   132               need this parameter if Drupal is in a subdirectory on your
       
   133               localhost and you have not set \$base_url in settings.php. Tests
       
   134               can be run under SSL by including https:// in the URL.
       
   135 
       
   136   --php       The absolute path to the PHP executable. Usually not needed.
       
   137 
       
   138   --concurrency [num]
       
   139 
       
   140               Run tests in parallel, up to [num] tests at a time.
       
   141 
       
   142   --all       Run all available tests.
       
   143 
       
   144   --class     Run tests identified by specific class names, instead of group names.
       
   145 
       
   146   --file      Run tests identified by specific file names, instead of group names.
       
   147               Specify the path and the extension (i.e. 'modules/user/user.test').
       
   148 
       
   149   --directory Run all tests found within the specified file directory.
       
   150 
       
   151   --xml       <path>
       
   152 
       
   153               If provided, test results will be written as xml files to this path.
       
   154 
       
   155   --color     Output text format results with color highlighting.
       
   156 
       
   157   --verbose   Output detailed assertion messages in addition to summary.
       
   158 
       
   159   <test1>[,<test2>[,<test3> ...]]
       
   160 
       
   161               One or more tests to be run. By default, these are interpreted
       
   162               as the names of test groups as shown at
       
   163               ?q=admin/config/development/testing.
       
   164               These group names typically correspond to module names like "User"
       
   165               or "Profile" or "System", but there is also a group "XML-RPC".
       
   166               If --class is specified then these are interpreted as the names of
       
   167               specific test classes whose test methods will be run. Tests must
       
   168               be separated by commas. Ignored if --all is specified.
       
   169 
       
   170 To run this script you will normally invoke it from the root directory of your
       
   171 Drupal installation as the webserver user (differs per configuration), or root:
       
   172 
       
   173 sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
       
   174   --url http://example.com/ --all
       
   175 sudo -u [wwwrun|www-data|etc] php ./scripts/{$args['script']}
       
   176   --url http://example.com/ --class BlockTestCase
       
   177 \n
       
   178 EOF;
       
   179 }
       
   180 
       
   181 /**
       
   182  * Parse execution argument and ensure that all are valid.
       
   183  *
       
   184  * @return The list of arguments.
       
   185  */
       
   186 function simpletest_script_parse_args() {
       
   187   // Set default values.
       
   188   $args = array(
       
   189     'script' => '',
       
   190     'help' => FALSE,
       
   191     'list' => FALSE,
       
   192     'clean' => FALSE,
       
   193     'url' => '',
       
   194     'php' => '',
       
   195     'concurrency' => 1,
       
   196     'all' => FALSE,
       
   197     'class' => FALSE,
       
   198     'file' => FALSE,
       
   199     'directory' => '',
       
   200     'color' => FALSE,
       
   201     'verbose' => FALSE,
       
   202     'test_names' => array(),
       
   203     // Used internally.
       
   204     'test-id' => 0,
       
   205     'execute-test' => '',
       
   206     'xml' => '',
       
   207   );
       
   208 
       
   209   // Override with set values.
       
   210   $args['script'] = basename(array_shift($_SERVER['argv']));
       
   211 
       
   212   $count = 0;
       
   213   while ($arg = array_shift($_SERVER['argv'])) {
       
   214     if (preg_match('/--(\S+)/', $arg, $matches)) {
       
   215       // Argument found.
       
   216       if (array_key_exists($matches[1], $args)) {
       
   217         // Argument found in list.
       
   218         $previous_arg = $matches[1];
       
   219         if (is_bool($args[$previous_arg])) {
       
   220           $args[$matches[1]] = TRUE;
       
   221         }
       
   222         else {
       
   223           $args[$matches[1]] = array_shift($_SERVER['argv']);
       
   224         }
       
   225         // Clear extraneous values.
       
   226         $args['test_names'] = array();
       
   227         $count++;
       
   228       }
       
   229       else {
       
   230         // Argument not found in list.
       
   231         simpletest_script_print_error("Unknown argument '$arg'.");
       
   232         exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
       
   233       }
       
   234     }
       
   235     else {
       
   236       // Values found without an argument should be test names.
       
   237       $args['test_names'] += explode(',', $arg);
       
   238       $count++;
       
   239     }
       
   240   }
       
   241 
       
   242   // Validate the concurrency argument
       
   243   if (!is_numeric($args['concurrency']) || $args['concurrency'] <= 0) {
       
   244     simpletest_script_print_error("--concurrency must be a strictly positive integer.");
       
   245     exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
       
   246   }
       
   247 
       
   248   return array($args, $count);
       
   249 }
       
   250 
       
   251 /**
       
   252  * Initialize script variables and perform general setup requirements.
       
   253  */
       
   254 function simpletest_script_init($server_software) {
       
   255   global $args, $php;
       
   256 
       
   257   $host = 'localhost';
       
   258   $path = '';
       
   259   // Determine location of php command automatically, unless a command line argument is supplied.
       
   260   if (!empty($args['php'])) {
       
   261     $php = $args['php'];
       
   262   }
       
   263   elseif ($php_env = getenv('_')) {
       
   264     // '_' is an environment variable set by the shell. It contains the command that was executed.
       
   265     $php = $php_env;
       
   266   }
       
   267   elseif ($sudo = getenv('SUDO_COMMAND')) {
       
   268     // 'SUDO_COMMAND' is an environment variable set by the sudo program.
       
   269     // Extract only the PHP interpreter, not the rest of the command.
       
   270     list($php, ) = explode(' ', $sudo, 2);
       
   271   }
       
   272   else {
       
   273     simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Supply the --php command line argument.');
       
   274     simpletest_script_help();
       
   275     exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
       
   276   }
       
   277 
       
   278   // Get URL from arguments.
       
   279   if (!empty($args['url'])) {
       
   280     $parsed_url = parse_url($args['url']);
       
   281     $host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
       
   282     $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
       
   283 
       
   284     // If the passed URL schema is 'https' then setup the $_SERVER variables
       
   285     // properly so that testing will run under HTTPS.
       
   286     if ($parsed_url['scheme'] == 'https') {
       
   287       $_SERVER['HTTPS'] = 'on';
       
   288     }
       
   289   }
       
   290 
       
   291   $_SERVER['HTTP_HOST'] = $host;
       
   292   $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
       
   293   $_SERVER['SERVER_ADDR'] = '127.0.0.1';
       
   294   $_SERVER['SERVER_SOFTWARE'] = $server_software;
       
   295   $_SERVER['SERVER_NAME'] = 'localhost';
       
   296   $_SERVER['REQUEST_URI'] = $path .'/';
       
   297   $_SERVER['REQUEST_METHOD'] = 'GET';
       
   298   $_SERVER['SCRIPT_NAME'] = $path .'/index.php';
       
   299   $_SERVER['PHP_SELF'] = $path .'/index.php';
       
   300   $_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
       
   301 
       
   302   if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
       
   303     // Ensure that any and all environment variables are changed to https://.
       
   304     foreach ($_SERVER as $key => $value) {
       
   305       $_SERVER[$key] = str_replace('http://', 'https://', $_SERVER[$key]);
       
   306     }
       
   307   }
       
   308 
       
   309   chdir(realpath(dirname(__FILE__) . '/..'));
       
   310   define('DRUPAL_ROOT', getcwd());
       
   311   require_once DRUPAL_ROOT . '/includes/bootstrap.inc';
       
   312 }
       
   313 
       
   314 /**
       
   315  * Execute a batch of tests.
       
   316  */
       
   317 function simpletest_script_execute_batch($test_id, $test_classes) {
       
   318   global $args;
       
   319 
       
   320   $total_status = SIMPLETEST_SCRIPT_EXIT_SUCCESS;
       
   321 
       
   322   // Multi-process execution.
       
   323   $children = array();
       
   324   while (!empty($test_classes) || !empty($children)) {
       
   325     while (count($children) < $args['concurrency']) {
       
   326       if (empty($test_classes)) {
       
   327         break;
       
   328       }
       
   329 
       
   330       // Fork a child process.
       
   331       $test_class = array_shift($test_classes);
       
   332       $command = simpletest_script_command($test_id, $test_class);
       
   333       $process = proc_open($command, array(), $pipes, NULL, NULL, array('bypass_shell' => TRUE));
       
   334 
       
   335       if (!is_resource($process)) {
       
   336         echo "Unable to fork test process. Aborting.\n";
       
   337         exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
       
   338       }
       
   339 
       
   340       // Register our new child.
       
   341       $children[] = array(
       
   342         'process' => $process,
       
   343         'class' => $test_class,
       
   344         'pipes' => $pipes,
       
   345       );
       
   346     }
       
   347 
       
   348     // Wait for children every 200ms.
       
   349     usleep(200000);
       
   350 
       
   351     // Check if some children finished.
       
   352     foreach ($children as $cid => $child) {
       
   353       $status = proc_get_status($child['process']);
       
   354       if (empty($status['running'])) {
       
   355         // The child exited, unregister it.
       
   356         proc_close($child['process']);
       
   357         if ($status['exitcode'] == SIMPLETEST_SCRIPT_EXIT_FAILURE) {
       
   358           if ($status['exitcode'] > $total_status) {
       
   359             $total_status = $status['exitcode'];
       
   360           }
       
   361         }
       
   362         elseif ($status['exitcode']) {
       
   363           $total_status = $status['exitcode'];
       
   364           echo 'FATAL ' . $test_class . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').' . "\n";
       
   365         }
       
   366 
       
   367         // Remove this child.
       
   368         unset($children[$cid]);
       
   369       }
       
   370     }
       
   371   }
       
   372   return $total_status;
       
   373 }
       
   374 
       
   375 /**
       
   376  * Bootstrap Drupal and run a single test.
       
   377  */
       
   378 function simpletest_script_run_one_test($test_id, $test_class) {
       
   379   try {
       
   380     // Bootstrap Drupal.
       
   381     drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
       
   382 
       
   383     simpletest_classloader_register();
       
   384 
       
   385     $test = new $test_class($test_id);
       
   386     $test->run();
       
   387     $info = $test->getInfo();
       
   388 
       
   389     $had_fails = (isset($test->results['#fail']) && $test->results['#fail'] > 0);
       
   390     $had_exceptions = (isset($test->results['#exception']) && $test->results['#exception'] > 0);
       
   391     $status = ($had_fails || $had_exceptions ? 'fail' : 'pass');
       
   392     simpletest_script_print($info['name'] . ' ' . _simpletest_format_summary_line($test->results) . "\n", simpletest_script_color_code($status));
       
   393 
       
   394     // Finished, kill this runner.
       
   395     if ($had_fails || $had_exceptions) {
       
   396       exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
       
   397     }
       
   398     exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
       
   399   }
       
   400   catch (Exception $e) {
       
   401     echo (string) $e;
       
   402     exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
       
   403   }
       
   404 }
       
   405 
       
   406 /**
       
   407  * Return a command used to run a test in a separate process.
       
   408  *
       
   409  * @param $test_id
       
   410  *  The current test ID.
       
   411  * @param $test_class
       
   412  *  The name of the test class to run.
       
   413  */
       
   414 function simpletest_script_command($test_id, $test_class) {
       
   415   global $args, $php;
       
   416 
       
   417   $command = escapeshellarg($php) . ' ' . escapeshellarg('./scripts/' . $args['script']) . ' --url ' . escapeshellarg($args['url']);
       
   418   if ($args['color']) {
       
   419     $command .= ' --color';
       
   420   }
       
   421   $command .= " --php " . escapeshellarg($php) . " --test-id $test_id --execute-test " . escapeshellarg($test_class);
       
   422   return $command;
       
   423 }
       
   424 
       
   425 /**
       
   426  * Get list of tests based on arguments. If --all specified then
       
   427  * returns all available tests, otherwise reads list of tests.
       
   428  *
       
   429  * Will print error and exit if no valid tests were found.
       
   430  *
       
   431  * @return List of tests.
       
   432  */
       
   433 function simpletest_script_get_test_list() {
       
   434   global $args, $all_tests, $groups;
       
   435 
       
   436   $test_list = array();
       
   437   if ($args['all']) {
       
   438     $test_list = $all_tests;
       
   439   }
       
   440   else {
       
   441     if ($args['class']) {
       
   442       // Check for valid class names.
       
   443       $test_list = array();
       
   444       foreach ($args['test_names'] as $test_class) {
       
   445         if (class_exists($test_class)) {
       
   446           $test_list[] = $test_class;
       
   447         }
       
   448         else {
       
   449           $groups = simpletest_test_get_all();
       
   450           $all_classes = array();
       
   451           foreach ($groups as $group) {
       
   452             $all_classes = array_merge($all_classes, array_keys($group));
       
   453           }
       
   454           simpletest_script_print_error('Test class not found: ' . $test_class);
       
   455           simpletest_script_print_alternatives($test_class, $all_classes, 6);
       
   456           exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
       
   457         }
       
   458       }
       
   459     }
       
   460     elseif ($args['file']) {
       
   461       $files = array();
       
   462       foreach ($args['test_names'] as $file) {
       
   463         $files[drupal_realpath($file)] = 1;
       
   464       }
       
   465 
       
   466       // Check for valid class names.
       
   467       foreach ($all_tests as $class_name) {
       
   468         $refclass = new ReflectionClass($class_name);
       
   469         $file = $refclass->getFileName();
       
   470         if (isset($files[$file])) {
       
   471           $test_list[] = $class_name;
       
   472         }
       
   473       }
       
   474     }
       
   475     elseif ($args['directory']) {
       
   476       // Extract test case class names from specified directory.
       
   477       // Find all tests in the PSR-X structure; Drupal\$extension\Tests\*.php
       
   478       // Since we do not want to hard-code too many structural file/directory
       
   479       // assumptions about PSR-0/4 files and directories, we check for the
       
   480       // minimal conditions only; i.e., a '*.php' file that has '/Tests/' in
       
   481       // its path.
       
   482       // Ignore anything from third party vendors, and ignore template files used in tests.
       
   483       // And any api.php files.
       
   484       $ignore = array('nomask' => '/vendor|\.tpl\.php|\.api\.php/');
       
   485       $files = array();
       
   486       if ($args['directory'][0] === '/') {
       
   487         $directory = $args['directory'];
       
   488       }
       
   489       else {
       
   490         $directory = DRUPAL_ROOT . "/" . $args['directory'];
       
   491       }
       
   492       $file_list = file_scan_directory($directory, '/\.php|\.test$/', $ignore);
       
   493       foreach ($file_list as $file) {
       
   494         // '/Tests/' can be contained anywhere in the file's path (there can be
       
   495         // sub-directories below /Tests), but must be contained literally.
       
   496         // Case-insensitive to match all Simpletest and PHPUnit tests:
       
   497         //   ./lib/Drupal/foo/Tests/Bar/Baz.php
       
   498         //   ./foo/src/Tests/Bar/Baz.php
       
   499         //   ./foo/tests/Drupal/foo/Tests/FooTest.php
       
   500         //   ./foo/tests/src/FooTest.php
       
   501         // $file->filename doesn't give us a directory, so we use $file->uri
       
   502         // Strip the drupal root directory and trailing slash off the URI
       
   503         $filename = substr($file->uri, strlen(DRUPAL_ROOT)+1);
       
   504         if (stripos($filename, '/Tests/')) {
       
   505           $files[drupal_realpath($filename)] = 1;
       
   506         } else if (stripos($filename, '.test')){
       
   507           $files[drupal_realpath($filename)] = 1;
       
   508         }
       
   509       }
       
   510 
       
   511       // Check for valid class names.
       
   512       foreach ($all_tests as $class_name) {
       
   513         $refclass = new ReflectionClass($class_name);
       
   514         $classfile = $refclass->getFileName();
       
   515         if (isset($files[$classfile])) {
       
   516           $test_list[] = $class_name;
       
   517         }
       
   518       }
       
   519     }
       
   520     else {
       
   521       // Check for valid group names and get all valid classes in group.
       
   522       foreach ($args['test_names'] as $group_name) {
       
   523         if (isset($groups[$group_name])) {
       
   524           $test_list = array_merge($test_list, array_keys($groups[$group_name]));
       
   525         }
       
   526         else {
       
   527           simpletest_script_print_error('Test group not found: ' . $group_name);
       
   528           simpletest_script_print_alternatives($group_name, array_keys($groups));
       
   529           exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
       
   530         }
       
   531       }
       
   532     }
       
   533   }
       
   534 
       
   535   if (empty($test_list)) {
       
   536     simpletest_script_print_error('No valid tests were specified.');
       
   537     exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
       
   538   }
       
   539   return $test_list;
       
   540 }
       
   541 
       
   542 /**
       
   543  * Initialize the reporter.
       
   544  */
       
   545 function simpletest_script_reporter_init() {
       
   546   global $args, $all_tests, $test_list, $results_map;
       
   547 
       
   548   $results_map = array(
       
   549     'pass' => 'Pass',
       
   550     'fail' => 'Fail',
       
   551     'exception' => 'Exception'
       
   552   );
       
   553 
       
   554   echo "\n";
       
   555   echo "Drupal test run\n";
       
   556   echo "---------------\n";
       
   557   echo "\n";
       
   558 
       
   559   // Tell the user about what tests are to be run.
       
   560   if ($args['all']) {
       
   561     echo "All tests will run.\n\n";
       
   562   }
       
   563   else {
       
   564     echo "Tests to be run:\n";
       
   565     foreach ($test_list as $class_name) {
       
   566       $info = call_user_func(array($class_name, 'getInfo'));
       
   567       echo " - " . $info['name'] . ' (' . $class_name . ')' . "\n";
       
   568     }
       
   569     echo "\n";
       
   570   }
       
   571 
       
   572   echo "Test run started:\n";
       
   573   echo " " . format_date($_SERVER['REQUEST_TIME'], 'long') . "\n";
       
   574   timer_start('run-tests');
       
   575   echo "\n";
       
   576 
       
   577   echo "Test summary\n";
       
   578   echo "------------\n";
       
   579   echo "\n";
       
   580 }
       
   581 
       
   582 /**
       
   583  * Display jUnit XML test results.
       
   584  */
       
   585 function simpletest_script_reporter_write_xml_results() {
       
   586   global $args, $test_id, $results_map;
       
   587 
       
   588   $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
       
   589 
       
   590   $test_class = '';
       
   591   $xml_files = array();
       
   592 
       
   593   foreach ($results as $result) {
       
   594     if (isset($results_map[$result->status])) {
       
   595       if ($result->test_class != $test_class) {
       
   596         // We've moved onto a new class, so write the last classes results to a file:
       
   597         if (isset($xml_files[$test_class])) {
       
   598           file_put_contents($args['xml'] . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
       
   599           unset($xml_files[$test_class]);
       
   600         }
       
   601         $test_class = $result->test_class;
       
   602         if (!isset($xml_files[$test_class])) {
       
   603           $doc = new DomDocument('1.0');
       
   604           $root = $doc->createElement('testsuite');
       
   605           $root = $doc->appendChild($root);
       
   606           $xml_files[$test_class] = array('doc' => $doc, 'suite' => $root);
       
   607         }
       
   608       }
       
   609 
       
   610       // For convenience:
       
   611       $dom_document = &$xml_files[$test_class]['doc'];
       
   612 
       
   613       // Create the XML element for this test case:
       
   614       $case = $dom_document->createElement('testcase');
       
   615       $case->setAttribute('classname', $test_class);
       
   616       list($class, $name) = explode('->', $result->function, 2);
       
   617       $case->setAttribute('name', $name);
       
   618 
       
   619       // Passes get no further attention, but failures and exceptions get to add more detail:
       
   620       if ($result->status == 'fail') {
       
   621         $fail = $dom_document->createElement('failure');
       
   622         $fail->setAttribute('type', 'failure');
       
   623         $fail->setAttribute('message', $result->message_group);
       
   624         $text = $dom_document->createTextNode($result->message);
       
   625         $fail->appendChild($text);
       
   626         $case->appendChild($fail);
       
   627       }
       
   628       elseif ($result->status == 'exception') {
       
   629         // In the case of an exception the $result->function may not be a class
       
   630         // method so we record the full function name:
       
   631         $case->setAttribute('name', $result->function);
       
   632 
       
   633         $fail = $dom_document->createElement('error');
       
   634         $fail->setAttribute('type', 'exception');
       
   635         $fail->setAttribute('message', $result->message_group);
       
   636         $full_message = $result->message . "\n\nline: " . $result->line . "\nfile: " . $result->file;
       
   637         $text = $dom_document->createTextNode($full_message);
       
   638         $fail->appendChild($text);
       
   639         $case->appendChild($fail);
       
   640       }
       
   641       // Append the test case XML to the test suite:
       
   642       $xml_files[$test_class]['suite']->appendChild($case);
       
   643     }
       
   644   }
       
   645   // The last test case hasn't been saved to a file yet, so do that now:
       
   646   if (isset($xml_files[$test_class])) {
       
   647     file_put_contents($args['xml'] . '/' . $test_class . '.xml', $xml_files[$test_class]['doc']->saveXML());
       
   648     unset($xml_files[$test_class]);
       
   649   }
       
   650 }
       
   651 
       
   652 /**
       
   653  * Stop the test timer.
       
   654  */
       
   655 function simpletest_script_reporter_timer_stop() {
       
   656   echo "\n";
       
   657   $end = timer_stop('run-tests');
       
   658   echo "Test run duration: " . format_interval($end['time'] / 1000);
       
   659   echo "\n\n";
       
   660 }
       
   661 
       
   662 /**
       
   663  * Display test results.
       
   664  */
       
   665 function simpletest_script_reporter_display_results() {
       
   666   global $args, $test_id, $results_map;
       
   667 
       
   668   if ($args['verbose']) {
       
   669     // Report results.
       
   670     echo "Detailed test results\n";
       
   671     echo "---------------------\n";
       
   672 
       
   673     $results = db_query("SELECT * FROM {simpletest} WHERE test_id = :test_id ORDER BY test_class, message_id", array(':test_id' => $test_id));
       
   674     $test_class = '';
       
   675     foreach ($results as $result) {
       
   676       if (isset($results_map[$result->status])) {
       
   677         if ($result->test_class != $test_class) {
       
   678           // Display test class every time results are for new test class.
       
   679           echo "\n\n---- $result->test_class ----\n\n\n";
       
   680           $test_class = $result->test_class;
       
   681 
       
   682           // Print table header.
       
   683           echo "Status    Group      Filename          Line Function                            \n";
       
   684           echo "--------------------------------------------------------------------------------\n";
       
   685         }
       
   686 
       
   687         simpletest_script_format_result($result);
       
   688       }
       
   689     }
       
   690   }
       
   691 }
       
   692 
       
   693 /**
       
   694  * Format the result so that it fits within the default 80 character
       
   695  * terminal size.
       
   696  *
       
   697  * @param $result The result object to format.
       
   698  */
       
   699 function simpletest_script_format_result($result) {
       
   700   global $results_map, $color;
       
   701 
       
   702   $summary = sprintf("%-9.9s %-10.10s %-17.17s %4.4s %-35.35s\n",
       
   703     $results_map[$result->status], $result->message_group, basename($result->file), $result->line, $result->function);
       
   704 
       
   705   simpletest_script_print($summary, simpletest_script_color_code($result->status));
       
   706 
       
   707   $lines = explode("\n", wordwrap(trim(strip_tags($result->message)), 76));
       
   708   foreach ($lines as $line) {
       
   709     echo "    $line\n";
       
   710   }
       
   711 }
       
   712 
       
   713 /**
       
   714  * Print error message prefixed with "  ERROR: " and displayed in fail color
       
   715  * if color output is enabled.
       
   716  *
       
   717  * @param $message The message to print.
       
   718  */
       
   719 function simpletest_script_print_error($message) {
       
   720   simpletest_script_print("  ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
       
   721 }
       
   722 
       
   723 /**
       
   724  * Print a message to the console, if color is enabled then the specified
       
   725  * color code will be used.
       
   726  *
       
   727  * @param $message The message to print.
       
   728  * @param $color_code The color code to use for coloring.
       
   729  */
       
   730 function simpletest_script_print($message, $color_code) {
       
   731   global $args;
       
   732   if ($args['color']) {
       
   733     echo "\033[" . $color_code . "m" . $message . "\033[0m";
       
   734   }
       
   735   else {
       
   736     echo $message;
       
   737   }
       
   738 }
       
   739 
       
   740 /**
       
   741  * Get the color code associated with the specified status.
       
   742  *
       
   743  * @param $status The status string to get code for.
       
   744  * @return Color code.
       
   745  */
       
   746 function simpletest_script_color_code($status) {
       
   747   switch ($status) {
       
   748     case 'pass':
       
   749       return SIMPLETEST_SCRIPT_COLOR_PASS;
       
   750     case 'fail':
       
   751       return SIMPLETEST_SCRIPT_COLOR_FAIL;
       
   752     case 'exception':
       
   753       return SIMPLETEST_SCRIPT_COLOR_EXCEPTION;
       
   754   }
       
   755   return 0; // Default formatting.
       
   756 }
       
   757 
       
   758 /**
       
   759  * Prints alternative test names.
       
   760  *
       
   761  * Searches the provided array of string values for close matches based on the
       
   762  * Levenshtein algorithm.
       
   763  *
       
   764  * @see http://php.net/manual/en/function.levenshtein.php
       
   765  *
       
   766  * @param string $string
       
   767  *   A string to test.
       
   768  * @param array $array
       
   769  *   A list of strings to search.
       
   770  * @param int $degree
       
   771  *   The matching strictness. Higher values return fewer matches. A value of
       
   772  *   4 means that the function will return strings from $array if the candidate
       
   773  *   string in $array would be identical to $string by changing 1/4 or fewer of
       
   774  *   its characters.
       
   775  */
       
   776 function simpletest_script_print_alternatives($string, $array, $degree = 4) {
       
   777   $alternatives = array();
       
   778   foreach ($array as $item) {
       
   779     $lev = levenshtein($string, $item);
       
   780     if ($lev <= strlen($item) / $degree || FALSE !== strpos($string, $item)) {
       
   781       $alternatives[] = $item;
       
   782     }
       
   783   }
       
   784   if (!empty($alternatives)) {
       
   785     simpletest_script_print("  Did you mean?\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
       
   786     foreach ($alternatives as $alternative) {
       
   787       simpletest_script_print("  - $alternative\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
       
   788     }
       
   789   }
       
   790 }