blob: da76d7ebb70d7be9bd1628662c342003486a110d [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 <gtest/gtest.h>
#include <lib/support/Pool.h>
#include <lib/support/PoolWrapper.h>
#include <system/SystemConfig.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;
class TestPool : public ::testing::Test
{
public:
static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); }
static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
};
template <typename T, size_t N, ObjectPoolMem P>
void TestReleaseNull()
{
ObjectPool<T, N, P> pool;
pool.ReleaseObject(nullptr);
EXPECT_EQ(GetNumObjectsInUse(pool), 0u);
EXPECT_EQ(pool.Allocated(), 0u);
}
TEST_F(TestPool, TestReleaseNullStatic)
{
TestReleaseNull<uint32_t, 10, ObjectPoolMem::kInline>();
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
TEST_F(TestPool, TestReleaseNullDynamic)
{
TestReleaseNull<uint32_t, 10, ObjectPoolMem::kHeap>();
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
template <typename T, size_t N, ObjectPoolMem P>
void TestCreateReleaseObject()
{
ObjectPool<uint32_t, N, ObjectPoolMem::kInline> pool;
uint32_t * obj[N];
EXPECT_EQ(pool.Allocated(), 0u);
for (int t = 0; t < 2; ++t)
{
pool.ReleaseAll();
EXPECT_EQ(pool.Allocated(), 0u);
for (size_t i = 0; i < N; ++i)
{
obj[i] = pool.CreateObject();
ASSERT_NE(obj[i], nullptr);
EXPECT_EQ(GetNumObjectsInUse(pool), i + 1);
EXPECT_EQ(pool.Allocated(), i + 1);
}
}
for (size_t i = 0; i < N; ++i)
{
pool.ReleaseObject(obj[i]);
EXPECT_EQ(GetNumObjectsInUse(pool), N - i - 1);
EXPECT_EQ(pool.Allocated(), N - i - 1);
}
}
TEST_F(TestPool, TestCreateReleaseObjectStatic)
{
constexpr const size_t kSize = 100;
TestCreateReleaseObject<uint32_t, kSize, ObjectPoolMem::kInline>();
ObjectPool<uint32_t, kSize, ObjectPoolMem::kInline> pool;
uint32_t * obj[kSize];
for (size_t i = 0; i < kSize; ++i)
{
obj[i] = pool.CreateObject();
ASSERT_NE(obj[i], nullptr);
EXPECT_EQ(GetNumObjectsInUse(pool), i + 1);
EXPECT_EQ(pool.Allocated(), i + 1);
}
uint32_t * fail = pool.CreateObject();
EXPECT_EQ(fail, nullptr);
EXPECT_EQ(GetNumObjectsInUse(pool), kSize);
EXPECT_EQ(pool.Allocated(), kSize);
EXPECT_TRUE(pool.Exhausted());
pool.ReleaseObject(obj[55]);
EXPECT_EQ(GetNumObjectsInUse(pool), kSize - 1);
EXPECT_EQ(pool.Allocated(), kSize - 1);
EXPECT_FALSE(pool.Exhausted());
EXPECT_EQ(obj[55], pool.CreateObject());
EXPECT_EQ(GetNumObjectsInUse(pool), kSize);
EXPECT_EQ(pool.Allocated(), kSize);
EXPECT_TRUE(pool.Exhausted());
fail = pool.CreateObject();
ASSERT_EQ(fail, nullptr);
EXPECT_EQ(GetNumObjectsInUse(pool), kSize);
EXPECT_EQ(pool.Allocated(), kSize);
EXPECT_TRUE(pool.Exhausted());
pool.ReleaseAll();
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
TEST_F(TestPool, TestCreateReleaseObjectDynamic)
{
TestCreateReleaseObject<uint32_t, 100, ObjectPoolMem::kHeap>();
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
template <ObjectPoolMem P>
void TestCreateReleaseStruct()
{
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);
ASSERT_NE(objs2[i], nullptr);
EXPECT_EQ(pool.Allocated(), i + 1);
EXPECT_EQ(GetNumObjectsInUse(pool), i + 1);
EXPECT_EQ(GetNumObjectsInUse(pool), objs1.size());
}
for (size_t i = 0; i < kSize; ++i)
{
pool.ReleaseObject(objs2[i]);
EXPECT_EQ(pool.Allocated(), kSize - i - 1);
EXPECT_EQ(GetNumObjectsInUse(pool), kSize - i - 1);
EXPECT_EQ(GetNumObjectsInUse(pool), objs1.size());
}
// Verify that ReleaseAll() calls the destructors.
for (auto & obj : objs2)
{
obj = pool.CreateObject(objs1);
}
EXPECT_EQ(objs1.size(), kSize);
EXPECT_EQ(pool.Allocated(), kSize);
EXPECT_EQ(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()));
EXPECT_EQ(objs1.size(), 0u);
EXPECT_EQ(GetNumObjectsInUse(pool), 0u);
EXPECT_EQ(pool.Allocated(), 0u);
EXPECT_EQ(pool.HighWaterMark(), kSize);
}
TEST_F(TestPool, TestCreateReleaseStructStatic)
{
TestCreateReleaseStruct<ObjectPoolMem::kInline>();
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
TEST_F(TestPool, TestCreateReleaseStructDynamic)
{
TestCreateReleaseStruct<ObjectPoolMem::kHeap>();
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
template <ObjectPoolMem P>
void TestForEachActiveObject()
{
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);
ASSERT_NE(objArray[i], nullptr);
EXPECT_EQ(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;
EXPECT_EQ(defaultIterator, pool.end());
}
// Verify that iteration visits all objects.
size_t count = 0;
{
size_t sum = 0;
pool.ForEachActiveObject([&](S * object) -> Loop {
EXPECT_NE(object, nullptr);
if (object == nullptr)
{
// Using EXPECT_NE instead of ASSERT_NE due to compilation errors when using ASSERT_NE
return Loop::Continue;
}
EXPECT_EQ(objIds.count(object->mId), 1u);
objIds.erase(object->mId);
++count;
sum += object->mId;
return Loop::Continue;
});
EXPECT_EQ(count, kSize);
EXPECT_EQ(sum, kSize * (kSize - 1) / 2);
EXPECT_EQ(objIds.size(), 0u);
}
// 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)
{
EXPECT_EQ(objIds.count((*v)->mId), 1u);
objIds.erase((*v)->mId);
++count;
sum += (*v)->mId;
}
EXPECT_EQ(count, kSize);
EXPECT_EQ(sum, kSize * (kSize - 1) / 2);
EXPECT_EQ(objIds.size(), 0u);
}
// 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;
});
EXPECT_EQ(count, kSize / 2);
EXPECT_EQ(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;
});
EXPECT_EQ(count, (kSize - 1) * kSize / 2);
EXPECT_EQ(objIds.size(), 0u);
// 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;
}
}
}
EXPECT_EQ(count, (kSize - 1) * kSize / 2);
EXPECT_EQ(objIds.size(), 0u);
}
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;
});
EXPECT_EQ(count, kSize);
EXPECT_EQ(objIds.size(), kSize / 2);
for (size_t i = 0; i < kSize; ++i)
{
if ((i % 2) == 0)
{
EXPECT_EQ(objArray[i], nullptr);
}
else
{
ASSERT_NE(objArray[i], nullptr);
EXPECT_EQ(objArray[i]->mId, i);
}
}
count = 0;
pool.ForEachActiveObject([&](S * object) {
++count;
if ((object->mId % 2) == 1)
{
size_t id = object->mId - 1;
EXPECT_EQ(objArray[id], nullptr);
objArray[id] = pool.CreateObject(id);
EXPECT_NE(objArray[id], nullptr);
}
return Loop::Continue;
});
for (size_t i = 0; i < kSize; ++i)
{
ASSERT_NE(objArray[i], nullptr);
EXPECT_EQ(objArray[i]->mId, i);
}
EXPECT_GE(count, kSize / 2);
EXPECT_LE(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);
}
}
EXPECT_EQ(count, kSize);
EXPECT_EQ(objIds.size(), kSize / 2);
// validate we iterate only over active objects
for (auto object : pool)
{
EXPECT_EQ((object->mId % 2), 1u);
}
for (size_t i = 0; i < kSize; ++i)
{
if ((i % 2) == 0)
{
EXPECT_EQ(objArray[i], nullptr);
}
else
{
ASSERT_NE(objArray[i], nullptr);
EXPECT_EQ(objArray[i]->mId, i);
}
}
count = 0;
for (auto object : pool)
{
++count;
if ((object->mId % 2) != 1)
{
continue;
}
size_t id = object->mId - 1;
EXPECT_EQ(objArray[id], nullptr);
objArray[id] = pool.CreateObject(id);
EXPECT_NE(objArray[id], nullptr);
}
for (size_t i = 0; i < kSize; ++i)
{
ASSERT_NE(objArray[i], nullptr);
EXPECT_EQ(objArray[i]->mId, i);
}
EXPECT_GE(count, kSize / 2);
EXPECT_LE(count, kSize);
}
pool.ReleaseAll();
}
TEST_F(TestPool, TestForEachActiveObjectStatic)
{
TestForEachActiveObject<ObjectPoolMem::kInline>();
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
TEST_F(TestPool, TestForEachActiveObjectDynamic)
{
TestForEachActiveObject<ObjectPoolMem::kHeap>();
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
template <ObjectPoolMem P>
void TestPoolInterface()
{
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);
ASSERT_NE(objs2[i], nullptr);
EXPECT_EQ(GetNumObjectsInUse(poolHolder.mTestObjectPoolInterface), i + 1);
EXPECT_EQ(bits, (1ul << (i + 1)) - 1);
}
for (size_t i = 0; i < kSize; ++i)
{
poolHolder.mTestObjectPoolInterface.ReleaseObject(objs2[i]);
EXPECT_EQ(GetNumObjectsInUse(poolHolder.mTestObjectPoolInterface), kSize - i - 1);
}
EXPECT_EQ(bits, 0u);
// Verify that ReleaseAll() calls the destructors.
for (size_t i = 0; i < kSize; ++i)
{
objs2[i] = poolHolder.mTestObjectPoolInterface.CreateObject(&bits, i);
}
EXPECT_EQ(bits, (1ul << kSize) - 1);
EXPECT_EQ(GetNumObjectsInUse(poolHolder.mTestObjectPoolInterface), kSize);
poolHolder.mTestObjectPoolInterface.ReleaseAll();
EXPECT_EQ(bits, 0u);
EXPECT_EQ(GetNumObjectsInUse(poolHolder.mTestObjectPoolInterface), 0u);
}
TEST_F(TestPool, TestPoolInterfaceStatic)
{
TestPoolInterface<ObjectPoolMem::kInline>();
}
#if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
TEST_F(TestPool, TestPoolInterfaceDynamic)
{
TestPoolInterface<ObjectPoolMem::kHeap>();
}
#endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP
} // namespace