dirvote: Fix memleak when computing consensus
[tor.git] / src / test / test_conscache.c
blob5254efbf007bf71765686880aa8bc4ddbb12f9e4
1 /* Copyright (c) 2017-2021, The Tor Project, Inc. */
2 /* See LICENSE for licensing information */
4 #include "core/or/or.h"
5 #include "app/config/config.h"
6 #include "feature/dircache/conscache.h"
7 #include "lib/encoding/confline.h"
8 #include "test/test.h"
10 #ifdef HAVE_UTIME_H
11 #include <utime.h>
12 #endif
14 static void
15 test_conscache_open_failure(void *arg)
17 (void) arg;
18 /* Try opening a directory that doesn't exist and which we shouldn't be
19 * able to create. */
20 consensus_cache_t *cache = consensus_cache_open("a/b/c/d/e/f/g", 128);
21 tt_ptr_op(cache, OP_EQ, NULL);
23 done:
27 static void
28 test_conscache_simple_usage(void *arg)
30 (void)arg;
31 consensus_cache_entry_t *ent = NULL, *ent2 = NULL;
33 /* Make a temporary datadir for these tests */
34 char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cache"));
35 tor_free(get_options_mutable()->CacheDirectory);
36 get_options_mutable()->CacheDirectory = tor_strdup(ddir_fname);
37 check_private_dir(ddir_fname, CPD_CREATE, NULL);
38 consensus_cache_t *cache = consensus_cache_open("cons", 128);
40 tt_assert(cache);
42 /* Create object; make sure it exists. */
43 config_line_t *labels = NULL;
44 config_line_append(&labels, "Hello", "world");
45 config_line_append(&labels, "Adios", "planetas");
46 ent = consensus_cache_add(cache,
47 labels, (const uint8_t *)"A\0B\0C", 5);
48 config_free_lines(labels);
49 labels = NULL;
50 tt_assert(ent);
52 /* Make a second object */
53 config_line_append(&labels, "Hello", "mundo");
54 config_line_append(&labels, "Adios", "planets");
55 ent2 = consensus_cache_add(cache,
56 labels, (const uint8_t *)"xyzzy", 5);
57 config_free_lines(labels);
58 labels = NULL;
59 tt_assert(ent2);
60 tt_assert(! consensus_cache_entry_is_mapped(ent2));
61 consensus_cache_entry_decref(ent2);
62 ent2 = NULL;
64 /* Check get_value */
65 tt_ptr_op(NULL, OP_EQ, consensus_cache_entry_get_value(ent, "hebbo"));
66 tt_str_op("world", OP_EQ, consensus_cache_entry_get_value(ent, "Hello"));
68 /* Check find_first */
69 ent2 = consensus_cache_find_first(cache, "Hello", "world!");
70 tt_ptr_op(ent2, OP_EQ, NULL);
71 ent2 = consensus_cache_find_first(cache, "Hello", "world");
72 tt_ptr_op(ent2, OP_EQ, ent);
73 ent2 = consensus_cache_find_first(cache, "Hello", "mundo");
74 tt_ptr_op(ent2, OP_NE, ent);
76 tt_assert(! consensus_cache_entry_is_mapped(ent));
78 /* Check get_body */
79 const uint8_t *bp = NULL;
80 size_t sz = 0;
81 int r = consensus_cache_entry_get_body(ent, &bp, &sz);
82 tt_int_op(r, OP_EQ, 0);
83 tt_u64_op(sz, OP_EQ, 5);
84 tt_mem_op(bp, OP_EQ, "A\0B\0C", 5);
85 tt_assert(consensus_cache_entry_is_mapped(ent));
87 /* Free and re-create the cache, to rescan the directory. */
88 consensus_cache_free(cache);
89 consensus_cache_entry_decref(ent);
90 cache = consensus_cache_open("cons", 128);
92 /* Make sure the entry is still there */
93 ent = consensus_cache_find_first(cache, "Hello", "mundo");
94 tt_assert(ent);
95 ent2 = consensus_cache_find_first(cache, "Adios", "planets");
96 tt_ptr_op(ent, OP_EQ, ent2);
97 consensus_cache_entry_incref(ent);
98 tt_assert(! consensus_cache_entry_is_mapped(ent));
99 r = consensus_cache_entry_get_body(ent, &bp, &sz);
100 tt_int_op(r, OP_EQ, 0);
101 tt_u64_op(sz, OP_EQ, 5);
102 tt_mem_op(bp, OP_EQ, "xyzzy", 5);
103 tt_assert(consensus_cache_entry_is_mapped(ent));
105 /* There should be two entries total. */
106 smartlist_t *entries = smartlist_new();
107 consensus_cache_find_all(entries, cache, NULL, NULL);
108 int n = smartlist_len(entries);
109 smartlist_free(entries);
110 tt_int_op(n, OP_EQ, 2);
112 done:
113 consensus_cache_entry_decref(ent);
114 tor_free(ddir_fname);
115 consensus_cache_free(cache);
118 static void
119 test_conscache_cleanup(void *arg)
121 (void)arg;
122 const int N = 20;
123 consensus_cache_entry_t **ents =
124 tor_calloc(N, sizeof(consensus_cache_entry_t*));
126 /* Make a temporary datadir for these tests */
127 char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cache"));
128 tor_free(get_options_mutable()->CacheDirectory);
129 get_options_mutable()->CacheDirectory = tor_strdup(ddir_fname);
130 check_private_dir(ddir_fname, CPD_CREATE, NULL);
131 consensus_cache_t *cache = consensus_cache_open("cons", 128);
133 tt_assert(cache);
135 /* Create a bunch of entries. */
136 int i;
137 for (i = 0; i < N; ++i) {
138 config_line_t *labels = NULL;
139 char num[8];
140 tor_snprintf(num, sizeof(num), "%d", i);
141 config_line_append(&labels, "test-id", "cleanup");
142 config_line_append(&labels, "index", num);
143 size_t bodylen = i * 3;
144 uint8_t *body = tor_malloc(bodylen);
145 memset(body, i, bodylen);
146 ents[i] = consensus_cache_add(cache, labels, body, bodylen);
147 tor_free(body);
148 config_free_lines(labels);
149 tt_assert(ents[i]);
150 /* We're still holding a reference to each entry at this point. */
153 /* Page all of the entries into RAM */
154 for (i = 0; i < N; ++i) {
155 const uint8_t *bp;
156 size_t sz;
157 tt_assert(! consensus_cache_entry_is_mapped(ents[i]));
158 consensus_cache_entry_get_body(ents[i], &bp, &sz);
159 tt_assert(consensus_cache_entry_is_mapped(ents[i]));
162 /* Mark some of the entries as deletable. */
163 for (i = 7; i < N; i += 7) {
164 consensus_cache_entry_mark_for_removal(ents[i]);
165 tt_assert(consensus_cache_entry_is_mapped(ents[i]));
168 /* Mark some of the entries as aggressively unpaged. */
169 for (i = 3; i < N; i += 3) {
170 consensus_cache_entry_mark_for_aggressive_release(ents[i]);
171 tt_assert(consensus_cache_entry_is_mapped(ents[i]));
174 /* Incref some of the entries again */
175 for (i = 0; i < N; i += 2) {
176 consensus_cache_entry_incref(ents[i]);
179 /* Now we're going to decref everything. We do so at a specific time. I'm
180 * picking the moment when I was writing this test, at 2017-04-05 12:16:48
181 * UTC. */
182 const time_t example_time = 1491394608;
183 update_approx_time(example_time);
184 for (i = 0; i < N; ++i) {
185 consensus_cache_entry_decref(ents[i]);
186 if (i % 2) {
187 ents[i] = NULL; /* We're no longer holding any reference here. */
191 /* At this point, the aggressively-released items with refcount 1 should
192 * be unmapped. Nothing should be deleted. */
193 consensus_cache_entry_t *e_tmp;
194 e_tmp = consensus_cache_find_first(cache, "index", "3");
195 tt_assert(e_tmp);
196 tt_assert(! consensus_cache_entry_is_mapped(e_tmp));
197 e_tmp = consensus_cache_find_first(cache, "index", "5");
198 tt_assert(e_tmp);
199 tt_assert(consensus_cache_entry_is_mapped(e_tmp));
200 e_tmp = consensus_cache_find_first(cache, "index", "6");
201 tt_assert(e_tmp);
202 tt_assert(consensus_cache_entry_is_mapped(e_tmp));
203 e_tmp = consensus_cache_find_first(cache, "index", "7");
204 tt_ptr_op(e_tmp, OP_EQ, NULL); // not found because pending deletion.
206 /* Delete the pending-deletion items. */
207 consensus_cache_delete_pending(cache, 0);
209 smartlist_t *entries = smartlist_new();
210 consensus_cache_find_all(entries, cache, NULL, NULL);
211 int n = smartlist_len(entries);
212 smartlist_free(entries);
213 tt_int_op(n, OP_EQ, 20 - 2); /* 1 entry was deleted; 1 is not-found. */
215 e_tmp = consensus_cache_find_first(cache, "index", "7"); // refcnt == 1...
216 tt_ptr_op(e_tmp, OP_EQ, NULL); // so deleted.
217 e_tmp = consensus_cache_find_first(cache, "index", "14"); // refcnt == 2
218 tt_ptr_op(e_tmp, OP_EQ, NULL); // not deleted; but not found.
220 /* Now do lazy unmapping. */
221 // should do nothing.
222 consensus_cache_unmap_lazy(cache, example_time - 10);
223 e_tmp = consensus_cache_find_first(cache, "index", "11");
224 tt_assert(e_tmp);
225 tt_assert(consensus_cache_entry_is_mapped(e_tmp));
226 // should actually unmap
227 consensus_cache_unmap_lazy(cache, example_time + 10);
228 e_tmp = consensus_cache_find_first(cache, "index", "11");
229 tt_assert(e_tmp);
230 tt_assert(! consensus_cache_entry_is_mapped(e_tmp));
231 // This one will still be mapped, since it has a reference.
232 e_tmp = consensus_cache_find_first(cache, "index", "16");
233 tt_assert(e_tmp);
234 tt_assert(consensus_cache_entry_is_mapped(e_tmp));
236 for (i = 0; i < N; ++i) {
237 consensus_cache_entry_decref(ents[i]);
238 ents[i] = NULL;
241 /* Free and re-create the cache, to rescan the directory. Make sure the
242 * deleted thing is still deleted, along with the other deleted thing. */
243 consensus_cache_free(cache);
244 cache = consensus_cache_open("cons", 128);
246 smartlist_t *entries = smartlist_new();
247 consensus_cache_find_all(entries, cache, NULL, NULL);
248 int n = smartlist_len(entries);
249 smartlist_free(entries);
250 tt_int_op(n, OP_EQ, 18);
253 done:
254 for (i = 0; i < N; ++i) {
255 consensus_cache_entry_decref(ents[i]);
257 tor_free(ents);
258 tor_free(ddir_fname);
259 consensus_cache_free(cache);
262 static void
263 test_conscache_filter(void *arg)
265 (void)arg;
266 const int N = 30;
267 smartlist_t *lst = NULL;
269 /* Make a temporary datadir for these tests */
270 char *ddir_fname = tor_strdup(get_fname_rnd("datadir_cache"));
271 tor_free(get_options_mutable()->CacheDirectory);
272 get_options_mutable()->CacheDirectory = tor_strdup(ddir_fname);
273 check_private_dir(ddir_fname, CPD_CREATE, NULL);
274 consensus_cache_t *cache = consensus_cache_open("cons", 128);
276 tt_assert(cache);
278 /* Create a bunch of entries with different labels */
279 int i;
280 for (i = 0; i < N; ++i) {
281 config_line_t *labels = NULL;
282 char num[8];
283 tor_snprintf(num, sizeof(num), "%d", i);
284 config_line_append(&labels, "test-id", "filter");
285 config_line_append(&labels, "index", num);
286 tor_snprintf(num, sizeof(num), "%d", i % 3);
287 config_line_append(&labels, "mod3", num);
288 tor_snprintf(num, sizeof(num), "%d", i % 5);
289 config_line_append(&labels, "mod5", num);
291 size_t bodylen = i * 3;
292 uint8_t *body = tor_malloc(bodylen);
293 memset(body, i, bodylen);
294 consensus_cache_entry_t *ent =
295 consensus_cache_add(cache, labels, body, bodylen);
296 tor_free(body);
297 config_free_lines(labels);
298 tt_assert(ent);
299 consensus_cache_entry_decref(ent);
302 lst = smartlist_new();
303 /* Find nothing. */
304 consensus_cache_find_all(lst, cache, "mod5", "5");
305 tt_int_op(smartlist_len(lst), OP_EQ, 0);
306 /* Find everything. */
307 consensus_cache_find_all(lst, cache, "test-id", "filter");
308 tt_int_op(smartlist_len(lst), OP_EQ, N);
310 /* Now filter to find the entries that have i%3 == 1 */
311 consensus_cache_filter_list(lst, "mod3", "1");
312 tt_int_op(smartlist_len(lst), OP_EQ, 10);
313 /* Now filter to find the entries that also have i%5 == 3 */
314 consensus_cache_filter_list(lst, "mod5", "3");
315 tt_int_op(smartlist_len(lst), OP_EQ, 2);
316 /* So now we have those entries for which i%15 == 13. */
318 consensus_cache_entry_t *ent1 = smartlist_get(lst, 0);
319 consensus_cache_entry_t *ent2 = smartlist_get(lst, 1);
320 const char *idx1 = consensus_cache_entry_get_value(ent1, "index");
321 const char *idx2 = consensus_cache_entry_get_value(ent2, "index");
322 tt_assert( (!strcmp(idx1, "28") && !strcmp(idx2, "13")) ||
323 (!strcmp(idx1, "13") && !strcmp(idx2, "28")) );
325 done:
326 tor_free(ddir_fname);
327 consensus_cache_free(cache);
328 smartlist_free(lst);
331 #define ENT(name) \
332 { #name, test_conscache_ ## name, TT_FORK, NULL, NULL }
334 struct testcase_t conscache_tests[] = {
335 ENT(open_failure),
336 ENT(simple_usage),
337 ENT(cleanup),
338 ENT(filter),
339 END_OF_TESTCASES