diff --git a/src/Container/StartedGenericContainer.php b/src/Container/StartedGenericContainer.php index 259abd6..d3a8194 100644 --- a/src/Container/StartedGenericContainer.php +++ b/src/Container/StartedGenericContainer.php @@ -108,7 +108,7 @@ class StartedGenericContainer implements StartedTestContainer public function getMappedPort(int $port): int { - $ports = (array) $this->ports(); + $ports = (array) $this->getBoundPorts(); /** @var PortBinding | null $portBinding */ $portBinding = $ports["{$port}/tcp"][0] ?? null; $mappedPort = $portBinding?->getHostPort(); @@ -121,7 +121,7 @@ class StartedGenericContainer implements StartedTestContainer public function getFirstMappedPort(): int { - $ports = (array) $this->ports(); + $ports = (array) $this->getBoundPorts(); $port = array_key_first($ports); /** @var PortBinding | null $firstPortBinding */ $firstPortBinding = $ports[$port][0] ?? null; @@ -193,10 +193,10 @@ class StartedGenericContainer implements StartedTestContainer } /** - * @return array> + * @return iterable> * @throws RuntimeException */ - protected function ports(): iterable + public function getBoundPorts(): iterable { $ports = $this->inspect()?->getNetworkSettings()?->getPorts(); diff --git a/src/Container/StartedTestContainer.php b/src/Container/StartedTestContainer.php index 8d1acd3..551d9be 100644 --- a/src/Container/StartedTestContainer.php +++ b/src/Container/StartedTestContainer.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Testcontainers\Container; +use Docker\API\Model\PortBinding; use Docker\Docker; interface StartedTestContainer @@ -13,6 +14,11 @@ interface StartedTestContainer */ public function exec(array $command): string; + /** + * @return iterable> + */ + public function getBoundPorts(): iterable; + public function getClient(): Docker; public function getFirstMappedPort(): int; diff --git a/src/Wait/WaitForHostPort.php b/src/Wait/WaitForHostPort.php index d9f7f86..9a97388 100644 --- a/src/Wait/WaitForHostPort.php +++ b/src/Wait/WaitForHostPort.php @@ -9,18 +9,9 @@ use Testcontainers\Exception\ContainerWaitingTimeoutException; class WaitForHostPort extends BaseWaitStrategy { - public function __construct( - protected int $port, - int $timeout = 10000, - int $pollInterval = 500 - ) { - parent::__construct($timeout, $pollInterval); - } - public function wait(StartedTestContainer $container): void { $startTime = microtime(true) * 1000; - $containerAddress = $container->getHost(); while (true) { $elapsedTime = (microtime(true) * 1000) - $startTime; @@ -29,7 +20,7 @@ class WaitForHostPort extends BaseWaitStrategy throw new ContainerWaitingTimeoutException($container->getId()); } - if ($this->isPortOpen($containerAddress, $this->port)) { + if ($this->boundPortsOpened($container)) { return; // Port is open, container is ready } @@ -37,6 +28,28 @@ class WaitForHostPort extends BaseWaitStrategy } } + /** + * @param StartedTestContainer $container + * @return bool + */ + private function boundPortsOpened(StartedTestContainer $container): bool + { + $boundPorts = $container->getBoundPorts(); + foreach ($boundPorts as $bindings) { + foreach ($bindings as $binding) { + $hostIp = trim($binding->getHostIp() ?? ''); + if ($hostIp === '' || $hostIp === '0.0.0.0') { + $hostIp = $container->getHost(); + } + $hostPort = (int)$binding->getHostPort(); + if (!$this->isPortOpen($hostIp, $hostPort)) { + return false; + } + } + } + return true; + } + private function isPortOpen(string $ipAddress, int $port): bool { $connection = @fsockopen($ipAddress, $port, $errno, $errstr, 2); diff --git a/tests/Integration/GenericContainerTest.php b/tests/Integration/GenericContainerTest.php index 79ca358..7ed65bb 100644 --- a/tests/Integration/GenericContainerTest.php +++ b/tests/Integration/GenericContainerTest.php @@ -7,7 +7,6 @@ namespace Testcontainers\Tests\Integration; use Docker\API\Model\ContainersIdJsonGetResponse200; use PHPUnit\Framework\TestCase; use Testcontainers\Container\GenericContainer; -use Testcontainers\Utils\PortGenerator\FixedPortGenerator; use Testcontainers\Wait\WaitForHostPort; class GenericContainerTest extends TestCase @@ -115,13 +114,12 @@ class GenericContainerTest extends TestCase public function testShouldReturnFirstMappedPort(): void { $container = (new GenericContainer('nginx')) - ->withPortGenerator(new FixedPortGenerator([9090])) ->withExposedPorts(80) - ->withWait(new WaitForHostPort(9090)) + ->withWait(new WaitForHostPort()) ->start(); $firstMappedPort = $container->getFirstMappedPort(); - self::assertSame($firstMappedPort, 9090, 'First mapped port does not match 9090'); + self::assertSame($firstMappedPort, $container->getMappedPort(80)); $container->stop(); } diff --git a/tests/Integration/StartedGenericContainerTest.php b/tests/Integration/StartedGenericContainerTest.php index a59976c..6ec5a5f 100644 --- a/tests/Integration/StartedGenericContainerTest.php +++ b/tests/Integration/StartedGenericContainerTest.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Testcontainers\Tests\Integration; use Testcontainers\Container\GenericContainer; +use Testcontainers\Wait\WaitForHostPort; class StartedGenericContainerTest extends ContainerTestCase {