diff --git a/core/lib/Drupal/Core/Config/Testing/ConfigSchemaChecker.php b/core/lib/Drupal/Core/Config/Testing/ConfigSchemaChecker.php new file mode 100644 index 0000000..1c5b043 --- /dev/null +++ b/core/lib/Drupal/Core/Config/Testing/ConfigSchemaChecker.php @@ -0,0 +1,94 @@ +typedManager = $typed_manager; + } + + /** + * Removes stale static cache entries when configuration is saved. + * + * @param ConfigCrudEvent $event + * The configuration event. + */ + public function onConfigSave(ConfigCrudEvent $event) { + $saved_config = $event->getConfig(); + // Check that we have a valid for all config saved during tests. Do not + // validate the same config / data. + $name = $saved_config->getName(); + $data = $saved_config->get(); + $checksum = crc32(serialize($data)); + if (!isset($this->checked[$name . ':' . $checksum])) { + $this->checked[$name . ':' . $checksum] = TRUE; + $errors = $this->checkConfigSchema($this->typedManager, $name, $data); + if ($errors === FALSE) { + throw new SchemaIncompleteException(String::format('No schema for @config_name', array('@config_name' => $name))); + } + elseif (is_array($errors)) { + $text_errors = []; + foreach ($errors as $key => $error) { + $text_errors[] = String::format('@key: @error', array('@key' => $key, '@error' => $error)); + } + throw new SchemaIncompleteException(String::format('Schema errors for @config_name with the following errors: @errors', array('@config_name' => $name, '@errors' => implode(', ', $text_errors)))); + } + } + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() { + $events[ConfigEvents::SAVE][] = array('onConfigSave', 255); + return $events; + } + +} diff --git a/core/modules/config/src/Tests/SchemaConfigListenerTest.php b/core/modules/config/src/Tests/SchemaConfigListenerTest.php new file mode 100644 index 0000000..070e268 --- /dev/null +++ b/core/modules/config/src/Tests/SchemaConfigListenerTest.php @@ -0,0 +1,70 @@ +set('foo', 'bar')->save(); + $this->fail($msg); + } + catch (SchemaIncompleteException $e) { + $this->pass($msg); + $this->assertEqual('No schema for config_schema_test.noschema', $e->getMessage()); + } + + $msg = 'Unexpected SchemaIncompleteException thrown'; + $config = \Drupal::config('config_test.types')->set('int', 10); + try { + $config->save(); + $this->pass($msg); + } + catch (SchemaIncompleteException $e) { + $this->fail($msg); + } + + $msg = 'Expected SchemaIncompleteException thrown'; + $config = \Drupal::config('config_test.types') + ->set('foo', 'bar') + ->set('array', 1); + try { + $config->save(); + $this->fail($msg); + } + catch (SchemaIncompleteException $e) { + $this->pass($msg); + $this->pass('Schema errors for config_test.types with the following errors: config_test.types:foo: Missing schema., config_test.types:array: Variable type is integer but applied schema class is Drupal\Core\Config\Schema\Sequence.'); + } + } + +} diff --git a/core/modules/config/src/Tests/SchemaConfigListenerWebTest.php b/core/modules/config/src/Tests/SchemaConfigListenerWebTest.php new file mode 100644 index 0000000..d38b055 --- /dev/null +++ b/core/modules/config/src/Tests/SchemaConfigListenerWebTest.php @@ -0,0 +1,74 @@ +set('foo', 'bar')->save(); + $this->fail($msg); + } + catch (SchemaIncompleteException $e) { + $this->pass($msg); + $this->assertEqual('No schema for config_schema_test.noschema', $e->getMessage()); + } + + $msg = 'Unexpected SchemaIncompleteException thrown'; + $config = \Drupal::config('config_test.types')->set('int', 10); + try { + $config->save(); + $this->pass($msg); + } + catch (SchemaIncompleteException $e) { + $this->fail($msg); + } + + $msg = 'Expected SchemaIncompleteException thrown'; + $config = \Drupal::config('config_test.types') + ->set('foo', 'bar') + ->set('array', 1); + try { + $config->save(); + $this->fail($msg); + } + catch (SchemaIncompleteException $e) { + $this->pass($msg); + $this->pass('Schema errors for config_test.types with the following errors: config_test.types:foo: Missing schema., config_test.types:array: Variable type is integer but applied schema class is Drupal\Core\Config\Schema\Sequence.'); + } + + $this->drupalGet('config_test/schema_listener'); + $this->assertText('No schema for config_schema_test.noschema'); + } + +} diff --git a/core/modules/config/tests/config_test/config_test.routing.yml b/core/modules/config/tests/config_test/config_test.routing.yml index 4873d20..f536326 100644 --- a/core/modules/config/tests/config_test/config_test.routing.yml +++ b/core/modules/config/tests/config_test/config_test.routing.yml @@ -58,3 +58,10 @@ entity.config_test.delete_form_config_test_no_status: _entity_form: 'config_test_no_status.delete' requirements: _access: 'TRUE' + +config_test.schema_listener: + path: '/config_test/schema_listener' + defaults: + _content: '\Drupal\config_test\SchemaListenerController::test' + requirements: + _access: 'TRUE' diff --git a/core/modules/config/tests/config_test/src/SchemaListenerController.php b/core/modules/config/tests/config_test/src/SchemaListenerController.php new file mode 100644 index 0000000..fe91ade --- /dev/null +++ b/core/modules/config/tests/config_test/src/SchemaListenerController.php @@ -0,0 +1,32 @@ +config('config_schema_test.noschema')->set('foo', 'bar')->save(); + } + catch (SchemaIncompleteException $e) { + return [ + '#markup' => $e->getMessage(), + ]; + } + } + +} diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php index e281988..d947caa 100644 --- a/core/modules/simpletest/src/KernelTestBase.php +++ b/core/modules/simpletest/src/KernelTestBase.php @@ -263,6 +263,13 @@ public function containerBuild(ContainerBuilder $container) { ->addArgument(Database::getConnection()) ->addArgument('config'); + if ($this->strictConfigSchema) { + $container + ->register('simpletest.config_schema_checker', 'Drupal\Core\Config\Testing\ConfigSchemaChecker') + ->addArgument(new Reference('config.typed')) + ->addTag('event_subscriber'); + } + $keyvalue_options = $container->getParameter('factory.keyvalue') ?: array(); $keyvalue_options['default'] = 'keyvalue.memory'; $container->setParameter('factory.keyvalue', $keyvalue_options); diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php index 9475715..e822adb 100644 --- a/core/modules/simpletest/src/TestBase.php +++ b/core/modules/simpletest/src/TestBase.php @@ -203,6 +203,15 @@ protected $originalSessionName; /** + * Set to TRUE to strict check all configuration saved. + * + * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker + * + * @var bool + */ + protected $strictConfigSchema = FALSE; + + /** * Constructor for Test. * * @param $test_id diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 730f84f..6792b6f 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -816,6 +816,16 @@ protected function setUp() { // testing specific overrides file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) ."';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' ."\n", FILE_APPEND); } + if ($this->strictConfigSchema) { + $yaml = new \Symfony\Component\Yaml\Yaml(); + $services = $yaml->parse($directory . '/services.yml'); + $services['services']['simpletest.config_schema_checker'] = [ + 'class' => 'Drupal\Core\Config\Testing\ConfigSchemaChecker', + 'arguments' => ['@config.typed'], + 'tags' => [['name' => 'event_subscriber']] + ]; + file_put_contents($directory . '/services.yml', $yaml->dump($services)); + } $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSite . '/testing.services.yml'; if (file_exists($settings_services_file)) { // Copy the testing-specific service overrides in place.