feature #8655 Adds PTY mode & convenience method mustRun() (schmittjoh)

This PR was merged into the 2.5-dev branch.

Discussion
----------

Adds PTY mode & convenience method mustRun()

This makes two additions. I've split them into separate commits so that you can pull them separately if required.

1. Adds PTY mode.
In contrast to the existing TTY mode, the proc_open call becomes the master not the process currently executing.

2. Adds a ``mustRun`` method
This is merely for convenience:

```php
# Before
$proc = new Process($cmd);
if (0 !== $proc->run()) {
    throw new ProcessFailedException($proc);
}

$proc = new Process($cmd);
if (0 !== $proc->run()) {
    throw new ProcessFailedException($proc);
}

# After
(new Process($cmd))->mustRun();
(new Process($cmd))->mustRun();

Commits
-------

dbd264a adds cache for isPtySupported()
6c11207 attempts to fix tests on Travis
2ff1870 adds convenience method mustRun
53441aa adds support for PTY mode
This commit is contained in:
Fabien Potencier 2014-02-03 09:11:05 +01:00
commit 51d3d62946
5 changed files with 147 additions and 2 deletions

View File

@ -1,6 +1,12 @@
CHANGELOG
=========
2.5.0
-----
* added support for PTY mode
* added the convenience method "mustRun"
2.4.0
-----

View File

@ -13,6 +13,7 @@ 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;
@ -62,6 +63,7 @@ class Process
private $incrementalOutputOffset;
private $incrementalErrorOutputOffset;
private $tty;
private $pty;
private $useFileHandles = false;
/** @var ProcessPipes */
@ -158,6 +160,7 @@ class Process
$this->stdin = $stdin;
$this->setTimeout($timeout);
$this->useFileHandles = defined('PHP_WINDOWS_VERSION_BUILD');
$this->pty = false;
$this->enhanceWindowsCompatibility = true;
$this->enhanceSigchildCompatibility = !defined('PHP_WINDOWS_VERSION_BUILD') && $this->isSigchildEnabled();
$this->options = array_replace(array('suppress_errors' => true, 'binary_pipes' => true), $options);
@ -200,6 +203,25 @@ class Process
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
*
* @return self
*/
public function mustRun($callback = null)
{
if (0 !== $this->run($callback)) {
throw new ProcessFailedException($this);
}
return $this;
}
/**
* Starts the process and returns after sending the STDIN.
*
@ -809,6 +831,30 @@ class Process
return $this->tty;
}
/**
* Sets PTY mode.
*
* @param Boolean $bool
*
* @return self
*/
public function setPty($bool)
{
$this->pty = (Boolean) $bool;
return $this;
}
/**
* Returns PTY state.
*
* @return Boolean
*/
public function isPty()
{
return $this->pty;
}
/**
* Gets the working directory.
*
@ -1000,6 +1046,33 @@ class Process
}
}
/**
* Returns whether PTY is supported on the current operating system.
*
* @return Boolean
*/
public static function isPtySupported()
{
static $result;
if (null !== $result) {
return $result;
}
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
return $result = false;
}
$proc = @proc_open('echo 1', array(array('pty'), array('pty'), array('pty')), $pipes);
if (is_resource($proc)) {
proc_close($proc);
return $result = true;
}
return $result = false;
}
/**
* Creates the descriptors needed by the proc_open.
*
@ -1007,7 +1080,7 @@ class Process
*/
private function getDescriptors()
{
$this->processPipes = new ProcessPipes($this->useFileHandles, $this->tty);
$this->processPipes = new ProcessPipes($this->useFileHandles, $this->tty, $this->pty);
$descriptors = $this->processPipes->getDescriptors();
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {

View File

@ -28,11 +28,14 @@ class ProcessPipes
private $useFiles;
/** @var Boolean */
private $ttyMode;
/** @var Boolean */
private $ptyMode;
public function __construct($useFiles, $ttyMode)
public function __construct($useFiles, $ttyMode, $ptyMode = false)
{
$this->useFiles = (Boolean) $useFiles;
$this->ttyMode = (Boolean) $ttyMode;
$this->ptyMode = (Boolean) $ptyMode;
// 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.
@ -117,6 +120,12 @@ class ProcessPipes
array('file', '/dev/tty', 'w'),
array('file', '/dev/tty', 'w'),
);
} elseif ($this->ptyMode && Porcess::isPtySupported()) {
$descriptors = array(
array('pty'),
array('pty'),
array('pty'),
);
}
return array(

View File

@ -247,6 +247,45 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->assertSame(Process::STATUS_TERMINATED, $process->getStatus());
}
/**
* @group pty
*/
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());
}
/**
* @group mustRun
*/
public function testMustRun()
{
$process = $this->getProcess('echo "foo"');
$this->assertSame($process, $process->mustRun());
$this->assertEquals("foo\n", $process->getOutput());
$this->assertEquals(0, $process->getExitCode());
}
/**
* @expectedException Symfony\Component\Process\Exception\ProcessFailedException
* @group mustRun
*/
public function testMustRunThrowsException()
{
$process = $this->getProcess('exit 1');
$process->mustRun();
}
public function testExitCodeText()
{
$process = $this->getProcess('');

View File

@ -45,6 +45,24 @@ class SigchildDisabledProcessTest extends AbstractProcessTest
parent::testExitCodeCommandFailed();
}
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
* @group mustRun
*/
public function testMustRun()
{
parent::testMustRun();
}
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
* @group mustRun
*/
public function testMustRunThrowsException()
{
parent::testMustRunThrowsException();
}
/**
* @expectedException \Symfony\Component\Process\Exception\RuntimeException
*/