Files
php-src/tests/probe_cache.inc
NickSdot 5d58876192 tests: Cache failed service probes (#22944)
Prevents unavailable optional test services from slowing the suite through repeated connection timeouts. PDO and SNMP availability probes cache the first failure for the duration of one test run, so later SKIPIF checks return immediately.

Only failures are cached; successful probes and test connections are unchanged. PDOs one second timeout applies only to the default availability probe. The caches are private, run-scoped and removed afterwards. A service becoming available mid run is detected on the next test-suite run.
2026-08-06 11:42:56 +02:00

74 lines
2.1 KiB
PHP

<?php
final class ProbeFailureException extends Exception
{
public function __construct(string|Throwable $failure, ?Throwable $previous = null)
{
if ($failure instanceof Throwable) {
$previous = $failure;
$failure = $failure->getMessage();
}
parent::__construct($failure, 0, $previous);
}
}
final class ProbeCache
{
private const FAILURE_PREFIX = 'failure:';
/**
* Runs a probe once per configuration during SKIPIF, caching only failures.
* Outside SKIPIF, the cache is bypassed and wrapped exceptions are rethrown.
*/
public static function getFailure(string $namespace, array $configuration, callable $probe): mixed
{
if (getenv('TEST_PHP_EVALUATING_SKIPIF') !== '1') {
try {
return $probe();
} catch (ProbeFailureException $e) {
throw $e->getPrevious() ?? $e;
}
}
$directory = getenv('TEST_PHP_SHARED_CACHE_DIR');
if (!is_string($directory) || !is_dir($directory)) {
return $probe();
}
$cacheFile = $directory
. DIRECTORY_SEPARATOR
. 'probe-'
. hash('sha256', serialize([$namespace, $configuration]));
$cache = @fopen($cacheFile, 'c+');
if ($cache === false || !flock($cache, LOCK_EX)) {
if ($cache !== false) {
fclose($cache);
}
return $probe();
}
try {
$cached = stream_get_contents($cache);
if (is_string($cached) && str_starts_with($cached, self::FAILURE_PREFIX)) {
throw new ProbeFailureException(substr($cached, strlen(self::FAILURE_PREFIX)));
}
try {
return $probe();
} catch (ProbeFailureException $e) {
rewind($cache);
ftruncate($cache, 0);
fwrite($cache, self::FAILURE_PREFIX . $e->getMessage());
fflush($cache);
throw $e;
}
} finally {
flock($cache, LOCK_UN);
fclose($cache);
}
}
}