blob: 5217a76a7bf7a601f8ad851d0151d08d982cfcbf [file] [log] [blame]
/** @file
Unit tests for RefCountCache
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <iostream>
#include <RefCountCache.cc>
#include <I_EventSystem.h>
#include "tscore/I_Layout.h"
#include <diags.i>
#include <set>
// TODO: add tests with expiry_time
class ExampleStruct : public RefCountObj
{
public:
int idx;
int name_offset; // pointer addr to name
static std::set<ExampleStruct *> items_freed;
// Return the char* to the name (TODO: cleaner interface??)
char *
name()
{
return reinterpret_cast<char *>(this) + this->name_offset;
}
static ExampleStruct *
alloc(int size = 0)
{
return new (malloc(sizeof(ExampleStruct) + size)) ExampleStruct();
}
static void
dealloc(ExampleStruct *e)
{
e->~ExampleStruct();
::free(e);
}
// Really free the memory, we can use asan leak detection to verify it was freed
void
free() override
{
this->idx = -1;
items_freed.insert(this);
printf("freeing: %p items_freed.size(): %zu\n", this, items_freed.size());
}
static ExampleStruct *
unmarshall(char *buf, unsigned int size)
{
if (size < sizeof(ExampleStruct)) {
return nullptr;
}
ExampleStruct *ret = ExampleStruct::alloc(size - sizeof(ExampleStruct));
memcpy((void *)ret, buf, size);
// Reset the refcount back to 0, this is a bit ugly-- but I'm not sure we want to expose a method
// to mess with the refcount, since this is a fairly unique use case
ret = new (ret) ExampleStruct();
return ret;
}
};
std::set<ExampleStruct *> ExampleStruct::items_freed;
void
fillCache(RefCountCache<ExampleStruct> *cache, int start, int end)
{
// TODO: name per?
std::string name = "foobar";
int allocSize = name.size() + 1;
for (int i = start; i < end; i++) {
ExampleStruct *tmp = ExampleStruct::alloc(allocSize);
cache->put(static_cast<uint64_t>(i), tmp);
tmp->idx = i;
tmp->name_offset = sizeof(ExampleStruct);
memcpy(tmp->name(), name.c_str(), name.size());
// nullptr terminate the string
*(tmp->name() + name.size()) = '\0';
// Print out the struct we put in there
// printf("New ExampleStruct%d idx=%d name=%s allocSize=%d\n", i, tmp->idx, name.c_str(), allocSize);
}
printf("Loading complete! Cache now has %ld items.\n\n", cache->count());
}
int
verifyCache(RefCountCache<ExampleStruct> *cache, int start, int end)
{
// Re-query all the structs to make sure they are there and accurate
for (int i = start; i < end; i++) {
Ptr<ExampleStruct> ccitem = cache->get(i);
ExampleStruct *tmp = ccitem.get();
if (tmp == nullptr) {
// printf("ExampleStruct %d missing, skipping\n", i);
continue;
}
// printf("Get (%p) ExampleStruct%d idx=%d name=%s\n", tmp, i, tmp->idx, tmp->name());
// Check that idx is correct
if (tmp->idx != i) {
printf("IDX of ExampleStruct%d incorrect! (%d)\n", i, tmp->idx);
return 1; // TODO: spin over all?
}
}
return 0;
}
// TODO: check that the memory was actually free-d better
int
testRefcounting()
{
int ret = 0;
RefCountCache<ExampleStruct> *cache = new RefCountCache<ExampleStruct>(4);
// Create and then immediately delete an item
ExampleStruct *to_delete = ExampleStruct::alloc();
ret |= to_delete->refcount() != 0;
cache->put(1, to_delete);
ret |= to_delete->refcount() != 1;
cache->erase(1);
ret |= to_delete->refcount() != 0;
ret |= to_delete->idx != -1;
// Set an item in the cache
ExampleStruct *tmp = ExampleStruct::alloc();
ret |= tmp->refcount() != 0;
printf("ret=%d ref=%d\n", ret, tmp->refcount());
cache->put(static_cast<uint64_t>(1), tmp);
ret |= tmp->refcount() != 1;
printf("ret=%d ref=%d\n", ret, tmp->refcount());
tmp->idx = 1;
// Grab a pointer to item 1
Ptr<ExampleStruct> ccitem = cache->get(static_cast<uint64_t>(1));
ret |= tmp->refcount() != 2;
printf("ret=%d ref=%d\n", ret, tmp->refcount());
Ptr<ExampleStruct> tmpAfter = cache->get(static_cast<uint64_t>(1));
ret |= tmp->refcount() != 3;
printf("ret=%d ref=%d\n", ret, tmp->refcount());
// Delete a single item
cache->erase(1);
ret |= tmp->refcount() != 2;
printf("ret=%d ref=%d\n", ret, tmp->refcount());
// verify that it still isn't in there
ret |= cache->get(1).get() != nullptr;
printf("ret=%d ref=%d\n", ret, tmp->refcount());
ret |= tmpAfter.get()->idx != 1;
printf("ret=%d ref=%d\n", ret, tmp->refcount());
delete cache;
return ret;
}
int
testclear()
{
int ret = 0;
RefCountCache<ExampleStruct> *cache = new RefCountCache<ExampleStruct>(4);
// Create and then immediately delete an item
ExampleStruct *item = ExampleStruct::alloc();
ret |= item->refcount() != 0;
cache->put(1, item);
ret |= item->refcount() != 1;
cache->clear();
ret |= item->refcount() != 0;
ret |= item->idx != -1;
return ret;
}
int
test()
{
// Initialize IOBufAllocator
RecModeT mode_type = RECM_STAND_ALONE;
Layout::create();
init_diags("", nullptr);
RecProcessInit(mode_type);
ink_event_system_init(EVENT_SYSTEM_MODULE_PUBLIC_VERSION);
int ret = 0;
printf("Starting tests\n");
printf("Testing refcounts\n");
ret |= testRefcounting();
printf("refcount ret %d\n", ret);
// Initialize our cache
int cachePartitions = 4;
RefCountCache<ExampleStruct> *cache = new RefCountCache<ExampleStruct>(cachePartitions);
printf("Created...\n");
LoadRefCountCacheFromPath<ExampleStruct>(*cache, "/tmp", "/tmp/hostdb_cache", ExampleStruct::unmarshall);
printf("Cache started...\n");
int numTestEntries = 10000;
// See if anything persisted across the restart
ret |= verifyCache(cache, 0, numTestEntries);
printf("done verifying startup\n");
// Clear the cache
cache->clear();
ret |= cache->count() != 0;
printf("clear %d\n", ret);
// fill it
printf("filling...\n");
fillCache(cache, 0, numTestEntries);
printf("filled...\n");
// Verify that it has items
printf("verifying...\n");
ret |= verifyCache(cache, 0, numTestEntries);
printf("verified %d\n", ret);
// Verify that we can alloc() with no extra space
printf("Alloc item idx 1\n");
ExampleStruct *tmp = ExampleStruct::alloc();
cache->put(static_cast<uint64_t>(1), tmp);
tmp->idx = 1;
Ptr<ExampleStruct> tmpAfter = cache->get(static_cast<uint64_t>(1));
printf("Item after (ret=%d) %d %d\n", ret, 1, tmpAfter->idx);
// Verify every item in the cache
ret |= verifyCache(cache, 0, numTestEntries);
printf("verified entire cache ret=%d\n", ret);
// Grab a pointer to item 1
Ptr<ExampleStruct> ccitem = cache->get(static_cast<uint64_t>(1));
ccitem->idx = 1;
// Delete a single item
cache->erase(1);
// verify that it still isn't in there
ret |= cache->get(1).get() != nullptr;
ret |= ccitem.get()->idx != 1;
printf("ret=%d\n", ret);
// Verify every item in the cache
ret |= verifyCache(cache, 0, numTestEntries);
// TODO: figure out how to test syncing/loading
// write out the whole thing
// printf("Sync return: %d\n", cache->sync_all());
printf("TestRun: %d\n", ret);
delete cache;
return ret;
}
int
main()
{
int ret = test();
for (const auto item : ExampleStruct::items_freed) {
printf("really freeing: %p\n", item);
ExampleStruct::dealloc(item);
}
ExampleStruct::items_freed.clear();
return ret;
}