1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "base/bind.h" 6#include "base/message_loop/message_loop.h" 7#include "base/run_loop.h" 8#include "components/dom_distiller/core/article_entry.h" 9#include "components/dom_distiller/core/distilled_content_store.h" 10#include "components/dom_distiller/core/proto/distilled_article.pb.h" 11#include "testing/gtest/include/gtest/gtest.h" 12 13namespace dom_distiller { 14 15namespace { 16 17ArticleEntry CreateEntry(std::string entry_id, 18 std::string page_url1, 19 std::string page_url2, 20 std::string page_url3) { 21 ArticleEntry entry; 22 entry.set_entry_id(entry_id); 23 if (!page_url1.empty()) { 24 ArticleEntryPage* page = entry.add_pages(); 25 page->set_url(page_url1); 26 } 27 if (!page_url2.empty()) { 28 ArticleEntryPage* page = entry.add_pages(); 29 page->set_url(page_url2); 30 } 31 if (!page_url3.empty()) { 32 ArticleEntryPage* page = entry.add_pages(); 33 page->set_url(page_url3); 34 } 35 return entry; 36} 37 38DistilledArticleProto CreateDistilledArticleForEntry( 39 const ArticleEntry& entry) { 40 DistilledArticleProto article; 41 for (int i = 0; i < entry.pages_size(); ++i) { 42 DistilledPageProto* page = article.add_pages(); 43 page->set_url(entry.pages(i).url()); 44 page->set_html("<div>" + entry.pages(i).url() + "</div>"); 45 } 46 return article; 47} 48 49} // namespace 50 51class InMemoryContentStoreTest : public testing::Test { 52 public: 53 void OnLoadCallback(bool success, scoped_ptr<DistilledArticleProto> proto) { 54 load_success_ = success; 55 loaded_proto_ = proto.Pass(); 56 } 57 58 void OnSaveCallback(bool success) { save_success_ = success; } 59 60 protected: 61 // testing::Test implementation: 62 virtual void SetUp() OVERRIDE { 63 store_.reset(new InMemoryContentStore(kDefaultMaxNumCachedEntries)); 64 save_success_ = false; 65 load_success_ = false; 66 loaded_proto_.reset(); 67 } 68 69 scoped_ptr<InMemoryContentStore> store_; 70 bool save_success_; 71 bool load_success_; 72 scoped_ptr<DistilledArticleProto> loaded_proto_; 73}; 74 75// Tests whether saving and then loading a single article works as expected. 76TEST_F(InMemoryContentStoreTest, SaveAndLoadSingleArticle) { 77 base::MessageLoop loop; 78 const ArticleEntry entry = CreateEntry("test-id", "url1", "url2", "url3"); 79 const DistilledArticleProto stored_proto = 80 CreateDistilledArticleForEntry(entry); 81 store_->SaveContent(entry, 82 stored_proto, 83 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 84 base::Unretained(this))); 85 base::MessageLoop::current()->RunUntilIdle(); 86 EXPECT_TRUE(save_success_); 87 save_success_ = false; 88 89 store_->LoadContent(entry, 90 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 91 base::Unretained(this))); 92 base::MessageLoop::current()->RunUntilIdle(); 93 EXPECT_TRUE(load_success_); 94 EXPECT_EQ(stored_proto.SerializeAsString(), 95 loaded_proto_->SerializeAsString()); 96} 97 98// Tests that loading articles which have never been stored, yields a callback 99// where success is false. 100TEST_F(InMemoryContentStoreTest, LoadNonExistentArticle) { 101 base::MessageLoop loop; 102 const ArticleEntry entry = CreateEntry("bogus-id", "url1", "url2", "url3"); 103 store_->LoadContent(entry, 104 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 105 base::Unretained(this))); 106 base::MessageLoop::current()->RunUntilIdle(); 107 EXPECT_FALSE(load_success_); 108} 109 110// Verifies that content store can store multiple articles, and that ordering 111// of save and store does not matter when the total number of articles does not 112// exceed |kDefaultMaxNumCachedEntries|. 113TEST_F(InMemoryContentStoreTest, SaveAndLoadMultipleArticles) { 114 base::MessageLoop loop; 115 // Store first article. 116 const ArticleEntry first_entry = CreateEntry("first", "url1", "url2", "url3"); 117 const DistilledArticleProto first_stored_proto = 118 CreateDistilledArticleForEntry(first_entry); 119 store_->SaveContent(first_entry, 120 first_stored_proto, 121 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 122 base::Unretained(this))); 123 base::MessageLoop::current()->RunUntilIdle(); 124 EXPECT_TRUE(save_success_); 125 save_success_ = false; 126 127 // Store second article. 128 const ArticleEntry second_entry = 129 CreateEntry("second", "url4", "url5", "url6"); 130 const DistilledArticleProto second_stored_proto = 131 CreateDistilledArticleForEntry(second_entry); 132 store_->SaveContent(second_entry, 133 second_stored_proto, 134 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 135 base::Unretained(this))); 136 base::MessageLoop::current()->RunUntilIdle(); 137 EXPECT_TRUE(save_success_); 138 save_success_ = false; 139 140 // Load second article. 141 store_->LoadContent(second_entry, 142 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 143 base::Unretained(this))); 144 base::MessageLoop::current()->RunUntilIdle(); 145 EXPECT_TRUE(load_success_); 146 load_success_ = false; 147 EXPECT_EQ(second_stored_proto.SerializeAsString(), 148 loaded_proto_->SerializeAsString()); 149 loaded_proto_.reset(); 150 151 // Load first article. 152 store_->LoadContent(first_entry, 153 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 154 base::Unretained(this))); 155 base::MessageLoop::current()->RunUntilIdle(); 156 EXPECT_TRUE(load_success_); 157 EXPECT_EQ(first_stored_proto.SerializeAsString(), 158 loaded_proto_->SerializeAsString()); 159} 160 161// Verifies that the content store does not store unlimited number of articles, 162// but expires the oldest ones when the limit for number of articles is reached. 163TEST_F(InMemoryContentStoreTest, SaveAndLoadMoreThanMaxArticles) { 164 base::MessageLoop loop; 165 166 // Create a new store with only |kMaxNumArticles| articles as the limit. 167 const int kMaxNumArticles = 3; 168 store_.reset(new InMemoryContentStore(kMaxNumArticles)); 169 170 // Store first article. 171 const ArticleEntry first_entry = CreateEntry("first", "url1", "url2", "url3"); 172 const DistilledArticleProto first_stored_proto = 173 CreateDistilledArticleForEntry(first_entry); 174 store_->SaveContent(first_entry, 175 first_stored_proto, 176 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 177 base::Unretained(this))); 178 base::MessageLoop::current()->RunUntilIdle(); 179 EXPECT_TRUE(save_success_); 180 save_success_ = false; 181 182 // Store second article. 183 const ArticleEntry second_entry = 184 CreateEntry("second", "url4", "url5", "url6"); 185 const DistilledArticleProto second_stored_proto = 186 CreateDistilledArticleForEntry(second_entry); 187 store_->SaveContent(second_entry, 188 second_stored_proto, 189 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 190 base::Unretained(this))); 191 base::MessageLoop::current()->RunUntilIdle(); 192 EXPECT_TRUE(save_success_); 193 save_success_ = false; 194 195 // Store third article. 196 const ArticleEntry third_entry = CreateEntry("third", "url7", "url8", "url9"); 197 const DistilledArticleProto third_stored_proto = 198 CreateDistilledArticleForEntry(third_entry); 199 store_->SaveContent(third_entry, 200 third_stored_proto, 201 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 202 base::Unretained(this))); 203 base::MessageLoop::current()->RunUntilIdle(); 204 EXPECT_TRUE(save_success_); 205 save_success_ = false; 206 207 // Load first article. This will make the first article the most recent 208 // accessed article. 209 store_->LoadContent(first_entry, 210 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 211 base::Unretained(this))); 212 base::MessageLoop::current()->RunUntilIdle(); 213 EXPECT_TRUE(load_success_); 214 load_success_ = false; 215 EXPECT_EQ(first_stored_proto.SerializeAsString(), 216 loaded_proto_->SerializeAsString()); 217 loaded_proto_.reset(); 218 219 // Store fourth article. 220 const ArticleEntry fourth_entry = 221 CreateEntry("fourth", "url10", "url11", "url12"); 222 const DistilledArticleProto fourth_stored_proto = 223 CreateDistilledArticleForEntry(fourth_entry); 224 store_->SaveContent(fourth_entry, 225 fourth_stored_proto, 226 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 227 base::Unretained(this))); 228 base::MessageLoop::current()->RunUntilIdle(); 229 EXPECT_TRUE(save_success_); 230 save_success_ = false; 231 232 // Load second article, which by now is the oldest accessed article, since 233 // the first article has been loaded once. 234 store_->LoadContent(second_entry, 235 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 236 base::Unretained(this))); 237 base::MessageLoop::current()->RunUntilIdle(); 238 // Since the store can only contain |kMaxNumArticles| entries, this load 239 // should fail. 240 EXPECT_FALSE(load_success_); 241} 242 243// Tests whether saving and then loading a single article works as expected. 244TEST_F(InMemoryContentStoreTest, LookupArticleByURL) { 245 base::MessageLoop loop; 246 const ArticleEntry entry = CreateEntry("test-id", "url1", "url2", "url3"); 247 const DistilledArticleProto stored_proto = 248 CreateDistilledArticleForEntry(entry); 249 store_->SaveContent(entry, 250 stored_proto, 251 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 252 base::Unretained(this))); 253 base::MessageLoop::current()->RunUntilIdle(); 254 EXPECT_TRUE(save_success_); 255 save_success_ = false; 256 257 // Create an entry where the entry ID does not match, but the first URL does. 258 const ArticleEntry lookup_entry1 = CreateEntry("lookup-id", "url1", "", ""); 259 store_->LoadContent(lookup_entry1, 260 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 261 base::Unretained(this))); 262 base::MessageLoop::current()->RunUntilIdle(); 263 EXPECT_TRUE(load_success_); 264 EXPECT_EQ(stored_proto.SerializeAsString(), 265 loaded_proto_->SerializeAsString()); 266 267 // Create an entry where the entry ID does not match, but the second URL does. 268 const ArticleEntry lookup_entry2 = 269 CreateEntry("lookup-id", "bogus", "url2", ""); 270 store_->LoadContent(lookup_entry2, 271 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 272 base::Unretained(this))); 273 base::MessageLoop::current()->RunUntilIdle(); 274 EXPECT_TRUE(load_success_); 275 EXPECT_EQ(stored_proto.SerializeAsString(), 276 loaded_proto_->SerializeAsString()); 277} 278 279// Verifies that the content store does not store unlimited number of articles, 280// but expires the oldest ones when the limit for number of articles is reached. 281TEST_F(InMemoryContentStoreTest, LoadArticleByURLAfterExpungedFromCache) { 282 base::MessageLoop loop; 283 284 // Create a new store with only |kMaxNumArticles| articles as the limit. 285 const int kMaxNumArticles = 1; 286 store_.reset(new InMemoryContentStore(kMaxNumArticles)); 287 288 // Store an article. 289 const ArticleEntry first_entry = CreateEntry("first", "url1", "url2", "url3"); 290 const DistilledArticleProto first_stored_proto = 291 CreateDistilledArticleForEntry(first_entry); 292 store_->SaveContent(first_entry, 293 first_stored_proto, 294 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 295 base::Unretained(this))); 296 base::MessageLoop::current()->RunUntilIdle(); 297 EXPECT_TRUE(save_success_); 298 save_success_ = false; 299 300 // Looking up the first entry by URL should succeed when it is still in the 301 // cache. 302 const ArticleEntry first_entry_lookup = 303 CreateEntry("lookup-id", "url1", "", ""); 304 store_->LoadContent(first_entry_lookup, 305 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 306 base::Unretained(this))); 307 base::MessageLoop::current()->RunUntilIdle(); 308 EXPECT_TRUE(load_success_); 309 EXPECT_EQ(first_stored_proto.SerializeAsString(), 310 loaded_proto_->SerializeAsString()); 311 312 // Store second article. This will remove the first article from the cache. 313 const ArticleEntry second_entry = 314 CreateEntry("second", "url4", "url5", "url6"); 315 const DistilledArticleProto second_stored_proto = 316 CreateDistilledArticleForEntry(second_entry); 317 store_->SaveContent(second_entry, 318 second_stored_proto, 319 base::Bind(&InMemoryContentStoreTest::OnSaveCallback, 320 base::Unretained(this))); 321 base::MessageLoop::current()->RunUntilIdle(); 322 EXPECT_TRUE(save_success_); 323 save_success_ = false; 324 325 // Looking up the first entry by URL should fail when it is not in the cache. 326 store_->LoadContent(first_entry_lookup, 327 base::Bind(&InMemoryContentStoreTest::OnLoadCallback, 328 base::Unretained(this))); 329 base::MessageLoop::current()->RunUntilIdle(); 330 EXPECT_FALSE(load_success_); 331} 332 333} // namespace dom_distiller 334