diff --git a/core/tests/Drupal/FunctionalTests/Core/Config/SchemaConfigListenerTest.php b/core/tests/Drupal/FunctionalTests/Core/Config/SchemaConfigListenerTest.php new file mode 100644 index 00000000000..7138c5e80fc --- /dev/null +++ b/core/tests/Drupal/FunctionalTests/Core/Config/SchemaConfigListenerTest.php @@ -0,0 +1,22 @@ +config('config_schema_test.schemaless')->set('foo', 'bar')->save(); - $this->fail($message); - } - catch (SchemaIncompleteException $e) { - $this->pass($message); - $this->assertEqual('No schema for config_schema_test.schemaless', $e->getMessage()); - } - - // Test a valid schema. - $message = 'Unexpected SchemaIncompleteException thrown'; - $config = $this->config('config_test.types')->set('int', 10); - try { - $config->save(); - $this->pass($message); - } - catch (SchemaIncompleteException $e) { - $this->fail($message); - } - - // Test an invalid schema. - $message = 'Expected SchemaIncompleteException thrown'; - $config = $this->config('config_test.types') - ->set('foo', 'bar') - ->set('array', 1); - try { - $config->save(); - $this->fail($message); - } - catch (SchemaIncompleteException $e) { - $this->pass($message); - $this->assertEqual('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', $e->getMessage()); - } + protected function setUp() { + parent::setUp(); + // Install configuration provided by the module so that the order of the + // config keys is the same as + // \Drupal\FunctionalTests\Core\Config\SchemaConfigListenerTest. + $this->installConfig(['config_test']); } } diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index 01e15d0066b..6a8b150b053 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -12,6 +12,7 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\SafeMarkup; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Cache\Cache; +use Drupal\Core\Config\Testing\ConfigSchemaChecker; use Drupal\Core\Database\Database; use Drupal\Core\DrupalKernel; use Drupal\Core\Serialization\Yaml; @@ -154,6 +155,31 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { */ protected $configImporter; + /** + * Set to TRUE to strict check all configuration saved. + * + * @see \Drupal\Core\Config\Testing\ConfigSchemaChecker + * + * @var bool + */ + protected $strictConfigSchema = TRUE; + + /** + * An array of config object names that are excluded from schema checking. + * + * @var string[] + */ + protected static $configSchemaCheckerExclusions = array( + // Following are used to test lack of or partial schema. Where partial + // schema is provided, that is explicitly tested in specific tests. + 'config_schema_test.noschema', + 'config_schema_test.someschema', + 'config_schema_test.schema_data_types', + 'config_schema_test.no_schema_data_types', + // Used to test application of schema to filtering of configuration. + 'config_test.dynamic.system', + ); + /** * The profile to install as a basis for testing. * @@ -990,6 +1016,17 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { } // Copy the testing-specific service overrides in place. copy($settings_services_file, $directory . '/services.yml'); + if ($this->strictConfigSchema) { + // Add a listener to validate configuration schema on save. + $content = file_get_contents($directory . '/services.yml'); + $services = Yaml::decode($content); + $services['services']['simpletest.config_schema_checker'] = [ + 'class' => ConfigSchemaChecker::class, + 'arguments' => ['@config.typed', $this->getConfigSchemaExclusions()], + 'tags' => [['name' => 'event_subscriber']] + ]; + file_put_contents($directory . '/services.yml', Yaml::encode($services)); + } // Since Drupal is bootstrapped already, install_begin_request() will not // bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to @@ -1686,4 +1723,23 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase { $file_cache->delete($filename); } + /** + * Gets the config schema exclusions for this test. + * + * @return string[] + * An array of config object names that are excluded from schema checking. + */ + protected function getConfigSchemaExclusions() { + $class = get_class($this); + $exceptions = []; + while ($class) { + if (property_exists($class, 'configSchemaCheckerExclusions')) { + $exceptions = array_merge($exceptions, $class::$configSchemaCheckerExclusions); + } + $class = get_parent_class($class); + } + // Filter out any duplicates. + return array_unique($exceptions); + } + } diff --git a/core/tests/Drupal/Tests/Traits/Core/Config/SchemaConfigListenerTestTrait.php b/core/tests/Drupal/Tests/Traits/Core/Config/SchemaConfigListenerTestTrait.php new file mode 100644 index 00000000000..02931e4577c --- /dev/null +++ b/core/tests/Drupal/Tests/Traits/Core/Config/SchemaConfigListenerTestTrait.php @@ -0,0 +1,54 @@ +config('config_schema_test.schemaless')->set('foo', 'bar')->save(); + $this->fail($message); + } + catch (SchemaIncompleteException $e) { + $this->pass($message); + $this->assertEqual('No schema for config_schema_test.schemaless', $e->getMessage()); + } + + // Test a valid schema. + $message = 'Unexpected SchemaIncompleteException thrown'; + $config = $this->config('config_test.types')->set('int', 10); + try { + $config->save(); + $this->pass($message); + } + catch (SchemaIncompleteException $e) { + $this->fail($message); + } + + // Test an invalid schema. + $message = 'Expected SchemaIncompleteException thrown'; + $config = $this->config('config_test.types') + ->set('foo', 'bar') + ->set('array', 1); + try { + $config->save(); + $this->fail($message); + } + catch (SchemaIncompleteException $e) { + $this->pass($message); + $this->assertEqual('Schema errors for config_test.types with the following errors: config_test.types:array variable type is integer but applied schema class is Drupal\Core\Config\Schema\Sequence, config_test.types:foo missing schema', $e->getMessage()); + } + + } + +}