Line data Source code
1 : /**
2 : * @file test_resolver_cache.c
3 : * @brief TEST-85 — functional coverage for the @username resolver cache.
4 : *
5 : * Drives src/domain/read/user_info.c::domain_resolve_username through the
6 : * mock Telegram server and verifies:
7 : *
8 : * 1. Cold call — first resolve of a @peer issues exactly one
9 : * contacts.resolveUsername RPC.
10 : * 2. Warm call — a second call for the same @peer within the positive
11 : * TTL is served from the in-process cache (zero new RPC).
12 : * 3. Different peer — a separate @peer adds one fresh RPC.
13 : * 4. TTL expiry — advancing the injected clock past the positive TTL
14 : * causes the next call to re-fire the RPC.
15 : * 5. Logout flush — the logout path (auth_logout with the registered
16 : * flush callback) invalidates all cached entries so the next call
17 : * fires the RPC again.
18 : * 6. Negative caching — USERNAME_NOT_OCCUPIED / USERNAME_INVALID
19 : * responses are remembered under a shorter TTL; a second call
20 : * within that TTL does NOT re-fire the RPC, but after the negative
21 : * TTL elapses the RPC is re-issued.
22 : * 7. Eviction — filling the cache past its capacity evicts the oldest
23 : * entry, so that re-resolving the first-inserted @peer fires a
24 : * fresh RPC while the newer entries stay cached.
25 : *
26 : * Timing is controlled by resolve_cache_set_now_fn() (a compile-time
27 : * seam in user_info.c), so no real sleeps are required.
28 : */
29 :
30 : #include "test_helpers.h"
31 :
32 : #include "mock_socket.h"
33 : #include "mock_tel_server.h"
34 :
35 : #include "api_call.h"
36 : #include "mtproto_session.h"
37 : #include "transport.h"
38 : #include "app/session_store.h"
39 : #include "tl_registry.h"
40 : #include "tl_serial.h"
41 : #include "domain/read/user_info.h"
42 : #include "infrastructure/auth_logout.h"
43 :
44 : #include <stdio.h>
45 : #include <stdlib.h>
46 : #include <string.h>
47 : #include <unistd.h>
48 : #include <time.h>
49 :
50 : /* ---- CRCs (not surfaced by public headers) ---- */
51 : #define CRC_contacts_resolveUsername 0xf93ccba3U
52 :
53 : /* ---- Injected clock for resolver-cache TTL ---- */
54 :
55 : static time_t s_fake_time = 0;
56 :
57 182 : static time_t fake_now(void) { return s_fake_time; }
58 :
59 : /* ---- Helpers ---- */
60 :
61 14 : static void with_tmp_home(const char *tag) {
62 : char tmp[256];
63 14 : snprintf(tmp, sizeof(tmp), "/tmp/tg-cli-ft-rcache-%s", tag);
64 : char bin[512];
65 14 : snprintf(bin, sizeof(bin), "%s/.config/tg-cli/session.bin", tmp);
66 14 : (void)unlink(bin);
67 14 : setenv("HOME", tmp, 1);
68 : /* CI runners (GitHub Actions) may export XDG_{CONFIG,CACHE}_HOME,
69 : * which makes platform_*_dir() ignore our redirected HOME. Force the
70 : * HOME-based fallback so production code and these tests agree. */
71 14 : unsetenv("XDG_CONFIG_HOME");
72 14 : unsetenv("XDG_CACHE_HOME");
73 14 : }
74 :
75 14 : static void connect_mock(Transport *t) {
76 14 : transport_init(t);
77 14 : ASSERT(transport_connect(t, "127.0.0.1", 443) == 0, "connect");
78 : }
79 :
80 14 : static void init_cfg(ApiConfig *cfg) {
81 14 : api_config_init(cfg);
82 14 : cfg->api_id = 12345;
83 14 : cfg->api_hash = "deadbeefcafebabef00dbaadfeedc0de";
84 14 : }
85 :
86 14 : static void load_session(MtProtoSession *s) {
87 14 : ASSERT(mt_server_seed_session(2, NULL, NULL, NULL) == 0, "seed");
88 14 : mtproto_session_init(s);
89 14 : int dc = 0;
90 14 : ASSERT(session_store_load(s, &dc) == 0, "load session");
91 : }
92 :
93 : /* ---- Responders ---- */
94 :
95 : /** contacts.resolvedPeer → user id 8001 with access_hash. */
96 14 : static void on_resolve_user_8001(MtRpcContext *ctx) {
97 : TlWriter w;
98 14 : tl_writer_init(&w);
99 14 : tl_write_uint32(&w, TL_contacts_resolvedPeer);
100 14 : tl_write_uint32(&w, TL_peerUser);
101 14 : tl_write_int64 (&w, 8001LL);
102 : /* chats vector: empty */
103 14 : tl_write_uint32(&w, TL_vector);
104 14 : tl_write_uint32(&w, 0);
105 : /* users vector: one user with access_hash */
106 14 : tl_write_uint32(&w, TL_vector);
107 14 : tl_write_uint32(&w, 1);
108 14 : tl_write_uint32(&w, TL_user);
109 14 : tl_write_uint32(&w, 1u); /* flags.0 → access_hash */
110 14 : tl_write_uint32(&w, 0); /* flags2 */
111 14 : tl_write_int64 (&w, 8001LL);
112 14 : tl_write_int64 (&w, 0xDEADBEEFCAFEBABEULL);
113 14 : mt_server_reply_result(ctx, w.data, w.len);
114 14 : tl_writer_free(&w);
115 14 : }
116 :
117 : /** contacts.resolvedPeer → user id 8002 (distinct @peer). */
118 2 : static void on_resolve_user_8002(MtRpcContext *ctx) {
119 : TlWriter w;
120 2 : tl_writer_init(&w);
121 2 : tl_write_uint32(&w, TL_contacts_resolvedPeer);
122 2 : tl_write_uint32(&w, TL_peerUser);
123 2 : tl_write_int64 (&w, 8002LL);
124 2 : tl_write_uint32(&w, TL_vector);
125 2 : tl_write_uint32(&w, 0);
126 2 : tl_write_uint32(&w, TL_vector);
127 2 : tl_write_uint32(&w, 1);
128 2 : tl_write_uint32(&w, TL_user);
129 2 : tl_write_uint32(&w, 1u);
130 2 : tl_write_uint32(&w, 0);
131 2 : tl_write_int64 (&w, 8002LL);
132 2 : tl_write_int64 (&w, 0x1122334455667788ULL);
133 2 : mt_server_reply_result(ctx, w.data, w.len);
134 2 : tl_writer_free(&w);
135 2 : }
136 :
137 : /** Parametric responder for eviction / capacity stress — walks the key
138 : * inside the incoming request to derive a unique id. */
139 68 : static void on_resolve_capture_id(MtRpcContext *ctx) {
140 : /* Request layout after the CRC: string @username (tl_read_string).
141 : * Strings under 254 bytes begin with a 1-byte length prefix. We use
142 : * keys like "p0"..."p31" so the short-form always applies. */
143 68 : const uint8_t *p = ctx->req_body + 4;
144 68 : size_t n = (size_t)p[0];
145 68 : int64_t id = 7000 + (int64_t)atoi((const char *)(p + 2));
146 : TlWriter w;
147 68 : tl_writer_init(&w);
148 68 : tl_write_uint32(&w, TL_contacts_resolvedPeer);
149 68 : tl_write_uint32(&w, TL_peerUser);
150 68 : tl_write_int64 (&w, id);
151 68 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 0);
152 68 : tl_write_uint32(&w, TL_vector); tl_write_uint32(&w, 1);
153 68 : tl_write_uint32(&w, TL_user);
154 68 : tl_write_uint32(&w, 1u);
155 68 : tl_write_uint32(&w, 0);
156 68 : tl_write_int64 (&w, id);
157 68 : tl_write_int64 (&w, (int64_t)(0xA000000000000000ULL | (uint64_t)id));
158 68 : mt_server_reply_result(ctx, w.data, w.len);
159 68 : tl_writer_free(&w);
160 : (void)n;
161 68 : }
162 :
163 : /** Error path: USERNAME_NOT_OCCUPIED. */
164 4 : static void on_resolve_not_occupied(MtRpcContext *ctx) {
165 4 : mt_server_reply_error(ctx, 400, "USERNAME_NOT_OCCUPIED");
166 4 : }
167 :
168 : /** auth.loggedOut#c3a2835f flags=0 — canonical happy-path logout reply. */
169 2 : static void on_logout_ok(MtRpcContext *ctx) {
170 : TlWriter w;
171 2 : tl_writer_init(&w);
172 2 : tl_write_uint32(&w, CRC_auth_loggedOut);
173 2 : tl_write_uint32(&w, 0);
174 2 : mt_server_reply_result(ctx, w.data, w.len);
175 2 : tl_writer_free(&w);
176 2 : }
177 :
178 : /* ================================================================ */
179 : /* Tests */
180 : /* ================================================================ */
181 :
182 : /**
183 : * @brief First `info @foo` must issue exactly one resolveUsername RPC.
184 : */
185 2 : static void test_cold_call_resolves_once(void) {
186 2 : with_tmp_home("cold");
187 2 : mt_server_init(); mt_server_reset();
188 2 : resolve_cache_set_now_fn(fake_now);
189 2 : s_fake_time = 1000;
190 2 : resolve_cache_flush();
191 :
192 2 : MtProtoSession s; load_session(&s);
193 2 : mt_server_expect(CRC_contacts_resolveUsername, on_resolve_user_8001, NULL);
194 :
195 2 : ApiConfig cfg; init_cfg(&cfg);
196 2 : Transport t; connect_mock(&t);
197 :
198 2 : ResolvedPeer rp = {0};
199 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@foo", &rp) == 0,
200 : "cold resolve ok");
201 2 : ASSERT(rp.id == 8001LL, "peer id matches mock reply");
202 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 1,
203 : "exactly one resolveUsername RPC was sent");
204 :
205 2 : resolve_cache_set_now_fn(NULL);
206 2 : transport_close(&t);
207 2 : mt_server_reset();
208 : }
209 :
210 : /**
211 : * @brief Second `info @foo` within the positive TTL must be cache-served.
212 : */
213 2 : static void test_warm_call_skips_rpc(void) {
214 2 : with_tmp_home("warm");
215 2 : mt_server_init(); mt_server_reset();
216 2 : resolve_cache_set_now_fn(fake_now);
217 2 : s_fake_time = 2000;
218 2 : resolve_cache_flush();
219 :
220 2 : MtProtoSession s; load_session(&s);
221 2 : mt_server_expect(CRC_contacts_resolveUsername, on_resolve_user_8001, NULL);
222 :
223 2 : ApiConfig cfg; init_cfg(&cfg);
224 2 : Transport t; connect_mock(&t);
225 :
226 2 : ResolvedPeer rp1 = {0};
227 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@foo", &rp1) == 0,
228 : "1st resolve ok");
229 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 1,
230 : "cold path → 1 RPC");
231 :
232 : /* +10 s — still well within the positive TTL. */
233 2 : s_fake_time += 10;
234 :
235 2 : ResolvedPeer rp2 = {0};
236 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@foo", &rp2) == 0,
237 : "2nd resolve ok (from cache)");
238 2 : ASSERT(rp2.id == rp1.id, "cached id matches");
239 2 : ASSERT(rp2.access_hash == rp1.access_hash, "cached access_hash matches");
240 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 1,
241 : "warm path added 0 RPCs");
242 :
243 2 : resolve_cache_set_now_fn(NULL);
244 2 : transport_close(&t);
245 2 : mt_server_reset();
246 : }
247 :
248 : /**
249 : * @brief A different @peer must still issue its own RPC, not reuse the
250 : * previous cache entry.
251 : */
252 2 : static void test_different_peer_does_not_hit_cache(void) {
253 2 : with_tmp_home("diff");
254 2 : mt_server_init(); mt_server_reset();
255 2 : resolve_cache_set_now_fn(fake_now);
256 2 : s_fake_time = 3000;
257 2 : resolve_cache_flush();
258 :
259 2 : MtProtoSession s; load_session(&s);
260 :
261 2 : ApiConfig cfg; init_cfg(&cfg);
262 2 : Transport t; connect_mock(&t);
263 :
264 : /* Fill cache with @foo → 8001. */
265 2 : mt_server_expect(CRC_contacts_resolveUsername, on_resolve_user_8001, NULL);
266 2 : ResolvedPeer rp_foo = {0};
267 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@foo", &rp_foo) == 0,
268 : "resolve @foo ok");
269 2 : ASSERT(rp_foo.id == 8001LL, "@foo id == 8001");
270 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 1,
271 : "after @foo → 1 RPC");
272 :
273 : /* Swap responder: @bar → 8002 distinct id. */
274 2 : mt_server_expect(CRC_contacts_resolveUsername, on_resolve_user_8002, NULL);
275 2 : ResolvedPeer rp_bar = {0};
276 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@bar", &rp_bar) == 0,
277 : "resolve @bar ok");
278 2 : ASSERT(rp_bar.id == 8002LL, "@bar id == 8002 (not from @foo cache)");
279 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 2,
280 : "second @peer fires a separate RPC");
281 :
282 2 : resolve_cache_set_now_fn(NULL);
283 2 : transport_close(&t);
284 2 : mt_server_reset();
285 : }
286 :
287 : /**
288 : * @brief After the positive TTL lapses the next call must fire a fresh
289 : * RPC and refresh the cache.
290 : */
291 2 : static void test_ttl_expiry_refreshes(void) {
292 2 : with_tmp_home("ttl");
293 2 : mt_server_init(); mt_server_reset();
294 2 : resolve_cache_set_now_fn(fake_now);
295 2 : s_fake_time = 4000;
296 2 : resolve_cache_flush();
297 :
298 2 : MtProtoSession s; load_session(&s);
299 2 : mt_server_expect(CRC_contacts_resolveUsername, on_resolve_user_8001, NULL);
300 :
301 2 : ApiConfig cfg; init_cfg(&cfg);
302 2 : Transport t; connect_mock(&t);
303 :
304 2 : ResolvedPeer rp = {0};
305 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@foo", &rp) == 0,
306 : "cold resolve ok");
307 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 1,
308 : "after cold → 1 RPC");
309 :
310 : /* Fast-forward past the positive TTL (plus a safety margin). */
311 2 : s_fake_time += resolve_cache_positive_ttl() + 1;
312 :
313 2 : ResolvedPeer rp2 = {0};
314 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@foo", &rp2) == 0,
315 : "refresh resolve ok");
316 2 : ASSERT(rp2.id == 8001LL, "refreshed id matches mock reply");
317 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 2,
318 : "TTL expiry triggers second RPC");
319 :
320 2 : resolve_cache_set_now_fn(NULL);
321 2 : transport_close(&t);
322 2 : mt_server_reset();
323 : }
324 :
325 : /**
326 : * @brief auth_logout() must invoke the registered cache-flush callback so
327 : * that a follow-up resolve re-hits the server.
328 : */
329 2 : static void test_logout_flushes_cache(void) {
330 2 : with_tmp_home("logout");
331 2 : mt_server_init(); mt_server_reset();
332 2 : resolve_cache_set_now_fn(fake_now);
333 2 : s_fake_time = 5000;
334 2 : resolve_cache_flush();
335 :
336 2 : MtProtoSession s; load_session(&s);
337 : /* Prime the cache. */
338 2 : mt_server_expect(CRC_contacts_resolveUsername, on_resolve_user_8001, NULL);
339 :
340 2 : ApiConfig cfg; init_cfg(&cfg);
341 2 : Transport t; connect_mock(&t);
342 :
343 2 : ResolvedPeer rp = {0};
344 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@foo", &rp) == 0,
345 : "cold resolve ok");
346 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 1,
347 : "after cold → 1 RPC");
348 :
349 : /* Register the production flush callback and arm a happy-path
350 : * auth.logOut responder so auth_logout() returns cleanly. */
351 2 : auth_logout_set_cache_flush_cb(resolve_cache_flush);
352 2 : mt_server_expect(CRC_auth_logOut, on_logout_ok, NULL);
353 :
354 2 : auth_logout(&cfg, &s, &t);
355 :
356 : /* Swap the resolver responder back in for the post-logout call. */
357 2 : mt_server_expect(CRC_contacts_resolveUsername, on_resolve_user_8001, NULL);
358 :
359 : /* The cache must be empty now — the next resolve must fire an RPC. */
360 2 : int rpc_before = mt_server_request_crc_count(CRC_contacts_resolveUsername);
361 2 : ResolvedPeer rp2 = {0};
362 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@foo", &rp2) == 0,
363 : "post-logout resolve ok");
364 2 : int rpc_after = mt_server_request_crc_count(CRC_contacts_resolveUsername);
365 2 : ASSERT(rpc_after == rpc_before + 1,
366 : "post-logout resolve issued a fresh RPC");
367 :
368 : /* Unregister the callback so later tests start clean. */
369 2 : auth_logout_set_cache_flush_cb(NULL);
370 :
371 2 : resolve_cache_set_now_fn(NULL);
372 2 : transport_close(&t);
373 2 : mt_server_reset();
374 : }
375 :
376 : /**
377 : * @brief USERNAME_NOT_OCCUPIED must be remembered: a repeat call inside
378 : * the negative TTL must not re-fire the RPC; after the negative
379 : * TTL elapses the RPC fires once more.
380 : */
381 2 : static void test_negative_result_cached_with_shorter_ttl(void) {
382 2 : with_tmp_home("neg");
383 2 : mt_server_init(); mt_server_reset();
384 2 : resolve_cache_set_now_fn(fake_now);
385 2 : s_fake_time = 6000;
386 2 : resolve_cache_flush();
387 :
388 2 : MtProtoSession s; load_session(&s);
389 2 : mt_server_expect(CRC_contacts_resolveUsername, on_resolve_not_occupied, NULL);
390 :
391 2 : ApiConfig cfg; init_cfg(&cfg);
392 2 : Transport t; connect_mock(&t);
393 :
394 : /* First miss: fires one RPC, caches the negative result. */
395 2 : ResolvedPeer rp1 = {0};
396 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@nobody", &rp1) == -1,
397 : "1st resolve returns not-found");
398 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 1,
399 : "negative cold path → 1 RPC");
400 :
401 : /* Second call within the negative TTL: still fails, NO new RPC. */
402 2 : s_fake_time += 5; /* +5 s, inside the negative TTL */
403 2 : ResolvedPeer rp2 = {0};
404 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@nobody", &rp2) == -1,
405 : "2nd resolve (neg-cached) returns not-found");
406 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 1,
407 : "negative cache suppressed 2nd RPC");
408 :
409 : /* Sanity: the positive TTL is strictly greater than the negative
410 : * TTL — otherwise the whole "shorter" premise breaks. */
411 2 : ASSERT(resolve_cache_positive_ttl() > resolve_cache_negative_ttl(),
412 : "positive TTL > negative TTL by construction");
413 :
414 : /* Fast-forward past the negative TTL: RPC must fire again. */
415 2 : s_fake_time += resolve_cache_negative_ttl() + 1;
416 2 : ResolvedPeer rp3 = {0};
417 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@nobody", &rp3) == -1,
418 : "3rd resolve after neg-TTL still not-found");
419 2 : ASSERT(mt_server_request_crc_count(CRC_contacts_resolveUsername) == 2,
420 : "neg-TTL expiry triggers second RPC");
421 :
422 2 : resolve_cache_set_now_fn(NULL);
423 2 : transport_close(&t);
424 2 : mt_server_reset();
425 : }
426 :
427 : /**
428 : * @brief Fill the cache past its fixed capacity; the first-inserted
429 : * entry must be evicted. After eviction re-resolving the first
430 : * @peer fires a fresh RPC, while one of the more recently
431 : * inserted entries stays cached (zero new RPC).
432 : */
433 2 : static void test_cache_eviction_oldest_first(void) {
434 2 : with_tmp_home("evict");
435 2 : mt_server_init(); mt_server_reset();
436 2 : resolve_cache_set_now_fn(fake_now);
437 2 : s_fake_time = 10000;
438 2 : resolve_cache_flush();
439 :
440 2 : MtProtoSession s; load_session(&s);
441 2 : mt_server_expect(CRC_contacts_resolveUsername, on_resolve_capture_id, NULL);
442 :
443 2 : ApiConfig cfg; init_cfg(&cfg);
444 2 : Transport t; connect_mock(&t);
445 :
446 2 : int cap = resolve_cache_capacity();
447 2 : ASSERT(cap >= 4, "cache capacity is non-trivial");
448 :
449 : /* Insert cap+1 distinct entries, advancing the clock so each entry
450 : * has a unique fetched_at (the eviction policy is oldest-first). */
451 68 : for (int i = 0; i <= cap; i++) {
452 : char key[16];
453 66 : snprintf(key, sizeof(key), "@p%d", i);
454 66 : ResolvedPeer rp = {0};
455 66 : ASSERT(domain_resolve_username(&cfg, &s, &t, key, &rp) == 0,
456 : "insert resolve ok");
457 66 : s_fake_time += 1; /* spread fetched_at so eviction order is deterministic */
458 : }
459 :
460 : int rpc_after_fill =
461 2 : mt_server_request_crc_count(CRC_contacts_resolveUsername);
462 2 : ASSERT(rpc_after_fill == cap + 1,
463 : "cap+1 distinct peers fired cap+1 RPCs");
464 :
465 : /* The first inserted entry (@p0) must have been evicted → resolving
466 : * it again fires a fresh RPC. */
467 2 : ResolvedPeer rp_first = {0};
468 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, "@p0", &rp_first) == 0,
469 : "re-resolve evicted entry ok");
470 : int rpc_after_re_first =
471 2 : mt_server_request_crc_count(CRC_contacts_resolveUsername);
472 2 : ASSERT(rpc_after_re_first == rpc_after_fill + 1,
473 : "re-resolving evicted @p0 fires RPC");
474 :
475 : /* A recently-inserted entry (@p<cap>) must still be cached → zero
476 : * new RPC. */
477 : char last_key[16];
478 2 : snprintf(last_key, sizeof(last_key), "@p%d", cap);
479 2 : ResolvedPeer rp_last = {0};
480 2 : ASSERT(domain_resolve_username(&cfg, &s, &t, last_key, &rp_last) == 0,
481 : "re-resolve recent entry ok");
482 : int rpc_after_re_last =
483 2 : mt_server_request_crc_count(CRC_contacts_resolveUsername);
484 2 : ASSERT(rpc_after_re_last == rpc_after_re_first,
485 : "recent entry still cached — no new RPC");
486 :
487 2 : resolve_cache_set_now_fn(NULL);
488 2 : transport_close(&t);
489 2 : mt_server_reset();
490 : }
491 :
492 : /* ---- Suite entry ---- */
493 :
494 2 : void run_resolver_cache_tests(void) {
495 2 : RUN_TEST(test_cold_call_resolves_once);
496 2 : RUN_TEST(test_warm_call_skips_rpc);
497 2 : RUN_TEST(test_different_peer_does_not_hit_cache);
498 2 : RUN_TEST(test_ttl_expiry_refreshes);
499 2 : RUN_TEST(test_logout_flushes_cache);
500 2 : RUN_TEST(test_negative_result_cached_with_shorter_ttl);
501 2 : RUN_TEST(test_cache_eviction_oldest_first);
502 2 : }
|