Skip to content

Commit

Permalink
Fixed #5230
Browse files Browse the repository at this point in the history
  • Loading branch information
WalterWoshid committed Feb 22, 2023
1 parent 260fdf4 commit bdf2488
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 103 deletions.
21 changes: 3 additions & 18 deletions src/Framework/TestBuilder.php
Expand Up @@ -47,7 +47,6 @@ public function build(ReflectionClass $theClass, string $methodName): Test
$data,
$this->shouldTestMethodBeRunInSeparateProcess($className, $methodName),
$this->shouldGlobalStateBePreserved($className, $methodName),
$this->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($className),
$this->backupSettings($className, $methodName)
);
} else {
Expand All @@ -59,7 +58,6 @@ public function build(ReflectionClass $theClass, string $methodName): Test
$test,
$this->shouldTestMethodBeRunInSeparateProcess($className, $methodName),
$this->shouldGlobalStateBePreserved($className, $methodName),
$this->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($className),
$this->backupSettings($className, $methodName)
);
}
Expand All @@ -68,10 +66,10 @@ public function build(ReflectionClass $theClass, string $methodName): Test
}

/**
* @psalm-param class-string $className
* @psalm-param class-string $className
* @psalm-param array{backupGlobals: ?bool, backupGlobalsExcludeList: list<string>, backupStaticProperties: ?bool, backupStaticPropertiesExcludeList: array<string,list<string>>} $backupSettings
*/
private function buildDataProviderTestSuite(string $methodName, string $className, array $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, bool $runClassInSeparateProcess, array $backupSettings): DataProviderTestSuite
private function buildDataProviderTestSuite(string $methodName, string $className, array $data, bool $runTestInSeparateProcess, ?bool $preserveGlobalState, array $backupSettings): DataProviderTestSuite
{
$dataProviderTestSuite = DataProviderTestSuite::empty(
$className . '::' . $methodName
Expand All @@ -90,7 +88,6 @@ private function buildDataProviderTestSuite(string $methodName, string $classNam
$_test,
$runTestInSeparateProcess,
$preserveGlobalState,
$runClassInSeparateProcess,
$backupSettings
);

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

if ($runClassInSeparateProcess) {
$test->setRunClassInSeparateProcess(true);
}

if ($preserveGlobalState !== null) {
$test->setPreserveGlobalState($preserveGlobalState);
}
Expand Down Expand Up @@ -254,12 +247,4 @@ private function shouldTestMethodBeRunInSeparateProcess(string $className, strin

return false;
}

/**
* @psalm-param class-string $className
*/
private function shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess(string $className): bool
{
return MetadataRegistry::parser()->forClass($className)->isRunClassInSeparateProcess()->isNotEmpty();
}
}
14 changes: 0 additions & 14 deletions src/Framework/TestCase.php
Expand Up @@ -124,7 +124,6 @@ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, T
*/
private array $backupStaticPropertiesExcludeList = [];
private ?Snapshot $snapshot = null;
private ?bool $runClassInSeparateProcess = null;
private ?bool $runTestInSeparateProcess = null;
private bool $preserveGlobalState = false;
private bool $inIsolation = false;
Expand Down Expand Up @@ -463,7 +462,6 @@ final public function run(): void
} else {
(new TestRunner)->runInSeparateProcess(
$this,
$this->runClassInSeparateProcess && !$this->runTestInSeparateProcess,
$this->preserveGlobalState
);
}
Expand Down Expand Up @@ -820,14 +818,6 @@ final public function setRunTestInSeparateProcess(bool $runTestInSeparateProcess
}
}

/**
* @internal This method is not covered by the backward compatibility promise for PHPUnit
*/
final public function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void
{
$this->runClassInSeparateProcess = $runClassInSeparateProcess;
}

/**
* @internal This method is not covered by the backward compatibility promise for PHPUnit
*/
Expand Down Expand Up @@ -1878,10 +1868,6 @@ private function shouldRunInSeparateProcess(): bool
return true;
}

if ($this->runClassInSeparateProcess) {
return true;
}

return ConfigurationRegistry::get()->processIsolation();
}

Expand Down
60 changes: 38 additions & 22 deletions src/Framework/TestRunner.php
Expand Up @@ -252,11 +252,13 @@ public function run(TestCase $test): void
* @throws ProcessIsolationException
* @throws StaticAnalysisCacheNotConfiguredException
*/
public function runInSeparateProcess(TestCase $test, bool $runEntireClass, bool $preserveGlobalState): void
public function runInSeparateProcess(TestCase|TestSuite $test, bool $preserveGlobalState): void
{
$class = new ReflectionClass($test);

if ($runEntireClass) {
$isTestSuite = $test instanceof TestSuite;

if ($isTestSuite) {
$template = new Template(
__DIR__ . '/../Util/PHP/Template/TestCaseClass.tpl'
);
Expand Down Expand Up @@ -297,45 +299,59 @@ public function runInSeparateProcess(TestCase $test, bool $runEntireClass, bool
$phar = '\'\'';
}

$data = var_export(serialize($test->providedData()), true);
$dataName = var_export($test->dataName(), true);
$dependencyInput = var_export(serialize($test->dependencyInput()), true);
$includePath = var_export(get_include_path(), true);
// must do these fixes because TestCaseMethod.tpl has unserialize('{data}') in it, and we can't break BC
// the lines above used to use addcslashes() rather than var_export(), which breaks null byte escape sequences
$data = "'." . $data . ".'";
$dataName = "'.(" . $dataName . ").'";
$dependencyInput = "'." . $dependencyInput . ".'";
$includePath = "'." . $includePath . ".'";
$tests = $isTestSuite ? $test->tests() : [$test];
$var = [];

$testData = [];
foreach ($tests as $t) {
assert($t instanceof TestCase);

$testCaseClass = new ReflectionClass($t);

$data = serialize($t->providedData());
$dataName = serialize($t->dataName());
$dependencyInput = serialize($t->dependencyInput());

$testData[] = [
'data' => $data,
'dataName' => $dataName,
'dependencyInput' => $dependencyInput,
'methodName' => $t->name(),
'className' => $testCaseClass->getName(),
];
}

if ($isTestSuite) {
foreach ($testData as $data) {
$var['testData'][] = $data;
}
$var['testData'] = serialize($var['testData']);
} else {
$var = $testData[0];
}

$includePath = serialize(get_include_path());

$offset = hrtime();

$serializedConfiguration = $this->saveConfigurationForChildProcess();

$var = [
$var = array_merge($var, [
'bootstrap' => $bootstrap,
'composerAutoload' => $composerAutoload,
'phar' => $phar,
'filename' => $class->getFileName(),
'className' => $class->getName(),
'collectCodeCoverageInformation' => $coverage,
'data' => $data,
'dataName' => $dataName,
'dependencyInput' => $dependencyInput,
'constants' => $constants,
'globals' => $globals,
'include_path' => $includePath,
'included_files' => $includedFiles,
'iniSettings' => $iniSettings,
'name' => $test->name(),
'offsetSeconds' => $offset[0],
'offsetNanoseconds' => $offset[1],
'serializedConfiguration' => $serializedConfiguration,
];

if (!$runEntireClass) {
$var['methodName'] = $test->name();
}
]);

$template->setVar($var);

Expand Down
60 changes: 55 additions & 5 deletions src/Framework/TestSuite.php
Expand Up @@ -30,22 +30,26 @@
use PHPUnit\Event\Code\TestDox;
use PHPUnit\Event\Code\TestMethod;
use PHPUnit\Event\NoPreviousThrowableException;
use PHPUnit\Event\TestData\MoreThanOneDataSetFromDataProviderException;
use PHPUnit\Metadata\Api\Dependencies;
use PHPUnit\Metadata\Api\Groups;
use PHPUnit\Metadata\Api\HookMethods;
use PHPUnit\Metadata\Api\Requirements;
use PHPUnit\Metadata\MetadataCollection;
use PHPUnit\Metadata\Parser\Registry as MetadataRegistry;
use PHPUnit\Runner\Exception as RunnerException;
use PHPUnit\Runner\Filter\Factory;
use PHPUnit\Runner\PhptTestCase;
use PHPUnit\Runner\TestSuiteLoader;
use PHPUnit\TestRunner\TestResult\Facade;
use PHPUnit\TextUI\Configuration\Registry;
use PHPUnit\Util\Exception as UtilException;
use PHPUnit\Util\Filter;
use PHPUnit\Util\Reflection;
use PHPUnit\Util\Test as TestUtil;
use ReflectionClass;
use ReflectionMethod;
use SebastianBergmann\CodeCoverage\StaticAnalysisCacheNotConfiguredException;
use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException;
use Throwable;

Expand Down Expand Up @@ -78,6 +82,8 @@ class TestSuite implements IteratorAggregate, Reorderable, SelfDescribing, Test
private readonly bool $stopOnSkipped;
private readonly bool $stopOnDefect;

private ?bool $runClassInSeparateProcess = null;

public static function empty(string $name = null): static
{
if ($name === null) {
Expand Down Expand Up @@ -141,6 +147,10 @@ public static function fromClassReflector(ReflectionClass $class): static
);
}

if ($testSuite->shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess($class->getName())) {
$testSuite->setRunClassInSeparateProcess(true);
}

return $testSuite;
}

Expand Down Expand Up @@ -328,6 +338,11 @@ public function getGroupDetails(): array
* @throws Exception
* @throws NoPreviousThrowableException
* @throws UnintentionallyCoveredCodeException
* @throws RunnerException
* @throws UtilException
* @throws MoreThanOneDataSetFromDataProviderException
* @throws ProcessIsolationException
* @throws StaticAnalysisCacheNotConfiguredException
*/
public function run(): void
{
Expand All @@ -344,12 +359,20 @@ public function run(): void
return;
}

foreach ($this as $test) {
if ($this->shouldStop()) {
break;
}
if ($this->shouldRunInSeparateProcess()) {
(new TestRunner)->runInSeparateProcess($this, false);
} else {
foreach ($this as $test) {
if ($this->shouldStop()) {
break;
}

$test->run();
if ($test instanceof self && $test->shouldRunInSeparateProcess()) {
(new TestRunner)->runInSeparateProcess($test, false);

Check warning on line 371 in src/Framework/TestSuite.php

View check run for this annotation

Codecov / codecov/patch

src/Framework/TestSuite.php#L371

Added line #L371 was not covered by tests
} else {
$test->run();
}
}
}

$this->invokeMethodsAfterLastTest($emitter);
Expand Down Expand Up @@ -377,6 +400,13 @@ public function setTests(array $tests): void
$this->tests = $tests;
}

private function setRunClassInSeparateProcess(bool $runClassInSeparateProcess): void
{
if ($this->runClassInSeparateProcess === null) {
$this->runClassInSeparateProcess = $runClassInSeparateProcess;
}
}

/**
* Mark the test suite as skipped.
*
Expand Down Expand Up @@ -722,4 +752,24 @@ private function invokeMethodsAfterLastTest(Event\Emitter $emitter): void
);
}
}

public function shouldRunInSeparateProcess(): bool
{
if ($this->runClassInSeparateProcess) {
return true;
}

// TODO: maybe a new option for "classIsolation" or "processIsolationClass"
// return Registry::get()->processIsolation();

return false;
}

/**
* @psalm-param class-string $className
*/
private function shouldAllTestMethodsOfTestClassBeRunInSingleSeparateProcess(string $className): bool
{
return MetadataRegistry::parser()->forClass($className)->isRunClassInSeparateProcess()->isNotEmpty();
}
}

0 comments on commit bdf2488

Please sign in to comment.