blob: f4713932809d1151dbd50fce87737ce499ec44be [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/dom_distiller/core/distilled_content_store.h"
#include <utility>
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "components/dom_distiller/core/article_entry.h"
#include "components/dom_distiller/core/proto/distilled_article.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace dom_distiller {
namespace {
ArticleEntry CreateEntry(std::string entry_id,
std::string page_url1,
std::string page_url2,
std::string page_url3) {
ArticleEntry entry;
entry.set_entry_id(entry_id);
if (!page_url1.empty()) {
ArticleEntryPage* page = entry.add_pages();
page->set_url(page_url1);
}
if (!page_url2.empty()) {
ArticleEntryPage* page = entry.add_pages();
page->set_url(page_url2);
}
if (!page_url3.empty()) {
ArticleEntryPage* page = entry.add_pages();
page->set_url(page_url3);
}
return entry;
}
DistilledArticleProto CreateDistilledArticleForEntry(
const ArticleEntry& entry) {
DistilledArticleProto article;
for (int i = 0; i < entry.pages_size(); ++i) {
DistilledPageProto* page = article.add_pages();
page->set_url(entry.pages(i).url());
page->set_html("<div>" + entry.pages(i).url() + "</div>");
}
return article;
}
} // namespace
class InMemoryContentStoreTest : public testing::Test {
public:
void OnLoadCallback(bool success,
std::unique_ptr<DistilledArticleProto> proto) {
load_success_ = success;
loaded_proto_ = std::move(proto);
}
void OnSaveCallback(bool success) { save_success_ = success; }
protected:
// testing::Test implementation:
void SetUp() override {
store_.reset(new InMemoryContentStore(kDefaultMaxNumCachedEntries));
save_success_ = false;
load_success_ = false;
loaded_proto_.reset();
}
std::unique_ptr<InMemoryContentStore> store_;
bool save_success_;
bool load_success_;
std::unique_ptr<DistilledArticleProto> loaded_proto_;
};
// Tests whether saving and then loading a single article works as expected.
TEST_F(InMemoryContentStoreTest, SaveAndLoadSingleArticle) {
base::test::ScopedTaskEnvironment task_environment;
const ArticleEntry entry = CreateEntry("test-id", "url1", "url2", "url3");
const DistilledArticleProto stored_proto =
CreateDistilledArticleForEntry(entry);
store_->SaveContent(entry,
stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
store_->LoadContent(entry,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(load_success_);
EXPECT_EQ(stored_proto.SerializeAsString(),
loaded_proto_->SerializeAsString());
}
// Tests that loading articles which have never been stored, yields a callback
// where success is false.
TEST_F(InMemoryContentStoreTest, LoadNonExistentArticle) {
base::test::ScopedTaskEnvironment task_environment;
const ArticleEntry entry = CreateEntry("bogus-id", "url1", "url2", "url3");
store_->LoadContent(entry,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(load_success_);
}
// Verifies that content store can store multiple articles, and that ordering
// of save and store does not matter when the total number of articles does not
// exceed |kDefaultMaxNumCachedEntries|.
TEST_F(InMemoryContentStoreTest, SaveAndLoadMultipleArticles) {
base::test::ScopedTaskEnvironment task_environment;
// Store first article.
const ArticleEntry first_entry = CreateEntry("first", "url1", "url2", "url3");
const DistilledArticleProto first_stored_proto =
CreateDistilledArticleForEntry(first_entry);
store_->SaveContent(first_entry,
first_stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
// Store second article.
const ArticleEntry second_entry =
CreateEntry("second", "url4", "url5", "url6");
const DistilledArticleProto second_stored_proto =
CreateDistilledArticleForEntry(second_entry);
store_->SaveContent(second_entry,
second_stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
// Load second article.
store_->LoadContent(second_entry,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(load_success_);
load_success_ = false;
EXPECT_EQ(second_stored_proto.SerializeAsString(),
loaded_proto_->SerializeAsString());
loaded_proto_.reset();
// Load first article.
store_->LoadContent(first_entry,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(load_success_);
EXPECT_EQ(first_stored_proto.SerializeAsString(),
loaded_proto_->SerializeAsString());
}
// Verifies that the content store does not store unlimited number of articles,
// but expires the oldest ones when the limit for number of articles is reached.
TEST_F(InMemoryContentStoreTest, SaveAndLoadMoreThanMaxArticles) {
base::test::ScopedTaskEnvironment task_environment;
// Create a new store with only |kMaxNumArticles| articles as the limit.
const int kMaxNumArticles = 3;
store_.reset(new InMemoryContentStore(kMaxNumArticles));
// Store first article.
const ArticleEntry first_entry = CreateEntry("first", "url1", "url2", "url3");
const DistilledArticleProto first_stored_proto =
CreateDistilledArticleForEntry(first_entry);
store_->SaveContent(first_entry,
first_stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
// Store second article.
const ArticleEntry second_entry =
CreateEntry("second", "url4", "url5", "url6");
const DistilledArticleProto second_stored_proto =
CreateDistilledArticleForEntry(second_entry);
store_->SaveContent(second_entry,
second_stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
// Store third article.
const ArticleEntry third_entry = CreateEntry("third", "url7", "url8", "url9");
const DistilledArticleProto third_stored_proto =
CreateDistilledArticleForEntry(third_entry);
store_->SaveContent(third_entry,
third_stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
// Load first article. This will make the first article the most recent
// accessed article.
store_->LoadContent(first_entry,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(load_success_);
load_success_ = false;
EXPECT_EQ(first_stored_proto.SerializeAsString(),
loaded_proto_->SerializeAsString());
loaded_proto_.reset();
// Store fourth article.
const ArticleEntry fourth_entry =
CreateEntry("fourth", "url10", "url11", "url12");
const DistilledArticleProto fourth_stored_proto =
CreateDistilledArticleForEntry(fourth_entry);
store_->SaveContent(fourth_entry,
fourth_stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
// Load second article, which by now is the oldest accessed article, since
// the first article has been loaded once.
store_->LoadContent(second_entry,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// Since the store can only contain |kMaxNumArticles| entries, this load
// should fail.
EXPECT_FALSE(load_success_);
}
// Tests whether saving and then loading a single article works as expected.
TEST_F(InMemoryContentStoreTest, LookupArticleByURL) {
base::test::ScopedTaskEnvironment task_environment;
const ArticleEntry entry = CreateEntry("test-id", "url1", "url2", "url3");
const DistilledArticleProto stored_proto =
CreateDistilledArticleForEntry(entry);
store_->SaveContent(entry,
stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
// Create an entry where the entry ID does not match, but the first URL does.
const ArticleEntry lookup_entry1 = CreateEntry("lookup-id", "url1", "", "");
store_->LoadContent(lookup_entry1,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(load_success_);
EXPECT_EQ(stored_proto.SerializeAsString(),
loaded_proto_->SerializeAsString());
// Create an entry where the entry ID does not match, but the second URL does.
const ArticleEntry lookup_entry2 =
CreateEntry("lookup-id", "bogus", "url2", "");
store_->LoadContent(lookup_entry2,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(load_success_);
EXPECT_EQ(stored_proto.SerializeAsString(),
loaded_proto_->SerializeAsString());
}
// Verifies that the content store does not store unlimited number of articles,
// but expires the oldest ones when the limit for number of articles is reached.
TEST_F(InMemoryContentStoreTest, LoadArticleByURLAfterExpungedFromCache) {
base::test::ScopedTaskEnvironment task_environment;
// Create a new store with only |kMaxNumArticles| articles as the limit.
const int kMaxNumArticles = 1;
store_.reset(new InMemoryContentStore(kMaxNumArticles));
// Store an article.
const ArticleEntry first_entry = CreateEntry("first", "url1", "url2", "url3");
const DistilledArticleProto first_stored_proto =
CreateDistilledArticleForEntry(first_entry);
store_->SaveContent(first_entry,
first_stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
// Looking up the first entry by URL should succeed when it is still in the
// cache.
const ArticleEntry first_entry_lookup =
CreateEntry("lookup-id", "url1", "", "");
store_->LoadContent(first_entry_lookup,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(load_success_);
EXPECT_EQ(first_stored_proto.SerializeAsString(),
loaded_proto_->SerializeAsString());
// Store second article. This will remove the first article from the cache.
const ArticleEntry second_entry =
CreateEntry("second", "url4", "url5", "url6");
const DistilledArticleProto second_stored_proto =
CreateDistilledArticleForEntry(second_entry);
store_->SaveContent(second_entry,
second_stored_proto,
base::Bind(&InMemoryContentStoreTest::OnSaveCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(save_success_);
save_success_ = false;
// Looking up the first entry by URL should fail when it is not in the cache.
store_->LoadContent(first_entry_lookup,
base::Bind(&InMemoryContentStoreTest::OnLoadCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(load_success_);
}
} // namespace dom_distiller