Skip to content

Commit bdf2488

Browse files
committed
Fixed #5230
1 parent 260fdf4 commit bdf2488

File tree

8 files changed

+164
-103
lines changed

8 files changed

+164
-103
lines changed

src/Framework/TestBuilder.php

+3-18
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
} else {
@@ -59,7 +58,6 @@ public function build(ReflectionClass $theClass, string $methodName): Test
5958
$test,
6059
$this->shouldTestMethodBeRunInSeparateProcess($className, $methodName),
6160
$this->shouldGlobalStateBePreserved($className, $methodName),
62-
$this->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($className),
6361
$this->backupSettings($className, $methodName)
6462
);
6563
}
@@ -68,10 +66,10 @@ public function build(ReflectionClass $theClass, string $methodName): Test
6866
}
6967

7068
/**
71-
* @psalm-param class-string $className
69+
* @psalm-param class-string $className
7270
* @psalm-param array{backupGlobals: ?bool, backupGlobalsExcludeList: list<string>, backupStaticProperties: ?bool, backupStaticPropertiesExcludeList: array<string,list<string>>} $backupSettings
7371
*/
74-
private function buildDataProviderTestSuite(string $methodName, string $className, array $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings): DataProviderTestSuite
72+
private function buildDataProviderTestSuite(string $methodName, string $className, array $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, array $backupSettings): DataProviderTestSuite
7573
{
7674
$dataProviderTestSuite = DataProviderTestSuite::empty(
7775
$className . '::' . $methodName
@@ -90,7 +88,6 @@ private function buildDataProviderTestSuite(string $methodName, string $classNam
9088
$_test,
9189
$runTestInSeparateProcess,
9290
$preserveGlobalState,
93-
$runClassInSeparateProcess,
9491
$backupSettings
9592
);
9693

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

112-
if ($runClassInSeparateProcess) {
113-
$test->setRunClassInSeparateProcess(true);
114-
}
115-
116109
if ($preserveGlobalState !== null) {
117110
$test->setPreserveGlobalState($preserveGlobalState);
118111
}
@@ -254,12 +247,4 @@ private function shouldTestMethodBeRunInSeparateProcess(string $className, strin
254247

255248
return false;
256249
}
257-
258-
/**
259-
* @psalm-param class-string $className
260-
*/
261-
private function shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess(string $className): bool
262-
{
263-
return MetadataRegistry::parser()->forClass($className)->isRunClassInSeparateProcess()->isNotEmpty();
264-
}
265250
}

src/Framework/TestCase.php

-14
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,6 @@ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, T
124124
*/
125125
private array $backupStaticPropertiesExcludeList = [];
126126
private ?Snapshot $snapshot = null;
127-
private ?bool $runClassInSeparateProcess = null;
128127
private ?bool $runTestInSeparateProcess = null;
129128
private bool $preserveGlobalState = false;
130129
private bool $inIsolation = false;
@@ -463,7 +462,6 @@ final public function run(): void
463462
} else {
464463
(new TestRunner)->runInSeparateProcess(
465464
$this,
466-
$this->runClassInSeparateProcess && !$this->runTestInSeparateProcess,
467465
$this->preserveGlobalState
468466
);
469467
}
@@ -820,14 +818,6 @@ final public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess
820818
}
821819
}
822820

823-
/**
824-
* @internal This method is not covered by the backward compatibility promise for PHPUnit
825-
*/
826-
final public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void
827-
{
828-
$this->runClassInSeparateProcess = $runClassInSeparateProcess;
829-
}
830-
831821
/**
832822
* @internal This method is not covered by the backward compatibility promise for PHPUnit
833823
*/
@@ -1878,10 +1868,6 @@ private function shouldRunInSeparateProcess(): bool
18781868
return true;
18791869
}
18801870

1881-
if ($this->runClassInSeparateProcess) {
1882-
return true;
1883-
}
1884-
18851871
return ConfigurationRegistry::get()->processIsolation();
18861872
}
18871873

src/Framework/TestRunner.php

+38-22
Original file line numberDiff line numberDiff line change
@@ -252,11 +252,13 @@ public function run(TestCase $test): void
252252
* @throws ProcessIsolationException
253253
* @throws StaticAnalysisCacheNotConfiguredException
254254
*/
255-
public function runInSeparateProcess(TestCase $test, bool $runEntireClass, bool $preserveGlobalState): void
255+
public function runInSeparateProcess(TestCase|TestSuite $test, bool $preserveGlobalState): void
256256
{
257257
$class = new ReflectionClass($test);
258258

259-
if ($runEntireClass) {
259+
$isTestSuite = $test instanceof TestSuite;
260+
261+
if ($isTestSuite) {
260262
$template = new Template(
261263
__DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl'
262264
);
@@ -297,45 +299,59 @@ public function runInSeparateProcess(TestCase $test, bool $runEntireClass, bool
297299
$phar = '\'\'';
298300
}
299301

300-
$data = var_export(serialize($test->providedData()), true);
301-
$dataName = var_export($test->dataName(), true);
302-
$dependencyInput = var_export(serialize($test->dependencyInput()), true);
303-
$includePath = var_export(get_include_path(), true);
304-
// must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC
305-
// the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences
306-
$data = "'." . $data . ".'";
307-
$dataName = "'.(" . $dataName . ").'";
308-
$dependencyInput = "'." . $dependencyInput . ".'";
309-
$includePath = "'." . $includePath . ".'";
302+
$tests = $isTestSuite ? $test->tests() : [$test];
303+
$var = [];
304+
305+
$testData = [];
306+
foreach ($tests as $t) {
307+
assert($t instanceof TestCase);
308+
309+
$testCaseClass = new ReflectionClass($t);
310+
311+
$data = serialize($t->providedData());
312+
$dataName = serialize($t->dataName());
313+
$dependencyInput = serialize($t->dependencyInput());
314+
315+
$testData[] = [
316+
'data' => $data,
317+
'dataName' => $dataName,
318+
'dependencyInput' => $dependencyInput,
319+
'methodName' => $t->name(),
320+
'className' => $testCaseClass->getName(),
321+
];
322+
}
323+
324+
if ($isTestSuite) {
325+
foreach ($testData as $data) {
326+
$var['testData'][] = $data;
327+
}
328+
$var['testData'] = serialize($var['testData']);
329+
} else {
330+
$var = $testData[0];
331+
}
332+
333+
$includePath = serialize(get_include_path());
310334

311335
$offset = hrtime();
312336

313337
$serializedConfiguration = $this->saveConfigurationForChildProcess();
314338

315-
$var = [
339+
$var = array_merge($var, [
316340
'bootstrap' => $bootstrap,
317341
'composerAutoload' => $composerAutoload,
318342
'phar' => $phar,
319343
'filename' => $class->getFileName(),
320344
'className' => $class->getName(),
321345
'collectCodeCoverageInformation' => $coverage,
322-
'data' => $data,
323-
'dataName' => $dataName,
324-
'dependencyInput' => $dependencyInput,
325346
'constants' => $constants,
326347
'globals' => $globals,
327348
'include_path' => $includePath,
328349
'included_files' => $includedFiles,
329350
'iniSettings' => $iniSettings,
330-
'name' => $test->name(),
331351
'offsetSeconds' => $offset[0],
332352
'offsetNanoseconds' => $offset[1],
333353
'serializedConfiguration' => $serializedConfiguration,
334-
];
335-
336-
if (!$runEntireClass) {
337-
$var['methodName'] = $test->name();
338-
}
354+
]);
339355

340356
$template->setVar($var);
341357

src/Framework/TestSuite.php

+55-5
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,26 @@
3030
use PHPUnit\Event\Code\TestDox;
3131
use PHPUnit\Event\Code\TestMethod;
3232
use PHPUnit\Event\NoPreviousThrowableException;
33+
use PHPUnit\Event\TestData\MoreThanOneDataSetFromDataProviderException;
3334
use PHPUnit\Metadata\Api\Dependencies;
3435
use PHPUnit\Metadata\Api\Groups;
3536
use PHPUnit\Metadata\Api\HookMethods;
3637
use PHPUnit\Metadata\Api\Requirements;
3738
use PHPUnit\Metadata\MetadataCollection;
39+
use PHPUnit\Metadata\Parser\Registry as MetadataRegistry;
3840
use PHPUnit\Runner\Exception as RunnerException;
3941
use PHPUnit\Runner\Filter\Factory;
4042
use PHPUnit\Runner\PhptTestCase;
4143
use PHPUnit\Runner\TestSuiteLoader;
4244
use PHPUnit\TestRunner\TestResult\Facade;
4345
use PHPUnit\TextUI\Configuration\Registry;
46+
use PHPUnit\Util\Exception as UtilException;
4447
use PHPUnit\Util\Filter;
4548
use PHPUnit\Util\Reflection;
4649
use PHPUnit\Util\Test as TestUtil;
4750
use ReflectionClass;
4851
use ReflectionMethod;
52+
use SebastianBergmann\CodeCoverage\StaticAnalysisCacheNotConfiguredException;
4953
use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException;
5054
use Throwable;
5155

@@ -78,6 +82,8 @@ class TestSuite implements IteratorAggregate, Reorderable, SelfDescribing, Test
7882
private readonly bool $stopOnSkipped;
7983
private readonly bool $stopOnDefect;
8084

85+
private ?bool $runClassInSeparateProcess = null;
86+
8187
public static function empty(string $name = null): static
8288
{
8389
if ($name === null) {
@@ -141,6 +147,10 @@ public static function fromClassReflector(ReflectionClass $class): static
141147
);
142148
}
143149

150+
if ($testSuite->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($class->getName())) {
151+
$testSuite->setRunClassInSeparateProcess(true);
152+
}
153+
144154
return $testSuite;
145155
}
146156

@@ -328,6 +338,11 @@ public function getGroupDetails(): array
328338
* @throws Exception
329339
* @throws NoPreviousThrowableException
330340
* @throws UnintentionallyCoveredCodeException
341+
* @throws RunnerException
342+
* @throws UtilException
343+
* @throws MoreThanOneDataSetFromDataProviderException
344+
* @throws ProcessIsolationException
345+
* @throws StaticAnalysisCacheNotConfiguredException
331346
*/
332347
public function run(): void
333348
{
@@ -344,12 +359,20 @@ public function run(): void
344359
return;
345360
}
346361

347-
foreach ($this as $test) {
348-
if ($this->shouldStop()) {
349-
break;
350-
}
362+
if ($this->shouldRunInSeparateProcess()) {
363+
(new TestRunner)->runInSeparateProcess($this, false);
364+
} else {
365+
foreach ($this as $test) {
366+
if ($this->shouldStop()) {
367+
break;
368+
}
351369

352-
$test->run();
370+
if ($test instanceof self && $test->shouldRunInSeparateProcess()) {
371+
(new TestRunner)->runInSeparateProcess($test, false);
372+
} else {
373+
$test->run();
374+
}
375+
}
353376
}
354377

355378
$this->invokeMethodsAfterLastTest($emitter);
@@ -377,6 +400,13 @@ public function setTests(array $tests): void
377400
$this->tests = $tests;
378401
}
379402

403+
private function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void
404+
{
405+
if ($this->runClassInSeparateProcess === null) {
406+
$this->runClassInSeparateProcess = $runClassInSeparateProcess;
407+
}
408+
}
409+
380410
/**
381411
* Mark the test suite as skipped.
382412
*
@@ -722,4 +752,24 @@ private function invokeMethodsAfterLastTest(Event\Emitter $emitter): void
722752
);
723753
}
724754
}
755+
756+
public function shouldRunInSeparateProcess(): bool
757+
{
758+
if ($this->runClassInSeparateProcess) {
759+
return true;
760+
}
761+
762+
// TODO: maybe a new option for "classIsolation" or "processIsolationClass"
763+
// return Registry::get()->processIsolation();
764+
765+
return false;
766+
}
767+
768+
/**
769+
* @psalm-param class-string $className
770+
*/
771+
private function shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess(string $className): bool
772+
{
773+
return MetadataRegistry::parser()->forClass($className)->isRunClassInSeparateProcess()->isNotEmpty();
774+
}
725775
}

0 commit comments

Comments
 (0)