Repository navigation
Expand file tree
/
Copy pathLiveStreamTest.php
More file actions
123 lines (111 loc) · 4.91 KB
/
Copy pathLiveStreamTest.php
File metadata and controls
123 lines (111 loc) · 4.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
<?php
declare(strict_types=1);
namespace Docker\Tests\Resource;
use Docker\API\Model\ContainersCreatePostBody;
use Docker\API\Model\ContainersIdExecPostBody;
use Docker\API\Model\ExecIdStartPostBody;
use Docker\Stream\DockerRawStream;
use Docker\Stream\StatsStream;
use Docker\Tests\TestCase;
class LiveStreamTest extends TestCase
{
public static function ttyProvider(): iterable
{
yield 'multiplexed stdout and stderr' => [false];
yield 'TTY output' => [true];
}
private function createContainer(bool $tty, array $command): string
{
$config = new ContainersCreatePostBody();
$config->setImage('busybox:latest');
$config->setCmd($command);
$config->setTty($tty);
$config->setExposedPorts(['80/tcp' => new \stdClass()]);
$config->setLabels(['docker-php-test' => 'true']);
$id = self::getDocker()->containerCreate($config)->getId();
self::getDocker()->containerStart($id);
return $id;
}
private function assertLiveOutput(DockerRawStream $stream, bool $tty): void
{
$stdout = '';
$stderr = '';
$first = null;
$last = null;
$stream->onStdout(static function (string $chunk) use (&$stdout, &$first, &$last): void {
$first ??= hrtime(true);
$last = hrtime(true);
$stdout .= $chunk;
});
$stream->onStderr(static function (string $chunk) use (&$stderr, &$first, &$last): void {
$first ??= hrtime(true);
$last = hrtime(true);
$stderr .= $chunk;
});
$stream->wait();
self::assertSame($tty ? "first\r\nerror\r\nlast\r\nfinal\r\n" : "first\nlast\n", $stdout);
self::assertSame($tty ? '' : "error\nfinal\n", $stderr);
self::assertNotNull($first);
self::assertGreaterThan(500_000_000, $last - $first, 'Callbacks must receive output before the command finishes, not a buffered response.');
}
/** @dataProvider ttyProvider */
public function testFollowLogsDeliversOutputWhileContainerRuns(bool $tty): void
{
$id = $this->createContainer($tty, ['sh', '-c', 'printf "first\n"; printf "error\n" >&2; sleep 2; printf "last\n"; printf "final\n" >&2']);
try {
$stream = self::getDocker()->containerLogs($id, ['stdout' => true, 'stderr' => true, 'follow' => true]);
self::assertInstanceOf(DockerRawStream::class, $stream);
$this->assertLiveOutput($stream, $tty);
self::assertNull(self::getDocker()->containerInspect($id)->getNetworkSettings()->getPorts()['80/tcp']);
} finally {
self::getDocker()->containerDelete($id, ['force' => true]);
}
}
/** @dataProvider ttyProvider */
public function testExecDeliversOutputBeforeExit(bool $tty): void
{
$id = $this->createContainer(false, ['sleep', '30']);
try {
$config = new ContainersIdExecPostBody();
$config->setAttachStdout(true);
$config->setAttachStderr(true);
$config->setTty($tty);
$config->setCmd(['sh', '-c', 'printf "first\n"; printf "error\n" >&2; sleep 2; printf "last\n"; printf "final\n" >&2']);
$exec = self::getDocker()->containerExec($id, $config);
$start = new ExecIdStartPostBody();
$start->setDetach(false);
$start->setTty($tty);
$stream = self::getDocker()->execStart($exec->getId(), $start);
self::assertInstanceOf(DockerRawStream::class, $stream);
$this->assertLiveOutput($stream, $tty);
$result = self::getDocker()->execInspect($exec->getId());
self::assertFalse($result->getRunning());
self::assertSame(0, $result->getExitCode());
} finally {
self::getDocker()->containerDelete($id, ['force' => true]);
}
}
public function testContainerStatsStreamsLiveSamples(): void
{
$id = $this->createContainer(false, ['sleep', '30']);
try {
$stats = self::getDocker()->containerStats($id);
$this->assertInstanceOf(StatsStream::class, $stats);
$reads = [];
$stats->onFrame(static function (object $sample) use ($stats, &$reads): void {
// ContainerStatsResponse from API 1.48; a decoded stdClass before that.
$reads[] = $sample instanceof \stdClass ? $sample->read : $sample->getRead()?->format(\DATE_RFC3339_EXTENDED);
if (2 === \count($reads)) {
$stats->stop();
}
});
$started = microtime(true);
$stats->wait();
$this->assertCount(2, $reads);
$this->assertNotSame($reads[0], $reads[1]);
$this->assertLessThan(15, microtime(true) - $started, 'stop() should end wait() before the container exits');
} finally {
self::getDocker()->containerDelete($id, ['force' => true]);
}
}
}