#include #include "deathTestCommon.h" #include "gsl/dyn_array" #include #include #include #include #include #include #include // Despite using and utilities in this test, they // are not being included directly by this file as a test to ensure // transitive inclusion via . static_assert(sizeof(gsl::dyn_array) == 2 * sizeof(void*), "gsl::dyn_array (with the default allocator) should be 16 bytes"); #if defined(__cpp_lib_concepts) && (__cpp_lib_concepts >= 202002L) static_assert(std::input_iterator::iterator>, "gsl::dyn_array should expose a valid input_iterator"); #endif /* __cpp_lib_concepts >= 202002L */ #if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) static_assert(std::ranges::input_range>, "gsl::dyn_array should be a valid input range"); #endif /* __cpp_lib_ranges >= 201911L */ TEST(dyn_array_tests, default_ctor) { gsl::dyn_array diamondbacks; EXPECT_TRUE(diamondbacks.empty()); EXPECT_EQ(diamondbacks.size(), 0); EXPECT_EQ(diamondbacks.data(), nullptr); } TEST(dyn_array_tests, count_ctor) { gsl::dyn_array athletics(10); EXPECT_FALSE(athletics.empty()); EXPECT_EQ(athletics.size(), 10); EXPECT_NE(athletics.data(), nullptr); gsl::dyn_array braves(0); EXPECT_TRUE(braves.empty()); EXPECT_EQ(braves.size(), 0); EXPECT_EQ(braves.data(), nullptr); EXPECT_TRUE(std::all_of(braves.begin(), braves.end(), [](char c) { return c == char{}; })); } TEST(dyn_array_tests, count_value_ctor) { gsl::dyn_array orioles(10, 'c'); EXPECT_FALSE(orioles.empty()); EXPECT_EQ(orioles.size(), 10); EXPECT_NE(orioles.data(), nullptr); EXPECT_TRUE(std::all_of(orioles.begin(), orioles.end(), [](char c) { return c == 'c'; })); gsl::dyn_array redsox(10, 42); EXPECT_FALSE(redsox.empty()); EXPECT_EQ(redsox.size(), 10); EXPECT_NE(redsox.data(), nullptr); EXPECT_TRUE(std::all_of(redsox.begin(), redsox.end(), [](int i) { return i == 42; })); } TEST(dyn_array_tests, inputit_ctor) { std::vector cubs(10, 'c'); gsl::dyn_array whitesox(cubs.begin(), cubs.end()); EXPECT_FALSE(whitesox.empty()); EXPECT_EQ(whitesox.size(), cubs.size()); EXPECT_NE(whitesox.data(), nullptr); EXPECT_TRUE(std::all_of(whitesox.begin(), whitesox.end(), [](char c) { return c == 'c'; })); } TEST(dyn_array_tests, copy_ctor) { gsl::dyn_array reds(10, 'c'); gsl::dyn_array guardians(reds); EXPECT_FALSE(guardians.empty()); EXPECT_EQ(guardians.size(), reds.size()); EXPECT_NE(guardians.data(), nullptr); EXPECT_TRUE(std::all_of(guardians.begin(), guardians.end(), [](char c) { return c == 'c'; })); } TEST(dyn_array_tests, access_operator) { gsl::dyn_array rockies(10, 'c'); using ST = typename decltype(rockies)::size_type; for (int i = 0; i < gsl::narrow(rockies.size()); i++) EXPECT_EQ(rockies[gsl::narrow(i)], 'c'); for (int i = 0; i < gsl::narrow(rockies.size()); i++) rockies[gsl::narrow(i)] = 'r'; for (int i = 0; i < gsl::narrow(rockies.size()); i++) EXPECT_EQ(rockies[gsl::narrow(i)], 'r'); gsl::dyn_array tigers(10); for (int i = 0; i < gsl::narrow(tigers.size()); i++) tigers[gsl::narrow(i)] = i; for (int i = 0; i < gsl::narrow(tigers.size()); i++) EXPECT_EQ(tigers[gsl::narrow(i)], i); } TEST(dyn_array_tests, iterators) { gsl::dyn_array astros(10, 'c'); for (auto it = astros.begin(); it != astros.end(); it++) EXPECT_EQ(*it, 'c'); for (auto it = astros.begin(); it != astros.end(); it++) *it = 'r'; for (auto it = astros.begin(); it != astros.end(); it++) EXPECT_EQ(*it, 'r'); EXPECT_TRUE(std::all_of(astros.begin(), astros.end(), [](char c) { return c == 'r'; })); gsl::dyn_array royals(10, 'c'); for (auto it = royals.begin(); it != royals.end(); ++it) EXPECT_EQ(*it, 'c'); for (auto it = royals.begin(); it != royals.end(); ++it) *it = 'r'; for (auto it = royals.begin(); it != royals.end(); ++it) EXPECT_EQ(*it, 'r'); EXPECT_TRUE(std::all_of(royals.begin(), royals.end(), [](char c) { return c == 'r'; })); } TEST(dyn_array_tests, range_for) { gsl::dyn_array angels(10, 'c'); for (auto x : angels) EXPECT_EQ(x, 'c'); for (auto& x : angels) x = 'r'; for (auto x : angels) EXPECT_EQ(x, 'r'); EXPECT_TRUE(std::all_of(angels.begin(), angels.end(), [](char c) { return c == 'r'; })); } TEST(dyn_array_tests, use_std_algorithms) { gsl::dyn_array dodgers(26); std::generate(dodgers.begin(), dodgers.end(), [i = 0]() mutable { return 'a' + i++; }); char ch = 'a'; for (auto x : dodgers) EXPECT_EQ(x, ch++); EXPECT_EQ(std::find(dodgers.begin(), dodgers.end(), 'a'), dodgers.begin()); { auto it = std::find(dodgers.begin(), dodgers.end(), 'c'); EXPECT_EQ(std::distance(dodgers.begin(), it), 'c' - 'a'); EXPECT_EQ(std::distance(it, dodgers.begin()), 'a' - 'c'); } { auto it = std::lower_bound(dodgers.begin(), dodgers.end(), 'j'); EXPECT_EQ(*it, 'j'); EXPECT_EQ(std::distance(dodgers.begin(), it), 'j' - 'a'); EXPECT_EQ(std::distance(it, dodgers.begin()), 'a' - 'j'); } EXPECT_EQ(dodgers.begin(), std::begin(dodgers)); EXPECT_EQ(dodgers.end(), std::end(dodgers)); } #if defined(__cpp_lib_constexpr_dynamic_alloc) && (__cpp_lib_constexpr_dynamic_alloc >= 201907L) constexpr auto default_constructed_count_dyn_array_is_constexpr() { gsl::dyn_array values(3); return values.size() == 3 && values[0] == 0 && values[1] == 0 && values[2] == 0; } TEST(dyn_array_tests, constexprness) { constexpr gsl::dyn_array marlins; static_assert(marlins == marlins); static_assert(marlins.empty()); static_assert(marlins.size() == 0); static_assert(marlins.data() == nullptr); static_assert(marlins.begin() == marlins.end()); static_assert(std::distance(marlins.begin(), marlins.end()) == 0); static_assert(default_constructed_count_dyn_array_is_constexpr()); } #endif /* __cpp_lib_constexpr_dynamic_alloc >= 201907L */ #if defined(__cpp_lib_ranges) && (__cpp_lib_ranges >= 201911L) TEST(dyn_array_tests, ranges) { gsl::dyn_array brewers(26); std::ranges::generate(brewers, [c = 'a']() mutable { return c++; }); char ch = 'a'; for (auto x : brewers) EXPECT_EQ(x, ch++); EXPECT_EQ(std::ranges::find(brewers, 'a'), std::ranges::begin(brewers)); { auto it = std::ranges::find(brewers, 'c'); EXPECT_EQ(std::ranges::distance(std::ranges::begin(brewers), it), 'c' - 'a'); EXPECT_EQ(std::ranges::distance(it, std::ranges::begin(brewers)), 'a' - 'c'); } #if defined(__cpp_lib_containers_ranges) && (__cpp_lib_containers_ranges >= 202202L) std::vector twins(10, 'c'); gsl::dyn_array mets(std::from_range, twins); EXPECT_EQ(twins.size(), mets.size()); EXPECT_TRUE(std::ranges::all_of(mets, [](char c) { return c == 'c'; })); #endif /* __cpp_lib_containers_ranges >= 202202L */ } #endif /* __cpp_lib_ranges >= 201911L */ #if defined(__cpp_lib_constexpr_dynamic_alloc) && (__cpp_lib_constexpr_dynamic_alloc >= 201907L) #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif // defined(__clang__) template struct ConstexprAllocator { using value_type = T; T buf[N]{}; std::size_t sz{}; constexpr ConstexprAllocator() = default; template constexpr ConstexprAllocator(const ConstexprAllocator&) noexcept : buf{}, sz{} {} template struct rebind { using other = ConstexprAllocator; }; constexpr auto allocate(std::size_t n) -> value_type* { auto addr = &buf[sz]; sz += n; return addr; } constexpr void deallocate(value_type*, std::size_t) noexcept {} }; #if defined(__clang__) #pragma clang diagnostic pop #endif // defined(__clang__) template constexpr auto operator==(const ConstexprAllocator& lhs, const ConstexprAllocator& rhs) noexcept { return std::addressof(lhs) == std::addressof(rhs); } template constexpr auto operator!=(const ConstexprAllocator& lhs, const ConstexprAllocator& rhs) noexcept { return !(lhs == rhs); } #endif /* __cpp_lib_constexpr_dynamic_alloc >= 201907L */ template static int AllocCounter = 0; template static int DeallocCounter = 0; struct LifetimeCounter { static int alive_count; int value{}; explicit LifetimeCounter(int v = 0) : value(v) { ++alive_count; } LifetimeCounter(const LifetimeCounter& other) : value(other.value) { ++alive_count; } ~LifetimeCounter() { --alive_count; } }; int LifetimeCounter::alive_count = 0; struct DefaultConstructionCounter { static int default_constructor_count; static int copy_constructor_count; int value{}; DefaultConstructionCounter() { ++default_constructor_count; } DefaultConstructionCounter(const DefaultConstructionCounter& other) : value(other.value) { ++copy_constructor_count; } static void reset() { default_constructor_count = 0; copy_constructor_count = 0; } }; int DefaultConstructionCounter::default_constructor_count = 0; int DefaultConstructionCounter::copy_constructor_count = 0; struct DefaultOnlyElement { DefaultOnlyElement() = default; DefaultOnlyElement(const DefaultOnlyElement&) = delete; DefaultOnlyElement& operator=(const DefaultOnlyElement&) = delete; DefaultOnlyElement(DefaultOnlyElement&&) = delete; DefaultOnlyElement& operator=(DefaultOnlyElement&&) = delete; }; struct ThrowOnCopy { static int alive_count; static int copy_count; static int throw_on_copy_index; int value{}; explicit ThrowOnCopy(int v = 0) : value(v) { ++alive_count; } ThrowOnCopy(const ThrowOnCopy& other) : value(other.value) { if (copy_count == throw_on_copy_index) { ++copy_count; throw 42; } ++copy_count; ++alive_count; } ~ThrowOnCopy() { --alive_count; } }; int ThrowOnCopy::alive_count = 0; int ThrowOnCopy::copy_count = 0; int ThrowOnCopy::throw_on_copy_index = -1; template class Newocator { public: using value_type = T; Newocator() = default; template Newocator(const Newocator&) noexcept {} static void init() { AllocCounter> = 0; DeallocCounter> = 0; } static void check() { EXPECT_EQ(AllocCounter>, DeallocCounter>); } auto allocate(std::size_t n) -> value_type* { AllocCounter> ++; return static_cast(::operator new(n * sizeof(value_type))); } void deallocate(value_type* p, std::size_t) noexcept { DeallocCounter> ++; ::operator delete(p); } template struct rebind { using other = Newocator; }; }; template constexpr auto operator==(const Newocator&, const Newocator&) noexcept { return true; } template constexpr auto operator!=(const Newocator& lhs, const Newocator& rhs) noexcept { return !(lhs == rhs); } template class OwnershipTrackingAllocator { public: using value_type = T; OwnershipTrackingAllocator() noexcept : owner_id(next_owner_id()) { ++next_owner_id(); } explicit OwnershipTrackingAllocator(int owner) noexcept : owner_id(owner) {} template OwnershipTrackingAllocator(const OwnershipTrackingAllocator& other) noexcept : owner_id(other.owner()) {} auto allocate(std::size_t count) -> value_type* { static_assert(alignof(value_type) <= alignof(int), "test allocator only supports types with int-or-smaller alignment"); auto raw = static_cast(::operator new(sizeof(int) + count * sizeof(value_type))); *reinterpret_cast(raw) = owner_id; ++allocation_count(); return reinterpret_cast(raw + sizeof(int)); } void deallocate(value_type* pointer, std::size_t) noexcept { auto raw = reinterpret_cast(pointer) - sizeof(int); if (*reinterpret_cast(raw) != owner_id) { ++mismatched_deallocation_count(); } ++deallocation_count(); ::operator delete(raw); } auto owner() const noexcept { return owner_id; } static void reset() { next_owner_id() = 1; allocation_count() = 0; deallocation_count() = 0; mismatched_deallocation_count() = 0; } static auto mismatched_deallocations() { return mismatched_deallocation_count(); } private: int owner_id; static auto next_owner_id() -> int& { static int value = 1; return value; } static auto allocation_count() -> int& { static int value = 0; return value; } static auto deallocation_count() -> int& { static int value = 0; return value; } static auto mismatched_deallocation_count() -> int& { static int value = 0; return value; } }; template constexpr auto operator==(const OwnershipTrackingAllocator& lhs, const OwnershipTrackingAllocator& rhs) noexcept { return lhs.owner() == rhs.owner(); } template constexpr auto operator!=(const OwnershipTrackingAllocator& lhs, const OwnershipTrackingAllocator& rhs) noexcept { return !(lhs == rhs); } TEST(dyn_array_tests, custom_allocator_models_allocator) { using traits = std::allocator_traits>; using ptr = traits::pointer; static_assert(std::is_same::value, "allocator trait type mismatch"); static_assert(std::is_same::value, "allocator trait type mismatch"); Newocator alloc; auto p = traits::allocate(alloc, 1); traits::deallocate(alloc, p, 1); #if defined(__cpp_lib_constexpr_dynamic_alloc) && (__cpp_lib_constexpr_dynamic_alloc >= 201907L) using constexpr_traits = std::allocator_traits>; static_assert(std::is_same::value, "allocator trait type mismatch"); #endif /* __cpp_lib_constexpr_dynamic_alloc >= 201907L */ } TEST(dyn_array_tests, custom_allocator) { #if defined(__cpp_lib_constexpr_dynamic_alloc) && (__cpp_lib_constexpr_dynamic_alloc >= 201907L) static constexpr gsl::dyn_array> mets(10, 'c'); static_assert(mets.size() == 10); static_assert(mets[0] == 'c'); static_assert(std::all_of(std::begin(mets), std::end(mets), [](char c) { return c == 'c'; })); #endif /* __cpp_lib_constexpr_dynamic_alloc >= 201907L */ Newocator::init(); { gsl::dyn_array> yankees(10, 'c'); EXPECT_EQ(yankees.size(), 10); EXPECT_TRUE( std::all_of(std::begin(yankees), std::end(yankees), [](char c) { return c == 'c'; })); yankees[0] = 'a'; yankees[1] = 'b'; EXPECT_EQ(yankees[0], 'a'); EXPECT_EQ(yankees[1], 'b'); EXPECT_EQ(yankees[2], 'c'); yankees.get_allocator().deallocate(yankees.get_allocator().allocate(1), 1); } Newocator::check(); } TEST(dyn_array_tests, non_trivial_elements_are_destroyed) { LifetimeCounter::alive_count = 0; { gsl::dyn_array values(5, LifetimeCounter{7}); EXPECT_EQ(values.size(), 5); EXPECT_EQ(LifetimeCounter::alive_count, 5); } EXPECT_EQ(LifetimeCounter::alive_count, 0); } TEST(dyn_array_tests, count_constructor_default_constructs_each_element) { DefaultConstructionCounter::reset(); { gsl::dyn_array values(4); EXPECT_EQ(values.size(), 4); } EXPECT_EQ(DefaultConstructionCounter::default_constructor_count, 4); EXPECT_EQ(DefaultConstructionCounter::copy_constructor_count, 0); } #ifdef GSL_DYN_ARRAY_COMPILE_FAILURE_TESTS TEST(dyn_array_compile_failure_tests, count_constructor_accepts_default_constructible_only_elements) { gsl::dyn_array values(4); EXPECT_EQ(values.size(), 4); } #endif /* GSL_DYN_ARRAY_COMPILE_FAILURE_TESTS */ TEST(dyn_array_tests, failed_element_construction_rolls_back) { ThrowOnCopy::alive_count = 0; ThrowOnCopy::copy_count = 0; ThrowOnCopy::throw_on_copy_index = 2; EXPECT_THROW((gsl::dyn_array(5, ThrowOnCopy{1})), int); EXPECT_EQ(ThrowOnCopy::alive_count, 0); ThrowOnCopy::throw_on_copy_index = -1; } TEST(dyn_array_tests, init_list) { gsl::dyn_array phillies = {'a', 'b', 'c'}; EXPECT_EQ(phillies.size(), 3); EXPECT_EQ(phillies[0], 'a'); EXPECT_EQ(phillies[1], 'b'); EXPECT_EQ(phillies[2], 'c'); } TEST(dyn_array_tests, const_operations) { const gsl::dyn_array pirates{'a', 'b', 'c', 'd'}; EXPECT_EQ(pirates.size(), 4); EXPECT_EQ(pirates[0], 'a'); } TEST(dyn_array_tests, reverse_iterator) { const gsl::dyn_array padres{'a', 'b', 'c'}; auto it = std::rbegin(padres); EXPECT_EQ(*it++, 'c'); EXPECT_EQ(*it++, 'b'); EXPECT_EQ(*it++, 'a'); } TEST(dyn_array_tests, random_access_iterator_arithmetic) { gsl::dyn_array bluejays{'a', 'b', 'c', 'd'}; auto first = bluejays.begin(); auto third = first + 2; EXPECT_EQ(*third, 'c'); EXPECT_EQ(third - first, 2); EXPECT_EQ(*(third - 1), 'b'); EXPECT_EQ(*std::prev(third), 'b'); } TEST(dyn_array_tests, random_access_iterator_arithmetic_accepts_negative_offsets) { gsl::dyn_array bluejays{'a', 'b', 'c', 'd'}; auto third = bluejays.begin() + 2; char previous{}; char next{}; EXPECT_NO_THROW(previous = *(third + -1)); EXPECT_EQ(previous, 'b'); EXPECT_NO_THROW(next = *(third - -1)); EXPECT_EQ(next, 'd'); } TEST(dyn_array_tests, input_iterator_constructor) { std::istringstream stream{"n a t s"}; std::istream_iterator first{stream}; const std::istream_iterator last{}; gsl::dyn_array nationals(first, last); ASSERT_EQ(nationals.size(), 4); EXPECT_EQ(nationals[0], 'n'); EXPECT_EQ(nationals[1], 'a'); EXPECT_EQ(nationals[2], 't'); EXPECT_EQ(nationals[3], 's'); } TEST(dyn_array_tests, contract_violations) { const auto terminateHandler = std::set_terminate([] { std::cerr << "Expected Death. dyn_array_contract_violations"; std::abort(); }); const auto expected = GetExpectedDeathString(terminateHandler); gsl::dyn_array values(3, 'v'); gsl::dyn_array other(3, 'o'); EXPECT_DEATH(values[values.size()], expected); EXPECT_DEATH((void) *values.end(), expected); EXPECT_DEATH(++values.end(), expected); EXPECT_DEATH(--values.begin(), expected); EXPECT_DEATH((void) (values.begin() == other.begin()), expected); } #ifdef _MSC_VER TEST(dyn_array_tests, unchecked_iterators) { gsl::dyn_array values; const gsl::dyn_array const_values(3, 'v'); EXPECT_TRUE((std::is_same::value)); EXPECT_TRUE((std::is_same::value)); std::size_t count = 0; for (const auto value : const_values) { EXPECT_EQ(value, 'v'); ++count; } EXPECT_EQ(count, const_values.size()); EXPECT_EQ(values._Unchecked_begin(), nullptr); EXPECT_EQ(values._Unchecked_end(), nullptr); } #endif /* _MSC_VER */ TEST(DynArrayTests, TypeConsistency) { static_assert(std::is_same::value_type, int>::value, "Value type mismatch"); static_assert(std::is_same::reference, int&>::value, "Reference type mismatch"); static_assert(std::is_same::const_reference, const int&>::value, "Const reference type mismatch"); static_assert(std::is_same::iterator::value_type, int>::value, "Iterator value type mismatch"); static_assert(std::is_same::iterator::reference, int&>::value, "Iterator reference type mismatch"); static_assert(std::is_same::iterator::const_reference, const int&>::value, "Iterator const reference type mismatch"); static_assert(std::is_same::size_type, std::size_t>::value, "Size type mismatch"); static_assert(std::is_same::difference_type, std::ptrdiff_t>::value, "Difference type mismatch"); } #if defined(__cpp_deduction_guides) && (__cpp_deduction_guides >= 201703L) TEST(dyn_array_tests, deduction_guides) { std::vector giants{10}; #if defined(__cpp_lib_containers_ranges) && (__cpp_lib_containers_ranges >= 202202L) gsl::dyn_array mariners(std::from_range, giants); #endif /* __cpp_lib_containers_ranges >= 202202L */ gsl::dyn_array cardinals(std::begin(giants), std::end(giants)); } #endif /* __cpp_deduction_guides >= 201703L */