blob: 7cf2ee5d0c4b0fa1a08f6026a28868e4ef31ff9b [file] [log] [blame]
// Copyright 2016 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/ledger_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/syscalls.h>
#include <cstdint>
#include <memory>
#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(storage::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(storage::Status, bool)> callback) override {
callback(storage::Status::OK, true);
}
bool IsOnline() override { return false; }
void CallIsSyncedCallback() {
storage::fake::FakePageStorage::IsSynced(std::move(is_synced_callback_));
}
private:
fit::function<void(storage::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 CreatePageStorage(
storage::PageId page_id,
fit::function<void(storage::Status,
std::unique_ptr<storage::PageStorage>)>
callback) override {
create_page_calls.push_back(std::move(page_id));
callback(storage::Status::IO_ERROR, nullptr);
}
void GetPageStorage(storage::PageId page_id,
fit::function<void(storage::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(storage::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(storage::Status::OK, std::move(fake_page_storage));
}
});
}
void DeletePageStorage(
storage::PageIdView /*page_id*/,
fit::function<void(storage::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(storage::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 LedgerManagerTest : public TestWithEnvironment {
public:
LedgerManagerTest() {}
~LedgerManagerTest() override {}
// gtest::TestWithEnvironment:
void SetUp() override {
TestWithEnvironment::SetUp();
std::unique_ptr<FakeLedgerStorage> storage =
std::make_unique<FakeLedgerStorage>(&environment_);
storage_ptr = storage.get();
std::unique_ptr<FakeLedgerSync> sync = std::make_unique<FakeLedgerSync>();
sync_ptr = sync.get();
disk_cleanup_manager_ = std::make_unique<FakeDiskCleanupManager>();
ledger_manager_ = std::make_unique<LedgerManager>(
&environment_, kLedgerName, inspect::Node(),
std::make_unique<encryption::FakeEncryptionService>(dispatcher()),
std::move(storage), std::move(sync), disk_cleanup_manager_.get());
ResetLedgerPtr();
}
void ResetLedgerPtr() { ledger_manager_->BindLedger(ledger_.NewRequest()); }
PageId RandomId() {
PageId result;
environment_.random()->Draw(&result.id);
return result;
}
protected:
FakeLedgerStorage* storage_ptr;
FakeLedgerSync* sync_ptr;
std::unique_ptr<FakeDiskCleanupManager> disk_cleanup_manager_;
std::unique_ptr<LedgerManager> ledger_manager_;
LedgerPtr ledger_;
private:
FXL_DISALLOW_COPY_AND_ASSIGN(LedgerManagerTest);
};
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_;
};
// Verifies that LedgerImpl proxies vended by LedgerManager work correctly,
// that is, make correct calls to ledger storage.
TEST_F(LedgerManagerTest, LedgerImpl) {
EXPECT_EQ(0u, storage_ptr->create_page_calls.size());
EXPECT_EQ(0u, storage_ptr->get_page_calls.size());
PagePtr page;
storage_ptr->should_get_page_fail = true;
ledger_->GetPage(nullptr, page.NewRequest());
RunLoopUntilIdle();
EXPECT_EQ(1u, storage_ptr->create_page_calls.size());
EXPECT_EQ(1u, storage_ptr->get_page_calls.size());
EXPECT_FALSE(ledger_);
page.Unbind();
storage_ptr->ClearCalls();
ResetLedgerPtr();
storage_ptr->should_get_page_fail = true;
ledger_->GetRootPage(page.NewRequest());
RunLoopUntilIdle();
EXPECT_EQ(1u, storage_ptr->create_page_calls.size());
EXPECT_EQ(1u, storage_ptr->get_page_calls.size());
EXPECT_FALSE(ledger_);
page.Unbind();
storage_ptr->ClearCalls();
ResetLedgerPtr();
PageId id = RandomId();
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
EXPECT_EQ(1u, storage_ptr->create_page_calls.size());
ASSERT_EQ(1u, storage_ptr->get_page_calls.size());
EXPECT_FALSE(ledger_);
EXPECT_EQ(convert::ToString(id.id), storage_ptr->get_page_calls[0]);
page.Unbind();
storage_ptr->ClearCalls();
}
// Verifies that deleting the LedgerManager closes the channels connected to
// LedgerImpl.
TEST_F(LedgerManagerTest, DeletingLedgerManagerClosesConnections) {
bool ledger_closed = false;
ledger_.set_error_handler([this, &ledger_closed](zx_status_t status) {
ledger_closed = true;
QuitLoop();
});
ledger_manager_.reset();
RunLoopUntilIdle();
EXPECT_TRUE(ledger_closed);
}
TEST_F(LedgerManagerTest, OnEmptyCalled) {
bool on_empty_called;
ledger_manager_->set_on_empty(callback::SetWhenCalled(&on_empty_called));
ledger_.Unbind();
RunLoopUntilIdle();
EXPECT_TRUE(on_empty_called);
}
// Verifies that the LedgerManager does not call its callback while a page is
// being deleted.
TEST_F(LedgerManagerTest, NonEmptyDuringDeletion) {
bool on_empty_called;
ledger_manager_->set_on_empty(callback::SetWhenCalled(&on_empty_called));
PageId id = RandomId();
bool delete_page_called;
storage::Status delete_page_status;
ledger_manager_->DeletePageStorage(
id.id, callback::Capture(callback::SetWhenCalled(&delete_page_called),
&delete_page_status));
// Empty the Ledger manager.
ledger_.Unbind();
RunLoopUntilIdle();
EXPECT_FALSE(on_empty_called);
// Complete the deletion successfully.
ASSERT_TRUE(storage_ptr->delete_page_storage_callback);
storage_ptr->delete_page_storage_callback(storage::Status::OK);
RunLoopUntilIdle();
EXPECT_TRUE(delete_page_called);
EXPECT_EQ(storage::Status::OK, delete_page_status);
EXPECT_TRUE(on_empty_called);
}
TEST_F(LedgerManagerTest, PageIsClosedAndSyncedCheckNotFound) {
bool called;
storage::Status status;
PagePredicateResult is_closed_and_synced;
PageId id = RandomId();
// Check for a page that doesn't exist.
storage_ptr->should_get_page_fail = true;
ledger_manager_->PageIsClosedAndSynced(
id.id, callback::Capture(callback::SetWhenCalled(&called), &status,
&is_closed_and_synced));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::PAGE_NOT_FOUND, status);
}
// Check for a page that exists, is synced and open. PageIsClosedAndSynced
// should be false.
TEST_F(LedgerManagerTest, PageIsClosedAndSyncedCheckClosed) {
bool called;
PagePredicateResult is_closed_and_synced;
storage_ptr->should_get_page_fail = false;
PagePtr page;
PageId id = RandomId();
storage::PageIdView storage_page_id = convert::ExtendedStringView(id.id);
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
storage::Status storage_status;
storage_ptr->set_page_storage_synced(storage_page_id, true);
ledger_manager_->PageIsClosedAndSynced(
storage_page_id,
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_and_synced));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::PAGE_OPENED, is_closed_and_synced);
// Close the page. PageIsClosedAndSynced should now be true.
page.Unbind();
RunLoopUntilIdle();
ledger_manager_->PageIsClosedAndSynced(
storage_page_id,
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_and_synced));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::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(LedgerManagerTest, PageIsClosedAndSyncedCheckSynced) {
bool called;
PagePredicateResult is_closed_and_synced;
storage_ptr->should_get_page_fail = false;
PagePtr page;
PageId id = RandomId();
storage::PageIdView storage_page_id = convert::ExtendedStringView(id.id);
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
// Mark the page as unsynced and close it.
storage_ptr->set_page_storage_synced(storage_page_id, false);
page.Unbind();
RunLoopUntilIdle();
storage::Status storage_status;
ledger_manager_->PageIsClosedAndSynced(
storage_page_id,
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_and_synced));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::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(LedgerManagerTest, PageIsClosedAndSyncedCheckPageOpened) {
PagePredicateResult is_closed_and_synced;
storage_ptr->should_get_page_fail = false;
PagePtr page;
PageId id = RandomId();
storage::PageIdView storage_page_id = convert::ExtendedStringView(id.id);
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
// Mark the page as synced and close it.
storage_ptr->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_ptr->DelayIsSyncedCallback(storage_page_id, true);
storage::Status storage_status;
ledger_manager_->PageIsClosedAndSynced(
storage_page_id,
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.
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
page.Unbind();
RunLoopUntilIdle();
// Make sure PageIsClosedAndSynced terminates with a |PAGE_OPENED| result.
storage_ptr->CallIsSyncedCallback(storage_page_id);
RunLoopUntilIdle();
EXPECT_TRUE(page_is_closed_and_synced_called);
EXPECT_EQ(storage::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(LedgerManagerTest, PageIsClosedAndSyncedConcurrentCalls) {
storage_ptr->should_get_page_fail = false;
PagePtr page;
PageId id = RandomId();
storage::PageIdView storage_page_id = convert::ExtendedStringView(id.id);
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
// Mark the page as synced and close it.
storage_ptr->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;
storage::Status status1;
PagePredicateResult is_closed_and_synced1;
storage_ptr->DelayIsSyncedCallback(storage_page_id, true);
ledger_manager_->PageIsClosedAndSynced(
storage_page_id, 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;
storage::Status status2;
PagePredicateResult is_closed_and_synced2;
storage_ptr->DelayIsSyncedCallback(storage_page_id, false);
ledger_manager_->PageIsClosedAndSynced(
storage_page_id, callback::Capture(callback::SetWhenCalled(&called2),
&status2, &is_closed_and_synced2));
RunLoopUntilIdle();
EXPECT_FALSE(called1);
EXPECT_TRUE(called2);
EXPECT_EQ(storage::Status::OK, status2);
EXPECT_EQ(PagePredicateResult::YES, is_closed_and_synced2);
// Open and close the page.
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
page.Unbind();
RunLoopUntilIdle();
// Call the callback and let the first call to PageIsClosedAndSynced
// terminate. The expected returned result is |PAGE_OPENED|.
storage_ptr->CallIsSyncedCallback(storage_page_id);
RunLoopUntilIdle();
EXPECT_TRUE(called1);
EXPECT_EQ(storage::Status::OK, status1);
EXPECT_EQ(PagePredicateResult::PAGE_OPENED, is_closed_and_synced1);
}
TEST_F(LedgerManagerTest, PageIsClosedOfflineAndEmptyCheckNotFound) {
bool called;
storage::Status status;
PagePredicateResult is_closed_offline_empty;
PageId id = RandomId();
// Check for a page that doesn't exist.
storage_ptr->should_get_page_fail = true;
ledger_manager_->PageIsClosedOfflineAndEmpty(
id.id, callback::Capture(callback::SetWhenCalled(&called), &status,
&is_closed_offline_empty));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::PAGE_NOT_FOUND, status);
}
TEST_F(LedgerManagerTest, PageIsClosedOfflineAndEmptyCheckClosed) {
bool called;
PagePredicateResult is_closed_offline_empty;
storage_ptr->should_get_page_fail = false;
PagePtr page;
PageId id = RandomId();
storage::PageIdView storage_page_id = convert::ExtendedStringView(id.id);
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
storage_ptr->set_page_storage_offline_empty(storage_page_id, true);
storage::Status storage_status;
ledger_manager_->PageIsClosedOfflineAndEmpty(
storage_page_id,
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_offline_empty));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::PAGE_OPENED, is_closed_offline_empty);
// Close the page. PagePredicateResult should now be true.
page.Unbind();
RunLoopUntilIdle();
ledger_manager_->PageIsClosedOfflineAndEmpty(
storage_page_id,
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&is_closed_offline_empty));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, storage_status);
EXPECT_EQ(PagePredicateResult::YES, is_closed_offline_empty);
}
TEST_F(LedgerManagerTest, PageIsClosedOfflineAndEmptyCanDeletePageOnCallback) {
bool page_is_empty_called = false;
storage::Status page_is_empty_status;
PagePredicateResult is_closed_offline_empty;
bool delete_page_called = false;
storage::Status delete_page_status;
PageId id = RandomId();
// The page is closed, offline and empty. Try to delete the page storage in
// the callback.
storage_ptr->set_page_storage_offline_empty(id.id, true);
ledger_manager_->PageIsClosedOfflineAndEmpty(
id.id, [&](storage::Status status, PagePredicateResult result) {
page_is_empty_called = true;
page_is_empty_status = status;
is_closed_offline_empty = result;
ledger_manager_->DeletePageStorage(
id.id,
callback::Capture(callback::SetWhenCalled(&delete_page_called),
&delete_page_status));
});
RunLoopUntilIdle();
// Make sure the deletion finishes successfully.
ASSERT_NE(nullptr, storage_ptr->delete_page_storage_callback);
storage_ptr->delete_page_storage_callback(storage::Status::OK);
RunLoopUntilIdle();
EXPECT_TRUE(page_is_empty_called);
EXPECT_EQ(storage::Status::OK, page_is_empty_status);
EXPECT_EQ(PagePredicateResult::YES, is_closed_offline_empty);
EXPECT_TRUE(delete_page_called);
EXPECT_EQ(storage::Status::OK, delete_page_status);
}
// Verifies that two successive calls to GetPage do not create 2 storages.
TEST_F(LedgerManagerTest, CallGetPageTwice) {
PageId id = RandomId();
PagePtr page1;
ledger_->GetPage(fidl::MakeOptional(id), page1.NewRequest());
PagePtr page2;
ledger_->GetPage(fidl::MakeOptional(id), page2.NewRequest());
RunLoopUntilIdle();
EXPECT_EQ(0u, storage_ptr->create_page_calls.size());
ASSERT_EQ(1u, storage_ptr->get_page_calls.size());
EXPECT_EQ(convert::ToString(id.id), storage_ptr->get_page_calls[0]);
}
// Cloud should never be queried.
TEST_F(LedgerManagerTest, GetPageDoNotCallTheCloud) {
storage_ptr->should_get_page_fail = true;
zx_status_t status;
PagePtr page;
PageId id = RandomId();
bool called;
storage_ptr->ClearCalls();
// Get the root page.
ledger_.set_error_handler(
callback::Capture(callback::SetWhenCalled(&called), &status));
ledger_->GetRootPage(page.NewRequest());
RunLoopUntilIdle();
EXPECT_FALSE(ledger_.is_bound());
EXPECT_TRUE(called);
EXPECT_EQ(static_cast<int32_t>(storage::Status::INTERNAL_ERROR),
static_cast<int32_t>(status));
EXPECT_FALSE(ledger_);
EXPECT_FALSE(sync_ptr->called);
page.Unbind();
ResetLedgerPtr();
storage_ptr->ClearCalls();
// Get a new page with a random id.
ledger_.set_error_handler(
callback::Capture(callback::SetWhenCalled(&called), &status));
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
EXPECT_FALSE(ledger_.is_bound());
EXPECT_TRUE(called);
EXPECT_EQ(static_cast<int32_t>(storage::Status::INTERNAL_ERROR),
static_cast<int32_t>(status));
EXPECT_FALSE(ledger_);
EXPECT_FALSE(sync_ptr->called);
page.Unbind();
ResetLedgerPtr();
storage_ptr->ClearCalls();
// Create a new page.
ledger_.set_error_handler(
callback::Capture(callback::SetWhenCalled(&called), &status));
ledger_->GetPage(nullptr, page.NewRequest());
RunLoopUntilIdle();
EXPECT_FALSE(ledger_.is_bound());
EXPECT_TRUE(called);
EXPECT_EQ(static_cast<int32_t>(storage::Status::INTERNAL_ERROR),
static_cast<int32_t>(status));
EXPECT_FALSE(ledger_);
EXPECT_FALSE(sync_ptr->called);
}
TEST_F(LedgerManagerTest, OnPageOpenedClosedCalls) {
PagePtr page1;
PagePtr page2;
PageId id = RandomId();
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.
ledger_->GetPage(fidl::MakeOptional(id), page1.NewRequest());
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);
// Open the page again and check that there is no new call to OnPageOpened.
ledger_->GetPage(fidl::MakeOptional(id), page2.NewRequest());
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 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(LedgerManagerTest, OnPageOpenedClosedCallInternalRequest) {
PagePtr page;
PageId id = RandomId();
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;
storage::Status storage_status;
PagePredicateResult page_state;
ledger_manager_->PageIsClosedAndSynced(
convert::ToString(id.id),
callback::Capture(callback::SetWhenCalled(&called), &storage_status,
&page_state));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::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.
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
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);
}
TEST_F(LedgerManagerTest, OnPageOpenedClosedUnused) {
PagePtr page;
PageId id = RandomId();
storage::PageIdView storage_page_id = convert::ExtendedStringView(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.
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
// Mark the page as synced and close it.
storage_ptr->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_ptr->DelayIsSyncedCallback(storage_page_id, true);
storage::Status storage_status;
ledger_manager_->PageIsClosedAndSynced(
storage_page_id,
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.
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
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_ptr->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(LedgerManagerTest, DeletePageStorageWhenPageOpenFails) {
PagePtr page;
PageId id = RandomId();
bool called;
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
// Try to delete the page while it is open. Expect to get an error.
storage::Status storage_status;
ledger_manager_->DeletePageStorage(
id.id,
callback::Capture(callback::SetWhenCalled(&called), &storage_status));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::ILLEGAL_STATE, storage_status);
}
TEST_F(LedgerManagerTest, OpenPageWithDeletePageStorageInProgress) {
PagePtr page;
PageId id = RandomId();
// Start deleting the page.
bool delete_called;
storage::Status delete_status;
ledger_manager_->DeletePageStorage(
id.id, callback::Capture(callback::SetWhenCalled(&delete_called),
&delete_status));
RunLoopUntilIdle();
EXPECT_FALSE(delete_called);
// Try to open the same page.
bool get_page_done;
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
ledger_->Sync(callback::SetWhenCalled(&get_page_done));
RunLoopUntilIdle();
EXPECT_FALSE(get_page_done);
// After calling the callback registered in |DeletePageStorage| both
// operations should terminate without an error.
storage_ptr->delete_page_storage_callback(storage::Status::OK);
RunLoopUntilIdle();
EXPECT_TRUE(delete_called);
EXPECT_EQ(storage::Status::OK, delete_status);
EXPECT_TRUE(get_page_done);
}
TEST_F(LedgerManagerTest, ChangeConflictResolver) {
fidl::InterfaceHandle<ConflictResolverFactory> handle1;
fidl::InterfaceHandle<ConflictResolverFactory> handle2;
StubConflictResolverFactory factory1(handle1.NewRequest());
StubConflictResolverFactory factory2(handle2.NewRequest());
ledger_->SetConflictResolverFactory(std::move(handle1));
RunLoopUntilIdle();
ledger_->SetConflictResolverFactory(std::move(handle2));
RunLoopUntilIdle();
EXPECT_FALSE(factory1.disconnected);
EXPECT_FALSE(factory2.disconnected);
}
TEST_F(LedgerManagerTest, MultipleConflictResolvers) {
fidl::InterfaceHandle<ConflictResolverFactory> handle1;
fidl::InterfaceHandle<ConflictResolverFactory> handle2;
StubConflictResolverFactory factory1(handle1.NewRequest());
StubConflictResolverFactory factory2(handle2.NewRequest());
LedgerPtr ledger2;
ledger_manager_->BindLedger(ledger2.NewRequest());
ledger_->SetConflictResolverFactory(std::move(handle1));
RunLoopUntilIdle();
ledger2->SetConflictResolverFactory(std::move(handle2));
RunLoopUntilIdle();
EXPECT_FALSE(factory1.disconnected);
EXPECT_FALSE(factory2.disconnected);
}
} // namespace
} // namespace ledger