blob: 8fa64556e155f05ca3f67006cbf8d1b39e593089 [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/backoff/exponential_backoff.h>
#include <lib/callback/capture.h>
#include <lib/callback/set_when_called.h>
#include <lib/fidl/cpp/binding.h>
#include <lib/fidl/cpp/clone.h>
#include <lib/fsl/socket/strings.h>
#include <lib/fsl/vmo/strings.h>
#include <lib/gtest/test_loop_fixture.h>
#include <algorithm>
#include <map>
#include <memory>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "peridot/lib/convert/convert.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/app/page_manager.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/lib/fxl/macros.h"
#include "src/lib/fxl/strings/string_printf.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 {
std::string ToString(const fuchsia::mem::BufferPtr& vmo) {
std::string value;
bool status = fsl::StringFromVmo(*vmo, &value);
FXL_DCHECK(status);
return value;
}
class PageImplTest : public TestWithEnvironment {
public:
PageImplTest() {}
~PageImplTest() override {}
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<backoff::ExponentialBackoff>(
zx::sec(0), 1u, zx::sec(0),
environment_.random()->NewBitGenerator<uint64_t>()));
resolver_ = resolver.get();
manager_ = std::make_unique<PageManager>(
&environment_, std::move(fake_storage), nullptr, std::move(resolver),
PageManager::PageStorageState::NEEDS_SYNC);
bool called;
storage::Status status;
auto page_impl =
std::make_unique<PageImpl>(page_id1_, page_ptr_.NewRequest());
manager_->AddPageImpl(
std::move(page_impl),
callback::Capture(callback::SetWhenCalled(&called), &status));
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, status);
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(storage::Status::OK);
return;
}
}
}
storage::ObjectIdentifier AddObjectToStorage(std::string value_string) {
bool called;
storage::Status status;
storage::ObjectIdentifier object_identifier;
fake_storage_->AddObjectFromLocal(
storage::ObjectType::BLOB,
storage::DataSource::Create(std::move(value_string)), {},
callback::Capture(callback::SetWhenCalled(&called), &status,
&object_identifier));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, status);
return object_identifier;
}
std::unique_ptr<const storage::Object> AddObject(const std::string& value) {
storage::ObjectIdentifier object_identifier = AddObjectToStorage(value);
bool called;
storage::Status status;
std::unique_ptr<const storage::Object> object;
fake_storage_->GetObject(
object_identifier, storage::PageStorage::Location::LOCAL,
callback::Capture(callback::SetWhenCalled(&called), &status, &object));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(storage::Status::OK, status);
return object;
}
std::string GetKey(size_t index, size_t min_key_size = 0u) {
std::string result = fxl::StringPrintf("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 = fxl::StringPrintf("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) {
FXL_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)));
}
page_ptr_->Commit();
}
PageSnapshotPtr GetSnapshot(
std::vector<uint8_t> prefix = std::vector<uint8_t>()) {
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<PageManager> manager_;
MergeResolver* resolver_;
PagePtr page_ptr_;
private:
FXL_DISALLOW_COPY_AND_ASSIGN(PageImplTest);
};
TEST_F(PageImplTest, GetId) {
bool called;
PageId page_id;
page_ptr_->GetId(
callback::Capture(callback::SetWhenCalled(&called), &page_id));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(page_id1_, convert::ToString(page_id.id));
}
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(1u, objects.size());
storage::ObjectIdentifier object_identifier = objects.begin()->first;
std::string actual_value = objects.begin()->second;
EXPECT_EQ(value, actual_value);
const std::map<std::string,
std::unique_ptr<storage::fake::FakeJournalDelegate>>&
journals = fake_storage_->GetJournals();
EXPECT_EQ(1u, journals.size());
auto it = journals.begin();
EXPECT_TRUE(it->second->IsCommitted());
EXPECT_EQ(1u, it->second->GetData().size());
storage::Entry entry = it->second->GetData().at(key);
EXPECT_EQ(object_identifier, entry.object_identifier);
EXPECT_EQ(storage::KeyPriority::EAGER, entry.priority);
}
TEST_F(PageImplTest, PutReferenceNoTransaction) {
std::string object_data("some_data");
fsl::SizedVmo vmo;
ASSERT_TRUE(fsl::VmoFromString(object_data, &vmo));
bool called;
CreateReferenceStatus status;
ReferencePtr reference;
page_ptr_->CreateReferenceFromBuffer(
std::move(vmo).ToTransport(),
callback::Capture(callback::SetWhenCalled(&called), &status, &reference));
DrainLoop();
ASSERT_TRUE(called);
ASSERT_EQ(CreateReferenceStatus::OK, status);
std::string key("some_key");
page_ptr_->PutReference(convert::ToArray(key), std::move(*reference),
Priority::LAZY);
DrainLoop();
auto objects = fake_storage_->GetObjects();
// No object should have been added.
EXPECT_EQ(1u, objects.size());
const std::map<std::string,
std::unique_ptr<storage::fake::FakeJournalDelegate>>&
journals = fake_storage_->GetJournals();
EXPECT_EQ(1u, journals.size());
auto it = journals.begin();
EXPECT_TRUE(it->second->IsCommitted());
EXPECT_EQ(1u, it->second->GetData().size());
storage::Entry entry = it->second->GetData().at(key);
std::unique_ptr<const storage::Object> object = AddObject(object_data);
EXPECT_EQ(object->GetIdentifier().object_digest(),
entry.object_identifier.object_digest());
EXPECT_EQ(storage::KeyPriority::LAZY, entry.priority);
}
TEST_F(PageImplTest, PutUnknownReference) {
std::string key("some_key");
ReferencePtr reference = Reference::New();
reference->opaque_id = convert::ToArray("12345678");
bool called;
zx_status_t status;
page_ptr_.set_error_handler(
callback::Capture(callback::SetWhenCalled(&called), &status));
page_ptr_->PutReference(convert::ToArray(key), std::move(*reference),
Priority::LAZY);
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(Status::REFERENCE_NOT_FOUND, static_cast<Status>(status));
auto objects = fake_storage_->GetObjects();
// No object should have been added.
EXPECT_EQ(0u, objects.size());
const std::map<std::string,
std::unique_ptr<storage::fake::FakeJournalDelegate>>&
journals = fake_storage_->GetJournals();
EXPECT_EQ(0u, journals.size());
}
TEST_F(PageImplTest, PutKeyTooLarge) {
std::string value("a small value");
zx::channel writer, reader;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &writer, &reader));
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(ZX_ERR_SHOULD_WAIT, status);
// 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(ZX_ERR_BUFFER_TOO_SMALL, status);
}
TEST_F(PageImplTest, PutReferenceKeyTooLarge) {
std::string object_data("some_data");
fsl::SizedVmo vmo;
ASSERT_TRUE(fsl::VmoFromString(object_data, &vmo));
bool called;
CreateReferenceStatus reference_status;
ReferencePtr reference;
page_ptr_->CreateReferenceFromBuffer(
std::move(vmo).ToTransport(),
callback::Capture(callback::SetWhenCalled(&called), &reference_status,
&reference));
DrainLoop();
ASSERT_EQ(CreateReferenceStatus::OK, reference_status);
zx::channel writer, reader;
ASSERT_EQ(ZX_OK, zx::channel::create(0, &writer, &reader));
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), fidl::Clone(*reference),
Priority::EAGER);
zx_status_t status = reader.read(0, nullptr, nullptr, 0, 0, nullptr, nullptr);
DrainLoop();
EXPECT_EQ(ZX_ERR_SHOULD_WAIT, status);
// With a smaller key, message goes through.
key = GetKey(1, kMaxKeySize);
page_ptr_->PutReference(convert::ToArray(key), std::move(*reference),
Priority::EAGER);
status = reader.read(0, nullptr, nullptr, 0, 0, nullptr, nullptr);
DrainLoop();
EXPECT_EQ(ZX_ERR_BUFFER_TOO_SMALL, status);
}
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(0u, objects.size());
const std::map<std::string,
std::unique_ptr<storage::fake::FakeJournalDelegate>>&
journals = fake_storage_->GetJournals();
EXPECT_EQ(1u, journals.size());
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(1u, journals.size());
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");
fsl::SizedVmo vmo;
ASSERT_TRUE(fsl::VmoFromString(value2, &vmo));
bool called;
CreateReferenceStatus status;
ReferencePtr reference;
page_ptr_->CreateReferenceFromBuffer(
std::move(vmo).ToTransport(),
callback::Capture(callback::SetWhenCalled(&called), &status, &reference));
DrainLoop();
ASSERT_TRUE(called);
ASSERT_EQ(CreateReferenceStatus::OK, status);
// 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(2u, objects.size());
// 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(1u, journals.size());
auto it = journals.begin();
EXPECT_FALSE(it->second->IsCommitted());
EXPECT_EQ(1u, it->second->GetData().size());
storage::Entry entry = it->second->GetData().at(key1);
EXPECT_EQ(object_digest1, entry.object_identifier.object_digest());
EXPECT_EQ(storage::KeyPriority::EAGER, entry.priority);
}
page_ptr_->PutReference(convert::ToArray(key2), std::move(*reference),
Priority::LAZY);
{
DrainLoop();
EXPECT_EQ(2u, fake_storage_->GetObjects().size());
// 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(1u, journals.size());
auto it = journals.begin();
EXPECT_FALSE(it->second->IsCommitted());
EXPECT_EQ(2u, it->second->GetData().size());
storage::Entry entry = it->second->GetData().at(key2);
EXPECT_EQ(AddObject(value2)->GetIdentifier().object_digest(),
entry.object_identifier.object_digest());
EXPECT_EQ(storage::KeyPriority::LAZY, entry.priority);
}
page_ptr_->Delete(convert::ToArray(key2));
{
DrainLoop();
EXPECT_EQ(2u, fake_storage_->GetObjects().size());
// 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(1u, journals.size());
auto it = journals.begin();
EXPECT_FALSE(it->second->IsCommitted());
EXPECT_EQ(1u, it->second->GetData().size());
EXPECT_THAT(it->second->GetData(), Not(Contains(Key(key2))));
}
page_ptr_->Commit();
{
DrainLoop();
EXPECT_EQ(2u, fake_storage_->GetObjects().size());
const std::map<std::string,
std::unique_ptr<storage::fake::FakeJournalDelegate>>&
journals = fake_storage_->GetJournals();
EXPECT_EQ(1u, journals.size());
auto it = journals.begin();
EXPECT_TRUE(it->second->IsCommitted());
EXPECT_EQ(1u, it->second->GetData().size());
}
}
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(2u, journals.size());
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(1u, fake_storage_->GetObjects().size());
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(2u, objects.size());
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(2u, fake_storage_->GetObjects().size());
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(0u, fake_storage_->GetObjects().size());
// Starting another transaction should now succeed.
bool called;
page_ptr_->StartTransaction();
page_ptr_->Sync(callback::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(
callback::Capture(callback::SetWhenCalled(&error_called), &error_status));
page_ptr_->StartTransaction();
page_ptr_->StartTransaction();
DrainLoop();
EXPECT_TRUE(error_called);
EXPECT_EQ(Status::TRANSACTION_ALREADY_IN_PROGRESS,
static_cast<Status>(error_status));
}
TEST_F(PageImplTest, NoTransactionCommit) {
// Sequence of operations:
// - Commit
bool error_called;
zx_status_t error_status;
page_ptr_.set_error_handler(
callback::Capture(callback::SetWhenCalled(&error_called), &error_status));
page_ptr_->Commit();
DrainLoop();
EXPECT_TRUE(error_called);
EXPECT_EQ(Status::NO_TRANSACTION_IN_PROGRESS,
static_cast<Status>(error_status));
}
TEST_F(PageImplTest, NoTransactionRollback) {
// Sequence of operations:
// - Rollback
bool error_called;
zx_status_t error_status;
page_ptr_.set_error_handler(
callback::Capture(callback::SetWhenCalled(&error_called), &error_status));
page_ptr_->Rollback();
DrainLoop();
EXPECT_TRUE(error_called);
EXPECT_EQ(Status::NO_TRANSACTION_IN_PROGRESS,
static_cast<Status>(error_status));
}
TEST_F(PageImplTest, CreateReferenceFromSocket) {
ASSERT_EQ(0u, fake_storage_->GetObjects().size());
std::string value("a small value");
bool called;
CreateReferenceStatus status;
ReferencePtr reference;
page_ptr_->CreateReferenceFromSocket(
value.size(), fsl::WriteStringToSocket(value),
callback::Capture(callback::SetWhenCalled(&called), &status, &reference));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(CreateReferenceStatus::OK, status);
ASSERT_EQ(1u, fake_storage_->GetObjects().size());
ASSERT_EQ(value, fake_storage_->GetObjects().begin()->second);
}
TEST_F(PageImplTest, CreateReferenceFromBuffer) {
ASSERT_EQ(0u, fake_storage_->GetObjects().size());
std::string value("a small value");
fsl::SizedVmo vmo;
ASSERT_TRUE(fsl::VmoFromString(value, &vmo));
bool called;
CreateReferenceStatus status;
ReferencePtr reference;
page_ptr_->CreateReferenceFromBuffer(
std::move(vmo).ToTransport(),
callback::Capture(callback::SetWhenCalled(&called), &status, &reference));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(CreateReferenceStatus::OK, status);
ASSERT_EQ(1u, fake_storage_->GetObjects().size());
ASSERT_EQ(value, fake_storage_->GetObjects().begin()->second);
}
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;
IterationStatus status;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> next_token;
snapshot->GetEntries(
std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(next_token);
ASSERT_EQ(2u, actual_entries.size());
EXPECT_EQ(eager_key, convert::ExtendedStringView(actual_entries.at(0).key));
EXPECT_EQ(eager_value, ToString(actual_entries.at(0).value));
EXPECT_EQ(Priority::EAGER, actual_entries.at(0).priority);
EXPECT_EQ(lazy_key, convert::ExtendedStringView(actual_entries.at(1).key));
EXPECT_EQ(lazy_value, ToString(actual_entries.at(1).value));
EXPECT_EQ(Priority::LAZY, actual_entries.at(1).priority);
}
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;
IterationStatus status;
std::unique_ptr<Token> next_token;
std::vector<InlinedEntry> actual_entries;
snapshot->GetEntriesInline(
fidl::VectorPtr<uint8_t>::New(0), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(next_token);
ASSERT_EQ(2u, actual_entries.size());
EXPECT_EQ(eager_key, convert::ExtendedStringView(actual_entries.at(0).key));
EXPECT_TRUE(actual_entries.at(0).inlined_value);
EXPECT_EQ(eager_value,
convert::ToString(actual_entries.at(0).inlined_value->value));
EXPECT_EQ(Priority::EAGER, actual_entries.at(0).priority);
EXPECT_EQ(lazy_key, convert::ExtendedStringView(actual_entries.at(1).key));
EXPECT_TRUE(actual_entries.at(1).inlined_value);
EXPECT_EQ(lazy_value,
convert::ToString(actual_entries.at(1).inlined_value->value));
EXPECT_EQ(Priority::LAZY, actual_entries.at(1).priority);
}
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;
IterationStatus status;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries(
std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::PARTIAL_RESULT, status);
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::vector<uint8_t>(), std::move(actual_next_token),
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_next_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
for (auto& entry : actual_next_entries) {
actual_entries.push_back(std::move(entry));
}
EXPECT_EQ(static_cast<size_t>(entry_count), actual_entries.size());
// 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(GetKey(i, min_key_size),
convert::ToString(actual_entries.at(i).key));
ASSERT_EQ(GetValue(i, 0), ToString(actual_entries.at(i).value));
}
}
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;
IterationStatus status;
std::vector<InlinedEntry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntriesInline(
std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::PARTIAL_RESULT, status);
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::vector<uint8_t>(), std::move(actual_next_token),
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries2, &actual_next_token2));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token2);
for (auto& entry : actual_entries2) {
actual_entries.push_back(std::move(entry));
}
EXPECT_EQ(static_cast<size_t>(entry_count), actual_entries.size());
// 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(GetKey(i, 0), convert::ToString(actual_entries.at(i).key));
ASSERT_TRUE(actual_entries.at(i).inlined_value);
ASSERT_EQ(GetValue(i, min_value_size),
convert::ToString(actual_entries.at(i).inlined_value->value));
}
}
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;
IterationStatus status;
std::vector<InlinedEntry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntriesInline(
std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::PARTIAL_RESULT, status);
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::vector<uint8_t>(), std::move(actual_next_token),
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries2, &actual_next_token2));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token2);
for (auto& entry : actual_entries2) {
actual_entries.push_back(std::move(entry));
}
EXPECT_EQ(static_cast<size_t>(entry_count), actual_entries.size());
// 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(GetKey(i, 0), convert::ToString(actual_entries.at(i).key));
ASSERT_TRUE(actual_entries.at(i).inlined_value);
ASSERT_EQ(GetValue(i, min_value_size),
convert::ToString(actual_entries.at(i).inlined_value->value));
}
}
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;
IterationStatus status;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries(
std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::PARTIAL_RESULT, status);
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::vector<uint8_t>(), std::move(actual_next_token),
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_next_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
for (auto& entry : actual_next_entries) {
actual_entries.push_back(std::move(entry));
}
EXPECT_EQ(static_cast<size_t>(entry_count), actual_entries.size());
// 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(GetKey(i), convert::ToString(actual_entries.at(i).key));
ASSERT_EQ(GetValue(i, 0), ToString(actual_entries.at(i).value));
}
}
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;
IterationStatus status;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries(
std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
ASSERT_EQ(2u, actual_entries.size());
EXPECT_EQ(eager_key, convert::ExtendedStringView(actual_entries.at(0).key));
EXPECT_EQ(eager_value, ToString(actual_entries.at(0).value));
EXPECT_EQ(Priority::EAGER, actual_entries.at(0).priority);
EXPECT_EQ(lazy_key, convert::ExtendedStringView(actual_entries.at(1).key));
EXPECT_FALSE(actual_entries.at(1).value);
EXPECT_EQ(Priority::LAZY, actual_entries.at(1).priority);
}
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;
IterationStatus status;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries(
std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
ASSERT_EQ(1u, actual_entries.size());
EXPECT_EQ(eager_key, convert::ExtendedStringView(actual_entries.at(0).key));
snapshot = GetSnapshot(convert::ToArray("00"));
snapshot->GetEntries(
std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
ASSERT_EQ(2u, actual_entries.size());
EXPECT_EQ(eager_key, convert::ExtendedStringView(actual_entries.at(0).key));
EXPECT_EQ(lazy_key, convert::ExtendedStringView(actual_entries.at(1).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;
IterationStatus status;
std::vector<Entry> actual_entries;
std::unique_ptr<Token> actual_next_token;
snapshot->GetEntries(
convert::ToArray("002"), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
ASSERT_EQ(1u, actual_entries.size());
EXPECT_EQ(lazy_key, convert::ExtendedStringView(actual_entries.at(0).key));
snapshot->GetEntries(
convert::ToArray("001"), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_entries, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
ASSERT_EQ(2u, actual_entries.size());
EXPECT_EQ(eager_key, convert::ExtendedStringView(actual_entries.at(0).key));
EXPECT_EQ(lazy_key, convert::ExtendedStringView(actual_entries.at(1).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;
IterationStatus status;
std::vector<std::vector<uint8_t>> actual_keys;
std::unique_ptr<Token> actual_next_token;
snapshot->GetKeys(std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(key1, convert::ExtendedStringView(actual_keys.at(0)));
EXPECT_EQ(key2, convert::ExtendedStringView(actual_keys.at(1)));
}
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;
IterationStatus status;
std::vector<std::vector<uint8_t>> actual_keys;
std::unique_ptr<Token> actual_next_token;
snapshot->GetKeys(std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::PARTIAL_RESULT, status);
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::vector<uint8_t>(), std::move(actual_next_token),
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_next_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
for (auto& key : actual_next_keys) {
actual_keys.push_back(std::move(key));
}
EXPECT_EQ(static_cast<size_t>(key_count), actual_keys.size());
// 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(GetKey(i, min_key_size), convert::ToString(actual_keys.at(i)));
}
}
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;
IterationStatus status;
std::vector<std::vector<uint8_t>> actual_keys;
std::unique_ptr<Token> actual_next_token;
snapshot->GetKeys(std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(1u, actual_keys.size());
EXPECT_EQ(key1, convert::ExtendedStringView(actual_keys.at(0)));
snapshot = GetSnapshot(convert::ToArray("00"));
snapshot->GetKeys(std::vector<uint8_t>(), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(2u, actual_keys.size());
EXPECT_EQ(key1, convert::ExtendedStringView(actual_keys.at(0)));
EXPECT_EQ(key2, convert::ExtendedStringView(actual_keys.at(1)));
}
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;
IterationStatus status;
std::vector<std::vector<uint8_t>> actual_keys;
std::unique_ptr<Token> actual_next_token;
snapshot->GetKeys(convert::ToArray("002"), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(1u, actual_keys.size());
EXPECT_EQ(key2, convert::ExtendedStringView(actual_keys.at(0)));
snapshot = GetSnapshot();
snapshot->GetKeys(convert::ToArray("001"), nullptr,
callback::Capture(callback::SetWhenCalled(&called), &status,
&actual_keys, &actual_next_token));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_EQ(IterationStatus::OK, status);
EXPECT_FALSE(actual_next_token);
EXPECT_EQ(2u, actual_keys.size());
EXPECT_EQ(key1, convert::ExtendedStringView(actual_keys.at(0)));
EXPECT_EQ(key2, convert::ExtendedStringView(actual_keys.at(1)));
}
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),
callback::Capture(callback::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),
callback::Capture(callback::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');
fsl::SizedVmo vmo;
ASSERT_TRUE(fsl::VmoFromString(value_string, &vmo));
bool called;
CreateReferenceStatus create_reference_status;
ReferencePtr reference;
page_ptr_->CreateReferenceFromBuffer(
std::move(vmo).ToTransport(),
callback::Capture(callback::SetWhenCalled(&called),
&create_reference_status, &reference));
DrainLoop();
ASSERT_TRUE(called);
ASSERT_EQ(CreateReferenceStatus::OK, create_reference_status);
std::string key("some_key");
page_ptr_->PutReference(convert::ToArray(key), std::move(*reference),
Priority::EAGER);
PageSnapshotPtr snapshot = GetSnapshot();
fuchsia::ledger::PageSnapshot_Get_Result actual_value;
snapshot->Get(
convert::ExtendedStringView(key).ToArray(),
callback::Capture(callback::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(callback::Capture(
callback::SetWhenCalled(&error_hander_called), &zx_status));
fuchsia::ledger::PageSnapshot_GetInline_Result inlined_value;
snapshot->GetInline(
convert::ToArray(key),
callback::Capture(callback::SetWhenCalled(&called), &inlined_value));
DrainLoop();
EXPECT_FALSE(called);
EXPECT_TRUE(error_hander_called);
EXPECT_EQ(Status::VALUE_TOO_LARGE, static_cast<Status>(zx_status));
}
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),
::callback::Capture(callback::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),
::callback::Capture(callback::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,
callback::Capture(callback::SetWhenCalled(&called), &result));
DrainLoop();
EXPECT_TRUE(called);
EXPECT_THAT(result, MatchesString("small"));
}
TEST_F(PageImplTest, ParallelPut) {
bool called;
storage::Status storage_status;
PagePtr page_ptr2;
auto page_impl =
std::make_unique<PageImpl>(page_id1_, page_ptr2.NewRequest());
manager_->AddPageImpl(
std::move(page_impl),
callback::Capture(callback::SetWhenCalled(&called), &storage_status));
DrainLoop();
ASSERT_TRUE(called);
ASSERT_EQ(storage::Status::OK, storage_status);
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(),
fidl::VectorPtr<uint8_t>::New(0), nullptr);
page_ptr2->GetSnapshot(snapshot2.NewRequest(),
fidl::VectorPtr<uint8_t>::New(0), nullptr);
fuchsia::ledger::PageSnapshot_Get_Result result1;
snapshot1->Get(convert::ToArray(key),
callback::Capture(callback::SetWhenCalled(&called), &result1));
DrainLoop();
EXPECT_TRUE(called);
fuchsia::ledger::PageSnapshot_Get_Result result2;
snapshot2->Get(convert::ToArray(key),
callback::Capture(callback::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(callback::SetWhenCalled(called));
page_ptr_->Clear();
page_ptr_->Sync(callback::SetWhenCalled(called + 1));
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value2));
page_ptr_->Sync(callback::SetWhenCalled(called + 2));
page_ptr_->Delete(convert::ToArray(key));
page_ptr_->Sync(callback::SetWhenCalled(called + 3));
page_ptr_->StartTransaction();
page_ptr_->Sync(callback::SetWhenCalled(called + 4));
page_ptr_->Put(convert::ToArray(key), convert::ToArray(value3));
page_ptr_->Sync(callback::SetWhenCalled(called + 5));
page_ptr_->Commit();
page_ptr_->Sync(callback::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(i + 1, fake_storage_->GetJournals().size());
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(
callback::Capture(callback::SetWhenCalled(&called), &status));
DrainLoop();
ASSERT_TRUE(called);
EXPECT_EQ(ConflictResolutionWaitStatus::NO_CONFLICTS, status);
EXPECT_TRUE(resolver_->IsEmpty());
// Special case: no changes from the previous call; event OnEmpty is not
// triggered, but WaitForConflictResolution should return right away, as there
// are no pending merges.
page_ptr_->WaitForConflictResolution(
callback::Capture(callback::SetWhenCalled(&called), &status));
DrainLoop();
ASSERT_TRUE(called);
EXPECT_EQ(ConflictResolutionWaitStatus::NO_CONFLICTS, status);
EXPECT_TRUE(resolver_->IsEmpty());
}
} // namespace
} // namespace ledger