Skip to content

Commit b94977a

Browse files
committed
Fix #5230
1 parent a6319b7 commit b94977a

File tree

8 files changed

+175
-106
lines changed

8 files changed

+175
-106
lines changed

src/Framework/TestBuilder.php

+2-17
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ public function build(ReflectionClass $theClass, string $methodName): Test
4747
$data,
4848
$this->shouldTestMethodBeRunInSeparateProcess($className, $methodName),
4949
$this->shouldGlobalStateBePreserved($className, $methodName),
50-
$this->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($className),
5150
$this->backupSettings($className, $methodName),
5251
);
5352
}
@@ -60,7 +59,6 @@ public function build(ReflectionClass $theClass, string $methodName): Test
6059
$test,
6160
$this->shouldTestMethodBeRunInSeparateProcess($className, $methodName),
6261
$this->shouldGlobalStateBePreserved($className, $methodName),
63-
$this->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($className),
6462
$this->backupSettings($className, $methodName),
6563
);
6664

@@ -72,7 +70,7 @@ public function build(ReflectionClass $theClass, string $methodName): Test
7270
* @psalm-param non-empty-string $methodName
7371
* @psalm-param array{backupGlobals: ?bool, backupGlobalsExcludeList: list<string>, backupStaticProperties: ?bool, backupStaticPropertiesExcludeList: array<string,list<string>>} $backupSettings
7472
*/
75-
private function buildDataProviderTestSuite(string $methodName, string $className, array $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings): DataProviderTestSuite
73+
private function buildDataProviderTestSuite(string $methodName, string $className, array $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, array $backupSettings): DataProviderTestSuite
7674
{
7775
$dataProviderTestSuite = DataProviderTestSuite::empty(
7876
$className . '::' . $methodName,
@@ -91,7 +89,6 @@ private function buildDataProviderTestSuite(string $methodName, string $classNam
9189
$_test,
9290
$runTestInSeparateProcess,
9391
$preserveGlobalState,
94-
$runClassInSeparateProcess,
9592
$backupSettings,
9693
);
9794

@@ -104,16 +101,12 @@ private function buildDataProviderTestSuite(string $methodName, string $classNam
104101
/**
105102
* @psalm-param array{backupGlobals: ?bool, backupGlobalsExcludeList: list<string>, backupStaticProperties: ?bool, backupStaticPropertiesExcludeList: array<string,list<string>>} $backupSettings
106103
*/
107-
private function configureTestCase(TestCase $test, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings): void
104+
private function configureTestCase(TestCase $test, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, array $backupSettings): void
108105
{
109106
if ($runTestInSeparateProcess) {
110107
$test->setRunTestInSeparateProcess(true);
111108
}
112109

113-
if ($runClassInSeparateProcess) {
114-
$test->setRunClassInSeparateProcess(true);
115-
}
116-
117110
if ($preserveGlobalState !== null) {
118111
$test->setPreserveGlobalState($preserveGlobalState);
119112
}
@@ -258,12 +251,4 @@ private function shouldTestMethodBeRunInSeparateProcess(string $className, strin
258251

259252
return false;
260253
}
261-
262-
/**
263-
* @psalm-param class-string $className
264-
*/
265-
private function shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess(string $className): bool
266-
{
267-
return MetadataRegistry::parser()->forClass($className)->isRunClassInSeparateProcess()->isNotEmpty();
268-
}
269254
}

src/Framework/TestCase.php

-14
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, T
126126
*/
127127
private array $backupStaticPropertiesExcludeList = [];
128128
private ?Snapshot $snapshot = null;
129-
private ?bool $runClassInSeparateProcess = null;
130129
private ?bool $runTestInSeparateProcess = null;
131130
private bool $preserveGlobalState = false;
132131
private bool $inIsolation = false;
@@ -489,7 +488,6 @@ final public function run(): void
489488
} else {
490489
(new TestRunner)->runInSeparateProcess(
491490
$this,
492-
$this->runClassInSeparateProcess && !$this->runTestInSeparateProcess,
493491
$this->preserveGlobalState,
494492
);
495493
}
@@ -885,14 +883,6 @@ final public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess
885883
}
886884
}
887885

888-
/**
889-
* @internal This method is not covered by the backward compatibility promise for PHPUnit
890-
*/
891-
final public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void
892-
{
893-
$this->runClassInSeparateProcess = $runClassInSeparateProcess;
894-
}
895-
896886
/**
897887
* @internal This method is not covered by the backward compatibility promise for PHPUnit
898888
*/
@@ -1942,10 +1932,6 @@ private function shouldRunInSeparateProcess(): bool
19421932
return true;
19431933
}
19441934

1945-
if ($this->runClassInSeparateProcess) {
1946-
return true;
1947-
}
1948-
19491935
return ConfigurationRegistry::get()->processIsolation();
19501936
}
19511937

src/Framework/TestRunner.php

+43-22
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace PHPUnit\Framework;
1111

1212
use const PHP_EOL;
13+
use function array_merge;
1314
use function assert;
1415
use function class_exists;
1516
use function defined;
@@ -253,11 +254,13 @@ public function run(TestCase $test): void
253254
* @throws ProcessIsolationException
254255
* @throws StaticAnalysisCacheNotConfiguredException
255256
*/
256-
public function runInSeparateProcess(TestCase $test, bool $runEntireClass, bool $preserveGlobalState): void
257+
public function runInSeparateProcess(TestCase|TestSuite $test, bool $preserveGlobalState): void
257258
{
258259
$class = new ReflectionClass($test);
259260

260-
if ($runEntireClass) {
261+
$isTestSuite = $test instanceof TestSuite;
262+
263+
if ($isTestSuite) {
261264
$template = new Template(
262265
__DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl',
263266
);
@@ -300,47 +303,65 @@ public function runInSeparateProcess(TestCase $test, bool $runEntireClass, bool
300303
$phar = '\'\'';
301304
}
302305

303-
$data = var_export(serialize($test->providedData()), true);
304-
$dataName = var_export($test->dataName(), true);
305-
$dependencyInput = var_export(serialize($test->dependencyInput()), true);
306-
$includePath = var_export(get_include_path(), true);
307-
// must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC
308-
// the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences
309-
$data = "'." . $data . ".'";
310-
$dataName = "'.(" . $dataName . ").'";
311-
$dependencyInput = "'." . $dependencyInput . ".'";
312-
$includePath = "'." . $includePath . ".'";
306+
$tests = $isTestSuite ? $test->tests() : [$test];
307+
308+
$var = [];
309+
310+
$testData = [];
311+
312+
foreach ($tests as $t) {
313+
assert($t instanceof TestCase);
314+
315+
$data = serialize($t->providedData());
316+
$dataName = serialize($t->dataName());
317+
$dependencyInput = serialize($t->dependencyInput());
318+
319+
$methodName = $t->name();
320+
$className = $t::class;
321+
322+
$testData[] = [
323+
'data' => $data,
324+
'dataName' => $dataName,
325+
'dependencyInput' => $dependencyInput,
326+
'methodName' => $methodName,
327+
'className' => $className,
328+
];
329+
}
330+
331+
if ($isTestSuite) {
332+
foreach ($testData as $data) {
333+
$var['testData'][] = $data;
334+
}
335+
$var['testData'] = serialize($var['testData']);
336+
} else {
337+
$var = $testData[0];
338+
}
339+
340+
$includePath = serialize(get_include_path());
341+
313342
$offset = hrtime();
314343
$serializedConfiguration = $this->saveConfigurationForChildProcess();
315344
$processResultFile = tempnam(sys_get_temp_dir(), 'phpunit_');
316345

317-
$var = [
346+
$var = array_merge($var, [
318347
'bootstrap' => $bootstrap,
319348
'composerAutoload' => $composerAutoload,
320349
'phar' => $phar,
321350
'filename' => $class->getFileName(),
322351
'className' => $class->getName(),
323352
'collectCodeCoverageInformation' => $coverage,
324353
'linesToBeIgnored' => $linesToBeIgnored,
325-
'data' => $data,
326-
'dataName' => $dataName,
327-
'dependencyInput' => $dependencyInput,
328354
'constants' => $constants,
329355
'globals' => $globals,
330356
'include_path' => $includePath,
331357
'included_files' => $includedFiles,
332358
'iniSettings' => $iniSettings,
333-
'name' => $test->name(),
334359
'offsetSeconds' => $offset[0],
335360
'offsetNanoseconds' => $offset[1],
336361
'serializedConfiguration' => $serializedConfiguration,
337362
'processResultFile' => $processResultFile,
338363
'exportObjects' => $exportObjects,
339-
];
340-
341-
if (!$runEntireClass) {
342-
$var['methodName'] = $test->name();
343-
}
364+
]);
344365

345366
$template->setVar($var);
346367

src/Framework/TestSuite.php

+56-10
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,25 @@
2929
use PHPUnit\Event;
3030
use PHPUnit\Event\Code\TestMethod;
3131
use PHPUnit\Event\NoPreviousThrowableException;
32+
use PHPUnit\Event\TestData\MoreThanOneDataSetFromDataProviderException;
3233
use PHPUnit\Metadata\Api\Dependencies;
3334
use PHPUnit\Metadata\Api\Groups;
3435
use PHPUnit\Metadata\Api\HookMethods;
3536
use PHPUnit\Metadata\Api\Requirements;
3637
use PHPUnit\Metadata\MetadataCollection;
38+
use PHPUnit\Metadata\Parser\Registry as MetadataRegistry;
3739
use PHPUnit\Runner\Exception as RunnerException;
3840
use PHPUnit\Runner\Filter\Factory;
3941
use PHPUnit\Runner\PhptTestCase;
4042
use PHPUnit\Runner\TestSuiteLoader;
4143
use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade;
44+
use PHPUnit\Util\Exception as UtilException;
4245
use PHPUnit\Util\Filter;
4346
use PHPUnit\Util\Reflection;
4447
use PHPUnit\Util\Test as TestUtil;
4548
use ReflectionClass;
4649
use ReflectionMethod;
50+
use SebastianBergmann\CodeCoverage\StaticAnalysisCacheNotConfiguredException;
4751
use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException;
4852
use Throwable;
4953

@@ -68,9 +72,10 @@ class TestSuite implements IteratorAggregate, Reorderable, SelfDescribing, Test
6872
/**
6973
* @psalm-var list<Test>
7074
*/
71-
private array $tests = [];
72-
private ?array $providedTests = null;
73-
private ?Factory $iteratorFilter = null;
75+
private array $tests = [];
76+
private ?array $providedTests = null;
77+
private ?Factory $iteratorFilter = null;
78+
private ?bool $runClassInSeparateProcess = null;
7479

7580
/**
7681
* @psalm-param non-empty-string $name
@@ -134,6 +139,10 @@ public static function fromClassReflector(ReflectionClass $class): static
134139
);
135140
}
136141

142+
if ($testSuite->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($class->getName())) {
143+
$testSuite->setRunClassInSeparateProcess(true);
144+
}
145+
137146
return $testSuite;
138147
}
139148

@@ -312,8 +321,13 @@ public function groupDetails(): array
312321
* @throws CodeCoverageException
313322
* @throws Event\RuntimeException
314323
* @throws Exception
324+
* @throws MoreThanOneDataSetFromDataProviderException
315325
* @throws NoPreviousThrowableException
326+
* @throws ProcessIsolationException
327+
* @throws RunnerException
328+
* @throws StaticAnalysisCacheNotConfiguredException
316329
* @throws UnintentionallyCoveredCodeException
330+
* @throws UtilException
317331
*/
318332
public function run(): void
319333
{
@@ -330,14 +344,22 @@ public function run(): void
330344
return;
331345
}
332346

333-
foreach ($this as $test) {
334-
if (TestResultFacade::shouldStop()) {
335-
$emitter->testRunnerExecutionAborted();
347+
if ($this->shouldRunInSeparateProcess()) {
348+
(new TestRunner)->runInSeparateProcess($this, false);
349+
} else {
350+
foreach ($this as $test) {
351+
if (TestResultFacade::shouldStop()) {
352+
$emitter->testRunnerExecutionAborted();
336353

337-
break;
338-
}
354+
break;
355+
}
339356

340-
$test->run();
357+
if ($test instanceof self && $test->shouldRunInSeparateProcess()) {
358+
(new TestRunner)->runInSeparateProcess($test, false);
359+
} else {
360+
$test->run();
361+
}
362+
}
341363
}
342364

343365
$this->invokeMethodsAfterLastTest($emitter);
@@ -462,9 +484,18 @@ public function isForTestClass(): bool
462484
return class_exists($this->name, false) && is_subclass_of($this->name, TestCase::class);
463485
}
464486

487+
public function shouldRunInSeparateProcess(): bool
488+
{
489+
if ($this->runClassInSeparateProcess) {
490+
return true;
491+
}
492+
493+
return false;
494+
}
495+
465496
/**
466-
* @throws \PHPUnit\Event\TestData\MoreThanOneDataSetFromDataProviderException
467497
* @throws Exception
498+
* @throws MoreThanOneDataSetFromDataProviderException
468499
*/
469500
protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method): void
470501
{
@@ -679,4 +710,19 @@ private function invokeMethodsAfterLastTest(Event\Emitter $emitter): void
679710
);
680711
}
681712
}
713+
714+
private function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void
715+
{
716+
if ($this->runClassInSeparateProcess === null) {
717+
$this->runClassInSeparateProcess = $runClassInSeparateProcess;
718+
}
719+
}
720+
721+
/**
722+
* @psalm-param class-string $className
723+
*/
724+
private function shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess(string $className): bool
725+
{
726+
return MetadataRegistry::parser()->forClass($className)->isRunClassInSeparateProcess()->isNotEmpty();
727+
}
682728
}

0 commit comments

Comments
 (0)