blob: 0094c31eb3c6b93403fc382f0c6d2d00dd6cb779 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/chromeos/drive/resource_metadata_storage.h"
#include <algorithm>
#include "base/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "chrome/browser/chromeos/drive/drive.pb.h"
#include "chrome/browser/chromeos/drive/test_util.h"
#include "chrome/browser/drive/drive_api_util.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
namespace drive {
namespace internal {
class ResourceMetadataStorageTest : public testing::Test {
protected:
virtual void SetUp() OVERRIDE {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
storage_.reset(new ResourceMetadataStorage(
temp_dir_.path(), base::MessageLoopProxy::current().get()));
ASSERT_TRUE(storage_->Initialize());
}
// Overwrites |storage_|'s version.
void SetDBVersion(int version) {
ResourceMetadataHeader header;
ASSERT_TRUE(storage_->GetHeader(&header));
header.set_version(version);
EXPECT_TRUE(storage_->PutHeader(header));
}
bool CheckValidity() {
return storage_->CheckValidity();
}
leveldb::DB* resource_map() { return storage_->resource_map_.get(); }
// Puts a child entry.
void PutChild(const std::string& parent_id,
const std::string& child_base_name,
const std::string& child_id) {
storage_->resource_map_->Put(
leveldb::WriteOptions(),
ResourceMetadataStorage::GetChildEntryKey(parent_id, child_base_name),
child_id);
}
// Removes a child entry.
void RemoveChild(const std::string& parent_id,
const std::string& child_base_name) {
storage_->resource_map_->Delete(
leveldb::WriteOptions(),
ResourceMetadataStorage::GetChildEntryKey(parent_id, child_base_name));
}
content::TestBrowserThreadBundle thread_bundle_;
base::ScopedTempDir temp_dir_;
scoped_ptr<ResourceMetadataStorage,
test_util::DestroyHelperForTests> storage_;
};
TEST_F(ResourceMetadataStorageTest, LargestChangestamp) {
const int64 kLargestChangestamp = 1234567890;
EXPECT_TRUE(storage_->SetLargestChangestamp(kLargestChangestamp));
EXPECT_EQ(kLargestChangestamp, storage_->GetLargestChangestamp());
}
TEST_F(ResourceMetadataStorageTest, PutEntry) {
const std::string key1 = "abcdefg";
const std::string key2 = "abcd";
const std::string key3 = "efgh";
const std::string name2 = "ABCD";
const std::string name3 = "EFGH";
// key1 not found.
ResourceEntry result;
EXPECT_FALSE(storage_->GetEntry(key1, &result));
// Put entry1.
ResourceEntry entry1;
entry1.set_local_id(key1);
EXPECT_TRUE(storage_->PutEntry(entry1));
// key1 found.
EXPECT_TRUE(storage_->GetEntry(key1, &result));
// key2 not found.
EXPECT_FALSE(storage_->GetEntry(key2, &result));
// Put entry2 as a child of entry1.
ResourceEntry entry2;
entry2.set_local_id(key2);
entry2.set_parent_local_id(key1);
entry2.set_base_name(name2);
EXPECT_TRUE(storage_->PutEntry(entry2));
// key2 found.
EXPECT_TRUE(storage_->GetEntry(key2, &result));
EXPECT_EQ(key2, storage_->GetChild(key1, name2));
// Put entry3 as a child of entry2.
ResourceEntry entry3;
entry3.set_local_id(key3);
entry3.set_parent_local_id(key2);
entry3.set_base_name(name3);
EXPECT_TRUE(storage_->PutEntry(entry3));
// key3 found.
EXPECT_TRUE(storage_->GetEntry(key3, &result));
EXPECT_EQ(key3, storage_->GetChild(key2, name3));
// Change entry3's parent to entry1.
entry3.set_parent_local_id(key1);
EXPECT_TRUE(storage_->PutEntry(entry3));
// entry3 is a child of entry1 now.
EXPECT_TRUE(storage_->GetChild(key2, name3).empty());
EXPECT_EQ(key3, storage_->GetChild(key1, name3));
// Remove entries.
EXPECT_TRUE(storage_->RemoveEntry(key3));
EXPECT_FALSE(storage_->GetEntry(key3, &result));
EXPECT_TRUE(storage_->RemoveEntry(key2));
EXPECT_FALSE(storage_->GetEntry(key2, &result));
EXPECT_TRUE(storage_->RemoveEntry(key1));
EXPECT_FALSE(storage_->GetEntry(key1, &result));
}
TEST_F(ResourceMetadataStorageTest, Iterator) {
// Prepare data.
std::vector<std::string> keys;
keys.push_back("entry1");
keys.push_back("entry2");
keys.push_back("entry3");
keys.push_back("entry4");
for (size_t i = 0; i < keys.size(); ++i) {
ResourceEntry entry;
entry.set_local_id(keys[i]);
EXPECT_TRUE(storage_->PutEntry(entry));
}
// Insert some cache entries.
std::map<std::string, FileCacheEntry> cache_entries;
cache_entries[keys[0]].set_md5("aaaaaa");
cache_entries[keys[1]].set_md5("bbbbbb");
for (std::map<std::string, FileCacheEntry>::iterator it =
cache_entries.begin(); it != cache_entries.end(); ++it)
EXPECT_TRUE(storage_->PutCacheEntry(it->first, it->second));
// Iterate and check the result.
std::map<std::string, ResourceEntry> found_entries;
std::map<std::string, FileCacheEntry> found_cache_entries;
scoped_ptr<ResourceMetadataStorage::Iterator> it = storage_->GetIterator();
ASSERT_TRUE(it);
for (; !it->IsAtEnd(); it->Advance()) {
const ResourceEntry& entry = it->GetValue();
found_entries[it->GetID()] = entry;
FileCacheEntry cache_entry;
if (it->GetCacheEntry(&cache_entry))
found_cache_entries[it->GetID()] = cache_entry;
}
EXPECT_FALSE(it->HasError());
EXPECT_EQ(keys.size(), found_entries.size());
for (size_t i = 0; i < keys.size(); ++i)
EXPECT_EQ(1U, found_entries.count(keys[i]));
EXPECT_EQ(cache_entries.size(), found_cache_entries.size());
for (std::map<std::string, FileCacheEntry>::iterator it =
cache_entries.begin(); it != cache_entries.end(); ++it) {
ASSERT_EQ(1U, found_cache_entries.count(it->first));
EXPECT_EQ(it->second.md5(), found_cache_entries[it->first].md5());
}
}
TEST_F(ResourceMetadataStorageTest, PutCacheEntry) {
FileCacheEntry entry;
const std::string key1 = "abcdefg";
const std::string key2 = "abcd";
const std::string md5_1 = "foo";
const std::string md5_2 = "bar";
// Put cache entries.
entry.set_md5(md5_1);
EXPECT_TRUE(storage_->PutCacheEntry(key1, entry));
entry.set_md5(md5_2);
EXPECT_TRUE(storage_->PutCacheEntry(key2, entry));
// Get cache entires.
EXPECT_TRUE(storage_->GetCacheEntry(key1, &entry));
EXPECT_EQ(md5_1, entry.md5());
EXPECT_TRUE(storage_->GetCacheEntry(key2, &entry));
EXPECT_EQ(md5_2, entry.md5());
// Remove cache entries.
EXPECT_TRUE(storage_->RemoveCacheEntry(key1));
EXPECT_FALSE(storage_->GetCacheEntry(key1, &entry));
EXPECT_TRUE(storage_->RemoveCacheEntry(key2));
EXPECT_FALSE(storage_->GetCacheEntry(key2, &entry));
}
TEST_F(ResourceMetadataStorageTest, CacheEntryIterator) {
// Prepare data.
std::map<std::string, FileCacheEntry> entries;
FileCacheEntry cache_entry;
cache_entry.set_md5("aA");
entries["entry1"] = cache_entry;
cache_entry.set_md5("bB");
entries["entry2"] = cache_entry;
cache_entry.set_md5("cC");
entries["entry3"] = cache_entry;
cache_entry.set_md5("dD");
entries["entry4"] = cache_entry;
for (std::map<std::string, FileCacheEntry>::iterator it = entries.begin();
it != entries.end(); ++it)
EXPECT_TRUE(storage_->PutCacheEntry(it->first, it->second));
// Insert some dummy entries.
ResourceEntry entry;
entry.set_local_id("entry1");
EXPECT_TRUE(storage_->PutEntry(entry));
entry.set_local_id("entry2");
EXPECT_TRUE(storage_->PutEntry(entry));
// Iterate and check the result.
scoped_ptr<ResourceMetadataStorage::CacheEntryIterator> it =
storage_->GetCacheEntryIterator();
ASSERT_TRUE(it);
size_t num_entries = 0;
for (; !it->IsAtEnd(); it->Advance()) {
EXPECT_EQ(1U, entries.count(it->GetID()));
EXPECT_EQ(entries[it->GetID()].md5(), it->GetValue().md5());
++num_entries;
}
EXPECT_FALSE(it->HasError());
EXPECT_EQ(entries.size(), num_entries);
}
TEST_F(ResourceMetadataStorageTest, GetIdByResourceId) {
const std::string local_id = "local_id";
const std::string resource_id = "resource_id";
// Resource ID to local ID mapping is not stored yet.
std::string id;
EXPECT_FALSE(storage_->GetIdByResourceId(resource_id, &id));
// Put an entry with the resource ID.
ResourceEntry entry;
entry.set_local_id(local_id);
entry.set_resource_id(resource_id);
EXPECT_TRUE(storage_->PutEntry(entry));
// Can get local ID by resource ID.
EXPECT_TRUE(storage_->GetIdByResourceId(resource_id, &id));
EXPECT_EQ(local_id, id);
// Resource ID to local ID mapping is removed.
EXPECT_TRUE(storage_->RemoveEntry(local_id));
EXPECT_FALSE(storage_->GetIdByResourceId(resource_id, &id));
}
TEST_F(ResourceMetadataStorageTest, GetChildren) {
const std::string parents_id[] = { "mercury", "venus", "mars", "jupiter",
"saturn" };
std::vector<std::vector<std::pair<std::string, std::string> > >
children_name_id(arraysize(parents_id));
// Skip children_name_id[0/1] here because Mercury and Venus have no moon.
children_name_id[2].push_back(std::make_pair("phobos", "mars_i"));
children_name_id[2].push_back(std::make_pair("deimos", "mars_ii"));
children_name_id[3].push_back(std::make_pair("io", "jupiter_i"));
children_name_id[3].push_back(std::make_pair("europa", "jupiter_ii"));
children_name_id[3].push_back(std::make_pair("ganymede", "jupiter_iii"));
children_name_id[3].push_back(std::make_pair("calisto", "jupiter_iv"));
children_name_id[4].push_back(std::make_pair("mimas", "saturn_i"));
children_name_id[4].push_back(std::make_pair("enceladus", "saturn_ii"));
children_name_id[4].push_back(std::make_pair("tethys", "saturn_iii"));
children_name_id[4].push_back(std::make_pair("dione", "saturn_iv"));
children_name_id[4].push_back(std::make_pair("rhea", "saturn_v"));
children_name_id[4].push_back(std::make_pair("titan", "saturn_vi"));
children_name_id[4].push_back(std::make_pair("iapetus", "saturn_vii"));
// Put parents.
for (size_t i = 0; i < arraysize(parents_id); ++i) {
ResourceEntry entry;
entry.set_local_id(parents_id[i]);
EXPECT_TRUE(storage_->PutEntry(entry));
}
// Put children.
for (size_t i = 0; i < children_name_id.size(); ++i) {
for (size_t j = 0; j < children_name_id[i].size(); ++j) {
ResourceEntry entry;
entry.set_local_id(children_name_id[i][j].second);
entry.set_parent_local_id(parents_id[i]);
entry.set_base_name(children_name_id[i][j].first);
EXPECT_TRUE(storage_->PutEntry(entry));
}
}
// Put some dummy cache entries.
for (size_t i = 0; i < arraysize(parents_id); ++i) {
FileCacheEntry cache_entry;
EXPECT_TRUE(storage_->PutCacheEntry(parents_id[i], cache_entry));
}
// Try to get children.
for (size_t i = 0; i < children_name_id.size(); ++i) {
std::vector<std::string> children;
storage_->GetChildren(parents_id[i], &children);
EXPECT_EQ(children_name_id[i].size(), children.size());
for (size_t j = 0; j < children_name_id[i].size(); ++j) {
EXPECT_EQ(1, std::count(children.begin(),
children.end(),
children_name_id[i][j].second));
}
}
}
TEST_F(ResourceMetadataStorageTest, OpenExistingDB) {
const std::string parent_id1 = "abcdefg";
const std::string child_name1 = "WXYZABC";
const std::string child_id1 = "qwerty";
ResourceEntry entry1;
entry1.set_local_id(parent_id1);
ResourceEntry entry2;
entry2.set_local_id(child_id1);
entry2.set_parent_local_id(parent_id1);
entry2.set_base_name(child_name1);
// Put some data.
EXPECT_TRUE(storage_->PutEntry(entry1));
EXPECT_TRUE(storage_->PutEntry(entry2));
// Close DB and reopen.
storage_.reset(new ResourceMetadataStorage(
temp_dir_.path(), base::MessageLoopProxy::current().get()));
ASSERT_TRUE(storage_->Initialize());
// Can read data.
ResourceEntry result;
EXPECT_TRUE(storage_->GetEntry(parent_id1, &result));
EXPECT_TRUE(storage_->GetEntry(child_id1, &result));
EXPECT_EQ(parent_id1, result.parent_local_id());
EXPECT_EQ(child_name1, result.base_name());
EXPECT_EQ(child_id1, storage_->GetChild(parent_id1, child_name1));
}
TEST_F(ResourceMetadataStorageTest, IncompatibleDB_M29) {
const int64 kLargestChangestamp = 1234567890;
// Construct M29 version DB.
SetDBVersion(6);
EXPECT_TRUE(storage_->SetLargestChangestamp(kLargestChangestamp));
leveldb::WriteBatch batch;
// Put a file entry and its cache entry.
ResourceEntry entry;
std::string serialized_entry;
entry.set_resource_id("file:abcd");
EXPECT_TRUE(entry.SerializeToString(&serialized_entry));
batch.Put("file:abcd", serialized_entry);
FileCacheEntry cache_entry;
EXPECT_TRUE(cache_entry.SerializeToString(&serialized_entry));
batch.Put(std::string("file:abcd") + '\0' + "CACHE", serialized_entry);
EXPECT_TRUE(resource_map()->Write(leveldb::WriteOptions(), &batch).ok());
// Upgrade and reopen.
storage_.reset();
EXPECT_TRUE(ResourceMetadataStorage::UpgradeOldDB(
temp_dir_.path(), base::Bind(&util::CanonicalizeResourceId)));
storage_.reset(new ResourceMetadataStorage(
temp_dir_.path(), base::MessageLoopProxy::current().get()));
ASSERT_TRUE(storage_->Initialize());
// Resource-ID-to-local-ID mapping is added.
std::string id;
EXPECT_TRUE(storage_->GetIdByResourceId("abcd", &id)); // "file:" is dropped.
// Data is erased, except cache entries.
EXPECT_EQ(0, storage_->GetLargestChangestamp());
EXPECT_FALSE(storage_->GetEntry(id, &entry));
EXPECT_TRUE(storage_->GetCacheEntry(id, &cache_entry));
}
TEST_F(ResourceMetadataStorageTest, IncompatibleDB_Unknown) {
const int64 kLargestChangestamp = 1234567890;
const std::string key1 = "abcd";
// Put some data.
EXPECT_TRUE(storage_->SetLargestChangestamp(kLargestChangestamp));
ResourceEntry entry;
entry.set_local_id(key1);
EXPECT_TRUE(storage_->PutEntry(entry));
FileCacheEntry cache_entry;
EXPECT_TRUE(storage_->PutCacheEntry(key1, cache_entry));
// Set newer version, upgrade and reopen DB.
SetDBVersion(ResourceMetadataStorage::kDBVersion + 1);
storage_.reset();
EXPECT_FALSE(ResourceMetadataStorage::UpgradeOldDB(
temp_dir_.path(), base::Bind(&util::CanonicalizeResourceId)));
storage_.reset(new ResourceMetadataStorage(
temp_dir_.path(), base::MessageLoopProxy::current().get()));
ASSERT_TRUE(storage_->Initialize());
// Data is erased because of the incompatible version.
EXPECT_EQ(0, storage_->GetLargestChangestamp());
EXPECT_FALSE(storage_->GetEntry(key1, &entry));
EXPECT_FALSE(storage_->GetCacheEntry(key1, &cache_entry));
}
TEST_F(ResourceMetadataStorageTest, WrongPath) {
// Create a file.
base::FilePath path;
ASSERT_TRUE(file_util::CreateTemporaryFileInDir(temp_dir_.path(), &path));
storage_.reset(new ResourceMetadataStorage(
path, base::MessageLoopProxy::current().get()));
// Cannot initialize DB beacause the path does not point a directory.
ASSERT_FALSE(storage_->Initialize());
}
TEST_F(ResourceMetadataStorageTest, RecoverCacheEntriesFromTrashedResourceMap) {
// Put some cache entries.
FileCacheEntry cache_entry;
cache_entry.set_md5("md5_foo");
EXPECT_TRUE(storage_->PutCacheEntry("id_foo", cache_entry));
cache_entry.set_md5("md5_bar");
cache_entry.set_is_dirty(true);
EXPECT_TRUE(storage_->PutCacheEntry("id_bar", cache_entry));
// Put entry with id_foo.
ResourceEntry entry;
entry.set_local_id("id_foo");
entry.set_base_name("foo");
entry.set_title("foo");
EXPECT_TRUE(storage_->PutEntry(entry));
// Put entry with id_bar as a id_foo's child.
entry.set_local_id("id_bar");
entry.set_parent_local_id("id_foo");
entry.set_base_name("bar");
entry.set_title("bar");
EXPECT_TRUE(storage_->PutEntry(entry));
// Remove parent-child relationship to make the DB invalid.
RemoveChild("id_foo", "bar");
EXPECT_FALSE(CheckValidity());
// Reopen. This should result in trashing the DB.
storage_.reset(new ResourceMetadataStorage(
temp_dir_.path(), base::MessageLoopProxy::current().get()));
ASSERT_TRUE(storage_->Initialize());
// Recover cache entries from the trashed DB.
ResourceMetadataStorage::RecoveredCacheInfoMap recovered_cache_info;
storage_->RecoverCacheInfoFromTrashedResourceMap(&recovered_cache_info);
EXPECT_EQ(2U, recovered_cache_info.size());
EXPECT_FALSE(recovered_cache_info["id_foo"].is_dirty);
EXPECT_EQ("md5_foo", recovered_cache_info["id_foo"].md5);
EXPECT_EQ("foo", recovered_cache_info["id_foo"].title);
EXPECT_TRUE(recovered_cache_info["id_bar"].is_dirty);
EXPECT_EQ("md5_bar", recovered_cache_info["id_bar"].md5);
EXPECT_EQ("bar", recovered_cache_info["id_bar"].title);
}
TEST_F(ResourceMetadataStorageTest, CheckValidity) {
const std::string key1 = "foo";
const std::string name1 = "hoge";
const std::string key2 = "bar";
const std::string name2 = "fuga";
const std::string key3 = "boo";
const std::string name3 = "piyo";
// Empty storage is valid.
EXPECT_TRUE(CheckValidity());
// Put entry with key1.
ResourceEntry entry;
entry.set_local_id(key1);
entry.set_base_name(name1);
EXPECT_TRUE(storage_->PutEntry(entry));
EXPECT_TRUE(CheckValidity());
// Put entry with key2 under key1.
entry.set_local_id(key2);
entry.set_parent_local_id(key1);
entry.set_base_name(name2);
EXPECT_TRUE(storage_->PutEntry(entry));
EXPECT_TRUE(CheckValidity());
RemoveChild(key1, name2);
EXPECT_FALSE(CheckValidity()); // Missing parent-child relationship.
// Add back parent-child relationship between key1 and key2.
PutChild(key1, name2, key2);
EXPECT_TRUE(CheckValidity());
// Add parent-child relationship between key2 and key3.
PutChild(key2, name3, key3);
EXPECT_FALSE(CheckValidity()); // key3 is not stored in the storage.
// Put entry with key3 under key2.
entry.set_local_id(key3);
entry.set_parent_local_id(key2);
entry.set_base_name(name3);
EXPECT_TRUE(storage_->PutEntry(entry));
EXPECT_TRUE(CheckValidity());
// Parent-child relationship with wrong name.
RemoveChild(key2, name3);
EXPECT_FALSE(CheckValidity());
PutChild(key2, name2, key3);
EXPECT_FALSE(CheckValidity());
// Fix up the relationship between key2 and key3.
RemoveChild(key2, name2);
EXPECT_FALSE(CheckValidity());
PutChild(key2, name3, key3);
EXPECT_TRUE(CheckValidity());
// Add some cache entries.
FileCacheEntry cache_entry;
EXPECT_TRUE(storage_->PutCacheEntry(key1, cache_entry));
EXPECT_TRUE(storage_->PutCacheEntry(key2, cache_entry));
// Remove key2.
RemoveChild(key1, name2);
EXPECT_FALSE(CheckValidity());
EXPECT_TRUE(storage_->RemoveEntry(key2));
EXPECT_FALSE(CheckValidity());
// Remove key3.
RemoveChild(key2, name3);
EXPECT_FALSE(CheckValidity());
EXPECT_TRUE(storage_->RemoveEntry(key3));
EXPECT_TRUE(CheckValidity());
// Remove key1.
EXPECT_TRUE(storage_->RemoveEntry(key1));
EXPECT_TRUE(CheckValidity());
}
} // namespace internal
} // namespace drive