Issue #3075608 by mondrake, Mile23, longwave: Introduce TestRun objects and refactor TestDatabase to be testable

merge-requests/3647/head
Alex Pott 2023-03-07 12:28:55 +00:00
parent 237373bca0
commit f711435482
No known key found for this signature in database
GPG Key ID: BDA67E7EE836E5CE
16 changed files with 1291 additions and 471 deletions

View File

@ -8,75 +8,38 @@ use Symfony\Component\Console\Output\OutputInterface;
/**
* Helper class for cleaning test environments.
*
* @internal
*/
class EnvironmentCleaner implements EnvironmentCleanerInterface {
/**
* Path to Drupal root directory.
*
* @var string
*/
protected $root;
/**
* Connection to the database being used for tests.
*
* @var \Drupal\Core\Database\Connection
*/
protected $testDatabase;
/**
* Connection to the database where test results are stored.
*
* This could be the same as $testDatabase, or it could be different.
* run-tests.sh allows you to specify a different results database with the
* --sqlite parameter.
*
* @var \Drupal\Core\Database\Connection
*/
protected $resultsDatabase;
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* Console output.
*
* @var \Symfony\Component\Console\Output\OutputInterface
*/
protected $output;
/**
* Construct an environment cleaner.
* Constructs a test environment cleaner.
*
* @param string $root
* The path to the root of the Drupal installation.
* @param \Drupal\Core\Database\Connection $test_database
* @param \Drupal\Core\Database\Connection $testDatabase
* Connection to the database against which tests were run.
* @param \Drupal\Core\Database\Connection $results_database
* Connection to the database where test results were stored. This could be
* the same as $test_database, or it could be different.
* @param \Drupal\Core\Test\TestRunResultsStorageInterface $testRunResultsStorage
* The test run results storage.
* @param \Symfony\Component\Console\Output\OutputInterface $output
* A symfony console output object.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file_system service.
* A Symfony console output object.
* @param \Drupal\Core\File\FileSystemInterface $fileSystem
* Drupal's file_system service.
*/
public function __construct($root, Connection $test_database, Connection $results_database, OutputInterface $output, FileSystemInterface $file_system) {
$this->root = $root;
$this->testDatabase = $test_database;
$this->resultsDatabase = $results_database;
$this->output = $output;
$this->fileSystem = $file_system;
public function __construct(
protected string $root,
protected Connection $testDatabase,
protected TestRunResultsStorageInterface $testRunResultsStorage,
protected OutputInterface $output,
protected FileSystemInterface $fileSystem
) {
}
/**
* {@inheritdoc}
*/
public function cleanEnvironment($clear_results = TRUE, $clear_temp_directories = TRUE, $clear_database = TRUE) {
public function cleanEnvironment(bool $clear_results = TRUE, bool $clear_temp_directories = TRUE, bool $clear_database = TRUE): void {
$count = 0;
if ($clear_database) {
$this->doCleanDatabase();
@ -85,7 +48,7 @@ class EnvironmentCleaner implements EnvironmentCleanerInterface {
$this->doCleanTemporaryDirectories();
}
if ($clear_results) {
$count = $this->cleanResultsTable();
$count = $this->cleanResults();
$this->output->write('Test results removed: ' . $count);
}
else {
@ -96,7 +59,7 @@ class EnvironmentCleaner implements EnvironmentCleanerInterface {
/**
* {@inheritdoc}
*/
public function cleanDatabase() {
public function cleanDatabase(): void {
$count = $this->doCleanDatabase();
if ($count > 0) {
$this->output->write('Leftover tables removed: ' . $count);
@ -112,7 +75,7 @@ class EnvironmentCleaner implements EnvironmentCleanerInterface {
* @return int
* The number of tables that were removed.
*/
protected function doCleanDatabase() {
protected function doCleanDatabase(): int {
/** @var \Drupal\Core\Database\Schema $schema */
$schema = $this->testDatabase->schema();
$tables = $schema->findTables('test%');
@ -131,7 +94,7 @@ class EnvironmentCleaner implements EnvironmentCleanerInterface {
/**
* {@inheritdoc}
*/
public function cleanTemporaryDirectories() {
public function cleanTemporaryDirectories(): void {
$count = $this->doCleanTemporaryDirectories();
if ($count > 0) {
$this->output->write('Temporary directories removed: ' . $count);
@ -147,7 +110,7 @@ class EnvironmentCleaner implements EnvironmentCleanerInterface {
* @return int
* The count of temporary directories removed.
*/
protected function doCleanTemporaryDirectories() {
protected function doCleanTemporaryDirectories(): int {
$count = 0;
$simpletest_dir = $this->root . '/sites/simpletest';
if (is_dir($simpletest_dir)) {
@ -168,27 +131,8 @@ class EnvironmentCleaner implements EnvironmentCleanerInterface {
/**
* {@inheritdoc}
*/
public function cleanResultsTable($test_id = NULL) {
$count = 0;
if ($test_id) {
$count = $this->resultsDatabase->query('SELECT COUNT([test_id]) FROM {simpletest_test_id} WHERE [test_id] = :test_id', [':test_id' => $test_id])->fetchField();
$this->resultsDatabase->delete('simpletest')
->condition('test_id', $test_id)
->execute();
$this->resultsDatabase->delete('simpletest_test_id')
->condition('test_id', $test_id)
->execute();
}
else {
$count = $this->resultsDatabase->query('SELECT COUNT([test_id]) FROM {simpletest_test_id}')->fetchField();
// Clear test results.
$this->resultsDatabase->delete('simpletest')->execute();
$this->resultsDatabase->delete('simpletest_test_id')->execute();
}
return $count;
public function cleanResults(TestRun $test_run = NULL): int {
return $test_run ? $test_run->removeResults() : $this->testRunResultsStorage->cleanUp();
}
}

View File

@ -8,11 +8,9 @@ namespace Drupal\Core\Test;
* This interface is marked internal. It does not imply an API.
*
* @todo Formalize this interface in
* https://www.drupal.org/project/drupal/issues/3075490 and
* https://www.drupal.org/project/drupal/issues/3075608
* https://www.drupal.org/project/drupal/issues/3075490
*
* @see https://www.drupal.org/project/drupal/issues/3075490
* @see https://www.drupal.org/project/drupal/issues/3075608
*
* @internal
*/
@ -25,33 +23,34 @@ interface EnvironmentCleanerInterface {
* under test.
*
* @param bool $clear_results
* (optional) Whether to clear the test results database. Defaults to TRUE.
* (optional) Whether to clear the test results storage. Defaults to TRUE.
* @param bool $clear_temp_directories
* (optional) Whether to clear the test site directories. Defaults to TRUE.
* @param bool $clear_database
* (optional) Whether to clean up the fixture database. Defaults to TRUE.
*/
public function cleanEnvironment($clear_results = TRUE, $clear_temp_directories = TRUE, $clear_database = TRUE);
public function cleanEnvironment(bool $clear_results = TRUE, bool $clear_temp_directories = TRUE, bool $clear_database = TRUE): void;
/**
* Remove database entries left over in the fixture database.
*/
public function cleanDatabase();
public function cleanDatabase(): void;
/**
* Finds all leftover fixture site directories and removes them.
*/
public function cleanTemporaryDirectories();
public function cleanTemporaryDirectories(): void;
/**
* Clears test result tables from the results database.
* Clears test results from the results storage.
*
* @param $test_id
* Test ID to remove results for, or NULL to remove all results.
* @param \Drupal\Core\Test\TestRun $test_run
* The test run object to remove results for, or NULL to remove all
* results.
*
* @return int
* The number of results that were removed.
*/
public function cleanResultsTable($test_id = NULL);
public function cleanResults(TestRun $test_run = NULL): int;
}

View File

@ -13,55 +13,42 @@ use Symfony\Component\Process\PhpExecutableFinder;
* This class runs PHPUnit-based tests and converts their JUnit results to a
* format that can be stored in the {simpletest} database schema.
*
* This class is @internal and not considered to be API.
* This class is internal and not considered to be API.
*
* @code
* $runner = PhpUnitTestRunner::create(\Drupal::getContainer());
* $results = $runner->runTests($test_id, $test_list['phpunit']);
* $results = $runner->execute($test_run, $test_list['phpunit']);
* @endcode
*
* @internal
*/
class PhpUnitTestRunner implements ContainerInjectionInterface {
/**
* Path to the working directory.
* Constructs a test runner.
*
* JUnit log files will be stored in this directory.
*
* @var string
* @param string $appRoot
* Path to the application root.
* @param string $workingDirectory
* Path to the working directory. JUnit log files will be stored in this
* directory.
*/
protected $workingDirectory;
/**
* Path to the application root.
*
* @var string
*/
protected $appRoot;
public function __construct(
protected string $appRoot,
protected string $workingDirectory
) {
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
public static function create(ContainerInterface $container): static {
return new static(
(string) $container->getParameter('app.root'),
(string) $container->get('file_system')->realpath('public://simpletest')
);
}
/**
* Constructs a test runner.
*
* @param string $app_root
* Path to the application root.
* @param string $working_directory
* Path to the working directory. JUnit log files will be stored in this
* directory.
*/
public function __construct($app_root, $working_directory) {
$this->appRoot = $app_root;
$this->workingDirectory = $working_directory;
}
/**
* Returns the path to use for PHPUnit's --log-junit option.
*
@ -73,7 +60,7 @@ class PhpUnitTestRunner implements ContainerInjectionInterface {
*
* @internal
*/
public function xmlLogFilePath($test_id) {
public function xmlLogFilePath(int $test_id): string {
return $this->workingDirectory . '/phpunit-' . $test_id . '.xml';
}
@ -85,7 +72,7 @@ class PhpUnitTestRunner implements ContainerInjectionInterface {
*
* @internal
*/
public function phpUnitCommand() {
public function phpUnitCommand(): string {
// Load the actual autoloader being used and determine its filename using
// reflection. We can determine the vendor directory based on that filename.
$autoloader = require $this->appRoot . '/autoload.php';
@ -124,7 +111,7 @@ class PhpUnitTestRunner implements ContainerInjectionInterface {
*
* @internal
*/
public function runCommand(array $unescaped_test_classnames, $phpunit_file, &$status = NULL, &$output = NULL) {
public function runCommand(array $unescaped_test_classnames, string $phpunit_file, int &$status = NULL, array &$output = NULL): string {
global $base_url;
// Setup an environment variable containing the database connection so that
// functional tests can connect to the database.
@ -184,8 +171,8 @@ class PhpUnitTestRunner implements ContainerInjectionInterface {
/**
* Executes PHPUnit tests and returns the results of the run.
*
* @param int $test_id
* The current test ID.
* @param \Drupal\Core\Test\TestRun $test_run
* The test run object.
* @param string[] $unescaped_test_classnames
* An array of test class names, including full namespaces, to be passed as
* a regular expression to PHPUnit's --filter option.
@ -199,18 +186,18 @@ class PhpUnitTestRunner implements ContainerInjectionInterface {
*
* @internal
*/
public function runTests($test_id, array $unescaped_test_classnames, &$status = NULL) {
$phpunit_file = $this->xmlLogFilePath($test_id);
public function execute(TestRun $test_run, array $unescaped_test_classnames, int &$status = NULL): array {
$phpunit_file = $this->xmlLogFilePath($test_run->id());
// Store output from our test run.
$output = [];
$this->runCommand($unescaped_test_classnames, $phpunit_file, $status, $output);
if ($status == TestStatus::PASS) {
return JUnitConverter::xmlToRows($test_id, $phpunit_file);
return JUnitConverter::xmlToRows($test_run->id(), $phpunit_file);
}
return [
[
'test_id' => $test_id,
'test_id' => $test_run->id(),
'test_class' => implode(",", $unescaped_test_classnames),
'status' => TestStatus::label($status),
'message' => 'PHPUnit Test failed to complete; Error: ' . implode("\n", $output),
@ -222,19 +209,35 @@ class PhpUnitTestRunner implements ContainerInjectionInterface {
];
}
/**
* Logs the parsed PHPUnit results into the test run.
*
* @param \Drupal\Core\Test\TestRun $test_run
* The test run object.
* @param array[] $phpunit_results
* An array of test results, as returned from
* \Drupal\Core\Test\JUnitConverter::xmlToRows(). Can be the return value of
* PhpUnitTestRunner::execute().
*/
public function processPhpUnitResults(TestRun $test_run, array $phpunit_results): void {
foreach ($phpunit_results as $result) {
$test_run->insertLogEntry($result);
}
}
/**
* Tallies test results per test class.
*
* @param string[][] $results
* Array of results in the {simpletest} schema. Can be the return value of
* PhpUnitTestRunner::runTests().
* PhpUnitTestRunner::execute().
*
* @return int[][]
* Array of status tallies, keyed by test class name and status type.
*
* @internal
*/
public function summarizeResults(array $results) {
public function summarizeResults(array $results): array {
$summaries = [];
foreach ($results as $result) {
if (!isset($summaries[$result['test_class']])) {

View File

@ -0,0 +1,272 @@
<?php
namespace Drupal\Core\Test;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\ConnectionNotDefinedException;
/**
* Implements a test run results storage compatible with legacy Simpletest.
*
* @internal
*/
class SimpletestTestRunResultsStorage implements TestRunResultsStorageInterface {
/**
* SimpletestTestRunResultsStorage constructor.
*
* @param \Drupal\Core\Database\Connection $connection
* The database connection to use for inserting assertions.
*/
public function __construct(
protected Connection $connection
) {
}
/**
* Returns the database connection to use for inserting assertions.
*
* @return \Drupal\Core\Database\Connection
* The database connection to use for inserting assertions.
*/
public static function getConnection(): Connection {
// Check whether there is a test runner connection.
// @see run-tests.sh
// @todo Convert Simpletest UI runner to create + use this connection, too.
try {
$connection = Database::getConnection('default', 'test-runner');
}
catch (ConnectionNotDefinedException $e) {
// Check whether there is a backup of the original default connection.
// @see FunctionalTestSetupTrait::prepareEnvironment()
try {
$connection = Database::getConnection('default', 'simpletest_original_default');
}
catch (ConnectionNotDefinedException $e) {
// If FunctionalTestSetupTrait::prepareEnvironment() failed, the
// test-specific database connection does not exist yet/anymore, so
// fall back to the default of the (UI) test runner.
$connection = Database::getConnection('default', 'default');
}
}
return $connection;
}
/**
* {@inheritdoc}
*/
public function createNew(): int|string {
return $this->connection->insert('simpletest_test_id')
->useDefaults(['test_id'])
->execute();
}
/**
* {@inheritdoc}
*/
public function setDatabasePrefix(TestRun $test_run, string $database_prefix): void {
$affected_rows = $this->connection->update('simpletest_test_id')
->fields(['last_prefix' => $database_prefix])
->condition('test_id', $test_run->id())
->execute();
if (!$affected_rows) {
throw new \RuntimeException('Failed to set up database prefix.');
}
}
/**
* {@inheritdoc}
*/
public function insertLogEntry(TestRun $test_run, array $entry): bool {
$entry['test_id'] = $test_run->id();
$entry = array_merge([
'function' => 'Unknown',
'line' => 0,
'file' => 'Unknown',
], $entry);
return (bool) $this->connection->insert('simpletest')
->fields($entry)
->execute();
}
/**
* {@inheritdoc}
*/
public function removeResults(TestRun $test_run): int {
$this->connection->startTransaction('delete_test_run');
$this->connection->delete('simpletest')
->condition('test_id', $test_run->id())
->execute();
$count = $this->connection->delete('simpletest_test_id')
->condition('test_id', $test_run->id())
->execute();
return $count;
}
/**
* {@inheritdoc}
*/
public function getLogEntriesByTestClass(TestRun $test_run): array {
return $this->connection->select('simpletest')
->fields('simpletest')
->condition('test_id', $test_run->id())
->orderBy('test_class')
->orderBy('message_id')
->execute()
->fetchAll();
}
/**
* {@inheritdoc}
*/
public function getCurrentTestRunState(TestRun $test_run): array {
// Define a subquery to identify the latest 'message_id' given the
// $test_id.
$max_message_id_subquery = $this->connection
->select('simpletest', 'sub')
->condition('test_id', $test_run->id());
$max_message_id_subquery->addExpression('MAX([message_id])', 'max_message_id');
// Run a select query to return 'last_prefix' from {simpletest_test_id} and
// 'test_class' from {simpletest}.
$select = $this->connection->select($max_message_id_subquery, 'st_sub');
$select->join('simpletest', 'st', '[st].[message_id] = [st_sub].[max_message_id]');
$select->join('simpletest_test_id', 'sttid', '[st].[test_id] = [sttid].[test_id]');
$select->addField('sttid', 'last_prefix', 'db_prefix');
$select->addField('st', 'test_class');
return $select->execute()->fetchAssoc();
}
/**
* {@inheritdoc}
*/
public function buildTestingResultsEnvironment(bool $keep_results): void {
$schema = $this->connection->schema();
foreach (static::testingResultsSchema() as $name => $table_spec) {
$table_exists = $schema->tableExists($name);
if (!$keep_results && $table_exists) {
$this->connection->truncate($name)->execute();
}
if (!$table_exists) {
$schema->createTable($name, $table_spec);
}
}
}
/**
* {@inheritdoc}
*/
public function validateTestingResultsEnvironment(): bool {
$schema = $this->connection->schema();
return $schema->tableExists('simpletest') && $schema->tableExists('simpletest_test_id');
}
/**
* {@inheritdoc}
*/
public function cleanUp(): int {
// Clear test results.
$this->connection->startTransaction('delete_simpletest');
$this->connection->delete('simpletest')->execute();
$count = $this->connection->delete('simpletest_test_id')->execute();
return $count;
}
/**
* Defines the database schema for run-tests.sh and simpletest module.
*
* @return array
* Array suitable for use in a hook_schema() implementation.
*/
public static function testingResultsSchema(): array {
$schema['simpletest'] = [
'description' => 'Stores simpletest messages',
'fields' => [
'message_id' => [
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique simpletest message ID.',
],
'test_id' => [
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Test ID, messages belonging to the same ID are reported together',
],
'test_class' => [
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'The name of the class that created this message.',
],
'status' => [
'type' => 'varchar',
'length' => 9,
'not null' => TRUE,
'default' => '',
'description' => 'Message status. Core understands pass, fail, exception.',
],
'message' => [
'type' => 'text',
'not null' => TRUE,
'description' => 'The message itself.',
],
'message_group' => [
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'The message group this message belongs to. For example: warning, browser, user.',
],
'function' => [
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Name of the assertion function or method that created this message.',
],
'line' => [
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Line number on which the function is called.',
],
'file' => [
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Name of the file where the function is called.',
],
],
'primary key' => ['message_id'],
'indexes' => [
'reporter' => ['test_class', 'message_id'],
],
];
$schema['simpletest_test_id'] = [
'description' => 'Stores simpletest test IDs, used to auto-increment the test ID so that a fresh test ID is used.',
'fields' => [
'test_id' => [
'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' => [
'type' => 'varchar',
'length' => 60,
'not null' => FALSE,
'default' => '',
'description' => 'The last database prefix used during testing.',
],
],
'primary key' => ['test_id'],
];
return $schema;
}
}

View File

@ -3,8 +3,6 @@
namespace Drupal\Core\Test;
use Drupal\Component\FileSystem\FileSystem;
use Drupal\Core\Database\ConnectionNotDefinedException;
use Drupal\Core\Database\Database;
/**
* Provides helper methods for interacting with the fixture database.
@ -28,36 +26,6 @@ class TestDatabase {
*/
protected $databasePrefix;
/**
* Returns the database connection to the site under test.
*
* @return \Drupal\Core\Database\Connection
* The database connection to use for inserting assertions.
*
* @see \Drupal\Core\Test\TestSetupTrait::getDatabaseConnection()
*/
public static function getConnection() {
// Check whether there is a test runner connection.
// @see run-tests.sh
try {
$connection = Database::getConnection('default', 'test-runner');
}
catch (ConnectionNotDefinedException $e) {
// Check whether there is a backup of the original default connection.
// @see \Drupal\Core\Test\TestSetupTrait::changeDatabasePrefix()
try {
$connection = Database::getConnection('default', 'simpletest_original_default');
}
catch (ConnectionNotDefinedException $e) {
// If TestBase::prepareEnvironment() or TestBase::restoreEnvironment()
// failed, the test-specific database connection does not exist
// yet/anymore, so fall back to the default of the (UI) test runner.
$connection = Database::getConnection('default', 'default');
}
}
return $connection;
}
/**
* TestDatabase constructor.
*
@ -71,7 +39,7 @@ class TestDatabase {
* @throws \InvalidArgumentException
* Thrown when $db_prefix does not match the regular expression.
*/
public function __construct($db_prefix = NULL, $create_lock = FALSE) {
public function __construct($db_prefix = NULL, bool $create_lock = FALSE) {
if ($db_prefix === NULL) {
$this->lockId = $this->getTestLock($create_lock);
$this->databasePrefix = 'test' . $this->lockId;
@ -95,7 +63,7 @@ class TestDatabase {
* @return string
* The relative path to the test site directory.
*/
public function getTestSitePath() {
public function getTestSitePath(): string {
return 'sites/simpletest/' . $this->lockId;
}
@ -105,7 +73,7 @@ class TestDatabase {
* @return string
* The test database prefix.
*/
public function getDatabasePrefix() {
public function getDatabasePrefix(): string {
return $this->databasePrefix;
}
@ -118,7 +86,7 @@ class TestDatabase {
* @return int
* The unique lock ID for the test method.
*/
protected function getTestLock($create_lock = FALSE) {
protected function getTestLock(bool $create_lock = FALSE): int {
// There is a risk that the generated random number is a duplicate. This
// would cause different tests to try to use the same database prefix.
// Therefore, if running with a concurrency of greater than 1, we need to
@ -144,7 +112,7 @@ class TestDatabase {
* @return bool
* TRUE if successful, FALSE if not.
*/
public function releaseLock() {
public function releaseLock(): bool {
return unlink($this->getLockFile($this->lockId));
}
@ -153,7 +121,7 @@ class TestDatabase {
*
* This should only be called once all the test fixtures have been cleaned up.
*/
public static function releaseAllTestLocks() {
public static function releaseAllTestLocks(): void {
$tmp = FileSystem::getOsTemporaryDirectory();
$dir = dir($tmp);
while (($entry = $dir->read()) !== FALSE) {
@ -176,256 +144,18 @@ class TestDatabase {
* @return string
* A file path to the symbolic link that prevents the lock ID being re-used.
*/
protected function getLockFile($lock_id) {
protected function getLockFile(int $lock_id): string {
return FileSystem::getOsTemporaryDirectory() . '/test_' . $lock_id;
}
/**
* Store an assertion from outside the testing context.
* Gets the file path of the PHP error log of the test.
*
* This is useful for inserting assertions that can only be recorded after
* the test case has been destroyed, such as PHP fatal errors. The caller
* information is not automatically gathered since the caller is most likely
* inserting the assertion on behalf of other code.
*
* @param string $test_id
* The test ID to which the assertion relates.
* @param string $test_class
* The test class to store an assertion for.
* @param bool|string $status
* A boolean or a string of 'pass' or 'fail'. TRUE means 'pass'.
* @param string $message
* The assertion message.
* @param string $group
* The assertion message group.
* @param array $caller
* The an array containing the keys 'file' and 'line' that represent the
* file and line number of that file that is responsible for the assertion.
*
* @return int
* Message ID of the stored assertion.
*
* @internal
* @return string
* The relative path to the test site PHP error log file.
*/
public static function insertAssert($test_id, $test_class, $status, $message = '', $group = 'Other', array $caller = []) {
// Convert boolean status to string status.
if (is_bool($status)) {
$status = $status ? 'pass' : 'fail';
}
$caller += [
'function' => 'Unknown',
'line' => 0,
'file' => 'Unknown',
];
$assertion = [
'test_id' => $test_id,
'test_class' => $test_class,
'status' => $status,
'message' => $message,
'message_group' => $group,
'function' => $caller['function'],
'line' => $caller['line'],
'file' => $caller['file'],
];
return static::getConnection()
->insert('simpletest')
->fields($assertion)
->execute();
}
/**
* Get information about the last test that ran given a test ID.
*
* @param int $test_id
* The test ID to get the last test from.
*
* @return array
* Associative array containing the last database prefix used and the
* last test class that ran.
*
* @internal
*/
public static function lastTestGet($test_id) {
$connection = static::getConnection();
// Define a subquery to identify the latest 'message_id' given the
// $test_id.
$max_message_id_subquery = $connection
->select('simpletest', 'sub')
->condition('test_id', $test_id);
$max_message_id_subquery->addExpression('MAX([message_id])', 'max_message_id');
// Run a select query to return 'last_prefix' from {simpletest_test_id} and
// 'test_class' from {simpletest}.
$select = $connection->select($max_message_id_subquery, 'st_sub');
$select->join('simpletest', 'st', '[st].[message_id] = [st_sub].[max_message_id]');
$select->join('simpletest_test_id', 'sttid', '[st].[test_id] = [sttid].[test_id]');
$select->addField('sttid', 'last_prefix');
$select->addField('st', 'test_class');
return $select->execute()->fetchAssoc();
}
/**
* 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 int $test_id
* The test ID to which the log relates.
* @param string $test_class
* The test class to which the log relates.
*
* @return bool
* Whether any fatal errors were found.
*
* @internal
*/
public function logRead($test_id, $test_class) {
$log = DRUPAL_ROOT . '/' . $this->getTestSitePath() . '/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 = [
'line' => $match[4],
'file' => $match[3],
];
static::insertAssert($test_id, $test_class, FALSE, $match[2], $match[1], $caller);
}
else {
// Unknown format, place the entire message in the log.
static::insertAssert($test_id, $test_class, FALSE, $line, 'Fatal error');
}
$found = TRUE;
}
}
return $found;
}
/**
* Defines the database schema for run-tests.sh and PHPUnit tests.
*
* @return array
* Array suitable for use in a hook_schema() implementation.
*
* @internal
*/
public static function testingSchema() {
$schema['simpletest'] = [
'description' => 'Stores test messages',
'fields' => [
'message_id' => [
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique test message ID.',
],
'test_id' => [
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Test ID, messages belonging to the same ID are reported together',
],
'test_class' => [
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'The name of the class that created this message.',
],
'status' => [
'type' => 'varchar',
'length' => 9,
'not null' => TRUE,
'default' => '',
'description' => 'Message status. Core understands pass, fail, exception.',
],
'message' => [
'type' => 'text',
'not null' => TRUE,
'description' => 'The message itself.',
],
'message_group' => [
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'The message group this message belongs to. For example: warning, browser, user.',
],
'function' => [
'type' => 'varchar_ascii',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Name of the assertion function or method that created this message.',
],
'line' => [
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'description' => 'Line number on which the function is called.',
],
'file' => [
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Name of the file where the function is called.',
],
],
'primary key' => ['message_id'],
'indexes' => [
'reporter' => ['test_class', 'message_id'],
],
];
$schema['simpletest_test_id'] = [
'description' => 'Stores test IDs, used to auto-increment the test ID so that a fresh test ID is used.',
'fields' => [
'test_id' => [
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique test ID used to group test results together. Each time a set of tests
are run a new test ID is used.',
],
'last_prefix' => [
'type' => 'varchar',
'length' => 60,
'not null' => FALSE,
'default' => '',
'description' => 'The last database prefix used during testing.',
],
],
'primary key' => ['test_id'],
];
return $schema;
}
/**
* Inserts the parsed PHPUnit results into {simpletest}.
*
* @param array[] $phpunit_results
* An array of test results, as returned from
* \Drupal\Core\Test\JUnitConverter::xmlToRows(). These results are in a
* form suitable for inserting into the {simpletest} table of the test
* results database.
*
* @internal
*/
public static function processPhpUnitResults($phpunit_results) {
if ($phpunit_results) {
$query = static::getConnection()
->insert('simpletest')
->fields(array_keys($phpunit_results[0]));
foreach ($phpunit_results as $result) {
$query->values($result);
}
$query->execute();
}
public function getPhpErrorLogPath(): string {
return $this->getTestSitePath() . '/error.log';
}
}

View File

@ -0,0 +1,202 @@
<?php
namespace Drupal\Core\Test;
/**
* Implements an object that tracks execution of a test run.
*
* @internal
*/
class TestRun {
/**
* The test database prefix.
*
* @var string
*/
protected $databasePrefix;
/**
* The latest class under test.
*
* @var string
*/
protected $testClass;
/**
* TestRun constructor.
*
* @param \Drupal\Core\Test\TestRunResultsStorageInterface $testRunResultsStorage
* The test run results storage.
* @param int|string $testId
* A unique test run id.
*/
public function __construct(
protected TestRunResultsStorageInterface $testRunResultsStorage,
protected int|string $testId
) {
}
/**
* Returns a new test run object.
*
* @param \Drupal\Core\Test\TestRunResultsStorageInterface $test_run_results_storage
* The test run results storage.
*
* @return self
* The new test run object.
*/
public static function createNew(TestRunResultsStorageInterface $test_run_results_storage): TestRun {
$test_id = $test_run_results_storage->createNew();
return new static($test_run_results_storage, $test_id);
}
/**
* Returns a test run object from storage.
*
* @param \Drupal\Core\Test\TestRunResultsStorageInterface $test_run_results_storage
* The test run results storage.
* @param int|string $test_id
* The test run id.
*
* @return self
* The test run object.
*/
public static function get(TestRunResultsStorageInterface $test_run_results_storage, int|string $test_id): TestRun {
return new static($test_run_results_storage, $test_id);
}
/**
* Returns the id of the test run object.
*
* @return int|string
* The id of the test run object.
*/
public function id(): int|string {
return $this->testId;
}
/**
* Sets the test database prefix.
*
* @param string $database_prefix
* The database prefix.
*
* @throws \RuntimeException
* If the database prefix cannot be saved to storage.
*/
public function setDatabasePrefix(string $database_prefix): void {
$this->databasePrefix = $database_prefix;
$this->testRunResultsStorage->setDatabasePrefix($this, $database_prefix);
}
/**
* Gets the test database prefix.
*
* @return string
* The database prefix.
*/
public function getDatabasePrefix(): string {
if (is_null($this->databasePrefix)) {
$state = $this->testRunResultsStorage->getCurrentTestRunState($this);
$this->databasePrefix = $state['db_prefix'];
$this->testClass = $state['test_class'];
}
return $this->databasePrefix;
}
/**
* Gets the latest class under test.
*
* @return string
* The test class.
*/
public function getTestClass(): string {
if (is_null($this->testClass)) {
$state = $this->testRunResultsStorage->getCurrentTestRunState($this);
$this->databasePrefix = $state['db_prefix'];
$this->testClass = $state['test_class'];
}
return $this->testClass;
}
/**
* Adds a test log entry.
*
* @param array $entry
* The array of the log entry elements.
*
* @return bool
* TRUE if the addition was successful, FALSE otherwise.
*/
public function insertLogEntry(array $entry): bool {
$this->testClass = $entry['test_class'];
return $this->testRunResultsStorage->insertLogEntry($this, $entry);
}
/**
* Get test results for a test run, ordered by test class.
*
* @return array
* Array of results ordered by test class and message id.
*/
public function getLogEntriesByTestClass(): array {
return $this->testRunResultsStorage->getLogEntriesByTestClass($this);
}
/**
* Removes the test results from the storage.
*
* @return int
* The number of log entries that were removed from storage.
*/
public function removeResults(): int {
return $this->testRunResultsStorage->removeResults($this);
}
/**
* Reads the PHP 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 string $error_log_path
* The path of log file.
* @param string $test_class
* The test class to which the log relates.
*
* @return bool
* Whether any fatal errors were found.
*/
public function processPhpErrorLogFile(string $error_log_path, string $test_class): bool {
$found = FALSE;
if (file_exists($error_log_path)) {
foreach (file($error_log_path) 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
$this->insertLogEntry([
'test_class' => $test_class,
'status' => 'fail',
'message' => $match[2],
'message_group' => $match[1],
'line' => $match[4],
'file' => $match[3],
]);
}
else {
// Unknown format, place the entire message in the log.
$this->insertLogEntry([
'test_class' => $test_class,
'status' => 'fail',
'message' => $line,
'message_group' => 'Fatal error',
]);
}
$found = TRUE;
}
}
return $found;
}
}

View File

@ -0,0 +1,106 @@
<?php
namespace Drupal\Core\Test;
/**
* Interface describing a test run results storage object.
*
* @internal
*/
interface TestRunResultsStorageInterface {
/**
* Gets a new unique identifier for a test run.
*
* @return int|string
* A unique identifier.
*/
public function createNew(): int|string;
/**
* Sets the test database prefix of a test run in storage.
*
* @param \Drupal\Core\Test\TestRun $test_run
* The test run object.
* @param string $database_prefix
* The database prefix.
*
* @throws \RuntimeException
* If the operation failed.
*/
public function setDatabasePrefix(TestRun $test_run, string $database_prefix): void;
/**
* Adds a test log entry for a test run to the storage.
*
* @param \Drupal\Core\Test\TestRun $test_run
* The test run object.
* @param array $entry
* The array of the log entry elements.
*
* @return bool
* TRUE if the addition was successful, FALSE otherwise.
*/
public function insertLogEntry(TestRun $test_run, array $entry): bool;
/**
* Removes the results of a test run from the storage.
*
* @param \Drupal\Core\Test\TestRun $test_run
* The test run object.
*
* @return int
* The number of log entries that were removed from storage.
*/
public function removeResults(TestRun $test_run): int;
/**
* Get test results for a test run, ordered by test class.
*
* @param \Drupal\Core\Test\TestRun $test_run
* The test run object.
*
* @return array
* Array of results ordered by test class and message id.
*/
public function getLogEntriesByTestClass(TestRun $test_run): array;
/**
* Get state information about a test run, from storage.
*
* @param \Drupal\Core\Test\TestRun $test_run
* The test run object.
*
* @return array
* Array of state information, for example 'last_prefix' and 'test_class'.
*/
public function getCurrentTestRunState(TestRun $test_run): array;
/**
* Prepares the test run storage.
*
* @param bool $keep_results
* If TRUE, any pre-existing storage will be preserved; if FALSE,
* pre-existing storage will be cleaned up.
*/
public function buildTestingResultsEnvironment(bool $keep_results): void;
/**
* Checks if the test run storage is valid.
*
* @return bool
* TRUE when the storage is valid and ready for use, FALSE otherwise.
*
* @see ::buildTestingResultsEnvironment()
*/
public function validateTestingResultsEnvironment(): bool;
/**
* Resets the test run storage.
*
* @return int
* The number of log entries that were removed from storage.
*/
public function cleanUp(): int;
}

View File

@ -119,9 +119,15 @@ trait TestSetupTrait {
*
* @return \Drupal\Core\Database\Connection
* The database connection to use for inserting assertions.
*
* @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no
* replacement.
*
* @see https://www.drupal.org/node/3176816
*/
public static function getDatabaseConnection() {
return TestDatabase::getConnection();
@trigger_error(__METHOD__ . ' is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3176816', E_USER_DEPRECATED);
return SimpletestTestRunResultsStorage::getConnection();
}
/**

View File

@ -18,9 +18,12 @@ use Drupal\Core\Composer\Composer;
use Drupal\Core\Database\Database;
use Drupal\Core\Test\EnvironmentCleaner;
use Drupal\Core\Test\PhpUnitTestRunner;
use Drupal\Core\Test\SimpletestTestRunResultsStorage;
use Drupal\Core\Test\RunTests\TestFileParser;
use Drupal\Core\Test\TestDatabase;
use Drupal\Core\Test\TestRun;
use Drupal\Core\Test\TestRunnerKernel;
use Drupal\Core\Test\TestRunResultsStorageInterface;
use Drupal\Core\Test\TestDiscovery;
use Drupal\TestTools\PhpUnitCompatibility\ClassWriter;
use PHPUnit\Framework\TestCase;
@ -60,7 +63,9 @@ if (!class_exists(TestCase::class)) {
if ($args['execute-test']) {
simpletest_script_setup_database();
simpletest_script_run_one_test($args['test-id'], $args['execute-test']);
$test_run_results_storage = simpletest_script_setup_test_run_results_storage();
$test_run = TestRun::get($test_run_results_storage, $args['test-id']);
simpletest_script_run_one_test($test_run, $args['execute-test']);
// Sub-process exited already; this is just for clarity.
exit(SIMPLETEST_SCRIPT_EXIT_SUCCESS);
}
@ -125,12 +130,16 @@ if ($args['list-files'] || $args['list-files-json']) {
simpletest_script_setup_database(TRUE);
// Setup the test run results storage environment. Currently, this coincides
// with the simpletest database schema.
$test_run_results_storage = simpletest_script_setup_test_run_results_storage(TRUE);
if ($args['clean']) {
// Clean up left-over tables and directories.
$cleaner = new EnvironmentCleaner(
DRUPAL_ROOT,
Database::getConnection(),
TestDatabase::getConnection(),
$test_run_results_storage,
new ConsoleOutput(),
\Drupal::service('file_system')
);
@ -168,7 +177,7 @@ for ($i = 0; $i < $args['repeat']; $i++) {
}
// Execute tests.
$status = simpletest_script_execute_batch($tests_to_run);
$status = simpletest_script_execute_batch($test_run_results_storage, $tests_to_run);
// Stop the timer.
simpletest_script_reporter_timer_stop();
@ -180,10 +189,10 @@ simpletest_script_reporter_timer_stop();
TestDatabase::releaseAllTestLocks();
// Display results before database is cleared.
simpletest_script_reporter_display_results();
simpletest_script_reporter_display_results($test_run_results_storage);
if ($args['xml']) {
simpletest_script_reporter_write_xml_results();
simpletest_script_reporter_write_xml_results($test_run_results_storage);
}
// Clean up all test results.
@ -192,11 +201,11 @@ if (!$args['keep-results']) {
$cleaner = new EnvironmentCleaner(
DRUPAL_ROOT,
Database::getConnection(),
TestDatabase::getConnection(),
$test_run_results_storage,
new ConsoleOutput(),
\Drupal::service('file_system')
);
$cleaner->cleanResultsTable();
$cleaner->cleanResults();
}
catch (Exception $e) {
echo (string) $e;
@ -628,6 +637,15 @@ function simpletest_script_setup_database($new = FALSE) {
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
Database::addConnectionInfo('default', 'default', $databases['default']['default']);
}
/**
* Sets up the test runs results storage.
*/
function simpletest_script_setup_test_run_results_storage($new = FALSE) {
global $args;
$databases['default'] = Database::getConnectionInfo('default');
// If no --sqlite parameter has been passed, then the test runner database
// connection is the default database connection.
@ -662,33 +680,24 @@ function simpletest_script_setup_database($new = FALSE) {
// Create the test result schema.
try {
$connection = Database::getConnection('default', 'test-runner');
$schema = $connection->schema();
$test_run_results_storage = new SimpletestTestRunResultsStorage(Database::getConnection('default', 'test-runner'));
}
catch (\PDOException $e) {
simpletest_script_print_error($databases['test-runner']['default']['driver'] . ': ' . $e->getMessage());
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
if ($new && $sqlite) {
foreach (TestDatabase::testingSchema() as $name => $table_spec) {
try {
$table_exists = $schema->tableExists($name);
if (empty($args['keep-results-table']) && $table_exists) {
$connection->truncate($name)->execute();
}
if (!$table_exists) {
$schema->createTable($name, $table_spec);
}
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}
try {
$test_run_results_storage->buildTestingResultsEnvironment(!empty($args['keep-results-table']));
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}
}
// Verify that the test result database schema exists by checking one table.
try {
if (!$schema->tableExists('simpletest')) {
if (!$test_run_results_storage->validateTestingResultsEnvironment()) {
simpletest_script_print_error('Missing test result database schema. Use the --sqlite parameter.');
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
}
@ -697,12 +706,14 @@ function simpletest_script_setup_database($new = FALSE) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}
return $test_run_results_storage;
}
/**
* Execute a batch of tests.
*/
function simpletest_script_execute_batch($test_classes) {
function simpletest_script_execute_batch(TestRunResultsStorageInterface $test_run_results_storage, $test_classes) {
global $args, $test_ids;
$total_status = SIMPLETEST_SCRIPT_EXIT_SUCCESS;
@ -716,20 +727,17 @@ function simpletest_script_execute_batch($test_classes) {
}
try {
$test_id = Database::getConnection('default', 'test-runner')
->insert('simpletest_test_id')
->useDefaults(['test_id'])
->execute();
$test_run = TestRun::createNew($test_run_results_storage);
}
catch (Exception $e) {
echo (string) $e;
exit(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
}
$test_ids[] = $test_id;
$test_ids[] = $test_run->id();
$test_class = array_shift($test_classes);
// Fork a child process.
$command = simpletest_script_command($test_id, $test_class);
$command = simpletest_script_command($test_run, $test_class);
$process = proc_open($command, [], $pipes, NULL, NULL, ['bypass_shell' => TRUE]);
if (!is_resource($process)) {
@ -740,7 +748,7 @@ function simpletest_script_execute_batch($test_classes) {
// Register our new child.
$children[] = [
'process' => $process,
'test_id' => $test_id,
'test_run' => $test_run,
'class' => $test_class,
'pipes' => $pipes,
];
@ -766,17 +774,21 @@ function simpletest_script_execute_batch($test_classes) {
// @see https://www.drupal.org/node/2780087
$total_status = max(SIMPLETEST_SCRIPT_EXIT_FAILURE, $total_status);
// Insert a fail for xml results.
TestDatabase::insertAssert($child['test_id'], $child['class'], FALSE, $message, 'run-tests.sh check');
$child['test_run']->insertLogEntry([
'test_class' => $child['class'],
'status' => 'fail',
'message' => $message,
'message_group' => 'run-tests.sh check',
]);
// Ensure that an error line is displayed for the class.
simpletest_script_reporter_display_summary(
$child['class'],
['#pass' => 0, '#fail' => 1, '#exception' => 0, '#debug' => 0]
);
if ($args['die-on-fail']) {
$db_prefix = TestDatabase::lastTestGet($child['test_id'])['last_prefix'];
$test_db = new TestDatabase($db_prefix);
$test_db = new TestDatabase($child['test_run']->getDatabasePrefix());
$test_directory = $test_db->getTestSitePath();
echo 'Test database and files kept and test exited immediately on fail so should be reproducible if you change settings.php to use the database prefix ' . $db_prefix . ' and config directories in ' . $test_directory . "\n";
echo 'Test database and files kept and test exited immediately on fail so should be reproducible if you change settings.php to use the database prefix ' . $child['test_run']->getDatabasePrefix() . ' and config directories in ' . $test_directory . "\n";
$args['keep-results'] = TRUE;
// Exit repeat loop immediately.
$args['repeat'] = -1;
@ -794,15 +806,15 @@ function simpletest_script_execute_batch($test_classes) {
/**
* Run a PHPUnit-based test.
*/
function simpletest_script_run_phpunit($test_id, $class) {
function simpletest_script_run_phpunit(TestRun $test_run, $class) {
$reflection = new \ReflectionClass($class);
if ($reflection->hasProperty('runLimit')) {
set_time_limit($reflection->getStaticPropertyValue('runLimit'));
}
$runner = PhpUnitTestRunner::create(\Drupal::getContainer());
$results = $runner->runTests($test_id, [$class], $status);
TestDatabase::processPhpUnitResults($results);
$results = $runner->execute($test_run, [$class], $status);
$runner->processPhpUnitResults($test_run, $results);
$summaries = $runner->summarizeResults($results);
foreach ($summaries as $class => $summary) {
@ -814,14 +826,14 @@ function simpletest_script_run_phpunit($test_id, $class) {
/**
* Run a single test, bootstrapping Drupal if needed.
*/
function simpletest_script_run_one_test($test_id, $test_class) {
function simpletest_script_run_one_test(TestRun $test_run, $test_class) {
global $args;
try {
if ($args['suppress-deprecations']) {
putenv('SYMFONY_DEPRECATIONS_HELPER=disabled');
}
$status = simpletest_script_run_phpunit($test_id, $test_class);
$status = simpletest_script_run_phpunit($test_run, $test_class);
exit($status);
}
// DrupalTestCase::run() catches exceptions already, so this is only reached
@ -843,7 +855,7 @@ function simpletest_script_run_one_test($test_id, $test_class) {
* @return string
* The assembled command string.
*/
function simpletest_script_command($test_id, $test_class) {
function simpletest_script_command(TestRun $test_run, $test_class) {
global $args, $php;
$command = escapeshellarg($php) . ' ' . escapeshellarg('./core/scripts/' . $args['script']);
@ -855,7 +867,7 @@ function simpletest_script_command($test_id, $test_class) {
$command .= ' --dburl ' . escapeshellarg($args['dburl']);
}
$command .= ' --php ' . escapeshellarg($php);
$command .= " --test-id $test_id";
$command .= " --test-id {$test_run->id()}";
foreach (['verbose', 'keep-results', 'color', 'die-on-fail', 'suppress-deprecations'] as $arg) {
if ($args[$arg]) {
$command .= ' --' . $arg;
@ -1081,11 +1093,11 @@ function simpletest_script_reporter_display_summary($class, $results) {
/**
* Display jUnit XML test results.
*/
function simpletest_script_reporter_write_xml_results() {
function simpletest_script_reporter_write_xml_results(TestRunResultsStorageInterface $test_run_results_storage) {
global $args, $test_ids, $results_map;
try {
$results = simpletest_script_load_messages_by_test_id($test_ids);
$results = simpletest_script_load_messages_by_test_id($test_run_results_storage, $test_ids);
}
catch (Exception $e) {
echo (string) $e;
@ -1174,7 +1186,7 @@ function simpletest_script_reporter_timer_stop() {
/**
* Display test results.
*/
function simpletest_script_reporter_display_results() {
function simpletest_script_reporter_display_results(TestRunResultsStorageInterface $test_run_results_storage) {
global $args, $test_ids, $results_map;
if ($args['verbose']) {
@ -1183,7 +1195,7 @@ function simpletest_script_reporter_display_results() {
echo "---------------------\n";
try {
$results = simpletest_script_load_messages_by_test_id($test_ids);
$results = simpletest_script_load_messages_by_test_id($test_run_results_storage, $test_ids);
}
catch (Exception $e) {
echo (string) $e;
@ -1333,7 +1345,7 @@ function simpletest_script_print_alternatives($string, $array, $degree = 4) {
* @return array
* Array of test result messages from the database.
*/
function simpletest_script_load_messages_by_test_id($test_ids) {
function simpletest_script_load_messages_by_test_id(TestRunResultsStorageInterface $test_run_results_storage, $test_ids) {
global $args;
$results = [];
@ -1348,10 +1360,11 @@ function simpletest_script_load_messages_by_test_id($test_ids) {
foreach ($test_id_chunks as $test_id_chunk) {
try {
$result_chunk = Database::getConnection('default', 'test-runner')
->query("SELECT * FROM {simpletest} WHERE [test_id] IN ( :test_ids[] ) ORDER BY [test_class], [message_id]", [
':test_ids[]' => $test_id_chunk,
])->fetchAll();
$result_chunk = [];
foreach ($test_id_chunk as $test_id) {
$test_run = TestRun::get($test_run_results_storage, $test_id);
$result_chunk = array_merge($result_chunk, $test_run->getLogEntriesByTestClass());
}
}
catch (Exception $e) {
echo (string) $e;

View File

@ -4,6 +4,7 @@ namespace Drupal\KernelTests\Core\Test;
use Drupal\Core\Database\Connection;
use Drupal\Core\Test\EnvironmentCleaner;
use Drupal\Core\Test\TestRunResultsStorageInterface;
use Drupal\KernelTests\KernelTestBase;
use org\bovigo\vfs\vfsStream;
use Symfony\Component\Console\Output\NullOutput;
@ -30,11 +31,12 @@ class EnvironmentCleanerTest extends KernelTestBase {
]);
$connection = $this->prophesize(Connection::class);
$test_run_results_storage = $this->prophesize(TestRunResultsStorageInterface::class);
$cleaner = new EnvironmentCleaner(
vfsStream::url('cleanup_test'),
$connection->reveal(),
$connection->reveal(),
$test_run_results_storage->reveal(),
new NullOutput(),
\Drupal::service('file_system')
);

View File

@ -0,0 +1,188 @@
<?php
namespace Drupal\KernelTests\Core\Test;
use Drupal\Core\Database\Database;
use Drupal\Core\Test\TestRun;
use Drupal\Core\Test\SimpletestTestRunResultsStorage;
use Drupal\KernelTests\KernelTestBase;
/**
* @coversDefaultClass \Drupal\Core\Test\SimpletestTestRunResultsStorage
* @group Test
*/
class SimpletestTestRunResultsStorageTest extends KernelTestBase {
/**
* The database connection for testing.
*
* NOTE: this is the connection to the fixture database to allow testing the
* storage class, NOT the database where actual tests results are stored.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* The test run results storage.
*
* @var \Drupal\Core\Test\TestRunResultsStorageInterface
*/
protected $testRunResultsStorage;
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->connection = Database::getConnection();
$this->testRunResultsStorage = new SimpletestTestRunResultsStorage($this->connection);
}
/**
* @covers ::buildTestingResultsEnvironment
* @covers ::validateTestingResultsEnvironment
*/
public function testBuildNewEnvironment(): void {
$schema = $this->connection->schema();
$this->assertFalse($schema->tableExists('simpletest'));
$this->assertFalse($schema->tableExists('simpletest_test_id'));
$this->assertFalse($this->testRunResultsStorage->validateTestingResultsEnvironment());
$this->testRunResultsStorage->buildTestingResultsEnvironment(FALSE);
$this->assertTrue($schema->tableExists('simpletest'));
$this->assertTrue($schema->tableExists('simpletest_test_id'));
$this->assertTrue($this->testRunResultsStorage->validateTestingResultsEnvironment());
}
/**
* @covers ::buildTestingResultsEnvironment
* @covers ::validateTestingResultsEnvironment
* @covers ::createNew
* @covers ::insertLogEntry
* @covers ::cleanUp
*/
public function testBuildEnvironmentKeepingExistingResults(): void {
$schema = $this->connection->schema();
// Initial build of the environment.
$this->testRunResultsStorage->buildTestingResultsEnvironment(FALSE);
$this->assertEquals(1, $this->testRunResultsStorage->createNew());
$test_run = TestRun::get($this->testRunResultsStorage, 1);
$this->assertEquals(1, $this->testRunResultsStorage->insertLogEntry($test_run, $this->getTestLogEntry('Test\GroundControl')));
$this->assertEquals(1, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(1, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
// Build the environment again, keeping results. Results should be kept.
$this->testRunResultsStorage->buildTestingResultsEnvironment(TRUE);
$this->assertTrue($schema->tableExists('simpletest'));
$this->assertTrue($schema->tableExists('simpletest_test_id'));
$this->assertTrue($this->testRunResultsStorage->validateTestingResultsEnvironment());
$this->assertEquals(1, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(1, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
$this->assertEquals(2, $this->testRunResultsStorage->createNew());
$test_run = TestRun::get($this->testRunResultsStorage, 2);
$this->assertEquals(2, $this->testRunResultsStorage->insertLogEntry($test_run, $this->getTestLogEntry('Test\GroundControl')));
$this->assertEquals(2, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(2, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
// Cleanup the environment.
$this->assertEquals(2, $this->testRunResultsStorage->cleanUp());
$this->assertEquals(0, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(0, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
}
/**
* @covers ::buildTestingResultsEnvironment
* @covers ::createNew
* @covers ::insertLogEntry
* @covers ::setDatabasePrefix
* @covers ::removeResults
*/
public function testGetCurrentTestRunState(): void {
$this->testRunResultsStorage->buildTestingResultsEnvironment(FALSE);
$this->assertEquals(1, $this->testRunResultsStorage->createNew());
$test_run_1 = TestRun::get($this->testRunResultsStorage, 1);
$this->testRunResultsStorage->setDatabasePrefix($test_run_1, 'oddity1234');
$this->assertEquals(1, $this->testRunResultsStorage->insertLogEntry($test_run_1, $this->getTestLogEntry('Test\GroundControl')));
$this->assertEquals([
'db_prefix' => 'oddity1234',
'test_class' => 'Test\GroundControl',
], $this->testRunResultsStorage->getCurrentTestRunState($test_run_1));
// Add another test run.
$this->assertEquals(2, $this->testRunResultsStorage->createNew());
$test_run_2 = TestRun::get($this->testRunResultsStorage, 2);
$this->assertEquals(2, $this->testRunResultsStorage->insertLogEntry($test_run_2, $this->getTestLogEntry('Test\GroundControl')));
// Remove test run 1 results.
$this->assertEquals(1, $this->testRunResultsStorage->removeResults($test_run_1));
$this->assertEquals(1, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(1, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
}
/**
* @covers ::buildTestingResultsEnvironment
* @covers ::createNew
* @covers ::insertLogEntry
* @covers ::setDatabasePrefix
* @covers ::getLogEntriesByTestClass
*/
public function testGetLogEntriesByTestClass(): void {
$this->testRunResultsStorage->buildTestingResultsEnvironment(FALSE);
$this->assertEquals(1, $this->testRunResultsStorage->createNew());
$test_run = TestRun::get($this->testRunResultsStorage, 1);
$this->testRunResultsStorage->setDatabasePrefix($test_run, 'oddity1234');
$this->assertEquals(1, $this->testRunResultsStorage->insertLogEntry($test_run, $this->getTestLogEntry('Test\PlanetEarth')));
$this->assertEquals(2, $this->testRunResultsStorage->insertLogEntry($test_run, $this->getTestLogEntry('Test\GroundControl')));
$this->assertEquals([
0 => (object) [
'message_id' => 2,
'test_id' => 1,
'test_class' => 'Test\GroundControl',
'status' => 'pass',
'message' => 'Major Tom',
'message_group' => 'other',
'function' => 'Unknown',
'line' => 0,
'file' => 'Unknown',
],
1 => (object) [
'message_id' => 1,
'test_id' => 1,
'test_class' => 'Test\PlanetEarth',
'status' => 'pass',
'message' => 'Major Tom',
'message_group' => 'other',
'function' => 'Unknown',
'line' => 0,
'file' => 'Unknown',
],
], $this->testRunResultsStorage->getLogEntriesByTestClass($test_run));
}
/**
* Returns a sample test run log entry.
*
* @param string $test_class
* The test class.
*
* @return string[]
* An array with the elements to be logged.
*/
protected function getTestLogEntry(string $test_class): array {
return [
'test_class' => $test_class,
'status' => 'pass',
'message' => 'Major Tom',
'message_group' => 'other',
];
}
}

View File

@ -0,0 +1,293 @@
<?php
namespace Drupal\KernelTests\Core\Test;
use Drupal\Core\Database\Database;
use Drupal\Core\Test\JUnitConverter;
use Drupal\Core\Test\PhpUnitTestRunner;
use Drupal\Core\Test\TestRun;
use Drupal\Core\Test\SimpletestTestRunResultsStorage;
use Drupal\KernelTests\KernelTestBase;
/**
* @coversDefaultClass \Drupal\Core\Test\TestRun
* @group Test
*/
class TestRunTest extends KernelTestBase {
/**
* The database connection for testing.
*
* NOTE: this is the connection to the fixture database to allow testing the
* storage class, NOT the database where actual tests results are stored.
*
* @var \Drupal\Core\Database\Connection
*/
protected $connection;
/**
* The test run results storage.
*
* @var \Drupal\Core\Test\TestRunResultsStorageInterface
*/
protected $testRunResultsStorage;
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->connection = Database::getConnection();
$this->testRunResultsStorage = new SimpletestTestRunResultsStorage($this->connection);
$this->testRunResultsStorage->buildTestingResultsEnvironment(FALSE);
}
/**
* @covers ::createNew
* @covers ::get
* @covers ::id
* @covers ::insertLogEntry
* @covers ::setDatabasePrefix
* @covers ::getDatabasePrefix
* @covers ::getTestClass
*/
public function testCreateAndGet(): void {
// Test ::createNew.
$test_run = TestRun::createNew($this->testRunResultsStorage);
$this->assertEquals(1, $test_run->id());
$this->assertEquals(0, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(1, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
$test_run->setDatabasePrefix('oddity1234');
$this->assertEquals('oddity1234', $test_run->getDatabasePrefix());
$this->assertEquals('oddity1234', $this->connection->select('simpletest_test_id', 's')->fields('s', ['last_prefix'])->execute()->fetchField());
$this->assertEquals(1, $test_run->insertLogEntry($this->getTestLogEntry('Test\GroundControl')));
$this->assertEquals('oddity1234', $test_run->getDatabasePrefix());
$this->assertEquals('Test\GroundControl', $test_run->getTestClass());
$this->assertEquals(1, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(1, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
// Explicitly void the $test_run variable.
$test_run = NULL;
// Test ::get.
$test_run = TestRun::get($this->testRunResultsStorage, 1);
$this->assertEquals(1, $test_run->id());
$this->assertEquals('oddity1234', $test_run->getDatabasePrefix());
$this->assertEquals('Test\GroundControl', $test_run->getTestClass());
}
/**
* @covers ::createNew
* @covers ::id
* @covers ::insertLogEntry
* @covers ::setDatabasePrefix
*/
public function testCreateAndRemove(): void {
$test_run_1 = TestRun::createNew($this->testRunResultsStorage);
$test_run_1->setDatabasePrefix('oddity1234');
$test_run_1->insertLogEntry($this->getTestLogEntry('Test\GroundControl'));
$this->assertEquals(1, $test_run_1->id());
$this->assertEquals(1, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(1, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
$test_run_2 = TestRun::createNew($this->testRunResultsStorage);
$test_run_2->setDatabasePrefix('oddity5678');
$test_run_2->insertLogEntry($this->getTestLogEntry('Test\PlanetEarth'));
$this->assertEquals(2, $test_run_2->id());
$this->assertEquals(2, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(2, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
$this->assertEquals(1, $test_run_1->removeResults());
$this->assertEquals(1, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
$this->assertEquals(1, $this->connection->select('simpletest_test_id')->countQuery()->execute()->fetchField());
}
/**
* @covers ::createNew
* @covers ::insertLogEntry
* @covers ::setDatabasePrefix
* @covers ::getLogEntriesByTestClass
* @covers ::getDatabasePrefix
* @covers ::getTestClass
*/
public function testGetLogEntriesByTestClass(): void {
$test_run = TestRun::createNew($this->testRunResultsStorage);
$test_run->setDatabasePrefix('oddity1234');
$this->assertEquals(1, $test_run->insertLogEntry($this->getTestLogEntry('Test\PlanetEarth')));
$this->assertEquals(2, $test_run->insertLogEntry($this->getTestLogEntry('Test\GroundControl')));
$this->assertEquals([
0 => (object) [
'message_id' => 2,
'test_id' => 1,
'test_class' => 'Test\GroundControl',
'status' => 'pass',
'message' => 'Major Tom',
'message_group' => 'other',
'function' => 'Unknown',
'line' => 0,
'file' => 'Unknown',
],
1 => (object) [
'message_id' => 1,
'test_id' => 1,
'test_class' => 'Test\PlanetEarth',
'status' => 'pass',
'message' => 'Major Tom',
'message_group' => 'other',
'function' => 'Unknown',
'line' => 0,
'file' => 'Unknown',
],
], $test_run->getLogEntriesByTestClass());
$this->assertEquals('oddity1234', $test_run->getDatabasePrefix());
$this->assertEquals('Test\GroundControl', $test_run->getTestClass());
}
/**
* @covers ::createNew
* @covers ::setDatabasePrefix
* @covers ::processPhpErrorLogFile
* @covers ::getLogEntriesByTestClass
*/
public function testProcessPhpErrorLogFile(): void {
$test_run = TestRun::createNew($this->testRunResultsStorage);
$test_run->setDatabasePrefix('oddity1234');
$test_run->processPhpErrorLogFile('core/tests/fixtures/test-error.log', 'Test\PlanetEarth');
$this->assertEquals([
0 => (object) [
'message_id' => '1',
'test_id' => '1',
'test_class' => 'Test\PlanetEarth',
'status' => 'fail',
'message' => "Argument 1 passed to Drupal\FunctionalTests\Bootstrap\ErrorContainer::Drupal\FunctionalTests\Bootstrap\{closure}() must be an instance of Drupal\FunctionalTests\Bootstrap\ErrorContainer, int given, called",
'message_group' => 'TypeError',
'function' => 'Unknown',
'line' => '18',
'file' => '/var/www/core/tests/Drupal/FunctionalTests/Bootstrap/ErrorContainer.php on line 20 in /var/www/core/tests/Drupal/FunctionalTests/Bootstrap/ErrorContainer.php',
],
1 => (object) [
'message_id' => '2',
'test_id' => '1',
'test_class' => 'Test\PlanetEarth',
'status' => 'fail',
'message' => "#1 /var/www/core/lib/Drupal/Core/DrupalKernel.php(1396): Drupal\FunctionalTests\Bootstrap\ErrorContainer->get('http_kernel')\n",
'message_group' => 'Fatal error',
'function' => 'Unknown',
'line' => '0',
'file' => 'Unknown',
],
2 => (object) [
'message_id' => '3',
'test_id' => '1',
'test_class' => 'Test\PlanetEarth',
'status' => 'fail',
'message' => "#2 /var/www/core/lib/Drupal/Core/DrupalKernel.php(693): Drupal\Core\DrupalKernel->getHttpKernel()\n",
'message_group' => 'Fatal error',
'function' => 'Unknown',
'line' => '0',
'file' => 'Unknown',
],
3 => (object) [
'message_id' => '4',
'test_id' => '1',
'test_class' => 'Test\PlanetEarth',
'status' => 'fail',
'message' => "#3 /var/www/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request))\n",
'message_group' => 'Fatal error',
'function' => 'Unknown',
'line' => '0',
'file' => 'Unknown',
],
4 => (object) [
'message_id' => '5',
'test_id' => '1',
'test_class' => 'Test\PlanetEarth',
'status' => 'fail',
'message' => "#4 {main}\n",
'message_group' => 'Fatal error',
'function' => 'Unknown',
'line' => '0',
'file' => 'Unknown',
],
5 => (object) [
'message_id' => '6',
'test_id' => '1',
'test_class' => 'Test\PlanetEarth',
'status' => 'fail',
'message' => "Thrown exception during Container::get",
'message_group' => 'Exception',
'function' => 'Unknown',
'line' => '17',
'file' => '/var/www/core/tests/Drupal/FunctionalTests/Bootstrap/ExceptionContainer.php',
],
6 => (object) [
'message_id' => '7',
'test_id' => '1',
'test_class' => 'Test\PlanetEarth',
'status' => 'fail',
'message' => "#1 /var/www/core/lib/Drupal/Core/DrupalKernel.php(693): Drupal\Core\DrupalKernel->getHttpKernel()\n",
'message_group' => 'Fatal error',
'function' => 'Unknown',
'line' => '0',
'file' => 'Unknown',
],
7 => (object) [
'message_id' => '8',
'test_id' => '1',
'test_class' => 'Test\PlanetEarth',
'status' => 'fail',
'message' => "#2 /var/www/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request))\n",
'message_group' => 'Fatal error',
'function' => 'Unknown',
'line' => '0',
'file' => 'Unknown',
],
8 => (object) [
'message_id' => '9',
'test_id' => '1',
'test_class' => 'Test\PlanetEarth',
'status' => 'fail',
'message' => "#3 {main}\n",
'message_group' => 'Fatal error',
'function' => 'Unknown',
'line' => '0',
'file' => 'Unknown',
],
], $test_run->getLogEntriesByTestClass());
}
/**
* @covers ::insertLogEntry
*/
public function testProcessPhpUnitResults(): void {
$phpunit_error_xml = __DIR__ . '/../../../Tests/Core/Test/fixtures/phpunit_error.xml';
$res = JUnitConverter::xmlToRows(1, $phpunit_error_xml);
$runner = PhpUnitTestRunner::create(\Drupal::getContainer());
$test_run = TestRun::createNew($this->testRunResultsStorage);
$runner->processPhpUnitResults($test_run, $res);
$this->assertEquals(4, $this->connection->select('simpletest')->countQuery()->execute()->fetchField());
}
/**
* Returns a sample test run log entry.
*
* @param string $test_class
* The test class.
*
* @return string[]
* An array with the elements to be logged.
*/
protected function getTestLogEntry(string $test_class): array {
return [
'test_class' => $test_class,
'status' => 'pass',
'message' => 'Major Tom',
'message_group' => 'other',
];
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Drupal\KernelTests\Core\Test;
use Drupal\Core\Test\TestSetupTrait;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests the TestSetupTrait trait.
*
* @coversDefaultClass \Drupal\Core\Test\TestSetupTrait
* @group Testing
*
* Run in a separate process as this test involves Database statics and
* environment variables.
* @runTestsInSeparateProcesses
* @preserveGlobalState disabled
*/
class TestSetupTraitTest extends KernelTestBase {
use TestSetupTrait;
/**
* @covers ::getDatabaseConnection
* @group legacy
*/
public function testGetDatabaseConnection(): void {
$this->expectDeprecation('Drupal\Core\Test\TestSetupTrait::getDatabaseConnection is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3176816');
$this->assertNotNull($this->getDatabaseConnection());
}
}

View File

@ -3,6 +3,8 @@
namespace Drupal\Tests\Core\Test;
use Drupal\Core\Test\PhpUnitTestRunner;
use Drupal\Core\Test\SimpletestTestRunResultsStorage;
use Drupal\Core\Test\TestRun;
use Drupal\Core\Test\TestStatus;
use Drupal\Tests\UnitTestCase;
@ -17,12 +19,23 @@ class PhpUnitTestRunnerTest extends UnitTestCase {
/**
* Tests an error in the test running phase.
*
* @covers ::runTests
* @covers ::execute
*/
public function testRunTestsError() {
$test_id = 23;
$log_path = 'test_log_path';
// Create a mock test run storage.
$storage = $this->getMockBuilder(SimpletestTestRunResultsStorage::class)
->disableOriginalConstructor()
->setMethods(['createNew'])
->getMock();
// Set some expectations for createNew().
$storage->expects($this->once())
->method('createNew')
->willReturn($test_id);
// Create a mock runner.
$runner = $this->getMockBuilder(PhpUnitTestRunner::class)
->disableOriginalConstructor()
@ -40,13 +53,15 @@ class PhpUnitTestRunnerTest extends UnitTestCase {
->willReturnCallback(
function ($unescaped_test_classnames, $phpunit_file, &$status) {
$status = TestStatus::EXCEPTION;
return ' ';
}
);
// The runTests() method expects $status by reference, so we initialize it
// The execute() method expects $status by reference, so we initialize it
// to some value we don't expect back.
$status = -1;
$results = $runner->runTests($test_id, ['SomeTest'], $status);
$test_run = TestRun::createNew($storage);
$results = $runner->execute($test_run, ['SomeTest'], $status);
// Make sure our status code made the round trip.
$this->assertEquals(TestStatus::EXCEPTION, $status);

View File

@ -27,6 +27,7 @@ class TestDatabaseTest extends UnitTestCase {
* @covers ::__construct
* @covers ::getDatabasePrefix
* @covers ::getTestSitePath
* @covers ::getPhpErrorLogPath
*
* @dataProvider providerTestConstructor
*/
@ -34,6 +35,7 @@ class TestDatabaseTest extends UnitTestCase {
$test_db = new TestDatabase($db_prefix);
$this->assertEquals($expected_db_prefix, $test_db->getDatabasePrefix());
$this->assertEquals($expected_site_path, $test_db->getTestSitePath());
$this->assertEquals($expected_site_path . '/error.log', $test_db->getPhpErrorLogPath());
}
/**
@ -50,6 +52,9 @@ class TestDatabaseTest extends UnitTestCase {
* Verify that a test lock is generated if there is no provided prefix.
*
* @covers ::__construct
* @covers ::getDatabasePrefix
* @covers ::getTestSitePath
* @covers ::getPhpErrorLogPath
*/
public function testConstructorNullPrefix() {
// We use a stub class here because we can't mock getTestLock() so that it's
@ -58,6 +63,7 @@ class TestDatabaseTest extends UnitTestCase {
$this->assertEquals('test23', $test_db->getDatabasePrefix());
$this->assertEquals('sites/simpletest/23', $test_db->getTestSitePath());
$this->assertEquals('sites/simpletest/23/error.log', $test_db->getPhpErrorLogPath());
}
}
@ -67,7 +73,7 @@ class TestDatabaseTest extends UnitTestCase {
*/
class TestTestDatabase extends TestDatabase {
protected function getTestLock($create_lock = FALSE) {
protected function getTestLock(bool $create_lock = FALSE): int {
return 23;
}

9
core/tests/fixtures/test-error.log vendored Normal file
View File

@ -0,0 +1,9 @@
[14-Sep-2019 12:39:18 UTC] TypeError: Argument 1 passed to Drupal\FunctionalTests\Bootstrap\ErrorContainer::Drupal\FunctionalTests\Bootstrap\{closure}() must be an instance of Drupal\FunctionalTests\Bootstrap\ErrorContainer, int given, called in /var/www/core/tests/Drupal/FunctionalTests/Bootstrap/ErrorContainer.php on line 20 in /var/www/core/tests/Drupal/FunctionalTests/Bootstrap/ErrorContainer.php on line 18 #0 /var/www/core/tests/Drupal/FunctionalTests/Bootstrap/ErrorContainer.php(20): Drupal\FunctionalTests\Bootstrap\ErrorContainer->Drupal\FunctionalTests\Bootstrap\{closure}(1)
#1 /var/www/core/lib/Drupal/Core/DrupalKernel.php(1396): Drupal\FunctionalTests\Bootstrap\ErrorContainer->get('http_kernel')
#2 /var/www/core/lib/Drupal/Core/DrupalKernel.php(693): Drupal\Core\DrupalKernel->getHttpKernel()
#3 /var/www/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#4 {main}
[14-Sep-2019 12:39:22 UTC] Exception: Thrown exception during Container::get in /var/www/core/tests/Drupal/FunctionalTests/Bootstrap/ExceptionContainer.php on line 17 #0 /var/www/core/lib/Drupal/Core/DrupalKernel.php(1396): Drupal\FunctionalTests\Bootstrap\ExceptionContainer->get('http_kernel')
#1 /var/www/core/lib/Drupal/Core/DrupalKernel.php(693): Drupal\Core\DrupalKernel->getHttpKernel()
#2 /var/www/index.php(19): Drupal\Core\DrupalKernel->handle(Object(Symfony\Component\HttpFoundation\Request))
#3 {main}