text: // Copyright 2008, Google Inc. All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without 
  // modification, are permitted provided that the following conditions are met:
  //
  //  1. Redistributions of source code must retain the above copyright notice, 
  //     this list of conditions and the following disclaimer.
  //  2. Redistributions in binary form must reproduce the above copyright notice,
  //     this list of conditions and the following disclaimer in the documentation
  //     and/or other materials provided with the distribution.
  //  3. Neither the name of Google Inc. nor the names of its contributors may be
  //     used to endorse or promote products derived from this software without
  //     specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
  // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
  // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
  // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  #include "kml/base/net_cache.h"
  // Uncomment this #define to enable output of timing results.
  // #define PRINT_TIME_RESULTS
  #ifdef PRINT_TIME_RESULTS
  #include <iostream>
  #endif
  #include "kml/base/memory_file.h"
  #include "kml/base/net_cache_test_util.h"
  #include "kml/base/referent.h"
  #include "gtest/gtest.h"
  
  namespace kmlbase {
  
  static const size_t kSize0 = 0;
  static const size_t kSize1 = 1;
  
  // This NetCache uses TestDataNetFetcher which maps each URL path to a file
  // under testdata.  The CacheItem is a MemoryFile which holds the file content.
  const size_t kTestDataNetCacheSize = 10;
  typedef NetCache<MemoryFile> TestDataNetCache;
  
  // This NetCache CacheItem is an empty which saves no data, however an
  // empty string indicates that no NullCacheItem is to be created.
  class NullCacheItem : public Referent {
   public:
    static NullCacheItem* CreateFromString(const string& data) {
      return data.empty() ? NULL : new NullCacheItem;
    }
  };
  
  typedef boost::intrusive_ptr<NullCacheItem> NullCacheItemPtr;
  
  // This NetCache CacheItem has instrumentation to track creation/deletion.
  static size_t instrumented_cache_item_count;
  class InstrumentedCacheItem : public Referent {
   public:
     static InstrumentedCacheItem* CreateFromString(const string& data) {
       return new InstrumentedCacheItem(data);
     }
  
     const string& get_content() const {
       return content_;
     }
  
   private:
    InstrumentedCacheItem(const string& content) : content_(content) {
      ++instrumented_cache_item_count;
    }
    ~InstrumentedCacheItem() {
      --instrumented_cache_item_count;
    }
  
    string content_;
  };
  
  typedef boost::intrusive_ptr<InstrumentedCacheItem> InstrumentedCacheItemPtr;
  
  // Since the default NetFetcher always returns false this cache will never
  // accept content.  The size is set to non-zero to verify that cache internal
  // limits are not the limiter for this behavior.
  const size_t kNullNetCacheSize = 1;
  typedef NetCache<NullCacheItem> NullNetCache;
  
  // This NetFetcher simply sets the output data to the url itself.
  class UrlDataNetFetcher : public NetFetcher {
   public:
    bool FetchUrl(const string& url, string* data) const {
      if (data) {
        *data = url;
        return true;
      }
      return false;
    }
  };
  
  // This NetCache essentially maps each URL to a MemoryFile
  // whose content is that URL.
  const size_t kUrlDataNetCacheSize = 1234;
  typedef NetCache<MemoryFile> UrlDataNetCache;
  
  class NetCacheTest : public testing::Test {
   protected:
    virtual void SetUp() {
      null_net_cache_.reset(new NullNetCache(&null_net_fetcher_,
                                             kNullNetCacheSize));
      testdata_net_cache_.reset(new TestDataNetCache(&testdata_net_fetcher_,
                                                     kTestDataNetCacheSize));
      url_data_net_cache_.reset(new UrlDataNetCache(&url_data_net_fetcher_,
                                                    kUrlDataNetCacheSize));
    }
  
    NetFetcher null_net_fetcher_;
    boost::scoped_ptr<NullNetCache> null_net_cache_;
    TestDataNetFetcher testdata_net_fetcher_;
    boost::scoped_ptr<TestDataNetCache> testdata_net_cache_;
    UrlDataNetFetcher url_data_net_fetcher_;
    boost::scoped_ptr<UrlDataNetCache> url_data_net_cache_;
  };
  
  // Verify very basic usage of the Size() method.
  TEST_F(NetCacheTest, TestBasicSize) {
    ASSERT_TRUE(kNullNetCacheSize >= null_net_cache_->Size());
  }
  
  // Verify basic usage of the Fetch() method.
  TEST_F(NetCacheTest, TestBasicFetch) {
    const string kUrl("http://host.com/style/simple.kml");
    // Fetch always fails on NullNetCache.
    ASSERT_FALSE(null_net_cache_->Fetch(kUrl));
    ASSERT_EQ(kSize0, null_net_cache_->Size());
    // Fetch of a valid testdata path succeeds.
    ASSERT_TRUE(testdata_net_cache_->Fetch(kUrl) != 0);
    // TODO read the test file directly and compare content
    ASSERT_EQ(static_cast<size_t>(1), testdata_net_cache_->Size());
    // Fetch on UrlDataNetCache returns URL.
    ASSERT_EQ(kUrl, url_data_net_cache_->Fetch(kUrl)->get_content());
    ASSERT_EQ(static_cast<size_t>(1), url_data_net_cache_->Size());
  }
  
  // Verify basic usage of the LookUp() method.
  TEST_F(NetCacheTest, TestBasicLookUp) {
    const string kUrl("http://host.com/style/simple.kml");
    // Verify that initially all caches return false for a LookUp of this URL.
    ASSERT_FALSE(null_net_cache_->LookUp(kUrl));
    ASSERT_FALSE(testdata_net_cache_->LookUp(kUrl));
    ASSERT_FALSE(url_data_net_cache_->LookUp(kUrl));
    // Fetch this URL into the cache in those caches that save content.
    ASSERT_TRUE(testdata_net_cache_->Fetch(kUrl) != 0);
    ASSERT_TRUE(url_data_net_cache_->Fetch(kUrl) != 0);
    // Verify that these caches return true now on LookUp.
    ASSERT_TRUE(testdata_net_cache_->LookUp(kUrl) != 0);
    ASSERT_TRUE(url_data_net_cache_->LookUp(kUrl) != 0);
  }
  
  // Verify basic usage of the Save() method.
  TEST_F(NetCacheTest, TestBasicSave) {
    const string kUrl("http://host.com/style/simple.kml");
    const string kContent("some random blob of data");
    MemoryFilePtr test_data_item = MemoryFile::CreateFromString(kContent);
    ASSERT_TRUE(url_data_net_cache_->Save(kUrl, test_data_item));
    ASSERT_EQ(static_cast<size_t>(1), url_data_net_cache_->Size());
    ASSERT_TRUE(url_data_net_cache_->LookUp(kUrl) != 0);
    ASSERT_EQ(kContent,
                         url_data_net_cache_->Fetch(kUrl)->get_content());
  }
  
  // Verify basic usage of the Delete() method.
  TEST_F(NetCacheTest, TestBasicDelete) {
    const string kUrl("http://host.com/style/simple.kml");
    ASSERT_TRUE(url_data_net_cache_->Fetch(kUrl) != 0);
    ASSERT_TRUE(url_data_net_cache_->Delete(kUrl));
    ASSERT_EQ(kSize0, url_data_net_cache_->Size());
    ASSERT_FALSE(url_data_net_cache_->LookUp(kUrl));
  }
  
  // Verify basic usage of the RemoveOldest method.
  TEST_F(NetCacheTest, TestBasicRemoveOldest) {
    const string kUrl("http://host.com/style/simple.kml");
    ASSERT_TRUE(url_data_net_cache_->Fetch(kUrl) != 0);
    ASSERT_TRUE(url_data_net_cache_->RemoveOldest());
    ASSERT_EQ(kSize0, url_data_net_cache_->Size());
    ASSERT_FALSE(url_data_net_cache_->LookUp(kUrl));
  }
  
  // TODO move to base/string_util.h
  template<typename T>
  inline string ToString(T value) {
    std::stringstream ss;
    ss.precision(15);
    ss << value;
    return ss.str();
  }
  
  // Verify that the NetCache never exceeds the maximum configured size and
  // that it drains fully.
  TEST_F(NetCacheTest, TestOverflow) {
    for (size_t i = 0; i < kUrlDataNetCacheSize*2; ++i) {
      const string kUrl("http://host.com/" + ToString(i));
      MemoryFilePtr url_data = url_data_net_cache_->Fetch(kUrl);
      ASSERT_TRUE(url_data != 0);  // UrlDataNetFetcher never fails.
      // UrlDataNetFetcher simply uses the url as the content.
      ASSERT_EQ(kUrl, url_data->get_content());
      // The most recently Fetch()'ed url is guaranteed to bein the cache.
      ASSERT_TRUE(url_data_net_cache_->LookUp(kUrl) != 0);
      const size_t want_size =
          i < kUrlDataNetCacheSize ? i + 1 : kUrlDataNetCacheSize;
      ASSERT_EQ(want_size, url_data_net_cache_->Size());
    }
    // Cache is full so LookUp will succeed on all URLs in the 2nd half of the
    // the test range.
    for (size_t i = kUrlDataNetCacheSize; i < kUrlDataNetCacheSize*2; ++i) {
      const string kUrl("http://host.com/" + ToString(i));
      ASSERT_TRUE(url_data_net_cache_->LookUp(kUrl) != 0);
    }
    // RemoveOldest() removes items one at a time.
    for (size_t i = 0; i < kUrlDataNetCacheSize; ++i) {
      ASSERT_TRUE(url_data_net_cache_->RemoveOldest());
      ASSERT_EQ(kUrlDataNetCacheSize - i - 1,
                           url_data_net_cache_->Size());
    }
  
    // Cache is empty so LookUp will fail on all URLs.
    for (size_t i = 0; i < kUrlDataNetCacheSize*2; ++i) {
      const string kUrl("http://host.com/" + ToString(i));
      ASSERT_FALSE(url_data_net_cache_->LookUp(kUrl));
    }
    
    // At size 0 RemoveOldest returns false.
    ASSERT_EQ(kSize0, url_data_net_cache_->Size());
    ASSERT_FALSE(url_data_net_cache_->RemoveOldest());
  }
  
  // Verify that destruction of the cache destroys all items in the cache.
  TEST_F(NetCacheTest, TestDeleteCache) {
    // Verify proper operation of the internal InstrumentedCacheItem class.
    ASSERT_EQ(kSize0, instrumented_cache_item_count);
    const string kContent("some random stuf");
    InstrumentedCacheItemPtr item =
        InstrumentedCacheItem::CreateFromString(kContent);
    ASSERT_EQ(kContent, item->get_content());
    ASSERT_EQ(kSize1, instrumented_cache_item_count);
    item = NULL;  // Forces delete of object managed by intrusive_ptr.
    ASSERT_EQ(kSize0, instrumented_cache_item_count);
  
    {
      NetCache<InstrumentedCacheItem> net_cache(&url_data_net_fetcher_,
                                                 kUrlDataNetCacheSize);
      ASSERT_EQ(kSize0, instrumented_cache_item_count);
      for (size_t i = 0; i < kUrlDataNetCacheSize*2; ++i) {
        const string kUrl("http://host.com/" + ToString(i));
        InstrumentedCacheItemPtr url_data = net_cache.Fetch(kUrl);
        const size_t want_size =
            i < kUrlDataNetCacheSize ? i + 1 : kUrlDataNetCacheSize;
        ASSERT_EQ(want_size, instrumented_cache_item_count);
        ASSERT_EQ(want_size, net_cache.Size());
      }
      ASSERT_TRUE(net_cache.RemoveOldest());
      ASSERT_EQ(kUrlDataNetCacheSize - 1,
                           instrumented_cache_item_count);
      ASSERT_EQ(kUrlDataNetCacheSize - 1,
                           net_cache.Size());
    }
    // End of scope deletes net_cache_ and all CacheItems
    ASSERT_EQ(kSize0, instrumented_cache_item_count);
  }
  
  #ifdef PRINT_TIME_RESULTS
  // This is a simple timing test to estimate when the cache_count_ rolls over.
  // On a near-zero-latency network such as the one faked in UrlDataNetFetcher's
  // FetchUrl (which does no I/O at all) the elapsed time below is 22 seconds on
  // 2.33 Ghz Intel Core 2 Duo on a MacBook Pro.
  TEST_F(NetCacheTest, TimingTest) {
    time_t start = time(NULL);
    const int count = 1000000;
    for (int i = 0; i < count; ++i) {
      ASSERT_TRUE(url_data_net_cache_->Fetch(ToString(i)));
    }
    time_t elapsed = time(NULL) - start;
    std::cerr << count << " Fetch's in " << elapsed << " seconds" << std::endl;
    // ISO/IEC 988:1999 7.18.2.1
  #define UINT64_MAX        18446744073709551615ULL
    std::cerr << "Seconds to roll over " << (UINT64_MAX/count)*elapsed
              << std::endl;
  }
  #endif
  
  }  // end namespace kmlengine
  