mirror of
https://github.com/pestphp/pest.git
synced 2026-09-05 14:23:34 +02:00
chore: upgrades to phpunit:13.3
This commit is contained in:
+5
-5
@@ -18,19 +18,19 @@
|
|||||||
],
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.4",
|
"php": "^8.4",
|
||||||
"brianium/paratest": "^7.23.1",
|
"brianium/paratest": "^7.24.0",
|
||||||
"nunomaduro/collision": "^8.9.5",
|
"nunomaduro/collision": "^8.9.5",
|
||||||
"nunomaduro/termwind": "^2.4.0",
|
"nunomaduro/termwind": "^2.4.0",
|
||||||
"pestphp/pest-plugin": "^5.0.0",
|
"pestphp/pest-plugin": "^5.0.0",
|
||||||
"pestphp/pest-plugin-arch": "^5.0.0",
|
"pestphp/pest-plugin-arch": "^5.0.0",
|
||||||
"pestphp/pest-plugin-mutate": "^5.0.1",
|
"pestphp/pest-plugin-mutate": "^5.0.1",
|
||||||
"pestphp/pest-plugin-profanity": "^5.0.0",
|
"pestphp/pest-plugin-profanity": "^5.0.0",
|
||||||
"phpunit/phpunit": "^13.2.6",
|
"phpunit/phpunit": "^13.3.0",
|
||||||
"symfony/process": "^8.1.0"
|
"symfony/process": "^8.1.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"filp/whoops": "<2.18.3",
|
"filp/whoops": "<2.18.3",
|
||||||
"phpunit/phpunit": ">13.2.6",
|
"phpunit/phpunit": ">13.3.0",
|
||||||
"sebastian/exporter": "<7.0.0",
|
"sebastian/exporter": "<7.0.0",
|
||||||
"webmozart/assert": "<1.11.0"
|
"webmozart/assert": "<1.11.0"
|
||||||
},
|
},
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"pestphp/pest-dev-tools": "^5.0.0",
|
"pestphp/pest-dev-tools": "^5.0.0",
|
||||||
"pestphp/pest-plugin-browser": "^5.0.0",
|
"pestphp/pest-plugin-browser": "^5.0.0",
|
||||||
"pestphp/pest-plugin-phpstan": "^5.0.0",
|
"pestphp/pest-plugin-phpstan": "^5.0.2",
|
||||||
"pestphp/pest-plugin-rector": "^5.0.3",
|
"pestphp/pest-plugin-rector": "^5.0.3",
|
||||||
"pestphp/pest-plugin-type-coverage": "^5.0.2",
|
"pestphp/pest-plugin-type-coverage": "^5.0.2",
|
||||||
"psy/psysh": "^0.12.24"
|
"psy/psysh": "^0.12.24"
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
"test:parallel": "php bin/pest --exclude-group=integration --parallel --processes=3",
|
"test:parallel": "php bin/pest --exclude-group=integration --parallel --processes=3",
|
||||||
"test:integration": "php bin/pest --group=integration -v",
|
"test:integration": "php bin/pest --group=integration -v",
|
||||||
"test:tia": "php bin/pest --group=tia -v",
|
"test:tia": "php bin/pest --group=tia -v",
|
||||||
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --update-snapshots",
|
"update:snapshots": "REBUILD_SNAPSHOTS=true php bin/pest --update-snapshots --exclude-group=tia",
|
||||||
"test": [
|
"test": [
|
||||||
"@test:lint",
|
"@test:lint",
|
||||||
"@test:type:check",
|
"@test:type:check",
|
||||||
|
|||||||
@@ -16,42 +16,32 @@ namespace PHPUnit\Runner\Filter;
|
|||||||
use Pest\Contracts\HasPrintableTestCaseName;
|
use Pest\Contracts\HasPrintableTestCaseName;
|
||||||
use PHPUnit\Framework\Test;
|
use PHPUnit\Framework\Test;
|
||||||
use PHPUnit\Framework\TestSuite;
|
use PHPUnit\Framework\TestSuite;
|
||||||
use PHPUnit\Runner\PhptTestCase;
|
use PHPUnit\Runner\Phpt\TestCase as PhptTestCase;
|
||||||
use RecursiveFilterIterator;
|
use RecursiveFilterIterator;
|
||||||
use RecursiveIterator;
|
use RecursiveIterator;
|
||||||
|
|
||||||
use function end;
|
use function end;
|
||||||
use function preg_match;
|
use function preg_match;
|
||||||
use function sprintf;
|
use function trim;
|
||||||
use function str_replace;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @extends RecursiveFilterIterator<int, Test, RecursiveIterator<int, Test>>
|
||||||
|
*
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
*/
|
*/
|
||||||
abstract class NameFilterIterator extends RecursiveFilterIterator
|
abstract class NameFilterIterator extends RecursiveFilterIterator
|
||||||
{
|
{
|
||||||
/**
|
private readonly CompiledNameFilter $filter;
|
||||||
* @psalm-var non-empty-string
|
|
||||||
*/
|
|
||||||
private readonly string $regularExpression;
|
|
||||||
|
|
||||||
private readonly ?int $dataSetMinimum;
|
|
||||||
|
|
||||||
private readonly ?int $dataSetMaximum;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @psalm-param RecursiveIterator<int, Test> $iterator
|
* @param RecursiveIterator<int, Test> $iterator
|
||||||
* @psalm-param non-empty-string $filter
|
* @param non-empty-string $filter
|
||||||
*/
|
*/
|
||||||
public function __construct(RecursiveIterator $iterator, string $filter)
|
public function __construct(RecursiveIterator $iterator, string $filter)
|
||||||
{
|
{
|
||||||
parent::__construct($iterator);
|
parent::__construct($iterator);
|
||||||
|
|
||||||
$preparedFilter = $this->prepareFilter($filter);
|
$this->filter = CompiledNameFilter::from($filter);
|
||||||
|
|
||||||
$this->regularExpression = $preparedFilter['regularExpression'];
|
|
||||||
$this->dataSetMinimum = $preparedFilter['dataSetMinimum'];
|
|
||||||
$this->dataSetMaximum = $preparedFilter['dataSetMaximum'];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function accept(): bool
|
public function accept(): bool
|
||||||
@@ -74,70 +64,15 @@ abstract class NameFilterIterator extends RecursiveFilterIterator
|
|||||||
$name = $test::class.'::'.$test->nameWithDataSet();
|
$name = $test::class.'::'.$test->nameWithDataSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
$accepted = @preg_match($this->regularExpression, $name, $matches) === 1;
|
$accepted = @preg_match($this->filter->regularExpression(), $name, $matches) === 1;
|
||||||
|
|
||||||
if ($accepted && isset($this->dataSetMaximum)) {
|
if ($accepted && $this->filter->hasDataSetRange()) {
|
||||||
$set = end($matches);
|
$set = end($matches);
|
||||||
$accepted = $set >= $this->dataSetMinimum && $set <= $this->dataSetMaximum;
|
$accepted = $set >= $this->filter->dataSetMinimum() && $set <= $this->filter->dataSetMaximum();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->doAccept($accepted);
|
return $this->doAccept($accepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract protected function doAccept(bool $result): bool;
|
abstract protected function doAccept(bool $result): bool;
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param non-empty-string $filter
|
|
||||||
*
|
|
||||||
* @psalm-return array{regularExpression: non-empty-string, dataSetMinimum: ?int, dataSetMaximum: ?int}
|
|
||||||
*/
|
|
||||||
private function prepareFilter(string $filter): array
|
|
||||||
{
|
|
||||||
$dataSetMinimum = null;
|
|
||||||
$dataSetMaximum = null;
|
|
||||||
|
|
||||||
if (@preg_match($filter, '') === false) {
|
|
||||||
if (preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) {
|
|
||||||
if (isset($matches[3]) && $matches[2] < $matches[3]) {
|
|
||||||
$filter = sprintf(
|
|
||||||
'%s.*with data set #(\d+)$',
|
|
||||||
$matches[1],
|
|
||||||
);
|
|
||||||
|
|
||||||
$dataSetMinimum = (int) $matches[2];
|
|
||||||
$dataSetMaximum = (int) $matches[3];
|
|
||||||
} else {
|
|
||||||
$filter = sprintf(
|
|
||||||
'%s.*with data set #%s$',
|
|
||||||
$matches[1],
|
|
||||||
$matches[2],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// * testDetermineJsonError@JSON_ERROR_NONE
|
|
||||||
// * testDetermineJsonError@JSON.*
|
|
||||||
elseif (preg_match('/^(.*?)@(.+)$/', $filter, $matches)) {
|
|
||||||
$filter = sprintf(
|
|
||||||
'%s.*with data set "%s"$',
|
|
||||||
$matches[1],
|
|
||||||
$matches[2],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$filter = sprintf(
|
|
||||||
'/%s/i',
|
|
||||||
str_replace(
|
|
||||||
'/',
|
|
||||||
'\\/',
|
|
||||||
$filter,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'regularExpression' => $filter,
|
|
||||||
'dataSetMinimum' => $dataSetMinimum,
|
|
||||||
'dataSetMaximum' => $dataSetMaximum,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,171 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This file is part of PHPUnit.
|
|
||||||
*
|
|
||||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
|
||||||
*
|
|
||||||
* For the full copyright and license information, please view the LICENSE
|
|
||||||
* file that was distributed with this source code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace PHPUnit\Runner\ResultCache;
|
|
||||||
|
|
||||||
use const DIRECTORY_SEPARATOR;
|
|
||||||
use const LOCK_EX;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestStatus\TestStatus;
|
|
||||||
use PHPUnit\Runner\DirectoryDoesNotExistException;
|
|
||||||
use PHPUnit\Runner\Exception;
|
|
||||||
use PHPUnit\Util\Filesystem;
|
|
||||||
|
|
||||||
use function array_keys;
|
|
||||||
use function assert;
|
|
||||||
use function dirname;
|
|
||||||
use function file_get_contents;
|
|
||||||
use function file_put_contents;
|
|
||||||
use function is_array;
|
|
||||||
use function is_dir;
|
|
||||||
use function is_file;
|
|
||||||
use function json_decode;
|
|
||||||
use function json_encode;
|
|
||||||
use function Pest\version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
|
|
||||||
*
|
|
||||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
|
||||||
*/
|
|
||||||
final class DefaultResultCache implements ResultCache
|
|
||||||
{
|
|
||||||
private const string DEFAULT_RESULT_CACHE_FILENAME = '.phpunit.result.cache';
|
|
||||||
|
|
||||||
private readonly string $cacheFilename;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<string, TestStatus>
|
|
||||||
*/
|
|
||||||
private array $defects = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array<string, float>
|
|
||||||
*/
|
|
||||||
private array $times = [];
|
|
||||||
|
|
||||||
public function __construct(?string $filepath = null)
|
|
||||||
{
|
|
||||||
if ($filepath !== null && is_dir($filepath)) {
|
|
||||||
$filepath .= DIRECTORY_SEPARATOR.self::DEFAULT_RESULT_CACHE_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->cacheFilename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_RESULT_CACHE_FILENAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setStatus(ResultCacheId $id, TestStatus $status): void
|
|
||||||
{
|
|
||||||
if ($status->isSuccess()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->defects[$id->asString()] = $status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function status(ResultCacheId $id): TestStatus
|
|
||||||
{
|
|
||||||
return $this->defects[$id->asString()] ?? TestStatus::unknown();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setTime(ResultCacheId $id, float $time): void
|
|
||||||
{
|
|
||||||
$this->times[$id->asString()] = $time;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function time(ResultCacheId $id): float
|
|
||||||
{
|
|
||||||
return $this->times[$id->asString()] ?? 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function mergeWith(self $other): void
|
|
||||||
{
|
|
||||||
foreach ($other->defects as $id => $defect) {
|
|
||||||
$this->defects[$id] = $defect;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($other->times as $id => $time) {
|
|
||||||
$this->times[$id] = $time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function load(): void
|
|
||||||
{
|
|
||||||
if (! is_file($this->cacheFilename)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$contents = file_get_contents($this->cacheFilename);
|
|
||||||
|
|
||||||
if ($contents === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = json_decode(
|
|
||||||
$contents,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
if ($data === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! isset($data['version'])) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($data['version'] !== $this->cacheVersion()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(isset($data['defects']) && is_array($data['defects']));
|
|
||||||
assert(isset($data['times']) && is_array($data['times']));
|
|
||||||
|
|
||||||
foreach (array_keys($data['defects']) as $test) {
|
|
||||||
$data['defects'][$test] = TestStatus::from($data['defects'][$test]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->defects = $data['defects'];
|
|
||||||
$this->times = $data['times'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public function persist(): void
|
|
||||||
{
|
|
||||||
if (! Filesystem::createDirectory(dirname($this->cacheFilename))) {
|
|
||||||
throw new DirectoryDoesNotExistException(dirname($this->cacheFilename));
|
|
||||||
}
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
'version' => $this->cacheVersion(),
|
|
||||||
'defects' => [],
|
|
||||||
'times' => $this->times,
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($this->defects as $test => $status) {
|
|
||||||
$data['defects'][$test] = $status->asInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents(
|
|
||||||
$this->cacheFilename,
|
|
||||||
json_encode($data),
|
|
||||||
LOCK_EX,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function cacheVersion(): string
|
|
||||||
{
|
|
||||||
return 'pest_'.version();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of PHPUnit.
|
||||||
|
*
|
||||||
|
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace PHPUnit\Runner\TestRunHistory;
|
||||||
|
|
||||||
|
use const DIRECTORY_SEPARATOR;
|
||||||
|
use const LOCK_EX;
|
||||||
|
use const LOCK_UN;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestStatus\TestStatus;
|
||||||
|
use PHPUnit\Runner\DirectoryDoesNotExistException;
|
||||||
|
use PHPUnit\Runner\Exception;
|
||||||
|
use PHPUnit\Util\Filesystem;
|
||||||
|
|
||||||
|
use function array_keys;
|
||||||
|
use function dirname;
|
||||||
|
use function fclose;
|
||||||
|
use function file_get_contents;
|
||||||
|
use function flock;
|
||||||
|
use function fopen;
|
||||||
|
use function ftruncate;
|
||||||
|
use function fwrite;
|
||||||
|
use function is_array;
|
||||||
|
use function is_dir;
|
||||||
|
use function is_file;
|
||||||
|
use function is_float;
|
||||||
|
use function is_int;
|
||||||
|
use function is_string;
|
||||||
|
use function json_decode;
|
||||||
|
use function json_encode;
|
||||||
|
use function Pest\version;
|
||||||
|
use function rewind;
|
||||||
|
use function stream_get_contents;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||||
|
*/
|
||||||
|
final class DefaultTestRunHistory implements TestRunHistory
|
||||||
|
{
|
||||||
|
private const string DEFAULT_FILENAME = '.phpunit.result.cache';
|
||||||
|
|
||||||
|
private readonly string $filename;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, TestStatus>
|
||||||
|
*/
|
||||||
|
private array $defects = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, float>
|
||||||
|
*/
|
||||||
|
private array $times = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, true>
|
||||||
|
*/
|
||||||
|
private array $changedDefects = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array<string, true>
|
||||||
|
*/
|
||||||
|
private array $changedTimes = [];
|
||||||
|
|
||||||
|
public function __construct(?string $filepath = null)
|
||||||
|
{
|
||||||
|
if ($filepath !== null && is_dir($filepath)) {
|
||||||
|
$filepath .= DIRECTORY_SEPARATOR.self::DEFAULT_FILENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->filename = $filepath ?? $_ENV['PHPUNIT_RESULT_CACHE'] ?? self::DEFAULT_FILENAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStatus(TestRunHistoryId $id, TestStatus $status): void
|
||||||
|
{
|
||||||
|
if ($status->isSuccess()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->defects[$id->asString()] = $status;
|
||||||
|
$this->changedDefects[$id->asString()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(TestRunHistoryId $id): void
|
||||||
|
{
|
||||||
|
unset($this->defects[$id->asString()]);
|
||||||
|
|
||||||
|
$this->changedDefects[$id->asString()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function status(TestRunHistoryId $id): TestStatus
|
||||||
|
{
|
||||||
|
return $this->defects[$id->asString()] ?? TestStatus::unknown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setTime(TestRunHistoryId $id, float $time): void
|
||||||
|
{
|
||||||
|
$this->times[$id->asString()] = $time;
|
||||||
|
$this->changedTimes[$id->asString()] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function time(TestRunHistoryId $id): float
|
||||||
|
{
|
||||||
|
return $this->times[$id->asString()] ?? 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mergeWith(self $other): void
|
||||||
|
{
|
||||||
|
foreach ($other->defects as $id => $defect) {
|
||||||
|
$this->defects[$id] = $defect;
|
||||||
|
$this->changedDefects[$id] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($other->times as $id => $time) {
|
||||||
|
$this->times[$id] = $time;
|
||||||
|
$this->changedTimes[$id] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load(): void
|
||||||
|
{
|
||||||
|
if (! is_file($this->filename)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$contents = file_get_contents($this->filename);
|
||||||
|
|
||||||
|
if ($contents === false) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
return;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
$parsed = $this->parse($contents);
|
||||||
|
|
||||||
|
if ($parsed === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$this->defects, $this->times] = $parsed;
|
||||||
|
|
||||||
|
$this->changedDefects = [];
|
||||||
|
$this->changedTimes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function persist(): void
|
||||||
|
{
|
||||||
|
$this->writeToFile(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function persistAndPrune(): void
|
||||||
|
{
|
||||||
|
$this->writeToFile(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
private function writeToFile(bool $prune): void
|
||||||
|
{
|
||||||
|
if (! Filesystem::createDirectory(dirname($this->filename))) {
|
||||||
|
throw new DirectoryDoesNotExistException(dirname($this->filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
$handle = fopen($this->filename, 'c+');
|
||||||
|
|
||||||
|
if ($handle === false) {
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
|
return;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
|
}
|
||||||
|
|
||||||
|
flock($handle, LOCK_EX);
|
||||||
|
|
||||||
|
if ($prune) {
|
||||||
|
$defects = [];
|
||||||
|
|
||||||
|
foreach ($this->defects as $id => $status) {
|
||||||
|
if (isset($this->changedDefects[$id])) {
|
||||||
|
$defects[$id] = $status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$times = [];
|
||||||
|
|
||||||
|
foreach ($this->times as $id => $time) {
|
||||||
|
if (isset($this->changedTimes[$id]) || isset($this->changedDefects[$id])) {
|
||||||
|
$times[$id] = $time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$parsed = $this->parse((string) stream_get_contents($handle));
|
||||||
|
|
||||||
|
if ($parsed !== null) {
|
||||||
|
[$defects, $times] = $parsed;
|
||||||
|
|
||||||
|
foreach (array_keys($this->changedDefects) as $id) {
|
||||||
|
if (isset($this->defects[$id])) {
|
||||||
|
$defects[$id] = $this->defects[$id];
|
||||||
|
} else {
|
||||||
|
unset($defects[$id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->times as $id => $time) {
|
||||||
|
if (isset($this->changedTimes[$id])) {
|
||||||
|
$times[$id] = $time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$defects = $this->defects;
|
||||||
|
$times = $this->times;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
'version' => $this->version(),
|
||||||
|
'defects' => [],
|
||||||
|
'times' => $times,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($defects as $test => $status) {
|
||||||
|
$data['defects'][$test] = $status->asInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
$json = json_encode($data);
|
||||||
|
|
||||||
|
if ($json !== false) {
|
||||||
|
ftruncate($handle, 0);
|
||||||
|
rewind($handle);
|
||||||
|
fwrite($handle, $json);
|
||||||
|
}
|
||||||
|
|
||||||
|
flock($handle, LOCK_UN);
|
||||||
|
fclose($handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return ?array{0: array<string, TestStatus>, 1: array<string, float>}
|
||||||
|
*/
|
||||||
|
private function parse(string $contents): ?array
|
||||||
|
{
|
||||||
|
$data = json_decode(
|
||||||
|
$contents,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (! is_array($data)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($data['version']) || $data['version'] !== $this->version()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($data['defects'], $data['times']) || ! is_array($data['defects']) || ! is_array($data['times'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defects = [];
|
||||||
|
|
||||||
|
foreach ($data['defects'] as $test => $status) {
|
||||||
|
if (! is_string($test) || ! is_int($status)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$defects[$test] = TestStatus::from($status);
|
||||||
|
}
|
||||||
|
|
||||||
|
$times = [];
|
||||||
|
|
||||||
|
foreach ($data['times'] as $test => $time) {
|
||||||
|
if (! is_string($test) || (! is_float($time) && ! is_int($time))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$times[$test] = (float) $time;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$defects, $times];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function version(): string
|
||||||
|
{
|
||||||
|
return 'pest_'.version();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,13 +14,14 @@ declare(strict_types=1);
|
|||||||
namespace PHPUnit\Runner;
|
namespace PHPUnit\Runner;
|
||||||
|
|
||||||
use PHPUnit\Framework\DataProviderTestSuite;
|
use PHPUnit\Framework\DataProviderTestSuite;
|
||||||
|
use PHPUnit\Framework\IterativeTestSuite;
|
||||||
use PHPUnit\Framework\Reorderable;
|
use PHPUnit\Framework\Reorderable;
|
||||||
use PHPUnit\Framework\Test;
|
use PHPUnit\Framework\Test;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use PHPUnit\Framework\TestSuite;
|
use PHPUnit\Framework\TestSuite;
|
||||||
use PHPUnit\Runner\ResultCache\NullResultCache;
|
use PHPUnit\Runner\TestRunHistory\NullTestRunHistory;
|
||||||
use PHPUnit\Runner\ResultCache\ResultCache;
|
use PHPUnit\Runner\TestRunHistory\TestRunHistory;
|
||||||
use PHPUnit\Runner\ResultCache\ResultCacheId;
|
use PHPUnit\Runner\TestRunHistory\TestRunHistoryId;
|
||||||
|
|
||||||
use function array_diff;
|
use function array_diff;
|
||||||
use function array_merge;
|
use function array_merge;
|
||||||
@@ -46,9 +47,13 @@ final class TestSuiteSorter
|
|||||||
|
|
||||||
public const int ORDER_DEFECTS_FIRST = 3;
|
public const int ORDER_DEFECTS_FIRST = 3;
|
||||||
|
|
||||||
public const int ORDER_DURATION = 4;
|
public const int ORDER_DURATION_ASCENDING = 4;
|
||||||
|
|
||||||
public const int ORDER_SIZE = 5;
|
public const int ORDER_SIZE_ASCENDING = 5;
|
||||||
|
|
||||||
|
public const int ORDER_DURATION_DESCENDING = 6;
|
||||||
|
|
||||||
|
public const int ORDER_SIZE_DESCENDING = 7;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var non-empty-array<non-empty-string, positive-int>
|
* @var non-empty-array<non-empty-string, positive-int>
|
||||||
@@ -65,11 +70,11 @@ final class TestSuiteSorter
|
|||||||
*/
|
*/
|
||||||
private array $defectSortOrder = [];
|
private array $defectSortOrder = [];
|
||||||
|
|
||||||
private readonly ResultCache $cache;
|
private readonly TestRunHistory $testRunHistory;
|
||||||
|
|
||||||
public function __construct(?ResultCache $cache = null)
|
public function __construct(?TestRunHistory $testRunHistory = null)
|
||||||
{
|
{
|
||||||
$this->cache = $cache ?? new NullResultCache;
|
$this->testRunHistory = $testRunHistory ?? new NullTestRunHistory;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,8 +86,10 @@ final class TestSuiteSorter
|
|||||||
self::ORDER_DEFAULT,
|
self::ORDER_DEFAULT,
|
||||||
self::ORDER_REVERSED,
|
self::ORDER_REVERSED,
|
||||||
self::ORDER_RANDOMIZED,
|
self::ORDER_RANDOMIZED,
|
||||||
self::ORDER_DURATION,
|
self::ORDER_DURATION_ASCENDING,
|
||||||
self::ORDER_SIZE,
|
self::ORDER_SIZE_ASCENDING,
|
||||||
|
self::ORDER_DURATION_DESCENDING,
|
||||||
|
self::ORDER_SIZE_DESCENDING,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (! in_array($order, $allowedOrders, true)) {
|
if (! in_array($order, $allowedOrders, true)) {
|
||||||
@@ -102,6 +109,10 @@ final class TestSuiteSorter
|
|||||||
// @codeCoverageIgnoreEnd
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($suite instanceof IterativeTestSuite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($suite instanceof TestSuite) {
|
if ($suite instanceof TestSuite) {
|
||||||
foreach ($suite as $_suite) {
|
foreach ($suite as $_suite) {
|
||||||
$this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects);
|
$this->reorderTestsInSuite($_suite, $order, $resolveDependencies, $orderDefects);
|
||||||
@@ -117,31 +128,37 @@ final class TestSuiteSorter
|
|||||||
|
|
||||||
private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void
|
private function sort(TestSuite $suite, int $order, bool $resolveDependencies, int $orderDefects): void
|
||||||
{
|
{
|
||||||
if ($suite->tests() === []) {
|
$tests = $suite->tests();
|
||||||
|
|
||||||
|
if ($tests === []) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($order === self::ORDER_REVERSED) {
|
if ($order === self::ORDER_REVERSED) {
|
||||||
$suite->setTests($this->reverse($suite->tests()));
|
$tests = $this->reverse($tests);
|
||||||
} elseif ($order === self::ORDER_RANDOMIZED) {
|
} elseif ($order === self::ORDER_RANDOMIZED) {
|
||||||
$suite->setTests($this->randomize($suite->tests()));
|
$tests = $this->randomize($tests);
|
||||||
} elseif ($order === self::ORDER_DURATION) {
|
} elseif ($order === self::ORDER_DURATION_ASCENDING) {
|
||||||
$suite->setTests($this->sortByDuration($suite->tests()));
|
$tests = $this->sortByDuration($tests);
|
||||||
} elseif ($order === self::ORDER_SIZE) {
|
} elseif ($order === self::ORDER_DURATION_DESCENDING) {
|
||||||
$suite->setTests($this->sortBySize($suite->tests()));
|
$tests = $this->sortByDurationDescending($tests);
|
||||||
|
} elseif ($order === self::ORDER_SIZE_ASCENDING) {
|
||||||
|
$tests = $this->sortBySize($tests);
|
||||||
|
} elseif ($order === self::ORDER_SIZE_DESCENDING) {
|
||||||
|
$tests = $this->sortBySizeDescending($tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($orderDefects === self::ORDER_DEFECTS_FIRST) {
|
if ($orderDefects === self::ORDER_DEFECTS_FIRST) {
|
||||||
$suite->setTests($this->sortDefectsFirst($suite->tests()));
|
$tests = $this->sortDefectsFirst($tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($resolveDependencies && ! ($suite instanceof DataProviderTestSuite)) {
|
if ($resolveDependencies && ! ($suite instanceof DataProviderTestSuite)) {
|
||||||
$tests = $suite->tests();
|
|
||||||
|
|
||||||
/** @noinspection PhpParamsInspection */
|
/** @noinspection PhpParamsInspection */
|
||||||
/** @phpstan-ignore argument.type */
|
/** @phpstan-ignore argument.type */
|
||||||
$suite->setTests($this->resolveDependencies($tests));
|
$tests = $this->resolveDependencies($tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$suite->setTests($tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function addSuiteToDefectSortOrder(TestSuite $suite): void
|
private function addSuiteToDefectSortOrder(TestSuite $suite): void
|
||||||
@@ -154,9 +171,10 @@ final class TestSuiteSorter
|
|||||||
$sortId = $test->sortId();
|
$sortId = $test->sortId();
|
||||||
|
|
||||||
if (! isset($this->defectSortOrder[$sortId])) {
|
if (! isset($this->defectSortOrder[$sortId])) {
|
||||||
$this->defectSortOrder[$sortId] = $this->cache->status(ResultCacheId::fromReorderable($test))->asInt();
|
$this->defectSortOrder[$sortId] = $this->testRunHistory->status(TestRunHistoryId::fromReorderable($test))->sortWeight();
|
||||||
$max = max($max, $this->defectSortOrder[$sortId]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$max = max($max, $this->defectSortOrder[$sortId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->defectSortOrder[$suite->sortId()] = $max;
|
$this->defectSortOrder[$suite->sortId()] = $max;
|
||||||
@@ -210,6 +228,20 @@ final class TestSuiteSorter
|
|||||||
return $tests;
|
return $tests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function sortByDurationDescending(array $tests): array
|
||||||
|
{
|
||||||
|
usort(
|
||||||
|
$tests,
|
||||||
|
fn (Test $left, Test $right) => $this->cmpDuration($right, $left),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<Test> $tests
|
* @param list<Test> $tests
|
||||||
* @return list<Test>
|
* @return list<Test>
|
||||||
@@ -224,6 +256,20 @@ final class TestSuiteSorter
|
|||||||
return $tests;
|
return $tests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<Test> $tests
|
||||||
|
* @return list<Test>
|
||||||
|
*/
|
||||||
|
private function sortBySizeDescending(array $tests): array
|
||||||
|
{
|
||||||
|
usort(
|
||||||
|
$tests,
|
||||||
|
fn (Test $left, Test $right) => $this->cmpSize($right, $left),
|
||||||
|
);
|
||||||
|
|
||||||
|
return $tests;
|
||||||
|
}
|
||||||
|
|
||||||
private function cmpDefectPriorityAndTime(Test $a, Test $b): int
|
private function cmpDefectPriorityAndTime(Test $a, Test $b): int
|
||||||
{
|
{
|
||||||
assert($a instanceof Reorderable);
|
assert($a instanceof Reorderable);
|
||||||
@@ -232,45 +278,72 @@ final class TestSuiteSorter
|
|||||||
$priorityA = $this->defectSortOrder[$a->sortId()] ?? 0;
|
$priorityA = $this->defectSortOrder[$a->sortId()] ?? 0;
|
||||||
$priorityB = $this->defectSortOrder[$b->sortId()] ?? 0;
|
$priorityB = $this->defectSortOrder[$b->sortId()] ?? 0;
|
||||||
|
|
||||||
if ($priorityA !== $priorityB) {
|
|
||||||
return $priorityB <=> $priorityA;
|
return $priorityB <=> $priorityA;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($priorityA > 0 || $priorityB > 0) {
|
|
||||||
return $this->cmpDuration($a, $b);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function cmpDuration(Test $a, Test $b): int
|
private function cmpDuration(Test $a, Test $b): int
|
||||||
{
|
{
|
||||||
if (! ($a instanceof Reorderable && $b instanceof Reorderable)) {
|
return $this->durationWeight($a) <=> $this->durationWeight($b);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->cache->time(ResultCacheId::fromReorderable($a)) <=> $this->cache->time(ResultCacheId::fromReorderable($b));
|
private function durationWeight(Test $test): float
|
||||||
|
{
|
||||||
|
if ($test instanceof TestSuite) {
|
||||||
|
$sum = 0.0;
|
||||||
|
|
||||||
|
foreach ($test->tests() as $inner) {
|
||||||
|
$sum += $this->durationWeight($inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($test instanceof Reorderable) {
|
||||||
|
return $this->testRunHistory->time(TestRunHistoryId::fromReorderable($test));
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function cmpSize(Test $a, Test $b): int
|
private function cmpSize(Test $a, Test $b): int
|
||||||
{
|
{
|
||||||
$sizeA = ($a instanceof TestCase || $a instanceof DataProviderTestSuite)
|
return $this->sizeWeight($a) <=> $this->sizeWeight($b);
|
||||||
? $a->size()->asString()
|
|
||||||
: 'unknown';
|
|
||||||
$sizeB = ($b instanceof TestCase || $b instanceof DataProviderTestSuite)
|
|
||||||
? $b->size()->asString()
|
|
||||||
: 'unknown';
|
|
||||||
|
|
||||||
return self::SIZE_SORT_WEIGHT[$sizeA] <=> self::SIZE_SORT_WEIGHT[$sizeB];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<TestCase> $tests
|
* @return positive-int
|
||||||
* @return array<TestCase>
|
*/
|
||||||
|
private function sizeWeight(Test $test): int
|
||||||
|
{
|
||||||
|
if ($test instanceof TestCase || $test instanceof DataProviderTestSuite) {
|
||||||
|
return self::SIZE_SORT_WEIGHT[$test->size()->asString()];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($test instanceof TestSuite) {
|
||||||
|
$max = 0;
|
||||||
|
|
||||||
|
foreach ($test->tests() as $inner) {
|
||||||
|
$weight = $this->sizeWeight($inner);
|
||||||
|
|
||||||
|
if ($weight > $max) {
|
||||||
|
$max = $weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($max > 0) {
|
||||||
|
return $max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::SIZE_SORT_WEIGHT['unknown'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<TestCase> $tests
|
||||||
|
* @return list<TestCase>
|
||||||
*/
|
*/
|
||||||
private function resolveDependencies(array $tests): array
|
private function resolveDependencies(array $tests): array
|
||||||
{
|
{
|
||||||
// when no test uses `->depends()` / PHPUnit `@depends`.
|
|
||||||
if (! $this->anyTestHasDependencies($tests)) {
|
if (! $this->anyTestHasDependencies($tests)) {
|
||||||
return $tests;
|
return $tests;
|
||||||
}
|
}
|
||||||
@@ -279,7 +352,7 @@ final class TestSuiteSorter
|
|||||||
$i = 0;
|
$i = 0;
|
||||||
$provided = [];
|
$provided = [];
|
||||||
|
|
||||||
do {
|
while ($tests !== [] && $i < count($tests)) {
|
||||||
if (array_diff($tests[$i]->requires(), $provided) === []) {
|
if (array_diff($tests[$i]->requires(), $provided) === []) {
|
||||||
$provided = array_merge($provided, $tests[$i]->provides());
|
$provided = array_merge($provided, $tests[$i]->provides());
|
||||||
$newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1));
|
$newTestOrder = array_merge($newTestOrder, array_splice($tests, $i, 1));
|
||||||
@@ -287,14 +360,12 @@ final class TestSuiteSorter
|
|||||||
} else {
|
} else {
|
||||||
$i++;
|
$i++;
|
||||||
}
|
}
|
||||||
} while ($tests !== [] && ($i < count($tests)));
|
}
|
||||||
|
|
||||||
return array_merge($newTestOrder, $tests);
|
return array_merge($newTestOrder, $tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cheaply determines whether any test in the tree declares @depends.
|
|
||||||
*
|
|
||||||
* @param iterable<Test> $tests
|
* @param iterable<Test> $tests
|
||||||
*/
|
*/
|
||||||
private function anyTestHasDependencies(iterable $tests): bool
|
private function anyTestHasDependencies(iterable $tests): bool
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ final class BootOverrides implements Bootstrapper
|
|||||||
public const array FILES = [
|
public const array FILES = [
|
||||||
'ParaTest/WrapperRunner/ProgressPrinterOutput.php',
|
'ParaTest/WrapperRunner/ProgressPrinterOutput.php',
|
||||||
'Runner/Filter/NameFilterIterator.php',
|
'Runner/Filter/NameFilterIterator.php',
|
||||||
'Runner/ResultCache/DefaultResultCache.php',
|
'Runner/TestRunHistory/DefaultTestRunHistory.php',
|
||||||
'Runner/TestSuiteLoader.php',
|
'Runner/TestSuiteLoader.php',
|
||||||
'Runner/TestSuiteSorter.php',
|
'Runner/TestSuiteSorter.php',
|
||||||
'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
|
'TextUI/Command/Commands/WarmCodeCoverageCacheCommand.php',
|
||||||
|
|||||||
+10
-2
@@ -44,10 +44,18 @@ final class Cache implements HandlesArguments
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->hasArgument('--parallel', $arguments) && ! $this->hasArgument('--do-not-cache-result', $arguments) && ! $this->hasArgument('--cache-result', $arguments)) {
|
if (! $this->hasArgument('--parallel', $arguments) && ! $this->hasTestRunHistoryArgument($arguments)) {
|
||||||
return $this->pushArgument('--cache-result', $arguments);
|
return $this->pushArgument('--record-test-run-history', $arguments);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $arguments;
|
return $arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<int, string> $arguments
|
||||||
|
*/
|
||||||
|
private function hasTestRunHistoryArgument(array $arguments): bool
|
||||||
|
{
|
||||||
|
return array_any(['--record-test-run-history', '--do-not-record-test-run-history', '--cache-result', '--do-not-cache-result'], fn (string $argument): bool => $this->hasArgument($argument, $arguments));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ final class Parallel implements HandlesArguments
|
|||||||
'-p',
|
'-p',
|
||||||
'--no-output',
|
'--no-output',
|
||||||
'--cache-result',
|
'--cache-result',
|
||||||
|
'--record-test-run-history',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function handleArguments(array $arguments): array
|
public function handleArguments(array $arguments): array
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use PHPUnit\Event\Facade as EventFacade;
|
|||||||
use PHPUnit\Event\Test\AfterLastTestMethodFailed;
|
use PHPUnit\Event\Test\AfterLastTestMethodFailed;
|
||||||
use PHPUnit\Event\TestRunner\WarningTriggered;
|
use PHPUnit\Event\TestRunner\WarningTriggered;
|
||||||
use PHPUnit\Runner\CodeCoverage;
|
use PHPUnit\Runner\CodeCoverage;
|
||||||
use PHPUnit\Runner\ResultCache\DefaultResultCache;
|
use PHPUnit\Runner\TestRunHistory\DefaultTestRunHistory;
|
||||||
use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade;
|
use PHPUnit\TestRunner\TestResult\Facade as TestResultFacade;
|
||||||
use PHPUnit\TestRunner\TestResult\TestResult;
|
use PHPUnit\TestRunner\TestResult\TestResult;
|
||||||
use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
|
use PHPUnit\TextUI\Configuration\CodeCoverageFilterRegistry;
|
||||||
@@ -417,6 +417,9 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
// @phpstan-ignore-next-line
|
// @phpstan-ignore-next-line
|
||||||
array_merge_recursive($testResultSum->phpWarnings(), $testResult->phpWarnings()),
|
array_merge_recursive($testResultSum->phpWarnings(), $testResult->phpWarnings()),
|
||||||
$testResultSum->numberOfIssuesIgnoredByBaseline() + $testResult->numberOfIssuesIgnoredByBaseline(),
|
$testResultSum->numberOfIssuesIgnoredByBaseline() + $testResult->numberOfIssuesIgnoredByBaseline(),
|
||||||
|
self::numberOfDeprecationsByTrigger($testResultSum, $testResult),
|
||||||
|
// @phpstan-ignore-next-line
|
||||||
|
array_merge_recursive($testResultSum->retriedTests(), $testResult->retriedTests()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -455,14 +458,16 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
$testResultSum->phpNotices(),
|
$testResultSum->phpNotices(),
|
||||||
$testResultSum->phpWarnings(),
|
$testResultSum->phpWarnings(),
|
||||||
$testResultSum->numberOfIssuesIgnoredByBaseline(),
|
$testResultSum->numberOfIssuesIgnoredByBaseline(),
|
||||||
|
self::numberOfDeprecationsByTrigger($testResultSum),
|
||||||
|
$testResultSum->retriedTests(),
|
||||||
);
|
);
|
||||||
|
|
||||||
self::$result = $testResultSum;
|
self::$result = $testResultSum;
|
||||||
|
|
||||||
if ($this->options->configuration->cacheResult()) {
|
if ($this->options->configuration->recordTestRunHistory()) {
|
||||||
$resultCacheSum = new DefaultResultCache($this->options->configuration->testResultCacheFile());
|
$resultCacheSum = new DefaultTestRunHistory($this->options->configuration->testRunHistoryFile());
|
||||||
foreach ($this->resultCacheFiles as $resultCacheFile) {
|
foreach ($this->resultCacheFiles as $resultCacheFile) {
|
||||||
$resultCache = new DefaultResultCache($resultCacheFile->getPathname());
|
$resultCache = new DefaultTestRunHistory($resultCacheFile->getPathname());
|
||||||
$resultCache->load();
|
$resultCache->load();
|
||||||
|
|
||||||
$resultCacheSum->mergeWith($resultCache);
|
$resultCacheSum->mergeWith($resultCache);
|
||||||
@@ -495,6 +500,28 @@ final class WrapperRunner implements RunnerInterface
|
|||||||
return $exitcode;
|
return $exitcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array{self: non-negative-int, direct: non-negative-int, indirect: non-negative-int, unknown: non-negative-int}
|
||||||
|
*/
|
||||||
|
private static function numberOfDeprecationsByTrigger(TestResult ...$results): array
|
||||||
|
{
|
||||||
|
$self = $direct = $indirect = $unknown = 0;
|
||||||
|
|
||||||
|
foreach ($results as $result) {
|
||||||
|
$self += max(0, $result->numberOfSelfDeprecations());
|
||||||
|
$direct += max(0, $result->numberOfDirectDeprecations());
|
||||||
|
$indirect += max(0, $result->numberOfIndirectDeprecations());
|
||||||
|
$unknown += max(0, $result->numberOfDeprecationsWithUnknownTrigger());
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'self' => $self,
|
||||||
|
'direct' => $direct,
|
||||||
|
'indirect' => $indirect,
|
||||||
|
'unknown' => $unknown,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
private function generateCodeCoverageReports(): void
|
private function generateCodeCoverageReports(): void
|
||||||
{
|
{
|
||||||
if ($this->coverageFiles === []) {
|
if ($this->coverageFiles === []) {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ final class CoverageMerger
|
|||||||
}
|
}
|
||||||
|
|
||||||
self::primeUncoveredFiles($cached);
|
self::primeUncoveredFiles($cached);
|
||||||
self::primeUncoveredFiles($current);
|
self::discardUnexercisedFiles($current);
|
||||||
|
|
||||||
self::stripCurrentTestsFromCached($cached, $current);
|
self::stripCurrentTestsFromCached($cached, $current);
|
||||||
|
|
||||||
@@ -77,6 +77,28 @@ final class CoverageMerger
|
|||||||
$coverage->getData(false);
|
$coverage->getData(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function discardUnexercisedFiles(CodeCoverage $coverage): void
|
||||||
|
{
|
||||||
|
$data = $coverage->getData(true);
|
||||||
|
$lineCoverage = $data->lineCoverage();
|
||||||
|
$discarded = false;
|
||||||
|
|
||||||
|
foreach ($lineCoverage as $file => $lines) {
|
||||||
|
foreach ($lines as $hits) {
|
||||||
|
if (is_array($hits) && $hits !== []) {
|
||||||
|
continue 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unset($lineCoverage[$file]);
|
||||||
|
$discarded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($discarded) {
|
||||||
|
$data->setLineCoverage($lineCoverage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static function compress(string $bytes): string
|
private static function compress(string $bytes): string
|
||||||
{
|
{
|
||||||
$compressed = @gzencode($bytes);
|
$compressed = @gzencode($bytes);
|
||||||
@@ -139,7 +161,7 @@ final class CoverageMerger
|
|||||||
*/
|
*/
|
||||||
private static function collectTestIds(CodeCoverage $coverage): array
|
private static function collectTestIds(CodeCoverage $coverage): array
|
||||||
{
|
{
|
||||||
$data = $coverage->getData();
|
$data = $coverage->getData(true);
|
||||||
$idByIndex = $data->testIds();
|
$idByIndex = $data->testIds();
|
||||||
|
|
||||||
$ids = [];
|
$ids = [];
|
||||||
|
|||||||
@@ -77,6 +77,9 @@
|
|||||||
--fail-on-warning Signal failure using shell exit code when a warning was triggered
|
--fail-on-warning Signal failure using shell exit code when a warning was triggered
|
||||||
--fail-on-risky Signal failure using shell exit code when a test was considered risky
|
--fail-on-risky Signal failure using shell exit code when a test was considered risky
|
||||||
--fail-on-deprecation Signal failure using shell exit code when a deprecation was triggered
|
--fail-on-deprecation Signal failure using shell exit code when a deprecation was triggered
|
||||||
|
--fail-on-self-deprecation Signal failure using shell exit code when a deprecation was triggered in first-party code
|
||||||
|
--fail-on-direct-deprecation Signal failure using shell exit code when a deprecation was triggered in third-party code called from first-party code
|
||||||
|
--fail-on-indirect-deprecation Signal failure using shell exit code when a deprecation was triggered in third-party code called from third-party code
|
||||||
--fail-on-phpunit-deprecation Signal failure using shell exit code when a PHPUnit deprecation was triggered
|
--fail-on-phpunit-deprecation Signal failure using shell exit code when a PHPUnit deprecation was triggered
|
||||||
--fail-on-phpunit-notice Signal failure using shell exit code when a PHPUnit notice was triggered
|
--fail-on-phpunit-notice Signal failure using shell exit code when a PHPUnit notice was triggered
|
||||||
--fail-on-phpunit-warning Signal failure using shell exit code when a PHPUnit warning was triggered
|
--fail-on-phpunit-warning Signal failure using shell exit code when a PHPUnit warning was triggered
|
||||||
@@ -88,19 +91,26 @@
|
|||||||
--do-not-fail-on-warning Do not signal failure using shell exit code when a warning was triggered
|
--do-not-fail-on-warning Do not signal failure using shell exit code when a warning was triggered
|
||||||
--do-not-fail-on-risky Do not signal failure using shell exit code when a test was considered risky
|
--do-not-fail-on-risky Do not signal failure using shell exit code when a test was considered risky
|
||||||
--do-not-fail-on-deprecation Do not signal failure using shell exit code when a deprecation was triggered
|
--do-not-fail-on-deprecation Do not signal failure using shell exit code when a deprecation was triggered
|
||||||
|
--do-not-fail-on-self-deprecation Do not signal failure using shell exit code when a deprecation was triggered in first-party code
|
||||||
|
--do-not-fail-on-direct-deprecation Do not signal failure using shell exit code when a deprecation was triggered in third-party code called from first-party code
|
||||||
|
--do-not-fail-on-indirect-deprecation Do not signal failure using shell exit code when a deprecation was triggered in third-party code called from third-party code
|
||||||
--do-not-fail-on-phpunit-deprecation Do not signal failure using shell exit code when a PHPUnit deprecation was triggered
|
--do-not-fail-on-phpunit-deprecation Do not signal failure using shell exit code when a PHPUnit deprecation was triggered
|
||||||
--do-not-fail-on-phpunit-notice Do not signal failure using shell exit code when a PHPUnit notice was triggered
|
--do-not-fail-on-phpunit-notice Do not signal failure using shell exit code when a PHPUnit notice was triggered
|
||||||
--do-not-fail-on-phpunit-warning Do not signal failure using shell exit code when a PHPUnit warning was triggered
|
--do-not-fail-on-phpunit-warning Do not signal failure using shell exit code when a PHPUnit warning was triggered
|
||||||
--do-not-fail-on-notice Do not signal failure using shell exit code when a notice was triggered
|
--do-not-fail-on-notice Do not signal failure using shell exit code when a notice was triggered
|
||||||
--do-not-fail-on-skipped Do not signal failure using shell exit code when a test was skipped
|
--do-not-fail-on-skipped Do not signal failure using shell exit code when a test was skipped
|
||||||
--do-not-fail-on-incomplete Do not signal failure using shell exit code when a test was marked incomplete
|
--do-not-fail-on-incomplete Do not signal failure using shell exit code when a test was marked incomplete
|
||||||
--cache-result ............................ Write test results to cache file
|
--record-test-run-history .......................... Record test run history
|
||||||
--do-not-cache-result .............. Do not write test results to cache file
|
--do-not-record-test-run-history ............ Do not record test run history
|
||||||
|
--warn-when-php-is-not-configured-for-development Trigger a test runner warning when PHP is not configured for development
|
||||||
|
--do-not-warn-when-php-is-not-configured-for-development Do not trigger a test runner warning when PHP is not configured for development
|
||||||
--order-by [order] Run tests in order: default|defects|depends|duration-ascending|duration-descending|no-depends|random|reverse|size-ascending|size-descending
|
--order-by [order] Run tests in order: default|defects|depends|duration-ascending|duration-descending|no-depends|random|reverse|size-ascending|size-descending
|
||||||
--resolve-dependencies ...................... Alias for "--order-by depends"
|
--resolve-dependencies ...................... Alias for "--order-by depends"
|
||||||
--ignore-dependencies .................... Alias for "--order-by no-depends"
|
--ignore-dependencies .................... Alias for "--order-by no-depends"
|
||||||
--random-order ............................... Alias for "--order-by random"
|
--random-order ............................... Alias for "--order-by random"
|
||||||
--random-order-seed [N] Use the specified random seed when running tests in random order
|
--random-order-seed [N] Use the specified random seed when running tests in random order
|
||||||
|
--repeat [N] .......... Run each test N times, stopping at the first failure
|
||||||
|
--retry [N] . Attempt each test up to N times, stopping at the first success
|
||||||
--reverse-order ............................. Alias for "--order-by reverse"
|
--reverse-order ............................. Alias for "--order-by reverse"
|
||||||
|
|
||||||
REPORTING OPTIONS:
|
REPORTING OPTIONS:
|
||||||
@@ -151,6 +161,8 @@
|
|||||||
--coverage-cobertura [file] Write code coverage report in Cobertura XML format to file
|
--coverage-cobertura [file] Write code coverage report in Cobertura XML format to file
|
||||||
--coverage-crap4j [file] Write code coverage report in Crap4J XML format to file
|
--coverage-crap4j [file] Write code coverage report in Crap4J XML format to file
|
||||||
--coverage-html [dir] Write code coverage report in HTML format to directory
|
--coverage-html [dir] Write code coverage report in HTML format to directory
|
||||||
|
--without-class-view Render code coverage report in HTML format without class view
|
||||||
|
--without-file-view Render code coverage report in HTML format without file view
|
||||||
--coverage-php [file] .......... Write serialized code coverage data to file
|
--coverage-php [file] .......... Write serialized code coverage data to file
|
||||||
--coverage-text=[file] Write code coverage report in text format to file [default: standard output]
|
--coverage-text=[file] Write code coverage report in text format to file [default: standard output]
|
||||||
--only-summary-for-coverage-text Option for code coverage report in text format: only show summary
|
--only-summary-for-coverage-text Option for code coverage report in text format: only show summary
|
||||||
|
|||||||
@@ -1226,7 +1226,7 @@
|
|||||||
✓ it does not leak mock objects between retries
|
✓ it does not leak mock objects between retries
|
||||||
✓ it does not stop retrying when snapshot changes are absent
|
✓ it does not stop retrying when snapshot changes are absent
|
||||||
✓ it does not leak dynamic properties between retries
|
✓ it does not leak dynamic properties between retries
|
||||||
✓ it clears output buffer between retries when expectOutputString is used
|
! it clears output buffer between retries when expectOutputString is used → Creation of dynamic property PHPUnit\Framework\TestCase\OutputBuffer::$expectedString is deprecated
|
||||||
✓ it preserves output between retries when no output expectation is set
|
✓ it preserves output between retries when no output expectation is set
|
||||||
|
|
||||||
WARN Tests\Features\Helpers
|
WARN Tests\Features\Helpers
|
||||||
@@ -1568,232 +1568,6 @@
|
|||||||
PASS Tests\Features\Tia
|
PASS Tests\Features\Tia
|
||||||
✓ it does not run user hooks when replaying cached skipped and incomplete results
|
✓ it does not run user hooks when replaying cached skipped and incomplete results
|
||||||
|
|
||||||
PASS Tests\Features\Tia\BranchShapes
|
|
||||||
✓ a branch name git allows is a branch key TIA can hold with dataset "slashes"
|
|
||||||
✓ a branch name git allows is a branch key TIA can hold with dataset "dots"
|
|
||||||
✓ a branch name git allows is a branch key TIA can hold with dataset "unicode"
|
|
||||||
✓ a branch name git allows is a branch key TIA can hold with dataset "digits"
|
|
||||||
✓ a branch name git allows is a branch key TIA can hold with dataset "underscores"
|
|
||||||
✓ a branch name git allows is a branch key TIA can hold with dataset "very long"
|
|
||||||
✓ a branch differing from the default only in case gets its own key
|
|
||||||
✓ a branch that only lives on the remote keeps its baseline
|
|
||||||
✓ a branch checked out in a worktree keeps its baseline
|
|
||||||
✓ deleting many branches reclaims every one of their baselines with dataset "sequential"
|
|
||||||
✓ deleting many branches reclaims every one of their baselines with dataset "parallel"
|
|
||||||
✓ a narrowed run does not reclaim anything
|
|
||||||
✓ a detached HEAD does not reclaim anything either
|
|
||||||
✓ the default branch baseline survives every branch that comes and goes
|
|
||||||
✓ a project below the git repository root refuses to run and writes nothing with dataset "sequential"
|
|
||||||
✓ a project below the git repository root refuses to run and writes nothing with dataset "parallel"
|
|
||||||
✓ a repository with no commits says so, and leaves plain runs alone
|
|
||||||
✓ a directory with no repository at all still asks for git
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\CompleteRunWriteTier
|
|
||||||
✓ a complete run prunes a deleted test with dataset "sequential"
|
|
||||||
✓ a complete run prunes a deleted test with dataset "parallel"
|
|
||||||
✓ a complete run records nothing for a test file the graph does not know
|
|
||||||
✓ a partial run records nothing for a test file the graph does not know
|
|
||||||
✓ a truncated run does not prune with dataset "sequential"
|
|
||||||
✓ a truncated run does not prune with dataset "parallel"
|
|
||||||
✓ a green bail run is complete
|
|
||||||
✓ --no-tia refreshes results without enabling tia with dataset "sequential"
|
|
||||||
✓ --no-tia refreshes results without enabling tia with dataset "parallel"
|
|
||||||
✓ a plain run refreshes the results it executed with dataset "sequential"
|
|
||||||
✓ a plain run refreshes the results it executed with dataset "parallel"
|
|
||||||
✓ a parallel replay keeps the recorded time of tests that did not run
|
|
||||||
✓ a run that never enables tia creates no graph with dataset "plain"
|
|
||||||
✓ a run that never enables tia creates no graph with dataset "filtered"
|
|
||||||
✓ a run that never enables tia creates no graph with dataset "parallel filtered"
|
|
||||||
✓ a test edit narrows to the affected file and replays the rest
|
|
||||||
✓ a parallel run merges worker results into the parent baseline
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\CoveragePiggyback
|
|
||||||
✓ a coverage report does not found a dependency graph with dataset "pest coverage"
|
|
||||||
✓ a coverage report does not found a dependency graph with dataset "phpunit coverage report"
|
|
||||||
✓ a coverage report does not found a dependency graph with dataset "parallel"
|
|
||||||
✓ a plain run after a coverage run records the whole project scope
|
|
||||||
✓ a coverage report leaves the edges of an existing graph alone
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\DefaultBranchReplay
|
|
||||||
✓ replays the default branch baseline on a new branch
|
|
||||||
✓ replays whatever the default branch is called with ('main')
|
|
||||||
✓ replays whatever the default branch is called with ('master')
|
|
||||||
✓ replays whatever the default branch is called with ('trunk')
|
|
||||||
✓ replays whatever the default branch is called with ('develop')
|
|
||||||
✓ replays on a second new branch too
|
|
||||||
✓ writes nothing on a second run on the same branch
|
|
||||||
✓ replays on a branch whose name contains slashes
|
|
||||||
✓ replays again once back on the default branch
|
|
||||||
✓ replays on a new branch when tia is enabled by configuration
|
|
||||||
✓ a narrowed run on a new branch does not cost the fallback with dataset "sequential"
|
|
||||||
✓ a narrowed run on a new branch does not cost the fallback with dataset "parallel"
|
|
||||||
✓ replays inside a worktree on a new branch
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\DefaultBranchResolution
|
|
||||||
✓ a declared default branch beats autodetection
|
|
||||||
✓ a declared default branch that does not exist degrades to a full run
|
|
||||||
✓ a renamed default branch replays and writes under its new name
|
|
||||||
✓ the CI provider names the default branch where the checkout cannot
|
|
||||||
✓ GitLab names the default branch through its own variable
|
|
||||||
✓ a lone recorded baseline names the default branch
|
|
||||||
✓ a default branch nothing can name is refused rather than guessed
|
|
||||||
✓ an init.defaultBranch naming a branch that exists is still trusted
|
|
||||||
✓ a repository with no remote is refused rather than silently re-run
|
|
||||||
✓ a remote-less repository holding one baseline is not refused
|
|
||||||
✓ a declared default branch stands in for a missing remote
|
|
||||||
✓ tia still requires git
|
|
||||||
✓ a plain run outside a repository creates no baseline
|
|
||||||
✓ the default branch is resolved once per run, not once per test
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\DefaultBranchWriteTier
|
|
||||||
✓ narrows to the affected tests on a new branch
|
|
||||||
✓ filtered mode reads the fallback too
|
|
||||||
✓ filtered mode finds nothing to do on a clean green feature branch
|
|
||||||
✓ filtered mode falls back to a full replay when a cached failure cannot be located
|
|
||||||
✓ filtered mode finds nothing to do on the default branch itself
|
|
||||||
✓ a detached HEAD replays without minting a branch key
|
|
||||||
✓ a detached HEAD does not write into the default branch baseline with dataset "sequential"
|
|
||||||
✓ a detached HEAD does not write into the default branch baseline with dataset "parallel"
|
|
||||||
✓ the branch that ran gets its own key and the default branch keeps its baseline
|
|
||||||
✓ the fallback reaches parallel workers
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\FilteredMode
|
|
||||||
✓ re-runs a cached failure on a clean tree
|
|
||||||
✓ an explicit path turns filtered mode off
|
|
||||||
✓ filtered mode runs the whole suite when there is no baseline
|
|
||||||
✓ filtered mode finds nothing to do in parallel either
|
|
||||||
✓ a corrupt graph is reported and does not crash the run
|
|
||||||
✓ --parallel --retry is refused and leaves the graph alone
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\HostileState
|
|
||||||
✓ a graph mangled beyond use still lets the suite run with dataset "empty"
|
|
||||||
✓ a graph mangled beyond use still lets the suite run with dataset "truncated"
|
|
||||||
✓ a graph mangled beyond use still lets the suite run with dataset "not json"
|
|
||||||
✓ a graph mangled beyond use still lets the suite run with dataset "json scalar"
|
|
||||||
✓ a graph mangled beyond use still lets the suite run with dataset "json list"
|
|
||||||
✓ a graph mangled beyond use still lets the suite run with dataset "json null"
|
|
||||||
✓ a graph mangled beyond use still lets the suite run with dataset "empty object"
|
|
||||||
✓ a graph mangled beyond use still lets the suite run with dataset "nul bytes"
|
|
||||||
✓ a graph whose shape is wrong everywhere is repaired rather than trusted with dataset "sequential"
|
|
||||||
✓ a graph whose shape is wrong everywhere is repaired rather than trusted with dataset "parallel"
|
|
||||||
✓ a cached status this build cannot interpret is re-run, not replayed with dataset "below the range"
|
|
||||||
✓ a cached status this build cannot interpret is re-run, not replayed with dataset "one past the range"
|
|
||||||
✓ a cached status this build cannot interpret is re-run, not replayed with dataset "far past the range"
|
|
||||||
✓ a cached status this build cannot interpret is re-run, not replayed with dataset "huge"
|
|
||||||
✓ a cached status with no replay of its own does not fail the run with dataset "notice"
|
|
||||||
✓ a cached status with no replay of its own does not fail the run with dataset "deprecation"
|
|
||||||
✓ a cached status with no replay of its own does not fail the run with dataset "warning"
|
|
||||||
✓ a cached skip or todo replays with its message intact with dataset "skipped"
|
|
||||||
✓ a cached skip or todo replays with its message intact with dataset "incomplete"
|
|
||||||
✓ a cached failure with a multi-line message re-runs rather than replaying the text
|
|
||||||
✓ a result pointing outside the project is not addressable and widens the run
|
|
||||||
✓ an edge pointing at a file id that does not exist is ignored
|
|
||||||
✓ a graph from a schema this build does not know is rebuilt, not read
|
|
||||||
✓ graph.json being a directory does not stop the run
|
|
||||||
✓ a state dir it cannot write to still replays
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\IssueStatuses
|
|
||||||
✓ a triggered issue is recorded as itself, not as a pass with dataset "sequential" / dataset "deprecation"
|
|
||||||
✓ a triggered issue is recorded as itself, not as a pass with dataset "sequential" / dataset "notice"
|
|
||||||
✓ a triggered issue is recorded as itself, not as a pass with dataset "sequential" / dataset "warning"
|
|
||||||
✓ a triggered issue is recorded as itself, not as a pass with dataset "parallel" / dataset "deprecation"
|
|
||||||
✓ a triggered issue is recorded as itself, not as a pass with dataset "parallel" / dataset "notice"
|
|
||||||
✓ a triggered issue is recorded as itself, not as a pass with dataset "parallel" / dataset "warning"
|
|
||||||
✓ a cached deprecation still fails the run that asked to fail on one with dataset "sequential"
|
|
||||||
✓ a cached deprecation still fails the run that asked to fail on one with dataset "parallel"
|
|
||||||
✓ replaying a cached issue does not downgrade it to a pass with dataset "sequential"
|
|
||||||
✓ replaying a cached issue does not downgrade it to a pass with dataset "parallel"
|
|
||||||
✓ a failure outranks an issue triggered on the way to it
|
|
||||||
✓ a skip outranks an issue triggered on the way to it
|
|
||||||
✓ a suppressed issue is not recorded
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\LivewireComponents
|
|
||||||
✓ a changed single-file component selects only the tests that rendered it, across workers
|
|
||||||
✓ a changed multi-file component asset selects the tests that rendered the component
|
|
||||||
✓ a deleted single-file component selects the tests that rendered it
|
|
||||||
✓ a Blade file with no generated view still falls back to the watch pattern
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\PartialRunWriteTier
|
|
||||||
✓ a filtered run rewrites only the test that ran
|
|
||||||
✓ a filtered run under --tia announces that tia does not apply
|
|
||||||
✓ a test suffix narrows the tier even though every test runs
|
|
||||||
✓ a dirty run narrows to the uncommitted test edit
|
|
||||||
✓ filtered mode yields to an explicit filter
|
|
||||||
✓ an env flag narrows exactly like the option it mirrors with ('PEST_TIA')
|
|
||||||
✓ an env flag narrows exactly like the option it mirrors with ('PEST_TIA_FILTERED')
|
|
||||||
✓ a partial run does not purge the graph even with --fresh
|
|
||||||
✓ --no-tia does not stop a partial run from refreshing its own entry
|
|
||||||
✓ two partial runs each keep the other entry
|
|
||||||
✓ a shard is a partial run
|
|
||||||
✓ a parallel partial run records the test that ran, like a sequential one
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\RemoteBaseline
|
|
||||||
✓ a published baseline is fetched instead of recorded locally
|
|
||||||
✓ a fetched baseline that will not decode is discarded rather than trusted
|
|
||||||
✓ a fetched baseline recorded against another tree is not used
|
|
||||||
✓ an artifact without a graph in it fails loudly
|
|
||||||
✓ a baseline that cannot be authenticated for fails loudly
|
|
||||||
✓ a workflow or artifact that is not there fails loudly
|
|
||||||
✓ a network failure warns and lets the suite run with dataset "querying the runs"
|
|
||||||
✓ a network failure warns and lets the suite run with dataset "downloading the artifact"
|
|
||||||
✓ no published baseline yet starts a cooldown, and a corrupt cooldown does not break the run
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\SelectionPaths
|
|
||||||
✓ a committed rename selects the tests that depended on the old path with dataset "sequential"
|
|
||||||
✓ a committed rename selects the tests that depended on the old path with dataset "parallel"
|
|
||||||
✓ an affected test file that is gone does not strand a filtered run with dataset "sequential"
|
|
||||||
✓ an affected test file that is gone does not strand a filtered run with dataset "parallel"
|
|
||||||
✓ a plain run reclaims the edge of a test file that is gone
|
|
||||||
✓ a changed view selects the test that rendered it
|
|
||||||
✓ a changed partial selects the test that rendered its ancestor with dataset "direct @include"
|
|
||||||
✓ a changed partial selects the test that rendered its ancestor with dataset "transitive @include"
|
|
||||||
✓ a changed partial selects the test that rendered its ancestor with dataset "x- component"
|
|
||||||
✓ a changed partial selects the test that rendered its ancestor with dataset "include cycle"
|
|
||||||
✓ a changed Inertia page selects the test that rendered its component
|
|
||||||
✓ a changed shared JS module selects the tests of the pages that import it
|
|
||||||
✓ a changed frontend runtime file selects every Inertia test
|
|
||||||
|
|
||||||
PASS Tests\Features\Tia\StateReclamation
|
|
||||||
✓ a detached HEAD does not purge the graph on structural drift with dataset "sequential"
|
|
||||||
✓ a detached HEAD does not purge the graph on structural drift with dataset "parallel"
|
|
||||||
✓ a detached HEAD does not purge the graph with --fresh either with dataset "sequential"
|
|
||||||
✓ a detached HEAD does not purge the graph with --fresh either with dataset "parallel"
|
|
||||||
✓ a detached HEAD leaves an unreadable graph for a checkout that can rebuild it
|
|
||||||
✓ a cached failure whose test file was deleted stops widening later runs with dataset "sequential"
|
|
||||||
✓ a cached failure whose test file was deleted stops widening later runs with dataset "parallel"
|
|
||||||
✓ a complete run reclaims the entry and the edge of a deleted test file with dataset "sequential"
|
|
||||||
✓ a complete run reclaims the entry and the edge of a deleted test file with dataset "parallel"
|
|
||||||
✓ a pruned result does not come back from the fallback with dataset "sequential"
|
|
||||||
✓ a pruned result does not come back from the fallback with dataset "parallel"
|
|
||||||
✓ the fallback still reaches a branch that has never run a test file
|
|
||||||
✓ a branch that git no longer knows loses its baseline
|
|
||||||
✓ an unknown cached status is re-run rather than replayed as a failure with dataset "unknown"
|
|
||||||
✓ an unknown cached status is re-run rather than replayed as a failure with dataset "future"
|
|
||||||
✓ an unknown cached status is re-run rather than replayed as a failure with dataset "garbage"
|
|
||||||
✓ a cached notice, deprecation or warning does not replay as a failure with dataset "notice"
|
|
||||||
✓ a cached notice, deprecation or warning does not replay as a failure with dataset "deprecation"
|
|
||||||
✓ a cached notice, deprecation or warning does not replay as a failure with dataset "warning"
|
|
||||||
✓ a malformed baseline entry cannot break the run with dataset "sequential"
|
|
||||||
✓ a malformed baseline entry cannot break the run with dataset "parallel"
|
|
||||||
✓ a run torn down mid-file does not prune the tests it never reached with dataset "sequential"
|
|
||||||
✓ a run torn down mid-file does not prune the tests it never reached with dataset "parallel"
|
|
||||||
✓ a fatal error mid-file is a test error, not a truncation with dataset "sequential"
|
|
||||||
✓ a fatal error mid-file is a test error, not a truncation with dataset "parallel"
|
|
||||||
✓ a green complete run leaves the graph exactly as it found it with dataset "bail"
|
|
||||||
✓ a green complete run leaves the graph exactly as it found it with dataset "stop-on-failure"
|
|
||||||
✓ a green complete run leaves the graph exactly as it found it with dataset "compact"
|
|
||||||
✓ a green complete run leaves the graph exactly as it found it with dataset "parallel bail"
|
|
||||||
✓ a green complete run leaves the graph exactly as it found it with dataset "parallel one process"
|
|
||||||
✓ a green complete run leaves the graph exactly as it found it with dataset "parallel more processes than files"
|
|
||||||
✓ --tia --no-tia is a plain run that still refreshes what it executed with dataset "sequential"
|
|
||||||
✓ --tia --no-tia is a plain run that still refreshes what it executed with dataset "parallel"
|
|
||||||
✓ --fresh on a partial run neither purges nor prunes with dataset "sequential"
|
|
||||||
✓ --fresh on a partial run neither purges nor prunes with dataset "parallel"
|
|
||||||
✓ a second green run on a feature branch writes nothing at all with dataset "sequential"
|
|
||||||
✓ a second green run on a feature branch writes nothing at all with dataset "parallel"
|
|
||||||
✓ a graph whose recorded commit is gone is re-anchored, not warned about forever with dataset "sequential"
|
|
||||||
✓ a graph whose recorded commit is gone is re-anchored, not warned about forever with dataset "parallel"
|
|
||||||
|
|
||||||
PASS Tests\Features\Ticket
|
PASS Tests\Features\Ticket
|
||||||
✓ it may be associated with an ticket #1, #2
|
✓ it may be associated with an ticket #1, #2
|
||||||
✓ nested → it may be associated with an ticket #1, #4, #5, #6, #3
|
✓ nested → it may be associated with an ticket #1, #4, #5, #6, #3
|
||||||
@@ -2435,4 +2209,4 @@
|
|||||||
✓ pass with dataset with ('my-datas-set-value')
|
✓ pass with dataset with ('my-datas-set-value')
|
||||||
✓ within describe → pass with dataset with ('my-datas-set-value')
|
✓ within describe → pass with dataset with ('my-datas-set-value')
|
||||||
|
|
||||||
Tests: 1 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 34 skipped, 1760 passed (3983 assertions)
|
Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 34 skipped, 1561 passed (3405 assertions)
|
||||||
@@ -26,13 +26,13 @@ test('parallel', function () use ($run): void {
|
|||||||
$file = file_get_contents(__FILE__);
|
$file = file_get_contents(__FILE__);
|
||||||
$file = preg_replace(
|
$file = preg_replace(
|
||||||
'/\$expected = \'.*?\';/',
|
'/\$expected = \'.*?\';/',
|
||||||
"\$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1543 passed (3345 assertions)';",
|
"\$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 26 skipped, 1543 passed (3350 assertions)';",
|
||||||
$file,
|
$file,
|
||||||
);
|
);
|
||||||
file_put_contents(__FILE__, $file);
|
file_put_contents(__FILE__, $file);
|
||||||
}
|
}
|
||||||
|
|
||||||
$expected = '1 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1543 passed (3345 assertions)';
|
$expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 26 skipped, 1543 passed (3350 assertions)';
|
||||||
|
|
||||||
expect($output)
|
expect($output)
|
||||||
->toContain("Tests: {$expected}")
|
->toContain("Tests: {$expected}")
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ test('visual snapshot of test suite on success', function (): void {
|
|||||||
|
|
||||||
$output = function () use ($testsPath): ?string {
|
$output = function () use ($testsPath): ?string {
|
||||||
$process = (new Process(
|
$process = (new Process(
|
||||||
['php', '-d', 'memory_limit=-1', 'bin/pest'],
|
['php', '-d', 'memory_limit=-1', 'bin/pest', '--exclude-group=tia'],
|
||||||
dirname($testsPath),
|
dirname($testsPath),
|
||||||
['EXCLUDE' => 'integration', '--exclude-group' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0, 'COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true', 'PAO_DISABLE' => '1'],
|
['EXCLUDE' => 'integration', 'REBUILD_SNAPSHOTS' => false, 'PARATEST' => 0, 'COLLISION_PRINTER' => 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true', 'PAO_DISABLE' => '1'],
|
||||||
));
|
));
|
||||||
|
|
||||||
$process->setTimeout(300.0);
|
$process->setTimeout(300.0);
|
||||||
|
|||||||
Reference in New Issue
Block a user