1 // Copyright 2013 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.
5 #include "components/dom_distiller/core/dom_distiller_database.h"
10 #include "base/file_util.h"
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/run_loop.h"
13 #include "base/threading/thread.h"
14 #include "components/dom_distiller/core/article_entry.h"
15 #include "testing/gmock/include/gmock/gmock.h"
16 #include "testing/gtest/include/gtest/gtest.h"
18 using base::MessageLoop
;
19 using base::ScopedTempDir
;
20 using testing::Invoke
;
21 using testing::Return
;
24 namespace dom_distiller
{
28 typedef std::map
<std::string
, ArticleEntry
> EntryMap
;
30 class MockDB
: public DomDistillerDatabase::Database
{
32 MOCK_METHOD1(Init
, bool(const base::FilePath
&));
33 MOCK_METHOD2(Save
, bool(const EntryVector
&, const EntryVector
&));
34 MOCK_METHOD1(Load
, bool(EntryVector
*));
37 ON_CALL(*this, Init(_
)).WillByDefault(Return(true));
38 ON_CALL(*this, Save(_
, _
)).WillByDefault(Return(true));
39 ON_CALL(*this, Load(_
)).WillByDefault(Return(true));
42 bool LoadEntries(EntryVector
* entries
);
45 class MockDatabaseCaller
{
47 MOCK_METHOD1(InitCallback
, void(bool));
48 MOCK_METHOD1(SaveCallback
, void(bool));
49 void LoadCallback(bool success
, scoped_ptr
<EntryVector
> entries
) {
50 LoadCallback1(success
, entries
.get());
52 MOCK_METHOD2(LoadCallback1
, void(bool, EntryVector
*));
57 EntryMap
GetSmallModel() {
60 model
["key0"].set_entry_id("key0");
61 model
["key0"].add_pages()->set_url("http://foo.com/1");
62 model
["key0"].add_pages()->set_url("http://foo.com/2");
63 model
["key0"].add_pages()->set_url("http://foo.com/3");
65 model
["key1"].set_entry_id("key1");
66 model
["key1"].add_pages()->set_url("http://bar.com/all");
68 model
["key2"].set_entry_id("key2");
69 model
["key2"].add_pages()->set_url("http://baz.com/1");
74 void ExpectEntryPointersEquals(EntryMap expected
, const EntryVector
& actual
) {
75 EXPECT_EQ(expected
.size(), actual
.size());
76 for (size_t i
= 0; i
< actual
.size(); i
++) {
77 EntryMap::iterator expected_it
=
78 expected
.find(std::string(actual
[i
].entry_id()));
79 EXPECT_TRUE(expected_it
!= expected
.end());
80 std::string serialized_expected
= expected_it
->second
.SerializeAsString();
81 std::string serialized_actual
= actual
[i
].SerializeAsString();
82 EXPECT_EQ(serialized_expected
, serialized_actual
);
83 expected
.erase(expected_it
);
87 class DomDistillerDatabaseTest
: public testing::Test
{
89 virtual void SetUp() {
90 main_loop_
.reset(new MessageLoop());
91 db_
.reset(new DomDistillerDatabase(main_loop_
->message_loop_proxy()));
94 virtual void TearDown() {
96 base::RunLoop().RunUntilIdle();
100 scoped_ptr
<DomDistillerDatabase
> db_
;
101 scoped_ptr
<MessageLoop
> main_loop_
;
104 // Test that DomDistillerDatabase calls Init on the underlying database and that
105 // the caller's InitCallback is called with the correct value.
106 TEST_F(DomDistillerDatabaseTest
, TestDBInitSuccess
) {
107 base::FilePath
path(FILE_PATH_LITERAL("/fake/path"));
109 MockDB
* mock_db
= new MockDB();
110 EXPECT_CALL(*mock_db
, Init(path
)).WillOnce(Return(true));
112 MockDatabaseCaller caller
;
113 EXPECT_CALL(caller
, InitCallback(true));
115 db_
->InitWithDatabase(
116 scoped_ptr
<DomDistillerDatabase::Database
>(mock_db
),
117 base::FilePath(path
),
118 base::Bind(&MockDatabaseCaller::InitCallback
, base::Unretained(&caller
)));
120 base::RunLoop().RunUntilIdle();
123 TEST_F(DomDistillerDatabaseTest
, TestDBInitFailure
) {
124 base::FilePath
path(FILE_PATH_LITERAL("/fake/path"));
126 MockDB
* mock_db
= new MockDB();
127 EXPECT_CALL(*mock_db
, Init(path
)).WillOnce(Return(false));
129 MockDatabaseCaller caller
;
130 EXPECT_CALL(caller
, InitCallback(false));
132 db_
->InitWithDatabase(
133 scoped_ptr
<DomDistillerDatabase::Database
>(mock_db
),
134 base::FilePath(path
),
135 base::Bind(&MockDatabaseCaller::InitCallback
, base::Unretained(&caller
)));
137 base::RunLoop().RunUntilIdle();
140 ACTION_P(AppendLoadEntries
, model
) {
141 EntryVector
* output
= arg0
;
142 for (EntryMap::const_iterator it
= model
.begin(); it
!= model
.end(); ++it
) {
143 output
->push_back(it
->second
);
148 ACTION_P(VerifyLoadEntries
, expected
) {
149 EntryVector
* actual
= arg1
;
150 ExpectEntryPointersEquals(expected
, *actual
);
153 // Test that DomDistillerDatabase calls Load on the underlying database and that
154 // the caller's LoadCallback is called with the correct success value. Also
155 // confirms that on success, the expected entries are passed to the caller's
157 TEST_F(DomDistillerDatabaseTest
, TestDBLoadSuccess
) {
158 base::FilePath
path(FILE_PATH_LITERAL("/fake/path"));
160 MockDB
* mock_db
= new MockDB();
161 MockDatabaseCaller caller
;
162 EntryMap model
= GetSmallModel();
164 EXPECT_CALL(*mock_db
, Init(_
));
165 EXPECT_CALL(caller
, InitCallback(_
));
166 db_
->InitWithDatabase(
167 scoped_ptr
<DomDistillerDatabase::Database
>(mock_db
),
168 base::FilePath(path
),
169 base::Bind(&MockDatabaseCaller::InitCallback
, base::Unretained(&caller
)));
171 EXPECT_CALL(*mock_db
, Load(_
)).WillOnce(AppendLoadEntries(model
));
172 EXPECT_CALL(caller
, LoadCallback1(true, _
))
173 .WillOnce(VerifyLoadEntries(testing::ByRef(model
)));
175 base::Bind(&MockDatabaseCaller::LoadCallback
, base::Unretained(&caller
)));
177 base::RunLoop().RunUntilIdle();
180 TEST_F(DomDistillerDatabaseTest
, TestDBLoadFailure
) {
181 base::FilePath
path(FILE_PATH_LITERAL("/fake/path"));
183 MockDB
* mock_db
= new MockDB();
184 MockDatabaseCaller caller
;
186 EXPECT_CALL(*mock_db
, Init(_
));
187 EXPECT_CALL(caller
, InitCallback(_
));
188 db_
->InitWithDatabase(
189 scoped_ptr
<DomDistillerDatabase::Database
>(mock_db
),
190 base::FilePath(path
),
191 base::Bind(&MockDatabaseCaller::InitCallback
, base::Unretained(&caller
)));
193 EXPECT_CALL(*mock_db
, Load(_
)).WillOnce(Return(false));
194 EXPECT_CALL(caller
, LoadCallback1(false, _
));
196 base::Bind(&MockDatabaseCaller::LoadCallback
, base::Unretained(&caller
)));
198 base::RunLoop().RunUntilIdle();
201 ACTION_P(VerifyUpdateEntries
, expected
) {
202 const EntryVector
& actual
= arg0
;
203 ExpectEntryPointersEquals(expected
, actual
);
207 // Test that DomDistillerDatabase calls Save on the underlying database with the
208 // correct entries to save and that the caller's SaveCallback is called with the
209 // correct success value.
210 TEST_F(DomDistillerDatabaseTest
, TestDBSaveSuccess
) {
211 base::FilePath
path(FILE_PATH_LITERAL("/fake/path"));
213 MockDB
* mock_db
= new MockDB();
214 MockDatabaseCaller caller
;
215 EntryMap model
= GetSmallModel();
217 EXPECT_CALL(*mock_db
, Init(_
));
218 EXPECT_CALL(caller
, InitCallback(_
));
219 db_
->InitWithDatabase(
220 scoped_ptr
<DomDistillerDatabase::Database
>(mock_db
),
221 base::FilePath(path
),
222 base::Bind(&MockDatabaseCaller::InitCallback
, base::Unretained(&caller
)));
224 scoped_ptr
<EntryVector
> entries(new EntryVector());
225 for (EntryMap::iterator it
= model
.begin(); it
!= model
.end(); ++it
) {
226 entries
->push_back(it
->second
);
228 scoped_ptr
<EntryVector
> entries_to_remove(new EntryVector());
230 EXPECT_CALL(*mock_db
, Save(_
, _
)).WillOnce(VerifyUpdateEntries(model
));
231 EXPECT_CALL(caller
, SaveCallback(true));
234 entries_to_remove
.Pass(),
235 base::Bind(&MockDatabaseCaller::SaveCallback
, base::Unretained(&caller
)));
237 base::RunLoop().RunUntilIdle();
240 TEST_F(DomDistillerDatabaseTest
, TestDBSaveFailure
) {
241 base::FilePath
path(FILE_PATH_LITERAL("/fake/path"));
243 MockDB
* mock_db
= new MockDB();
244 MockDatabaseCaller caller
;
245 scoped_ptr
<EntryVector
> entries(new EntryVector());
246 scoped_ptr
<EntryVector
> entries_to_remove(new EntryVector());
248 EXPECT_CALL(*mock_db
, Init(_
));
249 EXPECT_CALL(caller
, InitCallback(_
));
250 db_
->InitWithDatabase(
251 scoped_ptr
<DomDistillerDatabase::Database
>(mock_db
),
252 base::FilePath(path
),
253 base::Bind(&MockDatabaseCaller::InitCallback
, base::Unretained(&caller
)));
255 EXPECT_CALL(*mock_db
, Save(_
, _
)).WillOnce(Return(false));
256 EXPECT_CALL(caller
, SaveCallback(false));
259 entries_to_remove
.Pass(),
260 base::Bind(&MockDatabaseCaller::SaveCallback
, base::Unretained(&caller
)));
262 base::RunLoop().RunUntilIdle();
265 ACTION_P(VerifyRemoveEntries
, expected
) {
266 const EntryVector
& actual
= arg1
;
267 ExpectEntryPointersEquals(expected
, actual
);
271 // Test that DomDistillerDatabase calls Save on the underlying database with the
272 // correct entries to delete and that the caller's SaveCallback is called with
273 // the correct success value.
274 TEST_F(DomDistillerDatabaseTest
, TestDBRemoveSuccess
) {
275 base::FilePath
path(FILE_PATH_LITERAL("/fake/path"));
277 MockDB
* mock_db
= new MockDB();
278 MockDatabaseCaller caller
;
279 EntryMap model
= GetSmallModel();
281 EXPECT_CALL(*mock_db
, Init(_
));
282 EXPECT_CALL(caller
, InitCallback(_
));
283 db_
->InitWithDatabase(
284 scoped_ptr
<DomDistillerDatabase::Database
>(mock_db
),
285 base::FilePath(path
),
286 base::Bind(&MockDatabaseCaller::InitCallback
, base::Unretained(&caller
)));
288 scoped_ptr
<EntryVector
> entries(new EntryVector());
289 scoped_ptr
<EntryVector
> entries_to_remove(new EntryVector());
290 for (EntryMap::iterator it
= model
.begin(); it
!= model
.end(); ++it
) {
291 entries_to_remove
->push_back(it
->second
);
294 EXPECT_CALL(*mock_db
, Save(_
, _
)).WillOnce(VerifyRemoveEntries(model
));
295 EXPECT_CALL(caller
, SaveCallback(true));
298 entries_to_remove
.Pass(),
299 base::Bind(&MockDatabaseCaller::SaveCallback
, base::Unretained(&caller
)));
301 base::RunLoop().RunUntilIdle();
304 TEST_F(DomDistillerDatabaseTest
, TestDBRemoveFailure
) {
305 base::FilePath
path(FILE_PATH_LITERAL("/fake/path"));
307 MockDB
* mock_db
= new MockDB();
308 MockDatabaseCaller caller
;
309 scoped_ptr
<EntryVector
> entries(new EntryVector());
310 scoped_ptr
<EntryVector
> entries_to_remove(new EntryVector());
312 EXPECT_CALL(*mock_db
, Init(_
));
313 EXPECT_CALL(caller
, InitCallback(_
));
314 db_
->InitWithDatabase(
315 scoped_ptr
<DomDistillerDatabase::Database
>(mock_db
),
316 base::FilePath(path
),
317 base::Bind(&MockDatabaseCaller::InitCallback
, base::Unretained(&caller
)));
319 EXPECT_CALL(*mock_db
, Save(_
, _
)).WillOnce(Return(false));
320 EXPECT_CALL(caller
, SaveCallback(false));
323 entries_to_remove
.Pass(),
324 base::Bind(&MockDatabaseCaller::SaveCallback
, base::Unretained(&caller
)));
326 base::RunLoop().RunUntilIdle();
330 // This tests that normal usage of the real database does not cause any
331 // threading violations.
332 TEST(DomDistillerDatabaseThreadingTest
, TestDBDestruction
) {
333 base::MessageLoop main_loop
;
335 ScopedTempDir temp_dir
;
336 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
338 base::Thread
db_thread("dbthread");
339 ASSERT_TRUE(db_thread
.Start());
341 scoped_ptr
<DomDistillerDatabase
> db(
342 new DomDistillerDatabase(db_thread
.message_loop_proxy()));
344 MockDatabaseCaller caller
;
345 EXPECT_CALL(caller
, InitCallback(_
));
348 base::Bind(&MockDatabaseCaller::InitCallback
, base::Unretained(&caller
)));
352 base::RunLoop run_loop
;
353 db_thread
.message_loop_proxy()->PostTaskAndReply(
354 FROM_HERE
, base::Bind(base::DoNothing
), run_loop
.QuitClosure());
358 // Test that the LevelDB properly saves entries and that load returns the saved
359 // entries. If |close_after_save| is true, the database will be closed after
360 // saving and then re-opened to ensure that the data is properly persisted.
361 void TestLevelDBSaveAndLoad(bool close_after_save
) {
362 ScopedTempDir temp_dir
;
363 ASSERT_TRUE(temp_dir
.CreateUniqueTempDir());
365 EntryMap model
= GetSmallModel();
366 EntryVector save_entries
;
367 EntryVector load_entries
;
368 EntryVector remove_entries
;
370 for (EntryMap::iterator it
= model
.begin(); it
!= model
.end(); ++it
) {
371 save_entries
.push_back(it
->second
);
374 scoped_ptr
<DomDistillerDatabase::LevelDB
> db(
375 new DomDistillerDatabase::LevelDB());
376 EXPECT_TRUE(db
->Init(temp_dir
.path()));
377 EXPECT_TRUE(db
->Save(save_entries
, remove_entries
));
379 if (close_after_save
) {
380 db
.reset(new DomDistillerDatabase::LevelDB());
381 EXPECT_TRUE(db
->Init(temp_dir
.path()));
384 EXPECT_TRUE(db
->Load(&load_entries
));
386 ExpectEntryPointersEquals(model
, load_entries
);
389 TEST(DomDistillerDatabaseLevelDBTest
, TestDBSaveAndLoad
) {
390 TestLevelDBSaveAndLoad(false);
393 TEST(DomDistillerDatabaseLevelDBTest
, TestDBCloseAndReopen
) {
394 TestLevelDBSaveAndLoad(true);
397 } // namespace dom_distiller