diff --git a/package_manager/src/ComposerInspector.php b/package_manager/src/ComposerInspector.php index 1270c7ce59e60ff285564b36996f8ccba3c4f78b..b8e165321f9adeefc785fff907345059521363a1 100644 --- a/package_manager/src/ComposerInspector.php +++ b/package_manager/src/ComposerInspector.php @@ -263,7 +263,7 @@ class ComposerInspector implements LoggerAwareInterface { } } $output = $this->processCallback->getOutput(); - return isset($output) ? trim($output) : $output; + return $output ? trim(implode('', $output)) : NULL; } /** diff --git a/package_manager/src/ProcessOutputCallback.php b/package_manager/src/ProcessOutputCallback.php index 0f4096ebdfa0b9494d04a27efa8d6f6835f23da7..86eaabde2f98e731f27d869cc35e98ccd710845d 100644 --- a/package_manager/src/ProcessOutputCallback.php +++ b/package_manager/src/ProcessOutputCallback.php @@ -29,14 +29,14 @@ final class ProcessOutputCallback implements OutputCallbackInterface, LoggerAwar * * @var string */ - private string $outBuffer = ''; + private array $outBuffer = []; /** * The error buffer. * * @var string */ - private string $errorBuffer = ''; + private array $errorBuffer = []; /** * Constructs a ProcessOutputCallback object. @@ -51,10 +51,10 @@ final class ProcessOutputCallback implements OutputCallbackInterface, LoggerAwar public function __invoke(OutputTypeEnum $type, string $buffer): void { if ($type === OutputTypeEnum::OUT) { - $this->outBuffer .= $buffer; + $this->outBuffer[] = $buffer; } elseif ($type === OutputTypeEnum::ERR) { - $this->errorBuffer .= $buffer; + $this->errorBuffer[] = $buffer; } } @@ -66,12 +66,12 @@ final class ProcessOutputCallback implements OutputCallbackInterface, LoggerAwar * @return string|null * The output or NULL if there is none. */ - public function getOutput(): ?string { + public function getOutput(): array { $error_output = $this->getErrorOutput(); if ($error_output) { - $this->logger->warning($error_output); + $this->logger->warning(implode('', $error_output)); } - return trim($this->outBuffer) !== '' ? $this->outBuffer : NULL; + return $this->outBuffer; } /** @@ -82,8 +82,8 @@ final class ProcessOutputCallback implements OutputCallbackInterface, LoggerAwar */ public function parseJsonOutput(): mixed { $output = $this->getOutput(); - if ($output !== NULL) { - return json_decode($output, TRUE, flags: JSON_THROW_ON_ERROR); + if ($output) { + return json_decode(trim(implode('', $output)), TRUE, flags: JSON_THROW_ON_ERROR); } return NULL; } @@ -94,8 +94,22 @@ final class ProcessOutputCallback implements OutputCallbackInterface, LoggerAwar * @return string|null * The error output or NULL if there isn't any. */ - public function getErrorOutput(): ?string { - return trim($this->errorBuffer) !== '' ? $this->errorBuffer : NULL; + public function getErrorOutput(): array { + return $this->errorBuffer; + } + + /** + * {@inheritdoc} + */ + public function clearErrorOutput(): void { + $this->errorBuffer = []; + } + + /** + * {@inheritdoc} + */ + public function clearOutput(): void { + $this->outBuffer = []; } /** @@ -104,8 +118,8 @@ final class ProcessOutputCallback implements OutputCallbackInterface, LoggerAwar * @return self */ public function reset(): self { - $this->errorBuffer = ''; - $this->outBuffer = ''; + $this->clearErrorOutput(); + $this->clearOutput(); return $this; } diff --git a/package_manager/tests/modules/fixture_manipulator/src/FixtureManipulator.php b/package_manager/tests/modules/fixture_manipulator/src/FixtureManipulator.php index 6de6b6d443baf48ae83064c09184792f3de4396d..43b75169dcb3bf94789e0313858170246a1139ed 100644 --- a/package_manager/tests/modules/fixture_manipulator/src/FixtureManipulator.php +++ b/package_manager/tests/modules/fixture_manipulator/src/FixtureManipulator.php @@ -445,6 +445,34 @@ class FixtureManipulator { } } + /** + * {@inheritdoc} + */ + public function clearErrorOutput(): void { + throw new \LogicException("Unexpected call to clearErrorOutput()."); + } + + /** + * {@inheritdoc} + */ + public function clearOutput(): void { + throw new \LogicException("Unexpected call to clearOutput()."); + } + + /** + * {@inheritdoc} + */ + public function getErrorOutput(): array { + throw new \LogicException("Unexpected call to getErrorOutput()."); + } + + /** + * {@inheritdoc} + */ + public function getOutput(): array { + throw new \LogicException("Unexpected call to getOutput()."); + } + }; /** @var \PhpTuf\ComposerStager\API\Process\Service\ComposerProcessRunnerInterface $runner */ $runner = \Drupal::service(ComposerProcessRunnerInterface::class); diff --git a/package_manager/tests/src/Build/TemplateProjectTestBase.php b/package_manager/tests/src/Build/TemplateProjectTestBase.php index 8fbca7bc3c1606d4a7063dc2e6dc1db9aaba5fa2..d815f7ca70b49345ee94f44528bfb2e6becc12cf 100644 --- a/package_manager/tests/src/Build/TemplateProjectTestBase.php +++ b/package_manager/tests/src/Build/TemplateProjectTestBase.php @@ -63,6 +63,18 @@ abstract class TemplateProjectTestBase extends QuickStartTestBase { */ protected const MAX_EXECUTION_TIME = 20; + /** + * {@inheritdoc} + */ + protected function setUp(): void { + // Build tests cannot be run if Sqlite minimum version is not met. + $sqlite = (new \PDO('sqlite::memory:'))->query('select sqlite_version()')->fetch()[0]; + if (version_compare($sqlite, Tasks::SQLITE_MINIMUM_VERSION) < 0) { + $this->markTestSkipped(); + } + parent::setUp(); + } + /** * {@inheritdoc} */ diff --git a/package_manager/tests/src/Kernel/ComposerInspectorTest.php b/package_manager/tests/src/Kernel/ComposerInspectorTest.php index 297498dd48820ac6801b539a97c3c74e1d2594dc..466112f4a99e41e4d4e70cf6ae57ba888b22fdea 100644 --- a/package_manager/tests/src/Kernel/ComposerInspectorTest.php +++ b/package_manager/tests/src/Kernel/ComposerInspectorTest.php @@ -10,7 +10,6 @@ use Drupal\fixture_manipulator\ActiveFixtureManipulator; use Drupal\package_manager\ComposerInspector; use Drupal\package_manager\Exception\ComposerNotReadyException; use Drupal\package_manager\InstalledPackage; -use Drupal\package_manager\ProcessOutputCallback; use Drupal\package_manager\InstalledPackagesList; use Drupal\Tests\package_manager\Traits\InstalledPackagesListTrait; use Drupal\package_manager\PathLocator; @@ -19,6 +18,7 @@ use PhpTuf\ComposerStager\API\Exception\RuntimeException; use PhpTuf\ComposerStager\API\Path\Factory\PathFactoryInterface; use PhpTuf\ComposerStager\API\Precondition\Service\ComposerIsAvailableInterface; use PhpTuf\ComposerStager\API\Process\Service\ComposerProcessRunnerInterface; +use PhpTuf\ComposerStager\API\Process\Service\OutputCallbackInterface; use PhpTuf\ComposerStager\API\Process\Value\OutputTypeEnum; use Prophecy\Argument; use Prophecy\Prophecy\ObjectProphecy; @@ -250,10 +250,18 @@ class ComposerInspectorTest extends PackageManagerKernelTestBase { public function testVersionCheck(?string $reported_version, ?string $expected_message): void { $runner = $this->mockComposerRunner($reported_version); + // Mock the ComposerIsAvailableInterface so that if it uses the Composer + // runner it will not affect the test expectations. + $composerPrecondition = $this->prophesize(ComposerIsAvailableInterface::class); + $composerPrecondition + ->assertIsFulfilled(Argument::cetera()) + ->shouldBeCalledOnce(); + $this->container->set(ComposerIsAvailableInterface::class, $composerPrecondition->reveal()); + // The result of the version check is statically cached, so the runner // should only be called once, even though we call validate() twice in this // test. - $runner->getMethodProphecies('run')[0]->shouldBeCalledOnce(); + $runner->getMethodProphecies('run')[0]->withArguments([['--format=json'], NULL, [], Argument::any()])->shouldBeCalledOnce(); // The runner should be called with `validate` as the first argument, but // it won't affect the outcome of this test. $runner->run(Argument::withEntry(0, 'validate')); @@ -535,7 +543,7 @@ class ComposerInspectorTest extends PackageManagerKernelTestBase { ]); $callback = end($arguments_passed_to_runner); - assert($callback instanceof ProcessOutputCallback); + assert($callback instanceof OutputCallbackInterface); $callback(OutputTypeEnum::OUT, $command_output); }; diff --git a/package_manager/tests/src/Unit/ProcessOutputCallbackTest.php b/package_manager/tests/src/Unit/ProcessOutputCallbackTest.php index 84b9ed63e81449c1e7b007e6c8d42bd002d163d4..b401fbd119cb8e477f578a3ad8d4ccfd09e820a9 100644 --- a/package_manager/tests/src/Unit/ProcessOutputCallbackTest.php +++ b/package_manager/tests/src/Unit/ProcessOutputCallbackTest.php @@ -38,13 +38,13 @@ class ProcessOutputCallbackTest extends UnitTestCase { $error_text = 'What happened?'; $callback(OutputTypeEnum::ERR, $error_text); - $this->assertSame($error_text, $callback->getErrorOutput()); + $this->assertSame([$error_text], $callback->getErrorOutput()); // The error should not yet be logged. $this->assertEmpty($logger->records); // There should be no output data, but calling getOutput() should log the // error. - $this->assertNull($callback->getOutput()); + $this->assertSame([], $callback->getOutput()); $this->assertNull($callback->parseJsonOutput()); $this->assertTrue($logger->hasWarning($error_text)); @@ -63,8 +63,8 @@ class ProcessOutputCallbackTest extends UnitTestCase { $callback->setLogger($logger); // The buffers should initially be empty, and nothing should be logged. - $this->assertNull($callback->getOutput()); - $this->assertNull($callback->getErrorOutput()); + $this->assertSame([], $callback->getOutput()); + $this->assertSame([], $callback->getErrorOutput()); $this->assertNull($callback->parseJsonOutput()); $this->assertEmpty($logger->records); @@ -77,13 +77,15 @@ class ProcessOutputCallbackTest extends UnitTestCase { $json = json_encode($data, JSON_PRETTY_PRINT); // Ensure the JSON is a multi-line string. $this->assertGreaterThan(1, substr_count($json, "\n")); + $expected_output = []; foreach (explode("\n", $json) as $line) { $callback(OutputTypeEnum::OUT, "$line\n"); + $expected_output[] = "$line\n"; } - $this->assertSame("$json\n", $callback->getOutput()); + $this->assertSame($expected_output, $callback->getOutput()); // Ensure that parseJsonOutput() can parse the data without errors. $this->assertSame($data, $callback->parseJsonOutput()); - $this->assertNull($callback->getErrorOutput()); + $this->assertSame([], $callback->getErrorOutput()); $this->assertEmpty($logger->records); // If we send error output, it should be logged, but we should still be able @@ -91,34 +93,37 @@ class ProcessOutputCallbackTest extends UnitTestCase { $callback(OutputTypeEnum::ERR, 'Oh no, what happened?'); $callback(OutputTypeEnum::ERR, 'Really what happened?!'); $this->assertSame($data, $callback->parseJsonOutput()); - $this->assertSame('Oh no, what happened?Really what happened?!', $callback->getErrorOutput()); + $expected_error = ['Oh no, what happened?', 'Really what happened?!']; + $this->assertSame($expected_error, $callback->getErrorOutput()); $this->assertTrue($logger->hasWarning('Oh no, what happened?Really what happened?!')); // Send more output and error data to the callback; they should be appended // to the data we previously sent. $callback(OutputTypeEnum::OUT, '{}'); + $expected_output[] = '{}'; $callback(OutputTypeEnum::ERR, 'new Error 1!'); $callback(OutputTypeEnum::ERR, 'new Error 2!'); + $expected_error[] = 'new Error 1!'; + $expected_error[] = 'new Error 2!'; // The output buffer will no longer be valid JSON, so don't try to parse it. - $this->assertSame("$json\n{}", $callback->getOutput()); - $expected_error = 'Oh no, what happened?Really what happened?!new Error 1!new Error 2!'; + $this->assertSame($expected_output, $callback->getOutput()); $this->assertSame($expected_error, $callback->getErrorOutput()); - $this->assertTrue($logger->hasWarning($expected_error)); + $this->assertTrue($logger->hasWarning(implode('', $expected_error))); // The previously logged error output should still be there. $this->assertTrue($logger->hasWarning('Oh no, what happened?Really what happened?!')); // Clear all stored output and errors. $callback->reset(); - $this->assertNull($callback->getOutput()); - $this->assertNull($callback->getErrorOutput()); + $this->assertSame([], $callback->getOutput()); + $this->assertSame([], $callback->getErrorOutput()); $this->assertNull($callback->parseJsonOutput()); // Send more output and error data. $callback(OutputTypeEnum::OUT, 'Bonjour!'); $callback(OutputTypeEnum::ERR, 'You continue to annoy me.'); // We should now only see the stuff we just sent... - $this->assertSame('Bonjour!', $callback->getOutput()); - $this->assertSame('You continue to annoy me.', $callback->getErrorOutput()); + $this->assertSame(['Bonjour!'], $callback->getOutput()); + $this->assertSame(['You continue to annoy me.'], $callback->getErrorOutput()); $this->assertTrue($logger->hasWarning('You continue to annoy me.')); // ...but the previously logged errors should still be there. $this->assertTrue($logger->hasWarning('Oh no, what happened?Really what happened?!new Error 1!new Error 2!'));