Skip to content

Commit 546f158

Browse files
committed
Fix #5230
1 parent a6319b7 commit 546f158

File tree

8 files changed

+178
-106
lines changed

8 files changed

+178
-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

+45-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,67 @@ 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 = $test instanceof TestSuite ? $test->tests() : [$test];
307+
308+
$var = [
309+
'testData' => [],
310+
];
311+
312+
$testData = [];
313+
314+
foreach ($tests as $t) {
315+
assert($t instanceof TestCase);
316+
317+
$data = serialize($t->providedData());
318+
$dataName = serialize($t->dataName());
319+
$dependencyInput = serialize($t->dependencyInput());
320+
321+
$methodName = $t->name();
322+
$className = $t::class;
323+
324+
$testData[] = [
325+
'data' => $data,
326+
'dataName' => $dataName,
327+
'dependencyInput' => $dependencyInput,
328+
'methodName' => $methodName,
329+
'className' => $className,
330+
];
331+
}
332+
333+
if ($isTestSuite) {
334+
foreach ($testData as $data) {
335+
$var['testData'][] = $data;
336+
}
337+
$var['testData'] = serialize($var['testData']);
338+
} else {
339+
$var = $testData[0];
340+
}
341+
342+
$includePath = serialize(get_include_path());
343+
313344
$offset = hrtime();
314345
$serializedConfiguration = $this->saveConfigurationForChildProcess();
315346
$processResultFile = tempnam(sys_get_temp_dir(), 'phpunit_');
316347

317-
$var = [
348+
$var = array_merge($var, [
318349
'bootstrap' => $bootstrap,
319350
'composerAutoload' => $composerAutoload,
320351
'phar' => $phar,
321352
'filename' => $class->getFileName(),
322353
'className' => $class->getName(),
323354
'collectCodeCoverageInformation' => $coverage,
324355
'linesToBeIgnored' => $linesToBeIgnored,
325-
'data' => $data,
326-
'dataName' => $dataName,
327-
'dependencyInput' => $dependencyInput,
328356
'constants' => $constants,
329357
'globals' => $globals,
330358
'include_path' => $includePath,
331359
'included_files' => $includedFiles,
332360
'iniSettings' => $iniSettings,
333-
'name' => $test->name(),
334361
'offsetSeconds' => $offset[0],
335362
'offsetNanoseconds' => $offset[1],
336363
'serializedConfiguration' => $serializedConfiguration,
337364
'processResultFile' => $processResultFile,
338365
'exportObjects' => $exportObjects,
339-
];
340-
341-
if (!$runEntireClass) {
342-
$var['methodName'] = $test->name();
343-
}
366+
]);
344367

345368
$template->setVar($var);
346369

src/Framework/TestSuite.php

+57-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

@@ -309,11 +318,17 @@ public function groupDetails(): array
309318

310319
/**
311320
* @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
321+
* @throws \SebastianBergmann\Template\InvalidArgumentException
312322
* @throws CodeCoverageException
313323
* @throws Event\RuntimeException
314324
* @throws Exception
325+
* @throws MoreThanOneDataSetFromDataProviderException
315326
* @throws NoPreviousThrowableException
327+
* @throws ProcessIsolationException
328+
* @throws RunnerException
329+
* @throws StaticAnalysisCacheNotConfiguredException
316330
* @throws UnintentionallyCoveredCodeException
331+
* @throws UtilException
317332
*/
318333
public function run(): void
319334
{
@@ -330,14 +345,22 @@ public function run(): void
330345
return;
331346
}
332347

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

337-
break;
338-
}
355+
break;
356+
}
339357

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

343366
$this->invokeMethodsAfterLastTest($emitter);
@@ -462,9 +485,18 @@ public function isForTestClass(): bool
462485
return class_exists($this->name, false) && is_subclass_of($this->name, TestCase::class);
463486
}
464487

488+
public function shouldRunInSeparateProcess(): bool
489+
{
490+
if ($this->runClassInSeparateProcess) {
491+
return true;
492+
}
493+
494+
return false;
495+
}
496+
465497
/**
466-
* @throws \PHPUnit\Event\TestData\MoreThanOneDataSetFromDataProviderException
467498
* @throws Exception
499+
* @throws MoreThanOneDataSetFromDataProviderException
468500
*/
469501
protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method): void
470502
{
@@ -679,4 +711,19 @@ private function invokeMethodsAfterLastTest(Event\Emitter $emitter): void
679711
);
680712
}
681713
}
714+
715+
private function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void
716+
{
717+
if ($this->runClassInSeparateProcess === null) {
718+
$this->runClassInSeparateProcess = $runClassInSeparateProcess;
719+
}
720+
}
721+
722+
/**
723+
* @psalm-param class-string $className
724+
*/
725+
private function shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess(string $className): bool
726+
{
727+
return MetadataRegistry::parser()->forClass($className)->isRunClassInSeparateProcess()->isNotEmpty();
728+
}
682729
}

0 commit comments

Comments
 (0)