Implement WaitForExec using new Client library. Add BaseWait to avoid some code duplication. Adjust Postgres test

This commit is contained in:
Sergei Shitikov
2024-09-01 18:23:21 +02:00
parent 3dd4d2525a
commit ea07a96091
6 changed files with 94 additions and 45 deletions
+15 -5
View File
@@ -8,11 +8,18 @@ use Testcontainers\Wait\WaitForExec;
class PostgresContainer extends GenericContainer class PostgresContainer extends GenericContainer
{ {
public function __construct(string $version = 'latest', string $rootPassword = 'root') public function __construct(
{ string $version = 'latest',
public readonly string $username = 'test',
public readonly string $password = 'test',
public readonly string $database = 'test'
) {
parent::__construct('postgres:' . $version); parent::__construct('postgres:' . $version);
$this->withEnvironment('POSTGRES_PASSWORD', $rootPassword); $this->withExposedPorts(5432);
$this->withWait(new WaitForExec(["pg_isready", "-h", "127.0.0.1"])); $this->withEnvironment('POSTGRES_USER', $this->username);
$this->withEnvironment('POSTGRES_PASSWORD', $this->password);
$this->withEnvironment('POSTGRES_DB', $this->database);
$this->withWait(new WaitForExec(["pg_isready", "-h", "127.0.0.1", "-U", $this->username]));
} }
/** /**
@@ -21,7 +28,10 @@ class PostgresContainer extends GenericContainer
*/ */
public static function make(string $version = 'latest', string $dbPassword = 'root'): self public static function make(string $version = 'latest', string $dbPassword = 'root'): self
{ {
return new self($version, $dbPassword); return new self(
version: $version,
password: $dbPassword
);
} }
public function withPostgresUser(string $username): self public function withPostgresUser(string $username): self
+20
View File
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Testcontainers\Wait;
use Docker\Docker;
use Testcontainers\ContainerRuntime\ContainerRuntimeClient;
abstract class BaseWait implements WaitInterface
{
protected Docker $dockerClient;
public function __construct(protected int $timeout = 10000, protected int $pollInterval = 500)
{
$this->dockerClient = ContainerRuntimeClient::getDockerClient();
}
abstract public function wait(string $id): void;
}
+1 -12
View File
@@ -5,23 +5,14 @@ declare(strict_types=1);
namespace Testcontainers\Wait; namespace Testcontainers\Wait;
use Docker\API\Model\ContainersIdJsonGetResponse200; use Docker\API\Model\ContainersIdJsonGetResponse200;
use Docker\Docker;
use Testcontainers\ContainerRuntime\ContainerRuntimeClient;
use Testcontainers\Exception\ContainerNotReadyException; use Testcontainers\Exception\ContainerNotReadyException;
/** /**
* Simply makes container inspect and checks if container is running. * Simply makes container inspect and checks if container is running.
* Uses $timout and $pollInterval in milliseconds to set the parameters for waiting. * Uses $timout and $pollInterval in milliseconds to set the parameters for waiting.
*/ */
class WaitForContainerRunning implements WaitInterface class WaitForContainerRunning extends BaseWait
{ {
protected Docker $dockerClient;
public function __construct(protected int $timeout = 10000, protected int $pollInterval = 500)
{
$this->dockerClient = ContainerRuntimeClient::getDockerClient();
}
public function wait(string $id): void public function wait(string $id): void
{ {
$startTime = microtime(true) * 1000; $startTime = microtime(true) * 1000;
@@ -41,8 +32,6 @@ class WaitForContainerRunning implements WaitInterface
return; return;
} }
var_dump($containerStatus);
usleep($this->pollInterval * 1000); usleep($this->pollInterval * 1000);
} }
} }
+50 -17
View File
@@ -5,34 +5,67 @@ declare(strict_types=1);
namespace Testcontainers\Wait; namespace Testcontainers\Wait;
use Closure; use Closure;
use Docker\API\Client;
use Docker\API\Model\ContainersIdExecPostBody; use Docker\API\Model\ContainersIdExecPostBody;
use Docker\API\Model\ExecIdStartPostBody; use Testcontainers\Exception\ContainerWaitingTimeoutException;
use Docker\Docker;
class WaitForExec implements WaitInterface /**
* Uses $timout and $pollInterval in milliseconds to set the parameters for waiting.
*/
class WaitForExec extends BaseWait
{ {
protected Docker $dockerClient;
protected ContainersIdExecPostBody $execConfig; protected ContainersIdExecPostBody $execConfig;
/** /**
* @param array<string> $command * @param array<string> $command
*/ */
public function __construct(private array $command, private ?Closure $checkFunction = null) public function __construct(
{ protected array $command,
$this->dockerClient = Docker::create(); protected ?Closure $checkFunction = null,
$execConfig = new ContainersIdExecPostBody(); int $timeout = 10000,
$execConfig->setTty(true); int $pollInterval = 500
$execConfig->setAttachStdout(true); ) {
$execConfig->setAttachStderr(true); parent::__construct($timeout, $pollInterval);
$execConfig->setCmd($this->command);
} }
public function wait(string $id): void public function wait(string $id): void
{ {
$execid = $this->dockerClient->containerExec($id, $this->execConfig)->getId() ?? ''; $this->execConfig = (new ContainersIdExecPostBody())
$execStartConfig = new ExecIdStartPostBody(); ->setCmd($this->command)
$execStartConfig->setDetach(false); ->setAttachStdout(true)
$this->dockerClient->execStart($execid, $execStartConfig); ->setAttachStderr(true);
$startTime = microtime(true) * 1000;
while (true) {
$elapsedTime = (microtime(true) * 1000) - $startTime;
if ($elapsedTime > $this->timeout) {
throw new ContainerWaitingTimeoutException($id);
}
// Create and start the exec command
$exec = $this->dockerClient->containerExec($id, $this->execConfig);
$contents = $this->dockerClient
->execStart($exec->getId(), null, Client::FETCH_RESPONSE)
?->getBody()
->getContents() ?? '';
// Inspect the exec to check the exit code
$execInspect = $this->dockerClient->execInspect($exec->getId());
$exitCode = $execInspect->getExitCode();
// If a custom check function is provided, use it to validate the command output
if ($this->checkFunction !== null) {
$checkResult = ($this->checkFunction)($exitCode, $contents);
if ($checkResult) {
return;
}
} elseif ($exitCode === 0) {
return; // Command succeeded
}
usleep($this->pollInterval * 1000);
}
} }
} }
+4 -7
View File
@@ -5,23 +5,20 @@ declare(strict_types=1);
namespace Testcontainers\Wait; namespace Testcontainers\Wait;
use Docker\API\Runtime\Client\Client; use Docker\API\Runtime\Client\Client;
use Docker\Docker;
use Testcontainers\Exception\ContainerWaitingTimeoutException; use Testcontainers\Exception\ContainerWaitingTimeoutException;
/** /**
* Uses $timout and $pollInterval in milliseconds to set the parameters for waiting. * Uses $timout and $pollInterval in milliseconds to set the parameters for waiting.
*/ */
class WaitForLog implements WaitInterface class WaitForLog extends BaseWait
{ {
protected Docker $dockerClient;
public function __construct( public function __construct(
protected string $message, protected string $message,
protected bool $enableRegex = false, protected bool $enableRegex = false,
protected int $timeout = 10000, int $timeout = 10000,
protected int $pollInterval = 500 int $pollInterval = 500
) { ) {
$this->dockerClient = Docker::create(); parent::__construct($timeout, $pollInterval);
} }
public function wait(string $id): void public function wait(string $id): void
@@ -10,8 +10,8 @@ class PostgreSQLContainerTest extends ContainerTestCase
{ {
public static function setUpBeforeClass(): void public static function setUpBeforeClass(): void
{ {
self::$container = (new PostgresContainer('latest', 'test')) self::$container = (new PostgresContainer())
->withPostgresUser('test') ->withPostgresUser('bar')
->withPostgresDatabase('foo') ->withPostgresDatabase('foo')
->start(); ->start();
} }
@@ -19,8 +19,8 @@ class PostgreSQLContainerTest extends ContainerTestCase
public function testPostgreSQLContainer(): void public function testPostgreSQLContainer(): void
{ {
$pdo = new \PDO( $pdo = new \PDO(
sprintf('pgsql:host=%s;port=5432;dbname=foo', self::$container->getAddress()), 'pgsql:host=127.0.0.1;port=5432;dbname=foo',
'test', 'bar',
'test', 'test',
); );