/* * * 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 #include #include #include #include #include namespace chip { template 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 void TestReleaseNull() { ObjectPool pool; pool.ReleaseObject(nullptr); EXPECT_EQ(GetNumObjectsInUse(pool), 0u); EXPECT_EQ(pool.Allocated(), 0u); } TEST_F(TestPool, TestReleaseNullStatic) { TestReleaseNull(); } #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP TEST_F(TestPool, TestReleaseNullDynamic) { TestReleaseNull(); } #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP template void TestCreateReleaseObject() { ObjectPool 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(); ObjectPool 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(); } #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP template void TestCreateReleaseStruct() { struct S { S(std::set & set) : mSet(set) { mSet.insert(this); } ~S() { mSet.erase(this); } std::set & mSet; }; std::set objs1; constexpr const size_t kSize = 100; ObjectPool 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(pool.Allocated())); printf("highwater = %u\n", static_cast(pool.HighWaterMark())); pool.ReleaseAll(); printf("allocated = %u\n", static_cast(pool.Allocated())); printf("highwater = %u\n", static_cast(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(); } #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP TEST_F(TestPool, TestCreateReleaseStructDynamic) { TestCreateReleaseStruct(); } #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP template void TestForEachActiveObject() { struct S { S(size_t id) : mId(id) {} size_t mId; }; constexpr size_t kSize = 50; S * objArray[kSize]; std::set objIds; ObjectPool 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::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(); } #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP TEST_F(TestPool, TestForEachActiveObjectDynamic) { TestForEachActiveObject(); } #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP template 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; struct PoolHolder { PoolHolder(TestObjectPoolType & testObjectPool) : mTestObjectPoolInterface(testObjectPool) {} TestObjectPoolType & mTestObjectPoolInterface; }; constexpr size_t kSize = 10; PoolImpl 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(); } #if CHIP_SYSTEM_CONFIG_POOL_USE_HEAP TEST_F(TestPool, TestPoolInterfaceDynamic) { TestPoolInterface(); } #endif // CHIP_SYSTEM_CONFIG_POOL_USE_HEAP } // namespace