KernelTestBase.php 39 KB

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