blob: 6bf168cd8119c6740ce8bf03d6999e826f5931e8 [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/app/merging/common_ancestor.h"
#include <lib/callback/cancellable_helper.h>
#include <lib/callback/capture.h>
#include <lib/callback/set_when_called.h>
#include <lib/fit/function.h>
#include <algorithm>
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/ledger/bin/app/constants.h"
#include "src/ledger/bin/app/merging/test_utils.h"
#include "src/ledger/bin/encryption/primitives/hash.h"
#include "src/ledger/bin/storage/public/constants.h"
#include "src/ledger/bin/storage/public/page_storage.h"
#include "src/lib/fxl/macros.h"
using testing::ElementsAre;
using testing::IsEmpty;
using testing::UnorderedElementsAre;
namespace ledger {
namespace {
class CommonAncestorTest : public TestWithPageStorage {
public:
CommonAncestorTest() {}
~CommonAncestorTest() override {}
protected:
storage::PageStorage* page_storage() override { return storage_.get(); }
void SetUp() override {
TestWithPageStorage::SetUp();
ASSERT_TRUE(CreatePageStorage(&storage_));
}
std::unique_ptr<const storage::Commit> CreateCommit(
storage::CommitIdView parent_id,
fit::function<void(storage::Journal*)> contents) {
storage::Status status;
bool called;
std::unique_ptr<const storage::Commit> base;
storage_->GetCommit(
parent_id,
callback::Capture(callback::SetWhenCalled(&called), &status, &base));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, status);
std::unique_ptr<storage::Journal> journal =
storage_->StartCommit(std::move(base));
contents(journal.get());
std::unique_ptr<const storage::Commit> commit;
storage_->CommitJournal(
std::move(journal),
callback::Capture(callback::SetWhenCalled(&called), &status, &commit));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, status);
return commit;
}
std::unique_ptr<const storage::Commit> CreateMergeCommit(
std::unique_ptr<const storage::Commit> base_left,
std::unique_ptr<const storage::Commit> base_right,
fit::function<void(storage::Journal*)> contents) {
std::unique_ptr<storage::Journal> journal =
storage_->StartMergeCommit(std::move(base_left), std::move(base_right));
contents(journal.get());
storage::Status actual_status;
bool called;
std::unique_ptr<const storage::Commit> actual_commit;
storage_->CommitJournal(std::move(journal),
callback::Capture(callback::SetWhenCalled(&called),
&actual_status, &actual_commit));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, actual_status);
return actual_commit;
}
std::unique_ptr<const storage::Commit> GetRoot() {
bool called;
storage::Status status;
std::unique_ptr<const storage::Commit> root;
storage_->GetCommit(
storage::kFirstPageCommitId,
callback::Capture(callback::SetWhenCalled(&called), &status, &root));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, status);
return root;
}
std::vector<storage::CommitId> GetCommitIds(
const std::vector<std::unique_ptr<const storage::Commit>>& commits) {
std::vector<storage::CommitId> ids;
ids.reserve(commits.size());
for (auto& commit : commits) {
ids.push_back(commit->GetId());
}
return ids;
}
std::unique_ptr<storage::PageStorage> storage_;
private:
FXL_DISALLOW_COPY_AND_ASSIGN(CommonAncestorTest);
};
TEST_F(CommonAncestorTest, TwoChildrenOfRoot) {
std::unique_ptr<const storage::Commit> commit_1 = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "a"));
std::unique_ptr<const storage::Commit> commit_2 = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "b"));
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
storage::Status status;
CommitComparison comparison;
std::vector<std::unique_ptr<const storage::Commit>> result;
status = FindCommonAncestors(handler, storage_.get(), std::move(commit_1),
std::move(commit_2), &comparison, &result);
EXPECT_EQ(storage::Status::OK, status);
EXPECT_EQ(CommitComparison::UNORDERED, comparison);
EXPECT_THAT(GetCommitIds(result),
ElementsAre(storage::kFirstPageCommitId.ToString()));
});
}
TEST_F(CommonAncestorTest, RootAndChild) {
std::unique_ptr<const storage::Commit> root = GetRoot();
std::unique_ptr<const storage::Commit> child = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "a"));
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
storage::Status status;
CommitComparison comparison;
std::vector<std::unique_ptr<const storage::Commit>> result;
status = FindCommonAncestors(handler, storage_.get(), std::move(root),
std::move(child), &comparison, &result);
EXPECT_EQ(storage::Status::OK, status);
EXPECT_EQ(CommitComparison::LEFT_SUBSET_OF_RIGHT, comparison);
EXPECT_THAT(result, IsEmpty());
});
}
TEST_F(CommonAncestorTest, ChildAndRoot) {
std::unique_ptr<const storage::Commit> root = GetRoot();
std::unique_ptr<const storage::Commit> child = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "a"));
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
storage::Status status;
CommitComparison comparison;
std::vector<std::unique_ptr<const storage::Commit>> result;
status = FindCommonAncestors(handler, storage_.get(), std::move(child),
std::move(root), &comparison, &result);
EXPECT_EQ(storage::Status::OK, status);
EXPECT_EQ(CommitComparison::RIGHT_SUBSET_OF_LEFT, comparison);
EXPECT_THAT(result, IsEmpty());
});
}
// In this test the commits have the following structure:
// (root)
// / \
// (A) (B)
// / \ / \
// (1) (merge) (2)
TEST_F(CommonAncestorTest, MergeCommitAndSomeOthers) {
std::unique_ptr<const storage::Commit> commit_a = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "a"));
std::unique_ptr<const storage::Commit> commit_b = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "b"));
std::unique_ptr<const storage::Commit> commit_merge = CreateMergeCommit(
commit_a->Clone(), commit_b->Clone(), AddKeyValueToJournal("key", "c"));
std::unique_ptr<const storage::Commit> commit_1 =
CreateCommit(commit_a->GetId(), AddKeyValueToJournal("key", "1"));
std::unique_ptr<const storage::Commit> commit_2 =
CreateCommit(commit_b->GetId(), AddKeyValueToJournal("key", "2"));
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
// LCA of (1) and (merge) needs to be (A).
storage::Status status;
CommitComparison comparison;
std::vector<std::unique_ptr<const storage::Commit>> result;
status = FindCommonAncestors(handler, storage_.get(), std::move(commit_1),
std::move(commit_merge), &comparison, &result);
EXPECT_EQ(storage::Status::OK, status);
EXPECT_EQ(CommitComparison::UNORDERED, comparison);
EXPECT_THAT(GetCommitIds(result), ElementsAre(commit_a->GetId()));
// LCA of (2) and (A) is (root).
result.clear();
status = FindCommonAncestors(handler, storage_.get(), std::move(commit_2),
std::move(commit_a), &comparison, &result);
EXPECT_EQ(storage::Status::OK, status);
EXPECT_EQ(CommitComparison::UNORDERED, comparison);
EXPECT_THAT(GetCommitIds(result),
ElementsAre(storage::kFirstPageCommitId.ToString()));
});
}
// Regression test for LE-187.
TEST_F(CommonAncestorTest, LongChain) {
const int length = 180;
std::unique_ptr<const storage::Commit> commit_a = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "a"));
std::unique_ptr<const storage::Commit> commit_b = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "b"));
std::unique_ptr<const storage::Commit> last_commit = std::move(commit_a);
for (int i = 0; i < length; i++) {
last_commit = CreateCommit(last_commit->GetId(),
AddKeyValueToJournal(std::to_string(i), "val"));
}
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
// Ancestor of (last commit) and (b) needs to be (root).
storage::Status status;
CommitComparison comparison;
std::vector<std::unique_ptr<const storage::Commit>> result;
status =
FindCommonAncestors(handler, storage_.get(), std::move(last_commit),
std::move(commit_b), &comparison, &result);
// This test lasts ~2.5s on x86+qemu+kvm.
EXPECT_EQ(storage::Status::OK, status);
EXPECT_EQ(CommitComparison::UNORDERED, comparison);
EXPECT_THAT(GetCommitIds(result),
ElementsAre(storage::kFirstPageCommitId.ToString()));
});
}
// Test detection of equivalent commits.
// In this test the commits have the following structure:
// (root)
// / \
// (A) (B)
// |\ /|
// | X |
// |/ \|
// (M) (N)
// Requesting the common ancestors of (M) and (N) should return an empty vector
// and EQUIVALENT.
TEST_F(CommonAncestorTest, EquivalentCommits) {
std::unique_ptr<const storage::Commit> commit_a = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "a"));
std::unique_ptr<const storage::Commit> commit_b = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "b"));
std::unique_ptr<const storage::Commit> commit_m = CreateMergeCommit(
commit_a->Clone(), commit_b->Clone(), AddKeyValueToJournal("key", "m"));
std::unique_ptr<const storage::Commit> commit_n =
CreateMergeCommit(std::move(commit_a), std::move(commit_b),
AddKeyValueToJournal("key", "n"));
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
storage::Status status;
CommitComparison comparison;
std::vector<std::unique_ptr<const storage::Commit>> result;
status = FindCommonAncestors(handler, storage_.get(), std::move(commit_m),
std::move(commit_n), &comparison, &result);
EXPECT_EQ(storage::Status::OK, status);
EXPECT_EQ(CommitComparison::EQUIVALENT, comparison);
EXPECT_THAT(GetCommitIds(result), IsEmpty());
});
}
// Multiple common ancestors from the same generation
// In this test, the commits have the following structure:
// (root)
// / \
// (A) (B)
// | \ / |
// (C) \/ (D)
// | /\ |
// | / \ |
// (E) (F)
// Then the common ancestors of (E) and (F) are (A) and (B).
TEST_F(CommonAncestorTest, TwoBasesSameGeneration) {
std::unique_ptr<const storage::Commit> commit_a = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "a"));
std::unique_ptr<const storage::Commit> commit_b = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "b"));
std::unique_ptr<const storage::Commit> commit_c =
CreateCommit(commit_a->GetId(), AddKeyValueToJournal("key", "c"));
std::unique_ptr<const storage::Commit> commit_d =
CreateCommit(commit_b->GetId(), AddKeyValueToJournal("key", "d"));
std::unique_ptr<const storage::Commit> commit_e = CreateMergeCommit(
commit_c->Clone(), commit_b->Clone(), AddKeyValueToJournal("key", "e"));
std::unique_ptr<const storage::Commit> commit_f = CreateMergeCommit(
commit_a->Clone(), std::move(commit_d), AddKeyValueToJournal("key", "f"));
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
// The LCAs of (E) and (F) are (A) and (B)
storage::Status status;
CommitComparison comparison;
std::vector<std::unique_ptr<const storage::Commit>> result;
status = FindCommonAncestors(handler, storage_.get(), std::move(commit_e),
std::move(commit_f), &comparison, &result);
EXPECT_EQ(storage::Status::OK, status);
EXPECT_EQ(CommitComparison::UNORDERED, comparison);
EXPECT_THAT(GetCommitIds(result),
UnorderedElementsAre(commit_a->GetId(), commit_b->GetId()));
});
}
// Merges with multiple common ancestors from different generations
// In this test, the commits have the following structure:
// (root)
// / \
// | (X)
// | |
// (A) (B)
// | \ / |
// (C) \/ (D)
// | /\ |
// | / \ |
// (E) (F)
// The LCAs of (E) and (F) are (A) and (B).
TEST_F(CommonAncestorTest, TwoBasesDifferentGenerations) {
std::unique_ptr<const storage::Commit> commit_a = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "a"));
std::unique_ptr<const storage::Commit> commit_x = CreateCommit(
storage::kFirstPageCommitId, AddKeyValueToJournal("key", "x"));
std::unique_ptr<const storage::Commit> commit_b =
CreateCommit(commit_x->GetId(), AddKeyValueToJournal("key", "b"));
std::unique_ptr<const storage::Commit> commit_c =
CreateCommit(commit_a->GetId(), AddKeyValueToJournal("key", "c"));
std::unique_ptr<const storage::Commit> commit_d =
CreateCommit(commit_b->GetId(), AddKeyValueToJournal("key", "d"));
std::unique_ptr<const storage::Commit> commit_e = CreateMergeCommit(
std::move(commit_c), commit_b->Clone(), AddKeyValueToJournal("key", "e"));
std::unique_ptr<const storage::Commit> commit_f = CreateMergeCommit(
commit_a->Clone(), std::move(commit_d), AddKeyValueToJournal("key", "f"));
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
// The LCAs of (E) and (F) are (A) and (B)
storage::Status status;
CommitComparison comparison;
std::vector<std::unique_ptr<const storage::Commit>> result;
status = FindCommonAncestors(handler, storage_.get(), std::move(commit_e),
std::move(commit_f), &comparison, &result);
EXPECT_EQ(storage::Status::OK, status);
EXPECT_EQ(CommitComparison::UNORDERED, comparison);
EXPECT_THAT(GetCommitIds(result),
UnorderedElementsAre(commit_a->GetId(), commit_b->GetId()));
});
}
} // namespace
} // namespace ledger