Commit 488f5584 authored by webchick's avatar webchick
Browse files

Issue #2315791 by alexpott, cilefen, dawehner: Add functionality to open...

Issue #2315791 by alexpott, cilefen, dawehner: Add functionality to open results in browser window to run-tests.sh
parent 7141a8dc
......@@ -19,6 +19,12 @@
/**
* Test results form for $test_id.
*
* Note that the UI strings are not translated because this form is also used
* from run-tests.sh.
*
* @see simpletest_script_open_browser()
* @see run-tests.sh
*/
class SimpletestResultsForm extends FormBase {
......@@ -58,37 +64,36 @@ public function __construct(Connection $database) {
/**
* Builds the status image map.
*/
protected function buildStatusImageMap() {
// Initialize image mapping property.
protected static function buildStatusImageMap() {
$image_pass = array(
'#theme' => 'image',
'#uri' => 'core/misc/icons/73b355/check.svg',
'#width' => 18,
'#height' => 18,
'#alt' => $this->t('Pass'),
'#alt' => 'Pass',
);
$image_fail = array(
'#theme' => 'image',
'#uri' => 'core/misc/icons/ea2800/error.svg',
'#width' => 18,
'#height' => 18,
'#alt' => $this->t('Fail'),
'#alt' => 'Fail',
);
$image_exception = array(
'#theme' => 'image',
'#uri' => 'core/misc/icons/e29700/warning.svg',
'#width' => 18,
'#height' => 18,
'#alt' => $this->t('Exception'),
'#alt' => 'Exception',
);
$image_debug = array(
'#theme' => 'image',
'#uri' => 'core/misc/icons/e29700/warning.svg',
'#width' => 18,
'#height' => 18,
'#alt' => $this->t('Debug'),
'#alt' => 'Debug',
);
$this->statusImageMap = array(
return array(
'pass' => drupal_render($image_pass),
'fail' => drupal_render($image_fail),
'exception' => drupal_render($image_exception),
......@@ -107,11 +112,9 @@ public function getFormId() {
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $test_id = NULL) {
$this->buildStatusImageMap();
// Make sure there are test results to display and a re-run is not being
// performed.
$results = array();
if (is_numeric($test_id) && !$results = $this->getResults($test_id)) {
drupal_set_message($this->t('No test results to display.'), 'error');
return new RedirectResponse($this->url('simpletest.test_form', array(), array('absolute' => TRUE)));
......@@ -119,90 +122,11 @@ public function buildForm(array $form, FormStateInterface $form_state, $test_id
// Load all classes and include CSS.
$form['#attached']['library'][] = 'simpletest/drupal.simpletest';
// Keep track of which test cases passed or failed.
$filter = array(
'pass' => array(),
'fail' => array(),
);
// Summary result widget.
$form['result'] = array(
'#type' => 'fieldset',
'#title' => $this->t('Results'),
);
$form['result']['summary'] = $summary = array(
'#theme' => 'simpletest_result_summary',
'#pass' => 0,
'#fail' => 0,
'#exception' => 0,
'#debug' => 0,
);
simpletest_classloader_register();
// Cycle through each test group.
$header = array(
$this->t('Message'),
$this->t('Group'),
$this->t('Filename'),
$this->t('Line'),
$this->t('Function'),
array('colspan' => 2, 'data' => $this->t('Status'))
);
$form['result']['results'] = array();
foreach ($results as $group => $assertions) {
// Create group details with summary information.
$info = TestDiscovery::getTestInfo($group);
$form['result']['results'][$group] = array(
'#type' => 'details',
'#title' => $info['name'],
'#open' => TRUE,
'#description' => $info['description'],
);
$form['result']['results'][$group]['summary'] = $summary;
$group_summary =& $form['result']['results'][$group]['summary'];
// Create table of assertions for the group.
$rows = array();
foreach ($assertions as $assertion) {
$row = array();
// Assertion messages are in code, so we assume they are safe.
$row[] = SafeMarkup::set($assertion->message);
$row[] = $assertion->message_group;
$row[] = drupal_basename($assertion->file);
$row[] = $assertion->line;
$row[] = $assertion->function;
$row[] = $this->statusImageMap[$assertion->status];
$class = 'simpletest-' . $assertion->status;
if ($assertion->message_group == 'Debug') {
$class = 'simpletest-debug';
}
$rows[] = array('data' => $row, 'class' => array($class));
$group_summary['#' . $assertion->status]++;
$form['result']['summary']['#' . $assertion->status]++;
}
$form['result']['results'][$group]['table'] = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
);
// Set summary information.
$group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0;
$form['result']['results'][$group]['#open'] = !$group_summary['#ok'];
// Store test group (class) as for use in filter.
$filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group;
}
// Overall summary status.
$form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0;
// Add the results form.
$filter = static::addResultForm($form, $results, $this->getStringTranslation());
// Actions.
$form['#action'] = \Drupal::url('simpletest.result_form', array('test_id' => 're-run'));
$form['#action'] = $this->url('simpletest.result_form', array('test_id' => 're-run'));
$form['action'] = array(
'#type' => 'fieldset',
'#title' => $this->t('Actions'),
......@@ -299,13 +223,36 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
* Array of results grouped by test_class.
*/
protected function getResults($test_id) {
$results = $this->database->select('simpletest')
return $this->database->select('simpletest')
->fields('simpletest')
->condition('test_id', $test_id)
->orderBy('test_class')
->orderBy('message_id')
->execute();
->execute()
->fetchAll();
}
/**
* Adds the result form to a $form.
*
* This is a static method so that run-tests.sh can use it to generate a
* results page completely external to Drupal. This is why the UI strings are
* not wrapped in t().
*
* @param array $form
* The form to attach the results to.
* @param array $test_results
* The simpletest results.
*
* @return array
* A list of tests the passed and failed. The array has two keys, 'pass' and
* 'fail'. Each contains a list of test classes.
*
* @see simpletest_script_open_browser()
* @see run-tests.sh
*/
public static function addResultForm(array &$form, array $results) {
// Transform the test results to be grouped by test class.
$test_results = array();
foreach ($results as $result) {
if (!isset($test_results[$result->test_class])) {
......@@ -314,7 +261,93 @@ protected function getResults($test_id) {
$test_results[$result->test_class][] = $result;
}
return $test_results;
$image_status_map = static::buildStatusImageMap();
// Keep track of which test cases passed or failed.
$filter = array(
'pass' => array(),
'fail' => array(),
);
// Summary result widget.
$form['result'] = array(
'#type' => 'fieldset',
'#title' => 'Results',
// Because this is used in a theme-less situation need to provide a
// default.
'#attributes' => array(),
);
$form['result']['summary'] = $summary = array(
'#theme' => 'simpletest_result_summary',
'#pass' => 0,
'#fail' => 0,
'#exception' => 0,
'#debug' => 0,
);
\Drupal::service('test_discovery')->registerTestNamespaces();
// Cycle through each test group.
$header = array(
'Message',
'Group',
'Filename',
'Line',
'Function',
array('colspan' => 2, 'data' => 'Status')
);
$form['result']['results'] = array();
foreach ($test_results as $group => $assertions) {
// Create group details with summary information.
$info = TestDiscovery::getTestInfo($group);
$form['result']['results'][$group] = array(
'#type' => 'details',
'#title' => $info['name'],
'#open' => TRUE,
'#description' => $info['description'],
);
$form['result']['results'][$group]['summary'] = $summary;
$group_summary =& $form['result']['results'][$group]['summary'];
// Create table of assertions for the group.
$rows = array();
foreach ($assertions as $assertion) {
$row = array();
// Assertion messages are in code, so we assume they are safe.
$row[] = SafeMarkup::set($assertion->message);
$row[] = $assertion->message_group;
$row[] = \Drupal::service('file_system')->basename(($assertion->file));
$row[] = $assertion->line;
$row[] = $assertion->function;
$row[] = $image_status_map[$assertion->status];
$class = 'simpletest-' . $assertion->status;
if ($assertion->message_group == 'Debug') {
$class = 'simpletest-debug';
}
$rows[] = array('data' => $row, 'class' => array($class));
$group_summary['#' . $assertion->status]++;
$form['result']['summary']['#' . $assertion->status]++;
}
$form['result']['results'][$group]['table'] = array(
'#type' => 'table',
'#header' => $header,
'#rows' => $rows,
);
// Set summary information.
$group_summary['#ok'] = $group_summary['#fail'] + $group_summary['#exception'] == 0;
$form['result']['results'][$group]['#open'] = !$group_summary['#ok'];
// Store test group (class) as for use in filter.
$filter[$group_summary['#ok'] ? 'pass' : 'fail'][] = $group;
}
// Overall summary status.
$form['result']['summary']['#ok'] = $form['result']['summary']['#fail'] + $form['result']['summary']['#exception'] == 0;
return $filter;
}
}
......@@ -873,10 +873,10 @@ protected function verbose($message) {
return;
}
$message = '<hr />ID #' . $this->verboseId . ' (<a href="' . $this->verboseClassName . '-' . ($this->verboseId - 1) . '.html">Previous</a> | <a href="' . $this->verboseClassName . '-' . ($this->verboseId + 1) . '.html">Next</a>)<hr />' . $message;
$verbose_filename = $this->verboseDirectory . '/' . $this->verboseClassName . '-' . $this->verboseId . '.html';
if (file_put_contents($verbose_filename, $message, FILE_APPEND)) {
$url = $this->verboseDirectoryUrl . '/' . $this->verboseClassName . '-' . $this->verboseId . '.html';
$message = '<hr />ID #' . $this->verboseId . ' (<a href="' . $this->verboseClassName . '-' . ($this->verboseId - 1) . '-' . $this->testId . '.html">Previous</a> | <a href="' . $this->verboseClassName . '-' . ($this->verboseId + 1) . '-' . $this->testId . '.html">Next</a>)<hr />' . $message;
$verbose_filename = $this->verboseClassName . '-' . $this->verboseId . '-' . $this->testId . '.html';
if (file_put_contents($this->verboseDirectory . '/' . $verbose_filename, $message)) {
$url = $this->verboseDirectoryUrl . '/' . $verbose_filename;
// Not using _l() to avoid invoking the theme system, so that unit tests
// can use verbose() as well.
$url = '<a href="' . $url . '" target="_blank">Verbose message</a>';
......
......@@ -6,9 +6,13 @@
*/
use Drupal\Component\Utility\Timer;
use Drupal\Component\Uuid\Php;
use Drupal\Core\Database\Database;
use Drupal\Core\Form\FormState;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\Test\TestRunnerKernel;
use Drupal\simpletest\Form\SimpletestResultsForm;
use Symfony\Component\HttpFoundation\Request;
$autoloader = require_once __DIR__ . '/../vendor/autoload.php';
......@@ -88,7 +92,12 @@
simpletest_script_reporter_timer_stop();
// Display results before database is cleared.
simpletest_script_reporter_display_results();
if ($args['browser']) {
simpletest_script_open_browser();
}
else {
simpletest_script_reporter_display_results();
}
if ($args['xml']) {
simpletest_script_reporter_write_xml_results();
......@@ -188,6 +197,10 @@ function simpletest_script_help() {
test database and configuration directories. Use in combination
with --repeat for debugging random test failures.
--browser Opens the results in the browser. This enforces --keep-results and
if you want to also view any pages rendered in the simpletest
browser you need to add --verbose to the command line.
<test1>[,<test2>[,<test3> ...]]
One or more tests to be run. By default, these are interpreted
......@@ -246,6 +259,7 @@ function simpletest_script_parse_args() {
'test_names' => array(),
'repeat' => 1,
'die-on-fail' => FALSE,
'browser' => FALSE,
// Used internally.
'test-id' => 0,
'execute-test' => '',
......@@ -291,6 +305,9 @@ function simpletest_script_parse_args() {
exit;
}
if ($args['browser']) {
$args['keep-results'] = TRUE;
}
return array($args, $count);
}
......@@ -1162,3 +1179,65 @@ function simpletest_script_load_messages_by_test_id($test_ids) {
return $results;
}
/**
* Display test results.
*/
function simpletest_script_open_browser() {
global $test_ids;
$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();
// Get the results form.
$form = array();
SimpletestResultsForm::addResultForm($form, $results);
// Get the assets to make the details element collapsible and theme the result
// form.
$assets = new \Drupal\Core\Asset\AttachedAssets();
$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.
$html = '<head>' . drupal_render($js_assets_header) . drupal_render($css_assets) . '</head><body>' . drupal_render($form) . drupal_render($js_assets_footer) .'</body>';
// Ensure we have assets verbose directory - tests with no verbose output will not
// have created one.
$directory = PublicStream::basePath() . '/simpletest/verbose';
file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$uuid = new Php();
$filename = $directory .'/results-'. $uuid->generate() .'.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($filename));
}
else {
// Can't find assets valid browser.
print 'Open file://' . realpath($filename) . ' in your browser to see the verbose output.';
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment