Merge pull request #5 from rw4lll/feat/wait-host-port-adjust

adjust wait for host port strategy
This commit is contained in:
Sergei Shitikov
2025-02-16 18:09:40 +01:00
committed by GitHub
4 changed files with 35 additions and 18 deletions
+4 -4
View File
@@ -108,7 +108,7 @@ class StartedGenericContainer implements StartedTestContainer
public function getMappedPort(int $port): int public function getMappedPort(int $port): int
{ {
$ports = (array) $this->ports(); $ports = (array) $this->getBoundPorts();
/** @var PortBinding | null $portBinding */ /** @var PortBinding | null $portBinding */
$portBinding = $ports["{$port}/tcp"][0] ?? null; $portBinding = $ports["{$port}/tcp"][0] ?? null;
$mappedPort = $portBinding?->getHostPort(); $mappedPort = $portBinding?->getHostPort();
@@ -121,7 +121,7 @@ class StartedGenericContainer implements StartedTestContainer
public function getFirstMappedPort(): int public function getFirstMappedPort(): int
{ {
$ports = (array) $this->ports(); $ports = (array) $this->getBoundPorts();
$port = array_key_first($ports); $port = array_key_first($ports);
/** @var PortBinding | null $firstPortBinding */ /** @var PortBinding | null $firstPortBinding */
$firstPortBinding = $ports[$port][0] ?? null; $firstPortBinding = $ports[$port][0] ?? null;
@@ -193,10 +193,10 @@ class StartedGenericContainer implements StartedTestContainer
} }
/** /**
* @return array<string, array<PortBinding>> * @return iterable<string, array<PortBinding>>
* @throws RuntimeException * @throws RuntimeException
*/ */
protected function ports(): iterable public function getBoundPorts(): iterable
{ {
$ports = $this->inspect()?->getNetworkSettings()?->getPorts(); $ports = $this->inspect()?->getNetworkSettings()?->getPorts();
+6
View File
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Testcontainers\Container; namespace Testcontainers\Container;
use Docker\API\Model\PortBinding;
use Docker\Docker; use Docker\Docker;
interface StartedTestContainer interface StartedTestContainer
@@ -13,6 +14,11 @@ interface StartedTestContainer
*/ */
public function exec(array $command): string; public function exec(array $command): string;
/**
* @return iterable<string, array<PortBinding>>
*/
public function getBoundPorts(): iterable;
public function getClient(): Docker; public function getClient(): Docker;
public function getFirstMappedPort(): int; public function getFirstMappedPort(): int;
+23 -10
View File
@@ -9,18 +9,9 @@ use Testcontainers\Exception\ContainerWaitingTimeoutException;
class WaitForHostPort extends BaseWaitStrategy 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 public function wait(StartedTestContainer $container): void
{ {
$startTime = microtime(true) * 1000; $startTime = microtime(true) * 1000;
$containerAddress = $container->getHost();
while (true) { while (true) {
$elapsedTime = (microtime(true) * 1000) - $startTime; $elapsedTime = (microtime(true) * 1000) - $startTime;
@@ -29,7 +20,7 @@ class WaitForHostPort extends BaseWaitStrategy
throw new ContainerWaitingTimeoutException($container->getId()); throw new ContainerWaitingTimeoutException($container->getId());
} }
if ($this->isPortOpen($containerAddress, $this->port)) { if ($this->boundPortsOpened($container)) {
return; // Port is open, container is ready 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 private function isPortOpen(string $ipAddress, int $port): bool
{ {
$connection = @fsockopen($ipAddress, $port, $errno, $errstr, 2); $connection = @fsockopen($ipAddress, $port, $errno, $errstr, 2);
+2 -4
View File
@@ -7,7 +7,6 @@ namespace Testcontainers\Tests\Integration;
use Docker\API\Model\ContainersIdJsonGetResponse200; use Docker\API\Model\ContainersIdJsonGetResponse200;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
use Testcontainers\Container\GenericContainer; use Testcontainers\Container\GenericContainer;
use Testcontainers\Utils\PortGenerator\FixedPortGenerator;
use Testcontainers\Wait\WaitForHostPort; use Testcontainers\Wait\WaitForHostPort;
class GenericContainerTest extends TestCase class GenericContainerTest extends TestCase
@@ -115,13 +114,12 @@ class GenericContainerTest extends TestCase
public function testShouldReturnFirstMappedPort(): void public function testShouldReturnFirstMappedPort(): void
{ {
$container = (new GenericContainer('nginx')) $container = (new GenericContainer('nginx'))
->withPortGenerator(new FixedPortGenerator([9090]))
->withExposedPorts(80) ->withExposedPorts(80)
->withWait(new WaitForHostPort(9090)) ->withWait(new WaitForHostPort())
->start(); ->start();
$firstMappedPort = $container->getFirstMappedPort(); $firstMappedPort = $container->getFirstMappedPort();
self::assertSame($firstMappedPort, 9090, 'First mapped port does not match 9090'); self::assertSame($firstMappedPort, $container->getMappedPort(80));
$container->stop(); $container->stop();
} }