feature #21474 [Process] Accept command line arrays and per-run env vars, fixing signaling and escaping (nicolas-grekas)
This PR was merged into the 3.3-dev branch.
Discussion
----------
[Process] Accept command line arrays and per-run env vars, fixing signaling and escaping
| Q | A
| ------------- | ---
| Branch? | master
| Bug fix? | no
| New feature? | yes
| BC breaks? | no
| Deprecations? | yes
| Tests pass? | yes
| Fixed tickets | #12488, #11972, #10025, #11335, #5759, #5030, #19993, #10486
| License | MIT
| Doc PR | -
I think I found a way to fix this network of issues once for all.
Of all the linked ones, only the last two are still open: the remaining were closed in dead ends.
Instead of trying to make `ProcessUtil::escapeArgument` work correctly on Windows - which is impossible as discussed in #21347 - this PR deprecates it in favor of a more powerful approach.
Depending on the use case:
- when a simple command should be run, `Process` now accepts an array of arguments (the "binary" being the first arg). Making this the responsibility of `Process` (instead of `ProcessBuilder`) gives two benefits:
- escape becomes an internal detail that doesn't leak - thus can't be misused ([see here](https://github.com/symfony/symfony/pull/21347#issuecomment-274051370))
- since we know we're running a single command, we can prefix it automatically by "exec" - thus fixing a long standing issue with signaling
```php
$p = new Process(array('php', '-r', 'echo 123;'));
echo $p->getCommandLine();
// displays on Linux:
// exec 'php' '-r' 'echo 123;'
```
- when a shell expression is required, passing a string is still allowed. To make it easy and look-like sql prepared statements, env vars can be used when running the command. Since the shell is OS-specific (think Windows vs Linux) - this PR assumes no portability, so one should just use each shell's specific syntax.
From the fixtures:
```php
$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());
```
Commits
-------
330b61fecb
[Process] Accept command line arrays and per-run env vars, fixing signaling and escaping
This commit is contained in:
commit
3193331855
|
@ -66,6 +66,8 @@ HttpKernel
|
|||
Process
|
||||
-------
|
||||
|
||||
* The `ProcessUtils::escapeArgument()` method has been deprecated, use a command line array or give env vars to the `Process::start/run()` method instead.
|
||||
|
||||
* Not inheriting environment variables is deprecated.
|
||||
|
||||
* Configuring `proc_open()` options is deprecated.
|
||||
|
|
|
@ -228,6 +228,8 @@ HttpKernel
|
|||
Process
|
||||
-------
|
||||
|
||||
* The `ProcessUtils::escapeArgument()` method has been removed, use a command line array or give env vars to the `Process::start/run()` method instead.
|
||||
|
||||
* Environment variables are always inherited in sub-processes.
|
||||
|
||||
* Configuring `proc_open()` options has been removed.
|
||||
|
|
|
@ -4,6 +4,9 @@ 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
|
||||
|
|
|
@ -38,20 +38,16 @@ class PhpProcess extends Process
|
|||
$executableFinder = new PhpExecutableFinder();
|
||||
if (false === $php = $executableFinder->find()) {
|
||||
$php = null;
|
||||
} else {
|
||||
$php = explode(' ', $php);
|
||||
}
|
||||
if ('phpdbg' === PHP_SAPI) {
|
||||
$file = tempnam(sys_get_temp_dir(), 'dbg');
|
||||
file_put_contents($file, $script);
|
||||
register_shutdown_function('unlink', $file);
|
||||
$php .= ' '.ProcessUtils::escapeArgument($file);
|
||||
$php[] = $file;
|
||||
$script = null;
|
||||
}
|
||||
if ('\\' !== DIRECTORY_SEPARATOR && null !== $php) {
|
||||
// exec is mandatory to deal with sending a signal to the process
|
||||
// see https://github.com/symfony/symfony/issues/5030 about prepending
|
||||
// command with exec
|
||||
$php = 'exec '.$php;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
@ -70,12 +66,13 @@ class PhpProcess extends Process
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function start(callable $callback = null)
|
||||
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);
|
||||
parent::start($callback, $env);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ class Process implements \IteratorAggregate
|
|||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $commandline The command line to run
|
||||
* @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
|
||||
|
@ -151,7 +151,7 @@ class Process implements \IteratorAggregate
|
|||
throw new RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.');
|
||||
}
|
||||
|
||||
$this->commandline = $commandline;
|
||||
$this->setCommandline($commandline);
|
||||
$this->cwd = $cwd;
|
||||
|
||||
// on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
|
||||
|
@ -199,16 +199,20 @@ class Process implements \IteratorAggregate
|
|||
*
|
||||
* @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)
|
||||
public function run($callback = null/*, array $env = array()*/)
|
||||
{
|
||||
$this->start($callback);
|
||||
$env = 1 < func_num_args() ? func_get_arg(1) : null;
|
||||
$this->start($callback, $env);
|
||||
|
||||
return $this->wait();
|
||||
}
|
||||
|
@ -220,19 +224,23 @@ class Process implements \IteratorAggregate
|
|||
* 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)
|
||||
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)) {
|
||||
if (0 !== $this->run($callback, $env)) {
|
||||
throw new ProcessFailedException($this);
|
||||
}
|
||||
|
||||
|
@ -253,28 +261,48 @@ class Process implements \IteratorAggregate
|
|||
*
|
||||
* @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)
|
||||
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;
|
||||
$commandline = $this->commandline;
|
||||
|
||||
$env = $this->env;
|
||||
if (null === $env) {
|
||||
$env = $this->env;
|
||||
} else {
|
||||
if ($this->env) {
|
||||
$env += $this->env;
|
||||
}
|
||||
$inheritEnv = true;
|
||||
}
|
||||
|
||||
$envBackup = array();
|
||||
if (null !== $env && $this->inheritEnv) {
|
||||
if (null !== $env && $inheritEnv) {
|
||||
foreach ($env as $k => $v) {
|
||||
$envBackup[$k] = getenv($v);
|
||||
putenv(false === $v || null === $v ? $k : "$k=$v");
|
||||
|
@ -284,14 +312,8 @@ class Process implements \IteratorAggregate
|
|||
@trigger_error(sprintf('Not inheriting environment variables is deprecated since Symfony 3.3 and will always happen in 4.0. Set "Process::inheritEnvironmentVariables()" to true instead.', __METHOD__), E_USER_DEPRECATED);
|
||||
}
|
||||
if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) {
|
||||
$commandline = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $commandline).')';
|
||||
foreach ($this->processPipes->getFiles() as $offset => $filename) {
|
||||
$commandline .= ' '.$offset.'>"'.$filename.'"';
|
||||
}
|
||||
|
||||
if (!isset($this->options['bypass_shell'])) {
|
||||
$this->options['bypass_shell'] = true;
|
||||
}
|
||||
$this->options['bypass_shell'] = true;
|
||||
$commandline = $this->prepareWindowsCommandLine($commandline, $envBackup);
|
||||
} 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');
|
||||
|
@ -335,6 +357,7 @@ class Process implements \IteratorAggregate
|
|||
*
|
||||
* @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
|
||||
*
|
||||
|
@ -342,15 +365,18 @@ class Process implements \IteratorAggregate
|
|||
* @throws RuntimeException When process is already running
|
||||
*
|
||||
* @see start()
|
||||
*
|
||||
* @final since version 3.3
|
||||
*/
|
||||
public function restart(callable $callback = null)
|
||||
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);
|
||||
$process->start($callback, $env);
|
||||
|
||||
return $process;
|
||||
}
|
||||
|
@ -909,12 +935,20 @@ class Process implements \IteratorAggregate
|
|||
/**
|
||||
* Sets the command line to be executed.
|
||||
*
|
||||
* @param string $commandline The command to execute
|
||||
* @param string|array $commandline The command to execute
|
||||
*
|
||||
* @return self The current Process instance
|
||||
*/
|
||||
public function setCommandLine($commandline)
|
||||
{
|
||||
if (is_array($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;
|
||||
}
|
||||
}
|
||||
$this->commandline = $commandline;
|
||||
|
||||
return $this;
|
||||
|
@ -1589,6 +1623,50 @@ class Process implements \IteratorAggregate
|
|||
return true;
|
||||
}
|
||||
|
||||
private function prepareWindowsCommandLine($cmd, array &$envBackup)
|
||||
{
|
||||
$uid = uniqid('', true);
|
||||
$varCount = 0;
|
||||
$varCache = array();
|
||||
$cmd = preg_replace_callback(
|
||||
'/"(
|
||||
[^"%!^]*+
|
||||
(?:
|
||||
(?: !LF! | "(?:\^[%!^])?+" )
|
||||
[^"%!^]*+
|
||||
)++
|
||||
)"/x',
|
||||
function ($m) use (&$envBackup, &$varCache, &$varCount, $uid) {
|
||||
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;
|
||||
putenv("$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.
|
||||
*
|
||||
|
@ -1616,4 +1694,30 @@ class Process implements \IteratorAggregate
|
|||
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).'"';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -271,9 +271,7 @@ class ProcessBuilder
|
|||
}
|
||||
|
||||
$arguments = array_merge($this->prefix, $this->arguments);
|
||||
$script = implode(' ', array_map(array(__NAMESPACE__.'\\ProcessUtils', 'escapeArgument'), $arguments));
|
||||
|
||||
$process = new Process($script, $this->cwd, $this->env, $this->input, $this->timeout, $this->options);
|
||||
$process = new Process($arguments, $this->cwd, $this->env, $this->input, $this->timeout, $this->options);
|
||||
|
||||
if ($this->inheritEnv) {
|
||||
$process->inheritEnvironmentVariables();
|
||||
|
|
|
@ -35,9 +35,13 @@ class ProcessUtils
|
|||
* @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
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
|
||||
namespace Symfony\Component\Process\Tests;
|
||||
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\PhpProcess;
|
||||
|
||||
class PhpProcessTest extends \PHPUnit_Framework_TestCase
|
||||
|
@ -31,19 +30,18 @@ PHP
|
|||
public function testCommandLine()
|
||||
{
|
||||
$process = new PhpProcess(<<<'PHP'
|
||||
<?php echo 'foobar';
|
||||
<?php echo phpversion().PHP_SAPI;
|
||||
PHP
|
||||
);
|
||||
|
||||
$commandLine = $process->getCommandLine();
|
||||
|
||||
$f = new PhpExecutableFinder();
|
||||
$this->assertContains($f->find(), $commandLine, '::getCommandLine() returns the command line of PHP before start');
|
||||
|
||||
$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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,16 +90,16 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$proc = $pb->setArguments(array('-v'))->getProcess();
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$this->assertEquals('"/usr/bin/php" "-v"', $proc->getCommandLine());
|
||||
$this->assertEquals('/usr/bin/php -v', $proc->getCommandLine());
|
||||
} else {
|
||||
$this->assertEquals("'/usr/bin/php' '-v'", $proc->getCommandLine());
|
||||
$this->assertEquals("exec '/usr/bin/php' '-v'", $proc->getCommandLine());
|
||||
}
|
||||
|
||||
$proc = $pb->setArguments(array('-i'))->getProcess();
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$this->assertEquals('"/usr/bin/php" "-i"', $proc->getCommandLine());
|
||||
$this->assertEquals('/usr/bin/php -i', $proc->getCommandLine());
|
||||
} else {
|
||||
$this->assertEquals("'/usr/bin/php' '-i'", $proc->getCommandLine());
|
||||
$this->assertEquals("exec '/usr/bin/php' '-i'", $proc->getCommandLine());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,16 +110,16 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
$proc = $pb->setArguments(array('-v'))->getProcess();
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$this->assertEquals('"/usr/bin/php" "composer.phar" "-v"', $proc->getCommandLine());
|
||||
$this->assertEquals('/usr/bin/php composer.phar -v', $proc->getCommandLine());
|
||||
} else {
|
||||
$this->assertEquals("'/usr/bin/php' 'composer.phar' '-v'", $proc->getCommandLine());
|
||||
$this->assertEquals("exec '/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());
|
||||
$this->assertEquals('/usr/bin/php composer.phar -i', $proc->getCommandLine());
|
||||
} else {
|
||||
$this->assertEquals("'/usr/bin/php' 'composer.phar' '-i'", $proc->getCommandLine());
|
||||
$this->assertEquals("exec '/usr/bin/php' 'composer.phar' '-i'", $proc->getCommandLine());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -129,9 +129,9 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
|
|||
$proc = $pb->getProcess();
|
||||
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$this->assertSame('^%"path"^% "foo \\" bar" "%baz%baz"', $proc->getCommandLine());
|
||||
$this->assertSame('""^%"path"^%"" "foo "" bar" ""^%"baz"^%"baz"', $proc->getCommandLine());
|
||||
} else {
|
||||
$this->assertSame("'%path%' 'foo \" bar' '%baz%baz'", $proc->getCommandLine());
|
||||
$this->assertSame("exec '%path%' 'foo \" bar' '%baz%baz'", $proc->getCommandLine());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,9 +142,9 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
|
|||
$proc = $pb->getProcess();
|
||||
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$this->assertSame('^%"prefix"^% "arg"', $proc->getCommandLine());
|
||||
$this->assertSame('""^%"prefix"^%"" arg', $proc->getCommandLine());
|
||||
} else {
|
||||
$this->assertSame("'%prefix%' 'arg'", $proc->getCommandLine());
|
||||
$this->assertSame("exec '%prefix%' 'arg'", $proc->getCommandLine());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,9 +163,9 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
|
|||
->getProcess();
|
||||
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$this->assertEquals('"/usr/bin/php"', $process->getCommandLine());
|
||||
$this->assertEquals('/usr/bin/php', $process->getCommandLine());
|
||||
} else {
|
||||
$this->assertEquals("'/usr/bin/php'", $process->getCommandLine());
|
||||
$this->assertEquals("exec '/usr/bin/php'", $process->getCommandLine());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,9 +175,9 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
|
|||
->getProcess();
|
||||
|
||||
if ('\\' === DIRECTORY_SEPARATOR) {
|
||||
$this->assertEquals('"/usr/bin/php"', $process->getCommandLine());
|
||||
$this->assertEquals('/usr/bin/php', $process->getCommandLine());
|
||||
} else {
|
||||
$this->assertEquals("'/usr/bin/php'", $process->getCommandLine());
|
||||
$this->assertEquals("exec '/usr/bin/php'", $process->getCommandLine());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,12 +33,6 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$phpBin = new PhpExecutableFinder();
|
||||
self::$phpBin = getenv('SYMFONY_PROCESS_PHP_TEST_BINARY') ?: ('phpdbg' === PHP_SAPI ? 'php' : $phpBin->find());
|
||||
if ('\\' !== DIRECTORY_SEPARATOR) {
|
||||
// exec is mandatory to deal with sending a signal to the process
|
||||
// see https://github.com/symfony/symfony/issues/5030 about prepending
|
||||
// command with exec
|
||||
self::$phpBin = 'exec '.self::$phpBin;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
phpinfo(INFO_GENERAL);
|
||||
|
@ -59,7 +53,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
$this->markTestSkipped('This test is transient on Windows');
|
||||
}
|
||||
@trigger_error('Test Error', E_USER_NOTICE);
|
||||
$process = $this->getProcess(self::$phpBin." -r 'sleep(3)'");
|
||||
$process = $this->getProcessForCode('sleep(3)');
|
||||
$process->run();
|
||||
$actualError = error_get_last();
|
||||
$this->assertEquals('Test Error', $actualError['message']);
|
||||
|
@ -102,7 +96,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testStopWithTimeoutIsActuallyWorking()
|
||||
{
|
||||
$p = $this->getProcess(self::$phpBin.' '.__DIR__.'/NonStopableProcess.php 30');
|
||||
$p = $this->getProcess(array(self::$phpBin, __DIR__.'/NonStopableProcess.php', 30));
|
||||
$p->start();
|
||||
|
||||
while (false === strpos($p->getOutput(), 'received')) {
|
||||
|
@ -128,7 +122,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
$expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
|
||||
|
||||
$code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
|
||||
$p = $this->getProcessForCode($code);
|
||||
|
||||
$p->start();
|
||||
|
||||
|
@ -167,7 +161,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testProcessResponses($expected, $getter, $code)
|
||||
{
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
|
||||
$p = $this->getProcessForCode($code);
|
||||
$p->run();
|
||||
|
||||
$this->assertSame($expected, $p->$getter());
|
||||
|
@ -183,7 +177,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
$expected = str_repeat(str_repeat('*', 1024), $size).'!';
|
||||
$expectedLength = (1024 * $size) + 1;
|
||||
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
|
||||
$p = $this->getProcessForCode($code);
|
||||
$p->setInput($expected);
|
||||
$p->run();
|
||||
|
||||
|
@ -203,7 +197,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
fwrite($stream, $expected);
|
||||
rewind($stream);
|
||||
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
|
||||
$p = $this->getProcessForCode($code);
|
||||
$p->setInput($stream);
|
||||
$p->run();
|
||||
|
||||
|
@ -219,7 +213,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
fwrite($stream, 'hello');
|
||||
rewind($stream);
|
||||
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);')));
|
||||
$p = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
|
||||
$p->setInput($stream);
|
||||
$p->start(function ($type, $data) use ($stream) {
|
||||
if ('hello' === $data) {
|
||||
|
@ -237,7 +231,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testSetInputWhileRunningThrowsAnException()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(30);"');
|
||||
$process = $this->getProcessForCode('sleep(30);');
|
||||
$process->start();
|
||||
try {
|
||||
$process->setInput('foobar');
|
||||
|
@ -314,7 +308,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testCallbackIsExecutedForOutput()
|
||||
{
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('echo \'foo\';')));
|
||||
$p = $this->getProcessForCode('echo \'foo\';');
|
||||
|
||||
$called = false;
|
||||
$p->run(function ($type, $buffer) use (&$called) {
|
||||
|
@ -326,7 +320,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testCallbackIsExecutedForOutputWheneverOutputIsDisabled()
|
||||
{
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('echo \'foo\';')));
|
||||
$p = $this->getProcessForCode('echo \'foo\';');
|
||||
$p->disableOutput();
|
||||
|
||||
$called = false;
|
||||
|
@ -339,7 +333,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testGetErrorOutput()
|
||||
{
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
|
||||
$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));
|
||||
|
@ -347,7 +341,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testFlushErrorOutput()
|
||||
{
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
|
||||
$p = $this->getProcessForCode('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }');
|
||||
|
||||
$p->run();
|
||||
$p->clearErrorOutput();
|
||||
|
@ -361,7 +355,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$lock = tempnam(sys_get_temp_dir(), __FUNCTION__);
|
||||
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('file_put_contents($s = \''.$uri.'\', \'foo\'); flock(fopen('.var_export($lock, true).', \'r\'), LOCK_EX); file_put_contents($s, \'bar\');')));
|
||||
$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);
|
||||
|
@ -392,7 +386,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testGetOutput()
|
||||
{
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }')));
|
||||
$p = $this->getProcessForCode('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }');
|
||||
|
||||
$p->run();
|
||||
$this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
|
||||
|
@ -400,7 +394,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testFlushOutput()
|
||||
{
|
||||
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}')));
|
||||
$p = $this->getProcessForCode('$n=0;while ($n<3) {echo \' foo \';$n++;}');
|
||||
|
||||
$p->run();
|
||||
$p->clearOutput();
|
||||
|
@ -440,7 +434,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
$this->markTestSkipped('Windows does not have /dev/tty support');
|
||||
}
|
||||
|
||||
$process = $this->getProcess('echo "foo" >> /dev/null && '.self::$phpBin.' -r "usleep(100000);"');
|
||||
$process = $this->getProcess('echo "foo" >> /dev/null && '.$this->getProcessForCode('usleep(100000);')->getCommandLine());
|
||||
$process->setTty(true);
|
||||
$process->start();
|
||||
$this->assertTrue($process->isRunning());
|
||||
|
@ -544,7 +538,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testStartIsNonBlocking()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
|
||||
$process = $this->getProcessForCode('usleep(500000);');
|
||||
$start = microtime(true);
|
||||
$process->start();
|
||||
$end = microtime(true);
|
||||
|
@ -563,7 +557,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$this->skipIfNotEnhancedSigchild();
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
|
||||
$process = $this->getProcessForCode('usleep(100000);');
|
||||
$this->assertNull($process->getExitCode());
|
||||
$process->start();
|
||||
$this->assertNull($process->getExitCode());
|
||||
|
@ -575,7 +569,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$this->skipIfNotEnhancedSigchild();
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
|
||||
$process = $this->getProcessForCode('usleep(100000);');
|
||||
$process->run();
|
||||
$this->assertEquals(0, $process->getExitCode());
|
||||
$process->start();
|
||||
|
@ -595,7 +589,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testStatus()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
|
||||
$process = $this->getProcessForCode('usleep(100000);');
|
||||
$this->assertFalse($process->isRunning());
|
||||
$this->assertFalse($process->isStarted());
|
||||
$this->assertFalse($process->isTerminated());
|
||||
|
@ -614,7 +608,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testStop()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(31);"');
|
||||
$process = $this->getProcessForCode('sleep(31);');
|
||||
$process->start();
|
||||
$this->assertTrue($process->isRunning());
|
||||
$process->stop();
|
||||
|
@ -634,7 +628,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$this->skipIfNotEnhancedSigchild();
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r "usleep(100000);"');
|
||||
$process = $this->getProcessForCode('usleep(100000);');
|
||||
$process->start();
|
||||
|
||||
$this->assertFalse($process->isSuccessful());
|
||||
|
@ -648,7 +642,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$this->skipIfNotEnhancedSigchild();
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r "throw new \Exception(\'BOUM\');"');
|
||||
$process = $this->getProcessForCode('throw new \Exception(\'BOUM\');');
|
||||
$process->run();
|
||||
$this->assertFalse($process->isSuccessful());
|
||||
}
|
||||
|
@ -684,7 +678,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
}
|
||||
$this->skipIfNotEnhancedSigchild();
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(32);"');
|
||||
$process = $this->getProcessForCode('sleep(32);');
|
||||
$process->start();
|
||||
$process->stop();
|
||||
$this->assertTrue($process->hasBeenSignaled());
|
||||
|
@ -702,7 +696,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
}
|
||||
$this->skipIfNotEnhancedSigchild(false);
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(32.1)"');
|
||||
$process = $this->getProcessForCode('sleep(32.1);');
|
||||
$process->start();
|
||||
posix_kill($process->getPid(), 9); // SIGKILL
|
||||
|
||||
|
@ -711,7 +705,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testRestart()
|
||||
{
|
||||
$process1 = $this->getProcess(self::$phpBin.' -r "echo getmypid();"');
|
||||
$process1 = $this->getProcessForCode('echo getmypid();');
|
||||
$process1->run();
|
||||
$process2 = $process1->restart();
|
||||
|
||||
|
@ -733,7 +727,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testRunProcessWithTimeout()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(30);"');
|
||||
$process = $this->getProcessForCode('sleep(30);');
|
||||
$process->setTimeout(0.1);
|
||||
$start = microtime(true);
|
||||
try {
|
||||
|
@ -753,7 +747,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testIterateOverProcessWithTimeout()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(30);"');
|
||||
$process = $this->getProcessForCode('sleep(30);');
|
||||
$process->setTimeout(0.1);
|
||||
$start = microtime(true);
|
||||
try {
|
||||
|
@ -787,7 +781,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testCheckTimeoutOnStartedProcess()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(33);"');
|
||||
$process = $this->getProcessForCode('sleep(33);');
|
||||
$process->setTimeout(0.1);
|
||||
|
||||
$process->start();
|
||||
|
@ -809,7 +803,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testIdleTimeout()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(34);"');
|
||||
$process = $this->getProcessForCode('sleep(34);');
|
||||
$process->setTimeout(60);
|
||||
$process->setIdleTimeout(0.1);
|
||||
|
||||
|
@ -826,7 +820,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testIdleTimeoutNotExceededWhenOutputIsSent()
|
||||
{
|
||||
$process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('while (true) {echo \'foo \'; usleep(1000);}')));
|
||||
$process = $this->getProcessForCode('while (true) {echo \'foo \'; usleep(1000);}');
|
||||
$process->setTimeout(1);
|
||||
$process->start();
|
||||
|
||||
|
@ -852,7 +846,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testStartAfterATimeout()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(35);"');
|
||||
$process = $this->getProcessForCode('sleep(35);');
|
||||
$process->setTimeout(0.1);
|
||||
|
||||
try {
|
||||
|
@ -870,7 +864,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testGetPid()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(36);"');
|
||||
$process = $this->getProcessForCode('sleep(36);');
|
||||
$process->start();
|
||||
$this->assertGreaterThan(0, $process->getPid());
|
||||
$process->stop(0);
|
||||
|
@ -894,7 +888,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testSignal()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' '.__DIR__.'/SignalListener.php');
|
||||
$process = $this->getProcess(array(self::$phpBin, __DIR__.'/SignalListener.php'));
|
||||
$process->start();
|
||||
|
||||
while (false === strpos($process->getOutput(), 'Caught')) {
|
||||
|
@ -965,7 +959,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testMethodsThatNeedATerminatedProcess($method)
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(37);"');
|
||||
$process = $this->getProcessForCode('sleep(37);');
|
||||
$process->start();
|
||||
try {
|
||||
$process->{$method}();
|
||||
|
@ -998,7 +992,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
$this->markTestSkipped('POSIX signals do not work on Windows');
|
||||
}
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r "sleep(38);"');
|
||||
$process = $this->getProcessForCode('sleep(38);');
|
||||
$process->start();
|
||||
try {
|
||||
$process->signal($signal);
|
||||
|
@ -1034,7 +1028,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testDisableOutputWhileRunningThrowsException()
|
||||
{
|
||||
$p = $this->getProcess(self::$phpBin.' -r "sleep(39);"');
|
||||
$p = $this->getProcessForCode('sleep(39);');
|
||||
$p->start();
|
||||
$p->disableOutput();
|
||||
}
|
||||
|
@ -1045,7 +1039,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testEnableOutputWhileRunningThrowsException()
|
||||
{
|
||||
$p = $this->getProcess(self::$phpBin.' -r "sleep(40);"');
|
||||
$p = $this->getProcessForCode('sleep(40);');
|
||||
$p->disableOutput();
|
||||
$p->start();
|
||||
$p->enableOutput();
|
||||
|
@ -1097,7 +1091,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testGetOutputWhileDisabled($fetchMethod)
|
||||
{
|
||||
$p = $this->getProcess(self::$phpBin.' -r "sleep(41);"');
|
||||
$p = $this->getProcessForCode('sleep(41);');
|
||||
$p->disableOutput();
|
||||
$p->start();
|
||||
$p->{$fetchMethod}();
|
||||
|
@ -1115,7 +1109,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testStopTerminatesProcessCleanly()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(42);"');
|
||||
$process = $this->getProcessForCode('echo 123; sleep(42);');
|
||||
$process->run(function () use ($process) {
|
||||
$process->stop();
|
||||
});
|
||||
|
@ -1124,7 +1118,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testKillSignalTerminatesProcessCleanly()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(43);"');
|
||||
$process = $this->getProcessForCode('echo 123; sleep(43);');
|
||||
$process->run(function () use ($process) {
|
||||
$process->signal(9); // SIGKILL
|
||||
});
|
||||
|
@ -1133,7 +1127,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testTermSignalTerminatesProcessCleanly()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r "echo 123; sleep(44);"');
|
||||
$process = $this->getProcessForCode('echo 123; sleep(44);');
|
||||
$process->run(function () use ($process) {
|
||||
$process->signal(15); // SIGTERM
|
||||
});
|
||||
|
@ -1179,7 +1173,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testIncrementalOutputDoesNotRequireAnotherCall($stream, $method)
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\''.$stream.'\', $n, 1); $n++; usleep(1000); }'), null, null, null, null);
|
||||
$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;
|
||||
|
@ -1208,7 +1202,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
yield 'pong';
|
||||
};
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);'), null, null, $input());
|
||||
$process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);', null, null, $input());
|
||||
$process->run();
|
||||
$this->assertSame('pingpong', $process->getOutput());
|
||||
}
|
||||
|
@ -1217,7 +1211,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$input = new InputStream();
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo \'ping\'; stream_copy_to_stream(STDIN, STDOUT);'));
|
||||
$process = $this->getProcessForCode('echo \'ping\'; stream_copy_to_stream(STDIN, STDOUT);');
|
||||
$process->setInput($input);
|
||||
|
||||
$process->start(function ($type, $data) use ($input) {
|
||||
|
@ -1251,7 +1245,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
$input->onEmpty($stream);
|
||||
$input->write($stream());
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo fread(STDIN, 3);'));
|
||||
$process = $this->getProcessForCode('echo fread(STDIN, 3);');
|
||||
$process->setInput($input);
|
||||
$process->start(function ($type, $data) use ($input) {
|
||||
$input->close();
|
||||
|
@ -1269,7 +1263,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
$input->close();
|
||||
});
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);'));
|
||||
$process = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
|
||||
$process->setInput($input);
|
||||
$process->start();
|
||||
$input->write('ping');
|
||||
|
@ -1283,7 +1277,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
$input = new InputStream();
|
||||
$input->onEmpty(function () use (&$i) { ++$i; });
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo 123; echo fread(STDIN, 1); echo 456;'));
|
||||
$process = $this->getProcessForCode('echo 123; echo fread(STDIN, 1); echo 456;');
|
||||
$process->setInput($input);
|
||||
$process->start(function ($type, $data) use ($input) {
|
||||
if ('123' === $data) {
|
||||
|
@ -1300,7 +1294,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$input = new InputStream();
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('fwrite(STDOUT, 123); fwrite(STDERR, 234); flush(); usleep(10000); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);'));
|
||||
$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();
|
||||
|
@ -1336,7 +1330,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
{
|
||||
$input = new InputStream();
|
||||
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('fwrite(STDOUT, fread(STDIN, 3));'));
|
||||
$process = $this->getProcessForCode('fwrite(STDOUT, fread(STDIN, 3));');
|
||||
$process->setInput($input);
|
||||
$process->start();
|
||||
$output = array();
|
||||
|
@ -1370,8 +1364,8 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testChainedProcesses()
|
||||
{
|
||||
$p1 = new Process(self::$phpBin.' -r '.escapeshellarg('fwrite(STDERR, 123); fwrite(STDOUT, 456);'));
|
||||
$p2 = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);')));
|
||||
$p1 = $this->getProcessForCode('fwrite(STDERR, 123); fwrite(STDOUT, 456);');
|
||||
$p2 = $this->getProcessForCode('stream_copy_to_stream(STDIN, STDOUT);');
|
||||
$p2->setInput($p1);
|
||||
|
||||
$p1->start();
|
||||
|
@ -1385,7 +1379,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
|
||||
public function testEnvIsInherited()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo serialize($_SERVER);'), null, array('BAR' => 'BAZ'));
|
||||
$process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
|
||||
|
||||
putenv('FOO=BAR');
|
||||
|
||||
|
@ -1402,7 +1396,7 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
*/
|
||||
public function testInheritEnvDisabled()
|
||||
{
|
||||
$process = $this->getProcess(self::$phpBin.' -r '.escapeshellarg('echo serialize($_SERVER);'), null, array('BAR' => 'BAZ'));
|
||||
$process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
|
||||
|
||||
putenv('FOO=BAR');
|
||||
|
||||
|
@ -1418,6 +1412,39 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
$this->assertSame($expected, $env);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideEscapeArgument
|
||||
*/
|
||||
public function testEscapeArgument($arg)
|
||||
{
|
||||
$p = new Process(array(self::$phpBin, '-r', 'echo $argv[1];', $arg));
|
||||
$p->run();
|
||||
|
||||
$this->assertSame($arg, $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
|
||||
|
@ -1455,6 +1482,14 @@ class ProcessTest extends \PHPUnit_Framework_TestCase
|
|||
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) {
|
||||
|
|
|
@ -13,6 +13,9 @@ namespace Symfony\Component\Process\Tests;
|
|||
|
||||
use Symfony\Component\Process\ProcessUtils;
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
class ProcessUtilsTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
/**
|
||||
|
|
Reference in New Issue