diff --git a/Provision/Config/Drupal/provision_drupal_settings_8.tpl.php b/Provision/Config/Drupal/provision_drupal_settings_8.tpl.php index e173f45bc39c1d8957f43a3f6de6c168c8679b0e..e889b40cfc6ee40c906d27838705de92d88cdff7 100644 --- a/Provision/Config/Drupal/provision_drupal_settings_8.tpl.php +++ b/Provision/Config/Drupal/provision_drupal_settings_8.tpl.php @@ -173,7 +173,7 @@ if (isset($_SERVER['db_name'])) { $esc_uri = str_replace('.', '\.', $this->uri); print " '^{$esc_uri}\$',\n"; foreach ($this->aliases as $alias_url) { - $esc_alias = str_replace('.', '\.', $alias_url); + $esc_alias = preg_replace(['/\./', '/\/.+/'], ['\.', ''], $alias_url); print " '^{$esc_alias}\$',\n"; } ?> diff --git a/composer.json b/composer.json index 938c35827a4e2a39a3b4215a59656e39624e21e4..612735d4cc43bf725e94bab45d75073d24cdf83d 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,9 @@ "issues": "https://www.drupal.org/project/issues/provision", "docs": "http://docs.aegirproject.org/" }, - "require": {}, + "require": { + "symfony/process": "^3.3" + }, "config": { "sort-packages": true, "platform": { diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000000000000000000000000000000000000..ff81d077374b43c2201cdd3a83034f45175d84e1 --- /dev/null +++ b/composer.lock @@ -0,0 +1,70 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "c6615761b64ee98b0ecc471fd193dd04", + "packages": [ + { + "name": "symfony/process", + "version": "v3.3.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a", + "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com", + "time": "2017-07-13T13:05:09+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": true, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "platform-overrides": { + "php": "7.0" + } +} diff --git a/http/Provision/Config/Apache/vhost.tpl.php b/http/Provision/Config/Apache/vhost.tpl.php index 73b48192dbe7f19853c12b3f3f9be37955bbb6ff..361ecbc5915800bdd4776cbdff5ebd05961da34e 100644 --- a/http/Provision/Config/Apache/vhost.tpl.php +++ b/http/Provision/Config/Apache/vhost.tpl.php @@ -42,10 +42,14 @@ if ($this->redirection || $ssl_redirection) { if ($ssl_redirection && !$this->redirection) { print " # Redirect aliases in non-ssl to the same alias on ssl.\n"; + print " # Except for /.well-known/acme-challenge/ to prevent potential problems with Let's Encrypt\n"; + print " RewriteCond %{REQUEST_URI} '!/.well-known/acme-challenge/'\n"; print " RewriteRule ^/*(.*)$ https://%{HTTP_HOST}/$1 [NE,L,R=301]\n"; } elseif ($ssl_redirection && $this->redirection) { print " # Redirect all aliases + main uri to the selected alias https uri.\n"; + print " # Except for /.well-known/acme-challenge/ to prevent potential problems with Let's Encrypt\n"; + print " RewriteCond %{REQUEST_URI} '!/.well-known/acme-challenge/'\n"; print " RewriteRule ^/*(.*)$ https://{$this->redirection}/$1 [NE,L,R=301]\n"; } elseif (!$ssl_redirection && $this->redirection) { diff --git a/http/Provision/Config/Apache/vhost_disabled.tpl.php b/http/Provision/Config/Apache/vhost_disabled.tpl.php index 7d03c1751e6942b0ff44910935369ec51a64241c..62395a6bc4bfdb14becce2e6328c4ee9861b3187 100644 --- a/http/Provision/Config/Apache/vhost_disabled.tpl.php +++ b/http/Provision/Config/Apache/vhost_disabled.tpl.php @@ -15,6 +15,11 @@ ?> RewriteEngine on + + # Redirect ALL visitors to a configured url. + # Except for /.well-known/acme-challenge/ to prevent potential problems with Let's Encrypt + RewriteCond %{REQUEST_URI} '!/.well-known/acme-challenge/' + # the ? at the end is to remove any query string in the original url RewriteRule ^(.*)$ platform->server->web_disable_url . '/' . $this->uri ?>? diff --git a/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php b/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php index 41a5e173a31060caec2ce3e9aacbf4aaf3a0aeb0..9558567c6e478f1079d75769bcb71249029165bc 100644 --- a/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php +++ b/http/Provision/Config/Nginx/Inc/vhost_include.tpl.php @@ -921,7 +921,7 @@ location ~* /(?:cross-?domain)\.xml$ { ### ### Allow some known php files (like serve.php in the ad module). ### -location ~* /(?:modules|libraries)/(?:contrib/)?(?:ad|tinybrowser|f?ckeditor|tinymce|wysiwyg_spellcheck|ecc|civicrm|fbconnect|radioactivity)/.*\.php$ { +location ~* /(?:modules|libraries)/(?:contrib/)?(?:ad|tinybrowser|f?ckeditor|tinymce|wysiwyg_spellcheck|ecc|civicrm|fbconnect|radioactivity|statistics)/.*\.php$ { limit_conn limreq 88; diff --git a/http/Provision/Service/http/nginx.php b/http/Provision/Service/http/nginx.php index 07118508dac4ae637846097a690c241c2f8bd4c7..ca768bb54db522b354e347fbf31ac63971bcc606 100644 --- a/http/Provision/Service/http/nginx.php +++ b/http/Provision/Service/http/nginx.php @@ -2,9 +2,10 @@ class Provision_Service_http_nginx extends Provision_Service_http_public { - // Define socket file locations for various PHP versions. + // Define static socket file locations for various PHP versions. + // These are dynamic in PHP 7. const SOCKET_PATH_PHP5 = '/var/run/php5-fpm.sock'; - const SOCKET_PATH_PHP7 = '/var/run/php/php7.0-fpm.sock'; + const SOCKET_PATH_PHP7_BASE = '/var/run/php'; protected $application_name = 'nginx'; protected $has_restart_cmd = TRUE; @@ -171,9 +172,9 @@ class Provision_Service_http_nginx extends Provision_Service_http_public { $mode = 'socket'; $socket_path = self::SOCKET_PATH_PHP5; break; - case provision_file()->exists(self::SOCKET_PATH_PHP7)->status(): + case provision_file()->exists(static::getPhp7FpmSocketPath())->status(): $mode = 'socket'; - $socket_path = self::SOCKET_PATH_PHP7; + $socket_path = static::getPhp7FpmSocketPath(); break; default: $mode = 'port'; @@ -187,7 +188,7 @@ class Provision_Service_http_nginx extends Provision_Service_http_public { '@mode' => ($mode == 'socket') ? 'unix socket' : 'port', '@task' => strtoupper($server_task), '@yes_or_no' => ($mode == 'socket') ? 'YES' : 'NO', - '@path' => ($socket_path ? $socket_path : self::SOCKET_PATH_PHP5 . ' or ' . self::SOCKET_PATH_PHP7), + '@path' => ($socket_path ? $socket_path : self::SOCKET_PATH_PHP5 . ' or ' . static::getPhp7FpmSocketPath()), ))); } @@ -212,13 +213,34 @@ class Provision_Service_http_nginx extends Provision_Service_http_public { // Return the socket path based on the PHP version. if (strtok(phpversion(), '.') == 7) { - return self::SOCKET_PATH_PHP7; + return static::getPhp7FpmSocketPath(); } else { return self::SOCKET_PATH_PHP5; } } + /** + * Gets the PHP FPM unix socket path for PHP 7. + * + * In PHP 7, there isn't a fixed socked path. It could be any one of the + * following: + * * php7.0-fpm.sock + * * php7.2-fpm.sock + * * ... + * + * @return string + * The path, or FALSE if there isn't one. + */ + public static function getPhp7FpmSocketPath() { + foreach (scandir(static::SOCKET_PATH_PHP7_BASE, SCANDIR_SORT_DESCENDING) as $file) { + if (strpos($file, 'fpm.sock')) { + return static::SOCKET_PATH_PHP7_BASE . '/' . $file; + } + } + return FALSE; + } + /** * Guess at the likely value of the http_restart_cmd. * diff --git a/platform/verify.provision.inc b/platform/verify.provision.inc index 9d140d25595770e02ee26b0640e14f7da1ef01be..3f608948f6b9b03164cfd2f80394fbc487473654 100644 --- a/platform/verify.provision.inc +++ b/platform/verify.provision.inc @@ -104,31 +104,13 @@ function drush_provision_drupal_pre_provision_verify() { // Composer Install command: Do not interact, do not show download progress. // Customizable by setting drush option 'provision_composer_install_command' $composer_command = drush_get_option('provision_composer_install_command', 'composer install --no-interaction --no-progress --no-dev'); - drush_log(dt("Running command @command", array( - '@command' => $composer_command - )), 'ok'); - $start = time(); - - // @TODO: Implement Symfony Process component for line-by-line output logging. - if (drush_shell_cd_and_exec($composer_directory, $composer_command)) { - $stop = time(); - - $output = implode("\n", drush_shell_exec_output()); - $log_status = strpos($output, 'Warning:') === FALSE? 'success': 'warning'; - - drush_log($output, $log_status); - drush_log(dt(strpos($output, 'Warning:') . "Command ran successfully in @times: @command", array( - '@command' => $composer_command, - '@time' => $stop - $start, - )), $log_status); - } - else { - drush_log(implode("\n", drush_shell_exec_output()), 'error'); - drush_set_error('DRUSH_COMPOSER_ERROR', dt('The composer command failed in @dir: @command', array( - '@dir' => $composer_directory, - '@command' => $composer_command, - ))); - } + + + // Implement Symfony Process component for line-by-line output logging. + provision_process($composer_command, $composer_directory); + $process = drush_get_context('provision_process_result'); + + drush_log(print_r($process, 1), 'devshop_log'); } } diff --git a/provision.inc b/provision.inc index 0a4cbe00f2dcd9c8ca3f807633bbe33d7062d316..13d3d81803e7f383ec5b596c2c0bd9228e812177 100644 --- a/provision.inc +++ b/provision.inc @@ -413,7 +413,7 @@ function _scrub_object($input) { /** * Execute a command against a specific context object. - * + * * @param $target * the context to operate on, @ prefix is optional. * @param $command @@ -427,9 +427,75 @@ function _scrub_object($input) { * * @see drush_invoke_process() */ -function provision_backend_invoke($target, $command, $arguments = array(), $data = array(), $mode = 'GET') { - $context = '@' . ltrim($target, '@'); - return drush_invoke_process($context, $command, $arguments, $data, array('method' => $mode, 'integrate' => TRUE, 'dispatch-using-alias' => TRUE, 'drush-script' => DRUSH_COMMAND)); +function provision_backend_invoke($target, $drush_command, $arguments = array(), $task_options = array(), $mode = 'GET') { + + // Build the command + $command = $env = []; + $command[] = $drush_bin = 'drush'; // @TODO: Point at the exact drush bin somewhere. + $command[] = $context = '@' . ltrim($target, '@'); + $command[] = $drush_command; + $command = array_merge($command, $arguments); + $command[] = '--strict=0'; + foreach ($task_options as $name => $value) { + $value = escapeshellarg($value); + $command[] = "--$name=$value"; + } + + return provision_process(implode(' ', $command), d($context)->site_path, 'Drush', + $env, t('Command failed.')); + +// return drush_invoke_process($context, $command, $arguments, $data, array('method' => $mode, 'integrate' => TRUE, 'dispatch-using-alias' => TRUE)); +} + +/** + * Run a command, sending output to drush logs in real time. + * + * The Symfony\Component\Process\Process Object is used to run this command. + * After implementing provision_process(), you can get the Process result object + * via drush context: + * + * $process = drush_get_context('provision_process_result'); + * print $process->getExitCode(); + * + */ +function provision_process($command, $cwd = null, $label = 'Process', $env = array(), $log_output = TRUE, $error_message = 'Process Failed') { + drush_log("[$label] $command", 'p_command'); + + // Merge in env vars, inheriting the CLI's + if (is_array($env)) { + $env = array_merge($_SERVER, $env); + } + else { + $env = $_SERVER; + } + + // Make sure colors always come through + $env['TERM'] = 'xterm'; + + $process = new \Symfony\Component\Process\Process(escapeshellcmd($command), $cwd, $env); + $process->setTimeout(NULL); + if ($log_output) { + $exit_code = $process->run(function ($type, $buffer) { + drush_log($buffer, 'p_info'); + }); + } + else { + $exit_code = $process->run(); + } + + // Save the Provision Process object to Drush Context so that implementors can access the full object. + drush_set_context('provision_process_result', $process); + + // check exit code + if ($exit_code === 0) { + drush_log('', 'p_ok'); + return $process->getOutput(); + } + else { + drush_log('', 'p_error'); + drush_set_error('PROVISION_PROCESS_ERROR', dt($error_message)); + return $process->getErrorOutput(); + } } /** diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php index 2c72175e7723ad0c73fd3154eb0fecc420810448..95f7e0978bad11efb1f75f5049b0c2ac7ae1939b 100644 --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -377,11 +377,11 @@ class ClassLoader $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); - $search = $subPath.'\\'; + $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { - $length = $this->prefixLengthsPsr4[$first][$search]; - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + if (file_exists($file = $dir . $pathEnd)) { return $file; } } diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php index b265c64a22f6691ca4a508347bd3dee43b9b19e7..8ae063a759c1125136cd1973523d6c94423813f2 100644 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -6,4 +6,5 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( + 'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'), ); diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 8f98a329eb7423bb05dee8887418d43b3b8d9b96..db03ac485cd51bc1ee21990989d6ec55f432a3f1 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -6,6 +6,20 @@ namespace Composer\Autoload; class ComposerStaticInit93ce438f8ce8708ef24439d2070dee06 { + public static $prefixLengthsPsr4 = array ( + 'S' => + array ( + 'Symfony\\Component\\Process\\' => 26, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Symfony\\Component\\Process\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/process', + ), + ); + public static $prefixesPsr0 = array ( 'P' => array ( @@ -19,6 +33,8 @@ class ComposerStaticInit93ce438f8ce8708ef24439d2070dee06 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit93ce438f8ce8708ef24439d2070dee06::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit93ce438f8ce8708ef24439d2070dee06::$prefixDirsPsr4; $loader->prefixesPsr0 = ComposerStaticInit93ce438f8ce8708ef24439d2070dee06::$prefixesPsr0; }, null, ClassLoader::class); diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json index fe51488c7066f6687ef680d6bfaa4f7768ef205c..289474b491a1902a862bed8b91d17f9e2abb1ac7 100644 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -1 +1,53 @@ -[] +[ + { + "name": "symfony/process", + "version": "v3.3.6", + "version_normalized": "3.3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/07432804942b9f6dd7b7377faf9920af5f95d70a", + "reference": "07432804942b9f6dd7b7377faf9920af5f95d70a", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "time": "2017-07-13T13:05:09+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "https://symfony.com" + } +] diff --git a/vendor/symfony/process/.gitignore b/vendor/symfony/process/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..c49a5d8df5c6548379f00c77fe572a7217bce218 --- /dev/null +++ b/vendor/symfony/process/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/vendor/symfony/process/CHANGELOG.md b/vendor/symfony/process/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..bb719be711524acd2d96bc50bc15b21add10d42e --- /dev/null +++ b/vendor/symfony/process/CHANGELOG.md @@ -0,0 +1,51 @@ +CHANGELOG +========= + +3.3.0 +----- + + * added command line arrays in the `Process` class + * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods + * deprecated the `ProcessUtils::escapeArgument()` method + * deprecated not inheriting environment variables + * deprecated configuring `proc_open()` options + * deprecated configuring enhanced Windows compatibility + * deprecated configuring enhanced sigchild compatibility + +2.5.0 +----- + + * added support for PTY mode + * added the convenience method "mustRun" + * deprecation: Process::setStdin() is deprecated in favor of Process::setInput() + * deprecation: Process::getStdin() is deprecated in favor of Process::getInput() + * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types + +2.4.0 +----- + + * added the ability to define an idle timeout + +2.3.0 +----- + + * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows + * added Process::signal() + * added Process::getPid() + * added support for a TTY mode + +2.2.0 +----- + + * added ProcessBuilder::setArguments() to reset the arguments on a builder + * added a way to retrieve the standard and error output incrementally + * added Process:restart() + +2.1.0 +----- + + * added support for non-blocking processes (start(), wait(), isRunning(), stop()) + * enhanced Windows compatibility + * added Process::getExitCodeText() that returns a string representation for + the exit code returned by the process + * added ProcessBuilder diff --git a/vendor/symfony/process/Exception/ExceptionInterface.php b/vendor/symfony/process/Exception/ExceptionInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..75c1c9e5d800500d07f2790f21eb45a26439f3cd --- /dev/null +++ b/vendor/symfony/process/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * Marker Interface for the Process Component. + * + * @author Johannes M. Schmitt + */ +interface ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000000000000000000000000000000..926ee2118b03703381fabcd80e01bda817c7fa67 --- /dev/null +++ b/vendor/symfony/process/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * InvalidArgumentException for the Process Component. + * + * @author Romain Neutron + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/LogicException.php b/vendor/symfony/process/Exception/LogicException.php new file mode 100644 index 0000000000000000000000000000000000000000..be3d490dde8cdea7e594b6e18bc4825a1e2eee13 --- /dev/null +++ b/vendor/symfony/process/Exception/LogicException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * LogicException for the Process Component. + * + * @author Romain Neutron + */ +class LogicException extends \LogicException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/Exception/ProcessFailedException.php b/vendor/symfony/process/Exception/ProcessFailedException.php new file mode 100644 index 0000000000000000000000000000000000000000..328acfde5e88358313c365de13738d70a66755f1 --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessFailedException.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception for failed processes. + * + * @author Johannes M. Schmitt + */ +class ProcessFailedException extends RuntimeException +{ + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s", + $process->getCommandLine(), + $process->getExitCode(), + $process->getExitCodeText(), + $process->getWorkingDirectory() + ); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", + $process->getOutput(), + $process->getErrorOutput() + ); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/vendor/symfony/process/Exception/ProcessTimedOutException.php b/vendor/symfony/process/Exception/ProcessTimedOutException.php new file mode 100644 index 0000000000000000000000000000000000000000..d45114696f640d9a05a4eea92ce1fa1b203d2a4a --- /dev/null +++ b/vendor/symfony/process/Exception/ProcessTimedOutException.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +use Symfony\Component\Process\Process; + +/** + * Exception that is thrown when a process times out. + * + * @author Johannes M. Schmitt + */ +class ProcessTimedOutException extends RuntimeException +{ + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf( + 'The process "%s" exceeded the timeout of %s seconds.', + $process->getCommandLine(), + $this->getExceededTimeout() + )); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return $this->timeoutType === self::TYPE_GENERAL; + } + + public function isIdleTimeout() + { + return $this->timeoutType === self::TYPE_IDLE; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/vendor/symfony/process/Exception/RuntimeException.php b/vendor/symfony/process/Exception/RuntimeException.php new file mode 100644 index 0000000000000000000000000000000000000000..adead2536b10362f3e2c499612ce0ffeea3f6a49 --- /dev/null +++ b/vendor/symfony/process/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Exception; + +/** + * RuntimeException for the Process Component. + * + * @author Johannes M. Schmitt + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/vendor/symfony/process/ExecutableFinder.php b/vendor/symfony/process/ExecutableFinder.php new file mode 100644 index 0000000000000000000000000000000000000000..d8e689622a537ebf3a386f7b005745629b6392f2 --- /dev/null +++ b/vendor/symfony/process/ExecutableFinder.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * Generic executable finder. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class ExecutableFinder +{ + private $suffixes = array('.exe', '.bat', '.cmd', '.com'); + + /** + * Replaces default suffixes of executable. + * + * @param array $suffixes + */ + public function setSuffixes(array $suffixes) + { + $this->suffixes = $suffixes; + } + + /** + * Adds new possible suffix to check for executable. + * + * @param string $suffix + */ + public function addSuffix($suffix) + { + $this->suffixes[] = $suffix; + } + + /** + * Finds an executable by name. + * + * @param string $name The executable name (without the extension) + * @param string $default The default to return if no executable is found + * @param array $extraDirs Additional dirs to check into + * + * @return string The executable path or default value + */ + public function find($name, $default = null, array $extraDirs = array()) + { + if (ini_get('open_basedir')) { + $searchPath = explode(PATH_SEPARATOR, ini_get('open_basedir')); + $dirs = array(); + foreach ($searchPath as $path) { + // Silencing against https://bugs.php.net/69240 + if (@is_dir($path)) { + $dirs[] = $path; + } else { + if (basename($path) == $name && @is_executable($path)) { + return $path; + } + } + } + } else { + $dirs = array_merge( + explode(PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + } + + $suffixes = array(''); + if ('\\' === DIRECTORY_SEPARATOR) { + $pathExt = getenv('PATHEXT'); + $suffixes = array_merge($suffixes, $pathExt ? explode(PATH_SEPARATOR, $pathExt) : $this->suffixes); + } + foreach ($suffixes as $suffix) { + foreach ($dirs as $dir) { + if (@is_file($file = $dir.DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === DIRECTORY_SEPARATOR || is_executable($file))) { + return $file; + } + } + } + + return $default; + } +} diff --git a/vendor/symfony/process/InputStream.php b/vendor/symfony/process/InputStream.php new file mode 100644 index 0000000000000000000000000000000000000000..831b10932599d4663e0dd157f6a5a3b261fdad8a --- /dev/null +++ b/vendor/symfony/process/InputStream.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * Provides a way to continuously write to the input of a Process until the InputStream is closed. + * + * @author Nicolas Grekas + */ +class InputStream implements \IteratorAggregate +{ + private $onEmpty = null; + private $input = array(); + private $open = true; + + /** + * Sets a callback that is called when the write buffer becomes empty. + */ + public function onEmpty(callable $onEmpty = null) + { + $this->onEmpty = $onEmpty; + } + + /** + * Appends an input to the write buffer. + * + * @param resource|scalar|\Traversable|null The input to append as stream resource, scalar or \Traversable + */ + public function write($input) + { + if (null === $input) { + return; + } + if ($this->isClosed()) { + throw new RuntimeException(sprintf('%s is closed', static::class)); + } + $this->input[] = ProcessUtils::validateInput(__METHOD__, $input); + } + + /** + * Closes the write buffer. + */ + public function close() + { + $this->open = false; + } + + /** + * Tells whether the write buffer is closed or not. + */ + public function isClosed() + { + return !$this->open; + } + + public function getIterator() + { + $this->open = true; + + while ($this->open || $this->input) { + if (!$this->input) { + yield ''; + continue; + } + $current = array_shift($this->input); + + if ($current instanceof \Iterator) { + foreach ($current as $cur) { + yield $cur; + } + } else { + yield $current; + } + if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) { + $this->write($onEmpty($this)); + } + } + } +} diff --git a/vendor/symfony/process/LICENSE b/vendor/symfony/process/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..17d16a13367dd1c08e82d1de69e2c44852dcc3d2 --- /dev/null +++ b/vendor/symfony/process/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2017 Fabien Potencier + +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. diff --git a/vendor/symfony/process/PhpExecutableFinder.php b/vendor/symfony/process/PhpExecutableFinder.php new file mode 100644 index 0000000000000000000000000000000000000000..db31cc1b3ce8b2c354ef975c45049f96279ee7c5 --- /dev/null +++ b/vendor/symfony/process/PhpExecutableFinder.php @@ -0,0 +1,90 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +/** + * An executable finder specifically designed for the PHP executable. + * + * @author Fabien Potencier + * @author Johannes M. Schmitt + */ +class PhpExecutableFinder +{ + private $executableFinder; + + public function __construct() + { + $this->executableFinder = new ExecutableFinder(); + } + + /** + * Finds The PHP executable. + * + * @param bool $includeArgs Whether or not include command arguments + * + * @return string|false The PHP executable path or false if it cannot be found + */ + public function find($includeArgs = true) + { + $args = $this->findArguments(); + $args = $includeArgs && $args ? ' '.implode(' ', $args) : ''; + + // HHVM support + if (defined('HHVM_VERSION')) { + return (getenv('PHP_BINARY') ?: PHP_BINARY).$args; + } + + // PHP_BINARY return the current sapi executable + if (PHP_BINARY && in_array(PHP_SAPI, array('cli', 'cli-server', 'phpdbg')) && is_file(PHP_BINARY)) { + return PHP_BINARY.$args; + } + + if ($php = getenv('PHP_PATH')) { + if (!is_executable($php)) { + return false; + } + + return $php; + } + + if ($php = getenv('PHP_PEAR_PHP_BIN')) { + if (is_executable($php)) { + return $php; + } + } + + $dirs = array(PHP_BINDIR); + if ('\\' === DIRECTORY_SEPARATOR) { + $dirs[] = 'C:\xampp\php\\'; + } + + return $this->executableFinder->find('php', false, $dirs); + } + + /** + * Finds the PHP executable arguments. + * + * @return array The PHP executable arguments + */ + public function findArguments() + { + $arguments = array(); + + if (defined('HHVM_VERSION')) { + $arguments[] = '--php'; + } elseif ('phpdbg' === PHP_SAPI) { + $arguments[] = '-qrr'; + } + + return $arguments; + } +} diff --git a/vendor/symfony/process/PhpProcess.php b/vendor/symfony/process/PhpProcess.php new file mode 100644 index 0000000000000000000000000000000000000000..7afd182f5afbb16dfe8ca8980d5101fe89ca6e9d --- /dev/null +++ b/vendor/symfony/process/PhpProcess.php @@ -0,0 +1,78 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * PhpProcess runs a PHP script in an independent process. + * + * $p = new PhpProcess(''); + * $p->run(); + * print $p->getOutput()."\n"; + * + * @author Fabien Potencier + */ +class PhpProcess extends Process +{ + /** + * Constructor. + * + * @param string $script The PHP script to run (as a string) + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param int $timeout The timeout in seconds + * @param array $options An array of options for proc_open + */ + public function __construct($script, $cwd = null, array $env = null, $timeout = 60, array $options = null) + { + $executableFinder = new PhpExecutableFinder(); + if (false === $php = $executableFinder->find(false)) { + $php = null; + } else { + $php = array_merge(array($php), $executableFinder->findArguments()); + } + if ('phpdbg' === PHP_SAPI) { + $file = tempnam(sys_get_temp_dir(), 'dbg'); + file_put_contents($file, $script); + register_shutdown_function('unlink', $file); + $php[] = $file; + $script = null; + } + if (null !== $options) { + @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since version 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); + } + + parent::__construct($php, $cwd, $env, $script, $timeout, $options); + } + + /** + * Sets the path to the PHP binary to use. + */ + public function setPhpBinary($php) + { + $this->setCommandLine($php); + } + + /** + * {@inheritdoc} + */ + public function start(callable $callback = null/*, array $env = array()*/) + { + if (null === $this->getCommandLine()) { + throw new RuntimeException('Unable to find the PHP executable.'); + } + $env = 1 < func_num_args() ? func_get_arg(1) : null; + + parent::start($callback, $env); + } +} diff --git a/vendor/symfony/process/Pipes/AbstractPipes.php b/vendor/symfony/process/Pipes/AbstractPipes.php new file mode 100644 index 0000000000000000000000000000000000000000..4c67d5b82c31a24113ef8a4223d9f12f1cc50d70 --- /dev/null +++ b/vendor/symfony/process/Pipes/AbstractPipes.php @@ -0,0 +1,169 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * @author Romain Neutron + * + * @internal + */ +abstract class AbstractPipes implements PipesInterface +{ + /** @var array */ + public $pipes = array(); + + /** @var string */ + private $inputBuffer = ''; + /** @var resource|scalar|\Iterator|null */ + private $input; + /** @var bool */ + private $blocked = true; + + public function __construct($input) + { + if (is_resource($input) || $input instanceof \Iterator) { + $this->input = $input; + } elseif (is_string($input)) { + $this->inputBuffer = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = array(); + } + + /** + * Returns true if a system call has been interrupted. + * + * @return bool + */ + protected function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + // stream_select returns false when the `select` system call is interrupted by an incoming signal + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + /** + * Unblocks streams. + */ + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (is_resource($this->input)) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } + + /** + * Writes input to stdin. + * + * @throws InvalidArgumentException When an input iterator yields a non supported value + */ + protected function write() + { + if (!isset($this->pipes[0])) { + return; + } + $input = $this->input; + + if ($input instanceof \Iterator) { + if (!$input->valid()) { + $input = null; + } elseif (is_resource($input = $input->current())) { + stream_set_blocking($input, 0); + } elseif (!isset($this->inputBuffer[0])) { + if (!is_string($input)) { + if (!is_scalar($input)) { + throw new InvalidArgumentException(sprintf('%s yielded a value of type "%s", but only scalars and stream resources are supported', get_class($this->input), gettype($input))); + } + $input = (string) $input; + } + $this->inputBuffer = $input; + $this->input->next(); + $input = null; + } else { + $input = null; + } + } + + $r = $e = array(); + $w = array($this->pipes[0]); + + // let's have a look if something changed in streams + if (false === $n = @stream_select($r, $w, $e, 0, 0)) { + return; + } + + foreach ($w as $stdin) { + if (isset($this->inputBuffer[0])) { + $written = fwrite($stdin, $this->inputBuffer); + $this->inputBuffer = substr($this->inputBuffer, $written); + if (isset($this->inputBuffer[0])) { + return array($this->pipes[0]); + } + } + + if ($input) { + for (;;) { + $data = fread($input, self::CHUNK_SIZE); + if (!isset($data[0])) { + break; + } + $written = fwrite($stdin, $data); + $data = substr($data, $written); + if (isset($data[0])) { + $this->inputBuffer = $data; + + return array($this->pipes[0]); + } + } + if (feof($input)) { + if ($this->input instanceof \Iterator) { + $this->input->next(); + } else { + $this->input = null; + } + } + } + } + + // no input to read on resource, buffer is empty + if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { + $this->input = null; + fclose($this->pipes[0]); + unset($this->pipes[0]); + } elseif (!$w) { + return array($this->pipes[0]); + } + } +} diff --git a/vendor/symfony/process/Pipes/PipesInterface.php b/vendor/symfony/process/Pipes/PipesInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..52bbe76b8f67b76ebd83fa5d1b82a7c7c26371e1 --- /dev/null +++ b/vendor/symfony/process/Pipes/PipesInterface.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +/** + * PipesInterface manages descriptors and pipes for the use of proc_open. + * + * @author Romain Neutron + * + * @internal + */ +interface PipesInterface +{ + const CHUNK_SIZE = 16384; + + /** + * Returns an array of descriptors for the use of proc_open. + * + * @return array + */ + public function getDescriptors(); + + /** + * Returns an array of filenames indexed by their related stream in case these pipes use temporary files. + * + * @return string[] + */ + public function getFiles(); + + /** + * Reads data in file handles and pipes. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close pipes if they've reached EOF + * + * @return string[] An array of read data indexed by their fd + */ + public function readAndWrite($blocking, $close = false); + + /** + * Returns if the current state has open file handles or pipes. + * + * @return bool + */ + public function areOpen(); + + /** + * Returns if pipes are able to read output. + * + * @return bool + */ + public function haveReadSupport(); + + /** + * Closes file handles and pipes. + */ + public function close(); +} diff --git a/vendor/symfony/process/Pipes/UnixPipes.php b/vendor/symfony/process/Pipes/UnixPipes.php new file mode 100644 index 0000000000000000000000000000000000000000..3185fe76ee03fd165f48453de1e8e1ecf26d1faa --- /dev/null +++ b/vendor/symfony/process/Pipes/UnixPipes.php @@ -0,0 +1,153 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Process; + +/** + * UnixPipes implementation uses unix pipes as handles. + * + * @author Romain Neutron + * + * @internal + */ +class UnixPipes extends AbstractPipes +{ + /** @var bool */ + private $ttyMode; + /** @var bool */ + private $ptyMode; + /** @var bool */ + private $haveReadSupport; + + public function __construct($ttyMode, $ptyMode, $input, $haveReadSupport) + { + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->haveReadSupport = (bool) $haveReadSupport; + + parent::__construct($input); + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if (!$this->haveReadSupport) { + $nullstream = fopen('/dev/null', 'c'); + + return array( + array('pipe', 'r'), + $nullstream, + $nullstream, + ); + } + + if ($this->ttyMode) { + return array( + array('file', '/dev/tty', 'r'), + array('file', '/dev/tty', 'w'), + array('file', '/dev/tty', 'w'), + ); + } + + if ($this->ptyMode && Process::isPtySupported()) { + return array( + array('pty'), + array('pty'), + array('pty'), + ); + } + + return array( + array('pipe', 'r'), + array('pipe', 'w'), // stdout + array('pipe', 'w'), // stderr + ); + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return array(); + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->unblock(); + $w = $this->write(); + + $read = $e = array(); + $r = $this->pipes; + unset($r[0]); + + // let's have a look if something changed in streams + if (($r || $w) && false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + // if a system call has been interrupted, forget about it, let's try again + // otherwise, an error occurred, let's reset pipes + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = array(); + } + + return $read; + } + + foreach ($r as $pipe) { + // prior PHP 5.4 the array passed to stream_select is modified and + // lose key association, we have to find back the key + $read[$type = array_search($pipe, $this->pipes, true)] = ''; + + do { + $data = fread($pipe, self::CHUNK_SIZE); + $read[$type] .= $data; + } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1]))); + + if (!isset($read[$type][0])) { + unset($read[$type]); + } + + if ($close && feof($pipe)) { + fclose($pipe); + unset($this->pipes[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport() + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes; + } +} diff --git a/vendor/symfony/process/Pipes/WindowsPipes.php b/vendor/symfony/process/Pipes/WindowsPipes.php new file mode 100644 index 0000000000000000000000000000000000000000..a1e3115519f35fe1ffc4f136e207f15e2304987f --- /dev/null +++ b/vendor/symfony/process/Pipes/WindowsPipes.php @@ -0,0 +1,200 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Pipes; + +use Symfony\Component\Process\Process; +use Symfony\Component\Process\Exception\RuntimeException; + +/** + * WindowsPipes implementation uses temporary files as handles. + * + * @see https://bugs.php.net/bug.php?id=51800 + * @see https://bugs.php.net/bug.php?id=65650 + * + * @author Romain Neutron + * + * @internal + */ +class WindowsPipes extends AbstractPipes +{ + /** @var array */ + private $files = array(); + /** @var array */ + private $fileHandles = array(); + /** @var array */ + private $readBytes = array( + Process::STDOUT => 0, + Process::STDERR => 0, + ); + /** @var bool */ + private $haveReadSupport; + + public function __construct($input, $haveReadSupport) + { + $this->haveReadSupport = (bool) $haveReadSupport; + + if ($this->haveReadSupport) { + // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big. + // Workaround for this problem is to use temporary files instead of pipes on Windows platform. + // + // @see https://bugs.php.net/bug.php?id=51800 + $pipes = array( + Process::STDOUT => Process::OUT, + Process::STDERR => Process::ERR, + ); + $tmpCheck = false; + $tmpDir = sys_get_temp_dir(); + $lastError = 'unknown reason'; + set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; }); + for ($i = 0;; ++$i) { + foreach ($pipes as $pipe => $name) { + $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name); + if (file_exists($file) && !unlink($file)) { + continue 2; + } + $h = fopen($file, 'xb'); + if (!$h) { + $error = $lastError; + if ($tmpCheck || $tmpCheck = unlink(tempnam(false, 'sf_check_'))) { + continue; + } + restore_error_handler(); + throw new RuntimeException(sprintf('A temporary file could not be opened to write the process output: %s', $error)); + } + if (!$h || !$this->fileHandles[$pipe] = fopen($file, 'rb')) { + continue 2; + } + if (isset($this->files[$pipe])) { + unlink($this->files[$pipe]); + } + $this->files[$pipe] = $file; + } + break; + } + restore_error_handler(); + } + + parent::__construct($input); + } + + public function __destruct() + { + $this->close(); + $this->removeFiles(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if (!$this->haveReadSupport) { + $nullstream = fopen('NUL', 'c'); + + return array( + array('pipe', 'r'), + $nullstream, + $nullstream, + ); + } + + // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/bug.php?id=51800) + // We're not using file handles as it can produce corrupted output https://bugs.php.net/bug.php?id=65650 + // So we redirect output within the commandline and pass the nul device to the process + return array( + array('pipe', 'r'), + array('file', 'NUL', 'w'), + array('file', 'NUL', 'w'), + ); + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->unblock(); + $w = $this->write(); + $read = $r = $e = array(); + + if ($blocking) { + if ($w) { + @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6); + } elseif ($this->fileHandles) { + usleep(Process::TIMEOUT_PRECISION * 1E6); + } + } + foreach ($this->fileHandles as $type => $fileHandle) { + $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]); + + if (isset($data[0])) { + $this->readBytes[$type] += strlen($data); + $read[$type] = $data; + } + if ($close) { + fclose($fileHandle); + unset($this->fileHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function haveReadSupport() + { + return $this->haveReadSupport; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return $this->pipes && $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $handle) { + fclose($handle); + } + $this->fileHandles = array(); + } + + /** + * Removes temporary files. + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = array(); + } +} diff --git a/vendor/symfony/process/Process.php b/vendor/symfony/process/Process.php new file mode 100644 index 0000000000000000000000000000000000000000..05f88d2d1b1122abdd25bb570124bf477ec7a25b --- /dev/null +++ b/vendor/symfony/process/Process.php @@ -0,0 +1,1735 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessFailedException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\Pipes\PipesInterface; +use Symfony\Component\Process\Pipes\UnixPipes; +use Symfony\Component\Process\Pipes\WindowsPipes; + +/** + * Process is a thin wrapper around proc_* functions to easily + * start independent PHP processes. + * + * @author Fabien Potencier + * @author Romain Neutron + */ +class Process implements \IteratorAggregate +{ + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + // Timeout Precision in seconds. + const TIMEOUT_PRECISION = 0.2; + + const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking + const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory + const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating + const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating + + private $callback; + private $hasCallback = false; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $options = array('suppress_errors' => true); + private $exitcode; + private $fallbackStatus = array(); + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility = true; + private $enhanceSigchildCompatibility; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty; + private $pty; + private $inheritEnv = false; + + private $useFileHandles = false; + /** @var PipesInterface */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * Exit codes translation table. + * + * User-defined errors must use exit codes in the 64-113 range. + * + * @var array + */ + public static $exitCodes = array( + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ); + + /** + * Constructor. + * + * @param string|array $commandline The command line to run + * @param string|null $cwd The working directory or null to use the working dir of the current PHP process + * @param array|null $env The environment variables or null to use the same environment as the current PHP process + * @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input + * @param int|float|null $timeout The timeout in seconds or null to disable + * @param array $options An array of options for proc_open + * + * @throws RuntimeException When proc_open is not installed + */ + public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = null) + { + if (!function_exists('proc_open')) { + throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started + // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected + // @see : https://bugs.php.net/bug.php?id=51800 + // @see : https://bugs.php.net/bug.php?id=50524 + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->setInput($input); + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR; + $this->pty = false; + $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); + if (null !== $options) { + @trigger_error(sprintf('The $options parameter of the %s constructor is deprecated since version 3.3 and will be removed in 4.0.', __CLASS__), E_USER_DEPRECATED); + $this->options = array_replace($this->options, $options); + } + } + + public function __destruct() + { + $this->stop(0); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * Runs the process. + * + * The callback receives the type of output (out or err) and + * some bytes from the output in real-time. It allows to have feedback + * from the independent process during execution. + * + * The STDOUT and STDERR are also available after the process is finished + * via the getOutput() and getErrorOutput() methods. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param array $env An array of additional env vars to set when running the process + * + * @return int The exit status code + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process stopped after receiving signal + * @throws LogicException In case a callback is provided and output has been disabled + * + * @final since version 3.3 + */ + public function run($callback = null/*, array $env = array()*/) + { + $env = 1 < func_num_args() ? func_get_arg(1) : null; + $this->start($callback, $env); + + return $this->wait(); + } + + /** + * Runs the process. + * + * This is identical to run() except that an exception is thrown if the process + * exits with a non-zero exit code. + * + * @param callable|null $callback + * @param array $env An array of additional env vars to set when running the process + * + * @return self + * + * @throws RuntimeException if PHP was compiled with --enable-sigchild and the enhanced sigchild compatibility mode is not enabled + * @throws ProcessFailedException if the process didn't terminate successfully + * + * @final since version 3.3 + */ + public function mustRun(callable $callback = null/*, array $env = array()*/) + { + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + $env = 1 < func_num_args() ? func_get_arg(1) : null; + + if (0 !== $this->run($callback, $env)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * Starts the process and returns after writing the input to STDIN. + * + * This method blocks until all STDIN data is sent to the process then it + * returns while the process runs in the background. + * + * The termination of the process can be awaited with wait(). + * + * The callback receives the type of output (out or err) and some bytes from + * the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param array $env An array of additional env vars to set when running the process + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * @throws LogicException In case a callback is provided and output has been disabled + */ + public function start(callable $callback = null/*, array $env = array()*/) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + if (2 <= func_num_args()) { + $env = func_get_arg(1); + } else { + if (__CLASS__ !== static::class) { + $r = new \ReflectionMethod($this, __FUNCTION__); + if (__CLASS__ !== $r->getDeclaringClass()->getName() && (2 > $r->getNumberOfParameters() || 'env' !== $r->getParameters()[0]->name)) { + @trigger_error(sprintf('The %s::start() method expects a second "$env" argument since version 3.3. It will be made mandatory in 4.0.', static::class), E_USER_DEPRECATED); + } + } + $env = null; + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $this->hasCallback = null !== $callback; + $descriptors = $this->getDescriptors(); + $inheritEnv = $this->inheritEnv; + + if (is_array($commandline = $this->commandline)) { + $commandline = implode(' ', array_map(array($this, 'escapeArgument'), $commandline)); + + if ('\\' !== DIRECTORY_SEPARATOR) { + // exec is mandatory to deal with sending a signal to the process + $commandline = 'exec '.$commandline; + } + } + + if (null === $env) { + $env = $this->env; + } else { + if ($this->env) { + $env += $this->env; + } + $inheritEnv = true; + } + + $envBackup = array(); + if (null !== $env && $inheritEnv) { + foreach ($env as $k => $v) { + $envBackup[$k] = getenv($k); + putenv(false === $v || null === $v ? $k : "$k=$v"); + } + $env = null; + } elseif (null !== $env) { + @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', E_USER_DEPRECATED); + } + if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { + $this->options['bypass_shell'] = true; + $commandline = $this->prepareWindowsCommandLine($commandline, $envBackup, $env); + } elseif (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + // last exit code is output on the fourth pipe and caught to work around --enable-sigchild + $descriptors[3] = array('pipe', 'w'); + + // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input + $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;'; + $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code'; + + // Workaround for the bug, when PTS functionality is enabled. + // @see : https://bugs.php.net/69442 + $ptsWorkaround = fopen(__FILE__, 'r'); + } + + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $env, $this->options); + + foreach ($envBackup as $k => $v) { + putenv(false === $v ? $k : "$k=$v"); + } + + if (!is_resource($this->process)) { + throw new RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if (isset($descriptors[3])) { + $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]); + } + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * Restarts the process. + * + * Be warned that the process is cloned before being started. + * + * @param callable|null $callback A PHP callback to run whenever there is some + * output available on STDOUT or STDERR + * @param array $env An array of additional env vars to set when running the process + * + * @return $this + * + * @throws RuntimeException When process can't be launched + * @throws RuntimeException When process is already running + * + * @see start() + * + * @final since version 3.3 + */ + public function restart(callable $callback = null/*, array $env = array()*/) + { + if ($this->isRunning()) { + throw new RuntimeException('Process is already running'); + } + $env = 1 < func_num_args() ? func_get_arg(1) : null; + + $process = clone $this; + $process->start($callback, $env); + + return $process; + } + + /** + * Waits for the process to terminate. + * + * The callback receives the type of output (out or err) and some bytes + * from the output in real-time while writing the standard input to the process. + * It allows to have feedback from the independent process during execution. + * + * @param callable|null $callback A valid PHP callback + * + * @return int The exitcode of the process + * + * @throws RuntimeException When process timed out + * @throws RuntimeException When process stopped after receiving signal + * @throws LogicException When process is not yet started + */ + public function wait(callable $callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + + if (null !== $callback) { + if (!$this->processPipes->haveReadSupport()) { + $this->stop(0); + throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait'); + } + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running); + } while ($running); + + while ($this->isRunning()) { + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * Returns the Pid (process identifier), if applicable. + * + * @return int|null The process id if running, null otherwise + */ + public function getPid() + { + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php) + * + * @return $this + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + * @throws LogicException if an idle timeout is set + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new LogicException('Output can not be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return $this + * + * @throws RuntimeException In case the process is already running + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * Returns true in case the output is disabled, false otherwise. + * + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * Returns the current output of the process (STDOUT). + * + * @return string The process output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stdout, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the output incrementally. + * + * In comparison with the getOutput method which always return the whole + * output, this one returns the new output since the last call. + * + * @return string The process output since the last call + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + $this->incrementalOutputOffset = ftell($this->stdout); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR). + * + * @param int $flags A bit field of Process::ITER_* flags + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + * + * @return \Generator + */ + public function getIterator($flags = 0) + { + $this->readPipesForOutput(__FUNCTION__, false); + + $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags); + $blocking = !(self::ITER_NON_BLOCKING & $flags); + $yieldOut = !(self::ITER_SKIP_OUT & $flags); + $yieldErr = !(self::ITER_SKIP_ERR & $flags); + + while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) { + if ($yieldOut) { + $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset); + + if (isset($out[0])) { + if ($clearOutput) { + $this->clearOutput(); + } else { + $this->incrementalOutputOffset = ftell($this->stdout); + } + + yield self::OUT => $out; + } + } + + if ($yieldErr) { + $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + + if (isset($err[0])) { + if ($clearOutput) { + $this->clearErrorOutput(); + } else { + $this->incrementalErrorOutputOffset = ftell($this->stderr); + } + + yield self::ERR => $err; + } + } + + if (!$blocking && !isset($out[0]) && !isset($err[0])) { + yield self::OUT => ''; + } + + $this->checkTimeout(); + $this->readPipesForOutput(__FUNCTION__, $blocking); + } + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearOutput() + { + ftruncate($this->stdout, 0); + fseek($this->stdout, 0); + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * Returns the current error output of the process (STDERR). + * + * @return string The process error output + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + if (false === $ret = stream_get_contents($this->stderr, -1, 0)) { + return ''; + } + + return $ret; + } + + /** + * Returns the errorOutput incrementally. + * + * In comparison with the getErrorOutput method which always return the + * whole error output, this one returns the new error output since the last + * call. + * + * @return string The process error output since the last call + * + * @throws LogicException in case the output has been disabled + * @throws LogicException In case the process is not started + */ + public function getIncrementalErrorOutput() + { + $this->readPipesForOutput(__FUNCTION__); + + $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset); + $this->incrementalErrorOutputOffset = ftell($this->stderr); + + if (false === $latest) { + return ''; + } + + return $latest; + } + + /** + * Clears the process output. + * + * @return $this + */ + public function clearErrorOutput() + { + ftruncate($this->stderr, 0); + fseek($this->stderr, 0); + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * Returns the exit code returned by the process. + * + * @return null|int The exit status code, null if the Process is not terminated + * + * @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled + */ + public function getExitCode() + { + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * Returns a string representation for the exit code returned by the process. + * + * This method relies on the Unix exit code status standardization + * and might not be relevant for other operating systems. + * + * @return null|string A string representation for the exit status code, null if the Process is not terminated + * + * @see http://tldp.org/LDP/abs/html/exitcodes.html + * @see http://en.wikipedia.org/wiki/Unix_signal + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * Checks if the process ended successfully. + * + * @return bool true if the process ended successfully, false otherwise + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * Returns true if the child process has been terminated by an uncaught signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if (!$this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + return $this->processInformation['signaled']; + } + + /** + * Returns the number of the signal that caused the child process to terminate its execution. + * + * It is only meaningful if hasBeenSignaled() returns true. + * + * @return int + * + * @throws RuntimeException In case --enable-sigchild is activated + * @throws LogicException In case the process is not terminated + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled() && (!$this->enhanceSigchildCompatibility || -1 === $this->processInformation['termsig'])) { + throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + return $this->processInformation['termsig']; + } + + /** + * Returns true if the child process has been stopped by a signal. + * + * It always returns false on Windows. + * + * @return bool + * + * @throws LogicException In case the process is not terminated + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopped']; + } + + /** + * Returns the number of the signal that caused the child process to stop its execution. + * + * It is only meaningful if hasBeenStopped() returns true. + * + * @return int + * + * @throws LogicException In case the process is not terminated + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + return $this->processInformation['stopsig']; + } + + /** + * Checks if the process is currently running. + * + * @return bool true if the process is currently running, false otherwise + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * Checks if the process has been started with no regard to the current state. + * + * @return bool true if status is ready, false otherwise + */ + public function isStarted() + { + return $this->status != self::STATUS_READY; + } + + /** + * Checks if the process is terminated. + * + * @return bool true if process is terminated, false otherwise + */ + public function isTerminated() + { + $this->updateStatus(false); + + return $this->status == self::STATUS_TERMINATED; + } + + /** + * Gets the process status. + * + * The status is one of: ready, started, terminated. + * + * @return string The current process status + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * Stops the process. + * + * @param int|float $timeout The timeout in seconds + * @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9) + * + * @return int The exit-code of the process + */ + public function stop($timeout = 10, $signal = null) + { + $timeoutMicro = microtime(true) + $timeout; + if ($this->isRunning()) { + // given `SIGTERM` may not be defined and that `proc_terminate` uses the constant value and not the constant itself, we use the same here + $this->doSignal(15, false); + do { + usleep(1000); + } while ($this->isRunning() && microtime(true) < $timeoutMicro); + + if ($this->isRunning()) { + // Avoid exception here: process is supposed to be running, but it might have stopped just + // after this line. In any case, let's silently discard the error, we cannot do anything. + $this->doSignal($signal ?: 9, false); + } + } + + if ($this->isRunning()) { + if (isset($this->fallbackStatus['pid'])) { + unset($this->fallbackStatus['pid']); + + return $this->stop(0, $signal); + } + $this->close(); + } + + return $this->exitcode; + } + + /** + * Adds a line to the STDOUT stream. + * + * @internal + * + * @param string $line The line to append + */ + public function addOutput($line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stdout, 0, SEEK_END); + fwrite($this->stdout, $line); + fseek($this->stdout, $this->incrementalOutputOffset); + } + + /** + * Adds a line to the STDERR stream. + * + * @internal + * + * @param string $line The line to append + */ + public function addErrorOutput($line) + { + $this->lastOutputTime = microtime(true); + + fseek($this->stderr, 0, SEEK_END); + fwrite($this->stderr, $line); + fseek($this->stderr, $this->incrementalErrorOutputOffset); + } + + /** + * Gets the command line to be executed. + * + * @return string The command to execute + */ + public function getCommandLine() + { + return is_array($this->commandline) ? implode(' ', array_map(array($this, 'escapeArgument'), $this->commandline)) : $this->commandline; + } + + /** + * Sets the command line to be executed. + * + * @param string|array $commandline The command to execute + * + * @return self The current Process instance + */ + public function setCommandLine($commandline) + { + $this->commandline = $commandline; + + return $this; + } + + /** + * Gets the process timeout (max. runtime). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Gets the process idle timeout (max. time since last output). + * + * @return float|null The timeout in seconds or null if it's disabled + */ + public function getIdleTimeout() + { + return $this->idleTimeout; + } + + /** + * Sets the process timeout (max. runtime). + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return self The current Process instance + * + * @throws InvalidArgumentException if the timeout is negative + */ + public function setTimeout($timeout) + { + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Sets the process idle timeout (max. time since last output). + * + * To disable the timeout, set this value to null. + * + * @param int|float|null $timeout The timeout in seconds + * + * @return self The current Process instance + * + * @throws LogicException if the output is disabled + * @throws InvalidArgumentException if the timeout is negative + */ + public function setIdleTimeout($timeout) + { + if (null !== $timeout && $this->outputDisabled) { + throw new LogicException('Idle timeout can not be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * Enables or disables the TTY mode. + * + * @param bool $tty True to enabled and false to disable + * + * @return self The current Process instance + * + * @throws RuntimeException In case the TTY mode is not supported + */ + public function setTty($tty) + { + if ('\\' === DIRECTORY_SEPARATOR && $tty) { + throw new RuntimeException('TTY mode is not supported on Windows platform.'); + } + if ($tty) { + static $isTtySupported; + + if (null === $isTtySupported) { + $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', array(array('file', '/dev/tty', 'r'), array('file', '/dev/tty', 'w'), array('file', '/dev/tty', 'w')), $pipes); + } + + if (!$isTtySupported) { + throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.'); + } + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * Checks if the TTY mode is enabled. + * + * @return bool true if the TTY mode is enabled, false otherwise + */ + public function isTty() + { + return $this->tty; + } + + /** + * Sets PTY mode. + * + * @param bool $bool + * + * @return self + */ + public function setPty($bool) + { + $this->pty = (bool) $bool; + + return $this; + } + + /** + * Returns PTY state. + * + * @return bool + */ + public function isPty() + { + return $this->pty; + } + + /** + * Gets the working directory. + * + * @return string|null The current working directory or null on failure + */ + public function getWorkingDirectory() + { + if (null === $this->cwd) { + // getcwd() will return false if any one of the parent directories does not have + // the readable or search mode set, even if the current directory does + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * Sets the current working directory. + * + * @param string $cwd The new working directory + * + * @return self The current Process instance + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Gets the environment variables. + * + * @return array The current environment variables + */ + public function getEnv() + { + return $this->env; + } + + /** + * Sets the environment variables. + * + * An environment variable value should be a string. + * If it is an array, the variable is ignored. + * If it is false or null, it will be removed when + * env vars are otherwise inherited. + * + * That happens in PHP when 'argv' is registered into + * the $_ENV array for instance. + * + * @param array $env The new environment variables + * + * @return self The current Process instance + */ + public function setEnv(array $env) + { + // Process can not handle env values that are arrays + $env = array_filter($env, function ($value) { + return !is_array($value); + }); + + $this->env = $env; + + return $this; + } + + /** + * Gets the Process input. + * + * @return resource|string|\Iterator|null The Process input + */ + public function getInput() + { + return $this->input; + } + + /** + * Sets the input. + * + * This content will be passed to the underlying process standard input. + * + * @param resource|scalar|\Traversable|null $input The content + * + * @return self The current Process instance + * + * @throws LogicException In case the process is running + */ + public function setInput($input) + { + if ($this->isRunning()) { + throw new LogicException('Input can not be set while the process is running.'); + } + + $this->input = ProcessUtils::validateInput(__METHOD__, $input); + + return $this; + } + + /** + * Gets the options for proc_open. + * + * @return array The current options + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function getOptions() + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + + return $this->options; + } + + /** + * Sets the options for proc_open. + * + * @param array $options The new options + * + * @return self The current Process instance + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function setOptions(array $options) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + + $this->options = $options; + + return $this; + } + + /** + * Gets whether or not Windows compatibility is enabled. + * + * This is true by default. + * + * @return bool + * + * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled. + */ + public function getEnhanceWindowsCompatibility() + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); + + return $this->enhanceWindowsCompatibility; + } + + /** + * Sets whether or not Windows compatibility is enabled. + * + * @param bool $enhance + * + * @return self The current Process instance + * + * @deprecated since version 3.3, to be removed in 4.0. Enhanced Windows compatibility will always be enabled. + */ + public function setEnhanceWindowsCompatibility($enhance) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Enhanced Windows compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); + + $this->enhanceWindowsCompatibility = (bool) $enhance; + + return $this; + } + + /** + * Returns whether sigchild compatibility mode is activated or not. + * + * @return bool + * + * @deprecated since version 3.3, to be removed in 4.0. Sigchild compatibility will always be enabled. + */ + public function getEnhanceSigchildCompatibility() + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); + + return $this->enhanceSigchildCompatibility; + } + + /** + * Activates sigchild compatibility mode. + * + * Sigchild compatibility mode is required to get the exit code and + * determine the success of a process when PHP has been compiled with + * the --enable-sigchild option + * + * @param bool $enhance + * + * @return self The current Process instance + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function setEnhanceSigchildCompatibility($enhance) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Sigchild compatibility will always be enabled.', __METHOD__), E_USER_DEPRECATED); + + $this->enhanceSigchildCompatibility = (bool) $enhance; + + return $this; + } + + /** + * Sets whether environment variables will be inherited or not. + * + * @param bool $inheritEnv + * + * @return self The current Process instance + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + if (!$inheritEnv) { + @trigger_error('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', E_USER_DEPRECATED); + } + + $this->inheritEnv = (bool) $inheritEnv; + + return $this; + } + + /** + * Returns whether environment variables will be inherited or not. + * + * @return bool + * + * @deprecated since version 3.3, to be removed in 4.0. Environment variables will always be inherited. + */ + public function areEnvironmentVariablesInherited() + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Environment variables will always be inherited.', __METHOD__), E_USER_DEPRECATED); + + return $this->inheritEnv; + } + + /** + * Performs a check between the timeout definition and the time the process started. + * + * In case you run a background process (with the start method), you should + * trigger this method regularly to ensure the process timeout + * + * @throws ProcessTimedOutException In case the timeout was reached + */ + public function checkTimeout() + { + if ($this->status !== self::STATUS_STARTED) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(0); + + throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE); + } + } + + /** + * Returns whether PTY is supported on the current operating system. + * + * @return bool + */ + public static function isPtySupported() + { + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + return $result = false; + } + + return $result = (bool) @proc_open('echo 1 >/dev/null', array(array('pty'), array('pty'), array('pty')), $pipes); + } + + /** + * Creates the descriptors needed by the proc_open. + * + * @return array + */ + private function getDescriptors() + { + if ($this->input instanceof \Iterator) { + $this->input->rewind(); + } + if ('\\' === DIRECTORY_SEPARATOR) { + $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback); + } else { + $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback); + } + + return $this->processPipes->getDescriptors(); + } + + /** + * Builds up the callback used by wait(). + * + * The callbacks adds all occurred output to the specific buffer and calls + * the user callback (if present) with the received output. + * + * @param callable|null $callback The user defined PHP callback + * + * @return \Closure A PHP closure + */ + protected function buildCallback(callable $callback = null) + { + if ($this->outputDisabled) { + return function ($type, $data) use ($callback) { + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + } + + $out = self::OUT; + + return function ($type, $data) use ($callback, $out) { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + } + + /** + * Updates the status of the process, reads pipes. + * + * @param bool $blocking Whether to use a blocking read call + */ + protected function updateStatus($blocking) + { + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $running = $this->processInformation['running']; + + $this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running); + + if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->processInformation = $this->fallbackStatus + $this->processInformation; + } + + if (!$running) { + $this->close(); + } + } + + /** + * Returns whether PHP has been compiled with the '--enable-sigchild' option or not. + * + * @return bool + */ + protected function isSigchildEnabled() + { + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!function_exists('phpinfo') || defined('HHVM_VERSION')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * Reads pipes for the freshest output. + * + * @param string $caller The name of the method that needs fresh outputs + * @param bool $blocking Whether to use blocking calls or not + * + * @throws LogicException in case output has been disabled or process is not started + */ + private function readPipesForOutput($caller, $blocking = false) + { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted($caller); + + $this->updateStatus($blocking); + } + + /** + * Validates and returns the filtered timeout. + * + * @param int|float|null $timeout + * + * @return float|null + * + * @throws InvalidArgumentException if the given timeout is a negative number + */ + private function validateTimeout($timeout) + { + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * Reads pipes, executes callback. + * + * @param bool $blocking Whether to use blocking calls or not + * @param bool $close Whether to close file handles or not + */ + private function readPipes($blocking, $close) + { + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 !== $type) { + $callback($type === self::STDOUT ? self::OUT : self::ERR, $data); + } elseif (!isset($this->fallbackStatus['signaled'])) { + $this->fallbackStatus['exitcode'] = (int) $data; + } + } + } + + /** + * Closes process resource, closes file handles, sets the exitcode. + * + * @return int The exitcode + */ + private function close() + { + $this->processPipes->close(); + if (is_resource($this->process)) { + proc_close($this->process); + } + $this->exitcode = $this->processInformation['exitcode']; + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode) { + if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) { + // if process has been signaled, no exitcode but a valid termsig, apply Unix convention + $this->exitcode = 128 + $this->processInformation['termsig']; + } elseif ($this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + $this->processInformation['signaled'] = true; + $this->processInformation['termsig'] = -1; + } + } + + // Free memory from self-reference callback created by buildCallback + // Doing so in other contexts like __destruct or by garbage collector is ineffective + // Now pipes are closed, so the callback is no longer necessary + $this->callback = null; + + return $this->exitcode; + } + + /** + * Resets data related to the latest run of the process. + */ + private function resetProcessData() + { + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackStatus = array(); + $this->processInformation = null; + $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+'); + $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'wb+'); + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * Sends a POSIX signal to the process. + * + * @param int $signal A valid POSIX signal (see http://www.php.net/manual/en/pcntl.constants.php) + * @param bool $throwException Whether to throw exception in case signal failed + * + * @return bool True if the signal was sent successfully, false otherwise + * + * @throws LogicException In case the process is not running + * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed + * @throws RuntimeException In case of failure + */ + private function doSignal($signal, $throwException) + { + if (null === $pid = $this->getPid()) { + if ($throwException) { + throw new LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode); + if ($exitCode && $this->isRunning()) { + if ($throwException) { + throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output))); + } + + return false; + } + } else { + if (!$this->enhanceSigchildCompatibility || !$this->isSigchildEnabled()) { + $ok = @proc_terminate($this->process, $signal); + } elseif (function_exists('posix_kill')) { + $ok = @posix_kill($pid, $signal); + } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), array(2 => array('pipe', 'w')), $pipes)) { + $ok = false === fgets($pipes[2]); + } + if (!$ok) { + if ($throwException) { + throw new RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } + + return false; + } + } + + $this->latestSignal = (int) $signal; + $this->fallbackStatus['signaled'] = true; + $this->fallbackStatus['exitcode'] = -1; + $this->fallbackStatus['termsig'] = $this->latestSignal; + + return true; + } + + private function prepareWindowsCommandLine($cmd, array &$envBackup, array &$env = null) + { + $uid = uniqid('', true); + $varCount = 0; + $varCache = array(); + $cmd = preg_replace_callback( + '/"(?:( + [^"%!^]*+ + (?: + (?: !LF! | "(?:\^[%!^])?+" ) + [^"%!^]*+ + )++ + ) | [^"]*+ )"/x', + function ($m) use (&$envBackup, &$env, &$varCache, &$varCount, $uid) { + if (!isset($m[1])) { + return $m[0]; + } + if (isset($varCache[$m[0]])) { + return $varCache[$m[0]]; + } + if (false !== strpos($value = $m[1], "\0")) { + $value = str_replace("\0", '?', $value); + } + if (false === strpbrk($value, "\"%!\n")) { + return '"'.$value.'"'; + } + + $value = str_replace(array('!LF!', '"^!"', '"^%"', '"^^"', '""'), array("\n", '!', '%', '^', '"'), $value); + $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"'; + $var = $uid.++$varCount; + + if (null === $env) { + putenv("$var=$value"); + } else { + $env[$var] = $value; + } + + $envBackup[$var] = false; + + return $varCache[$m[0]] = '!'.$var.'!'; + }, + $cmd + ); + + $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $cmd .= ' '.$offset.'>"'.$filename.'"'; + } + + return $cmd; + } + + /** + * Ensures the process is running or terminated, throws a LogicException if the process has a not started. + * + * @param string $functionName The function name that was called + * + * @throws LogicException If the process has not run. + */ + private function requireProcessIsStarted($functionName) + { + if (!$this->isStarted()) { + throw new LogicException(sprintf('Process must be started before calling %s.', $functionName)); + } + } + + /** + * Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`. + * + * @param string $functionName The function name that was called + * + * @throws LogicException If the process is not yet terminated. + */ + private function requireProcessIsTerminated($functionName) + { + if (!$this->isTerminated()) { + throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); + } + } + + /** + * Escapes a string to be used as a shell argument. + * + * @param string $argument The argument that will be escaped + * + * @return string The escaped argument + */ + private function escapeArgument($argument) + { + if ('\\' !== DIRECTORY_SEPARATOR) { + return "'".str_replace("'", "'\\''", $argument)."'"; + } + if ('' === $argument = (string) $argument) { + return '""'; + } + if (false !== strpos($argument, "\0")) { + $argument = str_replace("\0", '?', $argument); + } + if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { + return $argument; + } + $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); + + return '"'.str_replace(array('"', '^', '%', '!', "\n"), array('""', '"^^"', '"^%"', '"^!"', '!LF!'), $argument).'"'; + } +} diff --git a/vendor/symfony/process/ProcessBuilder.php b/vendor/symfony/process/ProcessBuilder.php new file mode 100644 index 0000000000000000000000000000000000000000..36db35edb5e438ac514a1d66c014276fc56111b5 --- /dev/null +++ b/vendor/symfony/process/ProcessBuilder.php @@ -0,0 +1,288 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; +use Symfony\Component\Process\Exception\LogicException; + +/** + * Process builder. + * + * @author Kris Wallsmith + */ +class ProcessBuilder +{ + private $arguments; + private $cwd; + private $env = array(); + private $input; + private $timeout = 60; + private $options; + private $inheritEnv = true; + private $prefix = array(); + private $outputDisabled = false; + + /** + * Constructor. + * + * @param string[] $arguments An array of arguments + */ + public function __construct(array $arguments = array()) + { + $this->arguments = $arguments; + } + + /** + * Creates a process builder instance. + * + * @param string[] $arguments An array of arguments + * + * @return static + */ + public static function create(array $arguments = array()) + { + return new static($arguments); + } + + /** + * Adds an unescaped argument to the command string. + * + * @param string $argument A command argument + * + * @return $this + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * Adds a prefix to the command string. + * + * The prefix is preserved when resetting arguments. + * + * @param string|array $prefix A command prefix or an array of command prefixes + * + * @return $this + */ + public function setPrefix($prefix) + { + $this->prefix = is_array($prefix) ? $prefix : array($prefix); + + return $this; + } + + /** + * Sets the arguments of the process. + * + * Arguments must not be escaped. + * Previous arguments are removed. + * + * @param string[] $arguments + * + * @return $this + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * Sets the working directory. + * + * @param null|string $cwd The working directory + * + * @return $this + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * Sets whether environment variables will be inherited or not. + * + * @param bool $inheritEnv + * + * @return $this + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + + $this->inheritEnv = $inheritEnv; + + return $this; + } + + /** + * Sets an environment variable. + * + * Setting a variable overrides its previous value. Use `null` to unset a + * defined environment variable. + * + * @param string $name The variable name + * @param null|string $value The variable value + * + * @return $this + */ + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + /** + * Adds a set of environment variables. + * + * Already existing environment variables with the same name will be + * overridden by the new values passed to this method. Pass `null` to unset + * a variable. + * + * @param array $variables The variables + * + * @return $this + */ + public function addEnvironmentVariables(array $variables) + { + $this->env = array_replace($this->env, $variables); + + return $this; + } + + /** + * Sets the input of the process. + * + * @param resource|scalar|\Traversable|null $input The input content + * + * @return $this + * + * @throws InvalidArgumentException In case the argument is invalid + */ + public function setInput($input) + { + $this->input = ProcessUtils::validateInput(__METHOD__, $input); + + return $this; + } + + /** + * Sets the process timeout. + * + * To disable the timeout, set this value to null. + * + * @param float|null $timeout + * + * @return $this + * + * @throws InvalidArgumentException + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * Adds a proc_open option. + * + * @param string $name The option name + * @param string $value The option value + * + * @return $this + * + * @deprecated since version 3.3, to be removed in 4.0. + */ + public function setOption($name, $value) + { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0.', __METHOD__), E_USER_DEPRECATED); + + $this->options[$name] = $value; + + return $this; + } + + /** + * Disables fetching output and error output from the underlying process. + * + * @return $this + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return $this + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + + /** + * Creates a Process instance and returns it. + * + * @return Process + * + * @throws LogicException In case no arguments have been provided + */ + public function getProcess() + { + if (0 === count($this->prefix) && 0 === count($this->arguments)) { + throw new LogicException('You must add() command arguments before calling getProcess().'); + } + + $arguments = array_merge($this->prefix, $this->arguments); + $process = new Process($arguments, $this->cwd, $this->env, $this->input, $this->timeout, $this->options); + // to preserve the BC with symfony <3.3, we convert the array structure + // to a string structure to avoid the prefixing with the exec command + $process->setCommandLine($process->getCommandLine()); + + if ($this->inheritEnv) { + $process->inheritEnvironmentVariables(); + } + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; + } +} diff --git a/vendor/symfony/process/ProcessUtils.php b/vendor/symfony/process/ProcessUtils.php new file mode 100644 index 0000000000000000000000000000000000000000..24438d985cad9c040adb7b677bfa57eaec697ec2 --- /dev/null +++ b/vendor/symfony/process/ProcessUtils.php @@ -0,0 +1,123 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process; + +use Symfony\Component\Process\Exception\InvalidArgumentException; + +/** + * ProcessUtils is a bunch of utility methods. + * + * This class contains static methods only and is not meant to be instantiated. + * + * @author Martin Hasoň + */ +class ProcessUtils +{ + /** + * This class should not be instantiated. + */ + private function __construct() + { + } + + /** + * Escapes a string to be used as a shell argument. + * + * @param string $argument The argument that will be escaped + * + * @return string The escaped argument + * + * @deprecated since version 3.3, to be removed in 4.0. Use a command line array or give env vars to the `Process::start/run()` method instead. + */ + public static function escapeArgument($argument) + { + @trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 and will be removed in 4.0. Use a command line array or give env vars to the Process::start/run() method instead.', E_USER_DEPRECATED); + + //Fix for PHP bug #43784 escapeshellarg removes % from given string + //Fix for PHP bug #49446 escapeshellarg doesn't work on Windows + //@see https://bugs.php.net/bug.php?id=43784 + //@see https://bugs.php.net/bug.php?id=49446 + if ('\\' === DIRECTORY_SEPARATOR) { + if ('' === $argument) { + return escapeshellarg($argument); + } + + $escapedArgument = ''; + $quote = false; + foreach (preg_split('/(")/', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif (self::isSurroundedBy($part, '%')) { + // Avoid environment variable expansion + $escapedArgument .= '^%"'.substr($part, 1, -1).'"^%'; + } else { + // escape trailing backslash + if ('\\' === substr($part, -1)) { + $part .= '\\'; + } + $quote = true; + $escapedArgument .= $part; + } + } + if ($quote) { + $escapedArgument = '"'.$escapedArgument.'"'; + } + + return $escapedArgument; + } + + return "'".str_replace("'", "'\\''", $argument)."'"; + } + + /** + * Validates and normalizes a Process input. + * + * @param string $caller The name of method call that validates the input + * @param mixed $input The input to validate + * + * @return mixed The validated input + * + * @throws InvalidArgumentException In case the input is not valid + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (is_resource($input)) { + return $input; + } + if (is_string($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + if ($input instanceof Process) { + return $input->getIterator($input::ITER_SKIP_ERR); + } + if ($input instanceof \Iterator) { + return $input; + } + if ($input instanceof \Traversable) { + return new \IteratorIterator($input); + } + + throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller)); + } + + return $input; + } + + private static function isSurroundedBy($arg, $char) + { + return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; + } +} diff --git a/vendor/symfony/process/README.md b/vendor/symfony/process/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b7ca5b4254942c813ac60733057a47b1be072ec5 --- /dev/null +++ b/vendor/symfony/process/README.md @@ -0,0 +1,13 @@ +Process Component +================= + +The Process component executes commands in sub-processes. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/process.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/process/Tests/ExecutableFinderTest.php b/vendor/symfony/process/Tests/ExecutableFinderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bc692f6a75df6a77a2ef1cf3dec8f4e9f3bde902 --- /dev/null +++ b/vendor/symfony/process/Tests/ExecutableFinderTest.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\ExecutableFinder; + +/** + * @author Chris Smith + */ +class ExecutableFinderTest extends TestCase +{ + private $path; + + protected function tearDown() + { + if ($this->path) { + // Restore path if it was changed. + putenv('PATH='.$this->path); + } + } + + private function setPath($path) + { + $this->path = getenv('PATH'); + putenv('PATH='.$path); + } + + public function testFind() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + + $this->setPath(dirname(PHP_BINARY)); + + $finder = new ExecutableFinder(); + $result = $finder->find($this->getPhpBinaryName()); + + $this->assertSamePath(PHP_BINARY, $result); + } + + public function testFindWithDefault() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + + $expected = 'defaultValue'; + + $this->setPath(''); + + $finder = new ExecutableFinder(); + $result = $finder->find('foo', $expected); + + $this->assertEquals($expected, $result); + } + + public function testFindWithExtraDirs() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + + $this->setPath(''); + + $extraDirs = array(dirname(PHP_BINARY)); + + $finder = new ExecutableFinder(); + $result = $finder->find($this->getPhpBinaryName(), null, $extraDirs); + + $this->assertSamePath(PHP_BINARY, $result); + } + + public function testFindWithOpenBaseDir() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Cannot run test on windows'); + } + + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + + $this->iniSet('open_basedir', dirname(PHP_BINARY).(!defined('HHVM_VERSION') || HHVM_VERSION_ID >= 30800 ? PATH_SEPARATOR.'/' : '')); + + $finder = new ExecutableFinder(); + $result = $finder->find($this->getPhpBinaryName()); + + $this->assertSamePath(PHP_BINARY, $result); + } + + public function testFindProcessInOpenBasedir() + { + if (ini_get('open_basedir')) { + $this->markTestSkipped('Cannot test when open_basedir is set'); + } + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Cannot run test on windows'); + } + + $this->setPath(''); + $this->iniSet('open_basedir', PHP_BINARY.(!defined('HHVM_VERSION') || HHVM_VERSION_ID >= 30800 ? PATH_SEPARATOR.'/' : '')); + + $finder = new ExecutableFinder(); + $result = $finder->find($this->getPhpBinaryName(), false); + + $this->assertSamePath(PHP_BINARY, $result); + } + + private function assertSamePath($expected, $tested) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals(strtolower($expected), strtolower($tested)); + } else { + $this->assertEquals($expected, $tested); + } + } + + private function getPhpBinaryName() + { + return basename(PHP_BINARY, '\\' === DIRECTORY_SEPARATOR ? '.exe' : ''); + } +} diff --git a/vendor/symfony/process/Tests/NonStopableProcess.php b/vendor/symfony/process/Tests/NonStopableProcess.php new file mode 100644 index 0000000000000000000000000000000000000000..5643259657d50288a4e9bd811e93ea69eebc4666 --- /dev/null +++ b/vendor/symfony/process/Tests/NonStopableProcess.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds. + * + * @args duration Run this script with a custom duration + * + * @example `php NonStopableProcess.php 42` will run the script for 42 seconds + */ +function handleSignal($signal) +{ + switch ($signal) { + case SIGTERM: + $name = 'SIGTERM'; + break; + case SIGINT: + $name = 'SIGINT'; + break; + default: + $name = $signal.' (unknown)'; + break; + } + + echo "signal $name\n"; +} + +pcntl_signal(SIGTERM, 'handleSignal'); +pcntl_signal(SIGINT, 'handleSignal'); + +echo 'received '; + +$duration = isset($argv[1]) ? (int) $argv[1] : 3; +$start = microtime(true); + +while ($duration > (microtime(true) - $start)) { + usleep(10000); + pcntl_signal_dispatch(); +} diff --git a/vendor/symfony/process/Tests/PhpExecutableFinderTest.php b/vendor/symfony/process/Tests/PhpExecutableFinderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b08ad5d3b734fc11c73ba8ddfd7d3792808f09ab --- /dev/null +++ b/vendor/symfony/process/Tests/PhpExecutableFinderTest.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\PhpExecutableFinder; + +/** + * @author Robert Schönthal + */ +class PhpExecutableFinderTest extends TestCase +{ + /** + * tests find() with the constant PHP_BINARY. + */ + public function testFind() + { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('Should not be executed in HHVM context.'); + } + + $f = new PhpExecutableFinder(); + + $current = PHP_BINARY; + $args = 'phpdbg' === PHP_SAPI ? ' -qrr' : ''; + + $this->assertEquals($current.$args, $f->find(), '::find() returns the executable PHP'); + $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP'); + } + + /** + * tests find() with the env var / constant PHP_BINARY with HHVM. + */ + public function testFindWithHHVM() + { + if (!defined('HHVM_VERSION')) { + $this->markTestSkipped('Should be executed in HHVM context.'); + } + + $f = new PhpExecutableFinder(); + + $current = getenv('PHP_BINARY') ?: PHP_BINARY; + + $this->assertEquals($current.' --php', $f->find(), '::find() returns the executable PHP'); + $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP'); + } + + /** + * tests find() with the env var PHP_PATH. + */ + public function testFindArguments() + { + $f = new PhpExecutableFinder(); + + if (defined('HHVM_VERSION')) { + $this->assertEquals($f->findArguments(), array('--php'), '::findArguments() returns HHVM arguments'); + } elseif ('phpdbg' === PHP_SAPI) { + $this->assertEquals($f->findArguments(), array('-qrr'), '::findArguments() returns phpdbg arguments'); + } else { + $this->assertEquals($f->findArguments(), array(), '::findArguments() returns no arguments'); + } + } +} diff --git a/vendor/symfony/process/Tests/PhpProcessTest.php b/vendor/symfony/process/Tests/PhpProcessTest.php new file mode 100644 index 0000000000000000000000000000000000000000..988cd091598ceae6e75795a67061a60554ffb4f3 --- /dev/null +++ b/vendor/symfony/process/Tests/PhpProcessTest.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\PhpProcess; + +class PhpProcessTest extends TestCase +{ + public function testNonBlockingWorks() + { + $expected = 'hello world!'; + $process = new PhpProcess(<<start(); + $process->wait(); + $this->assertEquals($expected, $process->getOutput()); + } + + public function testCommandLine() + { + $process = new PhpProcess(<<<'PHP' +getCommandLine(); + + $process->start(); + $this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after start'); + + $process->wait(); + $this->assertContains($commandLine, $process->getCommandLine(), '::getCommandLine() returns the command line of PHP after wait'); + + $this->assertSame(phpversion().PHP_SAPI, $process->getOutput()); + } +} diff --git a/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php b/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php new file mode 100644 index 0000000000000000000000000000000000000000..bbd7ddfeb284e07c07ba195a7969e05268b0c06b --- /dev/null +++ b/vendor/symfony/process/Tests/PipeStdinInStdoutStdErrStreamSelect.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +define('ERR_SELECT_FAILED', 1); +define('ERR_TIMEOUT', 2); +define('ERR_READ_FAILED', 3); +define('ERR_WRITE_FAILED', 4); + +$read = array(STDIN); +$write = array(STDOUT, STDERR); + +stream_set_blocking(STDIN, 0); +stream_set_blocking(STDOUT, 0); +stream_set_blocking(STDERR, 0); + +$out = $err = ''; +while ($read || $write) { + $r = $read; + $w = $write; + $e = null; + $n = stream_select($r, $w, $e, 5); + + if (false === $n) { + die(ERR_SELECT_FAILED); + } elseif ($n < 1) { + die(ERR_TIMEOUT); + } + + if (in_array(STDOUT, $w) && strlen($out) > 0) { + $written = fwrite(STDOUT, (binary) $out, 32768); + if (false === $written) { + die(ERR_WRITE_FAILED); + } + $out = (binary) substr($out, $written); + } + if (null === $read && '' === $out) { + $write = array_diff($write, array(STDOUT)); + } + + if (in_array(STDERR, $w) && strlen($err) > 0) { + $written = fwrite(STDERR, (binary) $err, 32768); + if (false === $written) { + die(ERR_WRITE_FAILED); + } + $err = (binary) substr($err, $written); + } + if (null === $read && '' === $err) { + $write = array_diff($write, array(STDERR)); + } + + if ($r) { + $str = fread(STDIN, 32768); + if (false !== $str) { + $out .= $str; + $err .= $str; + } + if (false === $str || feof(STDIN)) { + $read = null; + if (!feof(STDIN)) { + die(ERR_READ_FAILED); + } + } + } +} diff --git a/vendor/symfony/process/Tests/ProcessBuilderTest.php b/vendor/symfony/process/Tests/ProcessBuilderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fa7c8c510adf35c1cc78d21a3102b7eae99aa11b --- /dev/null +++ b/vendor/symfony/process/Tests/ProcessBuilderTest.php @@ -0,0 +1,226 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\ProcessBuilder; + +class ProcessBuilderTest extends TestCase +{ + /** + * @group legacy + */ + public function testInheritEnvironmentVars() + { + $proc = ProcessBuilder::create() + ->add('foo') + ->getProcess(); + + $this->assertTrue($proc->areEnvironmentVariablesInherited()); + + $proc = ProcessBuilder::create() + ->add('foo') + ->inheritEnvironmentVariables(false) + ->getProcess(); + + $this->assertFalse($proc->areEnvironmentVariablesInherited()); + } + + public function testAddEnvironmentVariables() + { + $pb = new ProcessBuilder(); + $env = array( + 'foo' => 'bar', + 'foo2' => 'bar2', + ); + $proc = $pb + ->add('command') + ->setEnv('foo', 'bar2') + ->addEnvironmentVariables($env) + ->getProcess() + ; + + $this->assertSame($env, $proc->getEnv()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromSetter() + { + $pb = new ProcessBuilder(); + $pb->setTimeout(-1); + } + + public function testNullTimeout() + { + $pb = new ProcessBuilder(); + $pb->setTimeout(10); + $pb->setTimeout(null); + + $r = new \ReflectionObject($pb); + $p = $r->getProperty('timeout'); + $p->setAccessible(true); + + $this->assertNull($p->getValue($pb)); + } + + public function testShouldSetArguments() + { + $pb = new ProcessBuilder(array('initial')); + $pb->setArguments(array('second')); + + $proc = $pb->getProcess(); + + $this->assertContains('second', $proc->getCommandLine()); + } + + public function testPrefixIsPrependedToAllGeneratedProcess() + { + $pb = new ProcessBuilder(); + $pb->setPrefix('/usr/bin/php'); + + $proc = $pb->setArguments(array('-v'))->getProcess(); + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php" -v', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' '-v'", $proc->getCommandLine()); + } + + $proc = $pb->setArguments(array('-i'))->getProcess(); + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php" -i', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' '-i'", $proc->getCommandLine()); + } + } + + public function testArrayPrefixesArePrependedToAllGeneratedProcess() + { + $pb = new ProcessBuilder(); + $pb->setPrefix(array('/usr/bin/php', 'composer.phar')); + + $proc = $pb->setArguments(array('-v'))->getProcess(); + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php" composer.phar -v', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' 'composer.phar' '-v'", $proc->getCommandLine()); + } + + $proc = $pb->setArguments(array('-i'))->getProcess(); + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php" composer.phar -i', $proc->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php' 'composer.phar' '-i'", $proc->getCommandLine()); + } + } + + public function testShouldEscapeArguments() + { + $pb = new ProcessBuilder(array('%path%', 'foo " bar', '%baz%baz')); + $proc = $pb->getProcess(); + + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertSame('""^%"path"^%"" "foo "" bar" ""^%"baz"^%"baz"', $proc->getCommandLine()); + } else { + $this->assertSame("'%path%' 'foo \" bar' '%baz%baz'", $proc->getCommandLine()); + } + } + + public function testShouldEscapeArgumentsAndPrefix() + { + $pb = new ProcessBuilder(array('arg')); + $pb->setPrefix('%prefix%'); + $proc = $pb->getProcess(); + + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertSame('""^%"prefix"^%"" arg', $proc->getCommandLine()); + } else { + $this->assertSame("'%prefix%' 'arg'", $proc->getCommandLine()); + } + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + */ + public function testShouldThrowALogicExceptionIfNoPrefixAndNoArgument() + { + ProcessBuilder::create()->getProcess(); + } + + public function testShouldNotThrowALogicExceptionIfNoArgument() + { + $process = ProcessBuilder::create() + ->setPrefix('/usr/bin/php') + ->getProcess(); + + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php"', $process->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php'", $process->getCommandLine()); + } + } + + public function testShouldNotThrowALogicExceptionIfNoPrefix() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->getProcess(); + + if ('\\' === DIRECTORY_SEPARATOR) { + $this->assertEquals('"/usr/bin/php"', $process->getCommandLine()); + } else { + $this->assertEquals("'/usr/bin/php'", $process->getCommandLine()); + } + } + + public function testShouldReturnProcessWithDisabledOutput() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->disableOutput() + ->getProcess(); + + $this->assertTrue($process->isOutputDisabled()); + } + + public function testShouldReturnProcessWithEnabledOutput() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->disableOutput() + ->enableOutput() + ->getProcess(); + + $this->assertFalse($process->isOutputDisabled()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + * @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings, Traversable objects or stream resources. + */ + public function testInvalidInput() + { + $builder = ProcessBuilder::create(); + $builder->setInput(array()); + } + + public function testDoesNotPrefixExec() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test cannot run on Windows.'); + } + + $builder = ProcessBuilder::create(array('command', '-v', 'ls')); + $process = $builder->getProcess(); + $process->run(); + + $this->assertTrue($process->isSuccessful()); + } +} diff --git a/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php b/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5f739158c9b1467f635b24068c3f01020866a1d0 --- /dev/null +++ b/vendor/symfony/process/Tests/ProcessFailedExceptionTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\Exception\ProcessFailedException; + +/** + * @author Sebastian Marek + */ +class ProcessFailedExceptionTest extends TestCase +{ + /** + * tests ProcessFailedException throws exception if the process was successful. + */ + public function testProcessFailedExceptionThrowsException() + { + $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(array('isSuccessful'))->setConstructorArgs(array('php'))->getMock(); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(true)); + + $this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}( + '\InvalidArgumentException', + 'Expected a failed process, but the given process was successful.' + ); + + new ProcessFailedException($process); + } + + /** + * tests ProcessFailedException uses information from process output + * to generate exception message. + */ + public function testProcessFailedExceptionPopulatesInformationFromProcessOutput() + { + $cmd = 'php'; + $exitCode = 1; + $exitText = 'General error'; + $output = 'Command output'; + $errorOutput = 'FATAL: Unexpected error'; + $workingDirectory = getcwd(); + + $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(array('isSuccessful', 'getOutput', 'getErrorOutput', 'getExitCode', 'getExitCodeText', 'isOutputDisabled', 'getWorkingDirectory'))->setConstructorArgs(array($cmd))->getMock(); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(false)); + + $process->expects($this->once()) + ->method('getOutput') + ->will($this->returnValue($output)); + + $process->expects($this->once()) + ->method('getErrorOutput') + ->will($this->returnValue($errorOutput)); + + $process->expects($this->once()) + ->method('getExitCode') + ->will($this->returnValue($exitCode)); + + $process->expects($this->once()) + ->method('getExitCodeText') + ->will($this->returnValue($exitText)); + + $process->expects($this->once()) + ->method('isOutputDisabled') + ->will($this->returnValue(false)); + + $process->expects($this->once()) + ->method('getWorkingDirectory') + ->will($this->returnValue($workingDirectory)); + + $exception = new ProcessFailedException($process); + + $this->assertEquals( + "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}\n\nOutput:\n================\n{$output}\n\nError Output:\n================\n{$errorOutput}", + $exception->getMessage() + ); + } + + /** + * Tests that ProcessFailedException does not extract information from + * process output if it was previously disabled. + */ + public function testDisabledOutputInFailedExceptionDoesNotPopulateOutput() + { + $cmd = 'php'; + $exitCode = 1; + $exitText = 'General error'; + $workingDirectory = getcwd(); + + $process = $this->getMockBuilder('Symfony\Component\Process\Process')->setMethods(array('isSuccessful', 'isOutputDisabled', 'getExitCode', 'getExitCodeText', 'getOutput', 'getErrorOutput', 'getWorkingDirectory'))->setConstructorArgs(array($cmd))->getMock(); + $process->expects($this->once()) + ->method('isSuccessful') + ->will($this->returnValue(false)); + + $process->expects($this->never()) + ->method('getOutput'); + + $process->expects($this->never()) + ->method('getErrorOutput'); + + $process->expects($this->once()) + ->method('getExitCode') + ->will($this->returnValue($exitCode)); + + $process->expects($this->once()) + ->method('getExitCodeText') + ->will($this->returnValue($exitText)); + + $process->expects($this->once()) + ->method('isOutputDisabled') + ->will($this->returnValue(true)); + + $process->expects($this->once()) + ->method('getWorkingDirectory') + ->will($this->returnValue($workingDirectory)); + + $exception = new ProcessFailedException($process); + + $this->assertEquals( + "The command \"$cmd\" failed.\n\nExit Code: $exitCode($exitText)\n\nWorking directory: {$workingDirectory}", + $exception->getMessage() + ); + } +} diff --git a/vendor/symfony/process/Tests/ProcessTest.php b/vendor/symfony/process/Tests/ProcessTest.php new file mode 100644 index 0000000000000000000000000000000000000000..267257117d43d35696519f80a8dd7a5fb0f5c662 --- /dev/null +++ b/vendor/symfony/process/Tests/ProcessTest.php @@ -0,0 +1,1591 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Process\Exception\ProcessTimedOutException; +use Symfony\Component\Process\Exception\RuntimeException; +use Symfony\Component\Process\InputStream; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\Pipes\PipesInterface; +use Symfony\Component\Process\Process; + +/** + * @author Robert Schönthal + */ +class ProcessTest extends TestCase +{ + private static $phpBin; + private static $process; + private static $sigchild; + private static $notEnhancedSigchild = false; + + public static function setUpBeforeClass() + { + $phpBin = new PhpExecutableFinder(); + self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === PHP_SAPI ? 'php' : $phpBin->find()); + + ob_start(); + phpinfo(INFO_GENERAL); + self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + protected function tearDown() + { + if (self::$process) { + self::$process->stop(0); + self::$process = null; + } + } + + public function testThatProcessDoesNotThrowWarningDuringRun() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test is transient on Windows'); + } + @trigger_error('Test Error', E_USER_NOTICE); + $process = $this->getProcessForCode('sleep(3)'); + $process->run(); + $actualError = error_get_last(); + $this->assertEquals('Test Error', $actualError['message']); + $this->assertEquals(E_USER_NOTICE, $actualError['type']); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromConstructor() + { + $this->getProcess('', null, null, null, -1); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + */ + public function testNegativeTimeoutFromSetter() + { + $p = $this->getProcess(''); + $p->setTimeout(-1); + } + + public function testFloatAndNullTimeout() + { + $p = $this->getProcess(''); + + $p->setTimeout(10); + $this->assertSame(10.0, $p->getTimeout()); + + $p->setTimeout(null); + $this->assertNull($p->getTimeout()); + + $p->setTimeout(0.0); + $this->assertNull($p->getTimeout()); + } + + /** + * @requires extension pcntl + */ + public function testStopWithTimeoutIsActuallyWorking() + { + $p = $this->getProcess(array(self::$phpBin, __DIR__.'/NonStopableProcess.php', 30)); + $p->start(); + + while (false === strpos($p->getOutput(), 'received')) { + usleep(1000); + } + $start = microtime(true); + $p->stop(0.1); + + $p->wait(); + + $this->assertLessThan(15, microtime(true) - $start); + } + + public function testAllOutputIsActuallyReadOnTermination() + { + // this code will result in a maximum of 2 reads of 8192 bytes by calling + // start() and isRunning(). by the time getOutput() is called the process + // has terminated so the internal pipes array is already empty. normally + // the call to start() will not read any data as the process will not have + // generated output, but this is non-deterministic so we must count it as + // a possibility. therefore we need 2 * PipesInterface::CHUNK_SIZE plus + // another byte which will never be read. + $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2; + + $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize); + $p = $this->getProcessForCode($code); + + $p->start(); + + // Don't call Process::run nor Process::wait to avoid any read of pipes + $h = new \ReflectionProperty($p, 'process'); + $h->setAccessible(true); + $h = $h->getValue($p); + $s = @proc_get_status($h); + + while (!empty($s['running'])) { + usleep(1000); + $s = proc_get_status($h); + } + + $o = $p->getOutput(); + + $this->assertEquals($expectedOutputSize, strlen($o)); + } + + public function testCallbacksAreExecutedWithStart() + { + $process = $this->getProcess('echo foo'); + $process->start(function ($type, $buffer) use (&$data) { + $data .= $buffer; + }); + + $process->wait(); + + $this->assertSame('foo'.PHP_EOL, $data); + } + + /** + * tests results from sub processes. + * + * @dataProvider responsesCodeProvider + */ + public function testProcessResponses($expected, $getter, $code) + { + $p = $this->getProcessForCode($code); + $p->run(); + + $this->assertSame($expected, $p->$getter()); + } + + /** + * tests results from sub processes. + * + * @dataProvider pipesCodeProvider + */ + public function testProcessPipes($code, $size) + { + $expected = str_repeat(str_repeat('*', 1024), $size).'!'; + $expectedLength = (1024 * $size) + 1; + + $p = $this->getProcessForCode($code); + $p->setInput($expected); + $p->run(); + + $this->assertEquals($expectedLength, strlen($p->getOutput())); + $this->assertEquals($expectedLength, strlen($p->getErrorOutput())); + } + + /** + * @dataProvider pipesCodeProvider + */ + public function testSetStreamAsInput($code, $size) + { + $expected = str_repeat(str_repeat('*', 1024), $size).'!'; + $expectedLength = (1024 * $size) + 1; + + $stream = fopen('php://temporary', 'w+'); + fwrite($stream, $expected); + rewind($stream); + + $p = $this->getProcessForCode($code); + $p->setInput($stream); + $p->run(); + + fclose($stream); + + $this->assertEquals($expectedLength, strlen($p->getOutput())); + $this->assertEquals($expectedLength, strlen($p->getErrorOutput())); + } + + public function testLiveStreamAsInput() + { + $stream = fopen('php://memory', 'r+'); + fwrite($stream, 'hello'); + rewind($stream); + + $p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);'); + $p->setInput($stream); + $p->start(function ($type, $data) use ($stream) { + if ('hello' === $data) { + fclose($stream); + } + }); + $p->wait(); + + $this->assertSame('hello', $p->getOutput()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Input can not be set while the process is running. + */ + public function testSetInputWhileRunningThrowsAnException() + { + $process = $this->getProcessForCode('sleep(30);'); + $process->start(); + try { + $process->setInput('foobar'); + $process->stop(); + $this->fail('A LogicException should have been raised.'); + } catch (LogicException $e) { + } + $process->stop(); + + throw $e; + } + + /** + * @dataProvider provideInvalidInputValues + * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException + * @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources. + */ + public function testInvalidInput($value) + { + $process = $this->getProcess('foo'); + $process->setInput($value); + } + + public function provideInvalidInputValues() + { + return array( + array(array()), + array(new NonStringifiable()), + ); + } + + /** + * @dataProvider provideInputValues + */ + public function testValidInput($expected, $value) + { + $process = $this->getProcess('foo'); + $process->setInput($value); + $this->assertSame($expected, $process->getInput()); + } + + public function provideInputValues() + { + return array( + array(null, null), + array('24.5', 24.5), + array('input data', 'input data'), + ); + } + + public function chainedCommandsOutputProvider() + { + if ('\\' === DIRECTORY_SEPARATOR) { + return array( + array("2 \r\n2\r\n", '&&', '2'), + ); + } + + return array( + array("1\n1\n", ';', '1'), + array("2\n2\n", '&&', '2'), + ); + } + + /** + * @dataProvider chainedCommandsOutputProvider + */ + public function testChainedCommandsOutput($expected, $operator, $input) + { + $process = $this->getProcess(sprintf('echo %s %s echo %s', $input, $operator, $input)); + $process->run(); + $this->assertEquals($expected, $process->getOutput()); + } + + public function testCallbackIsExecutedForOutput() + { + $p = $this->getProcessForCode('echo \'foo\';'); + + $called = false; + $p->run(function ($type, $buffer) use (&$called) { + $called = $buffer === 'foo'; + }); + + $this->assertTrue($called, 'The callback should be executed with the output'); + } + + public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled() + { + $p = $this->getProcessForCode('echo \'foo\';'); + $p->disableOutput(); + + $called = false; + $p->run(function ($type, $buffer) use (&$called) { + $called = $buffer === 'foo'; + }); + + $this->assertTrue($called, 'The callback should be executed with the output'); + } + + public function testGetErrorOutput() + { + $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'); + + $p->run(); + $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches)); + } + + public function testFlushErrorOutput() + { + $p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'); + + $p->run(); + $p->clearErrorOutput(); + $this->assertEmpty($p->getErrorOutput()); + } + + /** + * @dataProvider provideIncrementalOutput + */ + public function testIncrementalOutput($getOutput, $getIncrementalOutput, $uri) + { + $lock = tempnam(sys_get_temp_dir(), __FUNCTION__); + + $p = $this->getProcessForCode('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');'); + + $h = fopen($lock, 'w'); + flock($h, LOCK_EX); + + $p->start(); + + foreach (array('foo', 'bar') as $s) { + while (false === strpos($p->$getOutput(), $s)) { + usleep(1000); + } + + $this->assertSame($s, $p->$getIncrementalOutput()); + $this->assertSame('', $p->$getIncrementalOutput()); + + flock($h, LOCK_UN); + } + + fclose($h); + } + + public function provideIncrementalOutput() + { + return array( + array('getOutput', 'getIncrementalOutput', 'php://stdout'), + array('getErrorOutput', 'getIncrementalErrorOutput', 'php://stderr'), + ); + } + + public function testGetOutput() + { + $p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }'); + + $p->run(); + $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches)); + } + + public function testFlushOutput() + { + $p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}'); + + $p->run(); + $p->clearOutput(); + $this->assertEmpty($p->getOutput()); + } + + public function testZeroAsOutput() + { + if ('\\' === DIRECTORY_SEPARATOR) { + // see http://stackoverflow.com/questions/7105433/windows-batch-echo-without-new-line + $p = $this->getProcess('echo | set /p dummyName=0'); + } else { + $p = $this->getProcess('printf 0'); + } + + $p->run(); + $this->assertSame('0', $p->getOutput()); + } + + public function testExitCodeCommandFailed() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX exit code'); + } + $this->skipIfNotEnhancedSigchild(); + + // such command run in bash return an exitcode 127 + $process = $this->getProcess('nonexistingcommandIhopeneversomeonewouldnameacommandlikethis'); + $process->run(); + + $this->assertGreaterThan(0, $process->getExitCode()); + } + + /** + * @group tty + */ + public function testTTYCommand() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not have /dev/tty support'); + } + + $process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine()); + $process->setTty(true); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->wait(); + + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + } + + /** + * @group tty + */ + public function testTTYCommandExitCode() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does have /dev/tty support'); + } + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('echo "foo" >> /dev/null'); + $process->setTty(true); + $process->run(); + + $this->assertTrue($process->isSuccessful()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage TTY mode is not supported on Windows platform. + */ + public function testTTYInWindowsEnvironment() + { + if ('\\' !== DIRECTORY_SEPARATOR) { + $this->markTestSkipped('This test is for Windows platform only'); + } + + $process = $this->getProcess('echo "foo" >> /dev/null'); + $process->setTty(false); + $process->setTty(true); + } + + public function testExitCodeTextIsNullWhenExitCodeIsNull() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess(''); + $this->assertNull($process->getExitCodeText()); + } + + public function testPTYCommand() + { + if (!Process::isPtySupported()) { + $this->markTestSkipped('PTY is not supported on this operating system.'); + } + + $process = $this->getProcess('echo "foo"'); + $process->setPty(true); + $process->run(); + + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + $this->assertEquals("foo\r\n", $process->getOutput()); + } + + public function testMustRun() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('echo foo'); + + $this->assertSame($process, $process->mustRun()); + $this->assertEquals('foo'.PHP_EOL, $process->getOutput()); + } + + public function testSuccessfulMustRunHasCorrectExitCode() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('echo foo')->mustRun(); + $this->assertEquals(0, $process->getExitCode()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\ProcessFailedException + */ + public function testMustRunThrowsException() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('exit 1'); + $process->mustRun(); + } + + public function testExitCodeText() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess(''); + $r = new \ReflectionObject($process); + $p = $r->getProperty('exitcode'); + $p->setAccessible(true); + + $p->setValue($process, 2); + $this->assertEquals('Misuse of shell builtins', $process->getExitCodeText()); + } + + public function testStartIsNonBlocking() + { + $process = $this->getProcessForCode('usleep(500000);'); + $start = microtime(true); + $process->start(); + $end = microtime(true); + $this->assertLessThan(0.4, $end - $start); + $process->stop(); + } + + public function testUpdateStatus() + { + $process = $this->getProcess('echo foo'); + $process->run(); + $this->assertTrue(strlen($process->getOutput()) > 0); + } + + public function testGetExitCodeIsNullOnStart() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcessForCode('usleep(100000);'); + $this->assertNull($process->getExitCode()); + $process->start(); + $this->assertNull($process->getExitCode()); + $process->wait(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testGetExitCodeIsNullOnWhenStartingAgain() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcessForCode('usleep(100000);'); + $process->run(); + $this->assertEquals(0, $process->getExitCode()); + $process->start(); + $this->assertNull($process->getExitCode()); + $process->wait(); + $this->assertEquals(0, $process->getExitCode()); + } + + public function testGetExitCode() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('echo foo'); + $process->run(); + $this->assertSame(0, $process->getExitCode()); + } + + public function testStatus() + { + $process = $this->getProcessForCode('usleep(100000);'); + $this->assertFalse($process->isRunning()); + $this->assertFalse($process->isStarted()); + $this->assertFalse($process->isTerminated()); + $this->assertSame(Process::STATUS_READY, $process->getStatus()); + $process->start(); + $this->assertTrue($process->isRunning()); + $this->assertTrue($process->isStarted()); + $this->assertFalse($process->isTerminated()); + $this->assertSame(Process::STATUS_STARTED, $process->getStatus()); + $process->wait(); + $this->assertFalse($process->isRunning()); + $this->assertTrue($process->isStarted()); + $this->assertTrue($process->isTerminated()); + $this->assertSame(Process::STATUS_TERMINATED, $process->getStatus()); + } + + public function testStop() + { + $process = $this->getProcessForCode('sleep(31);'); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->stop(); + $this->assertFalse($process->isRunning()); + } + + public function testIsSuccessful() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('echo foo'); + $process->run(); + $this->assertTrue($process->isSuccessful()); + } + + public function testIsSuccessfulOnlyAfterTerminated() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcessForCode('usleep(100000);'); + $process->start(); + + $this->assertFalse($process->isSuccessful()); + + $process->wait(); + + $this->assertTrue($process->isSuccessful()); + } + + public function testIsNotSuccessful() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcessForCode('throw new \Exception(\'BOUM\');'); + $process->run(); + $this->assertFalse($process->isSuccessful()); + } + + public function testProcessIsNotSignaled() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('echo foo'); + $process->run(); + $this->assertFalse($process->hasBeenSignaled()); + } + + public function testProcessWithoutTermSignal() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('echo foo'); + $process->run(); + $this->assertEquals(0, $process->getTermSignal()); + } + + public function testProcessIsSignaledIfStopped() + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('Windows does not support POSIX signals'); + } + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcessForCode('sleep(32);'); + $process->start(); + $process->stop(); + $this->assertTrue($process->hasBeenSignaled()); + $this->assertEquals(15, $process->getTermSignal()); // SIGTERM + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage The process has been signaled + */ + public function testProcessThrowsExceptionWhenExternallySignaled() + { + if (!function_exists('posix_kill')) { + $this->markTestSkipped('Function posix_kill is required.'); + } + $this->skipIfNotEnhancedSigchild(false); + + $process = $this->getProcessForCode('sleep(32.1);'); + $process->start(); + posix_kill($process->getPid(), 9); // SIGKILL + + $process->wait(); + } + + public function testRestart() + { + $process1 = $this->getProcessForCode('echo getmypid();'); + $process1->run(); + $process2 = $process1->restart(); + + $process2->wait(); // wait for output + + // Ensure that both processed finished and the output is numeric + $this->assertFalse($process1->isRunning()); + $this->assertFalse($process2->isRunning()); + $this->assertInternalType('numeric', $process1->getOutput()); + $this->assertInternalType('numeric', $process2->getOutput()); + + // Ensure that restart returned a new process by check that the output is different + $this->assertNotEquals($process1->getOutput(), $process2->getOutput()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException + * @expectedExceptionMessage exceeded the timeout of 0.1 seconds. + */ + public function testRunProcessWithTimeout() + { + $process = $this->getProcessForCode('sleep(30);'); + $process->setTimeout(0.1); + $start = microtime(true); + try { + $process->run(); + $this->fail('A RuntimeException should have been raised'); + } catch (RuntimeException $e) { + } + + $this->assertLessThan(15, microtime(true) - $start); + + throw $e; + } + + /** + * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException + * @expectedExceptionMessage exceeded the timeout of 0.1 seconds. + */ + public function testIterateOverProcessWithTimeout() + { + $process = $this->getProcessForCode('sleep(30);'); + $process->setTimeout(0.1); + $start = microtime(true); + try { + $process->start(); + foreach ($process as $buffer); + $this->fail('A RuntimeException should have been raised'); + } catch (RuntimeException $e) { + } + + $this->assertLessThan(15, microtime(true) - $start); + + throw $e; + } + + public function testCheckTimeoutOnNonStartedProcess() + { + $process = $this->getProcess('echo foo'); + $this->assertNull($process->checkTimeout()); + } + + public function testCheckTimeoutOnTerminatedProcess() + { + $process = $this->getProcess('echo foo'); + $process->run(); + $this->assertNull($process->checkTimeout()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException + * @expectedExceptionMessage exceeded the timeout of 0.1 seconds. + */ + public function testCheckTimeoutOnStartedProcess() + { + $process = $this->getProcessForCode('sleep(33);'); + $process->setTimeout(0.1); + + $process->start(); + $start = microtime(true); + + try { + while ($process->isRunning()) { + $process->checkTimeout(); + usleep(100000); + } + $this->fail('A ProcessTimedOutException should have been raised'); + } catch (ProcessTimedOutException $e) { + } + + $this->assertLessThan(15, microtime(true) - $start); + + throw $e; + } + + public function testIdleTimeout() + { + $process = $this->getProcessForCode('sleep(34);'); + $process->setTimeout(60); + $process->setIdleTimeout(0.1); + + try { + $process->run(); + + $this->fail('A timeout exception was expected.'); + } catch (ProcessTimedOutException $e) { + $this->assertTrue($e->isIdleTimeout()); + $this->assertFalse($e->isGeneralTimeout()); + $this->assertEquals(0.1, $e->getExceededTimeout()); + } + } + + public function testIdleTimeoutNotExceededWhenOutputIsSent() + { + $process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}'); + $process->setTimeout(1); + $process->start(); + + while (false === strpos($process->getOutput(), 'foo')) { + usleep(1000); + } + + $process->setIdleTimeout(0.5); + + try { + $process->wait(); + $this->fail('A timeout exception was expected.'); + } catch (ProcessTimedOutException $e) { + $this->assertTrue($e->isGeneralTimeout(), 'A general timeout is expected.'); + $this->assertFalse($e->isIdleTimeout(), 'No idle timeout is expected.'); + $this->assertEquals(1, $e->getExceededTimeout()); + } + } + + /** + * @expectedException \Symfony\Component\Process\Exception\ProcessTimedOutException + * @expectedExceptionMessage exceeded the timeout of 0.1 seconds. + */ + public function testStartAfterATimeout() + { + $process = $this->getProcessForCode('sleep(35);'); + $process->setTimeout(0.1); + + try { + $process->run(); + $this->fail('A ProcessTimedOutException should have been raised.'); + } catch (ProcessTimedOutException $e) { + } + $this->assertFalse($process->isRunning()); + $process->start(); + $this->assertTrue($process->isRunning()); + $process->stop(0); + + throw $e; + } + + public function testGetPid() + { + $process = $this->getProcessForCode('sleep(36);'); + $process->start(); + $this->assertGreaterThan(0, $process->getPid()); + $process->stop(0); + } + + public function testGetPidIsNullBeforeStart() + { + $process = $this->getProcess('foo'); + $this->assertNull($process->getPid()); + } + + public function testGetPidIsNullAfterRun() + { + $process = $this->getProcess('echo foo'); + $process->run(); + $this->assertNull($process->getPid()); + } + + /** + * @requires extension pcntl + */ + public function testSignal() + { + $process = $this->getProcess(array(self::$phpBin, __DIR__.'/SignalListener.php')); + $process->start(); + + while (false === strpos($process->getOutput(), 'Caught')) { + usleep(1000); + } + $process->signal(SIGUSR1); + $process->wait(); + + $this->assertEquals('Caught SIGUSR1', $process->getOutput()); + } + + /** + * @requires extension pcntl + */ + public function testExitCodeIsAvailableAfterSignal() + { + $this->skipIfNotEnhancedSigchild(); + + $process = $this->getProcess('sleep 4'); + $process->start(); + $process->signal(SIGKILL); + + while ($process->isRunning()) { + usleep(10000); + } + + $this->assertFalse($process->isRunning()); + $this->assertTrue($process->hasBeenSignaled()); + $this->assertFalse($process->isSuccessful()); + $this->assertEquals(137, $process->getExitCode()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Can not send signal on a non running process. + */ + public function testSignalProcessNotRunning() + { + $process = $this->getProcess('foo'); + $process->signal(1); // SIGHUP + } + + /** + * @dataProvider provideMethodsThatNeedARunningProcess + */ + public function testMethodsThatNeedARunningProcess($method) + { + $process = $this->getProcess('foo'); + + if (method_exists($this, 'expectException')) { + $this->expectException('Symfony\Component\Process\Exception\LogicException'); + $this->expectExceptionMessage(sprintf('Process must be started before calling %s.', $method)); + } else { + $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method)); + } + + $process->{$method}(); + } + + public function provideMethodsThatNeedARunningProcess() + { + return array( + array('getOutput'), + array('getIncrementalOutput'), + array('getErrorOutput'), + array('getIncrementalErrorOutput'), + array('wait'), + ); + } + + /** + * @dataProvider provideMethodsThatNeedATerminatedProcess + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Process must be terminated before calling + */ + public function testMethodsThatNeedATerminatedProcess($method) + { + $process = $this->getProcessForCode('sleep(37);'); + $process->start(); + try { + $process->{$method}(); + $process->stop(0); + $this->fail('A LogicException must have been thrown'); + } catch (\Exception $e) { + } + $process->stop(0); + + throw $e; + } + + public function provideMethodsThatNeedATerminatedProcess() + { + return array( + array('hasBeenSignaled'), + array('getTermSignal'), + array('hasBeenStopped'), + array('getStopSignal'), + ); + } + + /** + * @dataProvider provideWrongSignal + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + */ + public function testWrongSignal($signal) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $this->markTestSkipped('POSIX signals do not work on Windows'); + } + + $process = $this->getProcessForCode('sleep(38);'); + $process->start(); + try { + $process->signal($signal); + $this->fail('A RuntimeException must have been thrown'); + } catch (RuntimeException $e) { + $process->stop(0); + } + + throw $e; + } + + public function provideWrongSignal() + { + return array( + array(-4), + array('Céphalopodes'), + ); + } + + public function testDisableOutputDisablesTheOutput() + { + $p = $this->getProcess('foo'); + $this->assertFalse($p->isOutputDisabled()); + $p->disableOutput(); + $this->assertTrue($p->isOutputDisabled()); + $p->enableOutput(); + $this->assertFalse($p->isOutputDisabled()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage Disabling output while the process is running is not possible. + */ + public function testDisableOutputWhileRunningThrowsException() + { + $p = $this->getProcessForCode('sleep(39);'); + $p->start(); + $p->disableOutput(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\RuntimeException + * @expectedExceptionMessage Enabling output while the process is running is not possible. + */ + public function testEnableOutputWhileRunningThrowsException() + { + $p = $this->getProcessForCode('sleep(40);'); + $p->disableOutput(); + $p->start(); + $p->enableOutput(); + } + + public function testEnableOrDisableOutputAfterRunDoesNotThrowException() + { + $p = $this->getProcess('echo foo'); + $p->disableOutput(); + $p->run(); + $p->enableOutput(); + $p->disableOutput(); + $this->assertTrue($p->isOutputDisabled()); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Output can not be disabled while an idle timeout is set. + */ + public function testDisableOutputWhileIdleTimeoutIsSet() + { + $process = $this->getProcess('foo'); + $process->setIdleTimeout(1); + $process->disableOutput(); + } + + /** + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage timeout can not be set while the output is disabled. + */ + public function testSetIdleTimeoutWhileOutputIsDisabled() + { + $process = $this->getProcess('foo'); + $process->disableOutput(); + $process->setIdleTimeout(1); + } + + public function testSetNullIdleTimeoutWhileOutputIsDisabled() + { + $process = $this->getProcess('foo'); + $process->disableOutput(); + $this->assertSame($process, $process->setIdleTimeout(null)); + } + + /** + * @dataProvider provideOutputFetchingMethods + * @expectedException \Symfony\Component\Process\Exception\LogicException + * @expectedExceptionMessage Output has been disabled. + */ + public function testGetOutputWhileDisabled($fetchMethod) + { + $p = $this->getProcessForCode('sleep(41);'); + $p->disableOutput(); + $p->start(); + $p->{$fetchMethod}(); + } + + public function provideOutputFetchingMethods() + { + return array( + array('getOutput'), + array('getIncrementalOutput'), + array('getErrorOutput'), + array('getIncrementalErrorOutput'), + ); + } + + public function testStopTerminatesProcessCleanly() + { + $process = $this->getProcessForCode('echo 123; sleep(42);'); + $process->run(function () use ($process) { + $process->stop(); + }); + $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException'); + } + + public function testKillSignalTerminatesProcessCleanly() + { + $process = $this->getProcessForCode('echo 123; sleep(43);'); + $process->run(function () use ($process) { + $process->signal(9); // SIGKILL + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + + public function testTermSignalTerminatesProcessCleanly() + { + $process = $this->getProcessForCode('echo 123; sleep(44);'); + $process->run(function () use ($process) { + $process->signal(15); // SIGTERM + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); + } + + public function responsesCodeProvider() + { + return array( + //expected output / getter / code to execute + //array(1,'getExitCode','exit(1);'), + //array(true,'isSuccessful','exit();'), + array('output', 'getOutput', 'echo \'output\';'), + ); + } + + public function pipesCodeProvider() + { + $variations = array( + 'fwrite(STDOUT, $in = file_get_contents(\'php://stdin\')); fwrite(STDERR, $in);', + 'include \''.__DIR__.'/PipeStdinInStdoutStdErrStreamSelect.php\';', + ); + + if ('\\' === DIRECTORY_SEPARATOR) { + // Avoid XL buffers on Windows because of https://bugs.php.net/bug.php?id=65650 + $sizes = array(1, 2, 4, 8); + } else { + $sizes = array(1, 16, 64, 1024, 4096); + } + + $codes = array(); + foreach ($sizes as $size) { + foreach ($variations as $code) { + $codes[] = array($code, $size); + } + } + + return $codes; + } + + /** + * @dataProvider provideVariousIncrementals + */ + public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method) + { + $process = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }', null, null, null, null); + $process->start(); + $result = ''; + $limit = microtime(true) + 3; + $expected = '012'; + + while ($result !== $expected && microtime(true) < $limit) { + $result .= $process->$method(); + } + + $this->assertSame($expected, $result); + $process->stop(); + } + + public function provideVariousIncrementals() + { + return array( + array('php://stdout', 'getIncrementalOutput'), + array('php://stderr', 'getIncrementalErrorOutput'), + ); + } + + public function testIteratorInput() + { + $input = function () { + yield 'ping'; + yield 'pong'; + }; + + $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input()); + $process->run(); + $this->assertSame('pingpong', $process->getOutput()); + } + + public function testSimpleInputStream() + { + $input = new InputStream(); + + $process = $this->getProcessForCode('echo \'ping\'; stream_copy_to_stream(STDIN, STDOUT);'); + $process->setInput($input); + + $process->start(function ($type, $data) use ($input) { + if ('ping' === $data) { + $input->write('pang'); + } elseif (!$input->isClosed()) { + $input->write('pong'); + $input->close(); + } + }); + + $process->wait(); + $this->assertSame('pingpangpong', $process->getOutput()); + } + + public function testInputStreamWithCallable() + { + $i = 0; + $stream = fopen('php://memory', 'w+'); + $stream = function () use ($stream, &$i) { + if ($i < 3) { + rewind($stream); + fwrite($stream, ++$i); + rewind($stream); + + return $stream; + } + }; + + $input = new InputStream(); + $input->onEmpty($stream); + $input->write($stream()); + + $process = $this->getProcessForCode('echo fread(STDIN, 3);'); + $process->setInput($input); + $process->start(function ($type, $data) use ($input) { + $input->close(); + }); + + $process->wait(); + $this->assertSame('123', $process->getOutput()); + } + + public function testInputStreamWithGenerator() + { + $input = new InputStream(); + $input->onEmpty(function ($input) { + yield 'pong'; + $input->close(); + }); + + $process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);'); + $process->setInput($input); + $process->start(); + $input->write('ping'); + $process->wait(); + $this->assertSame('pingpong', $process->getOutput()); + } + + public function testInputStreamOnEmpty() + { + $i = 0; + $input = new InputStream(); + $input->onEmpty(function () use (&$i) { ++$i; }); + + $process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;'); + $process->setInput($input); + $process->start(function ($type, $data) use ($input) { + if ('123' === $data) { + $input->close(); + } + }); + $process->wait(); + + $this->assertSame(0, $i, 'InputStream->onEmpty callback should be called only when the input *becomes* empty'); + $this->assertSame('123456', $process->getOutput()); + } + + public function testIteratorOutput() + { + $input = new InputStream(); + + $process = $this->getProcessForCode('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);'); + $process->setInput($input); + $process->start(); + $output = array(); + + foreach ($process as $type => $data) { + $output[] = array($type, $data); + break; + } + $expectedOutput = array( + array($process::OUT, '123'), + ); + $this->assertSame($expectedOutput, $output); + + $input->write(345); + + foreach ($process as $type => $data) { + $output[] = array($type, $data); + } + + $this->assertSame('', $process->getOutput()); + $this->assertFalse($process->isRunning()); + + $expectedOutput = array( + array($process::OUT, '123'), + array($process::ERR, '234'), + array($process::OUT, '345'), + array($process::ERR, '456'), + ); + $this->assertSame($expectedOutput, $output); + } + + public function testNonBlockingNorClearingIteratorOutput() + { + $input = new InputStream(); + + $process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));'); + $process->setInput($input); + $process->start(); + $output = array(); + + foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) { + $output[] = array($type, $data); + break; + } + $expectedOutput = array( + array($process::OUT, ''), + ); + $this->assertSame($expectedOutput, $output); + + $input->write(123); + + foreach ($process->getIterator($process::ITER_NON_BLOCKING | $process::ITER_KEEP_OUTPUT) as $type => $data) { + if ('' !== $data) { + $output[] = array($type, $data); + } + } + + $this->assertSame('123', $process->getOutput()); + $this->assertFalse($process->isRunning()); + + $expectedOutput = array( + array($process::OUT, ''), + array($process::OUT, '123'), + ); + $this->assertSame($expectedOutput, $output); + } + + public function testChainedProcesses() + { + $p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);'); + $p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);'); + $p2->setInput($p1); + + $p1->start(); + $p2->run(); + + $this->assertSame('123', $p1->getErrorOutput()); + $this->assertSame('', $p1->getOutput()); + $this->assertSame('', $p2->getErrorOutput()); + $this->assertSame('456', $p2->getOutput()); + } + + public function testSetBadEnv() + { + $process = $this->getProcess('echo hello'); + $process->setEnv(array('bad%%' => '123')); + $process->inheritEnvironmentVariables(true); + + $process->run(); + + $this->assertSame('hello'.PHP_EOL, $process->getOutput()); + $this->assertSame('', $process->getErrorOutput()); + } + + public function testEnvBackupDoesNotDeleteExistingVars() + { + putenv('existing_var=foo'); + $process = $this->getProcess('php -r "echo getenv(\'new_test_var\');"'); + $process->setEnv(array('existing_var' => 'bar', 'new_test_var' => 'foo')); + $process->inheritEnvironmentVariables(); + + $process->run(); + + $this->assertSame('foo', $process->getOutput()); + $this->assertSame('foo', getenv('existing_var')); + $this->assertFalse(getenv('new_test_var')); + } + + public function testEnvIsInherited() + { + $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ')); + + putenv('FOO=BAR'); + + $process->run(); + + $expected = array('BAR' => 'BAZ', 'FOO' => 'BAR'); + $env = array_intersect_key(unserialize($process->getOutput()), $expected); + + $this->assertEquals($expected, $env); + } + + /** + * @group legacy + */ + public function testInheritEnvDisabled() + { + $process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ')); + + putenv('FOO=BAR'); + + $this->assertSame($process, $process->inheritEnvironmentVariables(false)); + $this->assertFalse($process->areEnvironmentVariablesInherited()); + + $process->run(); + + $expected = array('BAR' => 'BAZ', 'FOO' => 'BAR'); + $env = array_intersect_key(unserialize($process->getOutput()), $expected); + unset($expected['FOO']); + + $this->assertSame($expected, $env); + } + + public function testGetCommandLine() + { + $p = new Process(array('/usr/bin/php')); + + $expected = '\\' === DIRECTORY_SEPARATOR ? '"/usr/bin/php"' : "'/usr/bin/php'"; + $this->assertSame($expected, $p->getCommandLine()); + } + + /** + * @dataProvider provideEscapeArgument + */ + public function testEscapeArgument($arg) + { + $p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg)); + $p->run(); + + $this->assertSame($arg, $p->getOutput()); + } + + /** + * @dataProvider provideEscapeArgument + * @group legacy + */ + public function testEscapeArgumentWhenInheritEnvDisabled($arg) + { + $p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg), null, array('BAR' => 'BAZ')); + $p->inheritEnvironmentVariables(false); + $p->run(); + + $this->assertSame($arg, $p->getOutput()); + } + + public function testRawCommandLine() + { + $p = new Process(sprintf('"%s" -r %s "a" "" "b"', self::$phpBin, escapeshellarg('print_r($argv);'))); + $p->run(); + + $expected = << - + [1] => a + [2] => + [3] => b +) + +EOTXT; + $this->assertSame($expected, $p->getOutput()); + } + + public function provideEscapeArgument() + { + yield array('a"b%c%'); + yield array('a"b^c^'); + yield array("a\nb'c"); + yield array('a^b c!'); + yield array("a!b\tc"); + yield array('a\\\\"\\"'); + yield array('éÉèÈàÀöä'); + } + + public function testEnvArgument() + { + $env = array('FOO' => 'Foo', 'BAR' => 'Bar'); + $cmd = '\\' === DIRECTORY_SEPARATOR ? 'echo !FOO! !BAR! !BAZ!' : 'echo $FOO $BAR $BAZ'; + $p = new Process($cmd, null, $env); + $p->run(null, array('BAR' => 'baR', 'BAZ' => 'baZ')); + + $this->assertSame('Foo baR baZ', rtrim($p->getOutput())); + $this->assertSame($env, $p->getEnv()); + } + + /** + * @param string $commandline + * @param null|string $cwd + * @param null|array $env + * @param null|string $input + * @param int $timeout + * @param array $options + * + * @return Process + */ + private function getProcess($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60) + { + $process = new Process($commandline, $cwd, $env, $input, $timeout); + $process->inheritEnvironmentVariables(); + + if (false !== $enhance = getenv('ENHANCE_SIGCHLD')) { + try { + $process->setEnhanceSigchildCompatibility(false); + $process->getExitCode(); + $this->fail('ENHANCE_SIGCHLD must be used together with a sigchild-enabled PHP.'); + } catch (RuntimeException $e) { + $this->assertSame('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.', $e->getMessage()); + if ($enhance) { + $process->setEnhanceSigchildCompatibility(true); + } else { + self::$notEnhancedSigchild = true; + } + } + } + + if (self::$process) { + self::$process->stop(0); + } + + return self::$process = $process; + } + + /** + * @return Process + */ + private function getProcessForCode($code, $cwd = null, array $env = null, $input = null, $timeout = 60) + { + return $this->getProcess(array(self::$phpBin, '-r', $code), $cwd, $env, $input, $timeout); + } + + private function skipIfNotEnhancedSigchild($expectException = true) + { + if (self::$sigchild) { + if (!$expectException) { + $this->markTestSkipped('PHP is compiled with --enable-sigchild.'); + } elseif (self::$notEnhancedSigchild) { + if (method_exists($this, 'expectException')) { + $this->expectException('Symfony\Component\Process\Exception\RuntimeException'); + $this->expectExceptionMessage('This PHP has been compiled with --enable-sigchild.'); + } else { + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild.'); + } + } + } + } +} + +class NonStringifiable +{ +} diff --git a/vendor/symfony/process/Tests/ProcessUtilsTest.php b/vendor/symfony/process/Tests/ProcessUtilsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..82fd8cfa8c89846d335ec0fa89f1d78589271681 --- /dev/null +++ b/vendor/symfony/process/Tests/ProcessUtilsTest.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Process\Tests; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Process\ProcessUtils; + +/** + * @group legacy + */ +class ProcessUtilsTest extends TestCase +{ + /** + * @dataProvider dataArguments + */ + public function testEscapeArgument($result, $argument) + { + $this->assertSame($result, ProcessUtils::escapeArgument($argument)); + } + + public function dataArguments() + { + if ('\\' === DIRECTORY_SEPARATOR) { + return array( + array('"\"php\" \"-v\""', '"php" "-v"'), + array('"foo bar"', 'foo bar'), + array('^%"path"^%', '%path%'), + array('"<|>\\" \\"\'f"', '<|>" "\'f'), + array('""', ''), + array('"with\trailingbs\\\\"', 'with\trailingbs\\'), + ); + } + + return array( + array("'\"php\" \"-v\"'", '"php" "-v"'), + array("'foo bar'", 'foo bar'), + array("'%path%'", '%path%'), + array("'<|>\" \"'\\''f'", '<|>" "\'f'), + array("''", ''), + array("'with\\trailingbs\\'", 'with\trailingbs\\'), + array("'withNonAsciiAccentLikeéÉèÈàÀöä'", 'withNonAsciiAccentLikeéÉèÈàÀöä'), + ); + } +} diff --git a/vendor/symfony/process/Tests/SignalListener.php b/vendor/symfony/process/Tests/SignalListener.php new file mode 100644 index 0000000000000000000000000000000000000000..9e30ce3bbbbc657d1fccf1e101aa90bda9e40f98 --- /dev/null +++ b/vendor/symfony/process/Tests/SignalListener.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +pcntl_signal(SIGUSR1, function () { echo 'SIGUSR1'; exit; }); + +echo 'Caught '; + +$n = 0; + +while ($n++ < 400) { + usleep(10000); + pcntl_signal_dispatch(); +} diff --git a/vendor/symfony/process/composer.json b/vendor/symfony/process/composer.json new file mode 100644 index 0000000000000000000000000000000000000000..f899c52b2b8181d778a0c4563684a90fbd3ae61b --- /dev/null +++ b/vendor/symfony/process/composer.json @@ -0,0 +1,33 @@ +{ + "name": "symfony/process", + "type": "library", + "description": "Symfony Process Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.5.9" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Process\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + } +} diff --git a/vendor/symfony/process/phpunit.xml.dist b/vendor/symfony/process/phpunit.xml.dist new file mode 100644 index 0000000000000000000000000000000000000000..d38846730d72b075adf261c0dff03bcceaa2deab --- /dev/null +++ b/vendor/symfony/process/phpunit.xml.dist @@ -0,0 +1,30 @@ + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./vendor + + + +