blob: b4762120b7626cf49f397cbc666ae7a2a9b108e1 [file] [log] [blame]
// Copyright 2017 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/diff.h"
#include <lib/callback/capture.h>
#include <lib/callback/set_when_called.h>
#include "gtest/gtest.h"
#include "src/ledger/bin/environment/environment.h"
#include "src/ledger/bin/storage/fake/fake_page_storage.h"
#include "src/ledger/bin/storage/impl/btree/builder.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/lib/fxl/macros.h"
namespace storage {
namespace btree {
namespace {
std::unique_ptr<Entry> CreateEntryPtr(std::string key,
ObjectIdentifier object_identifier,
KeyPriority priority) {
auto e = std::make_unique<Entry>();
e->key = key;
e->object_identifier = std::move(object_identifier);
e->priority = priority;
return e;
}
std::unique_ptr<Entry> CreateEntryPtr() { return std::unique_ptr<Entry>(); }
class FakePageStorageValidDigest : public fake::FakePageStorage {
public:
using fake::FakePageStorage::FakePageStorage;
protected:
ObjectDigest FakeDigest(fxl::StringView content) const override {
// BTree code needs storage to return valid digests.
return MakeObjectDigest(content.ToString());
}
};
class DiffTest : public StorageTest {
public:
DiffTest() : fake_storage_(&environment_, "page_id") {}
~DiffTest() override {}
protected:
PageStorage* GetStorage() override { return &fake_storage_; }
ObjectIdentifier CreateTree(const std::vector<EntryChange>& entries) {
ObjectIdentifier root_identifier;
EXPECT_TRUE(GetEmptyNodeIdentifier(&root_identifier));
ObjectIdentifier identifier;
EXPECT_TRUE(CreateTreeFromChanges(root_identifier, entries, &identifier));
return identifier;
}
FakePageStorageValidDigest fake_storage_;
private:
FXL_DISALLOW_COPY_AND_ASSIGN(DiffTest);
};
TEST_F(DiffTest, ForEachDiff) {
std::unique_ptr<const Object> object;
ASSERT_TRUE(AddObject("change1", &object));
ObjectIdentifier object_identifier = object->GetIdentifier();
std::vector<EntryChange> base_changes;
ASSERT_TRUE(CreateEntryChanges(50, &base_changes));
ObjectIdentifier base_root_identifier = CreateTree(base_changes);
std::vector<EntryChange> other_changes;
// Update value for key1.
other_changes.push_back(
EntryChange{Entry{"key1", object_identifier, KeyPriority::LAZY}, false});
// Add entry key255.
other_changes.push_back(EntryChange{
Entry{"key255", object_identifier, KeyPriority::LAZY}, false});
// Remove entry key40.
other_changes.push_back(
EntryChange{Entry{"key40", {}, KeyPriority::LAZY}, true});
ObjectIdentifier other_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, other_changes,
&other_root_identifier));
// ForEachDiff should return all changes just applied.
bool called;
Status status;
size_t current_change = 0;
ForEachDiff(
environment_.coroutine_service(), &fake_storage_, base_root_identifier,
other_root_identifier, "",
[&other_changes, &current_change](EntryChange e) {
EXPECT_EQ(other_changes[current_change].deleted, e.deleted);
if (e.deleted) {
EXPECT_EQ(other_changes[current_change].entry.key, e.entry.key);
} else {
EXPECT_EQ(other_changes[current_change].entry, e.entry);
}
++current_change;
return true;
},
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
ASSERT_EQ(Status::OK, status);
EXPECT_EQ(other_changes.size(), current_change);
}
TEST_F(DiffTest, ForEachDiffWithMinKey) {
// Expected base tree layout (XX is key "keyXX"):
// [50]
// / \
// [03, 07, 30] [65, 76]
// /
// [01, 02]
std::vector<EntryChange> base_entries;
ASSERT_TRUE(CreateEntryChanges(
std::vector<size_t>({1, 2, 3, 7, 30, 50, 65, 76}), &base_entries));
// Expected other tree layout (XX is key "keyXX"):
// [50, 75]
// / | \
// [03, 07, 30] [65] [76]
// / /
// [01, 02] [51]
std::vector<EntryChange> changes;
ASSERT_TRUE(CreateEntryChanges(std::vector<size_t>({51, 75}), &changes));
bool called;
Status status;
ObjectIdentifier base_root_identifier = CreateTree(base_entries);
ObjectIdentifier other_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, changes,
&other_root_identifier));
// ForEachDiff with a "key0" as min_key should return both changes.
size_t current_change = 0;
ForEachDiff(
environment_.coroutine_service(), &fake_storage_, base_root_identifier,
other_root_identifier, "key0",
[&changes, &current_change](EntryChange e) {
EXPECT_EQ(changes[current_change++].entry, e.entry);
return true;
},
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
ASSERT_EQ(Status::OK, status);
EXPECT_EQ(changes.size(), current_change);
// With "key60" as min_key, only key75 should be returned.
ForEachDiff(
environment_.coroutine_service(), &fake_storage_, base_root_identifier,
other_root_identifier, "key60",
[&changes](EntryChange e) {
EXPECT_EQ(changes[1].entry, e.entry);
return true;
},
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
ASSERT_EQ(Status::OK, status);
}
TEST_F(DiffTest, ForEachDiffWithMinKeySkipNodes) {
// Expected base tree layout (XX is key "keyXX"):
// [03, 07, 30]
// /
// [01, 02]
std::vector<EntryChange> base_entries;
ASSERT_TRUE(
CreateEntryChanges(std::vector<size_t>({1, 2, 3, 7, 30}), &base_entries));
// Expected other tree layout (XX is key "keyXX"):
// [50]
// /
// [03, 07, 30]
// /
// [01, 02]
std::vector<EntryChange> changes;
ASSERT_TRUE(CreateEntryChanges(std::vector<size_t>({50}), &changes));
bool called;
Status status;
ObjectIdentifier base_root_identifier = CreateTree(base_entries);
ObjectIdentifier other_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, changes,
&other_root_identifier));
ForEachDiff(
environment_.coroutine_service(), &fake_storage_, base_root_identifier,
other_root_identifier, "key01",
[&changes](EntryChange e) {
EXPECT_EQ(changes[0].entry, e.entry);
return true;
},
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
ASSERT_EQ(Status::OK, status);
}
TEST_F(DiffTest, ForEachDiffPriorityChange) {
std::vector<EntryChange> base_changes;
ASSERT_TRUE(CreateEntryChanges(50, &base_changes));
ObjectIdentifier base_root_identifier = CreateTree(base_changes);
Entry base_entry = base_changes[10].entry;
std::vector<EntryChange> other_changes;
// Update priority for a key.
other_changes.push_back(EntryChange{
Entry{base_entry.key, base_entry.object_identifier, KeyPriority::LAZY},
false});
bool called;
Status status;
ObjectIdentifier other_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, other_changes,
&other_root_identifier));
// ForEachDiff should return all changes just applied.
size_t change_count = 0;
EntryChange actual_change;
ForEachDiff(
environment_.coroutine_service(), &fake_storage_, base_root_identifier,
other_root_identifier, "",
[&actual_change, &change_count](EntryChange e) {
actual_change = e;
++change_count;
return true;
},
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
ASSERT_EQ(Status::OK, status);
EXPECT_EQ(1u, change_count);
EXPECT_FALSE(actual_change.deleted);
EXPECT_EQ(base_entry.key, actual_change.entry.key);
EXPECT_EQ(base_entry.object_identifier,
actual_change.entry.object_identifier);
EXPECT_EQ(KeyPriority::LAZY, actual_change.entry.priority);
}
TEST_F(DiffTest, ForEachThreeWayDiff) {
// Base tree.
std::vector<EntryChange> base_changes;
ASSERT_TRUE(CreateEntryChanges(50, &base_changes));
ObjectIdentifier base_object01_identifier =
base_changes[1].entry.object_identifier;
ObjectIdentifier base_object02_identifier =
base_changes[2].entry.object_identifier;
ObjectIdentifier base_object40_identifier =
base_changes[40].entry.object_identifier;
ObjectIdentifier base_root_identifier = CreateTree(base_changes);
std::unique_ptr<const Object> object;
ASSERT_TRUE(AddObject("change1", &object));
ObjectIdentifier object_identifier = object->GetIdentifier();
// Left tree.
std::vector<EntryChange> left_changes;
// Update value for key1.
left_changes.push_back(
EntryChange{Entry{"key01", object_identifier, KeyPriority::LAZY}, false});
// Add entry key255.
left_changes.push_back(EntryChange{
Entry{"key255", object_identifier, KeyPriority::LAZY}, false});
// Remove entry key40.
left_changes.push_back(
EntryChange{Entry{"key40", {}, KeyPriority::LAZY}, true});
ObjectIdentifier left_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, left_changes,
&left_root_identifier));
// Right tree.
std::unique_ptr<const Object> object2;
ASSERT_TRUE(AddObject("change2", &object2));
ObjectIdentifier object_identifier2 = object2->GetIdentifier();
std::vector<EntryChange> right_changes;
// Update to same value for key1.
right_changes.push_back(
EntryChange{Entry{"key01", object_identifier, KeyPriority::LAZY}, false});
// Update to different value for key2
right_changes.push_back(EntryChange{
Entry{"key02", object_identifier2, KeyPriority::LAZY}, false});
// Add entry key258.
right_changes.push_back(EntryChange{
Entry{"key258", object_identifier, KeyPriority::LAZY}, false});
ObjectIdentifier right_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, right_changes,
&right_root_identifier));
std::vector<ThreeWayChange> expected_three_way_changes;
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr("key01", base_object01_identifier, KeyPriority::EAGER),
CreateEntryPtr("key01", object_identifier, KeyPriority::LAZY),
CreateEntryPtr("key01", object_identifier, KeyPriority::LAZY)});
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr("key02", base_object02_identifier, KeyPriority::EAGER),
CreateEntryPtr("key02", base_object02_identifier, KeyPriority::EAGER),
CreateEntryPtr("key02", object_identifier2, KeyPriority::LAZY)});
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr(),
CreateEntryPtr("key255", object_identifier, KeyPriority::LAZY),
CreateEntryPtr()});
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr(), CreateEntryPtr(),
CreateEntryPtr("key258", object_identifier, KeyPriority::LAZY)});
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr("key40", base_object40_identifier, KeyPriority::EAGER),
CreateEntryPtr(),
CreateEntryPtr("key40", base_object40_identifier, KeyPriority::EAGER)});
bool called;
Status status;
unsigned int current_change = 0;
ForEachThreeWayDiff(
environment_.coroutine_service(), &fake_storage_, base_root_identifier,
left_root_identifier, right_root_identifier, "",
[&expected_three_way_changes, &current_change](ThreeWayChange e) {
EXPECT_LT(current_change, expected_three_way_changes.size());
if (current_change >= expected_three_way_changes.size()) {
return false;
}
EXPECT_EQ(expected_three_way_changes[current_change], e);
current_change++;
return true;
},
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
ASSERT_EQ(Status::OK, status);
EXPECT_EQ(current_change, expected_three_way_changes.size());
}
TEST_F(DiffTest, ForEachThreeWayDiffMinKey) {
// Base tree.
std::vector<EntryChange> base_changes;
ASSERT_TRUE(CreateEntryChanges(50, &base_changes));
ObjectIdentifier base_object01_identifier =
base_changes[1].entry.object_identifier;
ObjectIdentifier base_object02_identifier =
base_changes[2].entry.object_identifier;
ObjectIdentifier base_object40_identifier =
base_changes[40].entry.object_identifier;
ObjectIdentifier base_root_identifier = CreateTree(base_changes);
std::unique_ptr<const Object> object;
ASSERT_TRUE(AddObject("change1", &object));
ObjectIdentifier object_identifier = object->GetIdentifier();
// Left tree.
std::vector<EntryChange> left_changes;
// Update value for key1.
left_changes.push_back(
EntryChange{Entry{"key01", object_identifier, KeyPriority::LAZY}, false});
// Add entry key255.
left_changes.push_back(EntryChange{
Entry{"key255", object_identifier, KeyPriority::LAZY}, false});
// Remove entry key40.
left_changes.push_back(
EntryChange{Entry{"key40", {}, KeyPriority::LAZY}, true});
ObjectIdentifier left_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, left_changes,
&left_root_identifier));
// Right tree.
std::unique_ptr<const Object> object2;
ASSERT_TRUE(AddObject("change2", &object2));
ObjectIdentifier object_identifier2 = object2->GetIdentifier();
std::vector<EntryChange> right_changes;
// Update to same value for key1.
right_changes.push_back(
EntryChange{Entry{"key01", object_identifier, KeyPriority::LAZY}, false});
// Update to different value for key2
right_changes.push_back(EntryChange{
Entry{"key02", object_identifier2, KeyPriority::LAZY}, false});
// Add entry key258.
right_changes.push_back(EntryChange{
Entry{"key258", object_identifier, KeyPriority::LAZY}, false});
ObjectIdentifier right_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, right_changes,
&right_root_identifier));
std::vector<ThreeWayChange> expected_three_way_changes;
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr(), CreateEntryPtr(),
CreateEntryPtr("key258", object_identifier, KeyPriority::LAZY)});
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr("key40", base_object40_identifier, KeyPriority::EAGER),
CreateEntryPtr(),
CreateEntryPtr("key40", base_object40_identifier, KeyPriority::EAGER)});
bool called;
Status status;
unsigned int current_change = 0;
ForEachThreeWayDiff(
environment_.coroutine_service(), &fake_storage_, base_root_identifier,
left_root_identifier, right_root_identifier, "key257",
[&expected_three_way_changes, &current_change](ThreeWayChange e) {
EXPECT_LT(current_change, expected_three_way_changes.size());
if (current_change >= expected_three_way_changes.size()) {
return false;
}
EXPECT_EQ(expected_three_way_changes[current_change], e);
current_change++;
return true;
},
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
ASSERT_EQ(Status::OK, status);
EXPECT_EQ(current_change, expected_three_way_changes.size());
}
TEST_F(DiffTest, ForEachThreeWayDiffNoDiff) {
// Base tree.
std::vector<EntryChange> base_changes;
ASSERT_TRUE(CreateEntryChanges(50, &base_changes));
ObjectIdentifier base_object01_identifier =
base_changes[1].entry.object_identifier;
ObjectIdentifier base_object02_identifier =
base_changes[2].entry.object_identifier;
ObjectIdentifier base_object40_identifier =
base_changes[40].entry.object_identifier;
ObjectIdentifier base_root_identifier = CreateTree(base_changes);
std::unique_ptr<const Object> object;
ASSERT_TRUE(AddObject("change1", &object));
ObjectIdentifier object_identifier = object->GetIdentifier();
// Left tree.
std::vector<EntryChange> left_changes;
// Update value for key1.
left_changes.push_back(
EntryChange{Entry{"key01", object_identifier, KeyPriority::LAZY}, false});
// Add entry key255.
left_changes.push_back(EntryChange{
Entry{"key255", object_identifier, KeyPriority::LAZY}, false});
// Remove entry key40.
left_changes.push_back(
EntryChange{Entry{"key40", {}, KeyPriority::LAZY}, true});
ObjectIdentifier left_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, left_changes,
&left_root_identifier));
// Right tree.
std::unique_ptr<const Object> object2;
ASSERT_TRUE(AddObject("change2", &object2));
ObjectIdentifier object_identifier2 = object2->GetIdentifier();
std::vector<EntryChange> right_changes;
// Update to same value for key1.
right_changes.push_back(
EntryChange{Entry{"key01", object_identifier, KeyPriority::LAZY}, false});
// Update to different value for key2
right_changes.push_back(EntryChange{
Entry{"key02", object_identifier2, KeyPriority::LAZY}, false});
// Add entry key258.
right_changes.push_back(EntryChange{
Entry{"key258", object_identifier, KeyPriority::LAZY}, false});
ObjectIdentifier right_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, right_changes,
&right_root_identifier));
bool called;
Status status;
// No change is expected.
ForEachThreeWayDiff(
environment_.coroutine_service(), &fake_storage_, base_root_identifier,
left_root_identifier, right_root_identifier, "key5",
[](ThreeWayChange e) {
ADD_FAILURE();
return true;
},
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
ASSERT_EQ(Status::OK, status);
}
TEST_F(DiffTest, ForEachThreeWayNoBaseChange) {
// Base tree.
std::vector<EntryChange> base_changes;
ObjectIdentifier base_root_identifier = CreateTree(base_changes);
std::unique_ptr<const Object> object1, object2, object3, object4;
ASSERT_TRUE(AddObject("change1", &object1));
ObjectIdentifier object1_identifier = object1->GetIdentifier();
ASSERT_TRUE(AddObject("change2", &object2));
ObjectIdentifier object2_identifier = object2->GetIdentifier();
ASSERT_TRUE(AddObject("change3", &object3));
ObjectIdentifier object3_identifier = object3->GetIdentifier();
ASSERT_TRUE(AddObject("change4", &object4));
ObjectIdentifier object4_identifier = object4->GetIdentifier();
// Left tree.
std::vector<EntryChange> left_changes;
left_changes.push_back(EntryChange{
Entry{"key01", object1_identifier, KeyPriority::EAGER}, false});
left_changes.push_back(EntryChange{
Entry{"key03", object3_identifier, KeyPriority::EAGER}, false});
ObjectIdentifier left_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, left_changes,
&left_root_identifier));
// Right tree.
std::vector<EntryChange> right_changes;
right_changes.push_back(EntryChange{
Entry{"key02", object2_identifier, KeyPriority::EAGER}, false});
right_changes.push_back(EntryChange{
Entry{"key04", object4_identifier, KeyPriority::EAGER}, false});
ObjectIdentifier right_root_identifier;
ASSERT_TRUE(CreateTreeFromChanges(base_root_identifier, right_changes,
&right_root_identifier));
std::vector<ThreeWayChange> expected_three_way_changes;
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr(),
CreateEntryPtr("key01", object1_identifier, KeyPriority::EAGER),
CreateEntryPtr()});
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr(), CreateEntryPtr(),
CreateEntryPtr("key02", object2_identifier, KeyPriority::EAGER)});
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr(),
CreateEntryPtr("key03", object3_identifier, KeyPriority::EAGER),
CreateEntryPtr()});
expected_three_way_changes.push_back(ThreeWayChange{
CreateEntryPtr(), CreateEntryPtr(),
CreateEntryPtr("key04", object4_identifier, KeyPriority::EAGER)});
bool called;
Status status;
unsigned int current_change = 0;
ForEachThreeWayDiff(
environment_.coroutine_service(), &fake_storage_, base_root_identifier,
left_root_identifier, right_root_identifier, "",
[&expected_three_way_changes, &current_change](ThreeWayChange e) {
EXPECT_LT(current_change, expected_three_way_changes.size());
if (current_change >= expected_three_way_changes.size()) {
return false;
}
EXPECT_EQ(expected_three_way_changes[current_change], e);
current_change++;
return true;
},
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopFor(kSufficientDelay);
EXPECT_TRUE(called);
ASSERT_EQ(Status::OK, status);
EXPECT_EQ(current_change, expected_three_way_changes.size());
}
} // namespace
} // namespace btree
} // namespace storage