OLD | NEW |
| (Empty) |
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. | |
4 | |
5 #include "components/dom_distiller/core/dom_distiller_database.h" | |
6 | |
7 #include <map> | |
8 | |
9 #include "base/bind.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" | |
17 | |
18 using base::MessageLoop; | |
19 using base::ScopedTempDir; | |
20 using testing::Invoke; | |
21 using testing::Return; | |
22 using testing::_; | |
23 | |
24 namespace dom_distiller { | |
25 | |
26 namespace { | |
27 | |
28 typedef std::map<std::string, ArticleEntry> EntryMap; | |
29 | |
30 class MockDB : public DomDistillerDatabase::Database { | |
31 public: | |
32 MOCK_METHOD1(Init, bool(const base::FilePath&)); | |
33 MOCK_METHOD2(Save, bool(const EntryVector&, const EntryVector&)); | |
34 MOCK_METHOD1(Load, bool(EntryVector*)); | |
35 | |
36 MockDB() { | |
37 ON_CALL(*this, Init(_)).WillByDefault(Return(true)); | |
38 ON_CALL(*this, Save(_, _)).WillByDefault(Return(true)); | |
39 ON_CALL(*this, Load(_)).WillByDefault(Return(true)); | |
40 } | |
41 | |
42 bool LoadEntries(EntryVector* entries); | |
43 }; | |
44 | |
45 class MockDatabaseCaller { | |
46 public: | |
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()); | |
51 } | |
52 MOCK_METHOD2(LoadCallback1, void(bool, EntryVector*)); | |
53 }; | |
54 | |
55 } // namespace | |
56 | |
57 EntryMap GetSmallModel() { | |
58 EntryMap model; | |
59 | |
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"); | |
64 | |
65 model["key1"].set_entry_id("key1"); | |
66 model["key1"].add_pages()->set_url("http://bar.com/all"); | |
67 | |
68 model["key2"].set_entry_id("key2"); | |
69 model["key2"].add_pages()->set_url("http://baz.com/1"); | |
70 | |
71 return model; | |
72 } | |
73 | |
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); | |
84 } | |
85 } | |
86 | |
87 class DomDistillerDatabaseTest : public testing::Test { | |
88 public: | |
89 virtual void SetUp() { | |
90 main_loop_.reset(new MessageLoop()); | |
91 db_.reset(new DomDistillerDatabase(main_loop_->message_loop_proxy())); | |
92 } | |
93 | |
94 virtual void TearDown() { | |
95 db_.reset(); | |
96 base::RunLoop().RunUntilIdle(); | |
97 main_loop_.reset(); | |
98 } | |
99 | |
100 scoped_ptr<DomDistillerDatabase> db_; | |
101 scoped_ptr<MessageLoop> main_loop_; | |
102 }; | |
103 | |
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")); | |
108 | |
109 MockDB* mock_db = new MockDB(); | |
110 EXPECT_CALL(*mock_db, Init(path)).WillOnce(Return(true)); | |
111 | |
112 MockDatabaseCaller caller; | |
113 EXPECT_CALL(caller, InitCallback(true)); | |
114 | |
115 db_->InitWithDatabase( | |
116 scoped_ptr<DomDistillerDatabase::Database>(mock_db), | |
117 base::FilePath(path), | |
118 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); | |
119 | |
120 base::RunLoop().RunUntilIdle(); | |
121 } | |
122 | |
123 TEST_F(DomDistillerDatabaseTest, TestDBInitFailure) { | |
124 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); | |
125 | |
126 MockDB* mock_db = new MockDB(); | |
127 EXPECT_CALL(*mock_db, Init(path)).WillOnce(Return(false)); | |
128 | |
129 MockDatabaseCaller caller; | |
130 EXPECT_CALL(caller, InitCallback(false)); | |
131 | |
132 db_->InitWithDatabase( | |
133 scoped_ptr<DomDistillerDatabase::Database>(mock_db), | |
134 base::FilePath(path), | |
135 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); | |
136 | |
137 base::RunLoop().RunUntilIdle(); | |
138 } | |
139 | |
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); | |
144 } | |
145 return true; | |
146 } | |
147 | |
148 ACTION_P(VerifyLoadEntries, expected) { | |
149 EntryVector* actual = arg1; | |
150 ExpectEntryPointersEquals(expected, *actual); | |
151 } | |
152 | |
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 | |
156 // LoadCallback. | |
157 TEST_F(DomDistillerDatabaseTest, TestDBLoadSuccess) { | |
158 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); | |
159 | |
160 MockDB* mock_db = new MockDB(); | |
161 MockDatabaseCaller caller; | |
162 EntryMap model = GetSmallModel(); | |
163 | |
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))); | |
170 | |
171 EXPECT_CALL(*mock_db, Load(_)).WillOnce(AppendLoadEntries(model)); | |
172 EXPECT_CALL(caller, LoadCallback1(true, _)) | |
173 .WillOnce(VerifyLoadEntries(testing::ByRef(model))); | |
174 db_->LoadEntries( | |
175 base::Bind(&MockDatabaseCaller::LoadCallback, base::Unretained(&caller))); | |
176 | |
177 base::RunLoop().RunUntilIdle(); | |
178 } | |
179 | |
180 TEST_F(DomDistillerDatabaseTest, TestDBLoadFailure) { | |
181 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); | |
182 | |
183 MockDB* mock_db = new MockDB(); | |
184 MockDatabaseCaller caller; | |
185 | |
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))); | |
192 | |
193 EXPECT_CALL(*mock_db, Load(_)).WillOnce(Return(false)); | |
194 EXPECT_CALL(caller, LoadCallback1(false, _)); | |
195 db_->LoadEntries( | |
196 base::Bind(&MockDatabaseCaller::LoadCallback, base::Unretained(&caller))); | |
197 | |
198 base::RunLoop().RunUntilIdle(); | |
199 } | |
200 | |
201 ACTION_P(VerifyUpdateEntries, expected) { | |
202 const EntryVector& actual = arg0; | |
203 ExpectEntryPointersEquals(expected, actual); | |
204 return true; | |
205 } | |
206 | |
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")); | |
212 | |
213 MockDB* mock_db = new MockDB(); | |
214 MockDatabaseCaller caller; | |
215 EntryMap model = GetSmallModel(); | |
216 | |
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))); | |
223 | |
224 scoped_ptr<EntryVector> entries(new EntryVector()); | |
225 for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) { | |
226 entries->push_back(it->second); | |
227 } | |
228 scoped_ptr<EntryVector> entries_to_remove(new EntryVector()); | |
229 | |
230 EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(VerifyUpdateEntries(model)); | |
231 EXPECT_CALL(caller, SaveCallback(true)); | |
232 db_->UpdateEntries( | |
233 entries.Pass(), | |
234 entries_to_remove.Pass(), | |
235 base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); | |
236 | |
237 base::RunLoop().RunUntilIdle(); | |
238 } | |
239 | |
240 TEST_F(DomDistillerDatabaseTest, TestDBSaveFailure) { | |
241 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); | |
242 | |
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()); | |
247 | |
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))); | |
254 | |
255 EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(Return(false)); | |
256 EXPECT_CALL(caller, SaveCallback(false)); | |
257 db_->UpdateEntries( | |
258 entries.Pass(), | |
259 entries_to_remove.Pass(), | |
260 base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); | |
261 | |
262 base::RunLoop().RunUntilIdle(); | |
263 } | |
264 | |
265 ACTION_P(VerifyRemoveEntries, expected) { | |
266 const EntryVector& actual = arg1; | |
267 ExpectEntryPointersEquals(expected, actual); | |
268 return true; | |
269 } | |
270 | |
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")); | |
276 | |
277 MockDB* mock_db = new MockDB(); | |
278 MockDatabaseCaller caller; | |
279 EntryMap model = GetSmallModel(); | |
280 | |
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))); | |
287 | |
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); | |
292 } | |
293 | |
294 EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(VerifyRemoveEntries(model)); | |
295 EXPECT_CALL(caller, SaveCallback(true)); | |
296 db_->UpdateEntries( | |
297 entries.Pass(), | |
298 entries_to_remove.Pass(), | |
299 base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); | |
300 | |
301 base::RunLoop().RunUntilIdle(); | |
302 } | |
303 | |
304 TEST_F(DomDistillerDatabaseTest, TestDBRemoveFailure) { | |
305 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); | |
306 | |
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()); | |
311 | |
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))); | |
318 | |
319 EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(Return(false)); | |
320 EXPECT_CALL(caller, SaveCallback(false)); | |
321 db_->UpdateEntries( | |
322 entries.Pass(), | |
323 entries_to_remove.Pass(), | |
324 base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); | |
325 | |
326 base::RunLoop().RunUntilIdle(); | |
327 } | |
328 | |
329 | |
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; | |
334 | |
335 ScopedTempDir temp_dir; | |
336 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | |
337 | |
338 base::Thread db_thread("dbthread"); | |
339 ASSERT_TRUE(db_thread.Start()); | |
340 | |
341 scoped_ptr<DomDistillerDatabase> db( | |
342 new DomDistillerDatabase(db_thread.message_loop_proxy())); | |
343 | |
344 MockDatabaseCaller caller; | |
345 EXPECT_CALL(caller, InitCallback(_)); | |
346 db->Init( | |
347 temp_dir.path(), | |
348 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); | |
349 | |
350 db.reset(); | |
351 | |
352 base::RunLoop run_loop; | |
353 db_thread.message_loop_proxy()->PostTaskAndReply( | |
354 FROM_HERE, base::Bind(base::DoNothing), run_loop.QuitClosure()); | |
355 run_loop.Run(); | |
356 } | |
357 | |
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()); | |
364 | |
365 EntryMap model = GetSmallModel(); | |
366 EntryVector save_entries; | |
367 EntryVector load_entries; | |
368 EntryVector remove_entries; | |
369 | |
370 for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) { | |
371 save_entries.push_back(it->second); | |
372 } | |
373 | |
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)); | |
378 | |
379 if (close_after_save) { | |
380 db.reset(new DomDistillerDatabase::LevelDB()); | |
381 EXPECT_TRUE(db->Init(temp_dir.path())); | |
382 } | |
383 | |
384 EXPECT_TRUE(db->Load(&load_entries)); | |
385 | |
386 ExpectEntryPointersEquals(model, load_entries); | |
387 } | |
388 | |
389 TEST(DomDistillerDatabaseLevelDBTest, TestDBSaveAndLoad) { | |
390 TestLevelDBSaveAndLoad(false); | |
391 } | |
392 | |
393 TEST(DomDistillerDatabaseLevelDBTest, TestDBCloseAndReopen) { | |
394 TestLevelDBSaveAndLoad(true); | |
395 } | |
396 | |
397 } // namespace dom_distiller | |
OLD | NEW |