KernelTestBase.php 39 KB

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