Home | History | Annotate | Download | only in core
      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 
     13 namespace dom_distiller {
     14 
     15 namespace {
     16 
     17 ArticleEntry 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 
     38 DistilledArticleProto 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 
     51 class 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.
     76 TEST_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.
    100 TEST_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|.
    113 TEST_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.
    163 TEST_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.
    244 TEST_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.
    281 TEST_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