// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics) // SPDX-FileCopyrightText: 2024 Jorrit Rouwe // SPDX-License-Identifier: MIT #include "UnitTestFramework.h" TEST_SUITE("ArrayTest") { // A test class that is non-trivially copyable to test if the Array class correctly constructs/destructs/copies and moves elements. class NonTriv { public: NonTriv() : mValue(0) { ++sNumConstructors; } explicit NonTriv(int inValue) : mValue(inValue) { ++sNumConstructors; } NonTriv(const NonTriv &inValue) : mValue(inValue.mValue) { ++sNumCopyConstructors; } NonTriv(NonTriv &&inValue) : mValue(inValue.mValue) { inValue.mValue = 0; ++sNumMoveConstructors; } ~NonTriv() { ++sNumDestructors; } NonTriv & operator = (const NonTriv &inRHS) { mValue = inRHS.mValue; return *this; } bool operator == (const NonTriv &inRHS) const { return mValue == inRHS.mValue; } bool operator != (const NonTriv &inRHS) const { return mValue != inRHS.mValue; } int Value() const { return mValue; } static void sReset() { sNumConstructors = 0; sNumCopyConstructors = 0; sNumMoveConstructors = 0; sNumDestructors = 0; } static inline int sNumConstructors = 0; static inline int sNumCopyConstructors = 0; static inline int sNumMoveConstructors = 0; static inline int sNumDestructors = 0; int mValue; }; TEST_CASE("TestConstructLength") { Array arr(55); CHECK(arr.size() == 55); } TEST_CASE("TestConstructLengthNonTriv") { NonTriv::sReset(); Array arr(55); CHECK(arr.size() == 55); CHECK(NonTriv::sNumConstructors == 55); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); } TEST_CASE("TestConstructValue") { Array arr(5, 55); CHECK(arr.size() == 5); for (int i = 0; i < 5; ++i) CHECK(arr[i] == 55); } TEST_CASE("TestConstructValueNonTriv") { NonTriv v(55); NonTriv::sReset(); Array arr(5, v); CHECK(arr.size() == 5); for (int i = 0; i < 5; ++i) CHECK(arr[i].Value() == 55); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 5); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); } TEST_CASE("TestConstructIterator") { int values[] = { 1, 2, 3 }; Array arr(values, values + 3); CHECK(arr.size() == 3); CHECK(arr[0] == 1); CHECK(arr[1] == 2); CHECK(arr[2] == 3); } TEST_CASE("TestConstructIteratorNonTriv") { NonTriv values[] = { NonTriv(1), NonTriv(2), NonTriv(3) }; NonTriv::sReset(); Array arr(values, values + 3); CHECK(arr.size() == 3); CHECK(arr[0].Value() == 1); CHECK(arr[1].Value() == 2); CHECK(arr[2].Value() == 3); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 3); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); } TEST_CASE("TestConstructInitializerList") { Array arr({ 1, 2, 3 }); CHECK(arr.size() == 3); CHECK(arr[0] == 1); CHECK(arr[1] == 2); CHECK(arr[2] == 3); } TEST_CASE("TestConstructInitializerListNonTriv") { NonTriv::sReset(); Array arr({ NonTriv(1), NonTriv(2), NonTriv(3) }); CHECK(arr.size() == 3); CHECK(arr[0].Value() == 1); CHECK(arr[1].Value() == 2); CHECK(arr[2].Value() == 3); CHECK(NonTriv::sNumConstructors == 3); // For the initializer list CHECK(NonTriv::sNumCopyConstructors == 3); // Initializing the array CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 3); // For the initializer list } TEST_CASE("TestConstructFromArray") { Array arr = { 1, 2, 3 }; Array arr2(arr); CHECK(arr2.size() == 3); CHECK(arr2[0] == 1); CHECK(arr2[1] == 2); CHECK(arr2[2] == 3); } TEST_CASE("TestConstructFromArrayNonTriv") { Array arr = { NonTriv(1), NonTriv(2), NonTriv(3) }; NonTriv::sReset(); Array arr2(arr); CHECK(arr2.size() == 3); CHECK(arr2[0].Value() == 1); CHECK(arr2[1].Value() == 2); CHECK(arr2[2].Value() == 3); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 3); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); } TEST_CASE("TestMoveFromArray") { Array arr = { 1, 2, 3 }; Array arr2(std::move(arr)); CHECK(arr2.size() == 3); CHECK(arr2[0] == 1); CHECK(arr2[1] == 2); CHECK(arr2[2] == 3); CHECK(arr.size() == 0); CHECK(arr.capacity() == 0); } TEST_CASE("TestMoveFromArrayNonTriv") { Array arr = { NonTriv(1), NonTriv(2), NonTriv(3) }; NonTriv::sReset(); Array arr2(std::move(arr)); // This just updates the mElements pointer so should not call any constructors/destructors etc. CHECK(arr2.size() == 3); CHECK(arr2[0].Value() == 1); CHECK(arr2[1].Value() == 2); CHECK(arr2[2].Value() == 3); CHECK(arr.size() == 0); CHECK(arr.capacity() == 0); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); } TEST_CASE("TestClear") { Array arr({ 1, 2, 3 }); CHECK(arr.size() == 3); arr.clear(); CHECK(arr.size() == 0); } TEST_CASE("TestClearNonTriv") { NonTriv::sReset(); Array arr({ NonTriv(1), NonTriv(2), NonTriv(3) }); CHECK(arr.size() == 3); CHECK(NonTriv::sNumConstructors == 3); // For initializer list CHECK(NonTriv::sNumCopyConstructors == 3); // To move into array CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 3); // For initializer list NonTriv::sReset(); arr.clear(); CHECK(arr.size() == 0); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 3); } TEST_CASE("TestPushBack") { Array arr; CHECK(arr.size() == 0); CHECK(arr.capacity() == 0); arr.push_back(1); CHECK(arr.size() == 1); CHECK(arr[0] == 1); arr.push_back(2); CHECK(arr.size() == 2); CHECK(arr[0] == 1); CHECK(arr[1] == 2); arr.pop_back(); CHECK(arr.size() == 1); arr.pop_back(); CHECK(arr.size() == 0); CHECK(arr.empty()); } TEST_CASE("TestPushBackNonTriv") { NonTriv v1(1); NonTriv v2(2); NonTriv::sReset(); Array arr; CHECK(arr.size() == 0); CHECK(arr.capacity() == 0); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); NonTriv::sReset(); arr.push_back(v1); CHECK(arr.size() == 1); CHECK(arr[0].Value() == 1); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 1); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); NonTriv::sReset(); arr.push_back(v2); CHECK(arr.size() == 2); CHECK(arr[0].Value() == 1); CHECK(arr[1].Value() == 2); #ifndef JPH_USE_STD_VECTOR CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 1); CHECK(NonTriv::sNumMoveConstructors == 1); // Array resizes from 1 to 2 CHECK(NonTriv::sNumDestructors == 1); // Array resizes from 1 to 2 #endif // JPH_USE_STD_VECTOR NonTriv::sReset(); arr.pop_back(); CHECK(arr.size() == 1); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 1); NonTriv::sReset(); arr.pop_back(); CHECK(arr.size() == 0); CHECK(arr.empty()); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 1); } TEST_CASE("TestPushBackMove") { Array> arr; Array arr2 = { 1, 2, 3 }; arr.push_back(std::move(arr2)); CHECK(arr2.size() == 0); CHECK(arr[0] == Array({ 1, 2, 3 })); } TEST_CASE("TestEmplaceBack") { struct Test { Test(int inA, int inB) : mA(inA), mB(inB) { } int mA; int mB; }; Array arr; arr.emplace_back(1, 2); CHECK(arr.size() == 1); CHECK(arr[0].mA == 1); CHECK(arr[0].mB == 2); } TEST_CASE("TestReserve") { Array arr; CHECK(arr.capacity() == 0); arr.reserve(123); CHECK(arr.size() == 0); CHECK(arr.capacity() == 123); arr.reserve(456); CHECK(arr.size() == 0); CHECK(arr.capacity() == 456); } TEST_CASE("TestReserveNonTriv") { NonTriv::sReset(); Array arr; CHECK(arr.capacity() == 0); arr.reserve(123); CHECK(arr.size() == 0); CHECK(arr.capacity() == 123); arr.reserve(456); CHECK(arr.size() == 0); CHECK(arr.capacity() == 456); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); } TEST_CASE("TestResize") { Array arr; CHECK(arr.size() == 0); CHECK(arr.capacity() == 0); CHECK(arr.data() == nullptr); arr.resize(0); CHECK(arr.size() == 0); CHECK(arr.capacity() == 0); CHECK(arr.data() == nullptr); arr.resize(123); CHECK(arr.size() == 123); CHECK(arr.capacity() == 123); for (int i = 0; i < 123; ++i) arr[i] = i; arr.resize(456); CHECK(arr.size() == 456); CHECK(arr.capacity() == 456); for (int i = 0; i < 123; ++i) CHECK(arr[i] == i); arr.resize(10); CHECK(arr.size() == 10); CHECK(arr.capacity() >= 10); } TEST_CASE("TestResizeNonTriv") { NonTriv::sReset(); Array arr; CHECK(arr.size() == 0); CHECK(arr.capacity() == 0); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); NonTriv::sReset(); arr.resize(123); CHECK(arr.size() == 123); CHECK(arr.capacity() == 123); CHECK(NonTriv::sNumConstructors == 123); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); for (int i = 0; i < 123; ++i) arr[i] = NonTriv(i); NonTriv::sReset(); arr.resize(456); CHECK(arr.size() == 456); CHECK(arr.capacity() == 456); for (int i = 0; i < 123; ++i) CHECK(arr[i].Value() == i); #ifndef JPH_USE_STD_VECTOR CHECK(NonTriv::sNumConstructors == 456 - 123); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 123); CHECK(NonTriv::sNumDestructors == 123); // Switched to a new block, all old elements are destroyed after being moved #endif // JPH_USE_STD_VECTOR NonTriv::sReset(); arr.resize(10); CHECK(arr.size() == 10); CHECK(arr.capacity() >= 10); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 456 - 10); } TEST_CASE("TestResizeWithValue") { Array arr; arr.resize(10, 55); CHECK(arr.size() == 10); CHECK(arr.capacity() == 10); for (int i = 0; i < 10; ++i) CHECK(arr[i] == 55); } TEST_CASE("TestResizeWithValueNonTriv") { NonTriv v(55); Array arr; NonTriv::sReset(); arr.resize(10, v); CHECK(arr.size() == 10); CHECK(arr.capacity() == 10); for (int i = 0; i < 10; ++i) CHECK(arr[i].Value() == 55); CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 10); CHECK(NonTriv::sNumMoveConstructors == 0); CHECK(NonTriv::sNumDestructors == 0); } TEST_CASE("TestShrinkToFit") { Array arr; for (int i = 0; i < 5; ++i) arr.push_back(i); CHECK(arr.capacity() > 5); CHECK(arr.size() == 5); arr.shrink_to_fit(); CHECK(arr.capacity() == 5); CHECK(arr.size() == 5); for (int i = 0; i < 5; ++i) CHECK(arr[i] == i); arr.clear(); CHECK(arr.capacity() == 5); CHECK(arr.size() == 0); arr.shrink_to_fit(); CHECK(arr.capacity() == 0); CHECK(arr.data() == nullptr); } TEST_CASE("TestShrinkToFitNonTriv") { Array arr; for (int i = 0; i < 5; ++i) arr.push_back(NonTriv(i)); CHECK(arr.capacity() > 5); CHECK(arr.size() == 5); NonTriv::sReset(); arr.shrink_to_fit(); CHECK(arr.capacity() == 5); CHECK(arr.size() == 5); for (int i = 0; i < 5; ++i) CHECK(arr[i].Value() == i); #ifndef JPH_USE_STD_VECTOR CHECK(NonTriv::sNumConstructors == 0); CHECK(NonTriv::sNumCopyConstructors == 0); CHECK(NonTriv::sNumMoveConstructors == 5); CHECK(NonTriv::sNumDestructors == 5); // Switched to a new block, all old elements are destroyed after being moved #endif // JPH_USE_STD_VECTOR } TEST_CASE("TestAssignIterator") { int values[] = { 1, 2, 3 }; Array arr({ 4, 5, 6 }); arr.assign(values, values + 3); CHECK(arr.size() == 3); CHECK(arr[0] == 1); CHECK(arr[1] == 2); CHECK(arr[2] == 3); } TEST_CASE("TestAssignInitializerList") { Array arr({ 4, 5, 6 }); arr.assign({ 1, 2, 3 }); CHECK(arr.size() == 3); CHECK(arr[0] == 1); CHECK(arr[1] == 2); CHECK(arr[2] == 3); } TEST_CASE("TestSwap") { Array arr({ 1, 2, 3 }); Array arr2({ 4, 5, 6 }); arr.swap(arr2); CHECK(arr == Array({ 4, 5, 6 })); CHECK(arr2 == Array({ 1, 2, 3 })); } TEST_CASE("TestInsertBegin") { Array arr = { 1, 2, 3 }; arr.insert(arr.begin(), 4); CHECK(arr == Array({ 4, 1, 2, 3 })); } TEST_CASE("TestInsertMid") { Array arr = { 1, 2, 3 }; arr.insert(arr.begin() + 1, 4); CHECK(arr == Array({ 1, 4, 2, 3 })); } TEST_CASE("TestInsertEnd") { Array arr = { 1, 2, 3 }; arr.insert(arr.begin() + 3, 4); CHECK(arr == Array({ 1, 2, 3, 4 })); } TEST_CASE("TestInsertMultipleBegin") { Array values_to_insert = { 4, 5, 6, 7 }; Array arr = { 1, 2, 3 }; arr.insert(arr.begin(), values_to_insert.begin(), values_to_insert.end()); CHECK(arr == Array({ 4, 5, 6, 7, 1, 2, 3 })); } TEST_CASE("TestInsertMultipleMid") { Array values_to_insert = { 4, 5, 6, 7 }; Array arr = { 1, 2, 3 }; arr.insert(arr.begin() + 1, values_to_insert.begin(), values_to_insert.end()); CHECK(arr == Array({ 1, 4, 5, 6, 7, 2, 3 })); } TEST_CASE("TestInsertMultipleEnd") { Array values_to_insert = { 4, 5, 6, 7 }; Array arr = { 1, 2, 3 }; arr.insert(arr.begin() + 3, values_to_insert.begin(), values_to_insert.end()); CHECK(arr == Array({ 1, 2, 3, 4, 5, 6, 7 })); } TEST_CASE("TestFrontBack") { Array arr({ 1, 2, 3 }); CHECK(arr.front() == 1); CHECK(arr.back() == 3); } TEST_CASE("TestAssign") { Array arr({ 1, 2, 3 }); Array arr2({ 4, 5, 6 }); arr = arr2; CHECK(arr == Array({ 4, 5, 6 })); Array &arr3 = arr; // Avoid compiler warning arr = arr3; CHECK(arr == Array({ 4, 5, 6 })); arr = { 7, 8, 9 }; CHECK(arr == Array({ 7, 8, 9 })); } TEST_CASE("TestAssignMove") { Array arr({ 1, 2, 3 }); Array arr2({ 4, 5, 6 }); arr = std::move(arr2); CHECK(arr == Array({ 4, 5, 6 })); CHECK(arr2.empty()); } TEST_CASE("TestEraseBegin") { Array arr({ 1, 2, 3 }); arr.erase(arr.begin()); CHECK(arr == Array({ 2, 3 })); } TEST_CASE("TestEraseMid") { Array arr({ 1, 2, 3 }); arr.erase(arr.begin() + 1); CHECK(arr == Array({ 1, 3 })); } TEST_CASE("TestEraseEnd") { Array arr({ 1, 2, 3 }); arr.erase(arr.begin() + 2); CHECK(arr == Array({ 1, 2 })); } TEST_CASE("TestEraseMultipleBegin") { Array arr({ 1, 2, 3, 4, 5 }); arr.erase(arr.begin(), arr.begin() + 2); CHECK(arr == Array({ 3, 4, 5 })); } TEST_CASE("TestEraseMultipleMid") { Array arr({ 1, 2, 3, 4, 5 }); arr.erase(arr.begin() + 2, arr.begin() + 4); CHECK(arr == Array({ 1, 2, 5 })); } TEST_CASE("TestEraseMultipleEnd") { Array arr({ 1, 2, 3, 4, 5 }); arr.erase(arr.begin() + 3, arr.begin() + 5); CHECK(arr == Array({ 1, 2, 3 })); } TEST_CASE("TestEquals") { Array arr({ 1, 2, 3 }); Array arr2({ 4, 5, 6 }); CHECK(arr == arr); CHECK(!(arr == arr2)); CHECK(!(arr != arr)); CHECK(arr != arr2); } }