Home | History | Annotate | Download | only in leveldb_proto
      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