From 586eb3bb41fb63b42c18fac97f39d5d4d66e9088 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Fri, 5 Aug 2016 11:43:56 +0100 Subject: [PATCH 1/9] Restart with xdebug disabled --- bin/composer | 5 + src/Composer/XdebugHandler.php | 277 ++++++++++++++++++ .../Composer/Test/Mock/XdebugHandlerMock.php | 39 +++ tests/Composer/Test/XdebugHandlerTest.php | 56 ++++ 4 files changed, 377 insertions(+) create mode 100644 src/Composer/XdebugHandler.php create mode 100644 tests/Composer/Test/Mock/XdebugHandlerMock.php create mode 100644 tests/Composer/Test/XdebugHandlerTest.php diff --git a/bin/composer b/bin/composer index 401efb5b0..927ebd1cb 100755 --- a/bin/composer +++ b/bin/composer @@ -7,10 +7,15 @@ if (PHP_SAPI !== 'cli') { require __DIR__.'/../src/bootstrap.php'; +use Composer\XdebugHandler; use Composer\Console\Application; error_reporting(-1); +$xdebug = new XdebugHandler($argv); +$xdebug->check(); +unset($xdebug); + if (function_exists('ini_set')) { @ini_set('display_errors', 1); diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php new file mode 100644 index 000000000..ca2887eb2 --- /dev/null +++ b/src/Composer/XdebugHandler.php @@ -0,0 +1,277 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +/** + * @author John Stevenson + */ +class XdebugHandler +{ + const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG'; + + private $argv; + private $loaded; + private $tmpIni; + private $scanDir; + + /** + * @param array $argv The global argv passed to script + */ + public function __construct(array $argv) + { + $this->argv = $argv; + $this->loaded = extension_loaded('xdebug'); + + $tmp = sys_get_temp_dir(); + $this->tmpIni = $tmp.DIRECTORY_SEPARATOR.'composer-php.ini'; + $this->scanDir = $tmp.DIRECTORY_SEPARATOR.'composer-php-empty'; + } + + /** + * Checks if xdebug is loaded and composer needs to be restarted + * + * If so, then a tmp ini is created with the xdebug ini entry commented out. + * If additional inis have been loaded, these are combined into the tmp ini + * and PHP_INI_SCAN_DIR is set to an empty directory. An environment + * variable is set so that the new process is created only once. + */ + public function check() + { + if (!$this->needsRestart()) { + return; + } + + if ($this->prepareRestart($command)) { + $this->restart($command); + } + } + + /** + * Executes the restarted command + * + * @param string $command + */ + protected function restart($command) + { + passthru($command, $exitCode); + exit($exitCode); + } + + /** + * Returns true if a restart is needed + * + * @return bool + */ + private function needsRestart() + { + if (PHP_SAPI !== 'cli' || !defined('PHP_BINARY')) { + return false; + } + + return !getenv(self::ENV_ALLOW) && $this->loaded; + } + + /** + * Returns true if everything was written for the restart + * + * If any of the following fails (however unlikely) we must return false to + * stop potential recursion: + * - tmp ini file creation + * - environment variable creation + * - tmp scan dir creation + * + * @param null|string $command The command to run, set by method + * + * @return bool + */ + private function prepareRestart(&$command) + { + $iniFiles = array(); + if ($loadedIni = php_ini_loaded_file()) { + $iniFiles[] = $loadedIni; + } + + $additional = $this->getAdditionalInis($iniFiles, $replace); + if ($this->writeTmpIni($iniFiles, $replace)) { + $command = $this->getCommand($additional); + } + + return !empty($command) && putenv(self::ENV_ALLOW.'=1'); + } + + /** + * Writes the temporary ini file, or clears its name if no ini + * + * If there are no ini files, the tmp ini name is + * @param array $iniFiles The php.ini locations + * @param bool $replace Whether we need to modify the files + * + * @return bool False if the tmp ini could not be created + */ + private function writeTmpIni(array $iniFiles, $replace) + { + if (empty($iniFiles)) { + // Unlikely, maybe xdebug was loaded through the -d option. + $this->tmpIni = ''; + return true; + } + + $content = $this->getIniHeader($iniFiles); + foreach ($iniFiles as $file) { + $content .= $this->getIniData($file, $replace); + } + + return @file_put_contents($this->tmpIni, $content); + } + + /** + * Return true if additional inis were loaded + * + * @param array $iniFiles Populated by method + * @param bool $replace Whether we need to modify the files + * + * @return bool + */ + private function getAdditionalInis(array &$iniFiles , &$replace) + { + $replace = true; + + if ($scanned = php_ini_scanned_files()) { + $list = explode(',', $scanned); + + foreach ($list as $file) { + $file = trim($file); + if (preg_match('/xdebug.ini$/', $file)) { + // Skip the file, no need for regex replacing + $replace = false; + } else { + $iniFiles[] = $file; + } + } + } + + return !empty($scanned); + } + + /** + * Returns formatted ini file data + * + * @param string $iniFile The location of the ini file + * @param bool $replace Whether to regex replace content + * + * @return string The ini data + */ + private function getIniData($iniFile, $replace) + { + $data = str_repeat("\n", 3); + $data .= sprintf('; %s%s', $iniFile, PHP_EOL); + $contents = file_get_contents($iniFile); + + if ($replace) { + // Comment out xdebug config + $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; + $data .= preg_replace($regex, ';$1', $contents); + } else { + $data .= $contents; + } + + return $data; + } + + /** + * Returns the command line to restart composer + * + * @param bool $additional Whether additional inis were loaded + * + * @return string The command line + */ + private function getCommand($additional) + { + if ($additional) { + if (!file_exists($this->scanDir) && !@mkdir($this->scanDir, 0777)) { + return; + } + putenv('PHP_INI_SCAN_DIR='.$this->scanDir); + } + + $phpArgs = array(PHP_BINARY, '-c', $this->tmpIni); + $params = array_merge($phpArgs, $this->argv); + + return implode(' ', array_map(array($this, 'escape'), $params)); + } + + /** + * 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($arg, $meta = true) + { + if (!defined('PHP_WINDOWS_VERSION_BUILD')) { + return escapeshellarg($arg); + } + + $quote = strpbrk($arg, " \t") !== false || $arg === ''; + $arg = preg_replace('/(\\\\*)"/', '$1$1\\"', $arg, -1, $dquotes); + + if ($meta) { + $meta = $dquotes || 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; + } + + /** + * Returns the location of the original ini data used. + * + * @param array $iniFiles loaded php.ini locations + * + * @return string + */ + private function getIniHeader($iniFiles) + { + $ini = implode(PHP_EOL.'; ', $iniFiles); + $header = << + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test\Mock; + +use Composer\XdebugHandler; + +class XdebugHandlerMock extends XdebugHandler +{ + public $command; + public $restarted; + + public function __construct(array $argv, $loaded) + { + parent::__construct($argv); + + $class = new \ReflectionClass(get_parent_class($this)); + $prop = $class->getProperty('loaded'); + $prop->setAccessible(true); + $prop->setValue($this, $loaded); + + $this->command = ''; + $this->restarted = false; + } + + protected function restart($command) + { + $this->command = $command; + $this->restarted = true; + } +} diff --git a/tests/Composer/Test/XdebugHandlerTest.php b/tests/Composer/Test/XdebugHandlerTest.php new file mode 100644 index 000000000..3917896cd --- /dev/null +++ b/tests/Composer/Test/XdebugHandlerTest.php @@ -0,0 +1,56 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Test; + +use Composer\Test\Mock\XdebugHandlerMock as XdebugHandler; + +/** + * @author John Stevenson + */ +class XdebugHandlerTest extends \PHPUnit_Framework_TestCase +{ + protected $argv; + + public function setup() + { + $this->argv = $GLOBALS['argv']; + } + + public function testRestartWhenLoaded() + { + $loaded = true; + + $xdebug = new XdebugHandler($this->argv, $loaded); + $xdebug->check(); + $this->assertTrue($xdebug->restarted || !defined('PHP_BINARY')); + } + + public function testNoRestartWhenNotLoaded() + { + $loaded = false; + + $xdebug = new XdebugHandler($this->argv, $loaded); + $xdebug->check(); + $this->assertFalse($xdebug->restarted); + } + + public function testNoRestartWhenLoadedAndAllowed() + { + $loaded = true; + putenv(XdebugHandler::ENV_ALLOW.'=1'); + + $xdebug = new XdebugHandler($this->argv, $loaded); + $xdebug->check(); + $this->assertFalse($xdebug->restarted); + } +} From cfaa122ade9b5bb474a71cefd7dcbf2a713c029a Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Wed, 10 Aug 2016 19:08:31 +0100 Subject: [PATCH 2/9] Ensure colored output if restarted from a capable terminal --- src/Composer/XdebugHandler.php | 52 ++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php index ca2887eb2..54fb11447 100644 --- a/src/Composer/XdebugHandler.php +++ b/src/Composer/XdebugHandler.php @@ -112,7 +112,9 @@ class XdebugHandler /** * Writes the temporary ini file, or clears its name if no ini * - * If there are no ini files, the tmp ini name is + * If there are no ini files, the tmp ini name is cleared so that + * an empty value is passed with the -c option. + * * @param array $iniFiles The php.ini locations * @param bool $replace Whether we need to modify the files * @@ -205,11 +207,57 @@ class XdebugHandler } $phpArgs = array(PHP_BINARY, '-c', $this->tmpIni); - $params = array_merge($phpArgs, $this->argv); + $params = array_merge($phpArgs, $this->getScriptArgs($this->argv)); return implode(' ', array_map(array($this, 'escape'), $params)); } + /** + * Returns the restart script arguments, adding --ansi if required + * + * @param array $args The argv array + * + * @return array + */ + private function getScriptArgs(array $args) + { + if (in_array('--no-ansi', $args) || in_array('--ansi', $args)) { + return $args; + } + + if ($this->isColorTerminal()) { + $offset = count($args) > 1 ? 2: 1; + array_splice($args, $offset, 0, '--ansi'); + } + + return $args; + } + + /** + * Returns whether we are a terminal and have colour capabilities + * + * @return bool + */ + private function isColorTerminal() + { + if (function_exists('posix_isatty')) { + $result = posix_isatty(STDOUT); + } else { + // See if STDOUT is a character device (S_IFCHR) + $stat = fstat(STDOUT); + $result = ($stat['mode'] & 0170000) === 0020000; + } + + if ($result && defined('PHP_WINDOWS_VERSION_BUILD')) { + $result = 0 >= version_compare('10.0.10586', PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return $result; + } + /** * Escapes a string to be used as a shell argument. * From 89d6e6f0bdc8659c76209820122bd27cf76a20e3 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sat, 3 Sep 2016 18:50:11 +0100 Subject: [PATCH 3/9] Fix Windows anniversary update change --- src/Composer/XdebugHandler.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php index 54fb11447..63647b40d 100644 --- a/src/Composer/XdebugHandler.php +++ b/src/Composer/XdebugHandler.php @@ -248,9 +248,8 @@ class XdebugHandler $result = ($stat['mode'] & 0170000) === 0020000; } - if ($result && defined('PHP_WINDOWS_VERSION_BUILD')) { - $result = 0 >= version_compare('10.0.10586', PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD) - || false !== getenv('ANSICON') + if (defined('PHP_WINDOWS_VERSION_BUILD') && $result) { + $result = false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); } From fe861ac36564c727b9851aeae573d41d388054c6 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sat, 3 Sep 2016 18:51:26 +0100 Subject: [PATCH 4/9] Ensure consistent eols in tmp ini --- src/Composer/XdebugHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php index 63647b40d..1b445d2b2 100644 --- a/src/Composer/XdebugHandler.php +++ b/src/Composer/XdebugHandler.php @@ -175,7 +175,7 @@ class XdebugHandler */ private function getIniData($iniFile, $replace) { - $data = str_repeat("\n", 3); + $data = str_repeat(PHP_EOL, 3); $data .= sprintf('; %s%s', $iniFile, PHP_EOL); $contents = file_get_contents($iniFile); @@ -318,7 +318,7 @@ class XdebugHandler ; Make any changes there because this data will not be used again. EOD; - $header .= str_repeat("\n", 50); + $header .= str_repeat(PHP_EOL, 50); return $header; } } From e381abeec05bdcd7fb7695616aa42ddb2155e091 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Sat, 3 Sep 2016 19:00:41 +0100 Subject: [PATCH 5/9] Simplify argv handling --- bin/composer | 2 +- src/Composer/XdebugHandler.php | 9 +++------ tests/Composer/Test/Mock/XdebugHandlerMock.php | 4 ++-- tests/Composer/Test/XdebugHandlerTest.php | 13 +++---------- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/bin/composer b/bin/composer index 927ebd1cb..c2b428e06 100755 --- a/bin/composer +++ b/bin/composer @@ -12,7 +12,7 @@ use Composer\Console\Application; error_reporting(-1); -$xdebug = new XdebugHandler($argv); +$xdebug = new XdebugHandler(); $xdebug->check(); unset($xdebug); diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php index 1b445d2b2..59c0f2574 100644 --- a/src/Composer/XdebugHandler.php +++ b/src/Composer/XdebugHandler.php @@ -19,19 +19,16 @@ class XdebugHandler { const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG'; - private $argv; private $loaded; private $tmpIni; private $scanDir; /** - * @param array $argv The global argv passed to script + * Constructor */ - public function __construct(array $argv) + public function __construct() { - $this->argv = $argv; $this->loaded = extension_loaded('xdebug'); - $tmp = sys_get_temp_dir(); $this->tmpIni = $tmp.DIRECTORY_SEPARATOR.'composer-php.ini'; $this->scanDir = $tmp.DIRECTORY_SEPARATOR.'composer-php-empty'; @@ -207,7 +204,7 @@ class XdebugHandler } $phpArgs = array(PHP_BINARY, '-c', $this->tmpIni); - $params = array_merge($phpArgs, $this->getScriptArgs($this->argv)); + $params = array_merge($phpArgs, $this->getScriptArgs($_SERVER['argv'])); return implode(' ', array_map(array($this, 'escape'), $params)); } diff --git a/tests/Composer/Test/Mock/XdebugHandlerMock.php b/tests/Composer/Test/Mock/XdebugHandlerMock.php index bf462b318..612dd7bf9 100644 --- a/tests/Composer/Test/Mock/XdebugHandlerMock.php +++ b/tests/Composer/Test/Mock/XdebugHandlerMock.php @@ -18,9 +18,9 @@ class XdebugHandlerMock extends XdebugHandler public $command; public $restarted; - public function __construct(array $argv, $loaded) + public function __construct($loaded) { - parent::__construct($argv); + parent::__construct(); $class = new \ReflectionClass(get_parent_class($this)); $prop = $class->getProperty('loaded'); diff --git a/tests/Composer/Test/XdebugHandlerTest.php b/tests/Composer/Test/XdebugHandlerTest.php index 3917896cd..860c173de 100644 --- a/tests/Composer/Test/XdebugHandlerTest.php +++ b/tests/Composer/Test/XdebugHandlerTest.php @@ -19,18 +19,11 @@ use Composer\Test\Mock\XdebugHandlerMock as XdebugHandler; */ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase { - protected $argv; - - public function setup() - { - $this->argv = $GLOBALS['argv']; - } - public function testRestartWhenLoaded() { $loaded = true; - $xdebug = new XdebugHandler($this->argv, $loaded); + $xdebug = new XdebugHandler($loaded); $xdebug->check(); $this->assertTrue($xdebug->restarted || !defined('PHP_BINARY')); } @@ -39,7 +32,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase { $loaded = false; - $xdebug = new XdebugHandler($this->argv, $loaded); + $xdebug = new XdebugHandler($loaded); $xdebug->check(); $this->assertFalse($xdebug->restarted); } @@ -49,7 +42,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase $loaded = true; putenv(XdebugHandler::ENV_ALLOW.'=1'); - $xdebug = new XdebugHandler($this->argv, $loaded); + $xdebug = new XdebugHandler($loaded); $xdebug->check(); $this->assertFalse($xdebug->restarted); } From 4249bd1456f4f65e331b551a60b62156bb55d8bd Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Mon, 5 Sep 2016 20:19:12 +0100 Subject: [PATCH 6/9] Code review fix and doc comment tweaks --- src/Composer/XdebugHandler.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php index 59c0f2574..1f9e63aa8 100644 --- a/src/Composer/XdebugHandler.php +++ b/src/Composer/XdebugHandler.php @@ -30,8 +30,8 @@ class XdebugHandler { $this->loaded = extension_loaded('xdebug'); $tmp = sys_get_temp_dir(); - $this->tmpIni = $tmp.DIRECTORY_SEPARATOR.'composer-php.ini'; - $this->scanDir = $tmp.DIRECTORY_SEPARATOR.'composer-php-empty'; + $this->tmpIni = $tmp.'/composer-php.ini'; + $this->scanDir = $tmp.'/composer-php-empty'; } /** @@ -134,14 +134,14 @@ class XdebugHandler } /** - * Return true if additional inis were loaded + * Returns true if additional inis were loaded * * @param array $iniFiles Populated by method * @param bool $replace Whether we need to modify the files * * @return bool */ - private function getAdditionalInis(array &$iniFiles , &$replace) + private function getAdditionalInis(array &$iniFiles, &$replace) { $replace = true; @@ -188,11 +188,11 @@ class XdebugHandler } /** - * Returns the command line to restart composer + * Creates the required environment and returns the restart command line * * @param bool $additional Whether additional inis were loaded * - * @return string The command line + * @return string|null The command line or null on failure */ private function getCommand($additional) { @@ -200,7 +200,9 @@ class XdebugHandler if (!file_exists($this->scanDir) && !@mkdir($this->scanDir, 0777)) { return; } - putenv('PHP_INI_SCAN_DIR='.$this->scanDir); + if (!putenv('PHP_INI_SCAN_DIR='.$this->scanDir)) { + return; + } } $phpArgs = array(PHP_BINARY, '-c', $this->tmpIni); From 896d1d71f836295d93c5f755703f7643c1983d04 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Tue, 6 Sep 2016 14:48:37 +0100 Subject: [PATCH 7/9] Use ConsoleOutput to determine color support --- bin/composer | 8 +++-- src/Composer/Console/Application.php | 6 +--- src/Composer/Factory.php | 15 ++++++++ src/Composer/XdebugHandler.php | 35 +++++-------------- .../Composer/Test/Mock/XdebugHandlerMock.php | 8 +++-- tests/Composer/Test/XdebugHandlerTest.php | 34 +++++++++++++++--- 6 files changed, 66 insertions(+), 40 deletions(-) diff --git a/bin/composer b/bin/composer index c2b428e06..ed4979f7b 100755 --- a/bin/composer +++ b/bin/composer @@ -7,12 +7,16 @@ if (PHP_SAPI !== 'cli') { require __DIR__.'/../src/bootstrap.php'; +use Composer\Factory; use Composer\XdebugHandler; use Composer\Console\Application; error_reporting(-1); -$xdebug = new XdebugHandler(); +// Create output for XdebugHandler and Application +$output = Factory::createOutput(); + +$xdebug = new XdebugHandler($output); $xdebug->check(); unset($xdebug); @@ -46,4 +50,4 @@ if (function_exists('ini_set')) { // run the command application $application = new Application(); -$application->run(); +$application->run(null, $output); diff --git a/src/Composer/Console/Application.php b/src/Composer/Console/Application.php index 430f7816c..147c29dc2 100644 --- a/src/Composer/Console/Application.php +++ b/src/Composer/Console/Application.php @@ -18,8 +18,6 @@ use Symfony\Component\Console\Application as BaseApplication; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Formatter\OutputFormatter; use Composer\Command; use Composer\Composer; use Composer\Factory; @@ -96,9 +94,7 @@ class Application extends BaseApplication public function run(InputInterface $input = null, OutputInterface $output = null) { if (null === $output) { - $styles = Factory::createAdditionalStyles(); - $formatter = new OutputFormatter(null, $styles); - $output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter); + $output = Factory::createOutput(); } return parent::run($input, $output); diff --git a/src/Composer/Factory.php b/src/Composer/Factory.php index 4bfae989a..21ac5ac08 100644 --- a/src/Composer/Factory.php +++ b/src/Composer/Factory.php @@ -28,7 +28,9 @@ use Composer\Util\Silencer; use Composer\Plugin\PluginEvents; use Composer\EventDispatcher\Event; use Seld\JsonLint\DuplicateKeyException; +use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Output\ConsoleOutput; use Composer\EventDispatcher\EventDispatcher; use Composer\Autoload\AutoloadGenerator; use Composer\Package\Version\VersionParser; @@ -225,6 +227,19 @@ class Factory ); } + /** + * Creates a ConsoleOutput instance + * + * @return ConsoleOutput + */ + public static function createOutput() + { + $styles = self::createAdditionalStyles(); + $formatter = new OutputFormatter(null, $styles); + + return new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, null, $formatter); + } + /** * @deprecated Use Composer\Repository\RepositoryFactory::defaultRepos instead */ diff --git a/src/Composer/XdebugHandler.php b/src/Composer/XdebugHandler.php index 1f9e63aa8..cdd7e68ec 100644 --- a/src/Composer/XdebugHandler.php +++ b/src/Composer/XdebugHandler.php @@ -12,6 +12,8 @@ namespace Composer; +use Symfony\Component\Console\Output\OutputInterface; + /** * @author John Stevenson */ @@ -19,6 +21,7 @@ class XdebugHandler { const ENV_ALLOW = 'COMPOSER_ALLOW_XDEBUG'; + private $output; private $loaded; private $tmpIni; private $scanDir; @@ -26,8 +29,9 @@ class XdebugHandler /** * Constructor */ - public function __construct() + public function __construct(OutputInterface $output) { + $this->output = $output; $this->loaded = extension_loaded('xdebug'); $tmp = sys_get_temp_dir(); $this->tmpIni = $tmp.'/composer-php.ini'; @@ -214,6 +218,9 @@ class XdebugHandler /** * 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 @@ -224,7 +231,7 @@ class XdebugHandler return $args; } - if ($this->isColorTerminal()) { + if ($this->output->isDecorated()) { $offset = count($args) > 1 ? 2: 1; array_splice($args, $offset, 0, '--ansi'); } @@ -232,30 +239,6 @@ class XdebugHandler return $args; } - /** - * Returns whether we are a terminal and have colour capabilities - * - * @return bool - */ - private function isColorTerminal() - { - if (function_exists('posix_isatty')) { - $result = posix_isatty(STDOUT); - } else { - // See if STDOUT is a character device (S_IFCHR) - $stat = fstat(STDOUT); - $result = ($stat['mode'] & 0170000) === 0020000; - } - - if (defined('PHP_WINDOWS_VERSION_BUILD') && $result) { - $result = false !== getenv('ANSICON') - || 'ON' === getenv('ConEmuANSI') - || 'xterm' === getenv('TERM'); - } - - return $result; - } - /** * Escapes a string to be used as a shell argument. * diff --git a/tests/Composer/Test/Mock/XdebugHandlerMock.php b/tests/Composer/Test/Mock/XdebugHandlerMock.php index 612dd7bf9..7f07a58b4 100644 --- a/tests/Composer/Test/Mock/XdebugHandlerMock.php +++ b/tests/Composer/Test/Mock/XdebugHandlerMock.php @@ -11,17 +11,21 @@ namespace Composer\Test\Mock; +use Composer\Factory; use Composer\XdebugHandler; class XdebugHandlerMock extends XdebugHandler { public $command; public $restarted; + public $output; - public function __construct($loaded) + public function __construct($loaded = null) { - parent::__construct(); + $this->output = Factory::createOutput(); + parent::__construct($this->output); + $loaded = $loaded === null ? true: $loaded; $class = new \ReflectionClass(get_parent_class($this)); $prop = $class->getProperty('loaded'); $prop->setAccessible(true); diff --git a/tests/Composer/Test/XdebugHandlerTest.php b/tests/Composer/Test/XdebugHandlerTest.php index 860c173de..2ada703eb 100644 --- a/tests/Composer/Test/XdebugHandlerTest.php +++ b/tests/Composer/Test/XdebugHandlerTest.php @@ -12,10 +12,13 @@ namespace Composer\Test; -use Composer\Test\Mock\XdebugHandlerMock as XdebugHandler; +use Composer\Test\Mock\XdebugHandlerMock; /** * @author John Stevenson + * + * @backupGlobals disabled + * @runTestsInSeparateProcesses */ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase { @@ -23,7 +26,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase { $loaded = true; - $xdebug = new XdebugHandler($loaded); + $xdebug = new XdebugHandlerMock($loaded); $xdebug->check(); $this->assertTrue($xdebug->restarted || !defined('PHP_BINARY')); } @@ -32,7 +35,7 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase { $loaded = false; - $xdebug = new XdebugHandler($loaded); + $xdebug = new XdebugHandlerMock($loaded); $xdebug->check(); $this->assertFalse($xdebug->restarted); } @@ -40,10 +43,31 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase public function testNoRestartWhenLoadedAndAllowed() { $loaded = true; - putenv(XdebugHandler::ENV_ALLOW.'=1'); + putenv(XdebugHandlerMock::ENV_ALLOW.'=1'); - $xdebug = new XdebugHandler($loaded); + $xdebug = new XdebugHandlerMock($loaded); $xdebug->check(); $this->assertFalse($xdebug->restarted); } + + public function testForceColorSupport() + { + $xdebug = new XdebugHandlerMock(); + $xdebug->output->setDecorated(true); + $xdebug->check(); + + $args = explode(' ', $xdebug->command); + $this->assertTrue(in_array('--ansi', $args) || !defined('PHP_BINARY')); + } + + public function testIgnoreColorSupportIfNoAnsi() + { + $xdebug = new XdebugHandlerMock(); + $xdebug->output->setDecorated(true); + $_SERVER['argv'][] = '--no-ansi'; + $xdebug->check(); + + $args = explode(' ', $xdebug->command); + $this->assertTrue(!in_array('--ansi', $args) || !defined('PHP_BINARY')); + } } From 589b1d6fa2c88747fadd6e103f7e198c7bda83ae Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Tue, 6 Sep 2016 16:16:20 +0100 Subject: [PATCH 8/9] Fix hhvm not running tests in a separate process --- tests/Composer/Test/XdebugHandlerTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Composer/Test/XdebugHandlerTest.php b/tests/Composer/Test/XdebugHandlerTest.php index 2ada703eb..d9875e9a1 100644 --- a/tests/Composer/Test/XdebugHandlerTest.php +++ b/tests/Composer/Test/XdebugHandlerTest.php @@ -16,9 +16,6 @@ use Composer\Test\Mock\XdebugHandlerMock; /** * @author John Stevenson - * - * @backupGlobals disabled - * @runTestsInSeparateProcesses */ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase { @@ -48,6 +45,9 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase $xdebug = new XdebugHandlerMock($loaded); $xdebug->check(); $this->assertFalse($xdebug->restarted); + + // Clear env for subsequent tests + putenv(XdebugHandlerMock::ENV_ALLOW.'=0'); } public function testForceColorSupport() From 0256f62b3b8985c7df162d4105741378beb303c3 Mon Sep 17 00:00:00 2001 From: johnstevenson Date: Tue, 6 Sep 2016 20:17:18 +0100 Subject: [PATCH 9/9] Fix and rationalize tests --- .../Composer/Test/Mock/XdebugHandlerMock.php | 5 +-- tests/Composer/Test/XdebugHandlerTest.php | 31 +++++++++---------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/tests/Composer/Test/Mock/XdebugHandlerMock.php b/tests/Composer/Test/Mock/XdebugHandlerMock.php index 7f07a58b4..65fe07329 100644 --- a/tests/Composer/Test/Mock/XdebugHandlerMock.php +++ b/tests/Composer/Test/Mock/XdebugHandlerMock.php @@ -16,7 +16,6 @@ use Composer\XdebugHandler; class XdebugHandlerMock extends XdebugHandler { - public $command; public $restarted; public $output; @@ -25,19 +24,17 @@ class XdebugHandlerMock extends XdebugHandler $this->output = Factory::createOutput(); parent::__construct($this->output); - $loaded = $loaded === null ? true: $loaded; + $loaded = null === $loaded ? true: $loaded; $class = new \ReflectionClass(get_parent_class($this)); $prop = $class->getProperty('loaded'); $prop->setAccessible(true); $prop->setValue($this, $loaded); - $this->command = ''; $this->restarted = false; } protected function restart($command) { - $this->command = $command; $this->restarted = true; } } diff --git a/tests/Composer/Test/XdebugHandlerTest.php b/tests/Composer/Test/XdebugHandlerTest.php index d9875e9a1..57222fa5a 100644 --- a/tests/Composer/Test/XdebugHandlerTest.php +++ b/tests/Composer/Test/XdebugHandlerTest.php @@ -19,6 +19,8 @@ use Composer\Test\Mock\XdebugHandlerMock; */ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase { + public static $envAllow; + public function testRestartWhenLoaded() { $loaded = true; @@ -45,29 +47,24 @@ class XdebugHandlerTest extends \PHPUnit_Framework_TestCase $xdebug = new XdebugHandlerMock($loaded); $xdebug->check(); $this->assertFalse($xdebug->restarted); - - // Clear env for subsequent tests - putenv(XdebugHandlerMock::ENV_ALLOW.'=0'); } - public function testForceColorSupport() + public static function setUpBeforeClass() { - $xdebug = new XdebugHandlerMock(); - $xdebug->output->setDecorated(true); - $xdebug->check(); - - $args = explode(' ', $xdebug->command); - $this->assertTrue(in_array('--ansi', $args) || !defined('PHP_BINARY')); + self::$envAllow = (bool) getenv(XdebugHandlerMock::ENV_ALLOW); } - public function testIgnoreColorSupportIfNoAnsi() + public static function tearDownAfterClass() { - $xdebug = new XdebugHandlerMock(); - $xdebug->output->setDecorated(true); - $_SERVER['argv'][] = '--no-ansi'; - $xdebug->check(); + if (self::$envAllow) { + putenv(XdebugHandlerMock::ENV_ALLOW.'=1'); + } else { + putenv(XdebugHandlerMock::ENV_ALLOW.'=0'); + } + } - $args = explode(' ', $xdebug->command); - $this->assertTrue(!in_array('--ansi', $args) || !defined('PHP_BINARY')); + protected function setUp() + { + putenv(XdebugHandlerMock::ENV_ALLOW.'=0'); } }