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