mirror of
https://github.com/stan220/testcontainers-php.git
synced 2026-09-08 17:29:29 +00:00
fixes and improvements
This commit is contained in:
@@ -232,10 +232,12 @@ class GenericContainer implements TestContainer
|
||||
$this->id = $containerCreateResponse?->getId() ?? '';
|
||||
} catch (ContainerCreateNotFoundException) {
|
||||
/** @var CreateImageStream $imageCreateResponse */
|
||||
$this->dockerClient->imageCreate(null, [
|
||||
$imageCreateResponse = $this->dockerClient->imageCreate(null, [
|
||||
'fromImage' => explode(':', $this->image)[0],
|
||||
'tag' => explode(':', $this->image)[1] ?? 'latest',
|
||||
]);
|
||||
$imageCreateResponse->wait();
|
||||
|
||||
return $this->start();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Testcontainers\Container;
|
||||
|
||||
use Testcontainers\Wait\WaitForLog;
|
||||
use Testcontainers\Wait\WaitForExec;
|
||||
|
||||
/**
|
||||
* Left for namespace backward compatibility
|
||||
@@ -17,8 +17,12 @@ class MariaDBContainer extends Container
|
||||
{
|
||||
parent::__construct('mariadb:' . $version);
|
||||
$this->withExposedPorts(3306);
|
||||
$this->withWait(new WaitForLog('ready for connections'));
|
||||
$this->withEnvironment('MARIADB_ROOT_PASSWORD', $mysqlRootPassword);
|
||||
$this->withWait(new WaitForExec([
|
||||
"mariadb-admin",
|
||||
"ping",
|
||||
"-h", "127.0.0.1",
|
||||
]));
|
||||
}
|
||||
|
||||
public static function make(string $version = 'latest', string $mysqlRootPassword = 'root'): self
|
||||
@@ -40,4 +44,4 @@ class MariaDBContainer extends Container
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Testcontainers\Container;
|
||||
|
||||
use Testcontainers\Wait\WaitForLog;
|
||||
use Testcontainers\Wait\WaitForExec;
|
||||
|
||||
/**
|
||||
* Left for namespace backward compatibility
|
||||
@@ -18,7 +18,11 @@ class MySQLContainer extends Container
|
||||
parent::__construct('mysql:' . $version);
|
||||
$this->withExposedPorts(3306);
|
||||
$this->withEnvironment('MYSQL_ROOT_PASSWORD', $mysqlRootPassword);
|
||||
$this->withWait(new WaitForLog('ready for connections'));
|
||||
$this->withWait(new WaitForExec([
|
||||
"mysqladmin",
|
||||
"ping",
|
||||
"-h", "127.0.0.1",
|
||||
]));
|
||||
}
|
||||
|
||||
public static function make(string $version = 'latest', string $mysqlRootPassword = 'root'): self
|
||||
|
||||
@@ -6,10 +6,10 @@ namespace Testcontainers\Container;
|
||||
|
||||
use Docker\API\Client;
|
||||
use Docker\API\Model\ContainersIdExecPostBody;
|
||||
use Docker\API\Model\ContainersIdJsonGetResponse200;
|
||||
use Docker\API\Model\IdResponse;
|
||||
use Docker\API\Runtime\Client\Client as DockerRuntimeClient;
|
||||
use Docker\Docker;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Testcontainers\ContainerClient\DockerContainerClient;
|
||||
|
||||
class StartedGenericContainer implements StartedTestContainer
|
||||
@@ -53,7 +53,7 @@ class StartedGenericContainer implements StartedTestContainer
|
||||
/** @var IdResponse | null $exec */
|
||||
$exec = $this->dockerClient->containerExec($this->id, $execConfig);
|
||||
|
||||
if($exec === null || $exec->getId() === null) {
|
||||
if ($exec === null || $exec->getId() === null) {
|
||||
throw new \RuntimeException('Failed to create exec command');
|
||||
}
|
||||
|
||||
@@ -108,17 +108,36 @@ class StartedGenericContainer implements StartedTestContainer
|
||||
return $this->inspect()->ports[$port];
|
||||
}
|
||||
|
||||
//TODO: not ready yet
|
||||
/**
|
||||
* @throws \JsonException
|
||||
*/
|
||||
public function getFirstMappedPort(): int
|
||||
{
|
||||
/** @var ContainersIdJsonGetResponse200 | null $containerInspectResponse */
|
||||
$containerInspectResponse = $this->dockerClient->containerInspect($this->id);
|
||||
$settings = $containerInspectResponse->getNetworkSettings();
|
||||
//For some reason, containerInspect can crash when using FETCH_OBJECT option (e.g. with OpenSearch)
|
||||
//should be checked within beluga-php/docker-php client library
|
||||
/** @var ResponseInterface | null $containerInspectResponse */
|
||||
$containerInspectResponse = $this->dockerClient->containerInspect($this->id, [], Docker::FETCH_RESPONSE);
|
||||
if ($containerInspectResponse === null) {
|
||||
throw new \RuntimeException('Failed to inspect container');
|
||||
}
|
||||
|
||||
$containerInspectResponseAsArray = json_decode(
|
||||
$containerInspectResponse->getBody()->getContents(),
|
||||
true,
|
||||
512,
|
||||
JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
/** @var array<string, array<array<string, string>>> $ports */
|
||||
$ports = $containerInspectResponseAsArray['NetworkSettings']['Ports'] ?? [];
|
||||
|
||||
if ($ports === []) {
|
||||
throw new \RuntimeException('Failed to get ports from container');
|
||||
}
|
||||
|
||||
$ports = (array)$settings->getPorts();
|
||||
$port = array_key_first($ports);
|
||||
|
||||
return (int) $ports[$port][0]->getHostPort();
|
||||
return (int) $ports[$port][0]['HostPort'];
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Testcontainers\ContainerClient;
|
||||
|
||||
use Docker\Docker as DockerClient;
|
||||
@@ -11,9 +13,6 @@ class DockerContainerClient
|
||||
*/
|
||||
private static ?DockerClient $dockerClient = null;
|
||||
|
||||
/**
|
||||
* Private constructor to prevent creating instance outside the class.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace Testcontainers\Modules;
|
||||
|
||||
use Testcontainers\Container\GenericContainer;
|
||||
use Testcontainers\Wait\WaitForLog;
|
||||
use Testcontainers\Wait\WaitForExec;
|
||||
|
||||
class MariaDBContainer extends GenericContainer
|
||||
{
|
||||
@@ -13,8 +13,12 @@ class MariaDBContainer extends GenericContainer
|
||||
{
|
||||
parent::__construct('mariadb:' . $version);
|
||||
$this->withExposedPorts(3306);
|
||||
$this->withWait(new WaitForLog('ready for connections'));
|
||||
$this->withEnvironment('MARIADB_ROOT_PASSWORD', $mysqlRootPassword);
|
||||
$this->withWait(new WaitForExec([
|
||||
"mariadb-admin",
|
||||
"ping",
|
||||
"-h", "127.0.0.1",
|
||||
]));
|
||||
}
|
||||
|
||||
public function withMariaDBUser(string $username, string $password): self
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace Testcontainers\Modules;
|
||||
|
||||
use Testcontainers\Container\GenericContainer;
|
||||
use Testcontainers\Wait\WaitForLog;
|
||||
use Testcontainers\Wait\WaitForExec;
|
||||
|
||||
class MySQLContainer extends GenericContainer
|
||||
{
|
||||
@@ -14,7 +14,11 @@ class MySQLContainer extends GenericContainer
|
||||
parent::__construct('mysql:' . $version);
|
||||
$this->withExposedPorts(3306);
|
||||
$this->withEnvironment('MYSQL_ROOT_PASSWORD', $mysqlRootPassword);
|
||||
$this->withWait(new WaitForLog('ready for connections'));
|
||||
$this->withWait(new WaitForExec([
|
||||
"mysqladmin",
|
||||
"ping",
|
||||
"-h", "127.0.0.1",
|
||||
]));
|
||||
}
|
||||
|
||||
public function withMySQLUser(string $username, string $password): self
|
||||
|
||||
@@ -27,4 +27,4 @@ class RandomPortGenerator implements PortGenerator
|
||||
|
||||
return $port;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ use Testcontainers\Container\StartedTestContainer;
|
||||
|
||||
abstract class BaseWaitStrategy implements WaitStrategy
|
||||
{
|
||||
|
||||
public function __construct(protected int $timeout = 10000, protected int $pollInterval = 500)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ declare(strict_types=1);
|
||||
namespace Testcontainers\Wait;
|
||||
|
||||
use Closure;
|
||||
use Docker\API\Model\ContainersIdExecPostBody;
|
||||
use Docker\API\Model\ExecIdJsonGetResponse200;
|
||||
use Testcontainers\Container\StartedTestContainer;
|
||||
use Testcontainers\Exception\ContainerWaitingTimeoutException;
|
||||
@@ -15,8 +14,6 @@ use Testcontainers\Exception\ContainerWaitingTimeoutException;
|
||||
*/
|
||||
class WaitForExec extends BaseWaitStrategy
|
||||
{
|
||||
protected ContainersIdExecPostBody $execConfig;
|
||||
|
||||
/**
|
||||
* @param array<string> $command
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,7 @@ use Http\Client\Socket\Exception\TimeoutException;
|
||||
use Testcontainers\Container\StartedTestContainer;
|
||||
use Testcontainers\Exception\ContainerNotReadyException;
|
||||
|
||||
//TODO: not ready yet
|
||||
class WaitForHealthCheck extends BaseWaitStrategy
|
||||
{
|
||||
public function __construct(protected int $timeout = 5000, protected int $pollInterval = 1000)
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Testcontainers\Wait;
|
||||
use Docker\Docker;
|
||||
use Testcontainers\Exception\ContainerNotReadyException;
|
||||
|
||||
//TODO: not ready yet
|
||||
class WaitForHttp implements WaitStrategy
|
||||
{
|
||||
public const METHOD_GET = 'GET';
|
||||
@@ -61,7 +62,7 @@ class WaitForHttp implements WaitStrategy
|
||||
$containerNetworks = $this->dockerClient->containerInspect($id)->getNetworkSettings()->getNetworks();
|
||||
$containerAddress = null;
|
||||
foreach ($containerNetworks as $network) {
|
||||
if($network->getNetworkID() === $id) {
|
||||
if ($network->getNetworkID() === $id) {
|
||||
$containerAddress = $network->getIpAddress();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use JsonException;
|
||||
use RuntimeException;
|
||||
use Testcontainers\Exception\ContainerNotReadyException;
|
||||
|
||||
//TODO: not ready yet
|
||||
final class WaitForTcpPortOpen implements WaitStrategy
|
||||
{
|
||||
private Docker $dockerClient;
|
||||
|
||||
@@ -18,6 +18,7 @@ use Testcontainers\Container\RedisContainer;
|
||||
class ContainerTest extends TestCase
|
||||
{
|
||||
//TODO: remove after check
|
||||
//To make it work, fixed port should be first implemented
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->markTestIncomplete();
|
||||
|
||||
@@ -54,104 +54,104 @@ class WaitStrategyTest extends TestCase
|
||||
$this->assertNotEmpty($version);
|
||||
}
|
||||
|
||||
public function testWaitForLog(): void
|
||||
{
|
||||
$container = Container::make('redis:6.2.5')
|
||||
->withWait(new WaitForLog('Ready to accept connections'));
|
||||
public function testWaitForLog(): void
|
||||
{
|
||||
$container = Container::make('redis:6.2.5')
|
||||
->withWait(new WaitForLog('Ready to accept connections'));
|
||||
|
||||
$container->run();
|
||||
$container->run();
|
||||
|
||||
$redis = new Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => $container->getAddress(),
|
||||
'port' => 6379,
|
||||
]);
|
||||
$redis = new Client([
|
||||
'scheme' => 'tcp',
|
||||
'host' => $container->getAddress(),
|
||||
'port' => 6379,
|
||||
]);
|
||||
|
||||
$redis->set('foo', 'bar');
|
||||
$redis->set('foo', 'bar');
|
||||
|
||||
$this->assertEquals('bar', $redis->get('foo'));
|
||||
$this->assertEquals('bar', $redis->get('foo'));
|
||||
|
||||
$container->stop();
|
||||
$container->stop();
|
||||
|
||||
$this->expectException(ConnectionException::class);
|
||||
$this->expectException(ConnectionException::class);
|
||||
|
||||
$redis->get('foo');
|
||||
$redis->get('foo');
|
||||
|
||||
$container->remove();
|
||||
$container->remove();
|
||||
}
|
||||
|
||||
public function testWaitForHTTP(): void
|
||||
{
|
||||
$container = Container::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 = Container::make('nginx:alpine');
|
||||
|
||||
if ($wait) {
|
||||
$container->withWait(WaitForTcpPortOpen::make(80));
|
||||
}
|
||||
|
||||
public function testWaitForHTTP(): void
|
||||
{
|
||||
$container = Container::make('nginx:alpine')
|
||||
->withWait(WaitForHttp::make(80));
|
||||
$container->run();
|
||||
|
||||
$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);
|
||||
if ($wait) {
|
||||
static::assertIsResource(fsockopen($container->getAddress(), 80), 'Failed to connect to container');
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider provideWaitForTcpPortOpen
|
||||
*/
|
||||
public function testWaitForTcpPortOpen(bool $wait): void
|
||||
{
|
||||
$container = Container::make('nginx:alpine');
|
||||
$containerId = $container->getId();
|
||||
|
||||
if ($wait) {
|
||||
$container->withWait(WaitForTcpPortOpen::make(80));
|
||||
}
|
||||
$this->expectExceptionObject(new ContainerNotReadyException($containerId));
|
||||
|
||||
$container->run();
|
||||
(new WaitForTcpPortOpen(8080))->wait($containerId);
|
||||
}
|
||||
|
||||
if ($wait) {
|
||||
static::assertIsResource(fsockopen($container->getAddress(), 80), 'Failed to connect to container');
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* @return array<string, array<bool>>
|
||||
*/
|
||||
public function provideWaitForTcpPortOpen(): array
|
||||
{
|
||||
return [
|
||||
'Can connect to container' => [true],
|
||||
'Cannot connect to container' => [false],
|
||||
];
|
||||
}
|
||||
|
||||
$containerId = $container->getId();
|
||||
public function testWaitForHealthCheck(): void
|
||||
{
|
||||
$container = Container::make('nginx')
|
||||
->withHealthCheckCommand('curl --fail http://localhost')
|
||||
->withWait(new WaitForHealthCheck());
|
||||
|
||||
$this->expectExceptionObject(new ContainerNotReadyException($containerId));
|
||||
$container->run();
|
||||
|
||||
(new WaitForTcpPortOpen(8080))->wait($containerId);
|
||||
}
|
||||
$ch = curl_init();
|
||||
|
||||
/**
|
||||
* @return array<string, array<bool>>
|
||||
*/
|
||||
public function provideWaitForTcpPortOpen(): array
|
||||
{
|
||||
return [
|
||||
'Can connect to container' => [true],
|
||||
'Cannot connect to container' => [false],
|
||||
];
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_URL, sprintf('http://%s:%d', $container->getAddress(), 80));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
public function testWaitForHealthCheck(): void
|
||||
{
|
||||
$container = Container::make('nginx')
|
||||
->withHealthCheckCommand('curl --fail http://localhost')
|
||||
->withWait(new WaitForHealthCheck());
|
||||
$response = curl_exec($ch);
|
||||
|
||||
$container->run();
|
||||
$this->assertNotEmpty($response);
|
||||
$this->assertIsString($response);
|
||||
|
||||
$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);
|
||||
}
|
||||
$this->assertStringContainsString('Welcome to nginx!', $response);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user