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,6 @@
clear_results: true
httpauth:
method: 1
password: ''
username: ''
verbose: true

View file

@ -0,0 +1,25 @@
# Schema for the configuration files of the Simpletest module.
simpletest.settings:
type: config_object
label: 'Testing'
mapping:
clear_results:
type: boolean
label: 'Clear results after each complete test suite run'
verbose:
type: boolean
label: 'Provide verbose information when running tests'
httpauth:
type: mapping
label: 'HTTP authentication'
mapping:
method:
type: integer
label: 'Method'
username:
type: string
label: 'Username'
password:
type: string
label: 'Password'

View file

@ -0,0 +1,89 @@
/* Test Table */
#simpletest-form-table th.select-all {
width: 1em;
}
th.simpletest-test-label {
width: 40%;
}
.simpletest-image {
display: inline-block;
cursor: pointer;
width: 1em;
}
.simpletest-group-label label {
display: inline;
font-weight: bold;
}
.simpletest-test-label label {
margin-left: 1em; /* LTR */
}
.simpletest-test-description .description {
margin: 0;
}
#simpletest-form-table tr td {
background-color: white;
color: #494949;
}
#simpletest-form-table tr.simpletest-group td {
background-color: #edf5fa;
color: #494949;
}
table#simpletest-form-table tr.simpletest-group label {
display: inline;
}
div.message > div.item-list {
font-weight: normal;
}
div.simpletest-pass {
color: #33a333;
}
.simpletest-fail {
color: #981010;
}
tr.simpletest-pass.odd {
background-color: #b6ffb6;
}
tr.simpletest-pass.even {
background-color: #9bff9b;
}
tr.simpletest-fail.odd {
background-color: #ffc9c9;
}
tr.simpletest-fail.even {
background-color: #ffacac;
}
tr.simpletest-exception.odd {
background-color: #f4ea71;
}
tr.simpletest-exception.even {
background-color: #f5e742;
}
tr.simpletest-debug.odd {
background-color: #eee;
}
tr.simpletest-debug.even {
background-color: #fff;
}
a.simpletest-collapse {
height: 0;
width: 0;
top: -99em;
position: absolute;
}
a.simpletest-collapse:focus,
a.simpletest-collapse:hover {
font-size: 80%;
top: 0px;
height: auto;
width: auto;
overflow: visible;
position: relative;
z-index: 1000;
}

View file

@ -0,0 +1,5 @@
These files are useful in tests that upload files or otherwise need to
manipulate files, in which case they are copied to the files directory as
specified in the site settings. Dummy files can also be generated by tests in
order to save space.

View file

@ -0,0 +1 @@
<h1>SimpleTest HTML</h1>

View file

@ -0,0 +1 @@
<h1>SimpleTest HTML</h1>

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 964 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 B

View file

@ -0,0 +1,3 @@
<script>
alert('SimpleTest PHP was executed!');
</script>

View file

@ -0,0 +1,3 @@
<script>
alert('SimpleTest PHP was executed!');
</script>

View file

@ -0,0 +1,3 @@
<?php
print 'SimpleTest PHP was executed!';
?>

View file

@ -0,0 +1,2 @@
<?php
print 'SimpleTest PHP was executed!';

View file

@ -0,0 +1 @@
SELECT invalid_field FROM {invalid_table}

View file

@ -0,0 +1 @@
SELECT invalid_field FROM {invalid_table}

View file

@ -0,0 +1,61 @@
<?php
/**
* @file
* Hooks provided by the SimpleTest module.
*/
/**
* @addtogroup hooks
* @{
*/
/**
* Alter the list of tests.
*
* @param $groups
* A two dimensional array, the first key is the test group, the second is the
* name of the test class, and the value is in associative array containing
* 'name', 'description', 'group', and 'requires' keys.
*/
function hook_simpletest_alter(&$groups) {
// An alternative session handler module would not want to run the original
// Session HTTPS handling test because it checks the sessions table in the
// database.
unset($groups['Session']['testHttpsSession']);
}
/**
* A test group has started.
*
* This hook is called just once at the beginning of a test group.
*/
function hook_test_group_started() {
}
/**
* A test group has finished.
*
* This hook is called just once at the end of a test group.
*/
function hook_test_group_finished() {
}
/**
* An individual test has finished.
*
* This hook is called when an individual test has finished.
*
* @param
* $results The results of the test as gathered by
* \Drupal\simpletest\WebTestBase.
*
* @see \Drupal\simpletest\WebTestBase->results()
*/
function hook_test_finished($results) {
}
/**
* @} End of "addtogroup hooks".
*/

View file

@ -0,0 +1,7 @@
name: Testing
type: module
description: 'Provides a framework for unit and functional testing.'
package: Core
version: VERSION
core: 8.x
configure: simpletest.settings

View file

@ -0,0 +1,192 @@
<?php
/**
* @file
* Install, update and uninstall functions for the simpletest module.
*/
use Drupal\Component\Utility\Environment;
/**
* Minimum value of PHP memory_limit for SimpleTest.
*/
const SIMPLETEST_MINIMUM_PHP_MEMORY_LIMIT = '128M';
/**
* Implements hook_requirements().
*/
function simpletest_requirements($phase) {
$requirements = array();
$has_curl = function_exists('curl_init');
$has_domdocument = method_exists('DOMDocument', 'loadHTML');
$open_basedir = ini_get('open_basedir');
$requirements['curl'] = array(
'title' => t('cURL'),
'value' => $has_curl ? t('Enabled') : t('Not found'),
);
if (!$has_curl) {
$requirements['curl']['severity'] = REQUIREMENT_ERROR;
$requirements['curl']['description'] = t('The testing framework could not be installed because the PHP <a href="@curl_url">cURL</a> library is not available.', array('@curl_url' => 'http://php.net/manual/curl.setup.php'));
}
$requirements['php_domdocument'] = array(
'title' => t('PHP DOMDocument class'),
'value' => $has_domdocument ? t('Enabled') : t('Not found'),
);
if (!$has_domdocument) {
$requirements['php_domdocument']['severity'] = REQUIREMENT_ERROR;
$requirements['php_domdocument']['description'] = t('The testing framework requires the DOMDocument class to be available. Check the configure command at the <a href="@link-phpinfo">PHP info page</a>.', array('@link-phpinfo' => \Drupal::url('system.php')));
}
// SimpleTest currently needs 2 cURL options which are incompatible with
// having PHP's open_basedir restriction set.
// See https://www.drupal.org/node/674304.
$requirements['php_open_basedir'] = array(
'title' => t('PHP open_basedir restriction'),
'value' => $open_basedir ? t('Enabled') : t('Disabled'),
);
if ($open_basedir) {
$requirements['php_open_basedir']['severity'] = REQUIREMENT_ERROR;
$requirements['php_open_basedir']['description'] = t('The testing framework requires the PHP <a href="@open_basedir-url">open_basedir</a> restriction to be disabled. Check your webserver configuration or contact your web host.', array('@open_basedir-url' => 'http://php.net/manual/ini.core.php#ini.open-basedir'));
}
// Check the current memory limit. If it is set too low, SimpleTest will fail
// to load all tests and throw a fatal error.
$memory_limit = ini_get('memory_limit');
if (!Environment::checkMemoryLimit(SIMPLETEST_MINIMUM_PHP_MEMORY_LIMIT, $memory_limit)) {
$requirements['php_memory_limit']['severity'] = REQUIREMENT_WARNING;
$requirements['php_memory_limit']['description'] = t('The testing framework requires the PHP memory limit to be at least %memory_minimum_limit. The current value is %memory_limit. <a href="@url">Follow these steps to continue</a>.', array('%memory_limit' => $memory_limit, '%memory_minimum_limit' => SIMPLETEST_MINIMUM_PHP_MEMORY_LIMIT, '@url' => 'https://www.drupal.org/node/207036'));
}
$site_directory = 'sites/simpletest';
if (!drupal_verify_install_file(\Drupal::root() . '/' . $site_directory, FILE_EXIST|FILE_READABLE|FILE_WRITABLE|FILE_EXECUTABLE, 'dir')) {
$requirements['simpletest_site_directory'] = array(
'title' => t('Simpletest site directory'),
'value' => is_dir(\Drupal::root() . '/' . $site_directory) ? t('Not writable') : t('Missing'),
'severity' => REQUIREMENT_ERROR,
'description' => t('The testing framework requires the %sites-simpletest directory to exist and be writable in order to run tests.', array(
'%sites-simpletest' => $site_directory,
)),
);
}
elseif (!file_save_htaccess(\Drupal::root() . '/' . $site_directory, FALSE)) {
$requirements['simpletest_site_directory'] = array(
'title' => t('Simpletest site directory'),
'value' => t('Not protected'),
'severity' => REQUIREMENT_ERROR,
'description' => t('The file %file does not exist and could not be created automatically, which poses a security risk. Ensure that the directory is writable.', array(
'%file' => $site_directory . '/.htaccess',
)),
);
}
return $requirements;
}
/**
* Implements hook_schema().
*/
function simpletest_schema() {
$schema['simpletest'] = array(
'description' => 'Stores simpletest messages',
'fields' => array(
'message_id' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique simpletest message ID.',
),
'test_id' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Test ID, messages belonging to the same ID are reported together',
),
'test_class' => array(
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'The name of the class that created this message.',
),
'status' => array(
'type' => 'varchar',
'length' => 9,
'not null' => TRUE,
'default' => '',
'description' => 'Message status. Core understands pass, fail, exception.',
),
'message' => array(
'type' => 'text',
'not null' => TRUE,
'description' => 'The message itself.',
),
'message_group' => array(
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'The message group this message belongs to. For example: warning, browser, user.',
),
'function' => array(
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Name of the assertion function or method that created this message.',
),
'line' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Line number on which the function is called.',
),
'file' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Name of the file where the function is called.',
),
),
'primary key' => array('message_id'),
'indexes' => array(
'reporter' => array('test_class', 'message_id'),
),
);
$schema['simpletest_test_id'] = array(
'description' => 'Stores simpletest test IDs, used to auto-increment the test ID so that a fresh test ID is used.',
'fields' => array(
'test_id' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique simpletest ID used to group test results together. Each time a set of tests
are run a new test ID is used.',
),
'last_prefix' => array(
'type' => 'varchar',
'length' => 60,
'not null' => FALSE,
'default' => '',
'description' => 'The last database prefix used during testing.',
),
),
'primary key' => array('test_id'),
);
return $schema;
}
/**
* Implements hook_uninstall().
*/
function simpletest_uninstall() {
// Do not clean the environment in case the Simpletest module is uninstalled
// in a (recursive) test for itself, since simpletest_clean_environment()
// would also delete the test site of the parent test process.
if (!drupal_valid_test_ua()) {
simpletest_clean_environment();
}
// Delete verbose test output and any other testing framework files.
file_unmanaged_delete_recursive('public://simpletest');
}

View file

@ -0,0 +1,121 @@
/**
* @file
* Simpletest behaviors.
*/
(function ($) {
"use strict";
/**
* Collapses table rows followed by group rows on the test listing page.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.simpleTestGroupCollapse = {
attach: function (context) {
$(context).find('.simpletest-group').once('simpletest-group-collapse').each(function () {
var $group = $(this);
var $image = $group.find('.simpletest-image');
$image
.html(drupalSettings.simpleTest.images[0])
.on('click', function () {
var $tests = $group.nextUntil('.simpletest-group');
var expand = !$group.hasClass('expanded');
$group.toggleClass('expanded', expand);
$tests.toggleClass('js-hide', !expand);
$image.html(drupalSettings.simpleTest.images[+expand]);
});
});
}
};
/**
* Toggles test checkboxes to match the group checkbox.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.simpleTestSelectAll = {
attach: function (context) {
$(context).find('.simpletest-group').once('simpletest-group-select-all').each(function () {
var $group = $(this);
var $cell = $group.find('.simpletest-group-select-all');
var $groupCheckbox = $('<input type="checkbox" id="' + $cell.attr('id') + '-group-select-all" class="form-checkbox" />');
var $testCheckboxes = $group.nextUntil('.simpletest-group').find('input[type=checkbox]');
$cell.append($groupCheckbox);
// Toggle the test checkboxes when the group checkbox is toggled.
$groupCheckbox.on('change', function () {
var checked = $(this).prop('checked');
$testCheckboxes.prop('checked', checked);
});
// Update the group checkbox when a test checkbox is toggled.
function updateGroupCheckbox() {
var allChecked = true;
$testCheckboxes.each(function () {
if (!$(this).prop('checked')) {
allChecked = false;
return false;
}
});
$groupCheckbox.prop('checked', allChecked);
}
$testCheckboxes.on('change', updateGroupCheckbox);
});
}
};
/**
* Filters the test list table by a text input search string.
*
* Text search input: input.table-filter-text
* Target table: input.table-filter-text[data-table]
* Source text: .table-filter-text-source
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.simpletestTableFilterByText = {
attach: function (context) {
var $input = $('input.table-filter-text').once('table-filter-text');
var $table = $($input.attr('data-table'));
var $rows;
var searched = false;
function filterTestList(e) {
var query = $(e.target).val().toLowerCase();
function showTestRow(index, row) {
var $row = $(row);
var $sources = $row.find('.table-filter-text-source');
var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
$row.closest('tr').toggle(textMatch);
}
// Filter if the length of the query is at least 3 characters.
if (query.length >= 3) {
// Indicate that a search has been performed, and hide the "select all"
// checkbox.
searched = true;
$('#simpletest-form-table thead th.select-all input').hide();
$rows.each(showTestRow);
}
// Restore to the original state if any searching has occurred.
else if (searched) {
searched = false;
$('#simpletest-form-table thead th.select-all input').show();
// Restore all rows to their original display state.
$rows.css('display', '');
}
}
if ($table.length) {
$rows = $table.find('tbody tr');
$input.trigger('focus').on('keyup', Drupal.debounce(filterTestList, 200));
}
}
};
})(jQuery);

View file

@ -0,0 +1,14 @@
drupal.simpletest:
version: VERSION
js:
simpletest.js: {}
css:
component:
css/simpletest.module.css: {}
dependencies:
- core/jquery
- core/drupal
- core/drupalSettings
- core/jquery.once
- core/drupal.tableselect
- core/drupal.debounce

View file

@ -0,0 +1,6 @@
simpletest.test_form:
title: Testing
description: 'Run tests against Drupal core and your modules. These tests help assure that your site code is working as designed.'
route_name: simpletest.test_form
parent: system.admin_config_development
weight: -5

View file

@ -0,0 +1,9 @@
simpletest.test_form:
title: List
route_name: simpletest.test_form
base_route: simpletest.test_form
simpletest.settings:
title: Settings
route_name: simpletest.settings
base_route: simpletest.test_form
weight: 100

View file

@ -0,0 +1,749 @@
<?php
use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Database\Database;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\simpletest\TestBase;
use Drupal\simpletest\TestDiscovery;
use Symfony\Component\Process\PhpExecutableFinder;
/**
* @file
* Provides testing functionality.
*/
/**
* Implements hook_help().
*/
function simpletest_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.simpletest':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Testing module provides a framework for running automated tests. It can be used to verify a working state of Drupal before and after any code changes, or as a means for developers to write and execute tests for their modules. For more information, see <a href="!simpletest">the online documentation for the Testing module</a>.', array('!simpletest' => 'https://www.drupal.org/documentation/modules/simpletest')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Running tests') . '</dt>';
$output .= '<dd><p>' . t('Visit the <a href="!admin-simpletest">Testing page</a> to display a list of available tests. For comprehensive testing, select <em>all</em> tests, or individually select tests for more targeted testing. Note that it might take several minutes for all tests to complete.', array('!admin-simpletest' => \Drupal::url('simpletest.test_form'))) . '</p>';
$output .= '<p>' . t('After the tests run, a message will be displayed next to each test group indicating whether tests within it passed, failed, or had exceptions. A pass means that the test returned the expected results, while fail means that it did not. An exception normally indicates an error outside of the test, such as a PHP warning or notice. If there were failures or exceptions, the results will be expanded to show details, and the tests that had failures or exceptions will be indicated in red or pink rows. You can then use these results to refine your code and tests, until all tests pass.') . '</p></dd>';
$output .= '</dl>';
return $output;
case 'simpletest.test_form':
$output = t('Select the test(s) or test group(s) you would like to run, and click <em>Run tests</em>.');
return $output;
}
}
/**
* Implements hook_theme().
*/
function simpletest_theme() {
return array(
'simpletest_result_summary' => array(
'variables' => array('label' => NULL, 'items' => array(), 'pass' => 0, 'fail' => 0, 'exception' => 0, 'debug' => 0),
),
);
}
/**
* Implements hook_js_alter().
*/
function simpletest_js_alter(&$javascript, AttachedAssetsInterface $assets) {
// Since SimpleTest is a special use case for the table select, stick the
// SimpleTest JavaScript above the table select.
$simpletest = drupal_get_path('module', 'simpletest') . '/simpletest.js';
if (array_key_exists($simpletest, $javascript) && array_key_exists('core/misc/tableselect.js', $javascript)) {
$javascript[$simpletest]['weight'] = $javascript['core/misc/tableselect.js']['weight'] - 1;
}
}
/**
* Prepares variables for simpletest result summary templates.
*
* Default template: simpletest-result-summary.html.twig.
*
* @param array $variables
* An associative array containing:
* - label: An optional label to be rendered before the results.
* - ok: The overall group result pass or fail.
* - pass: The number of passes.
* - fail: The number of fails.
* - exception: The number of exceptions.
* - debug: The number of debug messages.
*/
function template_preprocess_simpletest_result_summary(&$variables) {
$variables['items'] = _simpletest_build_summary_line($variables);
}
/**
* Formats each test result type pluralized summary.
*
* @param array $summary
* A summary of the test results.
*
* @return array
* The pluralized test summary items.
*/
function _simpletest_build_summary_line($summary) {
$translation = \Drupal::translation();
$items['pass'] = $translation->formatPlural($summary['pass'], '1 pass', '@count passes');
$items['fail'] = $translation->formatPlural($summary['fail'], '1 fail', '@count fails');
$items['exception'] = $translation->formatPlural($summary['exception'], '1 exception', '@count exceptions');
if ($summary['debug']) {
$items['debug'] = $translation->formatPlural($summary['debug'], '1 debug message', '@count debug messages');
}
return $items;
}
/**
* Formats test result summaries into a comma separated string for run-tests.sh.
*
* @param array $summary
* A summary of the test results.
*
* @return string
* A concatenated string of the formatted test results.
*/
function _simpletest_format_summary_line($summary) {
$parts = _simpletest_build_summary_line($summary);
return implode(', ', $parts);
}
/**
* Runs tests.
*
* @param $test_list
* List of tests to run.
*
* @return string
* The test ID.
*/
function simpletest_run_tests($test_list) {
$test_id = db_insert('simpletest_test_id')
->useDefaults(array('test_id'))
->execute();
if (!empty($test_list['phpunit'])) {
$phpunit_results = simpletest_run_phpunit_tests($test_id, $test_list['phpunit']);
simpletest_process_phpunit_results($phpunit_results);
}
// Early return if there are no further tests to run.
if (empty($test_list['simpletest'])) {
return $test_id;
}
// Continue with SimpleTests only.
$test_list = $test_list['simpletest'];
// Clear out the previous verbose files.
file_unmanaged_delete_recursive('public://simpletest/verbose');
// Get the info for the first test being run.
$first_test = reset($test_list);
$info = TestDiscovery::getTestInfo($first_test);
$batch = array(
'title' => t('Running tests'),
'operations' => array(
array('_simpletest_batch_operation', array($test_list, $test_id)),
),
'finished' => '_simpletest_batch_finished',
'progress_message' => '',
'library' => array('simpletest/drupal.simpletest'),
'init_message' => t('Processing test @num of @max - %test.', array('%test' => $info['name'], '@num' => '1', '@max' => count($test_list))),
);
batch_set($batch);
\Drupal::moduleHandler()->invokeAll('test_group_started');
return $test_id;
}
/**
* Executes PHPUnit tests and returns the results of the run.
*
* @param $test_id
* The current test ID.
* @param $unescaped_test_classnames
* An array of test class names, including full namespaces, to be passed as
* a regular expression to PHPUnit's --filter option.
*
* @return array
* The parsed results of PHPUnit's JUnit XML output, in the format of
* {simpletest}'s schema.
*/
function simpletest_run_phpunit_tests($test_id, array $unescaped_test_classnames) {
$phpunit_file = simpletest_phpunit_xml_filepath($test_id);
simpletest_phpunit_run_command($unescaped_test_classnames, $phpunit_file);
return simpletest_phpunit_xml_to_rows($test_id, $phpunit_file);
}
/**
* Inserts the parsed PHPUnit results into {simpletest}.
*
* @param array[] $phpunit_results
* An array of test results returned from simpletest_phpunit_xml_to_rows().
*/
function simpletest_process_phpunit_results($phpunit_results) {
// Insert the results of the PHPUnit test run into the database so the results
// are displayed along with Simpletest's results.
if (!empty($phpunit_results)) {
$query = TestBase::getDatabaseConnection()
->insert('simpletest')
->fields(array_keys($phpunit_results[0]));
foreach ($phpunit_results as $result) {
$query->values($result);
}
$query->execute();
}
}
/**
* Returns the path to use for PHPUnit's --log-junit option.
*
* @param $test_id
* The current test ID.
*
* @return string
* Path to the PHPUnit XML file to use for the current $test_id.
*/
function simpletest_phpunit_xml_filepath($test_id) {
return drupal_realpath('public://simpletest') . '/phpunit-' . $test_id . '.xml';
}
/**
* Returns the path to core's phpunit.xml.dist configuration file.
*
* @return string
* The path to core's phpunit.xml.dist configuration file.
*/
function simpletest_phpunit_configuration_filepath() {
return \Drupal::root() . '/core/phpunit.xml.dist';
}
/**
* Executes the PHPUnit command.
*
* @param array $unescaped_test_classnames
* An array of test class names, including full namespaces, to be passed as
* a regular expression to PHPUnit's --filter option.
* @param string $phpunit_file
* A filepath to use for PHPUnit's --log-junit option.
*
* @return string
* The results as returned by exec().
*/
function simpletest_phpunit_run_command(array $unescaped_test_classnames, $phpunit_file) {
// Setup an environment variable containing the database connection so that
// functional tests can connect to the database.
putenv('SIMPLETEST_DB=' . Database::getConnectionInfoAsUrl());
$phpunit_bin = simpletest_phpunit_command();
$command = array(
$phpunit_bin,
'--log-junit',
escapeshellarg($phpunit_file),
);
// Optimized for running a single test.
if (count($unescaped_test_classnames) == 1) {
$class = new \ReflectionClass($unescaped_test_classnames[0]);
$command[] = escapeshellarg($class->getFileName());
}
else {
// Double escape namespaces so they'll work in a regexp.
$escaped_test_classnames = array_map(function($class) {
return addslashes($class);
}, $unescaped_test_classnames);
$filter_string = implode("|", $escaped_test_classnames);
$command = array_merge($command, array(
'--filter',
escapeshellarg($filter_string),
));
}
// Need to change directories before running the command so that we can use
// relative paths in the configuration file's exclusions.
$old_cwd = getcwd();
chdir(\Drupal::root() . "/core");
// exec in a subshell so that the environment is isolated when running tests
// via the simpletest UI.
$ret = exec(join($command, " "));
chdir($old_cwd);
putenv('SIMPLETEST_DB=');
return $ret;
}
/**
* Returns the command to run PHPUnit.
*
* @return string
* The command that can be run through exec().
*/
function simpletest_phpunit_command() {
// Don't use the committed version in composer's bin dir if running on
// windows.
if (substr(PHP_OS, 0, 3) == 'WIN') {
$php_executable_finder = new PhpExecutableFinder();
$php = $php_executable_finder->find();
$phpunit_bin = escapeshellarg($php) . " -f " . escapeshellarg(\Drupal::root() . "/core/vendor/phpunit/phpunit/composer/bin/phpunit") . " --";
}
else {
$phpunit_bin = \Drupal::root() . "/core/vendor/bin/phpunit";
}
return $phpunit_bin;
}
/**
* Implements callback_batch_operation().
*/
function _simpletest_batch_operation($test_list_init, $test_id, &$context) {
simpletest_classloader_register();
// Get working values.
if (!isset($context['sandbox']['max'])) {
// First iteration: initialize working values.
$test_list = $test_list_init;
$context['sandbox']['max'] = count($test_list);
$test_results = array('#pass' => 0, '#fail' => 0, '#exception' => 0, '#debug' => 0);
}
else {
// Nth iteration: get the current values where we last stored them.
$test_list = $context['sandbox']['tests'];
$test_results = $context['sandbox']['test_results'];
}
$max = $context['sandbox']['max'];
// Perform the next test.
$test_class = array_shift($test_list);
$test = new $test_class($test_id);
$test->run();
$size = count($test_list);
$info = TestDiscovery::getTestInfo($test_class);
\Drupal::moduleHandler()->invokeAll('test_finished', array($test->results));
// Gather results and compose the report.
$test_results[$test_class] = $test->results;
foreach ($test_results[$test_class] as $key => $value) {
$test_results[$key] += $value;
}
$test_results[$test_class]['#name'] = $info['name'];
$items = array();
foreach (Element::children($test_results) as $class) {
$class_test_result = $test_results[$class] + array(
'#theme' => 'simpletest_result_summary',
'#label' => t($test_results[$class]['#name'] . ':'),
);
array_unshift($items, drupal_render($class_test_result));
}
$context['message'] = t('Processed test @num of @max - %test.', array('%test' => $info['name'], '@num' => $max - $size, '@max' => $max));
$overall_results = $test_results + array(
'#theme' => 'simpletest_result_summary',
'#label' => t('Overall results:'),
);
$context['message'] .= drupal_render($overall_results);
$item_list = array(
'#theme' => 'item_list',
'#items' => $items,
);
$context['message'] .= drupal_render($item_list);
// Save working values for the next iteration.
$context['sandbox']['tests'] = $test_list;
$context['sandbox']['test_results'] = $test_results;
// The test_id is the only thing we need to save for the report page.
$context['results']['test_id'] = $test_id;
// Multistep processing: report progress.
$context['finished'] = 1 - $size / $max;
}
/**
* Implements callback_batch_finished().
*/
function _simpletest_batch_finished($success, $results, $operations, $elapsed) {
if ($success) {
drupal_set_message(t('The test run finished in @elapsed.', array('@elapsed' => $elapsed)));
}
else {
// Use the test_id passed as a parameter to _simpletest_batch_operation().
$test_id = $operations[0][1][1];
// Retrieve the last database prefix used for testing and the last test
// class that was run from. Use the information to read the lgo file
// in case any fatal errors caused the test to crash.
list($last_prefix, $last_test_class) = simpletest_last_test_get($test_id);
simpletest_log_read($test_id, $last_prefix, $last_test_class);
drupal_set_message(t('The test run did not successfully finish.'), 'error');
drupal_set_message(t('Use the <em>Clean environment</em> button to clean-up temporary files and tables.'), 'warning');
}
\Drupal::moduleHandler()->invokeAll('test_group_finished');
}
/**
* Get information about the last test that ran given a test ID.
*
* @param $test_id
* The test ID to get the last test from.
* @return array
* Array containing the last database prefix used and the last test class
* that ran.
*/
function simpletest_last_test_get($test_id) {
$last_prefix = TestBase::getDatabaseConnection()
->queryRange('SELECT last_prefix FROM {simpletest_test_id} WHERE test_id = :test_id', 0, 1, array(
':test_id' => $test_id,
))
->fetchField();
$last_test_class = TestBase::getDatabaseConnection()
->queryRange('SELECT test_class FROM {simpletest} WHERE test_id = :test_id ORDER BY message_id DESC', 0, 1, array(
':test_id' => $test_id,
))
->fetchField();
return array($last_prefix, $last_test_class);
}
/**
* Reads the error log and reports any errors as assertion failures.
*
* The errors in the log should only be fatal errors since any other errors
* will have been recorded by the error handler.
*
* @param $test_id
* The test ID to which the log relates.
* @param $database_prefix
* The database prefix to which the log relates.
* @param $test_class
* The test class to which the log relates.
*
* @return bool
* Whether any fatal errors were found.
*/
function simpletest_log_read($test_id, $database_prefix, $test_class) {
$log = DRUPAL_ROOT . '/sites/simpletest/' . substr($database_prefix, 10) . '/error.log';
$found = FALSE;
if (file_exists($log)) {
foreach (file($log) as $line) {
if (preg_match('/\[.*?\] (.*?): (.*?) in (.*) on line (\d+)/', $line, $match)) {
// Parse PHP fatal errors for example: PHP Fatal error: Call to
// undefined function break_me() in /path/to/file.php on line 17
$caller = array(
'line' => $match[4],
'file' => $match[3],
);
TestBase::insertAssert($test_id, $test_class, FALSE, $match[2], $match[1], $caller);
}
else {
// Unknown format, place the entire message in the log.
TestBase::insertAssert($test_id, $test_class, FALSE, $line, 'Fatal error');
}
$found = TRUE;
}
}
return $found;
}
/**
* Gets a list of all of the tests provided by the system.
*
* The list of test classes is loaded by searching the designated directory for
* each module for files matching the PSR-0 standard. Once loaded the test list
* is cached and stored in a static variable.
*
* @param string $module
* Name of a module. If set then only tests belonging to this module are
* returned.
*
* @return array[]
* An array of tests keyed with the groups, and then keyed by test classes.
* For example:
* @code
* $groups['Block'] => array(
* 'BlockTestCase' => array(
* 'name' => 'Block functionality',
* 'description' => 'Add, edit and delete custom block.',
* 'group' => 'Block',
* ),
* );
* @endcode
*/
function simpletest_test_get_all($module = NULL) {
return \Drupal::service('test_discovery')->getTestClasses($module);
}
/**
* Registers namespaces for disabled modules.
*/
function simpletest_classloader_register() {
\Drupal::service('test_discovery')->registerTestNamespaces();
}
/**
* Generates test file.
*
* @param string $filename
* The name of the file, including the path.
* @param int $width
* The number of characters on one line.
* @param int $lines
* The number of lines in the file.
* @param string $type
* (optional) The type, for example: "text", "binary", or "binary-text".
*
* @return string
* The name of the file, including the path.
*/
function simpletest_generate_file($filename, $width, $lines, $type = 'binary-text') {
$size = $width * $lines - $lines;
// Generate random text
$text = '';
for ($i = 0; $i < $size; $i++) {
switch ($type) {
case 'text':
$text .= chr(rand(32, 126));
break;
case 'binary':
$text .= chr(rand(0, 31));
break;
case 'binary-text':
default:
$text .= rand(0, 1);
break;
}
}
// Add \n for symmetrical file.
$text = wordwrap($text, $width - 1, "\n", TRUE) . "\n";
// Create filename.
file_put_contents('public://' . $filename . '.txt', $text);
return $filename;
}
/**
* Removes all temporary database tables and directories.
*/
function simpletest_clean_environment() {
simpletest_clean_database();
simpletest_clean_temporary_directories();
if (\Drupal::config('simpletest.settings')->get('clear_results')) {
$count = simpletest_clean_results_table();
drupal_set_message(\Drupal::translation()->formatPlural($count, 'Removed 1 test result.', 'Removed @count test results.'));
}
else {
drupal_set_message(t('Clear results is disabled and the test results table will not be cleared.'), 'warning');
}
// Detect test classes that have been added, renamed or deleted.
\Drupal::cache()->delete('simpletest');
\Drupal::cache()->delete('simpletest_phpunit');
}
/**
* Removes prefixed tables from the database from crashed tests.
*/
function simpletest_clean_database() {
$tables = db_find_tables(Database::getConnection()->prefixTables('{simpletest}') . '%');
$schema = drupal_get_module_schema('simpletest');
$count = 0;
foreach (array_diff_key($tables, $schema) as $table) {
// Strip the prefix and skip tables without digits following "simpletest",
// e.g. {simpletest_test_id}.
if (preg_match('/simpletest\d+.*/', $table, $matches)) {
db_drop_table($matches[0]);
$count++;
}
}
if ($count > 0) {
drupal_set_message(\Drupal::translation()->formatPlural($count, 'Removed 1 leftover table.', 'Removed @count leftover tables.'));
}
else {
drupal_set_message(t('No leftover tables to remove.'));
}
}
/**
* Finds all leftover temporary directories and removes them.
*/
function simpletest_clean_temporary_directories() {
$count = 0;
if (is_dir(DRUPAL_ROOT . '/sites/simpletest')) {
$files = scandir(DRUPAL_ROOT . '/sites/simpletest');
foreach ($files as $file) {
if ($file[0] != '.') {
$path = DRUPAL_ROOT . '/sites/simpletest/' . $file;
file_unmanaged_delete_recursive($path, array('Drupal\simpletest\TestBase', 'filePreDeleteCallback'));
$count++;
}
}
}
if ($count > 0) {
drupal_set_message(\Drupal::translation()->formatPlural($count, 'Removed 1 temporary directory.', 'Removed @count temporary directories.'));
}
else {
drupal_set_message(t('No temporary directories to remove.'));
}
}
/**
* Clears the test result tables.
*
* @param $test_id
* Test ID to remove results for, or NULL to remove all results.
*
* @return int
* The number of results that were removed.
*/
function simpletest_clean_results_table($test_id = NULL) {
if (\Drupal::config('simpletest.settings')->get('clear_results')) {
$connection = TestBase::getDatabaseConnection();
if ($test_id) {
$count = $connection->query('SELECT COUNT(test_id) FROM {simpletest_test_id} WHERE test_id = :test_id', array(':test_id' => $test_id))->fetchField();
$connection->delete('simpletest')
->condition('test_id', $test_id)
->execute();
$connection->delete('simpletest_test_id')
->condition('test_id', $test_id)
->execute();
}
else {
$count = $connection->query('SELECT COUNT(test_id) FROM {simpletest_test_id}')->fetchField();
// Clear test results.
$connection->delete('simpletest')->execute();
$connection->delete('simpletest_test_id')->execute();
}
return $count;
}
return 0;
}
/**
* Implements hook_mail_alter().
*
* Aborts sending of messages with ID 'simpletest_cancel_test'.
*
* @see MailTestCase::testCancelMessage()
*/
function simpletest_mail_alter(&$message) {
if ($message['id'] == 'simpletest_cancel_test') {
$message['send'] = FALSE;
}
}
/**
* Converts PHPUnit's JUnit XML output to an array.
*
* @param $test_id
* The current test ID.
* @param $phpunit_xml_file
* Path to the PHPUnit XML file.
*
* @return array[]
* The results as array of rows in a format that can be inserted into
* {simpletest}.
*/
function simpletest_phpunit_xml_to_rows($test_id, $phpunit_xml_file) {
$contents = @file_get_contents($phpunit_xml_file);
if (!$contents) {
return;
}
$records = array();
$testcases = simpletest_phpunit_find_testcases(new SimpleXMLElement($contents));
foreach ($testcases as $testcase) {
$records[] = simpletest_phpunit_testcase_to_row($test_id, $testcase);
}
return $records;
}
/**
* Finds all test cases recursively from a test suite list.
*
* @param \SimpleXMLElement $element
* The PHPUnit xml to search for test cases.
* @param \SimpleXMLElement $suite
* (Optional) The parent of the current element. Defaults to NULL.
*
* @return array
* A list of all test cases.
*/
function simpletest_phpunit_find_testcases(\SimpleXMLElement $element, \SimpleXMLElement $parent = NULL) {
$testcases = array();
if (!isset($parent)) {
$parent = $element;
}
if ($element->getName() === 'testcase' && (int) $parent->attributes()->tests > 0) {
// Add the class attribute if the testcase does not have one. This is the
// case for tests using a data provider. The name of the parent testsuite
// will be in the format class::method.
if (!$element->attributes()->class) {
$name = explode('::', $parent->attributes()->name, 2);
$element->addAttribute('class', $name[0]);
}
$testcases[] = $element;
}
else {
foreach ($element as $child) {
$file = (string) $parent->attributes()->file;
if ($file && !$child->attributes()->file) {
$child->addAttribute('file', $file);
}
$testcases = array_merge($testcases, simpletest_phpunit_find_testcases($child, $element));
}
}
return $testcases;
}
/**
* Converts a PHPUnit test case result to a {simpletest} result row.
*
* @param int $test_id
* The current test ID.
* @param \SimpleXMLElement $testcase
* The PHPUnit test case represented as XML element.
*
* @return array
* An array containing the {simpletest} result row.
*/
function simpletest_phpunit_testcase_to_row($test_id, \SimpleXMLElement $testcase) {
$message = '';
$pass = TRUE;
if ($testcase->failure) {
$lines = explode("\n", $testcase->failure);
$message = $lines[2];
$pass = FALSE;
}
if ($testcase->error) {
$message = $testcase->error;
$pass = FALSE;
}
$attributes = $testcase->attributes();
$record = array(
'test_id' => $test_id,
'test_class' => (string) $attributes->class,
'status' => $pass ? 'pass' : 'fail',
'message' => $message,
// @todo: Check on the proper values for this.
'message_group' => 'Other',
'function' => $attributes->class . '->' . $attributes->name . '()',
'line' => $attributes->line ?: 0,
'file' => $attributes->file,
);
return $record;
}

View file

@ -0,0 +1,3 @@
administer unit tests:
title: 'Administer tests'
restrict access: true

View file

@ -0,0 +1,23 @@
simpletest.settings:
path: '/admin/config/development/testing/settings'
defaults:
_form: 'Drupal\simpletest\Form\SimpletestSettingsForm'
_title: 'Settings'
requirements:
_permission: 'administer unit tests'
simpletest.test_form:
path: '/admin/config/development/testing'
defaults:
_form: '\Drupal\simpletest\Form\SimpletestTestForm'
_title: 'Testing'
requirements:
_permission: 'administer unit tests'
simpletest.result_form:
path: '/admin/config/development/testing/results/{test_id}'
defaults:
_form: 'Drupal\simpletest\Form\SimpletestResultsForm'
_title: 'Test result'
requirements:
_permission: 'administer unit tests'

View file

@ -0,0 +1,4 @@
services:
test_discovery:
class: Drupal\simpletest\TestDiscovery
arguments: ['@class_loader', '@?cache.discovery']

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,14 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Exception\MissingGroupException.
*/
namespace Drupal\simpletest\Exception;
/**
* Exception thrown when a simpletest class is missing an @group annotation.
*/
class MissingGroupException extends \LogicException {
}

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)
);
}
}
}

View file

@ -0,0 +1,218 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\InstallerTestBase.
*/
namespace Drupal\simpletest;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Language\Language;
use Drupal\Core\Session\UserSession;
use Drupal\Core\Site\Settings;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Base class for testing the interactive installer.
*/
abstract class InstallerTestBase extends WebTestBase {
/**
* Custom settings.php values to write for a test run.
*
* @var array
* An array of settings to write out, in the format expected by
* drupal_rewrite_settings().
*/
protected $settings = array();
/**
* The language code in which to install Drupal.
*
* @var string
*/
protected $langcode = 'en';
/**
* The installation profile to install.
*
* @var string
*/
protected $profile = 'testing';
/**
* Additional parameters to use for installer screens.
*
* @see WebTestBase::installParameters()
*
* @var array
*/
protected $parameters = array();
/**
* A string translation map used for translated installer screens.
*
* Keys are English strings, values are translated strings.
*
* @var array
*/
protected $translations = array(
'Save and continue' => 'Save and continue',
);
/**
* Whether the installer has completed.
*
* @var bool
*/
protected $isInstalled = FALSE;
/**
* Overrides WebTestBase::setUp().
*/
protected function setUp() {
$this->isInstalled = FALSE;
// Define information about the user 1 account.
$this->rootUser = new UserSession(array(
'uid' => 1,
'name' => 'admin',
'mail' => 'admin@example.com',
'pass_raw' => $this->randomMachineName(),
));
// If any $settings are defined for this test, copy and prepare an actual
// settings.php, so as to resemble a regular installation.
if (!empty($this->settings)) {
// Not using File API; a potential error must trigger a PHP warning.
copy(DRUPAL_ROOT . '/sites/default/default.settings.php', DRUPAL_ROOT . '/' . $this->siteDirectory . '/settings.php');
$this->writeSettings($this->settings);
}
// Note that WebTestBase::installParameters() returns form input values
// suitable for a programmed \Drupal::formBuilder()->submitForm().
// @see WebTestBase::translatePostValues()
$this->parameters = $this->installParameters();
// Set up a minimal container (required by WebTestBase).
// @see install_begin_request()
$request = Request::create($GLOBALS['base_url'] . '/core/install.php');
$this->container = new ContainerBuilder();
$request_stack = new RequestStack();
$request_stack->push($request);
$this->container
->set('request_stack', $request_stack);
$this->container
->setParameter('language.default_values', Language::$defaultValues);
$this->container
->register('language.default', 'Drupal\Core\Language\LanguageDefault')
->addArgument('%language.default_values%');
$this->container
->register('language_manager', 'Drupal\Core\Language\LanguageManager')
->addArgument(new Reference('language.default'));
$this->container
->register('string_translation', 'Drupal\Core\StringTranslation\TranslationManager')
->addArgument(new Reference('language_manager'));
$this->container
->set('app.root', DRUPAL_ROOT);
\Drupal::setContainer($this->container);
$this->drupalGet($GLOBALS['base_url'] . '/core/install.php');
// Select language.
$this->setUpLanguage();
// Select profile.
$this->setUpProfile();
// Configure settings.
$this->setUpSettings();
// @todo Allow test classes based on this class to act on further installer
// screens.
// Configure site.
$this->setUpSite();
// Import new settings.php written by the installer.
$request = Request::createFromGlobals();
$class_loader = require $this->container->get('app.root') . '/autoload.php';
Settings::initialize($this->container->get('app.root'), DrupalKernel::findSitePath($request), $class_loader);
foreach ($GLOBALS['config_directories'] as $type => $path) {
$this->configDirectories[$type] = $path;
}
// After writing settings.php, the installer removes write permissions
// from the site directory. To allow drupal_generate_test_ua() to write
// a file containing the private key for drupal_valid_test_ua(), the site
// directory has to be writable.
// WebTestBase::tearDown() will delete the entire test site directory.
// Not using File API; a potential error must trigger a PHP warning.
chmod($this->container->get('app.root') . '/' . $this->siteDirectory, 0777);
$this->kernel = DrupalKernel::createFromRequest($request, $class_loader, 'prod', FALSE);
$this->kernel->prepareLegacyRequest($request);
$this->container = $this->kernel->getContainer();
$config = $this->container->get('config.factory');
// Manually configure the test mail collector implementation to prevent
// tests from sending out e-mails and collect them in state instead.
$config->getEditable('system.mail')
->set('interface.default', 'test_mail_collector')
->save();
$this->isInstalled = TRUE;
}
/**
* Installer step: Select language.
*/
protected function setUpLanguage() {
$edit = array(
'langcode' => $this->langcode,
);
$this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
}
/**
* Installer step: Select installation profile.
*/
protected function setUpProfile() {
$edit = array(
'profile' => $this->profile,
);
$this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
}
/**
* Installer step: Configure settings.
*/
protected function setUpSettings() {
$edit = $this->translatePostValues($this->parameters['forms']['install_settings_form']);
$this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
}
/**
* Installer step: Configure site.
*/
protected function setUpSite() {
$edit = $this->translatePostValues($this->parameters['forms']['install_configure_form']);
$this->drupalPostForm(NULL, $edit, $this->translations['Save and continue']);
}
/**
* {@inheritdoc}
*
* WebTestBase::refreshVariables() tries to operate on persistent storage,
* which is only available after the installer completed.
*/
protected function refreshVariables() {
if ($this->isInstalled) {
parent::refreshVariables();
}
}
}

View file

@ -0,0 +1,585 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\KernelTestBase.
*/
namespace Drupal\simpletest;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Variable;
use Drupal\Core\Database\Database;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
use Drupal\Core\Language\Language;
use Drupal\Core\Site\Settings;
use Symfony\Component\DependencyInjection\Parameter;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpFoundation\Request;
/**
* Base class for integration tests.
*
* Tests extending this base class can access files and the database, but the
* entire environment is initially empty. Drupal runs in a minimal mocked
* environment, comparable to the one in the early installer.
*
* The module/hook system is functional and operates on a fixed module list.
* Additional modules needed in a test may be loaded and added to the fixed
* module list.
*
* @see \Drupal\simpletest\KernelTestBase::$modules
* @see \Drupal\simpletest\KernelTestBase::enableModules()
*
* @ingroup testing
*/
abstract class KernelTestBase extends TestBase {
use AssertContentTrait;
/**
* Modules to enable.
*
* Test classes extending this class, and any classes in the hierarchy up to
* this class, may specify individual lists of modules to enable by setting
* this property. The values of all properties in all classes in the hierarchy
* are merged.
*
* Any modules specified in the $modules property are automatically loaded and
* set as the fixed module list.
*
* Unlike WebTestBase::setUp(), the specified modules are loaded only, but not
* automatically installed. Modules need to be installed manually, if needed.
*
* @see \Drupal\simpletest\KernelTestBase::enableModules()
* @see \Drupal\simpletest\KernelTestBase::setUp()
*
* @var array
*/
public static $modules = array();
private $moduleFiles;
private $themeFiles;
/**
* The configuration directories for this test run.
*
* @var array
*/
protected $configDirectories = array();
/**
* A KeyValueMemoryFactory instance to use when building the container.
*
* @var \Drupal\Core\KeyValueStore\KeyValueMemoryFactory.
*/
protected $keyValueFactory;
/**
* Array of registered stream wrappers.
*
* @var array
*/
protected $streamWrappers = array();
/**
* {@inheritdoc}
*/
function __construct($test_id = NULL) {
parent::__construct($test_id);
$this->skipClasses[__CLASS__] = TRUE;
}
/**
* {@inheritdoc}
*/
protected function beforePrepareEnvironment() {
// Copy/prime extension file lists once to avoid filesystem scans.
if (!isset($this->moduleFiles)) {
$this->moduleFiles = \Drupal::state()->get('system.module.files') ?: array();
$this->themeFiles = \Drupal::state()->get('system.theme.files') ?: array();
}
}
/**
* Create and set new configuration directories.
*
* @see config_get_config_directory()
*
* @throws \RuntimeException
* Thrown when CONFIG_ACTIVE_DIRECTORY or CONFIG_STAGING_DIRECTORY cannot
* be created or made writable.
*/
protected function prepareConfigDirectories() {
$this->configDirectories = array();
include_once DRUPAL_ROOT . '/core/includes/install.inc';
foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) {
// Assign the relative path to the global variable.
$path = $this->siteDirectory . '/config_' . $type;
$GLOBALS['config_directories'][$type] = $path;
// Ensure the directory can be created and is writeable.
if (!install_ensure_config_directory($type)) {
throw new \RuntimeException("Failed to create '$type' config directory $path");
}
// Provide the already resolved path for tests.
$this->configDirectories[$type] = $path;
}
}
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->keyValueFactory = new KeyValueMemoryFactory();
// Back up settings from TestBase::prepareEnvironment().
$settings = Settings::getAll();
// Allow for test-specific overrides.
$directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
$settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml';
$container_yamls = [];
if (file_exists($settings_services_file)) {
// Copy the testing-specific service overrides in place.
$testing_services_file = $directory . '/services.yml';
copy($settings_services_file, $testing_services_file);
$container_yamls[] = $testing_services_file;
}
$settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php';
if (file_exists($settings_testing_file)) {
// Copy the testing-specific settings.php overrides in place.
copy($settings_testing_file, $directory . '/settings.testing.php');
}
if (file_exists($directory . '/settings.testing.php')) {
// Add the name of the testing class to settings.php and include the
// testing specific overrides
$hash_salt = Settings::getHashSalt();
$test_class = get_class($this);
$container_yamls_export = Variable::export($container_yamls);
$php = <<<EOD
<?php
\$settings['hash_salt'] = '$hash_salt';
\$settings['container_yamls'] = $container_yamls_export;
\$test_class = '$test_class';
include DRUPAL_ROOT . '/' . \$site_path . '/settings.testing.php';
EOD;
file_put_contents($directory . '/settings.php', $php);
}
// Add this test class as a service provider.
// @todo Remove the indirection; implement ServiceProviderInterface instead.
$GLOBALS['conf']['container_service_providers']['TestServiceProvider'] = 'Drupal\simpletest\TestServiceProvider';
// Bootstrap a new kernel.
$class_loader = require DRUPAL_ROOT . '/autoload.php';
$this->kernel = new DrupalKernel('testing', $class_loader, FALSE);
$request = Request::create('/');
$site_path = DrupalKernel::findSitePath($request);
$this->kernel->setSitePath($site_path);
if (file_exists($directory . '/settings.testing.php')) {
Settings::initialize(DRUPAL_ROOT, $site_path, $class_loader);
}
$this->kernel->boot();
// Save the original site directory path, so that extensions in the
// site-specific directory can still be discovered in the test site
// environment.
// @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
$settings['test_parent_site'] = $this->originalSite;
// Restore and merge settings.
// DrupalKernel::boot() initializes new Settings, and the containerBuild()
// method sets additional settings.
new Settings($settings + Settings::getAll());
// Create and set new configuration directories.
$this->prepareConfigDirectories();
// Set the request scope.
$this->container = $this->kernel->getContainer();
$this->container->get('request_stack')->push($request);
// Re-inject extension file listings into state, unless the key/value
// service was overridden (in which case its storage does not exist yet).
if ($this->container->get('keyvalue') instanceof KeyValueMemoryFactory) {
$this->container->get('state')->set('system.module.files', $this->moduleFiles);
$this->container->get('state')->set('system.theme.files', $this->themeFiles);
}
// Create a minimal core.extension configuration object so that the list of
// enabled modules can be maintained allowing
// \Drupal\Core\Config\ConfigInstaller::installDefaultConfig() to work.
// Write directly to active storage to avoid early instantiation of
// the event dispatcher which can prevent modules from registering events.
\Drupal::service('config.storage')->write('core.extension', array('module' => array(), 'theme' => array()));
// Collect and set a fixed module list.
$class = get_class($this);
$modules = array();
while ($class) {
if (property_exists($class, 'modules')) {
// Only add the modules, if the $modules property was not inherited.
$rp = new \ReflectionProperty($class, 'modules');
if ($rp->class == $class) {
$modules[$class] = $class::$modules;
}
}
$class = get_parent_class($class);
}
// Modules have been collected in reverse class hierarchy order; modules
// defined by base classes should be sorted first. Then, merge the results
// together.
$modules = array_reverse($modules);
$modules = call_user_func_array('array_merge_recursive', $modules);
if ($modules) {
$this->enableModules($modules);
}
// Tests based on this class are entitled to use Drupal's File and
// StreamWrapper APIs.
// @todo Move StreamWrapper management into DrupalKernel.
// @see https://www.drupal.org/node/2028109
file_prepare_directory($this->publicFilesDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
$this->settingsSet('file_public_path', $this->publicFilesDirectory);
$this->streamWrappers = array();
$this->registerStreamWrapper('public', 'Drupal\Core\StreamWrapper\PublicStream');
// The temporary stream wrapper is able to operate both with and without
// configuration.
$this->registerStreamWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream');
}
/**
* {@inheritdoc}
*/
protected function tearDown() {
if ($this->kernel instanceof DrupalKernel) {
$this->kernel->shutdown();
}
// Before tearing down the test environment, ensure that no stream wrapper
// of this test leaks into the parent environment. Unlike all other global
// state variables in Drupal, stream wrappers are a global state construct
// of PHP core, which has to be maintained manually.
// @todo Move StreamWrapper management into DrupalKernel.
// @see https://www.drupal.org/node/2028109
foreach ($this->streamWrappers as $scheme => $type) {
$this->unregisterStreamWrapper($scheme, $type);
}
parent::tearDown();
}
/**
* Sets up the base service container for this test.
*
* Extend this method in your test to register additional service overrides
* that need to persist a DrupalKernel reboot. This method is called whenever
* the kernel is rebuilt.
*
* @see \Drupal\simpletest\KernelTestBase::setUp()
* @see \Drupal\simpletest\KernelTestBase::enableModules()
* @see \Drupal\simpletest\KernelTestBase::disableModules()
*/
public function containerBuild(ContainerBuilder $container) {
// Keep the container object around for tests.
$this->container = $container;
// Set the default language on the minimal container.
$this->container->setParameter('language.default_values', $this->defaultLanguageData());
$container->register('lock', 'Drupal\Core\Lock\NullLockBackend');
$container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
$container
->register('config.storage', 'Drupal\Core\Config\DatabaseStorage')
->addArgument(Database::getConnection())
->addArgument('config');
if ($this->strictConfigSchema) {
$container
->register('simpletest.config_schema_checker', 'Drupal\Core\Config\Testing\ConfigSchemaChecker')
->addArgument(new Reference('config.typed'))
->addTag('event_subscriber');
}
$keyvalue_options = $container->getParameter('factory.keyvalue') ?: array();
$keyvalue_options['default'] = 'keyvalue.memory';
$container->setParameter('factory.keyvalue', $keyvalue_options);
$container->set('keyvalue.memory', $this->keyValueFactory);
if (!$container->has('keyvalue')) {
// TestBase::setUp puts a completely empty container in
// $this->container which is somewhat the mirror of the empty
// environment being set up. Unit tests need not to waste time with
// getting a container set up for them. Drupal Unit Tests might just get
// away with a simple container holding the absolute bare minimum. When
// a kernel is overridden then there's no need to re-register the keyvalue
// service but when a test is happy with the superminimal container put
// together here, it still might a keyvalue storage for anything using
// \Drupal::state() -- that's why a memory service was added in the first
// place.
$container->register('settings', 'Drupal\Core\Site\Settings')
->setFactoryClass('Drupal\Core\Site\Settings')
->setFactoryMethod('getInstance');
$container
->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory')
->addArgument(new Reference('service_container'))
->addArgument(new Parameter('factory.keyvalue'));
$container->register('state', 'Drupal\Core\State\State')
->addArgument(new Reference('keyvalue'));
}
if ($container->hasDefinition('path_processor_alias')) {
// Prevent the alias-based path processor, which requires a url_alias db
// table, from being registered to the path processor manager. We do this
// by removing the tags that the compiler pass looks for. This means the
// url generator can safely be used within tests.
$definition = $container->getDefinition('path_processor_alias');
$definition->clearTag('path_processor_inbound')->clearTag('path_processor_outbound');
}
if ($container->hasDefinition('password')) {
$container->getDefinition('password')->setArguments(array(1));
}
// Register the stream wrapper manager.
$container
->register('stream_wrapper_manager', 'Drupal\Core\StreamWrapper\StreamWrapperManager')
->addArgument(new Reference('module_handler'))
->addMethodCall('setContainer', array(new Reference('service_container')));
$request = Request::create('/');
$container->get('request_stack')->push($request);
}
/**
* Provides the data for setting the default language on the container.
*
* @return array
* The data array for the default language.
*/
protected function defaultLanguageData() {
return Language::$defaultValues;
}
/**
* Installs default configuration for a given list of modules.
*
* @param array $modules
* A list of modules for which to install default configuration.
*
* @throws \RuntimeException
* Thrown when any module listed in $modules is not enabled.
*/
protected function installConfig(array $modules) {
foreach ($modules as $module) {
if (!$this->container->get('module_handler')->moduleExists($module)) {
throw new \RuntimeException(format_string("'@module' module is not enabled.", array(
'@module' => $module,
)));
}
\Drupal::service('config.installer')->installDefaultConfig('module', $module);
}
$this->pass(format_string('Installed default config: %modules.', array(
'%modules' => implode(', ', $modules),
)));
}
/**
* Installs a specific table from a module schema definition.
*
* @param string $module
* The name of the module that defines the table's schema.
* @param string|array $tables
* The name or an array of the names of the tables to install.
*
* @throws \RuntimeException
* Thrown when $module is not enabled or when the table schema cannot be
* found in the module specified.
*/
protected function installSchema($module, $tables) {
// drupal_get_module_schema() is technically able to install a schema
// of a non-enabled module, but its ability to load the module's .install
// file depends on many other factors. To prevent differences in test
// behavior and non-reproducible test failures, we only allow the schema of
// explicitly loaded/enabled modules to be installed.
if (!$this->container->get('module_handler')->moduleExists($module)) {
throw new \RuntimeException(format_string("'@module' module is not enabled.", array(
'@module' => $module,
)));
}
$tables = (array) $tables;
foreach ($tables as $table) {
$schema = drupal_get_module_schema($module, $table);
if (empty($schema)) {
throw new \RuntimeException(format_string("Unknown '@table' table schema in '@module' module.", array(
'@module' => $module,
'@table' => $table,
)));
}
$this->container->get('database')->schema()->createTable($table, $schema);
}
$this->pass(format_string('Installed %module tables: %tables.', array(
'%tables' => '{' . implode('}, {', $tables) . '}',
'%module' => $module,
)));
}
/**
* Installs the storage schema for a specific entity type.
*
* @param string $entity_type_id
* The ID of the entity type.
*/
protected function installEntitySchema($entity_type_id) {
/** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */
$entity_manager = $this->container->get('entity.manager');
$entity_type = $entity_manager->getDefinition($entity_type_id);
$entity_manager->onEntityTypeCreate($entity_type);
// For test runs, the most common storage backend is a SQL database. For
// this case, ensure the tables got created.
$storage = $entity_manager->getStorage($entity_type_id);
if ($storage instanceof SqlEntityStorageInterface) {
$tables = $storage->getTableMapping()->getTableNames();
$db_schema = $this->container->get('database')->schema();
$all_tables_exist = TRUE;
foreach ($tables as $table) {
if (!$db_schema->tableExists($table)) {
$this->fail(SafeMarkup::format('Installed entity type table for the %entity_type entity type: %table', array(
'%entity_type' => $entity_type_id,
'%table' => $table,
)));
$all_tables_exist = FALSE;
}
}
if ($all_tables_exist) {
$this->pass(SafeMarkup::format('Installed entity type tables for the %entity_type entity type: %tables', array(
'%entity_type' => $entity_type_id,
'%tables' => '{' . implode('}, {', $tables) . '}',
)));
}
}
}
/**
* Enables modules for this test.
*
* @param array $modules
* A list of modules to enable. Dependencies are not resolved; i.e.,
* multiple modules have to be specified with dependent modules first.
* The new modules are only added to the active module list and loaded.
*/
protected function enableModules(array $modules) {
// Perform an ExtensionDiscovery scan as this function may receive a
// profile that is not the current profile, and we don't yet have a cached
// way to receive inactive profile information.
// @todo Remove as part of https://www.drupal.org/node/2186491
$listing = new ExtensionDiscovery(\Drupal::root());
$module_list = $listing->scan('module');
// In ModuleHandlerTest we pass in a profile as if it were a module.
$module_list += $listing->scan('profile');
// Set the list of modules in the extension handler.
$module_handler = $this->container->get('module_handler');
// Write directly to active storage to avoid early instantiation of
// the event dispatcher which can prevent modules from registering events.
$active_storage = \Drupal::service('config.storage');
$extensions = $active_storage->read('core.extension');
foreach ($modules as $module) {
$module_handler->addModule($module, $module_list[$module]->getPath());
// Maintain the list of enabled modules in configuration.
$extensions['module'][$module] = 0;
}
$active_storage->write('core.extension', $extensions);
// Update the kernel to make their services available.
$module_filenames = $module_handler->getModuleList();
$this->kernel->updateModules($module_filenames, $module_filenames);
// Ensure isLoaded() is TRUE in order to make _theme() work.
// Note that the kernel has rebuilt the container; this $module_handler is
// no longer the $module_handler instance from above.
$this->container->get('module_handler')->reload();
$this->pass(format_string('Enabled modules: %modules.', array(
'%modules' => implode(', ', $modules),
)));
}
/**
* Disables modules for this test.
*
* @param array $modules
* A list of modules to disable. Dependencies are not resolved; i.e.,
* multiple modules have to be specified with dependent modules first.
* Code of previously active modules is still loaded. The modules are only
* removed from the active module list.
*/
protected function disableModules(array $modules) {
// Unset the list of modules in the extension handler.
$module_handler = $this->container->get('module_handler');
$module_filenames = $module_handler->getModuleList();
$extension_config = $this->config('core.extension');
foreach ($modules as $module) {
unset($module_filenames[$module]);
$extension_config->clear('module.' . $module);
}
$extension_config->save();
$module_handler->setModuleList($module_filenames);
$module_handler->resetImplementations();
// Update the kernel to remove their services.
$this->kernel->updateModules($module_filenames, $module_filenames);
// Ensure isLoaded() is TRUE in order to make _theme() work.
// Note that the kernel has rebuilt the container; this $module_handler is
// no longer the $module_handler instance from above.
$module_handler = $this->container->get('module_handler');
$module_handler->reload();
$this->pass(format_string('Disabled modules: %modules.', array(
'%modules' => implode(', ', $modules),
)));
}
/**
* Registers a stream wrapper for this test.
*
* @param string $scheme
* The scheme to register.
* @param string $class
* The fully qualified class name to register.
* @param int $type
* The Drupal Stream Wrapper API type. Defaults to
* StreamWrapperInterface::NORMAL.
*/
protected function registerStreamWrapper($scheme, $class, $type = StreamWrapperInterface::NORMAL) {
$this->container->get('stream_wrapper_manager')->registerWrapper($scheme, $class, $type);
}
/**
* Renders a render array.
*
* @param array $elements
* The elements to render.
*
* @return string
* The rendered string output (typically HTML).
*/
protected function render(array &$elements) {
$content = $this->container->get('renderer')->renderRoot($elements);
drupal_process_attached($elements);
$this->setRawContent($content);
$this->verbose('<pre style="white-space: pre-wrap">' . SafeMarkup::checkPlain($content));
return $content;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\SessionTestTrait.
*/
namespace Drupal\simpletest;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides methods to generate and get session name in tests.
*/
trait SessionTestTrait {
/**
* The name of the session cookie.
*
* @var string
*/
protected $sessionName;
/**
* Generates a session cookie name.
*
* @param string $data
* The data to generate session name.
*/
protected function generateSessionName($data) {
$prefix = (Request::createFromGlobals()->isSecure() ? 'SSESS' : 'SESS');
$this->sessionName = $prefix . substr(hash('sha256', $data), 0, 32);
}
/**
* Returns the session name in use on the child site.
*
* @return string
* The name of the session cookie.
*/
protected function getSessionName() {
return $this->sessionName;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,455 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\TestDiscovery.
*/
namespace Drupal\simpletest;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Reflection\StaticReflectionParser;
use Drupal\Component\Annotation\Reflection\MockFileFinder;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\simpletest\Exception\MissingGroupException;
use PHPUnit_Util_Test;
/**
* Discovers available tests.
*/
class TestDiscovery {
/**
* The class loader.
*
* @var \Composer\Autoload\ClassLoader
*/
protected $classLoader;
/**
* Backend for caching discovery results.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheBackend;
/**
* Cached map of all test namespaces to respective directories.
*
* @var array
*/
protected $testNamespaces;
/**
* Cached list of all available extension names, keyed by extension type.
*
* @var array
*/
protected $availableExtensions;
/**
* Constructs a new test discovery.
*
* @param $class_loader
* The class loader. Normally Composer's ClassLoader, as included by the
* front controller, but may also be decorated; e.g.,
* \Symfony\Component\ClassLoader\ApcClassLoader.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* (optional) Backend for caching discovery results.
*/
public function __construct($class_loader, CacheBackendInterface $cache_backend = NULL) {
$this->classLoader = $class_loader;
$this->cacheBackend = $cache_backend;
}
/**
* Registers test namespaces of all available extensions.
*
* @return array
* An associative array whose keys are PSR-4 namespace prefixes and whose
* values are directory names.
*/
public function registerTestNamespaces() {
if (isset($this->testNamespaces)) {
return $this->testNamespaces;
}
$this->testNamespaces = array();
$existing = $this->classLoader->getPrefixesPsr4();
// Add PHPUnit test namespaces of Drupal core.
$this->testNamespaces['Drupal\\Tests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/Tests'];
$this->testNamespaces['Drupal\\FunctionalTests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/FunctionalTests'];
$this->availableExtensions = array();
foreach ($this->getExtensions() as $name => $extension) {
$this->availableExtensions[$extension->getType()][$name] = $name;
$base_path = DRUPAL_ROOT . '/' . $extension->getPath();
// Add namespace of disabled/uninstalled extensions.
if (!isset($existing["Drupal\\$name\\"])) {
$this->classLoader->addPsr4("Drupal\\$name\\", "$base_path/src");
}
// Add Simpletest test namespace.
$this->testNamespaces["Drupal\\$name\\Tests\\"][] = "$base_path/src/Tests";
// Add PHPUnit test namespaces.
$this->testNamespaces["Drupal\\Tests\\$name\\Unit\\"][] = "$base_path/tests/src/Unit";
$this->testNamespaces["Drupal\\Tests\\$name\\Functional\\"][] = "$base_path/tests/src/Functional";
}
foreach ($this->testNamespaces as $prefix => $paths) {
$this->classLoader->addPsr4($prefix, $paths);
}
return $this->testNamespaces;
}
/**
* Discovers all available tests in all extensions.
*
* @param string $extension
* (optional) The name of an extension to limit discovery to; e.g., 'node'.
*
* @return array
* An array of tests keyed by the first @group specified in each test's
* PHPDoc comment block, and then keyed by class names. For example:
* @code
* $groups['block'] => array(
* 'Drupal\block\Tests\BlockTest' => array(
* 'name' => 'Drupal\block\Tests\BlockTest',
* 'description' => 'Tests block UI CRUD functionality.',
* 'group' => 'block',
* ),
* );
* @endcode
*
* @throws \ReflectionException
* If a discovered test class does not match the expected class name.
*
* @todo Remove singular grouping; retain list of groups in 'group' key.
* @see https://www.drupal.org/node/2296615
* @todo Add base class groups 'Kernel' + 'Web', complementing 'PHPUnit'.
*/
public function getTestClasses($extension = NULL) {
$reader = new SimpleAnnotationReader();
$reader->addNamespace('Drupal\\simpletest\\Annotation');
if (!isset($extension)) {
if ($this->cacheBackend && $cache = $this->cacheBackend->get('simpletest:discovery:classes')) {
return $cache->data;
}
}
$list = array();
$classmap = $this->findAllClassFiles($extension);
// Prevent expensive class loader lookups for each reflected test class by
// registering the complete classmap of test classes to the class loader.
// This also ensures that test classes are loaded from the discovered
// pathnames; a namespace/classname mismatch will throw an exception.
$this->classLoader->addClassMap($classmap);
foreach ($classmap as $classname => $pathname) {
$finder = MockFileFinder::create($pathname);
$parser = new StaticReflectionParser($classname, $finder, TRUE);
try {
$info = static::getTestInfo($classname, $parser->getDocComment());
}
catch (MissingGroupException $e) {
// If the class name ends in Test and is not a migrate table dump.
if (preg_match('/Test$/', $classname) && strpos($classname, 'migrate_drupal\Tests\Table') === FALSE) {
throw $e;
}
// If the class is @group annotation just skip it. Most likely it is an
// abstract class, trait or test fixture.
continue;
}
// Skip this test class if it requires unavailable modules.
// @todo PHPUnit skips tests with unmet requirements when executing a test
// (instead of excluding them upfront). Refactor test runner to follow
// that approach.
// @see https://www.drupal.org/node/1273478
if (!empty($info['requires']['module'])) {
if (array_diff($info['requires']['module'], $this->availableExtensions['module'])) {
continue;
}
}
$list[$info['group']][$classname] = $info;
}
// Sort the groups and tests within the groups by name.
uksort($list, 'strnatcasecmp');
foreach ($list as &$tests) {
uksort($tests, 'strnatcasecmp');
}
// Allow modules extending core tests to disable originals.
\Drupal::moduleHandler()->alter('simpletest', $list);
if (!isset($extension)) {
if ($this->cacheBackend) {
$this->cacheBackend->set('simpletest:discovery:classes', $list);
}
}
return $list;
}
/**
* Discovers all class files in all available extensions.
*
* @param string $extension
* (optional) The name of an extension to limit discovery to; e.g., 'node'.
*
* @return array
* A classmap containing all discovered class files; i.e., a map of
* fully-qualified classnames to pathnames.
*/
public function findAllClassFiles($extension = NULL) {
$classmap = array();
$namespaces = $this->registerTestNamespaces();
if (isset($extension)) {
// Include tests in the \Drupal\Tests\{$extension} namespace.
$pattern = "/Drupal\\\(Tests\\\)?$extension\\\/";
$namespaces = array_intersect_key($namespaces, array_flip(preg_grep($pattern, array_keys($namespaces))));
}
foreach ($namespaces as $namespace => $paths) {
foreach ($paths as $path) {
if (!is_dir($path)) {
continue;
}
$classmap += static::scanDirectory($namespace, $path);
}
}
return $classmap;
}
/**
* Scans a given directory for class files.
*
* @param string $namespace_prefix
* The namespace prefix to use for discovered classes. Must contain a
* trailing namespace separator (backslash).
* For example: 'Drupal\\node\\Tests\\'
* @param string $path
* The directory path to scan.
* For example: '/path/to/drupal/core/modules/node/tests/src'
*
* @return array
* An associative array whose keys are fully-qualified class names and whose
* values are corresponding filesystem pathnames.
*
* @throws \InvalidArgumentException
* If $namespace_prefix does not end in a namespace separator (backslash).
*
* @todo Limit to '*Test.php' files (~10% less files to reflect/introspect).
* @see https://www.drupal.org/node/2296635
*/
public static function scanDirectory($namespace_prefix, $path) {
if (substr($namespace_prefix, -1) !== '\\') {
throw new \InvalidArgumentException("Namespace prefix for $path must contain a trailing namespace separator.");
}
$flags = \FilesystemIterator::UNIX_PATHS;
$flags |= \FilesystemIterator::SKIP_DOTS;
$flags |= \FilesystemIterator::FOLLOW_SYMLINKS;
$flags |= \FilesystemIterator::CURRENT_AS_SELF;
$iterator = new \RecursiveDirectoryIterator($path, $flags);
$filter = new \RecursiveCallbackFilterIterator($iterator, function ($current, $key, $iterator) {
if ($iterator->hasChildren()) {
return TRUE;
}
return $current->isFile() && $current->getExtension() === 'php';
});
$files = new \RecursiveIteratorIterator($filter);
$classes = array();
foreach ($files as $fileinfo) {
$class = $namespace_prefix;
if ('' !== $subpath = $fileinfo->getSubPath()) {
$class .= strtr($subpath, '/', '\\') . '\\';
}
$class .= $fileinfo->getBasename('.php');
$classes[$class] = $fileinfo->getPathname();
}
return $classes;
}
/**
* Retrieves information about a test class for UI purposes.
*
* @param string $class
* The test classname.
* @param string $doc_comment
* (optional) The class PHPDoc comment. If not passed in reflection will be
* used but this is very expensive when parsing all the test classes.
*
* @return array
* An associative array containing:
* - name: The test class name.
* - description: The test (PHPDoc) summary.
* - group: The test's first @group (parsed from PHPDoc annotations).
* - requires: An associative array containing test requirements parsed from
* PHPDoc annotations:
* - module: List of Drupal module extension names the test depends on.
*
* @throws \Drupal\simpletest\Exception\MissingGroupException
* If the class does not have a @group annotation.
*/
public static function getTestInfo($classname, $doc_comment = NULL) {
if (!$doc_comment) {
$reflection = new \ReflectionClass($classname);
$doc_comment = $reflection->getDocComment();
}
$info = array(
'name' => $classname,
);
$annotations = array();
// Look for annotations, allow an arbitrary amount of spaces before the
// * but nothing else.
preg_match_all('/^[ ]*\* \@([^\s]*) (.*$)/m', $doc_comment, $matches);
if (isset($matches[1])) {
foreach ($matches[1] as $key => $annotation) {
if (!empty($annotations[$annotation])) {
// Only have the first match per annotation. This deals with
// multiple @group annotations.
continue;
}
$annotations[$annotation] = $matches[2][$key];
}
}
if (empty($annotations['group'])) {
// Concrete tests must have a group.
throw new MissingGroupException(sprintf('Missing @group annotation in %s', $classname));
}
// Force all PHPUnit tests into the same group.
if (static::isUnitTest($classname)) {
$info['group'] = 'PHPUnit';
}
else {
$info['group'] = $annotations['group'];
}
if (!empty($annotations['coversDefaultClass'])) {
$info['description'] = 'Tests ' . $annotations['coversDefaultClass'] . '.';
}
else {
$info['description'] = static::parseTestClassSummary($doc_comment);
}
if (isset($annotations['dependencies'])) {
$info['requires']['module'] = array_map('trim', explode(',', $annotations['dependencies']));
}
return $info;
}
/**
* Parses the phpDoc summary line of a test class.
*
* @param string $doc_comment.
*
* @return string
* The parsed phpDoc summary line. An empty string is returned if no summary
* line can be parsed.
*/
public static function parseTestClassSummary($doc_comment) {
// Normalize line endings.
$doc_comment = preg_replace('/\r\n|\r/', '\n', $doc_comment);
// Strip leading and trailing doc block lines.
$doc_comment = substr($doc_comment, 4, -4);
$lines = explode("\n", $doc_comment);
$summary = [];
// Add every line to the summary until the first empty line or annotation
// is found.
foreach ($lines as $line) {
if (preg_match('/^[ ]*\*$/', $line) || preg_match('/^[ ]*\* \@/', $line)) {
break;
}
$summary[] = trim($line, ' *');
}
return implode(' ', $summary);
}
/**
* Parses annotations in the phpDoc of a test class.
*
* @param \ReflectionClass $class
* The reflected test class.
*
* @return array
* An associative array that contains all annotations on the test class;
* typically including:
* - group: A list of @group values.
* - requires: An associative array of @requires values; e.g.:
* - module: A list of Drupal module dependencies that are required to
* exist.
*
* @see PHPUnit_Util_Test::parseTestMethodAnnotations()
* @see http://phpunit.de/manual/current/en/incomplete-and-skipped-tests.html#incomplete-and-skipped-tests.skipping-tests-using-requires
*/
public static function parseTestClassAnnotations(\ReflectionClass $class) {
$annotations = PHPUnit_Util_Test::parseTestMethodAnnotations($class->getName())['class'];
// @todo Enhance PHPUnit upstream to allow for custom @requires identifiers.
// @see PHPUnit_Util_Test::getRequirements()
// @todo Add support for 'PHP', 'OS', 'function', 'extension'.
// @see https://www.drupal.org/node/1273478
if (isset($annotations['requires'])) {
foreach ($annotations['requires'] as $i => $value) {
list($type, $value) = explode(' ', $value, 2);
if ($type === 'module') {
$annotations['requires']['module'][$value] = $value;
unset($annotations['requires'][$i]);
}
}
}
return $annotations;
}
/**
* Determines if the provided classname is a unit test.
*
* @param $classname
* The test classname.
*
* @return bool
* TRUE if the class is a unit test. FALSE if not.
*/
public static function isUnitTest($classname) {
if (strpos($classname, 'Drupal\\Tests\\') === 0) {
$namespace = explode('\\', $classname);
$first_letter = Unicode::substr($namespace[2], 0, 1);
if (Unicode::strtoupper($first_letter) === $first_letter) {
// A core unit test.
return TRUE;
}
elseif ($namespace[3] == 'Unit') {
// A module unit test.
return TRUE;
}
}
return FALSE;
}
/**
* Returns all available extensions.
*
* @return \Drupal\Core\Extension\Extension[]
* An array of Extension objects, keyed by extension name.
*/
protected function getExtensions() {
$listing = new ExtensionDiscovery(DRUPAL_ROOT);
// Ensure that tests in all profiles are discovered.
$listing->setProfileDirectories(array());
$extensions = $listing->scan('module', TRUE);
$extensions += $listing->scan('profile', TRUE);
$extensions += $listing->scan('theme', TRUE);
return $extensions;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\TestServiceProvider.
*/
namespace Drupal\simpletest;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderInterface;
class TestServiceProvider implements ServiceProviderInterface {
/**
* @var \Drupal\simpletest\TestBase;
*/
public static $currentTest;
/**
* {@inheritdoc}
*/
function register(ContainerBuilder $container) {
if (static::$currentTest && method_exists(static::$currentTest, 'containerBuild')) {
static::$currentTest->containerBuild($container);
}
}
}

View file

@ -0,0 +1,124 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\BrokenSetUpTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests a test case that does not call parent::setUp().
*
* If a test case does not call parent::setUp(), running
* \Drupal\simpletest\WebTestBase::tearDown() would destroy the main site's
* database tables. Therefore, we ensure that tests which are not set up
* properly are skipped.
*
* @group simpletest
* @see \Drupal\simpletest\WebTestBase
*/
class BrokenSetUpTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('simpletest');
/**
* The path to the shared trigger file.
*
* @var string
*/
protected $sharedTriggerFile;
protected function setUp() {
// If the test is being run from the main site, set up normally.
if (!$this->isInChildSite()) {
parent::setUp();
$this->sharedTriggerFile = $this->publicFilesDirectory . '/trigger';
// Create and log in user.
$admin_user = $this->drupalCreateUser(array('administer unit tests'));
$this->drupalLogin($admin_user);
}
// If the test is being run from within simpletest, set up the broken test.
else {
$this->sharedTriggerFile = $this->originalFileDirectory . '/trigger';
if (file_get_contents($this->sharedTriggerFile) === 'setup') {
throw new \Exception('Broken setup');
}
$this->pass('The setUp() method has run.');
}
}
protected function tearDown() {
// If the test is being run from the main site, tear down normally.
if (!$this->isInChildSite()) {
unlink($this->sharedTriggerFile);
parent::tearDown();
}
// If the test is being run from within simpletest, output a message.
else {
if (file_get_contents($this->sharedTriggerFile) === 'teardown') {
throw new \Exception('Broken teardown');
}
$this->pass('The tearDown() method has run.');
}
}
/**
* Runs this test case from within the simpletest child site.
*/
function testMethod() {
// If the test is being run from the main site, run it again from the web
// interface within the simpletest child site.
if (!$this->isInChildSite()) {
// Verify that a broken setUp() method is caught.
file_put_contents($this->sharedTriggerFile, 'setup');
$edit['tests[Drupal\simpletest\Tests\BrokenSetUpTest]'] = TRUE;
$this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
$this->assertRaw('Broken setup');
$this->assertNoRaw('The setUp() method has run.');
$this->assertNoRaw('Broken test');
$this->assertNoRaw('The test method has run.');
$this->assertNoRaw('Broken teardown');
$this->assertNoRaw('The tearDown() method has run.');
// Verify that a broken tearDown() method is caught.
file_put_contents($this->sharedTriggerFile, 'teardown');
$edit['tests[Drupal\simpletest\Tests\BrokenSetUpTest]'] = TRUE;
$this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
$this->assertNoRaw('Broken setup');
$this->assertRaw('The setUp() method has run.');
$this->assertNoRaw('Broken test');
$this->assertRaw('The test method has run.');
$this->assertRaw('Broken teardown');
$this->assertNoRaw('The tearDown() method has run.');
// Verify that a broken test method is caught.
file_put_contents($this->sharedTriggerFile, 'test');
$edit['tests[Drupal\simpletest\Tests\BrokenSetUpTest]'] = TRUE;
$this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
$this->assertNoRaw('Broken setup');
$this->assertRaw('The setUp() method has run.');
$this->assertRaw('Broken test');
$this->assertNoRaw('The test method has run.');
$this->assertNoRaw('Broken teardown');
$this->assertRaw('The tearDown() method has run.');
}
// If the test is being run from within simpletest, output a message.
else {
if (file_get_contents($this->sharedTriggerFile) === 'test') {
throw new \Exception('Broken test');
}
$this->pass('The test method has run.');
}
}
}

View file

@ -0,0 +1,116 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\BrowserTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the internal browser of the testing framework.
*
* @group simpletest
*/
class BrowserTest extends WebTestBase {
/**
* A flag indicating whether a cookie has been set in a test.
*
* @var bool
*/
protected static $cookieSet = FALSE;
/**
* Test \Drupal\simpletest\WebTestBase::getAbsoluteUrl().
*/
function testGetAbsoluteUrl() {
$url = 'user/login';
$this->drupalGet($url);
$absolute = \Drupal::url('user.login', array(), array('absolute' => TRUE));
$this->assertEqual($absolute, $this->url, 'Passed and requested URL are equal.');
$this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), 'Requested and returned absolute URL are equal.');
$this->drupalPostForm(NULL, array(), t('Log in'));
$this->assertEqual($absolute, $this->url, 'Passed and requested URL are equal.');
$this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), 'Requested and returned absolute URL are equal.');
$this->clickLink('Create new account');
$absolute = \Drupal::url('user.register', array(), array('absolute' => TRUE));
$this->assertEqual($absolute, $this->url, 'Passed and requested URL are equal.');
$this->assertEqual($this->url, $this->getAbsoluteUrl($this->url), 'Requested and returned absolute URL are equal.');
}
/**
* Tests XPath escaping.
*/
function testXPathEscaping() {
$testpage = <<< EOF
<html>
<body>
<a href="link1">A "weird" link, just to bother the dumb "XPath 1.0"</a>
<a href="link2">A second "even more weird" link, in memory of George O'Malley</a>
<a href="link3">A \$third$ link, so weird it's worth $1 million</a>
<a href="link4">A fourth link, containing alternative \\1 regex backreferences \\2</a>
</body>
</html>
EOF;
$this->setRawContent($testpage);
// Matches the first link.
$urls = $this->xpath('//a[text()=:text]', array(':text' => 'A "weird" link, just to bother the dumb "XPath 1.0"'));
$this->assertEqual($urls[0]['href'], 'link1', 'Match with quotes.');
$urls = $this->xpath('//a[text()=:text]', array(':text' => 'A second "even more weird" link, in memory of George O\'Malley'));
$this->assertEqual($urls[0]['href'], 'link2', 'Match with mixed single and double quotes.');
$urls = $this->xpath('//a[text()=:text]', array(':text' => 'A $third$ link, so weird it\'s worth $1 million'));
$this->assertEqual($urls[0]['href'], 'link3', 'Match with a regular expression back reference symbol (dollar sign).');
$urls = $this->xpath('//a[text()=:text]', array(':text' => 'A fourth link, containing alternative \\1 regex backreferences \\2'));
$this->assertEqual($urls[0]['href'], 'link4', 'Match with another regular expression back reference symbol (double backslash).');
}
/**
* Tests that cookies set during a request are available for testing.
*/
public function testCookies() {
// Check that the $this->cookies property is populated when a user logs in.
$user = $this->drupalCreateUser();
$edit = ['name' => $user->getUsername(), 'pass' => $user->pass_raw];
$this->drupalPostForm('<front>', $edit, t('Log in'));
$this->assertEqual(count($this->cookies), 1, 'A cookie is set when the user logs in.');
// Check that the name and value of the cookie match the request data.
$cookie_header = $this->drupalGetHeader('set-cookie', TRUE);
// The name and value are located at the start of the string, separated by
// an equals sign and ending in a semicolon.
preg_match('/^([^=]+)=([^;]+)/', $cookie_header, $matches);
$name = $matches[1];
$value = $matches[2];
$this->assertTrue(array_key_exists($name, $this->cookies), 'The cookie name is correct.');
$this->assertEqual($value, $this->cookies[$name]['value'], 'The cookie value is correct.');
// Set a flag indicating that a cookie has been set in this test.
// @see testCookieDoesNotBleed()
static::$cookieSet = TRUE;
}
/**
* Tests that the cookies from a previous test do not bleed into a new test.
*
* @see static::testCookies()
*/
public function testCookieDoesNotBleed() {
// In order for this test to be effective it should always run after the
// testCookies() test.
$this->assertTrue(static::$cookieSet, 'Tests have been executed in the expected order.');
$this->assertEqual(count($this->cookies), 0, 'No cookies are present at the start of a new test.');
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\FolderTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* This test will check SimpleTest's treatment of hook_install during setUp.
* Image module is used for test.
*
* @group simpletest
*/
class FolderTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('image');
function testFolderSetup() {
$directory = file_default_scheme() . '://styles';
$this->assertTrue(file_prepare_directory($directory, FALSE), 'Directory created.');
}
}

View file

@ -0,0 +1,66 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\InstallationProfileModuleTestsTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Verifies that tests bundled with installation profile modules are found.
*
* @group simpletest
*/
class InstallationProfileModuleTestsTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('simpletest');
/**
* An administrative user with permission to adminsiter unit tests.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* Use the Testing profile.
*
* The Testing profile contains drupal_system_listing_compatible_test.test,
* which attempts to:
* - run tests using the Minimal profile (which does not contain the
* drupal_system_listing_compatible_test.module)
* - but still install the drupal_system_listing_compatible_test.module
* contained in the Testing profile.
*
* @see \Drupal\drupal_system_listing_compatible_test\Tests\SystemListingCompatibleTest
*/
protected $profile = 'testing';
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(array('administer unit tests'));
$this->drupalLogin($this->adminUser);
}
/**
* Tests existence of test case located in an installation profile module.
*/
function testInstallationProfileTests() {
$this->drupalGet('admin/config/development/testing');
$this->assertText('Drupal\drupal_system_listing_compatible_test\Tests\SystemListingCompatibleTest');
$edit = array(
'tests[Drupal\drupal_system_listing_compatible_test\Tests\SystemListingCompatibleTest]' => TRUE,
);
$this->drupalPostForm(NULL, $edit, t('Run tests'));
$this->assertText('SystemListingCompatibleTest test executed.');
}
}

View file

@ -0,0 +1,327 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\KernelTestBaseTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\KernelTestBase;
/**
* Tests KernelTestBase functionality.
*
* @group simpletest
*/
class KernelTestBaseTest extends KernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity_test');
/**
* {@inheritdoc}
*/
protected function setUp() {
$php = <<<'EOS'
<?php
# Make sure that the $test_class variable is defined when this file is included.
if ($test_class) {
}
# Define a function to be able to check that this file was loaded with
# function_exists().
if (!function_exists('simpletest_test_stub_settings_function')) {
function simpletest_test_stub_settings_function() {}
}
EOS;
$settings_testing_file = $this->siteDirectory . '/settings.testing.php';
file_put_contents($settings_testing_file, $php);
$original_container = $this->originalContainer;
parent::setUp();
$this->assertNotIdentical(\Drupal::getContainer(), $original_container, 'KernelTestBase test creates a new container.');
}
/**
* Tests expected behavior of setUp().
*/
function testSetUp() {
$modules = array('entity_test');
$table = 'entity_test';
// Verify that specified $modules have been loaded.
$this->assertTrue(function_exists('entity_test_entity_bundle_info'), 'entity_test.module was loaded.');
// Verify that there is a fixed module list.
$this->assertIdentical(array_keys(\Drupal::moduleHandler()->getModuleList()), $modules);
$this->assertIdentical(\Drupal::moduleHandler()->getImplementations('entity_bundle_info'), ['entity_test']);
$this->assertIdentical(\Drupal::moduleHandler()->getImplementations('entity_type_alter'), ['entity_test']);
// Verify that no modules have been installed.
$this->assertFalse(db_table_exists($table), "'$table' database table not found.");
// Verify that the settings.testing.php got taken into account.
$this->assertTrue(function_exists('simpletest_test_stub_settings_function'));
}
/**
* Tests expected load behavior of enableModules().
*/
function testEnableModulesLoad() {
$module = 'field_test';
// Verify that the module does not exist yet.
$this->assertFalse(\Drupal::moduleHandler()->moduleExists($module), "$module module not found.");
$list = array_keys(\Drupal::moduleHandler()->getModuleList());
$this->assertFalse(in_array($module, $list), "$module module not found in the extension handler's module list.");
$list = \Drupal::moduleHandler()->getImplementations('entity_display_build_alter');
$this->assertFalse(in_array($module, $list), "{$module}_entity_display_build_alter() in \Drupal::moduleHandler()->getImplementations() not found.");
// Enable the module.
$this->enableModules(array($module));
// Verify that the module exists.
$this->assertTrue(\Drupal::moduleHandler()->moduleExists($module), "$module module found.");
$list = array_keys(\Drupal::moduleHandler()->getModuleList());
$this->assertTrue(in_array($module, $list), "$module module found in the extension handler's module list.");
$list = \Drupal::moduleHandler()->getImplementations('query_efq_table_prefixing_test_alter');
$this->assertTrue(in_array($module, $list), "{$module}_query_efq_table_prefixing_test_alter() in \Drupal::moduleHandler()->getImplementations() found.");
}
/**
* Tests expected installation behavior of enableModules().
*/
function testEnableModulesInstall() {
$module = 'module_test';
$table = 'module_test';
// Verify that the module does not exist yet.
$this->assertFalse(\Drupal::moduleHandler()->moduleExists($module), "$module module not found.");
$list = array_keys(\Drupal::moduleHandler()->getModuleList());
$this->assertFalse(in_array($module, $list), "$module module not found in the extension handler's module list.");
$list = \Drupal::moduleHandler()->getImplementations('hook_info');
$this->assertFalse(in_array($module, $list), "{$module}_hook_info() in \Drupal::moduleHandler()->getImplementations() not found.");
$this->assertFalse(db_table_exists($table), "'$table' database table not found.");
// Install the module.
\Drupal::service('module_installer')->install(array($module));
// Verify that the enabled module exists.
$this->assertTrue(\Drupal::moduleHandler()->moduleExists($module), "$module module found.");
$list = array_keys(\Drupal::moduleHandler()->getModuleList());
$this->assertTrue(in_array($module, $list), "$module module found in the extension handler's module list.");
$list = \Drupal::moduleHandler()->getImplementations('hook_info');
$this->assertTrue(in_array($module, $list), "{$module}_hook_info() in \Drupal::moduleHandler()->getImplementations() found.");
$this->assertTrue(db_table_exists($table), "'$table' database table found.");
$schema = drupal_get_module_schema($module, $table);
$this->assertTrue($schema, "'$table' table schema found.");
}
/**
* Tests installing modules with DependencyInjection services.
*/
function testEnableModulesInstallContainer() {
// Install Node module.
$this->enableModules(array('user', 'field', 'node'));
$this->installEntitySchema('node', array('node', 'node_field_data'));
// Perform an entity query against node.
$query = \Drupal::entityQuery('node');
// Disable node access checks, since User module is not enabled.
$query->accessCheck(FALSE);
$query->condition('nid', 1);
$query->execute();
$this->pass('Entity field query was executed.');
}
/**
* Tests expected behavior of installSchema().
*/
function testInstallSchema() {
$module = 'entity_test';
$table = 'entity_test_example';
// Verify that we can install a table from the module schema.
$this->installSchema($module, $table);
$this->assertTrue(db_table_exists($table), "'$table' database table found.");
// Verify that the schema is known to Schema API.
$schema = drupal_get_module_schema($module, $table);
$this->assertTrue($schema, "'$table' table schema found.");
// Verify that a unknown table from an enabled module throws an error.
$table = 'unknown_entity_test_table';
try {
$this->installSchema($module, $table);
$this->fail('Exception for non-retrievable schema found.');
}
catch (\Exception $e) {
$this->pass('Exception for non-retrievable schema found.');
}
$this->assertFalse(db_table_exists($table), "'$table' database table not found.");
$schema = drupal_get_module_schema($module, $table);
$this->assertFalse($schema, "'$table' table schema not found.");
// Verify that a table from a unknown module cannot be installed.
$module = 'database_test';
$table = 'test';
try {
$this->installSchema($module, $table);
$this->fail('Exception for non-retrievable schema found.');
}
catch (\Exception $e) {
$this->pass('Exception for non-retrievable schema found.');
}
$this->assertFalse(db_table_exists($table), "'$table' database table not found.");
$schema = drupal_get_module_schema($module, $table);
$this->assertTrue($schema, "'$table' table schema found.");
// Verify that the same table can be installed after enabling the module.
$this->enableModules(array($module));
$this->installSchema($module, $table);
$this->assertTrue(db_table_exists($table), "'$table' database table found.");
$schema = drupal_get_module_schema($module, $table);
$this->assertTrue($schema, "'$table' table schema found.");
}
/**
* Tests expected behavior of installEntitySchema().
*/
function testInstallEntitySchema() {
$entity = 'entity_test';
// The entity_test Entity has a field that depends on the User module.
$this->enableModules(array('user'));
// Verity that the entity schema is created properly.
$this->installEntitySchema($entity);
$this->assertTrue(db_table_exists($entity), "'$entity' database table found.");
}
/**
* Tests expected behavior of installConfig().
*/
function testInstallConfig() {
// The user module has configuration that depends on system.
$this->enableModules(array('system'));
$module = 'user';
// Verify that default config can only be installed for enabled modules.
try {
$this->installConfig(array($module));
$this->fail('Exception for non-enabled module found.');
}
catch (\Exception $e) {
$this->pass('Exception for non-enabled module found.');
}
$this->assertFalse($this->container->get('config.storage')->exists('user.settings'));
// Verify that default config can be installed.
$this->enableModules(array('user'));
$this->installConfig(array('user'));
$this->assertTrue($this->container->get('config.storage')->exists('user.settings'));
$this->assertTrue($this->config('user.settings')->get('register'));
}
/**
* Tests that the module list is retained after enabling/installing/disabling.
*/
function testEnableModulesFixedList() {
// Install system module.
$this->container->get('module_installer')->install(array('system', 'menu_link_content'));
$entity_manager = \Drupal::entityManager();
// entity_test is loaded via $modules; its entity type should exist.
$this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
$this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
// Load some additional modules; entity_test should still exist.
$this->enableModules(array('field', 'text', 'entity_test'));
$this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
$this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
// Install some other modules; entity_test should still exist.
$this->container->get('module_installer')->install(array('user', 'field', 'field_test'), FALSE);
$this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
$this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
// Uninstall one of those modules; entity_test should still exist.
$this->container->get('module_installer')->uninstall(array('field_test'));
$this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
$this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
// Set the weight of a module; entity_test should still exist.
module_set_weight('field', -1);
$this->assertEqual($this->container->get('module_handler')->moduleExists('entity_test'), TRUE);
$this->assertTrue(TRUE == $entity_manager->getDefinition('entity_test'));
// Reactivate the previously uninstalled module.
$this->enableModules(array('field_test'));
// Create a field.
entity_create('entity_view_display', array(
'targetEntityType' => 'entity_test',
'bundle' => 'entity_test',
'mode' => 'default',
));
$field_storage = entity_create('field_storage_config', array(
'field_name' => 'test_field',
'entity_type' => 'entity_test',
'type' => 'test_field'
));
$field_storage->save();
entity_create('field_config', array(
'field_storage' => $field_storage,
'bundle' => 'entity_test',
))->save();
}
/**
* Tests that _theme() works right after loading a module.
*/
function testEnableModulesTheme() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = $this->container->get('renderer');
$original_element = $element = array(
'#type' => 'container',
'#markup' => 'Foo',
'#attributes' => array(),
);
$this->enableModules(array('system'));
// _theme() throws an exception if modules are not loaded yet.
$this->assertTrue($renderer->renderRoot($element));
$element = $original_element;
$this->disableModules(array('entity_test'));
$this->assertTrue($renderer->renderRoot($element));
}
/**
* Tests that there is no theme by default.
*/
function testNoThemeByDefault() {
$themes = $this->config('core.extension')->get('theme');
$this->assertEqual($themes, array());
$extensions = $this->container->get('config.storage')->read('core.extension');
$this->assertEqual($extensions['theme'], array());
$active_theme = $this->container->get('theme.manager')->getActiveTheme();
$this->assertEqual($active_theme->getName(), 'core');
}
/**
* Tests that drupal_get_profile() returns NULL.
*
* As the currently active installation profile is used when installing
* configuration, for example, this is essential to ensure test isolation.
*/
public function testDrupalGetProfile() {
$this->assertNull(drupal_get_profile());
}
}

View file

@ -0,0 +1,82 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\MailCaptureTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests the SimpleTest email capturing logic, the assertMail assertion and the
* drupalGetMails function.
*
* @group simpletest
*/
class MailCaptureTest extends WebTestBase {
/**
* Test to see if the wrapper function is executed correctly.
*/
function testMailSend() {
// Create an email.
$subject = $this->randomString(64);
$body = $this->randomString(128);
$message = array(
'id' => 'drupal_mail_test',
'headers' => array('Content-type'=> 'text/html'),
'subject' => $subject,
'to' => 'foobar@example.com',
'body' => $body,
);
// Before we send the email, drupalGetMails should return an empty array.
$captured_emails = $this->drupalGetMails();
$this->assertEqual(count($captured_emails), 0, 'The captured emails queue is empty.', 'Email');
// Send the email.
\Drupal::service('plugin.manager.mail')->getInstance(array('module' => 'simpletest', 'key' => 'drupal_mail_test'))->mail($message);
// Ensure that there is one email in the captured emails array.
$captured_emails = $this->drupalGetMails();
$this->assertEqual(count($captured_emails), 1, 'One email was captured.', 'Email');
// Assert that the email was sent by iterating over the message properties
// and ensuring that they are captured intact.
foreach ($message as $field => $value) {
$this->assertMail($field, $value, format_string('The email was sent and the value for property @field is intact.', array('@field' => $field)), 'Email');
}
// Send additional emails so more than one email is captured.
for ($index = 0; $index < 5; $index++) {
$message = array(
'id' => 'drupal_mail_test_' . $index,
'headers' => array('Content-type'=> 'text/html'),
'subject' => $this->randomString(64),
'to' => $this->randomMachineName(32) . '@example.com',
'body' => $this->randomString(512),
);
\Drupal::service('plugin.manager.mail')->getInstance(array('module' => 'drupal_mail_test', 'key' => $index))->mail($message);
}
// There should now be 6 emails captured.
$captured_emails = $this->drupalGetMails();
$this->assertEqual(count($captured_emails), 6, 'All emails were captured.', 'Email');
// Test different ways of getting filtered emails via drupalGetMails().
$captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test'));
$this->assertEqual(count($captured_emails), 1, 'Only one email is returned when filtering by id.', 'Email');
$captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test', 'subject' => $subject));
$this->assertEqual(count($captured_emails), 1, 'Only one email is returned when filtering by id and subject.', 'Email');
$captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test', 'subject' => $subject, 'from' => 'this_was_not_used@example.com'));
$this->assertEqual(count($captured_emails), 0, 'No emails are returned when querying with an unused from address.', 'Email');
// Send the last email again, so we can confirm that the
// drupalGetMails-filter correctly returns all emails with a given
// property/value.
\Drupal::service('plugin.manager.mail')->getInstance(array('module' => 'drupal_mail_test', 'key' => $index))->mail($message);
$captured_emails = $this->drupalGetMails(array('id' => 'drupal_mail_test_4'));
$this->assertEqual(count($captured_emails), 2, 'All emails with the same id are returned when filtering by id.', 'Email');
}
}

View file

@ -0,0 +1,61 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\MissingCheckedRequirementsTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests a test case with missing requirements.
*
* @group simpletest
*/
class MissingCheckedRequirementsTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('simpletest');
protected function setUp() {
parent::setUp();
$admin_user = $this->drupalCreateUser(array('administer unit tests'));
$this->drupalLogin($admin_user);
}
/**
* Overrides checkRequirements().
*/
protected function checkRequirements() {
if ($this->isInChildSite()) {
return array(
'Test is not allowed to run.'
);
}
return parent::checkRequirements();
}
/**
* Ensures test will not run when requirements are missing.
*/
public function testCheckRequirements() {
// If this is the main request, run the web test script and then assert
// that the child tests did not run.
if (!$this->isInChildSite()) {
// Run this test from web interface.
$edit['tests[Drupal\simpletest\Tests\MissingCheckedRequirementsTest]'] = TRUE;
$this->drupalPostForm('admin/config/development/testing', $edit, t('Run tests'));
$this->assertRaw('Test is not allowed to run.', 'Test check for requirements came up.');
$this->assertNoText('Test ran when it failed requirements check.', 'Test requirements stopped test from running.');
}
else {
$this->fail('Test ran when it failed requirements check.');
}
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\MissingDependentModuleUnitTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\KernelTestBase;
/**
* This test should not load since it requires a module that is not found.
*
* @group simpletest
* @dependencies simpletest_missing_module
*/
class MissingDependentModuleUnitTest extends KernelTestBase {
/**
* Ensure that this test will not be loaded despite its dependency.
*/
function testFail() {
$this->fail('Running test with missing required module.');
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\OtherInstallationProfileTestsTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Verifies that tests in other installation profiles are found.
*
* @group simpletest
* @see SimpleTestInstallationProfileModuleTestsTestCase
*/
class OtherInstallationProfileTestsTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('simpletest');
/**
* Use the Minimal profile.
*
* The Testing profile contains drupal_system_listing_compatible_test.test,
* which should be found.
*
* The Standard profile contains \Drupal\standard\Tests\StandardTest, which
* should be found.
*
* @see \Drupal\simpletest\Tests\InstallationProfileModuleTestsTest
* @see \Drupal\drupal_system_listing_compatible_test\Tests\SystemListingCompatibleTest
*/
protected $profile = 'minimal';
/**
* An administrative user with permission to administer unit tests.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(array('administer unit tests'));
$this->drupalLogin($this->adminUser);
}
/**
* Tests that tests located in another installation profile appear.
*/
function testOtherInstallationProfile() {
// Assert the existence of a test in a different installation profile than
// the current.
$this->drupalGet('admin/config/development/testing');
$this->assertText('Tests Standard installation profile expectations.');
// Assert the existence of a test for a module in a different installation
// profile than the current.
$this->assertText('Drupal\drupal_system_listing_compatible_test\Tests\SystemListingCompatibleTest');
}
}

View file

@ -0,0 +1,150 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\SimpleTestBrowserTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
/**
* Tests the Simpletest UI internal browser.
*
* @group simpletest
*/
class SimpleTestBrowserTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('simpletest', 'test_page_test');
public function setUp() {
parent::setUp();
// Create and log in an admin user.
$this->drupalLogin($this->drupalCreateUser(array('administer unit tests')));
}
/**
* Test the internal browsers functionality.
*/
public function testInternalBrowser() {
// Retrieve the test page and check its title and headers.
$this->drupalGet('test-page');
$this->assertTrue($this->drupalGetHeader('Date'), 'An HTTP header was received.');
$this->assertTitle(t('Test page | @site-name', array(
'@site-name' => $this->config('system.site')->get('name'),
)));
$this->assertNoTitle('Foo');
$old_user_id = $this->container->get('current_user')->id();
$user = $this->drupalCreateUser();
$this->drupalLogin($user);
// Check that current user service updated.
$this->assertNotEqual($old_user_id, $this->container->get('current_user')->id(), 'Current user service updated.');
$headers = $this->drupalGetHeaders(TRUE);
$this->assertEqual(count($headers), 2, 'There was one intermediate request.');
$this->assertTrue(strpos($headers[0][':status'], '303') !== FALSE, 'Intermediate response code was 303.');
$this->assertFalse(empty($headers[0]['location']), 'Intermediate request contained a Location header.');
$this->assertEqual($this->getUrl(), $headers[0]['location'], 'HTTP redirect was followed');
$this->assertFalse($this->drupalGetHeader('Location'), 'Headers from intermediate request were reset.');
$this->assertResponse(200, 'Response code from intermediate request was reset.');
$this->drupalLogout();
// Check that current user service updated to anonymous user.
$this->assertEqual(0, $this->container->get('current_user')->id(), 'Current user service updated.');
// Test the maximum redirection option.
$this->maximumRedirects = 1;
$edit = array(
'name' => $user->getUsername(),
'pass' => $user->pass_raw
);
$this->drupalPostForm('user/login', $edit, t('Log in'), array(
'query' => array('destination' => 'user/logout'),
));
$headers = $this->drupalGetHeaders(TRUE);
$this->assertEqual(count($headers), 2, 'Simpletest stopped following redirects after the first one.');
// Remove the Simpletest private key file so we can test the protection
// against requests that forge a valid testing user agent to gain access
// to the installer.
// @see drupal_valid_test_ua()
// Not using File API; a potential error must trigger a PHP warning.
unlink($this->siteDirectory . '/.htkey');
$this->drupalGet(Url::fromUri('base:core/install.php', array('external' => TRUE, 'absolute' => TRUE))->toString());
$this->assertResponse(403, 'Cannot access install.php.');
}
/**
* Test validation of the User-Agent header we use to perform test requests.
*/
public function testUserAgentValidation() {
global $base_url;
// Logout the user which was logged in during test-setup.
$this->drupalLogout();
$system_path = $base_url . '/' . drupal_get_path('module', 'system');
$HTTP_path = $system_path .'/tests/http.php/user/login';
$https_path = $system_path .'/tests/https.php/user/login';
// Generate a valid simpletest User-Agent to pass validation.
$this->assertTrue(preg_match('/simpletest\d+/', $this->databasePrefix, $matches), 'Database prefix contains simpletest prefix.');
$test_ua = drupal_generate_test_ua($matches[0]);
$this->additionalCurlOptions = array(CURLOPT_USERAGENT => $test_ua);
// Test pages only available for testing.
$this->drupalGet($HTTP_path);
$this->assertResponse(200, 'Requesting http.php with a legitimate simpletest User-Agent returns OK.');
$this->drupalGet($https_path);
$this->assertResponse(200, 'Requesting https.php with a legitimate simpletest User-Agent returns OK.');
// Now slightly modify the HMAC on the header, which should not validate.
$this->additionalCurlOptions = array(CURLOPT_USERAGENT => $test_ua . 'X');
$this->drupalGet($HTTP_path);
$this->assertResponse(403, 'Requesting http.php with a bad simpletest User-Agent fails.');
$this->drupalGet($https_path);
$this->assertResponse(403, 'Requesting https.php with a bad simpletest User-Agent fails.');
// Use a real User-Agent and verify that the special files http.php and
// https.php can't be accessed.
$this->additionalCurlOptions = array(CURLOPT_USERAGENT => 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.12) Gecko/20101026 Firefox/3.6.12');
$this->drupalGet($HTTP_path);
$this->assertResponse(403, 'Requesting http.php with a normal User-Agent fails.');
$this->drupalGet($https_path);
$this->assertResponse(403, 'Requesting https.php with a normal User-Agent fails.');
}
/**
* Tests that PHPUnit and KernelTestBase tests work through the UI.
*/
public function testTestingThroughUI() {
// We can not test WebTestBase tests here since they require a valid .htkey
// to be created. However this scenario is covered by the testception of
// \Drupal\simpletest\Tests\SimpleTestTest.
$tests = array(
// A KernelTestBase test.
'Drupal\system\Tests\DrupalKernel\DrupalKernelTest',
// A PHPUnit unit test.
'Drupal\Tests\action\Unit\Menu\ActionLocalTasksTest',
// A PHPUnit functional test.
'Drupal\Tests\simpletest\Functional\BrowserTestBaseTest',
);
foreach ($tests as $test) {
$this->drupalGet('admin/config/development/testing');
$edit = array(
"tests[$test]" => TRUE,
);
$this->drupalPostForm(NULL, $edit, t('Run tests'));
$this->assertText('0 fails, 0 exceptions');
}
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\SimpleTestInstallBatchTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests batch operations during tests execution.
*
* This demonstrates that a batch will be successfully executed during module
* installation when running tests.
*
* @group simpletest
*/
class SimpleTestInstallBatchTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('simpletest', 'simpletest_test', 'entity_test');
/**
* Tests loading entities created in a batch in simpletest_test_install().
*/
public function testLoadingEntitiesCreatedInBatch() {
$entity1 = entity_load('entity_test', 1);
$this->assertNotNull($entity1, 'Successfully loaded entity 1.');
$entity2 = entity_load('entity_test', 2);
$this->assertNotNull($entity2, 'Successfully loaded entity 2.');
}
}

View file

@ -0,0 +1,356 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\SimpleTestTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\Component\Utility\Crypt;
use Drupal\simpletest\WebTestBase;
/**
* Tests SimpleTest's web interface: check that the intended tests were run and
* ensure that test reports display the intended results. Also test SimpleTest's
* internal browser and APIs implicitly.
*
* @group simpletest
*/
class SimpleTestTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['simpletest'];
/**
* The results array that has been parsed by getTestResults().
*
* @var array
*/
protected $childTestResults;
/**
* Stores the test ID from each test run for comparison.
*
* Used to ensure they are incrementing.
*/
protected $testIds = array();
/**
* Translated fail message.
*
* @var string
*/
private $failMessage = '';
/**
* Translated pass message.
* @var string
*/
private $passMessage = '';
/**
* A valid and recognized permission.
*
* @var string
*/
protected $validPermission;
/**
* An invalid or unrecognized permission.
*
* @var string
*/
protected $invalidPermission;
protected function setUp() {
if (!$this->isInChildSite()) {
$php = <<<'EOD'
<?php
# Make sure that the $test_class variable is defined when this file is included.
if ($test_class) {
}
# Define a function to be able to check that this file was loaded with
# function_exists().
if (!function_exists('simpletest_test_stub_settings_function')) {
function simpletest_test_stub_settings_function() {}
}
EOD;
file_put_contents($this->siteDirectory. '/' . 'settings.testing.php', $php);
// @see \Drupal\system\Tests\DrupalKernel\DrupalKernelSiteTest
$class = __CLASS__;
$yaml = <<<EOD
services:
# Add a new service.
site.service.yml:
class: $class
# Swap out a core service.
cache.backend.database:
class: Drupal\Core\Cache\MemoryBackendFactory
EOD;
file_put_contents($this->siteDirectory . '/testing.services.yml', $yaml);
$original_container = $this->originalContainer;
parent::setUp();
$this->assertNotIdentical(\Drupal::getContainer(), $original_container, 'WebTestBase test creates a new container.');
// Create and log in an admin user.
$this->drupalLogin($this->drupalCreateUser(array('administer unit tests')));
}
else {
// This causes three of the five fails that are asserted in
// confirmStubResults().
self::$modules = array('non_existent_module');
parent::setUp();
}
}
/**
* Ensures the tests selected through the web interface are run and displayed.
*/
function testWebTestRunner() {
$this->passMessage = t('SimpleTest pass.');
$this->failMessage = t('SimpleTest fail.');
$this->validPermission = 'access administration pages';
$this->invalidPermission = 'invalid permission';
if ($this->isInChildSite()) {
// Only run following code if this test is running itself through a CURL
// request.
$this->stubTest();
}
else {
// Run twice so test_ids can be accumulated.
for ($i = 0; $i < 2; $i++) {
// Run this test from web interface.
$this->drupalGet('admin/config/development/testing');
$edit = array();
$edit['tests[Drupal\simpletest\Tests\SimpleTestTest]'] = TRUE;
$this->drupalPostForm(NULL, $edit, t('Run tests'));
// Parse results and confirm that they are correct.
$this->getTestResults();
$this->confirmStubTestResults();
}
// Regression test for #290316.
// Check that test_id is incrementing.
$this->assertTrue($this->testIds[0] != $this->testIds[1], 'Test ID is incrementing.');
}
}
/**
* Test to be run and the results confirmed.
*
* Here we force test results which must match the expected results from
* confirmStubResults().
*/
function stubTest() {
// Ensure the .htkey file exists since this is only created just before a
// request. This allows the stub test to make requests. The event does not
// fire here and drupal_generate_test_ua() can not generate a key for a
// test in a test since the prefix has changed.
// @see \Drupal\Core\Test\EventSubscriber\HttpRequestSubscriber::onBeforeSendRequest()
// @see drupal_generate_test_ua();
$key_file = DRUPAL_ROOT . '/sites/simpletest/' . substr($this->databasePrefix, 10) . '/.htkey';
$private_key = Crypt::randomBytesBase64(55);
$site_path = $this->container->get('site.path');
file_put_contents($key_file, $private_key);
// This causes the first of the fifteen passes asserted in
// confirmStubResults().
$this->pass($this->passMessage);
// The first three fails are caused by enabling a non-existent module in
// setUp().
// This causes the fourth of the five fails asserted in
// confirmStubResults().
$this->fail($this->failMessage);
// This causes the second to fourth of the fifteen passes asserted in
// confirmStubResults().
$user = $this->drupalCreateUser(array($this->validPermission), 'SimpleTestTest');
// This causes the fifth of the five fails asserted in confirmStubResults().
$this->drupalCreateUser(array($this->invalidPermission));
// Test logging in as a user.
// This causes the fifth to ninth of the fifteen passes asserted in
// confirmStubResults().
$this->drupalLogin($user);
// This causes the tenth of the fifteen passes asserted in
// confirmStubResults().
$this->pass(t('Test ID is @id.', array('@id' => $this->testId)));
// These cause the eleventh to fourteenth of the fifteen passes asserted in
// confirmStubResults().
$this->assertTrue(file_exists($site_path . '/settings.testing.php'));
// Check the settings.testing.php file got included.
$this->assertTrue(function_exists('simpletest_test_stub_settings_function'));
// Check that the test-specific service file got loaded.
$this->assertTrue($this->container->has('site.service.yml'));
$this->assertIdentical(get_class($this->container->get('cache.backend.database')), 'Drupal\Core\Cache\MemoryBackendFactory');
// These cause the two exceptions asserted in confirmStubResults().
// Call trigger_error() without the required argument to trigger an E_WARNING.
trigger_error();
// Generates a warning inside a PHP function.
array_key_exists(NULL, NULL);
// This causes the fifteenth of the fifteen passes asserted in
// confirmStubResults().
$this->assertNothing();
// This causes the debug message asserted in confirmStubResults().
debug('Foo', 'Debug', FALSE);
}
/**
* Assert nothing.
*/
function assertNothing() {
$this->pass("This is nothing.");
}
/**
* Confirm that the stub test produced the desired results.
*/
function confirmStubTestResults() {
$this->assertAssertion(t('Unable to install modules %modules due to missing modules %missing.', array('%modules' => 'non_existent_module', '%missing' => 'non_existent_module')), 'Other', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->setUp()');
$this->assertAssertion($this->passMessage, 'Other', 'Pass', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
$this->assertAssertion($this->failMessage, 'Other', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
$this->assertAssertion(t('Created permissions: @perms', array('@perms' => $this->validPermission)), 'Role', 'Pass', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
$this->assertAssertion(t('Invalid permission %permission.', array('%permission' => $this->invalidPermission)), 'Role', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
// Check that the user was logged in successfully.
$this->assertAssertion('User SimpleTestTest successfully logged in.', 'User login', 'Pass', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
// Check that a warning is caught by simpletest. The exact error message
// differs between PHP versions so only the function name is checked.
$this->assertAssertion('trigger_error()', 'Warning', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
// Check that the backtracing code works for specific assert function.
$this->assertAssertion('This is nothing.', 'Other', 'Pass', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
// Check that errors that occur inside PHP internal functions are correctly
// reported. The exact error message differs between PHP versions so we
// check only the function name 'array_key_exists'.
$this->assertAssertion('array_key_exists', 'Warning', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
$this->assertAssertion("Debug: 'Foo'", 'Debug', 'Fail', 'SimpleTestTest.php', 'Drupal\simpletest\Tests\SimpleTestTest->stubTest()');
$this->assertEqual('15 passes, 3 fails, 2 exceptions, 3 debug messages', $this->childTestResults['summary']);
$this->testIds[] = $test_id = $this->getTestIdFromResults();
$this->assertTrue($test_id, 'Found test ID in results.');
}
/**
* Fetch the test id from the test results.
*/
function getTestIdFromResults() {
foreach ($this->childTestResults['assertions'] as $assertion) {
if (preg_match('@^Test ID is ([0-9]*)\.$@', $assertion['message'], $matches)) {
return $matches[1];
}
}
return NULL;
}
/**
* Asserts that an assertion with specified values is displayed in results.
*
* @param string $message Assertion message.
* @param string $type Assertion type.
* @param string $status Assertion status.
* @param string $file File where the assertion originated.
* @param string $function Function where the assertion originated.
*
* @return Assertion result.
*/
function assertAssertion($message, $type, $status, $file, $function) {
$message = trim(strip_tags($message));
$found = FALSE;
foreach ($this->childTestResults['assertions'] as $assertion) {
if ((strpos($assertion['message'], $message) !== FALSE) &&
$assertion['type'] == $type &&
$assertion['status'] == $status &&
$assertion['file'] == $file &&
$assertion['function'] == $function) {
$found = TRUE;
break;
}
}
return $this->assertTrue($found, format_string('Found assertion {"@message", "@type", "@status", "@file", "@function"}.', array('@message' => $message, '@type' => $type, '@status' => $status, "@file" => $file, "@function" => $function)));
}
/**
* Get the results from a test and store them in the class array $results.
*/
function getTestResults() {
$results = array();
if ($this->parse()) {
if ($details = $this->getResultFieldSet()) {
// Code assumes this is the only test in group.
$results['summary'] = $this->asText($details->div->div[1]);
$results['name'] = $this->asText($details->summary);
$results['assertions'] = array();
$tbody = $details->div->table->tbody;
foreach ($tbody->tr as $row) {
$assertion = array();
$assertion['message'] = $this->asText($row->td[0]);
$assertion['type'] = $this->asText($row->td[1]);
$assertion['file'] = $this->asText($row->td[2]);
$assertion['line'] = $this->asText($row->td[3]);
$assertion['function'] = $this->asText($row->td[4]);
$ok_url = file_create_url('core/misc/icons/73b355/check.svg');
$assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail';
$results['assertions'][] = $assertion;
}
}
}
$this->childTestResults = $results;
}
/**
* Get the details containing the results for group this test is in.
*/
function getResultFieldSet() {
$all_details = $this->xpath('//details');
foreach ($all_details as $details) {
if ($this->asText($details->summary) == __CLASS__) {
return $details;
}
}
return FALSE;
}
/**
* Extract the text contained by the element.
*
* @param $element
* Element to extract text from.
*
* @return
* Extracted text.
*/
function asText(\SimpleXMLElement $element) {
if (!is_object($element)) {
return $this->fail('The element is not an element.');
}
return trim(html_entity_decode(strip_tags($element->asXML())));
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\UserHelpersTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests User related helper methods of WebTestBase.
*
* @group simpletest
*/
class UserHelpersTest extends WebTestBase {
/**
* Tests WebTestBase::drupalUserIsLoggedIn().
*/
function testDrupalUserIsLoggedIn() {
$first_user = $this->drupalCreateUser();
$second_user = $this->drupalCreateUser();
// After logging in, the first user should be logged in, the second not.
$this->drupalLogin($first_user);
$this->assertTrue($this->drupalUserIsLoggedIn($first_user));
$this->assertFalse($this->drupalUserIsLoggedIn($second_user));
// Verify that logged in state is retained across pages.
$this->drupalGet('');
$this->assertTrue($this->drupalUserIsLoggedIn($first_user));
$this->assertFalse($this->drupalUserIsLoggedIn($second_user));
// After logging out, both users should be logged out.
$this->drupalLogout();
$this->assertFalse($this->drupalUserIsLoggedIn($first_user));
$this->assertFalse($this->drupalUserIsLoggedIn($second_user));
// After logging back in, the second user should still be logged out.
$this->drupalLogin($first_user);
$this->assertTrue($this->drupalUserIsLoggedIn($first_user));
$this->assertFalse($this->drupalUserIsLoggedIn($second_user));
// After logging in the second user, the first one should be logged out.
$this->drupalLogin($second_user);
$this->assertTrue($this->drupalUserIsLoggedIn($second_user));
$this->assertFalse($this->drupalUserIsLoggedIn($first_user));
// After logging out, both should be logged out.
$this->drupalLogout();
$this->assertFalse($this->drupalUserIsLoggedIn($first_user));
$this->assertFalse($this->drupalUserIsLoggedIn($second_user));
}
}

View file

@ -0,0 +1,217 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\UserCreationTrait.
*/
namespace Drupal\simpletest;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Session\AccountInterface;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\user\RoleInterface;
/**
* Provides methods to create additional test users and switch the currently
* logged in one.
*
* This trait is meant to be used only by test classes extending
* \Drupal\simpletest\TestBase.
*/
trait UserCreationTrait {
/**
* Switch the current logged in user.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The user account object.
*/
protected function setCurrentUser(AccountInterface $account) {
\Drupal::currentUser()->setAccount($account);
}
/**
* Create a user with a given set of permissions.
*
* @param array $permissions
* Array of permission names to assign to user. Note that the user always
* has the default permissions derived from the "authenticated users" role.
* @param string $name
* The user name.
* @param bool $admin
* (optional) Whether the user should be an administrator
* with all the available permissions.
*
* @return \Drupal\user\Entity\User|false
* A fully loaded user object with pass_raw property, or FALSE if account
* creation fails.
*/
protected function createUser(array $permissions = array(), $name = NULL, $admin = FALSE) {
// Create a role with the given permission set, if any.
$rid = FALSE;
if ($permissions) {
$rid = $this->createRole($permissions);
if (!$rid) {
return FALSE;
}
}
// Create a user assigned to that role.
$edit = array();
$edit['name'] = !empty($name) ? $name : $this->randomMachineName();
$edit['mail'] = $edit['name'] . '@example.com';
$edit['pass'] = user_password();
$edit['status'] = 1;
if ($rid) {
$edit['roles'] = array($rid);
}
if ($admin) {
$edit['roles'][] = $this->createAdminRole();
}
$account = User::create($edit);
$account->save();
$this->assertTrue($account->id(), SafeMarkup::format('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), 'User login');
if (!$account->id()) {
return FALSE;
}
// Add the raw password so that we can log in as this user.
$account->pass_raw = $edit['pass'];
return $account;
}
/**
* Creates an administrative role.
*
* @param string $rid
* (optional) The role ID (machine name). Defaults to a random name.
* @param string $name
* (optional) The label for the role. Defaults to a random string.
* @param integer $weight
* (optional) The weight for the role. Defaults NULL so that entity_create()
* sets the weight to maximum + 1.
*
* @return string
* Role ID of newly created role, or FALSE if role creation failed.
*/
protected function createAdminRole($rid = NULL, $name = NULL, $weight = NULL) {
$rid = $this->createRole([], $rid, $name, $weight);
if ($rid) {
/** @var \Drupal\user\RoleInterface $role */
$role = Role::load($rid);
$role->setIsAdmin(TRUE);
$role->save();
}
return $rid;
}
/**
* Creates a role with specified permissions.
*
* @param array $permissions
* Array of permission names to assign to role.
* @param string $rid
* (optional) The role ID (machine name). Defaults to a random name.
* @param string $name
* (optional) The label for the role. Defaults to a random string.
* @param integer $weight
* (optional) The weight for the role. Defaults NULL so that entity_create()
* sets the weight to maximum + 1.
*
* @return string
* Role ID of newly created role, or FALSE if role creation failed.
*/
protected function createRole(array $permissions, $rid = NULL, $name = NULL, $weight = NULL) {
// Generate a random, lowercase machine name if none was passed.
if (!isset($rid)) {
$rid = strtolower($this->randomMachineName(8));
}
// Generate a random label.
if (!isset($name)) {
// In the role UI role names are trimmed and random string can start or
// end with a space.
$name = trim($this->randomString(8));
}
// Check the all the permissions strings are valid.
if (!$this->checkPermissions($permissions)) {
return FALSE;
}
// Create new role.
$role = Role::create(array(
'id' => $rid,
'label' => $name,
));
if (isset($weight)) {
$role->set('weight', $weight);
}
$result = $role->save();
$this->assertIdentical($result, SAVED_NEW, SafeMarkup::format('Created role ID @rid with name @name.', array(
'@name' => var_export($role->label(), TRUE),
'@rid' => var_export($role->id(), TRUE),
)), 'Role');
if ($result === SAVED_NEW) {
// Grant the specified permissions to the role, if any.
if (!empty($permissions)) {
$this->grantPermissions($role, $permissions);
$assigned_permissions = Role::load($role->id())->getPermissions();
$missing_permissions = array_diff($permissions, $assigned_permissions);
if (!$missing_permissions) {
$this->pass(SafeMarkup::format('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), 'Role');
}
else {
$this->fail(SafeMarkup::format('Failed to create permissions: @perms', array('@perms' => implode(', ', $missing_permissions))), 'Role');
}
}
return $role->id();
}
else {
return FALSE;
}
}
/**
* Checks whether a given list of permission names is valid.
*
* @param array $permissions
* The permission names to check.
*
* @return bool
* TRUE if the permissions are valid, FALSE otherwise.
*/
protected function checkPermissions(array $permissions) {
$available = array_keys(\Drupal::service('user.permissions')->getPermissions());
$valid = TRUE;
foreach ($permissions as $permission) {
if (!in_array($permission, $available)) {
$this->fail(SafeMarkup::format('Invalid permission %permission.', array('%permission' => $permission)), 'Role');
$valid = FALSE;
}
}
return $valid;
}
/**
* Grant permissions to a user role.
*
* @param \Drupal\user\RoleInterface $role
* The ID of a user role to alter.
* @param array $permissions
* (optional) A list of permission names to grant.
*/
protected function grantPermissions(RoleInterface $role, array $permissions) {
foreach ($permissions as $permission) {
$role->grantPermission($permission);
}
$role->trustData()->save();
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\WebAssert.
*/
namespace Drupal\simpletest;
use Behat\Mink\WebAssert as MinkWebAssert;
use Behat\Mink\Element\TraversableElement;
use Behat\Mink\Exception\ElementNotFoundException;
/**
* Defines a class with methods for asserting presence of elements during tests.
*/
class WebAssert extends MinkWebAssert {
/**
* Checks that specific button exists on the current page.
*
* @param string $button
* One of id|name|label|value for the button.
* @param \Behat\Mink\Element\TraversableElement $container
* (optional) The document to check against. Defaults to the current page.
*
* @return \Behat\Mink\Element\NodeElement
* The matching element.
*
* @throws \Behat\Mink\Exception\ElementNotFoundException
* When the element doesn't exist.
*/
public function buttonExists($button, TraversableElement $container = NULL) {
$container = $container ?: $this->session->getPage();
$node = $container->findButton($button);
if ($node === NULL) {
throw new ElementNotFoundException($this->session, 'button', 'id|name|label|value', $button);
}
return $node;
}
/**
* Checks that specific select field exists on the current page.
*
* @param string $select
* One of id|name|label|value for the select field.
* @param \Behat\Mink\Element\TraversableElement $container
* (optional) The document to check against. Defaults to the current page.
*
* @return \Behat\Mink\Element\NodeElement
* The matching element
*
* @throws \Behat\Mink\Exception\ElementNotFoundException
* When the element doesn't exist.
*/
public function selectExists($select, TraversableElement $container = NULL) {
$container = $container ?: $this->session->getPage();
$node = $container->find('named', array(
'select',
$this->session->getSelectorsHandler()->xpathLiteral($select),
));
if ($node === NULL) {
throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
}
return $node;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
{#
/**
* @file
* Default theme implementation for simpletest result summaries.
*
* Available variables:
* - label: An optional label to be rendered before the results.
* - items: Pluralized summaries for each result type (number of passes, fails,
* exceptions, and debug messages).
* - pass: The number of passes.
* - fail: The number of fails.
* - exception: The number of exceptions.
* - debug: The number of debug messages.
*
* @see template_preprocess_simpletest_result_summary()
*
* @ingroup themeable
*/
#}
<div class="simpletest-{{ fail + exception == 0 ? 'pass' : 'fail' }}">
{{ label }} {{ items|join(', ') }}
</div>

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
<testsuite name="Drupal Unit Test Suite" tests="1" assertions="0" failures="0" errors="1" time="0.002680">
<testsuite name="Drupal\Tests\Component\PhpStorage\FileStorageTest" file="/home/chx/www/system/core/tests/Drupal/Tests/Component/PhpStorage/FileStorageTest.php" namespace="Drupal\Tests\Component\PhpStorage" fullPackage="Drupal.Tests.Component.PhpStorage" tests="0" assertions="0" failures="0" errors="0" time="0.000000"/>
<testsuite name="Drupal\Tests\Component\PhpStorage\MTimeProtectedFastFileStorageTest" file="/home/chx/www/system/core/tests/Drupal/Tests/Component/PhpStorage/MTimeProtectedFastFileStorageTest.php" namespace="Drupal\Tests\Component\PhpStorage" fullPackage="Drupal.Tests.Component.PhpStorage" tests="0" assertions="0" failures="0" errors="0" time="0.000000"/>
<testsuite name="Drupal\Tests\Core\Cache\BackendChainImplementationUnitTest" file="/home/chx/www/system/core/tests/Drupal/Tests/Core/Cache/BackendChainImplementationUnitTest.php" namespace="Drupal\Tests\Core\Cache" fullPackage="Drupal.Tests.Core.Cache" tests="0" assertions="0" failures="0" errors="0" time="0.000000"/>
<testsuite name="Drupal\Tests\Core\Cache\NullBackendTest" file="/home/chx/www/system/core/tests/Drupal/Tests/Core/Cache/NullBackendTest.php" namespace="Drupal\Tests\Core\Cache" fullPackage="Drupal.Tests.Core.Cache" tests="0" assertions="0" failures="0" errors="0" time="0.000000"/>
<testsuite name="Drupal\Tests\Core\Extension\ModuleHandlerUnitTest" file="/home/chx/www/system/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerUnitTest.php" namespace="Drupal\Tests\Core\Extension" fullPackage="Drupal.Tests.Core.Extension" tests="1" assertions="0" failures="0" errors="1" time="0.002680">
<testcase name="testloadInclude" class="Drupal\Tests\Core\Extension\ModuleHandlerUnitTest" file="/home/chx/www/system/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerUnitTest.php" line="37" assertions="0" time="0.002680">
<error type="PHPUnit_Framework_Error_Notice">Drupal\Tests\Core\Extension\ModuleHandlerUnitTest::testloadInclude
Undefined index: foo
/home/chx/www/system/core/lib/Drupal/Core/Extension/ModuleHandler.php:219
/home/chx/www/system/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerUnitTest.php:40
</error>
</testcase>
</testsuite>
<testsuite name="Drupal\Tests\Core\NestedArrayUnitTest" file="/home/chx/www/system/core/tests/Drupal/Tests/Core/NestedArrayUnitTest.php" namespace="Drupal\Tests\Core" fullPackage="Drupal.Tests.Core" tests="0" assertions="0" failures="0" errors="0" time="0.000000"/>
<testsuite name="Drupal\breakpoint\Tests\BreakpointMediaQueryTest" file="/home/chx/www/system/core/modules/breakpoint/tests/Drupal/breakpoint/Tests/BreakpointMediaQueryTest.php" namespace="Drupal\breakpoint\Tests" fullPackage="Drupal.breakpoint.Tests" tests="0" assertions="0" failures="0" errors="0" time="0.000000"/>
<testsuite name="Drupal\Tests\Core\Route\RoleAccessCheckTest" file="/var/www/d8/core/tests/Drupal/Tests/Core/Route/RoleAccessCheckTestkTest.php" namespace="Drupal\Tests\Core\Route" fullPackage="Drupal.Tests.Core.Route" tests="3" assertions="3" failures="3" errors="0" time="0.009176">
<testsuite name="Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess" tests="3" assertions="3" failures="3" errors="0" time="0.009176">
<testcase name="testRoleAccess with data set #0" assertions="1" time="0.004519">
<failure type="PHPUnit_Framework_ExpectationFailedException">Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess with data set #0 ('role_test_1', array(Drupal\user\Entity\User, Drupal\user\Entity\User))
Access granted for user with the roles role_test_1 on path: role_test_1
Failed asserting that false is true.
</failure>
</testcase>
<testcase name="testRoleAccess with data set #1" assertions="1" time="0.002354">
<failure type="PHPUnit_Framework_ExpectationFailedException">Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess with data set #1 ('role_test_2', array(Drupal\user\Entity\User, Drupal\user\Entity\User))
Access granted for user with the roles role_test_2 on path: role_test_2
Failed asserting that false is true.
</failure>
</testcase>
<testcase name="testRoleAccess with data set #2" assertions="1" time="0.002303">
<failure type="PHPUnit_Framework_ExpectationFailedException">Drupal\Tests\Core\Route\RoleAccessCheckTest::testRoleAccess with data set #2 ('role_test_3', array(Drupal\user\Entity\User))
Access granted for user with the roles role_test_1, role_test_2 on path: role_test_3
Failed asserting that false is true.
</failure>
</testcase>
</testsuite>
</testsuite>
</testsuite>
</testsuites>

View file

@ -0,0 +1,4 @@
<select name="test">
<option value="1">One</option>
<option value="2" selected="selected">Two</option>
</select>

View file

@ -0,0 +1,4 @@
<select name="test">
<option value="1">One</option>
<option value="2">Two</option>
</select>

View file

@ -0,0 +1,6 @@
name: PHPUnit Test
type: module
description: 'Provides dummy classes for use by SimpleTest tests.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,11 @@
<?php
/**
* @file
* Contains \Drupal\phpunit_test\PhpUnitTestDummyClass.
*/
namespace Drupal\phpunit_test;
class PhpUnitTestDummyClass {
}

View file

@ -0,0 +1,8 @@
name: 'Simpletest test'
type: module
description: 'Support module for Simpletest tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- entity_test

View file

@ -0,0 +1,27 @@
<?php
/**
* Implements hook_install().
*/
function simpletest_test_install() {
$total = 2;
$operations = array();
for ($i = 1; $i <= $total; $i++) {
$operations[] = array('_simpletest_test_callback', array($i));
}
$batch = array(
'operations' => $operations,
);
batch_set($batch);
$batch =& batch_get();
$batch['progressive'] = FALSE;
batch_process();
}
/**
* Callback for batch operations.
*/
function _simpletest_test_callback($id) {
$entity = entity_create('entity_test', array('id' => $id));
$entity->save();
}

View file

@ -0,0 +1,64 @@
<?php
/**
* @file
* Contains \Drupal\Tests\simpletest\Functional\BrowserTestBaseTest.
*/
namespace Drupal\Tests\simpletest\Functional;
use Drupal\simpletest\BrowserTestBase;
/**
* Tests BrowserTestBase functionality.
*
* @group simpletest
*
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class BrowserTestBaseTest extends BrowserTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('test_page_test', 'form_test');
/**
* Tests basic page test.
*/
public function testGoTo() {
$account = $this->drupalCreateUser();
$this->drupalLogin($account);
// Visit a Drupal page that requires login.
$this->drupalGet('/test-page');
$this->assertSession()->statusCodeEquals(200);
// Test page contains some text.
$this->assertSession()->pageTextContains('Test page text.');
}
/**
* Tests basic form functionality.
*/
public function testForm() {
// Ensure the proper response code for a _form route.
$this->drupalGet('/form-test/object-builder');
$this->assertSession()->statusCodeEquals(200);
// Ensure the form and text field exist.
$this->assertSession()->elementExists('css', 'form#form-test-form-test-object');
$this->assertSession()->fieldExists('bananas');
$edit = ['bananas' => 'green'];
$this->submitForm($edit, 'Save', 'form-test-form-test-object');
$config_factory = $this->container->get('config.factory');
$value = $config_factory->get('form_test.object')->get('bananas');
$this->assertSame('green', $value);
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\Tests\simpletest\Unit\PhpUnitAutoloaderTest.
*/
namespace Drupal\Tests\simpletest\Unit;
use Drupal\Tests\UnitTestCase;
/**
* Tests that classes are correctly loaded during PHPUnit initialization.
*
* @group simpletest
*/
class PhpUnitAutoloaderTest extends UnitTestCase {
/**
* Test loading of classes provided by test sub modules.
*/
public function testPhpUnitTestClassesLoading() {
$this->assertTrue(class_exists('\Drupal\phpunit_test\PhpUnitTestDummyClass'), 'Class provided by test module was not autoloaded.');
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* @file
* Contains \Drupal\Tests\simpletest\Unit\PhpUnitErrorTest.
*/
namespace Drupal\Tests\simpletest\Unit;
use Drupal\Tests\UnitTestCase;
/**
* Tests PHPUnit errors are getting converted to Simpletest errors.
*
* @group simpletest
*/
class PhpUnitErrorTest extends UnitTestCase {
/**
* Test errors reported.
*
* @covers ::simpletest_phpunit_xml_to_rows
*/
public function testPhpUnitXmlParsing() {
require_once __DIR__ . '/../../../simpletest.module';
$phpunit_error_xml = __DIR__ . '/../../fixtures/phpunit_error.xml';
$res = simpletest_phpunit_xml_to_rows(1, $phpunit_error_xml);
$this->assertEquals(count($res), 4, 'All testcases got extracted');
$this->assertNotEquals($res[0]['status'], 'pass');
$this->assertEquals($res[0]['status'], 'fail');
// Test nested testsuites, which appear when you use @dataProvider.
for ($i = 0; $i < 3; $i++) {
$this->assertNotEquals($res[$i + 1]['status'], 'pass');
$this->assertEquals($res[$i + 1]['status'], 'fail');
}
// Make sure simpletest_phpunit_xml_to_rows() does not balk if the test
// didn't run.
simpletest_phpunit_xml_to_rows(1, 'foobar');
}
}

View file

@ -0,0 +1,472 @@
<?php
/**
* @file
* Contains \Drupal\Tests\simpletest\Unit\TestBaseTest.
*/
namespace Drupal\Tests\simpletest\Unit;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\simpletest\TestBase
* @group simpletest
*/
class TestBaseTest extends UnitTestCase {
/**
* Helper method for constructing a mock TestBase object.
*
* TestBase is abstract, so we have to mock it. We'll also
* mock the storeAssertion() method so we don't need the database.
*
* @param string $test_id
* An identifying name for the mocked test.
*
* @return object
* Mock of Drupal\simpletest\TestBase.
*/
public function getTestBaseForAssertionTests($test_id) {
$mock_test_base = $this->getMockBuilder('Drupal\simpletest\TestBase')
->setConstructorArgs(array($test_id))
->setMethods(array('storeAssertion'))
->getMockForAbstractClass();
// Override storeAssertion() so we don't need a database.
$mock_test_base->expects($this->any())
->method('storeAssertion')
->will($this->returnValue(NULL));
return $mock_test_base;
}
/**
* Invoke methods that are protected or private.
*
* @param object $object
* Object on which to invoke the method.
* @param string $method_name
* Name of the method to invoke.
* @param array $arguments
* Array of arguments to be passed to the method.
*
* @return mixed
* Value returned by the invoked method.
*/
public function invokeProtectedMethod($object, $method_name, array $arguments) {
$ref_method = new \ReflectionMethod($object, $method_name);
$ref_method->setAccessible(TRUE);
return $ref_method->invokeArgs($object, $arguments);
}
/**
* Provides data for the random string validation test.
*
* @return array
* - The expected result of the validation.
* - The string to validate.
*/
public function providerRandomStringValidate() {
return array(
array(FALSE, ' curry paste'),
array(FALSE, 'curry paste '),
array(FALSE, 'curry paste'),
array(FALSE, 'curry paste'),
array(TRUE, 'curry paste'),
array(TRUE, 'thai green curry paste'),
array(FALSE, '@startswithat'),
array(TRUE, 'contains@at'),
);
}
/**
* @covers ::randomStringValidate
* @dataProvider providerRandomStringValidate
*/
public function testRandomStringValidate($expected, $string) {
$mock_test_base = $this->getMockForAbstractClass('Drupal\simpletest\TestBase');
$actual = $mock_test_base->randomStringValidate($string);
$this->assertEquals($expected, $actual);
}
/**
* Provides data for testRandomString() and others.
*
* @return array
* - The number of items (characters, object properties) we expect any of
* the random functions to give us.
*/
public function providerRandomItems() {
return [
[NULL],
[0],
[1],
[2],
[3],
[4],
[7],
];
}
/**
* @covers ::randomString
* @dataProvider providerRandomItems
*/
public function testRandomString($length) {
$mock_test_base = $this->getMockForAbstractClass('Drupal\simpletest\TestBase');
$string = $mock_test_base->randomString($length);
$this->assertEquals($length, strlen($string));
// randomString() should always include an ampersand ('&') if $length is
// greater than 2.
if ($length > 2) {
$this->assertContains('&', $string);
}
}
/**
* @covers ::randomObject
* @dataProvider providerRandomItems
*/
public function testRandomObject($size) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
// Note: count((array)object) works for now, maybe not later.
$this->assertEquals($size, count((array) $test_base->randomObject($size)));
}
/**
* @covers ::checkRequirements
*/
public function testCheckRequirements() {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertInternalType(
'array',
$this->invokeProtectedMethod($test_base, 'checkRequirements', array())
);
}
/**
* Data provider for testAssert().
*
* @return array
* Standard dataProvider array of arrays:
* - Expected result from assert().
* - Expected status stored in TestBase->assertions.
* - Status, passed to assert().
* - Message, passed to assert().
* - Group, passed to assert().
* - Caller, passed to assert().
*/
public function providerAssert() {
return array(
array(TRUE, 'pass', TRUE, 'Yay pass', 'test', array()),
array(FALSE, 'fail', FALSE, 'Boo fail', 'test', array()),
array(TRUE, 'pass', 'pass', 'Yay pass', 'test', array()),
array(FALSE, 'fail', 'fail', 'Boo fail', 'test', array()),
array(FALSE, 'exception', 'exception', 'Boo fail', 'test', array()),
array(FALSE, 'debug', 'debug', 'Boo fail', 'test', array()),
);
}
/**
* @covers ::assert
* @dataProvider providerAssert
*/
public function testAssert($expected, $assertion_status, $status, $message, $group, $caller) {
$test_id = 'luke_i_am_your_' . $assertion_status;
$test_base = $this->getTestBaseForAssertionTests($test_id);
// Verify some startup values.
$this->assertAttributeEmpty('assertions', $test_base);
if (is_string($status)) {
$this->assertEquals(0, $test_base->results['#' . $status]);
}
// assert() is protected so we have to make it accessible.
$ref_assert = new \ReflectionMethod($test_base, 'assert');
$ref_assert->setAccessible(TRUE);
// Call assert() from within our hall of mirrors.
$this->assertEquals(
$expected,
$ref_assert->invokeArgs($test_base,
array($status, $message, $group, $caller)
)
);
// Check the side-effects of assert().
if (is_string($status)) {
$this->assertEquals(1, $test_base->results['#' . $status]);
}
$this->assertAttributeNotEmpty('assertions', $test_base);
// Make a ReflectionProperty for the assertions property,
// since it's protected.
$ref_assertions = new \ReflectionProperty($test_base, 'assertions');
$ref_assertions->setAccessible(TRUE);
$assertions = $ref_assertions->getValue($test_base);
$assertion = reset($assertions);
$this->assertEquals($assertion_status, $assertion['status']);
$this->assertEquals($test_id, $assertion['test_id']);
$this->assertEquals(get_class($test_base), $assertion['test_class']);
$this->assertEquals($message, $assertion['message']);
$this->assertEquals($group, $assertion['message_group']);
}
/**
* Data provider for assertTrue().
*/
public function providerAssertTrue() {
return array(
array(TRUE, TRUE),
array(FALSE, FALSE),
);
}
/**
* @covers ::assertTrue
* @dataProvider providerAssertTrue
*/
public function testAssertTrue($expected, $value) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
$expected,
$this->invokeProtectedMethod($test_base, 'assertTrue', array($value))
);
}
/**
* @covers ::assertFalse
* @dataProvider providerAssertTrue
*/
public function testAssertFalse($expected, $value) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
(!$expected),
$this->invokeProtectedMethod($test_base, 'assertFalse', array($value))
);
}
/**
* Data provider for assertNull().
*/
public function providerAssertNull() {
return array(
array(TRUE, NULL),
array(FALSE, ''),
);
}
/**
* @covers ::assertNull
* @dataProvider providerAssertNull
*/
public function testAssertNull($expected, $value) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
$expected,
$this->invokeProtectedMethod($test_base, 'assertNull', array($value))
);
}
/**
* @covers ::assertNotNull
* @dataProvider providerAssertNull
*/
public function testAssertNotNull($expected, $value) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
(!$expected),
$this->invokeProtectedMethod($test_base, 'assertNotNull', array($value))
);
}
/**
* Data provider for tests of equality assertions.
*
* Used by testAssertIdentical(), testAssertEqual(), testAssertNotIdentical(),
* and testAssertNotEqual().
*
* @return
* Array of test data.
* - Expected assertion value for identical comparison.
* - Expected assertion value for equal comparison.
* - First value to compare.
* - Second value to compare.
*/
public function providerEqualityAssertions() {
return [
// Integers and floats.
[TRUE, TRUE, 0, 0],
[FALSE, TRUE, 0, 0.0],
[FALSE, TRUE, '0', 0],
[FALSE, TRUE, '0.0', 0.0],
[FALSE, FALSE, 23, 77],
[TRUE, TRUE, 23.0, 23.0],
// Strings.
[FALSE, FALSE, 'foof', 'yay'],
[TRUE, TRUE, 'yay', 'yay'],
// Bools with type conversion.
[TRUE, TRUE, TRUE, TRUE],
[TRUE, TRUE, FALSE, FALSE],
[FALSE, TRUE, NULL, FALSE],
[FALSE, TRUE, 'TRUE', TRUE],
[FALSE, FALSE, 'FALSE', FALSE],
[FALSE, TRUE, 0, FALSE],
[FALSE, TRUE, 1, TRUE],
[FALSE, TRUE, -1, TRUE],
[FALSE, TRUE, '1', TRUE],
[FALSE, TRUE, '1.3', TRUE],
// Null.
[FALSE, FALSE, 'NULL', NULL],
[TRUE, TRUE, NULL, NULL],
];
}
/**
* @covers ::assertIdentical
* @dataProvider providerEqualityAssertions
*/
public function testAssertIdentical($expected_identical, $expected_equal, $first, $second) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
$expected_identical,
$this->invokeProtectedMethod($test_base, 'assertIdentical', array($first, $second))
);
}
/**
* @covers ::assertNotIdentical
* @dataProvider providerEqualityAssertions
*/
public function testAssertNotIdentical($expected_identical, $expected_equal, $first, $second) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
(!$expected_identical),
$this->invokeProtectedMethod($test_base, 'assertNotIdentical', array($first, $second))
);
}
/**
* @covers ::assertEqual
* @dataProvider providerEqualityAssertions
*/
public function testAssertEqual($expected_identical, $expected_equal, $first, $second) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
$expected_equal,
$this->invokeProtectedMethod($test_base, 'assertEqual', array($first, $second))
);
}
/**
* @covers ::assertNotEqual
* @dataProvider providerEqualityAssertions
*/
public function testAssertNotEqual($expected_identical, $expected_equal, $first, $second) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
(!$expected_equal),
$this->invokeProtectedMethod($test_base, 'assertNotEqual', array($first, $second))
);
}
/**
* Data provider for testAssertIdenticalObject().
*/
public function providerAssertIdenticalObject() {
$obj1 = new \stdClass();
$obj1->foof = 'yay';
$obj2 = $obj1;
$obj3 = clone $obj1;
$obj4 = new \stdClass();
return array(
array(TRUE, $obj1, $obj2),
array(TRUE, $obj1, $obj3),
array(FALSE, $obj1, $obj4),
);
}
/**
* @covers ::assertIdenticalObject
* @dataProvider providerAssertIdenticalObject
*/
public function testAssertIdenticalObject($expected, $first, $second) {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
$expected,
$this->invokeProtectedMethod($test_base, 'assertIdenticalObject', array($first, $second))
);
}
/**
* @covers ::pass
*/
public function testPass() {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
TRUE,
$this->invokeProtectedMethod($test_base, 'pass', array())
);
}
/**
* @covers ::fail
*/
public function testFail() {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertEquals(
FALSE,
$this->invokeProtectedMethod($test_base, 'fail', array())
);
}
/**
* Data provider for testError().
*
* @return array
* - Expected status for assertion.
* - Group for use in assert().
*/
public function providerError() {
return array(
array('debug', 'User notice'),
array('exception', 'Not User notice'),
);
}
/**
* @covers ::error
* @dataProvider providerError
*/
public function testError($status, $group) {
// Mock up a TestBase object.
$mock_test_base = $this->getMockBuilder('Drupal\simpletest\TestBase')
->setMethods(array('assert'))
->getMockForAbstractClass();
// Set expectations for assert().
$mock_test_base->expects($this->once())
->method('assert')
// The first argument to assert() should be the expected $status. This is
// the most important expectation of this test.
->with($status)
// Arbitrary return value.
->willReturn("$status:$group");
// Invoke error().
$this->assertEquals(
"$status:$group",
$this->invokeProtectedMethod($mock_test_base, 'error', array('msg', $group))
);
}
/**
* @covers ::getRandomGenerator
*/
public function testGetRandomGenerator() {
$test_base = $this->getTestBaseForAssertionTests('test_id');
$this->assertInstanceOf(
'Drupal\Component\Utility\Random',
$this->invokeProtectedMethod($test_base, 'getRandomGenerator', array())
);
}
}

View file

@ -0,0 +1,235 @@
<?php
/**
* @file
* Contains \Drupal\Tests\simpletest\Unit\TestInfoParsingTest.
*/
namespace Drupal\Tests\simpletest\Unit;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\simpletest\TestDiscovery
* @group simpletest
*/
class TestInfoParsingTest extends UnitTestCase {
/**
* @covers ::getTestInfo
* @dataProvider infoParserProvider
*/
public function testTestInfoParser($expected, $classname, $doc_comment = NULL) {
$info = \Drupal\simpletest\TestDiscovery::getTestInfo($classname, $doc_comment);
$this->assertEquals($expected, $info);
}
public function infoParserProvider() {
// A module provided unit test.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\Tests\simpletest\Unit\TestInfoParsingTest',
'group' => 'PHPUnit',
'description' => 'Tests \Drupal\simpletest\TestDiscovery.',
],
// Classname.
'Drupal\Tests\simpletest\Unit\TestInfoParsingTest',
];
// A core unit test.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\Tests\Core\DrupalTest',
'group' => 'PHPUnit',
'description' => 'Tests \Drupal.',
],
// Classname.
'Drupal\Tests\Core\DrupalTest',
];
// Functional PHPUnit test.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\Tests\simpletest\Functional\BrowserTestBaseTest',
'group' => 'simpletest',
'description' => 'Tests BrowserTestBase functionality.',
],
// Classname.
'Drupal\Tests\simpletest\Functional\BrowserTestBaseTest',
];
// Simpletest classes can not be autoloaded in a PHPUnit test, therefore
// provide a docblock.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\field\Tests\BulkDeleteTest',
'group' => 'field',
'description' => 'Bulk delete storages and fields, and clean up afterwards.',
],
// Classname.
'Drupal\field\Tests\BulkDeleteTest',
// Doc block.
"/**
* Bulk delete storages and fields, and clean up afterwards.
*
* @group field
*/
",
];
// Test with a different amount of leading spaces.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\field\Tests\BulkDeleteTest',
'group' => 'field',
'description' => 'Bulk delete storages and fields, and clean up afterwards.',
],
// Classname.
'Drupal\field\Tests\BulkDeleteTest',
// Doc block.
"/**
* Bulk delete storages and fields, and clean up afterwards.
*
* @group field
*/
",
];
// Make sure that a "* @" inside a string does not get parsed as an
// annotation.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\field\Tests\BulkDeleteTest',
'group' => 'field',
'description' => 'Bulk delete storages and fields, and clean up afterwards. * @',
],
// Classname.
'Drupal\field\Tests\BulkDeleteTest',
// Doc block.
"/**
* Bulk delete storages and fields, and clean up afterwards. * @
*
* @group field
*/
",
];
// Multiple @group annotations.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\field\Tests\BulkDeleteTest',
'group' => 'Test',
'description' => 'Bulk delete storages and fields, and clean up afterwards.',
],
// Classname.
'Drupal\field\Tests\BulkDeleteTest',
// Doc block.
"/**
* Bulk delete storages and fields, and clean up afterwards.
*
* @group Test
* @group field
*/
",
];
// @dependencies annotation.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\field\Tests\BulkDeleteTest',
'group' => 'field',
'description' => 'Bulk delete storages and fields, and clean up afterwards.',
'requires' => ['module' => ['test']],
],
// Classname.
'Drupal\field\Tests\BulkDeleteTest',
// Doc block.
"/**
* Bulk delete storages and fields, and clean up afterwards.
*
* @dependencies test
* @group field
*/
",
];
// Multiple @dependencies annotation.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\field\Tests\BulkDeleteTest',
'group' => 'field',
'description' => 'Bulk delete storages and fields, and clean up afterwards.',
'requires' => ['module' => ['test', 'test1', 'test2']],
],
// Classname.
'Drupal\field\Tests\BulkDeleteTest',
// Doc block.
"/**
* Bulk delete storages and fields, and clean up afterwards.
*
* @dependencies test, test1,test2
* @group field
*/
",
];
// Multi-line summary line.
$tests[] = [
// Expected result.
[
'name' => 'Drupal\field\Tests\BulkDeleteTest',
'group' => 'field',
'description' => 'Bulk delete storages and fields, and clean up afterwards. And the summary line continues and there is no gap to the annotation.',
],
// Classname.
'Drupal\field\Tests\BulkDeleteTest',
// Doc block.
"/**
* Bulk delete storages and fields, and clean up afterwards. And the summary
* line continues and there is no gap to the annotation.
* @group field
*/
",
];
return $tests;
}
/**
* @covers ::getTestInfo
* @expectedException \Drupal\simpletest\Exception\MissingGroupException
* @expectedExceptionMessage Missing @group annotation in Drupal\field\Tests\BulkDeleteTest
*/
public function testTestInfoParserMissingGroup() {
$classname = 'Drupal\field\Tests\BulkDeleteTest';
$doc_comment = <<<EOT
/**
* Bulk delete storages and fields, and clean up afterwards.
*/
EOT;
\Drupal\simpletest\TestDiscovery::getTestInfo($classname, $doc_comment);
}
/**
* @covers ::getTestInfo
*/
public function testTestInfoParserMissingSummary() {
$classname = 'Drupal\field\Tests\BulkDeleteTest';
$doc_comment = <<<EOT
/**
* @group field
*/
EOT;
$info = \Drupal\simpletest\TestDiscovery::getTestInfo($classname, $doc_comment);
$this->assertEmpty($info['description']);
}
}

View file

@ -0,0 +1,201 @@
<?php
/**
* @file
* Contains \Drupal\Tests\simpletest\Unit\WebTestBaseTest.
*/
namespace Drupal\Tests\simpletest\Unit;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\simpletest\WebTestBase
* @group simpletest
*/
class WebTestBaseTest extends UnitTestCase {
/**
* Provides data for testing the assertFieldByName() helper.
*
* @return array
* An array of values passed to the test method.
*/
public function providerAssertFieldByName() {
$data = array();
$data[] = array('select_2nd_selected', 'test', '1', FALSE);
$data[] = array('select_2nd_selected', 'test', '2', TRUE);
$data[] = array('select_none_selected', 'test', '', FALSE);
$data[] = array('select_none_selected', 'test', '1', TRUE);
$data[] = array('select_none_selected', 'test', NULL, TRUE);
return $data;
}
/**
* Tests the assertFieldByName() helper.
*
* @param string $filename
* Name of file containing the output to test.
* @param string $name
* Name of field to assert.
* @param string $value
* Value of the field to assert.
* @param bool $expected
* The expected result of the assert.
*
* @see \Drupal\simpletest\WebTestBase::assertFieldByName()
*
* @dataProvider providerAssertFieldByName
* @covers ::assertFieldByName
*/
public function testAssertFieldByName($filename, $name, $value, $expected) {
$content = file_get_contents(__DIR__ . '/../../fixtures/' . $filename . '.html');
$web_test = $this->getMockBuilder('Drupal\simpletest\WebTestBase')
->disableOriginalConstructor()
->setMethods(array('getRawContent', 'assertTrue', 'pass'))
->getMock();
$web_test->expects($this->any())
->method('getRawContent')
->will($this->returnValue($content));
$web_test->expects($this->once())
->method('assertTrue')
->with($this->identicalTo($expected),
$this->identicalTo('message'),
$this->identicalTo('Browser'));
$test_method = new \ReflectionMethod('Drupal\simpletest\WebTestBase', 'assertFieldByName');
$test_method->setAccessible(TRUE);
$test_method->invokeArgs($web_test, array($name, $value, 'message'));
}
/**
* Data provider for testClickLink().
*
* In the test method, we mock drupalGet() to return a known string:
* 'This Text Returned By drupalGet()'. Since clickLink() can only return
* either the value of drupalGet() or FALSE, our expected return value is the
* same as this mocked return value when we expect a link to be found.
*
* @see https://www.drupal.org/node/1452896
*
* @return array
* Array of arrays of test data. Test data is structured as follows:
* - Expected return value of clickLink().
* - Parameter $label to clickLink().
* - Parameter $index to clickLink().
* - Test data to be returned by mocked xpath(). Return an empty array here
* to mock no link found on the page.
*/
public function providerTestClickLink() {
return array(
// Test for a non-existent label.
array(
FALSE,
'does_not_exist',
0,
array(),
),
// Test for an existing label.
array(
'This Text Returned By drupalGet()',
'exists',
0,
array(0 => array('href' => 'this_is_a_url')),
),
// Test for an existing label that isn't the first one.
array(
'This Text Returned By drupalGet()',
'exists',
1,
array(
0 => array('href' => 'this_is_a_url'),
1 => array('href' => 'this_is_another_url'),
),
),
);
}
/**
* Test WebTestBase::clickLink().
*
* @param mixed $expected
* Expected return value of clickLink().
* @param string $label
* Parameter $label to clickLink().
* @param int $index
* Parameter $index to clickLink().
* @param array $xpath_data
* Test data to be returned by mocked xpath().
*
* @dataProvider providerTestClickLink
* @covers ::clickLink
*/
public function testClickLink($expected, $label, $index, $xpath_data) {
// Mock a WebTestBase object and some of its methods.
$web_test = $this->getMockBuilder('Drupal\simpletest\WebTestBase')
->disableOriginalConstructor()
->setMethods(array(
'pass',
'fail',
'getUrl',
'xpath',
'drupalGet',
'getAbsoluteUrl',
))
->getMock();
// Mocked getUrl() is only used for reporting so we just return a string.
$web_test->expects($this->any())
->method('getUrl')
->will($this->returnValue('url_before'));
// Mocked xpath() should return our test data.
$web_test->expects($this->any())
->method('xpath')
->will($this->returnValue($xpath_data));
if ($expected === FALSE) {
// If link does not exist clickLink() will not try to do a drupalGet() or
// a getAbsoluteUrl()
$web_test->expects($this->never())
->method('drupalGet');
$web_test->expects($this->never())
->method('getAbsoluteUrl');
// The test should fail and not pass.
$web_test->expects($this->never())
->method('pass');
$web_test->expects($this->once())
->method('fail')
->will($this->returnValue(NULL));
}
else {
// Mocked getAbsoluteUrl() should return whatever comes in.
$web_test->expects($this->once())
->method('getAbsoluteUrl')
->with($xpath_data[$index]['href'])
->will($this->returnArgument(0));
// We're only testing clickLink(), so drupalGet() always returns a string.
$web_test->expects($this->once())
->method('drupalGet')
->with($xpath_data[$index]['href'])
->will($this->returnValue('This Text Returned By drupalGet()'));
// The test should pass and not fail.
$web_test->expects($this->never())
->method('fail');
$web_test->expects($this->once())
->method('pass')
->will($this->returnValue(NULL));
}
// Set the clickLink() method to public so we can test it.
$clicklink_method = new \ReflectionMethod($web_test, 'clickLink');
$clicklink_method->setAccessible(TRUE);
$this->assertSame($expected, $clicklink_method->invoke($web_test, $label, $index));
}
}