From ea07a960917398342456d740cf8a55a80505b9cb Mon Sep 17 00:00:00 2001 From: Sergei Shitikov Date: Sun, 1 Sep 2024 18:23:21 +0200 Subject: [PATCH] Implement WaitForExec using new Client library. Add BaseWait to avoid some code duplication. Adjust Postgres test --- src/Container/PostgresContainer.php | 20 ++++-- src/Wait/BaseWait.php | 20 ++++++ src/Wait/WaitForContainerRunning.php | 13 +--- src/Wait/WaitForExec.php | 67 ++++++++++++++----- src/Wait/WaitForLog.php | 11 ++- tests/Integration/PostgreSQLContainerTest.php | 8 +-- 6 files changed, 94 insertions(+), 45 deletions(-) create mode 100644 src/Wait/BaseWait.php diff --git a/src/Container/PostgresContainer.php b/src/Container/PostgresContainer.php index bf77084..705f3b6 100644 --- a/src/Container/PostgresContainer.php +++ b/src/Container/PostgresContainer.php @@ -8,11 +8,18 @@ use Testcontainers\Wait\WaitForExec; 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); - $this->withEnvironment('POSTGRES_PASSWORD', $rootPassword); - $this->withWait(new WaitForExec(["pg_isready", "-h", "127.0.0.1"])); + $this->withExposedPorts(5432); + $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 { - return new self($version, $dbPassword); + return new self( + version: $version, + password: $dbPassword + ); } public function withPostgresUser(string $username): self diff --git a/src/Wait/BaseWait.php b/src/Wait/BaseWait.php new file mode 100644 index 0000000..b9b607a --- /dev/null +++ b/src/Wait/BaseWait.php @@ -0,0 +1,20 @@ +dockerClient = ContainerRuntimeClient::getDockerClient(); + } + + abstract public function wait(string $id): void; +} diff --git a/src/Wait/WaitForContainerRunning.php b/src/Wait/WaitForContainerRunning.php index cafeb70..570fd28 100644 --- a/src/Wait/WaitForContainerRunning.php +++ b/src/Wait/WaitForContainerRunning.php @@ -5,23 +5,14 @@ declare(strict_types=1); namespace Testcontainers\Wait; use Docker\API\Model\ContainersIdJsonGetResponse200; -use Docker\Docker; -use Testcontainers\ContainerRuntime\ContainerRuntimeClient; use Testcontainers\Exception\ContainerNotReadyException; /** * Simply makes container inspect and checks if container is running. * 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 { $startTime = microtime(true) * 1000; @@ -41,8 +32,6 @@ class WaitForContainerRunning implements WaitInterface return; } - var_dump($containerStatus); - usleep($this->pollInterval * 1000); } } diff --git a/src/Wait/WaitForExec.php b/src/Wait/WaitForExec.php index d48a22f..6d8aea9 100644 --- a/src/Wait/WaitForExec.php +++ b/src/Wait/WaitForExec.php @@ -5,34 +5,67 @@ declare(strict_types=1); namespace Testcontainers\Wait; use Closure; +use Docker\API\Client; use Docker\API\Model\ContainersIdExecPostBody; -use Docker\API\Model\ExecIdStartPostBody; -use Docker\Docker; +use Testcontainers\Exception\ContainerWaitingTimeoutException; -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; /** * @param array $command */ - public function __construct(private array $command, private ?Closure $checkFunction = null) - { - $this->dockerClient = Docker::create(); - $execConfig = new ContainersIdExecPostBody(); - $execConfig->setTty(true); - $execConfig->setAttachStdout(true); - $execConfig->setAttachStderr(true); - $execConfig->setCmd($this->command); + public function __construct( + protected array $command, + protected ?Closure $checkFunction = null, + int $timeout = 10000, + int $pollInterval = 500 + ) { + parent::__construct($timeout, $pollInterval); } public function wait(string $id): void { - $execid = $this->dockerClient->containerExec($id, $this->execConfig)->getId() ?? ''; - $execStartConfig = new ExecIdStartPostBody(); - $execStartConfig->setDetach(false); - $this->dockerClient->execStart($execid, $execStartConfig); + $this->execConfig = (new ContainersIdExecPostBody()) + ->setCmd($this->command) + ->setAttachStdout(true) + ->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); + } } } diff --git a/src/Wait/WaitForLog.php b/src/Wait/WaitForLog.php index 04ef8cc..c8ba18c 100644 --- a/src/Wait/WaitForLog.php +++ b/src/Wait/WaitForLog.php @@ -5,23 +5,20 @@ declare(strict_types=1); namespace Testcontainers\Wait; use Docker\API\Runtime\Client\Client; -use Docker\Docker; use Testcontainers\Exception\ContainerWaitingTimeoutException; /** * 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( protected string $message, protected bool $enableRegex = false, - protected int $timeout = 10000, - protected int $pollInterval = 500 + int $timeout = 10000, + int $pollInterval = 500 ) { - $this->dockerClient = Docker::create(); + parent::__construct($timeout, $pollInterval); } public function wait(string $id): void diff --git a/tests/Integration/PostgreSQLContainerTest.php b/tests/Integration/PostgreSQLContainerTest.php index 7691435..2b6bd70 100644 --- a/tests/Integration/PostgreSQLContainerTest.php +++ b/tests/Integration/PostgreSQLContainerTest.php @@ -10,8 +10,8 @@ class PostgreSQLContainerTest extends ContainerTestCase { public static function setUpBeforeClass(): void { - self::$container = (new PostgresContainer('latest', 'test')) - ->withPostgresUser('test') + self::$container = (new PostgresContainer()) + ->withPostgresUser('bar') ->withPostgresDatabase('foo') ->start(); } @@ -19,8 +19,8 @@ class PostgreSQLContainerTest extends ContainerTestCase public function testPostgreSQLContainer(): void { $pdo = new \PDO( - sprintf('pgsql:host=%s;port=5432;dbname=foo', self::$container->getAddress()), - 'test', + 'pgsql:host=127.0.0.1;port=5432;dbname=foo', + 'bar', 'test', );