Files
api/services/nginx/app/tests/Support/Api/ApiServer.php
T

151 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Support\Api;
use RuntimeException;
final class ApiServer
{
private mixed $process = null;
private string $stdoutLog;
private string $stderrLog;
public function __construct(
private readonly string $workingDirectory,
private readonly string $host,
private readonly int $port,
) {
$logDirectory = app_path('build/logs');
if (!is_dir($logDirectory)) {
mkdir($logDirectory, 0777, true);
}
$this->stdoutLog = $logDirectory . DIRECTORY_SEPARATOR . 'api-server.out.log';
$this->stderrLog = $logDirectory . DIRECTORY_SEPARATOR . 'api-server.err.log';
}
public function start(): void
{
$command = sprintf(
'%s -S %s index.php',
escapeshellarg((string)(PHP_BINARY ?: 'php')),
escapeshellarg($this->host . ':' . $this->port),
);
$environment = $this->inheritEnvironment();
$descriptorSpec = [
0 => ['pipe', 'r'],
1 => ['file', $this->stdoutLog, 'a'],
2 => ['file', $this->stderrLog, 'a'],
];
$this->process = proc_open($command, $descriptorSpec, $pipes, $this->workingDirectory, $environment);
if (!is_resource($this->process)) {
throw new RuntimeException('Unable to start the PHP test server.');
}
if (isset($pipes[0]) && is_resource($pipes[0])) {
fclose($pipes[0]);
}
}
public function isRunning(): bool
{
if (!is_resource($this->process)) {
return false;
}
$status = proc_get_status($this->process);
return (bool)($status['running'] ?? false);
}
public function stop(): void
{
if (!is_resource($this->process)) {
return;
}
proc_terminate($this->process);
usleep(250000);
$status = proc_get_status($this->process);
if (($status['running'] ?? false) && function_exists('posix_kill')) {
@posix_kill((int)$status['pid'], 9);
}
proc_close($this->process);
$this->process = null;
}
public function describeFailure(string $message): string
{
$stderr = is_file($this->stderrLog) ? trim((string)file_get_contents($this->stderrLog)) : '';
if ($stderr !== '') {
return $message . PHP_EOL . $stderr;
}
return $message;
}
public static function findAvailablePort(string $host): int
{
$socket = @stream_socket_server('tcp://' . $host . ':0', $errorCode, $errorMessage);
if ($socket === false) {
throw new RuntimeException(
sprintf('Unable to reserve an API test port on %s: %s (%d)', $host, $errorMessage, $errorCode)
);
}
$address = stream_socket_get_name($socket, false);
fclose($socket);
if (!is_string($address) || $address === '') {
throw new RuntimeException('Unable to determine the reserved API test port.');
}
$lastSeparator = strrpos($address, ':');
if ($lastSeparator === false) {
throw new RuntimeException('Unable to parse the reserved API test port.');
}
$port = (int)substr($address, $lastSeparator + 1);
if ($port <= 0) {
throw new RuntimeException('Unable to parse a valid API test port.');
}
return $port;
}
/**
* @return array<string, string>
*/
private function inheritEnvironment(): array
{
$environment = [];
$sources = [getenv(), $_ENV ?? [], $_SERVER ?? []];
foreach ($sources as $source) {
if (!is_array($source)) {
continue;
}
foreach ($source as $key => $value) {
if (!is_string($key) || $key === '') {
continue;
}
if (!is_scalar($value) && $value !== null) {
continue;
}
$environment[$key] = $value === null ? '' : (string)$value;
}
}
return $environment;
}
}