blob: 963a7b3c52f7ead96526920c6e2e4ff855501029 [file] [log] [blame]
/*
*
* Copyright (c) 2020 Project CHIP Authors
* Copyright (c) 2016-2017 Nest Labs, Inc.
* All rights reserved.
*
* Licensed 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.
*/
/**
* @file
* Unit tests for the Chip Pool API.
*
*/
#include <set>
#include <lib/support/Pool.h>
#include <lib/support/PoolWrapper.h>
#include <lib/support/UnitTestRegistration.h>
#include <system/SystemConfig.h>
#include <nlunit-test.h>
namespace chip {
template <class POOL>
size_t GetNumObjectsInUse(const POOL & pool)
{
size_t count = 0;
pool.ForEachActiveObject([&count](const void *) {
++count;
return Loop::Continue;
});
return count;
}
} // namespace chip
namespace {
using namespace chip;
template <typename T, size_t N, ObjectPoolMem P>
void TestReleaseNull(nlTestSuite * inSuite, void * inContext)
{
ObjectPool<T, N, P> pool;
pool.ReleaseObject(nullptr);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == 0);
NL_TEST_ASSERT(inSuite, pool.Allocated() == 0);
}
void TestReleaseNullStatic(nlTestSuite * inSuite, void * inContext)
{
TestReleaseNull<uint32_t, 10, ObjectPoolMem::kInline>(inSuite, inContext);
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
void TestReleaseNullDynamic(nlTestSuite * inSuite, void * inContext)
{
TestReleaseNull<uint32_t, 10, ObjectPoolMem::kHeap>(inSuite, inContext);
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
template <typename T, size_t N, ObjectPoolMem P>
void TestCreateReleaseObject(nlTestSuite * inSuite, void * inContext)
{
ObjectPool<uint32_t, N, ObjectPoolMem::kInline> pool;
uint32_t * obj[N];
NL_TEST_ASSERT(inSuite, pool.Allocated() == 0);
for (int t = 0; t < 2; ++t)
{
pool.ReleaseAll();
NL_TEST_ASSERT(inSuite, pool.Allocated() == 0);
for (size_t i = 0; i < N; ++i)
{
obj[i] = pool.CreateObject();
NL_TEST_ASSERT(inSuite, obj[i] != nullptr);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == i + 1);
NL_TEST_ASSERT(inSuite, pool.Allocated() == i + 1);
}
}
for (size_t i = 0; i < N; ++i)
{
pool.ReleaseObject(obj[i]);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == N - i - 1);
NL_TEST_ASSERT(inSuite, pool.Allocated() == N - i - 1);
}
}
void TestCreateReleaseObjectStatic(nlTestSuite * inSuite, void * inContext)
{
constexpr const size_t kSize = 100;
TestCreateReleaseObject<uint32_t, kSize, ObjectPoolMem::kInline>(inSuite, inContext);
ObjectPool<uint32_t, kSize, ObjectPoolMem::kInline> pool;
uint32_t * obj[kSize];
for (size_t i = 0; i < kSize; ++i)
{
obj[i] = pool.CreateObject();
NL_TEST_ASSERT(inSuite, obj[i] != nullptr);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == i + 1);
NL_TEST_ASSERT(inSuite, pool.Allocated() == i + 1);
}
uint32_t * fail = pool.CreateObject();
NL_TEST_ASSERT(inSuite, fail == nullptr);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == kSize);
NL_TEST_ASSERT(inSuite, pool.Allocated() == kSize);
NL_TEST_ASSERT(inSuite, pool.Exhausted());
pool.ReleaseObject(obj[55]);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == kSize - 1);
NL_TEST_ASSERT(inSuite, pool.Allocated() == kSize - 1);
NL_TEST_ASSERT(inSuite, !pool.Exhausted());
NL_TEST_ASSERT(inSuite, obj[55] == pool.CreateObject());
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == kSize);
NL_TEST_ASSERT(inSuite, pool.Allocated() == kSize);
NL_TEST_ASSERT(inSuite, pool.Exhausted());
fail = pool.CreateObject();
NL_TEST_ASSERT(inSuite, fail == nullptr);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == kSize);
NL_TEST_ASSERT(inSuite, pool.Allocated() == kSize);
NL_TEST_ASSERT(inSuite, pool.Exhausted());
pool.ReleaseAll();
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
void TestCreateReleaseObjectDynamic(nlTestSuite * inSuite, void * inContext)
{
TestCreateReleaseObject<uint32_t, 100, ObjectPoolMem::kHeap>(inSuite, inContext);
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
template <ObjectPoolMem P>
void TestCreateReleaseStruct(nlTestSuite * inSuite, void * inContext)
{
struct S
{
S(std::set<S *> & set) : mSet(set) { mSet.insert(this); }
~S() { mSet.erase(this); }
std::set<S *> & mSet;
};
std::set<S *> objs1;
constexpr const size_t kSize = 100;
ObjectPool<S, kSize, P> pool;
S * objs2[kSize];
for (size_t i = 0; i < kSize; ++i)
{
objs2[i] = pool.CreateObject(objs1);
NL_TEST_ASSERT(inSuite, objs2[i] != nullptr);
NL_TEST_ASSERT(inSuite, pool.Allocated() == i + 1);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == i + 1);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == objs1.size());
}
for (size_t i = 0; i < kSize; ++i)
{
pool.ReleaseObject(objs2[i]);
NL_TEST_ASSERT(inSuite, pool.Allocated() == kSize - i - 1);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == kSize - i - 1);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == objs1.size());
}
// Verify that ReleaseAll() calls the destructors.
for (auto & obj : objs2)
{
obj = pool.CreateObject(objs1);
}
NL_TEST_ASSERT(inSuite, objs1.size() == kSize);
NL_TEST_ASSERT(inSuite, pool.Allocated() == kSize);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == kSize);
printf("allocated = %u\n", static_cast<unsigned int>(pool.Allocated()));
printf("highwater = %u\n", static_cast<unsigned int>(pool.HighWaterMark()));
pool.ReleaseAll();
printf("allocated = %u\n", static_cast<unsigned int>(pool.Allocated()));
printf("highwater = %u\n", static_cast<unsigned int>(pool.HighWaterMark()));
NL_TEST_ASSERT(inSuite, objs1.size() == 0);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(pool) == 0);
NL_TEST_ASSERT(inSuite, pool.Allocated() == 0);
NL_TEST_ASSERT(inSuite, pool.HighWaterMark() == kSize);
}
void TestCreateReleaseStructStatic(nlTestSuite * inSuite, void * inContext)
{
TestCreateReleaseStruct<ObjectPoolMem::kInline>(inSuite, inContext);
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
void TestCreateReleaseStructDynamic(nlTestSuite * inSuite, void * inContext)
{
TestCreateReleaseStruct<ObjectPoolMem::kHeap>(inSuite, inContext);
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
template <ObjectPoolMem P>
void TestForEachActiveObject(nlTestSuite * inSuite, void * inContext)
{
struct S
{
S(size_t id) : mId(id) {}
size_t mId;
};
constexpr size_t kSize = 50;
S * objArray[kSize];
std::set<size_t> objIds;
ObjectPool<S, kSize, P> pool;
for (size_t i = 0; i < kSize; ++i)
{
objArray[i] = pool.CreateObject(i);
NL_TEST_ASSERT(inSuite, objArray[i] != nullptr);
NL_TEST_ASSERT(inSuite, objArray[i]->mId == i);
objIds.insert(i);
}
// Default constructor of an iterator should be pointing to the pool end.
{
typename ObjectPoolIterator<S, P>::Type defaultIterator;
NL_TEST_ASSERT(inSuite, defaultIterator == pool.end());
}
// Verify that iteration visits all objects.
size_t count = 0;
{
size_t sum = 0;
pool.ForEachActiveObject([&](S * object) {
NL_TEST_ASSERT(inSuite, object != nullptr);
if (object == nullptr)
{
// NL_TEST_ASSERT doesn't stop running the test and we want to avoid nullptr dereference.
return Loop::Continue;
}
NL_TEST_ASSERT(inSuite, objIds.count(object->mId) == 1);
objIds.erase(object->mId);
++count;
sum += object->mId;
return Loop::Continue;
});
NL_TEST_ASSERT(inSuite, count == kSize);
NL_TEST_ASSERT(inSuite, sum == kSize * (kSize - 1) / 2);
NL_TEST_ASSERT(inSuite, objIds.size() == 0);
}
// Test begin/end iteration
{
// re-create the above test environment, this time using iterators
for (size_t i = 0; i < kSize; ++i)
{
objIds.insert(i);
}
count = 0;
size_t sum = 0;
for (auto v = pool.begin(); v != pool.end(); ++v)
{
NL_TEST_ASSERT(inSuite, objIds.count((*v)->mId) == 1);
objIds.erase((*v)->mId);
++count;
sum += (*v)->mId;
}
NL_TEST_ASSERT(inSuite, count == kSize);
NL_TEST_ASSERT(inSuite, sum == kSize * (kSize - 1) / 2);
NL_TEST_ASSERT(inSuite, objIds.size() == 0);
}
// Verify that returning Loop::Break stops iterating.
count = 0;
pool.ForEachActiveObject([&](S * object) {
objIds.insert(object->mId);
return ++count != kSize / 2 ? Loop::Continue : Loop::Break;
});
NL_TEST_ASSERT(inSuite, count == kSize / 2);
NL_TEST_ASSERT(inSuite, objIds.size() == kSize / 2);
// Verify that iteration can be nested.
count = 0;
pool.ForEachActiveObject([&](S * outer) {
if (objIds.count(outer->mId) == 1)
{
pool.ForEachActiveObject([&](S * inner) {
if (inner == outer)
{
objIds.erase(inner->mId);
}
else
{
++count;
}
return Loop::Continue;
});
}
return Loop::Continue;
});
NL_TEST_ASSERT(inSuite, count == (kSize - 1) * kSize / 2);
NL_TEST_ASSERT(inSuite, objIds.size() == 0);
// Verify that iteration can be nested for iterator types
{
count = 0;
for (auto v : pool)
{
objIds.insert(v->mId);
if (++count == kSize / 2)
{
break;
}
}
count = 0;
for (auto outer : pool)
{
if (objIds.count(outer->mId) != 1)
{
continue;
}
for (auto inner : pool)
{
if (inner == outer)
{
objIds.erase(inner->mId);
}
else
{
++count;
}
}
}
NL_TEST_ASSERT(inSuite, count == (kSize - 1) * kSize / 2);
NL_TEST_ASSERT(inSuite, objIds.size() == 0);
}
count = 0;
pool.ForEachActiveObject([&](S * object) {
++count;
if ((object->mId % 2) == 0)
{
objArray[object->mId] = nullptr;
pool.ReleaseObject(object);
}
else
{
objIds.insert(object->mId);
}
return Loop::Continue;
});
NL_TEST_ASSERT(inSuite, count == kSize);
NL_TEST_ASSERT(inSuite, objIds.size() == kSize / 2);
for (size_t i = 0; i < kSize; ++i)
{
if ((i % 2) == 0)
{
NL_TEST_ASSERT(inSuite, objArray[i] == nullptr);
}
else
{
NL_TEST_ASSERT(inSuite, objArray[i] != nullptr);
NL_TEST_ASSERT(inSuite, objArray[i]->mId == i);
}
}
count = 0;
pool.ForEachActiveObject([&](S * object) {
++count;
if ((object->mId % 2) == 1)
{
size_t id = object->mId - 1;
NL_TEST_ASSERT(inSuite, objArray[id] == nullptr);
objArray[id] = pool.CreateObject(id);
NL_TEST_ASSERT(inSuite, objArray[id] != nullptr);
}
return Loop::Continue;
});
for (size_t i = 0; i < kSize; ++i)
{
NL_TEST_ASSERT(inSuite, objArray[i] != nullptr);
NL_TEST_ASSERT(inSuite, objArray[i]->mId == i);
}
NL_TEST_ASSERT(inSuite, count >= kSize / 2);
NL_TEST_ASSERT(inSuite, count <= kSize);
// Test begin/end iteration
{
count = 0;
for (auto object : pool)
{
++count;
if ((object->mId % 2) == 0)
{
objArray[object->mId] = nullptr;
// NOTE: this explicitly tests if pool supports releasing while iterating
// this MUST be supported by contract of Pool iterators
pool.ReleaseObject(object);
}
else
{
objIds.insert(object->mId);
}
}
NL_TEST_ASSERT(inSuite, count == kSize);
NL_TEST_ASSERT(inSuite, objIds.size() == kSize / 2);
// validate we iterate only over active objects
for (auto object : pool)
{
NL_TEST_ASSERT(inSuite, (object->mId % 2) == 1);
}
for (size_t i = 0; i < kSize; ++i)
{
if ((i % 2) == 0)
{
NL_TEST_ASSERT(inSuite, objArray[i] == nullptr);
}
else
{
NL_TEST_ASSERT(inSuite, objArray[i] != nullptr);
NL_TEST_ASSERT(inSuite, objArray[i]->mId == i);
}
}
count = 0;
for (auto object : pool)
{
++count;
if ((object->mId % 2) != 1)
{
continue;
}
size_t id = object->mId - 1;
NL_TEST_ASSERT(inSuite, objArray[id] == nullptr);
objArray[id] = pool.CreateObject(id);
NL_TEST_ASSERT(inSuite, objArray[id] != nullptr);
}
for (size_t i = 0; i < kSize; ++i)
{
NL_TEST_ASSERT(inSuite, objArray[i] != nullptr);
NL_TEST_ASSERT(inSuite, objArray[i]->mId == i);
}
NL_TEST_ASSERT(inSuite, count >= kSize / 2);
NL_TEST_ASSERT(inSuite, count <= kSize);
}
pool.ReleaseAll();
}
void TestForEachActiveObjectStatic(nlTestSuite * inSuite, void * inContext)
{
TestForEachActiveObject<ObjectPoolMem::kInline>(inSuite, inContext);
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
void TestForEachActiveObjectDynamic(nlTestSuite * inSuite, void * inContext)
{
TestForEachActiveObject<ObjectPoolMem::kHeap>(inSuite, inContext);
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
template <ObjectPoolMem P>
void TestPoolInterface(nlTestSuite * inSuite, void * inContext)
{
struct TestObject
{
TestObject(uint32_t * set, size_t id) : mSet(set), mId(id) { *mSet |= (1 << mId); }
~TestObject() { *mSet &= ~(1 << mId); }
uint32_t * mSet;
size_t mId;
};
using TestObjectPoolType = PoolInterface<TestObject, uint32_t *, size_t>;
struct PoolHolder
{
PoolHolder(TestObjectPoolType & testObjectPool) : mTestObjectPoolInterface(testObjectPool) {}
TestObjectPoolType & mTestObjectPoolInterface;
};
constexpr size_t kSize = 10;
PoolImpl<TestObject, kSize, P, typename TestObjectPoolType::Interface> testObjectPool;
PoolHolder poolHolder(testObjectPool);
uint32_t bits = 0;
TestObject * objs2[kSize];
for (size_t i = 0; i < kSize; ++i)
{
objs2[i] = poolHolder.mTestObjectPoolInterface.CreateObject(&bits, i);
NL_TEST_ASSERT(inSuite, objs2[i] != nullptr);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(poolHolder.mTestObjectPoolInterface) == i + 1);
NL_TEST_ASSERT(inSuite, bits == (1ul << (i + 1)) - 1);
}
for (size_t i = 0; i < kSize; ++i)
{
poolHolder.mTestObjectPoolInterface.ReleaseObject(objs2[i]);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(poolHolder.mTestObjectPoolInterface) == kSize - i - 1);
}
NL_TEST_ASSERT(inSuite, bits == 0);
// Verify that ReleaseAll() calls the destructors.
for (size_t i = 0; i < kSize; ++i)
{
objs2[i] = poolHolder.mTestObjectPoolInterface.CreateObject(&bits, i);
}
NL_TEST_ASSERT(inSuite, bits == (1ul << kSize) - 1);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(poolHolder.mTestObjectPoolInterface) == kSize);
poolHolder.mTestObjectPoolInterface.ReleaseAll();
NL_TEST_ASSERT(inSuite, bits == 0);
NL_TEST_ASSERT(inSuite, GetNumObjectsInUse(poolHolder.mTestObjectPoolInterface) == 0);
}
void TestPoolInterfaceStatic(nlTestSuite * inSuite, void * inContext)
{
TestPoolInterface<ObjectPoolMem::kInline>(inSuite, inContext);
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
void TestPoolInterfaceDynamic(nlTestSuite * inSuite, void * inContext)
{
TestPoolInterface<ObjectPoolMem::kHeap>(inSuite, inContext);
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
int Setup(void * inContext)
{
return ::chip::Platform::MemoryInit() == CHIP_NO_ERROR ? SUCCESS : FAILURE;
}
int Teardown(void * inContext)
{
::chip::Platform::MemoryShutdown();
return SUCCESS;
}
} // namespace
#define NL_TEST_DEF_FN(fn) NL_TEST_DEF("Test " #fn, fn)
/**
* Test Suite. It lists all the test functions.
*/
static const nlTest sTests[] = {
// clang-format off
NL_TEST_DEF_FN(TestReleaseNullStatic),
NL_TEST_DEF_FN(TestCreateReleaseObjectStatic),
NL_TEST_DEF_FN(TestCreateReleaseStructStatic),
NL_TEST_DEF_FN(TestForEachActiveObjectStatic),
NL_TEST_DEF_FN(TestPoolInterfaceStatic),
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
NL_TEST_DEF_FN(TestReleaseNullDynamic),
NL_TEST_DEF_FN(TestCreateReleaseObjectDynamic),
NL_TEST_DEF_FN(TestCreateReleaseStructDynamic),
NL_TEST_DEF_FN(TestForEachActiveObjectDynamic),
NL_TEST_DEF_FN(TestPoolInterfaceDynamic),
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
NL_TEST_SENTINEL()
// clang-format on
};
int TestPool()
{
nlTestSuite theSuite = { "CHIP Pool tests", &sTests[0], Setup, Teardown };
// Run test suite against one context.
nlTestRunner(&theSuite, nullptr);
return nlTestRunnerStats(&theSuite);
}
CHIP_REGISTER_TEST_SUITE(TestPool);