diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 15a75fc1a..654b3d8b3 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -394,6 +394,7 @@ openthread_core_files = [ "common/heap.cpp", "common/heap.hpp", "common/heap_allocatable.hpp", + "common/heap_array.hpp", "common/heap_data.cpp", "common/heap_data.hpp", "common/heap_string.cpp", diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 60d577538..c6cda192e 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -435,6 +435,7 @@ HEADERS_COMMON = \ common/extension.hpp \ common/heap.hpp \ common/heap_allocatable.hpp \ + common/heap_array.hpp \ common/heap_data.hpp \ common/heap_string.hpp \ common/instance.hpp \ diff --git a/src/core/common/heap_array.hpp b/src/core/common/heap_array.hpp new file mode 100644 index 000000000..e65e23a34 --- /dev/null +++ b/src/core/common/heap_array.hpp @@ -0,0 +1,551 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for `Heap::Array` (a heap allocated array of flexible length). + */ + +#ifndef HEAP_ARRAY_HPP_ +#define HEAP_ARRAY_HPP_ + +#include "openthread-core-config.h" + +#include +#include + +#include "common/array.hpp" +#include "common/code_utils.hpp" +#include "common/error.hpp" +#include "common/heap.hpp" +#include "common/new.hpp" + +namespace ot { +namespace Heap { + +/** + * This class represents a heap allocated array. + * + * The buffer to store the elements is allocated from heap and is managed by the `Heap::Array` class itself. The `Array` + * implementation will automatically grow the buffer when new entries are added. The `Heap::Array` destructor will + * always free the allocated buffer. + * + * The `Type` class MUST provide a move constructor `Type(Type &&aOther)` (or a copy constructor if no move constructor + * is provided). This constructor is used to move existing elements when array buffer is grown (new buffer is + * allocated) to make room for new elements. + * + * @tparam Type The array element type. + * @tparam kCapacityIncrements Number of elements to allocate at a time when updating the array buffer. + * + */ +template class Array +{ +public: + using IndexType = uint16_t; + + /** + * This constructor initializes the `Array` as empty. + * + */ + Array(void) + : mArray(nullptr) + , mLength(0) + , mCapacity(0) + { + } + + /** + * This is the destructor for `Array` object. + * + */ + ~Array(void) { Free(); } + + /** + * This method frees any buffer allocated by the `Array`. + * + * The `Array` destructor will automatically call `Free()`. This method allows caller to free buffer explicitly. + * + */ + void Free(void) + { + Clear(); + Heap::Free(mArray); + mArray = nullptr; + mCapacity = 0; + } + + /** + * This method clears the array. + * + * Note that `Clear()` method (unlike `Free()`) does not free the allocated buffer and therefore does not change + * the current capacity of the array. + * + * This method invokes `Type` destructor on all cleared existing elements of array. + * + */ + void Clear(void) + { + for (Type &entry : *this) + { + entry.~Type(); + } + + mLength = 0; + } + + /** + * This method returns the current array length (number of elements in the array). + * + * @returns The array length. + * + */ + IndexType GetLength(void) const { return mLength; } + + /** + * This method returns a raw pointer to the array buffer. + * + * The returned raw pointer is valid only while the `Array` remains unchanged. + * + * @returns A pointer to the array buffer or `nullptr` if the array is empty. + * + */ + const Type *AsCArray(void) const { return (mLength != 0) ? mArray : nullptr; } + + /** + * This method returns the current capacity of array (number of elements that can fit in current allocated buffer). + * + * The allocated buffer and array capacity are automatically increased (by the `Array` itself) when new elements + * are added to array. Removing elements does not change the buffer and the capacity. A desired capacity can be + * reserved using `ReserveCapacity()` method. + * + * @returns The current capacity of the array. + * + */ + IndexType GetCapacity(void) const { return mCapacity; } + + /** + * This method allocates buffer to reserve a given capacity for array. + * + * If the requested @p aCapacity is smaller than the current length of the array, capacity remains unchanged. + * + * @param[in] aCapacity The target capacity for the array. + * + * @retval kErrorNone Array was successfully updated to support @p aCapacity. + * @retval kErrorNoBufs Could not allocate buffer. + * + */ + Error ReserveCapacity(IndexType aCapacity) { return Allocate(aCapacity); } + + /** + * This method sets the array by taking the buffer from another given array (using move semantics). + * + * @param[in] aOther The other `Heap::Array` to take from (rvalue reference). + * + */ + void TakeFrom(Array &&aOther) + { + Free(); + mArray = aOther.mArray; + mLength = aOther.mLength; + mCapacity = aOther.mCapacity; + aOther.mArray = nullptr; + aOther.mLength = 0; + aOther.mCapacity = 0; + } + + /** + * This method overloads the `[]` operator to get the element at a given index. + * + * This method does not perform index bounds checking. Behavior is undefined if @p aIndex is not valid. + * + * @param[in] aIndex The index to get. + * + * @returns A reference to the element in array at @p aIndex. + * + */ + Type &operator[](IndexType aIndex) { return mArray[aIndex]; } + + /** + * This method overloads the `[]` operator to get the element at a given index. + * + * This method does not perform index bounds checking. Behavior is undefined if @p aIndex is not valid. + * + * @param[in] aIndex The index to get. + * + * @returns A reference to the element in array at @p aIndex. + * + */ + const Type &operator[](IndexType aIndex) const { return mArray[aIndex]; } + + /** + * This method gets a pointer to the element at a given index. + * + * Unlike `operator[]`, this method checks @p aIndex to be valid and within the current length. The returned + * pointer is valid only while the `Array` remains unchanged. + * + * @param[in] aIndex The index to get. + * + * @returns A pointer to element in array at @p aIndex or `nullptr` if @p aIndex is not valid. + * + */ + Type *At(IndexType aIndex) { return (aIndex < mLength) ? &mArray[aIndex] : nullptr; } + + /** + * This method gets a pointer to the element at a given index. + * + * Unlike `operator[]`, this method checks @p aIndex to be valid and within the current length. The returned + * pointer is valid only while the `Array` remains unchanged. + * + * @param[in] aIndex The index to get. + * + * @returns A pointer to element in array at @p aIndex or `nullptr` if @p aIndex is not valid. + * + */ + const Type *At(IndexType aIndex) const { return (aIndex < mLength) ? &mArray[aIndex] : nullptr; } + + /** + * This method gets a pointer to the element at the front of the array (first element). + * + * The returned pointer is valid only while the `Array` remains unchanged. + * + * @returns A pointer to the front element or `nullptr` if array is empty. + * + */ + Type *Front(void) { return At(0); } + + /** + * This method gets a pointer to the element at the front of the array (first element). + * + * The returned pointer is valid only while the `Array` remains unchanged. + * + * @returns A pointer to the front element or `nullptr` if array is empty. + * + */ + const Type *Front(void) const { return At(0); } + + /** + * This method gets a pointer to the element at the back of the array (last element). + * + * The returned pointer is valid only while the `Array` remains unchanged. + * + * @returns A pointer to the back element or `nullptr` if array is empty. + * + */ + Type *Back(void) { return (mLength > 0) ? &mArray[mLength - 1] : nullptr; } + + /** + * This method gets a pointer to the element at the back of the array (last element). + * + * The returned pointer is valid only while the `Array` remains unchanged. + * + * @returns A pointer to the back element or `nullptr` if array is empty. + * + */ + const Type *Back(void) const { return (mLength > 0) ? &mArray[mLength - 1] : nullptr; } + + /** + * This method appends a new entry to the end of the array. + * + * This method requires the `Type` to provide a copy constructor of format `Type(const Type &aOther)` to init the + * new element in the array from @p aEntry. + * + * @param[in] aEntry The new entry to push back. + * + * @retval kErrorNone Successfully pushed back @p aEntry to the end of the array. + * @retval kErrorNoBufs Could not allocate buffer to grow the array. + * + */ + Error PushBack(const Type &aEntry) + { + Error error = kErrorNone; + + if (mLength == mCapacity) + { + SuccessOrExit(error = Allocate(mCapacity + kCapacityIncrements)); + } + + new (&mArray[mLength++]) Type(aEntry); + + exit: + return error; + } + + /** + * This method appends a new entry to the end of the array. + * + * This method requires the `Type` to provide a copy constructor of format `Type(Type &&aOther)` to init the + * new element in the array from @p aEntry. + * + * @param[in] aEntry The new entry to push back (an rvalue reference) + * + * @retval kErrorNone Successfully pushed back @p aEntry to the end of the array. + * @retval kErrorNoBufs Could not allocate buffer to grow the array. + * + */ + Error PushBack(Type &&aEntry) + { + Error error = kErrorNone; + + if (mLength == mCapacity) + { + SuccessOrExit(error = Allocate(mCapacity + kCapacityIncrements)); + } + + new (&mArray[mLength++]) Type(static_cast(aEntry)); + + exit: + return error; + } + + /** + * This method appends a new entry to the end of the array. + * + * On success, this method returns a pointer to the newly appended element in the array for the caller to + * initialize and use. This method uses the `Type(void)` default constructor on the newly appended element (if not + * `nullptr`). + * + * The returned pointer is valid only while the `Array` remains unchanged. + * + * @return A pointer to the newly appended element or `nullptr` if could not allocate buffer to grow the array + * + */ + Type *PushBack(void) + { + Type *newEntry = nullptr; + + if (mLength == mCapacity) + { + SuccessOrExit(Allocate(mCapacity + kCapacityIncrements)); + } + + newEntry = new (&mArray[mLength++]) Type(); + + exit: + return newEntry; + } + + /** + * This method removes the last element in the array. + * + * This method will invoke the `Type` destructor on the removed element. + * + * @returns A pointer to the removed element from the array, or `nullptr` if array is empty. + * + */ + void PopBack(void) + { + if (mLength > 0) + { + mArray[mLength - 1].~Type(); + mLength--; + } + } + + /** + * This method returns the index of an element in the array. + * + * The @p aElement MUST be from the array, otherwise the behavior of this method is undefined. + * + * @param[in] aElement A reference to an element in the array. + * + * @returns The index of @p aElement in the array. + * + */ + IndexType IndexOf(const Type &aElement) const { return static_cast(&aElement - mArray); } + + /** + * This method finds the first match of a given entry in the array. + * + * This method uses `==` operator on `Type` to compare the array element with @p aEntry. The returned pointer is + * valid only while the `Array` remains unchanged. + * + * @param[in] aEntry The entry to search for within the array. + * + * @returns A pointer to matched array element, or `nullptr` if a match could not be found. + * + */ + Type *Find(const Type &aEntry) { return AsNonConst(AsConst(this)->Find(aEntry)); } + + /** + * This method finds the first match of a given entry in the array. + * + * This method uses `==` operator to compare the array elements with @p aEntry. The returned pointer is valid only + * while the `Array` remains unchanged. + * + * @param[in] aEntry The entry to search for within the array. + * + * @returns A pointer to matched array element, or `nullptr` if a match could not be found. + * + */ + const Type *Find(const Type &aEntry) const + { + const Type *matched = nullptr; + + for (const Type &element : *this) + { + if (element == aEntry) + { + matched = &element; + break; + } + } + + return matched; + } + + /** + * This method indicates whether or not a match to given entry exists in the array. + * + * This method uses `==` operator on `Type` to compare the array elements with @p aEntry. + * + * @param[in] aEntry The entry to search for within the array. + * + * @retval TRUE The array contains a matching element with @p aEntry. + * @retval FALSE The array does not contain a matching element with @p aEntry. + * + */ + bool Contains(const Type &aEntry) const { return Find(aEntry) != nullptr; } + + /** + * This template method finds the first element in the array matching a given indicator. + * + * The template type `Indicator` specifies the type of @p aIndicator object which is used to match against elements + * in the array. To check that an element matches the given indicator, the `Matches()` method is invoked on each + * `Type` element in the array. The `Matches()` method should be provided by `Type` class accordingly: + * + * bool Type::Matches(const Indicator &aIndicator) const + * + * The returned pointer is valid only while the `Array` remains unchanged. + * + * @param[in] aIndicator An indicator to match with elements in the array. + * + * @returns A pointer to the matched array element, or `nullptr` if a match could not be found. + * + */ + template Type *FindMatching(const Indicator &aIndicator) + { + return AsNonConst(AsConst(this)->FindMatching(aIndicator)); + } + + /** + * This template method finds the first element in the array matching a given indicator. + * + * The template type `Indicator` specifies the type of @p aIndicator object which is used to match against elements + * in the array. To check that an element matches the given indicator, the `Matches()` method is invoked on each + * `Type` element in the array. The `Matches()` method should be provided by `Type` class accordingly: + * + * bool Type::Matches(const Indicator &aIndicator) const + * + * The returned pointer is valid only while the `Array` remains unchanged. + * + * @param[in] aIndicator An indicator to match with elements in the array. + * + * @returns A pointer to the matched array element, or `nullptr` if a match could not be found. + * + */ + template const Type *FindMatching(const Indicator &aIndicator) const + { + const Type *matched = nullptr; + + for (const Type &element : *this) + { + if (element.Matches(aIndicator)) + { + matched = &element; + break; + } + } + + return matched; + } + + /** + * This template method indicates whether or not the array contains an element matching a given indicator. + * + * The template type `Indicator` specifies the type of @p aIndicator object which is used to match against elements + * in the array. To check that an element matches the given indicator, the `Matches()` method is invoked on each + * `Type` element in the array. The `Matches()` method should be provided by `Type` class accordingly: + * + * bool Type::Matches(const Indicator &aIndicator) const + * + * @param[in] aIndicator An indicator to match with elements in the array. + * + * @retval TRUE The array contains a matching element with @p aIndicator. + * @retval FALSE The array does not contain a matching element with @p aIndicator. + * + */ + template bool ContainsMatching(const Indicator &aIndicator) const + { + return FindMatching(aIndicator) != nullptr; + } + + // The following methods are intended to support range-based `for` + // loop iteration over the array elements and should not be used + // directly. + + Type * begin(void) { return (mLength > 0) ? mArray : nullptr; } + Type * end(void) { return (mLength > 0) ? &mArray[mLength] : nullptr; } + const Type *begin(void) const { return (mLength > 0) ? mArray : nullptr; } + const Type *end(void) const { return (mLength > 0) ? &mArray[mLength] : nullptr; } + + Array(const Array &) = delete; + Array &operator=(const Array &) = delete; + +private: + Error Allocate(IndexType aCapacity) + { + Error error = kErrorNone; + Type *newArray; + + VerifyOrExit((aCapacity != mCapacity) && (aCapacity >= mLength)); + newArray = static_cast(Heap::CAlloc(aCapacity, sizeof(Type))); + VerifyOrExit(newArray != nullptr, error = kErrorNoBufs); + + for (IndexType index = 0; index < mLength; index++) + { + new (&newArray[index]) Type(static_cast(mArray[index])); + mArray[index].~Type(); + } + + Heap::Free(mArray); + mArray = newArray; + mCapacity = aCapacity; + + exit: + return error; + } + + Type * mArray; + IndexType mLength; + IndexType mCapacity; +}; + +} // namespace Heap +} // namespace ot + +#endif // HEAP_ARRAY_HPP_ diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 8db8fbf19..c0e457fd6 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -341,6 +341,27 @@ target_link_libraries(ot-test-heap add_test(NAME ot-test-heap COMMAND ot-test-heap) +add_executable(ot-test-heap-array + test_heap_array.cpp +) + +target_include_directories(ot-test-heap-array + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-heap-array + PRIVATE + ${COMMON_COMPILE_OPTIONS} +) + +target_link_libraries(ot-test-heap-array + PRIVATE + ${COMMON_LIBS} +) + +add_test(NAME ot-test-heap-array COMMAND ot-test-heap-array) + add_executable(ot-test-heap-string test_heap_string.cpp ) diff --git a/tests/unit/Makefile.am b/tests/unit/Makefile.am index ac7df2f03..62a611803 100644 --- a/tests/unit/Makefile.am +++ b/tests/unit/Makefile.am @@ -124,6 +124,7 @@ check_PROGRAMS += \ ot-test-ecdsa \ ot-test-flash \ ot-test-heap \ + ot-test-heap-array \ ot-test-heap-string \ ot-test-hkdf-sha256 \ ot-test-hmac-sha256 \ @@ -243,6 +244,10 @@ ot_test_heap_LDADD = $(COMMON_LDADD) ot_test_heap_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS) ot_test_heap_SOURCES = $(COMMON_SOURCES) test_heap.cpp +ot_test_heap_array_LDADD = $(COMMON_LDADD) +ot_test_heap_array_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS) +ot_test_heap_array_SOURCES = $(COMMON_SOURCES) test_heap_array.cpp + ot_test_heap_string_LDADD = $(COMMON_LDADD) ot_test_heap_string_LIBTOOLFLAGS = $(COMMON_LIBTOOLFLAGS) ot_test_heap_string_SOURCES = $(COMMON_SOURCES) test_heap_string.cpp diff --git a/tests/unit/test_heap_array.cpp b/tests/unit/test_heap_array.cpp new file mode 100644 index 000000000..2ed953570 --- /dev/null +++ b/tests/unit/test_heap_array.cpp @@ -0,0 +1,494 @@ +/* + * Copyright (c) 2022, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "test_platform.h" + +#include + +#include + +#include "test_util.hpp" + +#include "common/heap_array.hpp" +#include "common/type_traits.hpp" + +namespace ot { + +// Counters tracking number of times `Entry` constructor and +// destructor are invoked. These are used to verify that the `Array` +// properly calls constructor/destructor when allocating and copying +// array buffer. +static uint16_t sConstructorCalls = 0; +static uint16_t sDestructorCalls = 0; + +class Entry +{ +public: + Entry(void) + : mValue(0) + , mInitialized(true) + { + sConstructorCalls++; + } + + explicit Entry(uint16_t aValue) + : mValue(aValue) + , mInitialized(true) + { + sConstructorCalls++; + } + + Entry(const Entry &aEntry) + : mValue(aEntry.mValue) + , mInitialized(true) + { + sConstructorCalls++; + } + + ~Entry(void) { sDestructorCalls++; } + + uint16_t GetValue(void) const { return mValue; } + void SetValue(uint16_t aValue) { mValue = aValue; } + bool IsInitialized(void) const { return mInitialized; } + bool operator==(const Entry &aOther) const { return mValue == aOther.mValue; } + bool Matches(uint16_t aValue) const { return mValue == aValue; } + +private: + uint16_t mValue; + bool mInitialized; +}; + +template +void VerifyEntry(const EntryType &aEntry, const Heap::Array &aArray, int aExpectedValue) +{ + // Verify the entry in a given array with an expected value. + // Specializations of this template are defined below for `EntryType` + // being `uint16_t` or `Entry` class. + + OT_UNUSED_VARIABLE(aEntry); + OT_UNUSED_VARIABLE(aArray); + OT_UNUSED_VARIABLE(aExpectedValue); + + VerifyOrQuit(false, "Specializations of this template method MUST be used instead"); +} + +template <> void VerifyEntry(const uint16_t &aEntry, const Heap::Array &aArray, int aExpectedValue) +{ + OT_UNUSED_VARIABLE(aArray); + VerifyOrQuit(aEntry == static_cast(aExpectedValue)); +} + +template <> void VerifyEntry(const Entry &aEntry, const Heap::Array &aArray, int aExpectedValue) +{ + VerifyOrQuit(aEntry.IsInitialized()); + VerifyOrQuit(aEntry.GetValue() == static_cast(aExpectedValue)); + + VerifyOrQuit(aArray.ContainsMatching(aEntry.GetValue())); + VerifyOrQuit(aArray.FindMatching(aEntry.GetValue()) == &aEntry); +} + +template void VerifyArray(const Heap::Array &aArray, Args... aArgs) +{ + // Verify that array content matches the `aArgs` sequence + // (which can be empty). + + constexpr uint16_t kUnusedValue = 0xffff; + + int values[] = {aArgs..., 0}; + uint16_t index = 0; + + printf(" - Array (len:%u, capacity:%u) = { ", aArray.GetLength(), aArray.GetCapacity()); + + VerifyOrQuit(aArray.GetLength() == sizeof...(aArgs)); + + if (aArray.GetLength() == 0) + { + VerifyOrQuit(aArray.AsCArray() == nullptr); + VerifyOrQuit(aArray.Front() == nullptr); + VerifyOrQuit(aArray.Back() == nullptr); + } + else + { + VerifyOrQuit(aArray.AsCArray() != nullptr); + } + + for (const EntryType &entry : aArray) + { + VerifyOrQuit(index < aArray.GetLength()); + + VerifyEntry(entry, aArray, values[index]); + + VerifyOrQuit(aArray.Contains(entry)); + VerifyOrQuit(aArray.Find(entry) == &entry); + VerifyOrQuit(aArray.IndexOf(entry) == index); + + if (index == 0) + { + VerifyOrQuit(aArray.Front() == &entry); + } + + if (index == aArray.GetLength()) + { + VerifyOrQuit(aArray.Back() == &entry); + } + + printf("%u ", values[index]); + + index++; + } + + VerifyOrQuit(index == aArray.GetLength()); + + VerifyOrQuit(!aArray.Contains(EntryType(kUnusedValue))); + VerifyOrQuit(aArray.Find(EntryType(kUnusedValue)) == nullptr); + + if (TypeTraits::IsSame::kValue) + { + printf("} (constructor-calls:%u, destructor-calls:%u)\n", sConstructorCalls, sDestructorCalls); + VerifyOrQuit(sConstructorCalls - sDestructorCalls == aArray.GetLength()); + } + else + { + printf("}\n"); + } +} + +void TestHeapArrayOfUint16(void) +{ + Heap::Array array; + Heap::Array array2; + uint16_t * entry; + + printf("\n\n====================================================================================\n"); + printf("TestHeapArrayOfUint16\n\n"); + + printf("------------------------------------------------------------------------------------\n"); + printf("After constructor\n"); + VerifyOrQuit(array.GetCapacity() == 0); + VerifyArray(array); + + printf("------------------------------------------------------------------------------------\n"); + printf("PushBack(aEntry)\n"); + + SuccessOrQuit(array.PushBack(1)); + VerifyArray(array, 1); + VerifyOrQuit(array.GetCapacity() == 2); + + SuccessOrQuit(array.PushBack(2)); + VerifyArray(array, 1, 2); + VerifyOrQuit(array.GetCapacity() == 2); + + SuccessOrQuit(array.PushBack(3)); + VerifyArray(array, 1, 2, 3); + VerifyOrQuit(array.GetCapacity() == 4); + + printf("------------------------------------------------------------------------------------\n"); + printf("entry = PushBack()\n"); + + entry = array.PushBack(); + VerifyOrQuit(entry != nullptr); + *entry = 4; + VerifyArray(array, 1, 2, 3, 4); + VerifyOrQuit(array.GetCapacity() == 4); + + entry = array.PushBack(); + VerifyOrQuit(entry != nullptr); + *entry = 5; + VerifyArray(array, 1, 2, 3, 4, 5); + VerifyOrQuit(array.GetCapacity() == 6); + + printf("------------------------------------------------------------------------------------\n"); + printf("Clear()\n"); + + array.Clear(); + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 6); + + *array.PushBack() = 11; + SuccessOrQuit(array.PushBack(22)); + SuccessOrQuit(array.PushBack(33)); + SuccessOrQuit(array.PushBack(44)); + *array.PushBack() = 55; + + VerifyArray(array, 11, 22, 33, 44, 55); + VerifyOrQuit(array.GetCapacity() == 6); + + SuccessOrQuit(array.PushBack(66)); + SuccessOrQuit(array.PushBack(77)); + VerifyArray(array, 11, 22, 33, 44, 55, 66, 77); + VerifyOrQuit(array.GetCapacity() == 8); + + printf("------------------------------------------------------------------------------------\n"); + printf("PopBack()\n"); + + array.PopBack(); + VerifyArray(array, 11, 22, 33, 44, 55, 66); + VerifyOrQuit(array.GetCapacity() == 8); + + array.PopBack(); + array.PopBack(); + array.PopBack(); + array.PopBack(); + array.PopBack(); + VerifyArray(array, 11); + VerifyOrQuit(array.GetCapacity() == 8); + + array.PopBack(); + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 8); + + array.PopBack(); + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 8); + + for (uint16_t num = 0; num < 11; num++) + { + SuccessOrQuit(array.PushBack(num + 0x100)); + } + + VerifyArray(array, 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, 0x106, 0x107, 0x108, 0x109, 0x10a); + VerifyOrQuit(array.GetCapacity() == 12); + + printf("------------------------------------------------------------------------------------\n"); + printf("Free()\n"); + + array.Free(); + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 0); + + array.Free(); + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 0); + + printf("------------------------------------------------------------------------------------\n"); + printf("ReserveCapacity()\n"); + + SuccessOrQuit(array.ReserveCapacity(5)); + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 5); + + SuccessOrQuit(array.PushBack(0)); + VerifyArray(array, 0); + VerifyOrQuit(array.GetCapacity() == 5); + + for (uint16_t num = 1; num < 5; num++) + { + SuccessOrQuit(array.PushBack(num)); + } + + VerifyArray(array, 0, 1, 2, 3, 4); + VerifyOrQuit(array.GetCapacity() == 5); + + SuccessOrQuit(array.PushBack(5)); + VerifyArray(array, 0, 1, 2, 3, 4, 5); + VerifyOrQuit(array.GetCapacity() == 7); + + SuccessOrQuit(array.ReserveCapacity(3)); + VerifyArray(array, 0, 1, 2, 3, 4, 5); + VerifyOrQuit(array.GetCapacity() == 7); + + SuccessOrQuit(array.ReserveCapacity(10)); + VerifyArray(array, 0, 1, 2, 3, 4, 5); + VerifyOrQuit(array.GetCapacity() == 10); + + printf("------------------------------------------------------------------------------------\n"); + printf("TakeFrom()\n"); + + for (uint16_t num = 0; num < 7; num++) + { + SuccessOrQuit(array2.PushBack(num + 0x20)); + } + + VerifyArray(array2, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26); + + array2.TakeFrom(static_cast &&>(array)); + + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 0); + + VerifyArray(array2, 0, 1, 2, 3, 4, 5); + VerifyOrQuit(array2.GetCapacity() == 10); + + printf("\n -- PASS\n"); +} + +void TestHeapArray(void) +{ + VerifyOrQuit(sConstructorCalls == 0); + VerifyOrQuit(sDestructorCalls == 0); + + printf("\n\n====================================================================================\n"); + printf("TestHeapArray\n\n"); + + { + Heap::Array array; + Heap::Array array2; + Entry * entry; + + printf("------------------------------------------------------------------------------------\n"); + printf("After constructor\n"); + VerifyOrQuit(array.GetCapacity() == 0); + VerifyArray(array); + + printf("------------------------------------------------------------------------------------\n"); + printf("PushBack(aEntry)\n"); + + SuccessOrQuit(array.PushBack(Entry(1))); + VerifyArray(array, 1); + VerifyOrQuit(array.GetCapacity() == 2); + + SuccessOrQuit(array.PushBack(Entry(2))); + VerifyArray(array, 1, 2); + VerifyOrQuit(array.GetCapacity() == 2); + + SuccessOrQuit(array.PushBack(Entry(3))); + VerifyArray(array, 1, 2, 3); + VerifyOrQuit(array.GetCapacity() == 4); + + entry = array.PushBack(); + VerifyOrQuit(entry != nullptr); + VerifyOrQuit(entry->IsInitialized()); + VerifyOrQuit(entry->GetValue() == 0); + entry->SetValue(4); + VerifyArray(array, 1, 2, 3, 4); + VerifyOrQuit(array.GetCapacity() == 4); + + entry = array.PushBack(); + VerifyOrQuit(entry != nullptr); + VerifyOrQuit(entry->IsInitialized()); + VerifyOrQuit(entry->GetValue() == 0); + entry->SetValue(5); + VerifyArray(array, 1, 2, 3, 4, 5); + VerifyOrQuit(array.GetCapacity() == 6); + + printf("------------------------------------------------------------------------------------\n"); + printf("PopBack()\n"); + + array.PopBack(); + VerifyArray(array, 1, 2, 3, 4); + VerifyOrQuit(array.GetCapacity() == 6); + + array.PopBack(); + VerifyArray(array, 1, 2, 3); + VerifyOrQuit(array.GetCapacity() == 6); + + SuccessOrQuit(array.PushBack(Entry(7))); + VerifyArray(array, 1, 2, 3, 7); + VerifyOrQuit(array.GetCapacity() == 6); + + array.PopBack(); + VerifyArray(array, 1, 2, 3); + VerifyOrQuit(array.GetCapacity() == 6); + + printf("------------------------------------------------------------------------------------\n"); + printf("Clear()\n"); + + array.Clear(); + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 6); + + for (uint16_t num = 0; num < 11; num++) + { + SuccessOrQuit(array.PushBack(Entry(num))); + } + + VerifyArray(array, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + VerifyOrQuit(array.GetCapacity() == 12); + + printf("------------------------------------------------------------------------------------\n"); + printf("Free()\n"); + array.Free(); + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 0); + + printf("------------------------------------------------------------------------------------\n"); + printf("ReserveCapacity()\n"); + + SuccessOrQuit(array.ReserveCapacity(5)); + VerifyArray(array); + VerifyOrQuit(array.GetCapacity() == 5); + + SuccessOrQuit(array.PushBack(Entry(0))); + VerifyArray(array, 0); + VerifyOrQuit(array.GetCapacity() == 5); + + for (uint16_t num = 1; num < 5; num++) + { + SuccessOrQuit(array.PushBack(Entry(num))); + } + + VerifyArray(array, 0, 1, 2, 3, 4); + VerifyOrQuit(array.GetCapacity() == 5); + + SuccessOrQuit(array.PushBack(Entry(5))); + VerifyArray(array, 0, 1, 2, 3, 4, 5); + VerifyOrQuit(array.GetCapacity() == 7); + + SuccessOrQuit(array.ReserveCapacity(3)); + VerifyArray(array, 0, 1, 2, 3, 4, 5); + VerifyOrQuit(array.GetCapacity() == 7); + + SuccessOrQuit(array.ReserveCapacity(10)); + VerifyArray(array, 0, 1, 2, 3, 4, 5); + VerifyOrQuit(array.GetCapacity() == 10); + + printf("------------------------------------------------------------------------------------\n"); + printf("TakeFrom()\n"); + + for (uint16_t num = 0; num < 7; num++) + { + SuccessOrQuit(array2.PushBack(Entry(num + 0x20))); + } + + array2.TakeFrom(static_cast &&>(array)); + + VerifyOrQuit(array.GetLength() == 0); + VerifyOrQuit(array.GetCapacity() == 0); + + VerifyArray(array2, 0, 1, 2, 3, 4, 5); + VerifyOrQuit(array2.GetCapacity() == 10); + } + + printf("------------------------------------------------------------------------------------\n"); + printf("Array destructor\n"); + printf(" - (constructor-calls:%u, destructor-calls:%u)\n", sConstructorCalls, sDestructorCalls); + VerifyOrQuit(sConstructorCalls == sDestructorCalls, + "Array destructor failed to invoke destructor on all its existing entries"); + + printf("\n -- PASS\n"); +} + +} // namespace ot + +int main(void) +{ + ot::TestHeapArrayOfUint16(); + ot::TestHeapArray(); + printf("\nAll tests passed.\n"); + return 0; +}