Skip to content

Commit

Permalink
Calculate results and show metrics if Infection is interrupted with `…
Browse files Browse the repository at this point in the history
…SIGINT` (ctrl + c) (#1857)

* Calculate results and show metrics if Infection is interrupted with `SIGINT` (ctrl + c)

Fixes #1498

* Fix CS

* Fix CS. Not sure what's wrong with Psalm

* Fix Psalm. Now I understand what was the issue, as I ran it on PHP 8.1 while GH Action runs it on PHP 8.2
  • Loading branch information
maks-rafalko committed May 16, 2023
1 parent 586f241 commit 556d086
Show file tree
Hide file tree
Showing 16 changed files with 384 additions and 11 deletions.
18 changes: 17 additions & 1 deletion src/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,14 @@
use Infection\Event\EventDispatcher\SyncEventDispatcher;
use Infection\Event\Subscriber\ChainSubscriberFactory;
use Infection\Event\Subscriber\CleanUpAfterMutationTestingFinishedSubscriberFactory;
use Infection\Event\Subscriber\DispatchPcntlSignalSubscriberFactory;
use Infection\Event\Subscriber\InitialTestsConsoleLoggerSubscriberFactory;
use Infection\Event\Subscriber\MutationGeneratingConsoleLoggerSubscriberFactory;
use Infection\Event\Subscriber\MutationTestingConsoleLoggerSubscriberFactory;
use Infection\Event\Subscriber\MutationTestingResultsCollectorSubscriberFactory;
use Infection\Event\Subscriber\MutationTestingResultsLoggerSubscriberFactory;
use Infection\Event\Subscriber\PerformanceLoggerSubscriberFactory;
use Infection\Event\Subscriber\StopInfectionOnSigintSignalSubscriberFactory;
use Infection\Event\Subscriber\SubscriberRegisterer;
use Infection\ExtensionInstaller\GeneratedExtensionsConfig;
use Infection\FileSystem\DummyFileSystem;
Expand Down Expand Up @@ -382,7 +384,9 @@ public static function create(): self
$container->getMutationTestingConsoleLoggerSubscriberFactory(),
$container->getMutationTestingResultsLoggerSubscriberFactory(),
$container->getPerformanceLoggerSubscriberFactory(),
$container->getCleanUpAfterMutationTestingFinishedSubscriberFactory()
$container->getCleanUpAfterMutationTestingFinishedSubscriberFactory(),
$container->getStopInfectionOnSigintSignalSubscriberFactory(),
$container->getDispatchPcntlSignalSubscriberFactory(),
),
CleanUpAfterMutationTestingFinishedSubscriberFactory::class => static function (self $container): CleanUpAfterMutationTestingFinishedSubscriberFactory {
$config = $container->getConfiguration();
Expand All @@ -393,6 +397,8 @@ public static function create(): self
$config->getTmpDir()
);
},
StopInfectionOnSigintSignalSubscriberFactory::class => static fn (self $container): StopInfectionOnSigintSignalSubscriberFactory => new StopInfectionOnSigintSignalSubscriberFactory(),
DispatchPcntlSignalSubscriberFactory::class => static fn (self $container): DispatchPcntlSignalSubscriberFactory => new DispatchPcntlSignalSubscriberFactory(),
InitialTestsConsoleLoggerSubscriberFactory::class => static function (self $container): InitialTestsConsoleLoggerSubscriberFactory {
$config = $container->getConfiguration();

Expand Down Expand Up @@ -950,6 +956,16 @@ public function getCleanUpAfterMutationTestingFinishedSubscriberFactory(): Clean
return $this->get(CleanUpAfterMutationTestingFinishedSubscriberFactory::class);
}

public function getStopInfectionOnSigintSignalSubscriberFactory(): StopInfectionOnSigintSignalSubscriberFactory
{
return $this->get(StopInfectionOnSigintSignalSubscriberFactory::class);
}

public function getDispatchPcntlSignalSubscriberFactory(): DispatchPcntlSignalSubscriberFactory
{
return $this->get(DispatchPcntlSignalSubscriberFactory::class);
}

public function getInitialTestsConsoleLoggerSubscriberFactory(): InitialTestsConsoleLoggerSubscriberFactory
{
return $this->get(InitialTestsConsoleLoggerSubscriberFactory::class);
Expand Down
9 changes: 8 additions & 1 deletion src/Event/MutationTestingWasStarted.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,24 @@

namespace Infection\Event;

use Infection\Process\Runner\ProcessRunner;

/**
* @internal
*/
final class MutationTestingWasStarted
{
public function __construct(private readonly int $mutationCount)
public function __construct(private readonly int $mutationCount, private readonly ProcessRunner $processRunner)
{
}

public function getMutationCount(): int
{
return $this->mutationCount;
}

public function getProcessRunner(): ProcessRunner
{
return $this->processRunner;
}
}
55 changes: 55 additions & 0 deletions src/Event/Subscriber/DispatchPcntlSignalSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\Event\Subscriber;

use function function_exists;
use Infection\Event\MutantProcessWasFinished;
use function Safe\pcntl_signal_dispatch;

/**
* @internal
*/
final class DispatchPcntlSignalSubscriber implements EventSubscriber
{
public function onMutantProcessWasFinished(MutantProcessWasFinished $event): void
{
if (!function_exists('pcntl_signal_dispatch')) {
return;
}

pcntl_signal_dispatch();
}
}
49 changes: 49 additions & 0 deletions src/Event/Subscriber/DispatchPcntlSignalSubscriberFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\Event\Subscriber;

use Symfony\Component\Console\Output\OutputInterface;

/**
* @internal
*/
final class DispatchPcntlSignalSubscriberFactory implements SubscriberFactory
{
public function create(OutputInterface $output): EventSubscriber
{
return new DispatchPcntlSignalSubscriber();
}
}
58 changes: 58 additions & 0 deletions src/Event/Subscriber/StopInfectionOnSigintSignalSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\Event\Subscriber;

use function function_exists;
use Infection\Event\MutationTestingWasStarted;
use function Safe\pcntl_signal;
use const SIGINT;

/**
* @internal
*/
final class StopInfectionOnSigintSignalSubscriber implements EventSubscriber
{
public function onMutationTestingWasStarted(MutationTestingWasStarted $event): void
{
if (!function_exists('pcntl_signal')) {
return;
}

pcntl_signal(SIGINT, static function () use ($event): void {
$event->getProcessRunner()->stop();
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/**
* This code is licensed under the BSD 3-Clause License.
*
* Copyright (c) 2017, Maks Rafalko
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

declare(strict_types=1);

namespace Infection\Event\Subscriber;

use Symfony\Component\Console\Output\OutputInterface;

/**
* @internal
*/
final class StopInfectionOnSigintSignalSubscriberFactory implements SubscriberFactory
{
public function create(OutputInterface $output): EventSubscriber
{
return new StopInfectionOnSigintSignalSubscriber();
}
}
5 changes: 5 additions & 0 deletions src/Process/Runner/DryProcessRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,9 @@ public function run(iterable $processes): void
// not even trigger the callback process
}
}

public function stop(): void
{
// not applicable for DryRunner
}
}
2 changes: 1 addition & 1 deletion src/Process/Runner/MutationTestingRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public function __construct(private readonly MutantProcessFactory $processFactor
public function run(iterable $mutations, string $testFrameworkExtraOptions): void
{
$numberOfMutants = IterableCounter::bufferAndCountIfNeeded($mutations, $this->runConcurrently);
$this->eventDispatcher->dispatch(new MutationTestingWasStarted($numberOfMutants));
$this->eventDispatcher->dispatch(new MutationTestingWasStarted($numberOfMutants, $this->processRunner));

$processes = take($mutations)
->cast(fn (Mutation $mutation): Mutant => $this->mutantFactory->create($mutation))
Expand Down
11 changes: 11 additions & 0 deletions src/Process/Runner/ParallelProcessRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,20 @@ final class ParallelProcessRunner implements ProcessRunner
*/
private array $availableThreadIndexes = [];

private bool $shouldStop = false;

/**
* @param int $poll Delay (in milliseconds) to wait in-between two polls
*/
public function __construct(private readonly int $threadCount, private readonly int $poll = self::POLL_WAIT_IN_MS)
{
}

public function stop(): void
{
$this->shouldStop = true;
}

public function run(iterable $processes): void
{
/*
Expand All @@ -96,6 +103,10 @@ public function run(iterable $processes): void

// start the initial batch of processes
while ($process = array_shift($bucket)) {
if ($this->shouldStop) {
break;
}

$threadIndex = array_shift($this->availableThreadIndexes);

Assert::integer($threadIndex, 'Thread index can not be null.');
Expand Down
2 changes: 2 additions & 0 deletions src/Process/Runner/ProcessRunner.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ interface ProcessRunner
* @param iterable<ProcessBearer> $processes
*/
public function run(iterable $processes): void;

public function stop(): void;
}
4 changes: 4 additions & 0 deletions tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,10 @@
use Infection\Console\OutputFormatter\OutputFormatter;
use Infection\Console\OutputFormatter\ProgressFormatter;
use Infection\Console\XdebugHandler;
use Infection\Event\Subscriber\DispatchPcntlSignalSubscriber;
use Infection\Event\Subscriber\MutationGeneratingConsoleLoggerSubscriber;
use Infection\Event\Subscriber\NullSubscriber;
use Infection\Event\Subscriber\StopInfectionOnSigintSignalSubscriber;
use Infection\FileSystem\DummyFileSystem;
use Infection\FileSystem\Finder\ComposerExecutableFinder;
use Infection\FileSystem\Finder\NonExecutableFinder;
Expand Down Expand Up @@ -113,6 +115,8 @@ final class ProjectCodeProvider
FormatterName::class,
ShellCommandLineExecutor::class,
CpuCoresCountProvider::class,
DispatchPcntlSignalSubscriber::class,
StopInfectionOnSigintSignalSubscriber::class,
];

/**
Expand Down
8 changes: 6 additions & 2 deletions tests/phpunit/Event/MutationTestingWasStartedTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,19 @@
namespace Infection\Tests\Event;

use Infection\Event\MutationTestingWasStarted;
use Infection\Process\Runner\ProcessRunner;
use PHPUnit\Framework\TestCase;

final class MutationTestingWasStartedTest extends TestCase
{
public function test_it_exposes_its_mutation_count(): void
public function test_it_exposes_its_mutation_count_and_process_runner(): void
{
$count = 5;
$event = new MutationTestingWasStarted($count);
$processRunner = $this->createMock(ProcessRunner::class);

$event = new MutationTestingWasStarted($count, $processRunner);

$this->assertSame($count, $event->getMutationCount());
$this->assertSame($processRunner, $event->getProcessRunner());
}
}

0 comments on commit 556d086

Please sign in to comment.