blob: 67e27f2ee9ead83f170ec75f6949670716671105 [file] [log] [blame]
// Copyright 2014 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 "sync/internal_api/public/attachments/on_disk_attachment_store.h"
#include <stdint.h>
#include <string>
#include <utility>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "sync/internal_api/attachments/attachment_store_test_template.h"
#include "sync/internal_api/attachments/proto/attachment_store.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/options.h"
#include "third_party/leveldatabase/src/include/leveldb/slice.h"
#include "third_party/leveldatabase/src/include/leveldb/status.h"
namespace syncer {
namespace {
void AttachmentStoreCreated(AttachmentStore::Result* result_dest,
const AttachmentStore::Result& result) {
*result_dest = result;
}
} // namespace
// Instantiation of common attachment store tests.
class OnDiskAttachmentStoreFactory {
public:
OnDiskAttachmentStoreFactory() {}
~OnDiskAttachmentStoreFactory() {}
std::unique_ptr<AttachmentStore> CreateAttachmentStore() {
EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
std::unique_ptr<AttachmentStore> store;
AttachmentStore::Result result = AttachmentStore::UNSPECIFIED_ERROR;
store = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &result));
base::RunLoop run_loop;
run_loop.RunUntilIdle();
EXPECT_EQ(AttachmentStore::SUCCESS, result);
return store;
}
private:
base::ScopedTempDir temp_dir_;
};
INSTANTIATE_TYPED_TEST_CASE_P(OnDisk,
AttachmentStoreTest,
OnDiskAttachmentStoreFactory);
// Tests specific to OnDiskAttachmentStore.
class OnDiskAttachmentStoreSpecificTest : public testing::Test {
public:
base::ScopedTempDir temp_dir_;
base::FilePath db_path_;
base::MessageLoop message_loop_;
std::unique_ptr<AttachmentStore> store_;
OnDiskAttachmentStoreSpecificTest() {}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
db_path_ = temp_dir_.path().Append(FILE_PATH_LITERAL("leveldb"));
base::CreateDirectory(db_path_);
}
void CopyResult(AttachmentStore::Result* destination_result,
const AttachmentStore::Result& source_result) {
*destination_result = source_result;
}
void CopyResultAttachments(
AttachmentStore::Result* destination_result,
AttachmentIdList* destination_failed_attachment_ids,
const AttachmentStore::Result& source_result,
std::unique_ptr<AttachmentMap> source_attachments,
std::unique_ptr<AttachmentIdList> source_failed_attachment_ids) {
CopyResult(destination_result, source_result);
*destination_failed_attachment_ids = *source_failed_attachment_ids;
}
void CopyResultMetadata(
AttachmentStore::Result* destination_result,
std::unique_ptr<AttachmentMetadataList>* destination_metadata,
const AttachmentStore::Result& source_result,
std::unique_ptr<AttachmentMetadataList> source_metadata) {
CopyResult(destination_result, source_result);
*destination_metadata = std::move(source_metadata);
}
std::unique_ptr<leveldb::DB> OpenLevelDB() {
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status s =
leveldb::DB::Open(options, db_path_.AsUTF8Unsafe(), &db);
EXPECT_TRUE(s.ok());
return base::WrapUnique(db);
}
void UpdateRecord(const std::string& key, const std::string& content) {
std::unique_ptr<leveldb::DB> db = OpenLevelDB();
leveldb::Status s = db->Put(leveldb::WriteOptions(), key, content);
EXPECT_TRUE(s.ok());
}
void UpdateStoreMetadataRecord(const std::string& content) {
UpdateRecord("database-metadata", content);
}
void UpdateAttachmentMetadataRecord(const AttachmentId& attachment_id,
const std::string& content) {
std::string metadata_key =
OnDiskAttachmentStore::MakeMetadataKeyFromAttachmentId(attachment_id);
UpdateRecord(metadata_key, content);
}
std::string ReadStoreMetadataRecord() {
std::unique_ptr<leveldb::DB> db = OpenLevelDB();
std::string content;
leveldb::Status s =
db->Get(leveldb::ReadOptions(), "database-metadata", &content);
EXPECT_TRUE(s.ok());
return content;
}
void VerifyAttachmentRecordsPresent(const AttachmentId& attachment_id,
bool expect_records_present) {
std::unique_ptr<leveldb::DB> db = OpenLevelDB();
std::string metadata_key =
OnDiskAttachmentStore::MakeMetadataKeyFromAttachmentId(attachment_id);
std::string data_key =
OnDiskAttachmentStore::MakeDataKeyFromAttachmentId(attachment_id);
std::string data;
leveldb::Status s = db->Get(leveldb::ReadOptions(), data_key, &data);
if (expect_records_present)
EXPECT_TRUE(s.ok());
else
EXPECT_TRUE(s.IsNotFound());
s = db->Get(leveldb::ReadOptions(), metadata_key, &data);
if (expect_records_present)
EXPECT_TRUE(s.ok());
else
EXPECT_TRUE(s.IsNotFound());
}
void RunLoop() {
base::RunLoop run_loop;
run_loop.RunUntilIdle();
}
};
// Ensure that store can be closed and reopen while retaining stored
// attachments.
TEST_F(OnDiskAttachmentStoreSpecificTest, CloseAndReopen) {
AttachmentStore::Result result;
result = AttachmentStore::UNSPECIFIED_ERROR;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &result));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, result);
result = AttachmentStore::UNSPECIFIED_ERROR;
std::string some_data = "data";
Attachment attachment =
Attachment::Create(base::RefCountedString::TakeString(&some_data));
AttachmentList attachments;
attachments.push_back(attachment);
store_->Write(attachments,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this),
&result));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, result);
// Close and reopen attachment store.
store_ = nullptr;
result = AttachmentStore::UNSPECIFIED_ERROR;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &result));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, result);
result = AttachmentStore::UNSPECIFIED_ERROR;
AttachmentIdList attachment_ids;
attachment_ids.push_back(attachment.GetId());
AttachmentIdList failed_attachment_ids;
store_->Read(
attachment_ids,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResultAttachments,
base::Unretained(this), &result, &failed_attachment_ids));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, result);
EXPECT_TRUE(failed_attachment_ids.empty());
}
// Ensure loading corrupt attachment store fails.
TEST_F(OnDiskAttachmentStoreSpecificTest, FailToOpen) {
// To simulate corrupt database write empty CURRENT file.
std::string current_file_content = "";
base::WriteFile(db_path_.Append(FILE_PATH_LITERAL("CURRENT")),
current_file_content.c_str(), current_file_content.size());
AttachmentStore::Result result = AttachmentStore::SUCCESS;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &result));
RunLoop();
EXPECT_EQ(AttachmentStore::UNSPECIFIED_ERROR, result);
}
// Ensure that attachment store works correctly when store metadata is missing,
// corrupt or has unknown schema version.
TEST_F(OnDiskAttachmentStoreSpecificTest, StoreMetadata) {
// Create and close empty database.
OpenLevelDB();
// Open database with AttachmentStore.
AttachmentStore::Result result = AttachmentStore::UNSPECIFIED_ERROR;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &result));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, result);
// Close AttachmentStore so that test can check content.
store_ = nullptr;
RunLoop();
// AttachmentStore should create metadata record.
std::string data = ReadStoreMetadataRecord();
attachment_store_pb::StoreMetadata metadata;
EXPECT_TRUE(metadata.ParseFromString(data));
EXPECT_EQ(1, metadata.schema_version());
// Set unknown future schema version.
metadata.set_schema_version(2);
data = metadata.SerializeAsString();
UpdateStoreMetadataRecord(data);
// AttachmentStore should fail to load.
result = AttachmentStore::SUCCESS;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &result));
RunLoop();
EXPECT_EQ(AttachmentStore::UNSPECIFIED_ERROR, result);
// Write garbage into metadata record.
UpdateStoreMetadataRecord("abra.cadabra");
// AttachmentStore should fail to load.
result = AttachmentStore::SUCCESS;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &result));
RunLoop();
EXPECT_EQ(AttachmentStore::UNSPECIFIED_ERROR, result);
}
// Ensure that attachment store correctly maintains metadata records for
// attachments.
TEST_F(OnDiskAttachmentStoreSpecificTest, RecordMetadata) {
// Create attachment store.
AttachmentStore::Result create_result = AttachmentStore::UNSPECIFIED_ERROR;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &create_result));
// Write two attachments.
AttachmentStore::Result write_result = AttachmentStore::UNSPECIFIED_ERROR;
std::string some_data;
AttachmentList attachments;
some_data = "data1";
attachments.push_back(
Attachment::Create(base::RefCountedString::TakeString(&some_data)));
some_data = "data2";
attachments.push_back(
Attachment::Create(base::RefCountedString::TakeString(&some_data)));
store_->Write(attachments,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this), &write_result));
// Delete one of written attachments.
AttachmentStore::Result drop_result = AttachmentStore::UNSPECIFIED_ERROR;
AttachmentIdList attachment_ids;
attachment_ids.push_back(attachments[0].GetId());
store_->Drop(attachment_ids,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this), &drop_result));
store_ = nullptr;
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, create_result);
EXPECT_EQ(AttachmentStore::SUCCESS, write_result);
EXPECT_EQ(AttachmentStore::SUCCESS, drop_result);
// Verify that attachment store contains only records for second attachment.
VerifyAttachmentRecordsPresent(attachments[0].GetId(), false);
VerifyAttachmentRecordsPresent(attachments[1].GetId(), true);
}
// Ensure that attachment store fails to load attachment if the crc in the store
// does not match the data.
TEST_F(OnDiskAttachmentStoreSpecificTest, MismatchedCrcInStore) {
// Create attachment store.
AttachmentStore::Result create_result = AttachmentStore::UNSPECIFIED_ERROR;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &create_result));
// Write attachment with incorrect crc32c.
AttachmentStore::Result write_result = AttachmentStore::UNSPECIFIED_ERROR;
const uint32_t intentionally_wrong_crc32c = 0;
scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString());
some_data->data() = "data1";
Attachment attachment = Attachment::CreateFromParts(
AttachmentId::Create(some_data->size(), intentionally_wrong_crc32c),
some_data);
AttachmentList attachments;
attachments.push_back(attachment);
store_->Write(attachments,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this), &write_result));
// Read attachment.
AttachmentStore::Result read_result = AttachmentStore::UNSPECIFIED_ERROR;
AttachmentIdList attachment_ids;
attachment_ids.push_back(attachment.GetId());
AttachmentIdList failed_attachment_ids;
store_->Read(
attachment_ids,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResultAttachments,
base::Unretained(this), &read_result, &failed_attachment_ids));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, create_result);
EXPECT_EQ(AttachmentStore::SUCCESS, write_result);
EXPECT_EQ(AttachmentStore::UNSPECIFIED_ERROR, read_result);
EXPECT_THAT(failed_attachment_ids, testing::ElementsAre(attachment.GetId()));
}
// Ensure that attachment store fails to load attachment if the crc in the id
// does not match the data.
TEST_F(OnDiskAttachmentStoreSpecificTest, MismatchedCrcInId) {
// Create attachment store.
AttachmentStore::Result create_result = AttachmentStore::UNSPECIFIED_ERROR;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &create_result));
AttachmentStore::Result write_result = AttachmentStore::UNSPECIFIED_ERROR;
scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString());
some_data->data() = "data1";
Attachment attachment = Attachment::Create(some_data);
AttachmentList attachments;
attachments.push_back(attachment);
store_->Write(attachments,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this), &write_result));
// Read, but with the wrong crc32c in the id.
AttachmentStore::Result read_result = AttachmentStore::SUCCESS;
AttachmentId id_with_bad_crc32c =
AttachmentId::Create(attachment.GetId().GetSize(), 12345);
AttachmentIdList attachment_ids;
attachment_ids.push_back(id_with_bad_crc32c);
AttachmentIdList failed_attachment_ids;
store_->Read(
attachment_ids,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResultAttachments,
base::Unretained(this), &read_result, &failed_attachment_ids));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, create_result);
EXPECT_EQ(AttachmentStore::SUCCESS, write_result);
EXPECT_EQ(AttachmentStore::UNSPECIFIED_ERROR, read_result);
EXPECT_THAT(failed_attachment_ids, testing::ElementsAre(id_with_bad_crc32c));
}
// Ensure that after store initialization failure ReadWrite/Drop operations fail
// with correct error.
TEST_F(OnDiskAttachmentStoreSpecificTest, OpsAfterInitializationFailed) {
// To simulate corrupt database write empty CURRENT file.
std::string current_file_content = "";
base::WriteFile(db_path_.Append(FILE_PATH_LITERAL("CURRENT")),
current_file_content.c_str(), current_file_content.size());
AttachmentStore::Result create_result = AttachmentStore::SUCCESS;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &create_result));
// Reading from uninitialized store should result in
// STORE_INITIALIZATION_FAILED.
AttachmentStore::Result read_result = AttachmentStore::SUCCESS;
AttachmentIdList attachment_ids;
std::string some_data("data1");
Attachment attachment =
Attachment::Create(base::RefCountedString::TakeString(&some_data));
attachment_ids.push_back(attachment.GetId());
AttachmentIdList failed_attachment_ids;
store_->Read(
attachment_ids,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResultAttachments,
base::Unretained(this), &read_result, &failed_attachment_ids));
// Dropping from uninitialized store should result in
// STORE_INITIALIZATION_FAILED.
AttachmentStore::Result drop_result = AttachmentStore::SUCCESS;
store_->Drop(attachment_ids,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this), &drop_result));
// Writing to uninitialized store should result in
// STORE_INITIALIZATION_FAILED.
AttachmentStore::Result write_result = AttachmentStore::SUCCESS;
AttachmentList attachments;
attachments.push_back(attachment);
store_->Write(attachments,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this), &write_result));
RunLoop();
EXPECT_EQ(AttachmentStore::UNSPECIFIED_ERROR, create_result);
EXPECT_EQ(AttachmentStore::STORE_INITIALIZATION_FAILED, read_result);
EXPECT_THAT(failed_attachment_ids, testing::ElementsAre(attachment_ids[0]));
EXPECT_EQ(AttachmentStore::STORE_INITIALIZATION_FAILED, drop_result);
EXPECT_EQ(AttachmentStore::STORE_INITIALIZATION_FAILED, write_result);
}
// Ensure that attachment store handles the case of having an unexpected
// record at the end without crashing.
TEST_F(OnDiskAttachmentStoreSpecificTest, ReadMetadataWithUnexpectedRecord) {
// Write a bogus entry at the end of the database.
UpdateRecord("zzz", "foobar");
// Create attachment store.
AttachmentStore::Result create_result = AttachmentStore::UNSPECIFIED_ERROR;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &create_result));
// Read all metadata. Should be getting no error and zero entries.
AttachmentStore::Result metadata_result = AttachmentStore::UNSPECIFIED_ERROR;
std::unique_ptr<AttachmentMetadataList> metadata_list;
store_->ReadMetadata(
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResultMetadata,
base::Unretained(this), &metadata_result, &metadata_list));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, create_result);
EXPECT_EQ(AttachmentStore::SUCCESS, metadata_result);
EXPECT_EQ(0U, metadata_list->size());
metadata_list.reset();
// Write 3 attachments to the store
AttachmentList attachments;
for (int i = 0; i < 3; i++) {
std::string some_data = "data";
Attachment attachment =
Attachment::Create(base::RefCountedString::TakeString(&some_data));
attachments.push_back(attachment);
}
AttachmentStore::Result write_result = AttachmentStore::UNSPECIFIED_ERROR;
store_->Write(attachments,
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResult,
base::Unretained(this), &write_result));
// Read all metadata back. We should be getting 3 entries.
store_->ReadMetadata(
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResultMetadata,
base::Unretained(this), &metadata_result, &metadata_list));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, write_result);
EXPECT_EQ(AttachmentStore::SUCCESS, metadata_result);
EXPECT_EQ(3U, metadata_list->size());
// Get Id of the attachment in the middle.
AttachmentId id = (*metadata_list.get())[1].GetId();
metadata_list.reset();
// Close the store.
store_ = nullptr;
RunLoop();
// Modify the middle attachment metadata entry so that it isn't valid anymore.
UpdateAttachmentMetadataRecord(id, "foobar");
// Reopen the store.
create_result = AttachmentStore::UNSPECIFIED_ERROR;
metadata_result = AttachmentStore::UNSPECIFIED_ERROR;
store_ = AttachmentStore::CreateOnDiskStore(
temp_dir_.path(), base::ThreadTaskRunnerHandle::Get(),
base::Bind(&AttachmentStoreCreated, &create_result));
// Read all metadata back. We should be getting a failure and
// only 2 entries now.
store_->ReadMetadata(
base::Bind(&OnDiskAttachmentStoreSpecificTest::CopyResultMetadata,
base::Unretained(this), &metadata_result, &metadata_list));
RunLoop();
EXPECT_EQ(AttachmentStore::SUCCESS, create_result);
EXPECT_EQ(AttachmentStore::UNSPECIFIED_ERROR, metadata_result);
EXPECT_EQ(2U, metadata_list->size());
}
} // namespace syncer