From f690a1f3c640e82025d3b4c60eac4ef94466844a Mon Sep 17 00:00:00 2001 From: Nikola Petkanski Date: Tue, 16 Jul 2024 01:19:02 +0300 Subject: [PATCH] Support for waiting on tcp port to open --- src/Container/Container.php | 5 +++ src/Wait/WaitForTcpPortOpen.php | 52 ++++++++++++++++++++++++++ tests/Integration/WaitStrategyTest.php | 35 +++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 src/Wait/WaitForTcpPortOpen.php diff --git a/src/Container/Container.php b/src/Container/Container.php index 1317383..46776c1 100644 --- a/src/Container/Container.php +++ b/src/Container/Container.php @@ -56,6 +56,11 @@ public static function make(string $image): self return new Container($image); } + public function getId(): string + { + return $this->id; + } + public function withEntryPoint(string $entryPoint): self { $this->entryPoint = $entryPoint; diff --git a/src/Wait/WaitForTcpPortOpen.php b/src/Wait/WaitForTcpPortOpen.php new file mode 100644 index 0000000..7a1cb62 --- /dev/null +++ b/src/Wait/WaitForTcpPortOpen.php @@ -0,0 +1,52 @@ +findContainerAddress($id), $this->port) === false) { + throw new ContainerNotReadyException($id, new RuntimeException('Unable to connect to container TCP port')); + } + } + + /** + * @throws JsonException + */ + protected function findContainerAddress(string $id): string + { + $process = new Process(['docker', 'inspect', $id]); + $process->mustRun(); + + /** @var array $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; + } +} diff --git a/tests/Integration/WaitStrategyTest.php b/tests/Integration/WaitStrategyTest.php index 2424196..db5081c 100644 --- a/tests/Integration/WaitStrategyTest.php +++ b/tests/Integration/WaitStrategyTest.php @@ -9,10 +9,12 @@ use Predis\Connection\ConnectionException; use Symfony\Component\Process\Process; use Testcontainer\Container\Container; +use Testcontainer\Exception\ContainerNotReadyException; use Testcontainer\Wait\WaitForExec; use Testcontainer\Wait\WaitForHealthCheck; use Testcontainer\Wait\WaitForHttp; use Testcontainer\Wait\WaitForLog; +use Testcontainer\Wait\WaitForTcpPortOpen; class WaitStrategyTest extends TestCase { @@ -90,6 +92,39 @@ public function testWaitForHTTP(): void $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); + } + + public function provideWaitForTcpPortOpen(): array + { + return [ + 'Can connect to container' => [true], + 'Cannot connect to container' => [false], + ]; + } + public function testWaitForHealthCheck(): void { $container = Container::make('nginx')