2017-01-11 19:24:02 +01:00
|
|
|
<?php
|
|
|
|
|
2017-12-03 04:16:52 +01:00
|
|
|
namespace Amp\Process\Test;
|
2017-01-11 19:24:02 +01:00
|
|
|
|
2017-12-10 22:17:04 +01:00
|
|
|
use Amp\ByteStream\Message;
|
2017-12-07 03:41:07 +01:00
|
|
|
use Amp\Delayed;
|
2017-03-16 16:04:52 +01:00
|
|
|
use Amp\Loop;
|
2017-01-11 19:24:02 +01:00
|
|
|
use Amp\Process\Process;
|
2017-12-05 06:02:41 +01:00
|
|
|
use Amp\Process\ProcessInputStream;
|
|
|
|
use Amp\Process\ProcessOutputStream;
|
2017-12-05 22:53:41 +01:00
|
|
|
use Amp\Promise;
|
2017-06-16 05:53:33 +02:00
|
|
|
use PHPUnit\Framework\TestCase;
|
2017-01-11 19:24:02 +01:00
|
|
|
|
2017-06-16 05:53:33 +02:00
|
|
|
class ProcessTest extends TestCase {
|
2017-09-17 19:07:13 +02:00
|
|
|
const CMD_PROCESS = \DIRECTORY_SEPARATOR === "\\" ? "cmd /c echo foo" : "echo foo";
|
2017-12-05 22:53:41 +01:00
|
|
|
const CMD_PROCESS_SLOW = \DIRECTORY_SEPARATOR === "\\" ? "cmd /c ping -n 3 127.0.0.1 > nul" : "sleep 2";
|
2017-01-11 19:24:02 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\StatusError
|
|
|
|
*/
|
|
|
|
public function testMultipleExecution() {
|
2017-06-15 19:11:38 +02:00
|
|
|
Loop::run(function () {
|
2017-01-11 19:24:02 +01:00
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-03-17 04:19:15 +01:00
|
|
|
$process->start();
|
|
|
|
$process->start();
|
2017-01-11 19:24:02 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-01-16 05:49:41 +01:00
|
|
|
public function testIsRunning() {
|
2017-06-15 19:11:38 +02:00
|
|
|
Loop::run(function () {
|
2017-09-17 19:07:13 +02:00
|
|
|
$process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42");
|
2017-03-17 04:19:15 +01:00
|
|
|
$process->start();
|
|
|
|
$promise = $process->join();
|
2017-01-16 05:49:41 +01:00
|
|
|
|
|
|
|
$this->assertTrue($process->isRunning());
|
|
|
|
|
|
|
|
yield $promise;
|
|
|
|
|
|
|
|
$this->assertFalse($process->isRunning());
|
2017-03-16 16:04:52 +01:00
|
|
|
});
|
2017-01-16 05:49:41 +01:00
|
|
|
}
|
|
|
|
|
2017-01-11 21:27:40 +01:00
|
|
|
public function testExecuteResolvesToExitCode() {
|
2017-06-15 19:11:38 +02:00
|
|
|
Loop::run(function () {
|
2017-09-17 19:07:13 +02:00
|
|
|
$process = new Process(\DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit 42" : "exit 42");
|
2017-03-17 04:19:15 +01:00
|
|
|
$process->start();
|
2017-09-17 19:07:13 +02:00
|
|
|
|
2017-03-17 04:19:15 +01:00
|
|
|
$code = yield $process->join();
|
2017-01-11 19:24:02 +01:00
|
|
|
|
2017-01-11 21:27:40 +01:00
|
|
|
$this->assertSame(42, $code);
|
2017-01-16 05:49:41 +01:00
|
|
|
$this->assertFalse($process->isRunning());
|
2017-03-16 16:04:52 +01:00
|
|
|
});
|
2017-01-11 19:24:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testCommandCanRun() {
|
2017-06-15 19:11:38 +02:00
|
|
|
Loop::run(function () {
|
2017-01-11 19:24:02 +01:00
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-03-17 04:19:15 +01:00
|
|
|
$process->start();
|
2017-01-11 19:24:02 +01:00
|
|
|
|
2017-09-17 19:07:13 +02:00
|
|
|
$this->assertInternalType('int', yield $process->getPid());
|
2017-12-07 03:41:07 +01:00
|
|
|
$this->assertSame(0, yield $process->join());
|
2017-01-11 19:24:02 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-12-05 06:02:41 +01:00
|
|
|
public function testProcessCanTerminate() {
|
2017-12-05 10:52:03 +01:00
|
|
|
if (\DIRECTORY_SEPARATOR === "\\") {
|
|
|
|
$this->markTestSkipped("Signals are not supported on Windows");
|
|
|
|
}
|
|
|
|
|
2017-12-05 06:02:41 +01:00
|
|
|
Loop::run(function () {
|
2017-12-05 22:53:41 +01:00
|
|
|
$process = new Process(self::CMD_PROCESS_SLOW);
|
2017-12-05 06:02:41 +01:00
|
|
|
$process->start();
|
|
|
|
$process->signal(0);
|
2017-12-05 22:53:41 +01:00
|
|
|
$this->assertInstanceOf(Promise::class, $process->getPid());
|
2017-12-07 03:41:07 +01:00
|
|
|
$this->assertSame(0, yield $process->join());
|
2017-12-05 06:02:41 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testGetWorkingDirectoryIsDefault() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(self::CMD_PROCESS);
|
|
|
|
$this->assertSame(getcwd(), $process->getWorkingDirectory());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testGetWorkingDirectoryIsCustomized() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(self::CMD_PROCESS, __DIR__);
|
|
|
|
$this->assertSame(__DIR__, $process->getWorkingDirectory());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testGetEnv() {
|
|
|
|
Loop::run(function () {
|
2017-12-05 15:22:23 +01:00
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-12-05 06:02:41 +01:00
|
|
|
$this->assertSame([], $process->getEnv());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-12-05 15:22:23 +01:00
|
|
|
public function testGetStdin() {
|
2017-12-05 06:02:41 +01:00
|
|
|
Loop::run(function () {
|
2017-12-05 15:22:23 +01:00
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-12-05 06:02:41 +01:00
|
|
|
$process->start();
|
|
|
|
$this->assertInstanceOf(ProcessOutputStream::class, $process->getStdin());
|
2017-12-05 15:22:23 +01:00
|
|
|
yield $process->join();
|
2017-12-05 06:02:41 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-12-05 15:22:23 +01:00
|
|
|
public function testGetStdout() {
|
2017-12-05 06:02:41 +01:00
|
|
|
Loop::run(function () {
|
2017-12-05 15:22:23 +01:00
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-12-05 06:02:41 +01:00
|
|
|
$process->start();
|
|
|
|
$this->assertInstanceOf(ProcessInputStream::class, $process->getStdout());
|
2017-12-05 15:22:23 +01:00
|
|
|
yield $process->join();
|
2017-12-05 06:02:41 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-12-05 15:22:23 +01:00
|
|
|
public function testGetStderr() {
|
2017-12-05 06:02:41 +01:00
|
|
|
Loop::run(function () {
|
2017-12-05 15:22:23 +01:00
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-12-05 06:02:41 +01:00
|
|
|
$process->start();
|
|
|
|
$this->assertInstanceOf(ProcessInputStream::class, $process->getStderr());
|
2017-12-05 15:22:23 +01:00
|
|
|
yield $process->join();
|
2017-12-05 06:02:41 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testProcessEnvIsValid() {
|
2017-12-05 15:22:23 +01:00
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(self::CMD_PROCESS, null, [
|
2017-12-05 22:41:30 +01:00
|
|
|
'test' => 'foobar',
|
|
|
|
'PATH' => \getenv('PATH'),
|
|
|
|
'SystemRoot' => \getenv('SystemRoot') ?: '', // required on Windows for process wrapper
|
2017-12-05 15:22:23 +01:00
|
|
|
]);
|
|
|
|
$process->start();
|
2017-12-05 22:41:30 +01:00
|
|
|
$this->assertSame('foobar', $process->getEnv()['test']);
|
2017-12-05 15:22:23 +01:00
|
|
|
yield $process->join();
|
|
|
|
});
|
2017-12-05 06:02:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Error
|
|
|
|
*/
|
|
|
|
public function testProcessEnvIsInvalid() {
|
|
|
|
$process = new Process(self::CMD_PROCESS, null, [
|
|
|
|
['error_value']
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\StatusError
|
|
|
|
* @expectedExceptionMessage Process has not been started.
|
|
|
|
*/
|
|
|
|
public function testGetStdinIsStatusError() {
|
|
|
|
$process = new Process(self::CMD_PROCESS, null, []);
|
|
|
|
$process->getStdin();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\StatusError
|
|
|
|
* @expectedExceptionMessage Process has not been started.
|
|
|
|
*/
|
|
|
|
public function testGetStdoutIsStatusError() {
|
|
|
|
$process = new Process(self::CMD_PROCESS, null, []);
|
|
|
|
$process->getStdout();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\StatusError
|
|
|
|
* @expectedExceptionMessage Process has not been started.
|
|
|
|
*/
|
|
|
|
public function testGetStderrIsStatusError() {
|
|
|
|
$process = new Process(self::CMD_PROCESS, null, []);
|
|
|
|
$process->getStderr();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Error
|
|
|
|
* @expectedExceptionMessage Cloning is not allowed!
|
|
|
|
*/
|
2017-12-05 22:53:41 +01:00
|
|
|
public function testProcessCantBeCloned() {
|
2017-12-05 06:02:41 +01:00
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-12-05 22:53:41 +01:00
|
|
|
clone $process;
|
2017-12-05 06:02:41 +01:00
|
|
|
}
|
|
|
|
|
2017-01-11 19:24:02 +01:00
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\ProcessException
|
|
|
|
* @expectedExceptionMessage The process was killed
|
|
|
|
*/
|
2017-12-07 03:41:07 +01:00
|
|
|
public function testKillImmediately() {
|
2017-06-15 19:11:38 +02:00
|
|
|
Loop::run(function () {
|
2017-12-05 22:53:41 +01:00
|
|
|
$process = new Process(self::CMD_PROCESS_SLOW);
|
2017-03-17 04:19:15 +01:00
|
|
|
$process->start();
|
2017-12-07 03:41:07 +01:00
|
|
|
$process->kill();
|
|
|
|
yield $process->join();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\ProcessException
|
|
|
|
* @expectedExceptionMessage The process was killed
|
|
|
|
*/
|
|
|
|
public function testKillThenReadStdout() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(self::CMD_PROCESS_SLOW);
|
|
|
|
$process->start();
|
|
|
|
|
|
|
|
yield new Delayed(100); // Give process a chance to start, otherwise a different error is thrown.
|
|
|
|
|
2017-01-11 19:24:02 +01:00
|
|
|
$process->kill();
|
|
|
|
|
2017-12-07 02:21:06 +01:00
|
|
|
$this->assertNull(yield $process->getStdout()->read());
|
|
|
|
|
2017-12-05 22:53:41 +01:00
|
|
|
yield $process->join();
|
2017-03-16 16:04:52 +01:00
|
|
|
});
|
2017-01-11 19:24:02 +01:00
|
|
|
}
|
|
|
|
|
2017-12-07 03:41:07 +01:00
|
|
|
|
2017-12-05 06:02:41 +01:00
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\StatusError
|
|
|
|
* @expectedExceptionMessage Process has not been started.
|
|
|
|
*/
|
|
|
|
public function testProcessHasNotBeenStartedWithJoin() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-12-05 22:53:41 +01:00
|
|
|
yield $process->join();
|
2017-12-05 06:02:41 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\StatusError
|
|
|
|
* @expectedExceptionMessage Process has not been started.
|
|
|
|
*/
|
|
|
|
public function testProcessHasNotBeenStartedWithGetPid() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-12-05 22:53:41 +01:00
|
|
|
yield $process->getPid();
|
2017-12-05 06:02:41 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\StatusError
|
|
|
|
* @expectedExceptionMessage Process is not running.
|
|
|
|
*/
|
|
|
|
public function testProcessIsNotRunningWithKill() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(self::CMD_PROCESS);
|
|
|
|
$process->kill();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\StatusError
|
|
|
|
* @expectedExceptionMessage Process is not running.
|
|
|
|
*/
|
|
|
|
public function testProcessIsNotRunningWithSignal() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(self::CMD_PROCESS);
|
|
|
|
$process->signal(0);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @expectedException \Amp\Process\StatusError
|
|
|
|
* @expectedExceptionMessage Process has not been started.
|
|
|
|
*/
|
|
|
|
public function testProcessHasBeenStarted() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(self::CMD_PROCESS);
|
2017-12-05 22:53:41 +01:00
|
|
|
yield $process->join();
|
2017-12-05 06:02:41 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-01-11 19:24:02 +01:00
|
|
|
public function testCommand() {
|
2017-12-05 06:02:41 +01:00
|
|
|
$process = new Process([self::CMD_PROCESS]);
|
|
|
|
$this->assertSame(\implode(" ", \array_map("escapeshellarg", [self::CMD_PROCESS])), $process->getCommand());
|
2017-01-11 19:24:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public function testOptions() {
|
|
|
|
$process = new Process(self::CMD_PROCESS);
|
|
|
|
$this->assertSame([], $process->getOptions());
|
|
|
|
}
|
2017-12-06 20:00:08 +01:00
|
|
|
|
|
|
|
public function getProcessCounts(): array {
|
|
|
|
return \array_map(function (int $count): array {
|
|
|
|
return [$count];
|
2017-12-07 02:21:06 +01:00
|
|
|
}, \range(2, 32, 2));
|
2017-12-06 20:00:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dataProvider getProcessCounts
|
|
|
|
*
|
|
|
|
* @param int $count
|
|
|
|
*/
|
|
|
|
public function testSpawnMultipleProcesses(int $count) {
|
|
|
|
Loop::run(function () use ($count) {
|
|
|
|
$processes = [];
|
|
|
|
for ($i = 0; $i < $count; ++$i) {
|
|
|
|
$command = \DIRECTORY_SEPARATOR === "\\" ? "cmd /c exit $i" : "exit $i";
|
|
|
|
$processes[] = new Process(self::CMD_PROCESS_SLOW . " && " . $command);
|
|
|
|
}
|
|
|
|
|
|
|
|
$promises = [];
|
|
|
|
foreach ($processes as $process) {
|
|
|
|
$process->start();
|
|
|
|
$promises[] = $process->join();
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->assertSame(\range(0, $count - 1), yield $promises);
|
|
|
|
});
|
|
|
|
}
|
2017-12-10 22:00:14 +01:00
|
|
|
|
|
|
|
public function testReadOutputAfterExit() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(["php", __DIR__ . "/bin/worker.php"]);
|
|
|
|
$process->start();
|
|
|
|
|
2017-12-10 22:17:04 +01:00
|
|
|
$process->getStdin()->write("exit 2");
|
|
|
|
$this->assertSame("..", yield $process->getStdout()->read());
|
|
|
|
|
|
|
|
$this->assertSame(0, yield $process->join());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public function testReadOutputAfterExitWithLongOutput() {
|
|
|
|
Loop::run(function () {
|
|
|
|
$process = new Process(["php", __DIR__ . "/bin/worker.php"]);
|
|
|
|
$process->start();
|
|
|
|
|
|
|
|
$count = 128 * 1024 + 1;
|
|
|
|
$process->getStdin()->write("exit " . $count);
|
|
|
|
$this->assertSame(str_repeat(".", $count), yield new Message($process->getStdout()));
|
2017-12-10 22:00:14 +01:00
|
|
|
|
|
|
|
$this->assertSame(0, yield $process->join());
|
|
|
|
});
|
|
|
|
}
|
2017-06-15 19:11:38 +02:00
|
|
|
}
|