From 71e33cf8b2441a6d008f8106e407852417431935 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 16 Dec 2023 00:28:47 +0100 Subject: [PATCH] Add unit tests for GenericObjectCache --- apps/openmw_test_suite/CMakeLists.txt | 2 + .../resource/testobjectcache.cpp | 308 ++++++++++++++++++ components/resource/objectcache.hpp | 2 +- 3 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 apps/openmw_test_suite/resource/testobjectcache.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 4f93319c96..203c4ac2db 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -94,6 +94,8 @@ file(GLOB UNITTEST_SRC_FILES nifosg/testnifloader.cpp esmterrain/testgridsampling.cpp + + resource/testobjectcache.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/resource/testobjectcache.cpp b/apps/openmw_test_suite/resource/testobjectcache.cpp new file mode 100644 index 0000000000..5b7741025a --- /dev/null +++ b/apps/openmw_test_suite/resource/testobjectcache.cpp @@ -0,0 +1,308 @@ +#include + +#include +#include + +#include + +namespace Resource +{ + namespace + { + using namespace ::testing; + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheShouldReturnNullptrByDefault) + { + osg::ref_ptr> cache(new GenericObjectCache); + EXPECT_EQ(cache->getRefFromObjectCache(42), nullptr); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheOrNoneShouldReturnNulloptByDefault) + { + osg::ref_ptr> cache(new GenericObjectCache); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(42), std::nullopt); + } + + struct Object : osg::Object + { + Object() = default; + + Object(const Object& other, const osg::CopyOp& copyOp = osg::CopyOp()) + : osg::Object(other, copyOp) + { + } + + META_Object(ResourceTest, Object) + }; + + TEST(ResourceGenericObjectCacheTest, shouldStoreValues) + { + osg::ref_ptr> cache(new GenericObjectCache); + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + EXPECT_EQ(cache->getRefFromObjectCache(key), value); + } + + TEST(ResourceGenericObjectCacheTest, shouldStoreNullptrValues) + { + osg::ref_ptr> cache(new GenericObjectCache); + const int key = 42; + cache->addEntryToObjectCache(key, nullptr); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(nullptr)); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldExtendLifetimeForItemsWithZeroTimestamp) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value, 0); + value = nullptr; + + const double referenceTime = 1000; + const double expiryDelay = 1; + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldReplaceExistingItemByKey) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const int key = 42; + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + cache->addEntryToObjectCache(key, value1); + ASSERT_EQ(cache->getRefFromObjectCache(key), value1); + cache->addEntryToObjectCache(key, value2); + EXPECT_EQ(cache->getRefFromObjectCache(key), value2); + } + + TEST(ResourceGenericObjectCacheTest, addEntryToObjectCacheShouldMarkLifetime) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + cache->addEntryToObjectCache(key, nullptr, referenceTime + expiryDelay); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + 2 * expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime + expiryDelay); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldRemoveExpiredItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 1; + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + value = nullptr; + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldKeepExternallyReferencedItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 1; + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(value)); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldKeepNotExpiredItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + value = nullptr; + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + } + + TEST(ResourceGenericObjectCacheTest, updateShouldKeepNotExpiredNullptrItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + cache->addEntryToObjectCache(key, nullptr); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + EXPECT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + } + + TEST(ResourceGenericObjectCacheTest, getRefFromObjectCacheOrNoneShouldNotExtendItemLifetime) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const double referenceTime = 1; + const double expiryDelay = 2; + + const int key = 42; + cache->addEntryToObjectCache(key, nullptr); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay / 2); + cache->removeExpiredObjectsInCache(referenceTime - expiryDelay / 2); + ASSERT_THAT(cache->getRefFromObjectCacheOrNone(key), Optional(_)); + + cache->updateTimeStampOfObjectsInCacheWithExternalReferences(referenceTime + expiryDelay); + cache->removeExpiredObjectsInCache(referenceTime); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, lowerBoundShouldSupportHeterogeneousLookup) + { + osg::ref_ptr> cache(new GenericObjectCache); + cache->addEntryToObjectCache("a", nullptr); + cache->addEntryToObjectCache("c", nullptr); + EXPECT_THAT(cache->lowerBound(std::string_view("b")), Optional(Pair("c", _))); + } + + TEST(ResourceGenericObjectCacheTest, shouldSupportRemovingItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + const int key = 42; + osg::ref_ptr value(new Object); + cache->addEntryToObjectCache(key, value); + ASSERT_EQ(cache->getRefFromObjectCache(key), value); + cache->removeFromObjectCache(key); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, clearShouldRemoveAllItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + const int key1 = 42; + const int key2 = 13; + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + cache->addEntryToObjectCache(key1, value1); + cache->addEntryToObjectCache(key2, value2); + + ASSERT_EQ(cache->getRefFromObjectCache(key1), value1); + ASSERT_EQ(cache->getRefFromObjectCache(key2), value2); + + cache->clear(); + + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key1), std::nullopt); + EXPECT_EQ(cache->getRefFromObjectCacheOrNone(key2), std::nullopt); + } + + TEST(ResourceGenericObjectCacheTest, callShouldIterateOverAllItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + osg::ref_ptr value3(new Object); + cache->addEntryToObjectCache(1, value1); + cache->addEntryToObjectCache(2, value2); + cache->addEntryToObjectCache(3, value3); + + std::vector> actual; + cache->call([&](int key, osg::Object* value) { actual.emplace_back(key, value); }); + + EXPECT_THAT(actual, ElementsAre(Pair(1, value1.get()), Pair(2, value2.get()), Pair(3, value3.get()))); + } + + TEST(ResourceGenericObjectCacheTest, getCacheSizeShouldReturnNumberOrAddedItems) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + cache->addEntryToObjectCache(13, value1); + cache->addEntryToObjectCache(42, value2); + + EXPECT_EQ(cache->getCacheSize(), 2); + } + + TEST(ResourceGenericObjectCacheTest, lowerBoundShouldReturnFirstNotLessThatGivenKey) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + osg::ref_ptr value3(new Object); + cache->addEntryToObjectCache(1, value1); + cache->addEntryToObjectCache(2, value2); + cache->addEntryToObjectCache(4, value3); + + EXPECT_THAT(cache->lowerBound(3), Optional(Pair(4, value3))); + } + + TEST(ResourceGenericObjectCacheTest, lowerBoundShouldReturnNulloptWhenKeyIsGreaterThanAnyOther) + { + osg::ref_ptr> cache(new GenericObjectCache); + + osg::ref_ptr value1(new Object); + osg::ref_ptr value2(new Object); + osg::ref_ptr value3(new Object); + cache->addEntryToObjectCache(1, value1); + cache->addEntryToObjectCache(2, value2); + cache->addEntryToObjectCache(3, value3); + + EXPECT_EQ(cache->lowerBound(4), std::nullopt); + } + } +} diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 881729ffc4..f8a5843395 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -180,7 +180,7 @@ namespace Resource /** call operator()(KeyType, osg::Object*) for each object in the cache. */ template - void call(Functor& f) + void call(Functor&& f) { std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it)