diff --git a/lib/phpunit/bootstrap.php b/lib/phpunit/bootstrap.php index 9bb548a67f4..d0261e2d6e3 100644 --- a/lib/phpunit/bootstrap.php +++ b/lib/phpunit/bootstrap.php @@ -80,8 +80,6 @@ if ($phpunitversion === '@package_version@') { } unset($phpunitversion); -define('NO_OUTPUT_BUFFERING', true); - // only load CFG from config.php, stop ASAP in lib/setup.php define('ABORT_AFTER_CONFIG', true); require(__DIR__ . '/../../config.php'); diff --git a/lib/phpunit/classes/advanced_testcase.php b/lib/phpunit/classes/advanced_testcase.php index b65db0c2c98..41a7da01b1e 100644 --- a/lib/phpunit/classes/advanced_testcase.php +++ b/lib/phpunit/classes/advanced_testcase.php @@ -56,7 +56,7 @@ abstract class advanced_testcase extends base_testcase { $this->setBackupGlobals(false); $this->setBackupStaticAttributes(false); - $this->setRunTestInSeparateProcess(false); + $this->setPreserveGlobalState(false); } /** @@ -490,19 +490,6 @@ abstract class advanced_testcase extends base_testcase { return phpunit_util::start_event_redirection(); } - /** - * Cleanup after all tests are executed. - * - * Note: do not forget to call this if overridden... - * - * @static - * @return void - */ - public static function tearDownAfterClass() { - self::resetAllData(); - } - - /** * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir. * diff --git a/lib/phpunit/classes/util.php b/lib/phpunit/classes/util.php index 798b69cccb3..e69ae8cecdd 100644 --- a/lib/phpunit/classes/util.php +++ b/lib/phpunit/classes/util.php @@ -984,4 +984,16 @@ class phpunit_util extends testing_util { return null; } + + /** + * Whether the current process is an isolated test process. + * + * @return bool + */ + public static function is_in_isolated_process(): bool { + // Note: There is no function to call, or much to go by in order to tell whether we are in an isolated process + // during Bootstrap, when this function is called. + // We can do so by testing the existence of the wrapper function, but there is nothing set until that point. + return function_exists('__phpunit_run_isolated_test'); + } } diff --git a/lib/phpunit/tests/advanced_test.php b/lib/phpunit/tests/advanced_test.php index 39c7c16f42c..525325d2a38 100644 --- a/lib/phpunit/tests/advanced_test.php +++ b/lib/phpunit/tests/advanced_test.php @@ -539,6 +539,10 @@ class core_phpunit_advanced_testcase extends advanced_testcase { * @depends test_message_redirection */ public function test_message_redirection_noreset($sink) { + if ($this->isInIsolation()) { + $this->markTestSkipped('State cannot be carried over between tests in isolated tests'); + } + $this->preventResetByRollback(); // Messaging is not compatible with transactions... $this->resetAfterTest(); diff --git a/lib/setup.php b/lib/setup.php index 52f67c761d0..2cf12822c6d 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -620,8 +620,12 @@ setup_validate_php_configuration(); setup_DB(); if (PHPUNIT_TEST and !PHPUNIT_UTIL) { - // make sure tests do not run in parallel - test_lock::acquire('phpunit'); + // Make sure tests do not run in parallel. + $suffix = ''; + if (phpunit_util::is_in_isolated_process()) { + $suffix = '.isolated'; + } + test_lock::acquire('phpunit', $suffix); $dbhash = null; try { if ($dbhash = $DB->get_field('config', 'value', array('name'=>'phpunittest'))) { diff --git a/lib/testing/classes/test_lock.php b/lib/testing/classes/test_lock.php index 169241e2c47..2c8314ae6fe 100644 --- a/lib/testing/classes/test_lock.php +++ b/lib/testing/classes/test_lock.php @@ -47,13 +47,15 @@ class test_lock { * * @internal * @static - * @param string $framework Test framework - * @return void + * @param string $framework phpunit|behat + * @param string $lockfilesuffix A sub-type used by the framework + * @return void */ - public static function acquire($framework) { + public static function acquire(string $framework, string $lockfilesuffix = '') { global $CFG; + $datarootpath = $CFG->{$framework . '_dataroot'} . '/' . $framework; - $lockfile = $datarootpath . '/lock'; + $lockfile = "{$datarootpath}/lock{$lockfilesuffix}"; if (!file_exists($datarootpath)) { // Dataroot not initialised yet. return; @@ -62,36 +64,58 @@ class test_lock { file_put_contents($lockfile, 'This file prevents concurrent execution of Moodle ' . $framework . ' tests'); testing_fix_file_permissions($lockfile); } - if (self::$lockhandles[$framework] = fopen($lockfile, 'r')) { + + $lockhandlename = self::get_lock_handle_name($framework, $lockfilesuffix); + if (self::$lockhandles[$lockhandlename] = fopen($lockfile, 'r')) { $wouldblock = null; - $locked = flock(self::$lockhandles[$framework], (LOCK_EX | LOCK_NB), $wouldblock); + $locked = flock(self::$lockhandles[$lockhandlename], (LOCK_EX | LOCK_NB), $wouldblock); if (!$locked) { if ($wouldblock) { echo "Waiting for other test execution to complete...\n"; } - $locked = flock(self::$lockhandles[$framework], LOCK_EX); + $locked = flock(self::$lockhandles[$lockhandlename], LOCK_EX); } if (!$locked) { - fclose(self::$lockhandles[$framework]); - self::$lockhandles[$framework] = null; + fclose(self::$lockhandles[$lockhandlename]); + self::$lockhandles[$lockhandlename] = null; } } - register_shutdown_function(array('test_lock', 'release'), $framework); + register_shutdown_function(['test_lock', 'release'], $framework, $lockfilesuffix); } /** * Note: do not call manually! * @internal * @static - * @param string $framework phpunit|behat - * @return void + * @param string $framework phpunit|behat + * @param string $lockfilesuffix A sub-type used by the framework + * @return void */ - public static function release($framework) { - if (self::$lockhandles[$framework]) { - flock(self::$lockhandles[$framework], LOCK_UN); - fclose(self::$lockhandles[$framework]); - self::$lockhandles[$framework] = null; + public static function release(string $framework, string $lockfilesuffix = '') { + $lockhandlename = self::get_lock_handle_name($framework, $lockfilesuffix); + + if (self::$lockhandles[$lockhandlename]) { + flock(self::$lockhandles[$lockhandlename], LOCK_UN); + fclose(self::$lockhandles[$lockhandlename]); + self::$lockhandles[$lockhandlename] = null; } } + /** + * Get the name of the lock handle stored in the class. + * + * @param string $framework + * @param string $lockfilesuffix + * @return string + */ + protected static function get_lock_handle_name(string $framework, string $lockfilesuffix): string { + $lockhandlepieces = [$framework]; + + if (!empty($lockfilesuffix)) { + $lockhandlepieces[] = $lockfilesuffix; + } + + return implode('%', $lockhandlepieces); + } + }