diff --git a/ext/ldap/tests/skipifbindfailure.inc b/ext/ldap/tests/skipifbindfailure.inc index 81c7998cfbb5..0b421ef1bd71 100644 --- a/ext/ldap/tests/skipifbindfailure.inc +++ b/ext/ldap/tests/skipifbindfailure.inc @@ -1,14 +1,23 @@ getMessage()}"); + } } if (isset($require_vendor)) { diff --git a/ext/mysqli/tests/skipifconnectfailure.inc b/ext/mysqli/tests/skipifconnectfailure.inc index 986f646f8bb9..f5cd763af140 100644 --- a/ext/mysqli/tests/skipifconnectfailure.inc +++ b/ext/mysqli/tests/skipifconnectfailure.inc @@ -1,7 +1,19 @@ getMessage()}"); +} ?> diff --git a/ext/mysqli/tests/test_setup/test_helpers.inc b/ext/mysqli/tests/test_setup/test_helpers.inc index c9ab401e7568..d32697aebf82 100644 --- a/ext/mysqli/tests/test_setup/test_helpers.inc +++ b/ext/mysqli/tests/test_setup/test_helpers.inc @@ -1,5 +1,7 @@ getMessage()}"); } } function have_innodb(mysqli $link): bool { @@ -123,11 +145,7 @@ function have_innodb(mysqli $link): bool { return $supported === 'YES' || $supported === 'DEFAULT'; } function mysqli_check_innodb_support_skip_test(): void { - try { - $link = default_mysqli_connect(); - } catch (\mysqli_sql_exception) { - die(sprintf("skip Can't connect to MySQL Server - [%d] %s", mysqli_connect_errno(), mysqli_connect_error())); - } + $link = mysqli_connect_or_skip(); if (! have_innodb($link)) { die(sprintf("skip Needs InnoDB support")); } diff --git a/ext/odbc/tests/skipif.inc b/ext/odbc/tests/skipif.inc index 9785f5843a77..a602debe2271 100644 --- a/ext/odbc/tests/skipif.inc +++ b/ext/odbc/tests/skipif.inc @@ -1,8 +1,17 @@ getMessage()}"); } diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_basic.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_basic.phpt index dfef3bb4839f..bd510b6ac636 100644 --- a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_basic.phpt +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_basic.phpt @@ -78,7 +78,7 @@ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); PDOTest::dropTableIfExists($db, "pdo_attr_statement_class_basic"); ?> ---EXPECT-- +--EXPECTF-- array(1) { [0]=> string(12) "PDOStatement" @@ -89,7 +89,7 @@ StatementWithPublicDestructor::__destruct Class derived from PDOStatement, with private constructor: bool(true) StatementWithPrivateConstructor::__construct -object(StatementWithPrivateConstructor)#2 (1) { +object(StatementWithPrivateConstructor)#%d (1) { ["queryString"]=> string(68) "SELECT id, label FROM pdo_attr_statement_class_basic ORDER BY id ASC" } @@ -97,7 +97,7 @@ string(6) "param1" Class derived from a child of PDOStatement: bool(true) StatementWithPrivateConstructor::__construct -object(StatementDerivedFromChild)#2 (1) { +object(StatementDerivedFromChild)#%d (1) { ["queryString"]=> string(68) "SELECT id, label FROM pdo_attr_statement_class_basic ORDER BY id ASC" } diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt index 301a8835eee2..aebf969b5832 100644 --- a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt @@ -49,8 +49,8 @@ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); PDOTest::dropTableIfExists($db, "pdo_attr_statement_class_ctor_arg_gc"); ?> ---EXPECT-- -object(Bar)#1 (1) { +--EXPECTF-- +object(Bar)#%d (1) { ["statementClass"]=> string(3) "Foo" } diff --git a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_cyclic_ctor_args.phpt b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_cyclic_ctor_args.phpt index 61cfe56f779d..672085441c95 100644 --- a/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_cyclic_ctor_args.phpt +++ b/ext/pdo/tests/attr_statement_class/pdo_ATTR_STATEMENT_CLASS_cyclic_ctor_args.phpt @@ -42,18 +42,18 @@ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); PDOTest::dropTableIfExists($db, "pdo_attr_statement_class_cyclic_ctor_args"); ?> ---EXPECT-- +--EXPECTF-- array(1) { [0]=> string(12) "PDOStatement" } bool(true) -object(PDO)#1 (0) { +object(PDO)#%d (0) { } -object(HoldPdo)#2 (2) { +object(HoldPdo)#%d (2) { ["queryString"]=> string(79) "SELECT id, label FROM pdo_attr_statement_class_cyclic_ctor_args ORDER BY id ASC" ["v"]=> - object(PDO)#1 (0) { + object(PDO)#%d (0) { } } diff --git a/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt b/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt index 86d2cfcefd5f..bc825d4f8523 100644 --- a/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt +++ b/ext/pdo/tests/attr_statement_class/pdo_prepare_ATTR_STATEMENT_CLASS_ctor_arg_gc.phpt @@ -51,8 +51,8 @@ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); PDOTest::dropTableIfExists($db, "pdo_prepare_attr_statement_class_ctor_arg_gc"); ?> ---EXPECT-- -object(Bar)#1 (1) { +--EXPECTF-- +object(Bar)#%d (1) { ["statementClass"]=> string(3) "Foo" } diff --git a/ext/pdo/tests/pdo_027.phpt b/ext/pdo/tests/pdo_027.phpt index 6bb350f0c133..969ec9462139 100644 --- a/ext/pdo/tests/pdo_027.phpt +++ b/ext/pdo/tests/pdo_027.phpt @@ -36,13 +36,13 @@ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); PDOTest::dropTableIfExists($db, "test027"); ?> ---EXPECT-- -object(PDOStatement)#2 (1) { +--EXPECTF-- +object(PDOStatement)#%d (1) { ["queryString"]=> string(21) "SELECT * FROM test027" } bool(false) -object(PDORow)#4 (3) { +object(PDORow)#%d (3) { ["queryString"]=> string(21) "SELECT * FROM test027" ["id"]=> @@ -52,7 +52,7 @@ object(PDORow)#4 (3) { } lazy: 1test1 bool(true) -object(PDORow)#4 (3) { +object(PDORow)#%d (3) { ["queryString"]=> string(21) "SELECT * FROM test027" ["id"]=> diff --git a/ext/pdo/tests/pdo_query_fetch_lazy001.phpt b/ext/pdo/tests/pdo_query_fetch_lazy001.phpt index cec5e9f6d6c9..7d9538513045 100644 --- a/ext/pdo/tests/pdo_query_fetch_lazy001.phpt +++ b/ext/pdo/tests/pdo_query_fetch_lazy001.phpt @@ -29,8 +29,8 @@ require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc'; $db = PDOTest::factory(); PDOTest::dropTableIfExists($db, "pdo_query_fetch_lazy_001"); ?> ---EXPECT-- -object(PDOStatement)#2 (1) { +--EXPECTF-- +object(PDOStatement)#%d (1) { ["queryString"]=> string(38) "SELECT * FROM pdo_query_fetch_lazy_001" } diff --git a/ext/pdo/tests/pdo_test.inc b/ext/pdo/tests/pdo_test.inc index b44d0b88e77b..817eaa7bf798 100644 --- a/ext/pdo/tests/pdo_test.inc +++ b/ext/pdo/tests/pdo_test.inc @@ -1,6 +1,8 @@ getMessage()); } - // Ignore errors about non-existent tables - $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); - - $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); - $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); - $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); - return $db; } static function skip() { try { - $db = PDOTest::factory(); + self::factory(); } catch (PDOException $e) { die("skip " . $e->getMessage()); } @@ -95,6 +97,33 @@ class PDOTest { default => $db->exec("DROP TABLE IF EXISTS $tableName"), }; } + + private static function createConnection($classname, bool $useConnectMethod, $dsn, $user, $pass, ?array $attributes) { + if ($useConnectMethod) { + $db = $classname::connect($dsn, $user, $pass, $attributes); + } else { + $db = new $classname($dsn, $user, $pass, $attributes); + } + + if (!$db) { + die("Could not create PDO object (DSN=$dsn, user=$user)\n"); + } + // Ignore errors about non-existent tables + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true); + return $db; + } + + private static function getAttributes(string $environmentVariable): ?array { + $attributes = getenv($environmentVariable); + if (is_string($attributes) && strlen($attributes)) { + return unserialize($attributes); + } + return null; + } } /** See https://stackoverflow.com/a/3732466 */ function get_dummy_sql_request(): string diff --git a/ext/pdo_dblib/tests/common.phpt b/ext/pdo_dblib/tests/common.phpt index 293597b623e3..f3a963ef9b63 100644 --- a/ext/pdo_dblib/tests/common.phpt +++ b/ext/pdo_dblib/tests/common.phpt @@ -5,7 +5,7 @@ pdo_dblib --REDIRECTTEST-- # magic auto-configuration -return [ +$config = [ 'ENV' => [ 'PDOTEST_DSN' => getenv('PDO_DBLIB_TEST_DSN') ?: 'dblib:host=localhost;dbname=test', 'PDOTEST_USER' => getenv('PDO_DBLIB_TEST_USER') ?: 'php', @@ -13,3 +13,11 @@ return [ ], 'TESTS' => __DIR__ . '/ext/pdo/tests', ]; + +if (getenv('PDO_DBLIB_TEST_DSN') === false) { + $config['ENV']['PDOTEST_SKIP_ATTR'] = serialize([ + Pdo\Dblib::ATTR_CONNECTION_TIMEOUT => 1, + ]); +} + +return $config; diff --git a/ext/pdo_dblib/tests/config.inc b/ext/pdo_dblib/tests/config.inc index 1612a80a9336..78bf0804890d 100644 --- a/ext/pdo_dblib/tests/config.inc +++ b/ext/pdo_dblib/tests/config.inc @@ -1,5 +1,7 @@ 1]; + } [$dsn, $user, $pass] = getCredentials(); try { - $db = new $class($dsn, $user, $pass, $attributes); - if ($attributes === null) { - setAttributes($db); - } - } catch (PDOException $e) { + return ProbeCache::getFailure('pdo', [$class, false, $dsn, $user, $pass, $connectionAttributes], static function () use ($class, $dsn, $user, $pass, $attributes, $connectionAttributes): PDO { + try { + $db = new $class($dsn, $user, $pass, $connectionAttributes); + if ($attributes === null) { + setAttributes($db); + } + return $db; + } catch (PDOException $e) { + throw new ProbeFailureException($e); + } + }); + } catch (ProbeFailureException $e) { die('skip ' . $e->getMessage()); } - - return $db; } function connectToDb() { diff --git a/ext/pdo_mysql/tests/inc/mysql_pdo_test.inc b/ext/pdo_mysql/tests/inc/mysql_pdo_test.inc index 75f299ff9e19..d840e66b6ff5 100644 --- a/ext/pdo_mysql/tests/inc/mysql_pdo_test.inc +++ b/ext/pdo_mysql/tests/inc/mysql_pdo_test.inc @@ -20,20 +20,19 @@ class MySQLPDOTest extends PDOTest { $attr = is_string($attr) && strlen($attr) ? unserialize($attr) : null; } - if ($useConnectMethod) { - $db = $classname::connect($dsn, $user, $pass, $attr); - } else { - $db = new $classname($dsn, $user, $pass, $attr); - } + $configuration = [$classname, $useConnectMethod, $dsn, $user, $pass, $attr]; - if (!$db) { - die("Could not create PDO object (DSN=$dsn, user=$user)\n"); + try { + return ProbeCache::getFailure('pdo', $configuration, static function () use ($classname, $useConnectMethod, $dsn, $user, $pass, $attr): PDO { + try { + return self::createConnection($classname, $useConnectMethod, $dsn, $user, $pass, $attr); + } catch (PDOException $e) { + throw new ProbeFailureException($e); + } + }); + } catch (ProbeFailureException $e) { + throw new PDOException($e->getMessage()); } - - $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); - $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); - - return $db; } static function factoryWithAttr($attr) { @@ -168,7 +167,7 @@ class MySQLPDOTest extends PDOTest { static function skip() { try { - $db = self::factory(); + self::factory(); } catch (PDOException $e) { die('skip could not connect'); } @@ -212,5 +211,22 @@ class MySQLPDOTest extends PDOTest { $message = $message ?? 'skip Transactional engine not found'; if (false == self::detect_transactional_mysql_engine($db)) die($message); } + + private static function createConnection($classname, bool $useConnectMethod, $dsn, $user, $pass, $attr) { + if ($useConnectMethod) { + $db = $classname::connect($dsn, $user, $pass, $attr); + } else { + $db = new $classname($dsn, $user, $pass, $attr); + } + + if (!$db) { + die("Could not create PDO object (DSN=$dsn, user=$user)\n"); + } + + $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); + $db->setAttribute(PDO::ATTR_CASE, PDO::CASE_LOWER); + + return $db; + } } ?> diff --git a/ext/pdo_pgsql/tests/bug75402.phpt b/ext/pdo_pgsql/tests/bug75402.phpt index be0fbd3f0bee..909c315f8004 100644 --- a/ext/pdo_pgsql/tests/bug75402.phpt +++ b/ext/pdo_pgsql/tests/bug75402.phpt @@ -88,12 +88,12 @@ require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc'; $db = PDOTest::test_factory(__DIR__ . '/common.phpt'); $db->exec('DROP TABLE IF EXISTS bug75402'); ?> ---EXPECT-- -object(stdClass)#2 (1) { +--EXPECTF-- +object(stdClass)#%d (1) { ["entries"]=> array(1) { [0]=> - object(stdClass)#4 (10) { + object(stdClass)#%d (10) { ["sid"]=> string(19) "20171016083645_5337" ["sgroupid"]=> diff --git a/ext/pgsql/tests/inc/skipif.inc b/ext/pgsql/tests/inc/skipif.inc index 2ce5f46e7787..03a00b0cde7b 100644 --- a/ext/pgsql/tests/inc/skipif.inc +++ b/ext/pgsql/tests/inc/skipif.inc @@ -8,15 +8,24 @@ include("config.inc"); include("lcmess.inc"); +require_once dirname(__DIR__, 4) . '/tests/probe_cache.inc'; if (getenv("SKIP_REPEAT")) { // pgsql tests are order-dependent. // We should probably change that, but in the meantime do not allow repetition. die("skip Cannot repeat pgsql tests"); } -$conn = @pg_connect($conn_str); -if (!$conn) { - die("skip could not connect\n"); +try { + $conn = ProbeCache::getFailure('pgsql', [$conn_str], static function () use ($conn_str): PgSql\Connection { + $conn = @pg_connect($conn_str); + if (!$conn) { + throw new ProbeFailureException('could not connect'); + } + + return $conn; + }); +} catch (ProbeFailureException $e) { + die("skip {$e->getMessage()}\n"); } function skip_server_version($version, $op = '<') diff --git a/ext/snmp/tests/skipif.inc b/ext/snmp/tests/skipif.inc index 0ae4ee16e5b8..283cfaa5357d 100644 --- a/ext/snmp/tests/skipif.inc +++ b/ext/snmp/tests/skipif.inc @@ -1,10 +1,18 @@ getMessage()}"); +} diff --git a/run-tests.php b/run-tests.php index 89bd8ffb797c..e092e0453c6e 100755 --- a/run-tests.php +++ b/run-tests.php @@ -232,6 +232,8 @@ function main(): void } } + SharedProbeCache::setUp($environment); + if (IS_WINDOWS && empty($environment["SystemRoot"])) { $environment["SystemRoot"] = getenv("SystemRoot"); } @@ -1073,13 +1075,68 @@ function get_file_cache_dir(): string return sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'php-run-tests-file-cache'; } +final class SharedProbeCache +{ + private string $directory; + + private function __construct(string $directory) + { + $this->directory = $directory; + } + + public static function setUp(array &$environment): void + { + unset($environment['TEST_PHP_SHARED_CACHE_DIR']); + if (getenv('TEST_PHP_SHARED_CACHE') === '0') { + return; + } + + $cache = self::create(); + if ($cache === null) { + return; + } + + $environment['TEST_PHP_SHARED_CACHE_DIR'] = $cache->directory; + register_shutdown_function(static function () use ($cache): void { + $cache->cleanup(); + }); + } + + private static function create(): ?self + { + $temporaryDirectory = sys_get_temp_dir(); + if ($temporaryDirectory === '') { + return null; + } + + for ($attempt = 0; $attempt < 3; $attempt++) { + $directory = $temporaryDirectory + . DIRECTORY_SEPARATOR + . 'php-run-tests-' + . bin2hex(random_bytes(8)); + if (@mkdir($directory, 0700)) { + return new self($directory); + } + } + + return null; + } + + private function cleanup(): void + { + if (is_dir($this->directory)) { + rmdir_recursive($this->directory); + } + } +} + function rmdir_recursive($dir) { - if (!file_exists($dir)) { + if (!file_exists($dir) && !is_link($dir)) { return; } - if (!is_dir($dir)) { - unlink($dir); + if (is_link($dir) || !is_dir($dir)) { + @unlink($dir); return; } @@ -3769,6 +3826,7 @@ public function checkSkip(array $command, string $code, string $checkFile, strin } save_text($checkFile, $code, $tempFile); + $env['TEST_PHP_EVALUATING_SKIPIF'] = '1'; $command[] = $checkFile; $result = trim(system_with_timeout($command, $env)); if (strpos($result, 'nocache') === 0) { diff --git a/tests/probe_cache.inc b/tests/probe_cache.inc new file mode 100644 index 000000000000..01ba1bec5564 --- /dev/null +++ b/tests/probe_cache.inc @@ -0,0 +1,73 @@ +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); + } + } +} diff --git a/tests/run-test/test_probe_cache.phpt b/tests/run-test/test_probe_cache.phpt new file mode 100644 index 000000000000..ef703ed8f637 --- /dev/null +++ b/tests/run-test/test_probe_cache.phpt @@ -0,0 +1,163 @@ +--TEST-- +Shared test probe cache caches failures across processes +--FILE-- + ['pipe', 'w'], + 2 => ['redirect', 1], + ], + $pipes, + null, + $environment, + ['bypass_shell' => true], + ); + + return [$process, $pipes]; +} + +function finish_probe_cache_process($process, array $pipes): string +{ + $output = stream_get_contents($pipes[1]); + fclose($pipes[1]); + + if (0 !== $exitCode = proc_close($process)) { + throw new Exception("PHP subprocess exited with code $exitCode: $output"); + } + + return $output; +} + +function run_probe_cache_process(string $code, array $environment): string +{ + [$process, $pipes] = start_probe_cache_process($code, $environment); + return finish_probe_cache_process($process, $pipes); +} + +$cacheDirectory = getenv('TEST_PHP_SHARED_CACHE_DIR'); +if (!is_string($cacheDirectory)) { + throw new Exception('Missing shared test cache directory'); +} + +$environment = getenv(); +$environment['TEST_PHP_EVALUATING_SKIPIF'] = '1'; + +$helper = var_export(dirname(__DIR__) . '/probe_cache.inc', true); +$namespace = 'probe-cache-test-' . bin2hex(random_bytes(8)); +$namespaceCode = var_export($namespace, true); +$first = run_probe_cache_process( + "require $helper; try { ProbeCache::getFailure($namespaceCode, ['shared'], static function (): never { throw new ProbeFailureException('shared failure'); }); } catch (ProbeFailureException \$e) { echo \$e->getMessage(); }", + $environment, +); +$second = run_probe_cache_process( + "require $helper; try { ProbeCache::getFailure($namespaceCode, ['shared'], static function (): never { throw new Exception('Probe should not run'); }); } catch (ProbeFailureException \$e) { echo \$e->getMessage(); }", + $environment, +); +echo "$first\n$second\n"; + +$probeStarted = $cacheDirectory . '/probe_started'; +$probeStartedCode = var_export($probeStarted, true); +@unlink($probeStarted); +[$firstProcess, $firstPipes] = start_probe_cache_process( + "require $helper; try { ProbeCache::getFailure($namespaceCode, ['concurrent'], static function (): never { file_put_contents($probeStartedCode, 'started'); usleep(1000000); throw new ProbeFailureException('concurrent failure'); }); } catch (ProbeFailureException \$e) { echo \$e->getMessage(); }", + $environment, +); + +$deadline = microtime(true) + 5; +while (!file_exists($probeStarted) && microtime(true) < $deadline) { + usleep(1000); +} +if (!file_exists($probeStarted)) { + $output = finish_probe_cache_process($firstProcess, $firstPipes); + throw new Exception("Concurrent probe did not start: $output"); +} + +[$secondProcess, $secondPipes] = start_probe_cache_process( + "require $helper; try { ProbeCache::getFailure($namespaceCode, ['concurrent'], static function (): never { throw new Exception('Concurrent probe should not run'); }); } catch (ProbeFailureException \$e) { echo \$e->getMessage(); }", + $environment, +); +$first = finish_probe_cache_process($firstProcess, $firstPipes); +$second = finish_probe_cache_process($secondProcess, $secondPipes); +echo "$first\n$second\n"; + +putenv("TEST_PHP_SHARED_CACHE_DIR=$cacheDirectory"); +putenv('TEST_PHP_EVALUATING_SKIPIF=1'); + +$failureCalls = 0; +$failureProbe = static function () use (&$failureCalls): never { + $failureCalls++; + throw new ProbeFailureException("failure $failureCalls"); +}; + +try { + ProbeCache::getFailure($namespace, ['first'], $failureProbe); +} catch (ProbeFailureException $e) { + var_dump($e->getMessage()); +} +try { + ProbeCache::getFailure($namespace, ['first'], $failureProbe); +} catch (ProbeFailureException $e) { + var_dump($e->getMessage()); +} +try { + ProbeCache::getFailure($namespace, ['second'], $failureProbe); +} catch (ProbeFailureException $e) { + var_dump($e->getMessage()); +} +var_dump($failureCalls); + +$successCalls = 0; +$successProbe = static function () use (&$successCalls): string { + $successCalls++; + return "success $successCalls"; +}; + +var_dump(ProbeCache::getFailure($namespace, ['available'], $successProbe)); +var_dump(ProbeCache::getFailure($namespace, ['available'], $successProbe)); +var_dump($successCalls); + +putenv('TEST_PHP_EVALUATING_SKIPIF'); +var_dump(ProbeCache::getFailure($namespace, ['first'], static fn(): string => 'uncached success')); + +$previous = new Exception('original failure'); +try { + ProbeCache::getFailure($namespace, ['wrapped'], static function () use ($previous): never { + throw new ProbeFailureException($previous); + }); +} catch (Throwable $e) { + var_dump($e === $previous); +} + +putenv('TEST_PHP_EVALUATING_SKIPIF=1'); +putenv('TEST_PHP_SHARED_CACHE_DIR'); +try { + ProbeCache::getFailure($namespace, ['uncached'], static function (): never { + throw new ProbeFailureException('uncached failure'); + }); +} catch (ProbeFailureException $e) { + var_dump($e->getMessage()); +} +?> +--EXPECT-- +shared failure +shared failure +concurrent failure +concurrent failure +string(9) "failure 1" +string(9) "failure 1" +string(9) "failure 2" +int(2) +string(9) "success 1" +string(9) "success 2" +int(2) +string(16) "uncached success" +bool(true) +string(16) "uncached failure" diff --git a/tests/run-test/test_skipif_environment.phpt b/tests/run-test/test_skipif_environment.phpt new file mode 100644 index 000000000000..8ccb69b0e508 --- /dev/null +++ b/tests/run-test/test_skipif_environment.phpt @@ -0,0 +1,14 @@ +--TEST-- +SKIPIF evaluation environment +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(false)