blob: f33770cbdc8b1a38b1c7a8e9255bfe1e080bdc11 [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/fidl/cpp/optional.h>
#include <lib/fit/function.h>
#include <zircon/errors.h>
#include <zircon/syscalls.h>
#include <cstdint>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/ledger/bin/app/constants.h"
#include "src/ledger/bin/app/disk_cleanup_manager_impl.h"
#include "src/ledger/bin/clocks/testing/device_id_manager_empty_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/platform/scoped_tmp_location.h"
#include "src/ledger/bin/storage/fake/fake_db_factory.h"
#include "src/ledger/bin/storage/fake/fake_ledger_storage.h"
#include "src/ledger/bin/storage/fake/fake_page_storage.h"
#include "src/ledger/bin/storage/impl/ledger_storage_impl.h"
#include "src/ledger/bin/storage/public/ledger_storage.h"
#include "src/ledger/bin/storage/testing/page_storage_empty_impl.h"
#include "src/ledger/bin/sync_coordinator/public/ledger_sync.h"
#include "src/ledger/bin/sync_coordinator/testing/fake_ledger_sync.h"
#include "src/ledger/bin/testing/fake_disk_cleanup_manager.h"
#include "src/ledger/bin/testing/test_with_environment.h"
#include "src/ledger/lib/callback/capture.h"
#include "src/ledger/lib/callback/set_when_called.h"
#include "src/ledger/lib/convert/convert.h"
#include "src/ledger/lib/logging/logging.h"
#include "third_party/abseil-cpp/absl/strings/string_view.h"
namespace ledger {
namespace {
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::ElementsAreArray;
constexpr absl::string_view kLedgerName = "ledger_under_test";
PageId RandomId(const Environment& environment) {
PageId result;
environment.random()->Draw(&result.id);
return result;
}
class LedgerManagerTest : public TestWithEnvironment {
public:
LedgerManagerTest() = default;
LedgerManagerTest(const LedgerManagerTest&) = delete;
LedgerManagerTest& operator=(const LedgerManagerTest&) = delete;
~LedgerManagerTest() override = default;
// gtest::TestWithEnvironment:
void SetUp() override {
TestWithEnvironment::SetUp();
std::unique_ptr<storage::fake::FakeLedgerStorage> storage =
std::make_unique<storage::fake::FakeLedgerStorage>(&environment_);
storage_ptr = storage.get();
std::unique_ptr<sync_coordinator::FakeLedgerSync> sync =
std::make_unique<sync_coordinator::FakeLedgerSync>();
sync_ptr = sync.get();
disk_cleanup_manager_ = std::make_unique<FakeDiskCleanupManager>();
ledger_manager_ = std::make_unique<LedgerManager>(
&environment_, convert::ToString(kLedgerName),
std::make_unique<encryption::FakeEncryptionService>(dispatcher()), std::move(storage),
std::move(sync), std::vector<PageUsageListener*>{disk_cleanup_manager_.get()});
ResetLedgerPtr();
}
void ResetLedgerPtr() { ledger_manager_->BindLedger(ledger_.NewRequest()); }
protected:
storage::fake::FakeLedgerStorage* storage_ptr;
sync_coordinator::FakeLedgerSync* sync_ptr;
std::unique_ptr<FakeDiskCleanupManager> disk_cleanup_manager_;
std::unique_ptr<LedgerManager> ledger_manager_;
LedgerPtr ledger_;
};
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(storage_ptr->create_page_calls.size(), 0u);
EXPECT_EQ(storage_ptr->get_page_calls.size(), 0u);
PagePtr page;
storage_ptr->should_get_page_fail = true;
ledger_->GetPage(nullptr, page.NewRequest());
RunLoopUntilIdle();
EXPECT_EQ(storage_ptr->create_page_calls.size(), 1u);
EXPECT_EQ(storage_ptr->get_page_calls.size(), 1u);
EXPECT_FALSE(ledger_);
page.Unbind();
storage_ptr->ClearCalls();
ResetLedgerPtr();
storage_ptr->should_get_page_fail = true;
ledger_->GetRootPage(page.NewRequest());
RunLoopUntilIdle();
EXPECT_EQ(storage_ptr->create_page_calls.size(), 1u);
EXPECT_EQ(storage_ptr->get_page_calls.size(), 1u);
EXPECT_FALSE(ledger_);
page.Unbind();
storage_ptr->ClearCalls();
ResetLedgerPtr();
PageId id = RandomId(environment_);
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
EXPECT_EQ(storage_ptr->create_page_calls.size(), 1u);
ASSERT_EQ(storage_ptr->get_page_calls.size(), 1u);
EXPECT_FALSE(ledger_);
EXPECT_EQ(storage_ptr->get_page_calls[0], convert::ToString(id.id));
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, OnDiscardableCalled) {
bool on_discardable_called;
ledger_manager_->SetOnDiscardable(SetWhenCalled(&on_discardable_called));
ledger_.Unbind();
RunLoopUntilIdle();
EXPECT_TRUE(on_discardable_called);
}
TEST_F(LedgerManagerTest, OnDiscardableCalledWhenLastDetacherCalled) {
bool on_discardable_called;
ledger_manager_->SetOnDiscardable(SetWhenCalled(&on_discardable_called));
auto first_detacher = ledger_manager_->CreateDetacher();
auto second_detacher = ledger_manager_->CreateDetacher();
ledger_.Unbind();
RunLoopUntilIdle();
EXPECT_FALSE(on_discardable_called);
first_detacher();
RunLoopUntilIdle();
EXPECT_FALSE(on_discardable_called);
auto third_detacher = ledger_manager_->CreateDetacher();
RunLoopUntilIdle();
EXPECT_FALSE(on_discardable_called);
second_detacher();
RunLoopUntilIdle();
EXPECT_FALSE(on_discardable_called);
third_detacher();
RunLoopUntilIdle();
EXPECT_TRUE(on_discardable_called);
}
// Verifies that the LedgerManager does not call its callback while a page is
// being deleted.
TEST_F(LedgerManagerTest, NonEmptyDuringDeletion) {
bool on_discardable_called;
ledger_manager_->SetOnDiscardable(SetWhenCalled(&on_discardable_called));
PageId id = RandomId(environment_);
bool delete_page_called;
Status delete_page_status;
ledger_manager_->DeletePageStorage(
id.id, Capture(SetWhenCalled(&delete_page_called), &delete_page_status));
// Empty the Ledger manager.
ledger_.Unbind();
RunLoopUntilIdle();
EXPECT_FALSE(on_discardable_called);
// Complete the deletion successfully.
ASSERT_TRUE(storage_ptr->delete_page_storage_callback);
storage_ptr->delete_page_storage_callback(Status::OK);
RunLoopUntilIdle();
EXPECT_TRUE(delete_page_called);
EXPECT_EQ(delete_page_status, Status::OK);
EXPECT_TRUE(on_discardable_called);
}
// 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(environment_);
bool called;
storage_ptr->ClearCalls();
// Get the root page.
ledger_.set_error_handler(Capture(SetWhenCalled(&called), &status));
ledger_->GetRootPage(page.NewRequest());
RunLoopUntilIdle();
EXPECT_FALSE(ledger_.is_bound());
EXPECT_TRUE(called);
EXPECT_EQ(status, ZX_ERR_IO);
EXPECT_FALSE(ledger_);
EXPECT_FALSE(sync_ptr->IsCalled());
page.Unbind();
ResetLedgerPtr();
storage_ptr->ClearCalls();
// Get a new page with a random id.
ledger_.set_error_handler(Capture(SetWhenCalled(&called), &status));
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
EXPECT_FALSE(ledger_.is_bound());
EXPECT_TRUE(called);
EXPECT_EQ(status, ZX_ERR_IO);
EXPECT_FALSE(ledger_);
EXPECT_FALSE(sync_ptr->IsCalled());
page.Unbind();
ResetLedgerPtr();
storage_ptr->ClearCalls();
// Create a new page.
ledger_.set_error_handler(Capture(SetWhenCalled(&called), &status));
ledger_->GetPage(nullptr, page.NewRequest());
RunLoopUntilIdle();
EXPECT_FALSE(ledger_.is_bound());
EXPECT_TRUE(called);
EXPECT_EQ(status, ZX_ERR_IO);
EXPECT_FALSE(ledger_);
EXPECT_FALSE(sync_ptr->IsCalled());
}
TEST_F(LedgerManagerTest, OpenPageWithDeletePageStorageInProgress) {
PagePtr page;
PageId id = RandomId(environment_);
// Start deleting the page.
bool delete_called;
Status delete_status;
ledger_manager_->DeletePageStorage(id.id, Capture(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(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(Status::OK);
RunLoopUntilIdle();
EXPECT_TRUE(delete_called);
EXPECT_EQ(delete_status, Status::OK);
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);
}
// Verifies that the LedgerManager triggers page sync for a page that exists and was closed.
TEST_F(LedgerManagerTest, TrySyncClosedPageSyncStarted) {
storage_ptr->should_get_page_fail = false;
PagePtr page;
PageId id = RandomId(environment_);
storage::PageIdView storage_page_id = convert::ExtendedStringView(id.id);
storage::PageId page_id = convert::ToString(id.id);
EXPECT_EQ(sync_ptr->GetSyncCallsCount(page_id), 0);
// Opens the page and starts the sync with the cloud for the first time.
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
EXPECT_EQ(sync_ptr->GetSyncCallsCount(page_id), 1);
page.Unbind();
RunLoopUntilIdle();
// Reopens closed page and starts the sync.
ledger_manager_->TrySyncClosedPage(storage_page_id);
RunLoopUntilIdle();
EXPECT_EQ(sync_ptr->GetSyncCallsCount(page_id), 2);
}
// Verifies that the LedgerManager does not triggers the sync for a currently open page.
TEST_F(LedgerManagerTest, TrySyncClosedPageWithOpenedPage) {
storage_ptr->should_get_page_fail = false;
PagePtr page;
PageId id = RandomId(environment_);
storage::PageIdView storage_page_id = convert::ExtendedStringView(id.id);
storage::PageId page_id = convert::ToString(id.id);
EXPECT_EQ(sync_ptr->GetSyncCallsCount(page_id), 0);
// Opens the page and starts the sync with the cloud for the first time.
ledger_->GetPage(fidl::MakeOptional(id), page.NewRequest());
RunLoopUntilIdle();
EXPECT_EQ(sync_ptr->GetSyncCallsCount(page_id), 1);
// Tries to reopen the already-open page.
ledger_manager_->TrySyncClosedPage(storage_page_id);
RunLoopUntilIdle();
EXPECT_EQ(sync_ptr->GetSyncCallsCount(page_id), 1);
}
class DelayingLedgerStorage : public storage::LedgerStorage {
public:
DelayingLedgerStorage() = default;
~DelayingLedgerStorage() override = default;
void CreatePageStorage(storage::PageId page_id,
fit::function<void(storage::Status, std::unique_ptr<storage::PageStorage>)>
callback) override {
get_page_calls_.emplace_back(std::move(page_id), std::move(callback));
}
void GetPageStorage(storage::PageId page_id,
fit::function<void(storage::Status, std::unique_ptr<storage::PageStorage>)>
callback) override {
get_page_calls_.emplace_back(std::move(page_id), std::move(callback));
}
void DeletePageStorage(storage::PageIdView /*page_id*/,
fit::function<void(storage::Status)> callback) override {
LEDGER_NOTIMPLEMENTED();
callback(Status::NOT_IMPLEMENTED);
}
void ListPages(
fit::function<void(storage::Status, std::set<storage::PageId>)> callback) override {
LEDGER_NOTIMPLEMENTED();
callback(Status::NOT_IMPLEMENTED, {});
};
std::vector<std::pair<
storage::PageId, fit::function<void(storage::Status, std::unique_ptr<storage::PageStorage>)>>>
get_page_calls_;
};
// Verifies that closing a page before PageStorage is ready does not leave live
// objects.
// In this test, we request a Page, but before we are able to construct a
// PageStorage, we close the connection. In that case, we should not leak
// anything.
TEST_F(LedgerManagerTest, GetPageDisconnect) {
// Setup
std::unique_ptr<DelayingLedgerStorage> storage = std::make_unique<DelayingLedgerStorage>();
auto storage_ptr = storage.get();
std::unique_ptr<sync_coordinator::FakeLedgerSync> sync =
std::make_unique<sync_coordinator::FakeLedgerSync>();
auto disk_cleanup_manager = std::make_unique<FakeDiskCleanupManager>();
auto ledger_manager = std::make_unique<LedgerManager>(
&environment_, convert::ToString(kLedgerName),
std::make_unique<encryption::FakeEncryptionService>(dispatcher()), std::move(storage),
std::move(sync), std::vector<PageUsageListener*>{disk_cleanup_manager_.get()});
LedgerPtr ledger;
ledger_manager->BindLedger(ledger.NewRequest());
RunLoopUntilIdle();
PageId id = RandomId(environment_);
PagePtr page1;
ledger->GetPage(fidl::MakeOptional(id), page1.NewRequest());
RunLoopUntilIdle();
ASSERT_THAT(storage_ptr->get_page_calls_, testing::SizeIs(1));
page1.Unbind();
RunLoopUntilIdle();
auto it = storage_ptr->get_page_calls_.begin();
it->second(storage::Status::OK,
std::make_unique<storage::fake::FakePageStorage>(&environment_, it->first));
RunLoopUntilIdle();
PagePtr page2;
ledger->GetPage(fidl::MakeOptional(id), page2.NewRequest());
RunLoopUntilIdle();
// We verify that we ask again for a new PageStorage, the previous one was
// correctly discarded.
EXPECT_THAT(storage_ptr->get_page_calls_, testing::SizeIs(2));
}
} // namespace
} // namespace ledger