blob: c53a9836c6d958f9beef1bb5baef38c246311ab8 [file] [log] [blame]
// Copyright 2016 The Fuchsia 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 "src/ledger/bin/storage/impl/object_impl.h"
#include <lib/fsl/vmo/strings.h>
#include <zircon/syscalls.h>
#include <memory>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "peridot/lib/scoped_tmpfs/scoped_tmpfs.h"
#include "src/ledger/bin/storage/impl/btree/encoding.h"
#include "src/ledger/bin/storage/impl/constants.h"
#include "src/ledger/bin/storage/impl/file_index.h"
#include "src/ledger/bin/storage/impl/object_digest.h"
#include "src/ledger/bin/storage/impl/storage_test_utils.h"
#include "src/ledger/bin/storage/public/data_source.h"
#include "src/ledger/bin/storage/public/types.h"
#include "src/ledger/bin/testing/test_with_environment.h"
#include "src/lib/fxl/logging.h"
#include "third_party/leveldb/include/leveldb/db.h"
#include "util/env_fuchsia.h"
namespace storage {
namespace {
using ::testing::IsEmpty;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
ObjectIdentifier CreateObjectIdentifier(ObjectDigest digest) {
return {1u, 2u, std::move(digest)};
}
::testing::AssertionResult CheckObjectValue(const Object& object,
ObjectIdentifier identifier,
fxl::StringView data) {
if (object.GetIdentifier() != identifier) {
return ::testing::AssertionFailure()
<< "Expected id: " << identifier
<< ", but got: " << object.GetIdentifier();
}
fxl::StringView found_data;
Status status = object.GetData(&found_data);
if (status != Status::OK) {
return ::testing::AssertionFailure()
<< "Unable to call GetData on object, status: " << status;
}
if (data != found_data) {
return ::testing::AssertionFailure()
<< "Expected data: " << convert::ToHex(data)
<< ", but got: " << convert::ToHex(found_data);
}
fsl::SizedVmo vmo;
status = object.GetVmo(&vmo);
if (status != Status::OK) {
return ::testing::AssertionFailure()
<< "Unable to call GetVmo on object, status: " << status;
}
std::string found_data_in_vmo;
if (!fsl::StringFromVmo(vmo, &found_data_in_vmo)) {
return ::testing::AssertionFailure() << "Unable to read from VMO.";
}
if (data != found_data_in_vmo) {
return ::testing::AssertionFailure()
<< "Expected data in vmo: " << convert::ToHex(data)
<< ", but got: " << convert::ToHex(found_data_in_vmo);
}
return ::testing::AssertionSuccess();
}
::testing::AssertionResult CheckPieceValue(const Piece& piece,
ObjectIdentifier identifier,
fxl::StringView data) {
if (piece.GetIdentifier() != identifier) {
return ::testing::AssertionFailure()
<< "Expected id: " << identifier
<< ", but got: " << piece.GetIdentifier();
}
fxl::StringView found_data = piece.GetData();
if (data != found_data) {
return ::testing::AssertionFailure()
<< "Expected data: " << convert::ToHex(data)
<< ", but got: " << convert::ToHex(found_data);
}
return ::testing::AssertionSuccess();
}
using ObjectImplTest = ledger::TestWithEnvironment;
TEST_F(ObjectImplTest, InlinedPiece) {
std::string data = RandomString(environment_.random(), 12);
ObjectIdentifier identifier = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB, data));
const InlinePiece piece(identifier);
EXPECT_TRUE(CheckPieceValue(piece, identifier, data));
}
TEST_F(ObjectImplTest, DataChunkPiece) {
std::string data = RandomString(environment_.random(), 12);
ObjectIdentifier identifier = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB, data));
const DataChunkPiece piece(identifier, DataSource::DataChunk::Create(data));
EXPECT_TRUE(CheckPieceValue(piece, identifier, data));
}
TEST_F(ObjectImplTest, LevelDBPiece) {
scoped_tmpfs::ScopedTmpFS tmpfs;
auto env = leveldb::MakeFuchsiaEnv(tmpfs.root_fd());
leveldb::DB* db = nullptr;
leveldb::Options options;
options.env = env.get();
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options, "db", &db);
ASSERT_TRUE(status.ok());
std::unique_ptr<leveldb::DB> db_ptr(db);
leveldb::WriteOptions write_options_;
leveldb::ReadOptions read_options_;
std::string data = RandomString(environment_.random(), 256);
ObjectIdentifier identifier = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB, data));
status = db_ptr->Put(write_options_, "", data);
ASSERT_TRUE(status.ok());
std::unique_ptr<leveldb::Iterator> iterator(
db_ptr->NewIterator(read_options_));
iterator->Seek("");
ASSERT_TRUE(iterator->Valid());
ASSERT_TRUE(iterator->key() == "");
const LevelDBPiece piece(identifier, std::move(iterator));
EXPECT_TRUE(CheckPieceValue(piece, identifier, data));
}
TEST_F(ObjectImplTest, PieceReferences) {
// Create various types of identifiers for the piece children. Small pieces
// fit in chunks, while bigger ones are split and yield identifiers of index
// pieces.
constexpr size_t kInlineSize = kStorageHashSize;
const ObjectIdentifier inline_chunk = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB,
RandomString(environment_.random(), kInlineSize)));
ASSERT_TRUE(GetObjectDigestInfo(inline_chunk.object_digest()).is_chunk());
ASSERT_TRUE(GetObjectDigestInfo(inline_chunk.object_digest()).is_inlined());
const ObjectIdentifier inline_index = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::INDEX, ObjectType::BLOB,
RandomString(environment_.random(), kInlineSize)));
ASSERT_FALSE(GetObjectDigestInfo(inline_index.object_digest()).is_chunk());
ASSERT_TRUE(GetObjectDigestInfo(inline_index.object_digest()).is_inlined());
constexpr size_t kNoInlineSize = kStorageHashSize + 1;
const ObjectIdentifier noinline_chunk = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB,
RandomString(environment_.random(), kNoInlineSize)));
ASSERT_TRUE(GetObjectDigestInfo(noinline_chunk.object_digest()).is_chunk());
ASSERT_FALSE(
GetObjectDigestInfo(noinline_chunk.object_digest()).is_inlined());
const ObjectIdentifier noinline_index = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::INDEX, ObjectType::BLOB,
RandomString(environment_.random(), kNoInlineSize)));
ASSERT_FALSE(GetObjectDigestInfo(noinline_index.object_digest()).is_chunk());
ASSERT_FALSE(
GetObjectDigestInfo(noinline_index.object_digest()).is_inlined());
// Create the parent piece.
std::unique_ptr<DataSource::DataChunk> data;
size_t total_size;
FileIndexSerialization::BuildFileIndex({{inline_chunk, kInlineSize},
{noinline_chunk, kInlineSize},
{inline_index, kNoInlineSize},
{noinline_index, kNoInlineSize}},
&data, &total_size);
const ObjectIdentifier identifier = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::INDEX, ObjectType::BLOB, data->Get()));
DataChunkPiece piece(identifier, std::move(data));
// Check that inline children are not included in the references.
ObjectReferencesAndPriority references;
ASSERT_EQ(Status::OK, piece.AppendReferences(&references));
EXPECT_THAT(references,
UnorderedElementsAre(
Pair(noinline_chunk.object_digest(), KeyPriority::EAGER),
Pair(noinline_index.object_digest(), KeyPriority::EAGER)));
}
TEST_F(ObjectImplTest, ChunkObject) {
std::string data = RandomString(environment_.random(), 12);
ObjectIdentifier identifier = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB, data));
ChunkObject object(std::make_unique<InlinePiece>(identifier));
EXPECT_TRUE(CheckObjectValue(object, identifier, data));
}
TEST_F(ObjectImplTest, VmoObject) {
std::string data = RandomString(environment_.random(), 256);
ObjectIdentifier identifier = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB, data));
fsl::SizedVmo vmo;
ASSERT_TRUE(fsl::VmoFromString(data, &vmo));
VmoObject object(identifier, std::move(vmo));
EXPECT_TRUE(CheckObjectValue(object, identifier, data));
}
TEST_F(ObjectImplTest, ObjectReferences) {
// Create various types of identifiers for the object children and values.
constexpr size_t kInlineSize = kStorageHashSize;
const ObjectIdentifier inline_blob = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB,
RandomString(environment_.random(), kInlineSize)));
ASSERT_TRUE(GetObjectDigestInfo(inline_blob.object_digest()).is_inlined());
const ObjectIdentifier inline_treenode = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::TREE_NODE,
RandomString(environment_.random(), kInlineSize)));
ASSERT_TRUE(
GetObjectDigestInfo(inline_treenode.object_digest()).is_inlined());
constexpr size_t kNoInlineSize = kStorageHashSize + 1;
const ObjectIdentifier noinline_blob = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB,
RandomString(environment_.random(), kNoInlineSize)));
ASSERT_FALSE(GetObjectDigestInfo(noinline_blob.object_digest()).is_inlined());
const ObjectIdentifier noinline_treenode = CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::TREE_NODE,
RandomString(environment_.random(), kNoInlineSize)));
ASSERT_FALSE(
GetObjectDigestInfo(noinline_treenode.object_digest()).is_inlined());
// Create a TreeNode object.
const std::string data =
btree::EncodeNode(/*level=*/0,
{
Entry{"key01", inline_blob, KeyPriority::EAGER},
Entry{"key02", noinline_blob, KeyPriority::EAGER},
Entry{"key03", inline_blob, KeyPriority::LAZY},
Entry{"key04", noinline_blob, KeyPriority::LAZY},
},
{
{0, inline_treenode},
{1, noinline_treenode},
});
const ChunkObject tree_object(std::make_unique<DataChunkPiece>(
CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::TREE_NODE, data)),
DataSource::DataChunk::Create(data)));
// Check that inline children are not included in the references.
ObjectReferencesAndPriority references;
ASSERT_EQ(Status::OK, tree_object.AppendReferences(&references));
EXPECT_THAT(references,
UnorderedElementsAre(
Pair(noinline_blob.object_digest(), KeyPriority::EAGER),
Pair(noinline_blob.object_digest(), KeyPriority::LAZY),
Pair(noinline_treenode.object_digest(), KeyPriority::EAGER)));
// Create a blob object with the exact same data and check that it does not
// return any reference.
const ChunkObject blob_object(std::make_unique<DataChunkPiece>(
CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::BLOB, data)),
DataSource::DataChunk::Create(data)));
references.clear();
ASSERT_EQ(Status::OK, blob_object.AppendReferences(&references));
EXPECT_THAT(references, IsEmpty());
// Create an invalid tree node object and check that we return an error.
const ChunkObject invalid_object(std::make_unique<DataChunkPiece>(
CreateObjectIdentifier(
ComputeObjectDigest(PieceType::CHUNK, ObjectType::TREE_NODE, "")),
DataSource::DataChunk::Create("")));
ASSERT_EQ(Status::FORMAT_ERROR, invalid_object.AppendReferences(&references));
}
} // namespace
} // namespace storage