diff --git a/link b/link index a5030d0683..6a2ec15e22 100755 --- a/link +++ b/link @@ -37,7 +37,12 @@ if (!is_dir("$argv[1]/vendor/symfony")) { $sfPackages = array('symfony/symfony' => __DIR__); $filesystem = new Filesystem(); -foreach (glob(__DIR__.'/src/Symfony/{Bundle,Bridge,Component,Component/Security}/*', GLOB_BRACE | GLOB_ONLYDIR | GLOB_NOSORT) as $dir) { +$braces = array('Bundle', 'Bridge', 'Component', 'Component/Security'); +$directories = call_user_func_array('array_merge', array_values(array_map(function ($part) { + return glob(__DIR__.'/src/Symfony/'.$part.'/*', GLOB_ONLYDIR | GLOB_NOSORT); +}, $braces))); + +foreach ($directories as $dir) { if ($filesystem->exists($composer = "$dir/composer.json")) { $sfPackages[json_decode(file_get_contents($composer))->name] = $dir; } diff --git a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php index f32e155078..abb1241653 100644 --- a/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php +++ b/src/Symfony/Bridge/PhpUnit/DeprecationErrorHandler.php @@ -214,41 +214,62 @@ class DeprecationErrorHandler return $b['count'] - $a['count']; }; - $groups = array('unsilenced', 'remaining'); - if (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode) { - $groups[] = 'remaining vendor'; - } - array_push($groups, 'legacy', 'other'); + $displayDeprecations = function ($deprecations) use ($colorize, $cmp) { + $groups = array('unsilenced', 'remaining'); + if (DeprecationErrorHandler::MODE_WEAK_VENDORS === $mode) { + $groups[] = 'remaining vendor'; + } + array_push($groups, 'legacy', 'other'); - foreach ($groups as $group) { - if ($deprecations[$group.'Count']) { - echo "\n", $colorize( - sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), - 'legacy' !== $group && 'remaining vendor' !== $group - ), "\n"; + foreach ($groups as $group) { + if ($deprecations[$group.'Count']) { + echo "\n", $colorize( + sprintf('%s deprecation notices (%d)', ucfirst($group), $deprecations[$group.'Count']), + 'legacy' !== $group && 'remaining vendor' !== $group + ), "\n"; - uasort($deprecations[$group], $cmp); + uasort($deprecations[$group], $cmp); - foreach ($deprecations[$group] as $msg => $notices) { - echo "\n ", $notices['count'], 'x: ', $msg, "\n"; + foreach ($deprecations[$group] as $msg => $notices) { + echo "\n ", $notices['count'], 'x: ', $msg, "\n"; - arsort($notices); + arsort($notices); - foreach ($notices as $method => $count) { - if ('count' !== $method) { - echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; + foreach ($notices as $method => $count) { + if ('count' !== $method) { + echo ' ', $count, 'x in ', preg_replace('/(.*)\\\\(.*?::.*?)$/', '$2 from $1', $method), "\n"; + } } } } } - } - if (!empty($notices)) { - echo "\n"; + if (!empty($notices)) { + echo "\n"; + } + }; + + $displayDeprecations($deprecations); + + // store failing status + $isFailing = DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']; + + // reset deprecations array + foreach ($deprecations as $group => $arrayOrInt) { + $deprecations[$group] = is_int($arrayOrInt) ? 0 : array(); } - if (DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) { - exit(1); - } + register_shutdown_function(function () use (&$deprecations, $isFailing, $displayDeprecations, $mode) { + foreach ($deprecations as $group => $arrayOrInt) { + if (0 < (is_int($arrayOrInt) ? $arrayOrInt : count($arrayOrInt))) { + echo "Shutdown-time deprecations:\n"; + break; + } + } + $displayDeprecations($deprecations); + if ($isFailing || DeprecationErrorHandler::MODE_WEAK !== $mode && $mode < $deprecations['unsilencedCount'] + $deprecations['remainingCount'] + $deprecations['otherCount']) { + exit(1); + } + }); }); } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php similarity index 76% rename from src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php rename to src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php index 5ed545a512..2da40f2c20 100644 --- a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListener.php +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV5.php @@ -18,7 +18,7 @@ namespace Symfony\Bridge\PhpUnit\Legacy; * * @internal */ -class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener +class SymfonyTestsListenerForV5 extends \PHPUnit_Framework_BaseTestListener { private $trait; @@ -34,26 +34,26 @@ class SymfonyTestsListener extends \PHPUnit_Framework_BaseTestListener public function startTestSuite(\PHPUnit_Framework_TestSuite $suite) { - return $this->trait->startTestSuite($suite); + $this->trait->startTestSuite($suite); } public function addSkippedTest(\PHPUnit_Framework_Test $test, \Exception $e, $time) { - return $this->trait->addSkippedTest($test, $e, $time); + $this->trait->addSkippedTest($test, $e, $time); } public function startTest(\PHPUnit_Framework_Test $test) { - return $this->trait->startTest($test); + $this->trait->startTest($test); } public function addWarning(\PHPUnit_Framework_Test $test, \PHPUnit_Framework_Warning $e, $time) { - return $this->trait->addWarning($test, $e, $time); + $this->trait->addWarning($test, $e, $time); } public function endTest(\PHPUnit_Framework_Test $test, $time) { - return $this->trait->endTest($test, $time); + $this->trait->endTest($test, $time); } } diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php new file mode 100644 index 0000000000..d8e74e8192 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV6.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit\Legacy; + +use PHPUnit\Framework\BaseTestListener; +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; + +/** + * Collects and replays skipped tests. + * + * @author Nicolas Grekas + * + * @internal + */ +class SymfonyTestsListenerForV6 extends BaseTestListener +{ + private $trait; + + public function __construct(array $mockedNamespaces = array()) + { + $this->trait = new Legacy\SymfonyTestsListenerTrait($mockedNamespaces); + } + + public function globalListenerDisabled() + { + $this->trait->globalListenerDisabled(); + } + + public function startTestSuite(TestSuite $suite) + { + $this->trait->startTestSuite($suite); + } + + public function addSkippedTest(Test $test, \Exception $e, $time) + { + $this->trait->addSkippedTest($test, $e, $time); + } + + public function startTest(Test $test) + { + $this->trait->startTest($test); + } + + public function addWarning(Test $test, Warning $e, $time) + { + $this->trait->addWarning($test, $e, $time); + } + + public function endTest(Test $test, $time) + { + $this->trait->endTest($test, $time); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php new file mode 100644 index 0000000000..d3acd53960 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Legacy/SymfonyTestsListenerForV7.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\PhpUnit; + +use PHPUnit\Framework\Test; +use PHPUnit\Framework\TestListener; +use PHPUnit\Framework\TestListenerDefaultImplementation; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Framework\Warning; + +/** + * Collects and replays skipped tests. + * + * @author Nicolas Grekas + * + * @internal + */ +class SymfonyTestsListenerForV7 implements TestListener +{ + use TestListenerDefaultImplementation; + + private $trait; + + public function __construct(array $mockedNamespaces = array()) + { + $this->trait = new SymfonyTestsListenerTrait($mockedNamespaces); + } + + public function globalListenerDisabled() + { + $this->trait->globalListenerDisabled(); + } + + public function startTestSuite(TestSuite $suite): void + { + $this->trait->startTestSuite($suite); + } + + public function addSkippedTest(Test $test, \Throwable $t, float $time): void + { + $this->trait->addSkippedTest($test, $t, $time); + } + + public function startTest(Test $test): void + { + $this->trait->startTest($test); + } + + public function addWarning(Test $test, Warning $e, float $time): void + { + $this->trait->addWarning($test, $e, $time); + } + + public function endTest(Test $test, float $time): void + { + $this->trait->endTest($test, $time); + } +} diff --git a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php index c11fde9526..d46a79e00e 100644 --- a/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php +++ b/src/Symfony/Bridge/PhpUnit/SymfonyTestsListener.php @@ -11,60 +11,10 @@ namespace Symfony\Bridge\PhpUnit; -use PHPUnit\Framework\BaseTestListener; -use PHPUnit\Framework\Test; -use PHPUnit\Framework\TestSuite; -use PHPUnit\Framework\Warning; - if (class_exists('PHPUnit_Runner_Version') && version_compare(\PHPUnit_Runner_Version::id(), '6.0.0', '<')) { - class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListener', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); -// Using an early return instead of a else does not work when using the PHPUnit phar due to some weird PHP behavior (the class -// gets defined without executing the code before it and so the definition is not properly conditional) + class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV5', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); +} elseif (version_compare(\PHPUnit\Runner\Version::id(), '7.0.0', '<')) { + class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV6', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); } else { - /** - * Collects and replays skipped tests. - * - * @author Nicolas Grekas - * - * @final - */ - class SymfonyTestsListener extends BaseTestListener - { - private $trait; - - public function __construct(array $mockedNamespaces = array()) - { - $this->trait = new Legacy\SymfonyTestsListenerTrait($mockedNamespaces); - } - - public function globalListenerDisabled() - { - $this->trait->globalListenerDisabled(); - } - - public function startTestSuite(TestSuite $suite) - { - return $this->trait->startTestSuite($suite); - } - - public function addSkippedTest(Test $test, \Exception $e, $time) - { - return $this->trait->addSkippedTest($test, $e, $time); - } - - public function startTest(Test $test) - { - return $this->trait->startTest($test); - } - - public function addWarning(Test $test, Warning $e, $time) - { - return $this->trait->addWarning($test, $e, $time); - } - - public function endTest(Test $test, $time) - { - return $this->trait->endTest($test, $time); - } - } + class_alias('Symfony\Bridge\PhpUnit\Legacy\SymfonyTestsListenerForV7', 'Symfony\Bridge\PhpUnit\SymfonyTestsListener'); } diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt index 39a3e98586..7a0595a7dd 100644 --- a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/default.phpt @@ -59,6 +59,10 @@ $foo = new FooTestCase(); $foo->testLegacyFoo(); $foo->testNonLegacyBar(); +register_shutdown_function(function () { + exit('I get precedence over any exit statements inside the deprecation error handler.'); +}); + ?> --EXPECTF-- Unsilenced deprecation notices (3) @@ -80,3 +84,4 @@ Other deprecation notices (1) 1x: root deprecation +I get precedence over any exit statements inside the deprecation error handler. diff --git a/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt new file mode 100644 index 0000000000..fddeed6085 --- /dev/null +++ b/src/Symfony/Bridge/PhpUnit/Tests/DeprecationErrorHandler/shutdown_deprecations.phpt @@ -0,0 +1,91 @@ +--TEST-- +Test DeprecationErrorHandler in default mode +--FILE-- +testLegacyFoo(); +$foo->testNonLegacyBar(); + +register_shutdown_function(function () { + @trigger_error('root deprecation during shutdown', E_USER_DEPRECATED); +}); + +?> +--EXPECTF-- +Unsilenced deprecation notices (3) + + 2x: unsilenced foo deprecation + 2x in FooTestCase::testLegacyFoo + + 1x: unsilenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Remaining deprecation notices (1) + + 1x: silenced bar deprecation + 1x in FooTestCase::testNonLegacyBar + +Legacy deprecation notices (1) + +Other deprecation notices (1) + + 1x: root deprecation + +Shutdown-time deprecations: + +Other deprecation notices (1) + + 1x: root deprecation during shutdown diff --git a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit index fbcedeca8c..fd7c800fed 100755 --- a/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit +++ b/src/Symfony/Bridge/PhpUnit/bin/simple-phpunit @@ -17,7 +17,7 @@ error_reporting(-1); if (PHP_VERSION_ID >= 70200) { // PHPUnit 6 is required for PHP 7.2+ - $PHPUNIT_VERSION = getenv('SYMFONY_PHPUNIT_VERSION') ?: '6.3'; + $PHPUNIT_VERSION = getenv('SYMFONY_PHPUNIT_VERSION') ?: '6.5'; } elseif (PHP_VERSION_ID >= 50600) { // PHPUnit 4 does not support PHP 7 $PHPUNIT_VERSION = getenv('SYMFONY_PHPUNIT_VERSION') ?: '5.7'; diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php index 6fc94bc8a2..1fb263f00f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/ControllerTrait.php @@ -370,12 +370,12 @@ trait ControllerTrait /** * Checks the validity of a CSRF token. * - * @param string $id The id used when generating the token - * @param string $token The actual token sent with the request that should be validated + * @param string $id The id used when generating the token + * @param string|null $token The actual token sent with the request that should be validated * * @final since version 3.4 */ - protected function isCsrfTokenValid(string $id, string $token): bool + protected function isCsrfTokenValid(string $id, ?string $token): bool { if (!$this->container->has('security.csrf.token_manager')) { throw new \LogicException('CSRF protection is not enabled in your application. Enable it with the "csrf_protection" key in "config/packages/framework.yaml".'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php index e064ce9f17..9fcae720b2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/ProfilerPassTest.php @@ -12,18 +12,11 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; use PHPUnit\Framework\TestCase; -use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass; class ProfilerPassTest extends TestCase { - private $profilerDefinition; - - protected function setUp() - { - $this->profilerDefinition = new Definition('ProfilerClass'); - } - /** * Tests that collectors that specify a template but no "id" will throw * an exception (both are needed if the template is specified). @@ -31,17 +24,15 @@ class ProfilerPassTest extends TestCase * Thus, a fully-valid tag looks something like this: * * + * + * @expectedException \InvalidArgumentException */ public function testTemplateNoIdThrowsException() { - // one service, with a template key, but no id - $services = array( - 'my_collector_service' => array(0 => array('template' => 'foo')), - ); - - $builder = $this->createContainerMock($services); - - $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException'); + $builder = new ContainerBuilder(); + $builder->register('profiler', 'ProfilerClass'); + $builder->register('my_collector_service') + ->addTag('data_collector', array('template' => 'foo')); $profilerPass = new ProfilerPass(); $profilerPass->process($builder); @@ -49,45 +40,19 @@ class ProfilerPassTest extends TestCase public function testValidCollector() { - // one service, with a template key, but no id - $services = array( - 'my_collector_service' => array(0 => array('template' => 'foo', 'id' => 'my_collector')), - ); - - $container = $this->createContainerMock($services); - - // fake the getDefinition() to return a Profiler definition - $container->expects($this->atLeastOnce()) - ->method('getDefinition'); - - // assert that the data_collector.templates parameter should be set - $container->expects($this->once()) - ->method('setParameter') - ->with('data_collector.templates', array('my_collector_service' => array('my_collector', 'foo'))); + $container = new ContainerBuilder(); + $profilerDefinition = $container->register('profiler', 'ProfilerClass'); + $container->register('my_collector_service') + ->addTag('data_collector', array('template' => 'foo', 'id' => 'my_collector')); $profilerPass = new ProfilerPass(); $profilerPass->process($container); + $this->assertSame(array('my_collector_service' => array('my_collector', 'foo')), $container->getParameter('data_collector.templates')); + // grab the method calls off of the "profiler" definition - $methodCalls = $this->profilerDefinition->getMethodCalls(); + $methodCalls = $profilerDefinition->getMethodCalls(); $this->assertCount(1, $methodCalls); $this->assertEquals('add', $methodCalls[0][0]); // grab the method part of the first call } - - private function createContainerMock($services) - { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'getDefinition', 'findTaggedServiceIds', 'setParameter'))->getMock(); - $container->expects($this->any()) - ->method('hasDefinition') - ->with($this->equalTo('profiler')) - ->will($this->returnValue(true)); - $container->expects($this->any()) - ->method('getDefinition') - ->will($this->returnValue($this->profilerDefinition)); - $container->expects($this->atLeastOnce()) - ->method('findTaggedServiceIds') - ->will($this->returnValue($services)); - - return $container; - } } diff --git a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php index 6575f28c97..e142d18a84 100644 --- a/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php +++ b/src/Symfony/Bundle/TwigBundle/Tests/DependencyInjection/Compiler/TwigLoaderPassTest.php @@ -13,13 +13,14 @@ namespace Symfony\Bundle\TwigBundle\Tests\DependencyInjection\Compiler; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Alias; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; use Symfony\Bundle\TwigBundle\DependencyInjection\Compiler\TwigLoaderPass; class TwigLoaderPassTest extends TestCase { /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var ContainerBuilder */ private $builder; /** @@ -33,64 +34,33 @@ class TwigLoaderPassTest extends TestCase protected function setUp() { - $this->builder = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder')->setMethods(array('hasDefinition', 'findTaggedServiceIds', 'setAlias', 'getDefinition'))->getMock(); + $this->builder = new ContainerBuilder(); + $this->builder->register('twig'); $this->chainLoader = new Definition('loader'); $this->pass = new TwigLoaderPass(); } - public function testMapperPassWithOneTaggedLoaders() + public function testMapperPassWithOneTaggedLoader() { - $serviceIds = array( - 'test_loader_1' => array( - array(), - ), - ); - - $this->builder->expects($this->once()) - ->method('hasDefinition') - ->with('twig') - ->will($this->returnValue(true)); - $this->builder->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('twig.loader') - ->will($this->returnValue($serviceIds)); - $this->builder->expects($this->once()) - ->method('setAlias') - ->with('twig.loader', 'test_loader_1') - ->will($this->returnValue(new Alias('test_loader_1'))); + $this->builder->register('test_loader_1') + ->addTag('twig.loader'); $this->pass->process($this->builder); + + $this->assertSame('test_loader_1', (string) $this->builder->getAlias('twig.loader')); } public function testMapperPassWithTwoTaggedLoaders() { - $serviceIds = array( - 'test_loader_1' => array( - array(), - ), - 'test_loader_2' => array( - array(), - ), - ); - - $this->builder->expects($this->once()) - ->method('hasDefinition') - ->with('twig') - ->will($this->returnValue(true)); - $this->builder->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('twig.loader') - ->will($this->returnValue($serviceIds)); - $this->builder->expects($this->once()) - ->method('getDefinition') - ->with('twig.loader.chain') - ->will($this->returnValue($this->chainLoader)); - $this->builder->expects($this->once()) - ->method('setAlias') - ->with('twig.loader', 'twig.loader.chain') - ->will($this->returnValue(new Alias('twig.loader.chain'))); + $this->builder->setDefinition('twig.loader.chain', $this->chainLoader); + $this->builder->register('test_loader_1') + ->addTag('twig.loader'); + $this->builder->register('test_loader_2') + ->addTag('twig.loader'); $this->pass->process($this->builder); + + $this->assertSame('twig.loader.chain', (string) $this->builder->getAlias('twig.loader')); $calls = $this->chainLoader->getMethodCalls(); $this->assertCount(2, $calls); $this->assertEquals('addLoader', $calls[0][0]); @@ -101,33 +71,15 @@ class TwigLoaderPassTest extends TestCase public function testMapperPassWithTwoTaggedLoadersWithPriority() { - $serviceIds = array( - 'test_loader_1' => array( - array('priority' => 100), - ), - 'test_loader_2' => array( - array('priority' => 200), - ), - ); - - $this->builder->expects($this->once()) - ->method('hasDefinition') - ->with('twig') - ->will($this->returnValue(true)); - $this->builder->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('twig.loader') - ->will($this->returnValue($serviceIds)); - $this->builder->expects($this->once()) - ->method('getDefinition') - ->with('twig.loader.chain') - ->will($this->returnValue($this->chainLoader)); - $this->builder->expects($this->once()) - ->method('setAlias') - ->with('twig.loader', 'twig.loader.chain') - ->will($this->returnValue(new Alias('twig.loader.chain'))); + $this->builder->setDefinition('twig.loader.chain', $this->chainLoader); + $this->builder->register('test_loader_1') + ->addTag('twig.loader', array('priority' => 100)); + $this->builder->register('test_loader_2') + ->addTag('twig.loader', array('priority' => 200)); $this->pass->process($this->builder); + + $this->assertSame('twig.loader.chain', (string) $this->builder->getAlias('twig.loader')); $calls = $this->chainLoader->getMethodCalls(); $this->assertCount(2, $calls); $this->assertEquals('addLoader', $calls[0][0]); @@ -141,15 +93,6 @@ class TwigLoaderPassTest extends TestCase */ public function testMapperPassWithZeroTaggedLoaders() { - $this->builder->expects($this->once()) - ->method('hasDefinition') - ->with('twig') - ->will($this->returnValue(true)); - $this->builder->expects($this->once()) - ->method('findTaggedServiceIds') - ->with('twig.loader') - ->will($this->returnValue(array())); - $this->pass->process($this->builder); } } diff --git a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php index e8563521ba..98d0e52693 100644 --- a/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/TraceableAdapter.php @@ -204,11 +204,12 @@ class TraceableAdapter implements AdapterInterface, PruneableInterface, Resettab public function getCalls() { - try { - return $this->calls; - } finally { - $this->calls = array(); - } + return $this->calls; + } + + public function clearCalls() + { + $this->calls = array(); } protected function start($name) diff --git a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php index 33e08f167f..e4b08790ad 100644 --- a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php +++ b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php @@ -57,8 +57,7 @@ class CacheDataCollector extends DataCollector implements LateDataCollectorInter { $this->data = array(); foreach ($this->instances as $instance) { - // Calling getCalls() will clear the calls. - $instance->getCalls(); + $instance->clearCalls(); } } diff --git a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php index bcb940cb0f..23974b3bc5 100644 --- a/src/Symfony/Component/Cache/Traits/FilesystemTrait.php +++ b/src/Symfony/Component/Cache/Traits/FilesystemTrait.php @@ -36,9 +36,9 @@ trait FilesystemTrait continue; } - if ($time >= (int) $expiresAt = fgets($h)) { + if (($expiresAt = (int) fgets($h)) && $time >= $expiresAt) { fclose($h); - $pruned = isset($expiresAt[0]) && @unlink($file) && !file_exists($file) && $pruned; + $pruned = @unlink($file) && !file_exists($file) && $pruned; } else { fclose($h); } @@ -60,11 +60,9 @@ trait FilesystemTrait if (!file_exists($file) || !$h = @fopen($file, 'rb')) { continue; } - if ($now >= (int) $expiresAt = fgets($h)) { + if (($expiresAt = (int) fgets($h)) && $now >= $expiresAt) { fclose($h); - if (isset($expiresAt[0])) { - @unlink($file); - } + @unlink($file); } else { $i = rawurldecode(rtrim(fgets($h))); $value = stream_get_contents($h); @@ -94,7 +92,7 @@ trait FilesystemTrait protected function doSave(array $values, $lifetime) { $ok = true; - $expiresAt = time() + ($lifetime ?: 31557600); // 31557600s = 1 year + $expiresAt = $lifetime ? (time() + $lifetime) : 0; foreach ($values as $id => $value) { $ok = $this->write($this->getFile($id, true), $expiresAt."\n".rawurlencode($id)."\n".serialize($value), $expiresAt) && $ok; diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index b3f0a4b51c..d34ee2fb77 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -120,6 +120,7 @@ class PhpDumper extends Dumper 'debug' => true, 'hot_path_tag' => 'container.hot_path', 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', + 'build_time' => time(), ), $options); $this->namespace = $options['namespace']; @@ -223,7 +224,7 @@ EOF; array_pop($code); $code["Container{$hash}/{$options['class']}.php"] = substr_replace($files[$options['class'].'.php'], "namespace ? "\nnamespace {$this->namespace};\n" : ''; - $time = time(); + $time = $options['build_time']; $id = hash('crc32', $hash.$time); $code[$options['class'].'.php'] = <<getMockBuilder('Symfony\Component\DependencyInjection\ParameterBag\ParameterBag') - ->setMethods(array('resolveValue')) - ->getMock() - ; - - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder') - ->setMethods(array('getParameterBag')) - ->getMock() - ; - - $pb->expects($this->once()) - ->method('resolveValue') - ->with($this->equalTo($enabled)) - ->will($this->returnValue($enabled)) - ; - - $container->expects($this->once()) - ->method('getParameterBag') - ->will($this->returnValue($pb)) - ; - - $extension = $this->getMockBuilder('Symfony\Component\DependencyInjection\Extension\Extension') - ->setMethods(array()) - ->getMockForAbstractClass() - ; - - $r = new \ReflectionMethod('Symfony\Component\DependencyInjection\Extension\Extension', 'isConfigEnabled'); - $r->setAccessible(true); - - $r->invoke($extension, $container, array('enabled' => $enabled)); + $extension = new EnableableExtension(); + $this->assertSame($enabled, $extension->isConfigEnabled(new ContainerBuilder(), array('enabled' => $enabled))); } public function getResolvedEnabledFixtures() @@ -66,18 +40,20 @@ class ExtensionTest extends TestCase */ public function testIsConfigEnabledOnNonEnableableConfig() { - $container = $this->getMockBuilder('Symfony\Component\DependencyInjection\ContainerBuilder') - ->getMock() - ; + $extension = new EnableableExtension(); - $extension = $this->getMockBuilder('Symfony\Component\DependencyInjection\Extension\Extension') - ->setMethods(array()) - ->getMockForAbstractClass() - ; - - $r = new \ReflectionMethod('Symfony\Component\DependencyInjection\Extension\Extension', 'isConfigEnabled'); - $r->setAccessible(true); - - $r->invoke($extension, $container, array()); + $extension->isConfigEnabled(new ContainerBuilder(), array()); + } +} + +class EnableableExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + } + + public function isConfigEnabled(ContainerBuilder $container, array $config) + { + return parent::isConfigEnabled($container, $config); } } diff --git a/src/Symfony/Component/Finder/Finder.php b/src/Symfony/Component/Finder/Finder.php index 1dab061f87..e7157b1b0a 100644 --- a/src/Symfony/Component/Finder/Finder.php +++ b/src/Symfony/Component/Finder/Finder.php @@ -313,6 +313,8 @@ class Finder implements \IteratorAggregate, \Countable /** * Excludes "hidden" directories and files (starting with a dot). * + * This option is enabled by default. + * * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not * * @return $this @@ -333,6 +335,8 @@ class Finder implements \IteratorAggregate, \Countable /** * Forces the finder to ignore version control directories. * + * This option is enabled by default. + * * @param bool $ignoreVCS Whether to exclude VCS files or not * * @return $this diff --git a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php index 9cd910ad18..8cb19c4790 100644 --- a/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php +++ b/src/Symfony/Component/HttpKernel/DependencyInjection/RegisterControllerArgumentLocatorsPass.php @@ -22,6 +22,7 @@ use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\LazyProxy\ProxyHelper; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\HttpFoundation\Request; /** * Creates the service-locators required by ServiceValueResolver. @@ -148,6 +149,10 @@ class RegisterControllerArgumentLocatorsPass implements CompilerPassInterface continue; } + if (Request::class === $type) { + continue; + } + if ($type && !$p->isOptional() && !$p->allowsNull() && !class_exists($type) && !interface_exists($type, false)) { $message = sprintf('Cannot determine controller argument for "%s::%s()": the $%s argument is type-hinted with the non-existent class or interface: "%s".', $class, $r->name, $p->name, $type); diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php index 9632ca7e4a..61c35201a8 100644 --- a/src/Symfony/Component/HttpKernel/Kernel.php +++ b/src/Symfony/Component/HttpKernel/Kernel.php @@ -693,6 +693,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl 'file' => $cache->getPath(), 'as_files' => true, 'debug' => $this->debug, + 'build_time' => $container->hasParameter('kernel.container_build_time') ? $container->getParameter('kernel.container_build_time') : time(), )); $rootCode = array_pop($content); diff --git a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php index 81fc8b455d..ae421d919b 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DependencyInjection/MergeExtensionConfigurationPassTest.php @@ -12,44 +12,39 @@ namespace Symfony\Component\HttpKernel\Tests\DependencyInjection; use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; use Symfony\Component\HttpKernel\DependencyInjection\MergeExtensionConfigurationPass; class MergeExtensionConfigurationPassTest extends TestCase { public function testAutoloadMainExtension() { - $container = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ContainerBuilder')->setMethods(array('getExtensionConfig', 'loadFromExtension', 'getParameterBag', 'getDefinitions', 'getAliases', 'getExtensions'))->getMock(); - $params = $this->getMockBuilder('Symfony\\Component\\DependencyInjection\\ParameterBag\\ParameterBag')->getMock(); + $container = new ContainerBuilder(); + $container->registerExtension(new LoadedExtension()); + $container->registerExtension(new NotLoadedExtension()); + $container->loadFromExtension('loaded', array()); - $container->expects($this->at(0)) - ->method('getExtensionConfig') - ->with('loaded') - ->will($this->returnValue(array(array()))); - $container->expects($this->at(1)) - ->method('getExtensionConfig') - ->with('notloaded') - ->will($this->returnValue(array())); - $container->expects($this->once()) - ->method('loadFromExtension') - ->with('notloaded', array()); - - $container->expects($this->any()) - ->method('getParameterBag') - ->will($this->returnValue($params)); - $params->expects($this->any()) - ->method('all') - ->will($this->returnValue(array())); - $container->expects($this->any()) - ->method('getDefinitions') - ->will($this->returnValue(array())); - $container->expects($this->any()) - ->method('getAliases') - ->will($this->returnValue(array())); - $container->expects($this->any()) - ->method('getExtensions') - ->will($this->returnValue(array())); - - $configPass = new MergeExtensionConfigurationPass(array('loaded', 'notloaded')); + $configPass = new MergeExtensionConfigurationPass(array('loaded', 'not_loaded')); $configPass->process($container); + + $this->assertTrue($container->hasDefinition('loaded.foo')); + $this->assertTrue($container->hasDefinition('not_loaded.bar')); + } +} + +class LoadedExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + $container->register('loaded.foo'); + } +} + +class NotLoadedExtension extends Extension +{ + public function load(array $configs, ContainerBuilder $container) + { + $container->register('not_loaded.bar'); } } diff --git a/src/Symfony/Component/Lock/Lock.php b/src/Symfony/Component/Lock/Lock.php index e906554f31..7936a5f7b6 100644 --- a/src/Symfony/Component/Lock/Lock.php +++ b/src/Symfony/Component/Lock/Lock.php @@ -89,7 +89,7 @@ final class Lock implements LockInterface, LoggerAwareInterface return true; } catch (LockConflictedException $e) { $this->dirty = false; - $this->logger->warning('Failed to acquire the "{resource}" lock. Someone else already acquired the lock.', array('resource' => $this->key)); + $this->logger->notice('Failed to acquire the "{resource}" lock. Someone else already acquired the lock.', array('resource' => $this->key)); if ($blocking) { throw $e; @@ -97,7 +97,7 @@ final class Lock implements LockInterface, LoggerAwareInterface return false; } catch (\Exception $e) { - $this->logger->warning('Failed to acquire the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e)); + $this->logger->notice('Failed to acquire the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e)); throw new LockAcquiringException(sprintf('Failed to acquire the "%s" lock.', $this->key), 0, $e); } } @@ -123,10 +123,10 @@ final class Lock implements LockInterface, LoggerAwareInterface $this->logger->info('Expiration defined for "{resource}" lock for "{ttl}" seconds.', array('resource' => $this->key, 'ttl' => $this->ttl)); } catch (LockConflictedException $e) { $this->dirty = false; - $this->logger->warning('Failed to define an expiration for the "{resource}" lock, someone else acquired the lock.', array('resource' => $this->key)); + $this->logger->notice('Failed to define an expiration for the "{resource}" lock, someone else acquired the lock.', array('resource' => $this->key)); throw $e; } catch (\Exception $e) { - $this->logger->warning('Failed to define an expiration for the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e)); + $this->logger->notice('Failed to define an expiration for the "{resource}" lock.', array('resource' => $this->key, 'exception' => $e)); throw new LockAcquiringException(sprintf('Failed to define an expiration for the "%s" lock.', $this->key), 0, $e); } } @@ -148,7 +148,7 @@ final class Lock implements LockInterface, LoggerAwareInterface $this->dirty = false; if ($this->store->exists($this->key)) { - $this->logger->warning('Failed to release the "{resource}" lock.', array('resource' => $this->key)); + $this->logger->notice('Failed to release the "{resource}" lock.', array('resource' => $this->key)); throw new LockReleasingException(sprintf('Failed to release the "%s" lock.', $this->key)); } } diff --git a/src/Symfony/Component/Lock/Tests/LockTest.php b/src/Symfony/Component/Lock/Tests/LockTest.php index ece5cf6679..7913644b9a 100644 --- a/src/Symfony/Component/Lock/Tests/LockTest.php +++ b/src/Symfony/Component/Lock/Tests/LockTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Lock\Tests; use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; use Symfony\Component\Lock\Exception\LockConflictedException; use Symfony\Component\Lock\Key; use Symfony\Component\Lock\Lock; @@ -192,6 +193,35 @@ class LockTest extends TestCase $lock->release(); } + /** + * @expectedException \Symfony\Component\Lock\Exception\LockReleasingException + */ + public function testReleaseThrowsAndLog() + { + $key = new Key(uniqid(__METHOD__, true)); + $store = $this->getMockBuilder(StoreInterface::class)->getMock(); + $logger = $this->getMockBuilder(LoggerInterface::class)->getMock(); + $lock = new Lock($key, $store, 10, true); + $lock->setLogger($logger); + + $logger->expects($this->atLeastOnce()) + ->method('notice') + ->with('Failed to release the "{resource}" lock.', array('resource' => $key)); + + $store + ->expects($this->once()) + ->method('delete') + ->with($key); + + $store + ->expects($this->once()) + ->method('exists') + ->with($key) + ->willReturn(true); + + $lock->release(); + } + /** * @dataProvider provideExpiredDates */ diff --git a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php index 89f2b10e22..182fcd8f23 100644 --- a/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php +++ b/src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher4.php @@ -62,7 +62,7 @@ class ProjectUrlMatcher extends Symfony\Component\Routing\Matcher\UrlMatcher not_get_and_head: // post_and_head - if ('/post_and_get' === $pathinfo) { + if ('/post_and_head' === $pathinfo) { if (!in_array($requestMethod, array('POST', 'HEAD'))) { $allow = array_merge($allow, array('POST', 'HEAD')); goto not_post_and_head; diff --git a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php index e4c18c47b1..f29a6d6a30 100644 --- a/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php +++ b/src/Symfony/Component/Routing/Tests/Matcher/Dumper/PhpMatcherDumperTest.php @@ -354,7 +354,7 @@ class PhpMatcherDumperTest extends TestCase array('GET', 'HEAD') )); $headMatchCasesCollection->add('post_and_head', new Route( - '/post_and_get', + '/post_and_head', array(), array(), array(), diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 8253276c99..c680a19b16 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -30,6 +30,7 @@ CHANGELOG * added getter for extra attributes in `ExtraAttributesException` * improved `CsvEncoder` to handle variable nested structures * CSV headers can be passed to the `CsvEncoder` via the `csv_headers` serialization context variable + * added `$context` when checking for encoding, decoding and normalizing in `Serializer` 3.3.0 ----- diff --git a/src/Symfony/Component/Serializer/Serializer.php b/src/Symfony/Component/Serializer/Serializer.php index 3c73ec3b20..92975a35bb 100644 --- a/src/Symfony/Component/Serializer/Serializer.php +++ b/src/Symfony/Component/Serializer/Serializer.php @@ -98,11 +98,11 @@ class Serializer implements SerializerInterface, ContextAwareNormalizerInterface */ final public function serialize($data, $format, array $context = array()) { - if (!$this->supportsEncoding($format)) { + if (!$this->supportsEncoding($format, $context)) { throw new NotEncodableValueException(sprintf('Serialization for the format %s is not supported', $format)); } - if ($this->encoder->needsNormalization($format)) { + if ($this->encoder->needsNormalization($format, $context)) { $data = $this->normalize($data, $format, $context); } @@ -114,7 +114,7 @@ class Serializer implements SerializerInterface, ContextAwareNormalizerInterface */ final public function deserialize($data, $type, $format, array $context = array()) { - if (!$this->supportsDecoding($format)) { + if (!$this->supportsDecoding($format, $context)) { throw new NotEncodableValueException(sprintf('Deserialization for the format %s is not supported', $format)); } diff --git a/src/Symfony/Component/Yaml/Dumper.php b/src/Symfony/Component/Yaml/Dumper.php index 254009733e..6681379816 100644 --- a/src/Symfony/Component/Yaml/Dumper.php +++ b/src/Symfony/Component/Yaml/Dumper.php @@ -63,7 +63,10 @@ class Dumper foreach ($input as $key => $value) { if ($inline >= 1 && Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r\n")) { - $output .= sprintf("%s%s%s |\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', ''); + // If the first line starts with a space character, the spec requires a blockIndicationIndicator + // http://www.yaml.org/spec/1.2/spec.html#id2793979 + $blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : ''; + $output .= sprintf("%s%s%s |%s\n", $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator); foreach (preg_split('/\n|\r\n/', $value) as $row) { $output .= sprintf("%s%s%s\n", $prefix, str_repeat(' ', $this->indentation), $row); diff --git a/src/Symfony/Component/Yaml/Tests/DumperTest.php b/src/Symfony/Component/Yaml/Tests/DumperTest.php index edcb734440..0a8c023d01 100644 --- a/src/Symfony/Component/Yaml/Tests/DumperTest.php +++ b/src/Symfony/Component/Yaml/Tests/DumperTest.php @@ -388,6 +388,17 @@ YAML; $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); } + public function testDumpMultiLineStringAsScalarBlockWhenFirstLineHasLeadingSpace() + { + $data = array( + 'data' => array( + 'multi_line' => " the first line has leading spaces\nThe second line does not.", + ), + ); + + $this->assertSame(file_get_contents(__DIR__.'/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml'), $this->dumper->dump($data, 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); + } + public function testCarriageReturnIsMaintainedWhenDumpingAsMultiLineLiteralBlock() { $this->assertSame("- \"a\\r\\nb\\nc\"\n", $this->dumper->dump(array("a\r\nb\nc"), 2, 0, Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK)); diff --git a/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml new file mode 100644 index 0000000000..3f2dedd10e --- /dev/null +++ b/src/Symfony/Component/Yaml/Tests/Fixtures/multiple_lines_as_literal_block_leading_space_in_first_line.yml @@ -0,0 +1,4 @@ +data: + multi_line: |4 + the first line has leading spaces + The second line does not.