test_conscache.c (11243B)
1 /* Copyright (c) 2017-2021, The Tor Project, Inc. */ 2 /* See LICENSE for licensing information */ 3 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" 9 10 #ifdef HAVE_UTIME_H 11 #include <utime.h> 12 #endif 13 14 static void 15 test_conscache_open_failure(void *arg) 16 { 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); 22 23 done: 24 ; 25 } 26 27 static void 28 test_conscache_simple_usage(void *arg) 29 { 30 (void)arg; 31 consensus_cache_entry_t *ent = NULL, *ent2 = NULL; 32 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); 39 40 tt_assert(cache); 41 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); 51 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; 63 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")); 67 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); 75 76 tt_assert(! consensus_cache_entry_is_mapped(ent)); 77 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)); 86 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); 91 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)); 104 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); 111 112 done: 113 consensus_cache_entry_decref(ent); 114 tor_free(ddir_fname); 115 consensus_cache_free(cache); 116 } 117 118 static void 119 test_conscache_cleanup(void *arg) 120 { 121 (void)arg; 122 const int N = 20; 123 consensus_cache_entry_t **ents = 124 tor_calloc(N, sizeof(consensus_cache_entry_t*)); 125 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); 132 133 tt_assert(cache); 134 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. */ 151 } 152 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])); 160 } 161 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])); 166 } 167 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])); 172 } 173 174 /* Incref some of the entries again */ 175 for (i = 0; i < N; i += 2) { 176 consensus_cache_entry_incref(ents[i]); 177 } 178 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. */ 188 } 189 } 190 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. 205 206 /* Delete the pending-deletion items. */ 207 consensus_cache_delete_pending(cache, 0); 208 { 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. */ 214 } 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. 219 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)); 235 236 for (i = 0; i < N; ++i) { 237 consensus_cache_entry_decref(ents[i]); 238 ents[i] = NULL; 239 } 240 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); 245 { 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); 251 } 252 253 done: 254 for (i = 0; i < N; ++i) { 255 consensus_cache_entry_decref(ents[i]); 256 } 257 tor_free(ents); 258 tor_free(ddir_fname); 259 consensus_cache_free(cache); 260 } 261 262 static void 263 test_conscache_filter(void *arg) 264 { 265 (void)arg; 266 const int N = 30; 267 smartlist_t *lst = NULL; 268 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); 275 276 tt_assert(cache); 277 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); 290 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); 300 } 301 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); 309 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. */ 317 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")) ); 324 325 done: 326 tor_free(ddir_fname); 327 consensus_cache_free(cache); 328 smartlist_free(lst); 329 } 330 331 #define ENT(name) \ 332 { #name, test_conscache_ ## name, TT_FORK, NULL, NULL } 333 334 struct testcase_t conscache_tests[] = { 335 ENT(open_failure), 336 ENT(simple_usage), 337 ENT(cleanup), 338 ENT(filter), 339 END_OF_TESTCASES 340 };