mirror of
https://github.com/pestphp/pest.git
synced 2026-06-05 10:52:14 +02:00
wip
This commit is contained in:
@ -25,6 +25,7 @@ final readonly class BootSubscribers implements Bootstrapper
|
|||||||
Subscribers\EnsureIgnorableTestCasesAreIgnored::class,
|
Subscribers\EnsureIgnorableTestCasesAreIgnored::class,
|
||||||
Subscribers\EnsureKernelDumpIsFlushed::class,
|
Subscribers\EnsureKernelDumpIsFlushed::class,
|
||||||
Subscribers\EnsureTeamCityEnabled::class,
|
Subscribers\EnsureTeamCityEnabled::class,
|
||||||
|
Subscribers\EnsureTiaIsRunningPestTestsOnly::class,
|
||||||
Subscribers\EnsureTiaCoverageIsRecorded::class,
|
Subscribers\EnsureTiaCoverageIsRecorded::class,
|
||||||
Subscribers\EnsureTiaCoverageIsFlushed::class,
|
Subscribers\EnsureTiaCoverageIsFlushed::class,
|
||||||
Subscribers\EnsureTiaResultsAreCollected::class,
|
Subscribers\EnsureTiaResultsAreCollected::class,
|
||||||
|
|||||||
46
src/Exceptions/TiaRequiresPestTests.php
Normal file
46
src/Exceptions/TiaRequiresPestTests.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Exceptions;
|
||||||
|
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||||
|
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||||
|
use Pest\Contracts\Panicable;
|
||||||
|
use RuntimeException;
|
||||||
|
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final class TiaRequiresPestTests extends RuntimeException implements ExceptionInterface, Panicable, RenderlessEditor, RenderlessTrace
|
||||||
|
{
|
||||||
|
public function __construct(private readonly string $className, private readonly string $file)
|
||||||
|
{
|
||||||
|
parent::__construct(sprintf(
|
||||||
|
'Tia mode requires Pest tests, but encountered PHPUnit class [%s] in [%s].',
|
||||||
|
$className,
|
||||||
|
$file,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$output->writeln([
|
||||||
|
'',
|
||||||
|
' <fg=white;options=bold;bg=red> ERROR </> Tia mode requires Pest tests.',
|
||||||
|
'',
|
||||||
|
sprintf(' Encountered PHPUnit class <fg=yellow>%s</>', $this->className),
|
||||||
|
sprintf(' in <fg=gray>%s</>.', $this->file),
|
||||||
|
'',
|
||||||
|
' Convert it to a Pest test, or run without Tia.',
|
||||||
|
'',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exitCode(): int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -787,18 +787,18 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
}
|
}
|
||||||
|
|
||||||
$affectedFromChanges = $changed === [] ? [] : $graph->affected($changed);
|
$affectedFromChanges = $changed === [] ? [] : $graph->affected($changed);
|
||||||
$failedFromCache = [];
|
$rerunFromCache = [];
|
||||||
|
|
||||||
if ($this->filteredMode) {
|
if ($this->filteredMode) {
|
||||||
$failedFromCache = $graph->failedOrErroredTestFiles($this->branch);
|
$rerunFromCache = $graph->testFilesToRerun($this->branch);
|
||||||
}
|
}
|
||||||
|
|
||||||
$affected = array_values(array_unique([
|
$affected = array_values(array_unique([
|
||||||
...$affectedFromChanges,
|
...$affectedFromChanges,
|
||||||
...$failedFromCache,
|
...$rerunFromCache,
|
||||||
]));
|
]));
|
||||||
|
|
||||||
$this->reportAffectedSummary($changed, $affectedFromChanges, $failedFromCache, $affected);
|
$this->reportAffectedSummary($changed, $affectedFromChanges, $rerunFromCache, $affected);
|
||||||
|
|
||||||
$affectedSet = array_fill_keys($affected, true);
|
$affectedSet = array_fill_keys($affected, true);
|
||||||
$canRefreshReplayEdges = $affected !== [] && $coverageAvailable;
|
$canRefreshReplayEdges = $affected !== [] && $coverageAvailable;
|
||||||
@ -852,10 +852,10 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
/**
|
/**
|
||||||
* @param array<int, string> $changedFiles
|
* @param array<int, string> $changedFiles
|
||||||
* @param array<int, string> $affectedFromChanges
|
* @param array<int, string> $affectedFromChanges
|
||||||
* @param array<int, string> $failedFromCache
|
* @param array<int, string> $rerunFromCache
|
||||||
* @param array<int, string> $affected
|
* @param array<int, string> $affected
|
||||||
*/
|
*/
|
||||||
private function reportAffectedSummary(array $changedFiles, array $affectedFromChanges, array $failedFromCache, array $affected): void
|
private function reportAffectedSummary(array $changedFiles, array $affectedFromChanges, array $rerunFromCache, array $affected): void
|
||||||
{
|
{
|
||||||
$this->output->writeln('');
|
$this->output->writeln('');
|
||||||
|
|
||||||
@ -865,12 +865,12 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$newFailures = $failedFromCache === []
|
$newReruns = $rerunFromCache === []
|
||||||
? 0
|
? 0
|
||||||
: count(array_diff($failedFromCache, $affectedFromChanges));
|
: count(array_diff($rerunFromCache, $affectedFromChanges));
|
||||||
|
|
||||||
$reasons = [];
|
$reasons = [];
|
||||||
$singleReason = (int) ($affectedFromChanges !== []) + (int) ($newFailures > 0) === 1;
|
$singleReason = (int) ($affectedFromChanges !== []) + (int) ($newReruns > 0) === 1;
|
||||||
|
|
||||||
if ($affectedFromChanges !== []) {
|
if ($affectedFromChanges !== []) {
|
||||||
$reasons[] = $singleReason
|
$reasons[] = $singleReason
|
||||||
@ -887,17 +887,17 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($newFailures > 0) {
|
if ($newReruns > 0) {
|
||||||
$reasons[] = $singleReason
|
$reasons[] = $singleReason
|
||||||
? sprintf(
|
? sprintf(
|
||||||
'from %d previous failure%s',
|
'from %d previously unsuccessful test%s',
|
||||||
$newFailures,
|
$newReruns,
|
||||||
$newFailures === 1 ? '' : 's',
|
$newReruns === 1 ? '' : 's',
|
||||||
)
|
)
|
||||||
: sprintf(
|
: sprintf(
|
||||||
'%d from previous failure%s',
|
'%d from previously unsuccessful test%s',
|
||||||
$newFailures,
|
$newReruns,
|
||||||
$newFailures === 1 ? '' : 's',
|
$newReruns === 1 ? '' : 's',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -516,13 +516,13 @@ final class Graph
|
|||||||
/**
|
/**
|
||||||
* @return array<int, string>
|
* @return array<int, string>
|
||||||
*/
|
*/
|
||||||
public function failedOrErroredTestFiles(string $branch, string $fallbackBranch = 'main'): array
|
public function testFilesToRerun(string $branch, string $fallbackBranch = 'main'): array
|
||||||
{
|
{
|
||||||
$baseline = $this->baselineFor($branch, $fallbackBranch);
|
$baseline = $this->baselineFor($branch, $fallbackBranch);
|
||||||
$files = [];
|
$files = [];
|
||||||
|
|
||||||
foreach ($baseline['results'] as $result) {
|
foreach ($baseline['results'] as $result) {
|
||||||
if ($result['status'] !== 7 && $result['status'] !== 8) {
|
if (! self::shouldRerun($result['status'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,12 +544,12 @@ final class Graph
|
|||||||
return array_keys($files);
|
return array_keys($files);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hasUnlocatedFailuresOrErrors(string $branch, string $fallbackBranch = 'main'): bool
|
public function hasUnlocatedTestsToRerun(string $branch, string $fallbackBranch = 'main'): bool
|
||||||
{
|
{
|
||||||
$baseline = $this->baselineFor($branch, $fallbackBranch);
|
$baseline = $this->baselineFor($branch, $fallbackBranch);
|
||||||
|
|
||||||
foreach ($baseline['results'] as $result) {
|
foreach ($baseline['results'] as $result) {
|
||||||
if ($result['status'] !== 7 && $result['status'] !== 8) {
|
if (! self::shouldRerun($result['status'])) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -563,6 +563,16 @@ final class Graph
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function shouldRerun(int $status): bool
|
||||||
|
{
|
||||||
|
$testStatus = TestStatus::from($status);
|
||||||
|
|
||||||
|
return $testStatus->isFailure()
|
||||||
|
|| $testStatus->isError()
|
||||||
|
|| $testStatus->isIncomplete()
|
||||||
|
|| $testStatus->isRisky();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, string> $tree project-relative path → content hash
|
* @param array<string, string> $tree project-relative path → content hash
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -119,8 +119,8 @@ final class Recorder
|
|||||||
$this->perTestUsesDatabase[$file] = true;
|
$this->perTestUsesDatabase[$file] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->linkAncestorFiles($className);
|
// $this->linkAncestorFiles($className);
|
||||||
$this->linkImportedFiles($file);
|
// $this->linkImportedFiles($file);
|
||||||
|
|
||||||
if ($this->driver === 'pcov') {
|
if ($this->driver === 'pcov') {
|
||||||
\pcov\clear();
|
\pcov\clear();
|
||||||
@ -175,7 +175,7 @@ final class Recorder
|
|||||||
$this->perTestFiles[$this->currentTestFile][$sourceFile] = true;
|
$this->perTestFiles[$this->currentTestFile][$sourceFile] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->linkSourceDependencies($coveredFiles);
|
// $this->linkSourceDependencies($coveredFiles);
|
||||||
|
|
||||||
$this->currentTestFile = null;
|
$this->currentTestFile = null;
|
||||||
$this->includedFilesAtTestStart = [];
|
$this->includedFilesAtTestStart = [];
|
||||||
|
|||||||
@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Pest\Plugins\Tia;
|
namespace Pest\Plugins\Tia;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestStatus\TestStatus;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@ -33,7 +35,7 @@ final class ResultCollector
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->record(0, '');
|
$this->record(TestStatus::success());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFailed(string $message): void
|
public function testFailed(string $message): void
|
||||||
@ -42,7 +44,7 @@ final class ResultCollector
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->record(7, $message);
|
$this->record(TestStatus::failure($message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testErrored(string $message): void
|
public function testErrored(string $message): void
|
||||||
@ -51,7 +53,7 @@ final class ResultCollector
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->record(8, $message);
|
$this->record(TestStatus::error($message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSkipped(string $message): void
|
public function testSkipped(string $message): void
|
||||||
@ -60,7 +62,7 @@ final class ResultCollector
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->record(1, $message);
|
$this->record(TestStatus::skipped($message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testIncomplete(string $message): void
|
public function testIncomplete(string $message): void
|
||||||
@ -69,7 +71,7 @@ final class ResultCollector
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->record(2, $message);
|
$this->record(TestStatus::incomplete($message));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRisky(string $message): void
|
public function testRisky(string $message): void
|
||||||
@ -78,7 +80,7 @@ final class ResultCollector
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->record(5, $message);
|
$this->record(TestStatus::risky($message));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,7 +123,7 @@ final class ResultCollector
|
|||||||
$this->startTime = null;
|
$this->startTime = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function record(int $status, string $message): void
|
private function record(TestStatus $status): void
|
||||||
{
|
{
|
||||||
if ($this->currentTestId === null) {
|
if ($this->currentTestId === null) {
|
||||||
return;
|
return;
|
||||||
@ -134,8 +136,8 @@ final class ResultCollector
|
|||||||
$existing = $this->results[$this->currentTestId] ?? null;
|
$existing = $this->results[$this->currentTestId] ?? null;
|
||||||
|
|
||||||
$this->results[$this->currentTestId] = [
|
$this->results[$this->currentTestId] = [
|
||||||
'status' => $status,
|
'status' => $status->asInt(),
|
||||||
'message' => $message,
|
'message' => $status->message(),
|
||||||
'time' => $time,
|
'time' => $time,
|
||||||
'assertions' => $existing['assertions'] ?? 0,
|
'assertions' => $existing['assertions'] ?? 0,
|
||||||
];
|
];
|
||||||
|
|||||||
64
src/Subscribers/EnsureTiaIsRunningPestTestsOnly.php
Normal file
64
src/Subscribers/EnsureTiaIsRunningPestTestsOnly.php
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Pest\Subscribers;
|
||||||
|
|
||||||
|
use Pest\Concerns\Testable;
|
||||||
|
use Pest\Exceptions\TiaRequiresPestTests;
|
||||||
|
use Pest\Panic;
|
||||||
|
use Pest\Plugins\Tia\Recorder;
|
||||||
|
use PHPUnit\Event\Code\TestMethod;
|
||||||
|
use PHPUnit\Event\Test\Prepared;
|
||||||
|
use PHPUnit\Event\Test\PreparedSubscriber;
|
||||||
|
use ReflectionClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
final readonly class EnsureTiaIsRunningPestTestsOnly implements PreparedSubscriber
|
||||||
|
{
|
||||||
|
public function __construct(private Recorder $recorder) {}
|
||||||
|
|
||||||
|
public function notify(Prepared $event): void
|
||||||
|
{
|
||||||
|
if (! $this->recorder->isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$test = $event->test();
|
||||||
|
|
||||||
|
if (! $test instanceof TestMethod) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$className = $test->className();
|
||||||
|
|
||||||
|
if (! class_exists($className, false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->usesTestableTrait($className)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Panic::with(new TiaRequiresPestTests($className, $test->file()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function usesTestableTrait(string $className): bool
|
||||||
|
{
|
||||||
|
$reflection = new ReflectionClass($className);
|
||||||
|
|
||||||
|
do {
|
||||||
|
foreach ($reflection->getTraitNames() as $trait) {
|
||||||
|
if ($trait === Testable::class) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$reflection = $reflection->getParentClass();
|
||||||
|
} while ($reflection !== false);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user