diff --git a/src/Contracts/Logger.php b/src/Contracts/Logger.php new file mode 100644 index 0000000..6fa9839 --- /dev/null +++ b/src/Contracts/Logger.php @@ -0,0 +1,22 @@ +outputPath = $outputPath; + } + + public function mutationSuiteFinished( MutationSuite $mutationSuite ): void + { + $json = json_encode( [ + 'format' => 'pest', + 'results' => array_values( array_map( function ( $testCollection ) { + return [ + 'path' => $testCollection->file->getRealPath(), + 'count_total' => $testCollection->count(), + 'count_not_run' => $testCollection->notRun(), + 'count_timed_out' => $testCollection->timedOut(), + 'count_uncovered' => $testCollection->uncovered(), + 'count_untested' => $testCollection->untested(), + 'tests' => array_map( function ( $test ) { + return [ + 'id' => $test->getId(), + 'duration' => round( $test->duration(), 4 ), + 'result' => $test->result()->value, + 'mutation' => [ + 'id' => $test->mutation->id, + 'mutator' => $test->mutation->mutator, + 'start_line' => $test->mutation->startLine, + 'end_line' => $test->mutation->endLine, + ], + ]; + }, $testCollection->tests() ) + ]; + }, $mutationSuite->repository->all() ) ), + 'stats' => [ + 'duration' => round( $mutationSuite->duration(), 4 ), + 'score' => $mutationSuite->repository->score(), + 'tests' => [ + 'count_total' => $mutationSuite->repository->count(), + 'count_not_run' => $mutationSuite->repository->notRun(), + 'count_timed_out' => $mutationSuite->repository->timedOut(), + 'count_uncovered' => $mutationSuite->repository->uncovered(), + 'count_untested' => $mutationSuite->repository->untested(), + ], + ], + ], JSON_THROW_ON_ERROR ); + file_put_contents( $this->outputPath, $json ); + } +} diff --git a/src/Logging/NullLogger.php b/src/Logging/NullLogger.php new file mode 100644 index 0000000..fddfcd6 --- /dev/null +++ b/src/Logging/NullLogger.php @@ -0,0 +1,28 @@ +logger = new NullLogger(); } public function boot(): void @@ -121,6 +130,13 @@ public function handleArguments(array $arguments): array throw new InvalidOption('Mutation testing requires code coverage to be enabled. You can find more about code coverage in the Pest documentation.'); } + foreach ($arguments as $argIndex => $arg) { + if (str_starts_with((string) $arg, "--mutate-output-json=")) { // @phpstan-ignore-linereturn true; + $this->logger = new JsonLogger(explode('=', $arg)[1]); + unset($arguments[$argIndex]); + } + } + $mutationTestRunner->enable(); $this->ensurePrinterIsRegistered(); @@ -132,7 +148,7 @@ public function handleArguments(array $arguments): array } $arguments = Container::getInstance()->get(ConfigurationRepository::class) // @phpstan-ignore-line - ->cliConfiguration->fromArguments($arguments); + ->cliConfiguration->fromArguments($arguments); $mutationTestRunner->setOriginalArguments($arguments); @@ -268,6 +284,15 @@ public function notify(FinishMutationSuite $event): void $this->printer()->reportMutationSuiteFinished($event->mutationSuite); } }, + + // Logging + new class($this->logger) extends LoggerSubscriber implements FinishMutationSuiteSubscriber + { + public function notify(FinishMutationSuite $event): void + { + $this->logger()->mutationSuiteFinished($event->mutationSuite); + } + }, ]; Facade::instance()->registerSubscribers(...$subscribers); diff --git a/src/Subscribers/LoggerSubscriber.php b/src/Subscribers/LoggerSubscriber.php new file mode 100644 index 0000000..358b4de --- /dev/null +++ b/src/Subscribers/LoggerSubscriber.php @@ -0,0 +1,20 @@ +logger; + } +} diff --git a/tests/Logging/JsonLoggerTest.php b/tests/Logging/JsonLoggerTest.php new file mode 100644 index 0000000..71ebabe --- /dev/null +++ b/tests/Logging/JsonLoggerTest.php @@ -0,0 +1,91 @@ +repository->add( new Mutation( + id: 'test-id', + file: new SplFileInfo( 'test.php', '', '' ), + mutator: EqualToIdentical::class, + startLine: 4, + endLine: 4, + diff: <<<'DIFF' + --- Expected + +++ Actual + @@ @@ + + - return 1 == '1'; + + return 1 === '1'; + + DIFF, + modifiedSourcePath: 'test-modified.php', + ) ); + $suite->repository->all()[0]->tests()[0]->updateResult( MutationTestResult::Tested ); + $suite->trackStart(); + $suite->trackFinish(); + + $logger->mutationSuiteFinished( $suite ); + + expect( __DIR__ . '/mutation-output.json' )->toBeReadableFile(); + + expect( file_get_contents( __DIR__ . '/mutation-output.json' ) )->json()->toMatchArray( [ + 'format' => 'pest', + 'results' => + [ + [ + 'path' => false, + 'count_total' => 1, + 'count_not_run' => 0, + 'count_timed_out' => 0, + 'count_uncovered' => 0, + 'count_untested' => 0, + 'tests' => + [ + 0 => + [ + 'id' => 'test-id', + 'duration' => 0, + 'result' => 'tested', + 'mutation' => + [ + 'id' => 'test-id', + 'mutator' => 'Pest\\Mutate\\Mutators\\Equality\\EqualToIdentical', + 'start_line' => 4, + 'end_line' => 4, + ], + ], + ], + ], + ], + 'stats' => + [ + 'duration' => 0, + 'score' => 100, + 'tests' => + [ + 'count_total' => 1, + 'count_not_run' => 0, + 'count_timed_out' => 0, + 'count_uncovered' => 0, + 'count_untested' => 0, + ], + ], + ] ); +} );