blob: 1b850190f6bbe127755180151f7913a4ea9ab5fc [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/page_impl.h"
#include <fuchsia/ledger/internal/cpp/fidl.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/clone.h>
#include <zircon/errors.h>
#include <algorithm>
#include <map>
#include <memory>
#include "fuchsia/ledger/cpp/fidl.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "src/ledger/bin/app/active_page_manager.h"
#include "src/ledger/bin/app/constants.h"
#include "src/ledger/bin/app/fidl/serialization_size.h"
#include "src/ledger/bin/app/merging/merge_resolver.h"
#include "src/ledger/bin/fidl/include/types.h"
#include "src/ledger/bin/storage/fake/fake_journal.h"
#include "src/ledger/bin/storage/fake/fake_journal_delegate.h"
#include "src/ledger/bin/storage/fake/fake_page_storage.h"
#include "src/ledger/bin/storage/testing/storage_matcher.h"
#include "src/ledger/bin/testing/ledger_matcher.h"
#include "src/ledger/bin/testing/test_with_environment.h"
#include "src/ledger/lib/backoff/exponential_backoff.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 "src/ledger/lib/socket/strings.h"
#include "src/ledger/lib/vmo/strings.h"
#include "third_party/abseil-cpp/absl/strings/str_format.h"
using testing::Contains;
using testing::ElementsAre;
using testing::IsEmpty;
using testing::Key;
using testing::Not;
using testing::Pair;
using testing::SizeIs;
namespace ledger {
namespace {
constexpr int kEntryCountToSendBeforeFlushing = 200;
std::string ToString(const fuchsia::mem::BufferPtr& vmo) {
std::string value;
bool status = StringFromVmo(*vmo, &value);
LEDGER_DCHECK(status);
return value;
}
class PageImplTest : public TestWithEnvironment {
public:
PageImplTest() = default;
PageImplTest(const PageImplTest&) = delete;
PageImplTest& operator=(const PageImplTest&) = delete;
~PageImplTest() override = default;
protected:
// ApplicationTestBase:
void SetUp() override {
::testing::Test::SetUp();
page_id1_ = storage::PageId(::fuchsia::ledger::PAGE_ID_SIZE, 'a');
auto fake_storage = std::make_unique<storage::fake::FakePageStorage>(&environment_, page_id1_);
fake_storage_ = fake_storage.get();
auto resolver = std::make_unique<MergeResolver>(
[] {}, &environment_, fake_storage_,
std::make_unique<ExponentialBackoff>(zx::sec(0), 1u, zx::sec(0),
environment_.random()->NewBitGenerator<uint64_t>()));
resolver_ = resolver.get();
manager_ = std::make_unique<ActivePageManager>(&environment_, std::move(fake_storage), nullptr,
std::move(resolver),
ActivePageManager::PageStorageState::NEEDS_SYNC);
bool called;
Status status;
auto page_impl =
std::make_unique<PageImpl>(environment_.dispatcher(), page_id1_, page_ptr_.NewRequest());
manager_->AddPageImpl(std::move(page_impl), Capture(SetWhenCalled(&called), &status));
EXPECT_TRUE(called);
EXPECT_EQ(status, Status::OK);
DrainLoop();
}
// Run the message loop until there is nothing left to dispatch.
void DrainLoop() { RunLoopRepeatedlyFor(storage::fake::kFakePageStorageDelay); }
void CommitFirstPendingJournal(
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals) {
for (const auto& journal_pair : journals) {
const auto& journal = journal_pair.second;
if (!journal->IsCommitted()) {
journal->ResolvePendingCommit(Status::OK);
return;
}
}
}
storage::ObjectIdentifier AddObjectToStorage(std::string value_string) {
bool called;
Status status;
storage::ObjectIdentifier object_identifier;
fake_storage_->AddObjectFromLocal(storage::ObjectType::BLOB,
storage::DataSource::Create(std::move(value_string)), {},
Capture(SetWhenCalled(&called), &status, &object_identifier));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(status, Status::OK);
return object_identifier;
}
std::unique_ptr<const storage::Object> AddObject(const std::string& value) {
storage::ObjectIdentifier object_identifier = AddObjectToStorage(value);
bool called;
Status status;
std::unique_ptr<const storage::Object> object;
fake_storage_->GetObject(object_identifier, storage::PageStorage::Location::Local(),
Capture(SetWhenCalled(&called), &status, &object));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(status, Status::OK);
return object;
}
std::string GetKey(size_t index, size_t min_key_size = 0u) {
std::string result = absl::StrFormat("key %04" PRIuMAX, index);
result.resize(std::max(result.size(), min_key_size));
return result;
}
std::string GetValue(size_t index, size_t min_value_size = 0u) {
std::string result = absl::StrFormat("val %zu", index);
result.resize(std::max(result.size(), min_value_size));
return result;
}
void AddEntries(int entry_count, size_t min_key_size = 0u, size_t min_value_size = 0u) {
LEDGER_DCHECK(entry_count <= 10000);
page_ptr_->StartTransaction();
for (int i = 0; i < entry_count; ++i) {
page_ptr_->Put(convert::ToArray(GetKey(i, min_key_size)),
convert::ToArray(GetValue(i, min_value_size)));
if (i % kEntryCountToSendBeforeFlushing == 0) {
// Drain the loop every so often, otherwise the kernel will kill the
// test because it queues too many messages on a channel.
DrainLoop();
}
}
page_ptr_->Commit();
}
PageSnapshotPtr GetSnapshot(std::vector<uint8_t> prefix = {}) {
PageSnapshotPtr snapshot;
page_ptr_->GetSnapshot(snapshot.NewRequest(), std::move(prefix), nullptr);
return snapshot;
}
storage::PageId page_id1_;
storage::fake::FakePageStorage* fake_storage_;
std::unique_ptr<ActivePageManager> manager_;
MergeResolver* resolver_;
PagePtr page_ptr_;
};
TEST_F(PageImplTest, GetId) {
bool called;
PageId page_id;
page_ptr_->GetId(Capture(SetWhenCalled(&called), &page_id));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(convert::ToString(page_id.id), page_id1_);
}
TEST_F(PageImplTest, PutNoTransaction) {
std::string key("some_key");
std::string value("a small value");
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value));
DrainLoop();
auto objects = fake_storage_->GetObjects();
EXPECT_EQ(objects.size(), 1u);
storage::ObjectIdentifier object_identifier = objects.begin()->first;
std::string actual_value = objects.begin()->second;
EXPECT_EQ(actual_value, value);
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 1u);
auto it = journals.begin();
EXPECT_TRUE(it->second->IsCommitted());
EXPECT_EQ(it->second->GetData().size(), 1u);
storage::Entry entry = it->second->GetData().at(key);
EXPECT_EQ(entry.object_identifier, object_identifier);
EXPECT_EQ(entry.priority, storage::KeyPriority::EAGER);
}
TEST_F(PageImplTest, PutReferenceNoTransaction) {
std::string object_data("some_data");
SizedVmo vmo;
ASSERT_TRUE(VmoFromString(object_data, &vmo));
bool called;
fuchsia::ledger::Page_CreateReferenceFromBuffer_Result result;
page_ptr_->CreateReferenceFromBuffer(std::move(vmo).ToTransport(),
Capture(SetWhenCalled(&called), &result));
DrainLoop();
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_response());
std::string key("some_key");
page_ptr_->PutReference(convert::ToArray(key), std::move(result.response().reference),
Priority::LAZY);
DrainLoop();
auto objects = fake_storage_->GetObjects();
// No object should have been added.
EXPECT_EQ(objects.size(), 1u);
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 1u);
auto it = journals.begin();
EXPECT_TRUE(it->second->IsCommitted());
EXPECT_EQ(it->second->GetData().size(), 1u);
storage::Entry entry = it->second->GetData().at(key);
std::unique_ptr<const storage::Object> object = AddObject(object_data);
EXPECT_EQ(entry.object_identifier.object_digest(), object->GetIdentifier().object_digest());
EXPECT_EQ(entry.priority, storage::KeyPriority::LAZY);
}
TEST_F(PageImplTest, PutUnknownReference) {
std::string key("some_key");
Reference reference;
reference.opaque_id = convert::ToArray("12345678");
bool called;
zx_status_t status;
page_ptr_.set_error_handler(Capture(SetWhenCalled(&called), &status));
page_ptr_->PutReference(convert::ToArray(key), std::move(reference), Priority::LAZY);
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(status, ZX_ERR_INVALID_ARGS);
auto objects = fake_storage_->GetObjects();
// No object should have been added.
EXPECT_EQ(objects.size(), 0u);
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 0u);
}
TEST_F(PageImplTest, PutKeyTooLarge) {
std::string value("a small value");
zx::channel writer, reader;
ASSERT_EQ(zx::channel::create(0, &writer, &reader), ZX_OK);
page_ptr_.Bind(std::move(writer));
// Key too large; message doesn't go through, failing on validation.
const size_t key_size = kMaxKeySize + 1;
std::string key = GetKey(1, key_size);
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value));
zx_status_t status = reader.read(0, nullptr, nullptr, 0, 0, nullptr, nullptr);
DrainLoop();
EXPECT_EQ(status, ZX_ERR_SHOULD_WAIT);
// With a smaller key, message goes through.
key = GetKey(1, kMaxKeySize);
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value));
status = reader.read(0, nullptr, nullptr, 0, 0, nullptr, nullptr);
DrainLoop();
EXPECT_EQ(status, ZX_ERR_BUFFER_TOO_SMALL);
}
TEST_F(PageImplTest, PutReferenceKeyTooLarge) {
std::string object_data("some_data");
SizedVmo vmo;
ASSERT_TRUE(VmoFromString(object_data, &vmo));
bool called;
fuchsia::ledger::Page_CreateReferenceFromBuffer_Result result;
page_ptr_->CreateReferenceFromBuffer(std::move(vmo).ToTransport(),
Capture(SetWhenCalled(&called), &result));
DrainLoop();
ASSERT_TRUE(result.is_response());
zx::channel writer, reader;
ASSERT_EQ(zx::channel::create(0, &writer, &reader), ZX_OK);
page_ptr_.Bind(std::move(writer));
// Key too large; message doesn't go through, failing on validation.
const size_t key_size = kMaxKeySize + 1;
std::string key = GetKey(1, key_size);
page_ptr_->PutReference(convert::ToArray(key), std::move(result.response().reference),
Priority::EAGER);
zx_status_t status = reader.read(0, nullptr, nullptr, 0, 0, nullptr, nullptr);
DrainLoop();
EXPECT_EQ(status, ZX_ERR_SHOULD_WAIT);
// With a smaller key, message goes through.
key = GetKey(1, kMaxKeySize);
page_ptr_->PutReference(convert::ToArray(key), std::move(result.response().reference),
Priority::EAGER);
status = reader.read(0, nullptr, nullptr, 0, 0, nullptr, nullptr);
DrainLoop();
EXPECT_EQ(status, ZX_ERR_BUFFER_TOO_SMALL);
}
TEST_F(PageImplTest, DeleteNoTransaction) {
std::string key("some_key");
page_ptr_->Delete(convert::ToArray(key));
DrainLoop();
auto objects = fake_storage_->GetObjects();
// No object should have been added.
EXPECT_EQ(objects.size(), 0u);
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 1u);
auto it = journals.begin();
EXPECT_TRUE(it->second->IsCommitted());
EXPECT_THAT(it->second->GetData(), IsEmpty());
}
TEST_F(PageImplTest, ClearNoTransaction) {
page_ptr_->Clear();
DrainLoop();
auto objects = fake_storage_->GetObjects();
// No object should have been added.
EXPECT_THAT(objects, IsEmpty());
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 1u);
auto it = journals.begin();
EXPECT_TRUE(it->second->IsCommitted());
EXPECT_THAT(it->second->GetData(), IsEmpty());
}
TEST_F(PageImplTest, TransactionCommit) {
std::string key1("some_key1");
storage::ObjectDigest object_digest1;
std::string value("a small value");
std::string key2("some_key2");
std::string value2("another value");
SizedVmo vmo;
ASSERT_TRUE(VmoFromString(value2, &vmo));
bool called;
fuchsia::ledger::Page_CreateReferenceFromBuffer_Result result;
page_ptr_->CreateReferenceFromBuffer(std::move(vmo).ToTransport(),
Capture(SetWhenCalled(&called), &result));
DrainLoop();
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_response());
// Sequence of operations:
// - StartTransaction
// - Put
// - PutReference
// - Delete
// - Commit
page_ptr_->StartTransaction();
page_ptr_->Put(convert::ToArray(key1), convert::ToArray(value));
{
DrainLoop();
auto objects = fake_storage_->GetObjects();
EXPECT_EQ(objects.size(), 2u);
// Objects are ordered by a randomly assigned object id, so we can't know
// the correct possition of the value in the map.
bool object_found = false;
for (const auto& object : objects) {
if (object.second == value) {
object_found = true;
object_digest1 = object.first.object_digest();
break;
}
}
EXPECT_TRUE(object_found);
// No finished commit yet.
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 1u);
auto it = journals.begin();
EXPECT_FALSE(it->second->IsCommitted());
EXPECT_EQ(it->second->GetData().size(), 1u);
storage::Entry entry = it->second->GetData().at(key1);
EXPECT_EQ(entry.object_identifier.object_digest(), object_digest1);
EXPECT_EQ(entry.priority, storage::KeyPriority::EAGER);
}
page_ptr_->PutReference(convert::ToArray(key2), std::move(result.response().reference),
Priority::LAZY);
{
DrainLoop();
EXPECT_EQ(fake_storage_->GetObjects().size(), 2u);
// No finished commit yet, with now two entries.
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 1u);
auto it = journals.begin();
EXPECT_FALSE(it->second->IsCommitted());
EXPECT_EQ(it->second->GetData().size(), 2u);
storage::Entry entry = it->second->GetData().at(key2);
EXPECT_EQ(entry.object_identifier.object_digest(),
AddObject(value2)->GetIdentifier().object_digest());
EXPECT_EQ(entry.priority, storage::KeyPriority::LAZY);
}
page_ptr_->Delete(convert::ToArray(key2));
{
DrainLoop();
EXPECT_EQ(fake_storage_->GetObjects().size(), 2u);
// No finished commit yet, with the second entry deleted.
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 1u);
auto it = journals.begin();
EXPECT_FALSE(it->second->IsCommitted());
EXPECT_EQ(it->second->GetData().size(), 1u);
EXPECT_THAT(it->second->GetData(), Not(Contains(Key(key2))));
}
page_ptr_->Commit();
{
DrainLoop();
EXPECT_EQ(fake_storage_->GetObjects().size(), 2u);
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 1u);
auto it = journals.begin();
EXPECT_TRUE(it->second->IsCommitted());
EXPECT_EQ(it->second->GetData().size(), 1u);
}
}
TEST_F(PageImplTest, TransactionClearCommit) {
std::string key1("some_key1");
std::string value1("a small value");
std::string key2("some_key2");
std::string value2("another value");
storage::ObjectDigest object_digest2;
// Sequence of operations:
// - Put key1
// - StartTransaction
// - Clear
// - Put key2
// - Commit
page_ptr_->Put(convert::ToArray(key1), convert::ToArray(value1));
page_ptr_->StartTransaction();
DrainLoop();
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_EQ(journals.size(), 2u);
const auto& journal_it = std::find_if(journals.begin(), journals.end(), [](const auto& pair) {
return !pair.second->IsCommitted();
});
EXPECT_NE(journals.end(), journal_it);
const auto& journal = journal_it->second;
{
EXPECT_FALSE(journal->IsCommitted());
EXPECT_THAT(journal->GetData(), SizeIs(1));
}
page_ptr_->Clear();
{
DrainLoop();
EXPECT_EQ(fake_storage_->GetObjects().size(), 1u);
EXPECT_FALSE(journal->IsCommitted());
EXPECT_THAT(journal->GetData(), IsEmpty());
}
page_ptr_->Put(convert::ToArray(key2), convert::ToArray(value2));
{
DrainLoop();
auto objects = fake_storage_->GetObjects();
EXPECT_EQ(objects.size(), 2u);
bool object_found = false;
for (const auto& object : objects) {
if (object.second == value2) {
object_found = true;
object_digest2 = object.first.object_digest();
break;
}
}
EXPECT_TRUE(object_found);
// No finished commit yet.
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_THAT(journals, SizeIs(2));
EXPECT_FALSE(journal->IsCommitted());
EXPECT_THAT(
journal->GetData(),
ElementsAre(Pair(key2, storage::MatchesEntry({key2, storage::MatchesDigest(object_digest2),
storage::KeyPriority::EAGER}))));
}
page_ptr_->Commit();
{
DrainLoop();
EXPECT_EQ(fake_storage_->GetObjects().size(), 2u);
const std::map<std::string, std::unique_ptr<storage::fake::FakeJournalDelegate>>& journals =
fake_storage_->GetJournals();
EXPECT_THAT(journals, SizeIs(2));
EXPECT_TRUE(journal->IsCommitted());
EXPECT_THAT(
journal->GetData(),
ElementsAre(Pair(key2, storage::MatchesEntry({key2, storage::MatchesDigest(object_digest2),
storage::KeyPriority::EAGER}))));
}
}
TEST_F(PageImplTest, TransactionRollback) {
// Sequence of operations:
// - StartTransaction
// - Rollback
// - StartTransaction
page_ptr_->StartTransaction();
page_ptr_->Rollback();
DrainLoop();
EXPECT_EQ(fake_storage_->GetObjects().size(), 0u);
// Starting another transaction should now succeed.
bool called;
page_ptr_->StartTransaction();
page_ptr_->Sync(SetWhenCalled(&called));
DrainLoop();
EXPECT_TRUE(called);
}
TEST_F(PageImplTest, NoTwoTransactions) {
// Sequence of operations:
// - StartTransaction
// - StartTransaction
bool error_called;
zx_status_t error_status;
page_ptr_.set_error_handler(Capture(SetWhenCalled(&error_called), &error_status));
page_ptr_->StartTransaction();
page_ptr_->StartTransaction();
DrainLoop();
EXPECT_TRUE(error_called);
EXPECT_EQ(error_status, ZX_ERR_BAD_STATE);
}
TEST_F(PageImplTest, NoTransactionCommit) {
// Sequence of operations:
// - Commit
bool error_called;
zx_status_t error_status;
page_ptr_.set_error_handler(Capture(SetWhenCalled(&error_called), &error_status));
page_ptr_->Commit();
DrainLoop();
EXPECT_TRUE(error_called);
EXPECT_EQ(error_status, ZX_ERR_BAD_STATE);
}
TEST_F(PageImplTest, NoTransactionRollback) {
// Sequence of operations:
// - Rollback
bool error_called;
zx_status_t error_status;
page_ptr_.set_error_handler(Capture(SetWhenCalled(&error_called), &error_status));
page_ptr_->Rollback();
DrainLoop();
EXPECT_TRUE(error_called);
EXPECT_EQ(error_status, ZX_ERR_BAD_STATE);
}
TEST_F(PageImplTest, CreateReferenceFromSocket) {
ASSERT_EQ(fake_storage_->GetObjects().size(), 0u);
std::string value("a small value");
bool called;
fuchsia::ledger::Page_CreateReferenceFromSocket_Result result;
page_ptr_->CreateReferenceFromSocket(value.size(), WriteStringToSocket(value),
Capture(SetWhenCalled(&called), &result));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_TRUE(result.is_response());
ASSERT_EQ(fake_storage_->GetObjects().size(), 1u);
ASSERT_EQ(fake_storage_->GetObjects().begin()->second, value);
}
TEST_F(PageImplTest, CreateReferenceFromBuffer) {
ASSERT_EQ(fake_storage_->GetObjects().size(), 0u);
std::string value("a small value");
SizedVmo vmo;
ASSERT_TRUE(VmoFromString(value, &vmo));
bool called;
fuchsia::ledger::Page_CreateReferenceFromBuffer_Result result;
page_ptr_->CreateReferenceFromBuffer(std::move(vmo).ToTransport(),
Capture(SetWhenCalled(&called), &result));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_TRUE(result.is_response());
ASSERT_EQ(fake_storage_->GetObjects().size(), 1u);
ASSERT_EQ(fake_storage_->GetObjects().begin()->second, value);
}
TEST_F(PageImplTest, PutGetSnapshotGetEntries) {
std::string eager_key("a_key");
std::string eager_value("an eager value");
std::string lazy_key("another_key");
std::string lazy_value("a lazy value");
page_ptr_->Put(convert::ToArray(eager_key), convert::ToArray(eager_value));
page_ptr_->PutWithPriority(convert::ToArray(lazy_key), convert::ToArray(lazy_value),
Priority::LAZY);
PageSnapshotPtr snapshot = GetSnapshot();
bool called;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> next_token;
snapshot->GetEntries({}, nullptr, Capture(SetWhenCalled(&called), &actual_entries, &next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(next_token);
ASSERT_EQ(actual_entries.size(), 2u);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(0).key), eager_key);
EXPECT_EQ(ToString(actual_entries.at(0).value), eager_value);
EXPECT_EQ(actual_entries.at(0).priority, Priority::EAGER);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(1).key), lazy_key);
EXPECT_EQ(ToString(actual_entries.at(1).value), lazy_value);
EXPECT_EQ(actual_entries.at(1).priority, Priority::LAZY);
}
TEST_F(PageImplTest, PutGetSnapshotGetEntriesInline) {
std::string eager_key("a_key");
std::string eager_value("an eager value");
std::string lazy_key("another_key");
std::string lazy_value("a lazy value");
page_ptr_->Put(convert::ToArray(eager_key), convert::ToArray(eager_value));
page_ptr_->PutWithPriority(convert::ToArray(lazy_key), convert::ToArray(lazy_value),
Priority::LAZY);
PageSnapshotPtr snapshot = GetSnapshot();
bool called;
std::unique_ptr<Token> next_token;
std::vector<InlinedEntry> actual_entries;
snapshot->GetEntriesInline({}, nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(next_token);
ASSERT_EQ(actual_entries.size(), 2u);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(0).key), eager_key);
EXPECT_TRUE(actual_entries.at(0).inlined_value);
EXPECT_EQ(convert::ToString(actual_entries.at(0).inlined_value->value), eager_value);
EXPECT_EQ(actual_entries.at(0).priority, Priority::EAGER);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(1).key), lazy_key);
EXPECT_TRUE(actual_entries.at(1).inlined_value);
EXPECT_EQ(convert::ToString(actual_entries.at(1).inlined_value->value), lazy_value);
EXPECT_EQ(actual_entries.at(1).priority, Priority::LAZY);
}
TEST_F(PageImplTest, PutGetSnapshotGetEntriesWithTokenForSize) {
const size_t min_key_size = kMaxKeySize;
// Put enough entries to ensure pagination of the result.
// The number of entries in a Page is bounded by the maximum number of
// handles, and the size of a fidl message (which cannot exceed
// |kMaxInlineDataSize|), so we put one entry more than that.
const size_t entry_count =
std::min(fidl_serialization::kMaxMessageHandles,
(fidl_serialization::kMaxInlineDataSize - fidl_serialization::kVectorHeaderSize) /
fidl_serialization::GetEntrySize(min_key_size)) +
1;
AddEntries(entry_count, min_key_size);
PageSnapshotPtr snapshot = GetSnapshot();
// Call GetEntries and find a partial result.
bool called;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries({}, nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_TRUE(actual_next_token);
// Call GetEntries with the previous token and receive the remaining results.
std::vector<Entry> actual_next_entries;
snapshot->GetEntries({}, std::move(actual_next_token),
Capture(SetWhenCalled(&called), &actual_next_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
for (auto& entry : actual_next_entries) {
actual_entries.push_back(std::move(entry));
}
EXPECT_EQ(actual_entries.size(), static_cast<size_t>(entry_count));
// Check that the correct values of the keys are all present in the result and
// in the correct order.
for (int i = 0; i < static_cast<int>(actual_entries.size()); ++i) {
ASSERT_EQ(convert::ToString(actual_entries.at(i).key), GetKey(i, min_key_size));
ASSERT_EQ(ToString(actual_entries.at(i).value), GetValue(i, 0));
}
}
TEST_F(PageImplTest, PutGetSnapshotGetEntriesInlineWithTokenForSize) {
const size_t entry_count = 20;
const size_t min_value_size = fidl_serialization::kMaxInlineDataSize * 3 / 2 / entry_count;
AddEntries(entry_count, 0, min_value_size);
PageSnapshotPtr snapshot = GetSnapshot();
// Call GetEntries and find a partial result.
bool called;
std::vector<InlinedEntry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntriesInline({}, nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_TRUE(actual_next_token);
// Call GetEntries with the previous token and receive the remaining results.
std::vector<InlinedEntry> actual_entries2;
std::unique_ptr<Token> actual_next_token2;
snapshot->GetEntriesInline(
{}, std::move(actual_next_token),
Capture(SetWhenCalled(&called), &actual_entries2, &actual_next_token2));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token2);
for (auto& entry : actual_entries2) {
actual_entries.push_back(std::move(entry));
}
EXPECT_EQ(actual_entries.size(), static_cast<size_t>(entry_count));
// Check that the correct values of the keys are all present in the result and
// in the correct order.
for (int i = 0; i < static_cast<int>(actual_entries.size()); ++i) {
ASSERT_EQ(convert::ToString(actual_entries.at(i).key), GetKey(i, 0));
ASSERT_TRUE(actual_entries.at(i).inlined_value);
ASSERT_EQ(convert::ToString(actual_entries.at(i).inlined_value->value),
GetValue(i, min_value_size));
}
}
TEST_F(PageImplTest, PutGetSnapshotGetEntriesInlineWithTokenForEntryCount) {
const size_t min_key_size = 8;
const size_t min_value_size = 1;
// Approximate size of the entry: takes into account size of the pointers for
// key, object and entry itself; enum size for Priority and size of the header
// for the InlinedEntry struct.
const size_t min_entry_size = fidl_serialization::Align(fidl_serialization::kPriorityEnumSize) +
fidl_serialization::GetByteVectorSize(min_key_size) +
fidl_serialization::GetByteVectorSize(min_value_size);
// Put enough inlined entries to cause pagination based on size of the
// message.
const size_t entry_count = fidl_serialization::kMaxInlineDataSize * 3 / 2 / min_entry_size;
AddEntries(entry_count, 0, min_value_size);
PageSnapshotPtr snapshot = GetSnapshot();
// Call GetEntries and find a partial result.
bool called;
std::vector<InlinedEntry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntriesInline({}, nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_TRUE(actual_next_token);
// Call GetEntries with the previous token and receive the remaining results.
std::vector<InlinedEntry> actual_entries2;
std::unique_ptr<Token> actual_next_token2;
snapshot->GetEntriesInline(
{}, std::move(actual_next_token),
Capture(SetWhenCalled(&called), &actual_entries2, &actual_next_token2));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token2);
for (auto& entry : actual_entries2) {
actual_entries.push_back(std::move(entry));
}
EXPECT_EQ(actual_entries.size(), static_cast<size_t>(entry_count));
// Check that the correct values of the keys are all present in the result and
// in the correct order.
for (int i = 0; i < static_cast<int>(actual_entries.size()); ++i) {
ASSERT_EQ(convert::ToString(actual_entries.at(i).key), GetKey(i, 0));
ASSERT_TRUE(actual_entries.at(i).inlined_value);
ASSERT_EQ(convert::ToString(actual_entries.at(i).inlined_value->value),
GetValue(i, min_value_size));
}
}
TEST_F(PageImplTest, PutGetSnapshotGetEntriesWithTokenForHandles) {
const size_t entry_count = 100;
AddEntries(entry_count);
PageSnapshotPtr snapshot = GetSnapshot();
// Call GetEntries and find a partial result.
bool called;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries({}, nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_TRUE(actual_next_token);
// Call GetEntries with the previous token and receive the remaining results.
std::vector<Entry> actual_next_entries;
snapshot->GetEntries({}, std::move(actual_next_token),
Capture(SetWhenCalled(&called), &actual_next_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
for (auto& entry : actual_next_entries) {
actual_entries.push_back(std::move(entry));
}
EXPECT_EQ(actual_entries.size(), static_cast<size_t>(entry_count));
// Check that the correct values of the keys are all present in the result and
// in the correct order.
for (int i = 0; i < static_cast<int>(actual_entries.size()); ++i) {
ASSERT_EQ(convert::ToString(actual_entries.at(i).key), GetKey(i));
ASSERT_EQ(ToString(actual_entries.at(i).value), GetValue(i, 0));
}
}
TEST_F(PageImplTest, PutGetSnapshotGetEntriesWithFetch) {
std::string eager_key("a_key");
std::string eager_value("an eager value");
std::string lazy_key("another_key");
std::string lazy_value("a lazy value");
page_ptr_->PutWithPriority(convert::ToArray(lazy_key), convert::ToArray(lazy_value),
Priority::LAZY);
DrainLoop();
storage::ObjectIdentifier lazy_object_identifier = fake_storage_->GetObjects().begin()->first;
page_ptr_->Put(convert::ToArray(eager_key), convert::ToArray(eager_value));
DrainLoop();
fake_storage_->DeleteObjectFromLocal(lazy_object_identifier);
PageSnapshotPtr snapshot = GetSnapshot();
bool called;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries({}, nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
ASSERT_EQ(actual_entries.size(), 2u);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(0).key), eager_key);
EXPECT_EQ(ToString(actual_entries.at(0).value), eager_value);
EXPECT_EQ(actual_entries.at(0).priority, Priority::EAGER);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(1).key), lazy_key);
EXPECT_FALSE(actual_entries.at(1).value);
EXPECT_EQ(actual_entries.at(1).priority, Priority::LAZY);
}
TEST_F(PageImplTest, PutGetSnapshotGetEntriesWithPrefix) {
std::string eager_key("001-a_key");
std::string eager_value("an eager value");
std::string lazy_key("002-another_key");
std::string lazy_value("a lazy value");
page_ptr_->Put(convert::ToArray(eager_key), convert::ToArray(eager_value));
page_ptr_->PutWithPriority(convert::ToArray(lazy_key), convert::ToArray(lazy_value),
Priority::LAZY);
PageSnapshotPtr snapshot = GetSnapshot(convert::ToArray("001"));
bool called;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries({}, nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
ASSERT_EQ(actual_entries.size(), 1u);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(0).key), eager_key);
snapshot = GetSnapshot(convert::ToArray("00"));
snapshot->GetEntries({}, nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
ASSERT_EQ(actual_entries.size(), 2u);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(0).key), eager_key);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(1).key), lazy_key);
}
TEST_F(PageImplTest, PutGetSnapshotGetEntriesWithStart) {
std::string eager_key("001-a_key");
std::string eager_value("an eager value");
std::string lazy_key("002-another_key");
std::string lazy_value("a lazy value");
page_ptr_->Put(convert::ToArray(eager_key), convert::ToArray(eager_value));
page_ptr_->PutWithPriority(convert::ToArray(lazy_key), convert::ToArray(lazy_value),
Priority::LAZY);
PageSnapshotPtr snapshot = GetSnapshot();
bool called;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries(convert::ToArray("002"), nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
ASSERT_EQ(actual_entries.size(), 1u);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(0).key), lazy_key);
snapshot->GetEntries(convert::ToArray("001"), nullptr,
Capture(SetWhenCalled(&called), &actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
ASSERT_EQ(actual_entries.size(), 2u);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(0).key), eager_key);
EXPECT_EQ(convert::ExtendedStringView(actual_entries.at(1).key), lazy_key);
}
TEST_F(PageImplTest, PutGetSnapshotGetKeys) {
std::string key1("some_key");
std::string value1("a small value");
std::string key2("some_key2");
std::string value2("another value");
page_ptr_->StartTransaction();
page_ptr_->Put(convert::ToArray(key1), convert::ToArray(value1));
page_ptr_->Put(convert::ToArray(key2), convert::ToArray(value2));
page_ptr_->Commit();
PageSnapshotPtr snapshot = GetSnapshot();
bool called;
std::vector<std::vector<uint8_t>> actual_keys;
std::unique_ptr<Token> actual_next_token;
snapshot->GetKeys({}, nullptr, Capture(SetWhenCalled(&called), &actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(convert::ExtendedStringView(actual_keys.at(0)), key1);
EXPECT_EQ(convert::ExtendedStringView(actual_keys.at(1)), key2);
}
TEST_F(PageImplTest, PutGetSnapshotGetKeysWithToken) {
const size_t min_key_size = kMaxKeySize;
const size_t key_count =
fidl_serialization::kMaxInlineDataSize / fidl_serialization::GetByteVectorSize(min_key_size) +
1;
AddEntries(key_count, min_key_size);
PageSnapshotPtr snapshot = GetSnapshot();
// Call GetKeys and find a partial result.
bool called;
std::vector<std::vector<uint8_t>> actual_keys;
std::unique_ptr<Token> actual_next_token;
snapshot->GetKeys({}, nullptr, Capture(SetWhenCalled(&called), &actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_TRUE(actual_next_token);
// Call GetKeys with the previous token and receive the remaining results.
std::vector<std::vector<uint8_t>> actual_next_keys;
snapshot->GetKeys({}, std::move(actual_next_token),
Capture(SetWhenCalled(&called), &actual_next_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
for (auto& key : actual_next_keys) {
actual_keys.push_back(std::move(key));
}
EXPECT_EQ(actual_keys.size(), static_cast<size_t>(key_count));
// Check that the correct values of the keys are all present in the result and
// in the correct order.
for (size_t i = 0; i < actual_keys.size(); ++i) {
ASSERT_EQ(convert::ToString(actual_keys.at(i)), GetKey(i, min_key_size));
}
}
TEST_F(PageImplTest, PutGetSnapshotGetKeysWithPrefix) {
std::string key1("001-some_key");
std::string value1("a small value");
std::string key2("002-some_key2");
std::string value2("another value");
page_ptr_->StartTransaction();
page_ptr_->Put(convert::ToArray(key1), convert::ToArray(value1));
page_ptr_->Put(convert::ToArray(key2), convert::ToArray(value2));
page_ptr_->Commit();
PageSnapshotPtr snapshot = GetSnapshot(convert::ToArray("001"));
bool called;
std::vector<std::vector<uint8_t>> actual_keys;
std::unique_ptr<Token> actual_next_token;
snapshot->GetKeys({}, nullptr, Capture(SetWhenCalled(&called), &actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(actual_keys.size(), 1u);
EXPECT_EQ(convert::ExtendedStringView(actual_keys.at(0)), key1);
snapshot = GetSnapshot(convert::ToArray("00"));
snapshot->GetKeys({}, nullptr, Capture(SetWhenCalled(&called), &actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(actual_keys.size(), 2u);
EXPECT_EQ(convert::ExtendedStringView(actual_keys.at(0)), key1);
EXPECT_EQ(convert::ExtendedStringView(actual_keys.at(1)), key2);
}
TEST_F(PageImplTest, PutGetSnapshotGetKeysWithStart) {
std::string key1("001-some_key");
std::string value1("a small value");
std::string key2("002-some_key2");
std::string value2("another value");
page_ptr_->StartTransaction();
page_ptr_->Put(convert::ToArray(key1), convert::ToArray(value1));
page_ptr_->Put(convert::ToArray(key2), convert::ToArray(value2));
page_ptr_->Commit();
PageSnapshotPtr snapshot = GetSnapshot();
bool called;
std::vector<std::vector<uint8_t>> actual_keys;
std::unique_ptr<Token> actual_next_token;
snapshot->GetKeys(convert::ToArray("002"), nullptr,
Capture(SetWhenCalled(&called), &actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(actual_keys.size(), 1u);
EXPECT_EQ(convert::ExtendedStringView(actual_keys.at(0)), key2);
snapshot = GetSnapshot();
snapshot->GetKeys(convert::ToArray("001"), nullptr,
Capture(SetWhenCalled(&called), &actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(actual_keys.size(), 2u);
EXPECT_EQ(convert::ExtendedStringView(actual_keys.at(0)), key1);
EXPECT_EQ(convert::ExtendedStringView(actual_keys.at(1)), key2);
}
TEST_F(PageImplTest, SnapshotGetSmall) {
std::string key("some_key");
std::string value("a small value");
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value));
PageSnapshotPtr snapshot = GetSnapshot();
bool called;
fuchsia::ledger::PageSnapshot_Get_Result actual_value;
snapshot->Get(convert::ToArray(key), Capture(SetWhenCalled(&called), &actual_value));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_THAT(actual_value, MatchesString(value));
fuchsia::ledger::PageSnapshot_GetInline_Result actual_inlined_value;
snapshot->GetInline(convert::ToArray(key),
Capture(SetWhenCalled(&called), &actual_inlined_value));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_THAT(actual_inlined_value, MatchesString(value));
}
TEST_F(PageImplTest, SnapshotGetLarge) {
std::string value_string(fidl_serialization::kMaxInlineDataSize + 1, 'a');
SizedVmo vmo;
ASSERT_TRUE(VmoFromString(value_string, &vmo));
bool called;
fuchsia::ledger::Page_CreateReferenceFromBuffer_Result result;
page_ptr_->CreateReferenceFromBuffer(std::move(vmo).ToTransport(),
Capture(SetWhenCalled(&called), &result));
DrainLoop();
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_response());
std::string key("some_key");
page_ptr_->PutReference(convert::ToArray(key), std::move(result.response().reference),
Priority::EAGER);
PageSnapshotPtr snapshot = GetSnapshot();
fuchsia::ledger::PageSnapshot_Get_Result actual_value;
snapshot->Get(convert::ExtendedStringView(key).ToArray(),
Capture(SetWhenCalled(&called), &actual_value));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_THAT(actual_value, MatchesString(value_string));
zx_status_t zx_status;
bool error_hander_called;
snapshot.set_error_handler(Capture(SetWhenCalled(&error_hander_called), &zx_status));
fuchsia::ledger::PageSnapshot_GetInline_Result inlined_value;
snapshot->GetInline(convert::ToArray(key), Capture(SetWhenCalled(&called), &inlined_value));
DrainLoop();
EXPECT_FALSE(called);
EXPECT_TRUE(error_hander_called);
EXPECT_EQ(zx_status, ZX_ERR_BAD_STATE);
}
TEST_F(PageImplTest, SnapshotGetNeedsFetch) {
std::string key("some_key");
std::string value("a small value");
page_ptr_->PutWithPriority(convert::ToArray(key), convert::ToArray(value), Priority::LAZY);
DrainLoop();
storage::ObjectIdentifier lazy_object_identifier = fake_storage_->GetObjects().begin()->first;
fake_storage_->DeleteObjectFromLocal(lazy_object_identifier);
PageSnapshotPtr snapshot = GetSnapshot();
bool called;
fuchsia::ledger::PageSnapshot_Get_Result actual_value;
snapshot->Get(convert::ToArray(key), Capture(SetWhenCalled(&called), &actual_value));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_THAT(actual_value, MatchesError(fuchsia::ledger::Error::NEEDS_FETCH));
fuchsia::ledger::PageSnapshot_GetInline_Result actual_inlined_value;
snapshot->GetInline(convert::ToArray(key),
Capture(SetWhenCalled(&called), &actual_inlined_value));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_THAT(actual_inlined_value, MatchesError(fuchsia::ledger::Error::NEEDS_FETCH));
}
TEST_F(PageImplTest, SnapshotFetchPartial) {
std::string key("some_key");
std::string value("a small value");
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value));
PageSnapshotPtr snapshot = GetSnapshot();
bool called;
fuchsia::ledger::PageSnapshot_FetchPartial_Result result;
snapshot->FetchPartial(convert::ToArray(key), 2, 5, Capture(SetWhenCalled(&called), &result));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_THAT(result, MatchesString("small"));
}
TEST_F(PageImplTest, ParallelPut) {
bool called;
Status storage_status;
PagePtr page_ptr2;
auto page_impl =
std::make_unique<PageImpl>(environment_.dispatcher(), page_id1_, page_ptr2.NewRequest());
manager_->AddPageImpl(std::move(page_impl), Capture(SetWhenCalled(&called), &storage_status));
DrainLoop();
ASSERT_TRUE(called);
ASSERT_EQ(storage_status, Status::OK);
std::string key("some_key");
std::string value1("a small value");
std::string value2("another value");
PageSnapshotPtr snapshot1;
PageSnapshotPtr snapshot2;
page_ptr_->StartTransaction();
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value1));
DrainLoop();
page_ptr2->StartTransaction();
page_ptr2->Put(convert::ToArray(key), convert::ToArray(value2));
page_ptr_->Commit();
page_ptr2->Commit();
page_ptr_->GetSnapshot(snapshot1.NewRequest(), {}, nullptr);
page_ptr2->GetSnapshot(snapshot2.NewRequest(), {}, nullptr);
fuchsia::ledger::PageSnapshot_Get_Result result1;
snapshot1->Get(convert::ToArray(key), Capture(SetWhenCalled(&called), &result1));
DrainLoop();
EXPECT_TRUE(called);
fuchsia::ledger::PageSnapshot_Get_Result result2;
snapshot2->Get(convert::ToArray(key), Capture(SetWhenCalled(&called), &result2));
DrainLoop();
EXPECT_TRUE(called);
//
// The two snapshots should have different contents.
EXPECT_THAT(result1, MatchesString(value1));
EXPECT_THAT(result2, MatchesString(value2));
}
TEST_F(PageImplTest, SerializedOperations) {
fake_storage_->set_autocommit(false);
std::string key("some_key");
std::string value1("a value");
std::string value2("a second value");
std::string value3("a third value");
bool called[7] = {false, false, false, false, false, false, false};
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value1));
page_ptr_->Sync(SetWhenCalled(called));
page_ptr_->Clear();
page_ptr_->Sync(SetWhenCalled(called + 1));
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value2));
page_ptr_->Sync(SetWhenCalled(called + 2));
page_ptr_->Delete(convert::ToArray(key));
page_ptr_->Sync(SetWhenCalled(called + 3));
page_ptr_->StartTransaction();
page_ptr_->Sync(SetWhenCalled(called + 4));
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value3));
page_ptr_->Sync(SetWhenCalled(called + 5));
page_ptr_->Commit();
page_ptr_->Sync(SetWhenCalled(called + 6));
// 4 first operations need to be serialized and blocked on commits.
for (size_t i = 0; i < 4; ++i) {
// Callbacks are blocked until operation commits.
DrainLoop();
EXPECT_FALSE(called[i]);
// The commit queue contains the new commit.
ASSERT_EQ(fake_storage_->GetJournals().size(), i + 1);
CommitFirstPendingJournal(fake_storage_->GetJournals());
// The operation can now succeed.
DrainLoop();
EXPECT_TRUE(called[i]);
}
// Neither StartTransaction, nor Put in a transaction should now be blocked.
DrainLoop();
for (size_t i = 4; i < 6; ++i) {
EXPECT_TRUE(called[i]);
}
// But committing the transaction should still be blocked.
DrainLoop();
EXPECT_FALSE(called[6]);
// Unblocking the transaction commit.
CommitFirstPendingJournal(fake_storage_->GetJournals());
// The operation can now succeed.
DrainLoop();
EXPECT_TRUE(called[6]);
}
TEST_F(PageImplTest, WaitForConflictResolutionNoConflicts) {
bool called;
ConflictResolutionWaitStatus status;
page_ptr_->WaitForConflictResolution(Capture(SetWhenCalled(&called), &status));
DrainLoop();
ASSERT_TRUE(called);
EXPECT_EQ(status, ConflictResolutionWaitStatus::NO_CONFLICTS);
EXPECT_TRUE(resolver_->IsDiscardable());
// Special case: no changes from the previous call; event OnDiscardable is not
// triggered, but WaitForConflictResolution should return right away, as there
// are no pending merges.
page_ptr_->WaitForConflictResolution(Capture(SetWhenCalled(&called), &status));
DrainLoop();
ASSERT_TRUE(called);
EXPECT_EQ(status, ConflictResolutionWaitStatus::NO_CONFLICTS);
EXPECT_TRUE(resolver_->IsDiscardable());
}
} // namespace
} // namespace ledger