KernelTestBase.php 39 KB

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