blob: c02f3d6bb7e91196f37e427eccf8b126bb8c14fa [file]
// 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/btree/tree_node.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/ledger/bin/storage/fake/fake_page_storage.h"
#include "src/ledger/bin/storage/impl/btree/encoding.h"
#include "src/ledger/bin/storage/impl/storage_test_utils.h"
#include "src/ledger/bin/storage/public/constants.h"
#include "src/ledger/bin/storage/public/types.h"
#include "src/ledger/lib/callback/capture.h"
#include "src/ledger/lib/callback/set_when_called.h"
#include "src/ledger/lib/convert/convert.h"
#include "third_party/abseil-cpp/absl/strings/string_view.h"
namespace storage {
namespace btree {
namespace {
using ::testing::IsEmpty;
using ::testing::IsSupersetOf;
using ::testing::Pair;
using ::testing::UnorderedElementsAre;
class FakePageStorageValidDigest : public fake::FakePageStorage {
public:
using fake::FakePageStorage::FakePageStorage;
protected:
ObjectDigest FakeDigest(absl::string_view content) const override {
// BTree code needs storage to return valid digests.
return MakeObjectDigest(convert::ToString(content));
}
};
class TreeNodeTest : public StorageTest {
public:
TreeNodeTest() : fake_storage_(&environment_, "page_id") {}
TreeNodeTest(const TreeNodeTest&) = delete;
TreeNodeTest& operator=(const TreeNodeTest&) = delete;
~TreeNodeTest() override = default;
protected:
PageStorage* GetStorage() override { return &fake_storage_; }
std::unique_ptr<const TreeNode> CreateEmptyNode() {
ObjectIdentifier root_identifier;
EXPECT_TRUE(GetEmptyNodeIdentifier(&root_identifier));
std::unique_ptr<const TreeNode> node;
EXPECT_TRUE(CreateNodeFromIdentifier(root_identifier, PageStorage::Location::Local(), &node));
return node;
}
Entry GetEntry(const TreeNode* node, int index) {
Entry found_entry;
EXPECT_EQ(node->GetEntry(index, &found_entry), Status::OK);
return found_entry;
}
std::map<size_t, ObjectIdentifier> CreateChildren(int size) {
std::map<size_t, ObjectIdentifier> children;
for (int i = 0; i < size; ++i) {
children[i] = CreateEmptyNode()->GetIdentifier();
}
return children;
}
FakePageStorageValidDigest fake_storage_;
};
TEST_F(TreeNodeTest, CreateGetTreeNode) {
std::unique_ptr<const TreeNode> node = CreateEmptyNode();
bool called;
Status status;
std::unique_ptr<const TreeNode> found_node;
TreeNode::FromIdentifier(&fake_storage_, {node->GetIdentifier(), PageStorage::Location::Local()},
ledger::Capture(ledger::SetWhenCalled(&called), &status, &found_node));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
EXPECT_EQ(status, Status::OK);
EXPECT_NE(nullptr, found_node);
TreeNode::FromIdentifier(
&fake_storage_,
{RandomObjectIdentifier(environment_.random(), fake_storage_.GetObjectIdentifierFactory()),
PageStorage::Location::Local()},
ledger::Capture(ledger::SetWhenCalled(&called), &status, &found_node));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
EXPECT_EQ(status, Status::INTERNAL_NOT_FOUND);
}
TEST_F(TreeNodeTest, GetEntry) {
int size = 10;
std::vector<Entry> entries;
ASSERT_TRUE(CreateEntries(size, &entries));
std::unique_ptr<const TreeNode> node;
ASSERT_TRUE(CreateNodeFromEntries(entries, {}, &node));
EXPECT_EQ(node->GetKeyCount(), size);
for (int i = 0; i < size; ++i) {
EXPECT_EQ(GetEntry(node.get(), i), entries[i]);
}
}
TEST_F(TreeNodeTest, FindKeyOrChild) {
int size = 10;
std::vector<Entry> entries;
ASSERT_TRUE(CreateEntries(size, &entries));
std::unique_ptr<const TreeNode> node;
ASSERT_TRUE(CreateNodeFromEntries(entries, {}, &node));
int index;
EXPECT_EQ(node->FindKeyOrChild("key00", &index), Status::OK);
EXPECT_EQ(index, 0);
EXPECT_EQ(node->FindKeyOrChild("key02", &index), Status::OK);
EXPECT_EQ(index, 2);
EXPECT_EQ(node->FindKeyOrChild("key09", &index), Status::OK);
EXPECT_EQ(index, 9);
EXPECT_EQ(node->FindKeyOrChild("0", &index), Status::KEY_NOT_FOUND);
EXPECT_EQ(index, 0);
EXPECT_EQ(node->FindKeyOrChild("key001", &index), Status::KEY_NOT_FOUND);
EXPECT_EQ(index, 1);
EXPECT_EQ(node->FindKeyOrChild("key020", &index), Status::KEY_NOT_FOUND);
EXPECT_EQ(index, 3);
EXPECT_EQ(node->FindKeyOrChild("key999", &index), Status::KEY_NOT_FOUND);
EXPECT_EQ(index, 10);
}
TEST_F(TreeNodeTest, Serialization) {
int size = 3;
std::vector<Entry> entries;
ASSERT_TRUE(CreateEntries(size, &entries));
std::map<size_t, ObjectIdentifier> children = CreateChildren(size + 1);
std::unique_ptr<const TreeNode> node;
ASSERT_TRUE(CreateNodeFromEntries(entries, children, &node));
bool called;
Status status;
std::unique_ptr<const Object> object;
fake_storage_.GetObject(node->GetIdentifier(), PageStorage::Location::Local(),
ledger::Capture(ledger::SetWhenCalled(&called), &status, &object));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
EXPECT_EQ(status, Status::OK);
std::unique_ptr<const TreeNode> retrieved_node;
EXPECT_EQ(object->GetIdentifier(), node->GetIdentifier());
ASSERT_TRUE(CreateNodeFromIdentifier(node->GetIdentifier(), PageStorage::Location::Local(),
&retrieved_node));
absl::string_view data;
EXPECT_EQ(object->GetData(&data), Status::OK);
uint8_t level;
std::vector<Entry> parsed_entries;
std::map<size_t, ObjectIdentifier> parsed_children;
EXPECT_TRUE(DecodeNode(data, fake_storage_.GetObjectIdentifierFactory(), &level, &parsed_entries,
&parsed_children));
EXPECT_EQ(parsed_entries, entries);
EXPECT_EQ(parsed_children, children);
}
TEST_F(TreeNodeTest, References) {
// Create a BTree with the following layout (XX is key "keyXX"):
// [03, 07]
// / | \
// [00, 01, 02] [04, 05, 06] [08, 09, 10, 11]
// Each key XX points to "objectYY" with either a lazy or eager link. YY is
// chosen so as to create a number of collisions to test various edge cases
// (see actual values below and comments in test expectation).
// References to inline objects are ignored so we ensure object00 and object01
// are big enough not to be inlined.
std::unique_ptr<const Object> object0, object1, object2;
ASSERT_TRUE(AddObject(
ObjectData(fake_storage_.GetObjectIdentifierFactory(), "object00", InlineBehavior::PREVENT)
.value,
&object0));
ASSERT_TRUE(AddObject(
ObjectData(fake_storage_.GetObjectIdentifierFactory(), "object01", InlineBehavior::PREVENT)
.value,
&object1));
// Inline object, the references to it should be skipped.
ASSERT_TRUE(AddObject("object02", &object2));
const ObjectIdentifier object0_id = object0->GetIdentifier();
const ObjectIdentifier object1_id = object1->GetIdentifier();
const ObjectIdentifier inlined_object_id = object2->GetIdentifier();
const std::vector<Entry> entries = {
// A single node pointing to the same value with both eager and lazy
// links.
Entry{"key00", object0_id, KeyPriority::LAZY, EntryId("id00")},
Entry{"key01", object1_id, KeyPriority::EAGER, EntryId("id01")},
Entry{"key02", object0_id, KeyPriority::EAGER, EntryId("id02")},
Entry{"key03", object1_id, KeyPriority::LAZY, EntryId("id03")},
// Two lazy references for the same object.
Entry{"key04", object0_id, KeyPriority::LAZY, EntryId("id04")},
Entry{"key05", object1_id, KeyPriority::EAGER, EntryId("id05")},
Entry{"key06", object0_id, KeyPriority::LAZY, EntryId("id06")},
Entry{"key07", object1_id, KeyPriority::EAGER, EntryId("id07")},
// Two eager references for the same object, and an inlined object.
Entry{"key08", object0_id, KeyPriority::EAGER, EntryId("id08")},
Entry{"key09", object1_id, KeyPriority::LAZY, EntryId("id09")},
Entry{"key10", object0_id, KeyPriority::EAGER, EntryId("id10")},
Entry{"key11", inlined_object_id, KeyPriority::EAGER, EntryId("id11")}};
std::unique_ptr<const TreeNode> root, child0, child1, child2;
ASSERT_TRUE(CreateNodeFromEntries({entries[0], entries[1], entries[2]}, {}, &child0));
ASSERT_TRUE(CreateNodeFromEntries({entries[4], entries[5], entries[6]}, {}, &child1));
ASSERT_TRUE(
CreateNodeFromEntries({entries[8], entries[9], entries[10], entries[11]}, {}, &child2));
ASSERT_TRUE(CreateNodeFromEntries(
{entries[3], entries[7]},
{{0, child0->GetIdentifier()}, {1, child1->GetIdentifier()}, {2, child2->GetIdentifier()}},
&root));
const ObjectDigest digest0 = object0->GetIdentifier().object_digest();
const ObjectDigest digest1 = object1->GetIdentifier().object_digest();
// Check that references returned by each TreeNode are correct.
ObjectReferencesAndPriority references;
root->AppendReferences(&references);
EXPECT_THAT(references, UnorderedElementsAre(
// Keys
Pair(digest1, KeyPriority::LAZY), // key03
Pair(digest1, KeyPriority::EAGER), // key07
// Children
Pair(child0->GetIdentifier().object_digest(), KeyPriority::EAGER),
Pair(child1->GetIdentifier().object_digest(), KeyPriority::EAGER),
Pair(child2->GetIdentifier().object_digest(), KeyPriority::EAGER)));
references.clear();
child0->AppendReferences(&references);
EXPECT_THAT(references, UnorderedElementsAre(Pair(digest0, KeyPriority::LAZY), // key00
Pair(digest1, KeyPriority::EAGER), // key01
Pair(digest0, KeyPriority::EAGER) // key02
));
references.clear();
child1->AppendReferences(&references);
EXPECT_THAT(references, UnorderedElementsAre(Pair(digest0, KeyPriority::LAZY), // key04 and key06
Pair(digest1, KeyPriority::EAGER) // key05
));
references.clear();
child2->AppendReferences(&references);
EXPECT_THAT(references,
UnorderedElementsAre(Pair(digest0, KeyPriority::EAGER), // key08 and key10
Pair(digest1, KeyPriority::LAZY) // key09
// No reference to key11 (points to inline object02)
));
// Check that references have been correctly added to PageStorage during
// object creation.
EXPECT_THAT(fake_storage_.GetReferences(),
// All the pieces are small enough not to get split so we know all objects
// and can exhaustively enumerate references.
UnorderedElementsAre(
// References from the root piece.
Pair(root->GetIdentifier().object_digest(),
UnorderedElementsAre(
// Keys
Pair(digest1, KeyPriority::LAZY), // key03
Pair(digest1, KeyPriority::EAGER), // key07
// Children
Pair(child0->GetIdentifier().object_digest(), KeyPriority::EAGER),
Pair(child1->GetIdentifier().object_digest(), KeyPriority::EAGER),
Pair(child2->GetIdentifier().object_digest(), KeyPriority::EAGER))),
// References from each child, which don't have any children
// themselves, but reference values.
Pair(child0->GetIdentifier().object_digest(),
UnorderedElementsAre(Pair(digest0, KeyPriority::LAZY), // key00
Pair(digest1, KeyPriority::EAGER), // key01
Pair(digest0, KeyPriority::EAGER) // key02
)),
Pair(child1->GetIdentifier().object_digest(),
UnorderedElementsAre(Pair(digest0, KeyPriority::LAZY), // key04 and key06
Pair(digest1, KeyPriority::EAGER) // key05
)),
Pair(child2->GetIdentifier().object_digest(),
UnorderedElementsAre(Pair(digest0, KeyPriority::EAGER), // key08 and key10
Pair(digest1, KeyPriority::LAZY) // key09
// No reference to key11 (points to inline object02)
)),
// References from values, which don't have any children themselves.
Pair(object0->GetIdentifier().object_digest(), IsEmpty()),
Pair(object1->GetIdentifier().object_digest(), IsEmpty()),
Pair(object2->GetIdentifier().object_digest(), IsEmpty())));
}
} // namespace
} // namespace btree
} // namespace storage