Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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());
Expand Down
8 changes: 4 additions & 4 deletions src/Container/StartedGenericContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public function getHost(): string

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();
Expand All @@ -121,7 +121,7 @@ public function getMappedPort(int $port): int

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;
Expand Down Expand Up @@ -193,10 +193,10 @@ protected function inspect(): ContainersIdJsonGetResponse200 | null
}

/**
* @return array<string, array<PortBinding>>
* @return iterable<string, array<PortBinding>>
* @throws RuntimeException
*/
protected function ports(): iterable
public function getBoundPorts(): iterable
{
$ports = $this->inspect()?->getNetworkSettings()?->getPorts();

Expand Down
6 changes: 6 additions & 0 deletions src/Container/StartedTestContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Testcontainers\Container;

use Docker\API\Model\PortBinding;
use Docker\Docker;

interface StartedTestContainer
Expand All @@ -13,6 +14,11 @@ interface StartedTestContainer
*/
public function exec(array $command): string;

/**
* @return iterable<string, array<PortBinding>>
*/
public function getBoundPorts(): iterable;

public function getClient(): Docker;

public function getFirstMappedPort(): int;
Expand Down
2 changes: 1 addition & 1 deletion src/Modules/MariaDBContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function __construct(string $version = 'latest', string $mysqlRootPasswor
"mariadb-admin",
"ping",
"-h", "127.0.0.1",
]));
], null, 15000));
}

public function withMariaDBUser(string $username, string $password): self
Expand Down
2 changes: 1 addition & 1 deletion src/Modules/MySQLContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function __construct(string $version = 'latest', string $mysqlRootPasswor
"mysqladmin",
"ping",
"-h", "127.0.0.1",
]));
], null, 15000));
}

public function withMySQLUser(string $username, string $password): self
Expand Down
33 changes: 23 additions & 10 deletions src/Wait/WaitForHostPort.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,9 @@

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;
Expand All @@ -29,14 +20,36 @@ public function wait(StartedTestContainer $container): void
throw new ContainerWaitingTimeoutException($container->getId());
}

if ($this->isPortOpen($containerAddress, $this->port)) {
if ($this->boundPortsOpened($container)) {
return; // Port is open, container is ready
}

usleep($this->pollInterval * 1000); // Wait for the next polling interval
}
}

/**
* @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);
Expand Down
10 changes: 0 additions & 10 deletions src/Wait/WaitForHttp.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,6 @@ public function __construct(
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<HttpMethod> $method
*/
Expand Down
6 changes: 2 additions & 4 deletions tests/Integration/GenericContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
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
Expand Down Expand Up @@ -115,13 +114,12 @@ public function testShouldCopyFileWithPermissions(): void
public function testShouldReturnFirstMappedPort(): void
{
$container = (new GenericContainer('nginx'))
->withPortGenerator(new FixedPortGenerator([9950]))
->withExposedPorts(80)
->withWait(new WaitForHostPort(9950))
->withWait(new WaitForHostPort())
->start();
$firstMappedPort = $container->getFirstMappedPort();

self::assertSame($firstMappedPort, 9950, 'First mapped port does not match 9950');
self::assertSame($firstMappedPort, $container->getMappedPort(80));

$container->stop();
}
Expand Down