Drupal PHP Cross Reference Content Management Systems

Source: /scripts/run-tests.sh - 674 lines - 20464 bytes - Summary - Text - Print

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

title

Description

title

Description

title

Description

title

title

Body