opr: Introduce opr_cache
[openafs.git] / tests / opr / cache-t.c
1 /*
2  * Copyright (c) 2019 Sine Nomine Associates. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
14  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
15  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
16  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
17  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
18  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
19  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
20  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
22  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #include <afsconfig.h>
26 #include <afs/param.h>
27
28 #include <errno.h>
29 #include <string.h>
30
31 #include <tests/tap/basic.h>
32 #include <afs/opr.h>
33
34 #include <stdlib.h>
35 #include <time.h>
36
37 struct {
38     char *key;
39     int key_len;
40
41     char *val;
42     int val_len;
43 } items[] = {
44
45 #define TCASE(key, len, val) { (key), (len), (val), sizeof(val)-1 }
46
47     TCASE("foo\0\0", 6, "one"),
48     TCASE("bar\0\0", 6, "two"),
49     TCASE("baz\0\0", 6, "three"),
50     TCASE("quux\0",  6, "four"),
51     TCASE("pants",   6, "five"),
52
53     TCASE("foo\0\0", 4, "six"),
54     TCASE("bar\0\0", 4, "seven"),
55     TCASE("baz\0\0", 4, "eight"),
56     TCASE("quux\0",  5, "nine"),
57
58     TCASE("foo1\0", 6, "ten"),
59     TCASE("bar1\0", 6, "eleven"),
60     TCASE("baz1\0", 6, "twelve"),
61     TCASE("quux1",  6, "thirteen"),
62
63     TCASE("f\xf3\x0a", 5, "value \x01"),
64     TCASE("ba\xffr", 5, "\x01\x02\x03"),
65     TCASE("ba\xffz", 5, "\0\0\0\0"),
66
67 #undef TCASE
68
69 };
70 static const int n_items = sizeof(items)/sizeof(items[0]);
71
72 static void
73 run_seed(int seed)
74 {
75     struct opr_cache_opts opts;
76     struct opr_cache *cache = NULL;
77     int item_i;
78     int missing;
79     int code;
80     char val[1024];
81     size_t val_len;
82
83     srand(seed);
84
85     val_len = sizeof(val);
86     code = opr_cache_get(cache, NULL, 0, val, &val_len);
87     is_int(ENOENT, code,
88        "Looking up in a NULL cache fails with ENOENT");
89
90     opr_cache_put(cache, NULL, 0, NULL, 0);
91     ok(1,
92        "Storing in a NULL cache does nothing");
93
94     memset(&opts, 0, sizeof(opts));
95
96     opts.n_buckets = 2;
97     opts.max_entries = 100;
98     ok(opr_cache_init(&opts, &cache) != 0,
99        "Initializing a cache with a tiny n_buckets fails");
100
101     opts.n_buckets = 0x40000000;
102     ok(opr_cache_init(&opts, &cache) != 0,
103        "Initializing a cache with a huge n_buckets fails");
104
105     opts.n_buckets = 23;
106     ok(opr_cache_init(&opts, &cache) != 0,
107        "Initializing a cache with non-power-of-2 n_buckets fails");
108
109     opts.n_buckets = 64;
110     opts.max_entries = 1;
111     ok(opr_cache_init(&opts, &cache) != 0,
112        "Initializing a cache with a tiny max_entries fails");
113
114     opts.max_entries = 0x7fffffff;
115     ok(opr_cache_init(&opts, &cache) != 0,
116        "Initializing a cache with a huge max_entries fails");
117
118     opts.n_buckets = 8;
119     opts.max_entries = 12;
120
121     code = opr_cache_init(&opts, &cache);
122     is_int(0, code,
123        "Initializing a reasonably-sized cache succeeds");
124
125     ok(cache != NULL,
126        "Initializing a cache gives us a cache");
127
128     for (item_i = 0; item_i < n_items; item_i++) {
129         val_len = sizeof(val);
130         code = opr_cache_get(cache, items[item_i].key,
131                              items[item_i].key_len,
132                              val, &val_len);
133         is_int(ENOENT, code,
134            "[item %d] Looking up in an empty cache fails with ENOENT", item_i);
135     }
136
137     for (item_i = 0; item_i < 12; item_i++) {
138         opr_cache_put(cache, items[item_i].key, items[item_i].key_len,
139                       items[item_i].val, items[item_i].val_len);
140     }
141     ok(1, "Cache filled successfully");
142
143     for (item_i = 0; item_i < 12; item_i++) {
144         val_len = sizeof(val);
145         code = opr_cache_get(cache, items[item_i].key, items[item_i].key_len,
146                              val, &val_len);
147         is_int(0, code, "[item %d] Lookup succeeds", item_i);
148         is_int(items[item_i].val_len, val_len,
149            "[item %d] Lookup returns correct val_len %d",
150            item_i, items[item_i].val_len);
151
152         ok(memcmp(val, items[item_i].val, val_len) == 0,
153            "[item %d] Lookup returns correct val", item_i);
154     }
155
156     val_len = sizeof(val);
157     code = opr_cache_get(cache, NULL, 5, val, &val_len);
158     is_int(ENOENT, code,
159         "Looking up NULL key fails with ENOENT");
160
161     code = opr_cache_get(cache, val, 0, val, &val_len);
162     is_int(ENOENT, code,
163         "Looking up 0-length key fails with ENOENT");
164
165     opr_cache_put(cache, NULL, 0, val, val_len);
166     opr_cache_put(cache, NULL, 5, val, val_len);
167     opr_cache_put(cache, val, 0, val, val_len);
168     opr_cache_put(cache, val, val_len, NULL, 0);
169     opr_cache_put(cache, val, val_len, NULL, 5);
170     opr_cache_put(cache, val, val_len, val, 0);
171     opr_cache_put(cache, NULL, 0, NULL, 0);
172     opr_cache_put(cache, NULL, 5, NULL, 5);
173     opr_cache_put(cache, val, 0, val, 0);
174     ok(1, "Storing NULL/0-length entries does nothing");
175
176     code = opr_cache_get(cache, "123", 3, val, &val_len);
177     is_int(ENOENT, code, "Cache lookup fails for nonexistent item");
178
179     memcpy(val, "replace", 7);
180     val_len = 7;
181     opr_cache_put(cache, items[0].key, items[0].key_len, val, val_len);
182     ok(1, "Replacement store succeeds");
183
184     val_len = 1;
185     code = opr_cache_get(cache, items[0].key, items[0].key_len, val, &val_len);
186     is_int(ENOSPC, code, "Small lookup returns ENOSPC");
187
188     val_len = sizeof(val);
189     code = opr_cache_get(cache, items[0].key, items[0].key_len, val, &val_len);
190     is_int(0, code, "Replacement lookup succeeds");
191     is_int(7, val_len, "Lookup trims val_len");
192     ok(memcmp(val, "replace", 7) == 0,
193         "Replacement lookup returns correct value");
194
195     /* Set items[0] back to the original value. */
196     opr_cache_put(cache, items[0].key, items[0].key_len, items[0].val,
197                   items[0].val_len);
198
199     for (item_i = 12; item_i < n_items; item_i++) {
200         opr_cache_put(cache, items[item_i].key, items[item_i].key_len,
201                       items[item_i].val, items[item_i].val_len);
202     }
203     ok(1, "[seed %d] Cache over-filled successfully", seed);
204
205     missing = 0;
206     for (item_i = 0; item_i < n_items; item_i++) {
207         val_len = sizeof(val);
208         code = opr_cache_get(cache, items[item_i].key, items[item_i].key_len,
209                              val, &val_len);
210         if (code == ENOENT) {
211             missing++;
212             continue;
213         }
214         is_int(0, code, "[item %d] Lookup succeeds", item_i);
215         is_int(items[item_i].val_len, val_len,
216            "[item %d] Lookup returns correct val_len %d",
217            item_i, items[item_i].val_len);
218
219         ok(memcmp(val, items[item_i].val, val_len) == 0,
220             "[item %d] Lookup returns correct val", item_i);
221     }
222
223     is_int(4, missing,
224         "[seed %d] Cache lookup fails for %d items", seed, missing);
225
226     opr_cache_free(&cache);
227     ok(1, "Cache free succeeds");
228     ok(cache == NULL, "Cache free NULLs arg");
229
230     opr_cache_free(&cache);
231     ok(1, "Double-free is noop");
232     ok(cache == NULL, "Cache is still NULL after double-free");
233 }
234
235 int
236 main(void)
237 {
238     int seed;
239
240     plan(113 * 32);
241
242     for (seed = 0; seed < 32; seed++) {
243         run_seed(seed);
244     }
245
246     return 0;
247 }