1 /** @file
2 
3   Unit tests for RefCountCache
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22  */
23 
24 #include <iostream>
25 #include <RefCountCache.cc>
26 #include <I_EventSystem.h>
27 #include "tscore/I_Layout.h"
28 #include <diags.i>
29 #include <set>
30 
31 // TODO: add tests with expiry_time
32 
33 class ExampleStruct : public RefCountObj
34 {
35 public:
36   int idx;
37   int name_offset; // pointer addr to name
38   static std::set<ExampleStruct *> items_freed;
39 
40   // Return the char* to the name (TODO: cleaner interface??)
41   char *
name()42   name()
43   {
44     return reinterpret_cast<char *>(this) + this->name_offset;
45   }
46 
47   static ExampleStruct *
alloc(int size=0)48   alloc(int size = 0)
49   {
50     return new (malloc(sizeof(ExampleStruct) + size)) ExampleStruct();
51   }
52 
53   static void
dealloc(ExampleStruct * e)54   dealloc(ExampleStruct *e)
55   {
56     e->~ExampleStruct();
57     ::free(e);
58   }
59 
60   // Really free the memory, we can use asan leak detection to verify it was freed
61   void
free()62   free() override
63   {
64     this->idx = -1;
65     items_freed.insert(this);
66     printf("freeing: %p items_freed.size(): %zu\n", this, items_freed.size());
67   }
68 
69   static ExampleStruct *
unmarshall(char * buf,unsigned int size)70   unmarshall(char *buf, unsigned int size)
71   {
72     if (size < sizeof(ExampleStruct)) {
73       return nullptr;
74     }
75     ExampleStruct *ret = ExampleStruct::alloc(size - sizeof(ExampleStruct));
76     memcpy((void *)ret, buf, size);
77     // Reset the refcount back to 0, this is a bit ugly-- but I'm not sure we want to expose a method
78     // to mess with the refcount, since this is a fairly unique use case
79     ret = new (ret) ExampleStruct();
80     return ret;
81   }
82 };
83 
84 std::set<ExampleStruct *> ExampleStruct::items_freed;
85 
86 void
fillCache(RefCountCache<ExampleStruct> * cache,int start,int end)87 fillCache(RefCountCache<ExampleStruct> *cache, int start, int end)
88 {
89   // TODO: name per?
90   std::string name = "foobar";
91   int allocSize    = name.size() + 1;
92 
93   for (int i = start; i < end; i++) {
94     ExampleStruct *tmp = ExampleStruct::alloc(allocSize);
95     cache->put(static_cast<uint64_t>(i), tmp);
96 
97     tmp->idx         = i;
98     tmp->name_offset = sizeof(ExampleStruct);
99     memcpy(tmp->name(), name.c_str(), name.size());
100     // nullptr terminate the string
101     *(tmp->name() + name.size()) = '\0';
102 
103     // Print out the struct we put in there
104     // printf("New ExampleStruct%d idx=%d name=%s allocSize=%d\n", i, tmp->idx, name.c_str(), allocSize);
105   }
106   printf("Loading complete! Cache now has %ld items.\n\n", cache->count());
107 }
108 
109 int
verifyCache(RefCountCache<ExampleStruct> * cache,int start,int end)110 verifyCache(RefCountCache<ExampleStruct> *cache, int start, int end)
111 {
112   // Re-query all the structs to make sure they are there and accurate
113   for (int i = start; i < end; i++) {
114     Ptr<ExampleStruct> ccitem = cache->get(i);
115     ExampleStruct *tmp        = ccitem.get();
116     if (tmp == nullptr) {
117       // printf("ExampleStruct %d missing, skipping\n", i);
118       continue;
119     }
120     // printf("Get (%p) ExampleStruct%d idx=%d name=%s\n", tmp, i, tmp->idx, tmp->name());
121 
122     // Check that idx is correct
123     if (tmp->idx != i) {
124       printf("IDX of ExampleStruct%d incorrect! (%d)\n", i, tmp->idx);
125       return 1; // TODO: spin over all?
126     }
127   }
128   return 0;
129 }
130 
131 // TODO: check that the memory was actually free-d better
132 int
testRefcounting()133 testRefcounting()
134 {
135   int ret = 0;
136 
137   RefCountCache<ExampleStruct> *cache = new RefCountCache<ExampleStruct>(4);
138 
139   // Create and then immediately delete an item
140   ExampleStruct *to_delete = ExampleStruct::alloc();
141   ret |= to_delete->refcount() != 0;
142   cache->put(1, to_delete);
143   ret |= to_delete->refcount() != 1;
144   cache->erase(1);
145   ret |= to_delete->refcount() != 0;
146   ret |= to_delete->idx != -1;
147 
148   // Set an item in the cache
149   ExampleStruct *tmp = ExampleStruct::alloc();
150   ret |= tmp->refcount() != 0;
151   printf("ret=%d ref=%d\n", ret, tmp->refcount());
152   cache->put(static_cast<uint64_t>(1), tmp);
153   ret |= tmp->refcount() != 1;
154   printf("ret=%d ref=%d\n", ret, tmp->refcount());
155   tmp->idx = 1;
156 
157   // Grab a pointer to item 1
158   Ptr<ExampleStruct> ccitem = cache->get(static_cast<uint64_t>(1));
159   ret |= tmp->refcount() != 2;
160   printf("ret=%d ref=%d\n", ret, tmp->refcount());
161 
162   Ptr<ExampleStruct> tmpAfter = cache->get(static_cast<uint64_t>(1));
163   ret |= tmp->refcount() != 3;
164   printf("ret=%d ref=%d\n", ret, tmp->refcount());
165 
166   // Delete a single item
167   cache->erase(1);
168   ret |= tmp->refcount() != 2;
169   printf("ret=%d ref=%d\n", ret, tmp->refcount());
170   // verify that it still isn't in there
171   ret |= cache->get(1).get() != nullptr;
172   printf("ret=%d ref=%d\n", ret, tmp->refcount());
173   ret |= tmpAfter.get()->idx != 1;
174   printf("ret=%d ref=%d\n", ret, tmp->refcount());
175 
176   delete cache;
177 
178   return ret;
179 }
180 
181 int
testclear()182 testclear()
183 {
184   int ret = 0;
185 
186   RefCountCache<ExampleStruct> *cache = new RefCountCache<ExampleStruct>(4);
187 
188   // Create and then immediately delete an item
189   ExampleStruct *item = ExampleStruct::alloc();
190   ret |= item->refcount() != 0;
191   cache->put(1, item);
192   ret |= item->refcount() != 1;
193   cache->clear();
194   ret |= item->refcount() != 0;
195   ret |= item->idx != -1;
196 
197   return ret;
198 }
199 
200 int
test()201 test()
202 {
203   // Initialize IOBufAllocator
204   RecModeT mode_type = RECM_STAND_ALONE;
205   Layout::create();
206   init_diags("", nullptr);
207   RecProcessInit(mode_type);
208   ink_event_system_init(EVENT_SYSTEM_MODULE_PUBLIC_VERSION);
209 
210   int ret = 0;
211 
212   printf("Starting tests\n");
213 
214   printf("Testing refcounts\n");
215   ret |= testRefcounting();
216   printf("refcount ret %d\n", ret);
217 
218   // Initialize our cache
219   int cachePartitions                 = 4;
220   RefCountCache<ExampleStruct> *cache = new RefCountCache<ExampleStruct>(cachePartitions);
221   printf("Created...\n");
222 
223   LoadRefCountCacheFromPath<ExampleStruct>(*cache, "/tmp", "/tmp/hostdb_cache", ExampleStruct::unmarshall);
224   printf("Cache started...\n");
225   int numTestEntries = 10000;
226 
227   // See if anything persisted across the restart
228   ret |= verifyCache(cache, 0, numTestEntries);
229   printf("done verifying startup\n");
230 
231   // Clear the cache
232   cache->clear();
233   ret |= cache->count() != 0;
234   printf("clear %d\n", ret);
235 
236   // fill it
237   printf("filling...\n");
238   fillCache(cache, 0, numTestEntries);
239   printf("filled...\n");
240 
241   // Verify that it has items
242   printf("verifying...\n");
243   ret |= verifyCache(cache, 0, numTestEntries);
244   printf("verified %d\n", ret);
245 
246   // Verify that we can alloc() with no extra space
247   printf("Alloc item idx 1\n");
248   ExampleStruct *tmp = ExampleStruct::alloc();
249   cache->put(static_cast<uint64_t>(1), tmp);
250   tmp->idx = 1;
251 
252   Ptr<ExampleStruct> tmpAfter = cache->get(static_cast<uint64_t>(1));
253   printf("Item after (ret=%d) %d %d\n", ret, 1, tmpAfter->idx);
254   // Verify every item in the cache
255   ret |= verifyCache(cache, 0, numTestEntries);
256   printf("verified entire cache ret=%d\n", ret);
257 
258   // Grab a pointer to item 1
259   Ptr<ExampleStruct> ccitem = cache->get(static_cast<uint64_t>(1));
260   ccitem->idx               = 1;
261   // Delete a single item
262   cache->erase(1);
263   // verify that it still isn't in there
264   ret |= cache->get(1).get() != nullptr;
265   ret |= ccitem.get()->idx != 1;
266   printf("ret=%d\n", ret);
267 
268   // Verify every item in the cache
269   ret |= verifyCache(cache, 0, numTestEntries);
270 
271   // TODO: figure out how to test syncing/loading
272   // write out the whole thing
273   // printf("Sync return: %d\n", cache->sync_all());
274 
275   printf("TestRun: %d\n", ret);
276 
277   delete cache;
278 
279   return ret;
280 }
281 
282 int
main()283 main()
284 {
285   int ret = test();
286 
287   for (const auto item : ExampleStruct::items_freed) {
288     printf("really freeing: %p\n", item);
289     ExampleStruct::dealloc(item);
290   }
291   ExampleStruct::items_freed.clear();
292 
293   return ret;
294 }
295