Skip to content

Commit

Permalink
Fix #5230
Browse files Browse the repository at this point in the history
  • Loading branch information
WalterWoshid committed Jan 16, 2024
1 parent a6319b7 commit 546f158
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 106 deletions.
19 changes: 2 additions & 17 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),
);
}
Expand All @@ -60,7 +59,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 @@ -72,7 +70,7 @@ public function build(ReflectionClass $theClass, string $methodName): Test
* @psalm-param non-empty-string $methodName
* @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 @@ -91,7 +89,6 @@ private function buildDataProviderTestSuite(string $methodName, string $classNam
$_test,
$runTestInSeparateProcess,
$preserveGlobalState,
$runClassInSeparateProcess,
$backupSettings,
);

Expand All @@ -104,16 +101,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 @@ -258,12 +251,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 @@ -126,7 +126,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 @@ -489,7 +488,6 @@ final public function run(): void
} else {
(new TestRunner)->runInSeparateProcess(
$this,
$this->runClassInSeparateProcess && !$this->runTestInSeparateProcess,
$this->preserveGlobalState,
);
}
Expand Down Expand Up @@ -885,14 +883,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 @@ -1942,10 +1932,6 @@ private function shouldRunInSeparateProcess(): bool
return true;
}

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

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

Expand Down
67 changes: 45 additions & 22 deletions src/Framework/TestRunner.php
Expand Up @@ -10,6 +10,7 @@
namespace PHPUnit\Framework;

use const PHP_EOL;
use function array_merge;
use function assert;
use function class_exists;
use function defined;
Expand Down Expand Up @@ -253,11 +254,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 @@ -300,47 +303,67 @@ 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 = $test instanceof TestSuite ? $test->tests() : [$test];

$var = [
'testData' => [],
];

$testData = [];

foreach ($tests as $t) {
assert($t instanceof TestCase);

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

$methodName = $t->name();
$className = $t::class;

$testData[] = [
'data' => $data,
'dataName' => $dataName,
'dependencyInput' => $dependencyInput,
'methodName' => $methodName,
'className' => $className,
];
}

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();
$processResultFile = tempnam(sys_get_temp_dir(), 'phpunit_');

$var = [
$var = array_merge($var, [
'bootstrap' => $bootstrap,
'composerAutoload' => $composerAutoload,
'phar' => $phar,
'filename' => $class->getFileName(),
'className' => $class->getName(),
'collectCodeCoverageInformation' => $coverage,
'linesToBeIgnored' => $linesToBeIgnored,
'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,
'processResultFile' => $processResultFile,
'exportObjects' => $exportObjects,
];

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

$template->setVar($var);

Expand Down
67 changes: 57 additions & 10 deletions src/Framework/TestSuite.php
Expand Up @@ -29,21 +29,25 @@
use PHPUnit\Event;
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 as TestResultFacade;
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 All @@ -68,9 +72,10 @@ class TestSuite implements IteratorAggregate, Reorderable, SelfDescribing, Test
/**
* @psalm-var list<Test>
*/
private array $tests = [];
private ?array $providedTests = null;
private ?Factory $iteratorFilter = null;
private array $tests = [];
private ?array $providedTests = null;
private ?Factory $iteratorFilter = null;
private ?bool $runClassInSeparateProcess = null;

/**
* @psalm-param non-empty-string $name
Expand Down Expand Up @@ -134,6 +139,10 @@ public static function fromClassReflector(ReflectionClass $class): static
);
}

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

return $testSuite;
}

Expand Down Expand Up @@ -309,11 +318,17 @@ public function groupDetails(): array

/**
* @throws \SebastianBergmann\CodeCoverage\InvalidArgumentException
* @throws \SebastianBergmann\Template\InvalidArgumentException
* @throws CodeCoverageException
* @throws Event\RuntimeException
* @throws Exception
* @throws MoreThanOneDataSetFromDataProviderException
* @throws NoPreviousThrowableException
* @throws ProcessIsolationException
* @throws RunnerException
* @throws StaticAnalysisCacheNotConfiguredException
* @throws UnintentionallyCoveredCodeException
* @throws UtilException
*/
public function run(): void
{
Expand All @@ -330,14 +345,22 @@ public function run(): void
return;
}

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

break;
}
break;
}

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

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

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

return false;
}

/**
* @throws \PHPUnit\Event\TestData\MoreThanOneDataSetFromDataProviderException
* @throws Exception
* @throws MoreThanOneDataSetFromDataProviderException
*/
protected function addTestMethod(ReflectionClass $class, ReflectionMethod $method): void
{
Expand Down Expand Up @@ -679,4 +711,19 @@ private function invokeMethodsAfterLastTest(Event\Emitter $emitter): void
);
}
}

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

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

0 comments on commit 546f158

Please sign in to comment.