KernelTestBase.php 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220
  1. <?php
  2. namespace Drupal\KernelTests;
  3. use Drupal\Component\FileCache\ApcuFileCacheBackend;
  4. use Drupal\Component\FileCache\FileCache;
  5. use Drupal\Component\FileCache\FileCacheFactory;
  6. use Drupal\Component\Utility\Html;
  7. use Drupal\Component\Utility\SafeMarkup;
  8. use Drupal\Core\Config\ConfigImporter;
  9. use Drupal\Core\Config\StorageComparer;
  10. use Drupal\Core\Config\StorageInterface;
  11. use Drupal\Core\Database\Database;
  12. use Drupal\Core\DependencyInjection\ContainerBuilder;
  13. use Drupal\Core\DependencyInjection\ServiceProviderInterface;
  14. use Drupal\Core\DrupalKernel;
  15. use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
  16. use Drupal\Core\Extension\ExtensionDiscovery;
  17. use Drupal\Core\Language\Language;
  18. use Drupal\Core\Site\Settings;
  19. use Drupal\simpletest\AssertContentTrait;
  20. use Drupal\simpletest\AssertHelperTrait;
  21. use Drupal\Tests\RandomGeneratorTrait;
  22. use Drupal\simpletest\TestServiceProvider;
  23. use Symfony\Component\DependencyInjection\Reference;
  24. use Symfony\Component\HttpFoundation\Request;
  25. use org\bovigo\vfs\vfsStream;
  26. use org\bovigo\vfs\visitor\vfsStreamPrintVisitor;
  27. /**
  28. * Base class for functional integration tests.
  29. *
  30. * Tests extending this base class can access files and the database, but the
  31. * entire environment is initially empty. Drupal runs in a minimal mocked
  32. * environment, comparable to the one in the early installer.
  33. *
  34. * Unlike \Drupal\Tests\UnitTestCase, modules specified in the $modules
  35. * property are automatically added to the service container for each test.
  36. * The module/hook system is functional and operates on a fixed module list.
  37. * Additional modules needed in a test may be loaded and added to the fixed
  38. * module list.
  39. *
  40. * Unlike \Drupal\simpletest\WebTestBase, the modules are only loaded, but not
  41. * installed. Modules have to be installed manually, if needed.
  42. *
  43. * @see \Drupal\Tests\KernelTestBase::$modules
  44. * @see \Drupal\Tests\KernelTestBase::enableModules()
  45. *
  46. * @todo Extend ::setRequirementsFromAnnotation() and ::checkRequirements() to
  47. * account for '@requires module'.
  48. */
  49. abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements ServiceProviderInterface {
  50. use AssertLegacyTrait;
  51. use AssertContentTrait;
  52. use AssertHelperTrait;
  53. use RandomGeneratorTrait;
  54. /**
  55. * {@inheritdoc}
  56. *
  57. * Back up and restore any global variables that may be changed by tests.
  58. *
  59. * @see self::runTestInSeparateProcess
  60. */
  61. protected $backupGlobals = TRUE;
  62. /**
  63. * {@inheritdoc}
  64. *
  65. * Kernel tests are run in separate processes to prevent collisions between
  66. * code that may be loaded by tests.
  67. */
  68. protected $runTestInSeparateProcess = TRUE;
  69. /**
  70. * {@inheritdoc}
  71. *
  72. * Back up and restore static class properties that may be changed by tests.
  73. *
  74. * @see self::runTestInSeparateProcess
  75. */
  76. protected $backupStaticAttributes = TRUE;
  77. /**
  78. * {@inheritdoc}
  79. *
  80. * Contains a few static class properties for performance.
  81. */
  82. protected $backupStaticAttributesBlacklist = [
  83. // Ignore static discovery/parser caches to speed up tests.
  84. 'Drupal\Component\Discovery\YamlDiscovery' => ['parsedFiles'],
  85. 'Drupal\Core\DependencyInjection\YamlFileLoader' => ['yaml'],
  86. 'Drupal\Core\Extension\ExtensionDiscovery' => ['files'],
  87. 'Drupal\Core\Extension\InfoParser' => ['parsedInfos'],
  88. // Drupal::$container cannot be serialized.
  89. 'Drupal' => ['container'],
  90. // Settings cannot be serialized.
  91. 'Drupal\Core\Site\Settings' => ['instance'],
  92. ];
  93. /**
  94. * {@inheritdoc}
  95. *
  96. * Do not forward any global state from the parent process to the processes
  97. * that run the actual tests.
  98. *
  99. * @see self::runTestInSeparateProcess
  100. */
  101. protected $preserveGlobalState = FALSE;
  102. /**
  103. * @var \Composer\Autoload\Classloader
  104. */
  105. protected $classLoader;
  106. /**
  107. * @var string
  108. */
  109. protected $siteDirectory;
  110. /**
  111. * @var string
  112. */
  113. protected $databasePrefix;
  114. /**
  115. * @var \Drupal\Core\DependencyInjection\ContainerBuilder
  116. */
  117. protected $container;
  118. /**
  119. * @var \Drupal\Core\DependencyInjection\ContainerBuilder
  120. */
  121. private static $initialContainerBuilder;
  122. /**
  123. * Modules to enable.
  124. *
  125. * The test runner will merge the $modules lists from this class, the class
  126. * it extends, and so on up the class hierarchy. It is not necessary to
  127. * include modules in your list that a parent class has already declared.
  128. *
  129. * @see \Drupal\Tests\KernelTestBase::enableModules()
  130. * @see \Drupal\Tests\KernelTestBase::bootKernel()
  131. *
  132. * @var array
  133. */
  134. public static $modules = array();
  135. /**
  136. * The virtual filesystem root directory.
  137. *
  138. * @var \org\bovigo\vfs\vfsStreamDirectory
  139. */
  140. protected $vfsRoot;
  141. /**
  142. * @var int
  143. */
  144. protected $expectedLogSeverity;
  145. /**
  146. * @var string
  147. */
  148. protected $expectedLogMessage;
  149. /**
  150. * @todo Move into Config test base class.
  151. * @var \Drupal\Core\Config\ConfigImporter
  152. */
  153. protected $configImporter;
  154. /**
  155. * The app root.
  156. *
  157. * @var string
  158. */
  159. protected $root;
  160. /**
  161. * Set to TRUE to strict check all configuration saved.
  162. *
  163. * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker
  164. *
  165. * @var bool
  166. */
  167. protected $strictConfigSchema = TRUE;
  168. /**
  169. * An array of config object names that are excluded from schema checking.
  170. *
  171. * @var string[]
  172. */
  173. protected static $configSchemaCheckerExclusions = array(
  174. // Following are used to test lack of or partial schema. Where partial
  175. // schema is provided, that is explicitly tested in specific tests.
  176. 'config_schema_test.noschema',
  177. 'config_schema_test.someschema',
  178. 'config_schema_test.schema_data_types',
  179. 'config_schema_test.no_schema_data_types',
  180. // Used to test application of schema to filtering of configuration.
  181. 'config_test.dynamic.system',
  182. );
  183. /**
  184. * {@inheritdoc}
  185. */
  186. public static function setUpBeforeClass() {
  187. parent::setUpBeforeClass();
  188. // Change the current dir to DRUPAL_ROOT.
  189. chdir(static::getDrupalRoot());
  190. }
  191. /**
  192. * Returns the drupal root directory.
  193. *
  194. * @return string
  195. */
  196. protected static function getDrupalRoot() {
  197. return dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
  198. }
  199. /**
  200. * {@inheritdoc}
  201. */
  202. protected function setUp() {
  203. parent::setUp();
  204. $this->root = static::getDrupalRoot();
  205. $this->initFileCache();
  206. $this->bootEnvironment();
  207. $this->bootKernel();
  208. }
  209. /**
  210. * Bootstraps a basic test environment.
  211. *
  212. * Should not be called by tests. Only visible for DrupalKernel integration
  213. * tests.
  214. *
  215. * @see \Drupal\system\Tests\DrupalKernel\DrupalKernelTest
  216. * @internal
  217. */
  218. protected function bootEnvironment() {
  219. $this->streamWrappers = array();
  220. \Drupal::unsetContainer();
  221. $this->classLoader = require $this->root . '/autoload.php';
  222. require_once $this->root . '/core/includes/bootstrap.inc';
  223. // Set up virtual filesystem.
  224. // Ensure that the generated test site directory does not exist already,
  225. // which may happen with a large amount of concurrent threads and
  226. // long-running tests.
  227. do {
  228. $suffix = mt_rand(100000, 999999);
  229. $this->siteDirectory = 'sites/simpletest/' . $suffix;
  230. $this->databasePrefix = 'simpletest' . $suffix;
  231. } while (is_dir($this->root . '/' . $this->siteDirectory));
  232. $this->vfsRoot = vfsStream::setup('root', NULL, array(
  233. 'sites' => array(
  234. 'simpletest' => array(
  235. $suffix => array(),
  236. ),
  237. ),
  238. ));
  239. $this->siteDirectory = vfsStream::url('root/sites/simpletest/' . $suffix);
  240. mkdir($this->siteDirectory . '/files', 0775);
  241. mkdir($this->siteDirectory . '/files/config/' . CONFIG_SYNC_DIRECTORY, 0775, TRUE);
  242. // Ensure that all code that relies on drupal_valid_test_ua() can still be
  243. // safely executed. This primarily affects the (test) site directory
  244. // resolution (used by e.g. LocalStream and PhpStorage).
  245. $this->databasePrefix = 'simpletest' . $suffix;
  246. drupal_valid_test_ua($this->databasePrefix);
  247. $settings = array(
  248. 'hash_salt' => get_class($this),
  249. 'file_public_path' => $this->siteDirectory . '/files',
  250. // Disable Twig template caching/dumping.
  251. 'twig_cache' => FALSE,
  252. // @see \Drupal\KernelTests\KernelTestBase::register()
  253. );
  254. new Settings($settings);
  255. $GLOBALS['config_directories'] = array(
  256. CONFIG_SYNC_DIRECTORY => $this->siteDirectory . '/files/config/sync',
  257. );
  258. foreach (Database::getAllConnectionInfo() as $key => $targets) {
  259. Database::removeConnection($key);
  260. }
  261. Database::addConnectionInfo('default', 'default', $this->getDatabaseConnectionInfo()['default']);
  262. }
  263. /**
  264. * @return string
  265. */
  266. public function getDatabasePrefix() {
  267. return $this->databasePrefix;
  268. }
  269. /**
  270. * Bootstraps a kernel for a test.
  271. */
  272. private function bootKernel() {
  273. $this->setSetting('container_yamls', []);
  274. // Allow for test-specific overrides.
  275. $settings_services_file = $this->root . '/sites/default' . '/testing.services.yml';
  276. if (file_exists($settings_services_file)) {
  277. // Copy the testing-specific service overrides in place.
  278. $testing_services_file = $this->root . '/' . $this->siteDirectory . '/services.yml';
  279. copy($settings_services_file, $testing_services_file);
  280. $this->setSetting('container_yamls', [$testing_services_file]);
  281. }
  282. // Allow for global test environment overrides.
  283. if (file_exists($test_env = $this->root . '/sites/default/testing.services.yml')) {
  284. $GLOBALS['conf']['container_yamls']['testing'] = $test_env;
  285. }
  286. // Add this test class as a service provider.
  287. $GLOBALS['conf']['container_service_providers']['test'] = $this;
  288. $modules = self::getModulesToEnable(get_class($this));
  289. // Prepare a precompiled container for all tests of this class.
  290. // Substantially improves performance, since ContainerBuilder::compile()
  291. // is very expensive. Encourages testing best practices (small tests).
  292. // Normally a setUpBeforeClass() operation, but object scope is required to
  293. // inject $this test class instance as a service provider (see above).
  294. $rc = new \ReflectionClass(get_class($this));
  295. $test_method_count = count(array_filter($rc->getMethods(), function ($method) {
  296. // PHPUnit's @test annotations are intentionally ignored/not supported.
  297. return strpos($method->getName(), 'test') === 0;
  298. }));
  299. if ($test_method_count > 1 && !$this->isTestInIsolation()) {
  300. // Clone a precompiled, empty ContainerBuilder instance for each test.
  301. $container = $this->getCompiledContainerBuilder($modules);
  302. }
  303. // Bootstrap the kernel. Do not use createFromRequest() to retain Settings.
  304. $kernel = new DrupalKernel('testing', $this->classLoader, FALSE);
  305. $kernel->setSitePath($this->siteDirectory);
  306. // Boot the precompiled container. The kernel will enhance it with synthetic
  307. // services.
  308. if (isset($container)) {
  309. $kernel->setContainer($container);
  310. unset($container);
  311. }
  312. // Boot a new one-time container from scratch. Ensure to set the module list
  313. // upfront to avoid a subsequent rebuild.
  314. elseif ($modules && $extensions = $this->getExtensionsForModules($modules)) {
  315. $kernel->updateModules($extensions, $extensions);
  316. }
  317. // DrupalKernel::boot() is not sufficient as it does not invoke preHandle(),
  318. // which is required to initialize legacy global variables.
  319. $request = Request::create('/');
  320. $kernel->prepareLegacyRequest($request);
  321. // register() is only called if a new container was built/compiled.
  322. $this->container = $kernel->getContainer();
  323. // Ensure database tasks have been run.
  324. require_once __DIR__ . '/../../../includes/install.inc';
  325. $connection = Database::getConnection();
  326. $errors = db_installer_object($connection->driver())->runTasks();
  327. if (!empty($errors)) {
  328. $this->fail('Failed to run installer database tasks: ' . implode(', ', $errors));
  329. }
  330. if ($modules) {
  331. $this->container->get('module_handler')->loadAll();
  332. }
  333. $this->container->get('request_stack')->push($request);
  334. // Setup the destion to the be frontpage by default.
  335. \Drupal::destination()->set('/');
  336. // Write the core.extension configuration.
  337. // Required for ConfigInstaller::installDefaultConfig() to work.
  338. $this->container->get('config.storage')->write('core.extension', array(
  339. 'module' => array_fill_keys($modules, 0),
  340. 'theme' => array(),
  341. ));
  342. $settings = Settings::getAll();
  343. $settings['php_storage']['default'] = [
  344. 'class' => '\Drupal\Component\PhpStorage\FileStorage',
  345. ];
  346. new Settings($settings);
  347. // Manually configure the test mail collector implementation to prevent
  348. // tests from sending out emails and collect them in state instead.
  349. // While this should be enforced via settings.php prior to installation,
  350. // some tests expect to be able to test mail system implementations.
  351. $GLOBALS['config']['system.mail']['interface']['default'] = 'test_mail_collector';
  352. }
  353. /**
  354. * Configuration accessor for tests. Returns non-overridden configuration.
  355. *
  356. * @param string $name
  357. * The configuration name.
  358. *
  359. * @return \Drupal\Core\Config\Config
  360. * The configuration object with original configuration data.
  361. */
  362. protected function config($name) {
  363. return $this->container->get('config.factory')->getEditable($name);
  364. }
  365. /**
  366. * Returns the Database connection info to be used for this test.
  367. *
  368. * This method only exists for tests of the Database component itself, because
  369. * they require multiple database connections. Each SQLite :memory: connection
  370. * creates a new/separate database in memory. A shared-memory SQLite file URI
  371. * triggers PHP open_basedir/allow_url_fopen/allow_url_include restrictions.
  372. * Due to that, Database tests are running against a SQLite database that is
  373. * located in an actual file in the system's temporary directory.
  374. *
  375. * Other tests should not override this method.
  376. *
  377. * @return array
  378. * A Database connection info array.
  379. *
  380. * @internal
  381. */
  382. protected function getDatabaseConnectionInfo() {
  383. // If the test is run with argument dburl then use it.
  384. $db_url = getenv('SIMPLETEST_DB');
  385. if (empty($db_url)) {
  386. throw new \Exception('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh. See https://www.drupal.org/node/2116263#skipped-tests for more information.');
  387. }
  388. else {
  389. $database = Database::convertDbUrlToConnectionInfo($db_url, $this->root);
  390. Database::addConnectionInfo('default', 'default', $database);
  391. }
  392. // Clone the current connection and replace the current prefix.
  393. $connection_info = Database::getConnectionInfo('default');
  394. if (!empty($connection_info)) {
  395. Database::renameConnection('default', 'simpletest_original_default');
  396. foreach ($connection_info as $target => $value) {
  397. // Replace the full table prefix definition to ensure that no table
  398. // prefixes of the test runner leak into the test.
  399. $connection_info[$target]['prefix'] = array(
  400. 'default' => $value['prefix']['default'] . $this->databasePrefix,
  401. );
  402. }
  403. }
  404. return $connection_info;
  405. }
  406. /**
  407. * Prepares a precompiled ContainerBuilder for all tests of this class.
  408. *
  409. * Avoids repetitive calls to ContainerBuilder::compile(), which is very slow.
  410. *
  411. * Based on the (always identical) list of $modules to enable, an initial
  412. * container is compiled, all instantiated services are reset/removed, and
  413. * this precompiled container is stored in a static class property. (Static,
  414. * because PHPUnit instantiates a new class instance for each test *method*.)
  415. *
  416. * This method is not invoked if there is only a single test method. It is
  417. * also not invoked for tests running in process isolation (since each test
  418. * method runs in a separate process).
  419. *
  420. * The ContainerBuilder is not dumped into the filesystem (which would yield
  421. * an actually compiled Container class), because
  422. *
  423. * 1. PHP code cannot be unloaded, so e.g. 900 tests would load 900 different,
  424. * full Container classes into memory, quickly exceeding any sensible
  425. * memory consumption (GigaBytes).
  426. * 2. Dumping a Container class requires to actually write to the system's
  427. * temporary directory. This is not really easy with vfs, because vfs
  428. * doesn't support yet "include 'vfs://container.php'.". Maybe we could fix
  429. * that upstream.
  430. * 3. PhpDumper is very slow on its own.
  431. *
  432. * @param string[] $modules
  433. * The list of modules to enable.
  434. *
  435. * @return \Drupal\Core\DependencyInjection\ContainerBuilder
  436. * A clone of the precompiled, empty service container.
  437. */
  438. private function getCompiledContainerBuilder(array $modules) {
  439. if (!isset(self::$initialContainerBuilder)) {
  440. $kernel = new DrupalKernel('testing', $this->classLoader, FALSE);
  441. $kernel->setSitePath($this->siteDirectory);
  442. if ($modules && $extensions = $this->getExtensionsForModules($modules)) {
  443. $kernel->updateModules($extensions, $extensions);
  444. }
  445. $kernel->boot();
  446. self::$initialContainerBuilder = $kernel->getContainer();
  447. // Remove all instantiated services, so the container is safe for cloning.
  448. // Technically, ContainerBuilder::set($id, NULL) removes each definition,
  449. // but the container is compiled/frozen already.
  450. foreach (self::$initialContainerBuilder->getServiceIds() as $id) {
  451. self::$initialContainerBuilder->set($id, NULL);
  452. }
  453. // Destruct and trigger garbage collection.
  454. \Drupal::unsetContainer();
  455. $kernel->shutdown();
  456. $kernel = NULL;
  457. // @see register()
  458. $this->container = NULL;
  459. }
  460. $container = clone self::$initialContainerBuilder;
  461. return $container;
  462. }
  463. /**
  464. * Initializes the FileCache component.
  465. *
  466. * We can not use the Settings object in a component, that's why we have to do
  467. * it here instead of \Drupal\Component\FileCache\FileCacheFactory.
  468. */
  469. protected function initFileCache() {
  470. $configuration = Settings::get('file_cache');
  471. // Provide a default configuration, if not set.
  472. if (!isset($configuration['default'])) {
  473. $configuration['default'] = [
  474. 'class' => FileCache::class,
  475. 'cache_backend_class' => NULL,
  476. 'cache_backend_configuration' => [],
  477. ];
  478. // @todo Use extension_loaded('apcu') for non-testbot
  479. // https://www.drupal.org/node/2447753.
  480. if (function_exists('apcu_fetch')) {
  481. $configuration['default']['cache_backend_class'] = ApcuFileCacheBackend::class;
  482. }
  483. }
  484. FileCacheFactory::setConfiguration($configuration);
  485. FileCacheFactory::setPrefix(Settings::getApcuPrefix('file_cache', $this->root));
  486. }
  487. /**
  488. * Returns Extension objects for $modules to enable.
  489. *
  490. * @param string[] $modules
  491. * The list of modules to enable.
  492. *
  493. * @return \Drupal\Core\Extension\Extension[]
  494. * Extension objects for $modules, keyed by module name.
  495. *
  496. * @throws \PHPUnit_Framework_Exception
  497. * If a module is not available.
  498. *
  499. * @see \Drupal\Tests\KernelTestBase::enableModules()
  500. * @see \Drupal\Core\Extension\ModuleHandler::add()
  501. */
  502. private function getExtensionsForModules(array $modules) {
  503. $extensions = array();
  504. $discovery = new ExtensionDiscovery($this->root);
  505. $discovery->setProfileDirectories(array());
  506. $list = $discovery->scan('module');
  507. foreach ($modules as $name) {
  508. if (!isset($list[$name])) {
  509. throw new \PHPUnit_Framework_Exception("Unavailable module: '$name'. If this module needs to be downloaded separately, annotate the test class with '@requires module $name'.");
  510. }
  511. $extensions[$name] = $list[$name];
  512. }
  513. return $extensions;
  514. }
  515. /**
  516. * Registers test-specific services.
  517. *
  518. * Extend this method in your test to register additional services. This
  519. * method is called whenever the kernel is rebuilt.
  520. *
  521. * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
  522. * The service container to enhance.
  523. *
  524. * @see \Drupal\Tests\KernelTestBase::bootKernel()
  525. */
  526. public function register(ContainerBuilder $container) {
  527. // Keep the container object around for tests.
  528. $this->container = $container;
  529. $container
  530. ->register('flood', 'Drupal\Core\Flood\MemoryBackend')
  531. ->addArgument(new Reference('request_stack'));
  532. $container
  533. ->register('lock', 'Drupal\Core\Lock\NullLockBackend');
  534. $container
  535. ->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
  536. $container
  537. ->register('keyvalue.memory', 'Drupal\Core\KeyValueStore\KeyValueMemoryFactory')
  538. // Must persist container rebuilds, or all data would vanish otherwise.
  539. ->addTag('persist');
  540. $container
  541. ->setAlias('keyvalue', 'keyvalue.memory');
  542. // Set the default language on the minimal container.
  543. $container->setParameter('language.default_values', Language::$defaultValues);
  544. if ($this->strictConfigSchema) {
  545. $container
  546. ->register('simpletest.config_schema_checker', 'Drupal\Core\Config\Testing\ConfigSchemaChecker')
  547. ->addArgument(new Reference('config.typed'))
  548. ->addArgument($this->getConfigSchemaExclusions())
  549. ->addTag('event_subscriber');
  550. }
  551. if ($container->hasDefinition('path_processor_alias')) {
  552. // Prevent the alias-based path processor, which requires a url_alias db
  553. // table, from being registered to the path processor manager. We do this
  554. // by removing the tags that the compiler pass looks for. This means the
  555. // url generator can safely be used within tests.
  556. $container->getDefinition('path_processor_alias')
  557. ->clearTag('path_processor_inbound')
  558. ->clearTag('path_processor_outbound');
  559. }
  560. if ($container->hasDefinition('password')) {
  561. $container->getDefinition('password')
  562. ->setArguments(array(1));
  563. }
  564. TestServiceProvider::addRouteProvider($container);
  565. }
  566. /**
  567. * Gets the config schema exclusions for this test.
  568. *
  569. * @return string[]
  570. * An array of config object names that are excluded from schema checking.
  571. */
  572. protected function getConfigSchemaExclusions() {
  573. $class = get_class($this);
  574. $exceptions = [];
  575. while ($class) {
  576. if (property_exists($class, 'configSchemaCheckerExclusions')) {
  577. $exceptions = array_merge($exceptions, $class::$configSchemaCheckerExclusions);
  578. }
  579. $class = get_parent_class($class);
  580. }
  581. // Filter out any duplicates.
  582. return array_unique($exceptions);
  583. }
  584. /**
  585. * {@inheritdoc}
  586. */
  587. protected function assertPostConditions() {
  588. // Execute registered Drupal shutdown functions prior to tearing down.
  589. // @see _drupal_shutdown_function()
  590. $callbacks = &drupal_register_shutdown_function();
  591. while ($callback = array_shift($callbacks)) {
  592. call_user_func_array($callback['callback'], $callback['arguments']);
  593. }
  594. // Shut down the kernel (if bootKernel() was called).
  595. // @see \Drupal\system\Tests\DrupalKernel\DrupalKernelTest
  596. if ($this->container) {
  597. $this->container->get('kernel')->shutdown();
  598. }
  599. // Fail in case any (new) shutdown functions exist.
  600. $this->assertCount(0, drupal_register_shutdown_function(), 'Unexpected Drupal shutdown callbacks exist after running shutdown functions.');
  601. parent::assertPostConditions();
  602. }
  603. /**
  604. * {@inheritdoc}
  605. */
  606. protected function tearDown() {
  607. // Destroy the testing kernel.
  608. if (isset($this->kernel)) {
  609. $this->kernel->shutdown();
  610. }
  611. // Remove all prefixed tables.
  612. $original_connection_info = Database::getConnectionInfo('simpletest_original_default');
  613. $original_prefix = $original_connection_info['default']['prefix']['default'];
  614. $test_connection_info = Database::getConnectionInfo('default');
  615. $test_prefix = $test_connection_info['default']['prefix']['default'];
  616. if ($original_prefix != $test_prefix) {
  617. $tables = Database::getConnection()->schema()->findTables('%');
  618. foreach ($tables as $table) {
  619. if (Database::getConnection()->schema()->dropTable($table)) {
  620. unset($tables[$table]);
  621. }
  622. }
  623. }
  624. // Free up memory: Own properties.
  625. $this->classLoader = NULL;
  626. $this->vfsRoot = NULL;
  627. $this->configImporter = NULL;
  628. // Free up memory: Custom test class properties.
  629. // Note: Private properties cannot be cleaned up.
  630. $rc = new \ReflectionClass(__CLASS__);
  631. $blacklist = array();
  632. foreach ($rc->getProperties() as $property) {
  633. $blacklist[$property->name] = $property->getDeclaringClass()->name;
  634. }
  635. $rc = new \ReflectionClass($this);
  636. foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $property) {
  637. if (!$property->isStatic() && !isset($blacklist[$property->name])) {
  638. $this->{$property->name} = NULL;
  639. }
  640. }
  641. // Clean FileCache cache.
  642. FileCache::reset();
  643. // Clean up statics, container, and settings.
  644. if (function_exists('drupal_static_reset')) {
  645. drupal_static_reset();
  646. }
  647. \Drupal::unsetContainer();
  648. $this->container = NULL;
  649. new Settings(array());
  650. parent::tearDown();
  651. }
  652. /**
  653. * @after
  654. *
  655. * Additional tear down method to close the connection at the end.
  656. */
  657. public function tearDownCloseDatabaseConnection() {
  658. // Destroy the database connection, which for example removes the memory
  659. // from sqlite in memory.
  660. foreach (Database::getAllConnectionInfo() as $key => $targets) {
  661. Database::removeConnection($key);
  662. }
  663. }
  664. /**
  665. * {@inheritdoc}
  666. */
  667. public static function tearDownAfterClass() {
  668. // Free up memory: Precompiled container.
  669. self::$initialContainerBuilder = NULL;
  670. parent::tearDownAfterClass();
  671. }
  672. /**
  673. * Installs default configuration for a given list of modules.
  674. *
  675. * @param string|string[] $modules
  676. * A list of modules for which to install default configuration.
  677. *
  678. * @throws \LogicException
  679. * If any module in $modules is not enabled.
  680. */
  681. protected function installConfig($modules) {
  682. foreach ((array) $modules as $module) {
  683. if (!$this->container->get('module_handler')->moduleExists($module)) {
  684. throw new \LogicException("$module module is not enabled.");
  685. }
  686. $this->container->get('config.installer')->installDefaultConfig('module', $module);
  687. }
  688. }
  689. /**
  690. * Installs database tables from a module schema definition.
  691. *
  692. * @param string $module
  693. * The name of the module that defines the table's schema.
  694. * @param string|array $tables
  695. * The name or an array of the names of the tables to install.
  696. *
  697. * @throws \LogicException
  698. * If $module is not enabled or the table schema cannot be found.
  699. */
  700. protected function installSchema($module, $tables) {
  701. // drupal_get_module_schema() is technically able to install a schema
  702. // of a non-enabled module, but its ability to load the module's .install
  703. // file depends on many other factors. To prevent differences in test
  704. // behavior and non-reproducible test failures, we only allow the schema of
  705. // explicitly loaded/enabled modules to be installed.
  706. if (!$this->container->get('module_handler')->moduleExists($module)) {
  707. throw new \LogicException("$module module is not enabled.");
  708. }
  709. $tables = (array) $tables;
  710. foreach ($tables as $table) {
  711. $schema = drupal_get_module_schema($module, $table);
  712. if (empty($schema)) {
  713. // BC layer to avoid some contrib tests to fail.
  714. // @todo Remove the BC layer before 8.1.x release.
  715. // @see https://www.drupal.org/node/2670360
  716. // @see https://www.drupal.org/node/2670454
  717. if ($module == 'system') {
  718. continue;
  719. }
  720. throw new \LogicException("$module module does not define a schema for table '$table'.");
  721. }
  722. $this->container->get('database')->schema()->createTable($table, $schema);
  723. }
  724. }
  725. /**
  726. * Installs the storage schema for a specific entity type.
  727. *
  728. * @param string $entity_type_id
  729. * The ID of the entity type.
  730. */
  731. protected function installEntitySchema($entity_type_id) {
  732. /** @var \Drupal\Core\Entity\EntityManagerInterface $entity_manager */
  733. $entity_manager = $this->container->get('entity.manager');
  734. $entity_type = $entity_manager->getDefinition($entity_type_id);
  735. $entity_manager->onEntityTypeCreate($entity_type);
  736. // For test runs, the most common storage backend is a SQL database. For
  737. // this case, ensure the tables got created.
  738. $storage = $entity_manager->getStorage($entity_type_id);
  739. if ($storage instanceof SqlEntityStorageInterface) {
  740. $tables = $storage->getTableMapping()->getTableNames();
  741. $db_schema = $this->container->get('database')->schema();
  742. $all_tables_exist = TRUE;
  743. foreach ($tables as $table) {
  744. if (!$db_schema->tableExists($table)) {
  745. $this->fail(SafeMarkup::format('Installed entity type table for the %entity_type entity type: %table', array(
  746. '%entity_type' => $entity_type_id,
  747. '%table' => $table,
  748. )));
  749. $all_tables_exist = FALSE;
  750. }
  751. }
  752. if ($all_tables_exist) {
  753. $this->pass(SafeMarkup::format('Installed entity type tables for the %entity_type entity type: %tables', array(
  754. '%entity_type' => $entity_type_id,
  755. '%tables' => '{' . implode('}, {', $tables) . '}',
  756. )));
  757. }
  758. }
  759. }
  760. /**
  761. * Enables modules for this test.
  762. *
  763. * To install test modules outside of the testing environment, add
  764. * @code
  765. * $settings['extension_discovery_scan_tests'] = TRUE;
  766. * @endcode
  767. * to your settings.php.
  768. *
  769. * @param string[] $modules
  770. * A list of modules to enable. Dependencies are not resolved; i.e.,
  771. * multiple modules have to be specified individually. The modules are only
  772. * added to the active module list and loaded; i.e., their database schema
  773. * is not installed. hook_install() is not invoked. A custom module weight
  774. * is not applied.
  775. *
  776. * @throws \LogicException
  777. * If any module in $modules is already enabled.
  778. * @throws \RuntimeException
  779. * If a module is not enabled after enabling it.
  780. */
  781. protected function enableModules(array $modules) {
  782. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  783. if ($trace[1]['function'] === 'setUp') {
  784. trigger_error('KernelTestBase::enableModules() should not be called from setUp(). Use the $modules property instead.', E_USER_DEPRECATED);
  785. }
  786. unset($trace);
  787. // Perform an ExtensionDiscovery scan as this function may receive a
  788. // profile that is not the current profile, and we don't yet have a cached
  789. // way to receive inactive profile information.
  790. // @todo Remove as part of https://www.drupal.org/node/2186491
  791. $listing = new ExtensionDiscovery(\Drupal::root());
  792. $module_list = $listing->scan('module');
  793. // In ModuleHandlerTest we pass in a profile as if it were a module.
  794. $module_list += $listing->scan('profile');
  795. // Set the list of modules in the extension handler.
  796. $module_handler = $this->container->get('module_handler');
  797. // Write directly to active storage to avoid early instantiation of
  798. // the event dispatcher which can prevent modules from registering events.
  799. $active_storage = $this->container->get('config.storage');
  800. $extension_config = $active_storage->read('core.extension');
  801. foreach ($modules as $module) {
  802. if ($module_handler->moduleExists($module)) {
  803. throw new \LogicException("$module module is already enabled.");
  804. }
  805. $module_handler->addModule($module, $module_list[$module]->getPath());
  806. // Maintain the list of enabled modules in configuration.
  807. $extension_config['module'][$module] = 0;
  808. }
  809. $active_storage->write('core.extension', $extension_config);
  810. // Update the kernel to make their services available.
  811. $extensions = $module_handler->getModuleList();
  812. $this->container->get('kernel')->updateModules($extensions, $extensions);
  813. // Ensure isLoaded() is TRUE in order to make
  814. // \Drupal\Core\Theme\ThemeManagerInterface::render() work.
  815. // Note that the kernel has rebuilt the container; this $module_handler is
  816. // no longer the $module_handler instance from above.
  817. $module_handler = $this->container->get('module_handler');
  818. $module_handler->reload();
  819. foreach ($modules as $module) {
  820. if (!$module_handler->moduleExists($module)) {
  821. throw new \RuntimeException("$module module is not enabled after enabling it.");
  822. }
  823. }
  824. }
  825. /**
  826. * Disables modules for this test.
  827. *
  828. * @param string[] $modules
  829. * A list of modules to disable. Dependencies are not resolved; i.e.,
  830. * multiple modules have to be specified with dependent modules first.
  831. * Code of previously enabled modules is still loaded. The modules are only
  832. * removed from the active module list.
  833. *
  834. * @throws \LogicException
  835. * If any module in $modules is already disabled.
  836. * @throws \RuntimeException
  837. * If a module is not disabled after disabling it.
  838. */
  839. protected function disableModules(array $modules) {
  840. // Unset the list of modules in the extension handler.
  841. $module_handler = $this->container->get('module_handler');
  842. $module_filenames = $module_handler->getModuleList();
  843. $extension_config = $this->config('core.extension');
  844. foreach ($modules as $module) {
  845. if (!$module_handler->moduleExists($module)) {
  846. throw new \LogicException("$module module cannot be disabled because it is not enabled.");
  847. }
  848. unset($module_filenames[$module]);
  849. $extension_config->clear('module.' . $module);
  850. }
  851. $extension_config->save();
  852. $module_handler->setModuleList($module_filenames);
  853. $module_handler->resetImplementations();
  854. // Update the kernel to remove their services.
  855. $this->container->get('kernel')->updateModules($module_filenames, $module_filenames);
  856. // Ensure isLoaded() is TRUE in order to make
  857. // \Drupal\Core\Theme\ThemeManagerInterface::render() work.
  858. // Note that the kernel has rebuilt the container; this $module_handler is
  859. // no longer the $module_handler instance from above.
  860. $module_handler = $this->container->get('module_handler');
  861. $module_handler->reload();
  862. foreach ($modules as $module) {
  863. if ($module_handler->moduleExists($module)) {
  864. throw new \RuntimeException("$module module is not disabled after disabling it.");
  865. }
  866. }
  867. }
  868. /**
  869. * Renders a render array.
  870. *
  871. * @param array $elements
  872. * The elements to render.
  873. *
  874. * @return string
  875. * The rendered string output (typically HTML).
  876. */
  877. protected function render(array &$elements) {
  878. // \Drupal\Core\Render\BareHtmlPageRenderer::renderBarePage calls out to
  879. // system_page_attachments() directly.
  880. if (!\Drupal::moduleHandler()->moduleExists('system')) {
  881. throw new \Exception(__METHOD__ . ' requires system module to be installed.');
  882. }
  883. // Use the bare HTML page renderer to render our links.
  884. $renderer = $this->container->get('bare_html_page_renderer');
  885. $response = $renderer->renderBarePage($elements, '', 'maintenance_page');
  886. // Glean the content from the response object.
  887. $content = $response->getContent();
  888. $this->setRawContent($content);
  889. $this->verbose('<pre style="white-space: pre-wrap">' . Html::escape($content));
  890. return $content;
  891. }
  892. /**
  893. * Sets an in-memory Settings variable.
  894. *
  895. * @param string $name
  896. * The name of the setting to set.
  897. * @param bool|string|int|array|null $value
  898. * The value to set. Note that array values are replaced entirely; use
  899. * \Drupal\Core\Site\Settings::get() to perform custom merges.
  900. */
  901. protected function setSetting($name, $value) {
  902. $settings = Settings::getAll();
  903. $settings[$name] = $value;
  904. new Settings($settings);
  905. }
  906. /**
  907. * Returns a ConfigImporter object to import test configuration.
  908. *
  909. * @return \Drupal\Core\Config\ConfigImporter
  910. *
  911. * @todo Move into Config-specific test base class.
  912. */
  913. protected function configImporter() {
  914. if (!$this->configImporter) {
  915. // Set up the ConfigImporter object for testing.
  916. $storage_comparer = new StorageComparer(
  917. $this->container->get('config.storage.sync'),
  918. $this->container->get('config.storage'),
  919. $this->container->get('config.manager')
  920. );
  921. $this->configImporter = new ConfigImporter(
  922. $storage_comparer,
  923. $this->container->get('event_dispatcher'),
  924. $this->container->get('config.manager'),
  925. $this->container->get('lock'),
  926. $this->container->get('config.typed'),
  927. $this->container->get('module_handler'),
  928. $this->container->get('module_installer'),
  929. $this->container->get('theme_handler'),
  930. $this->container->get('string_translation')
  931. );
  932. }
  933. // Always recalculate the changelist when called.
  934. return $this->configImporter->reset();
  935. }
  936. /**
  937. * Copies configuration objects from a source storage to a target storage.
  938. *
  939. * @param \Drupal\Core\Config\StorageInterface $source_storage
  940. * The source config storage.
  941. * @param \Drupal\Core\Config\StorageInterface $target_storage
  942. * The target config storage.
  943. *
  944. * @todo Move into Config-specific test base class.
  945. */
  946. protected function copyConfig(StorageInterface $source_storage, StorageInterface $target_storage) {
  947. $target_storage->deleteAll();
  948. foreach ($source_storage->listAll() as $name) {
  949. $target_storage->write($name, $source_storage->read($name));
  950. }
  951. }
  952. /**
  953. * Stops test execution.
  954. */
  955. protected function stop() {
  956. $this->getTestResultObject()->stop();
  957. }
  958. /**
  959. * Dumps the current state of the virtual filesystem to STDOUT.
  960. */
  961. protected function vfsDump() {
  962. vfsStream::inspect(new vfsStreamPrintVisitor());
  963. }
  964. /**
  965. * Returns the modules to enable for this test.
  966. *
  967. * @param string $class
  968. * The fully-qualified class name of this test.
  969. *
  970. * @return array
  971. */
  972. private static function getModulesToEnable($class) {
  973. $modules = array();
  974. while ($class) {
  975. if (property_exists($class, 'modules')) {
  976. // Only add the modules, if the $modules property was not inherited.
  977. $rp = new \ReflectionProperty($class, 'modules');
  978. if ($rp->class == $class) {
  979. $modules[$class] = $class::$modules;
  980. }
  981. }
  982. $class = get_parent_class($class);
  983. }
  984. // Modules have been collected in reverse class hierarchy order; modules
  985. // defined by base classes should be sorted first. Then, merge the results
  986. // together.
  987. $modules = array_reverse($modules);
  988. return call_user_func_array('array_merge_recursive', $modules);
  989. }
  990. /**
  991. * {@inheritdoc}
  992. */
  993. protected function prepareTemplate(\Text_Template $template) {
  994. $bootstrap_globals = '';
  995. // Fix missing bootstrap.php when $preserveGlobalState is FALSE.
  996. // @see https://github.com/sebastianbergmann/phpunit/pull/797
  997. $bootstrap_globals .= '$__PHPUNIT_BOOTSTRAP = ' . var_export($GLOBALS['__PHPUNIT_BOOTSTRAP'], TRUE) . ";\n";
  998. // Avoid repetitive test namespace discoveries to improve performance.
  999. // @see /core/tests/bootstrap.php
  1000. $bootstrap_globals .= '$namespaces = ' . var_export($GLOBALS['namespaces'], TRUE) . ";\n";
  1001. $template->setVar(array(
  1002. 'constants' => '',
  1003. 'included_files' => '',
  1004. 'globals' => $bootstrap_globals,
  1005. ));
  1006. }
  1007. /**
  1008. * Returns whether the current test runs in isolation.
  1009. *
  1010. * @return bool
  1011. *
  1012. * @see https://github.com/sebastianbergmann/phpunit/pull/1350
  1013. */
  1014. protected function isTestInIsolation() {
  1015. return function_exists('__phpunit_run_isolated_test');
  1016. }
  1017. /**
  1018. * BC: Automatically resolve former KernelTestBase class properties.
  1019. *
  1020. * Test authors should follow the provided instructions and adjust their tests
  1021. * accordingly.
  1022. *
  1023. * @deprecated in Drupal 8.0.x, will be removed before Drupal 8.2.0.
  1024. */
  1025. public function __get($name) {
  1026. if (in_array($name, array(
  1027. 'public_files_directory',
  1028. 'private_files_directory',
  1029. 'temp_files_directory',
  1030. 'translation_files_directory',
  1031. ))) {
  1032. // @comment it in again.
  1033. trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use the regular API method to retrieve it instead (e.g., Settings).", $name), E_USER_DEPRECATED);
  1034. switch ($name) {
  1035. case 'public_files_directory':
  1036. return Settings::get('file_public_path', \Drupal::service('site.path') . '/files');
  1037. case 'private_files_directory':
  1038. return $this->container->get('config.factory')->get('system.file')->get('path.private');
  1039. case 'temp_files_directory':
  1040. return file_directory_temp();
  1041. case 'translation_files_directory':
  1042. return Settings::get('file_public_path', \Drupal::service('site.path') . '/translations');
  1043. }
  1044. }
  1045. if ($name === 'configDirectories') {
  1046. trigger_error(sprintf("KernelTestBase::\$%s no longer exists. Use config_get_config_directory() directly instead.", $name), E_USER_DEPRECATED);
  1047. return array(
  1048. CONFIG_SYNC_DIRECTORY => config_get_config_directory(CONFIG_SYNC_DIRECTORY),
  1049. );
  1050. }
  1051. $denied = array(
  1052. // @see \Drupal\simpletest\TestBase
  1053. 'testId',
  1054. 'timeLimit',
  1055. 'results',
  1056. 'assertions',
  1057. 'skipClasses',
  1058. 'verbose',
  1059. 'verboseId',
  1060. 'verboseClassName',
  1061. 'verboseDirectory',
  1062. 'verboseDirectoryUrl',
  1063. 'dieOnFail',
  1064. 'kernel',
  1065. // @see \Drupal\simpletest\TestBase::prepareEnvironment()
  1066. 'generatedTestFiles',
  1067. // Properties from the old KernelTestBase class that has been removed.
  1068. 'keyValueFactory',
  1069. );
  1070. if (in_array($name, $denied) || strpos($name, 'original') === 0) {
  1071. throw new \RuntimeException(sprintf('TestBase::$%s property no longer exists', $name));
  1072. }
  1073. }
  1074. /**
  1075. * Prevents serializing any properties.
  1076. *
  1077. * Kernel tests are run in a separate process. To do this PHPUnit creates a
  1078. * script to run the test. If it fails, the test result object will contain a
  1079. * stack trace which includes the test object. It will attempt to serialize
  1080. * it. Returning an empty array prevents it from serializing anything it
  1081. * should not.
  1082. *
  1083. * @return array
  1084. * An empty array.
  1085. *
  1086. * @see vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist
  1087. */
  1088. public function __sleep() {
  1089. return [];
  1090. }
  1091. /**
  1092. * {@inheritdoc}
  1093. */
  1094. public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) {
  1095. // Cast objects implementing MarkupInterface to string instead of
  1096. // relying on PHP casting them to string depending on what they are being
  1097. // comparing with.
  1098. $expected = static::castSafeStrings($expected);
  1099. $actual = static::castSafeStrings($actual);
  1100. parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
  1101. }
  1102. }