/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */


// 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
