Chris@0: lockId = $this->getTestLock($create_lock); Chris@0: $this->databasePrefix = 'test' . $this->lockId; Chris@0: } Chris@0: else { Chris@0: $this->databasePrefix = $db_prefix; Chris@0: // It is possible that we're running a test inside a test. In which case Chris@0: // $db_prefix will be something like test12345678test90123456 and the Chris@0: // generated lock ID for the running test method would be 90123456. Chris@0: preg_match('/test(\d+)$/', $db_prefix, $matches); Chris@0: if (!isset($matches[1])) { Chris@0: throw new \InvalidArgumentException("Invalid database prefix: $db_prefix"); Chris@0: } Chris@0: $this->lockId = $matches[1]; Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the relative path to the test site directory. Chris@0: * Chris@0: * @return string Chris@0: * The relative path to the test site directory. Chris@0: */ Chris@0: public function getTestSitePath() { Chris@0: return 'sites/simpletest/' . $this->lockId; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the test database prefix. Chris@0: * Chris@0: * @return string Chris@0: * The test database prefix. Chris@0: */ Chris@0: public function getDatabasePrefix() { Chris@0: return $this->databasePrefix; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Generates a unique lock ID for the test method. Chris@0: * Chris@17: * @param bool $create_lock Chris@17: * (optional) Whether or not to create a lock file. Defaults to FALSE. Chris@17: * Chris@0: * @return int Chris@0: * The unique lock ID for the test method. Chris@0: */ Chris@17: protected function getTestLock($create_lock = FALSE) { Chris@17: // There is a risk that the generated random number is a duplicate. This Chris@17: // would cause different tests to try to use the same database prefix. Chris@17: // Therefore, if running with a concurrency of greater than 1, we need to Chris@17: // create a lock. Chris@17: if (getenv('RUN_TESTS_CONCURRENCY') > 1) { Chris@17: $create_lock = TRUE; Chris@17: } Chris@17: Chris@0: do { Chris@0: $lock_id = mt_rand(10000000, 99999999); Chris@17: if ($create_lock && @symlink(__FILE__, $this->getLockFile($lock_id)) === FALSE) { Chris@17: // If we can't create a symlink, the lock ID is in use. Generate another Chris@17: // one. Symlinks are used because they are atomic and reliable. Chris@0: $lock_id = NULL; Chris@0: } Chris@0: } while ($lock_id === NULL); Chris@0: return $lock_id; Chris@0: } Chris@0: Chris@0: /** Chris@17: * Releases a lock. Chris@17: * Chris@17: * @return bool Chris@17: * TRUE if successful, FALSE if not. Chris@17: */ Chris@17: public function releaseLock() { Chris@17: return unlink($this->getLockFile($this->lockId)); Chris@17: } Chris@17: Chris@17: /** Chris@0: * Releases all test locks. Chris@0: * Chris@0: * This should only be called once all the test fixtures have been cleaned up. Chris@0: */ Chris@0: public static function releaseAllTestLocks() { Chris@17: $tmp = FileSystem::getOsTemporaryDirectory(); Chris@0: $dir = dir($tmp); Chris@0: while (($entry = $dir->read()) !== FALSE) { Chris@0: if ($entry === '.' || $entry === '..') { Chris@0: continue; Chris@0: } Chris@0: $entry_path = $tmp . '/' . $entry; Chris@0: if (preg_match('/^test_\d+/', $entry) && is_link($entry_path)) { Chris@0: unlink($entry_path); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the lock file path. Chris@0: * Chris@0: * @param int $lock_id Chris@0: * The test method lock ID. Chris@0: * Chris@0: * @return string Chris@0: * A file path to the symbolic link that prevents the lock ID being re-used. Chris@0: */ Chris@0: protected function getLockFile($lock_id) { Chris@0: return FileSystem::getOsTemporaryDirectory() . '/test_' . $lock_id; Chris@0: } Chris@0: Chris@0: }