mirror of
https://github.com/stan220/testcontainers-php.git
synced 2026-09-12 11:29:36 +00:00
refactored WaitForHealthCheck strategy and added more granular exceptions
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Exception;
|
||||||
|
|
||||||
|
class ContainerException extends \RuntimeException
|
||||||
|
{
|
||||||
|
protected string $containerId;
|
||||||
|
|
||||||
|
public function __construct(string $message, string $containerId = '', ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
$this->containerId = $containerId;
|
||||||
|
parent::__construct($message, 0, $previous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContainerId(): string
|
||||||
|
{
|
||||||
|
return $this->containerId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,6 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Exception;
|
namespace Testcontainers\Exception;
|
||||||
|
|
||||||
class ContainerNotReadyException extends \RuntimeException
|
class ContainerNotReadyException extends ContainerException
|
||||||
{
|
{
|
||||||
public function __construct(string $id, ?\Throwable $previous = null)
|
|
||||||
{
|
|
||||||
parent::__construct(sprintf('Container %s is not ready', $id), 0, $previous);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Exception;
|
||||||
|
|
||||||
|
class ContainerStateException extends ContainerException
|
||||||
|
{
|
||||||
|
public function __construct(string $containerId, ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
$message = sprintf('Unable to retrieve state for container %s', $containerId);
|
||||||
|
parent::__construct($message, $containerId, $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,19 +4,11 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Exception;
|
namespace Testcontainers\Exception;
|
||||||
|
|
||||||
class ContainerWaitingTimeoutException extends \RuntimeException
|
class ContainerWaitingTimeoutException extends ContainerNotReadyException
|
||||||
{
|
{
|
||||||
protected string $containerId;
|
|
||||||
|
|
||||||
public function __construct(string $containerId, ?string $message = null, ?\Throwable $previous = null)
|
public function __construct(string $containerId, ?string $message = null, ?\Throwable $previous = null)
|
||||||
{
|
{
|
||||||
$this->containerId = $containerId;
|
|
||||||
$message ??= sprintf('Timeout reached while waiting for container %s', $containerId);
|
$message ??= sprintf('Timeout reached while waiting for container %s', $containerId);
|
||||||
parent::__construct($message, 0, $previous);
|
parent::__construct($message, $containerId, $previous);
|
||||||
}
|
|
||||||
|
|
||||||
public function getContainerId(): string
|
|
||||||
{
|
|
||||||
return $this->containerId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Exception;
|
||||||
|
|
||||||
|
class HealthCheckFailedException extends ContainerNotReadyException
|
||||||
|
{
|
||||||
|
public function __construct(string $containerId, ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
$message = sprintf('Health check failed: Container %s is unhealthy', $containerId);
|
||||||
|
parent::__construct($message, $containerId, $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Exception;
|
||||||
|
|
||||||
|
class HealthCheckNotConfiguredException extends ContainerNotReadyException
|
||||||
|
{
|
||||||
|
public function __construct(string $containerId, ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
$message = sprintf('Health check not configured for container %s', $containerId);
|
||||||
|
parent::__construct($message, $containerId, $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Exception;
|
||||||
|
|
||||||
|
class UnknownHealthStatusException extends ContainerNotReadyException
|
||||||
|
{
|
||||||
|
public function __construct(string $containerId, string $status, ?\Throwable $previous = null)
|
||||||
|
{
|
||||||
|
$message = sprintf('Unknown health status %s for container %s', $status, $containerId);
|
||||||
|
parent::__construct($message, $containerId, $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,43 +4,70 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Wait;
|
namespace Testcontainers\Wait;
|
||||||
|
|
||||||
use Docker\Docker;
|
use Docker\API\Model\ContainersIdJsonGetResponse200;
|
||||||
use Http\Client\Socket\Exception\TimeoutException;
|
|
||||||
use Testcontainers\Container\StartedTestContainer;
|
use Testcontainers\Container\StartedTestContainer;
|
||||||
use Testcontainers\Exception\ContainerNotReadyException;
|
use Testcontainers\Exception\ContainerStateException;
|
||||||
|
use Testcontainers\Exception\ContainerWaitingTimeoutException;
|
||||||
|
use Testcontainers\Exception\HealthCheckFailedException;
|
||||||
|
use Testcontainers\Exception\HealthCheckNotConfiguredException;
|
||||||
|
use Testcontainers\Exception\UnknownHealthStatusException;
|
||||||
|
|
||||||
//TODO: not ready yet
|
/**
|
||||||
|
* Wait strategy that waits until the container's health status is 'healthy'.
|
||||||
|
*
|
||||||
|
* Possible health statuses:
|
||||||
|
* - "none": No health check configured.
|
||||||
|
* - "starting": Health check is in progress.
|
||||||
|
* - "healthy": Container is healthy.
|
||||||
|
* - "unhealthy": Container is unhealthy.
|
||||||
|
*/
|
||||||
class WaitForHealthCheck extends BaseWaitStrategy
|
class WaitForHealthCheck extends BaseWaitStrategy
|
||||||
{
|
{
|
||||||
public function __construct(protected int $timeout = 5000, protected int $pollInterval = 1000)
|
|
||||||
{
|
|
||||||
parent::__construct($timeout, $pollInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function wait(StartedTestContainer $container): void
|
public function wait(StartedTestContainer $container): void
|
||||||
{
|
{
|
||||||
$startTime = microtime(true) * 1000;
|
$startTime = microtime(true);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
$elapsedTime = (microtime(true) * 1000) - $startTime;
|
$elapsedTime = (microtime(true) - $startTime) * 1000;
|
||||||
|
|
||||||
if ($elapsedTime > $this->timeout) {
|
if ($elapsedTime > $this->timeout) {
|
||||||
throw new TimeoutException(sprintf("Health check not healthy after %d ms", $this->timeout));
|
throw new ContainerWaitingTimeoutException($container->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var \Psr\Http\Message\ResponseInterface | null $containerInspect */
|
/** @var ContainersIdJsonGetResponse200|null $containerInspect */
|
||||||
$containerInspect = $container->getClient()->containerInspect($container->getId(), [], Docker::FETCH_RESPONSE);
|
$containerInspect = $container->getClient()->containerInspect($container->getId());
|
||||||
//$containerStatus = $containerInspect?->getArrayCopy() ?? null;
|
|
||||||
$containerStatus = '';
|
$containerState = $containerInspect?->getState();
|
||||||
if ($containerStatus === 'healthy') {
|
|
||||||
return;
|
if ($containerState !== null) {
|
||||||
|
$health = $containerState->getHealth();
|
||||||
|
|
||||||
|
if ($health !== null) {
|
||||||
|
$status = $health->getStatus();
|
||||||
|
|
||||||
|
switch ($status) {
|
||||||
|
case 'healthy':
|
||||||
|
return; // Container is healthy
|
||||||
|
case 'starting':
|
||||||
|
// Health check is still in progress; continue waiting
|
||||||
|
break;
|
||||||
|
case 'unhealthy':
|
||||||
|
throw new HealthCheckFailedException($container->getId());
|
||||||
|
case 'none':
|
||||||
|
throw new HealthCheckNotConfiguredException($container->getId());
|
||||||
|
default:
|
||||||
|
throw new UnknownHealthStatusException($container->getId(), (string)$status);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Health is null; treat as 'none' status
|
||||||
|
throw new HealthCheckNotConfiguredException($container->getId());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Container state is null
|
||||||
|
throw new ContainerStateException($container->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($containerStatus === 'unhealthy') {
|
usleep($this->pollInterval * 1000);
|
||||||
throw new ContainerNotReadyException(sprintf("Health check failed: %s", $containerStatus));
|
|
||||||
}
|
|
||||||
|
|
||||||
usleep($this->pollInterval * 1000); // Sleep for the polling interval
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user