blob: 34e0b03c08c286ceb1d5f863f0e52447c4eed4f2 [file] [log] [blame]
/*
* Copyright 2012 Google Inc.
*
* 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.
*/
// Author: jmarantz@google.com (Joshua Marantz)
// Unit-test the property cache
#include "pagespeed/opt/http/property_cache.h"
#include <cstddef>
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/cache/lru_cache.h"
#include "pagespeed/kernel/util/platform.h"
#include "pagespeed/kernel/util/simple_stats.h"
#include "pagespeed/opt/http/cache_property_store.h"
#include "pagespeed/opt/http/mock_property_page.h"
#include "pagespeed/opt/http/property_store.h"
namespace net_instaweb {
namespace {
const size_t kMaxCacheSize = 200;
const char kCohortName1[] = "cohort1";
const char kCohortName2[] = "cohort2";
const char kCacheKey1[] = "Key1";
const char kCacheKey2[] = "Key2";
const char kPropertyName1[] = "prop1";
const char kPropertyName2[] = "prop2";
const char kOptionsSignatureHash[] = "hash";
const char kCacheKeySuffix[] = "CacheKeySuffix";
class PropertyCacheTest : public testing::Test {
protected:
PropertyCacheTest()
: lru_cache_(kMaxCacheSize),
thread_system_(Platform::CreateThreadSystem()),
timer_(thread_system_->NewMutex(), MockTimer::kApr_5_2010_ms),
stats_(thread_system_.get()),
cache_property_store_(
"test/", &lru_cache_, &timer_, &stats_, thread_system_.get()),
property_cache_(&cache_property_store_,
&timer_,
&stats_,
thread_system_.get()) {
PropertyCache::InitCohortStats(kCohortName1, &stats_);
PropertyCache::InitCohortStats(kCohortName2, &stats_);
PropertyStoreGetCallback::InitStats(&stats_);
cohort_ = property_cache_.AddCohort(kCohortName1);
cache_property_store_.AddCohort(cohort_->name());
}
// Performs a Read/Modify/Write transaction intended for a cold
// cache, verifying that this worked.
//
// Returns whether the value is considered Stable or not. In general
// we would expect this routine to return false.
bool ReadWriteInitial(const GoogleString& key, const GoogleString& value) {
MockPropertyPage page(thread_system_.get(),
&property_cache_,
key,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
EXPECT_FALSE(page.valid());
EXPECT_TRUE(page.called());
page.UpdateValue(cohort_, kPropertyName1, value);
page.WriteCohort(cohort_);
PropertyValue* property = page.GetProperty(cohort_, kPropertyName1);
EXPECT_TRUE(property->has_value());
return property_cache_.IsStable(property);
}
// Performs a Read/Modify/Write transaction intended for a warm
// cache, verifying that this worked, and that the old-value was
// previously found. Returns whether the value was considered
// stable.
bool ReadWriteTestStable(const GoogleString& key,
const GoogleString& old_value,
const GoogleString& new_value) {
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
PropertyValue* property = page.GetProperty(cohort_, kPropertyName1);
EXPECT_TRUE(page.valid());
EXPECT_TRUE(page.called());
EXPECT_STREQ(old_value, property->value());
page.UpdateValue(cohort_, kPropertyName1, new_value);
page.WriteCohort(cohort_);
return property_cache_.IsStable(property);
}
// Performs a Read transaction and returns whether the value was considered
// stable with num_writes_unchanged.
bool ReadTestRecentlyConstant(const GoogleString& key,
int num_writes_unchanged) {
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
PropertyValue* property = page.GetProperty(cohort_, kPropertyName1);
return property->IsRecentlyConstant(num_writes_unchanged);
}
// Performs a Read/Modify/Write transaction and returns whether the value was
// considered stable with num_writes_unchanged.
bool ReadWriteTestRecentlyConstant(const GoogleString& key,
const GoogleString& value,
int num_writes_unchanged) {
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
page.UpdateValue(cohort_, kPropertyName1, value);
page.WriteCohort(cohort_);
PropertyValue* property = page.GetProperty(cohort_, kPropertyName1);
return property->IsRecentlyConstant(num_writes_unchanged);
}
LRUCache lru_cache_;
scoped_ptr<ThreadSystem> thread_system_;
MockTimer timer_;
SimpleStats stats_;
CachePropertyStore cache_property_store_;
PropertyCache property_cache_;
const PropertyCache::Cohort* cohort_;
private:
DISALLOW_COPY_AND_ASSIGN(PropertyCacheTest);
};
TEST_F(PropertyCacheTest, TrackStability) {
// Tests that the current stability heuristics work as expected. Note
// that I don't think the heuristic is really great yet. It needs some
// iteration. The 0.3 threshold comes from
// const int kDefaultMutationsPer1000WritesThreshold = 300;
// in property_cache.cc.
EXPECT_FALSE(ReadWriteInitial(kCacheKey1, "Value1")) << "1/1 > .300";
EXPECT_FALSE(ReadWriteTestStable(kCacheKey1, "Value1", "Value1"))
<< "1/2 > .300";
EXPECT_FALSE(ReadWriteTestStable(kCacheKey1, "Value1", "Value1"))
<< "1/3 > .300";
EXPECT_TRUE(ReadWriteTestStable(kCacheKey1, "Value1", "Value1"))
<< "1/4 < .300";
EXPECT_TRUE(ReadWriteTestStable(kCacheKey1, "Value1", "Value1"))
<< "1/5 < .300";
EXPECT_FALSE(ReadWriteTestStable(kCacheKey1, "Value1", "Value2"))
<< "2/6 > .300";
EXPECT_TRUE(ReadWriteTestStable(kCacheKey1, "Value2", "Value2"))
<< "2/7 < .300";
EXPECT_TRUE(ReadWriteTestStable(kCacheKey1, "Value2", "Value2"))
<< "2/8 < .300";
// Saturate the update-count by looping 62 more times, making 64 straight
// writes where we did not change the value.
for (int i = 0; i < 62; ++i) {
EXPECT_TRUE(ReadWriteTestStable(kCacheKey1, "Value2", "Value2"))
<< "2/8 < .300";
}
// Now, to get to less than 300/1000 we'll have to change values 20
// times. On the first 19 we'll consider the system stable, but on
// the 20th, the system figures out this value looks stable enough.
//
// TODO(jmarantz): This feels like maybe it's not a good metric, and
// we should give up sooner once we see the instability. But at
// least for now this tests the system is working as expected.
GoogleString prev_value = "Value2";
for (int i = 0; i < 19; ++i) {
GoogleString new_value = StringPrintf("Value%d", i + 3);
EXPECT_TRUE(ReadWriteTestStable(kCacheKey1, prev_value, new_value)) <<
" still stable after " << i << " mutations";
prev_value = new_value;
}
EXPECT_FALSE(ReadWriteTestStable(kCacheKey1, prev_value, "Final"))
<< " finally unstable";
// Now that we have 20 mutations in the system, it will take 64-20=44
// repeats to flush them out to get back to 19 instabilities.
for (int i = 0; i < 44; ++i) {
EXPECT_FALSE(ReadWriteTestStable(kCacheKey1, "Final", "Final"))
<< "still unstable after " << i << " mutations";
}
EXPECT_TRUE(ReadWriteTestStable(kCacheKey1, "Final", "Final"))
<< " stable again";
}
TEST_F(PropertyCacheTest, IsIndexOfLeastSetBitSmallerTest) {
uint64 i = 1;
EXPECT_FALSE(PropertyValue::IsIndexOfLeastSetBitSmaller(i, 0));
EXPECT_FALSE(PropertyValue::IsIndexOfLeastSetBitSmaller(i << 1, 0));
EXPECT_TRUE(PropertyValue::IsIndexOfLeastSetBitSmaller(i << 1, 3));
EXPECT_TRUE(PropertyValue::IsIndexOfLeastSetBitSmaller(i << 44, 60));
i = 1;
// Index of least set bit is 64.
EXPECT_FALSE(PropertyValue::IsIndexOfLeastSetBitSmaller(i << 63, 64));
// There is no bit set.
EXPECT_TRUE(PropertyValue::IsIndexOfLeastSetBitSmaller(i << 1, 64));
}
TEST_F(PropertyCacheTest, TestIsRecentlyConstant) {
// Nothing written to property_cache so constant.
EXPECT_TRUE(ReadTestRecentlyConstant(kCacheKey1, 1));
EXPECT_TRUE(ReadTestRecentlyConstant(kCacheKey1, 2));
// value1 written once.
EXPECT_TRUE(ReadWriteTestRecentlyConstant(kCacheKey1, "value1", 1));
EXPECT_TRUE(ReadTestRecentlyConstant(kCacheKey1, 2));
// value1 written twice.
EXPECT_TRUE(ReadWriteTestRecentlyConstant(kCacheKey1, "value1", 2));
EXPECT_TRUE(ReadTestRecentlyConstant(kCacheKey1, 3));
// value1 written thrice.
EXPECT_TRUE(ReadWriteTestRecentlyConstant(kCacheKey1, "value1", 3));
// A new value is written.
EXPECT_FALSE(ReadWriteTestRecentlyConstant(kCacheKey1, "value2", 2));
// value2 written twice.
EXPECT_TRUE(ReadWriteTestRecentlyConstant(kCacheKey1, "value2", 2));
EXPECT_FALSE(ReadWriteTestRecentlyConstant(kCacheKey1, "value2", 4));
// Write same value 44 times.
for (int i = 0; i < 44; ++i) {
ReadWriteTestRecentlyConstant(kCacheKey1, "value3", 45);
}
EXPECT_TRUE(ReadTestRecentlyConstant(kCacheKey1, 44));
EXPECT_FALSE(ReadTestRecentlyConstant(kCacheKey1, 46));
// Write same value for 20 more times.
for (int i = 0; i < 21; ++i) {
EXPECT_FALSE(ReadWriteTestRecentlyConstant(kCacheKey1, "value3", 65));
}
EXPECT_TRUE(ReadTestRecentlyConstant(kCacheKey1, 64));
}
TEST_F(PropertyCacheTest, DropOldWrites) {
timer_.SetTimeMs(MockTimer::kApr_5_2010_ms);
ReadWriteInitial(kCacheKey1, "Value1");
ReadWriteTestStable(kCacheKey1, "Value1", "Value1");
// Now imagine we are on a second server, which is trying to write
// an older value into the same physical cache. Make sure we don't let it.
MockTimer timer2(thread_system_->NewMutex(), MockTimer::kApr_5_2010_ms - 100);
CachePropertyStore cache_property_store2(
"test/", &lru_cache_, &timer2, &stats_, thread_system_.get());
PropertyCache property_cache2(&cache_property_store2,
&timer2,
&stats_,
thread_system_.get());
property_cache2.AddCohort(kCohortName1);
cache_property_store2.AddCohort(kCohortName1);
const PropertyCache::Cohort* cohort2 = property_cache2.GetCohort(
kCohortName1);
{
MockPropertyPage page(
thread_system_.get(),
&property_cache2,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache2.Read(&page);
EXPECT_TRUE(page.valid());
EXPECT_TRUE(page.called());
page.UpdateValue(cohort2, kPropertyName1, "Value2");
// Stale value dropped.
page.WriteCohort(cohort2);
}
{
MockPropertyPage page(
thread_system_.get(),
&property_cache2,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache2.Read(&page);
EXPECT_TRUE(page.valid());
EXPECT_TRUE(page.called());
PropertyValue* property = page.GetProperty(cohort2, kPropertyName1);
EXPECT_STREQ("Value1", property->value()); // Value2 was dropped.
}
}
TEST_F(PropertyCacheTest, EmptyReadNewPropertyWasRead) {
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
PropertyValue* property = page.GetProperty(cohort_, kPropertyName1);
EXPECT_TRUE(property->was_read());
EXPECT_FALSE(property->has_value());
}
TEST_F(PropertyCacheTest, TwoCohorts) {
EXPECT_EQ(cohort_, property_cache_.GetCohort(kCohortName1));
EXPECT_TRUE(property_cache_.GetCohort(kCohortName2) == NULL);
const PropertyCache::Cohort* cohort2 =
property_cache_.AddCohort(kCohortName2);
cache_property_store_.AddCohort(kCohortName2);
ReadWriteInitial(kCacheKey1, "Value1");
EXPECT_EQ(2, lru_cache_.num_misses()) << "one miss per cohort";
EXPECT_EQ(1, lru_cache_.num_inserts()) << "only cohort1 written";
lru_cache_.ClearStats();
// ReadWriteInitial found something for cohort1 but no value has
// yet been established for cohort2, so we'll get a hit and a miss.
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
EXPECT_EQ(1, lru_cache_.num_hits()) << "cohort1";
EXPECT_EQ(1, lru_cache_.num_misses()) << "cohort2";
PropertyValue* p2 = page.GetProperty(cohort2, kPropertyName2);
EXPECT_TRUE(p2->was_read());
EXPECT_FALSE(p2->has_value());
page.UpdateValue(cohort2, kPropertyName2, "v2");
page.WriteCohort(cohort2);
EXPECT_EQ(1, lru_cache_.num_inserts()) << "cohort2 written";
}
lru_cache_.ClearStats();
// Now a second read will get two hits, no misses, and both data elements
// present.
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
EXPECT_EQ(2, lru_cache_.num_hits()) << "both cohorts hit";
EXPECT_EQ(0, lru_cache_.num_misses());
PropertyValue* p2 = page.GetProperty(cohort2, kPropertyName2);
EXPECT_TRUE(p2->was_read());
EXPECT_TRUE(p2->has_value());
}
}
TEST_F(PropertyCacheTest, Expiration) {
timer_.SetTimeMs(MockTimer::kApr_5_2010_ms);
ReadWriteInitial(kCacheKey1, "Value1");
// Read a value & make sure it's not expired initially, but expires when
// we move time forward.
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
PropertyValue* property = page.GetProperty(cohort_, kPropertyName1);
// Initially it's not expired.
EXPECT_FALSE(property_cache_.IsExpired(property, Timer::kMinuteMs));
timer_.AdvanceMs(30 * Timer::kSecondMs);
EXPECT_FALSE(property_cache_.IsExpired(property, Timer::kMinuteMs));
timer_.AdvanceMs(20 * Timer::kSecondMs);
EXPECT_FALSE(property_cache_.IsExpired(property, Timer::kMinuteMs));
timer_.AdvanceMs(10 * Timer::kSecondMs);
EXPECT_FALSE(property_cache_.IsExpired(property, Timer::kMinuteMs));
timer_.AdvanceMs(1 * Timer::kSecondMs);
EXPECT_TRUE(property_cache_.IsExpired(property, Timer::kMinuteMs));
}
}
TEST_F(PropertyCacheTest, IsCacheValid) {
timer_.SetTimeMs(MockTimer::kApr_5_2010_ms);
ReadWriteInitial(kCacheKey1, "Value1");
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
// The timestamp for invalidation is older than the write time of value. So
// it as valid.
page.set_time_ms(timer_.NowMs() - 1);
property_cache_.Read(&page);
EXPECT_TRUE(page.valid());
EXPECT_TRUE(page.called());
PropertyValue* property1 = page.GetProperty(cohort_, kPropertyName1);
EXPECT_TRUE(property1->has_value());
}
{
// The timestamp for invalidation is newer than the write time of value. So
// it as invalid.
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
page.set_time_ms(timer_.NowMs());
property_cache_.Read(&page);
EXPECT_FALSE(page.valid());
EXPECT_TRUE(page.called());
PropertyValue* property1 = page.GetProperty(cohort_, kPropertyName1);
EXPECT_FALSE(property1->has_value());
}
}
TEST_F(PropertyCacheTest, IsCacheValidTwoValuesInACohort) {
timer_.SetTimeMs(MockTimer::kApr_5_2010_ms);
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
page.UpdateValue(cohort_, kPropertyName1, "Value1");
timer_.AdvanceMs(2);
page.UpdateValue(cohort_, kPropertyName2, "Value2");
page.WriteCohort(cohort_);
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
// The timestamp for invalidation is older than the write times of both
// value. So they are treated as valid.
page.set_time_ms(timer_.NowMs() - 3);
property_cache_.Read(&page);
EXPECT_TRUE(page.valid());
EXPECT_TRUE(page.called());
PropertyValue* property1 = page.GetProperty(cohort_, kPropertyName1);
PropertyValue* property2 = page.GetProperty(cohort_, kPropertyName2);
EXPECT_TRUE(property1->has_value());
EXPECT_TRUE(property2->has_value());
}
{
// The timestamp for invalidation is newer than the write time of one of the
// values. So both are treated as invalid.
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
page.set_time_ms(timer_.NowMs() - 1);
property_cache_.Read(&page);
EXPECT_FALSE(page.valid());
EXPECT_TRUE(page.called());
PropertyValue* property1 = page.GetProperty(cohort_, kPropertyName1);
PropertyValue* property2 = page.GetProperty(cohort_, kPropertyName2);
EXPECT_FALSE(property1->has_value());
EXPECT_FALSE(property2->has_value());
}
}
TEST_F(PropertyCacheTest, IsCacheValidTwoCohorts) {
timer_.SetTimeMs(MockTimer::kApr_5_2010_ms);
const PropertyCache::Cohort* cohort2 =
property_cache_.AddCohort(kCohortName2);
cache_property_store_.AddCohort(kCohortName2);
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
page.UpdateValue(cohort_, kPropertyName1, "Value1");
timer_.AdvanceMs(2);
page.UpdateValue(cohort2, kPropertyName2, "Value2");
page.WriteCohort(cohort_);
page.WriteCohort(cohort2);
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
// The timestamp for invalidation is older than the write times of values in
// both cohorts. So they are treated as valid.
page.set_time_ms(timer_.NowMs() - 3);
property_cache_.Read(&page);
EXPECT_TRUE(page.valid());
EXPECT_TRUE(page.called());
PropertyValue* property1 = page.GetProperty(cohort_, kPropertyName1);
PropertyValue* property2 = page.GetProperty(cohort2, kPropertyName2);
EXPECT_TRUE(property1->has_value());
EXPECT_TRUE(property2->has_value());
}
{
// The timestamp for invalidation is newer than the write time of one of the
// values. But the the values are in different cohorts and so the page is
// treated as valid.
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
page.set_time_ms(timer_.NowMs() - 1);
property_cache_.Read(&page);
EXPECT_TRUE(page.valid());
EXPECT_TRUE(page.called());
PropertyValue* property1 = page.GetProperty(cohort_, kPropertyName1);
PropertyValue* property2 = page.GetProperty(cohort2, kPropertyName2);
EXPECT_FALSE(property1->has_value());
EXPECT_TRUE(property2->has_value());
}
}
TEST_F(PropertyCacheTest, DeleteProperty) {
ReadWriteInitial(kCacheKey1, "Value1");
{
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
EXPECT_TRUE(page.valid());
EXPECT_TRUE(page.called());
// Deletes a property which already exists.
PropertyValue* property = page.GetProperty(
cohort_, kPropertyName1);
EXPECT_STREQ("Value1", property->value());
page.DeleteProperty(cohort_, kPropertyName1);
page.WriteCohort(cohort_);
}
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
PropertyValue* property = page.GetProperty(
cohort_, kPropertyName1);
EXPECT_FALSE(property->has_value());
// Deletes a property which does not exist.
property = page.GetProperty(cohort_, kPropertyName2);
EXPECT_FALSE(property->has_value());
page.DeleteProperty(cohort_, kPropertyName2);
property = page.GetProperty(cohort_, kPropertyName2);
EXPECT_FALSE(property->has_value());
// Unknown Cohort. No crashes.
scoped_ptr<PropertyCache::Cohort> unknown_cohort(
new PropertyCache::Cohort("unknown_cohort"));
page.DeleteProperty(cohort_, kPropertyName2);
EXPECT_TRUE(page.valid());
}
}
}
TEST_F(PropertyCacheTest, TwoCohortsDifferentCacheImplementations) {
// Verify the second cohort does not exist.
EXPECT_TRUE(property_cache_.GetCohort(kCohortName2) == NULL);
// Create a second cache implementation.
LRUCache second_cache(kMaxCacheSize);
// Add a second cohort backed by the second cache.
const PropertyCache::Cohort* cohort2 =
property_cache_.AddCohort(kCohortName2);
cache_property_store_.AddCohortWithCache(kCohortName2, &second_cache);
// Verify the first cohort behaves as expected.
ReadWriteInitial(kCacheKey1, "Value1");
EXPECT_EQ(1, lru_cache_.num_misses());
EXPECT_EQ(1, lru_cache_.num_inserts());
// We should miss the second cache for the second cohort.
EXPECT_EQ(1, second_cache.num_misses());
EXPECT_EQ(0, second_cache.num_inserts());
lru_cache_.ClearStats();
second_cache.ClearStats();
{
// Insert a value into cohort2.
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
EXPECT_EQ(1, lru_cache_.num_hits());
EXPECT_EQ(0, lru_cache_.num_misses());
EXPECT_EQ(0, lru_cache_.num_inserts());
EXPECT_EQ(0, second_cache.num_hits());
EXPECT_EQ(1, second_cache.num_misses());
EXPECT_EQ(0, second_cache.num_inserts());
PropertyValue* property = page.GetProperty(cohort2, kPropertyName2);
EXPECT_FALSE(property->has_value());
page.UpdateValue(cohort2, kPropertyName2, "Value2");
page.WriteCohort(cohort2);
EXPECT_EQ(0, lru_cache_.num_inserts());
EXPECT_EQ(1, second_cache.num_inserts());
}
lru_cache_.ClearStats();
second_cache.ClearStats();
{
// Read again. We should have properties in each cohort, each in their own
// cache.
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
EXPECT_EQ(1, lru_cache_.num_hits());
EXPECT_EQ(0, lru_cache_.num_misses());
EXPECT_EQ(0, lru_cache_.num_inserts());
EXPECT_EQ(1, second_cache.num_hits());
EXPECT_EQ(0, second_cache.num_misses());
EXPECT_EQ(0, second_cache.num_inserts());
PropertyValue* property = page.GetProperty(cohort_, kPropertyName1);
EXPECT_TRUE(property->has_value());
EXPECT_EQ("Value1", property->value());
property = page.GetProperty(cohort2, kPropertyName2);
EXPECT_TRUE(property->has_value());
EXPECT_EQ("Value2", property->value());
}
}
TEST_F(PropertyCacheTest, MultiReadWithCohorts) {
EXPECT_EQ(cohort_, property_cache_.GetCohort(kCohortName1));
EXPECT_TRUE(property_cache_.GetCohort(kCohortName2) == NULL);
const PropertyCache::Cohort* cohort2 =
property_cache_.AddCohort(kCohortName2);
cache_property_store_.AddCohort(kCohortName2);
ReadWriteInitial(kCacheKey1, "Value1");
EXPECT_EQ(2, lru_cache_.num_misses()) << "one miss per cohort";
EXPECT_EQ(1, lru_cache_.num_inserts()) << "only cohort1 written";
lru_cache_.ClearStats();
// ReadWithCohorts only checked cohort2 but no value has yet been established
// for cohort2, so we'll get a miss only. Unlike normal Read, ReadWithCohorts
// does not read data from all cohorts, it only reads data from the specified
// cohorts. In this case, cohort1 did not get touched, so that there is no
// hit.
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
PropertyCache::CohortVector cohort_list;
cohort_list.push_back(cohort2);
property_cache_.ReadWithCohorts(cohort_list, &page);
EXPECT_EQ(0, lru_cache_.num_hits()) << "cohort1";
EXPECT_EQ(1, lru_cache_.num_misses()) << "cohort2";
PropertyValue* p2 = page.GetProperty(cohort2, kPropertyName2);
EXPECT_TRUE(p2->was_read());
EXPECT_FALSE(p2->has_value());
page.UpdateValue(cohort2, kPropertyName2, "v2");
page.WriteCohort(cohort2);
EXPECT_EQ(1, lru_cache_.num_inserts()) << "cohort2 written";
}
lru_cache_.ClearStats();
// Now a second read will get one hit, no misses, and only one data element
// is present.
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
PropertyCache::CohortVector cohort_list;
cohort_list.push_back(cohort2);
property_cache_.ReadWithCohorts(cohort_list, &page);
EXPECT_EQ(1, lru_cache_.num_hits()) << "cohort2";
EXPECT_EQ(0, lru_cache_.num_misses());
PropertyValue* p2 = page.GetProperty(cohort2, kPropertyName2);
EXPECT_TRUE(p2->was_read());
EXPECT_TRUE(p2->has_value());
}
lru_cache_.ClearStats();
// Normal Read gets every thing from all cohorts, so that there are two hits.
{
MockPropertyPage page(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
property_cache_.Read(&page);
EXPECT_EQ(2, lru_cache_.num_hits()) << "both cohorts hit";
EXPECT_EQ(0, lru_cache_.num_misses());
PropertyValue* p2 = page.GetProperty(cohort2, kPropertyName2);
EXPECT_TRUE(p2->was_read());
EXPECT_TRUE(p2->has_value());
PropertyValue* p1 = page.GetProperty(cohort_, kPropertyName1);
EXPECT_TRUE(p1->was_read());
EXPECT_TRUE(p1->has_value());
}
}
TEST_F(PropertyCacheTest, ReadWithEmptyCohort) {
ReadWriteInitial(kCacheKey1, "Value1");
ReadWriteInitial(kCacheKey2, "Value2");
{
MockPropertyPage page1(
thread_system_.get(),
&property_cache_,
kCacheKey1,
kOptionsSignatureHash,
kCacheKeySuffix);
PropertyCache::CohortVector cohort_list;
property_cache_.ReadWithCohorts(cohort_list, &page1);
// Check for Page1.
EXPECT_FALSE(page1.valid());
EXPECT_TRUE(page1.called());
}
}
} // namespace
} // namespace net_instaweb