From 1fb7efd7bd5921e2d541b54efec5edc3d8c7419c Mon Sep 17 00:00:00 2001 From: Sergei Shitikov Date: Sun, 16 Feb 2025 13:42:33 +0100 Subject: [PATCH 1/5] increase default timeout for mysql and mariadb, change image for testShouldReturnFirstMappedPort() --- src/Modules/MariaDBContainer.php | 2 +- src/Modules/MySQLContainer.php | 2 +- tests/Integration/GenericContainerTest.php | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Modules/MariaDBContainer.php b/src/Modules/MariaDBContainer.php index c6603d4..b8c868e 100644 --- a/src/Modules/MariaDBContainer.php +++ b/src/Modules/MariaDBContainer.php @@ -18,7 +18,7 @@ class MariaDBContainer extends GenericContainer "mariadb-admin", "ping", "-h", "127.0.0.1", - ])); + ], null, 15000)); } public function withMariaDBUser(string $username, string $password): self diff --git a/src/Modules/MySQLContainer.php b/src/Modules/MySQLContainer.php index 283c999..580dfd8 100644 --- a/src/Modules/MySQLContainer.php +++ b/src/Modules/MySQLContainer.php @@ -18,7 +18,7 @@ class MySQLContainer extends GenericContainer "mysqladmin", "ping", "-h", "127.0.0.1", - ])); + ], null, 15000)); } public function withMySQLUser(string $username, string $password): self diff --git a/tests/Integration/GenericContainerTest.php b/tests/Integration/GenericContainerTest.php index 7e00fc8..88977cf 100644 --- a/tests/Integration/GenericContainerTest.php +++ b/tests/Integration/GenericContainerTest.php @@ -114,14 +114,14 @@ class GenericContainerTest extends TestCase public function testShouldReturnFirstMappedPort(): void { - $container = (new GenericContainer('nginx')) - ->withPortGenerator(new FixedPortGenerator([9950])) - ->withExposedPorts(80) - ->withWait(new WaitForHostPort(9950)) + $container = (new GenericContainer('cristianrgreco/testcontainer:1.1.14')) + ->withPortGenerator(new FixedPortGenerator([8080])) + ->withExposedPorts(8080) + ->withWait(new WaitForHostPort(8080)) ->start(); $firstMappedPort = $container->getFirstMappedPort(); - self::assertSame($firstMappedPort, 9950, 'First mapped port does not match 9950'); + self::assertSame($firstMappedPort, 8080, 'First mapped port does not match 8080'); $container->stop(); } From 1f7178e3135daa1678bef856aa6e11aca3fb9996 Mon Sep 17 00:00:00 2001 From: Sergei Shitikov Date: Sun, 16 Feb 2025 15:45:28 +0100 Subject: [PATCH 2/5] try to fix testShouldReturnFirstMappedPort in ci --- tests/Integration/GenericContainerTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/Integration/GenericContainerTest.php b/tests/Integration/GenericContainerTest.php index 88977cf..79ca358 100644 --- a/tests/Integration/GenericContainerTest.php +++ b/tests/Integration/GenericContainerTest.php @@ -114,14 +114,14 @@ class GenericContainerTest extends TestCase public function testShouldReturnFirstMappedPort(): void { - $container = (new GenericContainer('cristianrgreco/testcontainer:1.1.14')) - ->withPortGenerator(new FixedPortGenerator([8080])) - ->withExposedPorts(8080) - ->withWait(new WaitForHostPort(8080)) + $container = (new GenericContainer('nginx')) + ->withPortGenerator(new FixedPortGenerator([9090])) + ->withExposedPorts(80) + ->withWait(new WaitForHostPort(9090)) ->start(); $firstMappedPort = $container->getFirstMappedPort(); - self::assertSame($firstMappedPort, 8080, 'First mapped port does not match 8080'); + self::assertSame($firstMappedPort, 9090, 'First mapped port does not match 9090'); $container->stop(); } From 5e86ed1443f6084678d1bfb1b0b74054aa9a611a Mon Sep 17 00:00:00 2001 From: Sergei Shitikov Date: Sun, 16 Feb 2025 18:02:16 +0100 Subject: [PATCH 3/5] adjust wait for host port strategy --- src/Container/StartedGenericContainer.php | 8 ++--- src/Container/StartedTestContainer.php | 6 ++++ src/Wait/WaitForHostPort.php | 33 +++++++++++++------ tests/Integration/GenericContainerTest.php | 6 ++-- .../StartedGenericContainerTest.php | 1 + 5 files changed, 36 insertions(+), 18 deletions(-) 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 { From cb4dc2aff0b8aa668b997364981a248e13c0cde2 Mon Sep 17 00:00:00 2001 From: Sergei Shitikov Date: Sun, 16 Feb 2025 18:06:21 +0100 Subject: [PATCH 4/5] cs fix, remove unused import --- tests/Integration/StartedGenericContainerTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Integration/StartedGenericContainerTest.php b/tests/Integration/StartedGenericContainerTest.php index 6ec5a5f..a59976c 100644 --- a/tests/Integration/StartedGenericContainerTest.php +++ b/tests/Integration/StartedGenericContainerTest.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Testcontainers\Tests\Integration; use Testcontainers\Container\GenericContainer; -use Testcontainers\Wait\WaitForHostPort; class StartedGenericContainerTest extends ContainerTestCase { From 26e9a672fdc8b5bd1a0fb71ae4b5e5ab695f255b Mon Sep 17 00:00:00 2001 From: Sergei Shitikov Date: Sun, 16 Feb 2025 18:17:09 +0100 Subject: [PATCH 5/5] remove forgotten legacy part, adjust doc --- README.md | 6 +++++- src/Wait/WaitForHttp.php | 10 ---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index c5fad87..788453f 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,7 @@ use Testcontainers\Wait\WaitForExec; use Testcontainers\Wait\WaitForLog; use Testcontainers\Wait\WaitForHttp; use Testcontainers\Wait\WaitForHealthCheck; +use Testcontainers\Wait\WaitForHostPort; $container = new GenericContainer('nginx:alpine'); @@ -58,7 +59,10 @@ $container->withWait(new WaitForLog('Ready to accept connections')); // Wait for an http request to succeed -$container->withWait(WaitForHttp::make($port, $method = 'GET', $path = '/')); +$container->withWait(new WaitForHttp($port, $method = 'GET', $path = '/')); + +// Wait for all bound ports to be open +$container->withWait(new WaitForHostPort()); // Wait until the docker heartcheck is green $container->withWait(new WaitForHealthCheck()); diff --git a/src/Wait/WaitForHttp.php b/src/Wait/WaitForHttp.php index 3ae06c2..b2dceeb 100644 --- a/src/Wait/WaitForHttp.php +++ b/src/Wait/WaitForHttp.php @@ -38,16 +38,6 @@ class WaitForHttp extends BaseWaitStrategy parent::__construct($timeout, $pollInterval); } - /** - * @deprecated Use constructor instead - * Kept for backward compatibility - * Should be removed in next major version - */ - public static function make(int $port): self - { - return new self($port); - } - /** * @param HttpMethod|value-of $method */