annotate core/lib/Drupal/Core/Test/TestDatabase.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Test;
Chris@0 4
Chris@0 5 use Drupal\Component\FileSystem\FileSystem;
Chris@0 6 use Drupal\Core\Database\ConnectionNotDefinedException;
Chris@0 7 use Drupal\Core\Database\Database;
Chris@0 8
Chris@0 9 /**
Chris@0 10 * Provides helper methods for interacting with the Simpletest database.
Chris@0 11 */
Chris@0 12 class TestDatabase {
Chris@0 13
Chris@0 14 /**
Chris@0 15 * A random number used to ensure that test fixtures are unique to each test
Chris@0 16 * method.
Chris@0 17 *
Chris@0 18 * @var int
Chris@0 19 */
Chris@0 20 protected $lockId;
Chris@0 21
Chris@0 22 /**
Chris@0 23 * The test database prefix.
Chris@0 24 *
Chris@0 25 * @var string
Chris@0 26 */
Chris@0 27 protected $databasePrefix;
Chris@0 28
Chris@0 29 /**
Chris@0 30 * Returns the database connection to the site running Simpletest.
Chris@0 31 *
Chris@0 32 * @return \Drupal\Core\Database\Connection
Chris@0 33 * The database connection to use for inserting assertions.
Chris@0 34 *
Chris@0 35 * @see \Drupal\simpletest\TestBase::prepareEnvironment()
Chris@0 36 */
Chris@0 37 public static function getConnection() {
Chris@0 38 // Check whether there is a test runner connection.
Chris@0 39 // @see run-tests.sh
Chris@0 40 // @todo Convert Simpletest UI runner to create + use this connection, too.
Chris@0 41 try {
Chris@0 42 $connection = Database::getConnection('default', 'test-runner');
Chris@0 43 }
Chris@0 44 catch (ConnectionNotDefinedException $e) {
Chris@0 45 // Check whether there is a backup of the original default connection.
Chris@0 46 // @see TestBase::prepareEnvironment()
Chris@0 47 try {
Chris@0 48 $connection = Database::getConnection('default', 'simpletest_original_default');
Chris@0 49 }
Chris@0 50 catch (ConnectionNotDefinedException $e) {
Chris@0 51 // If TestBase::prepareEnvironment() or TestBase::restoreEnvironment()
Chris@0 52 // failed, the test-specific database connection does not exist
Chris@0 53 // yet/anymore, so fall back to the default of the (UI) test runner.
Chris@0 54 $connection = Database::getConnection('default', 'default');
Chris@0 55 }
Chris@0 56 }
Chris@0 57 return $connection;
Chris@0 58 }
Chris@0 59
Chris@0 60 /**
Chris@0 61 * TestDatabase constructor.
Chris@0 62 *
Chris@0 63 * @param string|null $db_prefix
Chris@0 64 * If not provided a new test lock is generated.
Chris@17 65 * @param bool $create_lock
Chris@17 66 * (optional) Whether or not to create a lock file. Defaults to FALSE. If
Chris@17 67 * the environment variable RUN_TESTS_CONCURRENCY is greater than 1 it will
Chris@17 68 * be overridden to TRUE regardless of its initial value.
Chris@0 69 *
Chris@0 70 * @throws \InvalidArgumentException
Chris@0 71 * Thrown when $db_prefix does not match the regular expression.
Chris@0 72 */
Chris@17 73 public function __construct($db_prefix = NULL, $create_lock = FALSE) {
Chris@0 74 if ($db_prefix === NULL) {
Chris@17 75 $this->lockId = $this->getTestLock($create_lock);
Chris@0 76 $this->databasePrefix = 'test' . $this->lockId;
Chris@0 77 }
Chris@0 78 else {
Chris@0 79 $this->databasePrefix = $db_prefix;
Chris@0 80 // It is possible that we're running a test inside a test. In which case
Chris@0 81 // $db_prefix will be something like test12345678test90123456 and the
Chris@0 82 // generated lock ID for the running test method would be 90123456.
Chris@0 83 preg_match('/test(\d+)$/', $db_prefix, $matches);
Chris@0 84 if (!isset($matches[1])) {
Chris@0 85 throw new \InvalidArgumentException("Invalid database prefix: $db_prefix");
Chris@0 86 }
Chris@0 87 $this->lockId = $matches[1];
Chris@0 88 }
Chris@0 89 }
Chris@0 90
Chris@0 91 /**
Chris@0 92 * Gets the relative path to the test site directory.
Chris@0 93 *
Chris@0 94 * @return string
Chris@0 95 * The relative path to the test site directory.
Chris@0 96 */
Chris@0 97 public function getTestSitePath() {
Chris@0 98 return 'sites/simpletest/' . $this->lockId;
Chris@0 99 }
Chris@0 100
Chris@0 101 /**
Chris@0 102 * Gets the test database prefix.
Chris@0 103 *
Chris@0 104 * @return string
Chris@0 105 * The test database prefix.
Chris@0 106 */
Chris@0 107 public function getDatabasePrefix() {
Chris@0 108 return $this->databasePrefix;
Chris@0 109 }
Chris@0 110
Chris@0 111 /**
Chris@0 112 * Generates a unique lock ID for the test method.
Chris@0 113 *
Chris@17 114 * @param bool $create_lock
Chris@17 115 * (optional) Whether or not to create a lock file. Defaults to FALSE.
Chris@17 116 *
Chris@0 117 * @return int
Chris@0 118 * The unique lock ID for the test method.
Chris@0 119 */
Chris@17 120 protected function getTestLock($create_lock = FALSE) {
Chris@17 121 // There is a risk that the generated random number is a duplicate. This
Chris@17 122 // would cause different tests to try to use the same database prefix.
Chris@17 123 // Therefore, if running with a concurrency of greater than 1, we need to
Chris@17 124 // create a lock.
Chris@17 125 if (getenv('RUN_TESTS_CONCURRENCY') > 1) {
Chris@17 126 $create_lock = TRUE;
Chris@17 127 }
Chris@17 128
Chris@0 129 do {
Chris@0 130 $lock_id = mt_rand(10000000, 99999999);
Chris@17 131 if ($create_lock && @symlink(__FILE__, $this->getLockFile($lock_id)) === FALSE) {
Chris@17 132 // If we can't create a symlink, the lock ID is in use. Generate another
Chris@17 133 // one. Symlinks are used because they are atomic and reliable.
Chris@0 134 $lock_id = NULL;
Chris@0 135 }
Chris@0 136 } while ($lock_id === NULL);
Chris@0 137 return $lock_id;
Chris@0 138 }
Chris@0 139
Chris@0 140 /**
Chris@17 141 * Releases a lock.
Chris@17 142 *
Chris@17 143 * @return bool
Chris@17 144 * TRUE if successful, FALSE if not.
Chris@17 145 */
Chris@17 146 public function releaseLock() {
Chris@17 147 return unlink($this->getLockFile($this->lockId));
Chris@17 148 }
Chris@17 149
Chris@17 150 /**
Chris@0 151 * Releases all test locks.
Chris@0 152 *
Chris@0 153 * This should only be called once all the test fixtures have been cleaned up.
Chris@0 154 */
Chris@0 155 public static function releaseAllTestLocks() {
Chris@17 156 $tmp = FileSystem::getOsTemporaryDirectory();
Chris@0 157 $dir = dir($tmp);
Chris@0 158 while (($entry = $dir->read()) !== FALSE) {
Chris@0 159 if ($entry === '.' || $entry === '..') {
Chris@0 160 continue;
Chris@0 161 }
Chris@0 162 $entry_path = $tmp . '/' . $entry;
Chris@0 163 if (preg_match('/^test_\d+/', $entry) && is_link($entry_path)) {
Chris@0 164 unlink($entry_path);
Chris@0 165 }
Chris@0 166 }
Chris@0 167 }
Chris@0 168
Chris@0 169 /**
Chris@0 170 * Gets the lock file path.
Chris@0 171 *
Chris@0 172 * @param int $lock_id
Chris@0 173 * The test method lock ID.
Chris@0 174 *
Chris@0 175 * @return string
Chris@0 176 * A file path to the symbolic link that prevents the lock ID being re-used.
Chris@0 177 */
Chris@0 178 protected function getLockFile($lock_id) {
Chris@0 179 return FileSystem::getOsTemporaryDirectory() . '/test_' . $lock_id;
Chris@0 180 }
Chris@0 181
Chris@0 182 }