| // Copyright 2023 The Pigweed Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| // use this file except in compliance with the License. You may obtain a copy of |
| // the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| // License for the specific language governing permissions and limitations under |
| // the License. |
| |
| #include "pw_containers/inline_var_len_entry_queue.h" |
| |
| #include <cstring> |
| #include <string_view> |
| #include <variant> |
| |
| #include "pw_containers/test/generic_var_len_entry_queue_testing.h" |
| #include "pw_containers_private/inline_var_len_entry_queue_test_oracle.h" |
| #include "pw_span/span.h" |
| #include "pw_unit_test/framework.h" |
| |
| namespace { |
| |
| struct PushOverwrite { |
| std::string_view data; |
| }; |
| struct Push { |
| std::string_view data; |
| }; |
| struct TryPush { |
| std::string_view data; |
| bool expected; |
| }; |
| struct Pop {}; |
| struct Clear {}; |
| struct SizeEquals { |
| size_t expected; |
| }; |
| |
| using TestStep = |
| std::variant<PushOverwrite, Push, TryPush, Pop, Clear, SizeEquals>; |
| |
| // Copies an entry, which might be wrapped, to a single std::vector. |
| std::vector<std::byte> ReadEntry(const pw_InlineVarLenEntryQueue_Iterator& it) { |
| auto entry = pw_InlineVarLenEntryQueue_GetEntry(&it); |
| std::vector<std::byte> value(entry.size_1 + entry.size_2); |
| EXPECT_EQ(value.size(), |
| pw_InlineVarLenEntryQueue_Entry_Copy( |
| &entry, value.data(), entry.size_1 + entry.size_2)); |
| return value; |
| } |
| |
| // Declares a test that performs a series of operations on the C and C++ |
| // versions of InlineVarLenEntryQueue and the "oracle" class, and checks that |
| // they match after every step. |
| template <size_t kMaxEntrySize> |
| void DataDrivenTest(pw::span<const TestStep> program) { |
| pw::InlineVarLenEntryQueue<kMaxEntrySize> cpp_queue; |
| PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE(c_queue, kMaxEntrySize); |
| pw::containers::InlineVarLenEntryQueueTestOracle oracle(kMaxEntrySize); |
| |
| /* Check the queue sizes */ |
| static_assert(sizeof(cpp_queue) == sizeof(c_queue)); |
| ASSERT_EQ(cpp_queue.raw_storage().data(), |
| reinterpret_cast<const std::byte*>(&cpp_queue)); |
| ASSERT_EQ(cpp_queue.raw_storage().size_bytes(), |
| pw_InlineVarLenEntryQueue_RawStorageSizeBytes(c_queue)); |
| |
| for (const TestStep& step : program) { |
| /* Take the action */ |
| if (auto ow = std::get_if<PushOverwrite>(&step); ow != nullptr) { |
| cpp_queue.push_overwrite(pw::as_bytes(pw::span(ow->data))); |
| pw_InlineVarLenEntryQueue_PushOverwrite( |
| c_queue, ow->data.data(), static_cast<uint32_t>(ow->data.size())); |
| oracle.push_overwrite(pw::as_bytes(pw::span(ow->data))); |
| } else if (auto push = std::get_if<Push>(&step); push != nullptr) { |
| cpp_queue.push(pw::as_bytes(pw::span(push->data))); |
| pw_InlineVarLenEntryQueue_Push( |
| c_queue, push->data.data(), static_cast<uint32_t>(push->data.size())); |
| oracle.push(pw::as_bytes(pw::span(push->data))); |
| } else if (auto try_push = std::get_if<TryPush>(&step); |
| try_push != nullptr) { |
| ASSERT_EQ(try_push->expected, |
| cpp_queue.try_push(pw::as_bytes(pw::span(try_push->data)))); |
| ASSERT_EQ(try_push->expected, |
| pw_InlineVarLenEntryQueue_TryPush( |
| c_queue, |
| try_push->data.data(), |
| static_cast<uint32_t>(try_push->data.size()))); |
| if (try_push->expected) { |
| oracle.push(pw::as_bytes(pw::span(try_push->data))); |
| } |
| } else if (std::holds_alternative<Pop>(step)) { |
| cpp_queue.pop(); |
| pw_InlineVarLenEntryQueue_Pop(c_queue); |
| oracle.pop(); |
| } else if (auto size = std::get_if<SizeEquals>(&step); size != nullptr) { |
| const size_t actual = cpp_queue.size(); |
| ASSERT_EQ(actual, pw_InlineVarLenEntryQueue_Size(c_queue)); |
| ASSERT_EQ(oracle.size(), actual); |
| ASSERT_EQ(size->expected, actual); |
| } else if (std::holds_alternative<Clear>(step)) { |
| cpp_queue.clear(); |
| pw_InlineVarLenEntryQueue_Clear(c_queue); |
| oracle.clear(); |
| } else { |
| FAIL() << "Unhandled case"; |
| } |
| /* Check sizes */ |
| ASSERT_EQ(cpp_queue.size(), oracle.size()); |
| ASSERT_EQ(cpp_queue.size_bytes(), oracle.size_bytes()); |
| ASSERT_EQ(cpp_queue.max_size_bytes(), oracle.max_size_bytes()); |
| |
| ASSERT_EQ(pw_InlineVarLenEntryQueue_Size(c_queue), oracle.size()); |
| ASSERT_EQ(pw_InlineVarLenEntryQueue_SizeBytes(c_queue), |
| oracle.size_bytes()); |
| ASSERT_EQ(pw_InlineVarLenEntryQueue_MaxSizeBytes(c_queue), |
| oracle.max_size_bytes()); |
| |
| /* Compare the contents */ |
| auto oracle_it = oracle.begin(); |
| auto c_queue_it = pw_InlineVarLenEntryQueue_Begin(c_queue); |
| |
| const auto c_queue_end = pw_InlineVarLenEntryQueue_End(c_queue); |
| uint32_t entries_compared = 0; |
| |
| for (auto entry : cpp_queue) { |
| entries_compared += 1; |
| |
| ASSERT_EQ(*oracle_it, ReadEntry(c_queue_it)); |
| ASSERT_EQ(*oracle_it, std::vector<std::byte>(entry.begin(), entry.end())); |
| |
| ASSERT_NE(oracle_it, oracle.end()); |
| ASSERT_FALSE( |
| pw_InlineVarLenEntryQueue_Iterator_Equal(&c_queue_it, &c_queue_end)); |
| |
| ++oracle_it; |
| pw_InlineVarLenEntryQueue_Iterator_Advance(&c_queue_it); |
| } |
| ASSERT_EQ(entries_compared, oracle.size()); |
| ASSERT_TRUE( |
| pw_InlineVarLenEntryQueue_Iterator_Equal(&c_queue_it, &c_queue_end)); |
| ASSERT_EQ(oracle_it, oracle.end()); |
| } |
| } |
| |
| #define DATA_DRIVEN_TEST(steps, max_entry_size) \ |
| TEST(InlineVarLenEntryQueue, \ |
| DataDrivenTest_##steps##_MaxSizeBytes##max_entry_size) { \ |
| DataDrivenTest<max_entry_size>({steps, sizeof(steps) / sizeof(TestStep)}); \ |
| } \ |
| static_assert(true, "use a semicolon") |
| |
| constexpr TestStep kPop[] = { |
| SizeEquals{0}, |
| PushOverwrite{""}, |
| SizeEquals{1}, |
| Pop{}, |
| SizeEquals{0}, |
| }; |
| |
| DATA_DRIVEN_TEST(kPop, 0); // Only holds one empty entry. |
| DATA_DRIVEN_TEST(kPop, 1); |
| DATA_DRIVEN_TEST(kPop, 6); |
| |
| constexpr TestStep kOverwriteLargeEntriesWithSmall[] = { |
| PushOverwrite{"12345"}, |
| PushOverwrite{"abcde"}, |
| PushOverwrite{""}, |
| PushOverwrite{""}, |
| PushOverwrite{""}, |
| PushOverwrite{""}, |
| PushOverwrite{""}, |
| PushOverwrite{""}, |
| SizeEquals{6}, |
| Pop{}, |
| Pop{}, |
| Pop{}, |
| Pop{}, |
| Pop{}, |
| Pop{}, |
| SizeEquals{0}, |
| }; |
| DATA_DRIVEN_TEST(kOverwriteLargeEntriesWithSmall, 5); |
| DATA_DRIVEN_TEST(kOverwriteLargeEntriesWithSmall, 6); |
| DATA_DRIVEN_TEST(kOverwriteLargeEntriesWithSmall, 7); |
| |
| constexpr TestStep kOverwriteVaryingSizes012[] = { |
| PushOverwrite{""}, PushOverwrite{""}, PushOverwrite{""}, |
| PushOverwrite{""}, PushOverwrite{""}, PushOverwrite{"1"}, |
| PushOverwrite{"2"}, PushOverwrite{""}, PushOverwrite{"3"}, |
| PushOverwrite{"4"}, PushOverwrite{""}, PushOverwrite{"5"}, |
| PushOverwrite{"6"}, PushOverwrite{"ab"}, PushOverwrite{"cd"}, |
| PushOverwrite{""}, PushOverwrite{"ef"}, PushOverwrite{"gh"}, |
| PushOverwrite{"ij"}, |
| }; |
| DATA_DRIVEN_TEST(kOverwriteVaryingSizes012, 2); |
| DATA_DRIVEN_TEST(kOverwriteVaryingSizes012, 3); |
| |
| constexpr TestStep kOverwriteVaryingSizesUpTo4[] = { |
| PushOverwrite{""}, |
| PushOverwrite{""}, |
| PushOverwrite{""}, |
| PushOverwrite{"1"}, |
| PushOverwrite{"2"}, |
| PushOverwrite{"3"}, |
| PushOverwrite{"ab"}, |
| PushOverwrite{"cd"}, |
| PushOverwrite{"ef"}, |
| PushOverwrite{"123"}, |
| PushOverwrite{"456"}, |
| PushOverwrite{"789"}, |
| PushOverwrite{"abcd"}, |
| PushOverwrite{"efgh"}, |
| PushOverwrite{"ijkl"}, |
| TryPush{"uhoh", false}, |
| Pop{}, |
| SizeEquals{0}, |
| }; |
| DATA_DRIVEN_TEST(kOverwriteVaryingSizesUpTo4, 4); |
| DATA_DRIVEN_TEST(kOverwriteVaryingSizesUpTo4, 5); |
| DATA_DRIVEN_TEST(kOverwriteVaryingSizesUpTo4, 6); |
| |
| constexpr char kBigEntryBytes[196]{}; |
| |
| template <size_t kSizeBytes> |
| constexpr std::string_view kBigEntry(kBigEntryBytes, kSizeBytes); |
| |
| constexpr TestStep kTwoBytePrefix[] = { |
| PushOverwrite{kBigEntry<128>}, |
| PushOverwrite{kBigEntry<128>}, |
| PushOverwrite{kBigEntry<127>}, |
| PushOverwrite{kBigEntry<128>}, |
| PushOverwrite{kBigEntry<127>}, |
| SizeEquals{1}, |
| Pop{}, |
| SizeEquals{0}, |
| }; |
| DATA_DRIVEN_TEST(kTwoBytePrefix, 128); |
| DATA_DRIVEN_TEST(kTwoBytePrefix, 129); |
| |
| constexpr TestStep kClear[] = { |
| Push{"abcdefg"}, |
| PushOverwrite{""}, |
| PushOverwrite{""}, |
| PushOverwrite{"a"}, |
| PushOverwrite{"b"}, |
| Clear{}, |
| SizeEquals{0}, |
| Clear{}, |
| }; |
| DATA_DRIVEN_TEST(kClear, 7); |
| DATA_DRIVEN_TEST(kClear, 100); |
| |
| constexpr TestStep kTryPushMaxSize5[] = { |
| TryPush{"", true}, |
| TryPush{"", true}, |
| TryPush{"", true}, |
| TryPush{"", true}, |
| TryPush{"", true}, |
| TryPush{"", true}, // max_size_bytes() of 5 => up to 6 empty entries |
| TryPush{"", false}, |
| TryPush{"1", false}, |
| Clear{}, |
| TryPush{"12345", true}, |
| TryPush{"", false}, |
| }; |
| DATA_DRIVEN_TEST(kTryPushMaxSize5, 5); |
| |
| constexpr TestStep kPushPopLargeEntry[] = { |
| Push{kBigEntry<196>}, |
| TryPush{kBigEntry<196>, false}, |
| Pop{}, |
| Push{kBigEntry<196>}, |
| TryPush{"", true}, |
| Pop{}, |
| TryPush{"1", true}, |
| TryPush{kBigEntry<196>, true}, |
| TryPush{"12", true}, |
| Pop{}, |
| Pop{}, |
| Pop{}, |
| TryPush{kBigEntry<196>, true}, |
| TryPush{kBigEntry<196>, false}, |
| }; |
| DATA_DRIVEN_TEST(kPushPopLargeEntry, 255); |
| DATA_DRIVEN_TEST(kPushPopLargeEntry, 256); |
| DATA_DRIVEN_TEST(kPushPopLargeEntry, 257); |
| |
| TEST(InlineVarLenEntryQueue, DeclareMacro) { |
| PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE(queue, 123); |
| |
| constexpr size_t kArraySizeBytes = |
| 123 + 1 /*prefix*/ + 1 /* end */ + 3 /* round up */ + |
| PW_VARIABLE_LENGTH_ENTRY_QUEUE_HEADER_SIZE_UINT32 * 4; |
| static_assert(sizeof(queue) == kArraySizeBytes); |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_RawStorageSizeBytes(queue), |
| kArraySizeBytes - 3 /* padding isn't included */); |
| |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_MaxSizeBytes(queue), 123u); |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_SizeBytes(queue), 0u); |
| EXPECT_TRUE(pw_InlineVarLenEntryQueue_Empty(queue)); |
| } |
| |
| TEST(InlineVarLenEntryQueue, InitializeExistingBuffer) { |
| constexpr size_t kArraySize = |
| 10 + PW_VARIABLE_LENGTH_ENTRY_QUEUE_HEADER_SIZE_UINT32; |
| uint32_t queue[kArraySize]; |
| pw_InlineVarLenEntryQueue_Init(queue, kArraySize); |
| |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_RawStorageSizeBytes(queue), |
| sizeof(queue)); |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_MaxSizeBytes(queue), |
| sizeof(uint32_t) * 10u - 1 /*prefix*/ - 1 /*end*/); |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_SizeBytes(queue), 0u); |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_Size(queue), 0u); |
| EXPECT_TRUE(pw_InlineVarLenEntryQueue_Empty(queue)); |
| } |
| |
| TEST(InlineVarLenEntryQueue, MaxSizeElement) { |
| // Test max size elements for a few sizes. Commented out statements fail an |
| // assert because the elements are too large. |
| PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE(q16, 126); |
| PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE(q17, 127); |
| PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE(q18, 128); |
| PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE(q19, 129); |
| |
| pw_InlineVarLenEntryQueue_PushOverwrite(q16, kBigEntryBytes, 126); |
| pw_InlineVarLenEntryQueue_PushOverwrite(q17, kBigEntryBytes, 126); |
| pw_InlineVarLenEntryQueue_PushOverwrite(q18, kBigEntryBytes, 126); |
| pw_InlineVarLenEntryQueue_PushOverwrite(q19, kBigEntryBytes, 126); |
| |
| // pw_InlineVarLenEntryQueue_PushOverwrite(q16, kBigEntryBytes, 127); |
| pw_InlineVarLenEntryQueue_PushOverwrite(q17, kBigEntryBytes, 127); |
| pw_InlineVarLenEntryQueue_PushOverwrite(q18, kBigEntryBytes, 127); |
| pw_InlineVarLenEntryQueue_PushOverwrite(q19, kBigEntryBytes, 127); |
| |
| // pw_InlineVarLenEntryQueue_PushOverwrite(q16, kBigEntryBytes, 128); |
| // pw_InlineVarLenEntryQueue_PushOverwrite(q17, kBigEntryBytes, 128); |
| pw_InlineVarLenEntryQueue_PushOverwrite(q18, kBigEntryBytes, 128); |
| pw_InlineVarLenEntryQueue_PushOverwrite(q19, kBigEntryBytes, 128); |
| |
| // pw_InlineVarLenEntryQueue_PushOverwrite(q16, kBigEntryBytes, 129); |
| // pw_InlineVarLenEntryQueue_PushOverwrite(q17, kBigEntryBytes, 129); |
| // pw_InlineVarLenEntryQueue_PushOverwrite(q18, kBigEntryBytes, 129); |
| pw_InlineVarLenEntryQueue_PushOverwrite(q19, kBigEntryBytes, 129); |
| |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_Size(q16), 1u); |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_Size(q17), 1u); |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_Size(q18), 1u); |
| EXPECT_EQ(pw_InlineVarLenEntryQueue_Size(q19), 1u); |
| } |
| |
| TEST(InlineVarLenEntryQueueClass, InitializeExistingBuffer) { |
| constexpr size_t kArraySize = |
| 10 + PW_VARIABLE_LENGTH_ENTRY_QUEUE_HEADER_SIZE_UINT32; |
| uint32_t queue_array[kArraySize]{50, 50, 99}; |
| pw::InlineVarLenEntryQueue<>& queue = |
| pw::InlineVarLenEntryQueue<>::Init(queue_array, kArraySize); |
| |
| EXPECT_EQ(queue.raw_storage().data(), |
| reinterpret_cast<const std::byte*>(queue_array)); |
| EXPECT_EQ(queue.raw_storage().size_bytes(), sizeof(queue_array)); |
| EXPECT_EQ(queue.max_size_bytes(), |
| sizeof(uint32_t) * 10u - 1 /*prefix*/ - 1 /*end*/); |
| EXPECT_EQ(queue.size_bytes(), 0u); |
| EXPECT_EQ(queue.size(), 0u); |
| EXPECT_TRUE(queue.empty()); |
| } |
| |
| TEST(InlineVarLenEntryQueueClass, Construct_Constexpr) { |
| constexpr pw::InlineVarLenEntryQueue<127> queue(pw::kConstexpr); |
| EXPECT_TRUE(queue.empty()); |
| EXPECT_EQ(queue.max_size(), 128u); |
| EXPECT_EQ(queue.size(), 0u); |
| } |
| |
| // Include tests of GenericVarLenEntryQueue, using a BasicInlineVarLenEntryQueue |
| // factory. |
| class BasicInlineVarLenEntryQueueTest |
| : public pw::containers::test::GenericVarLenEntryQueueTest< |
| BasicInlineVarLenEntryQueueTest> { |
| public: |
| template <typename T, size_t kMaxSizeBytes> |
| pw::BasicInlineVarLenEntryQueue<T, kMaxSizeBytes> MakeQueue() { |
| return pw::BasicInlineVarLenEntryQueue<T, kMaxSizeBytes>(); |
| } |
| }; |
| PW_GENERIC_VAR_LEN_ENTRY_QUEUE_TESTS(BasicInlineVarLenEntryQueueTest); |
| |
| } // namespace |