189 lines
5.3 KiB
PHP
189 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace Nih\CommandBuilder;
|
|
|
|
use InvalidArgumentException;
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\Attributes\CoversTrait;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
#[CoversClass(Child::class)]
|
|
#[CoversClass(ChildException::class)]
|
|
#[CoversClass(ChildStdout::class)]
|
|
#[CoversClass(Command::class)]
|
|
#[CoversClass(ExitStatus::class)]
|
|
#[CoversClass(Output::class)]
|
|
#[CoversClass(Stdio::class)]
|
|
#[CoversTrait(StreamReadTrait::class)]
|
|
#[CoversTrait(StreamWriteTrait::class)]
|
|
final class CommandIntegrationTest extends TestCase
|
|
{
|
|
public function testStatus(): void
|
|
{
|
|
$this->assertTrue(new Command('true')->status()->success());
|
|
}
|
|
|
|
/**
|
|
* Test that the stdin, stdout, and stderr handles are captured.
|
|
*/
|
|
public function testOutput(): void
|
|
{
|
|
$output = new Command('true')
|
|
->stdin(Stdio::null())
|
|
->stdout(Stdio::null())
|
|
->stderr(Stdio::null())
|
|
->output();
|
|
|
|
$this->assertTrue($output->status->success());
|
|
}
|
|
|
|
public function testChildOnlyAcceptsProcesses(): void
|
|
{
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$handle = fopen('php://memory', 'w');
|
|
$this->assertNotFalse($handle);
|
|
new Child($handle, []);
|
|
}
|
|
|
|
/**
|
|
* Test that the stdin, stdout, and stderr handles are captured.
|
|
*/
|
|
public function testChild(): void
|
|
{
|
|
$child = new Command('/usr/bin/sleep')
|
|
->stdin(Stdio::null())
|
|
->stdout(Stdio::null())
|
|
->stderr(Stdio::null())
|
|
->arg('1000')
|
|
->spawn();
|
|
|
|
$this->assertGreaterThan(1, $child->id());
|
|
|
|
$child->kill();
|
|
|
|
$status = $child->wait();
|
|
|
|
$this->assertEquals(null, $status->code());
|
|
$this->assertEquals(9, $status->signal());
|
|
$this->assertEquals(null, $status->stoppedSignal());
|
|
|
|
// Killing and waiting after the child exited does nothing.
|
|
$child->kill();
|
|
$this->assertSame($child->wait(), $status);
|
|
$output = $child->waitWithOutput();
|
|
$this->assertSame($output->status, $status);
|
|
$this->assertSame($child->waitWithOutput(), $output);
|
|
}
|
|
|
|
public function testChildDestructor(): void
|
|
{
|
|
$process = proc_open(
|
|
['true'],
|
|
[
|
|
0 => ['pipe', 'w'],
|
|
1 => ['pipe', 'r'],
|
|
2 => ['pipe', 'r'],
|
|
],
|
|
$pipes,
|
|
);
|
|
|
|
$this->assertNotFalse($process);
|
|
|
|
$child = new Child($process, $pipes);
|
|
|
|
$this->assertEquals('process', get_resource_type($process));
|
|
$this->assertEquals('stream', get_resource_type($pipes[0]));
|
|
$this->assertEquals('stream', get_resource_type($pipes[1]));
|
|
$this->assertEquals('stream', get_resource_type($pipes[2]));
|
|
|
|
unset($child);
|
|
gc_collect_cycles();
|
|
|
|
$this->assertEquals('Unknown', get_resource_type($process));
|
|
$this->assertEquals('Unknown', get_resource_type($pipes[0]));
|
|
$this->assertEquals('Unknown', get_resource_type($pipes[1]));
|
|
$this->assertEquals('Unknown', get_resource_type($pipes[2]));
|
|
}
|
|
|
|
public function testStdio(): void
|
|
{
|
|
if (PHP_OS_FAMILY === 'Windows') {
|
|
$this->markTestSkipped();
|
|
}
|
|
|
|
$cat = new Command('cat')
|
|
->stdin(Stdio::piped())
|
|
->stdout(Stdio::piped())
|
|
->stderr(Stdio::piped())
|
|
->spawn();
|
|
|
|
$cat->stdin?->write('Hello, World!');
|
|
$cat->stdin?->close();
|
|
|
|
$this->assertNotNull($cat->stdout);
|
|
|
|
$output = new Command('cat')
|
|
->stdin($cat->stdout)
|
|
->output();
|
|
|
|
$this->assertTrue($cat->wait()->success());
|
|
$this->assertTrue($output->status->success());
|
|
|
|
$this->assertEquals('Hello, World!', $output->stdout);
|
|
$this->assertEquals('', $output->stderr);
|
|
}
|
|
|
|
public function testThrowsOnNotExecutable(): void
|
|
{
|
|
$this->expectException(ChildException::class);
|
|
new Command(uniqid())->spawn();
|
|
}
|
|
|
|
public function testEnv(): void
|
|
{
|
|
if (PHP_OS_FAMILY === 'Windows') {
|
|
$this->markTestSkipped();
|
|
}
|
|
|
|
$output = new Command('env')
|
|
->envClear()
|
|
->envs([
|
|
'FOO' => 'foo',
|
|
'BAR' => 'bar',
|
|
'BAZ' => 'baz',
|
|
])
|
|
->envRemove('FOO')
|
|
->output();
|
|
|
|
$this->assertTrue($output->status->success());
|
|
$this->assertEquals("BAR=bar\nBAZ=baz\n", $output->stdout);
|
|
}
|
|
|
|
public function testEnvInherit(): void
|
|
{
|
|
if (PHP_OS_FAMILY === 'Windows') {
|
|
$this->markTestSkipped();
|
|
}
|
|
|
|
$home = getenv('HOME');
|
|
$this->assertIsString($home, 'Congratulations! You have a weird system');
|
|
|
|
$child = new Command('env')
|
|
->stdout(Stdio::piped())
|
|
->env('FOO', 'foo')
|
|
->spawn();
|
|
|
|
$this->assertNotNull($child->stdout);
|
|
|
|
$envs = [];
|
|
foreach (explode(PHP_EOL, $child->stdout->getContents()) as $line) {
|
|
[$var, $val] = explode('=', $line);
|
|
$envs[$var] = $val;
|
|
}
|
|
|
|
$this->assertEquals($home, $envs['HOME']);
|
|
$this->assertEquals('foo', $envs['FOO']);
|
|
}
|
|
}
|