1
0
mirror of https://github.com/danog/process.git synced 2024-11-30 04:39:04 +01:00

Use shared CS config; update styles

This commit is contained in:
Aaron Piotrowski 2018-10-14 23:16:09 -05:00
parent c2c529d72d
commit 9460633c7d
No known key found for this signature in database
GPG Key ID: ADD1EF783EDE9EEB
24 changed files with 242 additions and 157 deletions

46
.php_cs
View File

@ -1,40 +1,10 @@
<?php <?php
return PhpCsFixer\Config::create() $config = new Amp\CodeStyle\Config();
->setRiskyAllowed(true) $config->getFinder()->in(__DIR__);
->setRules([
"@PSR1" => true, $cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__;
"@PSR2" => true,
"braces" => [ $config->setCacheFile($cacheDir . '/.php_cs.cache');
"allow_single_line_closure" => true,
"position_after_functions_and_oop_constructs" => "same", return $config;
],
"array_syntax" => ["syntax" => "short"],
"cast_spaces" => true,
"combine_consecutive_unsets" => true,
"function_to_constant" => true,
"no_multiline_whitespace_before_semicolons" => true,
"no_unused_imports" => true,
"no_useless_else" => true,
"no_useless_return" => true,
"no_whitespace_before_comma_in_array" => true,
"no_whitespace_in_blank_line" => true,
"non_printable_character" => true,
"normalize_index_brace" => true,
"ordered_imports" => true,
"php_unit_construct" => true,
"php_unit_dedicate_assert" => true,
"php_unit_fqcn_annotation" => true,
"phpdoc_summary" => true,
"phpdoc_types" => true,
"psr4" => true,
"return_type_declaration" => ["space_before" => "none"],
"short_scalar_cast" => true,
"single_blank_line_before_namespace" => true,
])
->setFinder(
PhpCsFixer\Finder::create()
->in(__DIR__ . "/examples")
->in(__DIR__ . "/lib")
->in(__DIR__ . "/test")
);

View File

@ -5,12 +5,12 @@
"require": { "require": {
"php": ">=7", "php": ">=7",
"amphp/amp": "^2", "amphp/amp": "^2",
"amphp/byte-stream": "^1" "amphp/byte-stream": "^1.2"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^6", "phpunit/phpunit": "^6",
"amphp/phpunit-util": "^1", "amphp/phpunit-util": "^1",
"friendsofphp/php-cs-fixer": "^2.3" "amphp/php-cs-fixer-config": "dev-master"
}, },
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
@ -42,5 +42,14 @@
"platform": { "platform": {
"php": "7.0.0" "php": "7.0.0"
} }
},
"scripts": {
"check": [
"@cs",
"@test"
],
"cs": "php-cs-fixer fix -v --diff --dry-run",
"cs-fix": "php-cs-fixer fix -v --diff",
"test": "@php -dzend.assertions=1 -dassert.exception=1 ./vendor/bin/phpunit --coverage-text"
} }
} }

View File

@ -1,6 +1,6 @@
<?php <?php
include dirname(__DIR__) . "/vendor/autoload.php"; include \dirname(__DIR__) . "/vendor/autoload.php";
use Amp\ByteStream\Message; use Amp\ByteStream\Message;
use Amp\Process\Process; use Amp\Process\Process;

View File

@ -1,11 +1,12 @@
<?php <?php
include dirname(__DIR__) . "/vendor/autoload.php"; include \dirname(__DIR__) . "/vendor/autoload.php";
use Amp\Process\Process; use Amp\Process\Process;
use function Amp\Promise\all; use function Amp\Promise\all;
function show_process_output(Process $process): \Generator { function show_process_output(Process $process): \Generator
{
$stream = $process->getStdout(); $stream = $process->getStdout();
while (null !== $chunk = yield $stream->read()) { while (null !== $chunk = yield $stream->read()) {

View File

@ -1,6 +1,6 @@
<?php <?php
include dirname(__DIR__) . "/vendor/autoload.php"; include \dirname(__DIR__) . "/vendor/autoload.php";
use Amp\Process\Process; use Amp\Process\Process;

View File

@ -1,6 +1,6 @@
<?php <?php
include dirname(__DIR__) . "/vendor/autoload.php"; include \dirname(__DIR__) . "/vendor/autoload.php";
use Amp\ByteStream\Message; use Amp\ByteStream\Message;
use Amp\Process\Process; use Amp\Process\Process;

View File

@ -6,8 +6,10 @@ use Amp\Deferred;
use Amp\Process\Internal\ProcessHandle; use Amp\Process\Internal\ProcessHandle;
/** @internal */ /** @internal */
final class Handle extends ProcessHandle { final class Handle extends ProcessHandle
public function __construct() { {
public function __construct()
{
$this->pidDeferred = new Deferred; $this->pidDeferred = new Deferred;
$this->joinDeferred = new Deferred; $this->joinDeferred = new Deferred;
$this->originalParentPid = \getmypid(); $this->originalParentPid = \getmypid();

View File

@ -15,7 +15,8 @@ use Amp\Process\ProcessOutputStream;
use Amp\Promise; use Amp\Promise;
/** @internal */ /** @internal */
final class Runner implements ProcessRunner { final class Runner implements ProcessRunner
{
const FD_SPEC = [ const FD_SPEC = [
["pipe", "r"], // stdin ["pipe", "r"], // stdin
["pipe", "w"], // stdout ["pipe", "w"], // stdout
@ -23,7 +24,8 @@ final class Runner implements ProcessRunner {
["pipe", "w"], // exit code pipe ["pipe", "w"], // exit code pipe
]; ];
public static function onProcessEndExtraDataPipeReadable($watcher, $stream, Handle $handle) { public static function onProcessEndExtraDataPipeReadable($watcher, $stream, Handle $handle)
{
Loop::cancel($watcher); Loop::cancel($watcher);
$handle->extraDataPipeWatcher = null; $handle->extraDataPipeWatcher = null;
@ -36,7 +38,8 @@ final class Runner implements ProcessRunner {
} }
} }
public static function onProcessStartExtraDataPipeReadable($watcher, $stream, $data) { public static function onProcessStartExtraDataPipeReadable($watcher, $stream, $data)
{
Loop::cancel($watcher); Loop::cancel($watcher);
$pid = \rtrim(@\fgets($stream)); $pid = \rtrim(@\fgets($stream));
@ -70,7 +73,8 @@ final class Runner implements ProcessRunner {
} }
/** @inheritdoc */ /** @inheritdoc */
public function start(string $command, string $cwd = null, array $env = [], array $options = []): ProcessHandle { public function start(string $command, string $cwd = null, array $env = [], array $options = []): ProcessHandle
{
$command = \sprintf( $command = \sprintf(
'{ (%s) <&3 3<&- 3>/dev/null & } 3<&0;' . '{ (%s) <&3 3<&- 3>/dev/null & } 3<&0;' .
'pid=$!; echo $pid >&3; wait $pid; RC=$?; echo $RC >&3; exit $RC', 'pid=$!; echo $pid >&3; wait $pid; RC=$?; echo $RC >&3; exit $RC',
@ -124,7 +128,8 @@ final class Runner implements ProcessRunner {
} }
/** @inheritdoc */ /** @inheritdoc */
public function join(ProcessHandle $handle): Promise { public function join(ProcessHandle $handle): Promise
{
/** @var Handle $handle */ /** @var Handle $handle */
if ($handle->extraDataPipeWatcher !== null) { if ($handle->extraDataPipeWatcher !== null) {
Loop::reference($handle->extraDataPipeWatcher); Loop::reference($handle->extraDataPipeWatcher);
@ -134,7 +139,8 @@ final class Runner implements ProcessRunner {
} }
/** @inheritdoc */ /** @inheritdoc */
public function kill(ProcessHandle $handle) { public function kill(ProcessHandle $handle)
{
/** @var Handle $handle */ /** @var Handle $handle */
if ($handle->extraDataPipeWatcher !== null) { if ($handle->extraDataPipeWatcher !== null) {
Loop::cancel($handle->extraDataPipeWatcher); Loop::cancel($handle->extraDataPipeWatcher);
@ -160,7 +166,8 @@ final class Runner implements ProcessRunner {
} }
/** @inheritdoc */ /** @inheritdoc */
public function signal(ProcessHandle $handle, int $signo) { public function signal(ProcessHandle $handle, int $signo)
{
/** @var Handle $handle */ /** @var Handle $handle */
if (!\proc_terminate($handle->proc, $signo)) { if (!\proc_terminate($handle->proc, $signo)) {
throw new ProcessException("Sending signal to process failed"); throw new ProcessException("Sending signal to process failed");
@ -168,7 +175,8 @@ final class Runner implements ProcessRunner {
} }
/** @inheritdoc */ /** @inheritdoc */
public function destroy(ProcessHandle $handle) { public function destroy(ProcessHandle $handle)
{
/** @var Handle $handle */ /** @var Handle $handle */
if ($handle->status < ProcessStatus::ENDED && \getmypid() === $handle->originalParentPid) { if ($handle->status < ProcessStatus::ENDED && \getmypid() === $handle->originalParentPid) {
try { try {
@ -182,7 +190,8 @@ final class Runner implements ProcessRunner {
$this->free($handle); $this->free($handle);
} }
private function free(Handle $handle) { private function free(Handle $handle)
{
/** @var Handle $handle */ /** @var Handle $handle */
if ($handle->extraDataPipeWatcher !== null) { if ($handle->extraDataPipeWatcher !== null) {
Loop::cancel($handle->extraDataPipeWatcher); Loop::cancel($handle->extraDataPipeWatcher);

View File

@ -7,7 +7,8 @@ use Amp\Process\ProcessInputStream;
use Amp\Process\ProcessOutputStream; use Amp\Process\ProcessOutputStream;
use Amp\Struct; use Amp\Struct;
abstract class ProcessHandle { abstract class ProcessHandle
{
use Struct; use Struct;
/** @var ProcessOutputStream */ /** @var ProcessOutputStream */

View File

@ -5,7 +5,8 @@ namespace Amp\Process\Internal;
use Amp\Process\ProcessException; use Amp\Process\ProcessException;
use Amp\Promise; use Amp\Promise;
interface ProcessRunner { interface ProcessRunner
{
/** /**
* Start a process using the supplied parameters. * Start a process using the supplied parameters.
* *

View File

@ -2,12 +2,14 @@
namespace Amp\Process\Internal; namespace Amp\Process\Internal;
final class ProcessStatus { final class ProcessStatus
{
const STARTING = 0; const STARTING = 0;
const RUNNING = 1; const RUNNING = 1;
const ENDED = 2; const ENDED = 2;
private function __construct() { private function __construct()
{
// empty to prevent instances of this class // empty to prevent instances of this class
} }
} }

View File

@ -9,8 +9,10 @@ use Amp\Process\Internal\ProcessHandle;
* @internal * @internal
* @codeCoverageIgnore Windows only. * @codeCoverageIgnore Windows only.
*/ */
final class Handle extends ProcessHandle { final class Handle extends ProcessHandle
public function __construct() { {
public function __construct()
{
$this->joinDeferred = new Deferred; $this->joinDeferred = new Deferred;
$this->pidDeferred = new Deferred; $this->pidDeferred = new Deferred;
} }

View File

@ -6,7 +6,8 @@ namespace Amp\Process\Internal\Windows;
* @internal * @internal
* @codeCoverageIgnore Windows only. * @codeCoverageIgnore Windows only.
*/ */
final class HandshakeStatus { final class HandshakeStatus
{
const SUCCESS = 0; const SUCCESS = 0;
const SIGNAL_UNEXPECTED = 0x01; const SIGNAL_UNEXPECTED = 0x01;
const INVALID_STREAM_ID = 0x02; const INVALID_STREAM_ID = 0x02;
@ -14,7 +15,8 @@ final class HandshakeStatus {
const DUPLICATE_STREAM_ID = 0x04; const DUPLICATE_STREAM_ID = 0x04;
const INVALID_CLIENT_TOKEN = 0x05; const INVALID_CLIENT_TOKEN = 0x05;
private function __construct() { private function __construct()
{
// empty to prevent instances of this class // empty to prevent instances of this class
} }
} }

View File

@ -8,7 +8,8 @@ use Amp\Struct;
* @internal * @internal
* @codeCoverageIgnore Windows only. * @codeCoverageIgnore Windows only.
*/ */
final class PendingSocketClient { final class PendingSocketClient
{
use Struct; use Struct;
public $readWatcher; public $readWatcher;

View File

@ -17,7 +17,8 @@ use const Amp\Process\BIN_DIR;
* @internal * @internal
* @codeCoverageIgnore Windows only. * @codeCoverageIgnore Windows only.
*/ */
final class Runner implements ProcessRunner { final class Runner implements ProcessRunner
{
const FD_SPEC = [ const FD_SPEC = [
["pipe", "r"], // stdin ["pipe", "r"], // stdin
["pipe", "w"], // stdout ["pipe", "w"], // stdout
@ -33,11 +34,12 @@ final class Runner implements ProcessRunner {
private $socketConnector; private $socketConnector;
private function makeCommand(string $workingDirectory): string { private function makeCommand(string $workingDirectory): string
{
$wrapperPath = self::WRAPPER_EXE_PATH; $wrapperPath = self::WRAPPER_EXE_PATH;
// We can't execute the exe from within the PHAR, so copy it out... // We can't execute the exe from within the PHAR, so copy it out...
if (strncmp($wrapperPath, "phar://", 7) === 0) { if (\strncmp($wrapperPath, "phar://", 7) === 0) {
if (self::$pharWrapperPath === null) { if (self::$pharWrapperPath === null) {
self::$pharWrapperPath = \tempnam(\sys_get_temp_dir(), "amphp-process-wrapper-"); self::$pharWrapperPath = \tempnam(\sys_get_temp_dir(), "amphp-process-wrapper-");
\copy(self::WRAPPER_EXE_PATH, self::$pharWrapperPath); \copy(self::WRAPPER_EXE_PATH, self::$pharWrapperPath);
@ -65,12 +67,14 @@ final class Runner implements ProcessRunner {
return $result; return $result;
} }
public function __construct() { public function __construct()
{
$this->socketConnector = new SocketConnector; $this->socketConnector = new SocketConnector;
} }
/** @inheritdoc */ /** @inheritdoc */
public function start(string $command, string $cwd = null, array $env = [], array $options = []): ProcessHandle { public function start(string $command, string $cwd = null, array $env = [], array $options = []): ProcessHandle
{
if (\strpos($command, "\0") !== false) { if (\strpos($command, "\0") !== false) {
throw new ProcessException("Can't execute commands that contain null bytes."); throw new ProcessException("Can't execute commands that contain null bytes.");
} }
@ -130,7 +134,8 @@ final class Runner implements ProcessRunner {
} }
/** @inheritdoc */ /** @inheritdoc */
public function join(ProcessHandle $handle): Promise { public function join(ProcessHandle $handle): Promise
{
/** @var Handle $handle */ /** @var Handle $handle */
$handle->exitCodeRequested = true; $handle->exitCodeRequested = true;
@ -142,7 +147,8 @@ final class Runner implements ProcessRunner {
} }
/** @inheritdoc */ /** @inheritdoc */
public function kill(ProcessHandle $handle) { public function kill(ProcessHandle $handle)
{
/** @var Handle $handle */ /** @var Handle $handle */
// todo: send a signal to the wrapper to kill the child instead? // todo: send a signal to the wrapper to kill the child instead?
if (!\proc_terminate($handle->proc)) { if (!\proc_terminate($handle->proc)) {
@ -174,12 +180,14 @@ final class Runner implements ProcessRunner {
} }
/** @inheritdoc */ /** @inheritdoc */
public function signal(ProcessHandle $handle, int $signo) { public function signal(ProcessHandle $handle, int $signo)
{
throw new ProcessException('Signals are not supported on Windows'); throw new ProcessException('Signals are not supported on Windows');
} }
/** @inheritdoc */ /** @inheritdoc */
public function destroy(ProcessHandle $handle) { public function destroy(ProcessHandle $handle)
{
/** @var Handle $handle */ /** @var Handle $handle */
if ($handle->status < ProcessStatus::ENDED && \is_resource($handle->proc)) { if ($handle->status < ProcessStatus::ENDED && \is_resource($handle->proc)) {
try { try {
@ -193,7 +201,8 @@ final class Runner implements ProcessRunner {
$this->free($handle); $this->free($handle);
} }
private function free(Handle $handle) { private function free(Handle $handle)
{
if ($handle->childPidWatcher !== null) { if ($handle->childPidWatcher !== null) {
Loop::cancel($handle->childPidWatcher); Loop::cancel($handle->childPidWatcher);
$handle->childPidWatcher = null; $handle->childPidWatcher = null;

View File

@ -6,13 +6,15 @@ namespace Amp\Process\Internal\Windows;
* @internal * @internal
* @codeCoverageIgnore Windows only. * @codeCoverageIgnore Windows only.
*/ */
final class SignalCode { final class SignalCode
{
const HANDSHAKE = 0x01; const HANDSHAKE = 0x01;
const HANDSHAKE_ACK = 0x02; const HANDSHAKE_ACK = 0x02;
const CHILD_PID = 0x03; const CHILD_PID = 0x03;
const EXIT_CODE = 0x04; const EXIT_CODE = 0x04;
private function __construct() { private function __construct()
{
// empty to prevent instances of this class // empty to prevent instances of this class
} }
} }

View File

@ -12,7 +12,8 @@ use Amp\Process\ProcessException;
* @internal * @internal
* @codeCoverageIgnore Windows only. * @codeCoverageIgnore Windows only.
*/ */
final class SocketConnector { final class SocketConnector
{
const SERVER_SOCKET_URI = 'tcp://127.0.0.1:0'; const SERVER_SOCKET_URI = 'tcp://127.0.0.1:0';
const SECURITY_TOKEN_SIZE = 16; const SECURITY_TOKEN_SIZE = 16;
const CONNECT_TIMEOUT = 1000; const CONNECT_TIMEOUT = 1000;
@ -32,7 +33,8 @@ final class SocketConnector {
/** @var int */ /** @var int */
public $port; public $port;
public function __construct() { public function __construct()
{
$flags = \STREAM_SERVER_LISTEN | \STREAM_SERVER_BIND; $flags = \STREAM_SERVER_LISTEN | \STREAM_SERVER_BIND;
$this->server = \stream_socket_server(self::SERVER_SOCKET_URI, $errNo, $errStr, $flags); $this->server = \stream_socket_server(self::SERVER_SOCKET_URI, $errNo, $errStr, $flags);
@ -50,14 +52,16 @@ final class SocketConnector {
Loop::unreference(Loop::onReadable($this->server, [$this, 'onServerSocketReadable'])); Loop::unreference(Loop::onReadable($this->server, [$this, 'onServerSocketReadable']));
} }
private function failClientHandshake($socket, int $code) { private function failClientHandshake($socket, int $code)
{
\fwrite($socket, \chr(SignalCode::HANDSHAKE_ACK) . \chr($code)); \fwrite($socket, \chr(SignalCode::HANDSHAKE_ACK) . \chr($code));
\fclose($socket); \fclose($socket);
unset($this->pendingClients[(int) $socket]); unset($this->pendingClients[(int) $socket]);
} }
public function failHandleStart(Handle $handle, string $message, ...$args) { public function failHandleStart(Handle $handle, string $message, ...$args)
{
Loop::cancel($handle->connectTimeoutWatcher); Loop::cancel($handle->connectTimeoutWatcher);
unset($this->pendingProcesses[$handle->wrapperPid]); unset($this->pendingProcesses[$handle->wrapperPid]);
@ -88,7 +92,8 @@ final class SocketConnector {
* *
* @return string|null * @return string|null
*/ */
private function readDataFromPendingClient($socket, int $length, PendingSocketClient $state) { private function readDataFromPendingClient($socket, int $length, PendingSocketClient $state)
{
$data = \fread($socket, $length); $data = \fread($socket, $length);
if ($data === false || $data === '') { if ($data === false || $data === '') {
@ -109,7 +114,8 @@ final class SocketConnector {
return $data; return $data;
} }
public function onReadableHandshake($watcher, $socket) { public function onReadableHandshake($watcher, $socket)
{
$socketId = (int) $socket; $socketId = (int) $socket;
$pendingClient = $this->pendingClients[$socketId]; $pendingClient = $this->pendingClients[$socketId];
@ -169,7 +175,8 @@ final class SocketConnector {
$pendingClient->readWatcher = Loop::onReadable($socket, [$this, 'onReadableHandshakeAck']); $pendingClient->readWatcher = Loop::onReadable($socket, [$this, 'onReadableHandshakeAck']);
} }
public function onReadableHandshakeAck($watcher, $socket) { public function onReadableHandshakeAck($watcher, $socket)
{
$socketId = (int) $socket; $socketId = (int) $socket;
$pendingClient = $this->pendingClients[$socketId]; $pendingClient = $this->pendingClients[$socketId];
@ -217,7 +224,8 @@ final class SocketConnector {
} }
} }
public function onReadableChildPid($watcher, $socket, Handle $handle) { public function onReadableChildPid($watcher, $socket, Handle $handle)
{
$data = \fread($socket, 5); $data = \fread($socket, 5);
if ($data === false || $data === '') { if ($data === false || $data === '') {
@ -264,7 +272,8 @@ final class SocketConnector {
unset($this->pendingProcesses[$handle->wrapperPid]); unset($this->pendingProcesses[$handle->wrapperPid]);
} }
public function onReadableExitCode($watcher, $socket, Handle $handle) { public function onReadableExitCode($watcher, $socket, Handle $handle)
{
$data = \fread($socket, 5); $data = \fread($socket, 5);
if ($data === false || $data === '') { if ($data === false || $data === '') {
@ -305,7 +314,8 @@ final class SocketConnector {
} }
} }
public function onClientSocketConnectTimeout($watcher, $socket) { public function onClientSocketConnectTimeout($watcher, $socket)
{
$id = (int) $socket; $id = (int) $socket;
Loop::cancel($this->pendingClients[$id]->readWatcher); Loop::cancel($this->pendingClients[$id]->readWatcher);
@ -314,7 +324,8 @@ final class SocketConnector {
\fclose($socket); \fclose($socket);
} }
public function onServerSocketReadable() { public function onServerSocketReadable()
{
$socket = \stream_socket_accept($this->server); $socket = \stream_socket_accept($this->server);
if (!\stream_set_blocking($socket, false)) { if (!\stream_set_blocking($socket, false)) {
@ -328,7 +339,8 @@ final class SocketConnector {
$this->pendingClients[(int) $socket] = $pendingClient; $this->pendingClients[(int) $socket] = $pendingClient;
} }
public function onProcessConnectTimeout($watcher, Handle $handle) { public function onProcessConnectTimeout($watcher, Handle $handle)
{
$running = \is_resource($handle->proc) && \proc_get_status($handle->proc)['running']; $running = \is_resource($handle->proc) && \proc_get_status($handle->proc)['running'];
$error = null; $error = null;
@ -352,7 +364,8 @@ final class SocketConnector {
$handle->joinDeferred->fail($error); $handle->joinDeferred->fail($error);
} }
public function registerPendingProcess(Handle $handle) { public function registerPendingProcess(Handle $handle)
{
// Use Loop::defer() to start the timeout only after the loop has ticked once. This prevents issues with many // Use Loop::defer() to start the timeout only after the loop has ticked once. This prevents issues with many
// things started at once, see https://github.com/amphp/process/issues/21. // things started at once, see https://github.com/amphp/process/issues/21.
$handle->connectTimeoutWatcher = Loop::defer(function () use ($handle) { $handle->connectTimeoutWatcher = Loop::defer(function () use ($handle) {

View File

@ -10,7 +10,8 @@ use Amp\Process\Internal\ProcessStatus;
use Amp\Process\Internal\Windows\Runner as WindowsProcessRunner; use Amp\Process\Internal\Windows\Runner as WindowsProcessRunner;
use Amp\Promise; use Amp\Promise;
class Process { class Process
{
/** @var ProcessRunner */ /** @var ProcessRunner */
private $processRunner; private $processRunner;
@ -38,7 +39,8 @@ class Process {
* *
* @throws \Error If the arguments are invalid. * @throws \Error If the arguments are invalid.
*/ */
public function __construct($command, string $cwd = null, array $env = [], array $options = []) { public function __construct($command, string $cwd = null, array $env = [], array $options = [])
{
$command = \is_array($command) $command = \is_array($command)
? \implode(" ", \array_map("escapeshellarg", $command)) ? \implode(" ", \array_map("escapeshellarg", $command))
: (string) $command; : (string) $command;
@ -73,13 +75,15 @@ class Process {
/** /**
* Stops the process if it is still running. * Stops the process if it is still running.
*/ */
public function __destruct() { public function __destruct()
{
if ($this->handle !== null) { if ($this->handle !== null) {
$this->processRunner->destroy($this->handle); $this->processRunner->destroy($this->handle);
} }
} }
public function __clone() { public function __clone()
{
throw new \Error("Cloning is not allowed!"); throw new \Error("Cloning is not allowed!");
} }
@ -88,7 +92,8 @@ class Process {
* *
* @throws StatusError If the process has already been started. * @throws StatusError If the process has already been started.
*/ */
public function start() { public function start()
{
if ($this->handle) { if ($this->handle) {
throw new StatusError("Process has already been started."); throw new StatusError("Process has already been started.");
} }
@ -103,7 +108,8 @@ class Process {
* *
* @throws StatusError If the process has already been started. * @throws StatusError If the process has already been started.
*/ */
public function join(): Promise { public function join(): Promise
{
if (!$this->handle) { if (!$this->handle) {
throw new StatusError("Process has not been started."); throw new StatusError("Process has not been started.");
} }
@ -117,7 +123,8 @@ class Process {
* @throws StatusError If the process is not running. * @throws StatusError If the process is not running.
* @throws ProcessException If terminating the process fails. * @throws ProcessException If terminating the process fails.
*/ */
public function kill() { public function kill()
{
if (!$this->isRunning()) { if (!$this->isRunning()) {
throw new StatusError("Process is not running."); throw new StatusError("Process is not running.");
} }
@ -133,7 +140,8 @@ class Process {
* @throws StatusError If the process is not running. * @throws StatusError If the process is not running.
* @throws ProcessException If sending the signal fails. * @throws ProcessException If sending the signal fails.
*/ */
public function signal(int $signo) { public function signal(int $signo)
{
if (!$this->isRunning()) { if (!$this->isRunning()) {
throw new StatusError("Process is not running."); throw new StatusError("Process is not running.");
} }
@ -148,7 +156,8 @@ class Process {
* *
* @throws StatusError If the process has not started. * @throws StatusError If the process has not started.
*/ */
public function getPid(): Promise { public function getPid(): Promise
{
if (!$this->handle) { if (!$this->handle) {
throw new StatusError("Process has not been started."); throw new StatusError("Process has not been started.");
} }
@ -161,7 +170,8 @@ class Process {
* *
* @return string The command to execute. * @return string The command to execute.
*/ */
public function getCommand(): string { public function getCommand(): string
{
return $this->command; return $this->command;
} }
@ -170,7 +180,8 @@ class Process {
* *
* @return string The current working directory an empty string if inherited from the current PHP process. * @return string The current working directory an empty string if inherited from the current PHP process.
*/ */
public function getWorkingDirectory(): string { public function getWorkingDirectory(): string
{
if ($this->cwd === "") { if ($this->cwd === "") {
return \getcwd() ?: ""; return \getcwd() ?: "";
} }
@ -183,7 +194,8 @@ class Process {
* *
* @return string[] Array of environment variables. * @return string[] Array of environment variables.
*/ */
public function getEnv(): array { public function getEnv(): array
{
return $this->env; return $this->env;
} }
@ -192,7 +204,8 @@ class Process {
* *
* @return mixed[] Array of options. * @return mixed[] Array of options.
*/ */
public function getOptions(): array { public function getOptions(): array
{
return $this->options; return $this->options;
} }
@ -201,7 +214,8 @@ class Process {
* *
* @return bool * @return bool
*/ */
public function isRunning(): bool { public function isRunning(): bool
{
return $this->handle && $this->handle->status !== ProcessStatus::ENDED; return $this->handle && $this->handle->status !== ProcessStatus::ENDED;
} }
@ -210,7 +224,8 @@ class Process {
* *
* @return ProcessOutputStream * @return ProcessOutputStream
*/ */
public function getStdin(): ProcessOutputStream { public function getStdin(): ProcessOutputStream
{
if (!$this->handle) { if (!$this->handle) {
throw new StatusError("Process has not been started."); throw new StatusError("Process has not been started.");
} }
@ -223,7 +238,8 @@ class Process {
* *
* @return ProcessInputStream * @return ProcessInputStream
*/ */
public function getStdout(): ProcessInputStream { public function getStdout(): ProcessInputStream
{
if (!$this->handle) { if (!$this->handle) {
throw new StatusError("Process has not been started."); throw new StatusError("Process has not been started.");
} }
@ -236,7 +252,8 @@ class Process {
* *
* @return ProcessInputStream * @return ProcessInputStream
*/ */
public function getStderr(): ProcessInputStream { public function getStderr(): ProcessInputStream
{
if (!$this->handle) { if (!$this->handle) {
throw new StatusError("Process has not been started."); throw new StatusError("Process has not been started.");
} }

View File

@ -2,5 +2,6 @@
namespace Amp\Process; namespace Amp\Process;
class ProcessException extends \Exception { class ProcessException extends \Exception
{
} }

View File

@ -11,7 +11,8 @@ use Amp\Failure;
use Amp\Promise; use Amp\Promise;
use Amp\Success; use Amp\Success;
class ProcessInputStream implements InputStream { class ProcessInputStream implements InputStream
{
/** @var Deferred */ /** @var Deferred */
private $initialRead; private $initialRead;
@ -27,7 +28,8 @@ class ProcessInputStream implements InputStream {
/** @var StreamException|null */ /** @var StreamException|null */
private $error; private $error;
public function __construct(Promise $resourceStreamPromise) { public function __construct(Promise $resourceStreamPromise)
{
$resourceStreamPromise->onResolve(function ($error, $resourceStream) { $resourceStreamPromise->onResolve(function ($error, $resourceStream) {
if ($error) { if ($error) {
$this->error = new StreamException("Failed to launch process", 0, $error); $this->error = new StreamException("Failed to launch process", 0, $error);
@ -64,7 +66,8 @@ class ProcessInputStream implements InputStream {
* *
* @throws PendingReadError Thrown if another read operation is still pending. * @throws PendingReadError Thrown if another read operation is still pending.
*/ */
public function read(): Promise { public function read(): Promise
{
if ($this->initialRead) { if ($this->initialRead) {
throw new PendingReadError; throw new PendingReadError;
} }
@ -86,7 +89,8 @@ class ProcessInputStream implements InputStream {
return $this->initialRead->promise(); return $this->initialRead->promise();
} }
public function reference() { public function reference()
{
$this->referenced = true; $this->referenced = true;
if ($this->resourceStream) { if ($this->resourceStream) {
@ -94,7 +98,8 @@ class ProcessInputStream implements InputStream {
} }
} }
public function unreference() { public function unreference()
{
$this->referenced = false; $this->referenced = false;
if ($this->resourceStream) { if ($this->resourceStream) {
@ -102,7 +107,8 @@ class ProcessInputStream implements InputStream {
} }
} }
public function close() { public function close()
{
$this->shouldClose = true; $this->shouldClose = true;
if ($this->initialRead) { if ($this->initialRead) {

View File

@ -10,7 +10,8 @@ use Amp\Deferred;
use Amp\Failure; use Amp\Failure;
use Amp\Promise; use Amp\Promise;
class ProcessOutputStream implements OutputStream { class ProcessOutputStream implements OutputStream
{
/** @var \SplQueue */ /** @var \SplQueue */
private $queuedWrites; private $queuedWrites;
@ -23,7 +24,8 @@ class ProcessOutputStream implements OutputStream {
/** @var StreamException|null */ /** @var StreamException|null */
private $error; private $error;
public function __construct(Promise $resourceStreamPromise) { public function __construct(Promise $resourceStreamPromise)
{
$this->queuedWrites = new \SplQueue; $this->queuedWrites = new \SplQueue;
$resourceStreamPromise->onResolve(function ($error, $resourceStream) { $resourceStreamPromise->onResolve(function ($error, $resourceStream) {
if ($error) { if ($error) {
@ -55,7 +57,8 @@ class ProcessOutputStream implements OutputStream {
} }
/** @inheritdoc */ /** @inheritdoc */
public function write(string $data): Promise { public function write(string $data): Promise
{
if ($this->resourceStream) { if ($this->resourceStream) {
return $this->resourceStream->write($data); return $this->resourceStream->write($data);
} }
@ -75,7 +78,8 @@ class ProcessOutputStream implements OutputStream {
} }
/** @inheritdoc */ /** @inheritdoc */
public function end(string $finalData = ""): Promise { public function end(string $finalData = ""): Promise
{
if ($this->resourceStream) { if ($this->resourceStream) {
return $this->resourceStream->end($finalData); return $this->resourceStream->end($finalData);
} }
@ -96,7 +100,8 @@ class ProcessOutputStream implements OutputStream {
return $deferred->promise(); return $deferred->promise();
} }
public function close() { public function close()
{
$this->shouldClose = true; $this->shouldClose = true;
if ($this->resourceStream) { if ($this->resourceStream) {

View File

@ -2,5 +2,6 @@
namespace Amp\Process; namespace Amp\Process;
class StatusError extends \Error { class StatusError extends \Error
{
} }

View File

@ -11,14 +11,16 @@ use Amp\Process\ProcessOutputStream;
use Amp\Promise; use Amp\Promise;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ProcessTest extends TestCase { class ProcessTest extends TestCase
{
const CMD_PROCESS = \DIRECTORY_SEPARATOR === "\\" ? "cmd /c echo foo" : "echo foo"; const CMD_PROCESS = \DIRECTORY_SEPARATOR === "\\" ? "cmd /c echo foo" : "echo foo";
const CMD_PROCESS_SLOW = \DIRECTORY_SEPARATOR === "\\" ? "cmd /c ping -n 3 127.0.0.1 > nul" : "sleep 2"; const CMD_PROCESS_SLOW = \DIRECTORY_SEPARATOR === "\\" ? "cmd /c ping -n 3 127.0.0.1 > nul" : "sleep 2";
/** /**
* @expectedException \Amp\Process\StatusError * @expectedException \Amp\Process\StatusError
*/ */
public function testMultipleExecution() { public function testMultipleExecution()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$process->start(); $process->start();
@ -26,7 +28,8 @@ class ProcessTest extends TestCase {
}); });
} }
public function testIsRunning() { public function testIsRunning()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42"); $process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42");
$process->start(); $process->start();
@ -40,7 +43,8 @@ class ProcessTest extends TestCase {
}); });
} }
public function testExecuteResolvesToExitCode() { public function testExecuteResolvesToExitCode()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42"); $process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42");
$process->start(); $process->start();
@ -52,7 +56,8 @@ class ProcessTest extends TestCase {
}); });
} }
public function testCommandCanRun() { public function testCommandCanRun()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$process->start(); $process->start();
@ -62,7 +67,8 @@ class ProcessTest extends TestCase {
}); });
} }
public function testProcessCanTerminate() { public function testProcessCanTerminate()
{
if (\DIRECTORY_SEPARATOR === "\\") { if (\DIRECTORY_SEPARATOR === "\\") {
$this->markTestSkipped("Signals are not supported on Windows"); $this->markTestSkipped("Signals are not supported on Windows");
} }
@ -76,28 +82,32 @@ class ProcessTest extends TestCase {
}); });
} }
public function testGetWorkingDirectoryIsDefault() { public function testGetWorkingDirectoryIsDefault()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$this->assertSame(getcwd(), $process->getWorkingDirectory()); $this->assertSame(\getcwd(), $process->getWorkingDirectory());
}); });
} }
public function testGetWorkingDirectoryIsCustomized() { public function testGetWorkingDirectoryIsCustomized()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS, __DIR__); $process = new Process(self::CMD_PROCESS, __DIR__);
$this->assertSame(__DIR__, $process->getWorkingDirectory()); $this->assertSame(__DIR__, $process->getWorkingDirectory());
}); });
} }
public function testGetEnv() { public function testGetEnv()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$this->assertSame([], $process->getEnv()); $this->assertSame([], $process->getEnv());
}); });
} }
public function testGetStdin() { public function testGetStdin()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$process->start(); $process->start();
@ -106,7 +116,8 @@ class ProcessTest extends TestCase {
}); });
} }
public function testGetStdout() { public function testGetStdout()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$process->start(); $process->start();
@ -115,7 +126,8 @@ class ProcessTest extends TestCase {
}); });
} }
public function testGetStderr() { public function testGetStderr()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$process->start(); $process->start();
@ -124,7 +136,8 @@ class ProcessTest extends TestCase {
}); });
} }
public function testProcessEnvIsValid() { public function testProcessEnvIsValid()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS, null, [ $process = new Process(self::CMD_PROCESS, null, [
'test' => 'foobar', 'test' => 'foobar',
@ -140,7 +153,8 @@ class ProcessTest extends TestCase {
/** /**
* @expectedException \Error * @expectedException \Error
*/ */
public function testProcessEnvIsInvalid() { public function testProcessEnvIsInvalid()
{
$process = new Process(self::CMD_PROCESS, null, [ $process = new Process(self::CMD_PROCESS, null, [
['error_value'] ['error_value']
]); ]);
@ -150,7 +164,8 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\StatusError * @expectedException \Amp\Process\StatusError
* @expectedExceptionMessage Process has not been started. * @expectedExceptionMessage Process has not been started.
*/ */
public function testGetStdinIsStatusError() { public function testGetStdinIsStatusError()
{
$process = new Process(self::CMD_PROCESS, null, []); $process = new Process(self::CMD_PROCESS, null, []);
$process->getStdin(); $process->getStdin();
} }
@ -159,7 +174,8 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\StatusError * @expectedException \Amp\Process\StatusError
* @expectedExceptionMessage Process has not been started. * @expectedExceptionMessage Process has not been started.
*/ */
public function testGetStdoutIsStatusError() { public function testGetStdoutIsStatusError()
{
$process = new Process(self::CMD_PROCESS, null, []); $process = new Process(self::CMD_PROCESS, null, []);
$process->getStdout(); $process->getStdout();
} }
@ -168,7 +184,8 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\StatusError * @expectedException \Amp\Process\StatusError
* @expectedExceptionMessage Process has not been started. * @expectedExceptionMessage Process has not been started.
*/ */
public function testGetStderrIsStatusError() { public function testGetStderrIsStatusError()
{
$process = new Process(self::CMD_PROCESS, null, []); $process = new Process(self::CMD_PROCESS, null, []);
$process->getStderr(); $process->getStderr();
} }
@ -177,7 +194,8 @@ class ProcessTest extends TestCase {
* @expectedException \Error * @expectedException \Error
* @expectedExceptionMessage Cloning is not allowed! * @expectedExceptionMessage Cloning is not allowed!
*/ */
public function testProcessCantBeCloned() { public function testProcessCantBeCloned()
{
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
clone $process; clone $process;
} }
@ -186,7 +204,8 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\ProcessException * @expectedException \Amp\Process\ProcessException
* @expectedExceptionMessage The process was killed * @expectedExceptionMessage The process was killed
*/ */
public function testKillImmediately() { public function testKillImmediately()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS_SLOW); $process = new Process(self::CMD_PROCESS_SLOW);
$process->start(); $process->start();
@ -199,7 +218,8 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\ProcessException * @expectedException \Amp\Process\ProcessException
* @expectedExceptionMessage The process was killed * @expectedExceptionMessage The process was killed
*/ */
public function testKillThenReadStdout() { public function testKillThenReadStdout()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS_SLOW); $process = new Process(self::CMD_PROCESS_SLOW);
$process->start(); $process->start();
@ -219,7 +239,8 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\StatusError * @expectedException \Amp\Process\StatusError
* @expectedExceptionMessage Process has not been started. * @expectedExceptionMessage Process has not been started.
*/ */
public function testProcessHasNotBeenStartedWithJoin() { public function testProcessHasNotBeenStartedWithJoin()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
yield $process->join(); yield $process->join();
@ -230,7 +251,8 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\StatusError * @expectedException \Amp\Process\StatusError
* @expectedExceptionMessage Process has not been started. * @expectedExceptionMessage Process has not been started.
*/ */
public function testProcessHasNotBeenStartedWithGetPid() { public function testProcessHasNotBeenStartedWithGetPid()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
yield $process->getPid(); yield $process->getPid();
@ -241,7 +263,8 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\StatusError * @expectedException \Amp\Process\StatusError
* @expectedExceptionMessage Process is not running. * @expectedExceptionMessage Process is not running.
*/ */
public function testProcessIsNotRunningWithKill() { public function testProcessIsNotRunningWithKill()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$process->kill(); $process->kill();
@ -252,7 +275,8 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\StatusError * @expectedException \Amp\Process\StatusError
* @expectedExceptionMessage Process is not running. * @expectedExceptionMessage Process is not running.
*/ */
public function testProcessIsNotRunningWithSignal() { public function testProcessIsNotRunningWithSignal()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$process->signal(0); $process->signal(0);
@ -263,24 +287,28 @@ class ProcessTest extends TestCase {
* @expectedException \Amp\Process\StatusError * @expectedException \Amp\Process\StatusError
* @expectedExceptionMessage Process has not been started. * @expectedExceptionMessage Process has not been started.
*/ */
public function testProcessHasBeenStarted() { public function testProcessHasBeenStarted()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
yield $process->join(); yield $process->join();
}); });
} }
public function testCommand() { public function testCommand()
{
$process = new Process([self::CMD_PROCESS]); $process = new Process([self::CMD_PROCESS]);
$this->assertSame(\implode(" ", \array_map("escapeshellarg", [self::CMD_PROCESS])), $process->getCommand()); $this->assertSame(\implode(" ", \array_map("escapeshellarg", [self::CMD_PROCESS])), $process->getCommand());
} }
public function testOptions() { public function testOptions()
{
$process = new Process(self::CMD_PROCESS); $process = new Process(self::CMD_PROCESS);
$this->assertSame([], $process->getOptions()); $this->assertSame([], $process->getOptions());
} }
public function getProcessCounts(): array { public function getProcessCounts(): array
{
return \array_map(function (int $count): array { return \array_map(function (int $count): array {
return [$count]; return [$count];
}, \range(2, 32, 2)); }, \range(2, 32, 2));
@ -291,7 +319,8 @@ class ProcessTest extends TestCase {
* *
* @param int $count * @param int $count
*/ */
public function testSpawnMultipleProcesses(int $count) { public function testSpawnMultipleProcesses(int $count)
{
Loop::run(function () use ($count) { Loop::run(function () use ($count) {
$processes = []; $processes = [];
for ($i = 0; $i < $count; ++$i) { for ($i = 0; $i < $count; ++$i) {
@ -309,7 +338,8 @@ class ProcessTest extends TestCase {
}); });
} }
public function testReadOutputAfterExit() { public function testReadOutputAfterExit()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(["php", __DIR__ . "/bin/worker.php"]); $process = new Process(["php", __DIR__ . "/bin/worker.php"]);
$process->start(); $process->start();
@ -321,14 +351,15 @@ class ProcessTest extends TestCase {
}); });
} }
public function testReadOutputAfterExitWithLongOutput() { public function testReadOutputAfterExitWithLongOutput()
{
Loop::run(function () { Loop::run(function () {
$process = new Process(["php", __DIR__ . "/bin/worker.php"]); $process = new Process(["php", __DIR__ . "/bin/worker.php"]);
$process->start(); $process->start();
$count = 128 * 1024 + 1; $count = 128 * 1024 + 1;
$process->getStdin()->write("exit " . $count); $process->getStdin()->write("exit " . $count);
$this->assertSame(str_repeat(".", $count), yield new Message($process->getStdout())); $this->assertSame(\str_repeat(".", $count), yield new Message($process->getStdout()));
$this->assertSame(0, yield $process->join()); $this->assertSame(0, yield $process->join());
}); });

View File

@ -1,15 +1,15 @@
<?php <?php
$content = fread(STDIN, 1024); $content = \fread(STDIN, 1024);
$command = explode(" ", $content); $command = \explode(" ", $content);
if (count($command) !== 2) { if (\count($command) !== 2) {
exit(1); exit(1);
} }
if ($command[0] === "exit") { if ($command[0] === "exit") {
echo str_repeat(".", (int) $command[1]); echo \str_repeat(".", (int) $command[1]);
exit; exit;
} }