Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Contracts/Printer.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public function reportUncoveredMutation(MutationTest $test): void;

public function reportTimedOutMutation(MutationTest $test): void;

public function reportErroredMutation(MutationTest $test): void;

public function reportError(string $message): void;

public function reportScoreNotReached(float $scoreReached, float $scoreRequired): void;
Expand Down
12 changes: 12 additions & 0 deletions src/Event/Emitter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Pest\Mutate\Event\Events\Test\HookMethod\BeforeFirstTestExecuted;
use Pest\Mutate\Event\Events\Test\HookMethod\BeforeFirstTestExecutedSubscriber;
use Pest\Mutate\Event\Events\Test\Outcome\Errored;
use Pest\Mutate\Event\Events\Test\Outcome\ErroredSubscriber;
use Pest\Mutate\Event\Events\Test\Outcome\Tested;
use Pest\Mutate\Event\Events\Test\Outcome\TestedSubscriber;
use Pest\Mutate\Event\Events\Test\Outcome\Timeout;
Expand Down Expand Up @@ -69,6 +71,16 @@ public function mutationTimedOut(MutationTest $test): void
}
}

public function mutationErrored(MutationTest $test): void
{
$event = new Errored($test);

foreach (Facade::instance()->subscribers()[ErroredSubscriber::class] ?? [] as $subscriber) {
/** @var ErroredSubscriber $subscriber */
$subscriber->notify($event);
}
}

public function mutationUncovered(MutationTest $test): void
{
$event = new Uncovered($test);
Expand Down
15 changes: 15 additions & 0 deletions src/Event/Events/Test/Outcome/Errored.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Pest\Mutate\Event\Events\Test\Outcome;

use Pest\Mutate\Contracts\Event;
use Pest\Mutate\MutationTest;

class Errored implements Event
{
public function __construct(
public MutationTest $test,
) {}
}
12 changes: 12 additions & 0 deletions src/Event/Events/Test/Outcome/ErroredSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Pest\Mutate\Event\Events\Test\Outcome;

use Pest\Mutate\Contracts\Subscriber;

interface ErroredSubscriber extends Subscriber
{
public function notify(Errored $event): void;
}
14 changes: 12 additions & 2 deletions src/MutationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,22 @@ public function hasFinished(): bool
return true;
}

$this->updateResult(MutationTestResult::Tested);
if ($this->process->getExitCode() !== 1) {
$this->updateResult(MutationTestResult::Errored);

Facade::instance()->emitter()->mutationTested($this);
Facade::instance()->emitter()->mutationErrored($this);

$this->finish = microtime(true);

return true;
}

$this->updateResult(MutationTestResult::Tested);

$this->finish = microtime(true);

Facade::instance()->emitter()->mutationTested($this);

return true;
}

Expand Down
5 changes: 5 additions & 0 deletions src/MutationTestCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ public function timedOut(): int
return count(array_filter($this->tests, fn (MutationTest $test): bool => $test->result() === MutationTestResult::Timeout));
}

public function errored(): int
{
return count(array_filter($this->tests, fn (MutationTest $test): bool => $test->result() === MutationTestResult::Errored));
}

public function uncovered(): int
{
return count(array_filter($this->tests, fn (MutationTest $test): bool => $test->result() === MutationTestResult::Uncovered));
Expand Down
2 changes: 1 addition & 1 deletion src/Options/ProcessesOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class ProcessesOption

public static function remove(): bool
{
return false;
return true;
}

public static function match(string $argument): bool
Expand Down
10 changes: 10 additions & 0 deletions src/Plugins/Mutate.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use Pest\Mutate\Contracts\Printer;
use Pest\Mutate\Event\Events\Test\HookMethod\BeforeFirstTestExecuted;
use Pest\Mutate\Event\Events\Test\HookMethod\BeforeFirstTestExecutedSubscriber;
use Pest\Mutate\Event\Events\Test\Outcome\Errored;
use Pest\Mutate\Event\Events\Test\Outcome\ErroredSubscriber;
use Pest\Mutate\Event\Events\Test\Outcome\Tested;
use Pest\Mutate\Event\Events\Test\Outcome\TestedSubscriber;
use Pest\Mutate\Event\Events\Test\Outcome\Timeout;
Expand Down Expand Up @@ -233,6 +235,14 @@ public function notify(Timeout $event): void
}
},

new class($printer) extends PrinterSubscriber implements ErroredSubscriber
{
public function notify(Errored $event): void
{
$this->printer()->reportErroredMutation($event->test);
}
},

new class($printer) extends PrinterSubscriber implements UncoveredSubscriber
{
public function notify(Uncovered $event): void
Expand Down
5 changes: 5 additions & 0 deletions src/Repositories/MutationRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public function timedOut(): int
return array_sum(array_map(fn (MutationTestCollection $testCollection): int => $testCollection->timedOut(), $this->tests));
}

public function errored(): int
{
return array_sum(array_map(fn (MutationTestCollection $testCollection): int => $testCollection->errored(), $this->tests));
}

public function uncovered(): int
{
return array_sum(array_map(fn (MutationTestCollection $testCollection): int => $testCollection->uncovered(), $this->tests));
Expand Down
1 change: 1 addition & 0 deletions src/Support/MutationTestResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ enum MutationTestResult: string
case Uncovered = 'uncovered';
case Untested = 'untested';
case Timeout = 'timeout';
case Errored = 'errored';
}
18 changes: 16 additions & 2 deletions src/Support/Printers/DefaultPrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ public function reportTimedOutMutation(MutationTest $test): void
$this->writeMutationTestLine('yellow', 't', $test);
}

public function reportErroredMutation(MutationTest $test): void
{
if ($this->compact) {
$this->output->write('<fg=red;options=bold>e</>');

return;
}

$this->writeMutationTestLine('red', 'e', $test);
}

public function printFilename(MutationTestCollection $testCollection): void
{
if ($this->compact) {
Expand Down Expand Up @@ -132,7 +143,7 @@ public function reportMutationSuiteFinished(MutationSuite $mutationSuite): void
$this->writeMutationSuiteSummary($mutationSuite);

$this->output->writeln([
' <fg=gray>Mutations:</> <fg=default>'.($mutationSuite->repository->untested() !== 0 ? '<fg=red;options=bold>'.$mutationSuite->repository->untested().' untested</><fg=gray>,</> ' : '').($mutationSuite->repository->uncovered() !== 0 ? '<fg=yellow;options=bold>'.$mutationSuite->repository->uncovered().' uncovered</><fg=gray>,</> ' : '').($mutationSuite->repository->notRun() !== 0 ? '<fg=yellow;options=bold>'.$mutationSuite->repository->notRun().' pending</><fg=gray>,</> ' : '').($mutationSuite->repository->timedOut() !== 0 ? '<fg=green;options=bold>'.$mutationSuite->repository->timedOut().' timeout</><fg=gray>,</> ' : '').'<fg=green;options=bold>'.$mutationSuite->repository->tested().' tested</>',
' <fg=gray>Mutations:</> <fg=default>'.($mutationSuite->repository->untested() !== 0 ? '<fg=red;options=bold>'.$mutationSuite->repository->untested().' untested</><fg=gray>,</> ' : '').($mutationSuite->repository->errored() !== 0 ? '<fg=red;options=bold>'.$mutationSuite->repository->errored().' errored</><fg=gray>,</> ' : '').($mutationSuite->repository->uncovered() !== 0 ? '<fg=yellow;options=bold>'.$mutationSuite->repository->uncovered().' uncovered</><fg=gray>,</> ' : '').($mutationSuite->repository->notRun() !== 0 ? '<fg=yellow;options=bold>'.$mutationSuite->repository->notRun().' pending</><fg=gray>,</> ' : '').($mutationSuite->repository->timedOut() !== 0 ? '<fg=green;options=bold>'.$mutationSuite->repository->timedOut().' timeout</><fg=gray>,</> ' : '').'<fg=green;options=bold>'.$mutationSuite->repository->tested().' tested</>',
]);

$score = number_format($mutationSuite->score(), 2);
Expand Down Expand Up @@ -179,7 +190,7 @@ private function writeMutationSuiteSummary(MutationSuite $mutationSuite): void

private function writeMutationTestSummary(MutationTest $test): void
{
if (! in_array($test->result(), [MutationTestResult::Untested, MutationTestResult::Uncovered], true)) {
if (! in_array($test->result(), [MutationTestResult::Untested, MutationTestResult::Uncovered, MutationTestResult::Errored], true)) {
return;
}

Expand All @@ -195,6 +206,9 @@ private function writeMutationTestSummary(MutationTest $test): void
if ($test->result() === MutationTestResult::Untested) {
$color = 'red';
$label = 'UNTESTED';
} elseif ($test->result() === MutationTestResult::Errored) {
$color = 'red';
$label = 'ERRORED';
} else {
$color = 'bright-red';
$label = 'UNCOVERED';
Expand Down
77 changes: 77 additions & 0 deletions tests/Unit/MutationTestTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

use Pest\Mutate\Mutation;
use Pest\Mutate\MutationTest;
use Pest\Mutate\Repositories\TelemetryRepository;
use Pest\Mutate\Support\Configuration\Configuration;
use Pest\Mutate\Support\MutationTestResult;
use Pest\Support\Container;
use Symfony\Component\Finder\SplFileInfo;

it('does not report an errored mutation child process as tested', function (): void {
$sourcePath = tempnam(sys_get_temp_dir(), 'pest-mutate-source-');
$modifiedSourcePath = tempnam(sys_get_temp_dir(), 'pest-mutate-modified-');

expect($sourcePath)->toBeString()
->and($modifiedSourcePath)->toBeString();

file_put_contents($sourcePath, "<?php\nreturn true;\n");
file_put_contents($modifiedSourcePath, "<?php\nreturn false;\n");

$telemetryRepository = new TelemetryRepository;
$telemetryRepository->initialTestSuiteDuration(0.1);
Container::getInstance()->add(TelemetryRepository::class, $telemetryRepository);

$mutation = new Mutation(
file: new SplFileInfo($sourcePath, '', ''),
id: 'errored-mutation',
mutator: 'FakeMutator',
startLine: 2,
endLine: 2,
diff: '',
modifiedSourcePath: $modifiedSourcePath,
);

$mutationTest = new MutationTest($mutation);

$started = $mutationTest->start(
coveredLines: [
$mutation->file->getRealPath() => [
2 => ['Tests\\Unit\\MutationTestTest::it fails inside the child process'],
],
],
configuration: new Configuration(
coveredOnly: false,
paths: [],
pathsToIgnore: [],
mutators: [],
classes: [],
parallel: false,
processes: 1,
profile: false,
minScore: null,
ignoreMinScoreOnZeroMutations: false,
stopOnUntested: false,
stopOnUncovered: false,
mutationId: null,
retry: false,
everything: false,
),
originalArguments: [
'php',
'-r',
'fwrite(STDERR, "child error"); exit(2);',
'--',
],
);

expect($started)->toBeTrue();

while (! $mutationTest->hasFinished()) {
usleep(1_000);
}

expect($mutationTest->result())->not->toBe(MutationTestResult::Tested);
});
28 changes: 28 additions & 0 deletions tests/Unit/Options/ProcessesOptionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

use Pest\Mutate\Support\Configuration\CliConfiguration;

it('removes the processes option from forwarded test-run arguments when parallel is enabled', function (): void {
$configuration = new CliConfiguration;

$arguments = $configuration->fromArguments([
'vendor/bin/pest',
'tests',
'--mutate',
'--parallel',
'--processes=2',
]);

expect($configuration->toArray())
->parallel->toBeTrue()
->processes->toBe(2);

expect($arguments)
->toContain('vendor/bin/pest')
->toContain('tests')
->not->toContain('--mutate')
->not->toContain('--parallel')
->not->toContain('--processes=2');
});