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 Travis2ff1870
adds convenience method mustRun53441aa
adds support for PTY mode
This commit is contained in:
commit
51d3d62946
@ -1,6 +1,12 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
2.5.0
|
||||
-----
|
||||
|
||||
* added support for PTY mode
|
||||
* added the convenience method "mustRun"
|
||||
|
||||
2.4.0
|
||||
-----
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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(
|
||||
|
@ -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('');
|
||||
|
@ -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
|
||||
*/
|
||||
|
Reference in New Issue
Block a user