From e589acdd101d97813641996ef9a74f8c46dd34de Mon Sep 17 00:00:00 2001 From: Wyatt Hepler Date: Mon, 26 Feb 2024 22:02:46 +0000 Subject: [PATCH] pw_containers: VariableLengthEntryQueue C++ API The C++ API is similar to pw::InlineQueue, but uses the C implementation. Change-Id: Ief89ee808556305e75dccd0c90723177a14744b2 Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/169910 Pigweed-Auto-Submit: Wyatt Hepler Reviewed-by: Armando Montanez Commit-Queue: Auto-Submit --- pw_containers/BUILD.bazel | 1 + pw_containers/CMakeLists.txt | 1 + pw_containers/docs.rst | 100 +++++ .../variable_length_entry_queue.h | 395 ++++++++++++++++-- .../variable_length_entry_queue_test.cc | 159 ++++++- 5 files changed, 597 insertions(+), 59 deletions(-) diff --git a/pw_containers/BUILD.bazel b/pw_containers/BUILD.bazel index 4b57a7c542..f55c2fe82d 100644 --- a/pw_containers/BUILD.bazel +++ b/pw_containers/BUILD.bazel @@ -95,6 +95,7 @@ cc_library( hdrs = ["public/pw_containers/variable_length_entry_queue.h"], includes = ["public"], deps = [ + ":raw_storage", "//pw_assert", "//pw_varint", ], diff --git a/pw_containers/CMakeLists.txt b/pw_containers/CMakeLists.txt index a27f2f3d59..f97be207cc 100644 --- a/pw_containers/CMakeLists.txt +++ b/pw_containers/CMakeLists.txt @@ -106,6 +106,7 @@ pw_add_library(pw_containers.variable_length_entry_queue STATIC PUBLIC_INCLUDES public PUBLIC_DEPS + pw_containers._raw_storage pw_varint PRIVATE_DEPS pw_assert diff --git a/pw_containers/docs.rst b/pw_containers/docs.rst index 7eb0a7ac20..cb6b367b99 100644 --- a/pw_containers/docs.rst +++ b/pw_containers/docs.rst @@ -41,8 +41,108 @@ pw::VariableLengthEntryQueue .. doxygenfile:: pw_containers/variable_length_entry_queue.h :sections: detaileddescription +Example +======= +.. tab-set:: + + .. tab-item:: C++ + :sync: c++ + + Queues are declared with their max size + (``VariableLengthEntryQueue``) but may be used without + specifying the size (``VariableLengthEntryQueue<>&``). + + .. code-block:: c++ + + // Declare a queue with capacity sufficient for one 10-byte entry or + // multiple smaller entries. + pw::VariableLengthEntryQueue<10> queue; + + // Push an entry, asserting if the entry does not fit. + queue.push(queue, data) + + // Use push_overwrite() to push entries, overwriting older entries + // as needed. + queue.push_overwrite(queue, more_data) + + // Remove an entry. + queue.pop(); + + Alternately, a ``VariableLengthEntryQueue`` may be initialized in an + existing ``uint32_t`` array. + + .. code-block:: c++ + + // Initialize a VariableLengthEntryQueue. + uint32_t buffer[32]; + auto& queue = pw::VariableLengthEntryQueue<>::Init(buffer); + + // Largest supported entry is 114 B (13 B overhead + 1 B prefix) + assert(queue.max_size_bytes() == 114u); + + // Write data + queue.push_overwrite(data); + + .. tab-item:: C + :sync: c + + A ``VariableLengthEntryQueue`` may be declared and initialized in C with + the :c:macro:`PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE` macro. + + .. code-block:: c + + // Declare a queue with capacity sufficient for one 10-byte entry or + // multiple smaller entries. + PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE(queue, 10); + + // Push an entry, asserting if the entry does not fit. + pw_VariableLengthEntryQueue_Push(queue, "12345", 5); + + // Use push_overwrite() to push entries, overwriting older entries + // as needed. + pw_VariableLengthEntryQueue_PushOverwrite(queue, "abcdefg", 7); + + // Remove an entry. + pw_VariableLengthEntryQueue_Pop(queue); + + Alternately, a ``VariableLengthEntryQueue`` may be initialized in an + existing ``uint32_t`` array. + + .. code-block:: c + + // Initialize a VariableLengthEntryQueue. + uint32_t buffer[32]; + pw_VariableLengthEntryQueue_Init(buffer, 32); + + // Largest supported entry is 114 B (13 B overhead + 1 B prefix) + assert(pw_VariableLengthEntryQueue_MaxSizeBytes(buffer) == 114u); + + // Write some data + pw_VariableLengthEntryQueue_PushOverwrite(buffer, "123", 3); + +Queue vs. deque +=============== +This module provides :cpp:type:`VariableLengthEntryQueue`, but no corresponding +``VariableLengthEntryDeque`` class. Following the C++ Standard Library style, +the deque class would provide ``push_front()`` and ``pop_back()`` operations in +addition to ``push_back()`` and ``pop_front()`` (equivalent to a queue's +``push()`` and ``pop()``). + +There is no ``VariableLengthEntryDeque`` class because there is no efficient way +to implement ``push_front()`` and ``pop_back()``. These operations would +necessarily be O(n), since each entry knows the position of the next entry, but +not the previous, as in a single-linked list. Given that these operations would +be inefficient and unlikely to be used, they are not implemented, and only a +queue class is provided. + API Reference =============== +C++ +--- +.. doxygengroup:: variable_length_entry_queue_cpp_api + :content-only: + :members: + C - .. doxygengroup:: variable_length_entry_queue_c_api diff --git a/pw_containers/public/pw_containers/variable_length_entry_queue.h b/pw_containers/public/pw_containers/variable_length_entry_queue.h index 6752415d4a..1c3153d7fb 100644 --- a/pw_containers/public/pw_containers/variable_length_entry_queue.h +++ b/pw_containers/public/pw_containers/variable_length_entry_queue.h @@ -22,8 +22,8 @@ /// @file pw_containers/variable_length_entry_queue.h /// -/// A `VariableLengthEntryQueue` is a ended queue of inline variable-length -/// binary entries. It is implemented as a ring (circular) buffer and supports +/// A `VariableLengthEntryQueue` is a queue of inline variable-length binary +/// entries. It is implemented as a ring (circular) buffer and supports /// operations to append entries and overwrite if necessary. Entries may be zero /// bytes up to the maximum size supported by the queue. /// @@ -41,46 +41,13 @@ /// - A simple ring buffer of variable-length entries is needed. Advanced /// features like multiple readers and a user-defined preamble are not /// required. -/// - C support is required. /// - A consistent, parsable, in-memory representation is required (e.g. to /// decode the buffer from a block of memory). +/// - C support is required. /// -/// A `VariableLengthEntryQueue` may be declared and initialized in C with the -/// @c_macro{PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE} macro. -/// -/// @code{c} -/// -/// // Declares a queue with a maximum single entry size of 10 bytes. -/// PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE(queue, 10); -/// -/// // Write some data -/// pw_VariableLengthEntryQueue_PushOverwrite(queue, "123", 3); -/// pw_VariableLengthEntryQueue_PushOverwrite(queue, "456", 3); -/// -/// assert(pw_VariableLengthEntryQueue_Size(queue) == 2u); -/// -/// // Remove the entries -/// pw_VariableLengthEntryQueue_Pop(queue); -/// pw_VariableLengthEntryQueue_Pop(queue); -/// -/// @endcode -/// -/// Alternately, a `VariableLengthEntryQueue` may also be initialized in an -/// existing ``uint32_t`` array. -/// -/// @code{c} -/// -/// // Initialize a VariableLengthEntryQueue. -/// uint32_t buffer[32]; -/// pw_VariableLengthEntryQueue_Init(buffer, 32); -/// -/// // Largest supported entry works out to 114 B (13 B overhead + 1 B prefix) -/// assert(pw_VariableLengthEntryQueue_MaxEntrySizeBytes(buffer) == 114u); -/// -/// // Write some data -/// pw_VariableLengthEntryQueue_PushOverwrite(buffer, "123", 3); -/// -/// @endcode +/// `VariableLengthEntryQueue` is implemented in C and provides complete C and +/// C++ APIs. The `VariableLengthEntryQueue` C++ class is structured similarly +/// to `pw::InlineQueue` and `pw::Vector`. #ifdef __cplusplus extern "C" { @@ -327,4 +294,354 @@ static inline bool pw_VariableLengthEntryQueue_Empty( #ifdef __cplusplus } // extern "C" + +#include +#include +#include +#include + +#include "pw_containers/internal/raw_storage.h" +#include "pw_span/span.h" + +namespace pw { + +// A`BasicVariableLengthEntryQueue` with a known maximum size of a single entry. +// The member functions are immplemented in the generic-capacity base. +// TODO: b/303056683 - Add helper for calculating kMaxSizeBytes for N entries of +// a particular size. +template +class BasicVariableLengthEntryQueue : public BasicVariableLengthEntryQueue< + T, + containers::internal::kGenericSized> { + private: + using Base = + BasicVariableLengthEntryQueue; + + public: + constexpr BasicVariableLengthEntryQueue() : Base(kMaxSizeBytes) {} + + // `BasicVariableLengthEntryQueue` is trivially copyable. + BasicVariableLengthEntryQueue(const BasicVariableLengthEntryQueue&) = default; + BasicVariableLengthEntryQueue& operator=( + const BasicVariableLengthEntryQueue&) = default; + + private: + static_assert(kMaxSizeBytes <= + std::numeric_limits::max()); + + using Base::Init; // Disallow Init since the size template param is not used. + + uint32_t data_[_PW_VAR_QUEUE_DATA_SIZE_UINT32(kMaxSizeBytes)]; +}; + +/// @defgroup variable_length_entry_queue_cpp_api +/// @{ + +/// Variable-length entry queue class template for any byte type (e.g. +/// ``std::byte`` or ``uint8_t``). +/// +/// ``BasicVariableLengthEntryQueue`` instances are declared with their capacity +/// / max single entry size (``BasicVariableLengthEntryQueue``), but +/// may be referred to without the size +/// (``BasicVariableLengthEntryQueue&``). +template +class BasicVariableLengthEntryQueue { + public: + class Entry; + + using value_type = Entry; + using size_type = std::uint32_t; + using pointer = const value_type*; + using const_pointer = pointer; + using reference = const value_type&; + using const_reference = reference; + + // Refers to an entry in-place in the queue. Entries may not be contiguous. + class iterator; + + // Currently, iterators provide read-only access. + // TODO: b/303046109 - Provide a non-const iterator. + using const_iterator = iterator; + + /// @copydoc pw_VariableLengthEntryQueue_Init + template + static BasicVariableLengthEntryQueue& Init(uint32_t (&array)[kArraySize]) { + static_assert( + kArraySize > PW_VARIABLE_LENGTH_ENTRY_QUEUE_HEADER_SIZE_UINT32, + "VariableLengthEntryQueue must be backed by an array with more than " + "PW_VARIABLE_LENGTH_ENTRY_QUEUE_HEADER_SIZE_UINT32 (3) elements"); + return Init(array, kArraySize); + } + + /// @copydoc pw_VariableLengthEntryQueue_Init + static BasicVariableLengthEntryQueue& Init(uint32_t array[], + size_t array_size_uint32) { + pw_VariableLengthEntryQueue_Init(array, array_size_uint32); + return *std::launder( + reinterpret_cast(array)); + } + + /// Returns the first entry in the queue. + Entry front() const { return *begin(); } + + /// @copydoc pw_VariableLengthEntryQueue_Begin + const_iterator begin() const { + return const_iterator(pw_VariableLengthEntryQueue_Begin(array_)); + } + const_iterator cbegin() const { return begin(); } + + /// @copydoc pw_VariableLengthEntryQueue_End + const_iterator end() const { + return const_iterator(pw_VariableLengthEntryQueue_End(array_)); + } + const_iterator cend() const { return end(); } + + /// @copydoc pw_VariableLengthEntryQueue_Empty + [[nodiscard]] bool empty() const { + return pw_VariableLengthEntryQueue_Empty(array_); + } + + /// @copydoc pw_VariableLengthEntryQueue_Size + size_type size() const { return pw_VariableLengthEntryQueue_Size(array_); } + + /// @copydoc pw_VariableLengthEntryQueue_SizeBytes + size_type size_bytes() const { + return pw_VariableLengthEntryQueue_SizeBytes(array_); + } + + /// @copydoc pw_VariableLengthEntryQueue_MaxSizeBytes + size_type max_size_bytes() const { + return pw_VariableLengthEntryQueue_MaxSizeBytes(array_); + } + + /// Underlying storage of the variable-length entry queue. May be used to + /// memcpy the queue. + span raw_storage() const { + return span( + reinterpret_cast(array_), + pw_VariableLengthEntryQueue_RawStorageSizeBytes(array_)); + } + + /// @copydoc pw_VariableLengthEntryQueue_Clear + void clear() { pw_VariableLengthEntryQueue_Clear(array_); } + + /// @copydoc pw_VariableLengthEntryQueue_Push + void push(span value) { + pw_VariableLengthEntryQueue_Push( + array_, value.data(), static_cast(value.size())); + } + + /// @copydoc pw_VariableLengthEntryQueue_PushOverwrite + void push_overwrite(span value) { + pw_VariableLengthEntryQueue_PushOverwrite( + array_, value.data(), static_cast(value.size())); + } + + /// @copydoc pw_VariableLengthEntryQueue_Pop + void pop() { pw_VariableLengthEntryQueue_Pop(array_); } + + protected: + constexpr BasicVariableLengthEntryQueue(uint32_t max_size_bytes) + : array_{_PW_VAR_QUEUE_DATA_SIZE_BYTES(max_size_bytes), 0, 0} {} + + BasicVariableLengthEntryQueue(const BasicVariableLengthEntryQueue&) = default; + BasicVariableLengthEntryQueue& operator=( + const BasicVariableLengthEntryQueue&) = default; + + private: + static_assert(std::is_integral_v || std::is_same_v); + static_assert(sizeof(T) == sizeof(std::byte)); + + uint32_t array_[PW_VARIABLE_LENGTH_ENTRY_QUEUE_HEADER_SIZE_UINT32]; +}; + +/// Refers to an entry in-place in the queue. Entries may be discontiguous. +template +class BasicVariableLengthEntryQueue::Entry { + public: + using value_type = T; + using size_type = std::uint32_t; + using pointer = const T*; + using const_pointer = pointer; + using reference = const T&; + using const_reference = reference; + + /// Iterator for the bytes in an Entry. Entries may be discontiguous, so a + /// pointer cannot serve as an iterator. + class iterator { + public: + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = const T*; + using reference = const T&; + using iterator_category = std::forward_iterator_tag; + + constexpr iterator() : entry_(nullptr), index_(0) {} + + constexpr iterator(const iterator&) = default; + constexpr iterator& operator=(const iterator&) = default; + + constexpr iterator& operator++() { + index_ += 1; + return *this; + } + constexpr iterator operator++(int) { + iterator previous_value(*this); + operator++(); + return previous_value; + } + + reference operator*() const { return *GetIndex(*entry_, index_); } + pointer operator->() const { return GetIndex(*entry_, index_); } + + bool operator==(const iterator& rhs) const { + return entry_->data_1 == rhs.entry_->data_1 && index_ == rhs.index_; + } + bool operator!=(const iterator& rhs) const { return !(*this == rhs); } + + private: + friend class Entry; + + constexpr iterator(const pw_VariableLengthEntryQueue_Entry& entry, + size_t index) + : entry_(&entry), index_(index) {} + + const pw_VariableLengthEntryQueue_Entry* entry_; + size_t index_; + }; + + // TODO: b/303046109 - Provide mutable access to Entry contents. + using const_iterator = iterator; + + constexpr Entry(const Entry&) = default; + constexpr Entry& operator=(const Entry&) = default; + + const_reference at(size_t index) const { + return *reinterpret_cast( + _pw_VariableLengthEntryQueue_Entry_GetPointerChecked(&entry_, index)); + } + + const_reference operator[](size_t index) const { + return *GetIndex(entry_, index); + } + + const_reference front() const { return *entry_.data_1; } + const_reference back() const { *GetIndex(entry_, size() - 1); } + + /// Entries may be stored in up to two segments, so this returns spans + /// refering to both portions of the entry. If the entry is contiguous, the + /// second span is empty. + std::pair, span> contiguous_data() + const { + return std::make_pair( + span(reinterpret_cast(entry_.data_1), entry_.size_1), + span(reinterpret_cast(entry_.data_2), entry_.size_2)); + } + + /// @copydoc pw_VariableLengthEntryQueue_Entry_Copy + /// + /// Copying with `copy()` is likely more efficient than an iterator-based copy + /// with `std::copy()`, since `copy()` uses one or two `memcpy` calls instead + /// of copying byte-by-byte. + size_type copy(T* dest, size_type count) const { + return pw_VariableLengthEntryQueue_Entry_Copy(&entry_, dest, count); + } + + const_iterator begin() const { return const_iterator(entry_, 0); } + const_iterator cbegin() const { return begin(); } + + const_iterator end() const { return const_iterator(entry_, size()); } + const_iterator cend() const { return cend(); } + + [[nodiscard]] bool empty() const { return size() == 0; } + + size_type size() const { return entry_.size_1 + entry_.size_2; } + + private: + friend class BasicVariableLengthEntryQueue; + + static const T* GetIndex(const pw_VariableLengthEntryQueue_Entry& entry, + size_t index) { + return reinterpret_cast( + _pw_VariableLengthEntryQueue_Entry_GetPointer(&entry, index)); + } + + explicit constexpr Entry(const pw_VariableLengthEntryQueue_Entry& entry) + : entry_(entry) {} + + constexpr Entry() : entry_{} {} + + pw_VariableLengthEntryQueue_Entry entry_; +}; + +/// Iterator object for a `VariableLengthEntryQueue`. +/// +/// Iterators are invalidated by any operations that change the container or +/// its underlying data (push/pop/init). +template +class BasicVariableLengthEntryQueue::iterator { + public: + using difference_type = std::ptrdiff_t; + using value_type = Entry; + using pointer = const Entry*; + using reference = const Entry&; + using iterator_category = std::forward_iterator_tag; + + constexpr iterator() : iterator_{}, entry_{} {} + + constexpr iterator(const iterator&) = default; + constexpr iterator& operator=(const iterator&) = default; + + iterator& operator++() { + pw_VariableLengthEntryQueue_Iterator_Advance(&iterator_); + entry_.entry_.data_1 = nullptr; // mark the entry as unloaded + return *this; + } + iterator operator++(int) { + iterator previous_value(*this); + operator++(); + return previous_value; + } + + reference operator*() const { + LoadEntry(); + return entry_; + } + pointer operator->() const { + LoadEntry(); + return &entry_; + } + + bool operator==(const iterator& rhs) const { + return pw_VariableLengthEntryQueue_Iterator_Equal(&iterator_, + &rhs.iterator_); + } + bool operator!=(const iterator& rhs) const { return !(*this == rhs); } + + private: + friend class BasicVariableLengthEntryQueue; + + explicit constexpr iterator(const pw_VariableLengthEntryQueue_Iterator& it) + : iterator_(it) {} + + void LoadEntry() const { + if (entry_.entry_.data_1 == nullptr) { + entry_.entry_ = pw_VariableLengthEntryQueue_GetEntry(&iterator_); + } + } + + pw_VariableLengthEntryQueue_Iterator iterator_; + mutable Entry entry_; +}; + +/// Variable-length entry queue that uses ``std::byte`` for the byte type. +template +using VariableLengthEntryQueue = + BasicVariableLengthEntryQueue; + +/// @} + +} // namespace pw + #endif // __cplusplus diff --git a/pw_containers/variable_length_entry_queue_test.cc b/pw_containers/variable_length_entry_queue_test.cc index c1a2873d16..3fdbc34f0d 100644 --- a/pw_containers/variable_length_entry_queue_test.cc +++ b/pw_containers/variable_length_entry_queue_test.cc @@ -48,61 +48,89 @@ std::vector ReadEntry( return value; } -#define ASSERT_CONTENTS_EQ(oracle, queue) \ - auto oracle_it = oracle.begin(); \ - auto queue_it = pw_VariableLengthEntryQueue_Begin(queue); \ - const auto queue_end = pw_VariableLengthEntryQueue_End(queue); \ - uint32_t entries_compared = 0; \ - while (oracle_it != oracle.end() && \ - !pw_VariableLengthEntryQueue_Iterator_Equal(&queue_it, &queue_end)) { \ - ASSERT_EQ(*oracle_it++, ReadEntry(queue_it)); \ - pw_VariableLengthEntryQueue_Iterator_Advance(&queue_it); \ - entries_compared += 1; \ - } \ - ASSERT_EQ(entries_compared, oracle.size()) - -// Declares a test that performs a series of operations on a -// VariableLengthEntryQueue and the "oracle" class, and checks that they match -// after every step. +// Declares a test that performs a series of operations on the C and C++ +// versions of VariableLengthEntryQueue and the "oracle" class, and checks that +// they match after every step. #define DATA_DRIVEN_TEST(program, max_entry_size) \ TEST(VariableLengthEntryQueue, \ DataDrivenTest_##program##_MaxSizeBytes##max_entry_size) { \ - pw::containers::VariableLengthEntryQueueTestOracle oracle(max_entry_size); \ + pw::VariableLengthEntryQueue cpp_queue; \ PW_VARIABLE_LENGTH_ENTRY_QUEUE_DECLARE(c_queue, max_entry_size); \ + pw::containers::VariableLengthEntryQueueTestOracle oracle(max_entry_size); \ + \ + /* Check the queue sizes */ \ + static_assert(sizeof(cpp_queue) == sizeof(c_queue)); \ + ASSERT_EQ(cpp_queue.raw_storage().data(), \ + reinterpret_cast(&cpp_queue)); \ + ASSERT_EQ(cpp_queue.raw_storage().size_bytes(), \ + pw_VariableLengthEntryQueue_RawStorageSizeBytes(c_queue)); \ \ for (const TestStep& step : program) { \ /* Take the action */ \ if (auto ow = std::get_if(&step); ow != nullptr) { \ + cpp_queue.push_overwrite(pw::as_bytes(pw::span(ow->data))); \ pw_VariableLengthEntryQueue_PushOverwrite( \ c_queue, ow->data.data(), static_cast(ow->data.size())); \ oracle.push_overwrite(pw::as_bytes(pw::span(ow->data))); \ } else if (auto push = std::get_if(&step); push != nullptr) { \ + cpp_queue.push(pw::as_bytes(pw::span(push->data))); \ pw_VariableLengthEntryQueue_Push( \ c_queue, \ push->data.data(), \ static_cast(push->data.size())); \ oracle.push(pw::as_bytes(pw::span(push->data))); \ } else if (std::holds_alternative(step)) { \ + cpp_queue.pop(); \ pw_VariableLengthEntryQueue_Pop(c_queue); \ oracle.pop(); \ } else if (auto size = std::get_if(&step); \ size != nullptr) { \ - size_t actual = pw_VariableLengthEntryQueue_Size(c_queue); \ + const size_t actual = cpp_queue.size(); \ + ASSERT_EQ(actual, pw_VariableLengthEntryQueue_Size(c_queue)); \ ASSERT_EQ(oracle.size(), actual); \ ASSERT_EQ(size->expected, actual); \ } else if (std::holds_alternative(step)) { \ + cpp_queue.clear(); \ pw_VariableLengthEntryQueue_Clear(c_queue); \ oracle.clear(); \ } else { \ FAIL() << "Unhandled case"; \ } \ - /* Check size and other functions */ \ + /* 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_VariableLengthEntryQueue_Size(c_queue), oracle.size()); \ ASSERT_EQ(pw_VariableLengthEntryQueue_SizeBytes(c_queue), \ oracle.size_bytes()); \ ASSERT_EQ(pw_VariableLengthEntryQueue_MaxSizeBytes(c_queue), \ oracle.max_size_bytes()); \ - ASSERT_CONTENTS_EQ(oracle, c_queue); \ + \ + /* Compare the contents */ \ + auto oracle_it = oracle.begin(); \ + auto c_queue_it = pw_VariableLengthEntryQueue_Begin(c_queue); \ + const auto c_queue_end = pw_VariableLengthEntryQueue_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(entry.begin(), entry.end())); \ + \ + ASSERT_NE(oracle_it, oracle.end()); \ + ASSERT_FALSE(pw_VariableLengthEntryQueue_Iterator_Equal( \ + &c_queue_it, &c_queue_end)); \ + \ + ++oracle_it; \ + pw_VariableLengthEntryQueue_Iterator_Advance(&c_queue_it); \ + } \ + ASSERT_EQ(entries_compared, oracle.size()); \ + ASSERT_TRUE(pw_VariableLengthEntryQueue_Iterator_Equal(&c_queue_it, \ + &c_queue_end)); \ + ASSERT_EQ(oracle_it, oracle.end()); \ } \ } \ static_assert(true, "use a semicolon") @@ -268,4 +296,95 @@ TEST(VariableLengthEntryQueue, MaxSizeElement) { EXPECT_EQ(pw_VariableLengthEntryQueue_Size(q19), 1u); } +constexpr const char* kStrings[] = {"Haart", "Sandro", "", "Gelu", "Solmyr"}; + +TEST(VariableLengthEntryQueueClass, Iterate) { + pw::BasicVariableLengthEntryQueue queue; + + for (const char* string : kStrings) { + queue.push(std::string_view(string)); + } + + uint32_t i = 0; + for (auto entry : queue) { + char value[8]{}; + entry.copy(value, sizeof(value)); + EXPECT_STREQ(value, kStrings[i++]); + } + ASSERT_EQ(i, 5u); +} + +TEST(VariableLengthEntryQueueClass, IterateOverwrittenElements) { + pw::BasicVariableLengthEntryQueue queue; + + for (const char* string : kStrings) { + queue.push_overwrite(std::string_view(string)); + } + + ASSERT_EQ(queue.size(), 1u); + + for (auto entry : queue) { + char value[8]{}; + EXPECT_EQ(6u, entry.copy(value, sizeof(value))); + EXPECT_STREQ(value, "Solmyr"); + } +} + +TEST(VariableLengthEntryQueueClass, InitializeExistingBuffer) { + constexpr size_t kArraySize = + 10 + PW_VARIABLE_LENGTH_ENTRY_QUEUE_HEADER_SIZE_UINT32; + uint32_t queue_array[kArraySize]{50, 50, 99}; + pw::VariableLengthEntryQueue<>& queue = + pw::VariableLengthEntryQueue<>::Init(queue_array, kArraySize); + + EXPECT_EQ(queue.raw_storage().data(), + reinterpret_cast(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(VariableLengthEntryQueueClass, Entry) { + pw::BasicVariableLengthEntryQueue queue; + queue.push("12"); // Split the next entry across the end. + queue.push_overwrite(std::string_view("ABCDE")); + + decltype(queue)::Entry front = queue.front(); + + ASSERT_EQ(front.size(), 5u); + EXPECT_EQ(front[0], 'A'); + EXPECT_EQ(front[1], 'B'); + EXPECT_EQ(front[2], 'C'); + EXPECT_EQ(front[3], 'D'); + EXPECT_EQ(front[4], 'E'); + + EXPECT_EQ(front.at(0), 'A'); + EXPECT_EQ(front.at(1), 'B'); + EXPECT_EQ(front.at(2), 'C'); + EXPECT_EQ(front.at(3), 'D'); + EXPECT_EQ(front.at(4), 'E'); + + const auto [span_1, span_2] = front.contiguous_data(); + EXPECT_EQ(span_1.size(), 2u); + EXPECT_EQ(std::memcmp(span_1.data(), "AB", 2u), 0); + EXPECT_EQ(span_2.size(), 3u); + EXPECT_EQ(std::memcmp(span_2.data(), "CDE", 3u), 0); + + const char* expected_ptr = "ABCDE"; + for (char c : front) { + EXPECT_EQ(*expected_ptr, c); + ++expected_ptr; + } + + // Check the iterators with std::copy and std::equal. + char value[6] = {}; + std::copy(front.begin(), front.end(), value); + EXPECT_STREQ(value, "ABCDE"); + + EXPECT_TRUE(std::equal(front.begin(), front.end(), "ABCDE")); +} + } // namespace