diff --git a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php index 184c9d9..e12ffa6 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php @@ -7,17 +7,12 @@ namespace Drupal\path\Tests; +use Drupal\simpletest\SingleEnvInterface; + /** * Tests path alias functionality. */ -class PathAliasTest extends PathTestBase { - - /** - * Modules to enable. - * - * @var array - */ - public static $modules = array('path'); +class PathAliasTest extends PathTestBase implements SingleEnvInterface { public static function getInfo() { return array( @@ -27,8 +22,8 @@ public static function getInfo() { ); } - function setUp() { - parent::setUp(); + function setUpBeforeClass() { + parent::setUpBeforeClass(); // Create test user and login. $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer url aliases', 'create url aliases')); diff --git a/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php b/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php index 97f3f81..b4e71d6 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php @@ -8,6 +8,7 @@ namespace Drupal\path\Tests; use Drupal\simpletest\WebTestBase; +use Drupal\simpletest\SingleEnvInterface; /** * Provides a base class for testing the Path module. @@ -21,9 +22,21 @@ */ public static $modules = array('node', 'path'); + function setUpBeforeClass() { + parent::setUpBeforeClass(); + if ($this instanceof SingleEnvInterface) { + $this->doSetUp(); + } + } + function setUp() { parent::setUp(); + if (!$this instanceof SingleEnvInterface) { + $this->doSetUp(); + } + } + private function doSetUp() { // Create Basic page and Article node types. if ($this->profile != 'standard') { $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/SingleEnvInterface.php b/core/modules/simpletest/lib/Drupal/simpletest/SingleEnvInterface.php new file mode 100644 index 0000000..d1ccf9a --- /dev/null +++ b/core/modules/simpletest/lib/Drupal/simpletest/SingleEnvInterface.php @@ -0,0 +1,25 @@ +checkRequirements()) { + $object_info = new \ReflectionObject($this); + $caller = array( + 'file' => $object_info->getFileName(), + ); + foreach ($missing_requirements as $missing_requirement) { + TestBase::insertAssert($this->testId, $class, FALSE, $missing_requirement, 'Requirements check', $caller); + } + return; + } TestServiceProvider::$currentTest = $this; $simpletest_config = \Drupal::config('simpletest.settings'); @@ -789,87 +799,165 @@ public function run(array $methods = array()) { } set_error_handler(array($this, 'errorHandler')); + + $setup_once = $this instanceof SingleEnvInterface; + + if ($setup_once) { + // Sanity check. + $compatible = TRUE; + foreach (class_parents($this) as $parent_class) { + // TestBase itself is compatible. + if ($parent_class === __CLASS__) { + continue; + } + // If it implements the interface, it is compatible. + $implements_interface = in_array('Drupal\simpletest\SingleEnvInterface', class_implements($parent_class)); + if ($implements_interface) { + continue; + } + // Base classes may choose to support both ways (like WebTestBase). + $has_setup = method_exists($parent_class, 'setUp'); + $parent_method_info = new \ReflectionMethod($parent_class, 'setUpBeforeClass'); + if ($has_setup && $parent_method_info->class === $parent_class) { + continue; + } + $compatible = FALSE; + $this->fail("$parent_class does not support SingleEnvInterface.", 'Requirements check'); + } + if (!$compatible) { + return; + } + + // Insert a fail record. This will be deleted on completion to ensure + // that testing completed. + $class_info = new \ReflectionClass($class); + $caller = array( + 'file' => $class_info->getFileName(), + 'line' => $class_info->getStartLine(), + 'function' => $class, + ); + $class_completion_check_id = TestBase::insertAssert($this->testId, $class, FALSE, 'The test class did not complete due to a fatal error.', 'Completion check', $caller); + + try { + $this->prepareEnvironment(); + } + catch (\Exception $e) { + $this->exceptionHandler($e); + // The prepareEnvironment() method isolates the test from the parent + // Drupal site by creating a random database prefix and test site + // directory. If this fails, a test would possibly operate in the + // parent site. Therefore, the entire test run for this test class + // has to be aborted. + // restoreEnvironment() cannot be called, because we do not know + // where exactly the environment setup failed. + return; + } + try { + $this->setUpBeforeClass(); + } + catch (\Exception $e) { + $this->exceptionHandler($e); + // Abort if setUp() fails, since all test methods will fail. + // But ensure to clean up and restore the environment, since + // prepareEnvironment() succeeded. + $this->restoreEnvironment(); + return; + } + } + // Iterate through all the methods in this class, unless a specific list of // methods to run was passed. - $class_methods = get_class_methods($class); + $test_methods = array_filter(get_class_methods($class), function ($method) { + return strpos($method, 'test') === 0; + }); if ($methods) { - $class_methods = array_intersect($class_methods, $methods); + $test_methods = array_intersect($test_methods, $methods); } - $missing_requirements = $this->checkRequirements(); - if (!empty($missing_requirements)) { - $missing_requirements_object = new \ReflectionObject($this); + if (defined("$class::SORT_METHODS")) { + sort($test_methods); + } + foreach ($test_methods as $method) { + // Insert a fail record. This will be deleted on completion to ensure + // that testing completed. + $method_info = new \ReflectionMethod($class, $method); $caller = array( - 'file' => $missing_requirements_object->getFileName(), + 'file' => $method_info->getFileName(), + 'line' => $method_info->getStartLine(), + 'function' => $class . '->' . $method . '()', ); - foreach ($missing_requirements as $missing_requirement) { - TestBase::insertAssert($this->testId, $class, FALSE, $missing_requirement, 'Requirements check.', $caller); + $test_completion_check_id = TestBase::insertAssert($this->testId, $class, FALSE, 'The test did not complete due to a fatal error.', 'Completion check', $caller); + + if (!$setup_once) { + try { + $this->prepareEnvironment(); + } + catch (\Exception $e) { + $this->exceptionHandler($e); + // The prepareEnvironment() method isolates the test from the parent + // Drupal site by creating a random database prefix and test site + // directory. If this fails, a test would possibly operate in the + // parent site. Therefore, the entire test run for this test class + // has to be aborted. + // restoreEnvironment() cannot be called, because we do not know + // where exactly the environment setup failed. + break; + } } - } - else { - if (defined("$class::SORT_METHODS")) { - sort($class_methods); + + try { + $this->setUp(); + } + catch (\Exception $e) { + $this->exceptionHandler($e); + // Abort if setUp() fails, since all test methods will fail. + // But ensure to clean up and restore the environment, since + // prepareEnvironment() succeeded. + if (!$setup_once) { + $this->restoreEnvironment(); + } + break; } - foreach ($class_methods as $method) { - // If the current method starts with "test", run it - it's a test. - if (strtolower(substr($method, 0, 4)) == 'test') { - // Insert a fail record. This will be deleted on completion to ensure - // that testing completed. - $method_info = new \ReflectionMethod($class, $method); - $caller = array( - 'file' => $method_info->getFileName(), - 'line' => $method_info->getStartLine(), - 'function' => $class . '->' . $method . '()', - ); - $completion_check_id = TestBase::insertAssert($this->testId, $class, FALSE, 'The test did not complete due to a fatal error.', 'Completion check', $caller); - try { - $this->prepareEnvironment(); - } - catch (\Exception $e) { - $this->exceptionHandler($e); - // The prepareEnvironment() method isolates the test from the parent - // Drupal site by creating a random database prefix and test site - // directory. If this fails, a test would possibly operate in the - // parent site. Therefore, the entire test run for this test class - // has to be aborted. - // restoreEnvironment() cannot be called, because we do not know - // where exactly the environment setup failed. - break; - } - try { - $this->setUp(); - } - catch (\Exception $e) { - $this->exceptionHandler($e); - // Abort if setUp() fails, since all test methods will fail. - // But ensure to clean up and restore the environment, since - // prepareEnvironment() succeeded. - $this->restoreEnvironment(); - break; - } - try { - $this->$method(); - } - catch (\Exception $e) { - $this->exceptionHandler($e); - } - try { - $this->tearDown(); - } - catch (\Exception $e) { - $this->exceptionHandler($e); - // If a test fails to tear down, abort the entire test class, since - // it is likely that all tests will fail in the same way and a - // failure here only results in additional test artifacts that have - // to be manually deleted. - $this->restoreEnvironment(); - break; - } + try { + $this->$method(); + } + catch (\Exception $e) { + $this->exceptionHandler($e); + } + try { + $this->tearDown(); + } + catch (\Exception $e) { + $this->exceptionHandler($e); + // If a test fails to tear down, abort the entire test class, since + // it is likely that all tests will fail in the same way and a + // failure here only results in additional test artifacts that have + // to be manually deleted. + if (!$setup_once) { $this->restoreEnvironment(); - // Remove the completion check record. - TestBase::deleteAssert($completion_check_id); } + break; } + + if (!$setup_once) { + $this->restoreEnvironment(); + } + // Remove the test method completion check record. + TestBase::deleteAssert($test_completion_check_id); } + + if ($setup_once) { + try { + $this->tearDownAfterClass(); + } + catch (\Exception $e) { + $this->exceptionHandler($e); + } + $this->restoreEnvironment(); + + // Remove the test class completion check record. + TestBase::deleteAssert($class_completion_check_id); + } + TestServiceProvider::$currentTest = NULL; // Clear out the error messages and restore error handler. drupal_get_messages(); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index ccefb31..8d2b1db 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -732,6 +732,18 @@ protected function drupalLogout() { } } + public function setUpBeforeClass() { + if ($this instanceof SingleEnvInterface) { + $this->doSetUp(); + } + } + + protected function setUp() { + if (!$this instanceof SingleEnvInterface) { + $this->doSetUp(); + } + } + /** * Sets up a Drupal site for running functional and integration tests. * @@ -747,7 +759,7 @@ protected function drupalLogout() { * default values may be incompatible with the environment in which tests are * being executed. */ - protected function setUp() { + private function doSetUp() { // When running tests through the Simpletest UI (vs. on the command line), // Simpletest's batch conflicts with the installer's batch. Batch API does // not support the concept of nested batches (in which the nested is not @@ -1060,18 +1072,30 @@ protected function refreshVariables() { $this->container->get('state')->resetCache(); } + public function tearDownAfterClass() { + if ($this instanceof SingleEnvInterface) { + $this->doTearDown(); + } + } + + protected function tearDown() { + if (!$this instanceof SingleEnvInterface) { + $this->doTearDown(); + } + parent::tearDown(); + } + /** * Cleans up after testing. * * Deletes created files and temporary files directory, deletes the tables * created by setUp(), and resets the database prefix. */ - protected function tearDown() { + protected function doTearDown() { // Destroy the testing kernel. if (isset($this->kernel)) { $this->kernel->shutdown(); } - parent::tearDown(); // Ensure that internal logged in variable and cURL options are reset. $this->loggedInUser = FALSE;