TestDatabase.php 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. <?php
  2. namespace Drupal\Core\Test;
  3. use Drupal\Component\FileSystem\FileSystem;
  4. use Drupal\Core\Database\ConnectionNotDefinedException;
  5. use Drupal\Core\Database\Database;
  6. /**
  7. * Provides helper methods for interacting with the Simpletest database.
  8. */
  9. class TestDatabase {
  10. /**
  11. * A random number used to ensure that test fixtures are unique to each test
  12. * method.
  13. *
  14. * @var int
  15. */
  16. protected $lockId;
  17. /**
  18. * The test database prefix.
  19. *
  20. * @var string
  21. */
  22. protected $databasePrefix;
  23. /**
  24. * Returns the database connection to the site running Simpletest.
  25. *
  26. * @return \Drupal\Core\Database\Connection
  27. * The database connection to use for inserting assertions.
  28. *
  29. * @see \Drupal\simpletest\TestBase::prepareEnvironment()
  30. */
  31. public static function getConnection() {
  32. // Check whether there is a test runner connection.
  33. // @see run-tests.sh
  34. // @todo Convert Simpletest UI runner to create + use this connection, too.
  35. try {
  36. $connection = Database::getConnection('default', 'test-runner');
  37. }
  38. catch (ConnectionNotDefinedException $e) {
  39. // Check whether there is a backup of the original default connection.
  40. // @see TestBase::prepareEnvironment()
  41. try {
  42. $connection = Database::getConnection('default', 'simpletest_original_default');
  43. }
  44. catch (ConnectionNotDefinedException $e) {
  45. // If TestBase::prepareEnvironment() or TestBase::restoreEnvironment()
  46. // failed, the test-specific database connection does not exist
  47. // yet/anymore, so fall back to the default of the (UI) test runner.
  48. $connection = Database::getConnection('default', 'default');
  49. }
  50. }
  51. return $connection;
  52. }
  53. /**
  54. * TestDatabase constructor.
  55. *
  56. * @param string|null $db_prefix
  57. * If not provided a new test lock is generated.
  58. *
  59. * @throws \InvalidArgumentException
  60. * Thrown when $db_prefix does not match the regular expression.
  61. */
  62. public function __construct($db_prefix = NULL) {
  63. if ($db_prefix === NULL) {
  64. $this->lockId = $this->getTestLock();
  65. $this->databasePrefix = 'test' . $this->lockId;
  66. }
  67. else {
  68. $this->databasePrefix = $db_prefix;
  69. // It is possible that we're running a test inside a test. In which case
  70. // $db_prefix will be something like test12345678test90123456 and the
  71. // generated lock ID for the running test method would be 90123456.
  72. preg_match('/test(\d+)$/', $db_prefix, $matches);
  73. if (!isset($matches[1])) {
  74. throw new \InvalidArgumentException("Invalid database prefix: $db_prefix");
  75. }
  76. $this->lockId = $matches[1];
  77. }
  78. }
  79. /**
  80. * Gets the relative path to the test site directory.
  81. *
  82. * @return string
  83. * The relative path to the test site directory.
  84. */
  85. public function getTestSitePath() {
  86. return 'sites/simpletest/' . $this->lockId;
  87. }
  88. /**
  89. * Gets the test database prefix.
  90. *
  91. * @return string
  92. * The test database prefix.
  93. */
  94. public function getDatabasePrefix() {
  95. return $this->databasePrefix;
  96. }
  97. /**
  98. * Generates a unique lock ID for the test method.
  99. *
  100. * @return int
  101. * The unique lock ID for the test method.
  102. */
  103. protected function getTestLock() {
  104. // Ensure that the generated lock ID is not in use, which may happen when
  105. // tests are run concurrently.
  106. do {
  107. $lock_id = mt_rand(10000000, 99999999);
  108. // If we're only running with a concurrency of 1 there's no need to create
  109. // a test lock file as there is no chance of the random number generated
  110. // clashing.
  111. if (getenv('RUN_TESTS_CONCURRENCY') > 1 && @symlink(__FILE__, $this->getLockFile($lock_id)) === FALSE) {
  112. $lock_id = NULL;
  113. }
  114. } while ($lock_id === NULL);
  115. return $lock_id;
  116. }
  117. /**
  118. * Releases all test locks.
  119. *
  120. * This should only be called once all the test fixtures have been cleaned up.
  121. */
  122. public static function releaseAllTestLocks() {
  123. $tmp = file_directory_os_temp();
  124. $dir = dir($tmp);
  125. while (($entry = $dir->read()) !== FALSE) {
  126. if ($entry === '.' || $entry === '..') {
  127. continue;
  128. }
  129. $entry_path = $tmp . '/' . $entry;
  130. if (preg_match('/^test_\d+/', $entry) && is_link($entry_path)) {
  131. unlink($entry_path);
  132. }
  133. }
  134. }
  135. /**
  136. * Gets the lock file path.
  137. *
  138. * @param int $lock_id
  139. * The test method lock ID.
  140. *
  141. * @return string
  142. * A file path to the symbolic link that prevents the lock ID being re-used.
  143. */
  144. protected function getLockFile($lock_id) {
  145. return FileSystem::getOsTemporaryDirectory() . '/test_' . $lock_id;
  146. }
  147. }