diff --git a/src/Contracts/Printer.php b/src/Contracts/Printer.php index 397e541..dc2f6d9 100644 --- a/src/Contracts/Printer.php +++ b/src/Contracts/Printer.php @@ -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; diff --git a/src/Event/Emitter.php b/src/Event/Emitter.php index 6c891a4..edaa067 100644 --- a/src/Event/Emitter.php +++ b/src/Event/Emitter.php @@ -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; @@ -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); diff --git a/src/Event/Events/Test/Outcome/Errored.php b/src/Event/Events/Test/Outcome/Errored.php new file mode 100644 index 0000000..8eea4e0 --- /dev/null +++ b/src/Event/Events/Test/Outcome/Errored.php @@ -0,0 +1,15 @@ +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; } diff --git a/src/MutationTestCollection.php b/src/MutationTestCollection.php index 70bd2c9..0011ef5 100644 --- a/src/MutationTestCollection.php +++ b/src/MutationTestCollection.php @@ -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)); diff --git a/src/Options/ProcessesOption.php b/src/Options/ProcessesOption.php index 72e33a8..71c28b9 100644 --- a/src/Options/ProcessesOption.php +++ b/src/Options/ProcessesOption.php @@ -12,7 +12,7 @@ class ProcessesOption public static function remove(): bool { - return false; + return true; } public static function match(string $argument): bool diff --git a/src/Plugins/Mutate.php b/src/Plugins/Mutate.php index 8a3ddc1..4a66190 100644 --- a/src/Plugins/Mutate.php +++ b/src/Plugins/Mutate.php @@ -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; @@ -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 diff --git a/src/Repositories/MutationRepository.php b/src/Repositories/MutationRepository.php index 07fc90c..fcf6cc3 100644 --- a/src/Repositories/MutationRepository.php +++ b/src/Repositories/MutationRepository.php @@ -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)); diff --git a/src/Support/MutationTestResult.php b/src/Support/MutationTestResult.php index 5e2359e..cad9e83 100644 --- a/src/Support/MutationTestResult.php +++ b/src/Support/MutationTestResult.php @@ -11,4 +11,5 @@ enum MutationTestResult: string case Uncovered = 'uncovered'; case Untested = 'untested'; case Timeout = 'timeout'; + case Errored = 'errored'; } diff --git a/src/Support/Printers/DefaultPrinter.php b/src/Support/Printers/DefaultPrinter.php index 2e22cda..dfc13b6 100644 --- a/src/Support/Printers/DefaultPrinter.php +++ b/src/Support/Printers/DefaultPrinter.php @@ -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('e'); + + return; + } + + $this->writeMutationTestLine('red', 'e', $test); + } + public function printFilename(MutationTestCollection $testCollection): void { if ($this->compact) { @@ -132,7 +143,7 @@ public function reportMutationSuiteFinished(MutationSuite $mutationSuite): void $this->writeMutationSuiteSummary($mutationSuite); $this->output->writeln([ - ' Mutations: '.($mutationSuite->repository->untested() !== 0 ? ''.$mutationSuite->repository->untested().' untested, ' : '').($mutationSuite->repository->uncovered() !== 0 ? ''.$mutationSuite->repository->uncovered().' uncovered, ' : '').($mutationSuite->repository->notRun() !== 0 ? ''.$mutationSuite->repository->notRun().' pending, ' : '').($mutationSuite->repository->timedOut() !== 0 ? ''.$mutationSuite->repository->timedOut().' timeout, ' : '').''.$mutationSuite->repository->tested().' tested', + ' Mutations: '.($mutationSuite->repository->untested() !== 0 ? ''.$mutationSuite->repository->untested().' untested, ' : '').($mutationSuite->repository->errored() !== 0 ? ''.$mutationSuite->repository->errored().' errored, ' : '').($mutationSuite->repository->uncovered() !== 0 ? ''.$mutationSuite->repository->uncovered().' uncovered, ' : '').($mutationSuite->repository->notRun() !== 0 ? ''.$mutationSuite->repository->notRun().' pending, ' : '').($mutationSuite->repository->timedOut() !== 0 ? ''.$mutationSuite->repository->timedOut().' timeout, ' : '').''.$mutationSuite->repository->tested().' tested', ]); $score = number_format($mutationSuite->score(), 2); @@ -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; } @@ -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'; diff --git a/tests/Unit/MutationTestTest.php b/tests/Unit/MutationTestTest.php new file mode 100644 index 0000000..9e41d89 --- /dev/null +++ b/tests/Unit/MutationTestTest.php @@ -0,0 +1,77 @@ +toBeString() + ->and($modifiedSourcePath)->toBeString(); + + file_put_contents($sourcePath, "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); +}); diff --git a/tests/Unit/Options/ProcessesOptionTest.php b/tests/Unit/Options/ProcessesOptionTest.php new file mode 100644 index 0000000..cb5ffb1 --- /dev/null +++ b/tests/Unit/Options/ProcessesOptionTest.php @@ -0,0 +1,28 @@ +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'); +});