From 46e05533e2e55a8c2c8df3b87231dbff930d0e8d Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 14 Jul 2025 07:40:29 +0200 Subject: [PATCH] Implement `TestCase->expectProcessExit($exitCode)` --- ProcessExitTest.php | 33 +++++++++++++++++ src/Framework/TestCase.php | 11 ++++++ .../ChildProcessResultProcessor.php | 12 ++++++- src/Framework/TestRunner/templates/class.tpl | 36 ++++++++++--------- src/Framework/TestRunner/templates/method.tpl | 36 ++++++++++--------- src/Util/PHP/DefaultJobRunner.php | 8 ++++- src/Util/PHP/JobRunner.php | 1 + src/Util/PHP/Result.php | 9 ++++- 8 files changed, 111 insertions(+), 35 deletions(-) create mode 100644 ProcessExitTest.php diff --git a/ProcessExitTest.php b/ProcessExitTest.php new file mode 100644 index 00000000000..1166c5244b8 --- /dev/null +++ b/ProcessExitTest.php @@ -0,0 +1,33 @@ +expectProcessExit($expectedExit); + } + + exit($actualExitCode); + } + + static public function provideExitCodes():iterable { + yield [null, 0]; + yield [null, 1]; + + yield [0, 0]; + yield [0, 1]; + + yield [1, 1]; + yield [1, 0]; + } +} diff --git a/src/Framework/TestCase.php b/src/Framework/TestCase.php index b6d7fd6c706..738c1af42fc 100644 --- a/src/Framework/TestCase.php +++ b/src/Framework/TestCase.php @@ -194,6 +194,7 @@ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, T private bool $outputRetrievedForAssertion = false; private bool $doesNotPerformAssertions = false; private bool $expectErrorLog = false; + private ?int $expectProcessExit = null; /** * @var list @@ -1035,6 +1036,16 @@ final protected function expectOutputString(string $expectedString): void $this->outputExpectedString = $expectedString; } + final protected function expectProcessExit(int $exitCode): void + { + $this->expectProcessExit = $exitCode; + } + + final public function getExpectedProcessExitCode(): ?int + { + return $this->expectProcessExit; + } + final protected function expectErrorLog(): void { $this->expectErrorLog = true; diff --git a/src/Framework/TestRunner/ChildProcessResultProcessor.php b/src/Framework/TestRunner/ChildProcessResultProcessor.php index af487c1964e..7af509b02ee 100644 --- a/src/Framework/TestRunner/ChildProcessResultProcessor.php +++ b/src/Framework/TestRunner/ChildProcessResultProcessor.php @@ -37,7 +37,7 @@ public function __construct(Facade $eventFacade, Emitter $emitter, PassedTests $ $this->codeCoverage = $codeCoverage; } - public function process(Test $test, string $serializedProcessResult, string $stderr): void + public function process(Test $test, string $serializedProcessResult, string $stderr, int $exitCode): void { if ($stderr !== '') { $exception = new Exception(trim($stderr)); @@ -74,6 +74,16 @@ public function process(Test $test, string $serializedProcessResult, string $std return; } + if ($childResult->expectedProcessExit !== null && $childResult->testCalledExit === true) { + assert($test instanceof TestCase); + + $test->assertSame($childResult->expectedProcessExit, $exitCode, 'Process exit-code expectation failed'); + } elseif ($childResult->expectedProcessExit !== null && $childResult->testCalledExit === false) { + $test->fail('Process expected exit() to be called but test did not call it'); + } elseif ($childResult->expectedProcessExit === null && $childResult->testCalledExit === false) { + $test->fail('Process called exit() but the test did not expect it'); + } + $this->eventFacade->forward($childResult->events); $this->passedTests->import($childResult->passedTests); diff --git a/src/Framework/TestRunner/templates/class.tpl b/src/Framework/TestRunner/templates/class.tpl index 5edd7ee2943..0ee26e998e0 100644 --- a/src/Framework/TestRunner/templates/class.tpl +++ b/src/Framework/TestRunner/templates/class.tpl @@ -75,11 +75,29 @@ function __phpunit_run_isolated_test() $test->setInIsolation(true); ob_end_clean(); + $output = ''; + $testCalledExit = true; + register_shutdown_function(function() use ($test, $output, $dispatcher, $testCalledExit) { + file_put_contents( + '{processResultFile}', + serialize( + (object)[ + 'testResult' => $test->result(), + 'codeCoverage' => {collectCodeCoverageInformation} ? CodeCoverage::instance()->codeCoverage() : null, + 'numAssertions' => $test->numberOfAssertionsPerformed(), + 'testCalledExit' => $testCalledExit, + 'expectedProcessExit' => $test->getExpectedProcessExitCode(), + 'output' => $output, + 'events' => $dispatcher->flush(), + 'passedTests' => PassedTests::instance(), + ] + ) + ); + }); $test->run(); - $output = ''; - + $testCalledExit = false; if (!$test->expectsOutput()) { $output = $test->output(); } @@ -98,20 +116,6 @@ function __phpunit_run_isolated_test() @rewind(STDOUT); } } - - file_put_contents( - '{processResultFile}', - serialize( - (object)[ - 'testResult' => $test->result(), - 'codeCoverage' => {collectCodeCoverageInformation} ? CodeCoverage::instance()->codeCoverage() : null, - 'numAssertions' => $test->numberOfAssertionsPerformed(), - 'output' => $output, - 'events' => $dispatcher->flush(), - 'passedTests' => PassedTests::instance() - ] - ) - ); } function __phpunit_error_handler($errno, $errstr, $errfile, $errline) diff --git a/src/Framework/TestRunner/templates/method.tpl b/src/Framework/TestRunner/templates/method.tpl index 8d4f38439df..9e2960a28a6 100644 --- a/src/Framework/TestRunner/templates/method.tpl +++ b/src/Framework/TestRunner/templates/method.tpl @@ -75,11 +75,29 @@ function __phpunit_run_isolated_test() $test->setInIsolation(true); ob_end_clean(); + $output = ''; + $testCalledExit = true; + register_shutdown_function(function() use ($test, $output, $dispatcher, $testCalledExit) { + file_put_contents( + '{processResultFile}', + serialize( + (object)[ + 'testResult' => $test->result(), + 'codeCoverage' => {collectCodeCoverageInformation} ? CodeCoverage::instance()->codeCoverage() : null, + 'numAssertions' => $test->numberOfAssertionsPerformed(), + 'testCalledExit' => $testCalledExit, + 'expectedProcessExit' => $test->getExpectedProcessExitCode(), + 'output' => $output, + 'events' => $dispatcher->flush(), + 'passedTests' => PassedTests::instance(), + ] + ) + ); + }); $test->run(); - $output = ''; - + $testCalledExit = false; if (!$test->expectsOutput()) { $output = $test->output(); } @@ -98,20 +116,6 @@ function __phpunit_run_isolated_test() @rewind(STDOUT); } } - - file_put_contents( - '{processResultFile}', - serialize( - (object)[ - 'testResult' => $test->result(), - 'codeCoverage' => {collectCodeCoverageInformation} ? CodeCoverage::instance()->codeCoverage() : null, - 'numAssertions' => $test->numberOfAssertionsPerformed(), - 'output' => $output, - 'events' => $dispatcher->flush(), - 'passedTests' => PassedTests::instance() - ] - ) - ); } function __phpunit_error_handler($errno, $errstr, $errfile, $errline) diff --git a/src/Util/PHP/DefaultJobRunner.php b/src/Util/PHP/DefaultJobRunner.php index 073e4e4d463..6d0b92e98b8 100644 --- a/src/Util/PHP/DefaultJobRunner.php +++ b/src/Util/PHP/DefaultJobRunner.php @@ -147,6 +147,12 @@ private function runProcess(Job $job, ?string $temporaryFile): Result fclose($pipes[2]); } + $exitCode = 0; + $processStatus = proc_get_status($process); + if ($processStatus['running'] === false) { + $exitCode = $processStatus['exitcode']; + } + proc_close($process); if ($temporaryFile !== null) { @@ -156,7 +162,7 @@ private function runProcess(Job $job, ?string $temporaryFile): Result assert($stdout !== false); assert($stderr !== false); - return new Result($stdout, $stderr); + return new Result($stdout, $stderr, $exitCode); } /** diff --git a/src/Util/PHP/JobRunner.php b/src/Util/PHP/JobRunner.php index 6805a0c44df..ec4c29cc03e 100644 --- a/src/Util/PHP/JobRunner.php +++ b/src/Util/PHP/JobRunner.php @@ -52,6 +52,7 @@ final public function runTestJob(Job $job, string $processResultFile, Test $test $test, $processResult, $result->stderr(), + $result->exitCode(), ); EventFacade::emitter()->childProcessFinished($result->stdout(), $result->stderr()); diff --git a/src/Util/PHP/Result.php b/src/Util/PHP/Result.php index ed05822601b..cb4c0e03b3a 100644 --- a/src/Util/PHP/Result.php +++ b/src/Util/PHP/Result.php @@ -20,11 +20,13 @@ { private string $stdout; private string $stderr; + private int $exitCode; - public function __construct(string $stdout, string $stderr) + public function __construct(string $stdout, string $stderr, int $exitCode) { $this->stdout = $stdout; $this->stderr = $stderr; + $this->exitCode = $exitCode; } public function stdout(): string @@ -36,4 +38,9 @@ public function stderr(): string { return $this->stderr; } + + public function exitCode(): int + { + return $this->exitCode; + } }