mirror of
https://github.com/stan220/testcontainers-php.git
synced 2026-09-08 18:29:47 +00:00
- Move most of the stuff for backwards compatibility support into one class
- Added random port logic - Updated wait wtrategies - API alignments to make it similar to other official implementations - ...
This commit is contained in:
@@ -19,7 +19,7 @@ composer req --dev testcontainers/testcontainers
|
|||||||
|
|
||||||
use Testcontainers\Container\GenericContainer;
|
use Testcontainers\Container\GenericContainer;
|
||||||
|
|
||||||
$container = GenericContainer::make('nginx:alpine');
|
$container = new GenericContainer::make('nginx:alpine');
|
||||||
|
|
||||||
// set an environment variable
|
// set an environment variable
|
||||||
$container->withEnvironment('name', 'var');
|
$container->withEnvironment('name', 'var');
|
||||||
@@ -58,7 +58,7 @@ $container->withWait(new WaitForHealthCheck());
|
|||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Testcontainers\Container\MySQLContainer;
|
use Testcontainers\Modules\MySQLContainer;
|
||||||
|
|
||||||
$container = MySQLContainer::make('8.0');
|
$container = MySQLContainer::make('8.0');
|
||||||
$container->withMySQLDatabase('foo');
|
$container->withMySQLDatabase('foo');
|
||||||
@@ -80,7 +80,7 @@ $pdo = new \PDO(
|
|||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Testcontainers\Container\MariaDBContainer;
|
use Testcontainers\Modules\MariaDBContainer;
|
||||||
|
|
||||||
$container = MariaDBContainer::make('8.0');
|
$container = MariaDBContainer::make('8.0');
|
||||||
$container->withMariaDBDatabase('foo');
|
$container->withMariaDBDatabase('foo');
|
||||||
@@ -102,7 +102,7 @@ $pdo = new \PDO(
|
|||||||
```php
|
```php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Testcontainers\Container\PostgresContainer;
|
use Testcontainers\Modules\PostgresContainer;
|
||||||
|
|
||||||
$container = PostgresContainer::make('15.0', 'password');
|
$container = PostgresContainer::make('15.0', 'password');
|
||||||
$container->withPostgresDatabase('database');
|
$container->withPostgresDatabase('database');
|
||||||
@@ -123,7 +123,7 @@ $pdo = new \PDO(
|
|||||||
|
|
||||||
```php
|
```php
|
||||||
|
|
||||||
use Testcontainers\Container\RedisContainer;
|
use Testcontainers\Modules\RedisContainer;
|
||||||
|
|
||||||
$container = RedisContainer::make('6.0');
|
$container = RedisContainer::make('6.0');
|
||||||
|
|
||||||
@@ -139,7 +139,7 @@ $redis->connect($container->getAddress());
|
|||||||
|
|
||||||
```php
|
```php
|
||||||
|
|
||||||
use Testcontainers\Container\OpenSearchContainer;
|
use Testcontainers\Modules\OpenSearchContainer;
|
||||||
|
|
||||||
$container = OpenSearchContainer::make('2');
|
$container = OpenSearchContainer::make('2');
|
||||||
$container->disableSecurityPlugin();
|
$container->disableSecurityPlugin();
|
||||||
@@ -166,7 +166,7 @@ use Doctrine\Bundle\DoctrineBundle\ConnectionFactory;
|
|||||||
use Doctrine\Common\EventManager;
|
use Doctrine\Common\EventManager;
|
||||||
use Doctrine\DBAL\Configuration;
|
use Doctrine\DBAL\Configuration;
|
||||||
use Doctrine\DBAL\Tools\DsnParser;
|
use Doctrine\DBAL\Tools\DsnParser;
|
||||||
use Testcontainers\Container\PostgresContainer;
|
use Testcontainers\Modules\PostgresContainer;
|
||||||
|
|
||||||
class TestConnectionFactory extends ConnectionFactory
|
class TestConnectionFactory extends ConnectionFactory
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="default">
|
<testsuite name="default">
|
||||||
<directory>tests</directory>
|
<directory>tests</directory>
|
||||||
<exclude>tests/Integration/WaitStrategyTest.php</exclude>
|
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
|
|
||||||
|
|||||||
+147
-2
@@ -6,9 +6,154 @@ namespace Testcontainers\Container;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Added for backward compatibility.
|
* Added for backward compatibility.
|
||||||
* Just a wrapper for GenericContainer.
|
|
||||||
* @deprecated Use GenericContainer instead.
|
* @deprecated Use GenericContainer instead.
|
||||||
|
* TODO: Remove in next major release.
|
||||||
*/
|
*/
|
||||||
class Container extends GenericContainer
|
final class Container extends GenericContainer
|
||||||
{
|
{
|
||||||
|
protected ?StartedTestContainer $startedContainer = null;
|
||||||
|
|
||||||
|
protected ?StoppedTestContainer $stoppedContainer = null;
|
||||||
|
public static function make(string $image): self
|
||||||
|
{
|
||||||
|
return new self($image);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `withPrivilegedMode` instead
|
||||||
|
*/
|
||||||
|
public function withPrivileged(bool $privileged = true): self
|
||||||
|
{
|
||||||
|
return $this->withPrivilegedMode($privileged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `withExposedPorts` instead
|
||||||
|
*/
|
||||||
|
public function withPort(string $localPort, string $containerPort): self
|
||||||
|
{
|
||||||
|
return $this->withExposedPorts($containerPort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated there will be no replacement
|
||||||
|
*/
|
||||||
|
public function withImage(string $image): self
|
||||||
|
{
|
||||||
|
$this->image = $image;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `start` instead
|
||||||
|
*/
|
||||||
|
public function run(): self
|
||||||
|
{
|
||||||
|
$this->startedContainer = $this->start();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string> $commandAsArray
|
||||||
|
* @deprecated Use 'exec' from StartedTestContainer instead
|
||||||
|
*/
|
||||||
|
public function execute(array $commandAsArray): string
|
||||||
|
{
|
||||||
|
if($this->startedContainer === null) {
|
||||||
|
throw new \RuntimeException('Container is not started');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->startedContainer->exec($commandAsArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use 'logs' from StartedTestContainer instead
|
||||||
|
*/
|
||||||
|
public function logs(): string
|
||||||
|
{
|
||||||
|
if($this->startedContainer === null) {
|
||||||
|
throw new \RuntimeException('Container is not started');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->startedContainer->logs();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use 'getHost' from StartedTestContainer instead
|
||||||
|
*/
|
||||||
|
public function getAddress(): string
|
||||||
|
{
|
||||||
|
if($this->startedContainer === null) {
|
||||||
|
throw new \RuntimeException('Container is not started');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->startedContainer->getHost();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use 'getFirstMappedPort' from StartedTestContainer instead
|
||||||
|
*/
|
||||||
|
public function getPort(): int
|
||||||
|
{
|
||||||
|
if($this->startedContainer === null) {
|
||||||
|
throw new \RuntimeException('Container is not started');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->startedContainer->getFirstMappedPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use 'stop' from StartedTestContainer instead
|
||||||
|
*/
|
||||||
|
public function kill(): self
|
||||||
|
{
|
||||||
|
$this->dockerClient->containerKill($this->id);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use `stop` from StartedTestContainer instead
|
||||||
|
*/
|
||||||
|
public function stop(): self
|
||||||
|
{
|
||||||
|
if($this->startedContainer === null) {
|
||||||
|
throw new \RuntimeException('Container is not started');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stoppedContainer = $this->startedContainer->stop();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use 'restart' method from StartedTestContainer instead
|
||||||
|
*/
|
||||||
|
public function restart(): self
|
||||||
|
{
|
||||||
|
if($this->startedContainer === null) {
|
||||||
|
throw new \RuntimeException('Container is not started');
|
||||||
|
}
|
||||||
|
|
||||||
|
$restartedTestContainer = $this->startedContainer->restart();
|
||||||
|
$this->startedContainer = $restartedTestContainer;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use 'stop' method from StartedTestContainer instead
|
||||||
|
*/
|
||||||
|
public function remove(): self
|
||||||
|
{
|
||||||
|
if($this->startedContainer === null) {
|
||||||
|
throw new \RuntimeException('Container is not started');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->startedContainer->stop();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,39 +4,28 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Container;
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
use Docker\API\Client;
|
|
||||||
use Docker\API\Exception\ContainerCreateNotFoundException;
|
use Docker\API\Exception\ContainerCreateNotFoundException;
|
||||||
use Docker\API\Model\ContainersCreatePostBody;
|
use Docker\API\Model\ContainersCreatePostBody;
|
||||||
use Docker\API\Model\ContainersIdExecPostBody;
|
|
||||||
use Docker\API\Model\HealthConfig;
|
use Docker\API\Model\HealthConfig;
|
||||||
use Docker\API\Model\HostConfig;
|
use Docker\API\Model\HostConfig;
|
||||||
use Docker\API\Model\Mount;
|
use Docker\API\Model\Mount;
|
||||||
use Docker\API\Model\PortBinding;
|
use Docker\API\Model\PortBinding;
|
||||||
use Docker\Docker;
|
use Docker\Docker;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use InvalidArgumentException;
|
||||||
use Testcontainers\ContainerRuntime\ContainerRuntimeClient;
|
use Testcontainers\ContainerClient\DockerContainerClient;
|
||||||
use Testcontainers\Wait\WaitForContainerRunning;
|
use Testcontainers\Utils\PortGenerator\RandomUniquePortGenerator;
|
||||||
use Testcontainers\Wait\WaitInterface;
|
use Testcontainers\Wait\WaitForContainer;
|
||||||
|
use Testcontainers\Wait\WaitStrategy;
|
||||||
|
|
||||||
/**
|
class GenericContainer implements TestContainer
|
||||||
* @phpstan-type ContainerInspectSingleNetwork array<int, array{'NetworkSettings': array{'IPAddress': string}}>
|
|
||||||
* @phpstan-type ContainerInspectMultipleNetworks array<int, array{'NetworkSettings': array{'Networks': array<string, array{'IPAddress': string}>}}>
|
|
||||||
* @phpstan-type ContainerInspect ContainerInspectSingleNetwork|ContainerInspectMultipleNetworks
|
|
||||||
* @phpstan-type DockerNetwork array{CreatedAt: string, Driver: string, ID: string, IPv6: string, Internal: string, Labels: string, Name: string, Scope: string}
|
|
||||||
*/
|
|
||||||
class GenericContainer
|
|
||||||
{
|
{
|
||||||
protected Docker $dockerClient;
|
protected Docker $dockerClient;
|
||||||
|
|
||||||
protected ContainersCreatePostBody $containerConfig;
|
|
||||||
|
|
||||||
protected string $image;
|
protected string $image;
|
||||||
|
|
||||||
protected string $containerName;
|
|
||||||
|
|
||||||
protected string $id;
|
protected string $id;
|
||||||
|
|
||||||
/** @var array<string> */
|
/** @var list<string> */
|
||||||
protected array $command = [];
|
protected array $command = [];
|
||||||
|
|
||||||
protected ?string $entryPoint = null;
|
protected ?string $entryPoint = null;
|
||||||
@@ -48,7 +37,7 @@ class GenericContainer
|
|||||||
*/
|
*/
|
||||||
protected array $env = [];
|
protected array $env = [];
|
||||||
|
|
||||||
protected WaitInterface $wait;
|
protected WaitStrategy $waitStrategy;
|
||||||
|
|
||||||
protected bool $isPrivileged = false;
|
protected bool $isPrivileged = false;
|
||||||
protected ?string $networkName = null;
|
protected ?string $networkName = null;
|
||||||
@@ -64,16 +53,7 @@ class GenericContainer
|
|||||||
public function __construct(string $image)
|
public function __construct(string $image)
|
||||||
{
|
{
|
||||||
$this->image = $image;
|
$this->image = $image;
|
||||||
$this->dockerClient = ContainerRuntimeClient::getDockerClient();
|
$this->dockerClient = DockerContainerClient::getDockerClient();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use constructor instead
|
|
||||||
* Left for backward compatibility
|
|
||||||
*/
|
|
||||||
public static function make(string $image): self
|
|
||||||
{
|
|
||||||
return new GenericContainer($image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): string
|
public function getId(): string
|
||||||
@@ -81,58 +61,56 @@ class GenericContainer
|
|||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function withCommand(array $command): self
|
/**
|
||||||
|
* @param list<string> $command
|
||||||
|
*/
|
||||||
|
public function withCommand(array $command): static
|
||||||
{
|
{
|
||||||
$this->command = $command;
|
$this->command = $command;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function exec(array $command): string
|
public function withEntryPoint(string $entryPoint): static
|
||||||
{
|
|
||||||
$execConfig = (new ContainersIdExecPostBody())
|
|
||||||
->setCmd($command)
|
|
||||||
->setAttachStdout(true)
|
|
||||||
->setAttachStderr(true);
|
|
||||||
|
|
||||||
// Create and start the exec command
|
|
||||||
$exec = $this->dockerClient->containerExec($this->id, $execConfig);
|
|
||||||
$contents = $this->dockerClient
|
|
||||||
->execStart($exec->getId(), null, Client::FETCH_RESPONSE)
|
|
||||||
?->getBody()
|
|
||||||
->getContents() ?? '';
|
|
||||||
|
|
||||||
return preg_replace('/[\x00-\x1F\x7F]/u', '', $contents);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withEntryPoint(string $entryPoint): self
|
|
||||||
{
|
{
|
||||||
$this->entryPoint = $entryPoint;
|
$this->entryPoint = $entryPoint;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function withEnvironment(string $name, string $value): self
|
/**
|
||||||
|
* To support temporarily backwards compatibility, the method supports two formats:
|
||||||
|
* 1. A single key-value pair (deprecated): $object->withEnvironment('key', 'value');
|
||||||
|
* 2. An array of key-value pairs: $object->withEnvironment(['key1' => 'value1', 'key2' => 'value2']);
|
||||||
|
*
|
||||||
|
* @param string | array<string, string> $env An array of environment variables or the name of a single variable.
|
||||||
|
* @param string|null $value The value of the environment variable if a single variable is passed.
|
||||||
|
* @return static Returns itself for chaining purposes.
|
||||||
|
*/
|
||||||
|
public function withEnvironment(string | array $env, ?string $value = null): static
|
||||||
{
|
{
|
||||||
$this->env[$name] = $value;
|
if (is_array($env)) {
|
||||||
|
foreach ($env as $key => $val) {
|
||||||
|
$this->env[$key] = $val;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ($value === null) {
|
||||||
|
throw new InvalidArgumentException("Value cannot be null when setting a single environment variable.");
|
||||||
|
}
|
||||||
|
$this->env[$env] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function withImage(string $image): self
|
public function withWait(WaitStrategy $waitStrategy): static
|
||||||
{
|
{
|
||||||
$this->image = $image;
|
$this->waitStrategy = $waitStrategy;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function withWait(WaitInterface $wait): self
|
public function withHealthCheckCommand(string $command, int $healthCheckIntervalInMS = 1000): static
|
||||||
{
|
|
||||||
$this->wait = $wait;
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withHealthCheckCommand(string $command, int $healthCheckIntervalInMS = 1000): self
|
|
||||||
{
|
{
|
||||||
$this->healthConfig = new HealthConfig([
|
$this->healthConfig = new HealthConfig([
|
||||||
'Test' => ['CMD', $command],
|
'Test' => ['CMD', $command],
|
||||||
@@ -142,33 +120,22 @@ class GenericContainer
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function withMount(string $localPath, string $containerPath): self
|
public function withMount(string $localPath, string $containerPath): static
|
||||||
{
|
{
|
||||||
$this->mounts[] = new Mount(['type' => 'bind', 'source' => $localPath, 'target' => $containerPath]);
|
$this->mounts[] = new Mount(['type' => 'bind', 'source' => $localPath, 'target' => $containerPath]);
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use `withExposedPorts` instead
|
|
||||||
*/
|
|
||||||
public function withPort(string $localPort, string $containerPort): self
|
|
||||||
{
|
|
||||||
return $this->withExposedPorts($containerPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @psalm-param string|int|array<string|int> $port
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* Add ports to be exposed by the Docker container.
|
* Add ports to be exposed by the Docker container.
|
||||||
* This method accepts multiple inputs: single port, multiple ports, or ports with specific protocols
|
* This method accepts multiple inputs: single port, multiple ports, or ports with specific protocols
|
||||||
* to attempt to align with other language implementations.
|
* to attempt to align with other language implementations.
|
||||||
*
|
*
|
||||||
* @psalm-param int|string|array<int|string> $ports One or more ports to expose.
|
* @psalm-param int|string|array<int|string> $ports One or more ports to expose.
|
||||||
* @return self Fluent interface for chaining.
|
* @return static Fluent interface for chaining.
|
||||||
*/
|
*/
|
||||||
public function withExposedPorts(...$ports): self
|
public function withExposedPorts(...$ports): static
|
||||||
{
|
{
|
||||||
foreach ($ports as $port) {
|
foreach ($ports as $port) {
|
||||||
if (is_array($port)) {
|
if (is_array($port)) {
|
||||||
@@ -207,46 +174,51 @@ class GenericContainer
|
|||||||
return $port;
|
return $port;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function withPrivileged(bool $privileged = true): self
|
public function withPrivilegedMode(bool $privileged = true): static
|
||||||
{
|
{
|
||||||
$this->isPrivileged = $privileged;
|
$this->isPrivileged = $privileged;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function withNetwork(string $networkName): self
|
//TODO: not yet implemented
|
||||||
|
public function withNetwork(string $networkName): static
|
||||||
{
|
{
|
||||||
$this->networkName = $networkName;
|
$this->networkName = $networkName;
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function stop(): self
|
//TODO: needs refactoring
|
||||||
{
|
public function start(): StartedGenericContainer
|
||||||
$this->dockerClient->containerStop($this->id);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function start(): self
|
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$containerCreatePostBody = new ContainersCreatePostBody();
|
$containerCreatePostBody = new ContainersCreatePostBody();
|
||||||
//setup only if we need to expose ports
|
//handle withExposedPorts
|
||||||
if(!empty($this->exposedPorts)) {
|
if(!empty($this->exposedPorts)) {
|
||||||
|
$portGenerator = new RandomUniquePortGenerator();
|
||||||
$portMap = new \ArrayObject();
|
$portMap = new \ArrayObject();
|
||||||
|
|
||||||
foreach ($this->exposedPorts as $port) {
|
foreach ($this->exposedPorts as $port) {
|
||||||
$portBinding = new PortBinding();
|
$portBinding = new PortBinding();
|
||||||
$portBinding->setHostPort(explode('/', $port)[0]);
|
$portBinding->setHostPort((string) $portGenerator->generatePort());
|
||||||
$portBinding->setHostIp('0.0.0.0');
|
$portBinding->setHostIp('0.0.0.0');
|
||||||
$portMap[$port] = [$portBinding];
|
$portMap[$port] = [$portBinding];
|
||||||
}
|
}
|
||||||
|
|
||||||
$hostConfig = new HostConfig();
|
$hostConfig = new HostConfig();
|
||||||
$hostConfig->setPortBindings($portMap);
|
$hostConfig->setPortBindings($portMap);
|
||||||
|
//handle withPrivilegedMode
|
||||||
|
if($this->isPrivileged) {
|
||||||
|
$hostConfig->setPrivileged($this->isPrivileged);
|
||||||
|
}
|
||||||
$containerCreatePostBody->setHostConfig($hostConfig);
|
$containerCreatePostBody->setHostConfig($hostConfig);
|
||||||
}
|
}
|
||||||
|
//handle withPrivilegedMode
|
||||||
|
if($this->isPrivileged) {
|
||||||
|
$hostConfig = new HostConfig();
|
||||||
|
$hostConfig->setPrivileged($this->isPrivileged);
|
||||||
|
}
|
||||||
$containerCreatePostBody->setImage($this->image);
|
$containerCreatePostBody->setImage($this->image);
|
||||||
$containerCreatePostBody->setCmd($this->command);
|
$containerCreatePostBody->setCmd($this->command);
|
||||||
$envs = [];
|
$envs = [];
|
||||||
@@ -267,96 +239,13 @@ class GenericContainer
|
|||||||
|
|
||||||
$this->dockerClient->containerStart($this->id);
|
$this->dockerClient->containerStart($this->id);
|
||||||
|
|
||||||
if(!isset($this->wait)) {
|
if(!isset($this->waitStrategy)) {
|
||||||
$this->withWait(new WaitForContainerRunning());
|
$this->withWait(new WaitForContainer());
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->wait->wait($this->id);
|
$startedContainer = new StartedGenericContainer($this->id);
|
||||||
|
$this->waitStrategy->wait($startedContainer);
|
||||||
|
|
||||||
return $this;
|
return $startedContainer;
|
||||||
}
|
|
||||||
|
|
||||||
public function restart(): self
|
|
||||||
{
|
|
||||||
$this->dockerClient->containerRestart($this->id);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remove(): self
|
|
||||||
{
|
|
||||||
$this->dockerClient->containerStop($this->id);
|
|
||||||
$this->dockerClient->containerDelete($this->id);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function kill(): self
|
|
||||||
{
|
|
||||||
$this->dockerClient->containerKill($this->id);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use `start` instead
|
|
||||||
* Left for backward compatibility
|
|
||||||
*/
|
|
||||||
public function run(): self
|
|
||||||
{
|
|
||||||
return $this->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array<string> $commandAsArray
|
|
||||||
*/
|
|
||||||
public function execute(array $commandAsArray): ResponseInterface
|
|
||||||
{
|
|
||||||
$command = new ContainersIdExecPostBody();
|
|
||||||
$command->setCmd($commandAsArray);
|
|
||||||
return $this->dockerClient->containerExec($this->id, $command);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function logs(): string
|
|
||||||
{
|
|
||||||
return $this->dockerClient->containerLogs($this->id)?->getBody()?->getContents() ?? '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getAddress(): string
|
|
||||||
{
|
|
||||||
$inspection = $this->inspect();
|
|
||||||
return $inspection['gateway'];
|
|
||||||
// foreach ($containerNetworks as $network) {
|
|
||||||
// var_dump($network->getNetworkID(), $this->id, $network->getIPAddress());
|
|
||||||
// if($network->getNetworkID() === $this->id) {
|
|
||||||
// $containerAddress = $network->getIpAddress();
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return $containerAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array{gateway: string, ports: array<string, int>}
|
|
||||||
*/
|
|
||||||
public function inspect(): array
|
|
||||||
{
|
|
||||||
$response = $this->dockerClient->containerInspect($this->id);
|
|
||||||
$settings = $response->getNetworkSettings();
|
|
||||||
//var_dump($settings);
|
|
||||||
|
|
||||||
$ports = [];
|
|
||||||
foreach ($settings->getPorts() as $port => $value) {
|
|
||||||
if ($value === null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$ports[$port] = (int) $value[0]->getHostPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [
|
|
||||||
'gateway' => $settings->getGateway(),
|
|
||||||
'ports' => $ports,
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,39 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Container;
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
use Testcontainers\Wait\WaitForLog;
|
/**
|
||||||
|
* Left for namespace backward compatibility
|
||||||
class MariaDBContainer extends GenericContainer
|
* @deprecated Use \Testcontainers\Modules\MariaDBContainer instead.
|
||||||
{
|
* TODO: Remove in next major release.
|
||||||
public function __construct(string $version = 'latest', string $mysqlRootPassword = 'root')
|
|
||||||
{
|
|
||||||
parent::__construct('mariadb:' . $version);
|
|
||||||
$this->withExposedPorts(3306);
|
|
||||||
$this->withWait(new WaitForLog('ready for connections'));
|
|
||||||
$this->withEnvironment('MARIADB_ROOT_PASSWORD', $mysqlRootPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use constructor instead
|
|
||||||
* Left for backward compatibility
|
|
||||||
*/
|
*/
|
||||||
public static function make(string $version = 'latest', string $mysqlRootPassword = 'root'): self
|
class MariaDBContainer extends \Testcontainers\Modules\MariaDBContainer
|
||||||
{
|
{
|
||||||
return new self($version, $mysqlRootPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withMariaDBUser(string $username, string $password): self
|
|
||||||
{
|
|
||||||
$this->withEnvironment('MARIADB_USER', $username);
|
|
||||||
$this->withEnvironment('MARIADB_PASSWORD', $password);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withMariaDBDatabase(string $database): self
|
|
||||||
{
|
|
||||||
$this->withEnvironment('MARIADB_DATABASE', $database);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,39 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Container;
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
use Testcontainers\Wait\WaitForLog;
|
/**
|
||||||
|
* Left for namespace backward compatibility
|
||||||
class MySQLContainer extends GenericContainer
|
* @deprecated Use \Testcontainers\Modules\MySQLContainer instead.
|
||||||
{
|
* TODO: Remove in next major release.
|
||||||
public function __construct(string $version = 'latest', string $mysqlRootPassword = 'root')
|
|
||||||
{
|
|
||||||
parent::__construct('mysql:' . $version);
|
|
||||||
$this->withExposedPorts(3306);
|
|
||||||
$this->withEnvironment('MYSQL_ROOT_PASSWORD', $mysqlRootPassword);
|
|
||||||
$this->withWait(new WaitForLog('ready for connections'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use constructor instead
|
|
||||||
* Left for backward compatibility
|
|
||||||
*/
|
*/
|
||||||
public static function make(string $version = 'latest', string $mysqlRootPassword = 'root'): self
|
class MySQLContainer extends \Testcontainers\Modules\MySQLContainer
|
||||||
{
|
{
|
||||||
return new self($version, $mysqlRootPassword);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withMySQLUser(string $username, string $password): self
|
|
||||||
{
|
|
||||||
$this->withEnvironment('MYSQL_USER', $username);
|
|
||||||
$this->withEnvironment('MYSQL_PASSWORD', $password);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withMySQLDatabase(string $database): self
|
|
||||||
{
|
|
||||||
$this->withEnvironment('MYSQL_DATABASE', $database);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,45 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Container;
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
use Testcontainers\Wait\WaitForHttp;
|
/**
|
||||||
use Testcontainers\Wait\WaitForLog;
|
* Left for namespace backward compatibility
|
||||||
|
* @deprecated Use \Testcontainers\Modules\OpenSearchContainer instead.
|
||||||
class OpenSearchContainer extends GenericContainer
|
* TODO: Remove in next major release.
|
||||||
|
*/
|
||||||
|
class OpenSearchContainer extends \Testcontainers\Modules\OpenSearchContainer
|
||||||
{
|
{
|
||||||
public function __construct(string $version = 'latest')
|
|
||||||
{
|
|
||||||
parent::__construct('opensearchproject/opensearch:' . $version);
|
|
||||||
$this->withExposedPorts(9200);
|
|
||||||
$this->withEnvironment('discovery.type', 'single-node');
|
|
||||||
$this->withEnvironment('OPENSEARCH_INITIAL_ADMIN_PASSWORD', 'c3o_ZPHo!');
|
|
||||||
$this->withWait(new WaitForLog(
|
|
||||||
'/\]\s+started\?\[/',
|
|
||||||
true,
|
|
||||||
30000
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use constructor instead
|
|
||||||
* Left for backward compatibility
|
|
||||||
*/
|
|
||||||
public static function make(string $version = 'latest'): self
|
|
||||||
{
|
|
||||||
return new self($version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withDisabledSecurityPlugin(): self
|
|
||||||
{
|
|
||||||
$this->withEnvironment('plugins.security.disabled', 'true');
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use withDisabledSecurityPlugin instead
|
|
||||||
*/
|
|
||||||
public function disableSecurityPlugin(): self
|
|
||||||
{
|
|
||||||
return $this->withDisabledSecurityPlugin();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,47 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Container;
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
use Testcontainers\Wait\WaitForExec;
|
/**
|
||||||
|
* Left for namespace backward compatibility
|
||||||
class PostgresContainer extends GenericContainer
|
* @deprecated Use \Testcontainers\Modules\PostgresContainer instead.
|
||||||
{
|
* TODO: Remove in next major release.
|
||||||
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->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]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use constructor instead
|
|
||||||
* Left for backward compatibility
|
|
||||||
*/
|
*/
|
||||||
public static function make(string $version = 'latest', string $dbPassword = 'root'): self
|
class PostgresContainer extends \Testcontainers\Modules\PostgresContainer
|
||||||
{
|
{
|
||||||
return new self(
|
|
||||||
version: $version,
|
|
||||||
password: $dbPassword
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withPostgresUser(string $username): self
|
|
||||||
{
|
|
||||||
$this->withEnvironment('POSTGRES_USER', $username);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function withPostgresDatabase(string $database): self
|
|
||||||
{
|
|
||||||
$this->withEnvironment('POSTGRES_DB', $database);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,23 +4,12 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Container;
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
use Testcontainers\Wait\WaitForLog;
|
/**
|
||||||
|
* Left for namespace backward compatibility
|
||||||
class RedisContainer extends GenericContainer
|
* @deprecated Use \Testcontainers\Modules\RedisContainer instead.
|
||||||
{
|
* TODO: Remove in next major release.
|
||||||
public function __construct(string $version = 'latest')
|
|
||||||
{
|
|
||||||
parent::__construct('redis:' . $version);
|
|
||||||
$this->withExposedPorts(6379);
|
|
||||||
$this->withWait(new WaitForLog('Ready to accept connections'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use constructor instead
|
|
||||||
* Left for backward compatibility
|
|
||||||
*/
|
*/
|
||||||
public static function make(string $version = 'latest'): self
|
class RedisContainer extends \Testcontainers\Modules\RedisContainer
|
||||||
{
|
{
|
||||||
return new self($version);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
|
use Docker\API\Client;
|
||||||
|
use Docker\API\Model\ContainersIdExecPostBody;
|
||||||
|
use Docker\API\Model\IdResponse;
|
||||||
|
use Docker\API\Runtime\Client\Client as DockerRuntimeClient;
|
||||||
|
use Docker\Docker;
|
||||||
|
use Testcontainers\ContainerClient\DockerContainerClient;
|
||||||
|
|
||||||
|
class StartedGenericContainer implements StartedTestContainer
|
||||||
|
{
|
||||||
|
protected Docker $dockerClient;
|
||||||
|
|
||||||
|
protected ?string $lastExecId = null;
|
||||||
|
|
||||||
|
public function __construct(protected readonly string $id)
|
||||||
|
{
|
||||||
|
$this->dockerClient = DockerContainerClient::getDockerClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastExecId(): ?string
|
||||||
|
{
|
||||||
|
return $this->lastExecId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClient(): Docker
|
||||||
|
{
|
||||||
|
return $this->dockerClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $command
|
||||||
|
*/
|
||||||
|
public function exec(array $command): string
|
||||||
|
{
|
||||||
|
$execConfig = (new ContainersIdExecPostBody())
|
||||||
|
->setCmd($command)
|
||||||
|
->setAttachStdout(true)
|
||||||
|
->setAttachStderr(true);
|
||||||
|
|
||||||
|
// Create and start the exec command
|
||||||
|
/** @var IdResponse | null $exec */
|
||||||
|
$exec = $this->dockerClient->containerExec($this->id, $execConfig);
|
||||||
|
|
||||||
|
if($exec === null || $exec->getId() === null) {
|
||||||
|
throw new \RuntimeException('Failed to create exec command');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lastExecId = $exec->getId();
|
||||||
|
|
||||||
|
$contents = $this->dockerClient
|
||||||
|
->execStart($this->lastExecId, null, Client::FETCH_RESPONSE)
|
||||||
|
?->getBody()
|
||||||
|
->getContents() ?? '';
|
||||||
|
|
||||||
|
return preg_replace('/[\x00-\x1F\x7F]/u', '', $contents) ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stop(): StoppedTestContainer
|
||||||
|
{
|
||||||
|
$this->dockerClient->containerStop($this->id);
|
||||||
|
$this->dockerClient->containerDelete($this->id);
|
||||||
|
|
||||||
|
return new StoppedGenericContainer($this->id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function restart(): self
|
||||||
|
{
|
||||||
|
$this->dockerClient->containerRestart($this->id);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logs(): string
|
||||||
|
{
|
||||||
|
$output = $this->dockerClient
|
||||||
|
->containerLogs(
|
||||||
|
$this->id,
|
||||||
|
['stdout' => true, 'stderr' => true],
|
||||||
|
DockerRuntimeClient::FETCH_RESPONSE
|
||||||
|
)
|
||||||
|
?->getBody()
|
||||||
|
->getContents() ?? '';
|
||||||
|
|
||||||
|
return preg_replace('/[\x00-\x1F\x7F]/u', '', mb_convert_encoding($output, 'UTF-8', 'UTF-8')) ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: replace with the proper implementation
|
||||||
|
public function getHost(): string
|
||||||
|
{
|
||||||
|
return '127.0.0.1';
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: not ready yet
|
||||||
|
public function getMappedPort(int $port): int
|
||||||
|
{
|
||||||
|
return $this->inspect()->ports[$port];
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: not ready yet
|
||||||
|
public function getFirstMappedPort(): int
|
||||||
|
{
|
||||||
|
$containerInspectResponse = $this->dockerClient->containerInspect($this->id);
|
||||||
|
$settings = $containerInspectResponse->getNetworkSettings();
|
||||||
|
|
||||||
|
$ports = (array)$settings->getPorts();
|
||||||
|
$port = array_key_first($ports);
|
||||||
|
|
||||||
|
return (int) $ports[$port][0]->getHostPort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName(): string
|
||||||
|
{
|
||||||
|
// TODO: Implement getName() method.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLabels(): array
|
||||||
|
{
|
||||||
|
// TODO: Implement getLabels() method.
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getNetworkNames(): array
|
||||||
|
{
|
||||||
|
// TODO: Implement getNetworkNames() method.
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNetworkId(string $networkName): string
|
||||||
|
{
|
||||||
|
// TODO: Implement getNetworkId() method.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getIpAddress(string $networkName): string
|
||||||
|
{
|
||||||
|
// TODO: Implement getIpAddress() method.
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
|
use Docker\Docker;
|
||||||
|
|
||||||
|
interface StartedTestContainer
|
||||||
|
{
|
||||||
|
public function stop(): StoppedTestContainer;
|
||||||
|
|
||||||
|
public function restart(): self;
|
||||||
|
|
||||||
|
public function getClient(): Docker;
|
||||||
|
|
||||||
|
public function getHost(): string;
|
||||||
|
|
||||||
|
public function getFirstMappedPort(): int;
|
||||||
|
|
||||||
|
public function getMappedPort(int $port): int;
|
||||||
|
|
||||||
|
public function getName(): string;
|
||||||
|
|
||||||
|
public function getLabels(): array;
|
||||||
|
|
||||||
|
public function getId(): string;
|
||||||
|
|
||||||
|
public function getLastExecId(): string | null;
|
||||||
|
|
||||||
|
public function getNetworkNames(): array;
|
||||||
|
|
||||||
|
public function getNetworkId(string $networkName): string;
|
||||||
|
|
||||||
|
public function getIpAddress(string $networkName): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $command
|
||||||
|
*/
|
||||||
|
public function exec(array $command): string;
|
||||||
|
|
||||||
|
public function logs(): string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
|
class StoppedGenericContainer implements StoppedTestContainer
|
||||||
|
{
|
||||||
|
public function __construct(protected readonly string $id)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getId(): string
|
||||||
|
{
|
||||||
|
return $this->id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
|
interface StoppedTestContainer
|
||||||
|
{
|
||||||
|
public function getId(): string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Container;
|
||||||
|
|
||||||
|
use Testcontainers\Wait\WaitStrategy;
|
||||||
|
|
||||||
|
interface TestContainer
|
||||||
|
{
|
||||||
|
public function start(): StartedGenericContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: replace with array after deprecated implementation is removed
|
||||||
|
* @param array<string, string>|string $env
|
||||||
|
*/
|
||||||
|
public function withEnvironment(array | string $env, ?string $value): static;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string> $command
|
||||||
|
*/
|
||||||
|
public function withCommand(array $command): static;
|
||||||
|
|
||||||
|
public function withEntrypoint(string $entryPoint): static;
|
||||||
|
|
||||||
|
/** @param int|string|array<int|string> $ports One or more ports to expose. */
|
||||||
|
public function withExposedPorts(...$ports): static;
|
||||||
|
|
||||||
|
public function withWait(WaitStrategy $waitStrategy): static;
|
||||||
|
|
||||||
|
public function withNetwork(string $networkName): static;
|
||||||
|
|
||||||
|
public function withPrivilegedMode(): static;
|
||||||
|
}
|
||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Testcontainers\ContainerRuntime;
|
namespace Testcontainers\ContainerClient;
|
||||||
|
|
||||||
use Docker\Docker as DockerClient;
|
use Docker\Docker as DockerClient;
|
||||||
|
|
||||||
class ContainerRuntimeClient
|
class DockerContainerClient
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* @var DockerClient|null Singleton instance of DockerClient
|
* @var DockerClient|null Singleton instance of DockerClient
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Modules;
|
||||||
|
|
||||||
|
use Testcontainers\Container\GenericContainer;
|
||||||
|
use Testcontainers\Wait\WaitForLog;
|
||||||
|
|
||||||
|
class MariaDBContainer extends GenericContainer
|
||||||
|
{
|
||||||
|
public function __construct(string $version = 'latest', string $mysqlRootPassword = 'root')
|
||||||
|
{
|
||||||
|
parent::__construct('mariadb:' . $version);
|
||||||
|
$this->withExposedPorts(3306);
|
||||||
|
$this->withWait(new WaitForLog('ready for connections'));
|
||||||
|
$this->withEnvironment('MARIADB_ROOT_PASSWORD', $mysqlRootPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use constructor instead
|
||||||
|
* Left for backward compatibility
|
||||||
|
*/
|
||||||
|
public static function make(string $version = 'latest', string $mysqlRootPassword = 'root'): self
|
||||||
|
{
|
||||||
|
return new self($version, $mysqlRootPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withMariaDBUser(string $username, string $password): self
|
||||||
|
{
|
||||||
|
$this->withEnvironment('MARIADB_USER', $username);
|
||||||
|
$this->withEnvironment('MARIADB_PASSWORD', $password);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withMariaDBDatabase(string $database): self
|
||||||
|
{
|
||||||
|
$this->withEnvironment('MARIADB_DATABASE', $database);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Modules;
|
||||||
|
|
||||||
|
use Testcontainers\Container\GenericContainer;
|
||||||
|
use Testcontainers\Wait\WaitForLog;
|
||||||
|
|
||||||
|
class MySQLContainer extends GenericContainer
|
||||||
|
{
|
||||||
|
public function __construct(string $version = 'latest', string $mysqlRootPassword = 'root')
|
||||||
|
{
|
||||||
|
parent::__construct('mysql:' . $version);
|
||||||
|
$this->withExposedPorts(3306);
|
||||||
|
$this->withEnvironment('MYSQL_ROOT_PASSWORD', $mysqlRootPassword);
|
||||||
|
$this->withWait(new WaitForLog('ready for connections'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use constructor instead
|
||||||
|
* Left for backward compatibility
|
||||||
|
*/
|
||||||
|
public static function make(string $version = 'latest', string $mysqlRootPassword = 'root'): self
|
||||||
|
{
|
||||||
|
return new self($version, $mysqlRootPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withMySQLUser(string $username, string $password): self
|
||||||
|
{
|
||||||
|
$this->withEnvironment('MYSQL_USER', $username);
|
||||||
|
$this->withEnvironment('MYSQL_PASSWORD', $password);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withMySQLDatabase(string $database): self
|
||||||
|
{
|
||||||
|
$this->withEnvironment('MYSQL_DATABASE', $database);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Modules;
|
||||||
|
|
||||||
|
use Testcontainers\Container\GenericContainer;
|
||||||
|
use Testcontainers\Wait\WaitForLog;
|
||||||
|
|
||||||
|
class OpenSearchContainer extends GenericContainer
|
||||||
|
{
|
||||||
|
public function __construct(string $version = 'latest')
|
||||||
|
{
|
||||||
|
parent::__construct('opensearchproject/opensearch:' . $version);
|
||||||
|
$this->withExposedPorts(9200);
|
||||||
|
$this->withEnvironment('discovery.type', 'single-node');
|
||||||
|
$this->withEnvironment('OPENSEARCH_INITIAL_ADMIN_PASSWORD', 'c3o_ZPHo!');
|
||||||
|
$this->withWait(new WaitForLog(
|
||||||
|
'/\]\s+started\?\[/',
|
||||||
|
true,
|
||||||
|
30000
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use constructor instead
|
||||||
|
* Left for backward compatibility
|
||||||
|
*/
|
||||||
|
public static function make(string $version = 'latest'): self
|
||||||
|
{
|
||||||
|
return new self($version);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withDisabledSecurityPlugin(): self
|
||||||
|
{
|
||||||
|
$this->withEnvironment('plugins.security.disabled', 'true');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use withDisabledSecurityPlugin instead
|
||||||
|
*/
|
||||||
|
public function disableSecurityPlugin(): self
|
||||||
|
{
|
||||||
|
return $this->withDisabledSecurityPlugin();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Modules;
|
||||||
|
|
||||||
|
use Testcontainers\Container\GenericContainer;
|
||||||
|
use Testcontainers\Wait\WaitForExec;
|
||||||
|
|
||||||
|
class PostgresContainer extends GenericContainer
|
||||||
|
{
|
||||||
|
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->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]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use constructor instead
|
||||||
|
* Left for backward compatibility
|
||||||
|
*/
|
||||||
|
public static function make(string $version = 'latest', string $dbPassword = 'root'): self
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
version: $version,
|
||||||
|
password: $dbPassword
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPostgresUser(string $username): self
|
||||||
|
{
|
||||||
|
$this->withEnvironment('POSTGRES_USER', $username);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withPostgresDatabase(string $database): self
|
||||||
|
{
|
||||||
|
$this->withEnvironment('POSTGRES_DB', $database);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Modules;
|
||||||
|
|
||||||
|
use Testcontainers\Container\GenericContainer;
|
||||||
|
use Testcontainers\Wait\WaitForLog;
|
||||||
|
|
||||||
|
class RedisContainer extends GenericContainer
|
||||||
|
{
|
||||||
|
public function __construct(string $version = 'latest')
|
||||||
|
{
|
||||||
|
parent::__construct('redis:' . $version);
|
||||||
|
$this->withExposedPorts(6379);
|
||||||
|
$this->withWait(new WaitForLog('Ready to accept connections'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use constructor instead
|
||||||
|
* Left for backward compatibility
|
||||||
|
*/
|
||||||
|
public static function make(string $version = 'latest'): self
|
||||||
|
{
|
||||||
|
return new self($version);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Utils\PortGenerator;
|
||||||
|
|
||||||
|
class FixedPortGenerator implements PortGenerator
|
||||||
|
{
|
||||||
|
protected int $portIndex = 0;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
/** @var int[] */
|
||||||
|
protected array $ports
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generatePort(): int
|
||||||
|
{
|
||||||
|
if (!isset($this->ports[$this->portIndex])) {
|
||||||
|
throw new \RuntimeException('No more ports available in the fixed list.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->ports[$this->portIndex++];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Utils\PortGenerator;
|
||||||
|
|
||||||
|
interface PortGenerator
|
||||||
|
{
|
||||||
|
public function generatePort(): int;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Utils\PortGenerator;
|
||||||
|
|
||||||
|
class RandomPortGenerator implements PortGenerator
|
||||||
|
{
|
||||||
|
public function generatePort(): int
|
||||||
|
{
|
||||||
|
return $this->getRandomPort($this->randomBetweenInclusive(10000, 65535));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function randomBetweenInclusive(int $min, int $max): int
|
||||||
|
{
|
||||||
|
return random_int($min, $max);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getRandomPort(int $port): int
|
||||||
|
{
|
||||||
|
$connection = @fsockopen("localhost", $port);
|
||||||
|
|
||||||
|
if (is_resource($connection)) {
|
||||||
|
fclose($connection);
|
||||||
|
throw new \RuntimeException("Port $port is already in use.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $port;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Utils\PortGenerator;
|
||||||
|
|
||||||
|
class RandomUniquePortGenerator implements PortGenerator
|
||||||
|
{
|
||||||
|
/** @var int[] */
|
||||||
|
protected static array $assignedPorts = [];
|
||||||
|
|
||||||
|
public function __construct(protected PortGenerator $portGenerator = new RandomPortGenerator())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function generatePort(): int
|
||||||
|
{
|
||||||
|
do {
|
||||||
|
$port = $this->portGenerator->generatePort();
|
||||||
|
} while (in_array($port, self::$assignedPorts));
|
||||||
|
|
||||||
|
self::$assignedPorts[] = $port;
|
||||||
|
|
||||||
|
return $port;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<?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;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Wait;
|
||||||
|
|
||||||
|
use Testcontainers\Container\StartedTestContainer;
|
||||||
|
|
||||||
|
abstract class BaseWaitStrategy implements WaitStrategy
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __construct(protected int $timeout = 10000, protected int $pollInterval = 500)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function wait(StartedTestContainer $container): void;
|
||||||
|
}
|
||||||
@@ -5,16 +5,18 @@ declare(strict_types=1);
|
|||||||
namespace Testcontainers\Wait;
|
namespace Testcontainers\Wait;
|
||||||
|
|
||||||
use Docker\API\Model\ContainersIdJsonGetResponse200;
|
use Docker\API\Model\ContainersIdJsonGetResponse200;
|
||||||
|
use Testcontainers\Container\StartedTestContainer;
|
||||||
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 extends BaseWait
|
class WaitForContainer extends BaseWaitStrategy
|
||||||
{
|
{
|
||||||
public function wait(string $id): void
|
public function wait(StartedTestContainer $container): void
|
||||||
{
|
{
|
||||||
|
$id = $container->getId();
|
||||||
$startTime = microtime(true) * 1000;
|
$startTime = microtime(true) * 1000;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -25,7 +27,7 @@ class WaitForContainerRunning extends BaseWait
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @var ContainersIdJsonGetResponse200 | null $containerInspect */
|
/** @var ContainersIdJsonGetResponse200 | null $containerInspect */
|
||||||
$containerInspect = $this->dockerClient->containerInspect($id);
|
$containerInspect = $container->getClient()->containerInspect($id);
|
||||||
$containerStatus = $containerInspect?->getState()?->getStatus();
|
$containerStatus = $containerInspect?->getState()?->getStatus();
|
||||||
|
|
||||||
if ($containerStatus === 'running') {
|
if ($containerStatus === 'running') {
|
||||||
@@ -5,14 +5,15 @@ 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\ExecIdJsonGetResponse200;
|
||||||
|
use Testcontainers\Container\StartedTestContainer;
|
||||||
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 WaitForExec extends BaseWait
|
class WaitForExec extends BaseWaitStrategy
|
||||||
{
|
{
|
||||||
protected ContainersIdExecPostBody $execConfig;
|
protected ContainersIdExecPostBody $execConfig;
|
||||||
|
|
||||||
@@ -28,32 +29,23 @@ class WaitForExec extends BaseWait
|
|||||||
parent::__construct($timeout, $pollInterval);
|
parent::__construct($timeout, $pollInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function wait(string $id): void
|
public function wait(StartedTestContainer $container): void
|
||||||
{
|
{
|
||||||
$this->execConfig = (new ContainersIdExecPostBody())
|
|
||||||
->setCmd($this->command)
|
|
||||||
->setAttachStdout(true)
|
|
||||||
->setAttachStderr(true);
|
|
||||||
|
|
||||||
$startTime = microtime(true) * 1000;
|
$startTime = microtime(true) * 1000;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
$elapsedTime = (microtime(true) * 1000) - $startTime;
|
$elapsedTime = (microtime(true) * 1000) - $startTime;
|
||||||
|
|
||||||
if ($elapsedTime > $this->timeout) {
|
if ($elapsedTime > $this->timeout) {
|
||||||
throw new ContainerWaitingTimeoutException($id);
|
throw new ContainerWaitingTimeoutException($container->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create and start the exec command
|
$contents = $container->exec($this->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
|
// Inspect the exec to check the exit code
|
||||||
$execInspect = $this->dockerClient->execInspect($exec->getId());
|
/** @var ExecIdJsonGetResponse200 | null $execInspect */
|
||||||
$exitCode = $execInspect->getExitCode();
|
$execInspect = $container->getClient()->execInspect($container->getLastExecId() ?? '');
|
||||||
|
$exitCode = $execInspect?->getExitCode();
|
||||||
|
|
||||||
// If a custom check function is provided, use it to validate the command output
|
// If a custom check function is provided, use it to validate the command output
|
||||||
if ($this->checkFunction !== null) {
|
if ($this->checkFunction !== null) {
|
||||||
|
|||||||
@@ -5,25 +5,18 @@ declare(strict_types=1);
|
|||||||
namespace Testcontainers\Wait;
|
namespace Testcontainers\Wait;
|
||||||
|
|
||||||
use Docker\Docker;
|
use Docker\Docker;
|
||||||
use Docker\DockerClientFactory;
|
|
||||||
use Http\Client\Socket\Exception\TimeoutException;
|
use Http\Client\Socket\Exception\TimeoutException;
|
||||||
use Testcontainers\ContainerRuntime\ContainerRuntimeClient;
|
use Testcontainers\Container\StartedTestContainer;
|
||||||
use Testcontainers\Exception\ContainerNotReadyException;
|
use Testcontainers\Exception\ContainerNotReadyException;
|
||||||
|
|
||||||
class WaitForHealthCheck implements WaitInterface
|
class WaitForHealthCheck extends BaseWaitStrategy
|
||||||
{
|
{
|
||||||
protected Docker $dockerClient;
|
public function __construct(protected int $timeout = 5000, protected int $pollInterval = 1000)
|
||||||
protected int $timeout;
|
|
||||||
protected int $pollInterval;
|
|
||||||
|
|
||||||
public function __construct(int $timeout = 5000, int $pollInterval = 1000)
|
|
||||||
{
|
{
|
||||||
$this->dockerClient = ContainerRuntimeClient::getDockerClient();
|
parent::__construct($timeout, $pollInterval);
|
||||||
$this->timeout = $timeout;
|
|
||||||
$this->pollInterval = $pollInterval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function wait(string $id): void
|
public function wait(StartedTestContainer $container): void
|
||||||
{
|
{
|
||||||
$startTime = microtime(true) * 1000;
|
$startTime = microtime(true) * 1000;
|
||||||
|
|
||||||
@@ -34,10 +27,11 @@ class WaitForHealthCheck implements WaitInterface
|
|||||||
throw new TimeoutException(sprintf("Health check not healthy after %d ms", $this->timeout));
|
throw new TimeoutException(sprintf("Health check not healthy after %d ms", $this->timeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
$containerInspect = $this->dockerClient->containerInspect($id, [], Docker::FETCH_RESPONSE);
|
/** @var \Psr\Http\Message\ResponseInterface | null $containerInspect */
|
||||||
|
$containerInspect = $container->getClient()->containerInspect($container->getId(), [], Docker::FETCH_RESPONSE);
|
||||||
//$containerStatus = $containerInspect?->getArrayCopy() ?? null;
|
//$containerStatus = $containerInspect?->getArrayCopy() ?? null;
|
||||||
var_dump($containerInspect->getBody()->getContents());
|
var_dump($containerInspect->getBody()->getContents());
|
||||||
$containerStatus='';
|
$containerStatus = '';
|
||||||
if ($containerStatus === 'healthy') {
|
if ($containerStatus === 'healthy') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Testcontainers\Wait;
|
|||||||
use Docker\Docker;
|
use Docker\Docker;
|
||||||
use Testcontainers\Exception\ContainerNotReadyException;
|
use Testcontainers\Exception\ContainerNotReadyException;
|
||||||
|
|
||||||
class WaitForHttp implements WaitInterface
|
class WaitForHttp implements WaitStrategy
|
||||||
{
|
{
|
||||||
public const METHOD_GET = 'GET';
|
public const METHOD_GET = 'GET';
|
||||||
public const METHOD_POST = 'POST';
|
public const METHOD_POST = 'POST';
|
||||||
|
|||||||
+5
-10
@@ -4,13 +4,13 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Wait;
|
namespace Testcontainers\Wait;
|
||||||
|
|
||||||
use Docker\API\Runtime\Client\Client;
|
use Testcontainers\Container\StartedTestContainer;
|
||||||
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 extends BaseWait
|
class WaitForLog extends BaseWaitStrategy
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected string $message,
|
protected string $message,
|
||||||
@@ -21,7 +21,7 @@ class WaitForLog extends BaseWait
|
|||||||
parent::__construct($timeout, $pollInterval);
|
parent::__construct($timeout, $pollInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function wait(string $id): void
|
public function wait(StartedTestContainer $container): void
|
||||||
{
|
{
|
||||||
$startTime = microtime(true) * 1000;
|
$startTime = microtime(true) * 1000;
|
||||||
|
|
||||||
@@ -29,15 +29,10 @@ class WaitForLog extends BaseWait
|
|||||||
$elapsedTime = (microtime(true) * 1000) - $startTime;
|
$elapsedTime = (microtime(true) * 1000) - $startTime;
|
||||||
|
|
||||||
if ($elapsedTime > $this->timeout) {
|
if ($elapsedTime > $this->timeout) {
|
||||||
throw new ContainerWaitingTimeoutException($id);
|
throw new ContainerWaitingTimeoutException($container->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
$output = $this->dockerClient
|
$output = $container->logs();
|
||||||
->containerLogs($id, ['stdout' => true, 'stderr' => true], Client::FETCH_RESPONSE)
|
|
||||||
?->getBody()
|
|
||||||
->getContents() ?? '';
|
|
||||||
|
|
||||||
$output = preg_replace('/[\x00-\x1F\x7F]/u', '', mb_convert_encoding($output, 'UTF-8', 'UTF-8')) ?? '';
|
|
||||||
|
|
||||||
if ($this->enableRegex) {
|
if ($this->enableRegex) {
|
||||||
if (preg_match($this->message, $output)) {
|
if (preg_match($this->message, $output)) {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use JsonException;
|
|||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Testcontainers\Exception\ContainerNotReadyException;
|
use Testcontainers\Exception\ContainerNotReadyException;
|
||||||
|
|
||||||
final class WaitForTcpPortOpen implements WaitInterface
|
final class WaitForTcpPortOpen implements WaitStrategy
|
||||||
{
|
{
|
||||||
private Docker $dockerClient;
|
private Docker $dockerClient;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Testcontainers\Wait;
|
|
||||||
|
|
||||||
interface WaitInterface
|
|
||||||
{
|
|
||||||
public function wait(string $id): void;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Wait;
|
||||||
|
|
||||||
|
use Testcontainers\Container\StartedTestContainer;
|
||||||
|
|
||||||
|
interface WaitStrategy
|
||||||
|
{
|
||||||
|
public function wait(StartedTestContainer $container): void;
|
||||||
|
}
|
||||||
@@ -5,14 +5,14 @@ declare(strict_types=1);
|
|||||||
namespace Testcontainers\Tests\Integration;
|
namespace Testcontainers\Tests\Integration;
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Testcontainers\Container\GenericContainer;
|
use Testcontainers\Container\StartedTestContainer;
|
||||||
|
|
||||||
abstract class ContainerTestCase extends TestCase
|
abstract class ContainerTestCase extends TestCase
|
||||||
{
|
{
|
||||||
protected static GenericContainer $container;
|
protected static StartedTestContainer $container;
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
{
|
{
|
||||||
self::$container->remove();
|
self::$container->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Tests\Integration;
|
namespace Testcontainers\Tests\Integration;
|
||||||
|
|
||||||
use Testcontainers\Container\MariaDBContainer;
|
use Testcontainers\Modules\MariaDBContainer;
|
||||||
|
|
||||||
class MariaDBContainerTest extends ContainerTestCase
|
class MariaDBContainerTest extends ContainerTestCase
|
||||||
{
|
{
|
||||||
@@ -19,7 +19,11 @@ class MariaDBContainerTest extends ContainerTestCase
|
|||||||
public function testMariaDBContainer(): void
|
public function testMariaDBContainer(): void
|
||||||
{
|
{
|
||||||
$pdo = new \PDO(
|
$pdo = new \PDO(
|
||||||
sprintf('mysql:host=%s;port=3306', self::$container->getAddress()),
|
sprintf(
|
||||||
|
'mysql:host=%s;port=%d',
|
||||||
|
self::$container->getHost(),
|
||||||
|
self::$container->getFirstMappedPort()
|
||||||
|
),
|
||||||
'bar',
|
'bar',
|
||||||
'baz',
|
'baz',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Tests\Integration;
|
namespace Testcontainers\Tests\Integration;
|
||||||
|
|
||||||
use Testcontainers\Container\MySQLContainer;
|
use Testcontainers\Modules\MySQLContainer;
|
||||||
|
|
||||||
class MySQLContainerTest extends ContainerTestCase
|
class MySQLContainerTest extends ContainerTestCase
|
||||||
{
|
{
|
||||||
@@ -19,7 +19,11 @@ class MySQLContainerTest extends ContainerTestCase
|
|||||||
public function testMySQLContainer(): void
|
public function testMySQLContainer(): void
|
||||||
{
|
{
|
||||||
$pdo = new \PDO(
|
$pdo = new \PDO(
|
||||||
sprintf('mysql:host=%s;port=3306', '127.0.0.1'),
|
sprintf(
|
||||||
|
'mysql:host=%s;port=%d',
|
||||||
|
self::$container->getHost(),
|
||||||
|
self::$container->getFirstMappedPort()
|
||||||
|
),
|
||||||
'bar',
|
'bar',
|
||||||
'baz',
|
'baz',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Tests\Integration\OldTests;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Predis\Client;
|
||||||
|
use Testcontainers\Modules\MariaDBContainer;
|
||||||
|
use Testcontainers\Modules\MySQLContainer;
|
||||||
|
use Testcontainers\Modules\OpenSearchContainer;
|
||||||
|
use Testcontainers\Modules\PostgresContainer;
|
||||||
|
use Testcontainers\Modules\RedisContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Old test classes kept to check backward compatibility
|
||||||
|
*/
|
||||||
|
class ContainerTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testMySQL(): void
|
||||||
|
{
|
||||||
|
$container = MySQLContainer::make();
|
||||||
|
$container->withMySQLDatabase('foo');
|
||||||
|
$container->withMySQLUser('bar', 'baz');
|
||||||
|
|
||||||
|
$container->run();
|
||||||
|
|
||||||
|
$pdo = new \PDO(
|
||||||
|
sprintf('mysql:host=%s;port=3306', $container->getAddress()),
|
||||||
|
'bar',
|
||||||
|
'baz',
|
||||||
|
);
|
||||||
|
|
||||||
|
$query = $pdo->query('SHOW databases');
|
||||||
|
|
||||||
|
$this->assertInstanceOf(\PDOStatement::class, $query);
|
||||||
|
|
||||||
|
$databases = $query->fetchAll(\PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
$this->assertContains('foo', $databases);
|
||||||
|
|
||||||
|
$container->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMariaDB(): void
|
||||||
|
{
|
||||||
|
$container = MariaDBContainer::make();
|
||||||
|
$container->withMariaDBDatabase('foo');
|
||||||
|
$container->withMariaDBUser('bar', 'baz');
|
||||||
|
|
||||||
|
$container->run();
|
||||||
|
|
||||||
|
$pdo = new \PDO(
|
||||||
|
sprintf('mysql:host=%s;port=3306', $container->getAddress()),
|
||||||
|
'bar',
|
||||||
|
'baz',
|
||||||
|
);
|
||||||
|
|
||||||
|
$query = $pdo->query('SHOW databases');
|
||||||
|
|
||||||
|
$this->assertInstanceOf(\PDOStatement::class, $query);
|
||||||
|
|
||||||
|
$databases = $query->fetchAll(\PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
$this->assertContains('foo', $databases);
|
||||||
|
|
||||||
|
$container->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRedis(): void
|
||||||
|
{
|
||||||
|
$container = RedisContainer::make();
|
||||||
|
|
||||||
|
$container->run();
|
||||||
|
|
||||||
|
$redis = new Client([
|
||||||
|
'scheme' => 'tcp',
|
||||||
|
'host' => $container->getAddress(),
|
||||||
|
'port' => 6379,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$redis->ping();
|
||||||
|
|
||||||
|
$this->assertTrue($redis->isConnected());
|
||||||
|
|
||||||
|
$container->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \JsonException
|
||||||
|
*/
|
||||||
|
public function testOpenSearch(): void
|
||||||
|
{
|
||||||
|
$container = OpenSearchContainer::make();
|
||||||
|
$container->disableSecurityPlugin();
|
||||||
|
|
||||||
|
$container->run();
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, sprintf('http://%s:%d', $container->getAddress(), 9200));
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
|
||||||
|
$response = (string) curl_exec($ch);
|
||||||
|
|
||||||
|
$this->assertNotEmpty($response);
|
||||||
|
|
||||||
|
/** @var array{cluster_name: string} $data */
|
||||||
|
$data = json_decode($response, true, JSON_THROW_ON_ERROR, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('cluster_name', $data);
|
||||||
|
|
||||||
|
$this->assertEquals('docker-cluster', $data['cluster_name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPostgreSQLContainer(): void
|
||||||
|
{
|
||||||
|
$container = PostgresContainer::make('latest', 'test')
|
||||||
|
->withPostgresUser('test')
|
||||||
|
->withPostgresDatabase('foo')
|
||||||
|
->run();
|
||||||
|
|
||||||
|
|
||||||
|
$pdo = new \PDO(
|
||||||
|
sprintf('pgsql:host=%s;port=5432;dbname=foo', $container->getAddress()),
|
||||||
|
'test',
|
||||||
|
'test',
|
||||||
|
);
|
||||||
|
|
||||||
|
$query = $pdo->query('SELECT datname FROM pg_database');
|
||||||
|
|
||||||
|
$this->assertInstanceOf(\PDOStatement::class, $query);
|
||||||
|
|
||||||
|
$databases = $query->fetchAll(\PDO::FETCH_COLUMN);
|
||||||
|
|
||||||
|
$this->assertContains('foo', $databases);
|
||||||
|
|
||||||
|
$container->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Testcontainers\Tests\Integration\OldTests;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Predis\Client;
|
||||||
|
use Predis\Connection\ConnectionException;
|
||||||
|
use Testcontainers\Container\GenericContainer;
|
||||||
|
use Testcontainers\Exception\ContainerNotReadyException;
|
||||||
|
use Testcontainers\Wait\WaitForExec;
|
||||||
|
use Testcontainers\Wait\WaitForHealthCheck;
|
||||||
|
use Testcontainers\Wait\WaitForHttp;
|
||||||
|
use Testcontainers\Wait\WaitForLog;
|
||||||
|
use Testcontainers\Wait\WaitForTcpPortOpen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Old test classes kept to check backward compatibility
|
||||||
|
*/
|
||||||
|
class WaitStrategyTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testWaitForExec(): void
|
||||||
|
{
|
||||||
|
$container = GenericContainer::make('mysql')
|
||||||
|
->withEnvironment('MYSQL_ROOT_PASSWORD', 'root')
|
||||||
|
->withWait(
|
||||||
|
new WaitForExec([
|
||||||
|
'mysqladmin', 'ping',
|
||||||
|
'-h', '127.0.0.1',
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$container->run();
|
||||||
|
|
||||||
|
$pdo = new \PDO(
|
||||||
|
sprintf('mysql:host=%s;port=3306', $container->getAddress()),
|
||||||
|
'root',
|
||||||
|
'root'
|
||||||
|
);
|
||||||
|
|
||||||
|
$query = $pdo->query('select version()');
|
||||||
|
|
||||||
|
$this->assertInstanceOf(\PDOStatement::class, $query);
|
||||||
|
|
||||||
|
$version = $query->fetchColumn();
|
||||||
|
|
||||||
|
$this->assertNotEmpty($version);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function testWaitForLog(): void
|
||||||
|
// {
|
||||||
|
// $container = GenericContainer::make('redis:6.2.5')
|
||||||
|
// ->withWait(new WaitForLog('Ready to accept connections'));
|
||||||
|
//
|
||||||
|
// $container->run();
|
||||||
|
//
|
||||||
|
// $redis = new Client([
|
||||||
|
// 'scheme' => 'tcp',
|
||||||
|
// 'host' => $container->getAddress(),
|
||||||
|
// 'port' => 6379,
|
||||||
|
// ]);
|
||||||
|
//
|
||||||
|
// $redis->set('foo', 'bar');
|
||||||
|
//
|
||||||
|
// $this->assertEquals('bar', $redis->get('foo'));
|
||||||
|
//
|
||||||
|
// $container->stop();
|
||||||
|
//
|
||||||
|
// $this->expectException(ConnectionException::class);
|
||||||
|
//
|
||||||
|
// $redis->get('foo');
|
||||||
|
//
|
||||||
|
// $container->remove();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public function testWaitForHTTP(): void
|
||||||
|
// {
|
||||||
|
// $container = GenericContainer::make('nginx:alpine')
|
||||||
|
// ->withWait(WaitForHttp::make(80));
|
||||||
|
//
|
||||||
|
// $container->run();
|
||||||
|
//
|
||||||
|
// $ch = curl_init();
|
||||||
|
// curl_setopt($ch, CURLOPT_URL, sprintf('http://%s:%d', $container->getAddress(), 80));
|
||||||
|
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
//
|
||||||
|
// $response = (string) curl_exec($ch);
|
||||||
|
//
|
||||||
|
// curl_close($ch);
|
||||||
|
//
|
||||||
|
// $this->assertNotEmpty($response);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @dataProvider provideWaitForTcpPortOpen
|
||||||
|
// */
|
||||||
|
// public function testWaitForTcpPortOpen(bool $wait): void
|
||||||
|
// {
|
||||||
|
// $container = GenericContainer::make('nginx:alpine');
|
||||||
|
//
|
||||||
|
// if ($wait) {
|
||||||
|
// $container->withWait(WaitForTcpPortOpen::make(80));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// $container->run();
|
||||||
|
//
|
||||||
|
// if ($wait) {
|
||||||
|
// static::assertIsResource(fsockopen($container->getAddress(), 80), 'Failed to connect to container');
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// $containerId = $container->getId();
|
||||||
|
//
|
||||||
|
// $this->expectExceptionObject(new ContainerNotReadyException($containerId));
|
||||||
|
//
|
||||||
|
// (new WaitForTcpPortOpen(8080))->wait($containerId);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// /**
|
||||||
|
// * @return array<string, array<bool>>
|
||||||
|
// */
|
||||||
|
// public function provideWaitForTcpPortOpen(): array
|
||||||
|
// {
|
||||||
|
// return [
|
||||||
|
// 'Can connect to container' => [true],
|
||||||
|
// 'Cannot connect to container' => [false],
|
||||||
|
// ];
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public function testWaitForHealthCheck(): void
|
||||||
|
// {
|
||||||
|
// $container = GenericContainer::make('nginx')
|
||||||
|
// ->withHealthCheckCommand('curl --fail http://localhost')
|
||||||
|
// ->withWait(new WaitForHealthCheck());
|
||||||
|
//
|
||||||
|
// $container->run();
|
||||||
|
//
|
||||||
|
// $ch = curl_init();
|
||||||
|
//
|
||||||
|
// curl_setopt($ch, CURLOPT_URL, sprintf('http://%s:%d', $container->getAddress(), 80));
|
||||||
|
// curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
//
|
||||||
|
// $response = curl_exec($ch);
|
||||||
|
//
|
||||||
|
// $this->assertNotEmpty($response);
|
||||||
|
// $this->assertIsString($response);
|
||||||
|
//
|
||||||
|
// $this->assertStringContainsString('Welcome to nginx!', $response);
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Tests\Integration;
|
namespace Testcontainers\Tests\Integration;
|
||||||
|
|
||||||
use Testcontainers\Container\OpenSearchContainer;
|
use Testcontainers\Modules\OpenSearchContainer;
|
||||||
|
|
||||||
class OpenSearchContainerTest extends ContainerTestCase
|
class OpenSearchContainerTest extends ContainerTestCase
|
||||||
{
|
{
|
||||||
@@ -21,7 +21,11 @@ class OpenSearchContainerTest extends ContainerTestCase
|
|||||||
public function testOpenSearch(): void
|
public function testOpenSearch(): void
|
||||||
{
|
{
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, sprintf('http://%s:%d', '127.0.0.1', 9200));
|
curl_setopt($ch, CURLOPT_URL, sprintf(
|
||||||
|
'http://%s:%d',
|
||||||
|
self::$container->getHost(),
|
||||||
|
self::$container->getFirstMappedPort()
|
||||||
|
));
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
|
||||||
$response = (string) curl_exec($ch);
|
$response = (string) curl_exec($ch);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Testcontainers\Tests\Integration;
|
namespace Testcontainers\Tests\Integration;
|
||||||
|
|
||||||
use Testcontainers\Container\PostgresContainer;
|
use Testcontainers\Modules\PostgresContainer;
|
||||||
|
|
||||||
class PostgreSQLContainerTest extends ContainerTestCase
|
class PostgreSQLContainerTest extends ContainerTestCase
|
||||||
{
|
{
|
||||||
@@ -19,7 +19,11 @@ class PostgreSQLContainerTest extends ContainerTestCase
|
|||||||
public function testPostgreSQLContainer(): void
|
public function testPostgreSQLContainer(): void
|
||||||
{
|
{
|
||||||
$pdo = new \PDO(
|
$pdo = new \PDO(
|
||||||
'pgsql:host=127.0.0.1;port=5432;dbname=foo',
|
sprintf(
|
||||||
|
'pgsql:host=%s;port=%d;dbname=foo',
|
||||||
|
self::$container->getHost(),
|
||||||
|
self::$container->getFirstMappedPort()
|
||||||
|
),
|
||||||
'bar',
|
'bar',
|
||||||
'test',
|
'test',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Testcontainers\Tests\Integration;
|
namespace Testcontainers\Tests\Integration;
|
||||||
|
|
||||||
use Predis\Client;
|
use Predis\Client;
|
||||||
use Testcontainers\Container\RedisContainer;
|
use Testcontainers\Modules\RedisContainer;
|
||||||
|
|
||||||
class RedisContainerTest extends ContainerTestCase
|
class RedisContainerTest extends ContainerTestCase
|
||||||
{
|
{
|
||||||
@@ -18,8 +18,8 @@ class RedisContainerTest extends ContainerTestCase
|
|||||||
public function testRedisContainer(): void
|
public function testRedisContainer(): void
|
||||||
{
|
{
|
||||||
$redisClient = new Client([
|
$redisClient = new Client([
|
||||||
'host' => 'localhost',
|
'host' => self::$container->getHost(),
|
||||||
'port' => 6379,
|
'port' => self::$container->getFirstMappedPort(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$redisClient->ping();
|
$redisClient->ping();
|
||||||
|
|||||||
@@ -1,157 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Testcontainers\Tests\Integration;
|
|
||||||
|
|
||||||
use PHPUnit\Framework\TestCase;
|
|
||||||
use Predis\Client;
|
|
||||||
use Predis\Connection\ConnectionException;
|
|
||||||
use Symfony\Component\Process\Process;
|
|
||||||
use Testcontainers\Container\GenericContainer;
|
|
||||||
use Testcontainers\Exception\ContainerNotReadyException;
|
|
||||||
use Testcontainers\Wait\WaitForExec;
|
|
||||||
use Testcontainers\Wait\WaitForHealthCheck;
|
|
||||||
use Testcontainers\Wait\WaitForHttp;
|
|
||||||
use Testcontainers\Wait\WaitForLog;
|
|
||||||
use Testcontainers\Wait\WaitForTcpPortOpen;
|
|
||||||
|
|
||||||
class WaitStrategyTest extends TestCase
|
|
||||||
{
|
|
||||||
|
|
||||||
public static function tearDownAfterClass(): void
|
|
||||||
{
|
|
||||||
parent::tearDownAfterClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testWaitForExec(): void
|
|
||||||
{
|
|
||||||
$called = false;
|
|
||||||
|
|
||||||
$container = GenericContainer::make('mysql')
|
|
||||||
->withEnvironment('MYSQL_ROOT_PASSWORD', 'root')
|
|
||||||
->withWait(new WaitForExec(['mysqladmin', 'ping', '-h', '127.0.0.1'], function (Process $process) use (&$called) {
|
|
||||||
$called = true;
|
|
||||||
}));
|
|
||||||
|
|
||||||
$container->run();
|
|
||||||
|
|
||||||
$this->assertTrue($called, 'Wait function was not called');
|
|
||||||
unset($called);
|
|
||||||
|
|
||||||
$pdo = new \PDO(
|
|
||||||
sprintf('mysql:host=%s;port=3306', $container->getAddress()),
|
|
||||||
'root',
|
|
||||||
'root'
|
|
||||||
);
|
|
||||||
|
|
||||||
$query = $pdo->query('select version()');
|
|
||||||
|
|
||||||
$this->assertInstanceOf(\PDOStatement::class, $query);
|
|
||||||
|
|
||||||
$version = $query->fetchColumn();
|
|
||||||
|
|
||||||
$this->assertNotEmpty($version);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testWaitForLog(): void
|
|
||||||
{
|
|
||||||
$container = GenericContainer::make('redis:6.2.5')
|
|
||||||
->withWait(new WaitForLog('Ready to accept connections'));
|
|
||||||
|
|
||||||
$container->run();
|
|
||||||
|
|
||||||
$redis = new Client([
|
|
||||||
'scheme' => 'tcp',
|
|
||||||
'host' => $container->getAddress(),
|
|
||||||
'port' => 6379,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$redis->set('foo', 'bar');
|
|
||||||
|
|
||||||
$this->assertEquals('bar', $redis->get('foo'));
|
|
||||||
|
|
||||||
$container->stop();
|
|
||||||
|
|
||||||
$this->expectException(ConnectionException::class);
|
|
||||||
|
|
||||||
$redis->get('foo');
|
|
||||||
|
|
||||||
$container->remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testWaitForHTTP(): void
|
|
||||||
{
|
|
||||||
$container = GenericContainer::make('nginx:alpine')
|
|
||||||
->withWait(WaitForHttp::make(80));
|
|
||||||
|
|
||||||
$container->run();
|
|
||||||
|
|
||||||
$ch = curl_init();
|
|
||||||
curl_setopt($ch, CURLOPT_URL, sprintf('http://%s:%d', $container->getAddress(), 80));
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
|
|
||||||
$response = (string) curl_exec($ch);
|
|
||||||
|
|
||||||
curl_close($ch);
|
|
||||||
|
|
||||||
$this->assertNotEmpty($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dataProvider provideWaitForTcpPortOpen
|
|
||||||
*/
|
|
||||||
public function testWaitForTcpPortOpen(bool $wait): void
|
|
||||||
{
|
|
||||||
$container = GenericContainer::make('nginx:alpine');
|
|
||||||
|
|
||||||
if ($wait) {
|
|
||||||
$container->withWait(WaitForTcpPortOpen::make(80));
|
|
||||||
}
|
|
||||||
|
|
||||||
$container->run();
|
|
||||||
|
|
||||||
if ($wait) {
|
|
||||||
static::assertIsResource(fsockopen($container->getAddress(), 80), 'Failed to connect to container');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$containerId = $container->getId();
|
|
||||||
|
|
||||||
$this->expectExceptionObject(new ContainerNotReadyException($containerId));
|
|
||||||
|
|
||||||
(new WaitForTcpPortOpen(8080))->wait($containerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<string, array<bool>>
|
|
||||||
*/
|
|
||||||
public function provideWaitForTcpPortOpen(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'Can connect to container' => [true],
|
|
||||||
'Cannot connect to container' => [false],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testWaitForHealthCheck(): void
|
|
||||||
{
|
|
||||||
$container = GenericContainer::make('nginx')
|
|
||||||
->withHealthCheckCommand('curl --fail http://localhost')
|
|
||||||
->withWait(new WaitForHealthCheck());
|
|
||||||
|
|
||||||
$container->run();
|
|
||||||
|
|
||||||
$ch = curl_init();
|
|
||||||
|
|
||||||
curl_setopt($ch, CURLOPT_URL, sprintf('http://%s:%d', $container->getAddress(), 80));
|
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
||||||
|
|
||||||
$response = curl_exec($ch);
|
|
||||||
|
|
||||||
$this->assertNotEmpty($response);
|
|
||||||
$this->assertIsString($response);
|
|
||||||
|
|
||||||
$this->assertStringContainsString('Welcome to nginx!', $response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user