Files
XC_VM/tests/Support/RedisServer.php
T
obscuremindandClaude Opus 5.5 2151440ea2 test: make the "store is down" Redis client unreachable on phpredis 6
Three tests stand a bare `new \Redis()` in for a store that cannot be
read. That holds on phpredis 5, where every command of such an object
throws. Since phpredis 6 the object dials 127.0.0.1:6379 on its first
command, so the tests passed only while nothing answered there. The
PHPUnit job installs a Redis on that port, and ConnectionTouchTest
failed once on a pull request that does not touch the cluster code
(#363).

RedisServer::unreachable() returns a client whose connect() to a
missing socket failed: it dials nothing on either version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AneTzAHPPkYWPQjwyZ2td9
2026-10-11 17:26:07 +00:00

65 lines
2.3 KiB
PHP

<?php
namespace XcVm\Tests\Support;
/**
* A throwaway redis-server on 127.0.0.1 for tests, in the foreground and owned
* by the test process (a daemon would outlive a failed run and hold the test's
* output pipe open), never persisted.
*/
final class RedisServer {
/**
* Start one in $rDir, on a port the kernel picks free (a random one could be
* taken), giving it up to 10 s to listen: a busy CI runner was slower than
* 2.5 s (Connection refused). One that never listens is a redis not available.
*
* @param list<string> $rArgs extra redis-server arguments
* @param string|null $rSocket a unix socket ($rArgs' --unixsocket) to wait for as well
* @return array{0: resource, 1: int}|null the process and its port, or null (the caller skips)
*/
public static function start(string $rDir, array $rArgs = [], ?string $rSocket = null): ?array {
$rProbe = stream_socket_server('tcp://127.0.0.1:0');
if ($rProbe === false) {
return null;
}
$rPort = (int) substr((string) strrchr((string) stream_socket_get_name($rProbe, false), ':'), 1);
fclose($rProbe);
$rNull = ['file', '/dev/null', 'w'];
$rProc = proc_open(array_merge(['redis-server', '--port', (string) $rPort, '--bind', '127.0.0.1', '--save', '', '--appendonly', 'no', '--dir', $rDir], $rArgs), [0 => ['file', '/dev/null', 'r'], 1 => $rNull, 2 => $rNull], $rPipes);
if ($rProc === false) {
return null;
}
$rUp = static fn (): bool => @fsockopen('127.0.0.1', $rPort) !== false && ($rSocket === null || file_exists($rSocket));
for ($i = 0; $i < 200 && !$rUp(); $i++) {
usleep(50000);
}
if (!$rUp()) {
self::stop($rProc);
return null;
}
return [$rProc, $rPort];
}
/**
* A client of no server: every command throws. A bare `new \Redis()` is not
* one since phpredis 6, where its first command dials 127.0.0.1:6379 and is
* answered by whatever Redis the machine runs there. One whose connect()
* failed dials nothing, on phpredis 5 and 6 alike.
*/
public static function unreachable(): \Redis {
$rRedis = new \Redis();
try {
@$rRedis->connect('/nonexistent/xcvm-test-redis.sock');
} catch (\RedisException) {
// what a missing socket answers
}
return $rRedis;
}
/** @param resource $rProc */
public static function stop($rProc): void {
proc_terminate($rProc);
proc_close($rProc);
}
}