Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #5230: @runClassInSeparateProcess has the same effect as @runTestsInSeparateProcesses #5233

Open
wants to merge 1 commit into
base: 10.5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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();
}
}