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 "components/leveldb_proto/proto_database_impl.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/leveldb_proto/leveldb_database.h" 15 #include "components/leveldb_proto/testing/proto/test.pb.h" 16 #include "testing/gmock/include/gmock/gmock.h" 17 #include "testing/gtest/include/gtest/gtest.h" 18 19 using base::MessageLoop; 20 using base::ScopedTempDir; 21 using testing::Invoke; 22 using testing::Return; 23 using testing::_; 24 25 namespace leveldb_proto { 26 27 namespace { 28 29 typedef std::map<std::string, TestProto> EntryMap; 30 31 class MockDB : public LevelDB { 32 public: 33 MOCK_METHOD1(Init, bool(const base::FilePath&)); 34 MOCK_METHOD2(Save, bool(const KeyValueVector&, const KeyVector&)); 35 MOCK_METHOD1(Load, bool(std::vector<std::string>*)); 36 37 MockDB() { 38 ON_CALL(*this, Init(_)).WillByDefault(Return(true)); 39 ON_CALL(*this, Save(_, _)).WillByDefault(Return(true)); 40 ON_CALL(*this, Load(_)).WillByDefault(Return(true)); 41 } 42 }; 43 44 class MockDatabaseCaller { 45 public: 46 MOCK_METHOD1(InitCallback, void(bool)); 47 MOCK_METHOD1(SaveCallback, void(bool)); 48 void LoadCallback(bool success, scoped_ptr<std::vector<TestProto> > entries) { 49 LoadCallback1(success, entries.get()); 50 } 51 MOCK_METHOD2(LoadCallback1, void(bool, std::vector<TestProto>*)); 52 }; 53 54 } // namespace 55 56 EntryMap GetSmallModel() { 57 EntryMap model; 58 59 model["0"].set_id("0"); 60 model["0"].set_data("http://foo.com/1"); 61 62 model["1"].set_id("1"); 63 model["1"].set_data("http://bar.com/all"); 64 65 model["2"].set_id("2"); 66 model["2"].set_data("http://baz.com/1"); 67 68 return model; 69 } 70 71 void ExpectEntryPointersEquals(EntryMap expected, 72 const std::vector<TestProto>& actual) { 73 EXPECT_EQ(expected.size(), actual.size()); 74 for (size_t i = 0; i < actual.size(); i++) { 75 EntryMap::iterator expected_it = expected.find(actual[i].id()); 76 EXPECT_TRUE(expected_it != expected.end()); 77 std::string serialized_expected = expected_it->second.SerializeAsString(); 78 std::string serialized_actual = actual[i].SerializeAsString(); 79 EXPECT_EQ(serialized_expected, serialized_actual); 80 expected.erase(expected_it); 81 } 82 } 83 84 class ProtoDatabaseImplTest : public testing::Test { 85 public: 86 virtual void SetUp() { 87 main_loop_.reset(new MessageLoop()); 88 db_.reset( 89 new ProtoDatabaseImpl<TestProto>(main_loop_->message_loop_proxy())); 90 } 91 92 virtual void TearDown() { 93 db_.reset(); 94 base::RunLoop().RunUntilIdle(); 95 main_loop_.reset(); 96 } 97 98 scoped_ptr<ProtoDatabaseImpl<TestProto> > db_; 99 scoped_ptr<MessageLoop> main_loop_; 100 }; 101 102 // Test that ProtoDatabaseImpl calls Init on the underlying database and that 103 // the caller's InitCallback is called with the correct value. 104 TEST_F(ProtoDatabaseImplTest, TestDBInitSuccess) { 105 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); 106 107 MockDB* mock_db = new MockDB(); 108 EXPECT_CALL(*mock_db, Init(path)).WillOnce(Return(true)); 109 110 MockDatabaseCaller caller; 111 EXPECT_CALL(caller, InitCallback(true)); 112 113 db_->InitWithDatabase( 114 scoped_ptr<LevelDB>(mock_db), base::FilePath(path), 115 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); 116 117 base::RunLoop().RunUntilIdle(); 118 } 119 120 TEST_F(ProtoDatabaseImplTest, TestDBInitFailure) { 121 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); 122 123 MockDB* mock_db = new MockDB(); 124 EXPECT_CALL(*mock_db, Init(path)).WillOnce(Return(false)); 125 126 MockDatabaseCaller caller; 127 EXPECT_CALL(caller, InitCallback(false)); 128 129 db_->InitWithDatabase( 130 scoped_ptr<LevelDB>(mock_db), base::FilePath(path), 131 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); 132 133 base::RunLoop().RunUntilIdle(); 134 } 135 136 ACTION_P(AppendLoadEntries, model) { 137 std::vector<std::string>* output = arg0; 138 for (EntryMap::const_iterator it = model.begin(); it != model.end(); ++it) { 139 output->push_back(it->second.SerializeAsString()); 140 } 141 return true; 142 } 143 144 ACTION_P(VerifyLoadEntries, expected) { 145 std::vector<TestProto>* actual = arg1; 146 ExpectEntryPointersEquals(expected, *actual); 147 } 148 149 // Test that ProtoDatabaseImpl calls Load on the underlying database and that 150 // the caller's LoadCallback is called with the correct success value. Also 151 // confirms that on success, the expected entries are passed to the caller's 152 // LoadCallback. 153 TEST_F(ProtoDatabaseImplTest, TestDBLoadSuccess) { 154 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); 155 156 MockDB* mock_db = new MockDB(); 157 MockDatabaseCaller caller; 158 EntryMap model = GetSmallModel(); 159 160 EXPECT_CALL(*mock_db, Init(_)); 161 EXPECT_CALL(caller, InitCallback(_)); 162 db_->InitWithDatabase( 163 scoped_ptr<LevelDB>(mock_db), base::FilePath(path), 164 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); 165 166 EXPECT_CALL(*mock_db, Load(_)).WillOnce(AppendLoadEntries(model)); 167 EXPECT_CALL(caller, LoadCallback1(true, _)) 168 .WillOnce(VerifyLoadEntries(testing::ByRef(model))); 169 db_->LoadEntries( 170 base::Bind(&MockDatabaseCaller::LoadCallback, base::Unretained(&caller))); 171 172 base::RunLoop().RunUntilIdle(); 173 } 174 175 TEST_F(ProtoDatabaseImplTest, TestDBLoadFailure) { 176 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); 177 178 MockDB* mock_db = new MockDB(); 179 MockDatabaseCaller caller; 180 181 EXPECT_CALL(*mock_db, Init(_)); 182 EXPECT_CALL(caller, InitCallback(_)); 183 db_->InitWithDatabase( 184 scoped_ptr<LevelDB>(mock_db), base::FilePath(path), 185 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); 186 187 EXPECT_CALL(*mock_db, Load(_)).WillOnce(Return(false)); 188 EXPECT_CALL(caller, LoadCallback1(false, _)); 189 db_->LoadEntries( 190 base::Bind(&MockDatabaseCaller::LoadCallback, base::Unretained(&caller))); 191 192 base::RunLoop().RunUntilIdle(); 193 } 194 195 ACTION_P(VerifyUpdateEntries, expected) { 196 const KeyValueVector actual = arg0; 197 // Create a vector of TestProto from |actual| to reuse the comparison 198 // function. 199 std::vector<TestProto> extracted_entries; 200 for (KeyValueVector::const_iterator it = actual.begin(); it != actual.end(); 201 ++it) { 202 TestProto entry; 203 entry.ParseFromString(it->second); 204 extracted_entries.push_back(entry); 205 } 206 ExpectEntryPointersEquals(expected, extracted_entries); 207 return true; 208 } 209 210 // Test that ProtoDatabaseImpl calls Save on the underlying database with the 211 // correct entries to save and that the caller's SaveCallback is called with the 212 // correct success value. 213 TEST_F(ProtoDatabaseImplTest, TestDBSaveSuccess) { 214 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); 215 216 MockDB* mock_db = new MockDB(); 217 MockDatabaseCaller caller; 218 EntryMap model = GetSmallModel(); 219 220 EXPECT_CALL(*mock_db, Init(_)); 221 EXPECT_CALL(caller, InitCallback(_)); 222 db_->InitWithDatabase( 223 scoped_ptr<LevelDB>(mock_db), base::FilePath(path), 224 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); 225 226 scoped_ptr<ProtoDatabase<TestProto>::KeyEntryVector> entries( 227 new ProtoDatabase<TestProto>::KeyEntryVector()); 228 for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) { 229 entries->push_back(std::make_pair(it->second.id(), it->second)); 230 } 231 scoped_ptr<KeyVector> keys_to_remove(new KeyVector()); 232 233 EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(VerifyUpdateEntries(model)); 234 EXPECT_CALL(caller, SaveCallback(true)); 235 db_->UpdateEntries( 236 entries.Pass(), keys_to_remove.Pass(), 237 base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); 238 239 base::RunLoop().RunUntilIdle(); 240 } 241 242 TEST_F(ProtoDatabaseImplTest, TestDBSaveFailure) { 243 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); 244 245 MockDB* mock_db = new MockDB(); 246 MockDatabaseCaller caller; 247 scoped_ptr<ProtoDatabase<TestProto>::KeyEntryVector> entries( 248 new ProtoDatabase<TestProto>::KeyEntryVector()); 249 scoped_ptr<KeyVector> keys_to_remove(new KeyVector()); 250 251 EXPECT_CALL(*mock_db, Init(_)); 252 EXPECT_CALL(caller, InitCallback(_)); 253 db_->InitWithDatabase( 254 scoped_ptr<LevelDB>(mock_db), base::FilePath(path), 255 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); 256 257 EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(Return(false)); 258 EXPECT_CALL(caller, SaveCallback(false)); 259 db_->UpdateEntries( 260 entries.Pass(), keys_to_remove.Pass(), 261 base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); 262 263 base::RunLoop().RunUntilIdle(); 264 } 265 266 // Test that ProtoDatabaseImpl calls Save on the underlying database with the 267 // correct entries to delete and that the caller's SaveCallback is called with 268 // the correct success value. 269 TEST_F(ProtoDatabaseImplTest, TestDBRemoveSuccess) { 270 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); 271 272 MockDB* mock_db = new MockDB(); 273 MockDatabaseCaller caller; 274 EntryMap model = GetSmallModel(); 275 276 EXPECT_CALL(*mock_db, Init(_)); 277 EXPECT_CALL(caller, InitCallback(_)); 278 db_->InitWithDatabase( 279 scoped_ptr<LevelDB>(mock_db), base::FilePath(path), 280 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); 281 282 scoped_ptr<ProtoDatabase<TestProto>::KeyEntryVector> entries( 283 new ProtoDatabase<TestProto>::KeyEntryVector()); 284 scoped_ptr<KeyVector> keys_to_remove(new KeyVector()); 285 for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) { 286 keys_to_remove->push_back(it->second.id()); 287 } 288 289 KeyVector keys_copy(*keys_to_remove.get()); 290 EXPECT_CALL(*mock_db, Save(_, keys_copy)).WillOnce(Return(true)); 291 EXPECT_CALL(caller, SaveCallback(true)); 292 db_->UpdateEntries( 293 entries.Pass(), keys_to_remove.Pass(), 294 base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); 295 296 base::RunLoop().RunUntilIdle(); 297 } 298 299 TEST_F(ProtoDatabaseImplTest, TestDBRemoveFailure) { 300 base::FilePath path(FILE_PATH_LITERAL("/fake/path")); 301 302 MockDB* mock_db = new MockDB(); 303 MockDatabaseCaller caller; 304 scoped_ptr<ProtoDatabase<TestProto>::KeyEntryVector> entries( 305 new ProtoDatabase<TestProto>::KeyEntryVector()); 306 scoped_ptr<KeyVector> keys_to_remove(new KeyVector()); 307 308 EXPECT_CALL(*mock_db, Init(_)); 309 EXPECT_CALL(caller, InitCallback(_)); 310 db_->InitWithDatabase( 311 scoped_ptr<LevelDB>(mock_db), base::FilePath(path), 312 base::Bind(&MockDatabaseCaller::InitCallback, base::Unretained(&caller))); 313 314 EXPECT_CALL(*mock_db, Save(_, _)).WillOnce(Return(false)); 315 EXPECT_CALL(caller, SaveCallback(false)); 316 db_->UpdateEntries( 317 entries.Pass(), keys_to_remove.Pass(), 318 base::Bind(&MockDatabaseCaller::SaveCallback, base::Unretained(&caller))); 319 320 base::RunLoop().RunUntilIdle(); 321 } 322 323 // This tests that normal usage of the real database does not cause any 324 // threading violations. 325 TEST(ProtoDatabaseImplThreadingTest, TestDBDestruction) { 326 base::MessageLoop main_loop; 327 328 ScopedTempDir temp_dir; 329 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 330 331 base::Thread db_thread("dbthread"); 332 ASSERT_TRUE(db_thread.Start()); 333 334 scoped_ptr<ProtoDatabaseImpl<TestProto> > db( 335 new ProtoDatabaseImpl<TestProto>(db_thread.message_loop_proxy())); 336 337 MockDatabaseCaller caller; 338 EXPECT_CALL(caller, InitCallback(_)); 339 db->Init(temp_dir.path(), base::Bind(&MockDatabaseCaller::InitCallback, 340 base::Unretained(&caller))); 341 342 db.reset(); 343 344 base::RunLoop run_loop; 345 db_thread.message_loop_proxy()->PostTaskAndReply( 346 FROM_HERE, base::Bind(base::DoNothing), run_loop.QuitClosure()); 347 run_loop.Run(); 348 } 349 350 // Test that the LevelDB properly saves entries and that load returns the saved 351 // entries. If |close_after_save| is true, the database will be closed after 352 // saving and then re-opened to ensure that the data is properly persisted. 353 void TestLevelDBSaveAndLoad(bool close_after_save) { 354 ScopedTempDir temp_dir; 355 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); 356 357 EntryMap model = GetSmallModel(); 358 359 KeyValueVector save_entries; 360 std::vector<std::string> load_entries; 361 KeyVector remove_keys; 362 363 for (EntryMap::iterator it = model.begin(); it != model.end(); ++it) { 364 save_entries.push_back( 365 std::make_pair(it->second.id(), it->second.SerializeAsString())); 366 } 367 368 scoped_ptr<LevelDB> db(new LevelDB()); 369 EXPECT_TRUE(db->Init(temp_dir.path())); 370 EXPECT_TRUE(db->Save(save_entries, remove_keys)); 371 372 if (close_after_save) { 373 db.reset(new LevelDB()); 374 EXPECT_TRUE(db->Init(temp_dir.path())); 375 } 376 377 EXPECT_TRUE(db->Load(&load_entries)); 378 // Convert the strings back to TestProto. 379 std::vector<TestProto> loaded_protos; 380 for (std::vector<std::string>::iterator it = load_entries.begin(); 381 it != load_entries.end(); ++it) { 382 TestProto entry; 383 entry.ParseFromString(*it); 384 loaded_protos.push_back(entry); 385 } 386 ExpectEntryPointersEquals(model, loaded_protos); 387 } 388 389 TEST(ProtoDatabaseImplLevelDBTest, TestDBSaveAndLoad) { 390 TestLevelDBSaveAndLoad(false); 391 } 392 393 TEST(ProtoDatabaseImplLevelDBTest, TestDBCloseAndReopen) { 394 TestLevelDBSaveAndLoad(true); 395 } 396 397 } // namespace leveldb_proto 398