Skip to content

Commit 55ff287

Browse files
Merge branch '11.5' into 12.0
2 parents 0997228 + 03050de commit 55ff287

File tree

11 files changed

+256
-148
lines changed

11 files changed

+256
-148
lines changed

ChangeLog-12.0.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes of the PHPUnit 12.0 release series are documented in this file using the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
44

5+
## [12.0.4] - 2025-MM-DD
6+
7+
### Fixed
8+
9+
* [#6134](https://github.com/sebastianbergmann/phpunit/issues/6134): Missing event when child process ends unexpectedly
10+
511
## [12.0.3] - 2025-02-18
612

713
### Changed
@@ -74,6 +80,7 @@ All notable changes of the PHPUnit 12.0 release series are documented in this fi
7480
* [#5801](https://github.com/sebastianbergmann/phpunit/issues/5801): Support for targeting traits with `#[CoversClass]` and `#[UsesClass]` attributes
7581
* [#5978](https://github.com/sebastianbergmann/phpunit/issues/5978): Support for PHP 8.2
7682

83+
[12.0.4]: https://github.com/sebastianbergmann/phpunit/compare/12.0.3...12.0
7784
[12.0.3]: https://github.com/sebastianbergmann/phpunit/compare/12.0.2...12.0.3
7885
[12.0.2]: https://github.com/sebastianbergmann/phpunit/compare/12.0.1...12.0.2
7986
[12.0.1]: https://github.com/sebastianbergmann/phpunit/compare/12.0.0...12.0.1
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of PHPUnit.
4+
*
5+
* (c) Sebastian Bergmann <[email protected]>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace PHPUnit\Framework;
11+
12+
use function assert;
13+
use function trim;
14+
use function unserialize;
15+
use PHPUnit\Event\Code\TestMethodBuilder;
16+
use PHPUnit\Event\Code\ThrowableBuilder;
17+
use PHPUnit\Event\Emitter;
18+
use PHPUnit\Event\Facade;
19+
use PHPUnit\Runner\CodeCoverage;
20+
use PHPUnit\TestRunner\TestResult\PassedTests;
21+
22+
final readonly class ChildProcessResultProcessor
23+
{
24+
private Facade $eventFacade;
25+
private Emitter $emitter;
26+
private PassedTests $passedTests;
27+
private CodeCoverage $codeCoverage;
28+
29+
public function __construct(Facade $eventFacade, Emitter $emitter, PassedTests $passedTests, CodeCoverage $codeCoverage)
30+
{
31+
$this->eventFacade = $eventFacade;
32+
$this->emitter = $emitter;
33+
$this->passedTests = $passedTests;
34+
$this->codeCoverage = $codeCoverage;
35+
}
36+
37+
public function process(Test $test, string $serializedProcessResult, string $stderr): void
38+
{
39+
if (!empty($stderr)) {
40+
$exception = new Exception(trim($stderr));
41+
42+
assert($test instanceof TestCase);
43+
44+
$this->emitter->testErrored(
45+
TestMethodBuilder::fromTestCase($test),
46+
ThrowableBuilder::from($exception),
47+
);
48+
49+
return;
50+
}
51+
52+
$childResult = @unserialize($serializedProcessResult);
53+
54+
if ($childResult === false) {
55+
$exception = new AssertionFailedError('Test was run in child process and ended unexpectedly');
56+
57+
assert($test instanceof TestCase);
58+
59+
$this->emitter->testErrored(
60+
TestMethodBuilder::fromTestCase($test),
61+
ThrowableBuilder::from($exception),
62+
);
63+
64+
$this->emitter->testFinished(
65+
TestMethodBuilder::fromTestCase($test),
66+
0,
67+
);
68+
69+
return;
70+
}
71+
72+
$this->eventFacade->forward($childResult->events);
73+
$this->passedTests->import($childResult->passedTests);
74+
75+
assert($test instanceof TestCase);
76+
77+
$test->setResult($childResult->testResult);
78+
$test->addToAssertionCount($childResult->numAssertions);
79+
80+
if (!$this->codeCoverage->isActive()) {
81+
return;
82+
}
83+
84+
// @codeCoverageIgnoreStart
85+
if (!$childResult->codeCoverage instanceof \SebastianBergmann\CodeCoverage\CodeCoverage) {
86+
return;
87+
}
88+
89+
CodeCoverage::instance()->codeCoverage()->merge(
90+
$childResult->codeCoverage,
91+
);
92+
// @codeCoverageIgnoreEnd
93+
}
94+
}

src/Framework/TestRunner/SeparateProcessTestRunner.php

Lines changed: 1 addition & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,20 @@
1111

1212
use function assert;
1313
use function defined;
14-
use function file_get_contents;
1514
use function get_include_path;
1615
use function hrtime;
17-
use function is_array;
18-
use function is_file;
19-
use function restore_error_handler;
2016
use function serialize;
21-
use function set_error_handler;
2217
use function sys_get_temp_dir;
2318
use function tempnam;
24-
use function trim;
2519
use function unlink;
2620
use function unserialize;
2721
use function var_export;
28-
use ErrorException;
29-
use PHPUnit\Event\Code\TestMethodBuilder;
30-
use PHPUnit\Event\Code\ThrowableBuilder;
31-
use PHPUnit\Event\Facade;
3222
use PHPUnit\Event\NoPreviousThrowableException;
3323
use PHPUnit\Runner\CodeCoverage;
34-
use PHPUnit\TestRunner\TestResult\PassedTests;
3524
use PHPUnit\TextUI\Configuration\Registry as ConfigurationRegistry;
3625
use PHPUnit\Util\GlobalState;
3726
use PHPUnit\Util\PHP\Job;
3827
use PHPUnit\Util\PHP\JobRunnerRegistry;
39-
use PHPUnit\Util\PHP\PhpProcessException;
4028
use ReflectionClass;
4129
use SebastianBergmann\Template\InvalidArgumentException;
4230
use SebastianBergmann\Template\Template;
@@ -151,126 +139,11 @@ public function run(TestCase $test, bool $runEntireClass, bool $preserveGlobalSt
151139

152140
assert($code !== '');
153141

154-
$this->runTestJob($code, $test, $processResultFile);
142+
JobRunnerRegistry::runTestJob(new Job($code), $processResultFile, $test);
155143

156144
@unlink($serializedConfiguration);
157145
}
158146

159-
/**
160-
* @param non-empty-string $code
161-
*
162-
* @throws Exception
163-
* @throws NoPreviousThrowableException
164-
* @throws PhpProcessException
165-
*/
166-
private function runTestJob(string $code, Test $test, string $processResultFile): void
167-
{
168-
$result = JobRunnerRegistry::run(new Job($code));
169-
170-
$processResult = '';
171-
172-
if (is_file($processResultFile)) {
173-
$processResult = file_get_contents($processResultFile);
174-
175-
assert($processResult !== false);
176-
177-
@unlink($processResultFile);
178-
}
179-
180-
$this->processChildResult(
181-
$test,
182-
$processResult,
183-
$result->stderr(),
184-
);
185-
}
186-
187-
/**
188-
* @throws Exception
189-
* @throws NoPreviousThrowableException
190-
*/
191-
private function processChildResult(Test $test, string $stdout, string $stderr): void
192-
{
193-
if (!empty($stderr)) {
194-
$exception = new Exception(trim($stderr));
195-
196-
assert($test instanceof TestCase);
197-
198-
Facade::emitter()->testErrored(
199-
TestMethodBuilder::fromTestCase($test),
200-
ThrowableBuilder::from($exception),
201-
);
202-
203-
return;
204-
}
205-
206-
set_error_handler(
207-
/**
208-
* @throws ErrorException
209-
*/
210-
static function (int $errno, string $errstr, string $errfile, int $errline): never
211-
{
212-
throw new ErrorException($errstr, $errno, $errno, $errfile, $errline);
213-
},
214-
);
215-
216-
try {
217-
$childResult = unserialize($stdout);
218-
219-
restore_error_handler();
220-
221-
if ($childResult === false) {
222-
$exception = new AssertionFailedError('Test was run in child process and ended unexpectedly');
223-
224-
assert($test instanceof TestCase);
225-
226-
Facade::emitter()->testErrored(
227-
TestMethodBuilder::fromTestCase($test),
228-
ThrowableBuilder::from($exception),
229-
);
230-
231-
Facade::emitter()->testFinished(
232-
TestMethodBuilder::fromTestCase($test),
233-
0,
234-
);
235-
}
236-
} catch (ErrorException $e) {
237-
restore_error_handler();
238-
239-
$childResult = false;
240-
241-
$exception = new Exception(trim($stdout), 0, $e);
242-
243-
assert($test instanceof TestCase);
244-
245-
Facade::emitter()->testErrored(
246-
TestMethodBuilder::fromTestCase($test),
247-
ThrowableBuilder::from($exception),
248-
);
249-
}
250-
251-
if ($childResult !== false) {
252-
if (!is_array($childResult)) {
253-
$childResult = [$childResult];
254-
}
255-
256-
foreach ($childResult as $result) {
257-
Facade::instance()->forward($result->events);
258-
PassedTests::instance()->import($result->passedTests);
259-
260-
assert($test instanceof TestCase);
261-
262-
$test->setResult($result->testResult);
263-
$test->addToAssertionCount($result->numAssertions);
264-
265-
if (CodeCoverage::instance()->isActive() && $result->codeCoverage instanceof \SebastianBergmann\CodeCoverage\CodeCoverage) {
266-
CodeCoverage::instance()->codeCoverage()->merge(
267-
$result->codeCoverage,
268-
);
269-
}
270-
}
271-
}
272-
}
273-
274147
/**
275148
* @throws ProcessIsolationException
276149
*/

src/Framework/TestRunner/templates/class.tpl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,6 @@ function __phpunit_run_isolated_test()
9999
}
100100
}
101101

102-
Facade::emitter()->testRunnerFinishedChildProcess($output, '');
103-
104102
file_put_contents(
105103
'{processResultFile}',
106104
serialize(

src/Framework/TestRunner/templates/method.tpl

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,6 @@ function __phpunit_run_isolated_test()
9999
}
100100
}
101101

102-
Facade::emitter()->testRunnerFinishedChildProcess($output, '');
103-
104102
file_put_contents(
105103
'{processResultFile}',
106104
serialize(

src/Util/PHP/DefaultJobRunner.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use function assert;
1717
use function fclose;
1818
use function file_put_contents;
19+
use function function_exists;
1920
use function fwrite;
2021
use function ini_get_all;
2122
use function is_array;
@@ -37,7 +38,7 @@
3738
*
3839
* @internal This class is not covered by the backward compatibility promise for PHPUnit
3940
*/
40-
final readonly class DefaultJobRunner implements JobRunner
41+
final readonly class DefaultJobRunner extends JobRunner
4142
{
4243
/**
4344
* @throws PhpProcessException
@@ -117,8 +118,6 @@ private function runProcess(Job $job, ?string $temporaryFile): Result
117118
$environmentVariables,
118119
);
119120

120-
Facade::emitter()->testRunnerStartedChildProcess();
121-
122121
if (!is_resource($process)) {
123122
// @codeCoverageIgnoreStart
124123
throw new PhpProcessException(
@@ -127,6 +126,8 @@ private function runProcess(Job $job, ?string $temporaryFile): Result
127126
// @codeCoverageIgnoreEnd
128127
}
129128

129+
Facade::emitter()->testRunnerStartedChildProcess();
130+
130131
fwrite($pipes[0], $job->code());
131132
fclose($pipes[0]);
132133

@@ -178,6 +179,8 @@ private function buildCommand(Job $job, ?string $file): array
178179
),
179180
);
180181
} elseif ($runtime->hasXdebug()) {
182+
assert(function_exists('xdebug_is_debugger_active'));
183+
181184
$xdebugSettings = ini_get_all('xdebug');
182185

183186
assert($xdebugSettings !== false);
@@ -189,11 +192,8 @@ private function buildCommand(Job $job, ?string $file): array
189192
),
190193
);
191194

192-
// disable xdebug if not required to reduce xdebug performance overhead in subprocesses
193-
if (
194-
!CodeCoverage::instance()->isActive() &&
195-
xdebug_is_debugger_active() === false
196-
) {
195+
if (!CodeCoverage::instance()->isActive() &&
196+
xdebug_is_debugger_active() === false) {
197197
$phpSettings['xdebug.mode'] = 'xdebug.mode=off';
198198
}
199199
}

0 commit comments

Comments
 (0)