blob: 66b66371754f06d66983a2886ac80d14145d2498 [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/app/page_manager.h"
#include <lib/async/cpp/task.h>
#include <lib/callback/capture.h>
#include <lib/callback/set_when_called.h>
#include <lib/callback/waiter.h>
#include <lib/fidl/cpp/optional.h>
#include <lib/fit/function.h>
#include <lib/inspect/inspect.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <cstdint>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "gtest/gtest.h"
#include "peridot/lib/convert/convert.h"
#include "src/ledger/bin/app/constants.h"
#include "src/ledger/bin/app/disk_cleanup_manager_impl.h"
#include "src/ledger/bin/encryption/fake/fake_encryption_service.h"
#include "src/ledger/bin/environment/environment.h"
#include "src/ledger/bin/fidl/include/types.h"
#include "src/ledger/bin/storage/fake/fake_page_storage.h"
#include "src/ledger/bin/storage/public/ledger_storage.h"
#include "src/ledger/bin/sync_coordinator/public/ledger_sync.h"
#include "src/ledger/bin/testing/fake_disk_cleanup_manager.h"
#include "src/ledger/bin/testing/test_with_environment.h"
#include "src/lib/fxl/macros.h"
#include "src/lib/fxl/memory/ref_ptr.h"
namespace ledger {
namespace {
constexpr char kLedgerName[] = "ledger_under_test";
class DelayingCallbacksManager {
public:
DelayingCallbacksManager() {}
virtual ~DelayingCallbacksManager() {}
// Returns true if the PageStorage of the page with the given id should delay
// calling the callback of |IsSynced|.
virtual bool ShouldDelayIsSyncedCallback(storage::PageIdView page_id) = 0;
private:
FXL_DISALLOW_COPY_AND_ASSIGN(DelayingCallbacksManager);
};
class DelayIsSyncedCallbackFakePageStorage
: public storage::fake::FakePageStorage {
public:
explicit DelayIsSyncedCallbackFakePageStorage(
Environment* environment,
DelayingCallbacksManager* delaying_callbacks_manager, storage::PageId id)
: storage::fake::FakePageStorage(environment, id),
delaying_callbacks_manager_(delaying_callbacks_manager) {}
~DelayIsSyncedCallbackFakePageStorage() override {}
void IsSynced(fit::function<void(Status, bool)> callback) override {
if (!delaying_callbacks_manager_->ShouldDelayIsSyncedCallback(page_id_)) {
storage::fake::FakePageStorage::IsSynced(std::move(callback));
return;
}
is_synced_callback_ = std::move(callback);
}
void IsEmpty(fit::function<void(Status, bool)> callback) override {
callback(Status::OK, true);
}
bool IsOnline() override { return false; }
void CallIsSyncedCallback() {
storage::fake::FakePageStorage::IsSynced(std::move(is_synced_callback_));
}
private:
fit::function<void(Status, bool)> is_synced_callback_;
DelayingCallbacksManager* delaying_callbacks_manager_;
FXL_DISALLOW_COPY_AND_ASSIGN(DelayIsSyncedCallbackFakePageStorage);
};
class FakeLedgerStorage : public storage::LedgerStorage,
public DelayingCallbacksManager {
public:
explicit FakeLedgerStorage(Environment* environment)
: environment_(environment) {}
~FakeLedgerStorage() override {}
void ListPages(fit::function<void(storage::Status, std::set<storage::PageId>)>
callback) override {
FXL_NOTREACHED() << "Maybe implement this later on if needed?";
}
void CreatePageStorage(
storage::PageId page_id,
fit::function<void(Status, std::unique_ptr<storage::PageStorage>)>
callback) override {
create_page_calls.push_back(std::move(page_id));
callback(Status::IO_ERROR, nullptr);
}
void GetPageStorage(
storage::PageId page_id,
fit::function<void(Status, std::unique_ptr<storage::PageStorage>)>
callback) override {
get_page_calls.push_back(page_id);
async::PostTask(
environment_->dispatcher(),
[this, callback = std::move(callback), page_id]() mutable {
if (should_get_page_fail) {
callback(Status::PAGE_NOT_FOUND, nullptr);
} else {
auto fake_page_storage =
std::make_unique<DelayIsSyncedCallbackFakePageStorage>(
environment_, this, page_id);
// If the page was opened before, restore the previous sync state.
fake_page_storage->set_synced(synced_pages_.find(page_id) !=
synced_pages_.end());
page_storages_[std::move(page_id)] = fake_page_storage.get();
callback(Status::OK, std::move(fake_page_storage));
}
});
}
void DeletePageStorage(storage::PageIdView /*page_id*/,
fit::function<void(Status)> callback) override {
delete_page_storage_callback = std::move(callback);
}
void ClearCalls() {
create_page_calls.clear();
get_page_calls.clear();
page_storages_.clear();
}
void DelayIsSyncedCallback(storage::PageIdView page_id, bool delay_callback) {
if (delay_callback) {
pages_with_delayed_callback.insert(page_id.ToString());
} else {
pages_with_delayed_callback.erase(page_id.ToString());
}
}
// DelayingCallbacksManager:
bool ShouldDelayIsSyncedCallback(storage::PageIdView page_id) override {
return pages_with_delayed_callback.find(page_id.ToString()) !=
pages_with_delayed_callback.end();
}
void CallIsSyncedCallback(storage::PageIdView page_id) {
auto it = page_storages_.find(page_id.ToString());
FXL_CHECK(it != page_storages_.end());
it->second->CallIsSyncedCallback();
}
void set_page_storage_synced(storage::PageIdView page_id, bool is_synced) {
storage::PageId page_id_string = page_id.ToString();
if (is_synced) {
synced_pages_.insert(page_id_string);
} else {
auto it = synced_pages_.find(page_id_string);
if (it != synced_pages_.end()) {
synced_pages_.erase(it);
}
}
FXL_CHECK(page_storages_.find(page_id_string) != page_storages_.end());
page_storages_[page_id_string]->set_synced(is_synced);
}
void set_page_storage_offline_empty(storage::PageIdView page_id,
bool is_offline_empty) {
storage::PageId page_id_string = page_id.ToString();
if (is_offline_empty) {
offline_empty_pages_.insert(page_id_string);
} else {
auto it = offline_empty_pages_.find(page_id_string);
if (it != offline_empty_pages_.end()) {
offline_empty_pages_.erase(it);
}
}
}
bool should_get_page_fail = false;
std::vector<storage::PageId> create_page_calls;
std::vector<storage::PageId> get_page_calls;
fit::function<void(Status)> delete_page_storage_callback;
private:
Environment* const environment_;
std::map<storage::PageId, DelayIsSyncedCallbackFakePageStorage*>
page_storages_;
std::set<storage::PageId> synced_pages_;
std::set<storage::PageId> offline_empty_pages_;
std::set<storage::PageId> pages_with_delayed_callback;
FXL_DISALLOW_COPY_AND_ASSIGN(FakeLedgerStorage);
};
class FakeLedgerSync : public sync_coordinator::LedgerSync {
public:
FakeLedgerSync() {}
~FakeLedgerSync() override {}
std::unique_ptr<sync_coordinator::PageSync> CreatePageSync(
storage::PageStorage* /*page_storage*/,
storage::PageSyncClient* /*page_sync_client*/) override {
called = true;
return nullptr;
}
bool called = false;
private:
FXL_DISALLOW_COPY_AND_ASSIGN(FakeLedgerSync);
};
class PageManagerTest : public TestWithEnvironment {
public:
PageManagerTest() {}
~PageManagerTest() override {}
// gtest::TestWithEnvironment:
void SetUp() override {
TestWithEnvironment::SetUp();
page_id_ = RandomId();
ledger_merge_manager_ = std::make_unique<LedgerMergeManager>(&environment_);
storage_ = std::make_unique<FakeLedgerStorage>(&environment_);
sync_ = std::make_unique<FakeLedgerSync>();
disk_cleanup_manager_ = std::make_unique<FakeDiskCleanupManager>();
page_manager_ = std::make_unique<PageManager>(
&environment_, kLedgerName, convert::ToString(page_id_.id),
disk_cleanup_manager_.get(), storage_.get(), sync_.get(),
ledger_merge_manager_.get(), inspect::Node());
}
PageId RandomId() {
PageId result;
environment_.random()->Draw(&result.id);
return result;
}
protected:
std::unique_ptr<FakeLedgerStorage> storage_;
std::unique_ptr<FakeLedgerSync> sync_;
std::unique_ptr<LedgerMergeManager> ledger_merge_manager_;
std::unique_ptr<FakeDiskCleanupManager> disk_cleanup_manager_;
std::unique_ptr<PageManager> page_manager_;
PageId page_id_;
private:
FXL_DISALLOW_COPY_AND_ASSIGN(PageManagerTest);
};
class StubConflictResolverFactory : public ConflictResolverFactory {
public:
explicit StubConflictResolverFactory(
fidl::InterfaceRequest<ConflictResolverFactory> request)
: binding_(this, std::move(request)) {
binding_.set_error_handler(
[this](zx_status_t status) { disconnected = true; });
}
bool disconnected = false;
private:
void GetPolicy(PageId page_id,
fit::function<void(MergePolicy)> callback) override {}
void NewConflictResolver(
PageId page_id,
fidl::InterfaceRequest<ConflictResolver> resolver) override {}
fidl::Binding<ConflictResolverFactory> binding_;
};
TEST_F(PageManagerTest, OnEmptyCalled) {
bool get_page_callback_called;
Status get_page_status;
bool on_empty_callback_called;
page_manager_->set_on_empty(
callback::Capture(callback::SetWhenCalled(&on_empty_callback_called)));
PagePtr page;
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NEW, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
EXPECT_FALSE(on_empty_callback_called);
fit::closure detacher = page_manager_->CreateDetacher();
RunLoopUntilIdle();
EXPECT_FALSE(on_empty_callback_called);
page.Unbind();
RunLoopUntilIdle();
EXPECT_FALSE(on_empty_callback_called);
detacher();
RunLoopUntilIdle();
EXPECT_TRUE(on_empty_callback_called);
}
TEST_F(PageManagerTest, PageIsClosedAndSyncedCheckNotFound) {
bool called;
Status status;
PagePredicateResult is_closed_and_synced;
// Check for a page that doesn't exist.
storage_->should_get_page_fail = true;
page_manager_->PageIsClosedAndSynced(callback::Capture(
callback::SetWhenCalled(&called), &status, &is_closed_and_synced));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::PAGE_NOT_FOUND, status);
}
// Check for a page that exists, is synced and open. PageIsClosedAndSynced
// should be false.
TEST_F(PageManagerTest, PageIsClosedAndSyncedCheckClosed) {
bool get_page_callback_called;
Status get_page_status;
bool called;
PagePredicateResult is_closed_and_synced;
storage_->should_get_page_fail = false;
PagePtr page;
storage::PageIdView storage_page_id =
convert::ExtendedStringView(page_id_.id);
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
Status storage_status;
storage_->set_page_storage_synced(storage_page_id, true);
page_manager_->PageIsClosedAndSynced(
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_and_synced));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::PAGE_OPENED, is_closed_and_synced);
// Close the page. PageIsClosedAndSynced should now be true.
page.Unbind();
RunLoopUntilIdle();
page_manager_->PageIsClosedAndSynced(
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_and_synced));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::YES, is_closed_and_synced);
}
// Check for a page that exists, is closed, but is not synced.
// PageIsClosedAndSynced should be false.
TEST_F(PageManagerTest, PageIsClosedAndSyncedCheckSynced) {
bool get_page_callback_called;
Status get_page_status;
bool called;
PagePredicateResult is_closed_and_synced;
storage_->should_get_page_fail = false;
PagePtr page;
storage::PageIdView storage_page_id =
convert::ExtendedStringView(page_id_.id);
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
// Mark the page as unsynced and close it.
storage_->set_page_storage_synced(storage_page_id, false);
page.Unbind();
RunLoopUntilIdle();
Status storage_status;
page_manager_->PageIsClosedAndSynced(
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_and_synced));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::NO, is_closed_and_synced);
}
// Check for a page that exists, is closed, and synced, but was opened during
// the PageIsClosedAndSynced call. Expect an |PAGE_OPENED| result.
TEST_F(PageManagerTest, PageIsClosedAndSyncedCheckPageOpened) {
bool get_page_callback_called;
Status get_page_status;
PagePredicateResult is_closed_and_synced;
storage_->should_get_page_fail = false;
PagePtr page;
storage::PageIdView storage_page_id =
convert::ExtendedStringView(page_id_.id);
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
// Mark the page as synced and close it.
storage_->set_page_storage_synced(storage_page_id, true);
page.Unbind();
RunLoopUntilIdle();
// Call PageIsClosedAndSynced but don't let it terminate.
bool page_is_closed_and_synced_called = false;
storage_->DelayIsSyncedCallback(storage_page_id, true);
Status storage_status;
page_manager_->PageIsClosedAndSynced(callback::Capture(
callback::SetWhenCalled(&page_is_closed_and_synced_called),
&storage_status, &is_closed_and_synced));
RunLoopUntilIdle();
EXPECT_FALSE(page_is_closed_and_synced_called);
// Open and close the page.
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
page.Unbind();
RunLoopUntilIdle();
// Make sure PageIsClosedAndSynced terminates with a |PAGE_OPENED| result.
storage_->CallIsSyncedCallback(storage_page_id);
RunLoopUntilIdle();
EXPECT_TRUE(page_is_closed_and_synced_called);
EXPECT_EQ(Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::PAGE_OPENED, is_closed_and_synced);
}
// Check for a page that exists, is closed, and synced. Test two concurrent
// calls to PageIsClosedAndSynced, where the second one will start and terminate
// without the page being opened by external requests.
TEST_F(PageManagerTest, PageIsClosedAndSyncedConcurrentCalls) {
bool get_page_callback_called;
Status get_page_status;
storage_->should_get_page_fail = false;
PagePtr page;
storage::PageIdView storage_page_id =
convert::ExtendedStringView(page_id_.id);
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
// Mark the page as synced and close it.
storage_->set_page_storage_synced(storage_page_id, true);
page.Unbind();
RunLoopUntilIdle();
// Make a first call to PageIsClosedAndSynced but don't let it terminate.
bool called1 = false;
Status status1;
PagePredicateResult is_closed_and_synced1;
storage_->DelayIsSyncedCallback(storage_page_id, true);
page_manager_->PageIsClosedAndSynced(callback::Capture(
callback::SetWhenCalled(&called1), &status1, &is_closed_and_synced1));
RunLoopUntilIdle();
// Prepare for the second call: it will return immediately and the expected
// result is |YES|.
bool called2 = false;
Status status2;
PagePredicateResult is_closed_and_synced2;
storage_->DelayIsSyncedCallback(storage_page_id, false);
page_manager_->PageIsClosedAndSynced(callback::Capture(
callback::SetWhenCalled(&called2), &status2, &is_closed_and_synced2));
RunLoopUntilIdle();
EXPECT_FALSE(called1);
EXPECT_TRUE(called2);
EXPECT_EQ(Status::OK, status2);
EXPECT_EQ(PagePredicateResult::YES, is_closed_and_synced2);
// Open and close the page.
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
page.Unbind();
RunLoopUntilIdle();
// Call the callback and let the first call to PageIsClosedAndSynced
// terminate. The expected returned result is |PAGE_OPENED|.
storage_->CallIsSyncedCallback(storage_page_id);
RunLoopUntilIdle();
EXPECT_TRUE(called1);
EXPECT_EQ(Status::OK, status1);
EXPECT_EQ(PagePredicateResult::PAGE_OPENED, is_closed_and_synced1);
}
TEST_F(PageManagerTest, PageIsClosedOfflineAndEmptyCheckNotFound) {
bool called;
Status status;
PagePredicateResult is_closed_offline_empty;
// Check for a page that doesn't exist.
storage_->should_get_page_fail = true;
page_manager_->PageIsClosedOfflineAndEmpty(callback::Capture(
callback::SetWhenCalled(&called), &status, &is_closed_offline_empty));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::PAGE_NOT_FOUND, status);
}
TEST_F(PageManagerTest, PageIsClosedOfflineAndEmptyCheckClosed) {
bool get_page_callback_called;
Status get_page_status;
bool called;
PagePredicateResult is_closed_offline_empty;
storage_->should_get_page_fail = false;
PagePtr page;
storage::PageIdView storage_page_id =
convert::ExtendedStringView(page_id_.id);
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
storage_->set_page_storage_offline_empty(storage_page_id, true);
Status storage_status;
page_manager_->PageIsClosedOfflineAndEmpty(
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_offline_empty));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::PAGE_OPENED, is_closed_offline_empty);
// Close the page. PagePredicateResult should now be true.
page.Unbind();
RunLoopUntilIdle();
page_manager_->PageIsClosedOfflineAndEmpty(
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_offline_empty));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::YES, is_closed_offline_empty);
}
TEST_F(PageManagerTest, PageIsClosedOfflineAndEmptyCanDeletePageOnCallback) {
bool page_is_empty_called = false;
Status page_is_empty_status;
PagePredicateResult is_closed_offline_empty;
bool delete_page_called = false;
Status delete_page_status;
// The page is closed, offline and empty. Try to delete the page storage in
// the callback.
storage_->set_page_storage_offline_empty(page_id_.id, true);
page_manager_->PageIsClosedOfflineAndEmpty(
[&](Status status, PagePredicateResult result) {
page_is_empty_called = true;
page_is_empty_status = status;
is_closed_offline_empty = result;
page_manager_->DeletePageStorage(callback::Capture(
callback::SetWhenCalled(&delete_page_called), &delete_page_status));
});
RunLoopUntilIdle();
// Make sure the deletion finishes successfully.
ASSERT_NE(nullptr, storage_->delete_page_storage_callback);
storage_->delete_page_storage_callback(Status::OK);
RunLoopUntilIdle();
EXPECT_TRUE(page_is_empty_called);
EXPECT_EQ(Status::OK, page_is_empty_status);
EXPECT_EQ(PagePredicateResult::YES, is_closed_offline_empty);
EXPECT_TRUE(delete_page_called);
EXPECT_EQ(Status::OK, delete_page_status);
}
// Verifies that two successive calls to GetPage do not create 2 storages.
TEST_F(PageManagerTest, CallGetPageTwice) {
PagePtr page1;
bool get_page_callback_called1;
Status get_page_status1;
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page1.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called1),
&get_page_status1));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called1);
EXPECT_EQ(Status::OK, get_page_status1);
PagePtr page2;
bool get_page_callback_called2;
Status get_page_status2;
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page2.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called2),
&get_page_status2));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called2);
EXPECT_EQ(Status::OK, get_page_status2);
EXPECT_EQ(0u, storage_->create_page_calls.size());
ASSERT_EQ(1u, storage_->get_page_calls.size());
EXPECT_EQ(convert::ToString(page_id_.id), storage_->get_page_calls[0]);
}
TEST_F(PageManagerTest, OnPageOpenedClosedCalls) {
PagePtr page1;
bool get_page_callback_called1;
Status get_page_status1;
PagePtr page2;
bool get_page_callback_called2;
Status get_page_status2;
EXPECT_EQ(0, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_unused_count);
// Open a page and check that OnPageOpened was called once.
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page1.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called1),
&get_page_status1));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called1);
EXPECT_EQ(Status::OK, get_page_status1);
EXPECT_EQ(1, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_unused_count);
// Open the page again and check that there is no new call to OnPageOpened.
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page2.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called2),
&get_page_status2));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called2);
EXPECT_EQ(Status::OK, get_page_status2);
EXPECT_EQ(1, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_unused_count);
// Close one of the two connections and check that there is still no call to
// OnPageClosed.
page1.Unbind();
RunLoopUntilIdle();
EXPECT_EQ(1, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_unused_count);
// Close the second connection and check that OnPageClosed was called once.
page2.Unbind();
RunLoopUntilIdle();
EXPECT_EQ(1, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(1, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(1, disk_cleanup_manager_->page_unused_count);
}
TEST_F(PageManagerTest, OnPageOpenedClosedCallInternalRequest) {
PagePtr page;
EXPECT_EQ(0, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_unused_count);
// Make an internal request by calling PageIsClosedAndSynced. No calls to page
// opened/closed should be made.
bool called;
Status storage_status;
PagePredicateResult page_state;
page_manager_->PageIsClosedAndSynced(callback::Capture(
callback::SetWhenCalled(&called), &storage_status, &page_state));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::NO, page_state);
EXPECT_EQ(0, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_unused_count);
// Open the same page with an external request and check that OnPageOpened
// was called once.
bool get_page_callback_called;
Status get_page_status;
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
EXPECT_EQ(1, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_unused_count);
}
TEST_F(PageManagerTest, OnPageOpenedClosedUnused) {
PagePtr page;
storage::PageIdView storage_page_id =
convert::ExtendedStringView(page_id_.id);
EXPECT_EQ(0, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(0, disk_cleanup_manager_->page_unused_count);
// Open and close the page through an external request.
bool get_page_callback_called;
Status get_page_status;
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
// Mark the page as synced and close it.
storage_->set_page_storage_synced(storage_page_id, true);
page.Unbind();
RunLoopUntilIdle();
EXPECT_EQ(1, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(1, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(1, disk_cleanup_manager_->page_unused_count);
// Start an internal request but don't let it terminate. Nothing should have
// changed in the notifications received.
PagePredicateResult is_synced;
bool page_is_synced_called = false;
storage_->DelayIsSyncedCallback(storage_page_id, true);
Status storage_status;
page_manager_->PageIsClosedAndSynced(
callback::Capture(callback::SetWhenCalled(&page_is_synced_called),
&storage_status, &is_synced));
RunLoopUntilIdle();
EXPECT_FALSE(page_is_synced_called);
EXPECT_EQ(1, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(1, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(1, disk_cleanup_manager_->page_unused_count);
// Open the same page with an external request and check that OnPageOpened
// was called once.
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
EXPECT_EQ(2, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(1, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(1, disk_cleanup_manager_->page_unused_count);
// Close the page. We should get the page closed notification, but not the
// unused one: the internal request is still running.
page.Unbind();
RunLoopUntilIdle();
EXPECT_EQ(2, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(2, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(1, disk_cleanup_manager_->page_unused_count);
// Terminate the internal request. We should now see the unused page
// notification.
storage_->CallIsSyncedCallback(storage_page_id);
RunLoopUntilIdle();
EXPECT_EQ(2, disk_cleanup_manager_->page_opened_count);
EXPECT_EQ(2, disk_cleanup_manager_->page_closed_count);
EXPECT_EQ(2, disk_cleanup_manager_->page_unused_count);
}
TEST_F(PageManagerTest, DeletePageStorageWhenPageOpenFails) {
bool get_page_callback_called;
Status get_page_status;
PagePtr page;
bool called;
page_manager_->GetPage(
LedgerImpl::Delegate::PageState::NAMED, page.NewRequest(),
callback::Capture(callback::SetWhenCalled(&get_page_callback_called),
&get_page_status));
RunLoopUntilIdle();
EXPECT_TRUE(get_page_callback_called);
EXPECT_EQ(Status::OK, get_page_status);
// Try to delete the page while it is open. Expect to get an error.
Status storage_status;
page_manager_->DeletePageStorage(
callback::Capture(callback::SetWhenCalled(&called), &storage_status));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::ILLEGAL_STATE, storage_status);
}
} // namespace
} // namespace ledger