composer update
This commit is contained in:
Vendored
+2
-23
@@ -4,14 +4,14 @@ ParaTest
|
||||
[](https://packagist.org/packages/brianium/paratest)
|
||||
[](https://packagist.org/packages/brianium/paratest)
|
||||
[](https://github.com/paratestphp/paratest/actions)
|
||||
[](https://dashboard.stryker-mutator.io/reports/github.com/paratestphp/paratest/7.x)
|
||||
[](https://dashboard.stryker-mutator.io/reports/github.com/paratestphp/paratest/7.x)
|
||||
|
||||
The objective of ParaTest is to support parallel testing in PHPUnit. Provided you have well-written PHPUnit tests, you can drop `paratest` in your project and
|
||||
start using it with no additional bootstrap or configurations!
|
||||
|
||||
Benefits:
|
||||
|
||||
* Zero configuration. After the installation, run with `vendor/bin/paratest`. That's it!
|
||||
* Zero configuration. After the installation, run with `vendor/bin/paratest` to parallelize by TestCase or `vendor/bin/paratest --functional` to parallelize by Test. That's it!
|
||||
* Code Coverage report combining. Run your tests in N parallel processes and all the code coverage output will be combined into one report.
|
||||
|
||||
# Installation
|
||||
@@ -72,14 +72,6 @@ If you have `xDebug` installed, activating it by the environment variable is eno
|
||||
XDEBUG_MODE=coverage vendor/bin/paratest
|
||||
```
|
||||
|
||||
### PHPDBG
|
||||
|
||||
`PHPDBG` is automatically detected and used in the subprocesses if it's the running binary of the main process:
|
||||
|
||||
```
|
||||
phpdbg vendor/bin/paratest
|
||||
```
|
||||
|
||||
## Initial setup for all tests
|
||||
|
||||
Because ParaTest runs multiple processes in parallel, each with their own instance of the PHP interpreter,
|
||||
@@ -136,19 +128,6 @@ When a sub-process fails, the originating command is given in the output and can
|
||||
to be run and debugged. All internal commands run with `--printer [...]\NullPhpunitPrinter` which silence the original
|
||||
PHPUnit output: during a debugging run remove that option to restore the output and see what PHPUnit is doing.
|
||||
|
||||
## Windows
|
||||
|
||||
Windows users be sure to use the appropriate batch files.
|
||||
|
||||
An example being:
|
||||
|
||||
`vendor\bin\paratest.bat ...`
|
||||
|
||||
ParaTest assumes [PSR-0](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md) for loading tests.
|
||||
|
||||
For convenience, ParaTest for Windows uses 79 columns mode to prevent blank lines in the standard
|
||||
80x25 windows console.
|
||||
|
||||
## Caveats
|
||||
|
||||
1. Constants, static methods, static variables and everything exposed by test classes consumed by other test classes
|
||||
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
@ECHO OFF
|
||||
SET BIN_TARGET=%~dp0\"../bin"\paratest
|
||||
php "%BIN_TARGET%" %*
|
||||
+8
-2
@@ -9,10 +9,12 @@ use ParaTest\WrapperRunner\WrapperWorker;
|
||||
$getopt = getopt('', [
|
||||
'status-file:',
|
||||
'progress-file:',
|
||||
'unexpected-output-file:',
|
||||
'testresult-file:',
|
||||
'teamcity-file:',
|
||||
'testdox-file:',
|
||||
'testdox-color',
|
||||
'testdox-columns:',
|
||||
'phpunit-argv:',
|
||||
]);
|
||||
|
||||
@@ -24,8 +26,8 @@ use ParaTest\WrapperRunner\WrapperWorker;
|
||||
|
||||
foreach ($composerAutoloadFiles as $file) {
|
||||
if (file_exists($file)) {
|
||||
require_once $file;
|
||||
define('PHPUNIT_COMPOSER_INSTALL', $file);
|
||||
require_once $file;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -36,9 +38,11 @@ use ParaTest\WrapperRunner\WrapperWorker;
|
||||
assert(is_resource($statusFile));
|
||||
|
||||
assert(isset($getopt['progress-file']) && is_string($getopt['progress-file']));
|
||||
assert(isset($getopt['unexpected-output-file']) && is_string($getopt['unexpected-output-file']));
|
||||
assert(isset($getopt['testresult-file']) && is_string($getopt['testresult-file']));
|
||||
assert(!isset($getopt['teamcity-file']) || is_string($getopt['teamcity-file']));
|
||||
assert(!isset($getopt['testdox-file']) || is_string($getopt['testdox-file']));
|
||||
assert(!isset($getopt['testdox-columns']) || $getopt['testdox-columns'] === (string) (int) $getopt['testdox-columns']);
|
||||
|
||||
assert(isset($getopt['phpunit-argv']) && is_string($getopt['phpunit-argv']));
|
||||
$phpunitArgv = unserialize($getopt['phpunit-argv'], ['allowed_classes' => false]);
|
||||
@@ -47,10 +51,12 @@ use ParaTest\WrapperRunner\WrapperWorker;
|
||||
$application = new ApplicationForWrapperWorker(
|
||||
$phpunitArgv,
|
||||
$getopt['progress-file'],
|
||||
$getopt['unexpected-output-file'],
|
||||
$getopt['testresult-file'],
|
||||
$getopt['teamcity-file'] ?? null,
|
||||
$getopt['testdox-file'] ?? null,
|
||||
isset($getopt['testdox-color']),
|
||||
isset($getopt['testdox-columns']) ? (int) $getopt['testdox-columns'] : null,
|
||||
);
|
||||
|
||||
while (true) {
|
||||
@@ -66,7 +72,7 @@ use ParaTest\WrapperRunner\WrapperWorker;
|
||||
}
|
||||
|
||||
// It must be a 1 byte string to ensure filesize() is equal to the number of tests executed
|
||||
$exitCode = $application->runTest(trim($testPath));
|
||||
$exitCode = $application->runTest(trim($testPath, "\n"));
|
||||
|
||||
fwrite($statusFile, (string) $exitCode);
|
||||
fflush($statusFile);
|
||||
|
||||
+17
-19
@@ -33,32 +33,31 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "~8.1.0 || ~8.2.0",
|
||||
"php": "~8.2.0 || ~8.3.0 || ~8.4.0",
|
||||
"ext-dom": "*",
|
||||
"ext-pcre": "*",
|
||||
"ext-reflection": "*",
|
||||
"ext-simplexml": "*",
|
||||
"fidry/cpu-core-counter": "^0.4.1 || ^0.5.1",
|
||||
"jean85/pretty-package-versions": "^2.0.5",
|
||||
"phpunit/php-code-coverage": "^10.0.2",
|
||||
"phpunit/php-file-iterator": "^4.0.1",
|
||||
"phpunit/php-timer": "^6.0",
|
||||
"phpunit/phpunit": "^10.0.16",
|
||||
"sebastian/environment": "^6.0",
|
||||
"symfony/console": "^6.2.7",
|
||||
"symfony/process": "^6.2.7"
|
||||
"fidry/cpu-core-counter": "^1.2.0",
|
||||
"jean85/pretty-package-versions": "^2.0.6",
|
||||
"phpunit/php-code-coverage": "^10.1.16",
|
||||
"phpunit/php-file-iterator": "^4.1.0",
|
||||
"phpunit/php-timer": "^6.0.0",
|
||||
"phpunit/phpunit": "^10.5.47",
|
||||
"sebastian/environment": "^6.1.0",
|
||||
"symfony/console": "^6.4.7 || ^7.1.5",
|
||||
"symfony/process": "^6.4.7 || ^7.1.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-pcov": "*",
|
||||
"ext-posix": "*",
|
||||
"doctrine/coding-standard": "^11.1.0",
|
||||
"infection/infection": "^0.26.19",
|
||||
"phpstan/phpstan": "^1.10.6",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.1.2",
|
||||
"phpstan/phpstan-phpunit": "^1.3.10",
|
||||
"phpstan/phpstan-strict-rules": "^1.5",
|
||||
"squizlabs/php_codesniffer": "^3.7.2",
|
||||
"symfony/filesystem": "^6.2.7"
|
||||
"doctrine/coding-standard": "^12.0.0",
|
||||
"phpstan/phpstan": "^1.12.6",
|
||||
"phpstan/phpstan-deprecation-rules": "^1.2.1",
|
||||
"phpstan/phpstan-phpunit": "^1.4.0",
|
||||
"phpstan/phpstan-strict-rules": "^1.6.1",
|
||||
"squizlabs/php_codesniffer": "^3.10.3",
|
||||
"symfony/filesystem": "^6.4.3 || ^7.1.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -74,7 +73,6 @@
|
||||
},
|
||||
"bin": [
|
||||
"bin/paratest",
|
||||
"bin/paratest.bat",
|
||||
"bin/paratest_for_phpstorm"
|
||||
],
|
||||
"config": {
|
||||
|
||||
+1
-3
@@ -18,7 +18,7 @@ use function ksort;
|
||||
final class LogMerger
|
||||
{
|
||||
/** @param list<SplFileInfo> $junitFiles */
|
||||
public function merge(array $junitFiles): TestSuite
|
||||
public function merge(array $junitFiles): ?TestSuite
|
||||
{
|
||||
$mainSuite = null;
|
||||
foreach ($junitFiles as $junitFile) {
|
||||
@@ -67,8 +67,6 @@ final class LogMerger
|
||||
$mainSuite = $this->mergeSuites($mainSuite, $otherSuite);
|
||||
}
|
||||
|
||||
assert($mainSuite !== null);
|
||||
|
||||
return $mainSuite;
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -23,6 +23,7 @@ use const ENT_XML1;
|
||||
/** @internal */
|
||||
final class Writer
|
||||
{
|
||||
private const TESTSUITES_NAME = 'PHPUnit tests';
|
||||
private readonly DOMDocument $document;
|
||||
|
||||
public function __construct()
|
||||
@@ -47,6 +48,7 @@ final class Writer
|
||||
{
|
||||
$xmlTestsuites = $this->document->createElement('testsuites');
|
||||
$xmlTestsuites->appendChild($this->createSuiteNode($testSuite));
|
||||
$xmlTestsuites->setAttribute('name', self::TESTSUITES_NAME);
|
||||
$this->document->appendChild($xmlTestsuites);
|
||||
|
||||
$xml = $this->document->saveXML();
|
||||
@@ -58,7 +60,7 @@ final class Writer
|
||||
private function createSuiteNode(TestSuite $parentSuite): DOMElement
|
||||
{
|
||||
$suiteNode = $this->document->createElement('testsuite');
|
||||
$suiteNode->setAttribute('name', $parentSuite->name);
|
||||
$suiteNode->setAttribute('name', $parentSuite->name !== self::TESTSUITES_NAME ? $parentSuite->name : '');
|
||||
if ($parentSuite->file !== '') {
|
||||
$suiteNode->setAttribute('file', $parentSuite->file);
|
||||
}
|
||||
|
||||
+12
@@ -99,6 +99,7 @@ final class Options
|
||||
public readonly string $runner,
|
||||
public readonly string $tmpDir,
|
||||
public readonly bool $verbose,
|
||||
public readonly bool $functional,
|
||||
) {
|
||||
$this->needsTeamcity = $configuration->outputIsTeamCity() || $configuration->hasLogfileTeamcity();
|
||||
}
|
||||
@@ -137,6 +138,10 @@ final class Options
|
||||
$verbose = $options['verbose'];
|
||||
unset($options['verbose']);
|
||||
|
||||
assert(is_bool($options['functional']));
|
||||
$functional = $options['functional'];
|
||||
unset($options['functional']);
|
||||
|
||||
assert(array_key_exists('colors', $options));
|
||||
if ($options['colors'] === Configuration::COLOR_DEFAULT) {
|
||||
unset($options['colors']);
|
||||
@@ -192,6 +197,7 @@ final class Options
|
||||
$runner,
|
||||
$tmpDir,
|
||||
$verbose,
|
||||
$functional,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -206,6 +212,12 @@ final class Options
|
||||
),
|
||||
|
||||
// ParaTest options
|
||||
new InputOption(
|
||||
'functional',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Whether to enable functional testing, for unit and dataset parallelization',
|
||||
),
|
||||
new InputOption(
|
||||
'max-batch-size',
|
||||
'm',
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ final class ParaTestCommand extends Command
|
||||
$input,
|
||||
$this->cwd,
|
||||
);
|
||||
if (! $options->configuration->hasConfigurationFile() && ! $options->configuration->hasCliArgument()) {
|
||||
if (! $options->configuration->hasConfigurationFile() && ! $options->configuration->hasCliArguments()) {
|
||||
return $this->displayHelp($output);
|
||||
}
|
||||
|
||||
|
||||
+80
-11
@@ -11,10 +11,12 @@ use PHPUnit\Framework\TestSuite;
|
||||
use PHPUnit\Logging\JUnit\JunitXmlLogger;
|
||||
use PHPUnit\Logging\TeamCity\TeamCityLogger;
|
||||
use PHPUnit\Logging\TestDox\TestResultCollector;
|
||||
use PHPUnit\Metadata\Api\CodeCoverage as CodeCoverageMetadataApi;
|
||||
use PHPUnit\Runner\CodeCoverage;
|
||||
use PHPUnit\Runner\Extension\ExtensionBootstrapper;
|
||||
use PHPUnit\Runner\Extension\Facade as ExtensionFacade;
|
||||
use PHPUnit\Runner\Extension\PharLoader;
|
||||
use PHPUnit\Runner\Filter\Factory;
|
||||
use PHPUnit\Runner\TestSuiteLoader;
|
||||
use PHPUnit\Runner\TestSuiteSorter;
|
||||
use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade;
|
||||
@@ -23,6 +25,7 @@ use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
|
||||
use PHPUnit\TextUI\Configuration\Configuration;
|
||||
use PHPUnit\TextUI\Configuration\PhpHandler;
|
||||
use PHPUnit\TextUI\Output\Default\ProgressPrinter\ProgressPrinter;
|
||||
use PHPUnit\TextUI\Output\Default\UnexpectedOutputPrinter;
|
||||
use PHPUnit\TextUI\Output\DefaultPrinter;
|
||||
use PHPUnit\TextUI\Output\NullPrinter;
|
||||
use PHPUnit\TextUI\Output\TestDox\ResultPrinter as TestDoxResultPrinter;
|
||||
@@ -31,8 +34,12 @@ use PHPUnit\Util\ExcludeList;
|
||||
|
||||
use function assert;
|
||||
use function file_put_contents;
|
||||
use function is_file;
|
||||
use function mt_srand;
|
||||
use function serialize;
|
||||
use function str_ends_with;
|
||||
use function strpos;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -49,29 +56,61 @@ final class ApplicationForWrapperWorker
|
||||
public function __construct(
|
||||
private readonly array $argv,
|
||||
private readonly string $progressFile,
|
||||
private readonly string $unexpectedOutputFile,
|
||||
private readonly string $testresultFile,
|
||||
private readonly ?string $teamcityFile,
|
||||
private readonly ?string $testdoxFile,
|
||||
private readonly bool $testdoxColor,
|
||||
private readonly ?int $testdoxColumns,
|
||||
) {
|
||||
}
|
||||
|
||||
public function runTest(string $testPath): int
|
||||
{
|
||||
$null = strpos($testPath, "\0");
|
||||
$filter = null;
|
||||
if ($null !== false) {
|
||||
$filter = new Factory();
|
||||
$name = substr($testPath, $null + 1);
|
||||
assert($name !== '');
|
||||
$filter->addNameFilter($name);
|
||||
|
||||
$testPath = substr($testPath, 0, $null);
|
||||
}
|
||||
|
||||
$this->bootstrap();
|
||||
|
||||
$testSuiteRefl = (new TestSuiteLoader())->load($testPath);
|
||||
$testSuite = TestSuite::fromClassReflector($testSuiteRefl);
|
||||
if (is_file($testPath) && str_ends_with($testPath, '.phpt')) {
|
||||
$testSuite = TestSuite::empty($testPath);
|
||||
$testSuite->addTestFile($testPath);
|
||||
} else {
|
||||
$testSuiteRefl = (new TestSuiteLoader())->load($testPath);
|
||||
$testSuite = TestSuite::fromClassReflector($testSuiteRefl);
|
||||
}
|
||||
|
||||
if (CodeCoverage::instance()->isActive()) {
|
||||
CodeCoverage::instance()->ignoreLines(
|
||||
(new CodeCoverageMetadataApi())->linesToBeIgnored($testSuite),
|
||||
);
|
||||
}
|
||||
|
||||
(new TestSuiteFilterProcessor())->process($this->configuration, $testSuite);
|
||||
|
||||
if ($filter !== null) {
|
||||
$testSuite->injectFilter($filter);
|
||||
|
||||
EventFacade::emitter()->testSuiteFiltered(
|
||||
TestSuiteBuilder::from($testSuite),
|
||||
);
|
||||
}
|
||||
|
||||
EventFacade::emitter()->testRunnerExecutionStarted(
|
||||
TestSuiteBuilder::from($testSuite),
|
||||
);
|
||||
|
||||
$testSuite->run();
|
||||
|
||||
return TestResultFacade::result()->wasSuccessfulIgnoringPhpunitWarnings()
|
||||
return TestResultFacade::result()->wasSuccessful()
|
||||
? RunnerInterface::SUCCESS_EXIT
|
||||
: RunnerInterface::FAILURE_EXIT;
|
||||
}
|
||||
@@ -95,6 +134,7 @@ final class ApplicationForWrapperWorker
|
||||
EventFacade::emitter()->testRunnerBootstrapFinished($bootstrapFilename);
|
||||
}
|
||||
|
||||
$extensionRequiresCodeCoverageCollection = false;
|
||||
if (! $this->configuration->noExtensions()) {
|
||||
if ($this->configuration->hasPharExtensionDirectory()) {
|
||||
(new PharLoader())->loadPharExtensionsInDirectory(
|
||||
@@ -102,9 +142,10 @@ final class ApplicationForWrapperWorker
|
||||
);
|
||||
}
|
||||
|
||||
$extensionFacade = new ExtensionFacade();
|
||||
$extensionBootstrapper = new ExtensionBootstrapper(
|
||||
$this->configuration,
|
||||
new ExtensionFacade(),
|
||||
$extensionFacade,
|
||||
);
|
||||
|
||||
foreach ($this->configuration->extensionBootstrappers() as $bootstrapper) {
|
||||
@@ -113,30 +154,53 @@ final class ApplicationForWrapperWorker
|
||||
$bootstrapper['parameters'],
|
||||
);
|
||||
}
|
||||
|
||||
$extensionRequiresCodeCoverageCollection = $extensionFacade->requiresCodeCoverageCollection();
|
||||
}
|
||||
|
||||
CodeCoverage::instance()->init($this->configuration, CodeCoverageFilterRegistry::instance());
|
||||
CodeCoverage::instance()->init(
|
||||
$this->configuration,
|
||||
CodeCoverageFilterRegistry::instance(),
|
||||
$extensionRequiresCodeCoverageCollection,
|
||||
);
|
||||
|
||||
if ($this->configuration->hasLogfileJunit()) {
|
||||
new JunitXmlLogger(DefaultPrinter::from($this->configuration->logfileJunit()));
|
||||
new JunitXmlLogger(
|
||||
DefaultPrinter::from($this->configuration->logfileJunit()),
|
||||
EventFacade::instance(),
|
||||
);
|
||||
}
|
||||
|
||||
new ProgressPrinter(
|
||||
$printer = new ProgressPrinterOutput(
|
||||
DefaultPrinter::from($this->progressFile),
|
||||
DefaultPrinter::from($this->unexpectedOutputFile),
|
||||
);
|
||||
|
||||
new UnexpectedOutputPrinter($printer, EventFacade::instance());
|
||||
new ProgressPrinter(
|
||||
$printer,
|
||||
EventFacade::instance(),
|
||||
false,
|
||||
120,
|
||||
99999,
|
||||
$this->configuration->source(),
|
||||
);
|
||||
|
||||
if (isset($this->teamcityFile)) {
|
||||
new TeamCityLogger(DefaultPrinter::from($this->teamcityFile));
|
||||
new TeamCityLogger(
|
||||
DefaultPrinter::from($this->teamcityFile),
|
||||
EventFacade::instance(),
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($this->testdoxFile)) {
|
||||
$this->testdoxResultCollector = new TestResultCollector();
|
||||
$this->testdoxResultCollector = new TestResultCollector(
|
||||
EventFacade::instance(),
|
||||
$this->configuration->source(),
|
||||
);
|
||||
}
|
||||
|
||||
TestResultFacade::init();
|
||||
EventFacade::seal();
|
||||
EventFacade::instance()->seal();
|
||||
EventFacade::emitter()->testRunnerStarted();
|
||||
|
||||
if ($this->configuration->executionOrder() === TestSuiteSorter::ORDER_RANDOMIZED) {
|
||||
@@ -148,6 +212,10 @@ final class ApplicationForWrapperWorker
|
||||
|
||||
public function end(): void
|
||||
{
|
||||
if (! $this->hasBeenBootstrapped) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventFacade::emitter()->testRunnerExecutionFinished();
|
||||
EventFacade::emitter()->testRunnerFinished();
|
||||
|
||||
@@ -156,6 +224,7 @@ final class ApplicationForWrapperWorker
|
||||
$result = TestResultFacade::result();
|
||||
if (isset($this->testdoxResultCollector)) {
|
||||
assert(isset($this->testdoxFile));
|
||||
assert(isset($this->testdoxColumns));
|
||||
|
||||
(new TestDoxResultPrinter(DefaultPrinter::from($this->testdoxFile), $this->testdoxColor))->print(
|
||||
$this->testdoxResultCollector->testMethodsGroupedByClass(),
|
||||
|
||||
+50
-30
@@ -27,7 +27,6 @@ use function fread;
|
||||
use function fseek;
|
||||
use function ftell;
|
||||
use function fwrite;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_repeat;
|
||||
use function strlen;
|
||||
@@ -41,12 +40,12 @@ final class ResultPrinter
|
||||
{
|
||||
public readonly Printer $printer;
|
||||
|
||||
private int $numTestsWidth = 0;
|
||||
private int $maxColumn = 0;
|
||||
private int $totalCases = 0;
|
||||
private int $column = 0;
|
||||
private int $casesProcessed = 0;
|
||||
private int $numberOfColumns = 80;
|
||||
private int $numTestsWidth = 0;
|
||||
private int $maxColumn = 0;
|
||||
private int $totalCases = 0;
|
||||
private int $column = 0;
|
||||
private int $casesProcessed = 0;
|
||||
private int $numberOfColumns;
|
||||
/** @var resource|null */
|
||||
private $teamcityLogFileHandle;
|
||||
/** @var array<non-empty-string, int> */
|
||||
@@ -72,6 +71,8 @@ final class ResultPrinter
|
||||
}
|
||||
};
|
||||
|
||||
$this->numberOfColumns = $this->options->configuration->columns();
|
||||
|
||||
if (! $this->options->configuration->hasLogfileTeamcity()) {
|
||||
return;
|
||||
}
|
||||
@@ -128,11 +129,13 @@ final class ResultPrinter
|
||||
$output->write("\n");
|
||||
}
|
||||
|
||||
/** @param list<SplFileInfo> $teamcityFiles */
|
||||
public function printFeedback(SplFileInfo $progressFile, array $teamcityFiles): void
|
||||
{
|
||||
if ($this->options->needsTeamcity) {
|
||||
$teamcityProgress = $this->tailMultiple($teamcityFiles);
|
||||
public function printFeedback(
|
||||
SplFileInfo $progressFile,
|
||||
SplFileInfo $outputFile,
|
||||
SplFileInfo|null $teamcityFile
|
||||
): void {
|
||||
if ($this->options->needsTeamcity && $teamcityFile !== null) {
|
||||
$teamcityProgress = $this->tailMultiple([$teamcityFile]);
|
||||
|
||||
if ($this->teamcityLogFileHandle !== null) {
|
||||
fwrite($this->teamcityLogFileHandle, $teamcityProgress);
|
||||
@@ -150,14 +153,16 @@ final class ResultPrinter
|
||||
return;
|
||||
}
|
||||
|
||||
$unexpectedOutput = $this->tail($outputFile);
|
||||
if ($unexpectedOutput !== '') {
|
||||
$this->output->write($unexpectedOutput);
|
||||
}
|
||||
|
||||
$feedbackItems = $this->tail($progressFile);
|
||||
if ($feedbackItems === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
$feedbackItems = preg_replace('/ +\\d+ \\/ \\d+ \\( *\\d+%\\)\\s*/', '', $feedbackItems);
|
||||
assert($feedbackItems !== null);
|
||||
|
||||
$actualTestCount = strlen($feedbackItems);
|
||||
for ($index = 0; $index < $actualTestCount; ++$index) {
|
||||
$this->printFeedbackItem($feedbackItems[$index]);
|
||||
@@ -188,17 +193,13 @@ final class ResultPrinter
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->options->configuration->outputIsTestDox()) {
|
||||
$this->output->write($this->tailMultiple($testdoxFiles));
|
||||
$this->printer->print(PHP_EOL . (new ResourceUsageFormatter())->resourceUsageSinceStartOfRequest() . PHP_EOL . PHP_EOL);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$resultPrinter = new DefaultResultPrinter(
|
||||
$defaultResultPrinter = new DefaultResultPrinter(
|
||||
$this->printer,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
$this->options->configuration->displayDetailsOnPhpunitDeprecations(),
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
@@ -210,15 +211,34 @@ final class ResultPrinter
|
||||
$this->options->configuration->displayDetailsOnTestsThatTriggerWarnings(),
|
||||
false,
|
||||
);
|
||||
$summaryPrinter = new SummaryPrinter(
|
||||
|
||||
if ($this->options->configuration->outputIsTestDox()) {
|
||||
$this->output->write($this->tailMultiple($testdoxFiles));
|
||||
|
||||
$defaultResultPrinter = new DefaultResultPrinter(
|
||||
$this->printer,
|
||||
true,
|
||||
true,
|
||||
$this->options->configuration->displayDetailsOnPhpunitDeprecations(),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
$defaultResultPrinter->print($testResult);
|
||||
|
||||
(new SummaryPrinter(
|
||||
$this->printer,
|
||||
$this->options->configuration->colors(),
|
||||
);
|
||||
|
||||
$this->printer->print(PHP_EOL . (new ResourceUsageFormatter())->resourceUsageSinceStartOfRequest() . PHP_EOL . PHP_EOL);
|
||||
|
||||
$resultPrinter->print($testResult);
|
||||
$summaryPrinter->print($testResult);
|
||||
))->print($testResult);
|
||||
}
|
||||
|
||||
private function printFeedbackItem(string $item): void
|
||||
@@ -249,7 +269,7 @@ final class ResultPrinter
|
||||
'F' => $this->colorizeTextBox('bg-red, fg-white', $item),
|
||||
'I', 'N', 'D', 'R', 'W' => $this->colorizeTextBox('fg-yellow, bold', $item),
|
||||
'S' => $this->colorizeTextBox('fg-cyan, bold', $item),
|
||||
default => $item,
|
||||
'.' => $item,
|
||||
};
|
||||
$this->output->write($buffer);
|
||||
}
|
||||
|
||||
+45
-11
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace ParaTest\WrapperRunner;
|
||||
|
||||
use Generator;
|
||||
use ParaTest\Options;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
@@ -23,10 +24,12 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
use function array_keys;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function is_int;
|
||||
use function is_string;
|
||||
use function mt_srand;
|
||||
use function ob_get_clean;
|
||||
use function ob_start;
|
||||
use function sprintf;
|
||||
use function str_starts_with;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
@@ -36,7 +39,7 @@ final class SuiteLoader
|
||||
{
|
||||
public readonly int $testCount;
|
||||
/** @var list<non-empty-string> */
|
||||
public readonly array $files;
|
||||
public readonly array $tests;
|
||||
|
||||
public function __construct(
|
||||
private readonly Options $options,
|
||||
@@ -73,8 +76,36 @@ final class SuiteLoader
|
||||
$this->testCount = count($testSuite);
|
||||
|
||||
$files = [];
|
||||
$this->loadFiles($testSuite, $files);
|
||||
$this->files = array_keys($files);
|
||||
$tests = [];
|
||||
foreach ($this->loadFiles($testSuite) as $file => $test) {
|
||||
$files[$file] = null;
|
||||
|
||||
if ($test instanceof PhptTestCase) {
|
||||
$tests[] = $file;
|
||||
} else {
|
||||
$name = $test->name();
|
||||
if ($test->providedData() !== []) {
|
||||
$dataName = $test->dataName();
|
||||
if ($this->options->functional) {
|
||||
$name = sprintf('/%s\s.*%s.*$/', $name, $dataName);
|
||||
} else {
|
||||
if (is_int($dataName)) {
|
||||
$name .= '#' . $dataName;
|
||||
} else {
|
||||
$name .= '@' . $dataName;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$name = sprintf('/%s$/', $name);
|
||||
}
|
||||
|
||||
$tests[] = "$file\0$name";
|
||||
}
|
||||
}
|
||||
|
||||
$this->tests = $this->options->functional
|
||||
? $tests
|
||||
: array_keys($files);
|
||||
|
||||
if (! $this->options->configuration->hasCoverageReport()) {
|
||||
return;
|
||||
@@ -94,12 +125,13 @@ final class SuiteLoader
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<non-empty-string, bool> $files */
|
||||
private function loadFiles(TestSuite $testSuite, array &$files): void
|
||||
/** @return Generator<non-empty-string, (PhptTestCase|TestCase)> */
|
||||
private function loadFiles(TestSuite $testSuite): Generator
|
||||
{
|
||||
foreach ($testSuite as $test) {
|
||||
if ($test instanceof TestSuite) {
|
||||
$this->loadFiles($test, $files);
|
||||
yield from $this->loadFiles($test);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -107,8 +139,9 @@ final class SuiteLoader
|
||||
$refProperty = new ReflectionProperty(PhptTestCase::class, 'filename');
|
||||
$filename = $refProperty->getValue($test);
|
||||
assert(is_string($filename) && $filename !== '');
|
||||
$filename = $this->stripCwd($filename);
|
||||
$files[$filename] = true;
|
||||
$filename = $this->stripCwd($filename);
|
||||
|
||||
yield $filename => $test;
|
||||
|
||||
continue;
|
||||
}
|
||||
@@ -116,9 +149,10 @@ final class SuiteLoader
|
||||
if ($test instanceof TestCase) {
|
||||
$refClass = new ReflectionClass($test);
|
||||
$filename = $refClass->getFileName();
|
||||
assert(is_string($filename) && $filename !== '');
|
||||
$filename = $this->stripCwd($filename);
|
||||
$files[$filename] = true;
|
||||
assert(is_string($filename));
|
||||
$filename = $this->stripCwd($filename);
|
||||
|
||||
yield $filename => $test;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
+38
-25
@@ -54,6 +54,8 @@ final class WrapperRunner implements RunnerInterface
|
||||
/** @var list<SplFileInfo> */
|
||||
private array $progressFiles = [];
|
||||
/** @var list<SplFileInfo> */
|
||||
private array $unexpectedOutputFiles = [];
|
||||
/** @var list<SplFileInfo> */
|
||||
private array $testresultFiles = [];
|
||||
/** @var list<SplFileInfo> */
|
||||
private array $coverageFiles = [];
|
||||
@@ -80,8 +82,11 @@ final class WrapperRunner implements RunnerInterface
|
||||
$phpFinder = new PhpExecutableFinder();
|
||||
$phpBin = $phpFinder->find(false);
|
||||
assert($phpBin !== false);
|
||||
assert($phpBin !== '');
|
||||
$parameters = [$phpBin];
|
||||
$parameters = array_merge($parameters, $phpFinder->findArguments());
|
||||
/** @var array<non-empty-string> $arguments */
|
||||
$arguments = $phpFinder->findArguments();
|
||||
$parameters = array_merge($parameters, $arguments);
|
||||
|
||||
if ($options->passthruPhp !== null) {
|
||||
$parameters = array_merge($parameters, $options->passthruPhp);
|
||||
@@ -99,7 +104,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
assert($directory !== '');
|
||||
ExcludeList::addDirectory($directory);
|
||||
TestResultFacade::init();
|
||||
EventFacade::seal();
|
||||
EventFacade::instance()->seal();
|
||||
$suiteLoader = new SuiteLoader(
|
||||
$this->options,
|
||||
$this->output,
|
||||
@@ -107,7 +112,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
);
|
||||
$result = TestResultFacade::result();
|
||||
|
||||
$this->pending = $suiteLoader->files;
|
||||
$this->pending = $suiteLoader->tests;
|
||||
$this->printer->setTestCount($suiteLoader->testCount);
|
||||
$this->printer->start();
|
||||
$this->startWorkers();
|
||||
@@ -165,7 +170,8 @@ final class WrapperRunner implements RunnerInterface
|
||||
$this->exitcode = max($this->exitcode, $worker->getExitCode());
|
||||
$this->printer->printFeedback(
|
||||
$worker->progressFile,
|
||||
$this->teamcityFiles,
|
||||
$worker->unexpectedOutputFile,
|
||||
$worker->teamcityFile ?? null,
|
||||
);
|
||||
$worker->reset();
|
||||
}
|
||||
@@ -208,9 +214,10 @@ final class WrapperRunner implements RunnerInterface
|
||||
$worker->start();
|
||||
$this->batches[$token] = 0;
|
||||
|
||||
$this->statusFiles[] = $worker->statusFile;
|
||||
$this->progressFiles[] = $worker->progressFile;
|
||||
$this->testresultFiles[] = $worker->testresultFile;
|
||||
$this->statusFiles[] = $worker->statusFile;
|
||||
$this->progressFiles[] = $worker->progressFile;
|
||||
$this->unexpectedOutputFiles[] = $worker->unexpectedOutputFile;
|
||||
$this->testresultFiles[] = $worker->testresultFile;
|
||||
|
||||
if (isset($worker->junitFile)) {
|
||||
$this->junitFiles[] = $worker->junitFile;
|
||||
@@ -233,11 +240,11 @@ final class WrapperRunner implements RunnerInterface
|
||||
|
||||
private function destroyWorker(int $token): void
|
||||
{
|
||||
// Mutation Testing tells us that the following `unset()` already destroys
|
||||
// the `WrapperWorker`, which destroys the Symfony's `Process`, which
|
||||
// automatically calls `Process::stop` within `Process::__destruct()`.
|
||||
// But we prefer to have an explicit stops.
|
||||
$this->workers[$token]->stop();
|
||||
// We need to wait for ApplicationForWrapperWorker::end to end
|
||||
while ($this->workers[$token]->isRunning()) {
|
||||
usleep(self::CYCLE_SLEEP);
|
||||
}
|
||||
|
||||
unset($this->workers[$token]);
|
||||
}
|
||||
@@ -255,7 +262,7 @@ final class WrapperRunner implements RunnerInterface
|
||||
assert($testResult instanceof TestResult);
|
||||
|
||||
$testResultSum = new TestResult(
|
||||
$testResultSum->numberOfTests() + $testResult->numberOfTests(),
|
||||
(int) $testResultSum->hasTests() + (int) $testResult->hasTests(),
|
||||
$testResultSum->numberOfTestsRun() + $testResult->numberOfTestsRun(),
|
||||
$testResultSum->numberOfAssertions() + $testResult->numberOfAssertions(),
|
||||
array_merge_recursive($testResultSum->testErroredEvents(), $testResult->testErroredEvents()),
|
||||
@@ -264,18 +271,19 @@ final class WrapperRunner implements RunnerInterface
|
||||
array_merge_recursive($testResultSum->testSuiteSkippedEvents(), $testResult->testSuiteSkippedEvents()),
|
||||
array_merge_recursive($testResultSum->testSkippedEvents(), $testResult->testSkippedEvents()),
|
||||
array_merge_recursive($testResultSum->testMarkedIncompleteEvents(), $testResult->testMarkedIncompleteEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredDeprecationEvents(), $testResult->testTriggeredDeprecationEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredPhpDeprecationEvents(), $testResult->testTriggeredPhpDeprecationEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredPhpunitDeprecationEvents(), $testResult->testTriggeredPhpunitDeprecationEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredErrorEvents(), $testResult->testTriggeredErrorEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredNoticeEvents(), $testResult->testTriggeredNoticeEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredPhpNoticeEvents(), $testResult->testTriggeredPhpNoticeEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredWarningEvents(), $testResult->testTriggeredWarningEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredPhpWarningEvents(), $testResult->testTriggeredPhpWarningEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredPhpunitErrorEvents(), $testResult->testTriggeredPhpunitErrorEvents()),
|
||||
array_merge_recursive($testResultSum->testTriggeredPhpunitWarningEvents(), $testResult->testTriggeredPhpunitWarningEvents()),
|
||||
array_merge_recursive($testResultSum->testRunnerTriggeredDeprecationEvents(), $testResult->testRunnerTriggeredDeprecationEvents()),
|
||||
array_merge_recursive($testResultSum->testRunnerTriggeredWarningEvents(), $testResult->testRunnerTriggeredWarningEvents()),
|
||||
array_merge_recursive($testResultSum->errors(), $testResult->errors()),
|
||||
array_merge_recursive($testResultSum->deprecations(), $testResult->deprecations()),
|
||||
array_merge_recursive($testResultSum->notices(), $testResult->notices()),
|
||||
array_merge_recursive($testResultSum->warnings(), $testResult->warnings()),
|
||||
array_merge_recursive($testResultSum->phpDeprecations(), $testResult->phpDeprecations()),
|
||||
array_merge_recursive($testResultSum->phpNotices(), $testResult->phpNotices()),
|
||||
array_merge_recursive($testResultSum->phpWarnings(), $testResult->phpWarnings()),
|
||||
$testResultSum->numberOfIssuesIgnoredByBaseline() + $testResult->numberOfIssuesIgnoredByBaseline(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -288,16 +296,13 @@ final class WrapperRunner implements RunnerInterface
|
||||
$this->generateLogs();
|
||||
|
||||
$exitcode = (new ShellExitCodeCalculator())->calculate(
|
||||
$this->options->configuration->failOnEmptyTestSuite(),
|
||||
$this->options->configuration->failOnRisky(),
|
||||
$this->options->configuration->failOnWarning(),
|
||||
$this->options->configuration->failOnIncomplete(),
|
||||
$this->options->configuration->failOnSkipped(),
|
||||
$this->options->configuration,
|
||||
$testResultSum,
|
||||
);
|
||||
|
||||
$this->clearFiles($this->statusFiles);
|
||||
$this->clearFiles($this->progressFiles);
|
||||
$this->clearFiles($this->unexpectedOutputFiles);
|
||||
$this->clearFiles($this->testresultFiles);
|
||||
$this->clearFiles($this->coverageFiles);
|
||||
$this->clearFiles($this->junitFiles);
|
||||
@@ -314,7 +319,11 @@ final class WrapperRunner implements RunnerInterface
|
||||
}
|
||||
|
||||
$coverageManager = new CodeCoverage();
|
||||
$coverageManager->init($this->options->configuration, $this->codeCoverageFilterRegistry);
|
||||
$coverageManager->init(
|
||||
$this->options->configuration,
|
||||
$this->codeCoverageFilterRegistry,
|
||||
false,
|
||||
);
|
||||
$coverageMerger = new CoverageMerger($coverageManager->codeCoverage());
|
||||
foreach ($this->coverageFiles as $coverageFile) {
|
||||
$coverageMerger->addCoverageFromFile($coverageFile);
|
||||
@@ -333,6 +342,10 @@ final class WrapperRunner implements RunnerInterface
|
||||
}
|
||||
|
||||
$testSuite = (new LogMerger())->merge($this->junitFiles);
|
||||
if ($testSuite === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
(new Writer())->write(
|
||||
$testSuite,
|
||||
$this->options->configuration->logfileJunit(),
|
||||
|
||||
@@ -32,6 +32,7 @@ final class WrapperWorker
|
||||
|
||||
public readonly SplFileInfo $statusFile;
|
||||
public readonly SplFileInfo $progressFile;
|
||||
public readonly SplFileInfo $unexpectedOutputFile;
|
||||
public readonly SplFileInfo $testresultFile;
|
||||
public readonly SplFileInfo $junitFile;
|
||||
public readonly SplFileInfo $coverageFile;
|
||||
@@ -62,6 +63,8 @@ final class WrapperWorker
|
||||
touch($this->statusFile->getPathname());
|
||||
$this->progressFile = new SplFileInfo($commonTmpFilePath . 'progress');
|
||||
touch($this->progressFile->getPathname());
|
||||
$this->unexpectedOutputFile = new SplFileInfo($commonTmpFilePath . 'unexpected_output');
|
||||
touch($this->unexpectedOutputFile->getPathname());
|
||||
$this->testresultFile = new SplFileInfo($commonTmpFilePath . 'testresult');
|
||||
if ($options->configuration->hasLogfileJunit()) {
|
||||
$this->junitFile = new SplFileInfo($commonTmpFilePath . 'junit');
|
||||
@@ -83,6 +86,8 @@ final class WrapperWorker
|
||||
$parameters[] = $this->statusFile->getPathname();
|
||||
$parameters[] = '--progress-file';
|
||||
$parameters[] = $this->progressFile->getPathname();
|
||||
$parameters[] = '--unexpected-output-file';
|
||||
$parameters[] = $this->unexpectedOutputFile->getPathname();
|
||||
$parameters[] = '--testresult-file';
|
||||
$parameters[] = $this->testresultFile->getPathname();
|
||||
if (isset($this->teamcityFile)) {
|
||||
@@ -96,10 +101,17 @@ final class WrapperWorker
|
||||
if ($options->configuration->colors()) {
|
||||
$parameters[] = '--testdox-color';
|
||||
}
|
||||
|
||||
$parameters[] = '--testdox-columns';
|
||||
$parameters[] = (string) $options->configuration->columns();
|
||||
}
|
||||
|
||||
$phpunitArguments = [$options->phpunit];
|
||||
foreach ($options->phpunitOptions as $key => $value) {
|
||||
if ($options->functional && $key === 'filter') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$phpunitArguments[] = "--{$key}";
|
||||
if ($value === true) {
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user