Skip to content
Snippets Groups Projects
run-tests.sh 52.1 KiB
Newer Older
    }
    catch (FileException $e) {
      // Ignore failed deletes.
    }
  try {
    $schema = Database::getConnection('default', 'default')->schema();
    $count = 0;
    foreach ($schema->findTables($db_prefix . '%') as $table) {
      $schema->dropTable($table);
      $count++;
    }
  catch (Exception $e) {
    echo (string) $e;
    exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
  }

    $messages[] = "- Removed $count leftover tables.";
 * If --all specified then return all available tests, otherwise reads list of
 * tests.
 */
function simpletest_script_get_test_list() {
  // @todo Use \Drupal\Core\Test\TestDiscovery when we no longer need BC for
  //   hook_simpletest_alter().
  /** $test_discovery \Drupal\simpletest\TestDiscovery */
  $test_discovery = \Drupal::service('test_discovery');
  $types_processed = empty($args['types']);
  if ($args['all'] || $args['module']) {
      $groups = $test_discovery->getTestClasses($args['module'], $args['types']);
    }
    catch (Exception $e) {
      echo (string) $e;
      exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
    }
      $all_tests = array_merge($all_tests, array_keys($tests));
      foreach ($args['test_names'] as $test_class) {
        list($class_name) = explode('::', $test_class, 2);
            $groups = $test_discovery->getTestClasses(NULL, $args['types']);
          }
          catch (Exception $e) {
            echo (string) $e;
            exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
          }
          foreach ($groups as $group) {
            $all_classes = array_merge($all_classes, array_keys($group));
          }
          simpletest_script_print_error('Test class not found: ' . $class_name);
          simpletest_script_print_alternatives($class_name, $all_classes, 6);
      // Extract test case class names from specified files.
      $parser = new TestFileParser();
      foreach ($args['test_names'] as $file) {
        if (!file_exists($file)) {
          simpletest_script_print_error('File not found: ' . $file);
        $test_list = array_merge($test_list, $parser->getTestListFromFile($file));
      }
    }
    elseif ($args['directory']) {
      // Extract test case class names from specified directory.
      // Find all tests in the PSR-X structure; Drupal\$extension\Tests\*.php
      // Since we do not want to hard-code too many structural file/directory
      // assumptions about PSR-4 files and directories, we check for the
      // minimal conditions only; i.e., a '*.php' file that has '/Tests/' in
      // its path.
      // Ignore anything from third party vendors.
      $files = [];
      if ($args['directory'][0] === '/') {
        $directory = $args['directory'];
      }
      else {
        $directory = DRUPAL_ROOT . "/" . $args['directory'];
      }
      foreach (\Drupal::service('file_system')->scanDirectory($directory, '/\.php$/', $ignore) as $file) {
        // '/Tests/' can be contained anywhere in the file's path (there can be
        // sub-directories below /Tests), but must be contained literally.
        // Case-insensitive to match all Simpletest and PHPUnit tests:
        // ./lib/Drupal/foo/Tests/Bar/Baz.php
        // ./foo/src/Tests/Bar/Baz.php
        // ./foo/tests/Drupal/foo/Tests/FooTest.php
        // ./foo/tests/src/FooTest.php
        // $file->filename doesn't give us a directory, so we use $file->uri
        // Strip the drupal root directory and trailing slash off the URI.
        $filename = substr($file->uri, strlen(DRUPAL_ROOT) + 1);
        if (stripos($filename, '/Tests/')) {
          $files[$filename] = $filename;
        }
      }
      $parser = new TestFileParser();
        $test_list = array_merge($test_list, $parser->getTestListFromFile($file));
        $groups = $test_discovery->getTestClasses(NULL, $args['types']);
      }
      catch (Exception $e) {
        echo (string) $e;
        exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
      }
      // Store all the groups so we can suggest alternatives if we need to.
      $all_groups = array_keys($groups);
      // Verify that the groups exist.
      if (!empty($unknown_groups = array_diff($args['test_names'], $all_groups))) {
        $first_group = reset($unknown_groups);
        simpletest_script_print_error('Test group not found: ' . $first_group);
        simpletest_script_print_alternatives($first_group, $all_groups);
        exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
      }
      // Ensure our list of tests contains only one entry for each test.
      foreach ($args['test_names'] as $group_name) {
        $test_list = array_merge($test_list, array_flip(array_keys($groups[$group_name])));
  // If the test list creation does not automatically limit by test type then
  // we need to do so here.
  if (!$types_processed) {
    $test_list = array_filter($test_list, function ($test_class) use ($args) {
      $test_info = TestDiscovery::getTestInfo($test_class);
      return in_array($test_info['type'], $args['types'], TRUE);
    });
  }

  if (empty($test_list)) {
    simpletest_script_print_error('No valid tests were specified.');
/**
 * Initialize the reporter.
 */
function simpletest_script_reporter_init() {
  echo "\n";
  echo "Drupal test run\n";
  echo "---------------\n";
  echo "\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";
  echo "  " . date('l, F j, Y - H:i', $_SERVER['REQUEST_TIME']) . "\n";
  Timer::start('run-tests');
/**
 * Displays the assertion result summary for a single test class.
 *
 * @param string $class
 *   The test class name that was run.
 * @param array $results
 *   The assertion results using #pass, #fail, #exception, #debug array keys.
 */
function simpletest_script_reporter_display_summary($class, $results) {
  // Output all test results vertically aligned.
  // Cut off the class name after 60 chars, and pad each group with 3 digits
  // by default (more than 999 assertions are rare).
  $output = vsprintf('%-60.60s %10s %9s %14s %12s', [
    $results['#pass'] . ' passes',
    !$results['#fail'] ? '' : $results['#fail'] . ' fails',
    !$results['#exception'] ? '' : $results['#exception'] . ' exceptions',
    !$results['#debug'] ? '' : $results['#debug'] . ' messages',

  $status = ($results['#fail'] || $results['#exception'] ? 'fail' : 'pass');
  simpletest_script_print($output . "\n", simpletest_script_color_code($status));
}

function simpletest_script_reporter_write_xml_results() {
  try {
    $results = simpletest_script_load_messages_by_test_id($test_ids);
  }
  catch (Exception $e) {
    echo (string) $e;
    exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
  }

  foreach ($results as $result) {
    if (isset($results_map[$result->status])) {
      if ($result->test_class != $test_class) {
        // We've moved onto a new class, so write the last classes results to a
        // file:
          file_put_contents($args['xml'] . '/' . str_replace('\\', '_', $test_class) . '.xml', $xml_files[$test_class]['doc']->saveXML());
          unset($xml_files[$test_class]);
        }
        $test_class = $result->test_class;
        if (!isset($xml_files[$test_class])) {
          $doc = new DomDocument('1.0');
          $root = $doc->createElement('testsuite');
          $root = $doc->appendChild($root);
          $xml_files[$test_class] = ['doc' => $doc, 'suite' => $root];
        }
      }

      // For convenience:
      $dom_document = &$xml_files[$test_class]['doc'];

      // Create the XML element for this test case:
      $case = $dom_document->createElement('testcase');
      $case->setAttribute('classname', $test_class);
      if (strpos($result->function, '->') !== FALSE) {
        list($class, $name) = explode('->', $result->function, 2);
      }
      else {
        $name = $result->function;
      }
      // Passes get no further attention, but failures and exceptions get to add
      // more detail:
      if ($result->status == 'fail') {
        $fail = $dom_document->createElement('failure');
        $fail->setAttribute('type', 'failure');
        $fail->setAttribute('message', $result->message_group);
        $text = $dom_document->createTextNode($result->message);
        $fail->appendChild($text);
        $case->appendChild($fail);
      }
      elseif ($result->status == 'exception') {
        // In the case of an exception the $result->function may not be a class
        // method so we record the full function name:
        $case->setAttribute('name', $result->function);

        $fail = $dom_document->createElement('error');
        $fail->setAttribute('type', 'exception');
        $fail->setAttribute('message', $result->message_group);
        $full_message = $result->message . "\n\nline: " . $result->line . "\nfile: " . $result->file;
        $text = $dom_document->createTextNode($full_message);
        $fail->appendChild($text);
        $case->appendChild($fail);
      }
      // Append the test case XML to the test suite:
      $xml_files[$test_class]['suite']->appendChild($case);
    }
  }
  // The last test case hasn't been saved to a file yet, so do that now:
  if (isset($xml_files[$test_class])) {
    file_put_contents($args['xml'] . '/' . str_replace('\\', '_', $test_class) . '.xml', $xml_files[$test_class]['doc']->saveXML());
    unset($xml_files[$test_class]);
  }
}

/**
 * Stop the test timer.
 */
function simpletest_script_reporter_timer_stop() {
  $end = Timer::stop('run-tests');
  echo "Test run duration: " . \Drupal::service('date.formatter')->formatInterval($end['time'] / 1000);
}

/**
 * Display test results.
 */
function simpletest_script_reporter_display_results() {
    echo "Detailed test results\n";
    echo "---------------------\n";
    try {
      $results = simpletest_script_load_messages_by_test_id($test_ids);
    }
    catch (Exception $e) {
      echo (string) $e;
      exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
    }
      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;
          // Print table header.
          echo "Status    Group      Filename          Line Function                            \n";
          echo "--------------------------------------------------------------------------------\n";
 * Format the result so that it fits within 80 characters.
 * @param object $result
 *   The result object to format.
 */
function simpletest_script_format_result($result) {
  global $args, $results_map, $color;
  $summary = sprintf("%-9.9s %-10.10s %-17.17s %4.4s %-35.35s\n",
    $results_map[$result->status], $result->message_group, basename($result->file), $result->line, $result->function);

  simpletest_script_print($summary, simpletest_script_color_code($result->status));
  $message = trim(strip_tags($result->message));
  if ($args['non-html']) {
    $message = Html::decodeEntities($message, ENT_QUOTES, 'UTF-8');
  }
  $lines = explode("\n", wordwrap($message), 76);
 * Print error messages so the user will notice them.
 * Print error message prefixed with "  ERROR: " and displayed in fail color if
 * color output is enabled.
 *
 * @param string $message
 *   The message to print.
function simpletest_script_print_error($message) {
  simpletest_script_print("  ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
 * Print a message to the console, using a color.
 * @param string $message
 *   The message to print.
 * @param int $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 string $status
 *   The status string to get code for. Special cases are: 'pass', 'fail', or
 *   'exception'.
 *
 * @return int
 *   Color code. Returns 0 for default case.
 */
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;
  }

/**
 * Prints alternative test names.
 *
 * Searches the provided array of string values for close matches based on the
 * Levenshtein algorithm.
 *
 * @param string $string
 *   A string to test.
 * @param array $array
 *   A list of strings to search.
 * @param int $degree
 *   The matching strictness. Higher values return fewer matches. A value of
 *   4 means that the function will return strings from $array if the candidate
 *   string in $array would be identical to $string by changing 1/4 or fewer of
 *   its characters.
 * @see http://php.net/manual/function.levenshtein.php
 */
function simpletest_script_print_alternatives($string, $array, $degree = 4) {
  foreach ($array as $item) {
    $lev = levenshtein($string, $item);
    if ($lev <= strlen($item) / $degree || FALSE !== strpos($string, $item)) {
      $alternatives[] = $item;
    }
  }
  if (!empty($alternatives)) {
    simpletest_script_print("  Did you mean?\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
    foreach ($alternatives as $alternative) {
      simpletest_script_print("  - $alternative\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
    }
  }
}

/**
 * Loads the simpletest messages from the database.
 *
 * Messages are ordered by test class and message id.
 *
 * @param array $test_ids
 *   Array of test IDs of the messages to be loaded.
 *
 * @return array
 *   Array of simpletest messages from the database.
 */
function simpletest_script_load_messages_by_test_id($test_ids) {
  global $args;

  // Sqlite has a maximum number of variables per query. If required, the
  // database query is split into chunks.
  if (count($test_ids) > SIMPLETEST_SCRIPT_SQLITE_VARIABLE_LIMIT && !empty($args['sqlite'])) {
    $test_id_chunks = array_chunk($test_ids, SIMPLETEST_SCRIPT_SQLITE_VARIABLE_LIMIT);
  }
  else {
    try {
      $result_chunk = Database::getConnection('default', 'test-runner')
        ->query("SELECT * FROM {simpletest} WHERE test_id IN ( :test_ids[] ) ORDER BY test_class, message_id", [
    }
    catch (Exception $e) {
      echo (string) $e;
      exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
    }
    if ($result_chunk) {
      $results = array_merge($results, $result_chunk);
    }
  }

  return $results;
}
 *
 * @deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. This function
 *   supports the --browser option in this script. Use the --verbose option
 *   instead.
 *
 * @see https://www.drupal.org/node/3083549
 *
 * @todo Remove this in https://www.drupal.org/project/drupal/issues/3075490.
 */
function simpletest_script_open_browser() {
  // Note: the user already has received a message about the deprecation in CLI
  // so we trigger an error just in case this method has been used as API.
  @trigger_error('The --browser option is deprecated in drupal:8.8.0 and is removed from drupal:9.0.0. Use --verbose instead. See https://www.drupal.org/node/3083549', E_USER_DEPRECATED);
  if (function_exists('_simpletest_run_tests_script_open_browser')) {
    return _simpletest_run_tests_script_open_browser();
  simpletest_script_print_error('In order to use the --browser option the Simpletest module must be available. See https://www.drupal.org/node/3083549.');