/* * Copyright (C) 2012 The Android Open Source Project * * 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. */ #include #include #include #include #include namespace { typedef int SimpleKey; typedef const char* StringValue; struct ComplexKey { int k; explicit ComplexKey() : k(0) { instanceCount += 1; } explicit ComplexKey(int k) : k(k) { instanceCount += 1; } ComplexKey(const ComplexKey& other) : k(other.k) { instanceCount += 1; } ~ComplexKey() { instanceCount -= 1; } bool operator ==(const ComplexKey& other) const { return k == other.k; } bool operator !=(const ComplexKey& other) const { return k != other.k; } static ssize_t instanceCount; }; ssize_t ComplexKey::instanceCount = 0; struct ComplexValue { int v; explicit ComplexValue() : v(0) { instanceCount += 1; } explicit ComplexValue(int v) : v(v) { instanceCount += 1; } ComplexValue(const ComplexValue& other) : v(other.v) { instanceCount += 1; } ~ComplexValue() { instanceCount -= 1; } static ssize_t instanceCount; }; ssize_t ComplexValue::instanceCount = 0; struct KeyWithPointer { int *ptr; bool operator ==(const KeyWithPointer& other) const { return *ptr == *other.ptr; } }; struct KeyFailsOnCopy : public ComplexKey { public: KeyFailsOnCopy() : ComplexKey() {} KeyFailsOnCopy(const KeyFailsOnCopy& key) : ComplexKey(key) { ADD_FAILURE(); } KeyFailsOnCopy(int key) : ComplexKey(key) {} }; } // namespace namespace android { typedef LruCache ComplexCache; template<> inline android::hash_t hash_type(const ComplexKey& value) { return hash_type(value.k); } template<> inline android::hash_t hash_type(const KeyWithPointer& value) { return hash_type(*value.ptr); } template<> inline android::hash_t hash_type(const KeyFailsOnCopy& value) { return hash_type(value); } class EntryRemovedCallback : public OnEntryRemoved { public: EntryRemovedCallback() : callbackCount(0), lastKey(-1), lastValue(nullptr) { } ~EntryRemovedCallback() {} void operator()(SimpleKey& k, StringValue& v) { callbackCount += 1; lastKey = k; lastValue = v; } ssize_t callbackCount; SimpleKey lastKey; StringValue lastValue; }; class InvalidateKeyCallback : public OnEntryRemoved { public: void operator()(KeyWithPointer& k, StringValue&) { delete k.ptr; k.ptr = nullptr; } }; class LruCacheTest : public testing::Test { protected: virtual void SetUp() { ComplexKey::instanceCount = 0; ComplexValue::instanceCount = 0; } virtual void TearDown() { ASSERT_NO_FATAL_FAILURE(assertInstanceCount(0, 0)); } void assertInstanceCount(ssize_t keys, ssize_t values) { if (keys != ComplexKey::instanceCount || values != ComplexValue::instanceCount) { FAIL() << "Expected " << keys << " keys and " << values << " values " "but there were actually " << ComplexKey::instanceCount << " keys and " << ComplexValue::instanceCount << " values"; } } }; TEST_F(LruCacheTest, Empty) { LruCache cache(100); EXPECT_EQ(nullptr, cache.get(0)); EXPECT_EQ(0u, cache.size()); } TEST_F(LruCacheTest, Simple) { LruCache cache(100); cache.put(1, "one"); cache.put(2, "two"); cache.put(3, "three"); EXPECT_STREQ("one", cache.get(1)); EXPECT_STREQ("two", cache.get(2)); EXPECT_STREQ("three", cache.get(3)); EXPECT_EQ(3u, cache.size()); } TEST_F(LruCacheTest, MaxCapacity) { LruCache cache(2); cache.put(1, "one"); cache.put(2, "two"); cache.put(3, "three"); EXPECT_EQ(nullptr, cache.get(1)); EXPECT_STREQ("two", cache.get(2)); EXPECT_STREQ("three", cache.get(3)); EXPECT_EQ(2u, cache.size()); } TEST_F(LruCacheTest, RemoveLru) { LruCache cache(100); cache.put(1, "one"); cache.put(2, "two"); cache.put(3, "three"); cache.removeOldest(); EXPECT_EQ(nullptr, cache.get(1)); EXPECT_STREQ("two", cache.get(2)); EXPECT_STREQ("three", cache.get(3)); EXPECT_EQ(2u, cache.size()); } TEST_F(LruCacheTest, GetUpdatesLru) { LruCache cache(100); cache.put(1, "one"); cache.put(2, "two"); cache.put(3, "three"); EXPECT_STREQ("one", cache.get(1)); cache.removeOldest(); EXPECT_STREQ("one", cache.get(1)); EXPECT_EQ(nullptr, cache.get(2)); EXPECT_STREQ("three", cache.get(3)); EXPECT_EQ(2u, cache.size()); } uint32_t hash_int(int x) { return JenkinsHashWhiten(JenkinsHashMix(0, x)); } TEST_F(LruCacheTest, StressTest) { const size_t kCacheSize = 512; LruCache cache(512); const size_t kNumKeys = 16 * 1024; const size_t kNumIters = 100000; char* strings[kNumKeys]; for (size_t i = 0; i < kNumKeys; i++) { strings[i] = (char *)malloc(16); sprintf(strings[i], "%zu", i); } srandom(12345); int hitCount = 0; for (size_t i = 0; i < kNumIters; i++) { int index = random() % kNumKeys; uint32_t key = hash_int(index); const char *val = cache.get(key); if (val != nullptr) { EXPECT_EQ(strings[index], val); hitCount++; } else { cache.put(key, strings[index]); } } size_t expectedHitCount = kNumIters * kCacheSize / kNumKeys; EXPECT_LT(int(expectedHitCount * 0.9), hitCount); EXPECT_GT(int(expectedHitCount * 1.1), hitCount); EXPECT_EQ(kCacheSize, cache.size()); for (size_t i = 0; i < kNumKeys; i++) { free((void *)strings[i]); } } TEST_F(LruCacheTest, NoLeak) { ComplexCache cache(100); cache.put(ComplexKey(0), ComplexValue(0)); cache.put(ComplexKey(1), ComplexValue(1)); EXPECT_EQ(2U, cache.size()); assertInstanceCount(2, 3); // the member mNullValue counts as an instance } TEST_F(LruCacheTest, Clear) { ComplexCache cache(100); cache.put(ComplexKey(0), ComplexValue(0)); cache.put(ComplexKey(1), ComplexValue(1)); EXPECT_EQ(2U, cache.size()); assertInstanceCount(2, 3); cache.clear(); assertInstanceCount(0, 1); } TEST_F(LruCacheTest, ClearNoDoubleFree) { { ComplexCache cache(100); cache.put(ComplexKey(0), ComplexValue(0)); cache.put(ComplexKey(1), ComplexValue(1)); EXPECT_EQ(2U, cache.size()); assertInstanceCount(2, 3); cache.removeOldest(); cache.clear(); assertInstanceCount(0, 1); } assertInstanceCount(0, 0); } TEST_F(LruCacheTest, ClearReuseOk) { ComplexCache cache(100); cache.put(ComplexKey(0), ComplexValue(0)); cache.put(ComplexKey(1), ComplexValue(1)); EXPECT_EQ(2U, cache.size()); assertInstanceCount(2, 3); cache.clear(); assertInstanceCount(0, 1); cache.put(ComplexKey(0), ComplexValue(0)); cache.put(ComplexKey(1), ComplexValue(1)); EXPECT_EQ(2U, cache.size()); assertInstanceCount(2, 3); } TEST_F(LruCacheTest, Callback) { EntryRemovedCallback callback; LruCache cache(100); cache.setOnEntryRemovedListener(&callback); cache.put(1, "one"); cache.put(2, "two"); cache.put(3, "three"); EXPECT_EQ(3U, cache.size()); cache.removeOldest(); EXPECT_EQ(1, callback.callbackCount); EXPECT_EQ(1, callback.lastKey); EXPECT_STREQ("one", callback.lastValue); } TEST_F(LruCacheTest, CallbackOnClear) { EntryRemovedCallback callback; LruCache cache(100); cache.setOnEntryRemovedListener(&callback); cache.put(1, "one"); cache.put(2, "two"); cache.put(3, "three"); EXPECT_EQ(3U, cache.size()); cache.clear(); EXPECT_EQ(3, callback.callbackCount); } TEST_F(LruCacheTest, CallbackRemovesKeyWorksOK) { InvalidateKeyCallback callback; LruCache cache(1); cache.setOnEntryRemovedListener(&callback); KeyWithPointer key1; key1.ptr = new int(1); KeyWithPointer key2; key2.ptr = new int(2); cache.put(key1, "one"); // As the size of the cache is 1, the put will call the callback. // Make sure everything goes smoothly even if the callback invalidates // the key (b/24785286) cache.put(key2, "two"); EXPECT_EQ(1U, cache.size()); EXPECT_STREQ("two", cache.get(key2)); cache.clear(); } TEST_F(LruCacheTest, IteratorCheck) { LruCache cache(100); cache.put(1, 4); cache.put(2, 5); cache.put(3, 6); EXPECT_EQ(3U, cache.size()); LruCache::Iterator it(cache); std::unordered_set returnedValues; while (it.next()) { int v = it.value(); // Check we haven't seen the value before. EXPECT_TRUE(returnedValues.find(v) == returnedValues.end()); returnedValues.insert(v); } EXPECT_EQ(std::unordered_set({4, 5, 6}), returnedValues); } TEST_F(LruCacheTest, EmptyCacheIterator) { // Check that nothing crashes... LruCache cache(100); LruCache::Iterator it(cache); std::unordered_set returnedValues; while (it.next()) { returnedValues.insert(it.value()); } EXPECT_EQ(std::unordered_set(), returnedValues); } TEST_F(LruCacheTest, OneElementCacheIterator) { // Check that nothing crashes... LruCache cache(100); cache.put(1, 2); LruCache::Iterator it(cache); std::unordered_set returnedValues; while (it.next()) { returnedValues.insert(it.value()); } EXPECT_EQ(std::unordered_set({ 2 }), returnedValues); } TEST_F(LruCacheTest, OneElementCacheRemove) { LruCache cache(100); cache.put(1, 2); cache.remove(1); LruCache::Iterator it(cache); std::unordered_set returnedValues; while (it.next()) { returnedValues.insert(it.value()); } EXPECT_EQ(std::unordered_set({ }), returnedValues); } TEST_F(LruCacheTest, Remove) { LruCache cache(100); cache.put(1, 4); cache.put(2, 5); cache.put(3, 6); cache.remove(2); LruCache::Iterator it(cache); std::unordered_set returnedValues; while (it.next()) { returnedValues.insert(it.value()); } EXPECT_EQ(std::unordered_set({ 4, 6 }), returnedValues); } TEST_F(LruCacheTest, RemoveYoungest) { LruCache cache(100); cache.put(1, 4); cache.put(2, 5); cache.put(3, 6); cache.remove(3); LruCache::Iterator it(cache); std::unordered_set returnedValues; while (it.next()) { returnedValues.insert(it.value()); } EXPECT_EQ(std::unordered_set({ 4, 5 }), returnedValues); } TEST_F(LruCacheTest, RemoveNonMember) { LruCache cache(100); cache.put(1, 4); cache.put(2, 5); cache.put(3, 6); cache.remove(7); LruCache::Iterator it(cache); std::unordered_set returnedValues; while (it.next()) { returnedValues.insert(it.value()); } EXPECT_EQ(std::unordered_set({ 4, 5, 6 }), returnedValues); } TEST_F(LruCacheTest, DontCopyKeyInGet) { LruCache cache(1); // Check that get doesn't copy the key cache.get(KeyFailsOnCopy(0)); } }