blob: 415fbf9b2ae49cbdbf9b0fd45136ec95afb1fc6b [file] [log] [blame]
// Copyright 2019 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/commit_pruner.h"
#include <utility>
#include <vector>
// gtest matchers are in gmock and we cannot include the specific header file
// directly as it is private to the library.
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/ledger/bin/storage/fake/fake_object_identifier_factory.h"
#include "src/ledger/bin/storage/impl/commit_random_impl.h"
#include "src/ledger/bin/storage/impl/storage_test_utils.h"
#include "src/ledger/bin/storage/public/constants.h"
#include "src/ledger/bin/storage/testing/page_storage_empty_impl.h"
#include "src/ledger/lib/convert/convert.h"
#include "src/ledger/lib/rng/random.h"
using ::testing::_;
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::Field;
using ::testing::IsEmpty;
using ::testing::Pair;
using ::testing::SizeIs;
using ::testing::VariantWith;
namespace storage {
namespace {
testing::Matcher<const Clock&> ClockMatchesCommit(
testing::Matcher<const clocks::DeviceId&> device_id, const Commit& commit) {
return Contains(
Pair(std::move(device_id),
VariantWith<DeviceEntry>(Field(
"head", &DeviceEntry::head,
AllOf(Field("commit_id", &ClockEntry::commit_id, commit.GetId()),
Field("generation", &ClockEntry::generation, commit.GetGeneration()))))));
}
class FakeCommitTracker : public LiveCommitTracker {
public:
FakeCommitTracker() = default;
~FakeCommitTracker() override = default;
// Returns a copy of every currently live/tracked commit.
std::vector<std::unique_ptr<const Commit>> GetLiveCommits() const override {
std::vector<std::unique_ptr<const Commit>> result;
for (const Commit* commit : current_live_commits_) {
result.push_back(commit->Clone());
}
return result;
}
void SetLiveCommits(std::vector<const Commit*> current_live_commits) {
current_live_commits_ = std::move(current_live_commits);
}
private:
std::vector<const Commit*> current_live_commits_;
};
class FakeCommitPrunerDelegate : public CommitPruner::CommitPrunerDelegate {
public:
void AddCommit(std::unique_ptr<const Commit> commit) {
CommitId id = commit->GetId();
commits_[std::move(id)] = std::move(commit);
}
void GetCommit(CommitIdView commit_id,
fit::function<void(Status, std::unique_ptr<const Commit>)> callback) override {
auto it = commits_.find(commit_id);
if (it == commits_.end()) {
callback(Status::INTERNAL_NOT_FOUND, nullptr);
return;
}
callback(Status::OK, it->second->Clone());
}
Status DeleteCommits(coroutine::CoroutineHandler* handler,
std::vector<std::unique_ptr<const Commit>> commits) override {
Status status;
std::vector<CommitId> commit_ids;
commit_ids.reserve(commits.size());
std::transform(commits.begin(), commits.end(), std::back_inserter(commit_ids),
[](const auto& commit) { return commit->GetId(); });
if (coroutine::SyncCall(
handler,
[this, commits = std::move(commits)](fit::function<void(Status)> callback) mutable {
delete_commit_calls_.emplace_back(std::move(commits), std::move(callback));
},
&status) == coroutine::ContinuationStatus::INTERRUPTED) {
return Status::INTERRUPTED;
}
for (const auto& id : commit_ids) {
commits_.erase(id);
}
return status;
}
std::vector<std::pair<std::vector<std::unique_ptr<const Commit>>, fit::function<void(Status)>>>
delete_commit_calls_;
Status SetClock(coroutine::CoroutineHandler* handler, const Clock& clock) override {
clocks_.push_back(clock);
return Status::OK;
};
std::vector<Clock> clocks_;
private:
std::map<CommitId, std::unique_ptr<const Commit>, convert::StringViewComparator> commits_;
};
class CommitPrunerTest : public ledger::TestWithEnvironment {
public:
CommitPrunerTest() = default;
CommitPrunerTest(const CommitPrunerTest&) = delete;
CommitPrunerTest& operator=(const CommitPrunerTest&) = delete;
~CommitPrunerTest() override = default;
};
TEST_F(CommitPrunerTest, NoPruningPolicy) {
FakeCommitTracker commit_tracker;
FakeCommitPrunerDelegate storage;
fake::FakeObjectIdentifierFactory factory;
CommitPruner pruner(&environment_, &storage, &commit_tracker, CommitPruningPolicy::NEVER);
// Add some commits.
std::unique_ptr<const Commit> commit_0 =
std::make_unique<const CommitRandomImpl>(environment_.random(), &factory);
storage.AddCommit(std::move(commit_0));
std::unique_ptr<Commit> commit_1 =
std::make_unique<CommitRandomImpl>(environment_.random(), &factory);
Commit* commit_1_ptr = commit_1.get();
storage.AddCommit(std::move(commit_1));
std::unique_ptr<Commit> commit_2 =
std::make_unique<CommitRandomImpl>(environment_.random(), &factory);
commit_tracker.SetLiveCommits({commit_1_ptr, commit_2.get()});
storage.AddCommit(std::move(commit_2));
pruner.SchedulePruning();
RunLoopUntilIdle();
EXPECT_THAT(storage.delete_commit_calls_, IsEmpty());
}
class FakeCommit : public CommitRandomImpl {
public:
FakeCommit(ledger::Random* random, ObjectIdentifierFactory* factory, CommitId parent,
uint64_t generation)
: CommitRandomImpl(random, factory), generation_(generation) {
parents_.push_back(std::move(parent));
}
FakeCommit(ledger::Random* random, ObjectIdentifierFactory* factory, CommitId parent_1,
CommitId parent_2, uint64_t generation)
: CommitRandomImpl(random, factory), generation_(generation) {
parents_.push_back(std::move(parent_1));
parents_.push_back(std::move(parent_2));
}
FakeCommit(const FakeCommit& other) = default;
FakeCommit& operator=(const FakeCommit& other) = default;
std::vector<CommitIdView> GetParentIds() const override {
std::vector<CommitIdView> result;
for (const CommitId& commit_id : parents_) {
result.emplace_back(commit_id);
}
return result;
}
uint64_t GetGeneration() const override { return generation_; }
std::unique_ptr<const Commit> Clone() const override {
return std::make_unique<FakeCommit>(*this);
}
private:
std::vector<CommitId> parents_;
uint64_t generation_;
};
// Verify that only commits before the latest unique common ancestor are pruned. Here, we have the
// following commit graph:
// 0
// |
// 1
// / \
// 2 3
// \ /
// 4
// where commits 0, 2, 3 and 4 are live. No commit should be pruned.
TEST_F(CommitPrunerTest, PruneBeforeLucaNoPruning) {
FakeCommitTracker commit_tracker;
FakeCommitPrunerDelegate storage;
fake::FakeObjectIdentifierFactory factory;
CommitPruner pruner(&environment_, &storage, &commit_tracker,
CommitPruningPolicy::LOCAL_IMMEDIATE);
// Add some commits. The parent of commit 0 does not exist in the database.
std::unique_ptr<Commit> commit_0 =
std::make_unique<FakeCommit>(environment_.random(), &factory, "random_commit_id", 10);
CommitId commit_id_0 = commit_0->GetId();
Commit* commit_0_ptr = commit_0.get();
storage.AddCommit(std::move(commit_0));
std::unique_ptr<Commit> commit_1 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_0, 11);
CommitId commit_id_1 = commit_1->GetId();
storage.AddCommit(std::move(commit_1));
std::unique_ptr<Commit> commit_2 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_1, 12);
CommitId commit_id_2 = commit_2->GetId();
Commit* commit_2_ptr = commit_2.get();
storage.AddCommit(std::move(commit_2));
std::unique_ptr<Commit> commit_3 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_1, 12);
CommitId commit_id_3 = commit_3->GetId();
Commit* commit_3_ptr = commit_3.get();
storage.AddCommit(std::move(commit_3));
std::unique_ptr<Commit> commit_4 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_2, commit_id_3, 13);
Commit* commit_4_ptr = commit_4.get();
commit_tracker.SetLiveCommits({commit_0_ptr, commit_2_ptr, commit_3_ptr, commit_4_ptr});
storage.AddCommit(std::move(commit_4));
pruner.SchedulePruning();
RunLoopUntilIdle();
EXPECT_THAT(storage.delete_commit_calls_, IsEmpty());
}
// Verify that only commits before the latest unique common ancestor are pruned. Here, we have the
// following commit graph:
// 0
// |
// 1
// / \
// 2 3
// \ /
// 4
// where commits 2, 3 and 4 are live. Only commit 0 should be pruned.
TEST_F(CommitPrunerTest, PruneBeforeLuca1) {
FakeCommitTracker commit_tracker;
FakeCommitPrunerDelegate storage;
fake::FakeObjectIdentifierFactory factory;
CommitPruner pruner(&environment_, &storage, &commit_tracker,
CommitPruningPolicy::LOCAL_IMMEDIATE);
// Add some commits. The parent of commit 0 does not exist in the database.
std::unique_ptr<Commit> commit_0 =
std::make_unique<FakeCommit>(environment_.random(), &factory, "random_commit_id", 10);
CommitId commit_id_0 = commit_0->GetId();
storage.AddCommit(std::move(commit_0));
std::unique_ptr<Commit> commit_1 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_0, 11);
CommitId commit_id_1 = commit_1->GetId();
Commit* commit1_ptr = commit_1.get();
storage.AddCommit(std::move(commit_1));
std::unique_ptr<Commit> commit_2 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_1, 12);
CommitId commit_id_2 = commit_2->GetId();
Commit* commit_2_ptr = commit_2.get();
storage.AddCommit(std::move(commit_2));
std::unique_ptr<Commit> commit_3 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_1, 12);
CommitId commit_id_3 = commit_3->GetId();
Commit* commit_3_ptr = commit_3.get();
storage.AddCommit(std::move(commit_3));
std::unique_ptr<Commit> commit_4 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_2, commit_id_3, 13);
Commit* commit_4_ptr = commit_4.get();
commit_tracker.SetLiveCommits({commit_2_ptr, commit_3_ptr, commit_4_ptr});
storage.AddCommit(std::move(commit_4));
pruner.SchedulePruning();
RunLoopUntilIdle();
EXPECT_THAT(storage.delete_commit_calls_, SizeIs(1));
EXPECT_THAT(storage.delete_commit_calls_[0].first, SizeIs(1));
EXPECT_EQ(storage.delete_commit_calls_[0].first[0]->GetId(), commit_id_0);
EXPECT_THAT(*storage.clocks_.rbegin(), ClockMatchesCommit(_, *commit1_ptr));
// Schedule a new pruning: if it runs, it means the first pruning completed.
pruner.SchedulePruning();
storage.delete_commit_calls_[0].second(Status::OK);
storage.delete_commit_calls_.clear();
RunLoopUntilIdle();
// The two prunings completed.
EXPECT_THAT(storage.delete_commit_calls_, IsEmpty());
EXPECT_THAT(storage.clocks_, SizeIs(2));
}
// Verify that only commits before the latest unique common ancestor are pruned. Here, we have the
// following commit graph:
// 0
// |
// 1
// / \
// 2 3
// \ /
// 4
// where commit 4 is live. Commits 0, 1, 2, and 3 should be pruned.
TEST_F(CommitPrunerTest, PruneBeforeLuca2) {
FakeCommitTracker commit_tracker;
FakeCommitPrunerDelegate storage;
fake::FakeObjectIdentifierFactory factory;
CommitPruner pruner(&environment_, &storage, &commit_tracker,
CommitPruningPolicy::LOCAL_IMMEDIATE);
// Add some commits. The parent of commit 0 does not exist in the database.
std::unique_ptr<Commit> commit_0 =
std::make_unique<FakeCommit>(environment_.random(), &factory, "random_commit_id", 10);
CommitId commit_id_0 = commit_0->GetId();
storage.AddCommit(std::move(commit_0));
std::unique_ptr<Commit> commit_1 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_0, 11);
CommitId commit_id_1 = commit_1->GetId();
storage.AddCommit(std::move(commit_1));
std::unique_ptr<Commit> commit_2 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_1, 12);
CommitId commit_id_2 = commit_2->GetId();
storage.AddCommit(std::move(commit_2));
std::unique_ptr<Commit> commit_3 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_1, 12);
CommitId commit_id_3 = commit_3->GetId();
storage.AddCommit(std::move(commit_3));
std::unique_ptr<Commit> commit_4 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_2, commit_id_3, 13);
CommitId commit_id_4 = commit_4->GetId();
Commit* commit_4_ptr = commit_4.get();
commit_tracker.SetLiveCommits({commit_4_ptr});
storage.AddCommit(std::move(commit_4));
pruner.SchedulePruning();
RunLoopUntilIdle();
EXPECT_THAT(storage.delete_commit_calls_, SizeIs(1));
std::set<CommitId> golden_commit_ids{commit_id_0, commit_id_1, commit_id_2, commit_id_3};
std::set<CommitId> actual_commit_ids;
for (const std::unique_ptr<const Commit>& commit : storage.delete_commit_calls_[0].first) {
actual_commit_ids.insert(commit->GetId());
}
EXPECT_EQ(actual_commit_ids, golden_commit_ids);
EXPECT_THAT(*storage.clocks_.rbegin(), ClockMatchesCommit(_, *commit_4_ptr));
// Schedule a new pruning: if it runs, it means the first pruning completed.
pruner.SchedulePruning();
storage.delete_commit_calls_[0].second(Status::OK);
storage.delete_commit_calls_.clear();
RunLoopUntilIdle();
// The two prunings completed.
EXPECT_THAT(storage.delete_commit_calls_, IsEmpty());
EXPECT_THAT(storage.clocks_, SizeIs(2));
}
// Verify that we can queue two prunings, and that they will be executed sequentially.
// Here, we have the following commit graph:
// 0
// |
// 1
// |
// 2
// |
// 3
// For the first pruning, 1 and 2 are live. We drop the reference to 1 during pruning: only 2
// is live for the second pruning. We also schedule a third pruning, that should be ignored
// because only one pruning needs to be queued.
TEST_F(CommitPrunerTest, PruningQueue) {
FakeCommitTracker commit_tracker;
FakeCommitPrunerDelegate storage;
fake::FakeObjectIdentifierFactory factory;
CommitPruner pruner(&environment_, &storage, &commit_tracker,
CommitPruningPolicy::LOCAL_IMMEDIATE);
// Add some commits. The parent of commit 0 does not exist in the database.
std::unique_ptr<Commit> commit_0 =
std::make_unique<FakeCommit>(environment_.random(), &factory, "random_commit_id", 10);
CommitId commit_id_0 = commit_0->GetId();
storage.AddCommit(std::move(commit_0));
std::unique_ptr<Commit> commit_1 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_0, 11);
CommitId commit_id_1 = commit_1->GetId();
Commit* commit1_ptr = commit_1.get();
storage.AddCommit(std::move(commit_1));
std::unique_ptr<Commit> commit_2 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_1, 12);
CommitId commit_id_2 = commit_2->GetId();
Commit* commit2_ptr = commit_2.get();
storage.AddCommit(std::move(commit_2));
std::unique_ptr<Commit> commit_3 =
std::make_unique<FakeCommit>(environment_.random(), &factory, commit_id_2, 13);
CommitId commit_id_3 = commit_3->GetId();
Commit* commit3_ptr = commit_3.get();
storage.AddCommit(std::move(commit_3));
commit_tracker.SetLiveCommits({commit1_ptr, commit2_ptr, commit3_ptr});
// Schedule three prunings.
pruner.SchedulePruning();
pruner.SchedulePruning();
pruner.SchedulePruning();
RunLoopUntilIdle();
// The first pruning is in the deletion phase.
EXPECT_THAT(storage.delete_commit_calls_, SizeIs(1));
EXPECT_THAT(storage.delete_commit_calls_[0].first, SizeIs(1));
EXPECT_THAT(storage.delete_commit_calls_[0].first[0]->GetId(), commit_id_0);
EXPECT_THAT(*storage.clocks_.rbegin(), ClockMatchesCommit(_, *commit1_ptr));
// Unreference commit1 and continue pruning.
commit_tracker.SetLiveCommits({commit2_ptr, commit3_ptr});
storage.delete_commit_calls_[0].second(Status::OK);
RunLoopUntilIdle();
// The second pruning is in the deletion phase.
EXPECT_THAT(storage.delete_commit_calls_, SizeIs(2));
EXPECT_THAT(storage.delete_commit_calls_[1].first, SizeIs(1));
EXPECT_THAT(storage.delete_commit_calls_[1].first[0]->GetId(), commit_id_1);
EXPECT_THAT(*storage.clocks_.rbegin(), ClockMatchesCommit(_, *commit2_ptr));
// Unreference commit2 and continue pruning.
commit_tracker.SetLiveCommits({commit3_ptr});
storage.delete_commit_calls_[1].second(Status::OK);
RunLoopUntilIdle();
// commit2 is not deleted because no pruning cycle is scheduled.
EXPECT_THAT(storage.delete_commit_calls_, SizeIs(2));
EXPECT_THAT(storage.clocks_, SizeIs(2));
}
} // namespace
} // namespace storage