From 47c8350d11ea100184ac20fcd8eb85be2c78194a Mon Sep 17 00:00:00 2001 From: $iD Date: Thu, 30 Nov 2017 00:12:47 +0300 Subject: [PATCH] Disable xdebug for all php processes except code coverage generator (#85) --- .php_cs.dist | 3 +- app/bootstrap.php | 23 ++- app/container.php | 15 +- composer.lock | 14 +- src/Command/InfectionCommand.php | 4 - src/Finder/AbstractExecutableFinder.php | 28 ++- src/Finder/ComposerExecutableFinder.php | 13 +- src/Finder/TestFrameworkExecutableFinder.php | 52 +++-- src/Php/ConfigBuilder.php | 93 +++++++++ src/Php/PhpIniHelper.php | 34 ++++ src/Php/XdebugHandler.php | 185 ++++++++++++++++++ src/Process/Builder/ProcessBuilder.php | 55 +++--- .../ExecutableFinder/PhpExecutableFinder.php | 27 +++ .../AbstractTestFrameworkAdapter.php | 14 +- src/TestFramework/Factory.php | 4 +- tests/Php/ConfigBuilderTest.php | 67 +++++++ tests/Php/Mock/XdebugHandlerMock.php | 36 ++++ tests/Php/PhpIniHelperTest.php | 60 ++++++ tests/Php/XdebugHandlerTest.php | 96 +++++++++ tests/Process/Builder/ProcessBuilderTest.php | 9 +- .../PhpExecutableFinderTest.php | 43 ++++ 21 files changed, 764 insertions(+), 111 deletions(-) create mode 100644 src/Php/ConfigBuilder.php create mode 100644 src/Php/PhpIniHelper.php create mode 100644 src/Php/XdebugHandler.php create mode 100644 src/Process/ExecutableFinder/PhpExecutableFinder.php create mode 100644 tests/Php/ConfigBuilderTest.php create mode 100644 tests/Php/Mock/XdebugHandlerMock.php create mode 100644 tests/Php/PhpIniHelperTest.php create mode 100644 tests/Php/XdebugHandlerTest.php create mode 100644 tests/Process/ExecutableFinder/PhpExecutableFinderTest.php diff --git a/.php_cs.dist b/.php_cs.dist index f96c3855a..575988924 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -14,6 +14,7 @@ return \PhpCsFixer\Config::create() 'phpdoc_summary' => false, 'phpdoc_align' => false, 'yoda_style' => false, + 'is_null' => true, ]) ->setFinder($finder) -; \ No newline at end of file +; diff --git a/app/bootstrap.php b/app/bootstrap.php index e9cdc1732..3307ca0ee 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -4,6 +4,9 @@ * * License: https://opensource.org/licenses/BSD-3-Clause New BSD License */ +use Infection\Php\XdebugHandler; +use Infection\Php\ConfigBuilder; + $files = [ __DIR__ . '/../../../autoload.php', __DIR__ . '/../vendor/autoload.php', @@ -18,8 +21,11 @@ } } +unset($files); + if (!defined('INFECTION_COMPOSER_INSTALL')) { - fwrite(STDERR, + fwrite( + STDERR, 'You need to set up the project dependencies using Composer:' . PHP_EOL . PHP_EOL . ' composer install' . PHP_EOL . PHP_EOL . 'You can learn all about Composer on https://getcomposer.org/.' . PHP_EOL @@ -30,4 +36,19 @@ require INFECTION_COMPOSER_INSTALL; +$isDebuggerDisabled = empty((string) getenv(XdebugHandler::ENV_DISABLE_XDEBUG)); + +$xdebug = new XdebugHandler(new ConfigBuilder(sys_get_temp_dir())); +$xdebug->check(); +unset($xdebug); + +if (PHP_SAPI !== 'phpdbg' && $isDebuggerDisabled && !extension_loaded('xdebug')) { + fwrite( + STDERR, + 'You need to use phpdbg or install and enable xdebug in order to allow for code coverage generation.' . PHP_EOL + ); + + die(1); +} + $container = require __DIR__ . '/container.php'; diff --git a/app/container.php b/app/container.php index 64402ad61..1dfd12ab7 100644 --- a/app/container.php +++ b/app/container.php @@ -12,7 +12,6 @@ use Infection\TestFramework\Factory; use Infection\Differ\Differ; use Infection\Mutant\MutantCreator; -use Infection\Command\InfectionCommand; use Infection\Process\Runner\Parallel\ParallelProcessRunner; use Infection\EventDispatcher\EventDispatcher; use Infection\Filesystem\Filesystem; @@ -26,6 +25,7 @@ use Infection\TestFramework\PhpUnit\Adapter\PhpUnitAdapter; use Infection\Config\InfectionConfig; use Infection\Utils\VersionParser; +use Infection\Command; use Infection\TestFramework\Coverage\CachedTestFileDataProvider; use Infection\TestFramework\PhpUnit\Config\XmlConfigurationHelper; use SebastianBergmann\Diff\Differ as BaseDiffer; @@ -54,7 +54,7 @@ return $c['temp.dir.creator']->createAndGet(); }; -$c['filesystem'] = function (Container $c): Filesystem { +$c['filesystem'] = function (): Filesystem { return new Filesystem(); }; @@ -138,14 +138,14 @@ return (new ParserFactory())->create(ParserFactory::PREFER_PHP7, $c['lexer']); }; -$c['pretty.printer'] = function ($c): Standard { +$c['pretty.printer'] = function (): Standard { return new Standard(); }; function registerMutators(array $mutators, Container $container) { foreach ($mutators as $mutator) { - $container[$mutator] = function (Container $c) use ($mutator) { + $container[$mutator] = function () use ($mutator) { return new $mutator(); }; } @@ -158,11 +158,10 @@ function registerMutators(array $mutators, Container $container) 'Infection - PHP Mutation Testing Framework', '@package_version@' ); - $infectionCommand = new InfectionCommand($container); - $application->add(new \Infection\Command\ConfigureCommand()); - $application->add(new \Infection\Command\SelfUpdateCommand()); - $application->add($infectionCommand); + $application->add(new Command\ConfigureCommand()); + $application->add(new Command\SelfUpdateCommand()); + $application->add(new Command\InfectionCommand($container)); return $application; }; diff --git a/composer.lock b/composer.lock index cdfcf5069..d30baffb9 100644 --- a/composer.lock +++ b/composer.lock @@ -546,25 +546,25 @@ }, { "name": "symfony/process", - "version": "v3.2.7", + "version": "v3.3.13", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "57fdaa55827ae14d617550ebe71a820f0a5e2282" + "reference": "a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/57fdaa55827ae14d617550ebe71a820f0a5e2282", - "reference": "57fdaa55827ae14d617550ebe71a820f0a5e2282", + "url": "https://api.github.com/repos/symfony/process/zipball/a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d", + "reference": "a56a3989fb762d7b19a0cf8e7693ee99a6ffb78d", "shasum": "" }, "require": { - "php": ">=5.5.9" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "3.3-dev" } }, "autoload": { @@ -591,7 +591,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2017-03-27T18:07:02+00:00" + "time": "2017-11-13T15:31:11+00:00" }, { "name": "symfony/yaml", diff --git a/src/Command/InfectionCommand.php b/src/Command/InfectionCommand.php index 65f215ddc..e3180150b 100644 --- a/src/Command/InfectionCommand.php +++ b/src/Command/InfectionCommand.php @@ -177,10 +177,6 @@ protected function initialize(InputInterface $input, OutputInterface $output) throw new \Exception('Configuration aborted'); } } - - if (PHP_SAPI !== 'phpdbg' && !defined('HHVM_VERSION') && !extension_loaded('xdebug')) { - throw new \Exception('You need to use phpdbg or install and enable xDebug in order to allow for code coverage generation.'); - } } private function setOutputFormatterStyles(OutputInterface $output) diff --git a/src/Finder/AbstractExecutableFinder.php b/src/Finder/AbstractExecutableFinder.php index 4bd7e7460..d8af7e7c4 100644 --- a/src/Finder/AbstractExecutableFinder.php +++ b/src/Finder/AbstractExecutableFinder.php @@ -8,46 +8,42 @@ namespace Infection\Finder; -use Symfony\Component\Process\PhpExecutableFinder; +use Infection\Process\ExecutableFinder\PhpExecutableFinder; abstract class AbstractExecutableFinder { - /** - * @return string - */ - abstract public function find(); + abstract public function find(bool $includeArgs = true): string; /** * @param array $probableNames * @param array $extraDirectories + * @param bool $includeArgs * * @return string|null */ - protected function searchNonExecutables(array $probableNames, array $extraDirectories = []) + protected function searchNonExecutables(array $probableNames, array $extraDirectories = [], bool $includeArgs = true) { $dirs = array_merge( explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), $extraDirectories ); + foreach ($dirs as $dir) { foreach ($probableNames as $name) { $path = sprintf('%s/%s', $dir, $name); if (file_exists($path)) { - return $this->makeExecutable($path); + return $this->makeExecutable($path, $includeArgs); } } } } - /** - * @param string $path - * - * @return string - */ - protected function makeExecutable($path) + protected function makeExecutable(string $path, bool $includeArgs = true): string { - $phpFinder = new PhpExecutableFinder(); - - return sprintf('%s %s', $phpFinder->find(), $path); + return sprintf( + '%s %s', + (new PhpExecutableFinder())->find($includeArgs), + $path + ); } } diff --git a/src/Finder/ComposerExecutableFinder.php b/src/Finder/ComposerExecutableFinder.php index a6cf6f7c2..6f1feb410 100644 --- a/src/Finder/ComposerExecutableFinder.php +++ b/src/Finder/ComposerExecutableFinder.php @@ -10,14 +10,9 @@ use Symfony\Component\Process\ExecutableFinder; -class ComposerExecutableFinder extends AbstractExecutableFinder +final class ComposerExecutableFinder extends AbstractExecutableFinder { - /** - * @return string - * - * @throws \Exception - */ - public function find() + public function find(bool $includeArgs = true): string { $probable = ['composer', 'composer.phar']; $finder = new ExecutableFinder(); @@ -33,9 +28,9 @@ public function find() * Check for options without execute permissions and prefix the PHP * executable instead. */ - $result = $this->searchNonExecutables($probable, $immediatePaths); + $result = $this->searchNonExecutables($probable, $immediatePaths, $includeArgs); - if (!is_null($result)) { + if (null !== $result) { return $result; } diff --git a/src/Finder/TestFrameworkExecutableFinder.php b/src/Finder/TestFrameworkExecutableFinder.php index 973b5f114..5ac6adcb4 100644 --- a/src/Finder/TestFrameworkExecutableFinder.php +++ b/src/Finder/TestFrameworkExecutableFinder.php @@ -9,8 +9,8 @@ namespace Infection\Finder; use Infection\Finder\Exception\TestFrameworkExecutableFinderNotFound; +use Infection\Process\ExecutableFinder\PhpExecutableFinder; use Symfony\Component\Process\ExecutableFinder; -use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; class TestFrameworkExecutableFinder extends AbstractExecutableFinder @@ -21,7 +21,7 @@ class TestFrameworkExecutableFinder extends AbstractExecutableFinder private $cachedExecutable; /** - * @var + * @var string */ private $testFrameworkName; @@ -30,25 +30,29 @@ class TestFrameworkExecutableFinder extends AbstractExecutableFinder */ private $customPath; + /** + * @var bool + */ + private $cachedIncludedArgs; + public function __construct(string $testFrameworkName, string $customPath = null) { $this->testFrameworkName = $testFrameworkName; $this->customPath = $customPath; } - /** - * @return string - */ - public function find() + public function find(bool $includeArgs = true): string { - if ($this->cachedExecutable === null) { + if ($this->cachedExecutable === null || $this->cachedIncludedArgs !== $includeArgs) { if (!$this->doesCustomPathExist()) { $this->addVendorFolderToPath(); } - $this->cachedExecutable = $this->findExecutable(); + $this->cachedExecutable = $this->findExecutable($includeArgs); } + $this->cachedIncludedArgs = $includeArgs; + return $this->cachedExecutable; } @@ -66,25 +70,17 @@ private function addVendorFolderToPath() } } - if (!is_null($vendorPath)) { + if (null !== $vendorPath) { putenv('PATH=' . $vendorPath . PATH_SEPARATOR . getenv('PATH')); } } - /** - * @return string - */ - private function findComposer() + private function findComposer(): string { return (new ComposerExecutableFinder())->find(); } - /** - * @return string - * - * @throws TestFrameworkExecutableFinderNotFound - */ - private function findExecutable() + private function findExecutable(bool $includeArgs = true): string { if ($this->doesCustomPathExist()) { return $this->makeExecutable($this->customPath); @@ -95,13 +91,13 @@ private function findExecutable() foreach ($candidates as $name) { if ($path = $finder->find($name, null, [getcwd()])) { - return $this->makeExecutable($path); + return $this->makeExecutable($path, $includeArgs); } } - $result = $this->searchNonExecutables($candidates, [getcwd()]); + $result = $this->searchNonExecutables($candidates, [getcwd()], $includeArgs); - if (!is_null($result)) { + if (null !== $result) { return $result; } @@ -119,28 +115,26 @@ private function findExecutable() * are enforced and end PHP processes properly * * @param string $path + * @param bool $includeArgs * * @return string */ - protected function makeExecutable($path) + protected function makeExecutable(string $path, bool $includeArgs = true): string { $path = realpath($path); $phpFinder = new PhpExecutableFinder(); - if (defined('PHP_WINDOWS_VERSION_BUILD')) { + if (\defined('PHP_WINDOWS_VERSION_BUILD')) { if (false !== strpos($path, '.bat')) { return $path; } - return sprintf('%s %s', $phpFinder->find(), $path); + return sprintf('%s %s', $phpFinder->find($includeArgs), $path); } - return sprintf('%s %s %s', 'exec', $phpFinder->find(), $path); + return sprintf('%s %s %s', 'exec', $phpFinder->find($includeArgs), $path); } - /** - * @return bool - */ private function doesCustomPathExist(): bool { return $this->customPath && file_exists($this->customPath); diff --git a/src/Php/ConfigBuilder.php b/src/Php/ConfigBuilder.php new file mode 100644 index 000000000..c52ca1201 --- /dev/null +++ b/src/Php/ConfigBuilder.php @@ -0,0 +1,93 @@ +tempDir = $tempDir; + } + + /** + * @return string|null config path + * + * @throws \Exception + */ + public function build() + { + $tmpIniPath = (string) getenv(self::ENV_TEMP_PHP_CONFIG_PATH); + + if (!empty($tmpIniPath) && file_exists($tmpIniPath)) { + return $tmpIniPath; + } + + $iniPaths = PhpIniHelper::get(); + + if ($this->writeTempIni($iniPaths)) { + $additional = count($iniPaths) > 1; + + $this->setEnvironment($additional); + + return $this->tmpIniPath; + } + + throw new IOException('Can not create temporary php config with disabled xdebug.'); + } + + /** + * @param string[] $originalIniPaths + * + * @return bool + */ + private function writeTempIni(array $originalIniPaths): bool + { + if (!($this->tmpIniPath = tempnam($this->tempDir, 'infection'))) { + return false; + } + + // $originalIniPaths is either empty or has at least one element + if (empty($originalIniPaths[0])) { + array_shift($originalIniPaths); + } + + $content = ''; + $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; + + foreach ($originalIniPaths as $iniPath) { + $content .= preg_replace($regex, ';$1', file_get_contents($iniPath)) . PHP_EOL; + } + + return (bool) @file_put_contents($this->tmpIniPath, $content); + } + + private function setEnvironment(bool $additional): bool + { + if ($additional && !putenv(self::ENV_PHP_INI_SCAN_DIR . '=')) { + return false; + } + + return putenv(self::ENV_TEMP_PHP_CONFIG_PATH . '=' . $this->tmpIniPath); + } +} diff --git a/src/Php/PhpIniHelper.php b/src/Php/PhpIniHelper.php new file mode 100644 index 000000000..74e1403be --- /dev/null +++ b/src/Php/PhpIniHelper.php @@ -0,0 +1,34 @@ +configBuilder = $configBuilder; + + $this->isLoaded = extension_loaded('xdebug'); + $this->envScanDir = (string) getenv(ConfigBuilder::ENV_PHP_INI_SCAN_DIR); + } + + public function check() + { + $args = explode('|', (string) getenv(self::ENV_DISABLE_XDEBUG)); + + if ($this->needsRestart($args[0])) { + if ($this->prepareRestart()) { + $this->restart($this->getCommand()); + } + } + + if (self::RESTART_HANDLE === $args[0]) { + putenv(self::ENV_DISABLE_XDEBUG); + + if (false !== $this->envScanDir) { + // $args[1] contains the original value + if (isset($args[1])) { + putenv(ConfigBuilder::ENV_PHP_INI_SCAN_DIR . '=' . $args[1]); + } else { + putenv(ConfigBuilder::ENV_PHP_INI_SCAN_DIR); + } + } + } + } + + private function needsRestart(string $allow): bool + { + if (PHP_SAPI !== 'cli' || !\defined('PHP_BINARY')) { + return false; + } + + return $this->isLoaded && '' === $allow; + } + + private function prepareRestart(): bool + { + if ($this->tmpIniPath = $this->configBuilder->build()) { + return $this->setEnvironment(); + } + + return false; + } + + /** + * Returns true if the restart environment variables were set + * + * @return bool + */ + private function setEnvironment(): bool + { + return putenv(self::ENV_DISABLE_XDEBUG . '=' . self::RESTART_HANDLE); + } + + /** + * Returns the restart command line + * + * @return string + */ + private function getCommand(): string + { + $params = array_merge( + [PHP_BINARY, '-c', $this->tmpIniPath], + $this->getScriptArgs($_SERVER['argv']) + ); + + return implode(' ', array_map([$this, 'escape'], $params)); + } + + /** + * Returns the restart script arguments, adding --ansi if required + * + * If we are a terminal with color support we must ensure that the --ansi + * option is set, because the restarted output is piped. + * + * @param array $args The argv array + * + * @return array + */ + private function getScriptArgs(array $args): array + { + if (\in_array(['--no-ansi', '--ansi'], $args, true)) { + return $args; + } + + $offset = \count($args) > 1 ? 2 : 1; + array_splice($args, $offset, 0, '--ansi'); + + return $args; + } + + /** + * Escapes a string to be used as a shell argument. + * + * From https://github.com/johnstevenson/winbox-args + * MIT Licensed (c) John Stevenson + * + * @param string $arg The argument to be escaped + * @param bool $meta Additionally escape cmd.exe meta characters + * + * @return string The escaped argument + */ + private function escape(string $arg, bool $meta = true): string + { + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + return escapeshellarg($arg); + } + + $quote = strpbrk($arg, " \t") !== false || $arg === ''; + $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $quotesCount); + + if ($meta) { + $meta = $quotesCount || preg_match('/%[^%]+%/', $arg); + + if (!$meta && !$quote) { + $quote = strpbrk($arg, '^&|<>()') !== false; + } + } + + if ($quote) { + $arg = preg_replace('/(\\\\*)$/', '$1$1', $arg); + $arg = '"' . $arg . '"'; + } + + if ($meta) { + $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg); + } + + return $arg; + } + + /** + * Executes the restarted command then deletes the tmp ini + * + * @param string $command + */ + protected function restart(string $command) + { + passthru($command, $exitCode); + + exit($exitCode); + } +} diff --git a/src/Process/Builder/ProcessBuilder.php b/src/Process/Builder/ProcessBuilder.php index 5390fd740..7309b8f36 100644 --- a/src/Process/Builder/ProcessBuilder.php +++ b/src/Process/Builder/ProcessBuilder.php @@ -14,7 +14,7 @@ use Symfony\Component\Process\Exception\RuntimeException; use Symfony\Component\Process\Process; -class ProcessBuilder +final class ProcessBuilder { /** * @var AbstractTestFrameworkAdapter @@ -32,21 +32,26 @@ public function __construct(AbstractTestFrameworkAdapter $testFrameworkAdapter, $this->timeout = $timeout; } + /** + * Creates process with enabled debugger as test framework is going to use in the code coverage. + * + * @param string $testFrameworkExtraOptions + * + * @return Process + */ public function getProcessForInitialTestRun(string $testFrameworkExtraOptions = ''): Process { - $configPath = $this->testFrameworkAdapter->buildInitialConfigFile(); - - return $this->getProcess($configPath, $testFrameworkExtraOptions); - - // TODO debug why processBuilder does not work with env - // TODO read and add -vvv - /* - $processBuilder = new SymfonyProcessBuilder([ - $this->testFrameworkAdapter->getExecutableCommandLine() - ]); - - return $processBuilder->getProcess(); - */ + return new Process( + $this->testFrameworkAdapter->getExecutableCommandLine( + $this->testFrameworkAdapter->buildInitialConfigFile(), + $testFrameworkExtraOptions, + false + ), + null, + [], + null, + null + ); } /** @@ -59,21 +64,19 @@ public function getProcessForInitialTestRun(string $testFrameworkExtraOptions = */ public function getProcessForMutant(Mutant $mutant, string $testFrameworkExtraOptions = ''): MutantProcess { - $configPath = $this->testFrameworkAdapter->buildMutationConfigFile($mutant); - - $symfonyProcess = $this->getProcess($configPath, $testFrameworkExtraOptions, $this->timeout); - - return new MutantProcess($symfonyProcess, $mutant, $this->testFrameworkAdapter); - } - - private function getProcess(string $configPath, string $testFrameworkExtraOptions, int $timeout = null): Process - { - return new Process( - $this->testFrameworkAdapter->getExecutableCommandLine($configPath, $testFrameworkExtraOptions), + $process = new Process( + $this->testFrameworkAdapter->getExecutableCommandLine( + $this->testFrameworkAdapter->buildMutationConfigFile($mutant), + $testFrameworkExtraOptions + ), null, array_replace($_ENV, $_SERVER), null, - $timeout + $this->timeout ); + + $process->inheritEnvironmentVariables(); + + return new MutantProcess($process, $mutant, $this->testFrameworkAdapter); } } diff --git a/src/Process/ExecutableFinder/PhpExecutableFinder.php b/src/Process/ExecutableFinder/PhpExecutableFinder.php new file mode 100644 index 000000000..6455dd192 --- /dev/null +++ b/src/Process/ExecutableFinder/PhpExecutableFinder.php @@ -0,0 +1,27 @@ +executableFinder = $executableFinder; + $this->phpExecutableFinder = $phpExecutableFinder; $this->initialConfigBuilder = $initialConfigBuilder; $this->mutationConfigBuilder = $mutationConfigBuilder; $this->argumentsAndOptionsBuilder = $argumentsAndOptionsBuilder; @@ -68,14 +69,15 @@ abstract public function getName(): string; * * @param string $configPath * @param string $extraOptions + * @param bool $includePhpArgs * * @return string */ - public function getExecutableCommandLine(string $configPath, string $extraOptions): string + public function getExecutableCommandLine(string $configPath, string $extraOptions, bool $includePhpArgs = true): string { return sprintf( '%s %s', - $this->executableFinder->find(), + $this->phpExecutableFinder->find($includePhpArgs), $this->argumentsAndOptionsBuilder->build($configPath, $extraOptions) ); } @@ -95,7 +97,7 @@ public function getVersion(): string $process = new Process( sprintf( '%s %s', - $this->executableFinder->find(), + $this->phpExecutableFinder->find(), '--version' ) ); diff --git a/src/TestFramework/Factory.php b/src/TestFramework/Factory.php index e97528d13..0ea8cfdcf 100644 --- a/src/TestFramework/Factory.php +++ b/src/TestFramework/Factory.php @@ -22,7 +22,7 @@ use Infection\TestFramework\PhpUnit\Config\XmlConfigurationHelper; use Infection\Utils\VersionParser; -class Factory +final class Factory { /** * @var string @@ -77,7 +77,7 @@ public function __construct( $this->versionParser = $versionParser; } - public function create($adapterName): AbstractTestFrameworkAdapter + public function create(string $adapterName): AbstractTestFrameworkAdapter { if ($adapterName === TestFrameworkTypes::PHPUNIT) { $phpUnitConfigPath = $this->configLocator->locate(TestFrameworkTypes::PHPUNIT); diff --git a/tests/Php/ConfigBuilderTest.php b/tests/Php/ConfigBuilderTest.php new file mode 100644 index 000000000..50da969e6 --- /dev/null +++ b/tests/Php/ConfigBuilderTest.php @@ -0,0 +1,67 @@ +workspace = sys_get_temp_dir() . DIRECTORY_SEPARATOR . microtime(true) . random_int(100, 999); + mkdir($this->workspace, 0777, true); + } + + protected function tearDown() + { + @unlink($this->workspace); + } + + public function test_it_builds_return_existing_path() + { + $builder = new ConfigBuilder(sys_get_temp_dir()); + + $file = $this->workspace . DIRECTORY_SEPARATOR . 'infectionTest'; + + touch($file); + + $this->setEnv($file); + + $this->assertSame($file, $builder->build()); + } + + private function setEnv(string $path) + { + putenv(ConfigBuilder::ENV_TEMP_PHP_CONFIG_PATH . '=' . $path); + } +} diff --git a/tests/Php/Mock/XdebugHandlerMock.php b/tests/Php/Mock/XdebugHandlerMock.php new file mode 100644 index 000000000..9a675fd06 --- /dev/null +++ b/tests/Php/Mock/XdebugHandlerMock.php @@ -0,0 +1,36 @@ +getProperty('isLoaded'); + $prop->setAccessible(true); + $prop->setValue($this, $isLoaded); + + $this->restarted = false; + } + + protected function restart(string $command) + { + $this->restarted = true; + } +} diff --git a/tests/Php/PhpIniHelperTest.php b/tests/Php/PhpIniHelperTest.php new file mode 100644 index 000000000..46918e4c0 --- /dev/null +++ b/tests/Php/PhpIniHelperTest.php @@ -0,0 +1,60 @@ +setEnv($paths); + $this->assertSame($paths, PhpIniHelper::get()); + } + + public function test_it_works_without_loaded_ini() + { + $paths = [ + '', + 'one.ini', + 'two.ini', + ]; + + $this->setEnv($paths); + $this->assertSame($paths, PhpIniHelper::get()); + } + + private function setEnv(array $paths) + { + putenv(PhpIniHelper::ENV_ORIGINALS_PHP_INIS . '=' . implode(PATH_SEPARATOR, $paths)); + } +} diff --git a/tests/Php/XdebugHandlerTest.php b/tests/Php/XdebugHandlerTest.php new file mode 100644 index 000000000..0a178b2ad --- /dev/null +++ b/tests/Php/XdebugHandlerTest.php @@ -0,0 +1,96 @@ + $value) { + if (false !== $value) { + putenv($name.'='.$value); + } else { + putenv($name); + } + } + } + + protected function setUp() + { + putenv(PhpIniHelper::ENV_ORIGINALS_PHP_INIS); + putenv(XdebugHandlerMock::ENV_DISABLE_XDEBUG); + } + + public function test_it_restart_when_loaded() + { + $loaded = true; + + $xdebug = new XdebugHandlerMock($loaded); + $xdebug->check(); + $this->assertTrue($xdebug->restarted); + $this->assertInternalType('string', getenv(PhpIniHelper::ENV_ORIGINALS_PHP_INIS)); + } + + public function test_it_not_restart_when_loaded() + { + $loaded = false; + + $xdebug = new XdebugHandlerMock($loaded); + $xdebug->check(); + $this->assertFalse($xdebug->restarted); + $this->assertFalse(getenv(PhpIniHelper::ENV_ORIGINALS_PHP_INIS)); + } + + public function test_it_not_restart_when_loaded_and_allowed() + { + $loaded = true; + putenv(XdebugHandlerMock::ENV_DISABLE_XDEBUG.'=1'); + + $xdebug = new XdebugHandlerMock($loaded); + $xdebug->check(); + $this->assertFalse($xdebug->restarted); + } + + public function test_env_allow() + { + $loaded = true; + + $xdebug = new XdebugHandlerMock($loaded); + $xdebug->check(); + $expected = XdebugHandlerMock::RESTART_HANDLE; + $this->assertEquals($expected, getenv(XdebugHandlerMock::ENV_DISABLE_XDEBUG)); + + // Mimic restart + $xdebug = new XdebugHandlerMock($loaded); + $xdebug->check(); + $this->assertFalse($xdebug->restarted); + $this->assertFalse(getenv(XdebugHandlerMock::ENV_DISABLE_XDEBUG)); + } +} diff --git a/tests/Process/Builder/ProcessBuilderTest.php b/tests/Process/Builder/ProcessBuilderTest.php index 9fd614984..cdbb6ba02 100644 --- a/tests/Process/Builder/ProcessBuilderTest.php +++ b/tests/Process/Builder/ProcessBuilderTest.php @@ -1,4 +1,10 @@ -workspace = sys_get_temp_dir() . DIRECTORY_SEPARATOR . microtime(true) . random_int(100, 999); + mkdir($this->workspace, 0777, true); + } + + public function test_it_find_temp_php_config() + { + $finder = new PhpExecutableFinder(); + + $tempConfig = $this->workspace . DIRECTORY_SEPARATOR . 'php.ini'; + + touch($tempConfig); + + putenv(ConfigBuilder::ENV_TEMP_PHP_CONFIG_PATH . '=' . $tempConfig); + + $this->assertSame(['-c', $tempConfig], $finder->findArguments()); + } + + public function tearDown() + { + @unlink($this->workspace); + } +}