From f31b7159bb28c0a6b2535ab56518e15d4d19066d Mon Sep 17 00:00:00 2001 From: Adam Fonseca Date: Thu, 20 Jun 2024 10:19:22 -0700 Subject: [PATCH] Provide strong guarantee for rad::Vector * Add overloads that provide the strong guarantee for types that can throw exceptions * Add test cases to verify the strong guarantee * Add fixture that checks to make sure all destructors are called * Refactor tests to verify a throwing type --- radiant/Algorithm.h | 31 + radiant/TotallyRad.h | 7 +- radiant/Vector.h | 61 +- radiant/detail/VectorOperations.h | 364 +++++++++--- test/TestAlloc.h | 83 +++ test/test_Vector.cpp | 927 ++++++++++++++++++++++++------ 6 files changed, 1179 insertions(+), 294 deletions(-) create mode 100644 radiant/Algorithm.h diff --git a/radiant/Algorithm.h b/radiant/Algorithm.h new file mode 100644 index 0000000..4b289bf --- /dev/null +++ b/radiant/Algorithm.h @@ -0,0 +1,31 @@ +// Copyright 2023 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/TypeTraits.h" +#include "radiant/Utility.h" +#include "radiant/TotallyRad.h" + +namespace rad +{ +template +void Swap(T& a, T& b) noexcept +{ + RAD_S_ASSERT_NOTHROW_MOVE_T(T); + T tmp = Move(a); + a = Move(b); + b = Move(tmp); +} +} // namespace rad diff --git a/radiant/TotallyRad.h b/radiant/TotallyRad.h index d84fb5d..78fcd6b 100644 --- a/radiant/TotallyRad.h +++ b/radiant/TotallyRad.h @@ -282,8 +282,13 @@ inline bool DoAssert(const char* Assertion, const char* File, int Line) #if RAD_ENABLE_NOTHROW_MOVE_ASSERTIONS #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") #else -#define RAD_S_ASSERT_NOTHROW_MOVE(x) RAD_S_ASSERT(true) +#define RAD_S_ASSERT_NOTHROW_MOVE(x) RAD_S_ASSERT(true) +#define RAD_S_ASSERT_NOTHROW_MOVE_T(x) RAD_S_ASSERT(true) #endif #define RAD_NOT_COPYABLE(x) \ diff --git a/radiant/Vector.h b/radiant/Vector.h index 295900f..cd59555 100644 --- a/radiant/Vector.h +++ b/radiant/Vector.h @@ -43,29 +43,11 @@ class Vector final using SizeType = uint32_t; static constexpr uint16_t InlineCount = TInlineCount; using AllocatorType = TAllocator; + template + using OtherType = Vector; - RAD_S_ASSERT_NOTHROW_MOVE((IsNoThrowMoveCtor && IsNoThrowMoveAssign && - IsNoThrowMoveCtor && - IsNoThrowMoveAssign)); - - // - // TODO - rad::Vector does not yet support types that might throw exceptions - // when manipulating it. It is the intention of the radiant authors for the - // library to be exception free. Radiant will never throw an exception - // itself, rather it will communicate errors by returning a Result. However, - // the radiant containers may be used with types that do not adhere to this - // philosophy. For rad::Vector to comply to this, it must cleanly handle - // cases where an exception interjects manipulation of it. If an exception - // happens rad::Vector should be in the state prior to the call to - // manipulate it. In other words, an exception occurring when manipulating - // rad::Vector should not leave it in a partially-manipulated state. This - // does not mean it should swallow the exception internally and return an - // error. The exception should propagate from rad::Vector but rad::Vector - // itself should be in a valid state when this happens. - // - RAD_S_ASSERTMSG((IsNoThrowDtor && IsNoThrowDefaultCtor && - IsNoThrowCopyCtor && IsNoThrowCopyAssign), - "rad::Vector does not yet support types that might throw."); + RAD_S_ASSERT_NOTHROW_MOVE_T(T); + RAD_S_ASSERT_NOTHROW_MOVE_T(TAllocator); ~Vector() { @@ -81,14 +63,14 @@ class Vector final /// @brief Constructs empty container with copy-constructed allocator. /// @param alloc Allocator to copy. - Vector(const AllocatorType& alloc) + explicit Vector(const AllocatorType& alloc) : m_storage(alloc) { } /// @brief Constructs empty container with move-constructed allocator. /// @param alloc Allocator to move. - Vector(AllocatorType&& alloc) + explicit Vector(AllocatorType&& alloc) : m_storage(Forward(alloc)) { } @@ -129,7 +111,7 @@ class Vector final /// @brief Retrieves a pointer to the first element in the contiguous set of /// elements. Pointer value is undefined if the container is empty. - /// @return Pointer to the first element in the contiguious set of elements. + /// @return Pointer to the first element in the contiguous set of elements. ValueType* Data() noexcept { return Storage().Data(); @@ -137,7 +119,7 @@ class Vector final /// @brief Retrieves a pointer to the first element in the contiguous set of /// elements. Pointer value is undefined if the container is empty. - /// @return Pointer to the first element in the contiguious set of elements. + /// @return Pointer to the first element in the contiguous set of elements. const ValueType* Data() const noexcept { return Storage().Data(); @@ -258,7 +240,7 @@ class Vector final /// less than count a number of default-constructed elements are appended. /// @param count Requested size of the container. /// @return Result reference to this container on success or an error. - Res Resize(SizeType count) noexcept + Res Resize(SizeType count) noexcept(IsNoThrowDefaultCtor) { return Storage().Resize(Allocator(), count).OnOk(*this); } @@ -270,7 +252,8 @@ class Vector final /// @param count Requested size of the container. /// @param value Value to initialize new elements with. /// @return Result reference to this container on success or an error. - Res Resize(SizeType count, const ValueType& value) noexcept + Res Resize(SizeType count, + const ValueType& value) noexcept(IsNoThrowCopyCtor) { return Storage().Resize(Allocator(), count, value).OnOk(*this); } @@ -279,7 +262,8 @@ class Vector final /// @param count Number of elements to assign to the container. /// @param value Value to initialize elements with. /// @return Result reference to this container on success or an error. - Res Assign(SizeType count, const ValueType& value) noexcept + Res Assign(SizeType count, + const ValueType& value) noexcept(IsNoThrowCopyCtor) { return Storage().Assign(Allocator(), count, value).OnOk(*this); } @@ -288,7 +272,8 @@ class Vector final /// @brief Replaces the contents of the container. /// @param Init List of values to initialize elements with. /// @return Result reference to this container on success or an error. - Res Assign(std::initializer_list init) noexcept + Res Assign(std::initializer_list init) noexcept( + IsNoThrowCopyCtor) { return Storage() .Assign(Allocator(), @@ -300,7 +285,7 @@ class Vector final /// @brief Replaces the contents of the container. /// @param span Span of values to initialize elements with. /// @return Result reference to this container on success or an error. - Res Assign(Span span) noexcept + Res Assign(Span span) noexcept(IsNoThrowCopyCtor) { return Storage().Assign(Allocator(), span).OnOk(*this); } @@ -311,7 +296,8 @@ class Vector final /// @param ...args Arguments to forward to the constructor of the element. /// @return Result reference to this container on success or an error. template - Res EmplaceBack(TArgs&&... args) noexcept + Res EmplaceBack(TArgs&&... args) noexcept( + IsNoThrowCtor) { return Storage() .EmplaceBack(Allocator(), Forward(args)...) @@ -321,7 +307,8 @@ class Vector final /// @brief Appends a new element to the end of the container. /// @param value Value to be appended. /// @return Result reference to this container on success or an error. - Res PushBack(const ValueType& value) noexcept + Res PushBack(const ValueType& value) noexcept( + IsNoThrowCopyCtor) { return EmplaceBack(value); } @@ -375,9 +362,13 @@ class Vector final /// @brief Copies the elements in this container to another. /// @param to Container to copy elements to. /// @return Result reference to this container on success or an error. - Res Copy(ThisType& to) noexcept + Res Copy(ThisType& to) noexcept(IsNoThrowCopyCtor) { - return Storage().Copy(Allocator(), to.Storage()).OnOk(*this); + // Note: for now we can only copy types with the same allocator because + // the storage and allocator are private when the from and to vectors + // are not exactly the same type. This problem can be solved, but + // requires a bit of work. + return Storage().Copy(to.Allocator(), to.Storage()).OnOk(*this); } /// @brief Moves the elements in this container to another. diff --git a/radiant/detail/VectorOperations.h b/radiant/detail/VectorOperations.h index eeb2529..b62595d 100644 --- a/radiant/detail/VectorOperations.h +++ b/radiant/detail/VectorOperations.h @@ -17,6 +17,7 @@ #include "radiant/Res.h" #include "radiant/Utility.h" #include "radiant/Span.h" +#include "radiant/Algorithm.h" namespace rad { @@ -24,14 +25,57 @@ namespace rad namespace detail { -// -// TODO - The noexcept contracts are not complete yet, see the comment near -// the top of the rad::Vector class. Many of the storage and operational -// contracts are just set to noexcept, when we cleanly support objects that -// might throw during manipulation, we need to go back and make the decorations -// correct. The manipulation decorations are largely correct, but missing -// assertions. -// +template +struct VectorAlloc +{ + ~VectorAlloc() + { + if (buffer) + { + for (T* p = buffer; p != buffer + size; p++) + { + p->~T(); + } + + allocator.Free(buffer); + } + } + + explicit VectorAlloc(TAllocator& alloc) noexcept + : allocator(alloc), + buffer(nullptr), + size(0), + capacity(0) + { + } + + bool Alloc(uint32_t count) + { + buffer = allocator.Alloc(count); + if (!buffer) + { + return false; + } + + capacity = count; + size = 0; + + return true; + } + + T* Release() noexcept + { + T* tmp = buffer; + buffer = nullptr; + size = 0; + return tmp; + } + + TAllocator& allocator; + T* buffer; + uint32_t size; + uint32_t capacity; +}; template struct VectorManipulation @@ -83,6 +127,16 @@ struct VectorManipulation } } + template + inline void DefaultCtor(Out& dest, uint32_t count) + { + for (uint32_t i = 0; i < count; i++) + { + new (dest.buffer + i) T(); + dest.size++; + } + } + template && (sizeof(T) > sizeof(void*)), int> = 0> inline void CopyCtor(T* dest, const T& value) noexcept @@ -97,7 +151,7 @@ struct VectorManipulation new (dest) T(value); } - inline void CopyCtor(T* dest, uint32_t count, const T& value) noexcept + inline void CopyCtor(T* dest, uint32_t count, const T& value) { for (uint32_t i = 0; i < count; i++) { @@ -105,6 +159,16 @@ struct VectorManipulation } } + template + inline void CopyCtor(Out& dest, uint32_t count, const T& value) + { + for (uint32_t i = 0; i < count; i++) + { + CopyCtor(dest.buffer + i, value); + dest.size++; + } + } + template , int> = 0> inline void CopyCtorDtorDest(T* dest, uint32_t count, @@ -135,7 +199,7 @@ struct VectorManipulation template , int> = 0> inline void CopyCtorDtorDestRange(T* dest, T* src, uint32_t count) noexcept { - memmove(dest, src, count * sizeof(T)); + memcpy(dest, src, count * sizeof(T)); } template , int> = 0> @@ -209,53 +273,63 @@ struct VectorManipulation } } - template , int> = 0> - inline void MoveAssignRange(T* dest, T* src, uint32_t count) noexcept + template , int> = 0> + inline void CopyCtorRange(Out& dest, const T* src, uint32_t count) noexcept { - memmove(dest, src, count * sizeof(T)); + memcpy(dest.buffer, src, count * sizeof(T)); + dest.size = count; } - template , int> = 0> - inline void MoveAssignRange(T* dest, - T* src, - uint32_t count) noexcept(IsNoThrowMoveAssign) + template , int> = 0> + inline void CopyCtorRange(Out& dest, const T* src, uint32_t count) { - for (uint32_t i = 0; i < count; i++) + if (dest.size > 0) { - T* s = src + i; - T* d = dest + i; + DtorRange(dest.buffer, dest.buffer + dest.size); + } - *d = Move(*s); + for (uint32_t i = 0; i < count; i++) + { + CopyCtor(dest.buffer + i, src[i]); + dest.size++; } } -}; -template -struct VectorAlloc -{ - ~VectorAlloc() + template , int> = 0> + inline void CopyCtorRange(T* dest, const T* src, uint32_t count) noexcept { - if (buffer) + memcpy(dest, src, count * sizeof(T)); + } + + template , int> = 0> + inline void CopyCtorRange(T* dest, const T* src, uint32_t count) + { + + for (uint32_t i = 0; i < count; i++) { - allocator.Free(buffer); + CopyCtor(dest + i, src[i]); } } - VectorAlloc(TAllocator& alloc) noexcept - : allocator(alloc), - buffer(nullptr) + template , int> = 0> + inline void MoveAssignRange(T* dest, T* src, uint32_t count) noexcept { + memmove(dest, src, count * sizeof(T)); } - T* Release() noexcept + template , int> = 0> + inline void MoveAssignRange(T* dest, + T* src, + uint32_t count) noexcept(IsNoThrowMoveAssign) { - T* tmp = buffer; - buffer = nullptr; - return tmp; - } + for (uint32_t i = 0; i < count; i++) + { + T* s = src + i; + T* d = dest + i; - TAllocator& allocator; - T* buffer; + *d = Move(*s); + } + } }; template 0)> @@ -344,17 +418,17 @@ struct VectorStorage return; } - auto data = m_data; - m_data = other.m_data; - other.m_data = data; - - auto size = m_size; - m_size = other.m_size; - other.m_size = size; + rad::Swap(m_data, other.m_data); + rad::Swap(m_size, other.m_size); + rad::Swap(m_capacity, other.m_capacity); + } - size = m_capacity; - m_capacity = other.m_capacity; - other.m_capacity = size; + template + void Swap(VectorAlloc& other) noexcept + { + rad::Swap(m_data, other.buffer); + rad::Swap(m_size, other.size); + rad::Swap(m_capacity, other.capacity); } ValueType* m_data; @@ -472,9 +546,7 @@ struct VectorStorage // // Neither are inline, just swap the pointers. // - auto temp = m_data; - m_data = other.m_data; - other.m_data = temp; + rad::Swap(m_data, other.m_data); } else if (IsInline() && other.IsInline()) { @@ -484,8 +556,10 @@ struct VectorStorage // uint8_t storage[sizeof(m_inline)]; auto buffer = reinterpret_cast(storage); - ManipType().MoveCtorRange(buffer, m_inline, m_size); - ManipType().MoveAssignRange(m_inline, other.m_inline, other.m_size); + ManipType().MoveCtorDtorSrcRange(buffer, m_inline, m_size); + ManipType().MoveCtorDtorSrcRange(m_inline, + other.m_inline, + other.m_size); ManipType().MoveCtorDtorSrcRange(other.m_inline, buffer, m_size); } else @@ -522,13 +596,8 @@ struct VectorStorage } } - auto size = m_size; - m_size = other.m_size; - other.m_size = size; - - size = m_capacity; - m_capacity = other.m_capacity; - other.m_capacity = size; + rad::Swap(m_size, other.m_size); + rad::Swap(m_capacity, other.m_capacity); } union @@ -583,8 +652,7 @@ struct VectorOperations : public VectorStorage } VectorAlloc vec(alloc); - vec.buffer = vec.allocator.Alloc(capacity); - if (!vec.buffer) + if (!vec.Alloc(capacity)) { return Error::NoMemory; } @@ -612,7 +680,41 @@ struct VectorOperations : public VectorStorage RAD_VERIFY(ShrinkToFit(alloc).IsOk()); } - template + template , int> = 0> + Err Copy(TAllocator& alloc, ThisType& to) + { + if RAD_UNLIKELY (this == &to) + { + return NoError; + } + + // Here we want to provide the strong guarantee, but if the copy + // constructor can throw we cannot just overwrite the memory in the + // old vector because if an exception happens we will wind up with a + // half edited vector. In the worst case the exception happens copying + // the last item. So, we have to maintain a copy of the entire old + // vector somewhere until the copy is complete. The amount of memory + // we need to have is this->Size + to.Size(). For now we have + // implemented the basic thing, but should/will optimize further. Two + // easy things we could are to use unused space in the vectors and/or + // some stack space to avoid expensive memory allocations when possible. + VectorAlloc vec(alloc); + if (!vec.Alloc(m_size)) + { + return Error::NoMemory; + } + + ManipType().CopyCtorRange(vec, Data(), m_size); + to.Swap(vec); + + return NoError; + } + + template , int> = 0> Err Copy(TAllocator& alloc, ThisType& to) noexcept { if RAD_UNLIKELY (this == &to) @@ -626,11 +728,20 @@ struct VectorOperations : public VectorStorage return res.Err(); } - ManipType().CopyCtorDtorDestRange(to.Data(), Data(), m_size); + if (to.m_size >= m_size) + { + ManipType().CopyCtorDtorDestRange(to.Data(), Data(), m_size); - if (to.m_size > m_size) + if (to.m_size > m_size) + { + ManipType().DtorRange(to.Data() + m_size, + to.Data() + to.m_size); + } + } + else { - ManipType().DtorRange(to.Data() + m_size, to.Data() + to.m_size); + ManipType().DtorRange(to.Data(), to.Data() + to.m_size); + ManipType().CopyCtorRange(to.Data(), Data(), m_size); } to.m_size = m_size; @@ -647,8 +758,12 @@ struct VectorOperations : public VectorStorage } } - template - Err Resize(TAllocator& alloc, SizeType count) noexcept + template || !IsNoThrowDefaultCtor, int> = 0> + Err Resize(TAllocator& alloc, + SizeType count, + const ValueType& value = ValueType()) { if RAD_UNLIKELY (count == m_size) { @@ -670,7 +785,15 @@ struct VectorOperations : public VectorStorage } } - ManipType().DefaultCtor(Data() + m_size, count - m_size); + // TODO: Optimize out this allocation when possible. See, comment + // in the copy method for further explanation. + VectorAlloc vec(alloc); + if (!vec.Alloc(count - m_size)) + { + return Error::NoMemory; + } + ManipType().CopyCtor(vec, count - m_size, value); + ManipType().MoveCtorRange(Data() + m_size, vec.buffer, vec.size); } m_size = count; @@ -678,10 +801,12 @@ struct VectorOperations : public VectorStorage return NoError; } - template + template && IsNoThrowDefaultCtor, int> = 0> Err Resize(TAllocator& alloc, SizeType count, - const ValueType& value) noexcept + const ValueType& value = ValueType()) noexcept { if RAD_UNLIKELY (count == m_size) { @@ -711,7 +836,42 @@ struct VectorOperations : public VectorStorage return NoError; } - template + template , int> = 0> + Err Assign(TAllocator& alloc, SizeType count, const ValueType& value) + { + // TODO: Optimize out this allocation when possible. See, comment + // in the copy method for further explanation + VectorAlloc vec(alloc); + if (!vec.Alloc(count)) + { + return Error::NoMemory; + } + + ManipType().CopyCtor(vec, count, value); + ManipType().DtorRange(Data(), Data() + m_size); + + if (count <= m_capacity) + { + // As an optimization we could decide to use the inline storage for + // small values of count and avoid the memory allocation. + ManipType().MoveCtorRange(Data(), vec.buffer, count); + } + else + { + m_data = vec.Release(); + m_capacity = count; + } + + m_size = count; + + return NoError; + } + + template , int> = 0> Err Assign(TAllocator& alloc, SizeType count, const ValueType& value) noexcept @@ -747,7 +907,47 @@ struct VectorOperations : public VectorStorage return NoError; } - template + template , int> = 0> + Err Assign(TAllocator& alloc, Span span) + { + if (SpansOverlap(span, Span(Data(), m_size))) + { + return Error::InvalidAddress; + } + + // TODO: Optimize out this allocation when possible. See, comment + // in the copy method for further explanation + VectorAlloc vec(alloc); + if (!vec.Alloc(span.Size())) + { + return Error::NoMemory; + } + + ManipType().CopyCtorRange(vec, span.Data(), span.Size()); + ManipType().DtorRange(Data(), Data() + m_size); + + if (span.Size() <= m_capacity) + { + // As an optimization we could decide to use the inline storage for + // small values of count and avoid the memory allocation. + ManipType().MoveCtorRange(Data(), vec.buffer, vec.size); + } + else + { + m_data = vec.Release(); + m_capacity = span.Size(); + } + + m_size = span.Size(); + + return NoError; + } + + template , int> = 0> Err Assign(TAllocator& Allocator, Span span) noexcept { if (SpansOverlap(span, Span(Data(), m_size))) @@ -775,7 +975,10 @@ struct VectorOperations : public VectorStorage data++; } - ManipType().DtorRange(data, end); + if (data < end) + { + ManipType().DtorRange(data, end); + } m_size = span.Size(); @@ -783,7 +986,8 @@ struct VectorOperations : public VectorStorage } template - Err EmplaceBack(TAllocator& alloc, TArgs&&... args) noexcept + Err EmplaceBack(TAllocator& alloc, + TArgs&&... args) noexcept(IsNoThrowCtor) { if RAD_LIKELY (RAD_VERIFY(m_size < UINT32_MAX)) { @@ -796,14 +1000,8 @@ struct VectorOperations : public VectorStorage } } - // - // See comments near top of of rad::Vector class. - // - RAD_S_ASSERTMSG( - noexcept(ValueType(Forward(args)...)), - "rad::Vector does not yet support types that might throw."); - - new (AddrOf(Data()[m_size++])) ValueType(Forward(args)...); + new (AddrOf(Data()[m_size])) ValueType(Forward(args)...); + m_size++; return NoError; } diff --git a/test/TestAlloc.h b/test/TestAlloc.h index a0d209b..834e877 100644 --- a/test/TestAlloc.h +++ b/test/TestAlloc.h @@ -219,6 +219,89 @@ class FailingAllocator } }; +template +class OOMAllocator +{ +public: + + static constexpr bool NeedsFree = true; + static constexpr bool HasRealloc = true; + static constexpr bool HasAllocBytes = true; + + using ThisType = StatefulAllocator; + using ValueType = T; + using SizeType = uint32_t; + using DifferenceType = ptrdiff_t; + + ~OOMAllocator() + { + } + + explicit OOMAllocator(int oom) noexcept + : m_oom(oom) + { + } + + OOMAllocator(const OOMAllocator&) noexcept = default; + + template + OOMAllocator(const OOMAllocator& other) noexcept + : m_oom(other.m_oom) + { + } + + template + struct Rebind + { + using Other = OOMAllocator; + }; + + void Free(ValueType* ptr) noexcept + { + free(ptr); + } + + ValueType* Alloc(SizeType count) noexcept + { + m_oom--; + if (m_oom < 0) + { + return nullptr; + } + + return (ValueType*)malloc(count * sizeof(T)); + } + + ValueType* Realloc(ValueType* ptr, SizeType count) noexcept + { + m_oom--; + if (m_oom < 0) + { + return nullptr; + } + + return (ValueType*)realloc(ptr, count * sizeof(T)); + } + + void FreeBytes(void* ptr) noexcept + { + free(ptr); + } + + void* AllocBytes(SizeType size) noexcept + { + m_oom--; + if (m_oom < 0) + { + return nullptr; + } + + return malloc(size); + } + + int m_oom; +}; + class CountingAllocatorImpl { public: diff --git a/test/test_Vector.cpp b/test/test_Vector.cpp index f3314cf..ab855e8 100644 --- a/test/test_Vector.cpp +++ b/test/test_Vector.cpp @@ -22,6 +22,7 @@ #include "gtest/gtest.h" #include "test/TestAlloc.h" + #define RAD_DEFAULT_ALLOCATOR radtest::Allocator #include "radiant/Vector.h" @@ -55,64 +56,108 @@ struct VecTestStats CopyCtorCount == 0 && MoveCtorCount == 0 && CopyAssignCount == 0 && MoveAssignCount == 0); } + + void Add(VecTestStats& stats) noexcept + { + DtorCount += stats.DtorCount; + DefaultCtorCount += stats.DefaultCtorCount; + CtorCount += stats.CtorCount; + CopyCtorCount += stats.CopyCtorCount; + MoveCtorCount += stats.MoveCtorCount; + CopyAssignCount += stats.CopyAssignCount; + MoveAssignCount += stats.MoveAssignCount; + } + + bool AllDestructorsCalled() + { + int ctor = MoveCtorCount + CopyCtorCount + CtorCount + DefaultCtorCount; + return ctor == DtorCount; + } }; static VecTestStats g_stats; -class VectorTests : public ::testing::Test +template +class VectorTest : public testing::Test { -public: - void SetUp() override { g_stats.Reset(); + stats.Reset(); } void TearDown() override { - EXPECT_TRUE(g_stats.Empty()); + stats.Add(g_stats); + EXPECT_TRUE(stats.AllDestructorsCalled()); + g_stats.Reset(); + } + + VecTestStats stats; + +public: + + void ResetStats() + { + stats.Add(g_stats); + g_stats.Reset(); } }; -class VecTester +class SafetyException : public std::exception +{ +}; + +template +class VecTesterBase { public: - ~VecTester() + static constexpr bool isNoThrow = NoThrow; + + ~VecTesterBase() { g_stats.DtorCount++; m_stats.DtorCount++; } - VecTester() noexcept + VecTesterBase() noexcept(NoThrow) { + TryThrow(); + g_stats.DefaultCtorCount++; m_stats.DefaultCtorCount++; } - VecTester(int value) noexcept + explicit VecTesterBase(int value) noexcept(NoThrow) : m_value(value) { + TryThrow(); + g_stats.CtorCount++; m_stats.CtorCount++; } - VecTester(const VecTester& o) noexcept + VecTesterBase(const VecTesterBase& o) noexcept(NoThrow) : m_value(o.m_value) { + TryThrow(); + g_stats.CopyCtorCount++; m_stats.CopyCtorCount++; } - VecTester(VecTester&& o) noexcept + VecTesterBase(VecTesterBase&& o) noexcept : m_value(o.m_value) { g_stats.MoveCtorCount++; m_stats.MoveCtorCount++; } - VecTester& operator=(const VecTester& o) noexcept + VecTesterBase& operator=(const VecTesterBase& o) noexcept(NoThrow) { + TryThrow(); + if (this != &o) { m_value = o.m_value; @@ -124,8 +169,10 @@ class VecTester return *this; } - VecTester& operator=(VecTester&& o) noexcept + VecTesterBase& operator=(VecTesterBase&& o) noexcept { + TryThrow(); + if (this != &o) { m_value = o.m_value; @@ -138,11 +185,44 @@ class VecTester return *this; } + static void ThrowIn(int count) + { + if (isNoThrow) + { + return; + } + + s_throwCount = count - 1; + } + + static void TryThrow() + { + if (isNoThrow) + { + return; + } + + if (s_throwCount == 0) + { + s_throwCount--; + throw SafetyException(); + } + + s_throwCount--; + } + int m_value{ 0 }; VecTestStats m_stats{}; + + static int s_throwCount; }; -TEST(VectorTests, DefaultConstruct) +template <> +int VecTesterBase::s_throwCount = -1; + +using TestVectorIntegral = VectorTest; + +TEST_F(TestVectorIntegral, DefaultConstruct) { rad::Vector vec; @@ -151,7 +231,7 @@ TEST(VectorTests, DefaultConstruct) EXPECT_EQ(vec.Capacity(), 0u); } -TEST(VectorTests, InlineDefaultConstruct) +TEST_F(TestVectorIntegral, InlineDefaultConstruct) { rad::InlineVector vec; @@ -160,7 +240,7 @@ TEST(VectorTests, InlineDefaultConstruct) EXPECT_EQ(vec.Capacity(), 10u); } -TEST(VectorTests, AllocatorCopyConstruct) +TEST_F(TestVectorIntegral, AllocatorCopyConstruct) { radtest::HeapAllocator heap; radtest::AllocWrapper alloc(heap); @@ -175,7 +255,7 @@ TEST(VectorTests, AllocatorCopyConstruct) EXPECT_EQ(vec.GetAllocator().base, &heap); } -TEST(VectorTests, AllocatorMoveConstruct) +TEST_F(TestVectorIntegral, AllocatorMoveConstruct) { using AllocWrap = radtest::AllocWrapper; @@ -191,7 +271,7 @@ TEST(VectorTests, AllocatorMoveConstruct) EXPECT_EQ(vec.GetAllocator().base, &heap); } -TEST(VectorTests, Reserve) +TEST_F(TestVectorIntegral, Reserve) { using AllocWrap = radtest::AllocWrapper; @@ -231,7 +311,7 @@ TEST(VectorTests, Reserve) EXPECT_EQ(heap.freeCount, 1); } -TEST(VectorTests, InlineReserve) +TEST_F(TestVectorIntegral, InlineReserve) { using AllocWrap = radtest::AllocWrapper; @@ -287,7 +367,7 @@ TEST(VectorTests, InlineReserve) EXPECT_EQ(heap.freeCount, 1); } -TEST(VectorTests, ReserveFail) +TEST_F(TestVectorIntegral, ReserveFail) { using AllocWrap = radtest::AllocWrapper; @@ -304,7 +384,7 @@ TEST(VectorTests, ReserveFail) EXPECT_EQ(heap.allocCount, 0); } -TEST(VectorTests, InlineReserveFail) +TEST_F(TestVectorIntegral, InlineReserveFail) { using AllocWrap = radtest::AllocWrapper; @@ -321,7 +401,7 @@ TEST(VectorTests, InlineReserveFail) EXPECT_EQ(heap.allocCount, 0); } -TEST(VectorTests, PushBack) +TEST_F(TestVectorIntegral, PushBack) { rad::Vector vec; @@ -341,7 +421,7 @@ TEST(VectorTests, PushBack) EXPECT_EQ(vec.At(2), 789); } -TEST(VectorTests, PushBackCopy) +TEST_F(TestVectorIntegral, PushBackCopy) { int value = 1337; rad::Vector vec; @@ -365,7 +445,7 @@ TEST(VectorTests, PushBackCopy) EXPECT_EQ(vec.At(2), 1337); } -TEST(VectorTests, EmplaceBack) +TEST_F(TestVectorIntegral, EmplaceBack) { struct TestStruct { @@ -399,7 +479,7 @@ TEST(VectorTests, EmplaceBack) EXPECT_EQ(vec.At(2).Second, false); } -TEST(VectorTests, FrontBack) +TEST_F(TestVectorIntegral, FrontBack) { rad::Vector vec; @@ -416,7 +496,7 @@ TEST(VectorTests, FrontBack) EXPECT_EQ(constVec.Back(), 789); } -TEST(VectorTests, At) +TEST_F(TestVectorIntegral, At) { rad::Vector vec; @@ -435,7 +515,7 @@ TEST(VectorTests, At) EXPECT_EQ(constVec.At(2), 789); } -TEST(VectorTests, Subscript) +TEST_F(TestVectorIntegral, Subscript) { rad::Vector vec; @@ -454,7 +534,7 @@ TEST(VectorTests, Subscript) EXPECT_EQ(constVec[2], 789); } -TEST(VectorTests, Data) +TEST_F(TestVectorIntegral, Data) { rad::Vector vec; @@ -473,7 +553,7 @@ TEST(VectorTests, Data) EXPECT_EQ(constVec.Data()[2], 789); } -TEST(VectorTests, PopBack) +TEST_F(TestVectorIntegral, PopBack) { rad::Vector vec; @@ -499,7 +579,7 @@ TEST(VectorTests, PopBack) EXPECT_EQ(vec.Size(), 0u); } -TEST(VectorTests, Resize) +TEST_F(TestVectorIntegral, Resize) { rad::Vector vec; @@ -530,7 +610,7 @@ TEST(VectorTests, Resize) EXPECT_EQ(vec.Back(), 0); } -TEST(VectorTests, ResizeCopy) +TEST_F(TestVectorIntegral, ResizeCopy) { int value = 1337; @@ -564,7 +644,7 @@ TEST(VectorTests, ResizeCopy) EXPECT_EQ(vec.Back(), 1337); } -TEST(VectorTests, Clear) +TEST_F(TestVectorIntegral, Clear) { rad::Vector vec; @@ -577,7 +657,7 @@ TEST(VectorTests, Clear) EXPECT_EQ(vec.Size(), 0u); } -TEST(VectorTests, ShrinkToFit) +TEST_F(TestVectorIntegral, ShrinkToFit) { rad::Vector vec; @@ -617,7 +697,7 @@ TEST(VectorTests, ShrinkToFit) EXPECT_EQ(vec.Capacity(), vec.Size()); } -TEST(VectorTests, ShrinkToFitInline) +TEST_F(TestVectorIntegral, ShrinkToFitInline) { rad::InlineVector vec; @@ -696,7 +776,7 @@ TEST(VectorTests, ShrinkToFitInline) EXPECT_EQ(vec.Capacity(), decltype(vec)::InlineCount); } -TEST(VectorTests, AssignCopy) +TEST_F(TestVectorIntegral, AssignCopy) { int value = 1337; @@ -730,7 +810,7 @@ TEST(VectorTests, AssignCopy) #if RAD_ENABLE_STD -TEST(VectorTests, AssignInit) +TEST_F(TestVectorIntegral, AssignInit) { rad::Vector vec; @@ -753,7 +833,7 @@ TEST(VectorTests, AssignInit) EXPECT_EQ(vec.Back(), 0); } -TEST(VectorTests, AssignRange) +TEST_F(TestVectorIntegral, AssignRange) { rad::Vector other; @@ -768,7 +848,7 @@ TEST(VectorTests, AssignRange) EXPECT_EQ(vec.Back(), other.Back()); } -TEST(VectorTests, Swap) +TEST_F(TestVectorIntegral, Swap) { rad::Vector vec; rad::Vector other; @@ -789,7 +869,7 @@ TEST(VectorTests, Swap) EXPECT_EQ(other.Back(), 3); } -TEST(VectorTests, InlineSwapBothInline) +TEST_F(TestVectorIntegral, InlineSwapBothInline) { rad::InlineVector vec; rad::InlineVector other; @@ -810,7 +890,7 @@ TEST(VectorTests, InlineSwapBothInline) EXPECT_EQ(other.Back(), 3); } -TEST(VectorTests, InlineSwapNeitherInline) +TEST_F(TestVectorIntegral, InlineSwapNeitherInline) { rad::InlineVector vec; rad::InlineVector other; @@ -831,7 +911,7 @@ TEST(VectorTests, InlineSwapNeitherInline) EXPECT_EQ(other.Back(), 6); } -TEST(VectorTests, InlineSwapDisjoint) +TEST_F(TestVectorIntegral, InlineSwapDisjoint) { rad::InlineVector vec; rad::InlineVector other; @@ -862,7 +942,7 @@ TEST(VectorTests, InlineSwapDisjoint) EXPECT_EQ(vec.Back(), 3); } -TEST(VectorTests, Copy) +TEST_F(TestVectorIntegral, Copy) { rad::Vector vec; rad::Vector other; @@ -873,16 +953,17 @@ TEST(VectorTests, Copy) EXPECT_EQ(vec.Size(), 3u); EXPECT_EQ(vec.Front(), 1); + EXPECT_EQ(vec[1], 2); EXPECT_EQ(vec.Back(), 3); EXPECT_TRUE(vec.Copy(other).IsOk()); EXPECT_EQ(vec.Size(), other.Size()); - EXPECT_EQ(vec.Front(), other.Front()); - EXPECT_EQ(vec.Back(), other.Back()); + EXPECT_EQ(1, other.Front()); + EXPECT_EQ(3, other.Back()); } -TEST(VectorTests, CopyOverwrite) +TEST_F(TestVectorIntegral, CopyOverwrite) { rad::Vector vec; rad::Vector other; @@ -901,7 +982,7 @@ TEST(VectorTests, CopyOverwrite) EXPECT_EQ(vec.Back(), other.Back()); } -TEST(VectorTests, Move) +TEST_F(TestVectorIntegral, Move) { rad::Vector vec; rad::Vector other; @@ -922,7 +1003,7 @@ TEST(VectorTests, Move) EXPECT_EQ(other.Back(), 3); } -TEST(VectorTests, Double) +TEST_F(TestVectorIntegral, Double) { rad::Vector vec; @@ -933,7 +1014,7 @@ TEST(VectorTests, Double) EXPECT_EQ(vec.At(2), 3.3); } -TEST(VectorTests, Float) +TEST_F(TestVectorIntegral, Float) { rad::Vector vec; @@ -944,7 +1025,7 @@ TEST(VectorTests, Float) EXPECT_EQ(vec.At(2), 3.3f); } -TEST(VectorTests, RemoveBack) +TEST_F(TestVectorIntegral, RemoveBack) { rad::Vector vec; @@ -955,7 +1036,7 @@ TEST(VectorTests, RemoveBack) EXPECT_EQ(vec.RemoveBack(), 1); } -TEST(VectorTests, Seek) +TEST_F(TestVectorIntegral, Seek) { rad::Vector vec; @@ -974,7 +1055,7 @@ TEST(VectorTests, Seek) EXPECT_TRUE(other.Seek(3).IsErr()); } -TEST(VectorTests, SeekFront) +TEST_F(TestVectorIntegral, SeekFront) { rad::Vector vec; @@ -989,7 +1070,7 @@ TEST(VectorTests, SeekFront) EXPECT_EQ(other.SeekFront(), 1); } -TEST(VectorTests, SeekBack) +TEST_F(TestVectorIntegral, SeekBack) { rad::Vector vec; @@ -1004,7 +1085,7 @@ TEST(VectorTests, SeekBack) EXPECT_EQ(other.SeekBack(), 3); } -TEST(VectorTests, MoveOperator) +TEST_F(TestVectorIntegral, MoveOperator) { rad::Vector vec; rad::Vector other; @@ -1021,7 +1102,7 @@ TEST(VectorTests, MoveOperator) EXPECT_EQ(other[2], 3); } -TEST(VectorTests, AssignDown) +TEST_F(TestVectorIntegral, AssignDown) { rad::Vector vec; @@ -1039,7 +1120,7 @@ TEST(VectorTests, AssignDown) EXPECT_EQ(vec[1], 123); } -TEST(VectorTests, AssignOverlapping) +TEST_F(TestVectorIntegral, AssignOverlapping) { rad::Vector vec; @@ -1051,7 +1132,7 @@ TEST(VectorTests, AssignOverlapping) EXPECT_EQ(vec.Assign(vec.ToSpan(2)).Err(), rad::Error::InvalidAddress); } -TEST(VectorTests, ResizeSame) +TEST_F(TestVectorIntegral, ResizeSame) { rad::Vector vec; @@ -1068,7 +1149,7 @@ TEST(VectorTests, ResizeSame) EXPECT_EQ(vec[2], 3); } -TEST(VectorTests, ShrinkToFitNoMemory) +TEST_F(TestVectorIntegral, ShrinkToFitNoMemory) { using AllocWrap = radtest::AllocWrapper; @@ -1087,7 +1168,7 @@ TEST(VectorTests, ShrinkToFitNoMemory) EXPECT_EQ(vec[1], 2); } -TEST(VectorTests, InlineShrinkToFitNoMemory) +TEST_F(TestVectorIntegral, InlineShrinkToFitNoMemory) { using AllocWrap = radtest::AllocWrapper; @@ -1106,7 +1187,7 @@ TEST(VectorTests, InlineShrinkToFitNoMemory) EXPECT_EQ(vec[1], 2); } -TEST(VectorTests, CopyNoMemory) +TEST_F(TestVectorIntegral, CopyNoMemory) { using AllocWrap = radtest::AllocWrapper; @@ -1127,7 +1208,7 @@ TEST(VectorTests, CopyNoMemory) #endif // RAD_ENABLE_STD -TEST(VectorTests, ResizeNoMemory) +TEST_F(TestVectorIntegral, ResizeNoMemory) { using AllocWrap = radtest::AllocWrapper; @@ -1147,7 +1228,7 @@ TEST(VectorTests, ResizeNoMemory) #if RAD_ENABLE_STD -TEST(VectorTests, AssignNoMemory) +TEST_F(TestVectorIntegral, AssignNoMemory) { using AllocWrap = radtest::AllocWrapper; @@ -1167,7 +1248,7 @@ TEST(VectorTests, AssignNoMemory) #endif // RAD_ENABLE_STD -TEST(VectorTests, EmplaceBackNoMemory) +TEST_F(TestVectorIntegral, EmplaceBackNoMemory) { using AllocWrap = radtest::AllocWrapper; @@ -1182,7 +1263,7 @@ TEST(VectorTests, EmplaceBackNoMemory) #if RAD_ENABLE_STD -TEST(VectorTests, EqualityOperators) +TEST_F(TestVectorIntegral, EqualityOperators) { rad::Vector left; rad::Vector right; @@ -1226,7 +1307,7 @@ TEST(VectorTests, EqualityOperators) EXPECT_TRUE(ilright != left); } -TEST(VectorTests, ComparisonOperators) +TEST_F(TestVectorIntegral, ComparisonOperators) { rad::Vector left; rad::Vector right; @@ -1289,11 +1370,14 @@ TEST(VectorTests, ComparisonOperators) #endif // RAD_ENABLE_STD -TEST(VectorTests, NonTrivReserve) -{ - g_stats.Reset(); +template +using NonTrivialStruct = VectorTest; - rad::Vector vec; +TYPED_TEST_SUITE_P(NonTrivialStruct); + +TYPED_TEST_P(NonTrivialStruct, Reserve) +{ + rad::Vector vec; EXPECT_TRUE(vec.Reserve(10).IsOk()); @@ -1304,49 +1388,47 @@ TEST(VectorTests, NonTrivReserve) EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - - g_stats.Reset(); } -TEST(VectorTests, NonTrivResize) +TYPED_TEST_P(NonTrivialStruct, Resize) { - VecTester value(123); - rad::Vector vec; + TypeParam value(123); + rad::Vector vec; - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Resize(5).IsOk()); - EXPECT_EQ(g_stats.DtorCount, 0); - EXPECT_EQ(g_stats.DefaultCtorCount, 5); + EXPECT_EQ(g_stats.DtorCount, TypeParam::isNoThrow ? 1 : 6); + EXPECT_EQ(g_stats.DefaultCtorCount, 1); EXPECT_EQ(g_stats.CtorCount, 0); - EXPECT_EQ(g_stats.CopyCtorCount, 0); - EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 5); + EXPECT_EQ(g_stats.MoveCtorCount, TypeParam::isNoThrow ? 0 : 5); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Resize(5).IsOk()); - EXPECT_EQ(g_stats.DtorCount, 0); - EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.DtorCount, 1); + EXPECT_EQ(g_stats.DefaultCtorCount, 1); EXPECT_EQ(g_stats.CtorCount, 0); EXPECT_EQ(g_stats.CopyCtorCount, 0); EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Resize(2).IsOk()); - EXPECT_EQ(g_stats.DtorCount, 3); - EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.DtorCount, 4); + EXPECT_EQ(g_stats.DefaultCtorCount, 1); EXPECT_EQ(g_stats.CtorCount, 0); EXPECT_EQ(g_stats.CopyCtorCount, 0); EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Resize(2, value).IsOk()); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1357,38 +1439,44 @@ TEST(VectorTests, NonTrivResize) EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Resize(5, value).IsOk()); - EXPECT_EQ(g_stats.DtorCount, 0); + EXPECT_EQ(g_stats.DtorCount, TypeParam::isNoThrow ? 0 : 3); EXPECT_EQ(g_stats.DefaultCtorCount, 0); EXPECT_EQ(g_stats.CtorCount, 0); EXPECT_EQ(g_stats.CopyCtorCount, 3); - EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.MoveCtorCount, TypeParam::isNoThrow ? 0 : 3); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Resize(10, value).IsOk()); - EXPECT_EQ(g_stats.DtorCount, 5); + EXPECT_EQ(g_stats.DtorCount, TypeParam::isNoThrow ? 5 : 10); EXPECT_EQ(g_stats.DefaultCtorCount, 0); EXPECT_EQ(g_stats.CtorCount, 0); EXPECT_EQ(g_stats.CopyCtorCount, 5); - EXPECT_EQ(g_stats.MoveCtorCount, 5); + EXPECT_EQ(g_stats.MoveCtorCount, TypeParam::isNoThrow ? 5 : 10); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + auto allocator = + radtest::OOMAllocator(TypeParam::isNoThrow ? 1 : 3); + rad::Vector> fail(allocator); + EXPECT_TRUE(fail.Resize(5, value).IsOk()); + + EXPECT_EQ(fail.Resize(10, value), rad::Error::NoMemory); + EXPECT_EQ(fail.Resize(20, value), rad::Error::NoMemory); } -TEST(VectorTests, NonTrivAssign) +TYPED_TEST_P(NonTrivialStruct, Assign) { - VecTester value(123); + TypeParam value(123); - rad::Vector vec; + rad::Vector vec; - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Assign(10, value).IsOk()); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1399,9 +1487,9 @@ TEST(VectorTests, NonTrivAssign) EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - value = 456; + value = TypeParam(456); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Assign(20, value).IsOk()); EXPECT_EQ(g_stats.DtorCount, 10); @@ -1412,27 +1500,111 @@ TEST(VectorTests, NonTrivAssign) EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Assign(5, value).IsOk()); - EXPECT_EQ(g_stats.DtorCount, 20); + EXPECT_EQ(g_stats.DtorCount, TypeParam::isNoThrow ? 20 : 25); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 5); + EXPECT_EQ(g_stats.MoveCtorCount, TypeParam::isNoThrow ? 0 : 5); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + rad::Vector> fail( + radtest::OOMAllocator(0)); + EXPECT_EQ(fail.Assign(10, value), rad::Error::NoMemory); +} + +TYPED_TEST_P(NonTrivialStruct, AssignSpan) +{ + TypeParam value(123); + + rad::Vector vec; + + VectorTest::ResetStats(); + EXPECT_TRUE(vec.Assign(10, value).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, 0); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 10); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + value = TypeParam(456); + rad::Vector spanVec; + EXPECT_TRUE(spanVec.Assign(20, value).IsOk()); + + VectorTest::ResetStats(); + + EXPECT_TRUE(vec.Assign(spanVec.ToSpan()).IsOk()); + + ASSERT_EQ(vec.Size(), 20UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 456); + } + + EXPECT_EQ(g_stats.DtorCount, TypeParam::isNoThrow ? 20 : 10); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 20); + EXPECT_EQ(g_stats.MoveCtorCount, TypeParam::isNoThrow ? 10 : 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + EXPECT_TRUE(spanVec.Assign(5, value).IsOk()); + + VectorTest::ResetStats(); + EXPECT_TRUE(vec.Assign(spanVec.ToSpan()).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, TypeParam::isNoThrow ? 20 : 25); EXPECT_EQ(g_stats.DefaultCtorCount, 0); EXPECT_EQ(g_stats.CtorCount, 0); EXPECT_EQ(g_stats.CopyCtorCount, 5); + EXPECT_EQ(g_stats.MoveCtorCount, TypeParam::isNoThrow ? 0 : 5); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + ASSERT_TRUE(vec.Reserve(20)); + EXPECT_TRUE(spanVec.Assign(15, value).IsOk()); + + VectorTest::ResetStats(); + EXPECT_TRUE(vec.Assign(spanVec.ToSpan()).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, TypeParam::isNoThrow ? 5 : 20); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 15); + EXPECT_EQ(g_stats.MoveCtorCount, TypeParam::isNoThrow ? 0 : 15); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + VectorTest::ResetStats(); + EXPECT_FALSE(vec.Assign(vec.ToSpan().Subspan(0, 5)).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, 0); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 0); EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + rad::Vector> fail( + radtest::OOMAllocator(0)); + EXPECT_EQ(fail.Assign(spanVec.ToSpan()), rad::Error::NoMemory); } -TEST(VectorTests, NonTrivClear) +TYPED_TEST_P(NonTrivialStruct, Clear) { - VecTester value(123); + TypeParam value(123); - rad::Vector vec; + rad::Vector vec; - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Assign(10, value).IsOk()); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1443,7 +1615,7 @@ TEST(VectorTests, NonTrivClear) EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + VectorTest::ResetStats(); vec.Clear(); EXPECT_EQ(g_stats.DtorCount, 10); @@ -1453,17 +1625,15 @@ TEST(VectorTests, NonTrivClear) EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - - g_stats.Reset(); } -TEST(VectorTests, NonTrivPushBackLVal) +TYPED_TEST_P(NonTrivialStruct, PushBackLVal) { - VecTester value(123); + TypeParam value(123); - rad::Vector vec; + rad::Vector vec; - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.PushBack(value).IsOk()); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1473,17 +1643,15 @@ TEST(VectorTests, NonTrivPushBackLVal) EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - - g_stats.Reset(); } -TEST(VectorTests, NonTrivPushBackRVal) +TYPED_TEST_P(NonTrivialStruct, PushBackRVal) { - VecTester value(123); + TypeParam value(123); - rad::Vector vec; + rad::Vector vec; - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.PushBack(rad::Move(value)).IsOk()); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1493,15 +1661,13 @@ TEST(VectorTests, NonTrivPushBackRVal) EXPECT_EQ(g_stats.MoveCtorCount, 1); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - - g_stats.Reset(); } -TEST(VectorTests, NonTrivPushEmplaceBack) +TYPED_TEST_P(NonTrivialStruct, PushEmplaceBack) { - rad::Vector vec; + rad::Vector vec; - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.EmplaceBack(123).IsOk()); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1511,24 +1677,22 @@ TEST(VectorTests, NonTrivPushEmplaceBack) EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - - g_stats.Reset(); } -TEST(VectorTests, NonTrivCopy) +TYPED_TEST_P(NonTrivialStruct, Copy) { - VecTester value(123); + TypeParam value(123); - rad::Vector vec; + rad::Vector vec; EXPECT_TRUE(vec.Assign(10, value).IsOk()); - rad::Vector other; + rad::Vector other; - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.Copy(other).IsOk()); - EXPECT_EQ(g_stats.DtorCount, 10); + EXPECT_EQ(g_stats.DtorCount, 0); EXPECT_EQ(g_stats.DefaultCtorCount, 0); EXPECT_EQ(g_stats.CtorCount, 0); EXPECT_EQ(g_stats.CopyCtorCount, 10); @@ -1536,20 +1700,48 @@ TEST(VectorTests, NonTrivCopy) EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - g_stats.Reset(); + vec.PopBack(); + VectorTest::ResetStats(); + EXPECT_TRUE(vec.Copy(other).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, 10); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 9); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + VectorTest::ResetStats(); + EXPECT_TRUE(vec.Copy(vec).IsOk()); + EXPECT_EQ(g_stats.DtorCount, 0); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 0); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + rad::Vector> good( + radtest::OOMAllocator(10)); + EXPECT_TRUE(good.Assign(10, value).IsOk()); + + rad::Vector> fail( + radtest::OOMAllocator(0)); + EXPECT_EQ(good.Copy(fail), rad::Error::NoMemory); } -TEST(VectorTests, NonTrivMove) +TYPED_TEST_P(NonTrivialStruct, Move) { - VecTester value(123); + TypeParam value(123); - rad::Vector vec; + rad::Vector vec; EXPECT_TRUE(vec.Assign(10, value).IsOk()); - rad::Vector other; + rad::Vector other; - g_stats.Reset(); + VectorTest::ResetStats(); vec.Move(other); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1559,21 +1751,19 @@ TEST(VectorTests, NonTrivMove) EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - - g_stats.Reset(); } -TEST(VectorTests, NonTrivShrinkToFit) +TYPED_TEST_P(NonTrivialStruct, ShrinkToFit) { - VecTester value(123); + TypeParam value(123); - rad::Vector vec; + rad::Vector vec; EXPECT_TRUE(vec.Assign(10, value).IsOk()); vec.PopBack(); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.ShrinkToFit().IsOk()); EXPECT_EQ(g_stats.DtorCount, 9); @@ -1589,7 +1779,7 @@ TEST(VectorTests, NonTrivShrinkToFit) vec.PopBack(); } - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.ShrinkToFit().IsOk()); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1599,21 +1789,19 @@ TEST(VectorTests, NonTrivShrinkToFit) EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - - g_stats.Reset(); } -TEST(VectorTests, NonTrivSwap) +TYPED_TEST_P(NonTrivialStruct, Swap) { - VecTester value(123); + TypeParam value(123); - rad::Vector vec; + rad::Vector vec; EXPECT_TRUE(vec.Assign(10, value).IsOk()); - rad::Vector other; + rad::Vector other; - g_stats.Reset(); + VectorTest::ResetStats(); vec.Swap(other); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1623,21 +1811,19 @@ TEST(VectorTests, NonTrivSwap) EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - - g_stats.Reset(); } -TEST(VectorTests, InlineNonTrivShrinkToFit) +TYPED_TEST_P(NonTrivialStruct, InlineShrinkToFit) { - VecTester value(123); + TypeParam value(123); - rad::InlineVector vec; + rad::InlineVector vec; EXPECT_TRUE(vec.Assign(10, value).IsOk()); vec.PopBack(); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.ShrinkToFit().IsOk()); EXPECT_EQ(g_stats.DtorCount, 9); @@ -1653,7 +1839,7 @@ TEST(VectorTests, InlineNonTrivShrinkToFit) vec.PopBack(); vec.PopBack(); - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.ShrinkToFit().IsOk()); EXPECT_EQ(g_stats.DtorCount, 5); @@ -1669,7 +1855,7 @@ TEST(VectorTests, InlineNonTrivShrinkToFit) vec.PopBack(); } - g_stats.Reset(); + VectorTest::ResetStats(); EXPECT_TRUE(vec.ShrinkToFit().IsOk()); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1679,23 +1865,21 @@ TEST(VectorTests, InlineNonTrivShrinkToFit) EXPECT_EQ(g_stats.MoveCtorCount, 0); EXPECT_EQ(g_stats.CopyAssignCount, 0); EXPECT_EQ(g_stats.MoveAssignCount, 0); - - g_stats.Reset(); } -TEST(VectorTests, InlineNonTrivSwap) +TYPED_TEST_P(NonTrivialStruct, InlineSwap) { - VecTester value(123); + TypeParam value(123); - rad::InlineVector vec; + rad::InlineVector vec; EXPECT_TRUE(vec.Assign(10, value).IsOk()); - rad::InlineVector other; + rad::InlineVector other; EXPECT_TRUE(other.Assign(10, value).IsOk()); - g_stats.Reset(); + VectorTest::ResetStats(); vec.Swap(other); EXPECT_EQ(g_stats.DtorCount, 0); @@ -1713,7 +1897,7 @@ TEST(VectorTests, InlineNonTrivSwap) vec.PopBack(); EXPECT_TRUE(vec.ShrinkToFit().IsOk()); - g_stats.Reset(); + VectorTest::ResetStats(); vec.Swap(other); EXPECT_EQ(g_stats.DtorCount, 5); @@ -1731,27 +1915,48 @@ TEST(VectorTests, InlineNonTrivSwap) vec.PopBack(); EXPECT_TRUE(vec.ShrinkToFit().IsOk()); - g_stats.Reset(); + VectorTest::ResetStats(); vec.Swap(other); - EXPECT_EQ(g_stats.DtorCount, 5); + EXPECT_EQ(g_stats.DtorCount, 15); EXPECT_EQ(g_stats.DefaultCtorCount, 0); EXPECT_EQ(g_stats.CtorCount, 0); EXPECT_EQ(g_stats.CopyCtorCount, 0); - EXPECT_EQ(g_stats.MoveCtorCount, 10); + EXPECT_EQ(g_stats.MoveCtorCount, 15); EXPECT_EQ(g_stats.CopyAssignCount, 0); - EXPECT_EQ(g_stats.MoveAssignCount, 5); - - g_stats.Reset(); + EXPECT_EQ(g_stats.MoveAssignCount, 0); } +using ThrowingVecTester = VecTesterBase; +using NonThrowingVecTester = VecTesterBase; + +REGISTER_TYPED_TEST_SUITE_P(NonTrivialStruct, + Reserve, + Resize, + Assign, + AssignSpan, + Clear, + PushBackLVal, + PushBackRVal, + PushEmplaceBack, + Copy, + Move, + ShrinkToFit, + Swap, + InlineShrinkToFit, + InlineSwap); + +using Types = testing::Types; + +INSTANTIATE_TYPED_TEST_SUITE_P(TestVector, NonTrivialStruct, Types); + struct VecTestStruct { uint64_t UInt64; double Double; }; -TEST(VectorTests, TrivialStructurePushBack) +TEST(TestVectorTrivialStruct, PushBack) { rad::Vector vec; @@ -1772,7 +1977,7 @@ TEST(VectorTests, TrivialStructurePushBack) } } -TEST(VectorTests, Pointers) +TEST(TestVectorTrivialStruct, Pointers) { rad::Vector vec; @@ -1790,7 +1995,379 @@ TEST(VectorTests, Pointers) auto& entry = vec.At(i); EXPECT_EQ(entry, &data); - EXPECT_EQ(entry->UInt64, 9); + EXPECT_EQ(entry->UInt64, 9UL); EXPECT_EQ(entry->Double, static_cast(9)); } } + +// Exception Safety Tests +// We provide the strong guarantee for Vector. To do this we require that the +// contained types have noexcept move, swap, and destruction +// +// The methods of vector that require special consideration in regard to +// exception safety are Resize, Assign, EmplaceBack, PushBack, and Copy. +using TestVectorStrongGuarantee = VectorTest; + +TEST_F(TestVectorStrongGuarantee, ResizeDefaultConstructed) +{ + rad::Vector vec; + ThrowingVecTester::ThrowIn(3); + EXPECT_THROW(vec.Resize(5).IsOk(), SafetyException); + + EXPECT_EQ(vec.Size(), 0UL); + EXPECT_EQ(g_stats.DtorCount, 2); + EXPECT_EQ(g_stats.DefaultCtorCount, 1); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 1); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + ResetStats(); + EXPECT_TRUE(vec.Resize(5).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, 6); + EXPECT_EQ(g_stats.DefaultCtorCount, 1); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 5); + EXPECT_EQ(g_stats.MoveCtorCount, 5); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); +} + +TEST_F(TestVectorStrongGuarantee, ResizeItemConstructed) +{ + ThrowingVecTester value(123); + rad::Vector vec; + + ResetStats(); + ThrowingVecTester::ThrowIn(2); + + EXPECT_THROW(vec.Resize(5, value).IsOk(), SafetyException); + + EXPECT_EQ(vec.Size(), 0UL); + EXPECT_EQ(g_stats.DtorCount, 1); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 1); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + ResetStats(); + EXPECT_TRUE(vec.Resize(5, value).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, 5); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 5); + EXPECT_EQ(g_stats.MoveCtorCount, 5); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); +} + +TEST_F(TestVectorStrongGuarantee, Assign) +{ + ThrowingVecTester value(123); + + rad::Vector vec; + + ResetStats(); + EXPECT_TRUE(vec.Assign(10, value).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, 0); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 10); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + value = ThrowingVecTester(456); + + ResetStats(); + ThrowingVecTester::ThrowIn(5); + EXPECT_THROW(vec.Assign(20, value).IsOk(), SafetyException); + + ASSERT_EQ(vec.Size(), 10UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 123); + } + + EXPECT_EQ(g_stats.DtorCount, 4); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 4); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + ResetStats(); + ThrowingVecTester::ThrowIn(3); + EXPECT_THROW(vec.Assign(5, value).IsOk(), SafetyException); + + ASSERT_EQ(vec.Size(), 10UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 123); + } + + EXPECT_EQ(g_stats.DtorCount, 2); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 2); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + ASSERT_TRUE(vec.Reserve(20)); + ResetStats(); + ThrowingVecTester::ThrowIn(2); + EXPECT_THROW(vec.Assign(15, value).IsOk(), SafetyException); + + ASSERT_EQ(vec.Size(), 10UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 123); + } + + EXPECT_EQ(g_stats.DtorCount, 1); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 1); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); +} + +TEST_F(TestVectorStrongGuarantee, AssignSpan) +{ + ThrowingVecTester value(123); + + rad::Vector vec; + + ResetStats(); + EXPECT_TRUE(vec.Assign(10, value).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, 0); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 10); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + value = ThrowingVecTester(456); + rad::Vector spanVec; + EXPECT_TRUE(spanVec.Assign(20, value).IsOk()); + + ResetStats(); + + ThrowingVecTester::ThrowIn(5); + EXPECT_THROW(vec.Assign(spanVec.ToSpan()).IsOk(), SafetyException); + + ASSERT_EQ(vec.Size(), 10UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 123); + } + + EXPECT_EQ(g_stats.DtorCount, 4); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 4); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + EXPECT_TRUE(spanVec.Assign(5, value).IsOk()); + + ResetStats(); + ThrowingVecTester::ThrowIn(3); + EXPECT_THROW(vec.Assign(spanVec.ToSpan()).IsOk(), SafetyException); + + ASSERT_EQ(vec.Size(), 10UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 123); + } + + EXPECT_EQ(g_stats.DtorCount, 2); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 2); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + ASSERT_TRUE(vec.Reserve(20)); + EXPECT_TRUE(spanVec.Assign(15, value).IsOk()); + + ResetStats(); + ThrowingVecTester::ThrowIn(2); + EXPECT_THROW(vec.Assign(spanVec.ToSpan()).IsOk(), SafetyException); + + ASSERT_EQ(vec.Size(), 10UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 123); + } + + EXPECT_EQ(g_stats.DtorCount, 1); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 1); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); +} + +#if RAD_ENABLE_STD + +TEST_F(TestVectorStrongGuarantee, AssignIList) +{ + ThrowingVecTester value(123); + + rad::Vector vec; + + ResetStats(); + EXPECT_TRUE(vec.Assign(5, value).IsOk()); + + EXPECT_EQ(g_stats.DtorCount, 0); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 5); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + ThrowingVecTester v(456); + ResetStats(); + + ThrowingVecTester::ThrowIn(8); + EXPECT_THROW(vec.Assign({ v, v, v, v, v, v }).IsOk(), SafetyException); + + ASSERT_EQ(vec.Size(), 5UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 123); + } + + EXPECT_EQ(g_stats.DtorCount, 7); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 7); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + ResetStats(); + ThrowingVecTester::ThrowIn(5); + EXPECT_THROW(vec.Assign({ v, v, v }).IsOk(), SafetyException); + + ASSERT_EQ(vec.Size(), 5UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 123); + } + + EXPECT_EQ(g_stats.DtorCount, 4); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 4); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); + + ASSERT_TRUE(vec.Reserve(20)); + + ResetStats(); + ThrowingVecTester::ThrowIn(8); + EXPECT_THROW(vec.Assign({ v, v, v, v, v, v }).IsOk(), SafetyException); + + ASSERT_EQ(vec.Size(), 5UL); + for (auto& tester : vec.ToSpan()) + { + EXPECT_EQ(tester.m_value, 123); + } + + EXPECT_EQ(g_stats.DtorCount, 7); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 7); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); +} + +#endif // RAD_ENABLE_STD + +TEST_F(TestVectorStrongGuarantee, EmplaceBack) +{ + rad::Vector vec; + + ResetStats(); + ThrowingVecTester::ThrowIn(1); + EXPECT_THROW(vec.EmplaceBack(123).IsOk(), SafetyException); + + EXPECT_EQ(vec.Size(), 0UL); + EXPECT_EQ(g_stats.DtorCount, 0); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 0); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); +} + +TEST_F(TestVectorStrongGuarantee, PushBackLVal) +{ + ThrowingVecTester value(123); + + rad::Vector vec; + + ResetStats(); + ThrowingVecTester::ThrowIn(1); + EXPECT_THROW(vec.PushBack(value).IsOk(), SafetyException); + + EXPECT_EQ(vec.Size(), 0UL); + EXPECT_EQ(g_stats.DtorCount, 0); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 0); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); +} + +TEST_F(TestVectorStrongGuarantee, Copy) +{ + ThrowingVecTester value(123); + rad::Vector vec; + + EXPECT_TRUE(vec.Assign(10, value).IsOk()); + + ThrowingVecTester otherValue(456); + rad::Vector other; + + EXPECT_TRUE(other.Assign(2, otherValue).IsOk()); + + ResetStats(); + ThrowingVecTester::ThrowIn(6); + EXPECT_THROW(vec.Copy(other).IsOk(), SafetyException); + + EXPECT_EQ(other.Size(), 2UL); + for (auto& tester : other.ToSpan()) + { + EXPECT_EQ(tester.m_value, 456); + } + + EXPECT_EQ(g_stats.DtorCount, 5); + EXPECT_EQ(g_stats.DefaultCtorCount, 0); + EXPECT_EQ(g_stats.CtorCount, 0); + EXPECT_EQ(g_stats.CopyCtorCount, 5); + EXPECT_EQ(g_stats.MoveCtorCount, 0); + EXPECT_EQ(g_stats.CopyAssignCount, 0); + EXPECT_EQ(g_stats.MoveAssignCount, 0); +}