diff --git a/core/core.libraries.yml b/core/core.libraries.yml index a6d963c..7bb80a4 100644 --- a/core/core.libraries.yml +++ b/core/core.libraries.yml @@ -14,7 +14,7 @@ backbone: classList: remote: https://github.com/eligrey/classList.js - version: 2014-12-13 + version: "2014-12-13" license: name: Public Domain url: https://github.com/eligrey/classList.js/blob/2014-12-13/LICENSE.md diff --git a/core/lib/Drupal/Component/Serialization/Yaml.php b/core/lib/Drupal/Component/Serialization/Yaml.php index 8f75ffd..f7bc09a 100644 --- a/core/lib/Drupal/Component/Serialization/Yaml.php +++ b/core/lib/Drupal/Component/Serialization/Yaml.php @@ -25,20 +25,16 @@ class Yaml implements SerializationInterface { * {@inheritdoc} */ public static function encode($data) { - if (!isset(static::$serializer)) { - static::ensureSerializer(); - } - return static::$serializer->encode($data); + $serializer = static::getSerializer(); + return $serializer::encode($data); } /** * {@inheritdoc} */ public static function decode($raw) { - if (!isset(static::$serializer)) { - static::ensureSerializer(); - } - return static::$serializer->decode($raw); + $serializer = static::getSerializer(); + return $serializer::decode($raw); } /** @@ -51,18 +47,18 @@ public static function getFileExtension() { /** * Determines the optimal implementation to use for encoding and parsing Yaml. */ - protected static function ensureSerializer() { + protected static function getSerializer() { - if (isset(static::$serializer)) { - return; - } - // If the PECL YAML extension installed, use that. - if (extension_loaded('yaml')) { - static::$serializer = new YamlPecl(); - } - else { - // Otherwise, fallback to the Symfony implementation. - static::$serializer = new YamlSymfony(); + if (!isset(static::$serializer)) { + // If the PECL YAML extension installed, use that. + if (extension_loaded('yaml')) { + static::$serializer = '\Drupal\Component\Serialization\YamlPecl'; + } + else { + // Otherwise, fallback to the Symfony implementation. + static::$serializer = '\Drupal\Component\Serialization\YamlSymfony'; + } } + return static::$serializer; } } diff --git a/core/lib/Drupal/Component/Serialization/YamlPecl.php b/core/lib/Drupal/Component/Serialization/YamlPecl.php index 96ef967..0fd833c 100644 --- a/core/lib/Drupal/Component/Serialization/YamlPecl.php +++ b/core/lib/Drupal/Component/Serialization/YamlPecl.php @@ -15,18 +15,16 @@ class YamlPecl implements SerializationInterface { /** - * Sets defaults according to Drupal coding standards. - */ - public function __construct() { - ini_set('yaml.output_indent', 2); - // Do not break lines at 80 characters. - ini_set('yaml.output_width', -1); - } - - /** * {@inheritdoc} */ public static function encode($data) { + static $init; + if (!isset($init)) { + ini_set('yaml.output_indent', 2); + // Do not break lines at 80 characters. + ini_set('yaml.output_width', -1); + $init = TRUE; + } return yaml_emit($data, YAML_UTF8_ENCODING, YAML_LN_BREAK); } @@ -34,6 +32,10 @@ public static function encode($data) { * {@inheritdoc} */ public static function decode($raw) { + // yaml_parse() will error with an empty value. + if (!trim($raw)) { + return NULL; + } // @todo Use ErrorExceptions when https://drupal.org/node/1247666 is in. // yaml_parse() will throw errors instead of raising an exception. Until // such time as Drupal supports native PHP ErrorExceptions as the error @@ -42,7 +44,7 @@ public static function decode($raw) { // parsing errors into a throwable exception. // @see Drupal\Component\Serialization\Exception\InvalidDataTypeException // @see http://php.net/manual/en/class.errorexception.php - set_error_handler(array(__CLASS__, 'errorHandler')); + set_error_handler([__CLASS__, 'errorHandler']); $ndocs = 0; $data = yaml_parse($raw, 0, $ndocs, [ YAML_BOOL_TAG => '\Drupal\Component\Serialization\YamlPecl::applyBooleanCallbacks', diff --git a/core/lib/Drupal/Core/Serialization/Yaml.php b/core/lib/Drupal/Core/Serialization/Yaml.php index ca6fb71..6b70775 100644 --- a/core/lib/Drupal/Core/Serialization/Yaml.php +++ b/core/lib/Drupal/Core/Serialization/Yaml.php @@ -20,18 +20,14 @@ class Yaml extends ComponentYaml { /** * {@inheritdoc} */ - protected static function ensureSerializer(){ - if (isset(static::$serializer)) { - return; - } - + protected static function getSerializer(){ // If there is a settings.php override, use that. - if ($class = Settings::get('yaml_parser_class')) { - static::$serializer = new $class(); - } - else { - parent::ensureSerializer(); + if (!isset(static::$serializer) && + $class = Settings::get('yaml_parser_class')) { + + static::$serializer = $class; } + return parent::getSerializer(); } } diff --git a/core/modules/system/tests/modules/theme_test/theme_test.services.yml b/core/modules/system/tests/modules/theme_test/theme_test.services.yml index add8b4c..9049f36 100644 --- a/core/modules/system/tests/modules/theme_test/theme_test.services.yml +++ b/core/modules/system/tests/modules/theme_test/theme_test.services.yml @@ -1,7 +1,7 @@ services: theme_test.subscriber: class: Drupal\theme_test\EventSubscriber\ThemeTestSubscriber - arguments: [@current_route_match] + arguments: ["@current_route_match"] tags: - { name: event_subscriber } diff --git a/core/tests/Drupal/Tests/Component/Serialization/YamlBaseTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlBaseTest.php new file mode 100644 index 0000000..36d0637 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlBaseTest.php @@ -0,0 +1,101 @@ + '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] + ]; + } + + 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; + } + + 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/Component/Serialization/YamlPeclTest.php b/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php index 0705e1f..33030f9 100644 --- a/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlPeclTest.php @@ -7,151 +7,80 @@ namespace Drupal\Tests\Component\Serialization; -use Drupal\Component\Serialization\YamlSymfony; use Drupal\Component\Serialization\YamlPecl; -use Drupal\Tests\UnitTestCase; /** - * Tests the \Drupal\Component\Serialization\Yaml* implementations. + * Test the YamlPecl serialization implementation. * * @group Drupal * @group Serialization + * @coversDefaultClass \Drupal\Component\Serialization\YamlPecl + * @requires extension yaml */ -class YamlPeclTest extends UnitTestCase { - - protected function setUp() { - if (!extension_loaded('yaml')) { - $this->markTestSkipped('The PECL Yaml extension is not available.'); - } - } +class YamlPeclTest extends YamlBaseTest { /** - * Tests encoding with Symfony and decoding with PECL and vice versa. + * Tests encoding and decoding basic data structures. * - * @dataProvider providerYamlData - * @covers \Drupal\Component\Serialization\Yaml::encode - * @covers \Drupal\Component\Serialization\Yaml::decode - * @covers \Drupal\Component\Serialization\YamlPecl::encode - * @covers \Drupal\Component\Serialization\YamlPecl::decode - * @covers \Drupal\Component\Serialization\YamlSymfony::encode - * @covers \Drupal\Component\Serialization\YamlSymfony::decode + * @covers ::encode + * @covers ::decode + * @dataProvider providerEncodeDecodeTests */ - public function testEncodeDecode($data, $parser_class, $dumper_class) { - $dumper = new $dumper_class; - $parser = new $parser_class; - - $dumped = $dumper->encode($data); - - $parsed = $parser->decode($dumped); - - $this->assertEquals($data, $parsed); + public function testEncodeDecode($data) { + $this->assertEquals($data, YamlPecl::decode(YamlPecl::encode($data))); } /** - * Tests decoding YAML node anchors with both Symfony and PECL. + * Tests decoding YAML node anchors. * - * @dataProvider providerYamlNodeAnchors + * @covers ::decode + * @dataProvider providerDecodeTests */ - public function testDecodeNodeAnchors($data) { - $symfony = new YamlSymfony(); - $pecl = new YamlPecl(); - $this->assertEquals($symfony->decode($data), $pecl->decode($data)); + public function testDecode($string, $data) { + $this->assertEquals($data, YamlPecl::decode($string)); } /** - * Data provider for YAML instance tests. + * Tests our encode settings. * - * @return array + * @covers ::encode */ - public function providerYamlData() { - $object = array( - 'foo' => 'bar', - 'id' => 'schnitzel', - 'ponies' => array('nope', 'thanks'), - 'how' => array( - 'about' => 'if', - 'i' => 'ask', - 'nicely' - ), - 'the' => array( - 'answer' => array( - 'still' => 'would', - 'be' => 'Y', - ), - ), - 'how_many_times' => 123, - 'should_i_ask' => FALSE, - 1, - FALSE, - array(1, FALSE), - array(10), - array(0 => '123456'), - ); - - // Test parsing with Symfony and dumping with PECL. - $data[] = array( - $object, - 'Drupal\Component\Serialization\YamlSymfony', - 'Drupal\Component\Serialization\YamlPecl' - ); - // Test parsing with PECL and dumping with Symfony. - $data[] = array( - $object, - 'Drupal\Component\Serialization\YamlPecl', - 'Drupal\Component\Serialization\YamlSymfony' - ); - return $data; + 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']])); } /** - * Data provider for YAML instance tests. + * Test yaml boolean callback. * - * @return array + * @covers ::applyBooleanCallbacks + * @dataProvider providerBoolTest + * @param string $string + * String value for the yaml boolean. + * @param string|bool $expected + * The expected return */ - public function providerYamlNodeAnchors() { - $yaml = <<assertEquals($expected, YamlPecl::applyBooleanCallbacks($string, 'bool', NULL)); } /** - * Tests all YAML files are decoded in the same way with both Symfony and PECL. - * - * @dataProvider providerYamlFilesInCore + * @covers ::getFileExtension */ - public function testYamlFilesInCore($file) { - $data = file_get_contents($file); - $symfony = new YamlSymfony(); - $pecl = new YamlPecl(); - $this->assertEquals($symfony->decode($data), $pecl->decode($data)); + public function testGetFileExtension() { + $this->assertEquals('yml', YamlPecl::getFileExtension()); } /** - * Data provider for YAML files in core test. - * @return array + * Invalid yaml throws exception. + * + * @covers ::errorHandler + * @expectedException \Drupal\Component\Serialization\Exception\InvalidDataTypeException */ - public function providerYamlFilesInCore() { - $files = array(); - $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; + 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..0836be4 --- /dev/null +++ b/core/tests/Drupal/Tests/Component/Serialization/YamlSymfonyTest.php @@ -0,0 +1,70 @@ +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()); + } + + /** + * Invalid yaml throws 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..b15898f 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,117 @@ 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 */ public function testEncode() { - $decoded = array( - 'foo' => 'bar', - ); - $this->assertSame('foo: bar' . "\n", Yaml::encode($decoded)); + $stub = $this->getMockBuilder('\stdClass') + ->setMethods(['encode', 'decode', 'getFileExtension']) + ->getMock(); + $stub + ->expects($this->once()) + ->method('encode'); + YamlParserProxy::setMock($stub); + YamlStub::encode([]); } /** * @covers ::getFileExtension */ public function testGetFileExtension() { - $this->assertEquals('yml', Yaml::getFileExtension()); + $stub = $this->getMockBuilder('\stdClass') + ->setMethods(['encode', 'decode', 'getFileExtension']) + ->getMock(); + $stub + ->expects($this->never()) + ->method('getFileExtension'); + YamlParserProxy::setMock($stub); + $this->assertEquals('yml', YamlStub::getFileExtension()); } + /** + * Tests all YAML files are decoded in the same way with both Symfony and PECL. + * + * This tests is a little bit slow but it tests that we don't have any bugs + * in our YAML that might not be decoded correctly in on of our + * implementations. + * + * @TODO this should exist as an integration test no as part of our unit tests. + * + * @requires extension yaml + * @dataProvider providerYamlFilesInCore + */ + public function testYamlFiles($file) { + $data = file_get_contents($file); + try { + $this->assertEquals(YamlSymfony::decode($data), YamlPecl::decode($data)); + } + 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 core. + * @return array + */ + 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(); + } }