Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,352 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Form\SimpletestResultsForm.
*/
namespace Drupal\simpletest\Form;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Database\Connection;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\simpletest\TestDiscovery;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
/**
* 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 {
/**
* Associative array of themed result images keyed by status.
*
* @var array
*/
protected $statusImageMap;
/**
* The database connection service.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('database')
);
}
/**
* Constructs a \Drupal\simpletest\Form\SimpletestResultsForm object.
*
* @param \Drupal\Core\Database\Connection $database
* The database connection service.
*/
public function __construct(Connection $database) {
$this->database = $database;
}
/**
* Builds the status image map.
*/
protected static function buildStatusImageMap() {
$image_pass = array(
'#theme' => 'image',
'#uri' => 'core/misc/icons/73b355/check.svg',
'#width' => 18,
'#height' => 18,
'#alt' => 'Pass',
);
$image_fail = array(
'#theme' => 'image',
'#uri' => 'core/misc/icons/ea2800/error.svg',
'#width' => 18,
'#height' => 18,
'#alt' => 'Fail',
);
$image_exception = array(
'#theme' => 'image',
'#uri' => 'core/misc/icons/e29700/warning.svg',
'#width' => 18,
'#height' => 18,
'#alt' => 'Exception',
);
$image_debug = array(
'#theme' => 'image',
'#uri' => 'core/misc/icons/e29700/warning.svg',
'#width' => 18,
'#height' => 18,
'#alt' => 'Debug',
);
return array(
'pass' => $image_pass,
'fail' => $image_fail,
'exception' => $image_exception,
'debug' => $image_debug,
);
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'simpletest_results_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state, $test_id = NULL) {
// 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)));
}
// Load all classes and include CSS.
$form['#attached']['library'][] = 'simpletest/drupal.simpletest';
// Add the results form.
$filter = static::addResultForm($form, $results, $this->getStringTranslation());
// Actions.
$form['#action'] = $this->url('simpletest.result_form', array('test_id' => 're-run'));
$form['action'] = array(
'#type' => 'fieldset',
'#title' => $this->t('Actions'),
'#attributes' => array('class' => array('container-inline')),
'#weight' => -11,
);
$form['action']['filter'] = array(
'#type' => 'select',
'#title' => 'Filter',
'#options' => array(
'all' => $this->t('All (@count)', array('@count' => count($filter['pass']) + count($filter['fail']))),
'pass' => $this->t('Pass (@count)', array('@count' => count($filter['pass']))),
'fail' => $this->t('Fail (@count)', array('@count' => count($filter['fail']))),
),
);
$form['action']['filter']['#default_value'] = ($filter['fail'] ? 'fail' : 'all');
// Categorized test classes for to be used with selected filter value.
$form['action']['filter_pass'] = array(
'#type' => 'hidden',
'#default_value' => implode(',', $filter['pass']),
);
$form['action']['filter_fail'] = array(
'#type' => 'hidden',
'#default_value' => implode(',', $filter['fail']),
);
$form['action']['op'] = array(
'#type' => 'submit',
'#value' => $this->t('Run tests'),
);
$form['action']['return'] = array(
'#type' => 'link',
'#title' => $this->t('Return to list'),
'#url' => Url::fromRoute('simpletest.test_form'),
);
if (is_numeric($test_id)) {
simpletest_clean_results_table($test_id);
}
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$pass = $form_state->getValue('filter_pass') ? explode(',', $form_state->getValue('filter_pass')) : array();
$fail = $form_state->getValue('filter_fail') ? explode(',', $form_state->getValue('filter_fail')) : array();
if ($form_state->getValue('filter') == 'all') {
$classes = array_merge($pass, $fail);
}
elseif ($form_state->getValue('filter') == 'pass') {
$classes = $pass;
}
else {
$classes = $fail;
}
if (!$classes) {
$form_state->setRedirect('simpletest.test_form');
return;
}
$form_execute = array();
$form_state_execute = new FormState();
foreach ($classes as $class) {
$form_state_execute->setValue(['tests', $class], $class);
}
// Submit the simpletest test form to rerun the tests.
// Under normal circumstances, a form object's submitForm() should never be
// called directly, FormBuilder::submitForm() should be called instead.
// However, it calls $form_state->setProgrammed(), which disables the Batch API.
$simpletest_test_form = SimpletestTestForm::create(\Drupal::getContainer());
$simpletest_test_form->buildForm($form_execute, $form_state_execute);
$simpletest_test_form->submitForm($form_execute, $form_state_execute);
if ($redirect = $form_state_execute->getRedirect()) {
$form_state->setRedirectUrl($redirect);
}
}
/**
* Get test results for $test_id.
*
* @param int $test_id
* The test_id to retrieve results of.
*
* @return array
* Array of results grouped by test_class.
*/
protected function getResults($test_id) {
return $this->database->select('simpletest')
->fields('simpletest')
->condition('test_id', $test_id)
->orderBy('test_class')
->orderBy('message_id')
->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])) {
$test_results[$result->test_class] = array();
}
$test_results[$result->test_class][] = $result;
}
$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();
$row[] = SafeMarkup::checkAdminXss($assertion->message);
$row[] = $assertion->message_group;
$row[] = \Drupal::service('file_system')->basename(($assertion->file));
$row[] = $assertion->line;
$row[] = $assertion->function;
$row[] = ['data' => $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;
}
}

View file

@ -0,0 +1,129 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Form\SimpletestSettingsForm.
*/
namespace Drupal\simpletest\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Configure simpletest settings for this site.
*/
class SimpletestSettingsForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'simpletest_settings_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['simpletest.settings'];
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('simpletest.settings');
$form['general'] = array(
'#type' => 'details',
'#title' => $this->t('General'),
'#open' => TRUE,
);
$form['general']['simpletest_clear_results'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Clear results after each complete test suite run'),
'#description' => $this->t('By default SimpleTest will clear the results after they have been viewed on the results page, but in some cases it may be useful to leave the results in the database. The results can then be viewed at <em>admin/config/development/testing/results/[test_id]</em>. The test ID can be found in the database, simpletest table, or kept track of when viewing the results the first time. Additionally, some modules may provide more analysis or features that require this setting to be disabled.'),
'#default_value' => $config->get('clear_results'),
);
$form['general']['simpletest_verbose'] = array(
'#type' => 'checkbox',
'#title' => $this->t('Provide verbose information when running tests'),
'#description' => $this->t('The verbose data will be printed along with the standard assertions and is useful for debugging. The verbose data will be erased between each test suite run. The verbose data output is very detailed and should only be used when debugging.'),
'#default_value' => $config->get('verbose'),
);
$form['httpauth'] = array(
'#type' => 'details',
'#title' => $this->t('HTTP authentication'),
'#description' => $this->t('HTTP auth settings to be used by the SimpleTest browser during testing. Useful when the site requires basic HTTP authentication.'),
);
$form['httpauth']['simpletest_httpauth_method'] = array(
'#type' => 'select',
'#title' => $this->t('Method'),
'#options' => array(
CURLAUTH_BASIC => $this->t('Basic'),
CURLAUTH_DIGEST => $this->t('Digest'),
CURLAUTH_GSSNEGOTIATE => $this->t('GSS negotiate'),
CURLAUTH_NTLM => $this->t('NTLM'),
CURLAUTH_ANY => $this->t('Any'),
CURLAUTH_ANYSAFE => $this->t('Any safe'),
),
'#default_value' => $config->get('httpauth.method'),
);
$username = $config->get('httpauth.username');
$password = $config->get('httpauth.password');
$form['httpauth']['simpletest_httpauth_username'] = array(
'#type' => 'textfield',
'#title' => $this->t('Username'),
'#default_value' => $username,
);
if (!empty($username) && !empty($password)) {
$form['httpauth']['simpletest_httpauth_username']['#description'] = $this->t('Leave this blank to delete both the existing username and password.');
}
$form['httpauth']['simpletest_httpauth_password'] = array(
'#type' => 'password',
'#title' => $this->t('Password'),
);
if ($password) {
$form['httpauth']['simpletest_httpauth_password']['#description'] = $this->t('To change the password, enter the new password here.');
}
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
$config = $this->config('simpletest.settings');
// If a username was provided but a password wasn't, preserve the existing
// password.
if (!$form_state->isValueEmpty('simpletest_httpauth_username') && $form_state->isValueEmpty('simpletest_httpauth_password')) {
$form_state->setValue('simpletest_httpauth_password', $config->get('httpauth.password'));
}
// If a password was provided but a username wasn't, the credentials are
// incorrect, so throw an error.
if ($form_state->isValueEmpty('simpletest_httpauth_username') && !$form_state->isValueEmpty('simpletest_httpauth_password')) {
$form_state->setErrorByName('simpletest_httpauth_username', $this->t('HTTP authentication credentials must include a username in addition to a password.'));
}
parent::validateForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('simpletest.settings')
->set('clear_results', $form_state->getValue('simpletest_clear_results'))
->set('verbose', $form_state->getValue('simpletest_verbose'))
->set('httpauth.method', $form_state->getValue('simpletest_httpauth_method'))
->set('httpauth.username', $form_state->getValue('simpletest_httpauth_username'))
->set('httpauth.password', $form_state->getValue('simpletest_httpauth_password'))
->save();
parent::submitForm($form, $form_state);
}
}

View file

@ -0,0 +1,250 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Form\SimpletestTestForm.
*/
namespace Drupal\simpletest\Form;
use Drupal\Component\Utility\SortArray;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\RendererInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* List tests arranged in groups that can be selected and run.
*/
class SimpletestTestForm extends FormBase {
/**
* The renderer.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('renderer')
);
}
/**
* Constructs a new SimpletestTestForm.
*
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/
public function __construct(RendererInterface $renderer) {
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'simpletest_test_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Run tests'),
'#tableselect' => TRUE,
'#button_type' => 'primary',
);
// Do not needlessly re-execute a full test discovery if the user input
// already contains an explicit list of test classes to run.
$user_input = $form_state->getUserInput();
if (!empty($user_input['tests'])) {
return $form;
}
// JavaScript-only table filters.
$form['filters'] = array(
'#type' => 'container',
'#attributes' => array(
'class' => array('table-filter', 'js-show'),
),
);
$form['filters']['text'] = array(
'#type' => 'search',
'#title' => $this->t('Search'),
'#size' => 30,
'#placeholder' => $this->t('Enter test name…'),
'#attributes' => array(
'class' => array('table-filter-text'),
'data-table' => '#simpletest-test-form',
'autocomplete' => 'off',
'title' => $this->t('Enter at least 3 characters of the test name or description to filter by.'),
),
);
$form['tests'] = array(
'#type' => 'table',
'#id' => 'simpletest-form-table',
'#tableselect' => TRUE,
'#header' => array(
array('data' => $this->t('Test'), 'class' => array('simpletest-test-label')),
array('data' => $this->t('Description'), 'class' => array('simpletest-test-description')),
),
'#empty' => $this->t('No tests to display.'),
'#attached' => array(
'library' => array(
'simpletest/drupal.simpletest',
),
),
);
// Define the images used to expand/collapse the test groups.
$image_collapsed = array(
'#theme' => 'image',
'#uri' => 'core/misc/menu-collapsed.png',
'#width' => '7',
'#height' => '7',
'#alt' => $this->t('Expand'),
'#title' => $this->t('Expand'),
'#suffix' => '<a href="#" class="simpletest-collapse">(' . $this->t('Expand') . ')</a>',
);
$image_extended = array(
'#theme' => 'image',
'#uri' => 'core/misc/menu-expanded.png',
'#width' => '7',
'#height' => '7',
'#alt' => $this->t('Collapse'),
'#title' => $this->t('Collapse'),
'#suffix' => '<a href="#" class="simpletest-collapse">(' . $this->t('Collapse') . ')</a>',
);
$form['tests']['#attached']['drupalSettings']['simpleTest']['images'] = [
$this->renderer->renderPlain($image_collapsed),
$this->renderer->renderPlain($image_extended),
];
// Generate the list of tests arranged by group.
$groups = simpletest_test_get_all();
foreach ($groups as $group => $tests) {
$form['tests'][$group] = array(
'#attributes' => array('class' => array('simpletest-group')),
);
// Make the class name safe for output on the page by replacing all
// non-word/decimal characters with a dash (-).
$group_class = 'module-' . strtolower(trim(preg_replace("/[^\w\d]/", "-", $group)));
// Override tableselect column with custom selector for this group.
// This group-select-all checkbox is injected via JavaScript.
$form['tests'][$group]['select'] = array(
'#wrapper_attributes' => array(
'id' => $group_class,
'class' => array('simpletest-group-select-all'),
),
);
$form['tests'][$group]['title'] = array(
// Expand/collapse image.
'#prefix' => '<div class="simpletest-image" id="simpletest-test-group-' . $group_class . '"></div>',
'#markup' => '<label for="' . $group_class . '-group-select-all">' . $group . '</label>',
'#wrapper_attributes' => array(
'class' => array('simpletest-group-label'),
),
);
$form['tests'][$group]['description'] = array(
'#markup' => '&nbsp;',
'#wrapper_attributes' => array(
'class' => array('simpletest-group-description'),
),
);
// Cycle through each test within the current group.
foreach ($tests as $class => $info) {
$form['tests'][$class] = array(
'#attributes' => array('class' => array($group_class . '-test', 'js-hide')),
);
$form['tests'][$class]['title'] = array(
'#type' => 'label',
'#title' => '\\' . $info['name'],
'#wrapper_attributes' => array(
'class' => array('simpletest-test-label', 'table-filter-text-source'),
),
);
$form['tests'][$class]['description'] = array(
'#prefix' => '<div class="description">',
'#markup' => SafeMarkup::checkPlain($info['description']),
'#suffix' => '</div>',
'#wrapper_attributes' => array(
'class' => array('simpletest-test-description', 'table-filter-text-source'),
),
);
}
}
$form['clean'] = array(
'#type' => 'fieldset',
'#title' => $this->t('Clean test environment'),
'#description' => $this->t('Remove tables with the prefix "simpletest" and temporary directories that are left over from tests that crashed. This is intended for developers when creating tests.'),
'#weight' => 200,
);
$form['clean']['op'] = array(
'#type' => 'submit',
'#value' => $this->t('Clean environment'),
'#submit' => array('simpletest_clean_environment'),
);
return $form;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
global $base_url;
// Test discovery does not run upon form submission.
simpletest_classloader_register();
// This form accepts arbitrary user input for 'tests'.
// An invalid value will cause the $class_name lookup below to die with a
// fatal error. Regular user access mechanisms to this form are intact.
// The only validation effectively being skipped is the validation of
// available checkboxes vs. submitted checkboxes.
// @todo Refactor Form API to allow to POST values without constructing the
// entire form more easily, BUT retaining routing access security and
// retaining Form API CSRF #token security validation, and without having
// to rely on form caching.
$user_input = $form_state->getUserInput();
if ($form_state->isValueEmpty('tests') && !empty($user_input['tests'])) {
$form_state->setValue('tests', $user_input['tests']);
}
$tests_list = array();
foreach ($form_state->getValue('tests') as $class_name => $value) {
if ($value === $class_name) {
if (is_subclass_of($class_name, 'PHPUnit_Framework_TestCase')) {
$test_type = 'phpunit';
}
else {
$test_type = 'simpletest';
}
$tests_list[$test_type][] = $class_name;
}
}
if (!empty($tests_list)) {
putenv('SIMPLETEST_BASE_URL=' . $base_url);
$test_id = simpletest_run_tests($tests_list, 'drupal');
$form_state->setRedirect(
'simpletest.result_form',
array('test_id' => $test_id)
);
}
}
}