blob: a2e08fee1ddf0626fe116157827a95defe5bafa6 [file] [log] [blame]
// Copyright 2018 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 "peridot/bin/ledger/app/page_eviction_policies.h"
#include <lib/callback/capture.h>
#include <lib/callback/set_when_called.h>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "peridot/bin/ledger/testing/test_with_environment.h"
namespace ledger {
namespace {
using ::testing::ElementsAre;
using ::testing::IsEmpty;
// A wrapper storage::Iterator for the elements of an std::vector<T>.
template <class T>
class VectorIterator : public storage::Iterator<T> {
public:
VectorIterator(const std::vector<T>& v) : it_(v.begin()), end_(v.end()) {}
~VectorIterator() {}
storage::Iterator<T>& Next() override {
++it_;
return *this;
}
bool Valid() const override { return it_ != end_; }
storage::Status GetStatus() const override { return storage::Status::OK; }
T& operator*() const override { return *it_; }
T* operator->() const override { return &*it_; }
private:
typename std::vector<T>::iterator it_;
typename std::vector<T>::iterator end_;
FXL_DISALLOW_COPY_AND_ASSIGN(VectorIterator);
};
// A fake PageEvictionDelegate, that stores the set of pages that were evicted.
class FakePageEvictionDelegate : public PageEvictionDelegate {
public:
FakePageEvictionDelegate() {}
~FakePageEvictionDelegate() {}
void TryEvictPage(fxl::StringView ledger_name, storage::PageIdView page_id,
PageEvictionCondition condition,
fit::function<void(Status, PageWasEvicted)> callback) {
if (try_evict_page_status_ != Status::OK) {
callback(try_evict_page_status_, PageWasEvicted(false));
return;
}
if (pages_not_to_evict_.find(page_id.ToString()) !=
pages_not_to_evict_.end()) {
callback(Status::OK, PageWasEvicted(false));
return;
}
evicted_pages_.push_back(page_id.ToString());
callback(Status::OK, PageWasEvicted(true));
}
const std::vector<storage::PageId>& GetEvictedPages() {
return evicted_pages_;
}
void SetPagesNotToEvict(std::set<storage::PageId> pages_not_to_evict) {
pages_not_to_evict_ = std::move(pages_not_to_evict);
}
void SetTryEvictPageStatus(Status status) { try_evict_page_status_ = status; }
private:
// The vector of pages for which |TryEvictPage| returned PageWasEvicted(true).
std::vector<storage::PageId> evicted_pages_;
// Pages in this set will return PageWasEvicted(false) if TryEvictPage is
// called on them.
std::set<storage::PageId> pages_not_to_evict_;
// The status to be returned by |TryEvictPage|.
Status try_evict_page_status_ = Status::OK;
FXL_DISALLOW_COPY_AND_ASSIGN(FakePageEvictionDelegate);
};
using PageEvictionPoliciesTest = TestWithEnvironment;
TEST_F(PageEvictionPoliciesTest, LeastRecentyUsed) {
FakePageEvictionDelegate delegate;
std::string ledger_name = "ledger";
std::vector<const PageInfo> pages = {
{ledger_name, "page1", zx::time_utc(1)},
{ledger_name, "page2", zx::time_utc(2)},
{ledger_name, "page3", zx::time_utc(3)},
{ledger_name, "page4", zx::time_utc(4)},
};
std::unique_ptr<PageEvictionPolicy> policy =
NewLeastRecentyUsedPolicy(environment_.coroutine_service(), &delegate);
// Expect to only evict the least recently used page, i.e. "page1".
bool called;
Status status;
policy->SelectAndEvict(
std::make_unique<VectorIterator<const PageInfo>>(pages),
callback::Capture(callback::SetWhenCalled(&called), &status));
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_THAT(delegate.GetEvictedPages(), ElementsAre("page1"));
}
TEST_F(PageEvictionPoliciesTest, LeastRecentyUsedWithOpenPages) {
FakePageEvictionDelegate delegate;
std::string ledger_name = "ledger";
std::vector<const PageInfo> pages = {
{ledger_name, "page1", PageInfo::kOpenedPageTimestamp},
{ledger_name, "page2", zx::time_utc(2)},
{ledger_name, "page3", zx::time_utc(3)},
{ledger_name, "page4", zx::time_utc(4)},
};
std::unique_ptr<PageEvictionPolicy> policy =
NewLeastRecentyUsedPolicy(environment_.coroutine_service(), &delegate);
// "page1" should not be evicted as it is marked as open. Expect to only evict
// the least recently used page, i.e. "page2".
bool called;
Status status;
policy->SelectAndEvict(
std::make_unique<VectorIterator<const PageInfo>>(pages),
callback::Capture(callback::SetWhenCalled(&called), &status));
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_THAT(delegate.GetEvictedPages(), ElementsAre("page2"));
}
TEST_F(PageEvictionPoliciesTest, LeastRecentyUsedNoPagesToEvict) {
FakePageEvictionDelegate delegate;
std::string ledger_name = "ledger";
std::vector<const PageInfo> pages = {
{ledger_name, "page1", PageInfo::kOpenedPageTimestamp},
{ledger_name, "page2", zx::time_utc(2)},
{ledger_name, "page3", zx::time_utc(3)},
{ledger_name, "page4", zx::time_utc(4)},
};
delegate.SetPagesNotToEvict({"page2", "page3", "page4"});
std::unique_ptr<PageEvictionPolicy> policy =
NewLeastRecentyUsedPolicy(environment_.coroutine_service(), &delegate);
// "page1" is marked as open, and pages 2-4 will fail to be evicted. The
// returned status should be ok, and not pages will be evicted.
bool called;
Status status;
policy->SelectAndEvict(
std::make_unique<VectorIterator<const PageInfo>>(pages),
callback::Capture(callback::SetWhenCalled(&called), &status));
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_THAT(delegate.GetEvictedPages(), IsEmpty());
}
TEST_F(PageEvictionPoliciesTest, LeastRecentyUsedErrorWhileEvicting) {
FakePageEvictionDelegate delegate;
std::string ledger_name = "ledger";
std::vector<const PageInfo> pages = {
{ledger_name, "page1", zx::time_utc(1)},
{ledger_name, "page2", zx::time_utc(2)},
{ledger_name, "page3", zx::time_utc(3)},
{ledger_name, "page4", zx::time_utc(4)},
};
delegate.SetTryEvictPageStatus(Status::INTERNAL_ERROR);
// If |TryEvictPage| fails, so should |SelectAndEvict|. Expect to find the
// same error status.
std::unique_ptr<PageEvictionPolicy> policy =
NewLeastRecentyUsedPolicy(environment_.coroutine_service(), &delegate);
bool called;
Status status;
policy->SelectAndEvict(
std::make_unique<VectorIterator<const PageInfo>>(pages),
callback::Capture(callback::SetWhenCalled(&called), &status));
EXPECT_TRUE(called);
EXPECT_EQ(Status::INTERNAL_ERROR, status);
}
} // namespace
} // namespace ledger