diff --git a/core/lib/Drupal/Component/Serialization/Yaml.php b/core/lib/Drupal/Component/Serialization/Yaml.php index 5e104ba..94efdd6 100644 --- a/core/lib/Drupal/Component/Serialization/Yaml.php +++ b/core/lib/Drupal/Component/Serialization/Yaml.php @@ -7,42 +7,37 @@ namespace Drupal\Component\Serialization; -use Drupal\Component\Serialization\Exception\InvalidDataTypeException; -use Symfony\Component\Yaml\Parser; -use Symfony\Component\Yaml\Dumper; - /** - * Default serialization for YAML using the Symfony component. + * Provides a YAML serialization implementation. + * + * Proxy implementation that will choose the best library based on availability. */ class Yaml implements SerializationInterface { /** + * The YAML implementation to use. + * + * @var \Drupal\Component\Serialization\SerializationInterface + */ + protected static $serializer; + + /** * {@inheritdoc} */ public static function encode($data) { - try { - $yaml = new Dumper(); - $yaml->setIndentation(2); - return $yaml->dump($data, PHP_INT_MAX, 0, TRUE, FALSE); - } - catch (\Exception $e) { - throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e); - } + // Instead of using \Drupal\Component\Serialization\Yaml::getSerializer(), + // always using Symfony for writing the data, to reduce the risk of having + // differences if different environments (like production and development) + // do not match in terms of what YAML implementation is available. + return YamlSymfony::encode($data); } /** * {@inheritdoc} */ public static function decode($raw) { - try { - $yaml = new Parser(); - // Make sure we have a single trailing newline. A very simple config like - // 'foo: bar' with no newline will fail to parse otherwise. - return $yaml->parse($raw, TRUE, FALSE); - } - catch (\Exception $e) { - throw new InvalidDataTypeException($e->getMessage(), $e->getCode(), $e); - } + $serializer = static::getSerializer(); + return $serializer::decode($raw); } /** @@ -52,4 +47,23 @@ public static function getFileExtension() { return 'yml'; } + /** + * Determines which implementation to use for parsing YAML. + */ + protected static function getSerializer() { + + if (!isset(static::$serializer)) { + // Use the PECL YAML extension if it is available. It has better + // performance for file reads and is YAML compliant. + if (extension_loaded('yaml')) { + static::$serializer = YamlPecl::class; + } + else { + // Otherwise, fallback to the Symfony implementation. + static::$serializer = YamlSymfony::class; + } + } + return static::$serializer; + } + } diff --git a/core/lib/Drupal/Component/Serialization/YamlPecl.php b/core/lib/Drupal/Component/Serialization/YamlPecl.php new file mode 100644 index 0000000..9c7397b --- /dev/null +++ b/core/lib/Drupal/Component/Serialization/YamlPecl.php @@ -0,0 +1,106 @@ + '\Drupal\Component\Serialization\YamlPecl::applyBooleanCallbacks', + ]); + restore_error_handler(); + return $data; + } + + /** + * Handles errors for \Drupal\Component\Serialization\YamlPecl::decode(). + * + * @param int $severity + * The severity level of the error. + * @param string $message + * The error message to display. + * + * @see \Drupal\Component\Serialization\YamlPecl::decode() + */ + public static function errorHandler($severity, $message) { + restore_error_handler(); + throw new InvalidDataTypeException($message, $severity); + } + + /** + * {@inheritdoc} + */ + public static function getFileExtension() { + return 'yml'; + } + + /** + * Applies callbacks after parsing to ignore 1.1 style booleans. + * + * @param mixed $value + * Value from YAML file. + * @param string $tag + * Tag that triggered the callback. + * @param int $flags + * Scalar entity style flags. + * + * @return string|bool + * FALSE, false, TRUE and true are returned as booleans, everything else is + * returned as a string. + */ + public static function applyBooleanCallbacks($value, $tag, $flags) { + // YAML 1.1 spec dictates that 'Y', 'N', 'y' and 'n' are booleans. But, we + // want the 1.2 behavior, so we only consider 'false', 'FALSE', 'true' and + // 'TRUE' as booleans. + if (!in_array(strtolower($value), ['false', 'true'], TRUE)) { + return $value; + } + $map = [ + 'false' => FALSE, + 'true' => TRUE, + ]; + return $map[strtolower($value)]; + } + +} diff --git a/core/lib/Drupal/Component/Serialization/Yaml.php b/core/lib/Drupal/Component/Serialization/YamlSymfony.php similarity index 91% copy from core/lib/Drupal/Component/Serialization/Yaml.php copy to core/lib/Drupal/Component/Serialization/YamlSymfony.php index 5e104ba..4ca5e25 100644 --- a/core/lib/Drupal/Component/Serialization/Yaml.php +++ b/core/lib/Drupal/Component/Serialization/YamlSymfony.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\Component\Serialization\Yaml. + * Contains \Drupal\Component\Serialization\YamlSymfony. */ namespace Drupal\Component\Serialization; @@ -14,7 +14,7 @@ /** * Default serialization for YAML using the Symfony component. */ -class Yaml implements SerializationInterface { +class YamlSymfony implements SerializationInterface { /** * {@inheritdoc} diff --git a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php index f25b7b0..1ab6e16 100644 --- a/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php +++ b/core/lib/Drupal/Core/Asset/LibraryDiscoveryParser.php @@ -12,9 +12,9 @@ use Drupal\Core\Asset\Exception\InvalidLibraryFileException; use Drupal\Core\Asset\Exception\LibraryDefinitionMissingLicenseException; use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\Serialization\Yaml; use Drupal\Core\Theme\ThemeManagerInterface; use Drupal\Component\Serialization\Exception\InvalidDataTypeException; -use Drupal\Component\Serialization\Yaml; use Drupal\Component\Utility\NestedArray; /** diff --git a/core/lib/Drupal/Core/Config/ConfigManager.php b/core/lib/Drupal/Core/Config/ConfigManager.php index f1dc2e5..0bd8395 100644 --- a/core/lib/Drupal/Core/Config/ConfigManager.php +++ b/core/lib/Drupal/Core/Config/ConfigManager.php @@ -8,12 +8,12 @@ namespace Drupal\Core\Config; use Drupal\Component\Diff\Diff; -use Drupal\Component\Serialization\Yaml; use Drupal\Core\Config\Entity\ConfigDependencyManager; use Drupal\Core\Config\Entity\ConfigEntityInterface; use Drupal\Core\Config\Entity\ConfigEntityTypeInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Serialization\Yaml; use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Core\StringTranslation\TranslationInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index 382733c..11f5056 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -7,8 +7,8 @@ namespace Drupal\Core\Config; -use Drupal\Component\Serialization\Yaml; use Drupal\Component\Serialization\Exception\InvalidDataTypeException; +use Drupal\Core\Serialization\Yaml; /** * Defines the file storage. diff --git a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php index 1c2bf81..16d52ea 100644 --- a/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php +++ b/core/lib/Drupal/Core/DependencyInjection/YamlFileLoader.php @@ -8,7 +8,7 @@ namespace Drupal\Core\DependencyInjection; use Drupal\Component\FileCache\FileCacheFactory; -use Drupal\Component\Serialization\Yaml; +use Drupal\Core\Serialization\Yaml; use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Definition; diff --git a/core/lib/Drupal/Core/Discovery/YamlDiscovery.php b/core/lib/Drupal/Core/Discovery/YamlDiscovery.php new file mode 100644 index 0000000..d7f17ec --- /dev/null +++ b/core/lib/Drupal/Core/Discovery/YamlDiscovery.php @@ -0,0 +1,30 @@ +findFiles() as $provider => $file) { + $all[$provider] = Yaml::decode(file_get_contents($file)); + } + + return $all; + } + +} diff --git a/core/lib/Drupal/Core/Extension/InfoParserDynamic.php b/core/lib/Drupal/Core/Extension/InfoParserDynamic.php index 64e4941..a74a4af 100644 --- a/core/lib/Drupal/Core/Extension/InfoParserDynamic.php +++ b/core/lib/Drupal/Core/Extension/InfoParserDynamic.php @@ -7,8 +7,8 @@ namespace Drupal\Core\Extension; -use Drupal\Component\Serialization\Yaml; use Drupal\Component\Serialization\Exception\InvalidDataTypeException; +use Drupal\Core\Serialization\Yaml; /** * Parses dynamic .info.yml files that might change during the page request. diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php index f8300cb..77df9ad 100644 --- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php +++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php @@ -7,12 +7,12 @@ namespace Drupal\Core\Extension; -use Drupal\Component\Serialization\Yaml; use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\DrupalKernelInterface; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Entity\FieldableEntityInterface; +use Drupal\Core\Serialization\Yaml; /** * Default implementation of the module installer. diff --git a/core/lib/Drupal/Core/Plugin/Discovery/YamlDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/YamlDiscovery.php index 058c752..78cf330 100644 --- a/core/lib/Drupal/Core/Plugin/Discovery/YamlDiscovery.php +++ b/core/lib/Drupal/Core/Plugin/Discovery/YamlDiscovery.php @@ -8,8 +8,8 @@ namespace Drupal\Core\Plugin\Discovery; use Drupal\Component\Plugin\Discovery\DiscoveryInterface; -use Drupal\Component\Discovery\YamlDiscovery as ComponentYamlDiscovery; use Drupal\Component\Plugin\Discovery\DiscoveryTrait; +use Drupal\Core\Discovery\YamlDiscovery as CoreYamlDiscovery; use Drupal\Core\StringTranslation\TranslatableMarkup; /** @@ -53,7 +53,7 @@ class YamlDiscovery implements DiscoveryInterface { * An array of directories to scan. */ function __construct($name, array $directories) { - $this->discovery = new ComponentYamlDiscovery($name, $directories); + $this->discovery = new CoreYamlDiscovery($name, $directories); } /** diff --git a/core/lib/Drupal/Core/Serialization/Yaml.php b/core/lib/Drupal/Core/Serialization/Yaml.php new file mode 100644 index 0000000..33a1054 --- /dev/null +++ b/core/lib/Drupal/Core/Serialization/Yaml.php @@ -0,0 +1,33 @@ +diff($active, $sync, $config_name); $edits = $diff->getEdits(); - $this->assertEqual($edits[0]->type, 'change', 'The first item in the diff is a change.'); - $this->assertEqual($edits[0]->orig[0], $change_key . ': ' . $original_data[$change_key], format_string("The active value for key '%change_key' is '%original_data'.", array('%change_key' => $change_key, '%original_data' => $original_data[$change_key]))); - $this->assertEqual($edits[0]->closing[0], $change_key . ': ' . $change_data, format_string("The sync value for key '%change_key' is '%change_data'.", array('%change_key' => $change_key, '%change_data' => $change_data))); + $this->assertYamlEdit($edits, $change_key, 'change', + [$change_key . ': ' . $original_data[$change_key]], + [$change_key . ': ' . $change_data]); // Reset data back to original, and remove a key $sync_data = $original_data; @@ -61,10 +61,11 @@ function testDiff() { // Verify that the diff reflects a removed key. $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name); $edits = $diff->getEdits(); - $this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.'); - $this->assertEqual($edits[1]->type, 'delete', 'The second item in the diff is a delete.'); - $this->assertEqual($edits[1]->orig[0], $remove_key . ': ' . $original_data[$remove_key], format_string("The active value for key '%remove_key' is '%original_data'.", array('%remove_key' => $remove_key, '%original_data' => $original_data[$remove_key]))); - $this->assertFalse($edits[1]->closing, format_string("The key '%remove_key' does not exist in sync.", array('%remove_key' => $remove_key))); + $this->assertYamlEdit($edits, $change_key, 'copy'); + $this->assertYamlEdit($edits, $remove_key, 'delete', + [$remove_key . ': ' . $original_data[$remove_key]], + FALSE + ); // Reset data back to original and add a key $sync_data = $original_data; @@ -74,10 +75,8 @@ function testDiff() { // Verify that the diff reflects an added key. $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name); $edits = $diff->getEdits(); - $this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.'); - $this->assertEqual($edits[1]->type, 'add', 'The second item in the diff is an add.'); - $this->assertFalse($edits[1]->orig, format_string("The key '%add_key' does not exist in active.", array('%add_key' => $add_key))); - $this->assertEqual($edits[1]->closing[0], $add_key . ': ' . $add_data, format_string("The sync value for key '%add_key' is '%add_data'.", array('%add_key' => $add_key, '%add_data' => $add_data))); + $this->assertYamlEdit($edits, $change_key, 'copy'); + $this->assertYamlEdit($edits, $add_key, 'add', FALSE, [$add_key . ': ' . $add_data]); // Test diffing a renamed config entity. $test_entity_id = $this->randomMachineName(); @@ -102,10 +101,11 @@ function testDiff() { $diff = \Drupal::service('config.manager')->diff($active, $sync, 'config_test.dynamic.' . $new_test_entity_id, $config_name); $edits = $diff->getEdits(); - $this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.'); - $this->assertEqual($edits[1]->type, 'change', 'The second item in the diff is a change.'); - $this->assertEqual($edits[1]->orig, array('id: ' . $new_test_entity_id)); - $this->assertEqual($edits[1]->closing, array('id: ' . $test_entity_id)); + $this->assertYamlEdit($edits, 'uuid', 'copy'); + $this->assertYamlEdit($edits, 'id', 'change', + ['id: ' . $new_test_entity_id], + ['id: ' . $test_entity_id]); + $this->assertYamlEdit($edits, 'label', 'copy'); $this->assertEqual($edits[2]->type, 'copy', 'The third item in the diff is a copy.'); $this->assertEqual(count($edits), 3, 'There are three items in the diff.'); } @@ -138,10 +138,56 @@ function testCollectionDiff() { // Test that the differences are detected when diffing the collection. $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name, NULL, 'test'); $edits = $diff->getEdits(); - $this->assertEqual($edits[0]->type, 'change', 'The second item in the diff is a copy.'); - $this->assertEqual($edits[0]->orig, array('foo: bar')); - $this->assertEqual($edits[0]->closing, array('foo: baz')); - $this->assertEqual($edits[1]->type, 'copy', 'The second item in the diff is a copy.'); + $this->assertYamlEdit($edits, 'foo', 'change', ['foo: bar'], ['foo: baz']); + } + + /** + * Helper method to test that an edit is found in a diff'd YAML file. + * + * @param array $edits + * A list of edits. + * @param string $field + * The field key that is being asserted. + * @param string $type + * The type of edit that is being asserted. + * @param mixed $orig + * (optional) The original value of of the edit. If not supplied, assertion + * is skipped. + * @param mixed $closing + * (optional) The closing value of of the edit. If not supplied, assertion + * is skipped. + */ + protected function assertYamlEdit(array $edits, $field, $type, $orig = NULL, $closing = NULL) { + $match = FALSE; + foreach ($edits as $edit) { + // Choose which section to search for the field. + $haystack = $type == 'add' ? $edit->closing : $edit->orig; + // Look through each line and try and find the key. + if (is_array($haystack)) { + foreach ($haystack as $item) { + if (strpos($item, $field . ':') === 0) { + $match = TRUE; + // Assert that the edit is of the type specified. + $this->assertEqual($edit->type, $type, "The $field item in the diff is a $type"); + // If an original value was given, assert that it matches. + if (isset($orig)) { + $this->assertIdentical($edit->orig, $orig, "The original value for key '$field' is correct."); + } + // If a closing value was given, assert that it matches. + if (isset($closing)) { + $this->assertIdentical($edit->closing, $closing, "The closing value for key '$field' is correct."); + } + // Break out of the search entirely. + break 2; + } + } + } + } + + // If we didn't match anything, fail. + if (!$match) { + $this->fail("$field edit was not matched"); + } } } diff --git a/core/modules/config/src/Tests/ConfigSingleImportExportTest.php b/core/modules/config/src/Tests/ConfigSingleImportExportTest.php index 088129a..ec2911a 100644 --- a/core/modules/config/src/Tests/ConfigSingleImportExportTest.php +++ b/core/modules/config/src/Tests/ConfigSingleImportExportTest.php @@ -211,8 +211,8 @@ public function testExport() { $this->assertFieldByXPath('//select[@name="config_name"]//option[@selected="selected"]', t('Fallback date format (fallback)'), 'The fallback date format config entity is selected when specified in the URL.'); $fallback_date = \Drupal::entityManager()->getStorage('date_format')->load('fallback'); - $data = Yaml::encode($fallback_date->toArray()); - $this->assertFieldByXPath('//textarea[@name="export"]', $data, 'The fallback date format config entity export code is displayed.'); + $yaml_text = (string) $this->xpath('//textarea[@name="export"]')[0]; + $this->assertEqual(Yaml::decode($yaml_text), $fallback_date->toArray(), 'The fallback date format config entity export code is displayed.'); } } diff --git a/core/modules/image/migration_templates/d6_imagecache_presets.yml b/core/modules/image/migration_templates/d6_imagecache_presets.yml index e4780a9..c3ec46f 100644 --- a/core/modules/image/migration_templates/d6_imagecache_presets.yml +++ b/core/modules/image/migration_templates/d6_imagecache_presets.yml @@ -18,7 +18,7 @@ process: effects: plugin: d6_imagecache_actions source: - - '@plugin' + - "@plugin" - data destination: plugin: entity:image_style diff --git a/core/modules/system/src/Tests/Installer/StandardInstallerTest.php b/core/modules/system/src/Tests/Installer/StandardInstallerTest.php index dfe920b..224b9fb 100644 --- a/core/modules/system/src/Tests/Installer/StandardInstallerTest.php +++ b/core/modules/system/src/Tests/Installer/StandardInstallerTest.php @@ -60,15 +60,15 @@ public function testStandardConfig() { $skipped_config = []; // \Drupal\simpletest\WebTestBase::installParameters() uses // simpletest@example.com as mail address. - $skipped_config['contact.form.feedback'][] = ' - simpletest@example.com'; + $skipped_config['contact.form.feedback'][] = '- simpletest@example.com'; // \Drupal\filter\Entity\FilterFormat::toArray() drops the roles of filter // formats. $skipped_config['filter.format.basic_html'][] = 'roles:'; - $skipped_config['filter.format.basic_html'][] = ' - authenticated'; + $skipped_config['filter.format.basic_html'][] = '- authenticated'; $skipped_config['filter.format.full_html'][] = 'roles:'; - $skipped_config['filter.format.full_html'][] = ' - administrator'; + $skipped_config['filter.format.full_html'][] = '- administrator'; $skipped_config['filter.format.restricted_html'][] = 'roles:'; - $skipped_config['filter.format.restricted_html'][] = ' - anonymous'; + $skipped_config['filter.format.restricted_html'][] = '- anonymous'; $this->assertInstalledConfig($skipped_config); } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_summary.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_summary.yml index daf92fa..54869da 100644 --- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_summary.yml +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_summary.yml @@ -95,7 +95,6 @@ display: footer: { } empty: { } relationships: { } - fields: { } display_extenders: { } cache_metadata: contexts: diff --git a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml index f880cfd..cd0b8b1 100644 --- a/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml +++ b/core/profiles/standard/config/install/core.entity_view_display.node.article.default.yml @@ -46,13 +46,6 @@ content: settings: link: true third_party_settings: { } - comment: - label: above - type: comment_default - weight: 110 - settings: - pager_id: 0 - third_party_settings: { } links: weight: 100 hidden: diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php new file mode 100644 index 0000000..e6e4be1 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php @@ -0,0 +1,88 @@ +assertEquals($data, YamlPecl::decode(YamlPecl::encode($data))); + } + + /** + * Tests decoding YAML node anchors. + * + * @covers ::decode + * @dataProvider providerDecodeTests + */ + public function testDecode($string, $data) { + $this->assertEquals($data, YamlPecl::decode($string)); + } + + /** + * Tests our encode settings. + * + * @covers ::encode + */ + public function testEncode() { + $this->assertEquals('--- +foo: + bar: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sapien ex, venenatis vitae nisi eu, posuere luctus dolor. Nullam convallis +... +', YamlPecl::encode(['foo' => ['bar' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sapien ex, venenatis vitae nisi eu, posuere luctus dolor. Nullam convallis']])); + } + + /** + * Tests YAML boolean callback. + * + * @param string $string + * String value for the YAML boolean. + * @param string|bool $expected + * The expected return value. + * + * @covers ::applyBooleanCallbacks + * @dataProvider providerBoolTest + */ + public function testApplyBooleanCallbacks($string, $expected) { + $this->assertEquals($expected, YamlPecl::applyBooleanCallbacks($string, 'bool', NULL)); + } + + /** + * @covers ::getFileExtension + */ + public function testGetFileExtension() { + $this->assertEquals('yml', YamlPecl::getFileExtension()); + } + + /** + * Tests that invalid YAML throws an exception. + * + * @covers ::errorHandler + * @expectedException \Drupal\Component\Serialization\Exception\InvalidDataTypeException + */ + public function testError() { + YamlPecl::decode('foo: [ads'); + } + +} diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlSymfonyTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlSymfonyTest.php new file mode 100644 index 0000000..f4029ea --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlSymfonyTest.php @@ -0,0 +1,71 @@ +assertEquals($data, YamlSymfony::decode(YamlSymfony::encode($data))); + } + + /** + * Tests decoding YAML node anchors. + * + * @covers ::decode + * @dataProvider providerDecodeTests + */ + public function testDecode($string, $data) { + $this->assertEquals($data, YamlSymfony::decode($string)); + } + + /** + * Tests our encode settings. + * + * @covers ::encode + */ + public function testEncode() { + $this->assertEquals('foo: + bar: \'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sapien ex, venenatis vitae nisi eu, posuere luctus dolor. Nullam convallis\' +', YamlSymfony::encode(['foo' => ['bar' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sapien ex, venenatis vitae nisi eu, posuere luctus dolor. Nullam convallis']])); + } + + /** + * @covers ::getFileExtension + */ + public function testGetFileExtension() { + $this->assertEquals('yml', YamlSymfony::getFileExtension()); + } + + /** + * Tests that invalid YAML throws an exception. + * + * @covers ::decode + * @expectedException \Drupal\Component\Serialization\Exception\InvalidDataTypeException + */ + public function testError() { + YamlSymfony::decode('foo: [ads'); + } + +} diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php index 94269e6..6009fe9 100644 --- a/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlTest.php @@ -7,7 +7,11 @@ namespace Drupal\Tests\Component\Serialization; +use Drupal\Component\Serialization\Exception\InvalidDataTypeException; +use Drupal\Component\Serialization\SerializationInterface; use Drupal\Component\Serialization\Yaml; +use Drupal\Component\Serialization\YamlPecl; +use Drupal\Component\Serialization\YamlSymfony; use Drupal\Tests\UnitTestCase; /** @@ -20,44 +24,104 @@ class YamlTest extends UnitTestCase { * @covers ::decode */ public function testDecode() { - // Test that files without line break endings are properly interpreted. - $yaml = 'foo: bar'; - $expected = array( - 'foo' => 'bar', - ); - $this->assertSame($expected, Yaml::decode($yaml)); - $yaml .= "\n"; - $this->assertSame($expected, Yaml::decode($yaml)); - $yaml .= "\n"; - $this->assertSame($expected, Yaml::decode($yaml)); - - $yaml = "{}\n"; - $expected = array(); - $this->assertSame($expected, Yaml::decode($yaml)); - - $yaml = ''; - $this->assertNULL(Yaml::decode($yaml)); - $yaml .= "\n"; - $this->assertNULL(Yaml::decode($yaml)); - $yaml .= "\n"; - $this->assertNULL(Yaml::decode($yaml)); + $stub = $this->getMockBuilder('\stdClass') + ->setMethods(['encode', 'decode', 'getFileExtension']) + ->getMock(); + $stub + ->expects($this->once()) + ->method('decode'); + YamlParserProxy::setMock($stub); + YamlStub::decode('test'); } /** - * @covers ::encode + * @covers ::getFileExtension */ - public function testEncode() { - $decoded = array( - 'foo' => 'bar', - ); - $this->assertSame('foo: bar' . "\n", Yaml::encode($decoded)); + public function testGetFileExtension() { + $stub = $this->getMockBuilder('\stdClass') + ->setMethods(['encode', 'decode', 'getFileExtension']) + ->getMock(); + $stub + ->expects($this->never()) + ->method('getFileExtension'); + YamlParserProxy::setMock($stub); + $this->assertEquals('yml', YamlStub::getFileExtension()); } /** - * @covers ::getFileExtension + * Tests all YAML files are decoded in the same way with Symfony and PECL. + * + * This test is a little bit slow but it tests that we do not have any bugs in + * our YAML that might not be decoded correctly in any of our implementations. + * + * @todo This should exist as an integration test not part of our unit tests. + * https://www.drupal.org/node/2597730 + * + * @requires extension yaml + * @dataProvider providerYamlFilesInCore */ - public function testGetFileExtension() { - $this->assertEquals('yml', Yaml::getFileExtension()); + public function testYamlFiles($file) { + $data = file_get_contents($file); + try { + $this->assertEquals(YamlSymfony::decode($data), YamlPecl::decode($data), $file); + } + catch (InvalidDataTypeException $e) { + // Provide file context to the failure so the exception message is useful. + $this->fail("Exception thrown parsing $file:\n" . $e->getMessage()); + } + } + + /** + * Data provider that lists all YAML files in core. + */ + public function providerYamlFilesInCore() { + $files = []; + $dirs = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(__DIR__ . '/../../../../../', \RecursiveDirectoryIterator::FOLLOW_SYMLINKS)); + foreach ($dirs as $dir) { + $pathname = $dir->getPathname(); + // Exclude vendor. + if ($dir->getExtension() == 'yml' && strpos($pathname, '/../../../../../vendor') === FALSE) { + if (strpos($dir->getRealPath(), 'invalid_file') !== FALSE) { + // There are some intentionally invalid files provided for testing + // library API behaviours, ignore them. + continue; + } + $files[] = array($dir->getRealPath()); + } + } + return $files; + } +} + +class YamlStub extends Yaml { + + public static function getSerializer() { + return '\Drupal\Tests\Component\Serialization\YamlParserProxy'; + } + +} + +class YamlParserProxy implements SerializationInterface { + + /** + * @var \Drupal\Component\Serialization\SerializationInterface + */ + static $mock; + + public static function setMock($mock) { + static::$mock = $mock; + } + + public static function encode($data) { + return static::$mock->encode($data); + } + + public static function decode($raw) { + return static::$mock->decode($raw); + } + + public static function getFileExtension() { + return static::$mock->getFileExtension(); } } diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlTestBase.php b/core/tests/Drupal/Tests/Component/Serialization/YamlTestBase.php new file mode 100644 index 0000000..aa5fef0 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlTestBase.php @@ -0,0 +1,104 @@ + 'bar', + 'id' => 'schnitzel', + 'ponies' => ['nope', 'thanks'], + 'how' => [ + 'about' => 'if', + 'i' => 'ask', + 'nicely', + ], + 'the' => [ + 'answer' => [ + 'still' => 'would', + 'be' => 'Y', + ], + ], + 'how_many_times' => 123, + 'should_i_ask' => FALSE, + 1, + FALSE, + [1, FALSE], + [10], + [0 => '123456'], + ], + [NULL] + ]; + } + + /** + * Some data that should be able to be de-serialized. + */ + public function providerDecodeTests() { + $data = [ + // NULL files. + ['', NULL], + ["\n", NULL], + ["---\n...\n", NULL], + + // Node anchors. + [ + " +jquery.ui: + version: &jquery_ui 1.10.2 + +jquery.ui.accordion: + version: *jquery_ui +", + [ + 'jquery.ui' => [ + 'version' => '1.10.2', + ], + 'jquery.ui.accordion' => [ + 'version' => '1.10.2', + ], + ], + ], + ]; + + // 1.2 Bool values. + foreach ($this->providerBoolTest() as $test) { + $data[] = ['bool: ' . $test[0], ['bool' => $test[1]]]; + } + $data = array_merge($data, $this->providerBoolTest()); + + return $data; + } + + /** + * Tests different boolean serialization and de-serialization. + */ + public function providerBoolTest() { + return [ + ['true', TRUE], + ['TRUE', TRUE], + ['True', TRUE], + ['y', 'y'], + ['Y', 'Y'], + ['false', FALSE], + ['FALSE', FALSE], + ['False', FALSE], + ['n', 'n'], + ['N', 'N'], + ]; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php index ed0c9ce..b79f5b1 100644 --- a/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php +++ b/core/tests/Drupal/Tests/Core/Asset/LibraryDiscoveryParserTest.php @@ -211,34 +211,6 @@ public function testVersion() { $this->assertEquals(\Drupal::VERSION, $libraries['core-versioned']['js'][0]['version']); } - /** - * Tests the version property with ISO dates. - * - * We want to make sure that versions defined in the YAML file are the same - * versions that are parsed. - * - * For example, ISO dates are converted into UNIX time by the YAML parser. - * - * @covers ::buildByExtension - */ - public function testNonStringVersion() { - $this->moduleHandler->expects($this->atLeastOnce()) - ->method('moduleExists') - ->with('versions') - ->will($this->returnValue(TRUE)); - - $path = __DIR__ . '/library_test_files'; - $path = substr($path, strlen($this->root) + 1); - $this->libraryDiscoveryParser->setPaths('module', 'versions', $path); - - $libraries = $this->libraryDiscoveryParser->buildByExtension('versions'); - - // As an example, we defined an ISO date in the YAML file and the YAML - // parser converts it into a UNIX timestamp. - $this->assertNotEquals('2014-12-13', $libraries['invalid-version']['version']); - // An example of an ISO date as a string which parses correctly. - $this->assertEquals('2014-12-13', $libraries['valid-version']['version']); - } /** * Tests that the version property of external libraries is handled. diff --git a/core/tests/Drupal/Tests/Core/Asset/library_test_files/versions.libraries.yml b/core/tests/Drupal/Tests/Core/Asset/library_test_files/versions.libraries.yml index bee49f5..7310a09 100644 --- a/core/tests/Drupal/Tests/Core/Asset/library_test_files/versions.libraries.yml +++ b/core/tests/Drupal/Tests/Core/Asset/library_test_files/versions.libraries.yml @@ -20,13 +20,3 @@ core-versioned: core-versioned.css: {} js: core-versioned.js: {} - -invalid-version: - version: 2014-12-13 - js: - versioned.js: {} - -valid-version: - version: "2014-12-13" - js: - versioned.js: {} diff --git a/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php b/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php index 262446e..91e4426 100644 --- a/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Extension/InfoParserUnitTest.php @@ -152,7 +152,7 @@ public function testInfoParserCommonInfo() { description: 'testing info file parsing' simple_string: 'A simple string' version: "VERSION" -double_colon: dummyClassName:: +double_colon: dummyClassName::method COMMONTEST; vfsStream::setup('modules'); @@ -164,7 +164,7 @@ public function testInfoParserCommonInfo() { $info_values = $this->infoParser->parse(vfsStream::url('modules/fixtures/common_test.info.txt')); $this->assertEquals($info_values['simple_string'], 'A simple string', 'Simple string value was parsed correctly.'); $this->assertEquals($info_values['version'], \Drupal::VERSION, 'Constant value was parsed correctly.'); - $this->assertEquals($info_values['double_colon'], 'dummyClassName::', 'Value containing double-colon was parsed correctly.'); + $this->assertEquals($info_values['double_colon'], 'dummyClassName::method', 'Value containing double-colon was parsed correctly.'); } }