Support for waiting on tcp port to open

This commit is contained in:
Nikola Petkanski
2024-07-16 02:34:24 +03:00
parent 3ddc75e717
commit 6c3c74c817
3 changed files with 98 additions and 0 deletions
+5
View File
@@ -56,6 +56,11 @@ class Container
return new Container($image); return new Container($image);
} }
public function getId(): string
{
return $this->id;
}
public function withEntryPoint(string $entryPoint): self public function withEntryPoint(string $entryPoint): self
{ {
$this->entryPoint = $entryPoint; $this->entryPoint = $entryPoint;
+55
View File
@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Testcontainer\Wait;
use JsonException;
use RuntimeException;
use Symfony\Component\Process\Process;
use Testcontainer\Exception\ContainerNotReadyException;
/**
* @phpstan-type ContainerInspect array<int, array{'NetworkSettings': array{'IPAddress': string}}>
*/
final class WaitForTcpPortOpen implements WaitInterface
{
public function __construct(private readonly int $port)
{
}
public static function make(int $port): self
{
return new self($port);
}
/**
* @throws JsonException
*/
public function wait(string $id): void
{
if (@fsockopen($this->findContainerAddress($id), $this->port) === false) {
throw new ContainerNotReadyException($id, new RuntimeException('Unable to connect to container TCP port'));
}
}
/**
* @throws JsonException
*/
private function findContainerAddress(string $id): string
{
$process = new Process(['docker', 'inspect', $id]);
$process->mustRun();
/** @var ContainerInspect $data */
$data = json_decode($process->getOutput(), true, 512, JSON_THROW_ON_ERROR);
$containerAddress = $data[0]['NetworkSettings']['IPAddress'] ?? null;
if (! is_string($containerAddress)) {
throw new ContainerNotReadyException($id, new RuntimeException('Unable to find container IP address'));
}
return $containerAddress;
}
}
+38
View File
@@ -9,10 +9,12 @@ use Predis\Client;
use Predis\Connection\ConnectionException; use Predis\Connection\ConnectionException;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
use Testcontainer\Container\Container; use Testcontainer\Container\Container;
use Testcontainer\Exception\ContainerNotReadyException;
use Testcontainer\Wait\WaitForExec; use Testcontainer\Wait\WaitForExec;
use Testcontainer\Wait\WaitForHealthCheck; use Testcontainer\Wait\WaitForHealthCheck;
use Testcontainer\Wait\WaitForHttp; use Testcontainer\Wait\WaitForHttp;
use Testcontainer\Wait\WaitForLog; use Testcontainer\Wait\WaitForLog;
use Testcontainer\Wait\WaitForTcpPortOpen;
class WaitStrategyTest extends TestCase class WaitStrategyTest extends TestCase
{ {
@@ -90,6 +92,42 @@ class WaitStrategyTest extends TestCase
$this->assertNotEmpty($response); $this->assertNotEmpty($response);
} }
/**
* @dataProvider provideWaitForTcpPortOpen
*/
public function testWaitForTcpPortOpen(bool $canConnect): void
{
$container = Container::make('nginx:alpine');
if ($canConnect) {
$container->withWait(WaitForTcpPortOpen::make(80));
}
$container->run();
if ($canConnect) {
static::assertIsResource(fsockopen($container->getAddress(), 80), 'Failed to connect to container');
return;
}
$containerId = $container->getId();
$this->expectExceptionObject(new ContainerNotReadyException($containerId));
(new WaitForTcpPortOpen(8080))->wait($containerId);
}
/**
* @return array<string, array<bool>>
*/
public function provideWaitForTcpPortOpen(): array
{
return [
'Can connect to container' => [true],
'Cannot connect to container' => [false],
];
}
public function testWaitForHealthCheck(): void public function testWaitForHealthCheck(): void
{ {
$container = Container::make('nginx') $container = Container::make('nginx')