| // Copyright 2017 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 "peridot/bin/ledger/storage/impl/storage_test_utils.h" |
| |
| #include <inttypes.h> |
| #include <numeric> |
| |
| #include <lib/callback/capture.h> |
| #include <lib/callback/set_when_called.h> |
| #include <lib/fxl/strings/string_printf.h> |
| #include <zircon/syscalls.h> |
| |
| #include "peridot/bin/ledger/encryption/fake/fake_encryption_service.h" |
| #include "peridot/bin/ledger/storage/impl/btree/builder.h" |
| #include "peridot/bin/ledger/storage/impl/btree/entry_change_iterator.h" |
| #include "peridot/bin/ledger/storage/impl/constants.h" |
| #include "peridot/bin/ledger/storage/impl/object_digest.h" |
| #include "peridot/bin/ledger/storage/impl/split.h" |
| #include "peridot/bin/ledger/storage/public/constants.h" |
| |
| namespace storage { |
| |
| namespace { |
| |
| std::vector<size_t> GetEnumeration(size_t size) { |
| FXL_CHECK(size <= 100); |
| |
| std::vector<size_t> values(size); |
| std::iota(values.begin(), values.end(), 0u); |
| |
| return values; |
| } |
| |
| std::string ResizeForBehavior(std::string value, |
| InlineBehavior inline_behavior) { |
| if (inline_behavior == InlineBehavior::PREVENT && |
| value.size() <= kStorageHashSize) { |
| value.resize(kStorageHashSize + 1); |
| } |
| return value; |
| } |
| |
| ObjectIdentifier GetObjectIdentifier(std::string value, |
| ObjectType object_type) { |
| ObjectDigest result; |
| auto data_source = DataSource::Create(std::move(value)); |
| SplitDataSource( |
| data_source.get(), object_type, |
| [](ObjectDigest object_digest) { |
| return encryption::MakeDefaultObjectIdentifier( |
| std::move(object_digest)); |
| }, |
| [&result](IterationStatus status, ObjectIdentifier object_identifier, |
| std::unique_ptr<DataSource::DataChunk> chunk) { |
| if (status == IterationStatus::DONE) { |
| result = object_identifier.object_digest(); |
| } |
| }); |
| return encryption::MakeDefaultObjectIdentifier(std::move(result)); |
| } |
| |
| // Pre-determined node level function. |
| uint8_t GetTestNodeLevel(convert::ExtendedStringView key) { |
| if (key == "key03" || key == "key07" || key == "key30" || key == "key60" || |
| key == "key89") { |
| return 1; |
| } |
| |
| if (key == "key50" || key == "key75") { |
| return 2; |
| } |
| |
| return 0; |
| } |
| |
| constexpr btree::NodeLevelCalculator kTestNodeLevelCalculator = { |
| &GetTestNodeLevel}; |
| |
| } // namespace |
| |
| ObjectData::ObjectData(std::string value, ObjectType object_type, |
| InlineBehavior inline_behavior) |
| : value(ResizeForBehavior(std::move(value), inline_behavior)), |
| size(this->value.size()), |
| object_identifier(GetObjectIdentifier(this->value, object_type)) {} |
| |
| std::unique_ptr<DataSource> ObjectData::ToDataSource() { |
| return DataSource::Create(value); |
| } |
| |
| std::unique_ptr<DataSource::DataChunk> ObjectData::ToChunk() { |
| return DataSource::DataChunk::Create(value); |
| } |
| |
| ObjectDigest MakeObjectDigest(std::string content, |
| InlineBehavior inline_behavior) { |
| return MakeObjectIdentifier(std::move(content), inline_behavior) |
| .object_digest(); |
| } |
| |
| ObjectIdentifier MakeObjectIdentifier(std::string content, |
| InlineBehavior inline_behavior) { |
| ObjectData data(std::move(content), inline_behavior); |
| return data.object_identifier; |
| } |
| |
| std::string RandomString(rng::Random* random, size_t size) { |
| std::string value; |
| value.resize(size); |
| random->Draw(&value); |
| return value; |
| } |
| |
| CommitId RandomCommitId(rng::Random* random) { |
| return RandomString(random, kCommitIdSize); |
| } |
| |
| ObjectDigest RandomObjectDigest(rng::Random* random) { |
| ObjectData data(RandomString(random, 16), InlineBehavior::PREVENT); |
| return data.object_identifier.object_digest(); |
| } |
| |
| ObjectIdentifier RandomObjectIdentifier(rng::Random* random) { |
| return encryption::MakeDefaultObjectIdentifier(RandomObjectDigest(random)); |
| } |
| |
| EntryChange NewEntryChange(std::string key, std::string object_digest, |
| KeyPriority priority) { |
| return EntryChange{ |
| Entry{std::move(key), MakeObjectIdentifier(std::move(object_digest)), |
| priority}, |
| false}; |
| } |
| |
| EntryChange NewRemoveEntryChange(std::string key) { |
| return EntryChange{ |
| Entry{std::move(key), MakeObjectIdentifier(""), KeyPriority::EAGER}, |
| true}; |
| } |
| |
| StorageTest::StorageTest() {} |
| |
| StorageTest::~StorageTest() {} |
| |
| ::testing::AssertionResult StorageTest::AddObject( |
| std::string value, std::unique_ptr<const Object>* object) { |
| bool called; |
| Status status; |
| ObjectIdentifier object_identifier; |
| GetStorage()->AddObjectFromLocal( |
| ObjectType::BLOB, DataSource::Create(value), |
| callback::Capture(callback::SetWhenCalled(&called), &status, |
| &object_identifier)); |
| RunLoopFor(kSufficientDelay); |
| if (!called) { |
| return ::testing::AssertionFailure() |
| << "AddObjectFromLocal callback wasn't called."; |
| } |
| if (status != Status::OK) { |
| return ::testing::AssertionFailure() |
| << "AddObjectFromLocal failed with status " << status |
| << ". value: " << value; |
| } |
| |
| std::unique_ptr<const Object> result; |
| GetStorage()->GetObject( |
| object_identifier, PageStorage::Location::LOCAL, |
| callback::Capture(callback::SetWhenCalled(&called), &status, &result)); |
| RunLoopFor(kSufficientDelay); |
| if (!called) { |
| return ::testing::AssertionFailure() << "GetObject callback wasn't called."; |
| } |
| if (status != Status::OK) { |
| return ::testing::AssertionFailure() |
| << "GetObject failed with status " << status << ". value: " << value |
| << ", object_identifier: " << object_identifier; |
| } |
| object->swap(result); |
| return ::testing::AssertionSuccess(); |
| } |
| |
| ::testing::AssertionResult StorageTest::CreateEntries( |
| size_t size, std::vector<Entry>* entries) { |
| return CreateEntries(GetEnumeration(size), entries); |
| } |
| |
| ::testing::AssertionResult StorageTest::CreateEntries( |
| std::vector<size_t> values, std::vector<Entry>* entries) { |
| std::vector<Entry> result; |
| for (auto i : values) { |
| FXL_DCHECK(i < 100); |
| std::unique_ptr<const Object> object; |
| ::testing::AssertionResult assertion_result = |
| AddObject(fxl::StringPrintf("object%02" PRIuMAX, i), &object); |
| if (!assertion_result) { |
| return assertion_result; |
| } |
| result.push_back(Entry{fxl::StringPrintf("key%02" PRIuMAX, i), |
| object->GetIdentifier(), KeyPriority::EAGER}); |
| } |
| entries->swap(result); |
| return ::testing::AssertionSuccess(); |
| } |
| |
| ::testing::AssertionResult StorageTest::CreateEntryChanges( |
| size_t size, std::vector<EntryChange>* changes) { |
| return CreateEntryChanges(GetEnumeration(size), changes, false); |
| } |
| |
| ::testing::AssertionResult StorageTest::CreateEntryChanges( |
| std::vector<size_t> values, std::vector<EntryChange>* changes, |
| bool deletion) { |
| std::vector<Entry> entries; |
| ::testing::AssertionResult assertion_result = |
| CreateEntries(std::move(values), &entries); |
| if (!assertion_result) { |
| return assertion_result; |
| } |
| std::vector<EntryChange> result; |
| |
| result.reserve(entries.size()); |
| for (auto& entry : entries) { |
| result.push_back(EntryChange{std::move(entry), deletion}); |
| } |
| changes->swap(result); |
| return ::testing::AssertionSuccess(); |
| } |
| |
| ::testing::AssertionResult StorageTest::GetEmptyNodeIdentifier( |
| ObjectIdentifier* empty_node_identifier) { |
| bool called; |
| Status status; |
| btree::TreeNode::Empty( |
| GetStorage(), callback::Capture(callback::SetWhenCalled(&called), &status, |
| empty_node_identifier)); |
| RunLoopFor(kSufficientDelay); |
| if (!called) { |
| return ::testing::AssertionFailure() |
| << "TreeNode::Empty callback wasn't called."; |
| } |
| if (status != Status::OK) { |
| return ::testing::AssertionFailure() |
| << "TreeNode::Empty failed with status " << status; |
| } |
| return ::testing::AssertionSuccess(); |
| } |
| |
| ::testing::AssertionResult StorageTest::CreateNodeFromIdentifier( |
| ObjectIdentifier identifier, std::unique_ptr<const btree::TreeNode>* node) { |
| bool called; |
| Status status; |
| std::unique_ptr<const btree::TreeNode> result; |
| btree::TreeNode::FromIdentifier( |
| GetStorage(), identifier, |
| callback::Capture(callback::SetWhenCalled(&called), &status, &result)); |
| RunLoopFor(kSufficientDelay); |
| if (!called) { |
| return ::testing::AssertionFailure() |
| << "TreeNode::FromIdentifier callback wasn't called."; |
| } |
| if (status != Status::OK) { |
| return ::testing::AssertionFailure() |
| << "TreeNode::FromIdentifier failed with status " << status; |
| } |
| node->swap(result); |
| return ::testing::AssertionSuccess(); |
| } |
| |
| ::testing::AssertionResult StorageTest::CreateNodeFromEntries( |
| const std::vector<Entry>& entries, |
| const std::map<size_t, ObjectIdentifier>& children, |
| std::unique_ptr<const btree::TreeNode>* node) { |
| bool called; |
| Status status; |
| ObjectIdentifier identifier; |
| btree::TreeNode::FromEntries( |
| GetStorage(), 0u, entries, children, |
| callback::Capture(callback::SetWhenCalled(&called), &status, |
| &identifier)); |
| |
| RunLoopFor(kSufficientDelay); |
| if (!called) { |
| return ::testing::AssertionFailure() |
| << "TreeNode::FromEntries callback wasn't called."; |
| } |
| if (status != Status::OK) { |
| return ::testing::AssertionFailure() |
| << "TreeNode::FromEntries failed with status " << status; |
| } |
| return CreateNodeFromIdentifier(std::move(identifier), node); |
| } |
| |
| ::testing::AssertionResult StorageTest::CreateTreeFromChanges( |
| const ObjectIdentifier& base_node_identifier, |
| const std::vector<EntryChange>& entries, |
| ObjectIdentifier* new_root_identifier) { |
| bool called; |
| Status status; |
| std::set<ObjectIdentifier> new_nodes; |
| btree::ApplyChanges( |
| environment_.coroutine_service(), GetStorage(), base_node_identifier, |
| std::make_unique<btree::EntryChangeIterator>(entries.begin(), |
| entries.end()), |
| callback::Capture(callback::SetWhenCalled(&called), &status, |
| new_root_identifier, &new_nodes), |
| &kTestNodeLevelCalculator); |
| RunLoopFor(kSufficientDelay); |
| if (!called) { |
| return ::testing::AssertionFailure() |
| << "btree::ApplyChanges callback wasn't called."; |
| } |
| if (status != Status::OK) { |
| return ::testing::AssertionFailure() |
| << "btree::ApplyChanges failed with status " << status; |
| } |
| return ::testing::AssertionSuccess(); |
| } |
| |
| } // namespace storage |