From 0883722ea827b1d990cef9039a77be6af1a3c0a3 Mon Sep 17 00:00:00 2001 From: maks-rafalko Date: Sat, 3 Sep 2022 15:19:50 +0300 Subject: [PATCH 1/3] Introduce `--threads=max` option value to automatically detect the number of CPU cores Implements https://github.com/infection/infection/issues/1722 --- src/Command/RunCommand.php | 23 +++- .../Processor/CpuCoresCountProvider.php | 110 ++++++++++++++++++ .../ProjectCode/ProjectCodeProvider.php | 3 +- tests/phpunit/Command/RunCommandTest.php | 61 ++++++++++ 4 files changed, 193 insertions(+), 4 deletions(-) create mode 100644 src/Resource/Processor/CpuCoresCountProvider.php create mode 100644 tests/phpunit/Command/RunCommandTest.php diff --git a/src/Command/RunCommand.php b/src/Command/RunCommand.php index 5ec9103d1..dc3311e43 100644 --- a/src/Command/RunCommand.php +++ b/src/Command/RunCommand.php @@ -57,9 +57,12 @@ use Infection\Logger\GitHub\NoFilesInDiffToMutate; use Infection\Metrics\MinMsiCheckFailed; use Infection\Process\Runner\InitialTestsFailed; +use Infection\Resource\Processor\CpuCoresCountProvider; use Infection\TestFramework\Coverage\XmlReport\NoLineExecutedInDiffLinesMode; use Infection\TestFramework\TestFrameworkTypes; use InvalidArgumentException; +use function is_numeric; +use function max; use const PHP_SAPI; use Psr\Log\LoggerInterface; use function sprintf; @@ -67,6 +70,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use function trim; +use Webmozart\Assert\Assert; /** * @internal @@ -177,7 +181,7 @@ protected function configure(): void self::OPTION_THREADS, 'j', InputOption::VALUE_REQUIRED, - 'Number of threads to use by the runner when executing the mutations', + 'Number of threads to use by the runner when executing the mutations. Use "max" to auto calculate it.', Container::DEFAULT_THREAD_COUNT ) ->addOption( @@ -478,8 +482,7 @@ private function createContainer(IO $io, LoggerInterface $logger): Container ? Container::DEFAULT_TEST_FRAMEWORK_EXTRA_OPTIONS : $testFrameworkExtraOptions, $filter, - // TODO: more validation here? - (int) $input->getOption(self::OPTION_THREADS), + $this->getThreadCount($input), // To keep in sync with Container::DEFAULT_DRY_RUN (bool) $input->getOption(self::OPTION_DRY_RUN), $gitDiffFilter, @@ -643,4 +646,18 @@ private function getUseGitHubLogger(InputInterface $input): ?bool self::OPTION_LOGGER_GITHUB )); } + + private function getThreadCount(InputInterface $input): int + { + $threads = $input->getOption(self::OPTION_THREADS); + + if (is_numeric($threads)) { + return (int) $threads; + } + + Assert::same($threads, 'max', sprintf('The value of option `--threads` must be of type integer or string "max". String "%s" provided.', $threads)); + + // we subtract 1 here to not use all the available cores by Infection + return max(1, CpuCoresCountProvider::provide() - 1); + } } diff --git a/src/Resource/Processor/CpuCoresCountProvider.php b/src/Resource/Processor/CpuCoresCountProvider.php new file mode 100644 index 000000000..276d53912 --- /dev/null +++ b/src/Resource/Processor/CpuCoresCountProvider.php @@ -0,0 +1,110 @@ += 0 + && version_compare(PHP_VERSION, '7.4.0') < 0 + ) { + return 1; + } + + if (!extension_loaded('pcntl') || !function_exists('shell_exec')) { + return 1; + } + + $hasNproc = trim(@shell_exec('command -v nproc')); + + if ($hasNproc !== '') { + $nproc = trim(shell_exec('nproc')); + $cpuCount = filter_var($nproc, FILTER_VALIDATE_INT); + + if (is_int($cpuCount)) { + return $cpuCount; + } + } + + $ncpu = trim(shell_exec('sysctl -n hw.ncpu')); + $cpuCount = filter_var($ncpu, FILTER_VALIDATE_INT); + + if (is_int($cpuCount)) { + return $cpuCount; + } + + if (is_readable('/proc/cpuinfo')) { + $cpuinfo = file_get_contents('/proc/cpuinfo'); + $cpuCount = substr_count($cpuinfo, 'processor'); + + if ($cpuCount > 0) { + return $cpuCount; + } + } + + throw new LogicException('Failed to detect number of CPUs!'); + } +} diff --git a/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php b/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php index 62b91da45..adbc2df5a 100644 --- a/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php +++ b/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php @@ -66,6 +66,7 @@ use Infection\Mutator\NodeMutationGenerator; use Infection\Process\Runner\IndexedProcessBearer; use Infection\Process\ShellCommandLineExecutor; +use Infection\Resource\Processor\CpuCoresCountProvider; use Infection\TestFramework\AdapterInstaller; use Infection\TestFramework\Coverage\JUnit\TestFileTimeData; use Infection\TestFramework\Coverage\NodeLineRangeData; @@ -96,7 +97,6 @@ final class ProjectCodeProvider */ public const NON_TESTED_CONCRETE_CLASSES = [ ConfigureCommand::class, - RunCommand::class, Application::class, ProgressFormatter::class, ComposerExecutableFinder::class, @@ -113,6 +113,7 @@ final class ProjectCodeProvider NullSubscriber::class, FormatterName::class, ShellCommandLineExecutor::class, + CpuCoresCountProvider::class, ]; /** diff --git a/tests/phpunit/Command/RunCommandTest.php b/tests/phpunit/Command/RunCommandTest.php new file mode 100644 index 000000000..c05cbc2e9 --- /dev/null +++ b/tests/phpunit/Command/RunCommandTest.php @@ -0,0 +1,61 @@ +expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The value of option `--threads` must be of type integer or string "max". String "abc" provided.'); + + $app = new Application(SingletonContainer::getContainer()); + + $tester = new CommandTester($app->find('run')); + + $result = $tester->execute(['--threads' => 'abc']); + $this->assertSame(1, $result); + } +} From 467bd29d8363a29f5ab7ccd498033dee1b4da58a Mon Sep 17 00:00:00 2001 From: maks-rafalko Date: Sat, 3 Sep 2022 15:24:18 +0300 Subject: [PATCH 2/3] Fix CS --- tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php b/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php index adbc2df5a..3f0fc006e 100644 --- a/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php +++ b/tests/phpunit/AutoReview/ProjectCode/ProjectCodeProvider.php @@ -41,7 +41,6 @@ use function in_array; use Infection\CannotBeInstantiated; use Infection\Command\ConfigureCommand; -use Infection\Command\RunCommand; use Infection\Config\ConsoleHelper; use Infection\Config\Guesser\SourceDirGuesser; use Infection\Configuration\Schema\SchemaConfigurationFactory; From d5510f143da0b5d07b6e1d5d4cd48a8ee5b47927 Mon Sep 17 00:00:00 2001 From: maks-rafalko Date: Sat, 3 Sep 2022 23:42:18 +0300 Subject: [PATCH 3/3] Remove code for PHP 7.4, added comments for Linus/Macos, return 1 instead of throwing an exception --- .../Processor/CpuCoresCountProvider.php | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/src/Resource/Processor/CpuCoresCountProvider.php b/src/Resource/Processor/CpuCoresCountProvider.php index 276d53912..9d8599c53 100644 --- a/src/Resource/Processor/CpuCoresCountProvider.php +++ b/src/Resource/Processor/CpuCoresCountProvider.php @@ -42,15 +42,10 @@ use function function_exists; use function is_int; use function is_readable; -use LogicException; -use const PHP_OS; -use const PHP_VERSION; use function Safe\file_get_contents; -use function Safe\ini_get; use function Safe\shell_exec; use function substr_count; use function trim; -use function version_compare; /** * @internal @@ -66,18 +61,11 @@ public static function provide(): int return 1; } - if (ini_get('pcre.jit') === '1' - && PHP_OS === 'Darwin' - && version_compare(PHP_VERSION, '7.3.0') >= 0 - && version_compare(PHP_VERSION, '7.4.0') < 0 - ) { - return 1; - } - if (!extension_loaded('pcntl') || !function_exists('shell_exec')) { return 1; } + // for Linux $hasNproc = trim(@shell_exec('command -v nproc')); if ($hasNproc !== '') { @@ -89,6 +77,7 @@ public static function provide(): int } } + // for MacOS $ncpu = trim(shell_exec('sysctl -n hw.ncpu')); $cpuCount = filter_var($ncpu, FILTER_VALIDATE_INT); @@ -97,14 +86,14 @@ public static function provide(): int } if (is_readable('/proc/cpuinfo')) { - $cpuinfo = file_get_contents('/proc/cpuinfo'); - $cpuCount = substr_count($cpuinfo, 'processor'); + $cpuInfo = file_get_contents('/proc/cpuinfo'); + $cpuCount = substr_count($cpuInfo, 'processor'); if ($cpuCount > 0) { return $cpuCount; } } - throw new LogicException('Failed to detect number of CPUs!'); + return 1; } }