$calls, 'news_name' => 'Public article')); }; function expect_cache($condition, $message) { if (!$condition) { throw new Exception($message); } } $first = txiaw_home_news_cached($directory, $loader); $second = txiaw_home_news_cached($directory, $loader); expect_cache($first === $second && $calls === 1, 'Fresh cache must avoid a second query'); file_put_contents($file, json_encode(array('created' => time() - 61, 'items' => $first))); $third = txiaw_home_news_cached($directory, $loader); expect_cache($calls === 2 && $third[0]['news_id'] === 2, 'Expired cache must refresh'); file_put_contents($file, json_encode(array('created' => time() - 61, 'items' => $third))); $held = fopen($directory . '/news-list-v1.lock', 'c'); flock($held, LOCK_EX); $started = microtime(true); $stale = txiaw_home_news_cached($directory, $loader); expect_cache($stale === $third && $calls === 2, 'Busy refresher must serve existing stale data'); expect_cache(microtime(true) - $started < 0.1, 'Stale read must not wait for the lock'); flock($held, LOCK_UN); fclose($held); $failedLoader = function () { throw new Exception('Simulated database failure'); }; expect_cache(txiaw_home_news_cached($directory, $failedLoader) === $third, 'Recent stale cache should survive a transient database failure'); file_put_contents($file, '{corrupt'); $recovered = txiaw_home_news_cached($directory, $loader); expect_cache($calls === 3 && $recovered[0]['news_id'] === 3, 'Corrupt cache must recover'); expect_cache(txiaw_read_home_news_cache($file) !== false, 'Recovery must write a valid cache'); echo "PASS: cache hit, TTL refresh, nonblocking stale read, transient error fallback, corrupt cache recovery.\n";