| // Copyright 2020 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. |
| |
| #ifndef ZIRCON_SYSTEM_ULIB_ZBITL_TEST_TESTS_H_ |
| #define ZIRCON_SYSTEM_ULIB_ZBITL_TEST_TESTS_H_ |
| |
| #include <lib/zbitl/error_string.h> |
| #include <lib/zbitl/image.h> |
| #include <lib/zbitl/item.h> |
| #include <lib/zbitl/json.h> |
| #include <lib/zbitl/view.h> |
| |
| #include <iterator> |
| #include <string> |
| #include <type_traits> |
| #include <vector> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/files/scoped_temp_dir.h" |
| |
| // While it is convenient to use std::string as a container in representing ZBI |
| // content, we alias the type to convey that it need not necessarily represent |
| // text. |
| using Bytes = std::string; |
| |
| constexpr size_t kMaxZbiSize = 4192; |
| constexpr size_t kOneItemZbiSize = 80; |
| |
| enum class TestDataZbiType { |
| kEmpty, |
| kOneItem, |
| kCompressedItem, |
| kBadCrcItem, |
| kMultipleSmallItems, |
| kSecondItemOnPageBoundary, |
| }; |
| |
| // Parameterizes the behavior of copying a single item. |
| enum class ItemCopyMode { |
| // Copy just the payload. |
| kRaw, |
| // Copy the header and payload. |
| kWithHeader, |
| // Copy the payload and decompress it as necessary. |
| kStorage, |
| }; |
| |
| // |
| // Helpers for accessing test data. |
| // |
| |
| size_t GetExpectedItemType(TestDataZbiType type); |
| |
| bool ExpectItemsAreCompressed(TestDataZbiType type); |
| |
| size_t GetExpectedNumberOfItems(TestDataZbiType type); |
| |
| void GetExpectedPayload(TestDataZbiType type, size_t idx, Bytes* contents); |
| |
| void GetExpectedPayloadWithHeader(TestDataZbiType type, size_t idx, Bytes* contents); |
| |
| std::string GetExpectedJson(TestDataZbiType type); |
| |
| void OpenTestDataZbi(TestDataZbiType type, std::string_view work_dir, fbl::unique_fd* fd, |
| size_t* num_bytes); |
| |
| struct TestAllocator { |
| fitx::result<std::string_view, std::unique_ptr<std::byte[]>> operator()(size_t bytes) { |
| allocated_.push_back(bytes); |
| return zbitl::decompress::DefaultAllocator(bytes); |
| } |
| |
| std::vector<size_t> allocated_; |
| }; |
| |
| // |
| // Each type of storage under test is expected to implement a "test traits" |
| // struct with the following properties: |
| // * `storage_type` type declaration; |
| // * `Context` struct with `storage_type TakeStorage()` method that transfers |
| // ownership of a storage object of type `storage_type`. It is |
| // expected to be called at most once and the associated storage object is |
| // valid only for as long as the context is alive. |
| // * `static void Create(fbl::unique_fd, size_t, Context*)` for initializing |
| // a context object from given file contents of a given size. |
| // * `static void Create(size_t, Context*)`, for initializing a context |
| // object corrsponding to a fresh storage object of a given size. |
| // * `static void Read(storage_type&, payload_type, size_t, std::string*)` |
| // for reading a payload of a given size into a string, where |
| // `payload_type` coincides with |
| // `zbitl::StorageTraits<storage_type>::payload_type`. |
| // * a static constexpr bool `kExpectExtensibility` giving the expectation of |
| // whether storage capacity can be extended. |
| // * a static constexpr bool `kExpectOneshotReads` giving the expectation of |
| // whether whole payloads can be accessed in memory directly. |
| // * a static constexpr bool `kExpectUnbufferedReads` giving the expectation of |
| // whether whole payloads can be access in memory directly or read into a |
| // provided buffer without copying. |
| // * a static constexpr bool `kExpectUnbufferedWrites` giving the expectation |
| // of whether references to whole payloads can be provided for direct |
| // mutation. |
| // |
| // If the storage type is default-constructible, the trait must have a static |
| // constexpr Boolean member `kDefaultConstructedViewHasStorageError` indicating |
| // whether a default-constructed view of that storage type yields an error on |
| // iteration. |
| // |
| // If the storage type is writable, it must also provide: |
| // * `static void ToPayload(storage_type&, uint32_t, payload_type&)` that |
| // populates a payload value given by an offset into a storage instance. |
| // |
| // If the storage type is writable, it must also provide: |
| // * `void Write(storage_type&, uint32_t offset, Bytes data)` that writes data |
| // to the provided offset. |
| // |
| // If the storage type supports creation |
| // (i.e., zbitl::StorageTraits<storage_type> defines Create()) then the test |
| // traits must declare a `creation_traits` type giving the associated test |
| // trait type of its created storage. |
| // |
| |
| // |
| // Test cases. |
| // |
| |
| template <typename TestTraits> |
| inline void TestDefaultConstructedView() { |
| using Storage = typename TestTraits::storage_type; |
| static_assert(std::is_default_constructible_v<Storage>, |
| "this test case only applies to default-constructible storage types"); |
| |
| zbitl::View<Storage> view; |
| |
| // This ensures that everything statically compiles when instantiating the |
| // templates, even though the header/payloads are never used. |
| for (auto [header, payload] : view) { |
| EXPECT_EQ(header->flags, header->flags); |
| EXPECT_TRUE(false) << "should not be reached"; |
| } |
| |
| auto error = view.take_error(); |
| ASSERT_TRUE(error.is_error()) << "no error when header cannot be read??"; |
| EXPECT_FALSE(error.error_value().zbi_error.empty()) << "empty zbi_error string!!"; |
| if constexpr (TestTraits::kDefaultConstructedViewHasStorageError) { |
| EXPECT_TRUE(error.error_value().storage_error.has_value()); |
| } else { |
| EXPECT_FALSE(error.error_value().storage_error.has_value()); |
| } |
| } |
| |
| template <typename TestTraits, zbitl::Checking Checking> |
| inline void TestIteration(TestDataZbiType type) { |
| using Storage = typename TestTraits::storage_type; |
| |
| files::ScopedTempDir dir; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE(OpenTestDataZbi(type, dir.path(), &fd, &size)); |
| |
| typename TestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Create(std::move(fd), size, &context)); |
| zbitl::View<Storage, Checking> view(context.TakeStorage()); |
| |
| auto container_result = view.container_header(); |
| ASSERT_FALSE(container_result.is_error()) << ViewErrorString(container_result.error_value()); |
| |
| size_t idx = 0; |
| for (auto [header, payload] : view) { |
| EXPECT_EQ(GetExpectedItemType(type), header->type); |
| |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Read(view.storage(), payload, header->length, &actual)); |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx++, &expected)); |
| EXPECT_EQ(expected, actual); |
| |
| const uint32_t flags = header->flags; |
| EXPECT_TRUE(flags & ZBI_FLAG_VERSION) << "flags: 0x" << std::hex << flags; |
| } |
| EXPECT_EQ(GetExpectedNumberOfItems(type), idx); |
| |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| #define TEST_ITERATION_BY_CHECKING_AND_TYPE(suite_name, TestTraits, checking_name, checking, \ |
| type_name, type) \ |
| TEST(suite_name, type_name##checking_name##Iteration) { \ |
| auto test = TestIteration<TestTraits, checking>; \ |
| ASSERT_NO_FATAL_FAILURE(test(type)); \ |
| } |
| |
| // Note: using the CRC32-checking in tests is a cheap and easy way to verify |
| // that the storage type is delivering the correct payload data. |
| #define TEST_ITERATION_BY_TYPE(suite_name, TestTraits, type_name, type) \ |
| TEST_ITERATION_BY_CHECKING_AND_TYPE(suite_name, TestTraits, Permissive, \ |
| zbitl::Checking::kPermissive, type_name, type) \ |
| TEST_ITERATION_BY_CHECKING_AND_TYPE(suite_name, TestTraits, Strict, zbitl::Checking::kStrict, \ |
| type_name, type) \ |
| TEST_ITERATION_BY_CHECKING_AND_TYPE(suite_name, TestTraits, Crc, zbitl::Checking::kCrc, \ |
| type_name, type) |
| |
| #define TEST_ITERATION(suite_name, TestTraits) \ |
| TEST_ITERATION_BY_TYPE(suite_name, TestTraits, EmptyZbi, TestDataZbiType::kEmpty) \ |
| TEST_ITERATION_BY_TYPE(suite_name, TestTraits, OneItemZbi, TestDataZbiType::kOneItem) \ |
| TEST_ITERATION_BY_CHECKING_AND_TYPE(suite_name, TestTraits, Permissive, \ |
| zbitl::Checking::kPermissive, BadCrcZbi, \ |
| TestDataZbiType::kBadCrcItem) \ |
| TEST_ITERATION_BY_CHECKING_AND_TYPE(suite_name, TestTraits, Strict, zbitl::Checking::kStrict, \ |
| BadCrcZbi, TestDataZbiType::kBadCrcItem) \ |
| TEST_ITERATION_BY_TYPE(suite_name, TestTraits, MultipleSmallItemsZbi, \ |
| TestDataZbiType::kMultipleSmallItems) \ |
| TEST_ITERATION_BY_TYPE(suite_name, TestTraits, SecondItemOnPageBoundaryZbi, \ |
| TestDataZbiType::kSecondItemOnPageBoundary) |
| |
| template <typename TestTraits> |
| void TestCrcCheckFailure() { |
| using Storage = typename TestTraits::storage_type; |
| |
| files::ScopedTempDir dir; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE(OpenTestDataZbi(TestDataZbiType::kBadCrcItem, dir.path(), &fd, &size)); |
| |
| typename TestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Create(std::move(fd), size, &context)); |
| zbitl::CrcCheckingView<Storage> view(context.TakeStorage()); |
| |
| auto container_result = view.container_header(); |
| ASSERT_FALSE(container_result.is_error()) << ViewErrorString(container_result.error_value()); |
| |
| for (auto [header, payload] : view) { |
| EXPECT_EQ(header->type, header->type); |
| EXPECT_TRUE(false) << "should not be reached"; |
| } |
| auto error = view.take_error(); |
| |
| ASSERT_TRUE(error.is_error()); |
| // The error shouldn't be one of storage. |
| EXPECT_FALSE(error.error_value().storage_error) << error.error_value().zbi_error; |
| |
| // For the file types with errno for error_type, print the storage error. |
| if constexpr (std::is_same_v<std::decay_t<decltype(error.error_value().storage_error.value())>, |
| int>) { |
| EXPECT_FALSE(error.error_value().storage_error) << *error.error_value().storage_error << ": " |
| << strerror(*error.error_value().storage_error); |
| } |
| |
| // This matches the exact error text, so it has to be kept in sync. |
| // But otherwise we're not testing that the right error is diagnosed. |
| EXPECT_EQ(error.error_value().zbi_error, "item CRC32 mismatch"); |
| } |
| |
| template <typename TestTraits> |
| void TestMutation(TestDataZbiType type) { |
| files::ScopedTempDir dir; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| OpenTestDataZbi(type, dir.path(), &fd, &size); |
| |
| typename TestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Create(std::move(fd), size, &context)); |
| zbitl::View view(context.TakeStorage()); // Yay deduction guides. |
| |
| auto container_result = view.container_header(); |
| ASSERT_FALSE(container_result.is_error()) << ViewErrorString(container_result.error_value()); |
| |
| size_t expected_num_items = GetExpectedNumberOfItems(type); |
| |
| size_t idx = 0; |
| for (auto it = view.begin(); it != view.end(); ++it, ++idx) { |
| auto [header, payload] = *it; |
| |
| EXPECT_EQ(GetExpectedItemType(type), header->type); |
| |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Read(view.storage(), payload, header->length, &actual)); |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx, &expected)); |
| EXPECT_EQ(expected, actual); |
| |
| ASSERT_TRUE(view.EditHeader(it, {.type = ZBI_TYPE_DISCARD}).is_ok()); |
| } |
| EXPECT_EQ(expected_num_items, idx); |
| |
| { |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| idx = 0; |
| for (auto [header, payload] : view) { |
| EXPECT_EQ(static_cast<uint32_t>(ZBI_TYPE_DISCARD), header->type); |
| |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Read(view.storage(), payload, header->length, &actual)); |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx++, &expected)); |
| EXPECT_EQ(expected, actual); |
| } |
| EXPECT_EQ(expected_num_items, idx); |
| |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| #define TEST_MUTATION_BY_TYPE(suite_name, TestTraits, type_name, type) \ |
| TEST(suite_name, type_name##Mutation) { ASSERT_NO_FATAL_FAILURE(TestMutation<TestTraits>(type)); } |
| |
| #define TEST_MUTATION(suite_name, TestTraits) \ |
| TEST_MUTATION_BY_TYPE(suite_name, TestTraits, OneItemZbi, TestDataZbiType::kOneItem) \ |
| TEST_MUTATION_BY_TYPE(suite_name, TestTraits, BadCrcItemZbi, TestDataZbiType::kBadCrcItem) \ |
| TEST_MUTATION_BY_TYPE(suite_name, TestTraits, MultipleSmallItemsZbi, \ |
| TestDataZbiType::kMultipleSmallItems) \ |
| TEST_MUTATION_BY_TYPE(suite_name, TestTraits, SecondItemOnPageBoundaryZbi, \ |
| TestDataZbiType::kSecondItemOnPageBoundary) |
| |
| template <typename SrcTestTraits, typename DestTestTraits> |
| constexpr bool kExpectOneShotDecompression = |
| SrcTestTraits::kExpectOneshotReads&& DestTestTraits::kExpectUnbufferedWrites; |
| |
| template <typename SrcTestTraits, typename DestTestTraits> |
| constexpr bool kExpectZeroCopying = SrcTestTraits::kExpectOneshotReads || |
| (SrcTestTraits::kExpectUnbufferedReads && |
| DestTestTraits::kExpectUnbufferedWrites); |
| |
| inline size_t OneShotDecompressionScratchSize() { |
| return zbitl::decompress::OneShot::GetScratchSize(); |
| } |
| |
| template <typename TestTraits> |
| void TestCopyCreation(TestDataZbiType type, ItemCopyMode mode) { |
| using CreationTraits = typename TestTraits::creation_traits; |
| using Storage = typename TestTraits::storage_type; |
| using CreationStorage = typename CreationTraits::storage_type; |
| |
| static_assert(zbitl::View<Storage>::template CanZeroCopy<CreationStorage>() == |
| kExpectZeroCopying<TestTraits, CreationTraits>); |
| |
| files::ScopedTempDir dir; |
| TestAllocator allocator; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE(OpenTestDataZbi(type, dir.path(), &fd, &size)); |
| |
| typename TestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Create(std::move(fd), size, &context)); |
| zbitl::View view(context.TakeStorage()); |
| |
| auto get_size = [&mode](const zbi_header_t& header) -> size_t { |
| switch (mode) { |
| case ItemCopyMode::kRaw: |
| return header.length; |
| case ItemCopyMode::kWithHeader: |
| return header.length + sizeof(header); |
| case ItemCopyMode::kStorage: |
| // Though we are officially using the code-under-test here, the spec |
| // currently provides no way to determine whether a given type is a |
| // a storage type; once can only check whether it is among an |
| // exhaustive list of such types, which is what this utility does. |
| return zbitl::TypeIsStorage(header.type) ? header.extra : header.length; |
| }; |
| __UNREACHABLE; |
| }; |
| |
| auto do_copy = [&mode, &view, &allocator](auto it) { |
| switch (mode) { |
| case ItemCopyMode::kRaw: |
| return view.CopyRawItem(it); |
| case ItemCopyMode::kWithHeader: |
| return view.CopyRawItemWithHeader(it); |
| case ItemCopyMode::kStorage: |
| return view.CopyStorageItem(it, allocator); |
| }; |
| __UNREACHABLE; |
| }; |
| |
| size_t idx = 0; |
| for (auto it = view.begin(); it != view.end(); ++it, ++idx) { |
| const zbi_header_t header = *((*it).header); |
| |
| const size_t created_size = get_size(header); |
| auto result = do_copy(it); |
| ASSERT_TRUE(result.is_ok()) << "item " << idx << ": " |
| << ViewCopyErrorString(result.error_value()); |
| if (mode == ItemCopyMode::kStorage) { |
| if (ExpectItemsAreCompressed(type)) { |
| ASSERT_FALSE(allocator.allocated_.empty()); |
| // The first allocated size is expected to be the scratch size. |
| if (kExpectOneShotDecompression<TestTraits, CreationTraits>) { |
| EXPECT_EQ(OneShotDecompressionScratchSize(), allocator.allocated_.front()); |
| EXPECT_EQ(1u, allocator.allocated_.size()); |
| } else { |
| EXPECT_GT(allocator.allocated_.front(), OneShotDecompressionScratchSize()); |
| } |
| } else { |
| EXPECT_TRUE(allocator.allocated_.empty()); |
| } |
| } |
| |
| auto created = std::move(result).value(); |
| |
| Bytes actual; |
| typename CreationTraits::payload_type created_payload; |
| ASSERT_NO_FATAL_FAILURE(CreationTraits::ToPayload(created, 0, created_payload)); |
| ASSERT_NO_FATAL_FAILURE(CreationTraits::Read(created, created_payload, created_size, &actual)); |
| |
| Bytes expected; |
| switch (mode) { |
| case ItemCopyMode::kRaw: |
| case ItemCopyMode::kStorage: |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx, &expected)); |
| break; |
| case ItemCopyMode::kWithHeader: |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayloadWithHeader(type, idx, &expected)); |
| break; |
| }; |
| |
| EXPECT_EQ(expected, actual); |
| } |
| EXPECT_EQ(GetExpectedNumberOfItems(type), idx); |
| |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| // We simply test in this case that we are able to copy-create the byte ranges |
| // associated with the item payloads. More strenuous exercise of the interface |
| // is done under-the-hood by the other copy-creation tests. |
| template <typename TestTraits> |
| void TestCopyCreationByByteRange(TestDataZbiType type) { |
| using CreationTestTraits = typename TestTraits::creation_traits; |
| |
| files::ScopedTempDir dir; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE(OpenTestDataZbi(type, dir.path(), &fd, &size)); |
| |
| typename TestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Create(std::move(fd), size, &context)); |
| zbitl::View view(context.TakeStorage()); |
| |
| size_t idx = 0; |
| for (auto it = view.begin(); it != view.end(); ++it, ++idx) { |
| uint32_t payload_size = (*it).header->length; |
| // We pick a `to_offset` of `idx` for want of a value of zero along with |
| // varying, non-zero, non-random values. |
| uint32_t to_offset = static_cast<uint32_t>(idx); |
| auto result = view.Copy(it.payload_offset(), payload_size, to_offset); |
| EXPECT_FALSE(result.is_error()) << ViewCopyErrorString(result.error_value()); |
| |
| auto created = std::move(result).value(); |
| |
| Bytes actual; |
| typename CreationTestTraits::payload_type created_payload; |
| ASSERT_NO_FATAL_FAILURE(CreationTestTraits::ToPayload(created, 0, created_payload)); |
| ASSERT_NO_FATAL_FAILURE( |
| CreationTestTraits::Read(created, created_payload, to_offset + payload_size, &actual)); |
| |
| // We expect a head of `to_offset`-many zeroes. |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx, &expected)); |
| expected = Bytes(to_offset, '\0').append(expected); |
| |
| EXPECT_EQ(expected, actual); |
| } |
| EXPECT_EQ(GetExpectedNumberOfItems(type), idx); |
| |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| template <typename TestTraits> |
| void TestCopyCreationByIteratorRange(TestDataZbiType type) { |
| using CreationTestTraits = typename TestTraits::creation_traits; |
| |
| files::ScopedTempDir dir; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE(OpenTestDataZbi(type, dir.path(), &fd, &size)); |
| |
| typename TestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Create(std::move(fd), size, &context)); |
| zbitl::View view(context.TakeStorage()); |
| |
| // [begin(), begin() + 1). |
| { |
| auto first = view.begin(); |
| |
| auto copy_result = view.Copy(first, std::next(first)); |
| EXPECT_FALSE(copy_result.is_error()) << ViewCopyErrorString(copy_result.error_value()); |
| |
| auto created = std::move(copy_result).value(); |
| |
| zbitl::View created_view(std::move(created)); |
| auto container_result = view.container_header(); |
| ASSERT_FALSE(container_result.is_error()) << ViewErrorString(container_result.error_value()); |
| |
| size_t idx = 0; |
| for (auto [header, payload] : created_view) { |
| EXPECT_EQ(GetExpectedItemType(type), header->type); |
| |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE( |
| CreationTestTraits::Read(created_view.storage(), payload, header->length, &actual)); |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx++, &expected)); |
| EXPECT_EQ(expected, actual); |
| |
| const uint32_t flags = header->flags; |
| EXPECT_TRUE(flags & ZBI_FLAG_VERSION) << "flags: 0x" << std::hex << flags; |
| } |
| EXPECT_EQ(1u, idx); |
| |
| auto result = created_view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| // [begin() + 1, end()). |
| if (std::next(view.begin()) != view.end()) { |
| auto copy_result = view.Copy(std::next(view.begin()), view.end()); |
| EXPECT_FALSE(copy_result.is_error()) << ViewCopyErrorString(copy_result.error_value()); |
| |
| auto created = std::move(copy_result).value(); |
| |
| zbitl::View created_view(std::move(created)); |
| auto container_result = view.container_header(); |
| ASSERT_FALSE(container_result.is_error()) << ViewErrorString(container_result.error_value()); |
| |
| // We might have filled slop with a single discard element; skip if so. |
| auto first = created_view.begin(); |
| if ((*first).header->type == ZBI_TYPE_DISCARD) { |
| ++first; |
| } |
| |
| size_t idx = 1; // Corresponding to begin() + 1. |
| for (auto it = first; it != created_view.end(); ++it, ++idx) { |
| auto [header, payload] = *it; |
| EXPECT_EQ(GetExpectedItemType(type), header->type); |
| |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE( |
| CreationTestTraits::Read(created_view.storage(), payload, header->length, &actual)); |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx, &expected)); |
| EXPECT_EQ(expected, actual); |
| |
| const uint32_t flags = header->flags; |
| EXPECT_TRUE(flags & ZBI_FLAG_VERSION) << "flags: 0x" << std::hex << flags; |
| } |
| EXPECT_EQ(GetExpectedNumberOfItems(type), idx); |
| |
| auto result = created_view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| template <typename SrcTestTraits, typename DestTestTraits> |
| void TestCopyingIntoSmallStorage() { |
| files::ScopedTempDir dir; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE(OpenTestDataZbi(TestDataZbiType::kOneItem, dir.path(), &fd, &size)); |
| |
| typename SrcTestTraits::Context src_context; |
| ASSERT_NO_FATAL_FAILURE(SrcTestTraits::Create(std::move(fd), size, &src_context)); |
| zbitl::View view(src_context.TakeStorage()); |
| |
| auto [header, payload] = *(view.begin()); |
| |
| typename DestTestTraits::Context dest_context; |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::Create((header->length) / 2, &dest_context)); |
| auto small_storage = dest_context.TakeStorage(); |
| |
| auto copy_result = view.Copy(small_storage, view.begin().payload_offset(), header->length); |
| ASSERT_FALSE(copy_result.is_error()) << ViewCopyErrorString(std::move(copy_result).error_value()); |
| |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(SrcTestTraits::Read(view.storage(), payload, header->length, &expected)); |
| |
| Bytes actual; |
| typename DestTestTraits::payload_type as_payload; |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::ToPayload(small_storage, 0, as_payload)); |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::Read(small_storage, as_payload, header->length, &actual)); |
| |
| EXPECT_EQ(expected, actual); |
| |
| auto result = view.take_error(); |
| ASSERT_FALSE(result.is_error()) << ViewErrorString(std::move(result).error_value()); |
| } |
| |
| #define TEST_COPY_CREATION_BY_TYPE_AND_MODE(suite_name, TestTraits, type_name, type, mode, \ |
| mode_name) \ |
| TEST(suite_name, type_name##CopyCreation##mode_name) { \ |
| ASSERT_NO_FATAL_FAILURE(TestCopyCreation<TestTraits>(type, mode)); \ |
| } |
| |
| #define TEST_COPY_CREATION_BY_TYPE(suite_name, TestTraits, type_name, type) \ |
| TEST_COPY_CREATION_BY_TYPE_AND_MODE(suite_name, TestTraits, type_name, type, \ |
| ItemCopyMode::kRaw, ) \ |
| TEST_COPY_CREATION_BY_TYPE_AND_MODE(suite_name, TestTraits, type_name, type, \ |
| ItemCopyMode::kWithHeader, WithHeader) \ |
| TEST_COPY_CREATION_BY_TYPE_AND_MODE(suite_name, TestTraits, type_name, type, \ |
| ItemCopyMode::kStorage, AsStorage) \ |
| TEST(suite_name, type_name##CopyCreationByByteRange) { \ |
| ASSERT_NO_FATAL_FAILURE(TestCopyCreationByByteRange<TestTraits>(type)); \ |
| } \ |
| TEST(suite_name, type_name##CopyCreationByIteratorRange) { \ |
| ASSERT_NO_FATAL_FAILURE(TestCopyCreationByIteratorRange<TestTraits>(type)); \ |
| } |
| |
| #define TEST_COPY_CREATION(suite_name, TestTraits) \ |
| TEST_COPY_CREATION_BY_TYPE(suite_name, TestTraits, OneItemZbi, TestDataZbiType::kOneItem) \ |
| TEST_COPY_CREATION_BY_TYPE_AND_MODE(suite_name, TestTraits, CompressedItemZbi, \ |
| TestDataZbiType::kCompressedItem, ItemCopyMode::kStorage, \ |
| AsStorage) \ |
| TEST_COPY_CREATION_BY_TYPE_AND_MODE(suite_name, TestTraits, BadCrcItemZbi, \ |
| TestDataZbiType::kBadCrcItem, ItemCopyMode::kRaw, ) \ |
| TEST_COPY_CREATION_BY_TYPE(suite_name, TestTraits, MultipleSmallItemsZbi, \ |
| TestDataZbiType::kMultipleSmallItems) \ |
| TEST_COPY_CREATION_BY_TYPE(suite_name, TestTraits, SecondItemOnPageBoundaryZbi, \ |
| TestDataZbiType::kSecondItemOnPageBoundary) |
| |
| template <typename SrcTestTraits, typename DestTestTraits> |
| void TestCopying(TestDataZbiType type, ItemCopyMode mode) { |
| using SrcStorage = typename SrcTestTraits::storage_type; |
| using DestStorage = typename DestTestTraits::storage_type; |
| |
| static_assert(zbitl::View<SrcStorage>::template CanZeroCopy<DestStorage>() == |
| kExpectZeroCopying<SrcTestTraits, DestTestTraits>); |
| |
| files::ScopedTempDir dir; |
| TestAllocator allocator; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE(OpenTestDataZbi(type, dir.path(), &fd, &size)); |
| |
| typename SrcTestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(SrcTestTraits::Create(std::move(fd), size, &context)); |
| zbitl::View view(context.TakeStorage()); |
| |
| auto get_size = [&mode](const zbi_header_t& header) -> size_t { |
| switch (mode) { |
| case ItemCopyMode::kRaw: |
| return header.length; |
| case ItemCopyMode::kWithHeader: |
| return header.length + sizeof(header); |
| case ItemCopyMode::kStorage: |
| return zbitl::UncompressedLength(header); |
| }; |
| __UNREACHABLE; |
| }; |
| |
| auto do_copy = [&mode, &view, &allocator ](auto&& storage, auto it) -> auto { |
| using Storage = decltype(storage); |
| switch (mode) { |
| case ItemCopyMode::kRaw: |
| return view.CopyRawItem(std::forward<Storage>(storage), it); |
| case ItemCopyMode::kWithHeader: |
| return view.CopyRawItemWithHeader(std::forward<Storage>(storage), it); |
| case ItemCopyMode::kStorage: |
| return view.CopyStorageItem(std::forward<Storage>(storage), it, allocator); |
| }; |
| __UNREACHABLE; |
| }; |
| |
| size_t idx = 0; |
| for (auto it = view.begin(); it != view.end(); ++it, ++idx) { |
| const zbi_header_t header = *((*it).header); |
| const size_t copy_size = get_size(header); |
| |
| typename DestTestTraits::Context copy_context; |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::Create(copy_size, ©_context)); |
| auto copy = copy_context.TakeStorage(); |
| auto result = do_copy(std::move(copy), it); |
| ASSERT_TRUE(result.is_ok()) << "item " << idx << ": " |
| << ViewCopyErrorString(result.error_value()); |
| |
| if (mode == ItemCopyMode::kStorage) { |
| if (ExpectItemsAreCompressed(type)) { |
| ASSERT_FALSE(allocator.allocated_.empty()); |
| // The first allocated size is expected to be the scratch size. |
| if (kExpectOneShotDecompression<SrcTestTraits, DestTestTraits>) { |
| EXPECT_EQ(OneShotDecompressionScratchSize(), allocator.allocated_.front()); |
| EXPECT_EQ(1u, allocator.allocated_.size()); |
| } else { |
| EXPECT_GT(allocator.allocated_.front(), OneShotDecompressionScratchSize()); |
| } |
| } else { |
| EXPECT_TRUE(allocator.allocated_.empty()); |
| } |
| } |
| |
| Bytes actual; |
| typename DestTestTraits::payload_type copy_payload; |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::ToPayload(copy, 0, copy_payload)); |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::Read(copy, copy_payload, copy_size, &actual)); |
| |
| Bytes expected; |
| switch (mode) { |
| case ItemCopyMode::kRaw: |
| case ItemCopyMode::kStorage: |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx, &expected)); |
| break; |
| case ItemCopyMode::kWithHeader: |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayloadWithHeader(type, idx, &expected)); |
| break; |
| }; |
| EXPECT_EQ(expected, actual); |
| } |
| EXPECT_EQ(GetExpectedNumberOfItems(type), idx); |
| |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| // We simply test in this case that we are able to copy the byte ranges |
| // associated with the item payloads. More strenuous exercise of the |
| // interface is done under-the-hood by the other copy tests. |
| template <typename SrcTestTraits, typename DestTestTraits> |
| void TestCopyingByByteRange(TestDataZbiType type) { |
| files::ScopedTempDir dir; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE(OpenTestDataZbi(type, dir.path(), &fd, &size)); |
| |
| typename SrcTestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(SrcTestTraits::Create(std::move(fd), size, &context)); |
| zbitl::View view(context.TakeStorage()); |
| |
| size_t idx = 0; |
| for (auto it = view.begin(); it != view.end(); ++it) { |
| // We pick a `to_offset` of `idx` for want of a value of zero along with |
| // varying, non-zero, non-random values. |
| uint32_t to_offset = static_cast<uint32_t>(idx); |
| |
| uint32_t payload_size = (*it).header->length; |
| |
| typename DestTestTraits::Context copy_context; |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::Create(to_offset + payload_size, ©_context)); |
| auto copy = copy_context.TakeStorage(); |
| |
| auto result = view.Copy(copy, it.payload_offset(), payload_size, to_offset); |
| EXPECT_FALSE(result.is_error()) << ViewCopyErrorString(result.error_value()); |
| |
| Bytes actual; |
| typename DestTestTraits::payload_type copy_payload; |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::ToPayload(copy, to_offset, copy_payload)); |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::Read(copy, copy_payload, payload_size, &actual)); |
| |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx++, &expected)); |
| EXPECT_EQ(expected, actual); |
| } |
| EXPECT_EQ(GetExpectedNumberOfItems(type), idx); |
| |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| template <typename SrcTestTraits, typename DestTestTraits> |
| void TestCopyingByIteratorRange(TestDataZbiType type) { |
| files::ScopedTempDir dir; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE(OpenTestDataZbi(type, dir.path(), &fd, &size)); |
| |
| typename SrcTestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(SrcTestTraits::Create(std::move(fd), size, &context)); |
| zbitl::View view(context.TakeStorage()); |
| |
| // [begin(), begin() + 1) |
| { |
| typename DestTestTraits::Context copy_context; |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::Create(kMaxZbiSize, ©_context)); |
| auto copy = copy_context.TakeStorage(); |
| |
| auto first = view.begin(); |
| auto copy_result = view.Copy(copy, first, std::next(view.begin())); |
| EXPECT_FALSE(copy_result.is_error()) << ViewCopyErrorString(copy_result.error_value()); |
| |
| zbitl::View copy_view(std::move(copy)); |
| auto container_result = view.container_header(); |
| ASSERT_FALSE(container_result.is_error()) << ViewErrorString(container_result.error_value()); |
| |
| size_t idx = 0; |
| for (auto [header, payload] : copy_view) { |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE( |
| DestTestTraits::Read(copy_view.storage(), payload, header->length, &actual)); |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx++, &expected)); |
| EXPECT_EQ(expected, actual); |
| } |
| EXPECT_EQ(1u, idx); |
| |
| auto result = copy_view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| // [begin() + 1, end()). |
| if (std::next(view.begin()) != view.end()) { |
| typename DestTestTraits::Context copy_context; |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::Create(kMaxZbiSize, ©_context)); |
| auto copy = copy_context.TakeStorage(); |
| |
| auto first = std::next(view.begin()); |
| { |
| auto result = view.Copy(copy, first, view.end()); |
| EXPECT_FALSE(result.is_error()) << ViewCopyErrorString(result.error_value()); |
| } |
| |
| zbitl::View copy_view(std::move(copy)); |
| auto container_result = view.container_header(); |
| ASSERT_FALSE(container_result.is_error()) << ViewErrorString(container_result.error_value()); |
| |
| size_t idx = 1; // Corresponding to begin() + 1. |
| for (auto [header, payload] : copy_view) { |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE( |
| DestTestTraits::Read(copy_view.storage(), payload, header->length, &actual)); |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE(GetExpectedPayload(type, idx++, &expected)); |
| EXPECT_EQ(expected, actual); |
| } |
| EXPECT_EQ(GetExpectedNumberOfItems(type), idx); |
| |
| auto result = copy_view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| |
| { |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(result.error_value()); |
| } |
| } |
| |
| #define TEST_COPYING_BY_TYPE_AND_MODE(suite_name, SrcTestTraits, src_name, DestTestTraits, \ |
| dest_name, type_name, type, mode, mode_name) \ |
| TEST(suite_name, type_name##Copy##src_name##To##dest_name##mode_name) { \ |
| auto test = TestCopying<SrcTestTraits, DestTestTraits>; \ |
| ASSERT_NO_FATAL_FAILURE(test(type, mode)); \ |
| } |
| |
| #define TEST_COPYING_BY_TYPE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name, \ |
| type_name, type) \ |
| TEST_COPYING_BY_TYPE_AND_MODE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name, \ |
| type_name, type, ItemCopyMode::kRaw, ) \ |
| TEST_COPYING_BY_TYPE_AND_MODE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name, \ |
| type_name, type, ItemCopyMode::kWithHeader, WithHeader) \ |
| TEST_COPYING_BY_TYPE_AND_MODE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name, \ |
| type_name, type, ItemCopyMode::kStorage, AsStorage) \ |
| TEST(suite_name, type_name##Copy##src_name##To##dest_name##ByByteRange) { \ |
| auto test = TestCopyingByByteRange<SrcTestTraits, DestTestTraits>; \ |
| ASSERT_NO_FATAL_FAILURE(test(type)); \ |
| } \ |
| TEST(suite_name, type_name##Copy##src_name##To##dest_name##ByIteratorRange) { \ |
| auto test = TestCopyingByIteratorRange<SrcTestTraits, DestTestTraits>; \ |
| ASSERT_NO_FATAL_FAILURE(test(type)); \ |
| } |
| |
| #define TEST_COPYING_INTO_SMALL_STORAGE(suite_name, SrcTestTraits, src_name, DestTestTraits, \ |
| dest_name) \ |
| TEST(suite_name, Copying##src_name##to##dest_name##SmallStorage) { \ |
| auto test = TestCopyingIntoSmallStorage<SrcTestTraits, DestTestTraits>; \ |
| ASSERT_NO_FATAL_FAILURE(test()); \ |
| } |
| |
| #define TEST_COPYING(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name) \ |
| TEST_COPYING_BY_TYPE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name, OneItemZbi, \ |
| TestDataZbiType::kOneItem) \ |
| TEST_COPYING_BY_TYPE_AND_MODE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name, \ |
| CompressedItemZbi, TestDataZbiType::kCompressedItem, \ |
| ItemCopyMode::kStorage, AsStorage) \ |
| TEST_COPYING_BY_TYPE_AND_MODE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name, \ |
| BadCrcItemZbi, TestDataZbiType::kBadCrcItem, ItemCopyMode::kRaw, ) \ |
| TEST_COPYING_BY_TYPE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name, \ |
| MultipleSmallItemsZbi, TestDataZbiType::kMultipleSmallItems) \ |
| TEST_COPYING_BY_TYPE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name, \ |
| SecondItemOnPageBoundaryZbi, TestDataZbiType::kSecondItemOnPageBoundary) \ |
| TEST_COPYING_INTO_SMALL_STORAGE(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name) |
| |
| template <typename TestTraits> |
| void TestAppending() { |
| using Storage = typename TestTraits::storage_type; |
| |
| const Bytes to_append[] = { |
| "", |
| "aligned ", |
| "unaligned", |
| }; |
| |
| // The expected resulting size from appending items corresponding to the |
| // entries in `to_append`, once per `Append` method. |
| constexpr size_t kExpectedFinalSize = 272; |
| |
| // For extensible storage, we expect the capacity to increase as needed |
| // during Image operations. |
| constexpr size_t kInitialSize = TestTraits::kExpectExtensibility ? 0 : kExpectedFinalSize; |
| |
| constexpr uint32_t kItemType = ZBI_TYPE_IMAGE_ARGS; |
| |
| typename TestTraits::Context context; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Create(kInitialSize, &context)); |
| // Checking::kCrc will help ensure that we are appending items with valid |
| // CRC32s in the append-with-payload API. |
| zbitl::CrcCheckingImage<Storage> image(context.TakeStorage()); |
| |
| // clear() will turn an empty storage object into an empty ZBI (i.e., of |
| // sufficient size to hold a trivial ZBI container header). |
| { |
| auto clear_result = image.clear(); |
| ASSERT_FALSE(clear_result.is_error()) << ViewErrorString(std::move(clear_result).error_value()); |
| ASSERT_EQ(image.end(), image.begin()); // Is indeed empty. |
| } |
| |
| // Append-with-payload. |
| for (const Bytes& bytes : to_append) { |
| auto append_result = image.Append( |
| zbi_header_t{ |
| .type = kItemType, |
| .flags = ZBI_FLAG_CRC32, |
| }, |
| zbitl::ByteView{reinterpret_cast<const std::byte*>(bytes.data()), bytes.size()}); |
| ASSERT_FALSE(append_result.is_error()) |
| << "bytes = \"" << bytes |
| << "\": " << ViewErrorString(std::move(append_result).error_value()); |
| } |
| |
| // Append-with-deferred-write. |
| for (const Bytes& bytes : to_append) { |
| auto append_result = image.Append(zbi_header_t{ |
| .type = kItemType, |
| .length = static_cast<uint32_t>(bytes.size()), |
| }); |
| ASSERT_FALSE(append_result.is_error()) |
| << "bytes = \"" << bytes |
| << "\": " << ViewErrorString(std::move(append_result).error_value()); |
| |
| auto it = std::move(append_result).value(); |
| ASSERT_NE(it, image.end()); |
| |
| // The recorded header should be sanitized. |
| auto [header, payload] = *it; |
| EXPECT_EQ(kItemType, header->type); |
| EXPECT_EQ(bytes.size(), header->length); |
| EXPECT_EQ(ZBI_ITEM_MAGIC, header->magic); |
| EXPECT_TRUE(ZBI_FLAG_VERSION & header->flags); |
| EXPECT_FALSE(ZBI_FLAG_CRC32 & header->flags); // We did not bother to set it. |
| EXPECT_EQ(static_cast<uint32_t>(ZBI_ITEM_NO_CRC32), header->crc32); |
| |
| if (!bytes.empty()) { |
| uint32_t offset = it.payload_offset(); |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Write(image.storage(), offset, bytes)); |
| } |
| } |
| |
| auto it = image.begin(); |
| for (size_t variation = 0; variation < 2; ++variation) { |
| for (size_t i = 0; i < std::size(to_append) && it != image.end(); ++i, ++it) { |
| auto [header, payload] = *it; |
| |
| // The recorded header should have add a number of fields set on the |
| // caller's behalf. |
| EXPECT_EQ(kItemType, header->type); |
| EXPECT_EQ(to_append[i].size(), header->length); // Auto-computed in append-with-payload. |
| EXPECT_EQ(ZBI_ITEM_MAGIC, header->magic); |
| EXPECT_TRUE(ZBI_FLAG_VERSION & header->flags); |
| // That we are using a CRC-checking image guarantees that the right |
| // CRC32 values are computed. |
| switch (variation) { |
| case 0: { // append-with-payload |
| EXPECT_TRUE(ZBI_FLAG_CRC32 & header->flags); |
| break; |
| } |
| case 1: { // append-with-deferred-write |
| EXPECT_FALSE(ZBI_FLAG_CRC32 & header->flags); |
| break; |
| } |
| }; |
| |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE(TestTraits::Read(image.storage(), payload, header->length, &actual)); |
| const Bytes expected = to_append[i]; |
| // `actual[0:expected.size()]` should coincide with `expected`, and its tail |
| // should be a |ZBI_ALIGNMENT|-pad of zeroes. |
| ASSERT_EQ(static_cast<uint32_t>(expected.size()), actual.size()); |
| EXPECT_EQ(expected, actual.substr(0, expected.size())); |
| EXPECT_TRUE(std::all_of(actual.begin() + expected.size(), actual.end(), |
| [](char c) -> bool { return c == 0; })); |
| } |
| } |
| EXPECT_EQ(image.end(), it); |
| EXPECT_EQ(kExpectedFinalSize, image.size_bytes()); |
| |
| // If we are dealing with non-extensible storage, attempting to append |
| // again should result in an error. |
| if constexpr (!TestTraits::kExpectExtensibility) { |
| { |
| auto result = image.Append(zbi_header_t{.type = kItemType}, zbitl::ByteView{}); |
| EXPECT_TRUE(result.is_error()); |
| } |
| { |
| auto result = image.Append(zbi_header_t{.type = kItemType, .length = 0}); |
| EXPECT_TRUE(result.is_error()); |
| } |
| } |
| |
| { |
| auto result = image.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(std::move(result).error_value()); |
| } |
| |
| // clear() will reset the underlying ZBI as empty. |
| { |
| auto clear_result = image.clear(); |
| ASSERT_FALSE(clear_result.is_error()) << ViewErrorString(std::move(clear_result).error_value()); |
| ASSERT_EQ(image.end(), image.begin()); // Is indeed empty. |
| |
| auto result = image.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(std::move(result).error_value()); |
| } |
| } |
| |
| template <typename SrcTestTraits, typename DestTestTraits> |
| void TestExtending() { |
| files::ScopedTempDir dir; |
| |
| fbl::unique_fd fd; |
| size_t size = 0; |
| ASSERT_NO_FATAL_FAILURE( |
| OpenTestDataZbi(TestDataZbiType::kMultipleSmallItems, dir.path(), &fd, &size)); |
| |
| typename SrcTestTraits::Context src_context; |
| ASSERT_NO_FATAL_FAILURE(SrcTestTraits::Create(std::move(fd), size, &src_context)); |
| zbitl::View view(src_context.TakeStorage()); |
| |
| typename DestTestTraits::Context dest_context; |
| ASSERT_NO_FATAL_FAILURE(DestTestTraits::Create(0, &dest_context)); |
| zbitl::Image image(dest_context.TakeStorage()); |
| |
| // clear() will turn an empty storage object into an empty ZBI (i.e., of |
| // sufficient size to hold a trivial ZBI container header). |
| { |
| auto clear_result = image.clear(); |
| ASSERT_FALSE(clear_result.is_error()) << ViewErrorString(std::move(clear_result).error_value()); |
| ASSERT_EQ(image.end(), image.begin()); // Is indeed empty. |
| } |
| |
| // [begin(), begin() + 1) |
| { |
| auto extend_result = image.Extend(view.begin(), ++view.begin()); |
| EXPECT_FALSE(extend_result.is_error()) |
| << ViewCopyErrorString(std::move(extend_result).error_value()); |
| |
| size_t idx = 0; |
| for (auto [header, payload] : image) { |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE( |
| DestTestTraits::Read(image.storage(), payload, header->length, &actual)); |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE( |
| GetExpectedPayload(TestDataZbiType::kMultipleSmallItems, idx++, &expected)); |
| EXPECT_EQ(expected, actual); |
| } |
| EXPECT_EQ(1u, idx); |
| } |
| |
| // [begin() + 1, end()) |
| { |
| auto extend_result = image.Extend(++view.begin(), view.end()); |
| EXPECT_FALSE(extend_result.is_error()) |
| << ViewCopyErrorString(std::move(extend_result).error_value()); |
| |
| size_t idx = 0; |
| for (auto [header, payload] : image) { |
| Bytes actual; |
| ASSERT_NO_FATAL_FAILURE( |
| DestTestTraits::Read(image.storage(), payload, header->length, &actual)); |
| Bytes expected; |
| ASSERT_NO_FATAL_FAILURE( |
| GetExpectedPayload(TestDataZbiType::kMultipleSmallItems, idx++, &expected)); |
| EXPECT_EQ(expected, actual); |
| } |
| EXPECT_EQ(GetExpectedNumberOfItems(TestDataZbiType::kMultipleSmallItems), idx); |
| } |
| |
| { |
| auto result = view.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(std::move(result).error_value()); |
| } |
| { |
| auto result = image.take_error(); |
| EXPECT_FALSE(result.is_error()) << ViewErrorString(std::move(result).error_value()); |
| } |
| } |
| |
| // Ensures that the relevant macros expansions of TEST_EXTENDING's arguments |
| // happen as expected. |
| #define TEST_EXTENDING_1(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name) \ |
| TEST(suite_name, Extend##dest_name##With##src_name) { \ |
| auto test = TestExtending<SrcTestTraits, DestTestTraits>; \ |
| ASSERT_NO_FATAL_FAILURE(test()); \ |
| } |
| |
| #define TEST_EXTENDING(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name) \ |
| TEST_EXTENDING_1(suite_name, SrcTestTraits, src_name, DestTestTraits, dest_name) |
| |
| #endif // ZIRCON_SYSTEM_ULIB_ZBITL_TEST_TESTS_H_ |