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..0c088de --- /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 + */ + private 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..455e07b 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,42 @@ 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); + } + + /** + * @return array> + */ + public function provideWaitForTcpPortOpen(): array + { + return [ + 'Can connect to container' => [true], + 'Cannot connect to container' => [false], + ]; + } + public function testWaitForHealthCheck(): void { $container = Container::make('nginx')