blob: 1f22d4f591ee43dd874bf791e1459d534bc6c59b [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/background_sync_manager.h"
#include <lib/gtest/test_loop_fixture.h>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "peridot/lib/scoped_tmpfs/scoped_tmpfs.h"
#include "src/ledger/bin/environment/environment.h"
#include "src/ledger/bin/storage/fake/fake_db_factory.h"
#include "src/ledger/bin/testing/test_with_environment.h"
#include "src/ledger/lib/coroutine/coroutine_manager.h"
namespace ledger {
namespace {
// A Delegate that helps to keep track of pages being propagated for synchronization with the
// cloud.
class FakeDelegate : public BackgroundSyncManager::Delegate {
public:
FakeDelegate(Environment* environment, PageUsageDb* db,
BackgroundSyncManager* background_sync_manager)
: db_(db),
background_sync_manager_(background_sync_manager),
coroutine_manager_(environment->coroutine_service()) {}
void TrySyncClosedPage(fxl::StringView ledger_name, storage::PageIdView page_id) override {
++sync_calls_[{ledger_name.ToString(), page_id.ToString()}];
coroutine_manager_.StartCoroutine(
[db = db_, background_sync_manager = background_sync_manager_,
ledger_name_str = ledger_name.ToString(),
page_id_str = page_id.ToString()](coroutine::CoroutineHandler* handler) {
Status status = Status::OK;
status = db->MarkPageOpened(handler, ledger_name_str, page_id_str);
EXPECT_EQ(status, Status::OK);
background_sync_manager->OnInternallyUsed(ledger_name_str, page_id_str);
});
}
// Simulates the end of page sync by marking it as closed and sending notifications about page
// being closed.
void FinishSyncPage(fxl::StringView ledger_name, storage::PageIdView page_id) {
coroutine_manager_.StartCoroutine(
[db = db_, background_sync_manager = background_sync_manager_,
ledger_name_str = ledger_name.ToString(),
page_id_str = page_id.ToString()](coroutine::CoroutineHandler* handler) {
Status status = Status::OK;
status = db->MarkPageClosed(handler, ledger_name_str, page_id_str);
EXPECT_EQ(status, Status::OK);
background_sync_manager->OnInternallyUnused(ledger_name_str, page_id_str);
});
}
// Returns the number of times synchronization was triggered for the given page.
int GetSyncCallsCount(const std::string& ledger_name, const storage::PageId& page_id) {
auto page_it = sync_calls_.find({ledger_name, page_id});
if (page_it == sync_calls_.end()) {
return 0;
}
return page_it->second;
}
// Removes all the information about previous calls for pages sync.
void Clear() { sync_calls_.clear(); }
private:
// Stores a counter per page that records how many times the sync with the cloud was triggered.
std::map<std::pair<std::string, storage::PageId>, int> sync_calls_;
PageUsageDb* db_;
BackgroundSyncManager* background_sync_manager_;
coroutine::CoroutineManager coroutine_manager_;
};
} // namespace
class BackgroundSyncManagerTest : public TestWithEnvironment {
public:
BackgroundSyncManagerTest()
: db_factory_(environment_.dispatcher()),
db_(std::make_unique<PageUsageDb>(environment_.clock(), &db_factory_,
DetachedPath(tmpfs_.root_fd()))),
background_sync_manager_(&environment_, db_.get(), /*open_pages_limit=*/10),
delegate_(&environment_, db_.get(), &background_sync_manager_) {}
// gtest::TestLoopFixture:
void SetUp() override {
Status status;
RunInCoroutine([&](coroutine::CoroutineHandler* handler) { status = db_->Init(handler); });
RunLoopUntilIdle();
FXL_DCHECK(status == Status::OK);
delegate_.Clear();
background_sync_manager_.SetDelegate(&delegate_);
}
private:
scoped_tmpfs::ScopedTmpFS tmpfs_;
protected:
storage::fake::FakeDbFactory db_factory_;
std::unique_ptr<PageUsageDb> db_;
BackgroundSyncManager background_sync_manager_;
FakeDelegate delegate_;
};
TEST_F(BackgroundSyncManagerTest, AsynchronousExternalUsedAndUnused) {
std::string ledger_name = "ledger";
storage::PageId first_page_id = std::string(::fuchsia::ledger::PAGE_ID_SIZE, '1');
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
// Check for external usage.
EXPECT_EQ(db_->MarkPageOpened(handler, ledger_name, first_page_id), Status::OK);
background_sync_manager_.OnExternallyUsed(ledger_name, first_page_id);
EXPECT_EQ(db_->MarkPageClosed(handler, ledger_name, first_page_id), Status::OK);
background_sync_manager_.OnExternallyUsed(ledger_name, first_page_id);
background_sync_manager_.OnExternallyUnused(ledger_name, first_page_id);
RunLoopUntilIdle();
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, first_page_id), 0);
background_sync_manager_.OnExternallyUnused(ledger_name, first_page_id);
RunLoopUntilIdle();
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, first_page_id), 1);
});
}
TEST_F(BackgroundSyncManagerTest, AsynchronousInternalUsedAndUnused) {
std::string ledger_name = "ledger";
storage::PageId first_page_id = std::string(::fuchsia::ledger::PAGE_ID_SIZE, '1');
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
// Check for internal usage.
EXPECT_EQ(db_->MarkPageOpened(handler, ledger_name, first_page_id), Status::OK);
background_sync_manager_.OnInternallyUsed(ledger_name, first_page_id);
EXPECT_EQ(db_->MarkPageClosed(handler, ledger_name, first_page_id), Status::OK);
background_sync_manager_.OnInternallyUsed(ledger_name, first_page_id);
background_sync_manager_.OnInternallyUnused(ledger_name, first_page_id);
RunLoopUntilIdle();
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, first_page_id), 0);
background_sync_manager_.OnInternallyUnused(ledger_name, first_page_id);
RunLoopUntilIdle();
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, first_page_id), 1);
});
}
// TODO(https://bugs.fuchsia.dev/p/fuchsia/issues/detail?id=35727): This test should be rewritten,
// once there is any mechanism to store a synchronization state of a page. In this case, sync should
// not be triggered for the page again, if it has just been closed after sync.
TEST_F(BackgroundSyncManagerTest, SyncOnPageUnused) {
std::string ledger_name = "ledger";
storage::PageId first_page_id = std::string(::fuchsia::ledger::PAGE_ID_SIZE, '1');
storage::PageId second_page_id = std::string(::fuchsia::ledger::PAGE_ID_SIZE, '2');
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
EXPECT_EQ(db_->MarkPageOpened(handler, ledger_name, first_page_id), Status::OK);
background_sync_manager_.OnExternallyUsed(ledger_name, first_page_id);
EXPECT_EQ(db_->MarkPageClosed(handler, ledger_name, first_page_id), Status::OK);
background_sync_manager_.OnExternallyUnused(ledger_name, first_page_id);
RunLoopUntilIdle();
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, first_page_id), 1);
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, second_page_id), 0);
delegate_.FinishSyncPage(ledger_name, first_page_id);
RunLoopUntilIdle();
EXPECT_EQ(db_->MarkPageOpened(handler, ledger_name, second_page_id), Status::OK);
background_sync_manager_.OnInternallyUsed(ledger_name, second_page_id);
EXPECT_EQ(db_->MarkPageClosed(handler, ledger_name, second_page_id), Status::OK);
background_sync_manager_.OnInternallyUnused(ledger_name, second_page_id);
RunLoopUntilIdle();
// By the time the second page is unused, first one is closed after synchronization. This leads
// to sync being triggered for this page again, as the limit of open at once pages was not
// reached.
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, first_page_id), 2);
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, second_page_id), 1);
});
}
TEST_F(BackgroundSyncManagerTest, DontSyncWhenInternalConnectionRemain) {
std::string ledger_name = "ledger";
storage::PageId first_page_id = std::string(::fuchsia::ledger::PAGE_ID_SIZE, '1');
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
EXPECT_EQ(db_->MarkPageOpened(handler, ledger_name, first_page_id), Status::OK);
background_sync_manager_.OnExternallyUsed(ledger_name, first_page_id);
background_sync_manager_.OnInternallyUsed(ledger_name, first_page_id);
background_sync_manager_.OnExternallyUnused(ledger_name, first_page_id);
RunLoopUntilIdle();
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, first_page_id), 0);
});
}
TEST_F(BackgroundSyncManagerTest, DontSyncWhenExternalConnectionRemain) {
std::string ledger_name = "ledger";
storage::PageId first_page_id = std::string(::fuchsia::ledger::PAGE_ID_SIZE, '1');
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
EXPECT_EQ(db_->MarkPageOpened(handler, ledger_name, first_page_id), Status::OK);
background_sync_manager_.OnExternallyUsed(ledger_name, first_page_id);
background_sync_manager_.OnInternallyUsed(ledger_name, first_page_id);
background_sync_manager_.OnInternallyUnused(ledger_name, first_page_id);
RunLoopUntilIdle();
EXPECT_EQ(delegate_.GetSyncCallsCount(ledger_name, first_page_id), 0);
});
}
// Verifies that BackgroundSyncManager does not exceed the limit of allowed number of open at once
// pages by starting synchronizartion of closed pages.
TEST_F(BackgroundSyncManagerTest, DontStartSyncWhenManyPageAreOpen) {
// Sets the limit of allowed number of open pages to 2.
BackgroundSyncManager background_sync_manager =
BackgroundSyncManager(&environment_, db_.get(), 2);
FakeDelegate delegate = FakeDelegate(&environment_, db_.get(), &background_sync_manager);
background_sync_manager.SetDelegate(&delegate);
std::string ledger_name = "ledger";
std::vector<storage::PageId> page_ids;
for (int id = 0; id < 4; ++id) {
page_ids.push_back(storage::PageId(std::string(::fuchsia::ledger::PAGE_ID_SIZE, '0' + id)));
}
RunInCoroutine([&](coroutine::CoroutineHandler* handler) {
for (size_t id = 0; id < 3; ++id) {
// Marks the given page as closed in the Db and waits for the operation to be finished to
// guarantee the preservation of the call order among corresponding closure timestamps.
EXPECT_EQ(db_->MarkPageClosed(handler, ledger_name, page_ids[id]), Status::OK);
RunLoopUntilIdle();
}
EXPECT_EQ(db_->MarkPageOpened(handler, ledger_name, page_ids[3]), Status::OK);
background_sync_manager.OnExternallyUsed(ledger_name, page_ids[3]);
EXPECT_EQ(db_->MarkPageClosed(handler, ledger_name, page_ids[3]), Status::OK);
background_sync_manager.OnExternallyUnused(ledger_name, page_ids[3]);
RunLoopUntilIdle();
// The closure of fourth page should trigger sync for the first and second ones since they are
// marked as closed and have the oldest timestamps.
EXPECT_EQ(delegate.GetSyncCallsCount(ledger_name, page_ids[0]), 1);
EXPECT_EQ(delegate.GetSyncCallsCount(ledger_name, page_ids[1]), 1);
EXPECT_EQ(delegate.GetSyncCallsCount(ledger_name, page_ids[2]), 0);
EXPECT_EQ(delegate.GetSyncCallsCount(ledger_name, page_ids[3]), 0);
delegate.FinishSyncPage(ledger_name, page_ids[0]);
RunLoopUntilIdle();
// The end of first page synchronization should trigger the sync of third one, since the page is
// marked as closed after sync is finished.
EXPECT_EQ(delegate.GetSyncCallsCount(ledger_name, page_ids[0]), 1);
EXPECT_EQ(delegate.GetSyncCallsCount(ledger_name, page_ids[1]), 1);
EXPECT_EQ(delegate.GetSyncCallsCount(ledger_name, page_ids[2]), 1);
EXPECT_EQ(delegate.GetSyncCallsCount(ledger_name, page_ids[3]), 0);
});
}
} // namespace ledger