Newer
Older

Lee Rowlands
committed
$test_list = [];
if ($args['all'] || $args['module']) {
try {
$groups = $test_discovery->getTestClasses($args['module'], $args['types']);
$types_processed = TRUE;
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}

Lee Rowlands
committed
$all_tests = [];

Dries Buytaert
committed
foreach ($groups as $group => $tests) {

Dries Buytaert
committed
$all_tests = array_merge($all_tests, array_keys($tests));

Dries Buytaert
committed
}

Alex Pott
committed
$test_list = array_unique($all_tests);

Dries Buytaert
committed
}
else {
if ($args['class']) {

Lee Rowlands
committed
$test_list = [];

Angie Byron
committed
foreach ($args['test_names'] as $test_class) {

Alex Pott
committed
list($class_name) = explode('::', $test_class, 2);

Angie Byron
committed
if (class_exists($class_name)) {

Angie Byron
committed
$test_list[] = $test_class;
}
else {
try {
$groups = $test_discovery->getTestClasses(NULL, $args['types']);
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}

Lee Rowlands
committed
$all_classes = [];

Angie Byron
committed
foreach ($groups as $group) {
$all_classes = array_merge($all_classes, array_keys($group));
}

Angie Byron
committed
simpletest_script_print_error('Test class not found: ' . $class_name);
simpletest_script_print_alternatives($class_name, $all_classes, 6);
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);

Angie Byron
committed
}

Dries Buytaert
committed
}
}

Angie Byron
committed
elseif ($args['file']) {

Dries Buytaert
committed
// Extract test case class names from specified files.
foreach ($args['test_names'] as $file) {

Dries Buytaert
committed
if (!file_exists($file)) {
simpletest_script_print_error('File not found: ' . $file);
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);

Dries Buytaert
committed
}
$content = file_get_contents($file);
// Extract a potential namespace.
$namespace = FALSE;
if (preg_match('@^namespace ([^ ;]+)@m', $content, $matches)) {
$namespace = $matches[1];
}
// Extract all class names.
// Abstract classes are excluded on purpose.
preg_match_all('@^class ([^ ]+)@m', $content, $matches);
if (!$namespace) {

Dries Buytaert
committed
$test_list = array_merge($test_list, $matches[1]);

Dries Buytaert
committed
}
else {
foreach ($matches[1] as $class_name) {

Alex Pott
committed
$namespace_class = $namespace . '\\' . $class_name;

Alex Pott
committed
if (is_subclass_of($namespace_class, '\Drupal\simpletest\TestBase') || is_subclass_of($namespace_class, TestCase::class)) {
$test_list[] = $namespace_class;
}
}
}
}
}
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

Gábor Hojtsy
committed
// 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.

Lee Rowlands
committed
$ignore = ['.', '..', 'vendor'];
$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:

Alex Pott
committed
// ./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

Alex Pott
committed
// 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;
}
}
foreach ($files as $file) {
$content = file_get_contents($file);
// Extract a potential namespace.
$namespace = FALSE;
if (preg_match('@^\s*namespace ([^ ;]+)@m', $content, $matches)) {
$namespace = $matches[1];
}
// Extract all class names.
// Abstract classes are excluded on purpose.
preg_match_all('@^\s*class ([^ ]+)@m', $content, $matches);
if (!$namespace) {
$test_list = array_merge($test_list, $matches[1]);
}
else {
foreach ($matches[1] as $class_name) {
$namespace_class = $namespace . '\\' . $class_name;

Alex Pott
committed
if (is_subclass_of($namespace_class, '\Drupal\simpletest\TestBase') || is_subclass_of($namespace_class, TestCase::class)) {

Alex Pott
committed
$test_list[] = $namespace_class;
}

Dries Buytaert
committed
}

Dries Buytaert
committed
else {
try {
$groups = $test_discovery->getTestClasses(NULL, $args['types']);
$types_processed = TRUE;
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}

Alex Pott
committed
// 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.

Dries Buytaert
committed
foreach ($args['test_names'] as $group_name) {

Alex Pott
committed
$test_list = array_merge($test_list, array_flip(array_keys($groups[$group_name])));

Dries Buytaert
committed
}

Alex Pott
committed
$test_list = array_flip($test_list);

Dries Buytaert
committed
}

Dries Buytaert
committed
// 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);
});
}

Dries Buytaert
committed
if (empty($test_list)) {
simpletest_script_print_error('No valid tests were specified.');
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);

Dries Buytaert
committed
}
return $test_list;

Dries Buytaert
committed
/**
* Initialize the reporter.
*/
function simpletest_script_reporter_init() {

Dries Buytaert
committed
global $args, $test_list, $results_map;

Dries Buytaert
committed

Lee Rowlands
committed
$results_map = [

Dries Buytaert
committed
'pass' => 'Pass',
'fail' => 'Fail',

Alex Pott
committed
'exception' => 'Exception',

Lee Rowlands
committed
];

Dries Buytaert
committed
echo "\n";
echo "Drupal test run\n";
echo "---------------\n";
echo "\n";

Dries Buytaert
committed

Dries Buytaert
committed
// 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";

Dries Buytaert
committed
foreach ($test_list as $class_name) {

Angie Byron
committed
echo " - $class_name\n";

Dries Buytaert
committed
}
echo "\n";

Dries Buytaert
committed

Dries Buytaert
committed
echo "Test run started:\n";

Angie Byron
committed
echo " " . date('l, F j, Y - H:i', $_SERVER['REQUEST_TIME']) . "\n";
Timer::start('run-tests');

Dries Buytaert
committed
echo "\n";

Dries Buytaert
committed
echo "Test summary\n";
echo "------------\n";

Dries Buytaert
committed
echo "\n";

Angie Byron
committed
/**
* 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).

Lee Rowlands
committed
$output = vsprintf('%-60.60s %10s %9s %14s %12s', [

Angie Byron
committed
$class,

Alex Pott
committed
$results['#pass'] . ' passes',
!$results['#fail'] ? '' : $results['#fail'] . ' fails',

Angie Byron
committed
!$results['#exception'] ? '' : $results['#exception'] . ' exceptions',

Alex Pott
committed
!$results['#debug'] ? '' : $results['#debug'] . ' messages',

Lee Rowlands
committed
]);

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

Dries Buytaert
committed
/**

Dries Buytaert
committed
* Display jUnit XML test results.

Dries Buytaert
committed
*/

Dries Buytaert
committed
function simpletest_script_reporter_write_xml_results() {

Dries Buytaert
committed
global $args, $test_ids, $results_map;

Dries Buytaert
committed
try {
$results = simpletest_script_load_messages_by_test_id($test_ids);
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}

Dries Buytaert
committed
$test_class = '';

Lee Rowlands
committed
$xml_files = [];

Dries Buytaert
committed
foreach ($results as $result) {
if (isset($results_map[$result->status])) {
if ($result->test_class != $test_class) {

Alex Pott
committed
// We've moved onto a new class, so write the last classes results to a
// file:

Dries Buytaert
committed
if (isset($xml_files[$test_class])) {
file_put_contents($args['xml'] . '/' . str_replace('\\', '_', $test_class) . '.xml', $xml_files[$test_class]['doc']->saveXML());

Dries Buytaert
committed
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);

Lee Rowlands
committed
$xml_files[$test_class] = ['doc' => $doc, 'suite' => $root];

Dries Buytaert
committed
}
}
// 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);

Alex Pott
committed
if (strpos($result->function, '->') !== FALSE) {
list($class, $name) = explode('->', $result->function, 2);
}
else {
$name = $result->function;
}

Dries Buytaert
committed
$case->setAttribute('name', $name);

Alex Pott
committed
// Passes get no further attention, but failures and exceptions get to add
// more detail:

Dries Buytaert
committed
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
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());

Dries Buytaert
committed
unset($xml_files[$test_class]);
}
}
/**
* Stop the test timer.
*/
function simpletest_script_reporter_timer_stop() {

Dries Buytaert
committed
echo "\n";
$end = Timer::stop('run-tests');

Alex Pott
committed
echo "Test run duration: " . \Drupal::service('date.formatter')->formatInterval($end['time'] / 1000);

Dries Buytaert
committed
echo "\n\n";

Dries Buytaert
committed
}
/**
* Display test results.
*/
function simpletest_script_reporter_display_results() {

Dries Buytaert
committed
global $args, $test_ids, $results_map;

Dries Buytaert
committed

Dries Buytaert
committed
if ($args['verbose']) {
// Report results.

Dries Buytaert
committed
echo "Detailed test results\n";
echo "---------------------\n";

Dries Buytaert
committed
try {
$results = simpletest_script_load_messages_by_test_id($test_ids);
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}

Dries Buytaert
committed
$test_class = '';

Dries Buytaert
committed
foreach ($results as $result) {

Dries Buytaert
committed
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;

Dries Buytaert
committed

catch
committed
// Print table header.
echo "Status Group Filename Line Function \n";
echo "--------------------------------------------------------------------------------\n";

Dries Buytaert
committed
}
simpletest_script_format_result($result);
}
}

Dries Buytaert
committed
/**

Alex Pott
committed
* Format the result so that it fits within 80 characters.

Dries Buytaert
committed
*

Alex Pott
committed
* @param object $result
* The result object to format.

Dries Buytaert
committed
*/
function simpletest_script_format_result($result) {
global $args, $results_map, $color;

Dries Buytaert
committed

Dries Buytaert
committed
$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);

Dries Buytaert
committed
simpletest_script_print($summary, simpletest_script_color_code($result->status));

Dries Buytaert
committed
$message = trim(strip_tags($result->message));
if ($args['non-html']) {
$message = Html::decodeEntities($message, ENT_QUOTES, 'UTF-8');
}
$lines = explode("\n", wordwrap($message), 76);

Dries Buytaert
committed
foreach ($lines as $line) {
echo " $line\n";
}
}

Dries Buytaert
committed

Dries Buytaert
committed
/**

Alex Pott
committed
* Print error messages so the user will notice them.

Dries Buytaert
committed
*

Alex Pott
committed
* Print error message prefixed with " ERROR: " and displayed in fail color if
* color output is enabled.
*
* @param string $message
* The message to print.

Dries Buytaert
committed
*/

Dries Buytaert
committed
function simpletest_script_print_error($message) {
simpletest_script_print(" ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);

Dries Buytaert
committed
}

Dries Buytaert
committed

Dries Buytaert
committed
/**

Alex Pott
committed
* Print a message to the console, using a color.

Dries Buytaert
committed
*

Alex Pott
committed
* @param string $message
* The message to print.
* @param int $color_code
* The color code to use for coloring.

Dries Buytaert
committed
*/
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.
*

Alex Pott
committed
* @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.

Dries Buytaert
committed
*/
function simpletest_script_color_code($status) {
switch ($status) {
case 'pass':
return SIMPLETEST_SCRIPT_COLOR_PASS;

Alex Pott
committed

Dries Buytaert
committed
case 'fail':
return SIMPLETEST_SCRIPT_COLOR_FAIL;

Alex Pott
committed

Dries Buytaert
committed
case 'exception':
return SIMPLETEST_SCRIPT_COLOR_EXCEPTION;
}

Alex Pott
committed
// Default formatting.
return 0;

Dries Buytaert
committed
}

Angie Byron
committed
/**
* 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.

Alex Pott
committed
*
* @see http://php.net/manual/function.levenshtein.php

Angie Byron
committed
*/
function simpletest_script_print_alternatives($string, $array, $degree = 4) {

Lee Rowlands
committed
$alternatives = [];

Angie Byron
committed
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);
}
}
}

Angie Byron
committed
/**
* 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;

Lee Rowlands
committed
$results = [];

Angie Byron
committed
// 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 {

Lee Rowlands
committed
$test_id_chunks = [$test_ids];

Angie Byron
committed
}
foreach ($test_id_chunks as $test_id_chunk) {
try {
$result_chunk = Database::getConnection('default', 'test-runner')

Lee Rowlands
committed
->query("SELECT * FROM {simpletest} WHERE test_id IN ( :test_ids[] ) ORDER BY test_class, message_id", [
':test_ids[]' => $test_id_chunk,

Lee Rowlands
committed
])->fetchAll();
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}

Angie Byron
committed
if ($result_chunk) {
$results = array_merge($results, $result_chunk);
}
}
return $results;
}
/**
* Display test results.
*/
function simpletest_script_open_browser() {
global $test_ids;
try {
$connection = Database::getConnection('default', 'test-runner');
$results = $connection->select('simpletest')
->fields('simpletest')
->condition('test_id', $test_ids, 'IN')
->orderBy('test_class')
->orderBy('message_id')
->execute()
->fetchAll();
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}
// Get the results form.

Lee Rowlands
committed
$form = [];
SimpletestResultsForm::addResultForm($form, $results);
// Get the assets to make the details element collapsible and theme the result
// form.

Lee Rowlands
committed
$assets = new AttachedAssets();

Alex Pott
committed
$assets->setLibraries([
'core/drupal.collapse',
'system/admin',
'simpletest/drupal.simpletest',
]);
$resolver = \Drupal::service('asset.resolver');
list($js_assets_header, $js_assets_footer) = $resolver->getJsAssets($assets, FALSE);
$js_collection_renderer = \Drupal::service('asset.js.collection_renderer');
$js_assets_header = $js_collection_renderer->render($js_assets_header);
$js_assets_footer = $js_collection_renderer->render($js_assets_footer);
$css_assets = \Drupal::service('asset.css.collection_renderer')->render($resolver->getCssAssets($assets, FALSE));
// Make the html page to write to disk.
$render_service = \Drupal::service('renderer');

Alex Pott
committed
$html = '<head>' . $render_service->renderPlain($js_assets_header) . $render_service->renderPlain($css_assets) . '</head><body>' . $render_service->renderPlain($form) . $render_service->renderPlain($js_assets_footer) . '</body>';

Alex Pott
committed
// Ensure we have assets verbose directory - tests with no verbose output will
// not have created one.
$directory = PublicStream::basePath() . '/simpletest/verbose';
\Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
$php = new Php();
$uuid = $php->generate();
$filename = $directory . '/results-' . $uuid . '.html';
$base_url = getenv('SIMPLETEST_BASE_URL');
if (empty($base_url)) {
simpletest_script_print_error("--browser needs argument --url.");
}
$url = $base_url . '/' . PublicStream::basePath() . '/simpletest/verbose/results-' . $uuid . '.html';
file_put_contents($filename, $html);
// See if we can find an OS helper to open URLs in default browser.
$browser = FALSE;
if (shell_exec('which xdg-open')) {
$browser = 'xdg-open';
}
elseif (shell_exec('which open')) {
$browser = 'open';
}
elseif (substr(PHP_OS, 0, 3) == 'WIN') {
$browser = 'start';
}
if ($browser) {
shell_exec($browser . ' ' . escapeshellarg($url));
}
else {
// Can't find assets valid browser.
print 'Open file://' . realpath($filename) . ' in your browser to see the verbose output.';
}
}