/* * ArrayVectorTest.cpp * * Copyright (c) 2023 Dominic Clark * * This file is part of LMMS - https://lmms.io * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program (see COPYING); if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA. * */ #include "ArrayVector.h" #include #include #include #include using lmms::ArrayVector; struct ShouldNotConstruct { ShouldNotConstruct() { QFAIL("should not construct"); } }; struct ShouldNotDestruct { ~ShouldNotDestruct() { QFAIL("should not destruct"); } }; enum class Construction { Default, Copy, Move, CopyAssign, MoveAssign }; struct Constructible { Constructible() : construction{Construction::Default} {} Constructible(const Constructible&) : construction{Construction::Copy} {} Constructible(Constructible&&) : construction{Construction::Move} {} Constructible& operator=(const Constructible&) { construction = Construction::CopyAssign; return *this; } Constructible& operator=(Constructible&&) { construction = Construction::MoveAssign; return *this; } Construction construction; }; struct DestructorCheck { ~DestructorCheck() { *destructed = true; } bool* destructed; }; class ArrayVectorTest : public QObject { Q_OBJECT private slots: void defaultConstructorTest() { // Ensure no elements are constructed const auto v = ArrayVector(); // Ensure the container is empty QVERIFY(v.empty()); } void copyConstructorTest() { { // Ensure all elements are copy constructed const auto v = ArrayVector{{}}; const auto copy = v; for (const auto& element : copy) { QCOMPARE(element.construction, Construction::Copy); } } { // Ensure corresponding elements are used const auto v = ArrayVector{1, 2, 3}; const auto copy = v; const auto expected = std::array{1, 2, 3}; QVERIFY(std::equal(copy.begin(), copy.end(), expected.begin(), expected.end())); } } void moveConstructorTest() { { // Ensure all elements are move constructed auto v = ArrayVector{{}}; const auto moved = std::move(v); for (const auto& element : moved) { QCOMPARE(element.construction, Construction::Move); } } { // Ensure corresponding elements are used auto v = ArrayVector{1, 2, 3}; const auto moved = std::move(v); const auto expected = std::array{1, 2, 3}; QVERIFY(std::equal(moved.begin(), moved.end(), expected.begin(), expected.end())); // Move construction should leave the source empty QVERIFY(v.empty()); } } void fillValueConstructorTest() { // Ensure all elements are copy constructed const auto v = ArrayVector(1, {}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Copy); } // Ensure the container has the correct size QCOMPARE(v.size(), std::size_t{1}); } void fillDefaultConstructorTest() { // Ensure all elements are copy constructed const auto v = ArrayVector(1); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Default); } // Ensure the container has the correct size QCOMPARE(v.size(), std::size_t{1}); } void rangeConstructorTest() { { // Ensure the elements are copy constructed from normal iterators const auto data = std::array{Constructible{}}; const auto v = ArrayVector(data.begin(), data.end()); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Copy); } } { // Ensure the elements are move constructed from move iterators auto data = std::array{Constructible{}}; const auto v = ArrayVector( std::move_iterator{data.begin()}, std::move_iterator{data.end()}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Move); } } { // Ensure corresponding elements are used const auto data = std::array{1, 2, 3}; const auto v = ArrayVector(data.begin(), data.end()); QVERIFY(std::equal(v.begin(), v.end(), data.begin(), data.end())); } } void initializerListConstructorTest() { // Ensure the container is constructed with the correct data const auto v = ArrayVector{1, 2, 3}; const auto expected = std::array{1, 2, 3}; QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end())); } void destructorTest() { { // Should not call destructors for space without elements const auto v = ArrayVector{}; } { // Should call destructors for all elements auto destructed = false; { const auto v = ArrayVector{{&destructed}}; } QVERIFY(destructed); } } void copyAssignmentTest() { { // Self-assignment should not change the contents auto v = ArrayVector{1, 2, 3}; const auto oldValue = v; v = v; QCOMPARE(v, oldValue); } { // Assignment to a larger container should copy assign const auto src = ArrayVector(3); auto dst = ArrayVector(5); dst = src; QCOMPARE(dst.size(), std::size_t{3}); for (const auto& element : dst) { QCOMPARE(element.construction, Construction::CopyAssign); } } { // Assignment to a smaller container should copy construct const auto src = ArrayVector(3); auto dst = ArrayVector{}; dst = src; QCOMPARE(dst.size(), std::size_t{3}); for (const auto& element : dst) { QCOMPARE(element.construction, Construction::Copy); } } { // Ensure corresponding elements are used const auto src = ArrayVector{1, 2, 3}; auto dst = ArrayVector{}; dst = src; QCOMPARE(dst, (ArrayVector{1, 2, 3})); } } void moveAssignmentTest() { { // Self-assignment should not change the contents //// Please note the following: //// https://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2468 //// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81159 auto v = ArrayVector{1, 2, 3}; const auto oldValue = v; #if __GNUC__ >= 13 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wself-move" #endif v = std::move(v); #if __GNUC__ >= 13 # pragma GCC diagnostic pop #endif QCOMPARE(v, oldValue); } { // Assignment to a larger container should move assign auto src = ArrayVector(3); auto dst = ArrayVector(5); dst = std::move(src); QCOMPARE(dst.size(), std::size_t{3}); for (const auto& element : dst) { QCOMPARE(element.construction, Construction::MoveAssign); } } { // Assignment to a smaller container should move construct auto src = ArrayVector(3); auto dst = ArrayVector{}; dst = std::move(src); QCOMPARE(dst.size(), std::size_t{3}); for (const auto& element : dst) { QCOMPARE(element.construction, Construction::Move); } } { // Ensure corresponding elements are used auto src = ArrayVector{1, 2, 3}; auto dst = ArrayVector{}; dst = std::move(src); QCOMPARE(dst, (ArrayVector{1, 2, 3})); } } void initializerListAssignmentTest() { { // Assignment to a larger container should copy assign auto v = ArrayVector(2); v = {Constructible{}}; QCOMPARE(v.size(), std::size_t{1}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::CopyAssign); } } { // Assignment to a smaller container should copy construct auto v = ArrayVector{}; v = {Constructible{}}; QCOMPARE(v.size(), std::size_t{1}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Copy); } } { // Ensure corresponding elements are used auto v = ArrayVector{}; v = {1, 2, 3}; QCOMPARE(v, (ArrayVector{1, 2, 3})); } } void fillValueAssignTest() { { // Assignment to a larger container should copy assign auto v = ArrayVector(5); v.assign(3, {}); QCOMPARE(v.size(), std::size_t{3}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::CopyAssign); } } { // Assignment to a smaller container should copy construct auto v = ArrayVector{}; v.assign(3, {}); QCOMPARE(v.size(), std::size_t{3}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Copy); } } { // Ensure correct value is filled auto v = ArrayVector{}; v.assign(3, 1); QCOMPARE(v, (ArrayVector{1, 1, 1})); } } void rangeAssignTest() { { // Assignment to a larger container should copy assign const auto data = std::array{Constructible{}}; auto v = ArrayVector(2); v.assign(data.begin(), data.end()); QCOMPARE(v.size(), std::size_t{1}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::CopyAssign); } } { // Assignment to a smaller container should copy construct const auto data = std::array{Constructible{}}; auto v = ArrayVector{}; v.assign(data.begin(), data.end()); QCOMPARE(v.size(), std::size_t{1}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Copy); } } { // Ensure correct value is filled const auto data = std::array{1, 2, 3}; auto v = ArrayVector{}; v.assign(data.begin(), data.end()); QCOMPARE(v, (ArrayVector{1, 2, 3})); } } void atTest() { { // Non-const version auto v = ArrayVector{1, 2, 3}; QCOMPARE(v.at(1), 2); QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range); } { // Const version const auto v = ArrayVector{1, 2, 3}; QCOMPARE(v.at(1), 2); QVERIFY_EXCEPTION_THROWN(v.at(3), std::out_of_range); } } void subscriptTest() { { // Non-const version auto v = ArrayVector{1, 2, 3}; QCOMPARE(v[1], 2); } { // Const version const auto v = ArrayVector{1, 2, 3}; QCOMPARE(v[1], 2); } } void frontTest() { { // Non-const version auto v = ArrayVector{1, 2, 3}; QCOMPARE(v.front(), 1); } { // Const version const auto v = ArrayVector{1, 2, 3}; QCOMPARE(v.front(), 1); } } void backTest() { { // Non-const version auto v = ArrayVector{1, 2, 3}; QCOMPARE(v.back(), 3); } { // Const version const auto v = ArrayVector{1, 2, 3}; QCOMPARE(v.back(), 3); } } void dataTest() { { // Non-const version auto v = ArrayVector{1, 2, 3}; QCOMPARE(v.data(), &v.front()); } { // Const version const auto v = ArrayVector{1, 2, 3}; QCOMPARE(v.data(), &v.front()); } } void beginEndTest() { const auto expected = std::array{1, 2, 3}; { // Non-const version auto v = ArrayVector{1, 2, 3}; QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end())); QVERIFY(std::equal(v.cbegin(), v.cend(), expected.begin(), expected.end())); } { // Const version const auto v = ArrayVector{1, 2, 3}; QVERIFY(std::equal(v.begin(), v.end(), expected.begin(), expected.end())); } } void rbeginRendTest() { const auto expected = std::array{3, 2, 1}; { // Non-const version auto v = ArrayVector{1, 2, 3}; QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end())); QVERIFY(std::equal(v.crbegin(), v.crend(), expected.begin(), expected.end())); } { // Const version const auto v = ArrayVector{1, 2, 3}; QVERIFY(std::equal(v.rbegin(), v.rend(), expected.begin(), expected.end())); } } void emptyFullSizeMaxCapacityTest() { auto v = ArrayVector{}; QVERIFY(v.empty()); QVERIFY(!v.full()); QCOMPARE(v.size(), std::size_t{0}); QCOMPARE(v.max_size(), std::size_t{2}); QCOMPARE(v.capacity(), std::size_t{2}); v.push_back(1); QVERIFY(!v.empty()); QVERIFY(!v.full()); QCOMPARE(v.size(), std::size_t{1}); QCOMPARE(v.max_size(), std::size_t{2}); QCOMPARE(v.capacity(), std::size_t{2}); v.push_back(2); QVERIFY(!v.empty()); QVERIFY(v.full()); QCOMPARE(v.size(), std::size_t{2}); QCOMPARE(v.max_size(), std::size_t{2}); QCOMPARE(v.capacity(), std::size_t{2}); auto empty = ArrayVector{}; QVERIFY(empty.empty()); QVERIFY(empty.full()); QCOMPARE(empty.size(), std::size_t{0}); QCOMPARE(empty.max_size(), std::size_t{0}); QCOMPARE(empty.capacity(), std::size_t{0}); } void insertValueTest() { { // Copy const auto data = Constructible{}; auto v = ArrayVector{}; v.insert(v.cbegin(), data); QCOMPARE(v.size(), std::size_t{1}); QCOMPARE(v[0].construction, Construction::Copy); } { // Move auto v = ArrayVector{}; v.insert(v.cbegin(), Constructible{}); QCOMPARE(v.size(), std::size_t{1}); QCOMPARE(v[0].construction, Construction::Move); } { // Ensure the correct value is used (copy) const auto data = 1; auto v = ArrayVector{2, 3}; v.insert(v.cbegin(), data); QCOMPARE(v, (ArrayVector{1, 2, 3})); } { // Ensure the correct value is used (move) auto v = ArrayVector{2, 3}; v.insert(v.cbegin(), 1); QCOMPARE(v, (ArrayVector{1, 2, 3})); } } void insertFillValueTest() { { // Insertion should copy construct auto v = ArrayVector{}; v.insert(v.cbegin(), 3, {}); QCOMPARE(v.size(), std::size_t{3}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Copy); } } { // Ensure correct value is filled auto v = ArrayVector{1, 3}; v.insert(v.cbegin() + 1, 3, 2); QCOMPARE(v, (ArrayVector{1, 2, 2, 2, 3})); } } void insertRangeTest() { { // Insertion should copy construct const auto data = std::array{Constructible{}}; auto v = ArrayVector{}; v.insert(v.cbegin(), data.begin(), data.end()); QCOMPARE(v.size(), std::size_t{1}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Copy); } } { // Ensure correct value is filled const auto data = std::array{2, 3}; auto v = ArrayVector{1, 4}; v.insert(v.cbegin() + 1, data.begin(), data.end()); QCOMPARE(v, (ArrayVector{1, 2, 3, 4})); } } void insertInitializerListTest() { { // Insertion should copy construct auto v = ArrayVector{}; v.insert(v.cbegin(), {Constructible{}}); QCOMPARE(v.size(), std::size_t{1}); for (const auto& element : v) { QCOMPARE(element.construction, Construction::Copy); } } { // Ensure corresponding elements are used auto v = ArrayVector{1, 4}; v.insert(v.cbegin() + 1, {2, 3}); QCOMPARE(v, (ArrayVector{1, 2, 3, 4})); } } void emplaceTest() { { // Ensure the value is constructed in-place auto v = ArrayVector{}; v.emplace(v.cbegin()); QCOMPARE(v.size(), std::size_t{1}); QCOMPARE(v[0].construction, Construction::Default); } { // Ensure the correct value is used (move) auto v = ArrayVector{2, 3}; v.emplace(v.cbegin(), 1); QCOMPARE(v, (ArrayVector{1, 2, 3})); } } void eraseTest() { { // Ensure destructors are run auto destructed = false; auto v = ArrayVector{{&destructed}}; v.erase(v.cbegin()); QVERIFY(destructed); } { // Ensure the result is correct auto v = ArrayVector{10, 1, 2, 3}; v.erase(v.cbegin()); QCOMPARE(v, (ArrayVector{1, 2, 3})); } } void eraseRangeTest() { { // Ensure destructors are run auto destructed = false; auto v = ArrayVector{{&destructed}}; v.erase(v.cbegin(), v.cend()); QVERIFY(destructed); } { // Ensure the result is correct auto v = ArrayVector{1, 20, 21, 2, 3}; v.erase(v.cbegin() + 1, v.cbegin() + 3); QCOMPARE(v, (ArrayVector{1, 2, 3})); } } void pushBackTest() { { // Copy const auto data = Constructible{}; auto v = ArrayVector{}; v.push_back(data); QCOMPARE(v.size(), std::size_t{1}); QCOMPARE(v[0].construction, Construction::Copy); } { // Move auto v = ArrayVector{}; v.push_back({}); QCOMPARE(v.size(), std::size_t{1}); QCOMPARE(v[0].construction, Construction::Move); } { // Ensure the correct value is used (copy) const auto data = 3; auto v = ArrayVector{1, 2}; v.push_back(data); QCOMPARE(v, (ArrayVector{1, 2, 3})); } { // Ensure the correct value is used (move) auto v = ArrayVector{1, 2}; v.push_back(3); QCOMPARE(v, (ArrayVector{1, 2, 3})); } } void emplaceBackTest() { { // Ensure the value is constructed in-place auto v = ArrayVector{}; v.emplace_back(); QCOMPARE(v.size(), std::size_t{1}); QCOMPARE(v[0].construction, Construction::Default); } { // Ensure the correct value is used (move) auto v = ArrayVector{1, 2}; v.emplace_back(3); QCOMPARE(v, (ArrayVector{1, 2, 3})); } } void popBackTest() { { // Ensure destructors are run auto destructed = false; auto v = ArrayVector{{&destructed}}; v.pop_back(); QVERIFY(destructed); } { // Ensure the result is correct auto v = ArrayVector{1, 2, 3}; v.pop_back(); QCOMPARE(v, (ArrayVector{1, 2})); } } void resizeDefaultTest() { { // Smaller auto destructed = false; auto v = ArrayVector{{&destructed}}; QCOMPARE(v.size(), std::size_t{1}); v.resize(0); QCOMPARE(v.size(), std::size_t{0}); QVERIFY(destructed); } { // Bigger auto v = ArrayVector{}; QCOMPARE(v.size(), std::size_t{0}); v.resize(1); QCOMPARE(v.size(), std::size_t{1}); QCOMPARE(v[0].construction, Construction::Default); } { // Too big auto v = ArrayVector{}; QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error); } } void resizeValueTest() { { // Smaller auto dummy = false; auto destructed = false; auto v = ArrayVector{{&destructed}}; QCOMPARE(v.size(), std::size_t{1}); v.resize(0, {&dummy}); QCOMPARE(v.size(), std::size_t{0}); QVERIFY(destructed); } { // Bigger auto v = ArrayVector{}; QCOMPARE(v.size(), std::size_t{0}); v.resize(1, {}); QCOMPARE(v.size(), std::size_t{1}); QCOMPARE(v[0].construction, Construction::Copy); } { // Too big auto v = ArrayVector{}; QVERIFY_EXCEPTION_THROWN(v.resize(2), std::length_error); } { // Ensure the correct value is used auto v = ArrayVector{}; v.resize(1, 1); QCOMPARE(v, (ArrayVector{1})); } } void clearTest() { { // Ensure destructors are run auto destructed = false; auto v = ArrayVector{{&destructed}}; v.clear(); QVERIFY(destructed); } { // Ensure the result is correct auto v = ArrayVector{1, 2, 3}; v.clear(); QCOMPARE(v, (ArrayVector{})); } } void memberSwapTest() { auto a = ArrayVector{1, 2, 3, 4}; auto b = ArrayVector{2, 4, 6}; const auto aOriginal = a; const auto bOriginal = b; a.swap(b); QCOMPARE(a, bOriginal); QCOMPARE(b, aOriginal); } void freeSwapTest() { auto a = ArrayVector{1, 2, 3, 4}; auto b = ArrayVector{2, 4, 6}; const auto aOriginal = a; const auto bOriginal = b; swap(a, b); QCOMPARE(a, bOriginal); QCOMPARE(b, aOriginal); } void comparisonTest() { const auto v = ArrayVector{1, 2, 3}; const auto l = ArrayVector{1, 2, 2}; const auto e = ArrayVector{1, 2, 3}; const auto g = ArrayVector{1, 3, 3}; QVERIFY(l < v); QVERIFY(!(e < v)); QVERIFY(!(g < v)); QVERIFY(l <= v); QVERIFY(e <= v); QVERIFY(!(g <= v)); QVERIFY(!(l > v)); QVERIFY(!(e > v)); QVERIFY(g > v); QVERIFY(!(l >= v)); QVERIFY(e >= v); QVERIFY(g >= v); QVERIFY(!(l == v)); QVERIFY(e == v); QVERIFY(!(g == v)); QVERIFY(l != v); QVERIFY(!(e != v)); QVERIFY(g != v); } }; QTEST_GUILESS_MAIN(ArrayVectorTest) #include "ArrayVectorTest.moc"