pax_global_header00006660000000000000000000000064135614443060014520gustar00rootroot0000000000000052 comment=a1ade28d76027334c736c73cef906906f84aae0b php-composer-xdebug-handler-1.4.0/000077500000000000000000000000001356144430600170255ustar00rootroot00000000000000php-composer-xdebug-handler-1.4.0/.editorconfig000066400000000000000000000002071356144430600215010ustar00rootroot00000000000000 [*] indent_size = 4 indent_style = space end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true php-composer-xdebug-handler-1.4.0/.gitignore000066400000000000000000000001021356144430600210060ustar00rootroot00000000000000composer.lock phpcs.xml phpunit.xml .phpunit.result.cache vendor/ php-composer-xdebug-handler-1.4.0/.travis.yml000066400000000000000000000014401356144430600211350ustar00rootroot00000000000000language: php git: depth: 5 cache: directories: - $HOME/.composer/cache - vendor jobs: include: - php: 5.3 dist: precise - php: 5.4 dist: trusty - php: 5.5 dist: trusty - php: 5.6 - php: 7.0 - php: 7.1 - php: 7.2 - php: 7.3 - php: 7.4snapshot - php: nightly env: platform=ignore fast_finish: true allow_failures: - php: 7.4snapshot - php: nightly before_install: # disable Xdebug if present - phpenv config-rm xdebug.ini || echo "Xdebug not present" install: - flags="--ansi --prefer-dist --no-interaction --optimize-autoloader --no-suggest --no-progress" - if [ "$platform" == "ignore" ]; then flags="$flags --ignore-platform-reqs"; fi - composer install $flags script: - vendor/bin/phpunit php-composer-xdebug-handler-1.4.0/CHANGELOG.md000066400000000000000000000067341356144430600206500ustar00rootroot00000000000000## [Unreleased] ## [1.4.0] - 2019-11-06 * Added: support for `NO_COLOR` environment variable: https://no-color.org * Added: color support for Hyper terminal: https://github.com/zeit/hyper * Fixed: correct capitalization of Xdebug (apparently). * Fixed: improved handling for uopz extension. ## [1.3.3] - 2019-05-27 * Fixed: add environment changes to `$_ENV` if it is being used. ## [1.3.2] - 2019-01-28 * Fixed: exit call being blocked by uopz extension, resulting in application code running twice. ## [1.3.1] - 2018-11-29 * Fixed: fail restart if `passthru` has been disabled in `disable_functions`. * Fixed: fail restart if an ini file cannot be opened, otherwise settings will be missing. ## [1.3.0] - 2018-08-31 * Added: `setPersistent` method to use environment variables for the restart. * Fixed: improved debugging by writing output to stderr. * Fixed: no restart when `php_ini_scanned_files` is not functional and is needed. ## [1.2.1] - 2018-08-23 * Fixed: fatal error with apc, when using `apc.mmap_file_mask`. ## [1.2.0] - 2018-08-16 * Added: debug information using `XDEBUG_HANDLER_DEBUG`. * Added: fluent interface for setters. * Added: `PhpConfig` helper class for calling PHP sub-processes. * Added: `PHPRC` original value to restart stettings, for use in a restarted process. * Changed: internal procedure to disable ini-scanning, using `-n` command-line option. * Fixed: replaced `escapeshellarg` usage to avoid locale problems. * Fixed: improved color-option handling to respect double-dash delimiter. * Fixed: color-option handling regression from main script changes. * Fixed: improved handling when checking main script. * Fixed: handling for standard input, that never actually did anything. * Fixed: fatal error when ctype extension is not available. ## [1.1.0] - 2018-04-11 * Added: `getRestartSettings` method for calling PHP processes in a restarted process. * Added: API definition and @internal class annotations. * Added: protected `requiresRestart` method for extending classes. * Added: `setMainScript` method for applications that change the working directory. * Changed: private `tmpIni` variable to protected for extending classes. * Fixed: environment variables not available in $_SERVER when restored in the restart. * Fixed: relative path problems caused by Phar::interceptFileFuncs. * Fixed: incorrect handling when script file cannot be found. ## [1.0.0] - 2018-03-08 * Added: PSR3 logging for optional status output. * Added: existing ini settings are merged to catch command-line overrides. * Added: code, tests and other artefacts to decouple from Composer. * Break: the following class was renamed: - `Composer\XdebugHandler` -> `Composer\XdebugHandler\XdebugHandler` [Unreleased]: https://github.com/composer/xdebug-handler/compare/1.4.0...HEAD [1.4.0]: https://github.com/composer/xdebug-handler/compare/1.3.3...1.4.0 [1.3.3]: https://github.com/composer/xdebug-handler/compare/1.3.2...1.3.3 [1.3.2]: https://github.com/composer/xdebug-handler/compare/1.3.1...1.3.2 [1.3.1]: https://github.com/composer/xdebug-handler/compare/1.3.0...1.3.1 [1.3.0]: https://github.com/composer/xdebug-handler/compare/1.2.1...1.3.0 [1.2.1]: https://github.com/composer/xdebug-handler/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/composer/xdebug-handler/compare/1.1.0...1.2.0 [1.1.0]: https://github.com/composer/xdebug-handler/compare/1.0.0...1.1.0 [1.0.0]: https://github.com/composer/xdebug-handler/compare/d66f0d15cb57...1.0.0 php-composer-xdebug-handler-1.4.0/LICENSE000066400000000000000000000020511356144430600200300ustar00rootroot00000000000000MIT License Copyright (c) 2017 Composer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. php-composer-xdebug-handler-1.4.0/README.md000066400000000000000000000307621356144430600203140ustar00rootroot00000000000000# composer/xdebug-handler [![packagist](https://img.shields.io/packagist/v/composer/xdebug-handler.svg)](https://packagist.org/packages/composer/xdebug-handler) [![linux build](https://img.shields.io/travis/composer/xdebug-handler/master.svg?label=linux+build)](https://travis-ci.org/composer/xdebug-handler) [![windows build](https://img.shields.io/appveyor/ci/Seldaek/xdebug-handler/master.svg?label=windows+build)](https://ci.appveyor.com/project/Seldaek/xdebug-handler) ![license](https://img.shields.io/github/license/composer/xdebug-handler.svg) ![php](https://img.shields.io/packagist/php-v/composer/xdebug-handler.svg?colorB=8892BF&label=php) Restart a CLI process without loading the Xdebug extension. Originally written as part of [composer/composer](https://github.com/composer/composer), now extracted and made available as a stand-alone library. ## Installation Install the latest version with: ```bash $ composer require composer/xdebug-handler ``` ## Requirements * PHP 5.3.2 minimum, although functionality is disabled below PHP 5.4.0. Using the latest PHP version is highly recommended. ## Basic Usage ```php use Composer\XdebugHandler\XdebugHandler; $xdebug = new XdebugHandler('myapp'); $xdebug->check(); unset($xdebug); ``` The constructor takes two parameters: #### _$envPrefix_ This is used to create distinct environment variables and is upper-cased and prepended to default base values. The above example enables the use of: - `MYAPP_ALLOW_XDEBUG=1` to override automatic restart and allow Xdebug - `MYAPP_ORIGINAL_INIS` to obtain ini file locations in a restarted process #### _$colorOption_ This optional value is added to the restart command-line and is needed to force color output in a piped child process. Only long-options are supported, for example `--ansi` or `--colors=always` etc. If the original command-line contains an argument that pattern matches this value (for example `--no-ansi`, `--colors=never`) then _$colorOption_ is ignored. If the pattern match ends with `=auto` (for example `--colors=auto`), the argument is replaced by _$colorOption_. Otherwise it is added at either the end of the command-line, or preceding the first double-dash `--` delimiter. ## Advanced Usage * [How it works](#how-it-works) * [Limitations](#limitations) * [Helper methods](#helper-methods) * [Setter methods](#setter-methods) * [Process configuration](#process-configuration) * [Troubleshooting](#troubleshooting) * [Extending the library](#extending-the-library) ### How it works A temporary ini file is created from the loaded (and scanned) ini files, with any references to the Xdebug extension commented out. Current ini settings are merged, so that most ini settings made on the command-line or by the application are included (see [Limitations](#limitations)) * `MYAPP_ALLOW_XDEBUG` is set with internal data to flag and use in the restart. * The command-line and environment are [configured](#process-configuration) for the restart. * The application is restarted in a new process using `passthru`. * The restart settings are stored in the environment. * `MYAPP_ALLOW_XDEBUG` is unset. * The application runs and exits. * The main process exits with the exit code from the restarted process. ### Limitations There are a few things to be aware of when running inside a restarted process. * Extensions set on the command-line will not be loaded. * Ini file locations will be reported as per the restart - see [getAllIniFiles()](#getallinifiles). * Php sub-processes may be loaded with Xdebug enabled - see [Process configuration](#process-configuration). ### Helper methods These static methods provide information from the current process, regardless of whether it has been restarted or not. #### _getAllIniFiles()_ Returns an array of the original ini file locations. Use this instead of calling `php_ini_loaded_file` and `php_ini_scanned_files`, which will report the wrong values in a restarted process. ```php use Composer\XdebugHandler\XdebugHandler; $files = XdebugHandler::getAllIniFiles(); # $files[0] always exists, it could be an empty string $loadedIni = array_shift($files); $scannedInis = $files; ``` These locations are also available in the `MYAPP_ORIGINAL_INIS` environment variable. This is a path-separated string comprising the location returned from `php_ini_loaded_file`, which could be empty, followed by locations parsed from calling `php_ini_scanned_files`. #### _getRestartSettings()_ Returns an array of settings that can be used with PHP [sub-processes](#sub-processes), or null if the process was not restarted. ```php use Composer\XdebugHandler\XdebugHandler; $settings = XdebugHandler::getRestartSettings(); /** * $settings: array (if the current process was restarted, * or called with the settings from a previous restart), or null * * 'tmpIni' => the temporary ini file used in the restart (string) * 'scannedInis' => if there were any scanned inis (bool) * 'scanDir' => the original PHP_INI_SCAN_DIR value (false|string) * 'phprc' => the original PHPRC value (false|string) * 'inis' => the original inis from getAllIniFiles (array) * 'skipped' => the skipped version from getSkippedVersion (string) */ ``` #### _getSkippedVersion()_ Returns the Xdebug version string that was skipped by the restart, or an empty value if there was no restart (or Xdebug is still loaded, perhaps by an extending class restarting for a reason other than removing Xdebug). ```php use Composer\XdebugHandler\XdebugHandler; $version = XdebugHandler::getSkippedVersion(); # $version: '2.6.0' (for example), or an empty string ``` ### Setter methods These methods implement a fluent interface and must be called before the main `check()` method. #### _setLogger($logger)_ Enables the output of status messages to an external PSR3 logger. All messages are reported with either `DEBUG` or `WARNING` log levels. For example (showing the level and message): ``` // Restart overridden DEBUG Checking MYAPP_ALLOW_XDEBUG DEBUG The Xdebug extension is loaded (2.5.0) DEBUG No restart (MYAPP_ALLOW_XDEBUG=1) // Failed restart DEBUG Checking MYAPP_ALLOW_XDEBUG DEBUG The Xdebug extension is loaded (2.5.0) WARNING No restart (Unable to create temp ini file at: ...) ``` Status messages can also be output with `XDEBUG_HANDLER_DEBUG`. See [Troubleshooting](#troubleshooting). #### _setMainScript($script)_ Sets the location of the main script to run in the restart. This is only needed in more esoteric use-cases, or if the `argv[0]` location is inaccessible. The script name `--` is supported for standard input. #### _setPersistent()_ Configures the restart using [persistent settings](#persistent-settings), so that Xdebug is not loaded in any sub-process. Use this method if your application invokes one or more PHP sub-process and the Xdebug extension is not needed. This avoids the overhead of implementing specific [sub-process](#sub-processes) strategies. Alternatively, this method can be used to set up a default _Xdebug-free_ environment which can be changed if a sub-process requires Xdebug, then restored afterwards: ```php function SubProcessWithXdebug() { $phpConfig = new Composer\XdebugHandler\PhpConfig(); # Set the environment to the original configuration $phpConfig->useOriginal(); # run the process with Xdebug loaded ... # Restore Xdebug-free environment $phpConfig->usePersistent(); } ``` ### Process configuration The library offers two strategies to invoke a new PHP process without loading Xdebug, using either _standard_ or _persistent_ settings. Note that this is only important if the application calls a PHP sub-process. #### Standard settings Uses command-line options to remove Xdebug from the new process only. * The -n option is added to the command-line. This tells PHP not to scan for additional inis. * The temporary ini is added to the command-line with the -c option. >_If the new process calls a PHP sub-process, Xdebug will be loaded in that sub-process (unless it implements xdebug-handler, in which case there will be another restart)._ This is the default strategy used in the restart. #### Persistent settings Uses environment variables to remove Xdebug from the new process and persist these settings to any sub-process. * `PHP_INI_SCAN_DIR` is set to an empty string. This tells PHP not to scan for additional inis. * `PHPRC` is set to the temporary ini. >_If the new process calls a PHP sub-process, Xdebug will not be loaded in that sub-process._ This strategy can be used in the restart by calling [setPersistent()](#setpersistent). #### Sub-processes The `PhpConfig` helper class makes it easy to invoke a PHP sub-process (with or without Xdebug loaded), regardless of whether there has been a restart. Each of its methods returns an array of PHP options (to add to the command-line) and sets up the environment for the required strategy. The [getRestartSettings()](#getrestartsettings) method is used internally. * `useOriginal()` - Xdebug will be loaded in the new process. * `useStandard()` - Xdebug will **not** be loaded in the new process - see [standard settings](#standard-settings). * `userPersistent()` - Xdebug will **not** be loaded in the new process - see [persistent settings](#persistent-settings) If there was no restart, an empty options array is returned and the environment is not changed. ```php use Composer\XdebugHandler\PhpConfig; $config = new PhpConfig; $options = $config->useOriginal(); # $options: empty array # environment: PHPRC and PHP_INI_SCAN_DIR set to original values $options = $config->useStandard(); # $options: [-n, -c, tmpIni] # environment: PHPRC and PHP_INI_SCAN_DIR set to original values $options = $config->usePersistent(); # $options: empty array # environment: PHPRC=tmpIni, PHP_INI_SCAN_DIR='' ``` ### Troubleshooting The following environment settings can be used to troubleshoot unexpected behavior: * `XDEBUG_HANDLER_DEBUG=1` Outputs status messages to `STDERR`, if it is defined, irrespective of any PSR3 logger. Each message is prefixed `xdebug-handler[pid]`, where pid is the process identifier. * `XDEBUG_HANDLER_DEBUG=2` As above, but additionally saves the temporary ini file and reports its location in a status message. ### Extending the library The API is defined by classes and their accessible elements that are not annotated as @internal. The main class has two protected methods that can be overridden to provide additional functionality: #### _requiresRestart($isLoaded)_ By default the process will restart if Xdebug is loaded. Extending this method allows an application to decide, by returning a boolean (or equivalent) value. It is only called if `MYAPP_ALLOW_XDEBUG` is empty, so it will not be called in the restarted process (where this variable contains internal data), or if the restart has been overridden. Note that the [setMainScript()](#setmainscriptscript) and [setPersistent()](#setpersistent) setters can be used here, if required. #### _restart($command)_ An application can extend this to modify the temporary ini file, its location given in the `tmpIni` property. New settings can be safely appended to the end of the data, which is `PHP_EOL` terminated. Note that the `$command` parameter is the escaped command-line string that will be used for the new process and must be treated accordingly. Remember to finish with `parent::restart($command)`. #### Example This example demonstrates two ways to extend basic functionality: * To avoid the overhead of spinning up a new process, the restart is skipped if a simple help command is requested. * The application needs write-access to phar files, so it will force a restart if `phar.readonly` is set (regardless of whether Xdebug is loaded) and change this value in the temporary ini file. ```php use Composer\XdebugHandler\XdebugHandler; use MyApp\Command; class MyRestarter extends XdebugHandler { private $required; protected function requiresRestart($isLoaded) { if (Command::isHelp()) { # No need to disable Xdebug for this return false; } $this->required = (bool) ini_get('phar.readonly'); return $isLoaded || $this->required; } protected function restart($command) { if ($this->required) { # Add required ini setting to tmpIni $content = file_get_contents($this->tmpIni); $content .= 'phar.readonly=0'.PHP_EOL; file_put_contents($this->tmpIni, $content); } parent::restart($command); } } ``` ## License composer/xdebug-handler is licensed under the MIT License, see the LICENSE file for details. php-composer-xdebug-handler-1.4.0/appveyor.yml000066400000000000000000000016711356144430600214220ustar00rootroot00000000000000build: false clone_depth: 5 environment: # This sets the PHP version (from Chocolatey) PHPCI_CHOCO_VERSION: 7.3.11 PHPCI_CACHE: C:\tools\phpci PHPCI_PHP: C:\tools\phpci\php PHPCI_COMPOSER: C:\tools\phpci\composer cache: - '%PHPCI_CACHE% -> appveyor.yml' init: - SET PATH=%PHPCI_PHP%;%PHPCI_COMPOSER%;%PATH% - SET COMPOSER_HOME=%PHPCI_COMPOSER%\home - SET COMPOSER_CACHE_DIR=%PHPCI_COMPOSER%\cache - SET COMPOSER_NO_INTERACTION=1 - SET PHP=0 - SET ANSICON=121x90 (121x90) install: - IF EXIST %PHPCI_CACHE% (SET PHP=1) - IF %PHP%==0 cinst php -i -y --version %PHPCI_CHOCO_VERSION% --params "/InstallDir:%PHPCI_PHP%" - IF %PHP%==0 cinst composer -i -y --ia "/DEV=%PHPCI_COMPOSER%" - php -v - IF %PHP%==0 (composer --version) ELSE (composer self-update) - cd %APPVEYOR_BUILD_FOLDER% - composer install --prefer-dist --no-progress test_script: - cd %APPVEYOR_BUILD_FOLDER% - vendor\bin\phpunit --colors=always php-composer-xdebug-handler-1.4.0/composer.json000066400000000000000000000016331356144430600215520ustar00rootroot00000000000000{ "name": "composer/xdebug-handler", "description": "Restarts a process without Xdebug.", "type": "library", "license": "MIT", "keywords": [ "xdebug", "performance" ], "authors": [ { "name": "John Stevenson", "email": "john-stevenson@blueyonder.co.uk" } ], "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/xdebug-handler/issues" }, "require": { "php": "^5.3.2 || ^7.0 || ^8.0", "psr/log": "^1.0" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8" }, "autoload": { "psr-4": { "Composer\\XdebugHandler\\": "src" } }, "autoload-dev": { "psr-4": { "Composer\\XdebugHandler\\": "tests" } }, "scripts": { "test": "phpunit" } } php-composer-xdebug-handler-1.4.0/phpunit.xml.dist000066400000000000000000000012021356144430600221730ustar00rootroot00000000000000 tests src php-composer-xdebug-handler-1.4.0/src/000077500000000000000000000000001356144430600176145ustar00rootroot00000000000000php-composer-xdebug-handler-1.4.0/src/PhpConfig.php000066400000000000000000000030651356144430600222060ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; /** * @author John Stevenson */ class PhpConfig { /** * Use the original PHP configuration * * @return array PHP cli options */ public function useOriginal() { $this->getDataAndReset(); return array(); } /** * Use standard restart settings * * @return array PHP cli options */ public function useStandard() { if ($data = $this->getDataAndReset()) { return array('-n', '-c', $data['tmpIni']); } return array(); } /** * Use environment variables to persist settings * * @return array PHP cli options */ public function usePersistent() { if ($data = $this->getDataAndReset()) { Process::setEnv('PHPRC', $data['tmpIni']); Process::setEnv('PHP_INI_SCAN_DIR', ''); } return array(); } /** * Returns restart data if available and resets the environment * * @return array|null */ private function getDataAndReset() { if ($data = XdebugHandler::getRestartSettings()) { Process::setEnv('PHPRC', $data['phprc']); Process::setEnv('PHP_INI_SCAN_DIR', $data['scanDir']); } return $data; } } php-composer-xdebug-handler-1.4.0/src/Process.php000066400000000000000000000122321356144430600217430ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; /** * Provides utility functions to prepare a child process command-line and set * environment variables in that process. * * @author John Stevenson * @internal */ class Process { /** * Returns an array of parameters, including a color option if required * * A color option is needed because child process output is piped. * * @param array $args The script parameters * @param string $colorOption The long option to force color output * * @return array */ public static function addColorOption(array $args, $colorOption) { if (!$colorOption || in_array($colorOption, $args) || !preg_match('/^--([a-z]+$)|(^--[a-z]+=)/', $colorOption, $matches)) { return $args; } if (isset($matches[2])) { // Handle --color(s)= options if (false !== ($index = array_search($matches[2].'auto', $args))) { $args[$index] = $colorOption; return $args; } elseif (preg_grep('/^'.$matches[2].'/', $args)) { return $args; } } elseif (in_array('--no-'.$matches[1], $args)) { return $args; } // Check for NO_COLOR variable (https://no-color.org/) if (false !== getenv('NO_COLOR')) { return $args; } if (false !== ($index = array_search('--', $args))) { // Position option before double-dash delimiter array_splice($args, $index, 0, $colorOption); } else { $args[] = $colorOption; } 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 * @param bool $module The argument is the module to invoke * * @return string The escaped argument */ public static function escape($arg, $meta = true, $module = false) { if (!defined('PHP_WINDOWS_VERSION_BUILD')) { return "'".str_replace("'", "'\\''", $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; } elseif ($module && !$dquotes && $quote) { $meta = false; } } if ($quote) { $arg = '"'.preg_replace('/(\\\\*)$/', '$1$1', $arg).'"'; } if ($meta) { $arg = preg_replace('/(["^&|<>()%])/', '^$1', $arg); } return $arg; } /** * Returns true if the output stream supports colors * * This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo * terminals via named pipes, so we can only check the environment. * * @param mixed $output A valid CLI output stream * * @return bool */ public static function supportsColor($output) { if ('Hyper' === getenv('TERM_PROGRAM')) { return true; } if (defined('PHP_WINDOWS_VERSION_BUILD')) { return (function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support($output)) || false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'); } if (function_exists('stream_isatty')) { return stream_isatty($output); } if (function_exists('posix_isatty')) { return posix_isatty($output); } $stat = fstat($output); // Check if formatted mode is S_IFCHR return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; } /** * Makes putenv environment changes available in $_SERVER and $_ENV * * @param string $name * @param string|false $value A false value unsets the variable * * @return bool Whether the environment variable was set */ public static function setEnv($name, $value = false) { $unset = false === $value; if (!putenv($unset ? $name : $name.'='.$value)) { return false; } if ($unset) { unset($_SERVER[$name]); } else { $_SERVER[$name] = $value; } // Update $_ENV if it is being used if (false !== stripos((string) ini_get('variables_order'), 'E')) { if ($unset) { unset($_ENV[$name]); } else { $_ENV[$name] = $value; } } return true; } } php-composer-xdebug-handler-1.4.0/src/Status.php000066400000000000000000000100351356144430600216070ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; /** * @author John Stevenson * @internal */ class Status { const ENV_RESTART = 'XDEBUG_HANDLER_RESTART'; const CHECK = 'Check'; const ERROR = 'Error'; const INFO = 'Info'; const NORESTART = 'NoRestart'; const RESTART = 'Restart'; const RESTARTING = 'Restarting'; const RESTARTED = 'Restarted'; private $debug; private $envAllowXdebug; private $loaded; private $logger; private $time; /** * Constructor * * @param string $envAllowXdebug Prefixed _ALLOW_XDEBUG name * @param bool $debug Whether debug output is required */ public function __construct($envAllowXdebug, $debug) { $start = getenv(self::ENV_RESTART); Process::setEnv(self::ENV_RESTART); $this->time = $start ? round((microtime(true) - $start) * 1000) : 0; $this->envAllowXdebug = $envAllowXdebug; $this->debug = $debug && defined('STDERR'); } /** * @param LoggerInterface $logger */ public function setLogger(LoggerInterface $logger) { $this->logger = $logger; } /** * Calls a handler method to report a message * * @param string $op The handler constant * @param null|string $data Data required by the handler */ public function report($op, $data) { if ($this->logger || $this->debug) { call_user_func(array($this, 'report'.$op), $data); } } /** * Outputs a status message * * @param string $text * @param string $level */ private function output($text, $level = null) { if ($this->logger) { $this->logger->log($level ?: LogLevel::DEBUG, $text); } if ($this->debug) { fwrite(STDERR, sprintf('xdebug-handler[%d] %s', getmypid(), $text.PHP_EOL)); } } private function reportCheck($loaded) { $this->loaded = $loaded; $this->output('Checking '.$this->envAllowXdebug); } private function reportError($error) { $this->output(sprintf('No restart (%s)', $error), LogLevel::WARNING); } private function reportInfo($info) { $this->output($info); } private function reportNoRestart() { $this->output($this->getLoadedMessage()); if ($this->loaded) { $text = sprintf('No restart (%s)', $this->getEnvAllow()); if (!getenv($this->envAllowXdebug)) { $text .= ' Allowed by application'; } $this->output($text); } } private function reportRestart() { $this->output($this->getLoadedMessage()); Process::setEnv(self::ENV_RESTART, (string) microtime(true)); } private function reportRestarted() { $loaded = $this->getLoadedMessage(); $text = sprintf('Restarted (%d ms). %s', $this->time, $loaded); $level = $this->loaded ? LogLevel::WARNING : null; $this->output($text, $level); } private function reportRestarting($command) { $text = sprintf('Process restarting (%s)', $this->getEnvAllow()); $this->output($text); $text = 'Running '.$command; $this->output($text); } /** * Returns the _ALLOW_XDEBUG environment variable as name=value * * @return string */ private function getEnvAllow() { return $this->envAllowXdebug.'='.getenv($this->envAllowXdebug); } /** * Returns the Xdebug status and version * * @return string */ private function getLoadedMessage() { $loaded = $this->loaded ? sprintf('loaded (%s)', $this->loaded) : 'not loaded'; return 'The Xdebug extension is '.$loaded; } } php-composer-xdebug-handler-1.4.0/src/XdebugHandler.php000066400000000000000000000403071356144430600230450ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use Psr\Log\LoggerInterface; /** * @author John Stevenson */ class XdebugHandler { const SUFFIX_ALLOW = '_ALLOW_XDEBUG'; const SUFFIX_INIS = '_ORIGINAL_INIS'; const RESTART_ID = 'internal'; const RESTART_SETTINGS = 'XDEBUG_HANDLER_SETTINGS'; const DEBUG = 'XDEBUG_HANDLER_DEBUG'; /** @var string|null */ protected $tmpIni; private static $inRestart; private static $name; private static $skipped; private $cli; private $colorOption; private $debug; private $envAllowXdebug; private $envOriginalInis; private $loaded; private $persistent; private $script; /** @var Status|null */ private $statusWriter; /** * Constructor * * The $envPrefix is used to create distinct environment variables. It is * uppercased and prepended to the default base values. For example 'myapp' * would result in MYAPP_ALLOW_XDEBUG and MYAPP_ORIGINAL_INIS. * * @param string $envPrefix Value used in environment variables * @param string $colorOption Command-line long option to force color output * @throws \RuntimeException If a parameter is invalid */ public function __construct($envPrefix, $colorOption = '') { if (!is_string($envPrefix) || empty($envPrefix) || !is_string($colorOption)) { throw new \RuntimeException('Invalid constructor parameter'); } self::$name = strtoupper($envPrefix); $this->envAllowXdebug = self::$name.self::SUFFIX_ALLOW; $this->envOriginalInis = self::$name.self::SUFFIX_INIS; $this->colorOption = $colorOption; if (extension_loaded('xdebug')) { $ext = new \ReflectionExtension('xdebug'); $this->loaded = $ext->getVersion() ?: 'unknown'; } if ($this->cli = PHP_SAPI === 'cli') { $this->debug = getenv(self::DEBUG); } $this->statusWriter = new Status($this->envAllowXdebug, (bool) $this->debug); } /** * Activates status message output to a PSR3 logger * * @param LoggerInterface $logger * * @return $this */ public function setLogger(LoggerInterface $logger) { $this->statusWriter->setLogger($logger); return $this; } /** * Sets the main script location if it cannot be called from argv * * @param string $script * * @return $this */ public function setMainScript($script) { $this->script = $script; return $this; } /** * Persist the settings to keep Xdebug out of sub-processes * * @return $this */ public function setPersistent() { $this->persistent = true; return $this; } /** * Checks if Xdebug is loaded and the process needs to be restarted * * This behaviour can be disabled by setting the MYAPP_ALLOW_XDEBUG * environment variable to 1. This variable is used internally so that * the restarted process is created only once. */ public function check() { $this->notify(Status::CHECK, $this->loaded); $envArgs = explode('|', (string) getenv($this->envAllowXdebug)); if (empty($envArgs[0]) && $this->requiresRestart((bool) $this->loaded)) { // Restart required $this->notify(Status::RESTART); if ($this->prepareRestart()) { $command = $this->getCommand(); $this->notify(Status::RESTARTING, $command); $this->restart($command); } return; } if (self::RESTART_ID === $envArgs[0] && count($envArgs) === 5) { // Restarting, so unset environment variable and use saved values $this->notify(Status::RESTARTED); Process::setEnv($this->envAllowXdebug); self::$inRestart = true; if (!$this->loaded) { // Skipped version is only set if Xdebug is not loaded self::$skipped = $envArgs[1]; } // Put restart settings in the environment $this->setEnvRestartSettings($envArgs); return; } $this->notify(Status::NORESTART); if ($settings = self::getRestartSettings()) { // Called with existing settings, so sync our settings $this->syncSettings($settings); } } /** * Returns an array of php.ini locations with at least one entry * * The equivalent of calling php_ini_loaded_file then php_ini_scanned_files. * The loaded ini location is the first entry and may be empty. * * @return array */ public static function getAllIniFiles() { if (!empty(self::$name)) { $env = getenv(self::$name.self::SUFFIX_INIS); if (false !== $env) { return explode(PATH_SEPARATOR, $env); } } $paths = array((string) php_ini_loaded_file()); if ($scanned = php_ini_scanned_files()) { $paths = array_merge($paths, array_map('trim', explode(',', $scanned))); } return $paths; } /** * Returns an array of restart settings or null * * Settings will be available if the current process was restarted, or * called with the settings from an existing restart. * * @return array|null */ public static function getRestartSettings() { $envArgs = explode('|', (string) getenv(self::RESTART_SETTINGS)); if (count($envArgs) !== 6 || (!self::$inRestart && php_ini_loaded_file() !== $envArgs[0])) { return; } return array( 'tmpIni' => $envArgs[0], 'scannedInis' => (bool) $envArgs[1], 'scanDir' => '*' === $envArgs[2] ? false : $envArgs[2], 'phprc' => '*' === $envArgs[3] ? false : $envArgs[3], 'inis' => explode(PATH_SEPARATOR, $envArgs[4]), 'skipped' => $envArgs[5], ); } /** * Returns the Xdebug version that triggered a successful restart * * @return string */ public static function getSkippedVersion() { return (string) self::$skipped; } /** * Returns true if Xdebug is loaded, or as directed by an extending class * * @param bool $isLoaded Whether Xdebug is loaded * * @return bool */ protected function requiresRestart($isLoaded) { return $isLoaded; } /** * Allows an extending class to access the tmpIni * * @param string $command */ protected function restart($command) { $this->doRestart($command); } /** * Executes the restarted command then deletes the tmp ini * * @param string $command */ private function doRestart($command) { passthru($command, $exitCode); $this->notify(Status::INFO, 'Restarted process exited '.$exitCode); if ($this->debug === '2') { $this->notify(Status::INFO, 'Temp ini saved: '.$this->tmpIni); } else { @unlink($this->tmpIni); } exit($exitCode); } /** * 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 * * @return bool */ private function prepareRestart() { $error = ''; $iniFiles = self::getAllIniFiles(); $scannedInis = count($iniFiles) > 1; $tmpDir = sys_get_temp_dir(); if (!$this->cli) { $error = 'Unsupported SAPI: '.PHP_SAPI; } elseif (!defined('PHP_BINARY')) { $error = 'PHP version is too old: '.PHP_VERSION; } elseif (!$this->checkConfiguration($info)) { $error = $info; } elseif (!$this->checkScanDirConfig()) { $error = 'PHP version does not report scanned inis: '.PHP_VERSION; } elseif (!$this->checkMainScript()) { $error = 'Unable to access main script: '.$this->script; } elseif (!$this->writeTmpIni($iniFiles, $tmpDir, $error)) { $error = $error ?: 'Unable to create temp ini file at: '.$tmpDir; } elseif (!$this->setEnvironment($scannedInis, $iniFiles)) { $error = 'Unable to set environment variables'; } if ($error) { $this->notify(Status::ERROR, $error); } return empty($error); } /** * Returns true if the tmp ini file was written * * @param array $iniFiles All ini files used in the current process * @param string $tmpDir The system temporary directory * @param string $error Set by method if ini file cannot be read * * @return bool */ private function writeTmpIni(array $iniFiles, $tmpDir, &$error) { if (!$this->tmpIni = @tempnam($tmpDir, '')) { return false; } // $iniFiles has at least one item and it may be empty if (empty($iniFiles[0])) { array_shift($iniFiles); } $content = ''; $regex = '/^\s*(zend_extension\s*=.*xdebug.*)$/mi'; foreach ($iniFiles as $file) { // Check for inaccessible ini files if (!$data = @file_get_contents($file)) { $error = 'Unable to read ini: '.$file; return false; } $content .= preg_replace($regex, ';$1', $data).PHP_EOL; } // Merge loaded settings into our ini content, if it is valid if ($config = parse_ini_string($content)) { $loaded = ini_get_all(null, false); $content .= $this->mergeLoadedConfig($loaded, $config); } // Work-around for https://bugs.php.net/bug.php?id=75932 $content .= 'opcache.enable_cli=0'.PHP_EOL; return @file_put_contents($this->tmpIni, $content); } /** * Returns the restart command line * * @return string */ private function getCommand() { $php = array(PHP_BINARY); $args = array_slice($_SERVER['argv'], 1); if (!$this->persistent) { // Use command-line options array_push($php, '-n', '-c', $this->tmpIni); } if (defined('STDOUT') && Process::supportsColor(STDOUT)) { $args = Process::addColorOption($args, $this->colorOption); } $args = array_merge($php, array($this->script), $args); $cmd = Process::escape(array_shift($args), true, true); foreach ($args as $arg) { $cmd .= ' '.Process::escape($arg); } return $cmd; } /** * Returns true if the restart environment variables were set * * No need to update $_SERVER since this is set in the restarted process. * * @param bool $scannedInis Whether there were scanned ini files * @param array $iniFiles All ini files used in the current process * * @return bool */ private function setEnvironment($scannedInis, array $iniFiles) { $scanDir = getenv('PHP_INI_SCAN_DIR'); $phprc = getenv('PHPRC'); // Make original inis available to restarted process if (!putenv($this->envOriginalInis.'='.implode(PATH_SEPARATOR, $iniFiles))) { return false; } if ($this->persistent) { // Use the environment to persist the settings if (!putenv('PHP_INI_SCAN_DIR=') || !putenv('PHPRC='.$this->tmpIni)) { return false; } } // Flag restarted process and save values for it to use $envArgs = array( self::RESTART_ID, $this->loaded, (int) $scannedInis, false === $scanDir ? '*' : $scanDir, false === $phprc ? '*' : $phprc, ); return putenv($this->envAllowXdebug.'='.implode('|', $envArgs)); } /** * Logs status messages * * @param string $op Status handler constant * @param null|string $data Optional data */ private function notify($op, $data = null) { $this->statusWriter->report($op, $data); } /** * Returns default, changed and command-line ini settings * * @param array $loadedConfig All current ini settings * @param array $iniConfig Settings from user ini files * * @return string */ private function mergeLoadedConfig(array $loadedConfig, array $iniConfig) { $content = ''; foreach ($loadedConfig as $name => $value) { // Value will either be null, string or array (HHVM only) if (!is_string($value) || strpos($name, 'xdebug') === 0 || $name === 'apc.mmap_file_mask') { continue; } if (!isset($iniConfig[$name]) || $iniConfig[$name] !== $value) { // Double-quote escape each value $content .= $name.'="'.addcslashes($value, '\\"').'"'.PHP_EOL; } } return $content; } /** * Returns true if the script name can be used * * @return bool */ private function checkMainScript() { if (null !== $this->script) { // Allow an application to set -- for standard input return file_exists($this->script) || '--' === $this->script; } if (file_exists($this->script = $_SERVER['argv'][0])) { return true; } // Use a backtrace to resolve Phar and chdir issues $options = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_IGNORE_ARGS : false; $trace = debug_backtrace($options); if (($main = end($trace)) && isset($main['file'])) { return file_exists($this->script = $main['file']); } return false; } /** * Adds restart settings to the environment * * @param string $envArgs */ private function setEnvRestartSettings($envArgs) { $settings = array( php_ini_loaded_file(), $envArgs[2], $envArgs[3], $envArgs[4], getenv($this->envOriginalInis), self::$skipped, ); Process::setEnv(self::RESTART_SETTINGS, implode('|', $settings)); } /** * Syncs settings and the environment if called with existing settings * * @param array $settings */ private function syncSettings(array $settings) { if (false === getenv($this->envOriginalInis)) { // Called by another app, so make original inis available Process::setEnv($this->envOriginalInis, implode(PATH_SEPARATOR, $settings['inis'])); } self::$skipped = $settings['skipped']; $this->notify(Status::INFO, 'Process called with existing restart settings'); } /** * Returns true if there are scanned inis and PHP is able to report them * * php_ini_scanned_files will fail when PHP_CONFIG_FILE_SCAN_DIR is empty. * Fixed in 7.1.13 and 7.2.1 * * @return bool */ private function checkScanDirConfig() { return !(getenv('PHP_INI_SCAN_DIR') && !PHP_CONFIG_FILE_SCAN_DIR && (PHP_VERSION_ID < 70113 || PHP_VERSION_ID === 70200)); } /** * Returns true if there are no known configuration issues * * @param string $info Set by method */ private function checkConfiguration(&$info) { if (false !== strpos(ini_get('disable_functions'), 'passthru')) { $info = 'passthru function is disabled'; return false; } if (extension_loaded('uopz') && !ini_get('uopz.disable')) { // uopz works at opcode level and disables exit calls if (function_exists('uopz_allow_exit')) { @uopz_allow_exit(true); } else { $info = 'uopz extension is not compatible'; return false; } } return true; } } php-composer-xdebug-handler-1.4.0/tests/000077500000000000000000000000001356144430600201675ustar00rootroot00000000000000php-composer-xdebug-handler-1.4.0/tests/ClassTest.php000066400000000000000000000034671356144430600226170ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use Composer\XdebugHandler\Helpers\Logger; use PHPUnit\Framework\TestCase; /** * This class does not need to extend Helpers\BaseTestCase */ class ClassTest extends TestCase { public function testConstructorThrowsOnEmptyEnvPrefix() { $this->setException('RuntimeException'); new XdebugHandler(''); } public function testConstructorThrowsOnInvalidEnvPrefix() { $this->setException('RuntimeException'); new XdebugHandler(array('name')); } public function testConstructorThrowsOnInvalidColorOption() { $this->setException('RuntimeException'); new XdebugHandler('test', false); } /** * @dataProvider setterProvider */ public function testSettersAreFluent($setter, $value) { $xdebug = new XdebugHandler('myapp'); $params = null !== $value ? array($value) : array(); $result = call_user_func_array(array($xdebug, $setter), $params); $this->assertInstanceOf(get_class($xdebug), $result); } public function setterProvider() { // $setter, $value return array( 'setLogger' => array('setLogger', new Logger()), 'setMainScript' => array('setMainScript', '--'), 'setPersistent' => array('setPersistent', null), ); } private function setException($exception) { if (!method_exists($this, 'expectException')) { $this->setExpectedException($exception); } else { $this->expectException($exception); } } } php-composer-xdebug-handler-1.4.0/tests/ColourOptionTest.php000066400000000000000000000075251356144430600242050ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use PHPUnit\Framework\TestCase; /** * This class does not need to extend Helpers\BaseTestCase */ class ColorOptionTest extends TestCase { private static $nocolor; /** * Saves the NO_COLOR environment variable * * @beforeClass */ public static function beforeClass() { self::$nocolor = getenv('NO_COLOR'); } /** * Restores the original NO_COLOR environment variable * * @afterClass */ public static function afterClass() { $value = false !== self::$nocolor ? '='.self::$nocolor : ''; putenv('NO_COLOR'.$value); } /** * Unsets the NO_COLOR environment variable for each test * * @before */ public function setUpEnvironment() { putenv('NO_COLOR'); } /** * Tests that a colorOption is added to the arguments * * @dataProvider neededProvider */ public function testOptionNeeded($args, $colorOption, $expected) { $result = Process::addColorOption($args, $colorOption); $this->assertSame($expected, implode(' ', $result)); } /** * Tests that a colorOption is not added if NO_COLOR is set * * @dataProvider neededProvider */ public function testOptionNeededNoColor($args, $colorOption, $unused) { putenv('NO_COLOR=1'); $result = Process::addColorOption($args, $colorOption); $this->assertSame($args, $result); } public function neededProvider() { // $args, $colorOption, $expected return array( 'simple' => array(array('--option', 'param'), '--xxx', '--option param --xxx'), 'complex' => array(array('--option', 'param'), '--xxx=yyy', '--option param --xxx=yyy'), 'position' => array(array('--option', '--', 'param'), '--xxx', '--option --xxx -- param'), ); } /** * Tests that a colorOption is not added to the arguments because it matches * an existing argument. * * @dataProvider notNeededProvider */ public function testOptionNotNeeded($existing, $colorOption) { $args = array($existing, '--option', 'param'); $result = Process::addColorOption($args, $colorOption); $this->assertContains($existing, $result); $this->assertNotContains($colorOption, $result); } public function notNeededProvider() { // $existing, $colorOption return array( 'simple' => array('--no-xxx', '--xxx'), 'complex' => array('--xxx=zzz', '--xxx=yyy'), ); } /** * Tests that a colorOption is not added to the arguments because it does * not match the required format. * * @dataProvider notMatchedProvider */ public function testOptionNotMatched($colorOption) { $args = array('--option', 'param'); $result = Process::addColorOption($args, $colorOption); $this->assertNotContains($colorOption, $result); } public function notMatchedProvider() { // $colorOption return array( 'simple1' => array('xxx'), 'simple2' => array('-xxx'), 'complex1' => array('xxx=yyy'), 'complex2' => array('-xxx=yyy'), ); } /** * Tests that a colorOption matching xxx=auto is replaced. */ public function testOptionReplaced() { $args = array('--xxx=auto', 'param'); $colorOption = '--xxx=always'; $result = Process::addColorOption($args, $colorOption); $this->assertSame('--xxx=always param', implode(' ', $result)); } } php-composer-xdebug-handler-1.4.0/tests/EnvironmentTest.php000066400000000000000000000064021356144430600240460ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use Composer\XdebugHandler\Helpers\BaseTestCase; use Composer\XdebugHandler\Helpers\EnvHelper; use Composer\XdebugHandler\Mocks\PartialMock; /** * We use PHP_BINARY which only became available in PHP 5.4 * * @requires PHP 5.4 */ class EnvironmentTest extends BaseTestCase { /** * Tests that the _ALLOW_XDEBUG environment variable is correctly formatted * for use in the restarted process. * * @param callable $iniFunc IniHelper method to use * @param mixed $scanDir Initial value for PHP_INI_SCAN_DIR * @param mixed $phprc Initial value for PHPRC * * @dataProvider envAllowBeforeProvider */ public function testEnvAllowBeforeRestart($iniFunc, $scanDir, $phprc) { if ($message = EnvHelper::shouldSkipTest($scanDir)) { $this->markTestSkipped($message); } $ini = EnvHelper::setInis($iniFunc, $scanDir, $phprc); $loaded = true; PartialMock::createAndCheck($loaded); $args = array( PartialMock::RESTART_ID, PartialMock::TEST_VERSION, $ini->hasScannedInis() ? '1' : '0', false !== $scanDir ? $scanDir : '*', false !== $phprc ? $phprc : '*', ); $expected = implode('|', $args); $this->assertSame($expected, getenv(PartialMock::ALLOW_XDEBUG)); } public function envAllowBeforeProvider() { return EnvHelper::dataProvider(); } /** * Tests that environment variables are correctly set for a restart. * * @param callable $iniFunc IniHelper method to use * @param false|string $scanDir Initial value for PHP_INI_SCAN_DIR * @param false|string $phprc Initial value for PHPRC * @param bool $standard If this is a standard restart * * @dataProvider environmentProvider */ public function testEnvironmentBeforeRestart($iniFunc, $scanDir, $phprc, $standard) { if ($message = EnvHelper::shouldSkipTest($scanDir)) { $this->markTestSkipped($message); } $ini = EnvHelper::setInis($iniFunc, $scanDir, $phprc); $loaded = true; $settings = $standard ? array() : array('setPersistent' => array()); $xdebug = PartialMock::createAndCheck($loaded, null, $settings); if (!$standard) { $scanDir = ''; $phprc = $xdebug->getTmpIni(); } $strategy = $standard ? 'standard' : 'persistent'; $this->assertSame($scanDir, getenv('PHP_INI_SCAN_DIR'), $strategy.' scanDir'); $this->assertSame($phprc, getenv('PHPRC'), $strategy.' phprc'); } public function environmentProvider() { // $iniFunc, $scanDir, $phprc, $standard (added below) $data = EnvHelper::dataProvider(); $result = array(); foreach ($data as $test => $params) { $result[$test.' standard'] = array_merge($params, array(true)); $result[$test.' persistent'] = array_merge($params, array(false)); } return $result; } } php-composer-xdebug-handler-1.4.0/tests/Fixtures/000077500000000000000000000000001356144430600220005ustar00rootroot00000000000000php-composer-xdebug-handler-1.4.0/tests/Fixtures/php.ini000066400000000000000000000003061356144430600232670ustar00rootroot00000000000000;;;;;;;;;;;;;;;;;;; ; About php.ini ; ;;;;;;;;;;;;;;;;;;; ; ; This file is used for test mocking xdebug-handler, it is not read by php. ; date.timezone = UTC zend_extension = /path/to/xdebug.so php-composer-xdebug-handler-1.4.0/tests/Fixtures/scandir/000077500000000000000000000000001356144430600234235ustar00rootroot00000000000000php-composer-xdebug-handler-1.4.0/tests/Fixtures/scandir/scan-one.ini000066400000000000000000000001201356144430600256200ustar00rootroot00000000000000; ; This file is used for test mocking xdebug-handler, it is not read by php. ; php-composer-xdebug-handler-1.4.0/tests/Fixtures/scandir/scan-two.ini000066400000000000000000000002011356144430600256500ustar00rootroot00000000000000; ; This file is used for test mocking xdebug-handler, it is not read by php. ; zend_extension= php_xdebug-2.5.0-7.1-vc14.dll php-composer-xdebug-handler-1.4.0/tests/Helpers/000077500000000000000000000000001356144430600215715ustar00rootroot00000000000000php-composer-xdebug-handler-1.4.0/tests/Helpers/BaseTestCase.php000066400000000000000000000102171356144430600246110ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler\Helpers; use Composer\XdebugHandler\Mocks\CoreMock; use Composer\XdebugHandler\XdebugHandler; use PHPUnit\Framework\TestCase; /** * BaseTestCase provides the framework for mock tests by ensuring that core * environment variables are unset before each test. It also provides two helper * methods to check the state of restarted and non-restarted processes. */ abstract class BaseTestCase extends TestCase { private static $env = array(); private static $argv = array(); private static $names = array( CoreMock::ALLOW_XDEBUG, CoreMock::ORIGINAL_INIS, 'PHP_INI_SCAN_DIR', 'PHPRC', XdebugHandler::RESTART_SETTINGS, ); /** * Saves the current environment and argv state * * @beforeClass */ public static function beforeClass() { foreach (self::$names as $name) { self::$env[$name] = getenv($name); // Note $_SERVER will already match } self::$argv = $_SERVER['argv']; } /** * Restores the original environment and argv state * * @afterClass */ public static function afterClass() { foreach (self::$env as $name => $value) { if (false !== $value) { putenv($name.'='.$value); $_SERVER[$name] = $value; } else { putenv($name); unset($_SERVER[$name]); } } $_SERVER['argv'] = self::$argv; } /** * Unsets environment variables for each test and restores argv * * @before */ public function setUpEnvironment() { foreach (self::$names as $name) { putenv($name); unset($_SERVER[$name]); } $_SERVER['argv'] = self::$argv; } /** * Provides basic assertions for a restarted process * * @param mixed $xdebug */ protected function checkRestart($xdebug) { // We must have been restarted $this->assertTrue($xdebug->restarted); // Env ALLOW_XDEBUG must be unset $this->assertSame(false, getenv(CoreMock::ALLOW_XDEBUG)); $this->assertSame(false, isset($_SERVER[CoreMock::ALLOW_XDEBUG])); // Env ORIGINAL_INIS must be set and be a string $this->assertTrue(is_string(getenv(CoreMock::ORIGINAL_INIS))); $this->assertSame(true, isset($_SERVER[CoreMock::ORIGINAL_INIS])); // Skipped version must only be reported if it was unloaded in the restart if (!$xdebug->parentLoaded || $xdebug->getProperty('loaded')) { $version = ''; } else { $version = CoreMock::TEST_VERSION; } $this->assertSame($version, $xdebug::getSkippedVersion()); // Env RESTART_SETTINGS must be set and be a string $this->assertTrue(is_string(getenv(CoreMock::RESTART_SETTINGS))); $this->assertSame(true, isset($_SERVER[CoreMock::RESTART_SETTINGS])); // Restart settings must be an array $this->assertTrue(is_array($xdebug::getRestartSettings())); } /** * Provides basic assertions for a non-restarted process * * @param mixed $xdebug */ protected function checkNoRestart($xdebug) { // We must not have been restarted $this->assertFalse($xdebug->restarted); // Env ORIGINAL_INIS must not be set $this->assertSame(false, getenv(CoreMock::ORIGINAL_INIS)); $this->assertSame(false, isset($_SERVER[CoreMock::ORIGINAL_INIS])); // Skipped version must be an empty string $this->assertSame('', $xdebug::getSkippedVersion()); // Env RESTART_SETTINGS must not be set $this->assertSame(false, getenv(CoreMock::RESTART_SETTINGS)); $this->assertSame(false, isset($_SERVER[CoreMock::RESTART_SETTINGS])); // Restart settings must be null $this->assertNull($xdebug::getRestartSettings()); } } php-composer-xdebug-handler-1.4.0/tests/Helpers/EnvHelper.php000066400000000000000000000047441356144430600242030ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler\Helpers; /** * This helper class provides a central data provider that uses IniHelper to * mock environment settings. */ class EnvHelper { /** * Mock the environment * * @param callable $iniFunc IniHelper method to use * @param mixed $scanDir Initial value for PHP_INI_SCAN_DIR * @param mixed $phprc Initial value for PHPRC * * @return IniHelper */ public static function setInis($iniFunc, $scanDir, $phprc) { $ini = new IniHelper(array($scanDir, $phprc)); call_user_func(array($ini, $iniFunc)); return $ini; } public static function dataProvider() { $ini = new IniHelper(); $loaded = $ini->getLoadedIni(); $scanDir = $ini->getScanDir(); // $iniFunc, $scanDir, $phprc return array( 'loaded false myini' => array('setLoadedIni', false, '/my.ini'), 'loaded empty false' => array('setLoadedIni', '', false), 'scanned false file' => array('setScannedInis', false, $loaded), 'scanned dir false' => array('setScannedInis', $scanDir, false), ); } /** * Checks if php_ini_scanned_files is supported. * * The process will not be restarted if there are scanned inis but PHP * is unable to list them. See https://bugs.php.net/73124 * * This method tests the behaviour of XdebugHandler::checkScanDirConfig by * checking each requirement separately. * * @param mixed $scanDir Initial value for PHP_INI_SCAN_DIR * * @return null|string The skip message */ public static function shouldSkipTest($scanDir) { // Not relevant if no scan dir or it has been overriden if ($scanDir === false || $scanDir === '') { return; } // Not relevant if --with-config-file-scan-dir was used if (PHP_CONFIG_FILE_SCAN_DIR !== '') { return; } // Bug fixed in 7.1.13 if (PHP_VERSION_ID >= 70113 && PHP_VERSION_ID < 70200) { return; } // Bug fixed in 7.2.1 if (PHP_VERSION_ID >= 70201) { return; } return 'php_ini_scanned_files not functional on '.PHP_VERSION; } } php-composer-xdebug-handler-1.4.0/tests/Helpers/IniHelper.php000066400000000000000000000064761356144430600241760ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler\Helpers; use Composer\XdebugHandler\Mocks\CoreMock; /** * This helper class allows us to mock the php ini files that the process would * otherwise report via php_ini_loaded_file and php_ini_scanned_files. */ class IniHelper { protected $loadedIni; protected $scanDir; protected $files; protected $envOptions; /** * envOptions is an array of additional environment values to set, * comprising: [PHP_INI_SCAN_DIR, optional PHPRC] * * @param mixed $envOptions */ public function __construct($envOptions = null) { $this->envOptions = $envOptions ?: array(); $base = dirname(__DIR__).DIRECTORY_SEPARATOR.'Fixtures'; $this->loadedIni = $base.DIRECTORY_SEPARATOR.'php.ini'; $this->scanDir = $base.DIRECTORY_SEPARATOR.'scandir'; } public function setNoInis() { // Must have at least one entry $this->files = array(''); $this->setEnvironment(); } public function setLoadedIni() { $this->files = array( $this->loadedIni, ); $this->setEnvironment(); } public function setScannedInis() { $this->files = array( '', $this->scanDir.DIRECTORY_SEPARATOR.'scan-one.ini', $this->scanDir.DIRECTORY_SEPARATOR.'scan-two.ini', ); $this->setEnvironment(); } public function setAllInis() { $this->files = array( $this->loadedIni, $this->scanDir.DIRECTORY_SEPARATOR.'scan-one.ini', $this->scanDir.DIRECTORY_SEPARATOR.'scan-two.ini', ); $this->setEnvironment(); } public function setInaccessibleIni() { $this->files = array( '', $this->scanDir.DIRECTORY_SEPARATOR.'scan-one.ini', $this->scanDir.DIRECTORY_SEPARATOR.'scan-two.ini', $this->scanDir.DIRECTORY_SEPARATOR.'scan-missing.ini', ); $this->setEnvironment(); } public function getIniFiles() { return $this->files; } public function hasScannedInis() { return count($this->files) > 1; } public function getLoadedIni() { return $this->loadedIni; } public function getScanDir() { return $this->scanDir; } private function setEnvironment() { // Set ORIGINAL_INIS. Values must be path-separated $this->setEnv(CoreMock::ORIGINAL_INIS, implode(PATH_SEPARATOR, $this->files)); $options = $this->envOptions ?: array(); if ($options) { $scanDir = array_shift($options); $phprc = array_shift($options); $this->setEnv('PHP_INI_SCAN_DIR', $scanDir); if (null !== $phprc) { $this->setEnv('PHPRC', $phprc); } } } private function setEnv($name, $value) { if (false !== $value) { putenv($name.'='.$value); $_SERVER[$name] = $value; } else { putenv($name); unset($_SERVER[$name]); } } } php-composer-xdebug-handler-1.4.0/tests/Helpers/Logger.php000066400000000000000000000011351356144430600235210ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler\Helpers; use Psr\Log\AbstractLogger; class Logger extends AbstractLogger { protected $output = array(); public function log($level, $message, array $context = array()) { $this->output[] = array($level, $message, $context); } public function getOutput() { return $this->output; } } php-composer-xdebug-handler-1.4.0/tests/IniFilesTest.php000066400000000000000000000114221356144430600232420ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use Composer\XdebugHandler\Helpers\BaseTestCase; use Composer\XdebugHandler\Helpers\IniHelper; use Composer\XdebugHandler\Mocks\CoreMock; use Composer\XdebugHandler\Mocks\PartialMock; /** * We use PHP_BINARY which only became available in PHP 5.4 * * @requires PHP 5.4 */ class IniFilesTest extends BaseTestCase { /** * Tests that the ini files stored in the _ORIGINAL_INIS environment * variable are formatted and reported correctly. * * @param callable $iniFunc IniHelper method to use * * @dataProvider iniFilesProvider */ public function testGetAllIniFiles($iniFunc) { $ini = new IniHelper(); call_user_func(array($ini, $iniFunc)); $loaded = true; $xdebug = CoreMock::createAndCheck($loaded); $this->checkRestart($xdebug); $this->assertEquals($ini->getIniFiles(), CoreMock::getAllIniFiles()); } public function iniFilesProvider() { // $iniFunc return array( 'no-inis' => array('setNoInis'), 'loaded-ini' => array('setLoadedIni'), 'scanned-inis' => array('setScannedInis'), 'all-inis' => array('setAllInis'), ); } /** * Tests that the tmpIni file is created, contains disabled Xdebug * entries and is correctly end-of-line terminated. * * @param callable $iniFunc IniHelper method to use * @param int $matches The number of disabled entries to match * @dataProvider tmpIniProvider */ public function testTmpIni($iniFunc, $matches) { $ini = new IniHelper(); call_user_func(array($ini, $iniFunc)); $loaded = true; $xdebug = PartialMock::createAndCheck($loaded); $content = $this->getTmpIniContent($xdebug); $regex = '/^\s*;zend_extension\s*=.*xdebug.*$/mi'; $result = preg_match_all($regex, $content); $this->assertSame($result, $matches); // Check content is end-of-line terminated $regex = sprintf('/%s/', preg_quote(PHP_EOL)); $this->assertTrue((bool) preg_match($regex, $content)); } public function tmpIniProvider() { // $iniFunc, $matches (number of disabled entries) return array( 'no-inis' => array('setNoInis', 0), 'loaded-ini' => array('setLoadedIni', 1), 'scanned-inis' => array('setScannedInis', 1), 'all-inis' => array('setAllInis', 2), ); } /** * Tests that changed values are added correctly in the tmp ini * * @param string $name Ini setting name * @param string $value Ini setting value * @dataProvider mergeIniProvider */ public function testMergeInis($name, $value) { $ini = new IniHelper(); $ini->setAllInis(); // Mock user -d setting $orig = ini_set($name, $value); $loaded = true; $xdebug = PartialMock::createAndCheck($loaded); ini_set($name, $orig); $content = $this->getTmpIniContent($xdebug); $config = parse_ini_string($content); $this->assertArrayHasKey($name, $config); $this->assertEquals($value, $config[$name]); } public function mergeIniProvider() { // $name, $value return array( 'simple' => array('date.timezone', 'Antarctica/McMurdo'), 'single-quotes' => array('error_append_string', "<'color'>"), 'newline' => array('error_append_string', ""), 'double-quotes' => array('error_append_string', ''), 'backslashes' => array('error_append_string', ''), ); } /** * Tests that an inaccessible ini file causes the restart to fail * */ public function testInaccessbleIni() { $ini = new IniHelper(); $ini->setInaccessibleIni(); $loaded = true; $xdebug = CoreMock::createAndCheck($loaded); // We need to remove the mock inis from the environment Process::setEnv(CoreMock::ORIGINAL_INIS, false); $this->checkNoRestart($xdebug); } /** * Common method to get mocked tmp ini content * * @param mixed $xdebug */ private function getTmpIniContent(PartialMock $xdebug) { $tmpIni = $xdebug->getTmpIni(); if (!$tmpIni) { $this->fail('The tmpIni file was not created'); } if (!file_exists($tmpIni)) { $this->fail($tmpIni.' does not exist'); } return file_get_contents($tmpIni); } } php-composer-xdebug-handler-1.4.0/tests/Mocks/000077500000000000000000000000001356144430600212435ustar00rootroot00000000000000php-composer-xdebug-handler-1.4.0/tests/Mocks/CoreMock.php000066400000000000000000000075411356144430600234650ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler\Mocks; use Composer\XdebugHandler\XdebugHandler; /** * CoreMock provides base functionality for mocking XdebugHandler, providing its * own restart method that mocks a restart by creating a new instance of itself * and setting the CoreMock::restarted property to true, and a public * getProperty method that accesses private properties. Extend this class to * provide further capabilities. * * It does not matter whether Xdebug is loaded, because this value is overriden * in the constructor. * * The tmpIni file is deleted in the destructor. */ class CoreMock extends XdebugHandler { const ALLOW_XDEBUG = 'MOCK_ALLOW_XDEBUG'; const ORIGINAL_INIS = 'MOCK_ORIGINAL_INIS'; const TEST_VERSION = '2.5.0'; public $restarted; public $parentLoaded; protected $childProcess; protected $refClass; protected static $settings; public static function createAndCheck($loaded, $parentProcess = null, $settings = array()) { $xdebug = new static($loaded); if ($parentProcess) { // This is a restart, so set restarted on parent so it is copied $parentProcess->restarted = true; // Copy all public properties $refClass = new \ReflectionClass($parentProcess); $props = $refClass->getProperties(\ReflectionProperty::IS_PUBLIC); foreach ($props as $prop) { $xdebug->{$prop->name} = $parentProcess->{$prop->name}; } $parentProcess->childProcess = $xdebug; // Ensure $_SERVER has our environment changes static::updateServerEnvironment(); } foreach ($settings as $method => $args) { call_user_func_array(array($xdebug, $method), $args); } static::$settings = $settings; $xdebug->check(); return $xdebug->childProcess ?: $xdebug; } public function __construct($loaded) { parent::__construct('mock'); $this->refClass = new \ReflectionClass('Composer\XdebugHandler\XdebugHandler'); $this->parentLoaded = $loaded ? static::TEST_VERSION : null; // Set private loaded $prop = $this->refClass->getProperty('loaded'); $prop->setAccessible(true); $prop->setValue($this, $this->parentLoaded); // Ensure static private skipped is unset $prop = $this->refClass->getProperty('skipped'); $prop->setAccessible(true); $prop->setValue($this, null); // Ensure static private inRestart is unset $prop = $this->refClass->getProperty('inRestart'); $prop->setAccessible(true); $prop->setValue($this, null); $this->restarted = false; } public function __destruct() { // Delete the tmpIni if one has been created if (!empty($this->tmpIni)) { @unlink($this->tmpIni); } } public function getProperty($name) { $prop = $this->refClass->getProperty($name); $prop->setAccessible(true); return $prop->getValue($this); } protected function restart($command) { static::createAndCheck(false, $this, static::$settings); } private static function updateServerEnvironment() { $names = array( CoreMock::ALLOW_XDEBUG, CoreMock::ORIGINAL_INIS, 'PHP_INI_SCAN_DIR', 'PHPRC', ); foreach ($names as $name) { $value = getenv($name); if (false === $value) { unset($_SERVER[$name]); } else { $_SERVER[$name] = $value; } } } } php-composer-xdebug-handler-1.4.0/tests/Mocks/FailMock.php000066400000000000000000000010331356144430600234360ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler\Mocks; /** * FailMock provides its own restart method that mocks a restart with Xdebug * still loaded. */ class FailMock extends CoreMock { protected function restart($command) { static::createAndCheck(true, $this, static::$settings); } } php-composer-xdebug-handler-1.4.0/tests/Mocks/PartialMock.php000066400000000000000000000014761356144430600241720ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler\Mocks; /** * PartialMock provides its own restart method that simply sets the restarted * property to true, rather than mocking a restart. * * It can be used to test the state of the original parent process. */ class PartialMock extends CoreMock { protected $command; public function getCommand() { return $this->command; } public function getTmpIni() { return $this->tmpIni; } protected function restart($command) { $this->command = $command; $this->restarted = true; } } php-composer-xdebug-handler-1.4.0/tests/Mocks/RequiredMock.php000066400000000000000000000014011356144430600243420ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler\Mocks; /** * RequiredMock provides the runCoreMock method to set the $required property * and then calls the parent createAndCheck method. */ class RequiredMock extends CoreMock { protected static $required; public static function runCoreMock($loaded, $required) { static::$required = $required; return parent::createAndCheck($loaded, null); } protected function requiresRestart($isLoaded) { return $isLoaded && static::$required; } } php-composer-xdebug-handler-1.4.0/tests/PhpConfigTest.php000066400000000000000000000067721356144430600234310ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use Composer\XdebugHandler\Helpers\BaseTestCase; use Composer\XdebugHandler\Helpers\EnvHelper; use Composer\XdebugHandler\Mocks\CoreMock; /** * We use PHP_BINARY which only became available in PHP 5.4 * * @requires PHP 5.4 */ class PhpConfigTest extends BaseTestCase { /** * Tests that the correct command-line options are returned. * * @param string $method PhpConfig method to call * @param array $expected * @dataProvider commandLineProvider */ public function testCommandLineOptions($method, $expected) { $loaded = true; CoreMock::createAndCheck($loaded); $config = new PhpConfig(); $options = call_user_func(array($config, $method)); if ($method === 'useStandard') { $data = CoreMock::getRestartSettings(); $expected[2] = $data['tmpIni']; } $this->assertSame($expected, $options); } public function commandLineProvider() { // $method, $expected return array( 'original' => array('useOriginal', array()), 'standard' => array('useStandard', array('-n', '-c', '')), 'persistent' => array('usePersistent', array()), ); } /** * Tests that the environment is set correctly for each mode. * * @param callable $iniFunc IniHelper method to use * @param mixed $scanDir Initial value for PHP_INI_SCAN_DIR * @param $phprc Initial value for PHPRC * @dataProvider environmentProvider */ public function testEnvironment($iniFunc, $scanDir, $phprc) { if ($message = EnvHelper::shouldSkipTest($scanDir)) { $this->markTestSkipped($message); } $ini = EnvHelper::setInis($iniFunc, $scanDir, $phprc); $loaded = true; CoreMock::createAndCheck($loaded); $config = new PhpConfig(); $data = CoreMock::getRestartSettings(); $tests = array('useOriginal', 'usePersistent', 'useStandard'); foreach ($tests as $method) { call_user_func(array($config, $method)); if ($method === 'usePersistent') { $expectedScanDir = ''; $expectedPhprc = $data['tmpIni']; } else { $expectedScanDir = $scanDir; $expectedPhprc = $phprc; } $this->checkEnvironment($expectedScanDir, $expectedPhprc, $method); } } public function environmentProvider() { return EnvHelper::dataProvider(); } /** * Checks the value of variables in the local environment and $_SERVER * * @param mixed $scanDir * @param mixed $phprc * @param string $name */ private function checkEnvironment($scanDir, $phprc, $name) { $tests = array('PHP_INI_SCAN_DIR' => $scanDir, 'PHPRC' => $phprc); foreach ($tests as $env => $value) { $message = $name.' '.strtolower($env); $this->assertSame($value, getenv($env), 'getenv '.$message); if (false === $value) { $this->assertSame($value, isset($_SERVER[$env]), '$_SERVER '.$message); } else { $this->assertSame($value, $_SERVER[$env], '$_SERVER '.$message); } } } } php-composer-xdebug-handler-1.4.0/tests/RestartTest.php000066400000000000000000000105221356144430600231640ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use Composer\XdebugHandler\Helpers\BaseTestCase; use Composer\XdebugHandler\Mocks\CoreMock; use Composer\XdebugHandler\Mocks\FailMock; use Composer\XdebugHandler\Mocks\PartialMock; use Composer\XdebugHandler\Mocks\RequiredMock; /** * We use PHP_BINARY which only became available in PHP 5.4 * * @requires PHP 5.4 */ class RestartTest extends BaseTestCase { public function testRestartWhenLoaded() { $loaded = true; $this->setArgv(); $xdebug = CoreMock::createAndCheck($loaded); $this->checkRestart($xdebug); // Check command $xdebug = PartialMock::createAndCheck($loaded); $command = $xdebug->getCommand(); $n = Process::escape('-n'); $c = Process::escape('-c'); $tmpIni = Process::escape($xdebug->getTmpIni()); $pattern = preg_quote(sprintf('%s %s %s', $n, $c, $tmpIni), '/'); $this->assertRegExp('/'.$pattern.'/', $command); } public function testNoRestartWhenNotLoaded() { $loaded = false; $xdebug = CoreMock::createAndCheck($loaded); $this->checkNoRestart($xdebug); } public function testNoRestartWhenLoadedAndAllowed() { $loaded = true; putenv(CoreMock::ALLOW_XDEBUG.'=1'); $xdebug = CoreMock::createAndCheck($loaded); $this->checkNoRestart($xdebug); } public function testFailedRestart() { $loaded = true; $xdebug = FailMock::createAndCheck($loaded); $this->checkRestart($xdebug); } /** * @dataProvider unreachableScriptProvider */ public function testNoRestartWithUnreachableScript($script) { $loaded = true; // We can only check this by setting a script $settings = array('setMainScript' => array($script)); $xdebug = CoreMock::createAndCheck($loaded, null, $settings); $this->checkNoRestart($xdebug); } public function unreachableScriptProvider() { return array( array('nonexistent.php'), array('-'), array('Standard input code'), ); } /** * @dataProvider scriptSetterProvider */ public function testRestartWithScriptSetter($script) { $loaded = true; $this->setArgv(); $settings = array('setMainScript' => array($script)); $xdebug = CoreMock::createAndCheck($loaded, null, $settings); $this->checkRestart($xdebug); // Check command $xdebug = PartialMock::createAndCheck($loaded, null, $settings); $command = $xdebug->getCommand(); $pattern = preg_quote(Process::escape($script), '/'); $this->assertRegExp('/'.$pattern.'/', $command); } public function scriptSetterProvider() { return array( array(realpath($_SERVER['argv'][0])), array('--'), ); } public function testSetPersistent() { $loaded = true; $this->setArgv(); $settings = array('setPersistent' => array()); // Check command $xdebug = PartialMock::createAndCheck($loaded, null, $settings); $command = $xdebug->getCommand(); $tmpIni = $xdebug->getTmpIni(); foreach (array('-n', '-c', $tmpIni) as $param) { $pattern = preg_quote(Process::escape($param), '/'); $matched = (bool) preg_match('/'.$pattern.'/', $command); $this->assertFalse($matched); } } public function testNoRestartWhenNotRequired() { $loaded = true; $required = false; $xdebug = RequiredMock::runCoreMock($loaded, $required); $this->checkNoRestart($xdebug); } public function testNoRestartWhenRequiredAndAllowed() { $loaded = true; putenv(CoreMock::ALLOW_XDEBUG.'=1'); $required = true; $xdebug = RequiredMock::runCoreMock($loaded, $required); $this->checkNoRestart($xdebug); } /** * Sets $_SERVER['argv'] for testing commands */ private function setArgv() { $_SERVER['argv'] = array(__FILE__, 'command', '--param1, --param2'); } } php-composer-xdebug-handler-1.4.0/tests/SettingsTest.php000066400000000000000000000053111356144430600233400ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use Composer\XdebugHandler\Helpers\BaseTestCase; use Composer\XdebugHandler\Helpers\EnvHelper; use Composer\XdebugHandler\Mocks\CoreMock; /** * We use PHP_BINARY which only became available in PHP 5.4 * * @requires PHP 5.4 */ class SettingsTest extends BaseTestCase { /** * Tests that the restart settings are correctly set. * * @param callable $iniFunc IniHelper method to use * @param mixed $scanDir Initial value for PHP_INI_SCAN_DIR * @param $phprc Initial value for PHPRC * @dataProvider environmentProvider */ public function testGetRestartSettings($iniFunc, $scanDir, $phprc) { if ($message = EnvHelper::shouldSkipTest($scanDir)) { $this->markTestSkipped($message); } $ini = EnvHelper::setInis($iniFunc, $scanDir, $phprc); $loaded = true; CoreMock::createAndCheck($loaded); $settings = CoreMock::getRestartSettings(); $this->assertTrue(is_string($settings['tmpIni'])); $this->assertSame($ini->hasScannedInis(), $settings['scannedInis']); $this->assertSame($scanDir, $settings['scanDir']); $this->assertSame($phprc, $settings['phprc']); $this->assertSame(CoreMock::getAllIniFiles(), $settings['inis']); $this->assertSame(CoreMock::TEST_VERSION, $settings['skipped']); } public function environmentProvider() { return EnvHelper::dataProvider(); } /** * Tests that a call with existing restart settings updates the current * settings. */ public function testSyncSettings() { $ini = EnvHelper::setInis('setAllInis', false, false); // Create the settings in the environment $loaded = true; CoreMock::createAndCheck($loaded); $originalInis = getenv(CoreMock::ORIGINAL_INIS); // Unset env ORIGINAL_INIS to mock a call by a different application putenv(CoreMock::ORIGINAL_INIS); unset($_SERVER[CoreMock::ORIGINAL_INIS]); // Mock not loaded ($inRestart and $skipped statics are unset) $loaded = false; CoreMock::createAndCheck($loaded); // Env ORIGINAL_INIS must be set and be a string $this->assertSame($originalInis, getenv(CoreMock::ORIGINAL_INIS)); $this->assertSame($originalInis, $_SERVER[CoreMock::ORIGINAL_INIS]); // Skipped version must be set $this->assertSame(CoreMock::TEST_VERSION, CoreMock::getSkippedVersion()); } } php-composer-xdebug-handler-1.4.0/tests/StatusTest.php000066400000000000000000000026201356144430600230230ustar00rootroot00000000000000 * * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace Composer\XdebugHandler; use Composer\XdebugHandler\Helpers\BaseTestCase; use Composer\XdebugHandler\Helpers\Logger; use Composer\XdebugHandler\Mocks\CoreMock; use Psr\Log\LogLevel; /** * We use PHP_BINARY which only became available in PHP 5.4 * * @requires PHP 5.4 */ class StatusTest extends BaseTestCase { public function testSetLoggerProvidesOutput() { $loaded = true; $logger = new Logger(); $settings = array('setLogger' => array($logger)); $xdebug = CoreMock::createAndCheck($loaded, null, $settings); $this->checkRestart($xdebug); $output = $logger->getOutput(); $this->assertNotEmpty($output); $this->checkStatusOutput($output); } /** * Assertions to check the status message and logging formats * * @param array $output */ protected function checkStatusOutput(array $output) { $levels = array(LogLevel::DEBUG, LogLevel::WARNING); foreach ($output as $record) { $this->assertCount(3, $record); $this->assertContains($record[0], $levels); $this->assertCount(0, $record[2]); } } }