1 // Copyright 2008, Google Inc. All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are met:
5 //
6 //  1. Redistributions of source code must retain the above copyright notice,
7 //     this list of conditions and the following disclaimer.
8 //  2. Redistributions in binary form must reproduce the above copyright notice,
9 //     this list of conditions and the following disclaimer in the documentation
10 //     and/or other materials provided with the distribution.
11 //  3. Neither the name of Google Inc. nor the names of its contributors may be
12 //     used to endorse or promote products derived from this software without
13 //     specific prior written permission.
14 //
15 // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
16 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
18 // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 
26 #include "kml/base/net_cache.h"
27 // Uncomment this #define to enable output of timing results.
28 // #define PRINT_TIME_RESULTS
29 #ifdef PRINT_TIME_RESULTS
30 #include <iostream>
31 #endif
32 #include "kml/base/memory_file.h"
33 #include "kml/base/net_cache_test_util.h"
34 #include "kml/base/referent.h"
35 #include "gtest/gtest.h"
36 
37 namespace kmlbase {
38 
39 static const size_t kSize0 = 0;
40 static const size_t kSize1 = 1;
41 
42 // This NetCache uses TestDataNetFetcher which maps each URL path to a file
43 // under testdata.  The CacheItem is a MemoryFile which holds the file content.
44 const size_t kTestDataNetCacheSize = 10;
45 typedef NetCache<MemoryFile> TestDataNetCache;
46 
47 // This NetCache CacheItem is an empty which saves no data, however an
48 // empty string indicates that no NullCacheItem is to be created.
49 class NullCacheItem : public Referent {
50  public:
CreateFromString(const string & data)51   static NullCacheItem* CreateFromString(const string& data) {
52     return data.empty() ? NULL : new NullCacheItem;
53   }
54 };
55 
56 typedef boost::intrusive_ptr<NullCacheItem> NullCacheItemPtr;
57 
58 // This NetCache CacheItem has instrumentation to track creation/deletion.
59 static size_t instrumented_cache_item_count;
60 class InstrumentedCacheItem : public Referent {
61  public:
CreateFromString(const string & data)62    static InstrumentedCacheItem* CreateFromString(const string& data) {
63      return new InstrumentedCacheItem(data);
64    }
65 
get_content() const66    const string& get_content() const {
67      return content_;
68    }
69 
70  private:
InstrumentedCacheItem(const string & content)71   InstrumentedCacheItem(const string& content) : content_(content) {
72     ++instrumented_cache_item_count;
73   }
~InstrumentedCacheItem()74   ~InstrumentedCacheItem() {
75     --instrumented_cache_item_count;
76   }
77 
78   string content_;
79 };
80 
81 typedef boost::intrusive_ptr<InstrumentedCacheItem> InstrumentedCacheItemPtr;
82 
83 // Since the default NetFetcher always returns false this cache will never
84 // accept content.  The size is set to non-zero to verify that cache internal
85 // limits are not the limiter for this behavior.
86 const size_t kNullNetCacheSize = 1;
87 typedef NetCache<NullCacheItem> NullNetCache;
88 
89 // This NetFetcher simply sets the output data to the url itself.
90 class UrlDataNetFetcher : public NetFetcher {
91  public:
FetchUrl(const string & url,string * data) const92   bool FetchUrl(const string& url, string* data) const {
93     if (data) {
94       *data = url;
95       return true;
96     }
97     return false;
98   }
99 };
100 
101 // This NetCache essentially maps each URL to a MemoryFile
102 // whose content is that URL.
103 const size_t kUrlDataNetCacheSize = 1234;
104 typedef NetCache<MemoryFile> UrlDataNetCache;
105 
106 class NetCacheTest : public testing::Test {
107  protected:
SetUp()108   virtual void SetUp() {
109     null_net_cache_.reset(new NullNetCache(&null_net_fetcher_,
110                                            kNullNetCacheSize));
111     testdata_net_cache_.reset(new TestDataNetCache(&testdata_net_fetcher_,
112                                                    kTestDataNetCacheSize));
113     url_data_net_cache_.reset(new UrlDataNetCache(&url_data_net_fetcher_,
114                                                   kUrlDataNetCacheSize));
115   }
116 
117   NetFetcher null_net_fetcher_;
118   boost::scoped_ptr<NullNetCache> null_net_cache_;
119   TestDataNetFetcher testdata_net_fetcher_;
120   boost::scoped_ptr<TestDataNetCache> testdata_net_cache_;
121   UrlDataNetFetcher url_data_net_fetcher_;
122   boost::scoped_ptr<UrlDataNetCache> url_data_net_cache_;
123 };
124 
125 // Verify very basic usage of the Size() method.
TEST_F(NetCacheTest,TestBasicSize)126 TEST_F(NetCacheTest, TestBasicSize) {
127   ASSERT_TRUE(kNullNetCacheSize >= null_net_cache_->Size());
128 }
129 
130 // Verify basic usage of the Fetch() method.
TEST_F(NetCacheTest,TestBasicFetch)131 TEST_F(NetCacheTest, TestBasicFetch) {
132   const string kUrl("http://host.com/style/simple.kml");
133   // Fetch always fails on NullNetCache.
134   ASSERT_FALSE(null_net_cache_->Fetch(kUrl));
135   ASSERT_EQ(kSize0, null_net_cache_->Size());
136   // Fetch of a valid testdata path succeeds.
137   ASSERT_TRUE(testdata_net_cache_->Fetch(kUrl));
138   // TODO read the test file directly and compare content
139   ASSERT_EQ(static_cast<size_t>(1), testdata_net_cache_->Size());
140   // Fetch on UrlDataNetCache returns URL.
141   ASSERT_EQ(kUrl, url_data_net_cache_->Fetch(kUrl)->get_content());
142   ASSERT_EQ(static_cast<size_t>(1), url_data_net_cache_->Size());
143 }
144 
145 // Verify basic usage of the LookUp() method.
TEST_F(NetCacheTest,TestBasicLookUp)146 TEST_F(NetCacheTest, TestBasicLookUp) {
147   const string kUrl("http://host.com/style/simple.kml");
148   // Verify that initially all caches return false for a LookUp of this URL.
149   ASSERT_FALSE(null_net_cache_->LookUp(kUrl));
150   ASSERT_FALSE(testdata_net_cache_->LookUp(kUrl));
151   ASSERT_FALSE(url_data_net_cache_->LookUp(kUrl));
152   // Fetch this URL into the cache in those caches that save content.
153   ASSERT_TRUE(testdata_net_cache_->Fetch(kUrl));
154   ASSERT_TRUE(url_data_net_cache_->Fetch(kUrl));
155   // Verify that these caches return true now on LookUp.
156   ASSERT_TRUE(testdata_net_cache_->LookUp(kUrl));
157   ASSERT_TRUE(url_data_net_cache_->LookUp(kUrl));
158 }
159 
160 // Verify basic usage of the Save() method.
TEST_F(NetCacheTest,TestBasicSave)161 TEST_F(NetCacheTest, TestBasicSave) {
162   const string kUrl("http://host.com/style/simple.kml");
163   const string kContent("some random blob of data");
164   MemoryFilePtr test_data_item = MemoryFile::CreateFromString(kContent);
165   ASSERT_TRUE(url_data_net_cache_->Save(kUrl, test_data_item));
166   ASSERT_EQ(static_cast<size_t>(1), url_data_net_cache_->Size());
167   ASSERT_TRUE(url_data_net_cache_->LookUp(kUrl));
168   ASSERT_EQ(kContent,
169                        url_data_net_cache_->Fetch(kUrl)->get_content());
170 }
171 
172 // Verify basic usage of the Delete() method.
TEST_F(NetCacheTest,TestBasicDelete)173 TEST_F(NetCacheTest, TestBasicDelete) {
174   const string kUrl("http://host.com/style/simple.kml");
175   ASSERT_TRUE(url_data_net_cache_->Fetch(kUrl));
176   ASSERT_TRUE(url_data_net_cache_->Delete(kUrl));
177   ASSERT_EQ(kSize0, url_data_net_cache_->Size());
178   ASSERT_FALSE(url_data_net_cache_->LookUp(kUrl));
179 }
180 
181 // Verify basic usage of the RemoveOldest method.
TEST_F(NetCacheTest,TestBasicRemoveOldest)182 TEST_F(NetCacheTest, TestBasicRemoveOldest) {
183   const string kUrl("http://host.com/style/simple.kml");
184   ASSERT_TRUE(url_data_net_cache_->Fetch(kUrl));
185   ASSERT_TRUE(url_data_net_cache_->RemoveOldest());
186   ASSERT_EQ(kSize0, url_data_net_cache_->Size());
187   ASSERT_FALSE(url_data_net_cache_->LookUp(kUrl));
188 }
189 
190 // TODO move to base/string_util.h
191 template<typename T>
ToString(T value)192 inline string ToString(T value) {
193   std::stringstream ss;
194   ss.precision(15);
195   ss << value;
196   return ss.str();
197 }
198 
199 // Verify that the NetCache never exceeds the maximum configured size and
200 // that it drains fully.
TEST_F(NetCacheTest,TestOverflow)201 TEST_F(NetCacheTest, TestOverflow) {
202   for (size_t i = 0; i < kUrlDataNetCacheSize*2; ++i) {
203     const string kUrl("http://host.com/" + ToString(i));
204     MemoryFilePtr url_data = url_data_net_cache_->Fetch(kUrl);
205     ASSERT_TRUE(url_data);  // UrlDataNetFetcher never fails.
206     // UrlDataNetFetcher simply uses the url as the content.
207     ASSERT_EQ(kUrl, url_data->get_content());
208     // The most recently Fetch()'ed url is guaranteed to bein the cache.
209     ASSERT_TRUE(url_data_net_cache_->LookUp(kUrl));
210     const size_t want_size =
211         i < kUrlDataNetCacheSize ? i + 1 : kUrlDataNetCacheSize;
212     ASSERT_EQ(want_size, url_data_net_cache_->Size());
213   }
214   // Cache is full so LookUp will succeed on all URLs in the 2nd half of the
215   // the test range.
216   for (size_t i = kUrlDataNetCacheSize; i < kUrlDataNetCacheSize*2; ++i) {
217     const string kUrl("http://host.com/" + ToString(i));
218     ASSERT_TRUE(url_data_net_cache_->LookUp(kUrl));
219   }
220   // RemoveOldest() removes items one at a time.
221   for (size_t i = 0; i < kUrlDataNetCacheSize; ++i) {
222     ASSERT_TRUE(url_data_net_cache_->RemoveOldest());
223     ASSERT_EQ(kUrlDataNetCacheSize - i - 1,
224                          url_data_net_cache_->Size());
225   }
226 
227   // Cache is empty so LookUp will fail on all URLs.
228   for (size_t i = 0; i < kUrlDataNetCacheSize*2; ++i) {
229     const string kUrl("http://host.com/" + ToString(i));
230     ASSERT_FALSE(url_data_net_cache_->LookUp(kUrl));
231   }
232 
233   // At size 0 RemoveOldest returns false.
234   ASSERT_EQ(kSize0, url_data_net_cache_->Size());
235   ASSERT_FALSE(url_data_net_cache_->RemoveOldest());
236 }
237 
238 // Verify that destruction of the cache destroys all items in the cache.
TEST_F(NetCacheTest,TestDeleteCache)239 TEST_F(NetCacheTest, TestDeleteCache) {
240   // Verify proper operation of the internal InstrumentedCacheItem class.
241   ASSERT_EQ(kSize0, instrumented_cache_item_count);
242   const string kContent("some random stuf");
243   InstrumentedCacheItemPtr item =
244       InstrumentedCacheItem::CreateFromString(kContent);
245   ASSERT_EQ(kContent, item->get_content());
246   ASSERT_EQ(kSize1, instrumented_cache_item_count);
247   item = NULL;  // Forces delete of object managed by intrusive_ptr.
248   ASSERT_EQ(kSize0, instrumented_cache_item_count);
249 
250   {
251     NetCache<InstrumentedCacheItem> net_cache(&url_data_net_fetcher_,
252                                                kUrlDataNetCacheSize);
253     ASSERT_EQ(kSize0, instrumented_cache_item_count);
254     for (size_t i = 0; i < kUrlDataNetCacheSize*2; ++i) {
255       const string kUrl("http://host.com/" + ToString(i));
256       InstrumentedCacheItemPtr url_data = net_cache.Fetch(kUrl);
257       const size_t want_size =
258           i < kUrlDataNetCacheSize ? i + 1 : kUrlDataNetCacheSize;
259       ASSERT_EQ(want_size, instrumented_cache_item_count);
260       ASSERT_EQ(want_size, net_cache.Size());
261     }
262     ASSERT_TRUE(net_cache.RemoveOldest());
263     ASSERT_EQ(kUrlDataNetCacheSize - 1,
264                          instrumented_cache_item_count);
265     ASSERT_EQ(kUrlDataNetCacheSize - 1,
266                          net_cache.Size());
267   }
268   // End of scope deletes net_cache_ and all CacheItems
269   ASSERT_EQ(kSize0, instrumented_cache_item_count);
270 }
271 
272 #ifdef PRINT_TIME_RESULTS
273 // This is a simple timing test to estimate when the cache_count_ rolls over.
274 // On a near-zero-latency network such as the one faked in UrlDataNetFetcher's
275 // FetchUrl (which does no I/O at all) the elapsed time below is 22 seconds on
276 // 2.33 Ghz Intel Core 2 Duo on a MacBook Pro.
TEST_F(NetCacheTest,TimingTest)277 TEST_F(NetCacheTest, TimingTest) {
278   time_t start = time(NULL);
279   const int count = 1000000;
280   for (int i = 0; i < count; ++i) {
281     ASSERT_TRUE(url_data_net_cache_->Fetch(ToString(i)));
282   }
283   time_t elapsed = time(NULL) - start;
284   std::cerr << count << " Fetch's in " << elapsed << " seconds" << std::endl;
285   // ISO/IEC 988:1999 7.18.2.1
286 #define UINT64_MAX        18446744073709551615ULL
287   std::cerr << "Seconds to roll over " << (UINT64_MAX/count)*elapsed
288             << std::endl;
289 }
290 #endif
291 
292 }  // end namespace kmlengine
293