diff --git a/.gitignore b/.gitignore index ad2ca6e..98213ad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.orig /compile_commands.json +/LastCoverageResults.log # vscode /.vscode/c_cpp_properties.json diff --git a/radiant/Algorithm.h b/radiant/Algorithm.h index 0bf4ad3..6d3c082 100644 --- a/radiant/Algorithm.h +++ b/radiant/Algorithm.h @@ -15,6 +15,10 @@ #pragma once #include "radiant/TotallyRad.h" +// RAD_S_ASSERT_NOTHROW_MOVE_T uses TypeTraits.h +#include "radiant/TypeTraits.h" // NOLINT(misc-include-cleaner) +// rad::Move needs radiant/Utility.h +#include "radiant/Utility.h" // NOLINT(misc-include-cleaner) namespace rad { diff --git a/radiant/Iterator.h b/radiant/Iterator.h index 56cc429..e4cf2a7 100644 --- a/radiant/Iterator.h +++ b/radiant/Iterator.h @@ -262,6 +262,7 @@ class ReverseIterator constexpr PointerType operator->() const noexcept { T tmp = m_current; + --tmp; return tmp; } @@ -269,18 +270,20 @@ class ReverseIterator constexpr PointerType operator->() const noexcept { T tmp = m_current; + --tmp; return tmp.operator->(); } constexpr ReferenceType operator*() const noexcept { T tmp = m_current; + --tmp; return *tmp; } constexpr ReferenceType operator[](DifferenceType diff) const noexcept { - return m_current[static_cast(-diff)]; + return m_current[static_cast(-diff - 1)]; } constexpr ThisType& operator++() noexcept diff --git a/radiant/List.h b/radiant/List.h new file mode 100644 index 0000000..1b6bb99 --- /dev/null +++ b/radiant/List.h @@ -0,0 +1,701 @@ +// Copyright 2024 The Radiant 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 +// +// http://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. + +#pragma once + +#include "radiant/TotallyRad.h" +// rad::Swap needs radiant/Utility.h +#include "radiant/Algorithm.h" // NOLINT(misc-include-cleaner) +#include "radiant/EmptyOptimizedPair.h" +#include "radiant/Iterator.h" +#include "radiant/Memory.h" +#include "radiant/Res.h" +#include "radiant/detail/ListOperations.h" + +#include + +#if RAD_ENABLE_STD +#include +#endif // RAD_ENABLE_STD + +namespace rad +{ + +/*! + @brief Cross platform (and kernel appropriate) doubly linked list + implementation + + @details Deviations from std::list: + + The container isn't copiable using the copy constructor or copy assignment. + This is because there is no way to indicate failure in these cases. Move + construction and move assignment work fine, and are guaranteed noexcept. Use + Clone if you need a deep copy. + + The set of constructors has been greatly reduced, due to error handling + difficulties when exceptions aren't available. + + The allocator returns nullptr on failure, rather than throwing an exception. + That error is propagated. Allocators are always propagated. "Fancy + pointers" aren't supported, as the allocators used aren't std allocators. So + you won't be able to use some offset based pointer to do shared memory + things with this container. + + Removed size() related functions, as making size() O(1) interferes with + efficient splicing, and having size() be O(N) is too much of a foot gun. The + standard made a mistake here. Empty() is still present and fine, and + ExpensiveSize() exists largely for test code. + + The Assign functions now all return Err to signal errors. The count + and initializer_list overloads of Assign were renamed to AssignCount and + AssignInitializerList to minimize overloading + + Renamed remove and remove_if to EraseValue and EraseIf, as the "remove" + names are mistakes in the standard. "remove" usually refers to algorithms + that move unwanted elements out of the way, but don't change the size of the + container. "erase" will get rid of unwanted elements and change the + container size. + + Renamed iterator overloads of "erase" to "EraseOne" and "EraseSome" to + better capture caller intent. There's a common bug where devs call + `erase(range_begin)` when they meant to call `erase(range_begin, + range_end)`, and splitting the "erase" overload set into two names + eliminates that bug. + + No Constructor Template Argument Deduction(CTAD), because that all requires + constructors that will do some initial population of the container, and we + don't have those constructors since we can't signal errors out of + constructors. + + The splice family of functions were renamed to SpliceAll, SpliceOne, and + SpliceSome, to better make their intent clear to readers without needing to + carefully count the number of parameters. + + @tparam T - Value type held by the list + @tparam TAllocator - Allocator to use. +*/ +template +class List +{ +public: + + // types + using ValueType = T; + using AllocatorType = TAllocator; + using PointerType = T*; + using ConstPointerType = const T*; + using ReferenceType = ValueType&; + using ConstReferenceType = const ValueType&; + using SizeType = size_t; + using DifferenceType = ptrdiff_t; + using IteratorType = ::rad::detail::ListIterator; + using ConstIteratorType = ::rad::detail::ListConstIterator; + using ReverseIteratorType = ReverseIterator; + using ConstReverseIteratorType = ReverseIterator; + + using Rebound = + typename TAllocator::template Rebind<::rad::detail::ListNode>::Other; + + // Not asserting noexcept movability on T, as we mostly don't need to move + // types once they are constructed. We support immovable types like + // mutexes. The Take* functions assert on noexcept movability since they + // do move contained elements. + RAD_S_ASSERT_ALLOCATOR_REQUIRES_T(TAllocator); + + ~List() + { + Clear(); + } + + List() = default; + RAD_NOT_COPYABLE(List); + + List(List&& x) noexcept + : m_storage(::rad::Move(x.m_storage.First())) + { + m_storage.Second().Swap(x.m_storage.Second()); + } + + List& operator=(List&& x) noexcept + { + Clear(); + Swap(x); + return *this; + } + + explicit List(const TAllocator& alloc) + : m_storage(alloc) + { + } + + Res Clone() + { + List local(m_storage.First()); + return local.AssignSomeImpl(this->begin(), this->end()) + .OnOk(::rad::Move(local)); + } + + AllocatorType GetAllocator() const noexcept + { + return m_storage.First(); + } + + // iterators + RAD_NODISCARD IteratorType begin() noexcept + { + return IteratorType(m_storage.Second().m_head.m_next); + } + + RAD_NODISCARD ConstIteratorType begin() const noexcept + { + return ConstIteratorType(m_storage.Second().m_head.m_next); + } + + RAD_NODISCARD IteratorType end() noexcept + { + return IteratorType(&m_storage.Second().m_head); + } + + RAD_NODISCARD ConstIteratorType end() const noexcept + { + return ConstIteratorType(&m_storage.Second().m_head); + } + + RAD_NODISCARD ConstIteratorType cbegin() const noexcept + { + return ConstIteratorType(m_storage.Second().m_head.m_next); + } + + RAD_NODISCARD ConstIteratorType cend() const noexcept + { + return ConstIteratorType(&m_storage.Second().m_head); + } + + RAD_NODISCARD ReverseIteratorType rbegin() noexcept + { + return ReverseIteratorType(end()); + } + + RAD_NODISCARD ReverseIteratorType rend() noexcept + { + return ReverseIteratorType(begin()); + } + + RAD_NODISCARD ConstReverseIteratorType crbegin() noexcept + { + return ConstReverseIteratorType(cend()); + } + + RAD_NODISCARD ConstReverseIteratorType crend() noexcept + { + return ConstReverseIteratorType(cbegin()); + } + + RAD_NODISCARD bool Empty() const noexcept + { + // debug perf optimization: Duplicate "Empty" + // implementation, rather than forward the call down + // three levels. + return m_storage.Second().m_head.m_next == &m_storage.Second().m_head; + } + + // O(N) operation, renamed so that people don't + // assume it is cheap and make things accidentally + // quadratic. Too useful in test code to omit + // entirely. + RAD_NODISCARD size_t ExpensiveSize() const noexcept + { + return m_storage.Second().ExpensiveSize(); + } + + template + Res AssignSome(InputIterator first, InputIterator last) + { + return AssignSomeImpl(first, last); + } + + template + Res AssignRange(InputRange&& rg) + { + return AssignSomeImpl(rg.begin(), rg.end()); + } + + Res AssignCount(SizeType n, const T& t) + { + List local(m_storage.First()); + auto end_iter = local.cend(); + for (SizeType i = 0; i < n; ++i) + { + void* ptr = local.EmplacePtr(end_iter, t); + if (ptr == nullptr) + { + return Error::NoMemory; + } + } + // using m_list Swap so that we don't need to swap allocators + m_storage.Second().Swap(local.m_storage.Second()); + return *this; + } + +#if RAD_ENABLE_STD + Res AssignInitializerList(std::initializer_list il) + { + return AssignSomeImpl(il.begin(), il.end()); + } +#endif + + Res Front() + { + if (Empty()) + { + return Res(ResErrTag, Error::OutOfRange); + } + return Res(ResOkTag, *begin()); + } + + Res Front() const + { + if (Empty()) + { + return Res(ResErrTag, Error::OutOfRange); + } + return Res(ResOkTag, *begin()); + } + + Res Back() + { + if (Empty()) + { + return Res(ResErrTag, Error::OutOfRange); + } + return Res(ResOkTag, *(--end())); + } + + Res Back() const + { + if (Empty()) + { + return Res(ResErrTag, Error::OutOfRange); + } + return Res(ResOkTag, *(--end())); + } + + template + Res EmplaceFront(Args&&... args) + { + return ToResSelf(EmplacePtr(cbegin(), Forward(args)...)); + } + + template + Res EmplaceBack(Args&&... args) + { + return ToResSelf(EmplacePtr(cend(), Forward(args)...)); + } + + Res PushFront(const T& x) + { + return ToResSelf(EmplacePtr(cbegin(), x)); + } + + Res PushFront(T&& x) + { + return ToResSelf(EmplacePtr(cbegin(), ::rad::Move(x))); + } + + Res PushBack(const T& x) + { + return ToResSelf(EmplacePtr(cend(), x)); + } + + Res PushBack(T&& x) + { + return ToResSelf(EmplacePtr(cend(), ::rad::Move(x))); + } + + template + Res PrependRange(InputRange&& rg) + { + return ToResSelf(InsertSomeImpl(cbegin(), rg.begin(), rg.end())); + } + + template + Res AppendRange(InputRange&& rg) + { + return ToResSelf(InsertSomeImpl(cend(), rg.begin(), rg.end())); + } + + // Calling PopFront or PopBack while the container is empty is erroneous + // behavior. It's wrong to do it, and we can diagnose it in debug mode, but + // in release mode we will instead do nothing. + List& PopFront() + { + RAD_ASSERT(!Empty()); + EraseOne(begin()); + return *this; + } + + List& PopBack() + { + RAD_ASSERT(!Empty()); + EraseOne(--end()); + return *this; + } + + // move the first T into the return value and pop it from the list + Res TakeFront() + { + // careful! T could be rad::Error + RAD_S_ASSERT_NOTHROW_MOVE_T(T); + if (Empty()) + { + return Res(ResErrTag, Error::OutOfRange); + } + List local(m_storage.First()); + local.m_storage.Second().SpliceOne(local.end().m_node, begin().m_node); + return Res(ResOkTag, ::rad::Move(*local.begin())); + } + + // move the last T into the return value and pop it from the list + Res TakeBack() + { + // careful! T could be rad::Error + RAD_S_ASSERT_NOTHROW_MOVE_T(T); + if (Empty()) + { + return Res(ResErrTag, Error::OutOfRange); + } + List local(m_storage.First()); + local.m_storage.Second().SpliceOne(local.end().m_node, + (--end()).m_node); + return Res(ResOkTag, ::rad::Move(*local.begin())); + } + + // The Insert and Emplace functions provide the strong error + // guarantee. If they fail, then the function returns without + // changing the container, invalidating iterators, or invalidating + // references. + template + RAD_NODISCARD Res Emplace(ConstIteratorType position, + Args&&... args) + { + return ToResIter(EmplacePtr(position, Forward(args)...)); + } + + RAD_NODISCARD Res Insert(ConstIteratorType position, + const T& x) + { + return ToResIter(EmplacePtr(position, x)); + } + + RAD_NODISCARD Res Insert(ConstIteratorType position, T&& x) + { + return ToResIter(EmplacePtr(position, ::rad::Move(x))); + } + + RAD_NODISCARD Res InsertCount(ConstIteratorType position, + SizeType n, + const T& x) + { + List local(m_storage.First()); + auto end_iter = local.cend(); + for (SizeType i = 0; i < n; ++i) + { + void* ptr = local.EmplacePtr(end_iter, x); + if (ptr == nullptr) + { + return Error::NoMemory; + } + } + ::rad::detail::ListBasicNode* new_pos = + m_storage.Second().SpliceSome(position.m_node, + local.begin().m_node, + local.end().m_node); + return IteratorType{ new_pos }; + } + + template + RAD_NODISCARD Res InsertSome(ConstIteratorType position, + InputIterator first, + InputIterator last) + + { + return ToResIter(InsertSomeImpl(position, first, last)); + } + + template + RAD_NODISCARD Res InsertRange(ConstIteratorType position, + InputRange&& rg) + { + return ToResIter(InsertSomeImpl(position, rg.begin(), rg.end())); + } + +#if RAD_ENABLE_STD + RAD_NODISCARD Res InsertInitializerList( + ConstIteratorType position, std::initializer_list il) + { + return ToResIter(InsertSomeImpl(position, il.begin(), il.end())); + } +#endif + + List& Clear() noexcept + { + EraseSome(begin(), end()); + return *this; + } + + IteratorType EraseOne(ConstIteratorType position) + { + if (position == cend()) + { + return end(); + } + ::rad::detail::ListBasicNode* cur = position.m_node; + ::rad::detail::ListBasicNode* retnode = cur->m_next; + cur->CheckSanityBeforeRelinking(); + cur->m_prev->m_next = cur->m_next; + cur->m_next->m_prev = cur->m_prev; + + ::rad::detail::ListNode* typed = + static_cast<::rad::detail::ListNode*>(cur); + typed->~ListNode(); + ReboundAlloc().Free(typed); + + return IteratorType(retnode); + } + + IteratorType EraseSome(ConstIteratorType position, ConstIteratorType last) + { + ::rad::detail::ListBasicNode* cur = position.m_node; + ::rad::detail::ListBasicNode* end = last.m_node; + if (cur == end) + { + return IteratorType(end); + } + cur->CheckSanityBeforeRelinking(); + end->CheckSanityBeforeRelinking(); + cur->m_prev->m_next = end; + end->m_prev = cur->m_prev; + + while (cur != end) + { + ::rad::detail::ListNode* typed = + static_cast<::rad::detail::ListNode*>(cur); + cur = cur->m_next; + + typed->~ListNode(); + ReboundAlloc().Free(typed); + } + return IteratorType(end); + } + + SizeType EraseValue(const T& value) + { + SizeType count = 0; + IteratorType i = begin(); + while (i != end()) + { + if (*i == value) + { + i = EraseOne(i); + ++count; + } + else + { + ++i; + } + } + return count; + } + + template + SizeType EraseIf(Predicate pred) + { + SizeType count = 0; + IteratorType i = begin(); + while (i != end()) + { + if (pred(*i)) + { + i = EraseOne(i); + ++count; + } + else + { + ++i; + } + } + return count; + } + + List& Swap(List& x) noexcept + { + { + rad::Swap(m_storage.First(), x.m_storage.First()); + } + m_storage.Second().Swap(x.m_storage.Second()); + return *this; + } + + // The list parameter to the splice functions is mostly unused. It's + // important to keep it though as a way to attest that you have mutable + // access to the source list. If we want to support unequal allocators, + // then we'll need access to the source list. We'll also need to add an + // error channel if we support unequal allocators. + List& SpliceAll(ConstIteratorType position, List& x) + { + m_storage.Second().SpliceSome(position.m_node, + x.begin().m_node, + x.end().m_node); + return *this; + } + + List& SpliceAll(ConstIteratorType position, List&& x) + { + m_storage.Second().SpliceSome(position.m_node, + x.begin().m_node, + x.end().m_node); + return *this; + } + + List& SpliceOne(ConstIteratorType position, List& x, ConstIteratorType i) + { + RAD_UNUSED(x); + m_storage.Second().SpliceOne(position.m_node, i.m_node); + return *this; + } + + List& SpliceOne(ConstIteratorType position, List&& x, ConstIteratorType i) + { + RAD_UNUSED(x); + m_storage.Second().SpliceOne(position.m_node, i.m_node); + return *this; + } + + List& SpliceSome(ConstIteratorType position, + List& x, + ConstIteratorType first, + ConstIteratorType last) + { + RAD_UNUSED(x); + m_storage.Second().SpliceSome(position.m_node, + first.m_node, + last.m_node); + return *this; + } + + List& SpliceSome(ConstIteratorType position, + List&& x, + ConstIteratorType first, + ConstIteratorType last) + { + RAD_UNUSED(x); + m_storage.Second().SpliceSome(position.m_node, + first.m_node, + last.m_node); + return *this; + } + + List& Reverse() noexcept + { + m_storage.Second().Reverse(); + return *this; + } + +private: + + static Res ToResIter(::rad::detail::ListBasicNode* ptr) + { + if (ptr == nullptr) + { + return Error::NoMemory; + } + return IteratorType(ptr); + } + + Res ToResSelf(const void* ptr) + { + if (ptr == nullptr) + { + return Error::NoMemory; + } + return *this; + } + + template + RAD_NODISCARD ::rad::detail::ListNode* EmplacePtr( + ConstIteratorType position, Args&&... args) + { + ::rad::detail::ListNode* storage = ReboundAlloc().Alloc(1); + if (storage == nullptr) + { + return nullptr; + } + // forward to placement new + ::rad::detail::ListNode* new_node = + new (storage)::rad::detail::ListNode(Forward(args)...); + + // insert the new node before passed in position + m_storage.Second().AttachNewNode(position.m_node, new_node); + + return new_node; + } + + template + RAD_NODISCARD ::rad::detail::ListBasicNode* InsertSomeImpl( + ConstIteratorType position, InputIter1 first, InputIter2 last) + { + List local(m_storage.First()); + auto end_iter = local.cend(); + for (; first != last; ++first) + { + void* ptr = local.EmplacePtr(end_iter, *first); + if (ptr == nullptr) + { + return nullptr; + } + } + ::rad::detail::ListBasicNode* new_pos = + m_storage.Second().SpliceSome(position.m_node, + local.begin().m_node, + local.end().m_node); + return new_pos; + } + + template + Res AssignSomeImpl(InputIter1 first, InputIter2 last) + { + List local(m_storage.First()); + auto end_iter = local.cend(); + for (; first != last; ++first) + { + void* ptr = local.EmplacePtr(end_iter, *first); + if (ptr == nullptr) + { + return Error::NoMemory; + } + } + // using m_list Swap so that we don't need to swap allocators + m_storage.Second().Swap(local.m_storage.Second()); + return *this; + } + + Rebound ReboundAlloc() + { + return m_storage.First(); + } + + EmptyOptimizedPair m_storage; +}; + +} // namespace rad diff --git a/radiant/Result.h b/radiant/Result.h index 5e9fdb9..b7ad138 100644 --- a/radiant/Result.h +++ b/radiant/Result.h @@ -21,6 +21,8 @@ #include #if RAD_ENABLE_STD #include +// Placement new needs clang-tidy errors without the header. +#include // NOLINT(misc-include-cleaner) #endif // @@ -330,8 +332,8 @@ class RAD_NODISCARD Result final : private detail::ResultStorage using typename StorageType::OkType; using typename StorageType::ErrType; - RAD_S_ASSERT_NOTHROW_MOVE_T(T); - RAD_S_ASSERT_NOTHROW_MOVE_T(E); + RAD_S_ASSERT_NOTHROW_MOVE_T(typename StorageType::OkWrap::ValueType); + RAD_S_ASSERT_NOTHROW_MOVE_T(typename StorageType::ErrWrap::ValueType); constexpr Result() noexcept : StorageType(ResultEmptyTag) diff --git a/radiant/Span.h b/radiant/Span.h index b2064c7..3843304 100644 --- a/radiant/Span.h +++ b/radiant/Span.h @@ -345,27 +345,12 @@ class Span constexpr ReverseIteratorType rbegin() const noexcept { - if (Empty()) - { - return ReverseIteratorType(nullptr); - } - return ReverseIteratorType(Data() + (Size() - 1)); + return ReverseIteratorType(Data() + Size()); } constexpr ReverseIteratorType rend() const noexcept { - if (Empty()) - { - return ReverseIteratorType(nullptr); - } -#if !RAD_DBG && defined(RAD_GCC_VERSION) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" -#endif - return ReverseIteratorType(Data() - 1); -#if !RAD_DBG && defined(RAD_GCC_VERSION) -#pragma GCC diagnostic pop -#endif + return ReverseIteratorType(Data()); } private: diff --git a/radiant/TotallyRad.h b/radiant/TotallyRad.h index 2767262..ab8c35b 100644 --- a/radiant/TotallyRad.h +++ b/radiant/TotallyRad.h @@ -198,6 +198,15 @@ inline bool DoAssert(const char* Assertion, const char* File, int Line) #endif +#if RAD_WINDOWS +extern "C" __declspec(noreturn) void __fastfail(unsigned int code); +#define RAD_FAST_FAIL_ALWAYS() (__fastfail('idar'), false) +#else // ^^^ RAD_WINDOWS / !RAD_WINDOWS vvv +#define RAD_FAST_FAIL_ALWAYS() (__builtin_trap(), false) +#endif // !RAD_WINDOWS ^^^ + +#define RAD_FAST_FAIL(x) ((!!(x)) || (RAD_FAST_FAIL_ALWAYS())) + #if RAD_DBG #if RAD_WINDOWS && RAD_KERNEL_MODE @@ -206,8 +215,9 @@ inline bool DoAssert(const char* Assertion, const char* File, int Line) #define RAD_VERIFY(x) NT_VERIFY(x) #define RAD_VERIFYMSG(x, msg) NT_VERIFYMSG(x) #else -#define RAD_VERIFY(x) ((!!(x)) || (rad::DoAssert(#x, __FILE__, __LINE__))) -#define RAD_VERIFYMSG(x, m) ((!!(x)) || (rad::DoAssert(m, __FILE__, __LINE__))) +#define RAD_VERIFY(x) ((!!(x)) || (::rad::DoAssert(#x, __FILE__, __LINE__))) +#define RAD_VERIFYMSG(x, m) \ + ((!!(x)) || (::rad::DoAssert(m, __FILE__, __LINE__))) #define RAD_ASSERT(x) (void)RAD_VERIFY(x) #define RAD_ASSERTMSG(x, m) (void)RAD_VERIFY(x, m) #endif @@ -259,9 +269,10 @@ inline bool DoAssert(const char* Assertion, const char* File, int Line) #define RAD_S_ASSERT_NOTHROW_DTOR(x) \ RAD_S_ASSERTMSG(x, "destructors should not throw") #define RAD_S_ASSERT_NOTHROW_DTOR_T(x) \ - RAD_S_ASSERTMSG(IsNoThrowDtor, "destructors should not throw") + RAD_S_ASSERT_NOTHROW_DTOR(::rad::IsNoThrowDtor) #else -#define RAD_S_ASSERT_NOTHROW_DTOR(x) RAD_S_ASSERT(true) +#define RAD_S_ASSERT_NOTHROW_DTOR(x) RAD_S_ASSERT(true) +#define RAD_S_ASSERT_NOTHROW_DTOR_T(x) RAD_S_ASSERT(true) #endif // @@ -278,8 +289,8 @@ inline bool DoAssert(const char* Assertion, const char* File, int Line) #define RAD_S_ASSERT_NOTHROW_MOVE(x) \ RAD_S_ASSERTMSG(x, "move operations should not throw") #define RAD_S_ASSERT_NOTHROW_MOVE_T(x) \ - RAD_S_ASSERTMSG(IsNoThrowMoveCtor&& IsNoThrowMoveAssign, \ - "move operations should not throw") + RAD_S_ASSERT_NOTHROW_MOVE( \ + ::rad::IsNoThrowMoveCtor&& ::rad::IsNoThrowMoveAssign) #else #define RAD_S_ASSERT_NOTHROW_MOVE(x) RAD_S_ASSERT(true) #define RAD_S_ASSERT_NOTHROW_MOVE_T(x) RAD_S_ASSERT(true) @@ -298,7 +309,7 @@ inline bool DoAssert(const char* Assertion, const char* File, int Line) #define RAD_S_ASSERT_ALLOCATOR_REQUIRES(x) \ RAD_S_ASSERTMSG(x, "allocator requirements not met") #define RAD_S_ASSERT_ALLOCATOR_REQUIRES_T(x) \ - RAD_S_ASSERTMSG(AllocatorRequires, "allocator requirements not met") + RAD_S_ASSERT_ALLOCATOR_REQUIRES(::rad::AllocatorRequires) #else #define RAD_S_ASSERT_ALLOCATOR_REQUIRES(x) RAD_S_ASSERT(true) #define RAD_S_ASSERT_ALLOCATOR_REQUIRES_T(x) RAD_S_ASSERT(true) diff --git a/radiant/TypeWrapper.h b/radiant/TypeWrapper.h index 68dd223..6717730 100644 --- a/radiant/TypeWrapper.h +++ b/radiant/TypeWrapper.h @@ -34,6 +34,8 @@ class TypeWrapper public: using Type = T; + using PointerType = T*; + using ValueType = Type; RAD_S_ASSERT_NOTHROW_MOVE_T(T); @@ -157,13 +159,11 @@ class TypeWrapper template class TypeWrapper { -private: - - using PointerType = RemoveRef*; - public: - using Type = T; + using Type = RemoveRef; + using PointerType = RemoveRef*; + using ValueType = PointerType; constexpr TypeWrapper() = delete; diff --git a/radiant/Vector.h b/radiant/Vector.h index 6d7d15f..a4ff3ba 100644 --- a/radiant/Vector.h +++ b/radiant/Vector.h @@ -57,6 +57,8 @@ class Vector final RAD_S_ASSERT_NOTHROW_MOVE_T(T); RAD_S_ASSERT_ALLOCATOR_REQUIRES_T(TAllocator); + RAD_NOT_COPYABLE(Vector); + ~Vector() { RAD_S_ASSERT_NOTHROW_DTOR(IsNoThrowDtor && @@ -67,24 +69,22 @@ class Vector final } /// @brief Constructs empty container with default-constructed allocator. - Vector() = default; + Vector() noexcept = default; /// @brief Constructs empty container with copy-constructed allocator. /// @param alloc Allocator to copy. - explicit Vector(const AllocatorType& alloc) + explicit Vector(const AllocatorType& alloc) noexcept : m_storage(alloc) { } /// @brief Constructs empty container with move-constructed allocator. /// @param alloc Allocator to move. - explicit Vector(AllocatorType&& alloc) + explicit Vector(AllocatorType&& alloc) noexcept : m_storage(Forward(alloc)) { } - ThisType& operator=(const ThisType& other) = delete; - /// @brief Moves elements in another container into this. /// @param other Other container to move elements from. /// @return Reference to this container. @@ -388,9 +388,10 @@ class Vector final return *this; } - /// @brief Removes an element from back of the container. - /// @return Removed element. - ValueType RemoveBack() noexcept + /// @brief Takes an element from back of the container. The element is moved + /// out of and then removed from the back of the container. + /// @return Taken element. + ValueType TakeBack() noexcept { auto value = rad::Move(Back()); PopBack(); diff --git a/radiant/detail/ListOperations.h b/radiant/detail/ListOperations.h new file mode 100644 index 0000000..8b3cc3a --- /dev/null +++ b/radiant/detail/ListOperations.h @@ -0,0 +1,407 @@ +// Copyright 2024 The Radiant 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 +// +// http://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. + +#pragma once + +#include "radiant/TotallyRad.h" +#include "radiant/Algorithm.h" +// rad::Forward needs radiant/Utility.h +#include "radiant/Utility.h" // NOLINT(misc-include-cleaner) + +#include + +#if RAD_ENABLE_STD +#include +#endif // RAD_ENABLE_STD + +namespace rad +{ + +template +class List; + +namespace detail +{ +template +class ListConstIterator; + +class ListBasicNode +{ +public: + + ListBasicNode() = default; + ~ListBasicNode() = default; + + // immovable + RAD_NOT_COPYABLE(ListBasicNode); + ListBasicNode(ListBasicNode&&) = delete; + ListBasicNode& operator=(ListBasicNode&&) = delete; + + void Unlink() + { + m_prev = m_next = this; + } + + void FixupHeadSwap(const ListBasicNode& old_head) noexcept + { + if (m_next == &old_head) + { + // This used to be an empty head, but now it + // looks like the old head is the first element + // in our list. unlink to reestablish emptiness. + Unlink(); + } + else + { + // if these fire, it indicates a heap overflow, + // and someone is trying to build a write primitive + RAD_FAST_FAIL(m_next->m_prev == &old_head); + RAD_FAST_FAIL(m_prev->m_next == &old_head); + m_next->m_prev = this; + m_prev->m_next = this; + } + } + + void CheckSanityBeforeRelinking() const noexcept + { + // if these fire, it indicates a heap overflow, + // and someone is trying to build a write primitive + RAD_FAST_FAIL(m_next->m_prev == this); + RAD_FAST_FAIL(m_prev->m_next == this); + } + + void Swap(ListBasicNode& x) noexcept + { + rad::Swap(m_next, x.m_next); + rad::Swap(m_prev, x.m_prev); + } + + void AssertOnEmpty() const noexcept + { + RAD_FAST_FAIL(m_next != this); + } + + ListBasicNode* m_next = this; + ListBasicNode* m_prev = this; +}; + +template +class ListNode : public ListBasicNode +{ +public: + + ListNode() = default; + ~ListNode() = default; + + // used by Emplace + template + explicit ListNode(Args&&... args) + : ListBasicNode(), + m_elt(Forward(args)...) + { + } + + // immovable + RAD_NOT_COPYABLE(ListNode); + ListNode(ListNode&&) = delete; + ListNode& operator=(ListNode&&) = delete; + + T m_elt; +}; + +template +class ListIterator +{ +public: + +#if RAD_ENABLE_STD + using iterator_category = std::bidirectional_iterator_tag; +#endif + + using ValueType = T; + using DifferenceType = ptrdiff_t; + using PointerType = T*; + using ReferenceType = T&; + + ListIterator() = default; + + explicit ListIterator(ListBasicNode* node) + : m_node(node) + { + } + + ListIterator(const ListIterator&) = default; + ListIterator(ListIterator&&) = default; + ListIterator& operator=(const ListIterator&) = default; + ListIterator& operator=(ListIterator&&) = default; + ~ListIterator() = default; + + bool operator==(ListIterator rhs) const noexcept + { + return m_node == rhs.m_node; + } + + bool operator!=(ListIterator rhs) const noexcept + { + return m_node != rhs.m_node; + } + + ReferenceType operator*() const noexcept + { + m_node->AssertOnEmpty(); + return static_cast*>(m_node)->m_elt; + } + + PointerType operator->() const noexcept + { + m_node->AssertOnEmpty(); + return &static_cast*>(m_node)->m_elt; + } + + ListIterator& operator++() noexcept + { + m_node = m_node->m_next; + return *this; + } + + ListIterator operator++(int) noexcept + { + ListIterator retval(m_node); + m_node = m_node->m_next; + return retval; + } + + ListIterator& operator--() noexcept + { + m_node = m_node->m_prev; + return *this; + } + + ListIterator operator--(int) noexcept + { + ListIterator retval(m_node); + m_node = m_node->m_prev; + return retval; + } + +private: + + ListBasicNode* m_node = nullptr; + + template + friend class ::rad::List; + + friend class ListConstIterator; +}; + +template +class ListConstIterator +{ +public: + +#if RAD_ENABLE_STD + using iterator_category = std::bidirectional_iterator_tag; +#endif + + using ValueType = const T; + using DifferenceType = ptrdiff_t; + using PointerType = const T*; + using ReferenceType = const T&; + + ListConstIterator() = default; + + explicit ListConstIterator(const ListBasicNode* node) + : m_node(const_cast(node)) + { + } + + ListConstIterator(const ListConstIterator&) = default; + ListConstIterator(ListConstIterator&&) = default; + + ListConstIterator& operator=(const ListConstIterator&) = default; + ListConstIterator& operator=(ListConstIterator&&) = default; + + ~ListConstIterator() = default; + + /* implicit */ ListConstIterator(ListIterator other) noexcept + : m_node(other.m_node) + { + } + + bool operator==(ListConstIterator rhs) const noexcept + { + return m_node == rhs.m_node; + } + + bool operator!=(ListConstIterator rhs) const noexcept + { + return m_node != rhs.m_node; + } + + ReferenceType operator*() const noexcept + { + m_node->AssertOnEmpty(); + return static_cast*>(m_node)->m_elt; + } + + PointerType operator->() const noexcept + { + m_node->AssertOnEmpty(); + return &static_cast*>(m_node)->m_elt; + } + + ListConstIterator& operator++() noexcept + { + m_node = m_node->m_next; + return *this; + } + + ListConstIterator operator++(int) noexcept + { + ListConstIterator retval(m_node); + m_node = m_node->m_next; + return retval; + } + + ListConstIterator& operator--() noexcept + { + m_node = m_node->m_prev; + return *this; + } + + ListConstIterator operator--(int) noexcept + { + ListConstIterator retval(m_node); + m_node = m_node->m_prev; + return retval; + } + +private: + + ListBasicNode* m_node = nullptr; + + template + friend class ::rad::List; +}; + +class ListUntyped +{ +public: + + ListUntyped() = default; + ~ListUntyped() = default; + + RAD_NOT_COPYABLE(ListUntyped); + + ListUntyped(ListUntyped&& x) noexcept = delete; + ListUntyped& operator=(ListUntyped&& x) noexcept = delete; + + // O(N) operation, renamed so that people don't + // assume it is cheap and make things accidentally + // quadratic. Too useful in test code to omit + // entirely. + RAD_NODISCARD size_t ExpensiveSize() const noexcept + { + size_t count = 0; + const ListBasicNode* cur = &m_head; + while (cur->m_next != &m_head) + { + cur = cur->m_next; + ++count; + } + return count; + } + + void Swap(ListUntyped& x) noexcept + { + m_head.Swap(x.m_head); + m_head.FixupHeadSwap(x.m_head); + x.m_head.FixupHeadSwap(m_head); + } + + void AttachNewNode(ListBasicNode* pos, ListBasicNode* i) + { + RAD_ASSERT(i->m_next == i); + RAD_ASSERT(i->m_prev == i); + i->m_next = pos; + i->m_prev = pos->m_prev; + + pos->CheckSanityBeforeRelinking(); + pos->m_prev->m_next = i; + pos->m_prev = i; + } + + void SpliceOne(ListBasicNode* pos, ListBasicNode* i) + { + i->CheckSanityBeforeRelinking(); + i->m_next->m_prev = i->m_prev; + i->m_prev->m_next = i->m_next; + + i->m_next = pos; + i->m_prev = pos->m_prev; + + pos->CheckSanityBeforeRelinking(); + pos->m_prev->m_next = i; + pos->m_prev = i; + } + + ListBasicNode* SpliceSome(ListBasicNode* position, + ListBasicNode* first, + ListBasicNode* last) + { + if (first == last) + { + return position; + } + last = last->m_prev; + last->CheckSanityBeforeRelinking(); + first->CheckSanityBeforeRelinking(); + + first->m_prev->m_next = last->m_next; + last->m_next->m_prev = first->m_prev; + + last->m_next = position; + first->m_prev = position->m_prev; + + position->CheckSanityBeforeRelinking(); + position->m_prev->m_next = first; + position->m_prev = last; + + return first; + } + + void Reverse() noexcept + { + if (m_head.m_next == m_head.m_prev) + { + return; + } + ListBasicNode* cur = &m_head; + do + { + ListBasicNode* temp = cur->m_next; + cur->m_next = cur->m_prev; + cur->m_prev = temp; + cur = cur->m_prev; + } + while (cur != &m_head); + // do I get the job? + } + + ListBasicNode m_head; +}; + +} // namespace detail + +} // namespace rad diff --git a/test/TestAlloc.h b/test/TestAlloc.h index 834e877..f5a9cd6 100644 --- a/test/TestAlloc.h +++ b/test/TestAlloc.h @@ -20,6 +20,7 @@ namespace radtest { static constexpr uint32_t k_BadState = 0xdeadc0de; +static constexpr uint32_t k_MovedFromState = 0xc001d00d; template class Allocator @@ -108,6 +109,7 @@ class StatefulAllocator } StatefulAllocator(const StatefulAllocator&) noexcept = default; + StatefulAllocator& operator=(const StatefulAllocator&) noexcept = default; template StatefulAllocator(const StatefulAllocator& other) noexcept @@ -115,6 +117,25 @@ class StatefulAllocator { } + StatefulAllocator(StatefulAllocator&& other) noexcept + : m_state(other.m_state) + { + if (other.m_state != k_BadState) + { + other.m_state = k_MovedFromState; + } + } + + StatefulAllocator& operator=(StatefulAllocator&& other) noexcept + { + m_state = other.m_state; + if (other.m_state != k_BadState) + { + other.m_state = k_MovedFromState; + } + return *this; + } + template struct Rebind { @@ -591,6 +612,14 @@ struct HeapAllocator forceAllocFails--; return nullptr; } + if (forceFutureAllocFail > 0) + { + forceFutureAllocFail--; + if (forceFutureAllocFail == 0) + { + return nullptr; + } + } allocCount++; return malloc(count); @@ -640,6 +669,7 @@ struct HeapAllocator int32_t freeCount{ 0 }; + int32_t forceFutureAllocFail{ 0 }; int32_t forceAllocFails{ 0 }; int32_t allocCount{ 0 }; diff --git a/test/TestInputStringLiteralRange.h b/test/TestInputStringLiteralRange.h new file mode 100644 index 0000000..ea4d138 --- /dev/null +++ b/test/TestInputStringLiteralRange.h @@ -0,0 +1,108 @@ +// Copyright 2024 The Radiant 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 +// +// http://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 + +namespace radtest +{ + +class StringLiteralSentinel +{ +}; + +class StringLiteralIterator +{ +public: + + using difference_type = ptrdiff_t; + using value_type = char; + + StringLiteralIterator() = default; + + explicit StringLiteralIterator(const char* data) + : m_data(data) + { + } + + value_type operator*() const + { + return m_data ? *m_data : 0; + } + + StringLiteralIterator& operator++() + { + ++m_data; + return *this; + } + + void operator++(int) + { + ++m_data; + } + + friend bool operator==(const StringLiteralIterator& lhs, + StringLiteralSentinel) + { + return *lhs == 0; + } + + friend bool operator==(StringLiteralSentinel, + const StringLiteralIterator& rhs) + { + return *rhs == 0; + } + + friend bool operator!=(const StringLiteralIterator& lhs, + StringLiteralSentinel) + { + return *lhs != 0; + } + + friend bool operator!=(StringLiteralSentinel, + const StringLiteralIterator& rhs) + { + return *rhs != 0; + } + +private: + + const char* m_data = nullptr; +}; + +// forward only, non-common range, as a stress on range member functions +class TestInputStringLiteralRange +{ +public: + + explicit TestInputStringLiteralRange(const char* data) + : m_data(data) + { + } + + StringLiteralIterator begin() + { + return StringLiteralIterator(m_data); + } + + StringLiteralSentinel end() + { + return StringLiteralSentinel{}; + } + +private: + + const char* m_data = nullptr; +}; + +} // namespace radtest diff --git a/test/test_Iterator.cpp b/test/test_Iterator.cpp index 516e433..2fc3ae9 100644 --- a/test/test_Iterator.cpp +++ b/test/test_Iterator.cpp @@ -44,7 +44,9 @@ TEST(IteratorTest, DefaultConstruct) EXPECT_EQ(it.operator->(), nullptr); RIt rit; - EXPECT_EQ(rit.operator->(), nullptr); + RAD_UNUSED(rit); + // UB + // EXPECT_EQ(rit.operator->(), nullptr); } TEST(InteratorTest, ValueConstruct) @@ -52,7 +54,7 @@ TEST(InteratorTest, ValueConstruct) It it(g_data); EXPECT_EQ(it.operator->(), g_data); - RIt rit(g_data); + RIt rit(g_data + 1); EXPECT_EQ(rit.operator->(), g_data); } @@ -72,7 +74,7 @@ TEST(InteratorTest, DereferenceOperator) It it(g_data); EXPECT_EQ(&it.operator*(), g_data); - RIt rit(g_data); + RIt rit(g_data + 1); EXPECT_EQ(&rit.operator*(), g_data); } @@ -82,7 +84,7 @@ TEST(InteratorTest, SubscriptOperator) EXPECT_EQ(&it.operator[](0), g_data); EXPECT_EQ(&it.operator[](1), g_data + 1); - RIt rit(g_data + 1); + RIt rit(g_data + 2); EXPECT_EQ(&rit.operator[](0), g_data + 1); EXPECT_EQ(&rit.operator[](1), g_data); } @@ -93,7 +95,7 @@ TEST(InteratorTest, Increment) it++; EXPECT_EQ(it.operator->(), g_data + 1); - RIt rit(g_data + 1); + RIt rit(g_data + 2); rit++; EXPECT_EQ(rit.operator->(), g_data); } @@ -104,7 +106,7 @@ TEST(InteratorTest, PreIncrement) ++it; EXPECT_EQ(it.operator->(), g_data + 1); - RIt rit(g_data + 1); + RIt rit(g_data + 2); ++rit; EXPECT_EQ(rit.operator->(), g_data); } @@ -115,7 +117,7 @@ TEST(InteratorTest, Decrement) it--; EXPECT_EQ(it.operator->(), g_data); - RIt rit(g_data); + RIt rit(g_data + 1); rit--; EXPECT_EQ(rit.operator->(), g_data + 1); } @@ -126,7 +128,7 @@ TEST(InteratorTest, PreDecrement) --it; EXPECT_EQ(it.operator->(), g_data); - RIt rit(g_data); + RIt rit(g_data + 1); --rit; EXPECT_EQ(rit.operator->(), g_data + 1); } @@ -137,7 +139,7 @@ TEST(InteratorTest, CompoundIncrement) it += 1; EXPECT_EQ(it.operator->(), g_data + 1); - RIt rit(g_data + 1); + RIt rit(g_data + 2); rit += 1; EXPECT_EQ(rit.operator->(), g_data); } @@ -148,7 +150,7 @@ TEST(InteratorTest, CompoundDecrement) it -= 1; EXPECT_EQ(it.operator->(), g_data); - RIt rit(g_data); + RIt rit(g_data + 1); rit -= 1; EXPECT_EQ(rit.operator->(), g_data + 1); } @@ -159,7 +161,7 @@ TEST(InteratorTest, AdditionOperator) it = it + 1; EXPECT_EQ(it.operator->(), g_data + 1); - RIt rit(g_data + 1); + RIt rit(g_data + 2); rit = rit + 1; EXPECT_EQ(rit.operator->(), g_data); } @@ -170,7 +172,7 @@ TEST(InteratorTest, SubtractionOperator) it = it - 1; EXPECT_EQ(it.operator->(), g_data); - RIt rit(g_data); + RIt rit(g_data + 1); rit = rit - 1; EXPECT_EQ(rit.operator->(), g_data + 1); } diff --git a/test/test_List.cpp b/test/test_List.cpp new file mode 100644 index 0000000..38f5b71 --- /dev/null +++ b/test/test_List.cpp @@ -0,0 +1,2239 @@ +// Copyright 2024 The Radiant 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 +// +// http://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. +#define RAD_DEFAULT_ALLOCATOR radtest::Allocator +#include "test/TestAlloc.h" +#include "test/TestInputStringLiteralRange.h" + +#include "radiant/List.h" +#include "radiant/Span.h" + +#include +#include +#include +#include + +using namespace testing; + +// ensure no_unique_address is doing what we want +RAD_S_ASSERTMSG(sizeof(rad::detail::ListUntyped) == 2 * sizeof(void*), + "unexpected object size"); +RAD_S_ASSERTMSG(sizeof(rad::List) == 2 * sizeof(void*), + "unexpected object size"); +RAD_S_ASSERTMSG(sizeof(rad::detail::ListUntyped) == sizeof(rad::List), + "unexpected object size"); + +template +void ListEqual(const rad::List& list, + std::initializer_list init_list) +{ + size_t expensive_size = list.ExpensiveSize(); + EXPECT_EQ(expensive_size, init_list.size()); + EXPECT_EQ(expensive_size == 0, list.Empty()); + auto it = list.begin(); + for (const T& elt : init_list) + { + EXPECT_EQ(elt, *it); + ++it; + } + EXPECT_EQ(it, list.end()); +} + +// for use with ImmovableStruct and friends +template +void ListValEqual(const rad::List& list, + std::initializer_list init_list) +{ + size_t expensive_size = list.ExpensiveSize(); + EXPECT_EQ(expensive_size, init_list.size()); + EXPECT_EQ(expensive_size == 0, list.Empty()); + auto it = list.begin(); + for (const U& elt : init_list) + { + EXPECT_EQ(elt, it->val); + ++it; + } + EXPECT_EQ(it, list.end()); +} + +TEST(ListTest, DefaultConstructIsEmpty) +{ + rad::List i; + EXPECT_TRUE(i.Empty()); + EXPECT_TRUE(i.begin() == i.end()); + EXPECT_TRUE(i.cbegin() == i.cend()); + const rad::List j; + EXPECT_TRUE(j.Empty()); + EXPECT_TRUE(j.begin() == j.end()); + EXPECT_TRUE(j.cbegin() == j.cend()); +} + +TEST(ListTest, AllocatorConstructors) +{ + using StatefulAlloc = radtest::StatefulAllocator; + { + rad::List default_ctor; + StatefulAlloc default_ctor_alloc = default_ctor.GetAllocator(); + EXPECT_EQ(default_ctor_alloc.m_state, 0u); + } + { + // allocator constructor + StatefulAlloc source_alloc; + source_alloc.m_state = 42; + rad::List alloc_ctor(source_alloc); + StatefulAlloc alloc = alloc_ctor.GetAllocator(); + EXPECT_EQ(alloc.m_state, 42u); + + // regular move constructor needs to steal the original allocator + rad::List moving_alloc_ctor(std::move(alloc_ctor)); + StatefulAlloc moved_from_alloc = alloc_ctor.GetAllocator(); + EXPECT_EQ(moved_from_alloc.m_state, radtest::k_MovedFromState); + StatefulAlloc moved_to_alloc = moving_alloc_ctor.GetAllocator(); + EXPECT_EQ(moved_to_alloc.m_state, 42u); + + StatefulAlloc source_alloc2; + source_alloc2.m_state = 99; + rad::List move_assigned(source_alloc2); + move_assigned = std::move(moving_alloc_ctor); + StatefulAlloc assigned_from_alloc = moving_alloc_ctor.GetAllocator(); + + // assigned_from_alloc should have whatever move_assigned had in it + // before + EXPECT_EQ(assigned_from_alloc.m_state, 99u); + StatefulAlloc assigned_to_alloc = move_assigned.GetAllocator(); + EXPECT_EQ(assigned_to_alloc.m_state, 42u); + } +} + +TEST(ListTest, PushBackFailureRecovery) +{ + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + + { + rad::List> list( + alloc); + + EXPECT_TRUE(list.PushBack(1).IsOk()); + EXPECT_EQ(heap.allocCount, 1); + EXPECT_EQ(heap.freeCount, 0); + + heap.forceAllocFails = 1; + EXPECT_EQ(rad::Error::NoMemory, list.PushBack(2)); + EXPECT_EQ(heap.allocCount, 1); + EXPECT_EQ(heap.freeCount, 0); + ListEqual(list, { 1 }); + + EXPECT_TRUE(list.PushBack(3).IsOk()); + EXPECT_EQ(heap.allocCount, 2); + EXPECT_EQ(heap.freeCount, 0); + ListEqual(list, { 1, 3 }); + } + EXPECT_EQ(heap.allocCount, 2); + EXPECT_EQ(heap.freeCount, 2); +} + +TEST(ListTest, DefaultConstructClear) +{ + rad::List i; + i.Clear(); + EXPECT_TRUE(i.Empty()); +} + +struct CopyStruct +{ + int val = 42; + CopyStruct() = default; + CopyStruct(const CopyStruct&) = default; + CopyStruct& operator=(const CopyStruct&) = default; + + // intentionally noexcept(false) + CopyStruct(CopyStruct&& other) + : val(other.val) + { + other.val = -1; + } + + // intentionally noexcept(false) + CopyStruct& operator=(CopyStruct&& other) + { + val = other.val; + other.val = -1; + return *this; + } +}; + +TEST(ListTest, CopyPushBack) +{ + rad::List i; + CopyStruct local; + local.val = 42; + EXPECT_TRUE(i.PushBack(local).IsOk()); + ListValEqual(i, { 42 }); + EXPECT_EQ(local.val, 42); + + local.val = 99; + EXPECT_TRUE(i.PushBack(local).IsOk()); + ListValEqual(i, { 42, 99 }); + EXPECT_EQ(local.val, 99); + + local.val = 77; + EXPECT_TRUE(i.PushBack(local).IsOk()); + ListValEqual(i, { 42, 99, 77 }); + EXPECT_EQ(local.val, 77); +} + +TEST(ListTest, CopyPushFront) +{ + rad::List i; + CopyStruct local; + local.val = 42; + EXPECT_TRUE(i.PushFront(local).IsOk()); + ListValEqual(i, { 42 }); + EXPECT_EQ(local.val, 42); + + local.val = 99; + EXPECT_TRUE(i.PushFront(local).IsOk()); + ListValEqual(i, { 99, 42 }); + EXPECT_EQ(local.val, 99); + + local.val = 77; + EXPECT_TRUE(i.PushFront(local).IsOk()); + ListValEqual(i, { 77, 99, 42 }); + EXPECT_EQ(local.val, 77); +} + +struct MoveStruct +{ + MoveStruct() = default; + MoveStruct(const MoveStruct&) = delete; + MoveStruct& operator=(const MoveStruct&) = delete; + + MoveStruct(MoveStruct&& other) noexcept + : val(other.val) + { + other.val = -1; + } + + MoveStruct& operator=(MoveStruct&& other) noexcept + { + val = other.val; + other.val = -2; + return *this; + } + + int val = 42; +}; + +TEST(ListTest, MovePushBack) +{ + { + rad::List i; + MoveStruct local; + EXPECT_TRUE(i.PushBack(std::move(local)).IsOk()); + ListValEqual(i, { 42 }); + EXPECT_EQ(local.val, -1); + + local.val = 99; + EXPECT_TRUE(i.PushBack(std::move(local)).IsOk()); + ListValEqual(i, { 42, 99 }); + EXPECT_EQ(local.val, -1); + + local.val = 77; + EXPECT_TRUE(i.PushBack(std::move(local)).IsOk()); + ListValEqual(i, { 42, 99, 77 }); + EXPECT_EQ(local.val, -1); + } + { + rad::List> lli; + rad::List ints; + + EXPECT_TRUE( + ints.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + EXPECT_TRUE(lli.PushBack(std::move(ints)).IsOk()); + EXPECT_TRUE(ints.Empty()); + EXPECT_EQ(lli.ExpensiveSize(), 1u); + ListEqual(*lli.begin(), { 1, 2, 3 }); + + EXPECT_TRUE( + ints.AssignRange(std::initializer_list{ 4, 5, 6 }).IsOk()); + EXPECT_TRUE(lli.PushBack(std::move(ints)).IsOk()); + EXPECT_TRUE(ints.Empty()); + EXPECT_EQ(lli.ExpensiveSize(), 2u); + ListEqual(*lli.begin(), { 1, 2, 3 }); + ListEqual(*++lli.begin(), { 4, 5, 6 }); + } +} + +TEST(ListTest, MovePushFront) +{ + rad::List i; + MoveStruct local; + EXPECT_TRUE(i.PushFront(std::move(local)).IsOk()); + ListValEqual(i, { 42 }); + EXPECT_EQ(local.val, -1); + + local.val = 99; + EXPECT_TRUE(i.PushFront(std::move(local)).IsOk()); + ListValEqual(i, { 99, 42 }); + EXPECT_EQ(local.val, -1); + + local.val = 77; + EXPECT_TRUE(i.PushFront(std::move(local)).IsOk()); + ListValEqual(i, { 77, 99, 42 }); + EXPECT_EQ(local.val, -1); +} + +struct ImmovableStruct +{ + explicit ImmovableStruct(int x) + : val(x) + { + } + + ImmovableStruct() = delete; + ImmovableStruct(const ImmovableStruct&) = delete; + ImmovableStruct& operator=(const ImmovableStruct&) = delete; + + ImmovableStruct(ImmovableStruct&&) = delete; + ImmovableStruct& operator=(ImmovableStruct&&) = delete; + const int val; +}; + +TEST(ListTest, ImmovableEmplaceBack) +{ + rad::List i; + EXPECT_TRUE(i.EmplaceBack(42).IsOk()); + ListValEqual(i, { 42 }); + + EXPECT_TRUE(i.EmplaceBack(99).IsOk()); + ListValEqual(i, { 42, 99 }); + + EXPECT_TRUE(i.EmplaceBack(77).IsOk()); + ListValEqual(i, { 42, 99, 77 }); +} + +TEST(ListTest, ImmovableEmplaceFront) +{ + rad::List i; + EXPECT_TRUE(i.EmplaceFront(42).IsOk()); + ListValEqual(i, { 42 }); + + EXPECT_TRUE(i.EmplaceFront(99).IsOk()); + ListValEqual(i, { 99, 42 }); + + EXPECT_TRUE(i.EmplaceFront(77).IsOk()); + ListValEqual(i, { 77, 99, 42 }); +} + +#if RAD_ENABLE_STD +TEST(ListTest, AssignInitList) +{ + rad::List li; + EXPECT_TRUE(li.AssignInitializerList({}).IsOk()); + EXPECT_TRUE(li.Empty()); + + EXPECT_TRUE(li.AssignInitializerList({ 42 }).IsOk()); + EXPECT_EQ(1u, li.ExpensiveSize()); + + EXPECT_TRUE(li.AssignInitializerList({ 100, 101, 102, 103 }).IsOk()); + EXPECT_EQ(4u, li.ExpensiveSize()); + + int i = 100; + for (int elt : li) + { + EXPECT_EQ(i, elt); + ++i; + } + + EXPECT_TRUE(li.AssignInitializerList({}).IsOk()); + EXPECT_TRUE(li.Empty()); +} +#endif + +TEST(ListTest, MoveConstruct) +{ + { + rad::List empty; + rad::List move_from_empty(std::move(empty)); + ListEqual(empty, {}); + ListEqual(move_from_empty, {}); + } + { + rad::List one; + EXPECT_TRUE(one.PushBack(1).IsOk()); + rad::List move_from_one(std::move(one)); + + ListEqual(one, {}); + ListEqual(move_from_one, { 1 }); + } + { + rad::List two; + EXPECT_TRUE(two.AssignRange(std::initializer_list{ 1, 2 }).IsOk()); + + rad::List move_from_two(std::move(two)); + ListEqual(two, {}); + ListEqual(move_from_two, { 1, 2 }); + } + { + rad::List three; + EXPECT_TRUE( + three.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + + rad::List move_from_three(std::move(three)); + ListEqual(three, {}); + ListEqual(move_from_three, { 1, 2, 3 }); + } + { + rad::List one; + EXPECT_TRUE(one.PushBack(1).IsOk()); + + rad::List move_from_one(std::move(one)); + + // ensure we can still mutate after moves + EXPECT_TRUE(one.PushBack(101).IsOk()); + EXPECT_TRUE(move_from_one.PushBack(201).IsOk()); + ListEqual(one, { 101 }); + ListEqual(move_from_one, { 1, 201 }); + } +} + +TEST(ListTest, ClearSome) +{ + rad::List i; + EXPECT_TRUE(i.PushBack(1).IsOk()); + EXPECT_FALSE(i.Empty()); + i.Clear(); + EXPECT_TRUE(i.Empty()); + + EXPECT_TRUE(i.AssignRange(std::initializer_list{ 2, 3 }).IsOk()); + EXPECT_EQ(i.ExpensiveSize(), 2u); + i.Clear(); + EXPECT_TRUE(i.Empty()); + + EXPECT_TRUE(i.AssignRange(std::initializer_list{ 4, 5, 6 }).IsOk()); + EXPECT_EQ(i.ExpensiveSize(), 3u); + i.Clear(); + EXPECT_TRUE(i.Empty()); +} + +#if RAD_WINDOWS +// MSVC warns that the body of the first for loop is unreachable, which is true. +// But that's also what the test is trying to ensure. So disable the unhelpful +// warning. Doing this for the whole function, because narrower scoped +// disables aren't getting accepted. +#pragma warning(push) +#pragma warning(disable : 4702) // C4702: unreachable code +#endif +TEST(ListTest, RangeForLoop) +{ + rad::List input; + + for (int elt : input) + { + (void)elt; + FAIL() << "should be empty"; + } + + EXPECT_TRUE( + input.AssignRange(std::initializer_list{ 0, 1, 2 }).IsOk()); + + // ensure that the references we get are "real" + int i = 0; + for (int& elt : input) + { + EXPECT_EQ(elt, i); + elt = 100 + i; + ++i; + } + + i = 100; + for (int elt : input) + { + EXPECT_EQ(elt, i); + ++i; + } +} +#if RAD_WINDOWS +#pragma warning(pop) +#endif + +TEST(ListTest, AssignSome) +{ + rad::List input; + int* np = nullptr; + EXPECT_TRUE(input.AssignSome(np, np).IsOk()); + EXPECT_TRUE(input.Empty()); + + int arr[] = { 101, 203, 304 }; + EXPECT_TRUE(input.AssignSome(std::begin(arr), std::end(arr)).IsOk()); + ListEqual(input, { 101, 203, 304 }); + + rad::List new_copy; + EXPECT_TRUE(new_copy.AssignSome(input.begin(), input.end()).IsOk()); + ListEqual(input, { 101, 203, 304 }); +} + +TEST(ListTest, AssignRange) +{ + rad::List input; + rad::Span empty; + + EXPECT_TRUE(input.AssignRange(empty).IsOk()); + EXPECT_TRUE(input.Empty()); + + std::array arr = { 101, 203, 304 }; + EXPECT_TRUE(input.AssignRange(arr).IsOk()); + ListEqual(input, { 101, 203, 304 }); + + rad::List new_copy; + EXPECT_TRUE(new_copy.AssignRange(input).IsOk()); + ListEqual(input, { 101, 203, 304 }); + + rad::List chars; + EXPECT_TRUE( + chars.AssignRange(radtest::TestInputStringLiteralRange("abc")).IsOk()); + ListEqual(chars, { 'a', 'b', 'c' }); +} + +TEST(ListTest, AssignCount) +{ + rad::List input; + + EXPECT_TRUE(input.AssignCount(0, 42).IsOk()); + EXPECT_TRUE(input.Empty()); + + EXPECT_TRUE(input.AssignCount(3, 99).IsOk()); + ListEqual(input, { 99, 99, 99 }); +} + +TEST(ListTest, Emplace) +{ + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> + input(alloc); + + // emplace at the end + auto /* Res */ iter = input.Emplace(input.end(), 42); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == --input.end()); + EXPECT_EQ(iter.Ok()->val, 42); + iter = input.Emplace(input.end(), 43); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == --input.end()); + EXPECT_EQ(iter.Ok()->val, 43); + ListValEqual(input, { 42, 43 }); + + // emplace at the beginning + iter = input.Emplace(input.begin(), 99); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == input.begin()); + EXPECT_EQ(iter.Ok()->val, 99); + iter = input.Emplace(input.begin(), 100); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == input.begin()); + EXPECT_EQ(iter.Ok()->val, 100); + ListValEqual(input, { 100, 99, 42, 43 }); + + // emplace near the beginning + auto old_iter = ++input.begin(); + iter = input.Emplace(old_iter, 23); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == ++input.begin()); + EXPECT_TRUE(iter != old_iter); + ListValEqual(input, { 100, 23, 99, 42, 43 }); + + // emplace near the end + old_iter = --input.end(); + iter = input.Emplace(old_iter, 77); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == --(--input.end())); + EXPECT_TRUE(iter != old_iter); + ListValEqual(input, { 100, 23, 99, 42, 77, 43 }); + + heap.forceAllocFails = 1; + iter = input.Emplace(input.begin(), -1); + EXPECT_TRUE(iter.IsErr()); + ListValEqual(input, { 100, 23, 99, 42, 77, 43 }); +} + +TEST(ListTest, MoveInsert) +{ + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> + input(alloc); + + MoveStruct ms; + ms.val = 42; + // insert at the end + auto /* Res */ iter = + input.Insert(input.end(), std::move(ms)); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == --input.end()); + EXPECT_EQ(iter.Ok()->val, 42); + EXPECT_EQ(ms.val, -1); + + ms.val = 43; + iter = input.Insert(input.end(), std::move(ms)); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == --input.end()); + EXPECT_EQ(iter.Ok()->val, 43); + ListValEqual(input, { 42, 43 }); + EXPECT_EQ(ms.val, -1); + + // insert at the beginning + ms.val = 99; + iter = input.Insert(input.begin(), std::move(ms)); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == input.begin()); + EXPECT_EQ(iter.Ok()->val, 99); + EXPECT_EQ(ms.val, -1); + + ms.val = 100; + iter = input.Insert(input.begin(), std::move(ms)); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == input.begin()); + EXPECT_EQ(iter.Ok()->val, 100); + ListValEqual(input, { 100, 99, 42, 43 }); + EXPECT_EQ(ms.val, -1); + + // insert near the beginning + ms.val = 23; + auto old_iter = ++input.begin(); + iter = input.Insert(old_iter, std::move(ms)); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == ++input.begin()); + EXPECT_TRUE(iter != old_iter); + ListValEqual(input, { 100, 23, 99, 42, 43 }); + EXPECT_EQ(ms.val, -1); + + // insert near the end + ms.val = 77; + old_iter = --input.end(); + iter = input.Insert(old_iter, std::move(ms)); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == --(--input.end())); + EXPECT_TRUE(iter != old_iter); + ListValEqual(input, { 100, 23, 99, 42, 77, 43 }); + EXPECT_EQ(ms.val, -1); + + ms.val = -100; + heap.forceAllocFails = 1; + iter = input.Insert(input.begin(), std::move(ms)); + EXPECT_TRUE(iter.IsErr()); + ListValEqual(input, { 100, 23, 99, 42, 77, 43 }); + EXPECT_EQ(ms.val, -100); +} + +TEST(ListTest, CopyInsert) +{ + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> + input(alloc); + + CopyStruct cs; + cs.val = 42; + // insert at the end + auto /* Res */ iter = input.Insert(input.end(), cs); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == --input.end()); + EXPECT_EQ(iter.Ok()->val, 42); + EXPECT_EQ(cs.val, 42); + + cs.val = 43; + iter = input.Insert(input.end(), cs); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == --input.end()); + EXPECT_EQ(iter.Ok()->val, 43); + ListValEqual(input, { 42, 43 }); + EXPECT_EQ(cs.val, 43); + + // insert at the beginning + cs.val = 99; + iter = input.Insert(input.begin(), cs); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == input.begin()); + EXPECT_EQ(iter.Ok()->val, 99); + EXPECT_EQ(cs.val, 99); + + cs.val = 100; + iter = input.Insert(input.begin(), cs); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == input.begin()); + EXPECT_EQ(iter.Ok()->val, 100); + ListValEqual(input, { 100, 99, 42, 43 }); + EXPECT_EQ(cs.val, 100); + + // insert near the beginning + cs.val = 23; + auto old_iter = ++input.begin(); + iter = input.Insert(old_iter, cs); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == ++input.begin()); + EXPECT_TRUE(iter != old_iter); + ListValEqual(input, { 100, 23, 99, 42, 43 }); + EXPECT_EQ(cs.val, 23); + + // insert near the end + cs.val = 77; + old_iter = --input.end(); + iter = input.Insert(old_iter, cs); + ASSERT_TRUE(iter); + EXPECT_TRUE(iter == --(--input.end())); + EXPECT_TRUE(iter != old_iter); + ListValEqual(input, { 100, 23, 99, 42, 77, 43 }); + EXPECT_EQ(cs.val, 77); + + cs.val = -100; + heap.forceAllocFails = 1; + iter = input.Insert(input.begin(), cs); + EXPECT_TRUE(iter.IsErr()); + ListValEqual(input, { 100, 23, 99, 42, 77, 43 }); + EXPECT_EQ(cs.val, -100); +} + +TEST(ListTest, AssignFailure) +{ + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> list( + alloc); + + // AssignCount fails back to empty when it starts empty + heap.forceFutureAllocFail = 3; + EXPECT_TRUE(list.AssignCount(5, 99).IsErr()); + EXPECT_EQ(heap.allocCount, 2); + EXPECT_EQ(heap.freeCount, 2); + EXPECT_TRUE(list.Empty()); + + // AssignInitializerList fails back to empty when it starts empty + heap.allocCount = heap.freeCount = 0; + heap.forceFutureAllocFail = 3; + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3, 4, 5 }).IsErr()); + EXPECT_EQ(heap.allocCount, 2); + EXPECT_EQ(heap.freeCount, 2); + EXPECT_TRUE(list.Empty()); + + // make sure nothing is corrupted + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3, 4, 5 }).IsOk()); + ListEqual(list, { 1, 2, 3, 4, 5 }); + auto old_begin = list.begin(); + auto old_end = list.end(); + int* first_addr = &*old_begin; + + // Original contents still in place if there's a failure + heap.allocCount = heap.freeCount = 0; + heap.forceFutureAllocFail = 5; + EXPECT_TRUE(list.AssignCount(5, 99).IsErr()); + EXPECT_EQ(heap.allocCount, 4); + EXPECT_EQ(heap.freeCount, 4); + EXPECT_EQ(list.end(), old_end); + EXPECT_EQ(list.begin(), old_begin); + EXPECT_EQ(&*list.begin(), first_addr); + ListEqual(list, { 1, 2, 3, 4, 5 }); + + heap.allocCount = heap.freeCount = 0; + heap.forceFutureAllocFail = 5; + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 101, 102, 103, 104, 105 }) + .IsErr()); + EXPECT_EQ(heap.allocCount, 4); + EXPECT_EQ(heap.freeCount, 4); + EXPECT_EQ(list.end(), old_end); + EXPECT_EQ(list.begin(), old_begin); + EXPECT_EQ(&*list.begin(), first_addr); + ListEqual(list, { 1, 2, 3, 4, 5 }); +} + +TEST(ListTest, PostIncrPostDecr) +{ + rad::List data; + EXPECT_TRUE(data.AssignRange(std::initializer_list{ 0, 1, 2, 3 })); + auto pre_begin = data.begin(); + auto post_begin = data.begin(); + EXPECT_EQ(pre_begin, post_begin++); + EXPECT_EQ(*pre_begin, 0); + EXPECT_EQ(*post_begin, 1); + EXPECT_NE(pre_begin, post_begin); + EXPECT_EQ(++pre_begin, post_begin); + EXPECT_EQ(*pre_begin, 1); + EXPECT_EQ(*post_begin, 1); + EXPECT_EQ(*++pre_begin, 2); + EXPECT_EQ(*post_begin++, 1); + + auto pre_end = --data.end(); + auto post_end = --data.end(); + EXPECT_EQ(pre_end, post_end--); + EXPECT_EQ(*pre_end, 3); + EXPECT_EQ(*post_end, 2); + EXPECT_NE(pre_end, post_end); + EXPECT_EQ(--pre_end, post_end); + EXPECT_EQ(*pre_end, 2); + EXPECT_EQ(*post_end, 2); + EXPECT_EQ(*--pre_end, 1); + EXPECT_EQ(*post_end--, 2); + + auto pre_cbegin = data.cbegin(); + auto post_cbegin = data.cbegin(); + EXPECT_EQ(pre_cbegin, post_cbegin++); + EXPECT_EQ(*pre_cbegin, 0); + EXPECT_EQ(*post_cbegin, 1); + EXPECT_NE(pre_cbegin, post_cbegin); + EXPECT_EQ(++pre_cbegin, post_cbegin); + EXPECT_EQ(*pre_cbegin, 1); + EXPECT_EQ(*post_cbegin, 1); + EXPECT_EQ(*++pre_cbegin, 2); + EXPECT_EQ(*post_cbegin++, 1); + + auto pre_cend = --data.cend(); + auto post_cend = --data.cend(); + EXPECT_EQ(pre_cend, post_cend--); + EXPECT_EQ(*pre_cend, 3); + EXPECT_EQ(*post_cend, 2); + EXPECT_NE(pre_cend, post_cend); + EXPECT_EQ(--pre_cend, post_cend); + EXPECT_EQ(*pre_cend, 2); + EXPECT_EQ(*post_cend, 2); + EXPECT_EQ(*--pre_cend, 1); + EXPECT_EQ(*post_cend--, 2); +} + +TEST(ListTest, SpliceSomeEmpties) +{ + std::array arr = { 101, 203, 304 }; + { + // double empty + rad::List source; + rad::List dest; + + dest.SpliceSome(dest.begin(), source, source.begin(), source.end()); + EXPECT_TRUE(source.Empty()); + EXPECT_TRUE(dest.Empty()); + + dest.SpliceSome(dest.begin(), + std::move(source), + source.begin(), + source.end()); + EXPECT_TRUE(source.Empty()); + EXPECT_TRUE(dest.Empty()); + + dest.SpliceSome(dest.end(), source, source.begin(), source.end()); + EXPECT_TRUE(source.Empty()); + EXPECT_TRUE(dest.Empty()); + + dest.SpliceSome(dest.end(), + std::move(source), + source.begin(), + source.end()); + EXPECT_TRUE(source.Empty()); + EXPECT_TRUE(dest.Empty()); + } + + { + // source empty + rad::List source; + rad::List dest; + + EXPECT_TRUE(dest.AssignRange(arr).IsOk()); + ListEqual(dest, { 101, 203, 304 }); + + dest.SpliceSome(dest.begin(), source, source.begin(), source.end()); + EXPECT_TRUE(source.Empty()); + ListEqual(dest, { 101, 203, 304 }); + + dest.SpliceSome(dest.begin(), + std::move(source), + source.begin(), + source.end()); + EXPECT_TRUE(source.Empty()); + ListEqual(dest, { 101, 203, 304 }); + } + + { + // dest empty lval + rad::List source; + rad::List dest; + + EXPECT_TRUE(source.AssignRange(arr).IsOk()); + ListEqual(source, { 101, 203, 304 }); + + dest.SpliceSome(dest.begin(), source, source.begin(), source.end()); + EXPECT_TRUE(source.Empty()); + ListEqual(dest, { 101, 203, 304 }); + + dest.Clear(); + EXPECT_TRUE(dest.Empty()); + EXPECT_TRUE(source.AssignRange(arr).IsOk()); + ListEqual(source, { 101, 203, 304 }); + + dest.SpliceSome(dest.end(), source, source.begin(), source.end()); + EXPECT_TRUE(source.Empty()); + ListEqual(dest, { 101, 203, 304 }); + } + + { + // dest empty rval + rad::List source; + rad::List dest; + + EXPECT_TRUE(source.AssignRange(arr).IsOk()); + ListEqual(source, { 101, 203, 304 }); + + dest.SpliceSome(dest.begin(), + std::move(source), + source.begin(), + source.end()); + EXPECT_TRUE(source.Empty()); + ListEqual(dest, { 101, 203, 304 }); + + dest.Clear(); + EXPECT_TRUE(dest.Empty()); + EXPECT_TRUE(source.AssignRange(arr).IsOk()); + ListEqual(source, { 101, 203, 304 }); + + dest.SpliceSome(dest.end(), + std::move(source), + source.begin(), + source.end()); + EXPECT_TRUE(source.Empty()); + ListEqual(dest, { 101, 203, 304 }); + } +} + +struct SpliceSomeExhaustive_data +{ + SpliceSomeExhaustive_data( + int src_size, int dest_size, int src_begin, int src_end, int dest_pos) + : src_size(src_size), + dest_size(dest_size), + src_begin(src_begin), + src_end(src_end), + dest_pos(dest_pos) + { + } + + int src_size; + int dest_size; + int src_begin; + int src_end; + int dest_pos; + rad::List source; + rad::List dest; + rad::List::IteratorType src_begin_iter; + rad::List::IteratorType src_end_iter; + rad::List::IteratorType dest_pos_iter; + + void BuildSrc() + { + src_begin_iter = source.end(); // .end() is the base case + src_end_iter = source.end(); + for (int i = 0; i < src_size; ++i) + { + // populate with i offset by 100, so that we can tell src and dest + // apart + ASSERT_TRUE(source.PushBack(i + 100).IsOk()); + if (i == src_begin) + { + src_begin_iter = --source.end(); + } + if (i == src_end) + { + src_end_iter = --source.end(); + } + } + } + + void BuildDest() + { + dest_pos_iter = dest.end(); // base case + for (int i = 0; i < dest_size; ++i) + { + ASSERT_TRUE(dest.PushBack(i).IsOk()); + if (i == dest_pos) + { + dest_pos_iter = --dest.end(); + } + } + } + + void VerifySrc(const std::string& case_id) + { + EXPECT_EQ((int)source.ExpensiveSize(), src_size - (src_end - src_begin)) + << case_id; + int i = 0; + for (auto it = source.begin(); it != source.end(); ++it, ++i) + { + if (i < src_begin) + { + EXPECT_EQ(*it, 100 + i) << i << case_id; + continue; + } + if (i < src_size - (src_end - src_begin)) + { + EXPECT_EQ(*it, 100 + i + (src_end - src_begin)) << i << case_id; + continue; + } + EXPECT_EQ(-1, *it) << "should be unreachable " << i << case_id; + } + } + + void VerifyDest(const std::string& case_id) + { + EXPECT_EQ((int)dest.ExpensiveSize(), dest_size + (src_end - src_begin)) + << case_id; + int i = 0; + for (auto it = dest.begin(); it != dest.end(); ++it, ++i) + { + if (i < dest_pos) + { + EXPECT_EQ(*it, i) << i << case_id; + continue; + } + if (i < dest_pos + (src_end - src_begin)) + { + int src_idx = (i - dest_pos) + src_begin; + EXPECT_EQ(*it, 100 + src_idx) << i << case_id; + continue; + } + if (i < dest_size + (src_end - src_begin)) + { + EXPECT_EQ(*it, i - (src_end - src_begin)) << i << case_id; + continue; + } + EXPECT_EQ(-1, *it) << "should be unreachable " << i << case_id; + } + } + + void Verify(const std::string& case_id) + { + VerifySrc(case_id); + VerifyDest(case_id); + } + + void Test() + { + // stringifying parameters so that failures have a chance at being + // useful. + std::string case_id = + " case id: " + std::to_string(src_size) + ", " + + std::to_string(dest_size) + ", " + std::to_string(src_begin) + + ", " + std::to_string(src_end) + ", " + std::to_string(dest_pos); + + // Populate Src with {100, 101, ...} and Dest with {0, 1, ...}. + // Then Splice a subrange of Src into a position in Dest. + BuildSrc(); + BuildDest(); + + dest.SpliceSome(dest_pos_iter, source, src_begin_iter, src_end_iter); + + Verify(case_id); + } + + void TestRValue() + { + // stringifying parameters so that failures have a chance at being + // useful. + std::string case_id = + " case id: rvalue " + std::to_string(src_size) + ", " + + std::to_string(dest_size) + ", " + std::to_string(src_begin) + + ", " + std::to_string(src_end) + ", " + std::to_string(dest_pos); + + // Populate Src with {100, 101, ...} and Dest with {0, 1, ...}. + // Then Splice a subrange of Src into a position in Dest. + BuildSrc(); + BuildDest(); + + dest.SpliceSome(dest_pos_iter, + std::move(source), + src_begin_iter, + src_end_iter); + + Verify(case_id); + } +}; + +TEST(ListTest, SpliceSomeExhaustive) +{ + const int kMaxListSize = 3; + // dimensions: Source size, dest size, dest pos, source begin, source end + // 3 * 3 * 4 * 4 * 4 = 576 iterations (less than that actually) + // not testing empty containers, but we are testing empty ranges + for (int src_size = 1; src_size <= kMaxListSize; ++src_size) + { + for (int dest_size = 1; dest_size <= kMaxListSize; ++dest_size) + { + for (int dest_pos = 0; dest_pos <= dest_size; ++dest_pos) + { + for (int src_begin = 0; src_begin <= src_size; ++src_begin) + { + for (int src_end = src_begin; src_end <= src_size; + ++src_end) + { + SpliceSomeExhaustive_data data{ src_size, + dest_size, + src_begin, + src_end, + dest_pos }; + data.Test(); + SpliceSomeExhaustive_data data2{ src_size, + dest_size, + src_begin, + src_end, + dest_pos }; + data2.TestRValue(); + } + } + } + } + } +} + +struct SpliceOneExhaustive_data +{ + SpliceOneExhaustive_data(int src_size, + int dest_size, + int src_pos, + int dest_pos) + : src_size(src_size), + dest_size(dest_size), + src_pos(src_pos), + dest_pos(dest_pos) + { + } + + int src_size; + int dest_size; + int src_pos; + int dest_pos; + rad::List source; + rad::List dest; + rad::List::IteratorType src_pos_iter; + rad::List::IteratorType dest_pos_iter; + + void BuildSrc() + { + src_pos_iter = source.end(); + for (int i = 0; i < src_size; ++i) + { + // populate with i offset by 100, so that we can tell src and dest + // apart + ASSERT_TRUE(source.PushBack(i + 100).IsOk()); + if (i == src_pos) + { + src_pos_iter = --source.end(); + } + } + ASSERT_NE(src_pos_iter, source.end()); + } + + void BuildDest() + { + dest_pos_iter = dest.end(); // base case + for (int i = 0; i < dest_size; ++i) + { + ASSERT_TRUE(dest.PushBack(i).IsOk()); + if (i == dest_pos) + { + dest_pos_iter = --dest.end(); + } + } + } + + void VerifySrc(const std::string& case_id) + { + EXPECT_EQ((int)source.ExpensiveSize(), src_size - 1) << case_id; + int i = 0; + for (auto it = source.begin(); it != source.end(); ++it, ++i) + { + if (i < src_pos) + { + EXPECT_EQ(*it, 100 + i) << i << case_id; + continue; + } + if (i < src_size - 1) + { + EXPECT_EQ(*it, 100 + i + 1) << i << case_id; + continue; + } + EXPECT_EQ(-1, *it) << "should be unreachable " << i << case_id; + } + } + + void VerifyDest(const std::string& case_id) + { + EXPECT_EQ((int)dest.ExpensiveSize(), dest_size + 1) << case_id; + int i = 0; + for (auto it = dest.begin(); it != dest.end(); ++it, ++i) + { + if (i < dest_pos) + { + EXPECT_EQ(*it, i) << i << case_id; + continue; + } + if (i == dest_pos) + { + EXPECT_EQ(*it, 100 + src_pos) << i << case_id; + continue; + } + if (i < dest_size + 1) + { + EXPECT_EQ(*it, i - 1) << i << case_id; + continue; + } + EXPECT_EQ(-1, *it) << "should be unreachable " << i << case_id; + } + } + + void Verify(const std::string& case_id) + { + VerifySrc(case_id); + VerifyDest(case_id); + } + + void Test() + { + // stringifying parameters so that failures have a chance at being + // useful. + std::string case_id = " case id: " + std::to_string(src_size) + ", " + + std::to_string(dest_size) + ", " + + std::to_string(src_pos) + ", " + + std::to_string(dest_pos); + + // Populate Src with {100, 101, ...} and Dest with {0, 1, ...}. + // Then Splice an element of Src into a position in Dest. + BuildSrc(); + BuildDest(); + + dest.SpliceOne(dest_pos_iter, source, src_pos_iter); + + Verify(case_id); + } + + void TestRValue() + { + // stringifying parameters so that failures have a chance at being + // useful. + std::string case_id = " case id: rvalue " + std::to_string(src_size) + + ", " + std::to_string(dest_size) + ", " + + std::to_string(src_pos) + ", " + + std::to_string(dest_pos); + + // Populate Src with {100, 101, ...} and Dest with {0, 1, ...}. + // Then Splice an element of Src into a position in Dest. + BuildSrc(); + BuildDest(); + + dest.SpliceOne(dest_pos_iter, std::move(source), src_pos_iter); + + Verify(case_id); + } +}; + +TEST(ListTest, SpliceOneExhaustive) +{ + const int kMaxListSize = 3; + // dimensions: Source size, dest size, dest pos, source pos + // 3 * 4 * 4 * 3 = 144 iterations (less than that actually) + // not testing empty source containers + for (int src_size = 1; src_size <= kMaxListSize; ++src_size) + { + for (int dest_size = 0; dest_size <= kMaxListSize; ++dest_size) + { + for (int dest_pos = 0; dest_pos <= dest_size; ++dest_pos) + { + for (int src_pos = 0; src_pos < src_size; ++src_pos) + { + SpliceOneExhaustive_data data{ src_size, + dest_size, + src_pos, + dest_pos }; + data.Test(); + SpliceOneExhaustive_data data2{ src_size, + dest_size, + src_pos, + dest_pos }; + data2.TestRValue(); + } + } + } + } +} + +struct SpliceAllExhaustive_data +{ + SpliceAllExhaustive_data(int src_size, int dest_size, int dest_pos) + : src_size(src_size), + dest_size(dest_size), + dest_pos(dest_pos) + { + } + + int src_size; + int dest_size; + int dest_pos; + rad::List source; + rad::List dest; + rad::List::IteratorType dest_pos_iter; + + void BuildSrc() + { + for (int i = 0; i < src_size; ++i) + { + // populate with i offset by 100, so that we can tell src and dest + // apart + ASSERT_TRUE(source.PushBack(i + 100).IsOk()); + } + } + + void BuildDest() + { + dest_pos_iter = dest.end(); // base case + for (int i = 0; i < dest_size; ++i) + { + ASSERT_TRUE(dest.PushBack(i).IsOk()); + if (i == dest_pos) + { + dest_pos_iter = --dest.end(); + } + } + } + + void VerifySrc(const std::string& case_id) + { + EXPECT_TRUE(source.Empty()) << case_id; + } + + void VerifyDest(const std::string& case_id) + { + EXPECT_EQ((int)dest.ExpensiveSize(), dest_size + src_size) << case_id; + int i = 0; + for (auto it = dest.begin(); it != dest.end(); ++it, ++i) + { + if (i < dest_pos) + { + EXPECT_EQ(*it, i) << i << case_id; + continue; + } + if (i < dest_pos + src_size) + { + int src_idx = i - dest_pos; + EXPECT_EQ(*it, 100 + src_idx) << i << case_id; + continue; + } + if (i < dest_size + src_size) + { + EXPECT_EQ(*it, i - src_size) << i << case_id; + continue; + } + EXPECT_EQ(-1, *it) << "should be unreachable " << i << case_id; + } + } + + void Verify(const std::string& case_id) + { + VerifySrc(case_id); + VerifyDest(case_id); + } + + void Test() + { + // stringifying parameters so that failures have a chance at being + // useful. + std::string case_id = " case id: " + std::to_string(src_size) + ", " + + std::to_string(dest_size) + ", " + + std::to_string(dest_pos); + + // Populate Src with {100, 101, ...} and Dest with {0, 1, ...}. + // Then Splice Src into a position in Dest. + BuildSrc(); + BuildDest(); + + dest.SpliceAll(dest_pos_iter, source); + + Verify(case_id); + } + + void TestRValue() + { + // stringifying parameters so that failures have a chance at being + // useful. + std::string case_id = " case id: rvalue " + std::to_string(src_size) + + ", " + std::to_string(dest_size) + ", " + + std::to_string(dest_pos); + + // Populate Src with {100, 101, ...} and Dest with {0, 1, ...}. + // Then Splice Src into a position in Dest. + BuildSrc(); + BuildDest(); + + dest.SpliceAll(dest_pos_iter, std::move(source)); + + Verify(case_id); + } +}; + +TEST(ListTest, SpliceAllExhaustive) +{ + const int kMaxListSize = 3; + // dimensions: Source size, dest size, dest pos + // 3 * 4 * 4 = 48 iterations (less than that actually) + // not testing empty source containers + for (int src_size = 1; src_size <= kMaxListSize; ++src_size) + { + for (int dest_size = 0; dest_size <= kMaxListSize; ++dest_size) + { + for (int dest_pos = 0; dest_pos <= dest_size; ++dest_pos) + { + SpliceAllExhaustive_data data{ src_size, dest_size, dest_pos }; + data.Test(); + SpliceAllExhaustive_data data2{ src_size, dest_size, dest_pos }; + data2.TestRValue(); + } + } + } +} + +TEST(ListTest, PrependRange) +{ + rad::List dest; + EXPECT_TRUE( + dest.AssignRange(std::initializer_list{ 0, 1, 2, 3 }).IsOk()); + std::array source = { 100, 101 }; + + EXPECT_TRUE(dest.PrependRange(source).IsOk()); + ListEqual(dest, { 100, 101, 0, 1, 2, 3 }); + + rad::List empty; + EXPECT_TRUE(dest.PrependRange(empty).IsOk()); + ListEqual(dest, { 100, 101, 0, 1, 2, 3 }); + + rad::List chars; + EXPECT_TRUE( + chars.AssignRange(std::initializer_list{ 'x', 'y', 'z' }).IsOk()); + EXPECT_TRUE( + chars.PrependRange(radtest::TestInputStringLiteralRange("abc")).IsOk()); + ListEqual(chars, { 'a', 'b', 'c', 'x', 'y', 'z' }); + + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> list( + alloc); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + heap.allocCount = heap.freeCount = 0; + // PrependRange doesn't modify the list when allocations fail + heap.forceFutureAllocFail = 2; + EXPECT_TRUE(list.PrependRange(source).IsErr()); + EXPECT_EQ(heap.allocCount, 1); + EXPECT_EQ(heap.freeCount, 1); + ListEqual(list, { 1, 2, 3 }); +} + +TEST(ListTest, AppendRange) +{ + rad::List dest; + EXPECT_TRUE( + dest.AssignRange(std::initializer_list{ 0, 1, 2, 3 }).IsOk()); + std::array source = { 100, 101 }; + + EXPECT_TRUE(dest.AppendRange(source).IsOk()); + ListEqual(dest, { 0, 1, 2, 3, 100, 101 }); + + rad::List empty; + EXPECT_TRUE(dest.AppendRange(empty).IsOk()); + ListEqual(dest, { 0, 1, 2, 3, 100, 101 }); + + rad::List chars; + EXPECT_TRUE( + chars.AssignRange(std::initializer_list{ 'x', 'y', 'z' }).IsOk()); + EXPECT_TRUE( + chars.AppendRange(radtest::TestInputStringLiteralRange("abc")).IsOk()); + ListEqual(chars, { 'x', 'y', 'z', 'a', 'b', 'c' }); + + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> list( + alloc); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + heap.allocCount = heap.freeCount = 0; + // AppendRange doesn't modify the list when allocations fail + heap.forceFutureAllocFail = 2; + EXPECT_TRUE(list.AppendRange(source).IsErr()); + EXPECT_EQ(heap.allocCount, 1); + EXPECT_EQ(heap.freeCount, 1); + ListEqual(list, { 1, 2, 3 }); +} + +TEST(ListTest, InsertRange) +{ + std::array source = { 100, 101 }; + + { + rad::List dest; + EXPECT_TRUE( + dest.AssignRange(std::initializer_list{ 0, 1, 2, 3 }).IsOk()); + + rad::Res::IteratorType> it; + rad::List::IteratorType insert_pos; + insert_pos = ++dest.begin(); + it = dest.InsertRange(insert_pos, source); + EXPECT_EQ(it, ++dest.begin()); + EXPECT_NE(it, insert_pos); + ListEqual(dest, { 0, 100, 101, 1, 2, 3 }); + + rad::List empty; + insert_pos = ++dest.begin(); + it = dest.InsertRange(insert_pos, empty); + EXPECT_EQ(it, ++dest.begin()); + EXPECT_EQ(it, insert_pos); + ListEqual(dest, { 0, 100, 101, 1, 2, 3 }); + + rad::List chars; + EXPECT_TRUE( + chars.AssignRange(std::initializer_list{ 'x', 'y', 'z' }) + .IsOk()); + auto char_insert_pos = ++chars.begin(); + auto res_char_it = + chars.InsertRange(char_insert_pos, + radtest::TestInputStringLiteralRange("abc")); + EXPECT_EQ(*res_char_it.Ok(), 'a'); + EXPECT_EQ(*char_insert_pos, 'y'); + EXPECT_EQ(*(++chars.begin()), 'a'); + ListEqual(chars, { 'x', 'a', 'b', 'c', 'y', 'z' }); + } + { + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> list( + alloc); + + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + heap.allocCount = heap.freeCount = 0; + auto insert_pos = ++list.begin(); + // InsertRange doesn't modify the list when allocations fail + heap.forceFutureAllocFail = 2; + auto it = list.InsertRange(insert_pos, source); + EXPECT_EQ(heap.allocCount, 1); + EXPECT_EQ(heap.freeCount, 1); + EXPECT_TRUE(it.IsErr()); + EXPECT_NE(insert_pos, it); + ListEqual(list, { 1, 2, 3 }); + } +} + +TEST(ListTest, InsertSome) +{ + std::array source = { 100, 101 }; + + { + rad::List dest; + EXPECT_TRUE( + dest.AssignRange(std::initializer_list{ 0, 1, 2, 3 }).IsOk()); + + rad::Res::IteratorType> it; + rad::List::IteratorType insert_pos; + insert_pos = ++dest.begin(); + it = dest.InsertSome(insert_pos, source.begin(), source.end()); + EXPECT_EQ(it, ++dest.begin()); + EXPECT_NE(it, insert_pos); + ListEqual(dest, { 0, 100, 101, 1, 2, 3 }); + + int* empty = nullptr; + insert_pos = ++dest.begin(); + it = dest.InsertSome(insert_pos, empty, empty); + EXPECT_EQ(it, ++dest.begin()); + EXPECT_EQ(it, insert_pos); + ListEqual(dest, { 0, 100, 101, 1, 2, 3 }); + } + { + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> list( + alloc); + + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + heap.allocCount = heap.freeCount = 0; + auto insert_pos = ++list.begin(); + // InsertSome doesn't modify the list when allocations fail + heap.forceFutureAllocFail = 2; + auto it = list.InsertSome(insert_pos, source.begin(), source.end()); + EXPECT_EQ(heap.allocCount, 1); + EXPECT_EQ(heap.freeCount, 1); + EXPECT_TRUE(it.IsErr()); + EXPECT_NE(insert_pos, it); + ListEqual(list, { 1, 2, 3 }); + } +} + +#if RAD_ENABLE_STD +TEST(ListTest, InsertInitializerList) +{ + { + rad::List dest; + EXPECT_TRUE( + dest.AssignRange(std::initializer_list{ 0, 1, 2, 3 }).IsOk()); + + rad::Res::IteratorType> it; + rad::List::IteratorType insert_pos; + insert_pos = ++dest.begin(); + it = dest.InsertInitializerList(insert_pos, { 100, 101 }); + EXPECT_EQ(it, ++dest.begin()); + EXPECT_NE(it, insert_pos); + ListEqual(dest, { 0, 100, 101, 1, 2, 3 }); + + insert_pos = ++dest.begin(); + it = dest.InsertInitializerList(insert_pos, {}); + EXPECT_EQ(it, ++dest.begin()); + EXPECT_EQ(it, insert_pos); + ListEqual(dest, { 0, 100, 101, 1, 2, 3 }); + } + { + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> list( + alloc); + + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + heap.allocCount = heap.freeCount = 0; + auto insert_pos = ++list.begin(); + // InsertSome doesn't modify the list when allocations fail + heap.forceFutureAllocFail = 2; + auto it = list.InsertInitializerList(insert_pos, { 100, 101 }); + EXPECT_EQ(heap.allocCount, 1); + EXPECT_EQ(heap.freeCount, 1); + EXPECT_TRUE(it.IsErr()); + EXPECT_NE(insert_pos, it); + ListEqual(list, { 1, 2, 3 }); + } +} +#endif + +TEST(ListTest, InsertCount) +{ + { + rad::List dest; + EXPECT_TRUE( + dest.AssignRange(std::initializer_list{ 0, 1, 2, 3 }).IsOk()); + + rad::Res::IteratorType> it; + rad::List::IteratorType insert_pos; + insert_pos = ++dest.begin(); + it = dest.InsertCount(insert_pos, 2, 100); + EXPECT_EQ(it, ++dest.begin()); + EXPECT_NE(it, insert_pos); + ListEqual(dest, { 0, 100, 100, 1, 2, 3 }); + + insert_pos = ++dest.begin(); + it = dest.InsertCount(insert_pos, 0, -3); + EXPECT_EQ(it, ++dest.begin()); + EXPECT_EQ(it, insert_pos); + ListEqual(dest, { 0, 100, 100, 1, 2, 3 }); + } + { + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> list( + alloc); + + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + heap.allocCount = heap.freeCount = 0; + auto insert_pos = ++list.begin(); + // InsertSome doesn't modify the list when allocations fail + heap.forceFutureAllocFail = 2; + auto it = list.InsertCount(insert_pos, 2, 100); + EXPECT_EQ(heap.allocCount, 1); + EXPECT_EQ(heap.freeCount, 1); + EXPECT_TRUE(it.IsErr()); + EXPECT_NE(insert_pos, it); + ListEqual(list, { 1, 2, 3 }); + } +} + +TEST(ListTest, Clone) +{ + { + rad::List li; + + rad::Res> empty_clone = li.Clone(); + ASSERT_TRUE(empty_clone.IsOk()); + ASSERT_TRUE(empty_clone.Ok().Empty()); + + EXPECT_TRUE( + li.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + + rad::Res> li2 = li.Clone(); + ASSERT_TRUE(li2.IsOk()); + ListEqual(li2.Ok(), { 1, 2, 3 }); + } + { + radtest::HeapAllocator heap; + radtest::AllocWrapper alloc(heap); + rad::List> list( + alloc); + + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + heap.allocCount = heap.freeCount = 0; + + auto success_clone_res = list.Clone(); + ASSERT_TRUE(success_clone_res.IsOk()); + auto& success_clone = success_clone_res.Ok(); + EXPECT_EQ(heap.allocCount, 3); + ListEqual(success_clone, { 1, 2, 3 }); + heap.allocCount = heap.freeCount = 0; + + // Ensure the allocator was copied + EXPECT_TRUE(success_clone.PushBack(4).IsOk()); + EXPECT_EQ(heap.allocCount, 1); + success_clone.Clear(); + EXPECT_EQ(heap.freeCount, 4); + + heap.allocCount = heap.freeCount = 0; + heap.forceFutureAllocFail = 2; + auto fail_clone = list.Clone(); + EXPECT_EQ(heap.allocCount, 1); + EXPECT_EQ(heap.freeCount, 1); + EXPECT_TRUE(fail_clone.IsErr()); + } +} + +struct EraseSomeExhaustive_data +{ + EraseSomeExhaustive_data(int size, int begin, int end) + : size(size), + begin(begin), + end(end) + { + } + + int size; + int begin; + int end; + rad::List list; + rad::List::IteratorType begin_iter; + rad::List::IteratorType end_iter; + + void BuildList() + { + begin_iter = list.end(); // .end() is the base case + end_iter = list.end(); + for (int i = 0; i < size; ++i) + { + ASSERT_TRUE(list.PushBack(i).IsOk()); + if (i == begin) + { + begin_iter = --list.end(); + } + if (i == end) + { + end_iter = --list.end(); + } + } + } + + void Verify(const std::string& case_id) + { + EXPECT_EQ((int)list.ExpensiveSize(), size - (end - begin)) << case_id; + int i = 0; + for (auto it = list.begin(); it != list.end(); ++it, ++i) + { + if (i < begin) + { + EXPECT_EQ(*it, i) << i << case_id; + continue; + } + if (i < size - (end - begin)) + { + EXPECT_EQ(*it, i + (end - begin)) << i << case_id; + continue; + } + EXPECT_EQ(-1, *it) << "should be unreachable " << i << case_id; + } + } + + void Test() + { + // stringifying parameters so that failures have a chance at being + // useful. + std::string case_id = " case id: " + std::to_string(size) + ", " + + std::to_string(begin) + ", " + + std::to_string(end); + + BuildList(); + + auto ret_iter = list.EraseSome(begin_iter, end_iter); + EXPECT_EQ(ret_iter, end_iter) << case_id; + + Verify(case_id); + } +}; + +TEST(ListTest, EraseSomeExhaustive) +{ + const int kMaxListSize = 4; + // dimensions: Size, begin, end + // 5 * 5 * 5 = 125 iterations (less than that actually) + for (int size = 0; size <= kMaxListSize; ++size) + { + for (int begin = 0; begin <= size; ++begin) + { + for (int end = begin; end <= size; ++end) + { + EraseSomeExhaustive_data data{ size, begin, end }; + data.Test(); + } + } + } +} + +struct EraseOneExhaustive_data +{ + EraseOneExhaustive_data(int size, int pos) + : size(size), + pos(pos) + { + } + + int size; + int pos; + rad::List list; + rad::List::IteratorType pos_iter; + + void BuildList() + { + pos_iter = list.end(); // .end() is the base case + for (int i = 0; i < size; ++i) + { + ASSERT_TRUE(list.PushBack(i).IsOk()); + if (i == pos) + { + pos_iter = --list.end(); + } + } + } + + void Verify(const std::string& case_id) + { + EXPECT_EQ((int)list.ExpensiveSize(), size - 1) << case_id; + int i = 0; + for (auto it = list.begin(); it != list.end(); ++it, ++i) + { + if (i < pos) + { + EXPECT_EQ(*it, i) << i << case_id; + continue; + } + if (i < size - 1) + { + EXPECT_EQ(*it, i + 1) << i << case_id; + continue; + } + EXPECT_EQ(-1, *it) << "should be unreachable " << i << case_id; + } + } + + void Test() + { + // stringifying parameters so that failures have a chance at being + // useful. + std::string case_id = + " case id: " + std::to_string(size) + ", " + std::to_string(pos); + + BuildList(); + + rad::List::IteratorType expected_iter = pos_iter; + ++expected_iter; + + auto ret_iter = list.EraseOne(pos_iter); + EXPECT_EQ(ret_iter, expected_iter); + + Verify(case_id); + } +}; + +TEST(ListTest, EraseOneExhaustive) +{ + const int kMaxListSize = 4; + // dimensions: Size, pos. Not testing empty containers or end iterators + // 4 * 4 = 16 iterations (less than that actually) + for (int size = 1; size <= kMaxListSize; ++size) + { + for (int pos = 0; pos < size; ++pos) + { + EraseOneExhaustive_data data{ size, pos }; + data.Test(); + } + } +} + +TEST(ListTest, EraseOneEnd) +{ + rad::List list; + EXPECT_EQ(list.end(), list.EraseOne(list.end())); + EXPECT_TRUE(list.Empty()); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1 }).IsOk()); + EXPECT_EQ(list.end(), list.EraseOne(list.end())); + ListEqual(list, { 1 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1, 2 }).IsOk()); + EXPECT_EQ(list.end(), list.EraseOne(list.end())); + ListEqual(list, { 1, 2 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + EXPECT_EQ(list.end(), list.EraseOne(list.end())); + ListEqual(list, { 1, 2, 3 }); +} + +TEST(ListTest, EraseValue) +{ + rad::List list; + EXPECT_EQ(0u, list.EraseValue(42)); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1 }).IsOk()); + EXPECT_EQ(0u, list.EraseValue(42)); + ListEqual(list, { 1 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1, 42 }).IsOk()); + EXPECT_EQ(1u, list.EraseValue(42)); + ListEqual(list, { 1 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 42, 1 }).IsOk()); + EXPECT_EQ(1u, list.EraseValue(42)); + ListEqual(list, { 1 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 42, 42 }).IsOk()); + EXPECT_EQ(2u, list.EraseValue(42)); + EXPECT_TRUE(list.Empty()); + + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 42, 1 }).IsOk()); + EXPECT_EQ(1u, list.EraseValue(42)); + ListEqual(list, { 1, 1 }); + + EXPECT_TRUE(list.AssignCount(50, 42).IsOk()); + EXPECT_EQ(50u, list.EraseValue(42)); + EXPECT_TRUE(list.Empty()); +} + +TEST(ListTest, EraseIf) +{ + auto is_even = [](int val) { return val % 2 == 0; }; + rad::List list; + EXPECT_EQ(0u, list.EraseIf(is_even)); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1 }).IsOk()); + EXPECT_EQ(0u, list.EraseIf(is_even)); + ListEqual(list, { 1 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1, 42 }).IsOk()); + EXPECT_EQ(1u, list.EraseIf(is_even)); + ListEqual(list, { 1 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 42, 1 }).IsOk()); + EXPECT_EQ(1u, list.EraseIf(is_even)); + ListEqual(list, { 1 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 42, 42 }).IsOk()); + EXPECT_EQ(2u, list.EraseIf(is_even)); + EXPECT_TRUE(list.Empty()); + + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 42, 1 }).IsOk()); + EXPECT_EQ(1u, list.EraseIf(is_even)); + ListEqual(list, { 1, 1 }); + + EXPECT_TRUE(list.AssignCount(50, 42).IsOk()); + EXPECT_EQ(50u, list.EraseIf(is_even)); + EXPECT_TRUE(list.Empty()); +} + +TEST(ListTest, PopFront) +{ + rad::List list; + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3, 4, 5 }).IsOk()); + + list.PopFront(); + ListEqual(list, { 2, 3, 4, 5 }); + list.PopFront(); + ListEqual(list, { 3, 4, 5 }); + list.PopFront(); + ListEqual(list, { 4, 5 }); + list.PopFront(); + ListEqual(list, { 5 }); + list.PopFront(); + EXPECT_TRUE(list.Empty()); + +#if !RAD_DBG + list.PopFront(); + EXPECT_TRUE(list.Empty()); +#endif +} + +TEST(ListTest, PopBack) +{ + rad::List list; + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3, 4, 5 }).IsOk()); + + list.PopBack(); + ListEqual(list, { 1, 2, 3, 4 }); + list.PopBack(); + ListEqual(list, { 1, 2, 3 }); + list.PopBack(); + ListEqual(list, { 1, 2 }); + list.PopBack(); + ListEqual(list, { 1 }); + list.PopBack(); + EXPECT_TRUE(list.Empty()); + +#if !RAD_DBG + list.PopBack(); + EXPECT_TRUE(list.Empty()); +#endif +} + +TEST(ListTest, TakeFront) +{ + { + rad::List list; + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3, 4, 5 }) + .IsOk()); + + EXPECT_EQ(list.TakeFront(), 1); + ListEqual(list, { 2, 3, 4, 5 }); + EXPECT_EQ(list.TakeFront(), 2); + ListEqual(list, { 3, 4, 5 }); + EXPECT_EQ(list.TakeFront(), 3); + ListEqual(list, { 4, 5 }); + EXPECT_EQ(list.TakeFront(), 4); + ListEqual(list, { 5 }); + EXPECT_EQ(list.TakeFront(), 5); + EXPECT_TRUE(list.Empty()); + + EXPECT_TRUE(list.TakeFront().IsErr()); + EXPECT_TRUE(list.Empty()); + } + { + rad::List list; + EXPECT_TRUE(list.EmplaceFront().IsOk()); + + EXPECT_EQ(list.TakeFront().Ok().val, 42); + + EXPECT_TRUE(list.TakeFront().IsErr()); + EXPECT_TRUE(list.Empty()); + } +#if 0 + { + // This intentionally does not build, because ImmovableStruct is immovable + rad::List list; + list.TakeFront(); + } +#endif +#if 0 + { + // This intentionally does not build, because CopyStruct has a throwing move + rad::List list; + list.TakeFront(); + } +#endif + { + rad::List list; + EXPECT_TRUE(list.PushBack(rad::Error::InvalidAddress).IsOk()); + auto res = list.TakeFront(); + EXPECT_TRUE(res.IsOk()); + EXPECT_EQ(res.Ok(), rad::Error::InvalidAddress); + + auto res2 = list.TakeFront(); + EXPECT_TRUE(res2.IsErr()); + EXPECT_EQ(res2.Err(), rad::Error::OutOfRange); + } +} + +TEST(ListTest, TakeBack) +{ + { + rad::List list; + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3, 4, 5 }) + .IsOk()); + + EXPECT_EQ(list.TakeBack(), 5); + ListEqual(list, { 1, 2, 3, 4 }); + EXPECT_EQ(list.TakeBack(), 4); + ListEqual(list, { 1, 2, 3 }); + EXPECT_EQ(list.TakeBack(), 3); + ListEqual(list, { 1, 2 }); + EXPECT_EQ(list.TakeBack(), 2); + ListEqual(list, { 1 }); + EXPECT_EQ(list.TakeBack(), 1); + EXPECT_TRUE(list.Empty()); + + EXPECT_TRUE(list.TakeBack().IsErr()); + EXPECT_TRUE(list.Empty()); + } + { + rad::List list; + EXPECT_TRUE(list.EmplaceFront().IsOk()); + + EXPECT_EQ(list.TakeBack().Ok().val, 42); + + EXPECT_TRUE(list.TakeBack().IsErr()); + EXPECT_TRUE(list.Empty()); + } +#if 0 + { + // This intentionally does not build, because ImmovableStruct is immovable + rad::List list; + list.TakeBack(); + } +#endif +#if 0 + { + // This intentionally does not build, because CopyStruct has a throwing move + rad::List list; + list.TakeBack(); + } +#endif + { + rad::List list; + EXPECT_TRUE(list.PushBack(rad::Error::InvalidAddress).IsOk()); + auto res = list.TakeBack(); + EXPECT_TRUE(res.IsOk()); + EXPECT_EQ(res.Ok(), rad::Error::InvalidAddress); + + auto res2 = list.TakeBack(); + EXPECT_TRUE(res2.IsErr()); + EXPECT_EQ(res2.Err(), rad::Error::OutOfRange); + } +} + +TEST(ListTest, Reverse) +{ + rad::List list; + list.Reverse(); + EXPECT_TRUE(list.Empty()); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1 }).IsOk()); + list.Reverse(); + ListEqual(list, { 1 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1, 2 }).IsOk()); + list.Reverse(); + ListEqual(list, { 2, 1 }); + + EXPECT_TRUE(list.AssignRange(std::initializer_list{ 1, 2, 3 }).IsOk()); + list.Reverse(); + ListEqual(list, { 3, 2, 1 }); + + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3, 4 }).IsOk()); + list.Reverse(); + ListEqual(list, { 4, 3, 2, 1 }); +} + +TEST(ListTest, FrontBack) +{ + { + rad::List list; + const rad::List& clist = list; + + EXPECT_TRUE(list.Front().IsErr()); + EXPECT_TRUE(list.Back().IsErr()); + EXPECT_TRUE(clist.Front().IsErr()); + EXPECT_TRUE(clist.Back().IsErr()); + } + { + rad::List list; + EXPECT_TRUE( + list.AssignRange(std::initializer_list{ 1, 2, 3, 4, 5 }) + .IsOk()); + EXPECT_EQ(list.Front(), 1); + EXPECT_EQ(list.Back(), 5); + + const rad::List& clist = list; + EXPECT_EQ(clist.Front(), 1); + EXPECT_EQ(clist.Back(), 5); + + // ensure Res::op= rebinds, and doesn't assign through + int val = 42; + auto res = list.Front(); + res = val; + EXPECT_EQ(res.Ok(), 42); + EXPECT_EQ(list.Front(), 1); + + res = list.Front(); + *res = val; // ensure *res::op= assigns through + EXPECT_EQ(list.Front(), 42); + + res = list.Back(); + *res = 99; + EXPECT_EQ(list.Back(), 99); + } +} + +TEST(ListTest, ReverseIterators) +{ + { + rad::List list1; + EXPECT_EQ(list1.rbegin(), list1.rend()); + + rad::List list2; + EXPECT_TRUE(list2.AssignSome(list1.rbegin(), list1.rend()).IsOk()); + EXPECT_TRUE(list2.Empty()); + } + { + rad::List list1; + EXPECT_TRUE( + list1.AssignRange(std::initializer_list{ 1, 2, 3, 4, 5 }) + .IsOk()); + + rad::List list2; + EXPECT_TRUE(list2.AssignSome(list1.rbegin(), list1.rend()).IsOk()); + ListEqual(list2, { 5, 4, 3, 2, 1 }); + } + + { + rad::List list1; + EXPECT_EQ(list1.crbegin(), list1.crend()); + + rad::List list2; + EXPECT_TRUE(list2.AssignSome(list1.crbegin(), list1.crend()).IsOk()); + EXPECT_TRUE(list2.Empty()); + } + { + rad::List list1; + EXPECT_TRUE( + list1.AssignRange(std::initializer_list{ 1, 2, 3, 4, 5 }) + .IsOk()); + + rad::List list2; + EXPECT_TRUE(list2.AssignSome(list1.crbegin(), list1.crend()).IsOk()); + ListEqual(list2, { 5, 4, 3, 2, 1 }); + } +} diff --git a/test/test_Vector.cpp b/test/test_Vector.cpp index 3013ba8..4f81926 100644 --- a/test/test_Vector.cpp +++ b/test/test_Vector.cpp @@ -1024,15 +1024,20 @@ TEST_F(TestVectorIntegral, Float) EXPECT_EQ(vec.At(2), 3.3f); } -TEST_F(TestVectorIntegral, RemoveBack) +TEST_F(TestVectorIntegral, TakeBack) { rad::Vector vec; EXPECT_TRUE(vec.Assign({ 1, 2, 3 }).IsOk()); - EXPECT_EQ(vec.RemoveBack(), 3); - EXPECT_EQ(vec.RemoveBack(), 2); - EXPECT_EQ(vec.RemoveBack(), 1); + EXPECT_EQ(vec.Size(), 3u); + EXPECT_EQ(vec.TakeBack(), 3); + EXPECT_EQ(vec.Size(), 2u); + EXPECT_EQ(vec.TakeBack(), 2); + EXPECT_EQ(vec.Size(), 1u); + EXPECT_EQ(vec.TakeBack(), 1); + EXPECT_EQ(vec.Size(), 0u); + EXPECT_TRUE(vec.Empty()); } TEST_F(TestVectorIntegral, Seek) diff --git a/tools/rad/rad/bazel.py b/tools/rad/rad/bazel.py index 6b6cac6..42f7370 100644 --- a/tools/rad/rad/bazel.py +++ b/tools/rad/rad/bazel.py @@ -230,12 +230,13 @@ def _get_cpplink_executable(args): return None -def get_label_executables(label): +def get_label_executables(label, deps=False): """Get the result executables from the given label""" + internal_label = f"deps({label})" if deps else label output = BAZEL.capture_output( [ "aquery", - f'mnemonic("CppLink", (outputs(".*exe", deps({label}))))', + f'mnemonic("CppLink", (outputs(".*exe", {internal_label})))', "--output=jsonproto", ] ) diff --git a/tools/rad/rad/cli.py b/tools/rad/rad/cli.py index 9837bd0..b1824ec 100644 --- a/tools/rad/rad/cli.py +++ b/tools/rad/rad/cli.py @@ -113,8 +113,9 @@ def test(args) -> bool: clang = ["--repo_env=CC=clang"] if os.name != "nt" and args.clang else [] nostd = ["--copt=-DRAD_NO_STD"] if args.no_std else [] nocache = ["--nocache_test_results"] if args.no_cache else [] + mode = ["-c", "opt"] if args.release else ["-c", "dbg"] return rad.bazel.test( - get_label(args), clang + platforms + nostd + nocache + verbosity + get_label(args), clang + mode + platforms + nostd + nocache + verbosity ) @@ -270,6 +271,9 @@ def main() -> int: action="store_true", help="ignore cached test results", ) + test_parser.add_argument( + "--release", action="store_true", help="builds release instead of debug" + ) test_parser.add_argument( "--label", required=False,