MDL-60978 testing: Support ability to run phpunit in isolated process
authorAndrew Nicols <andrew@nicols.co.uk>
Tue, 18 Jun 2019 04:12:03 +0000 (12:12 +0800)
committerAndrew Nicols <andrew@nicols.co.uk>
Fri, 21 Jun 2019 06:36:13 +0000 (14:36 +0800)
lib/phpunit/bootstrap.php
lib/phpunit/classes/advanced_testcase.php
lib/phpunit/classes/util.php
lib/phpunit/tests/advanced_test.php
lib/setup.php
lib/testing/classes/test_lock.php

index 9bb548a..d0261e2 100644 (file)
@@ -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');
index b65db0c..41a7da0 100644 (file)
@@ -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.
      *
index 798b69c..e69ae8c 100644 (file)
@@ -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');
+    }
 }
index 39c7c16..525325d 100644 (file)
@@ -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();
 
index 52f67c7..2cf1282 100644 (file)
@@ -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'))) {
index 169241e..2c8314a 100644 (file)
@@ -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);
+    }
+
 }