From f7dd26aa69017add3bc67a36603c9b40a41f6369 Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Mon, 8 Feb 2021 22:42:48 +0000 Subject: [PATCH] Issue #2795567 by joachim, jungle, daffie, Sophie.SK, mondrake, ravi.shankar, jonathanshaw, dawehner, AaronBauman, alexpott: Use Symfony's VarDumper for easier test debugging with dump() --- .../src/Controller/TestPageTestController.php | 14 ++++++ .../test_page_test/test_page_test.routing.yml | 8 ++++ .../FunctionalTests/BrowserTestBaseTest.php | 37 +++++++++++++++ .../Drupal/KernelTests/KernelTestBase.php | 6 +++ .../Drupal/KernelTests/KernelTestBaseTest.php | 22 +++++++++ core/tests/Drupal/TestTools/TestVarDumper.php | 46 +++++++++++++++++++ core/tests/Drupal/Tests/BrowserTestBase.php | 15 ++++++ core/tests/Drupal/Tests/StreamCapturer.php | 23 ++++++++++ core/tests/Drupal/Tests/UnitTestCase.php | 13 ++++++ core/tests/Drupal/Tests/UnitTestCaseTest.php | 44 ++++++++++++++++++ 10 files changed, 228 insertions(+) create mode 100644 core/tests/Drupal/TestTools/TestVarDumper.php create mode 100644 core/tests/Drupal/Tests/StreamCapturer.php diff --git a/core/modules/system/tests/modules/test_page_test/src/Controller/TestPageTestController.php b/core/modules/system/tests/modules/test_page_test/src/Controller/TestPageTestController.php index 02ea524b09ee..f23fc92cf59b 100644 --- a/core/modules/system/tests/modules/test_page_test/src/Controller/TestPageTestController.php +++ b/core/modules/system/tests/modules/test_page_test/src/Controller/TestPageTestController.php @@ -2,6 +2,8 @@ namespace Drupal\test_page_test\Controller; +use Drupal\user\Entity\Role; + /** * Controller routines for test_page_test routes. */ @@ -23,4 +25,16 @@ public function testPage() { ]; } + /** + * Returns a test page and with the call to the dump() function. + */ + public function testPageVarDump() { + $role = Role::create(['id' => 'test_role']); + dump($role); + return [ + '#title' => t('Test page with var dump'), + '#markup' => t('Test page text.'), + ]; + } + } diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml index 43b6cf54c187..f3744df5ee61 100644 --- a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml +++ b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml @@ -144,3 +144,11 @@ test_page_test.deprecations: _controller: '\Drupal\test_page_test\Controller\Test::deprecations' requirements: _access: 'TRUE' + +test_page_test.test_page_var_dump: + path: '/test-page-var-dump' + defaults: + _title: 'Test front page with var dump' + _controller: '\Drupal\test_page_test\Controller\TestPageTestController::testPageVarDump' + requirements: + _access: 'TRUE' diff --git a/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php index affb55bfc787..10489ebb5f88 100644 --- a/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php +++ b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php @@ -8,7 +8,9 @@ use Drupal\Component\Utility\Html; use Drupal\Core\Url; use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\StreamCapturer; use Drupal\Tests\Traits\Core\CronRunTrait; +use Drupal\user\Entity\Role; use PHPUnit\Framework\ExpectationFailedException; /** @@ -949,4 +951,39 @@ public function testDrupalGetHeader() { $this->drupalGetHeader('Content-Type'); } + /** + * Tests the dump() function provided by the var-dumper Symfony component. + */ + public function testVarDump() { + // Append the stream capturer to the STDOUT stream, so that we can test the + // dump() output and also prevent it from actually outputting in this + // particular test. + stream_filter_register("capture", StreamCapturer::class); + stream_filter_append(STDOUT, "capture"); + + // Dump some variables to check that dump() in test code produces output + // on the command line that is running the test. + $role = Role::load('authenticated'); + dump($role); + dump($role->id()); + + $this->assertStringContainsString('Drupal\user\Entity\Role', StreamCapturer::$cache); + $this->assertStringContainsString('authenticated', StreamCapturer::$cache); + + // Visit a Drupal page with call to the dump() function to check that dump() + // in site code produces output in the requested web page's HTML. + $body = $this->drupalGet('test-page-var-dump'); + $this->assertSession()->statusCodeEquals(200); + + // It is too strict to assert all properties of the Role and it is easy to + // break if one of these properties gets removed or gets a new default + // value. It should be sufficient to test just a couple of properties. + $this->assertStringContainsString('<span class=sf-dump-note>', $body); + $this->assertStringContainsString(' #<span class=sf-dump-protected title="Protected property">id</span>: "<span class=sf-dump-str title="9 characters">test_role</span>"', $body); + $this->assertStringContainsString(' #<span class=sf-dump-protected title="Protected property">label</span>: <span class=sf-dump-const>null</span>', $body); + $this->assertStringContainsString(' #<span class=sf-dump-protected title="Protected property">permissions</span>: []', $body); + $this->assertStringContainsString(' #<span class=sf-dump-protected title="Protected property">uuid</span>: "', $body); + $this->assertStringContainsString('</samp>}', $body); + } + } diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php index e5afa6bba918..1c520289dee2 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBase.php +++ b/core/tests/Drupal/KernelTests/KernelTestBase.php @@ -22,6 +22,7 @@ use Drupal\Tests\TestRequirementsTrait; use Drupal\Tests\Traits\PhpUnitWarnings; use Drupal\TestTools\Comparator\MarkupInterfaceComparator; +use Drupal\TestTools\TestVarDumper; use PHPUnit\Framework\Exception; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Reference; @@ -30,6 +31,7 @@ use org\bovigo\vfs\visitor\vfsStreamPrintVisitor; use Drupal\Core\Routing\RouteObjectInterface; use Symfony\Component\Routing\Route; +use Symfony\Component\VarDumper\VarDumper; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; /** @@ -64,6 +66,9 @@ * KernelTestBase::installEntitySchema(). Alternately, tests which need modules * to be fully installed could inherit from \Drupal\Tests\BrowserTestBase. * + * Using Symfony's dump() function in Kernel tests will produce output on the + * command line, whether the call to dump() is in test code or site code. + * * @see \Drupal\Tests\KernelTestBase::$modules * @see \Drupal\Tests\KernelTestBase::enableModules() * @see \Drupal\Tests\KernelTestBase::installConfig() @@ -223,6 +228,7 @@ abstract class KernelTestBase extends TestCase implements ServiceProviderInterfa */ public static function setUpBeforeClass() { parent::setUpBeforeClass(); + VarDumper::setHandler(TestVarDumper::class . '::cliHandler'); // Change the current dir to DRUPAL_ROOT. chdir(static::getDrupalRoot()); diff --git a/core/tests/Drupal/KernelTests/KernelTestBaseTest.php b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php index 4775bf53fa8e..025c5a490b54 100644 --- a/core/tests/Drupal/KernelTests/KernelTestBaseTest.php +++ b/core/tests/Drupal/KernelTests/KernelTestBaseTest.php @@ -5,6 +5,8 @@ use Drupal\Component\FileCache\FileCacheFactory; use Drupal\Core\Database\Database; use GuzzleHttp\Exception\GuzzleException; +use Drupal\Tests\StreamCapturer; +use Drupal\user\Entity\Role; use org\bovigo\vfs\vfsStream; use org\bovigo\vfs\visitor\vfsStreamStructureVisitor; use PHPUnit\Framework\SkippedTestError; @@ -396,4 +398,24 @@ public function testKernelTestBaseInstallSchema() { $this->assertFalse(Database::getConnection()->schema()->tableExists('key_value')); } + /** + * Tests the dump() function provided by the var-dumper Symfony component. + */ + public function testVarDump() { + // Append the stream capturer to the STDOUT stream, so that we can test the + // dump() output and also prevent it from actually outputting in this + // particular test. + stream_filter_register("capture", StreamCapturer::class); + stream_filter_append(STDOUT, "capture"); + + // Dump some variables. + $this->enableModules(['system', 'user']); + $role = Role::create(['id' => 'test_role']); + dump($role); + dump($role->id()); + + $this->assertStringContainsString('Drupal\user\Entity\Role', StreamCapturer::$cache); + $this->assertStringContainsString('test_role', StreamCapturer::$cache); + } + } diff --git a/core/tests/Drupal/TestTools/TestVarDumper.php b/core/tests/Drupal/TestTools/TestVarDumper.php new file mode 100644 index 000000000000..b47ffd704f8b --- /dev/null +++ b/core/tests/Drupal/TestTools/TestVarDumper.php @@ -0,0 +1,46 @@ +<?php + +namespace Drupal\TestTools; + +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * Provides handlers for the Symfony VarDumper to work within tests. + * + * This allows the dump() function to produce output on the terminal without + * causing PHPUnit to complain. + */ +class TestVarDumper { + + /** + * A CLI handler for \Symfony\Component\VarDumper\VarDumper. + */ + public static function cliHandler($var) { + $cloner = new VarCloner(); + $dumper = new CliDumper(); + fwrite(STDOUT, "\n"); + $dumper->setColors(TRUE); + $dumper->dump( + $cloner->cloneVar($var), + function ($line, $depth, $indent_pad) { + // A negative depth means "end of dump". + if ($depth >= 0) { + // Adds a two spaces indentation to the line. + fwrite(STDOUT, str_repeat($indent_pad, $depth) . $line . "\n"); + } + } + ); + } + + /** + * A HTML handler for \Symfony\Component\VarDumper\VarDumper. + */ + public static function htmlHandler($var) { + $cloner = new VarCloner(); + $dumper = new HtmlDumper(); + $dumper->dump($cloner->cloneVar($var)); + } + +} diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index 6dae38389f53..8da29400b4e4 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -20,10 +20,12 @@ use Drupal\Tests\Traits\PhpUnitWarnings; use Drupal\Tests\user\Traits\UserCreationTrait; use Drupal\TestTools\Comparator\MarkupInterfaceComparator; +use Drupal\TestTools\TestVarDumper; use GuzzleHttp\Cookie\CookieJar; use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; use Symfony\Component\CssSelector\CssSelectorConverter; +use Symfony\Component\VarDumper\VarDumper; /** * Provides a test case for functional Drupal tests. @@ -36,6 +38,11 @@ * translation functionality. For example, avoid wrapping test text with t() * or TranslatableMarkup(). * + * Using Symfony's dump() function in functional test test code will produce + * output on the command line; using dump() in site code will produce output in + * the requested web page, which can then be inspected in the HTML output from + * the test. + * * @ingroup testing */ abstract class BrowserTestBase extends TestCase { @@ -214,6 +221,14 @@ abstract class BrowserTestBase extends TestCase { */ protected $originalContainer; + /** + * {@inheritdoc} + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + VarDumper::setHandler(TestVarDumper::class . '::cliHandler'); + } + /** * Initializes Mink sessions. */ diff --git a/core/tests/Drupal/Tests/StreamCapturer.php b/core/tests/Drupal/Tests/StreamCapturer.php new file mode 100644 index 000000000000..9b8925723a41 --- /dev/null +++ b/core/tests/Drupal/Tests/StreamCapturer.php @@ -0,0 +1,23 @@ +<?php + +namespace Drupal\Tests; + +/** + * Captures output to a stream and stores it for retrieval. + */ +class StreamCapturer extends \php_user_filter { + + public static $cache = ''; + + public function filter($in, $out, &$consumed, $closing) { + while ($bucket = stream_bucket_make_writeable($in)) { + self::$cache .= $bucket->data; + // cSpell:disable-next-line + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + } + // cSpell:disable-next-line + return PSFS_FEED_ME; + } + +} diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php index c365afaf9ce0..7d81c84cd229 100644 --- a/core/tests/Drupal/Tests/UnitTestCase.php +++ b/core/tests/Drupal/Tests/UnitTestCase.php @@ -10,12 +10,17 @@ use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\StringTranslation\PluralTranslatableMarkup; use Drupal\Tests\Traits\PhpUnitWarnings; +use Drupal\TestTools\TestVarDumper; use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\VarDumper; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; /** * Provides a base class and helpers for Drupal unit tests. * + * Using Symfony's dump() function() in Unit tests will produce output on the + * command line. + * * @ingroup testing */ abstract class UnitTestCase extends TestCase { @@ -38,6 +43,14 @@ abstract class UnitTestCase extends TestCase { */ protected $root; + /** + * {@inheritdoc} + */ + public static function setUpBeforeClass() { + parent::setUpBeforeClass(); + VarDumper::setHandler(TestVarDumper::class . '::cliHandler'); + } + /** * {@inheritdoc} */ diff --git a/core/tests/Drupal/Tests/UnitTestCaseTest.php b/core/tests/Drupal/Tests/UnitTestCaseTest.php index bff0f13b6cc9..aac3b709c2f7 100644 --- a/core/tests/Drupal/Tests/UnitTestCaseTest.php +++ b/core/tests/Drupal/Tests/UnitTestCaseTest.php @@ -19,4 +19,48 @@ public function testAssertArrayEquals() { $this->assertArrayEquals([], []); } + /** + * Tests the dump() function in a test run in the same process. + */ + public function testVarDumpSameProcess() { + // Append the stream capturer to the STDOUT stream, so that we can test the + // dump() output and also prevent it from actually outputting in this + // particular test. + stream_filter_register("capture", StreamCapturer::class); + stream_filter_append(STDOUT, "capture"); + + // Dump some variables. + $object = (object) [ + 'foo' => 'bar', + ]; + dump($object); + dump('banana'); + + $this->assertStringContainsString('bar', StreamCapturer::$cache); + $this->assertStringContainsString('banana', StreamCapturer::$cache); + } + + /** + * Tests the dump() function in a test run in a separate process. + * + * @runInSeparateProcess + */ + public function testVarDumpSeparateProcess() { + // Append the stream capturer to the STDOUT stream, so that we can test the + // dump() output and also prevent it from actually outputting in this + // particular test. + stream_filter_register("capture", StreamCapturer::class); + stream_filter_append(STDOUT, "capture"); + + // Dump some variables. + $object = (object) [ + 'foo' => 'bar', + ]; + dump($object); + dump('banana'); + + $this->assertStringContainsString('bar', StreamCapturer::$cache); + $this->assertStringContainsString('banana', StreamCapturer::$cache); + } + } -- GitLab