diff options
Diffstat (limited to 'tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp')
-rw-r--r-- | tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp | 1376 |
1 files changed, 1286 insertions, 90 deletions
diff --git a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp index 52464ffc15..e7a84d57ee 100644 --- a/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp +++ b/tests/auto/corelib/tools/qarraydata/tst_qarraydata.cpp @@ -1,38 +1,31 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtTest/QtTest> +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +#undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses + +#include <QTest> #include <QtCore/QString> #include <QtCore/qarraydata.h> #include "simplevector.h" +#include <array> +#include <tuple> +#include <algorithm> +#include <vector> +#include <set> +#include <stdexcept> +#include <functional> +#include <memory> + +// A wrapper for a test function. Calls a function, if it fails, reports failure +#define RUN_TEST_FUNC(test, ...) \ +do { \ + test(__VA_ARGS__); \ + if (QTest::currentTestFailed()) \ + QFAIL("Test case " #test "(" #__VA_ARGS__ ") failed"); \ +} while (false) + class tst_QArrayData : public QObject { Q_OBJECT @@ -50,14 +43,28 @@ private slots: void alignment(); void typedData(); void gccBug43247(); + void arrayOps_data(); void arrayOps(); + void arrayOps2_data(); void arrayOps2(); + void arrayOpsExtra_data(); + void arrayOpsExtra(); void fromRawData_data(); void fromRawData(); void literals(); void variadicLiterals(); void rValueReferences(); void grow(); + void freeSpace_data(); + void freeSpace(); + void dataPointerAllocate_data(); + void dataPointerAllocate(); + void selfEmplaceBackwards(); + void selfEmplaceForward(); +#ifndef QT_NO_EXCEPTIONS + void relocateWithExceptions_data(); + void relocateWithExceptions(); +#endif // QT_NO_EXCEPTIONS }; template <class T> const T &const_(const T &t) { return t; } @@ -66,7 +73,7 @@ void tst_QArrayData::referenceCounting() { { // Reference counting initialized to 1 (owned) - QArrayData array = { Q_BASIC_ATOMIC_INITIALIZER(1), 0, 0 }; + QArrayData array = { Q_BASIC_ATOMIC_INITIALIZER(1), {}, 0 }; QCOMPARE(array.ref_.loadRelaxed(), 1); @@ -96,12 +103,12 @@ void tst_QArrayData::simpleVector() SimpleVector<int> v1; SimpleVector<int> v2(v1); - SimpleVector<int> v3(nullptr, nullptr, 0); + SimpleVector<int> v3(nullptr, (int *)nullptr, 0); SimpleVector<int> v4(nullptr, data, 0); SimpleVector<int> v5(nullptr, data, 1); SimpleVector<int> v6(nullptr, data, 7); - SimpleVector<int> v7(10, 5); - SimpleVector<int> v8(array, array + sizeof(array)/sizeof(*array)); + const SimpleVector<int> v7(10, 5); + const SimpleVector<int> v8(array, array + sizeof(array)/sizeof(*array)); v3 = v1; v1.swap(v3); @@ -229,7 +236,7 @@ void tst_QArrayData::simpleVector() { int count = 0; - Q_FOREACH (int value, v7) { + for (int value : v7) { QCOMPARE(value, 5); ++count; } @@ -239,7 +246,7 @@ void tst_QArrayData::simpleVector() { int count = 0; - Q_FOREACH (int value, v8) { + for (int value : v8) { QCOMPARE(value, count); ++count; } @@ -427,8 +434,7 @@ void tst_QArrayData::allocate_data() { QTest::addColumn<size_t>("objectSize"); QTest::addColumn<size_t>("alignment"); - QTest::addColumn<QArrayData::ArrayOptions>("allocateOptions"); - QTest::addColumn<bool>("isCapacityReserved"); + QTest::addColumn<bool>("grow"); struct { char const *typeName; @@ -442,12 +448,10 @@ void tst_QArrayData::allocate_data() struct { char const *description; - QArrayData::ArrayOptions allocateOptions; - bool isCapacityReserved; + bool grow; } options[] = { - { "Default", QArrayData::DefaultAllocationFlags, false }, - { "Reserved", QArrayData::CapacityReserved, true }, - { "Grow", QArrayData::GrowsForward, false } + { "Default", false }, + { "Grow", true } }; for (size_t i = 0; i < sizeof(types)/sizeof(types[0]); ++i) @@ -457,15 +461,14 @@ void tst_QArrayData::allocate_data() + QLatin1String(": ") + QLatin1String(options[j].description))) << types[i].objectSize << types[i].alignment - << options[j].allocateOptions << options[j].isCapacityReserved; + << options[j].grow; } void tst_QArrayData::allocate() { QFETCH(size_t, objectSize); QFETCH(size_t, alignment); - QFETCH(QArrayData::ArrayOptions, allocateOptions); - QFETCH(bool, isCapacityReserved); + QFETCH(bool, grow); // Minimum alignment that can be requested is that of QArrayData. // Typically, this alignment is sizeof(void *) and ensured by malloc. @@ -476,16 +479,14 @@ void tst_QArrayData::allocate() for (qsizetype capacity = 1; capacity <= 1024; capacity <<= 1) { QArrayData *data; - void *dataPointer = QArrayData::allocate(&data, objectSize, minAlignment, - capacity, QArrayData::ArrayOptions(allocateOptions)); + void *dataPointer = QArrayData::allocate(&data, objectSize, minAlignment, capacity, grow ? QArrayData::Grow : QArrayData::KeepSize); keeper.headers.append(data); - if (allocateOptions & QArrayData::GrowsForward) - QVERIFY(data->allocatedCapacity() > capacity); + if (grow) + QCOMPARE_GE(data->allocatedCapacity(), capacity); else QCOMPARE(data->allocatedCapacity(), capacity); - QCOMPARE(bool(data->flags & QArrayData::CapacityReserved), isCapacityReserved); // Check that the allocated array can be used. Best tested with a // memory checker, such as valgrind, running. @@ -497,8 +498,7 @@ void tst_QArrayData::reallocate() { QFETCH(size_t, objectSize); QFETCH(size_t, alignment); - QFETCH(QArrayData::ArrayOptions, allocateOptions); - QFETCH(bool, isCapacityReserved); + QFETCH(bool, grow); // Minimum alignment that can be requested is that of QArrayData. // Typically, this alignment is sizeof(void *) and ensured by malloc. @@ -507,27 +507,24 @@ void tst_QArrayData::reallocate() int capacity = 10; Deallocator keeper(objectSize, minAlignment); QArrayData *data; - void *dataPointer = QArrayData::allocate(&data, objectSize, minAlignment, capacity, - QArrayData::ArrayOptions(allocateOptions) & ~QArrayData::GrowsForward); + void *dataPointer = QArrayData::allocate(&data, objectSize, minAlignment, capacity, grow ? QArrayData::Grow : QArrayData::KeepSize); keeper.headers.append(data); memset(dataPointer, 'A', objectSize * capacity); // now try to reallocate int newCapacity = 40; - auto pair = QArrayData::reallocateUnaligned(data, dataPointer, objectSize, newCapacity, - QArrayData::ArrayOptions(allocateOptions)); + auto pair = QArrayData::reallocateUnaligned(data, dataPointer, objectSize, newCapacity, grow ? QArrayData::Grow : QArrayData::KeepSize); data = pair.first; dataPointer = pair.second; QVERIFY(data); keeper.headers.clear(); keeper.headers.append(data); - if (allocateOptions & QArrayData::GrowsForward) + if (grow) QVERIFY(data->allocatedCapacity() > newCapacity); else QCOMPARE(data->allocatedCapacity(), newCapacity); - QCOMPARE(!(data->flags & QArrayData::CapacityReserved), !isCapacityReserved); for (int i = 0; i < capacity; ++i) QCOMPARE(static_cast<char *>(dataPointer)[i], 'A'); @@ -561,8 +558,7 @@ void tst_QArrayData::alignment() for (int i = 0; i < 100; ++i) { QArrayData *data; - void *dataPointer = QArrayData::allocate(&data, sizeof(Unaligned), - minAlignment, 8, QArrayData::DefaultAllocationFlags); + void *dataPointer = QArrayData::allocate(&data, sizeof(Unaligned), minAlignment, 8, QArrayData::KeepSize); keeper.headers.append(data); QVERIFY(data); @@ -732,10 +728,24 @@ struct CountedObject static size_t liveCount; }; +bool operator==(const CountedObject &lhs, const CountedObject &rhs) +{ + return lhs.id == rhs.id; // TODO: anything better than this? +} + size_t CountedObject::liveCount = 0; +void tst_QArrayData::arrayOps_data() +{ + QTest::addColumn<bool>("capacityReserved"); + + QTest::newRow("default") << false; + QTest::newRow("capacity-reserved") << true; +} + void tst_QArrayData::arrayOps() { + QFETCH(bool, capacityReserved); CountedObject::LeakChecker leakChecker; Q_UNUSED(leakChecker); const int intArray[5] = { 80, 101, 100, 114, 111 }; @@ -748,9 +758,12 @@ void tst_QArrayData::arrayOps() }; const CountedObject objArray[5]; - QVERIFY(!QTypeInfo<int>::isComplex && !QTypeInfo<int>::isStatic); - QVERIFY(QTypeInfo<QString>::isComplex && !QTypeInfo<QString>::isStatic); - QVERIFY(QTypeInfo<CountedObject>::isComplex && QTypeInfo<CountedObject>::isStatic); + static_assert(!QTypeInfo<int>::isComplex); + static_assert(QTypeInfo<int>::isRelocatable); + static_assert(QTypeInfo<QString>::isComplex); + static_assert(QTypeInfo<QString>::isRelocatable); + static_assert(QTypeInfo<CountedObject>::isComplex); + static_assert(!QTypeInfo<CountedObject>::isRelocatable); QCOMPARE(CountedObject::liveCount, size_t(5)); for (size_t i = 0; i < 5; ++i) @@ -758,9 +771,9 @@ void tst_QArrayData::arrayOps() //////////////////////////////////////////////////////////////////////////// // copyAppend (I) - SimpleVector<int> vi(intArray, intArray + 5); - SimpleVector<QString> vs(stringArray, stringArray + 5); - SimpleVector<CountedObject> vo(objArray, objArray + 5); + SimpleVector<int> vi(intArray, intArray + 5, capacityReserved); + SimpleVector<QString> vs(stringArray, stringArray + 5, capacityReserved); + SimpleVector<CountedObject> vo(objArray, objArray + 5, capacityReserved); QCOMPARE(CountedObject::liveCount, size_t(10)); for (int i = 0; i < 5; ++i) { @@ -786,9 +799,9 @@ void tst_QArrayData::arrayOps() QString referenceString = QLatin1String("reference"); CountedObject referenceObject; - vi = SimpleVector<int>(5, referenceInt); - vs = SimpleVector<QString>(5, referenceString); - vo = SimpleVector<CountedObject>(5, referenceObject); + vi = SimpleVector<int>(5, referenceInt, capacityReserved); + vs = SimpleVector<QString>(5, referenceString, capacityReserved); + vo = SimpleVector<CountedObject>(5, referenceObject, capacityReserved); QCOMPARE(vi.size(), size_t(5)); QCOMPARE(vs.size(), size_t(5)); @@ -800,8 +813,11 @@ void tst_QArrayData::arrayOps() QVERIFY(vs[i].isSharedWith(referenceString)); QCOMPARE(vo[i].id, referenceObject.id); - QCOMPARE(int(vo[i].flags), CountedObject::CopyConstructed - | CountedObject::DefaultConstructed); + + // A temporary object is created as DefaultConstructed | + // CopyConstructed, then it is used instead of the original value to + // construct elements in the container which are CopyConstructed only + //QCOMPARE(int(vo[i].flags), CountedObject::CopyConstructed); } //////////////////////////////////////////////////////////////////////////// @@ -851,8 +867,14 @@ void tst_QArrayData::arrayOps() QVERIFY(vs[i].isSharedWith(stringArray[i % 5])); QCOMPARE(vo[i].id, objArray[i % 5].id); - QCOMPARE(int(vo[i].flags), CountedObject::DefaultConstructed - | CountedObject::CopyAssigned); + + // Insertion at begin (prepend) caused the elements to move, meaning + // that instead of being displaced, newly added elements got constructed + // in uninitialized memory with DefaultConstructed | CopyConstructed + // ### QArrayData::insert does copy assign some of the values, so this test doesn't + // work +// QCOMPARE(int(vo[i].flags), CountedObject::DefaultConstructed +// | CountedObject::CopyConstructed); } for (int i = 5; i < 15; ++i) { @@ -860,8 +882,8 @@ void tst_QArrayData::arrayOps() QVERIFY(vs[i].isSharedWith(stringArray[i % 5])); QCOMPARE(vo[i].id, objArray[i % 5].id); - QCOMPARE(int(vo[i].flags), CountedObject::CopyConstructed - | CountedObject::CopyAssigned); +// QCOMPARE(int(vo[i].flags), CountedObject::CopyConstructed +// | CountedObject::CopyAssigned); } for (int i = 15; i < 20; ++i) { @@ -869,8 +891,8 @@ void tst_QArrayData::arrayOps() QVERIFY(vs[i].isSharedWith(referenceString)); QCOMPARE(vo[i].id, referenceObject.id); - QCOMPARE(int(vo[i].flags), CountedObject::CopyConstructed - | CountedObject::CopyAssigned); +// QCOMPARE(int(vo[i].flags), CountedObject::CopyConstructed +// | CountedObject::CopyAssigned); } for (int i = 20; i < 25; ++i) { @@ -885,8 +907,8 @@ void tst_QArrayData::arrayOps() // Depending on implementation of rotate, final assignment can be: // - straight from source: DefaultConstructed | CopyAssigned // - through a temporary: CopyConstructed | CopyAssigned - QCOMPARE(vo[i].flags & CountedObject::CopyAssigned, - int(CountedObject::CopyAssigned)); +// QCOMPARE(vo[i].flags & CountedObject::CopyAssigned, +// int(CountedObject::CopyAssigned)); } for (int i = 25; i < 30; ++i) { @@ -894,20 +916,26 @@ void tst_QArrayData::arrayOps() QVERIFY(vs[i].isSharedWith(referenceString)); QCOMPARE(vo[i].id, referenceObject.id); - QCOMPARE(int(vo[i].flags), CountedObject::CopyConstructed - | CountedObject::CopyAssigned); +// QCOMPARE(int(vo[i].flags), CountedObject::CopyConstructed +// | CountedObject::CopyAssigned); } } +void tst_QArrayData::arrayOps2_data() +{ + arrayOps_data(); +} + void tst_QArrayData::arrayOps2() { + QFETCH(bool, capacityReserved); CountedObject::LeakChecker leakChecker; Q_UNUSED(leakChecker); //////////////////////////////////////////////////////////////////////////// // appendInitialize - SimpleVector<int> vi(5); - SimpleVector<QString> vs(5); - SimpleVector<CountedObject> vo(5); + SimpleVector<int> vi(5, capacityReserved); + SimpleVector<QString> vs(5, capacityReserved); + SimpleVector<CountedObject> vo(5, capacityReserved); QCOMPARE(vi.size(), size_t(5)); QCOMPARE(vs.size(), size_t(5)); @@ -1016,6 +1044,8 @@ void tst_QArrayData::arrayOps2() QVERIFY(vs[i].isNull()); QCOMPARE(vo[i].id, i); + // Erasing not from begin always shifts left - consistency with + // std::vector::erase. Elements before erase position are not affected. QCOMPARE(int(vo[i].flags), CountedObject::DefaultConstructed | CountedObject::CopyConstructed); } @@ -1039,6 +1069,629 @@ void tst_QArrayData::arrayOps2() } } +void tst_QArrayData::arrayOpsExtra_data() +{ + dataPointerAllocate_data(); +} + +void tst_QArrayData::arrayOpsExtra() +{ + QSKIP("Skipped while changing QArrayData operations.", SkipAll); + QFETCH(QArrayData::GrowthPosition, GrowthPosition); + CountedObject::LeakChecker leakChecker; Q_UNUSED(leakChecker); + + constexpr size_t inputSize = 5; + const std::array<int, inputSize> intArray = { 80, 101, 100, 114, 111 }; + const std::array<QString, inputSize> stringArray = { + QLatin1String("just"), QLatin1String("for"), QLatin1String("testing"), QLatin1String("a"), + QLatin1String("vector") + }; + const std::array<CountedObject, inputSize> objArray; + + QVERIFY(!QTypeInfo<int>::isComplex && QTypeInfo<int>::isRelocatable); + QVERIFY(QTypeInfo<QString>::isComplex && QTypeInfo<QString>::isRelocatable); + QVERIFY(QTypeInfo<CountedObject>::isComplex && !QTypeInfo<CountedObject>::isRelocatable); + + QCOMPARE(CountedObject::liveCount, inputSize); + for (size_t i = 0; i < 5; ++i) + QCOMPARE(objArray[i].id, i); + + const auto setupDataPointers = [&GrowthPosition] (size_t capacity, size_t initialSize = 0) { + const qsizetype alloc = qsizetype(capacity); + auto i = QArrayDataPointer<int>::allocateGrow(QArrayDataPointer<int>(), alloc, GrowthPosition); + auto s = QArrayDataPointer<QString>::allocateGrow(QArrayDataPointer<QString>(), alloc, GrowthPosition); + auto o = QArrayDataPointer<CountedObject>::allocateGrow(QArrayDataPointer<CountedObject>(), alloc, GrowthPosition); + if (initialSize) { + i->appendInitialize(initialSize); + s->appendInitialize(initialSize); + o->appendInitialize(initialSize); + } + + // assign unique values + std::generate(i.begin(), i.end(), [] () { static int i = 0; return i++; }); + std::generate(s.begin(), s.end(), [] () { static int i = 0; return QString::number(i++); }); + std::generate(o.begin(), o.end(), [] () { return CountedObject(); }); + return std::make_tuple(i, s, o); + }; + + const auto cloneArrayDataPointer = [] (auto &dataPointer, size_t capacity) { + using ArrayPointer = std::decay_t<decltype(dataPointer)>; + ArrayPointer copy{qsizetype(capacity)}; + copy->copyAppend(dataPointer.begin(), dataPointer.end()); + return copy; + }; + + // Test allocation first + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + auto [intData, strData, objData] = setupDataPointers(inputSize); + QVERIFY(intData.size == 0); + QVERIFY(intData.d_ptr() != nullptr); + QVERIFY(size_t(intData.constAllocatedCapacity()) >= inputSize); + QVERIFY(intData.data() != nullptr); + + QVERIFY(strData.size == 0); + QVERIFY(strData.d_ptr() != nullptr); + QVERIFY(size_t(strData.constAllocatedCapacity()) >= inputSize); + QVERIFY(strData.data() != nullptr); + + QVERIFY(objData.size == 0); + QVERIFY(objData.d_ptr() != nullptr); + QVERIFY(size_t(objData.constAllocatedCapacity()) >= inputSize); + QVERIFY(objData.data() != nullptr); + } + + // copyAppend (iterator version) + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + const auto testCopyAppend = [&] (auto &dataPointer, auto first, auto last) { + const size_t originalSize = dataPointer.size; + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + const size_t distance = std::distance(first, last); + + dataPointer->appendIteratorRange(first, last); + QCOMPARE(size_t(dataPointer.size), originalSize + distance); + size_t i = 0; + for (; i < originalSize; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], *(first + (i - originalSize))); + }; + + auto [intData, strData, objData] = setupDataPointers(inputSize * 2, inputSize / 2); + // empty range + const std::array<int, 0> emptyIntArray{}; + const std::array<QString, 0> emptyStrArray{}; + const std::array<CountedObject, 0> emptyObjArray{}; + RUN_TEST_FUNC(testCopyAppend, intData, emptyIntArray.begin(), emptyIntArray.end()); + RUN_TEST_FUNC(testCopyAppend, strData, emptyStrArray.begin(), emptyStrArray.end()); + RUN_TEST_FUNC(testCopyAppend, objData, emptyObjArray.begin(), emptyObjArray.end()); + + // from arbitrary iterators + RUN_TEST_FUNC(testCopyAppend, intData, intArray.begin(), intArray.end()); + RUN_TEST_FUNC(testCopyAppend, strData, stringArray.begin(), stringArray.end()); + RUN_TEST_FUNC(testCopyAppend, objData, objArray.begin(), objArray.end()); + + // append to full + const size_t intDataFreeSpace = intData.freeSpaceAtEnd(); +// QVERIFY(intDataFreeSpace > 0); + const size_t strDataFreeSpace = strData.freeSpaceAtEnd(); +// QVERIFY(strDataFreeSpace > 0); + const size_t objDataFreeSpace = objData.freeSpaceAtEnd(); +// QVERIFY(objDataFreeSpace > 0); + const std::vector<int> intVec(intDataFreeSpace, int(55)); + const std::vector<QString> strVec(strDataFreeSpace, QLatin1String("filler")); + const std::vector<CountedObject> objVec(objDataFreeSpace, CountedObject()); + RUN_TEST_FUNC(testCopyAppend, intData, intVec.begin(), intVec.end()); + RUN_TEST_FUNC(testCopyAppend, strData, strVec.begin(), strVec.end()); + RUN_TEST_FUNC(testCopyAppend, objData, objVec.begin(), objVec.end()); + QCOMPARE(intData.size, intData.constAllocatedCapacity() - intData.freeSpaceAtBegin()); + QCOMPARE(strData.size, strData.constAllocatedCapacity() - strData.freeSpaceAtBegin()); + QCOMPARE(objData.size, objData.constAllocatedCapacity() - objData.freeSpaceAtBegin()); + } + + // copyAppend (iterator version) - special case of copying from self iterators + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + const auto testCopyAppendSelf = [&] (auto &dataPointer, auto first, auto last) { + const size_t originalSize = dataPointer.size; + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + const size_t distance = std::distance(first, last); + auto firstCopy = copy->begin() + std::distance(dataPointer->begin(), first); + + dataPointer->copyAppend(first, last); + QCOMPARE(size_t(dataPointer.size), originalSize + distance); + size_t i = 0; + for (; i < originalSize; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], *(firstCopy + (i - originalSize))); + }; + + auto [intData, strData, objData] = setupDataPointers(inputSize * 2, inputSize / 2); + // make no free space at the end + intData->appendInitialize(intData.size + intData.freeSpaceAtEnd()); + strData->appendInitialize(strData.size + strData.freeSpaceAtEnd()); + objData->appendInitialize(objData.size + objData.freeSpaceAtEnd()); + + // make all values unique. this would ensure that we do not have erroneously passed test + int i = 0; + std::generate(intData.begin(), intData.end(), [&i] () { return i++; }); + std::generate(strData.begin(), strData.end(), [&i] () { return QString::number(i++); }); + std::generate(objData.begin(), objData.end(), [] () { return CountedObject(); }); + + // sanity checks: + if (GrowthPosition & QArrayData::GrowsAtBeginning) { + QVERIFY(intData.freeSpaceAtBegin() > 0); + QVERIFY(strData.freeSpaceAtBegin() > 0); + QVERIFY(objData.freeSpaceAtBegin() > 0); + } + QVERIFY(intData.freeSpaceAtBegin() <= intData.size); + QVERIFY(strData.freeSpaceAtBegin() <= strData.size); + QVERIFY(objData.freeSpaceAtBegin() <= objData.size); + QVERIFY(intData.freeSpaceAtEnd() == 0); + QVERIFY(strData.freeSpaceAtEnd() == 0); + QVERIFY(objData.freeSpaceAtEnd() == 0); + + // now, append to full size causing the data to move internally. passed + // iterators that refer to the object itself must be used correctly + RUN_TEST_FUNC(testCopyAppendSelf, intData, intData.begin(), + intData.begin() + intData.freeSpaceAtBegin()); + RUN_TEST_FUNC(testCopyAppendSelf, strData, strData.begin(), + strData.begin() + strData.freeSpaceAtBegin()); + RUN_TEST_FUNC(testCopyAppendSelf, objData, objData.begin(), + objData.begin() + objData.freeSpaceAtBegin()); + } + + // copyAppend (value version) + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + const auto testCopyAppend = [&] (auto &dataPointer, size_t n, auto value) { + const size_t originalSize = dataPointer.size; + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + + dataPointer->copyAppend(n, value); + QCOMPARE(size_t(dataPointer.size), originalSize + n); + size_t i = 0; + for (; i < originalSize; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], value); + }; + + auto [intData, strData, objData] = setupDataPointers(inputSize * 2, inputSize / 2); + // no values + RUN_TEST_FUNC(testCopyAppend, intData, 0, int()); + RUN_TEST_FUNC(testCopyAppend, strData, 0, QString()); + RUN_TEST_FUNC(testCopyAppend, objData, 0, CountedObject()); + + // several values + RUN_TEST_FUNC(testCopyAppend, intData, inputSize, int(5)); + RUN_TEST_FUNC(testCopyAppend, strData, inputSize, QLatin1String("42")); + RUN_TEST_FUNC(testCopyAppend, objData, inputSize, CountedObject()); + + // from self + RUN_TEST_FUNC(testCopyAppend, intData, 2, intData.data()[3]); + RUN_TEST_FUNC(testCopyAppend, strData, 2, strData.data()[3]); + RUN_TEST_FUNC(testCopyAppend, objData, 2, objData.data()[3]); + + // append to full + const size_t intDataFreeSpace = intData.constAllocatedCapacity() - intData.size; + QVERIFY(intDataFreeSpace > 0); + const size_t strDataFreeSpace = strData.constAllocatedCapacity() - strData.size; + QVERIFY(strDataFreeSpace > 0); + const size_t objDataFreeSpace = objData.constAllocatedCapacity() - objData.size; + QVERIFY(objDataFreeSpace > 0); + RUN_TEST_FUNC(testCopyAppend, intData, intDataFreeSpace, int(-1)); + RUN_TEST_FUNC(testCopyAppend, strData, strDataFreeSpace, QLatin1String("foo")); + RUN_TEST_FUNC(testCopyAppend, objData, objDataFreeSpace, CountedObject()); + QCOMPARE(intData.size, intData.constAllocatedCapacity()); + QCOMPARE(strData.size, strData.constAllocatedCapacity()); + QCOMPARE(objData.size, objData.constAllocatedCapacity()); + } + + // copyAppend (value version) - special case of copying self value + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + const auto testCopyAppendSelf = [&] (auto &dataPointer, size_t n, const auto &value) { + const size_t originalSize = dataPointer.size; + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + auto valueCopy = value; + + dataPointer->copyAppend(n, value); + QCOMPARE(size_t(dataPointer.size), originalSize + n); + size_t i = 0; + for (; i < originalSize; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], valueCopy); + }; + + auto [intData, strData, objData] = setupDataPointers(inputSize * 2, inputSize / 2); + // make no free space at the end + intData->appendInitialize(intData.size + intData.freeSpaceAtEnd()); + strData->appendInitialize(strData.size + strData.freeSpaceAtEnd()); + objData->appendInitialize(objData.size + objData.freeSpaceAtEnd()); + + // make all values unique. this would ensure that we do not have erroneously passed test + int i = 0; + std::generate(intData.begin(), intData.end(), [&i] () { return i++; }); + std::generate(strData.begin(), strData.end(), [&i] () { return QString::number(i++); }); + std::generate(objData.begin(), objData.end(), [] () { return CountedObject(); }); + + // sanity checks: + if (GrowthPosition & QArrayData::GrowsAtBeginning) { + QVERIFY(intData.freeSpaceAtBegin() > 0); + QVERIFY(strData.freeSpaceAtBegin() > 0); + QVERIFY(objData.freeSpaceAtBegin() > 0); + } + QVERIFY(intData.freeSpaceAtEnd() == 0); + QVERIFY(strData.freeSpaceAtEnd() == 0); + QVERIFY(objData.freeSpaceAtEnd() == 0); + + // now, append to full size causing the data to move internally. passed + // value that refers to the object itself must be used correctly + RUN_TEST_FUNC(testCopyAppendSelf, intData, intData.freeSpaceAtBegin(), intData.data()[0]); + RUN_TEST_FUNC(testCopyAppendSelf, strData, strData.freeSpaceAtBegin(), strData.data()[0]); + RUN_TEST_FUNC(testCopyAppendSelf, objData, objData.freeSpaceAtBegin(), objData.data()[0]); + } + + // moveAppend + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + // now there's only one version that accepts "T*" as input parameters + const auto testMoveAppend = [&] (auto &dataPointer, const auto &source) + { + const size_t originalSize = dataPointer.size; + const size_t addedSize = std::distance(source.begin(), source.end()); + auto sourceCopy = source; + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + + dataPointer->moveAppend(sourceCopy.data(), sourceCopy.data() + sourceCopy.size()); + QCOMPARE(size_t(dataPointer.size), originalSize + addedSize); + size_t i = 0; + for (; i < originalSize; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], source[i - originalSize]); + }; + + auto [intData, strData, objData] = setupDataPointers(inputSize * 2, inputSize / 2); + // empty range + RUN_TEST_FUNC(testMoveAppend, intData, std::array<int, 0>{}); + RUN_TEST_FUNC(testMoveAppend, strData, std::array<QString, 0>{}); + RUN_TEST_FUNC(testMoveAppend, objData, std::array<CountedObject, 0>{}); + + // non-empty range + RUN_TEST_FUNC(testMoveAppend, intData, intArray); + RUN_TEST_FUNC(testMoveAppend, strData, stringArray); + RUN_TEST_FUNC(testMoveAppend, objData, objArray); + + // append to full + const size_t intDataFreeSpace = intData.constAllocatedCapacity() - intData.size; + QVERIFY(intDataFreeSpace > 0); + const size_t strDataFreeSpace = strData.constAllocatedCapacity() - strData.size; + QVERIFY(strDataFreeSpace > 0); + const size_t objDataFreeSpace = objData.constAllocatedCapacity() - objData.size; + QVERIFY(objDataFreeSpace > 0); + RUN_TEST_FUNC(testMoveAppend, intData, std::vector<int>(intDataFreeSpace, int(55))); + RUN_TEST_FUNC(testMoveAppend, strData, + std::vector<QString>(strDataFreeSpace, QLatin1String("barbaz"))); + RUN_TEST_FUNC(testMoveAppend, objData, + std::vector<CountedObject>(objDataFreeSpace, CountedObject())); + QCOMPARE(intData.size, intData.constAllocatedCapacity()); + QCOMPARE(strData.size, strData.constAllocatedCapacity()); + QCOMPARE(objData.size, objData.constAllocatedCapacity()); + } + + // moveAppend - special case of moving from self (this is legal yet rather useless) + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + const auto testMoveAppendSelf = [&] (auto &dataPointer, auto first, auto last) { + const size_t originalSize = dataPointer.size; + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + const size_t addedSize = std::distance(first, last); + const size_t firstPos = std::distance(dataPointer->begin(), first); + auto firstCopy = copy->begin() + firstPos; + + dataPointer->moveAppend(first, last); + QCOMPARE(size_t(dataPointer.size), originalSize + addedSize); + size_t i = 0; + for (; i < originalSize; ++i) { + if (i >= firstPos && i < (firstPos + addedSize)) // skip "moved from" chunk + continue; + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + } + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], *(firstCopy + (i - originalSize))); + }; + + auto [intData, strData, objData] = setupDataPointers(inputSize * 2, inputSize / 2); + // make no free space at the end + intData->appendInitialize(intData.size + intData.freeSpaceAtEnd()); + strData->appendInitialize(strData.size + strData.freeSpaceAtEnd()); + objData->appendInitialize(objData.size + objData.freeSpaceAtEnd()); + + // make all values unique. this would ensure that we do not have erroneously passed test + int i = 0; + std::generate(intData.begin(), intData.end(), [&i] () { return i++; }); + std::generate(strData.begin(), strData.end(), [&i] () { return QString::number(i++); }); + std::generate(objData.begin(), objData.end(), [] () { return CountedObject(); }); + + // sanity checks: + if (GrowthPosition & QArrayData::GrowsAtBeginning) { + QVERIFY(intData.freeSpaceAtBegin() > 0); + QVERIFY(strData.freeSpaceAtBegin() > 0); + QVERIFY(objData.freeSpaceAtBegin() > 0); + } + QVERIFY(intData.freeSpaceAtBegin() <= intData.size); + QVERIFY(strData.freeSpaceAtBegin() <= strData.size); + QVERIFY(objData.freeSpaceAtBegin() <= objData.size); + QVERIFY(intData.freeSpaceAtEnd() == 0); + QVERIFY(strData.freeSpaceAtEnd() == 0); + QVERIFY(objData.freeSpaceAtEnd() == 0); + + // now, append to full size causing the data to move internally. passed + // iterators that refer to the object itself must be used correctly + RUN_TEST_FUNC(testMoveAppendSelf, intData, intData.begin(), + intData.begin() + intData.freeSpaceAtBegin()); + RUN_TEST_FUNC(testMoveAppendSelf, strData, strData.begin(), + strData.begin() + strData.freeSpaceAtBegin()); + RUN_TEST_FUNC(testMoveAppendSelf, objData, objData.begin(), + objData.begin() + objData.freeSpaceAtBegin()); + } + + // truncate + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + const auto testTruncate = [&] (auto &dataPointer, size_t newSize) + { + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + dataPointer->truncate(newSize); + QCOMPARE(size_t(dataPointer.size), newSize); + for (size_t i = 0; i < newSize; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + }; + + auto [intData, strData, objData] = setupDataPointers(inputSize, inputSize); + // truncate one + RUN_TEST_FUNC(testTruncate, intData, inputSize - 1); + RUN_TEST_FUNC(testTruncate, strData, inputSize - 1); + RUN_TEST_FUNC(testTruncate, objData, inputSize - 1); + + // truncate all + RUN_TEST_FUNC(testTruncate, intData, 0); + RUN_TEST_FUNC(testTruncate, strData, 0); + RUN_TEST_FUNC(testTruncate, objData, 0); + } + + // insert + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + const auto testInsertRange = [&] (auto &dataPointer, size_t pos, auto first, auto last) + { + const size_t originalSize = dataPointer.size; + const size_t distance = std::distance(first, last); + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + + dataPointer->insert(pos, first, last - first); + QCOMPARE(size_t(dataPointer.size), originalSize + distance); + size_t i = 0; + for (; i < pos; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + for (; i < pos + distance; ++i) + QCOMPARE(dataPointer.data()[i], *(first + (i - pos))); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i - distance]); + }; + + const auto testInsertValue = [&] (auto &dataPointer, size_t pos, size_t n, auto value) + { + const size_t originalSize = dataPointer.size; + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + + dataPointer->insert(pos, n, value); + QCOMPARE(size_t(dataPointer.size), originalSize + n); + size_t i = 0; + for (; i < pos; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + for (; i < pos + n; ++i) + QCOMPARE(dataPointer.data()[i], value); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i - n]); + }; + + auto [intData, strData, objData] = setupDataPointers(100, 10); + + // empty ranges + RUN_TEST_FUNC(testInsertRange, intData, 0, intArray.data(), intArray.data()); + RUN_TEST_FUNC(testInsertRange, strData, 0, stringArray.data(), stringArray.data()); + RUN_TEST_FUNC(testInsertRange, objData, 0, objArray.data(), objArray.data()); + RUN_TEST_FUNC(testInsertValue, intData, 1, 0, int()); + RUN_TEST_FUNC(testInsertValue, strData, 1, 0, QString()); + RUN_TEST_FUNC(testInsertValue, objData, 1, 0, CountedObject()); + + // insert at the beginning + RUN_TEST_FUNC(testInsertRange, intData, 0, intArray.data(), intArray.data() + 1); + RUN_TEST_FUNC(testInsertRange, strData, 0, stringArray.data(), stringArray.data() + 1); + RUN_TEST_FUNC(testInsertRange, objData, 0, objArray.data(), objArray.data() + 1); + RUN_TEST_FUNC(testInsertValue, intData, 0, 1, int(-100)); + RUN_TEST_FUNC(testInsertValue, strData, 0, 1, QLatin1String("12")); + RUN_TEST_FUNC(testInsertValue, objData, 0, 1, CountedObject()); + + // insert into the middle (with the left part of the data being smaller) + RUN_TEST_FUNC(testInsertRange, intData, 1, intArray.data() + 2, intArray.data() + 4); + RUN_TEST_FUNC(testInsertRange, strData, 1, stringArray.data() + 2, stringArray.data() + 4); + RUN_TEST_FUNC(testInsertRange, objData, 1, objArray.data() + 2, objArray.data() + 4); + RUN_TEST_FUNC(testInsertValue, intData, 2, 2, int(11)); + RUN_TEST_FUNC(testInsertValue, strData, 2, 2, QLatin1String("abcdefxdeadbeef")); + RUN_TEST_FUNC(testInsertValue, objData, 2, 2, CountedObject()); + + // insert into the middle (with the right part of the data being smaller) + RUN_TEST_FUNC(testInsertRange, intData, intData.size - 1, intArray.data(), + intArray.data() + intArray.size()); + RUN_TEST_FUNC(testInsertRange, strData, strData.size - 1, stringArray.data(), + stringArray.data() + stringArray.size()); + RUN_TEST_FUNC(testInsertRange, objData, objData.size - 1, objArray.data(), + objArray.data() + objArray.size()); + RUN_TEST_FUNC(testInsertValue, intData, intData.size - 3, 3, int(512)); + RUN_TEST_FUNC(testInsertValue, strData, strData.size - 3, 3, QLatin1String("foo")); + RUN_TEST_FUNC(testInsertValue, objData, objData.size - 3, 3, CountedObject()); + + // insert at the end + RUN_TEST_FUNC(testInsertRange, intData, intData.size, intArray.data(), intArray.data() + 3); + RUN_TEST_FUNC(testInsertRange, strData, strData.size, stringArray.data(), + stringArray.data() + 3); + RUN_TEST_FUNC(testInsertRange, objData, objData.size, objArray.data(), objArray.data() + 3); + RUN_TEST_FUNC(testInsertValue, intData, intData.size, 1, int(-42)); + RUN_TEST_FUNC(testInsertValue, strData, strData.size, 1, QLatin1String("hello, world")); + RUN_TEST_FUNC(testInsertValue, objData, objData.size, 1, CountedObject()); + } + + // insert - special case of inserting from self value. this test only makes + // sense for prepend - insert at begin. + { + const auto testInsertValueSelf = [&] (auto &dataPointer, size_t n, const auto &value) { + const size_t originalSize = dataPointer.size; + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + auto valueCopy = value; + + dataPointer->insert(0, n, value); + QCOMPARE(size_t(dataPointer.size), originalSize + n); + size_t i = 0; + for (; i < n; ++i) + QCOMPARE(dataPointer.data()[i], valueCopy); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i - n]); + }; + + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + auto [intData, strData, objData] = setupDataPointers(inputSize * 2, inputSize / 2); + + // make no free space at the begin + intData->insert(0, intData.freeSpaceAtBegin(), intData.data()[0]); + strData->insert(0, strData.freeSpaceAtBegin(), strData.data()[0]); + objData->insert(0, objData.freeSpaceAtBegin(), objData.data()[0]); + + // make all values unique. this would ensure that we do not have erroneously passed test + int i = 0; + std::generate(intData.begin(), intData.end(), [&i] () { return i++; }); + std::generate(strData.begin(), strData.end(), [&i] () { return QString::number(i++); }); + std::generate(objData.begin(), objData.end(), [] () { return CountedObject(); }); + + // sanity checks: + QVERIFY(intData.freeSpaceAtEnd() > 0); + QVERIFY(strData.freeSpaceAtEnd() > 0); + QVERIFY(objData.freeSpaceAtEnd() > 0); + QVERIFY(intData.freeSpaceAtBegin() == 0); + QVERIFY(strData.freeSpaceAtBegin() == 0); + QVERIFY(objData.freeSpaceAtBegin() == 0); + + // now, prepend to full size causing the data to move internally. passed + // value that refers to the object itself must be used correctly + RUN_TEST_FUNC(testInsertValueSelf, intData, intData.freeSpaceAtEnd(), + intData.data()[intData.size - 1]); + RUN_TEST_FUNC(testInsertValueSelf, strData, strData.freeSpaceAtEnd(), + strData.data()[strData.size - 1]); + RUN_TEST_FUNC(testInsertValueSelf, objData, objData.freeSpaceAtEnd(), + objData.data()[objData.size - 1]); + } + + // emplace + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + // testing simple case when emplacing a copy of the same type + const auto testEmplace = [&] (auto &dataPointer, size_t pos, auto value) + { + const size_t originalSize = dataPointer.size; + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + + dataPointer->emplace(pos, value); + QCOMPARE(size_t(dataPointer.size), originalSize + 1); + size_t i = 0; + for (; i < pos; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + QCOMPARE(dataPointer.data()[i++], value); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i - 1]); + }; + + auto [intData, strData, objData] = setupDataPointers(20, 5); + + // emplace at the beginning + RUN_TEST_FUNC(testEmplace, intData, 0, int(2)); + RUN_TEST_FUNC(testEmplace, strData, 0, QLatin1String("foo")); + RUN_TEST_FUNC(testEmplace, objData, 0, CountedObject()); + // emplace into the middle (with the left part of the data being smaller) + RUN_TEST_FUNC(testEmplace, intData, 1, int(-1)); + RUN_TEST_FUNC(testEmplace, strData, 1, QLatin1String("bar")); + RUN_TEST_FUNC(testEmplace, objData, 1, CountedObject()); + // emplace into the middle (with the right part of the data being smaller) + RUN_TEST_FUNC(testEmplace, intData, intData.size - 2, int(42)); + RUN_TEST_FUNC(testEmplace, strData, strData.size - 2, QLatin1String("baz")); + RUN_TEST_FUNC(testEmplace, objData, objData.size - 2, CountedObject()); + // emplace at the end + RUN_TEST_FUNC(testEmplace, intData, intData.size, int(123)); + RUN_TEST_FUNC(testEmplace, strData, strData.size, QLatin1String("bak")); + RUN_TEST_FUNC(testEmplace, objData, objData.size, CountedObject()); + } + + // erase + { + CountedObject::LeakChecker localLeakChecker; Q_UNUSED(localLeakChecker); + const auto testErase = [&] (auto &dataPointer, auto first, auto last) + { + const size_t originalSize = dataPointer.size; + const size_t distance = std::distance(first, last); + const size_t pos = std::distance(dataPointer.begin(), first); + auto copy = cloneArrayDataPointer(dataPointer, dataPointer.size); + + dataPointer->erase(first, last - first); + QCOMPARE(size_t(dataPointer.size), originalSize - distance); + size_t i = 0; + for (; i < pos; ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i]); + for (; i < size_t(dataPointer.size); ++i) + QCOMPARE(dataPointer.data()[i], copy.data()[i + distance]); + }; + + auto [intData, strData, objData] = setupDataPointers(100, 100); + + // erase chunk from the beginning + RUN_TEST_FUNC(testErase, intData, intData.begin(), intData.begin() + 10); + RUN_TEST_FUNC(testErase, strData, strData.begin(), strData.begin() + 10); + RUN_TEST_FUNC(testErase, objData, objData.begin(), objData.begin() + 10); + + // erase chunk from the end + RUN_TEST_FUNC(testErase, intData, intData.end() - 10, intData.end()); + RUN_TEST_FUNC(testErase, strData, strData.end() - 10, strData.end()); + RUN_TEST_FUNC(testErase, objData, objData.end() - 10, objData.end()); + + // erase the middle chunk + RUN_TEST_FUNC(testErase, intData, intData.begin() + (intData.size / 2) - 5, + intData.begin() + (intData.size / 2) + 5); + RUN_TEST_FUNC(testErase, strData, strData.begin() + (strData.size / 2) - 5, + strData.begin() + (strData.size / 2) + 5); + RUN_TEST_FUNC(testErase, objData, objData.begin() + (objData.size / 2) - 5, + objData.begin() + (objData.size / 2) + 5); + + // erase chunk in the left part of the data + RUN_TEST_FUNC(testErase, intData, intData.begin() + 1, intData.begin() + 6); + RUN_TEST_FUNC(testErase, strData, strData.begin() + 1, strData.begin() + 6); + RUN_TEST_FUNC(testErase, objData, objData.begin() + 1, objData.begin() + 6); + + // erase chunk in the right part of the data + RUN_TEST_FUNC(testErase, intData, intData.end() - 6, intData.end() - 1); + RUN_TEST_FUNC(testErase, strData, strData.end() - 6, strData.end() - 1); + RUN_TEST_FUNC(testErase, objData, objData.end() - 6, objData.end() - 1); + + // erase all + RUN_TEST_FUNC(testErase, intData, intData.begin(), intData.end()); + RUN_TEST_FUNC(testErase, strData, strData.begin(), strData.end()); + RUN_TEST_FUNC(testErase, objData, objData.begin(), objData.end()); + } +} + Q_DECLARE_METATYPE(QArrayDataPointer<int>) struct ResetOnDtor @@ -1119,7 +1772,7 @@ void tst_QArrayData::literals() { { QArrayDataPointer<char> d = Q_ARRAY_LITERAL(char, "ABCDEFGHIJ"); - QCOMPARE(d.size, 10u + 1u); + QCOMPARE(d.size, 10 + 1); for (int i = 0; i < 10; ++i) QCOMPARE(d.data()[i], char('A' + i)); } @@ -1142,7 +1795,7 @@ void tst_QArrayData::literals() { // wchar_t is not necessarily 2-bytes QArrayDataPointer<wchar_t> d = Q_ARRAY_LITERAL(wchar_t, L"ABCDEFGHIJ"); - QCOMPARE(d.size, 10u + 1u); + QCOMPARE(d.size, 10 + 1); for (int i = 0; i < 10; ++i) QCOMPARE(d.data()[i], wchar_t('A' + i)); } @@ -1165,6 +1818,8 @@ void tst_QArrayData::literals() QCOMPARE(l.capacity(), 0); for (int i = 0; i < 3; ++i) QCOMPARE(l.at(i).value, i); + l.squeeze(); // shouldn't detach + QCOMPARE(l.capacity(), 0); (void)l.begin(); // "detach" @@ -1181,7 +1836,7 @@ void tst_QArrayData::variadicLiterals() { QArrayDataPointer<int> d = Q_ARRAY_LITERAL(int, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); - QCOMPARE(d.size, 10u); + QCOMPARE(d.size, 10); for (int i = 0; i < 10; ++i) QCOMPARE(d.data()[i], i); } @@ -1189,7 +1844,7 @@ void tst_QArrayData::variadicLiterals() { QArrayDataPointer<char> d = Q_ARRAY_LITERAL(char, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'); - QCOMPARE(d.size, 10u); + QCOMPARE(d.size, 10); for (int i = 0; i < 10; ++i) QCOMPARE(d.data()[i], char('A' + i)); } @@ -1197,7 +1852,7 @@ void tst_QArrayData::variadicLiterals() { QArrayDataPointer<const char *> d = Q_ARRAY_LITERAL(const char *, "A", "B", "C", "D", "E", "F", "G", "H", "I", "J"); - QCOMPARE(d.size, 10u); + QCOMPARE(d.size, 10); for (int i = 0; i < 10; ++i) { QCOMPARE(d.data()[i][0], char('A' + i)); QCOMPARE(d.data()[i][1], '\0'); @@ -1336,5 +1991,546 @@ void tst_QArrayData::grow() } } +void tst_QArrayData::freeSpace_data() +{ + QTest::addColumn<size_t>("n"); + + for (const size_t n : {1, 3, 5, 7, 16, 25}) { + QString suffix = QString::number(n) + QLatin1String("-elements"); + QTest::newRow(qPrintable(QLatin1String("alloc-") + suffix)) + << n; + } +} + +void tst_QArrayData::freeSpace() +{ + QFETCH(size_t, n); + const auto testFreeSpace = [] (auto dummy, qsizetype n) { + using Type = std::decay_t<decltype(dummy)>; + using DataPointer = QArrayDataPointer<Type>; + Q_UNUSED(dummy); + const qsizetype capacity = n + 1; + auto ptr = DataPointer::allocateGrow(DataPointer(), capacity, QArrayData::GrowsAtEnd); + const auto alloc = qsizetype(ptr.constAllocatedCapacity()); + QVERIFY(alloc >= capacity); + QCOMPARE(ptr.freeSpaceAtBegin() + ptr.freeSpaceAtEnd(), alloc); + }; + RUN_TEST_FUNC(testFreeSpace, char(0), n); + RUN_TEST_FUNC(testFreeSpace, char16_t(0), n); + RUN_TEST_FUNC(testFreeSpace, int(0), n); + RUN_TEST_FUNC(testFreeSpace, QString(), n); + RUN_TEST_FUNC(testFreeSpace, CountedObject(), n); +} + +void tst_QArrayData::dataPointerAllocate_data() +{ + QTest::addColumn<QArrayData::GrowthPosition>("GrowthPosition"); + + QTest::newRow("at-end") << QArrayData::GrowsAtEnd; + QTest::newRow("at-begin") << QArrayData::GrowsAtBeginning; +} + +void tst_QArrayData::dataPointerAllocate() +{ + QFETCH(QArrayData::GrowthPosition, GrowthPosition); + const auto createDataPointer = [] (qsizetype capacity, auto initValue) { + using Type = std::decay_t<decltype(initValue)>; + Q_UNUSED(initValue); + return QArrayDataPointer<Type>(capacity); + }; + + const auto testRealloc = [&] (qsizetype capacity, qsizetype newSize, auto initValue) { + using Type = std::decay_t<decltype(initValue)>; + using DataPointer = QArrayDataPointer<Type>; + + auto oldDataPointer = createDataPointer(capacity, initValue); + oldDataPointer->insert(0, 1, initValue); + oldDataPointer->insert(0, 1, initValue); // trigger prepend + QVERIFY(!oldDataPointer.needsDetach()); + + auto newDataPointer = DataPointer::allocateGrow(oldDataPointer, newSize, GrowthPosition); + const auto newAlloc = newDataPointer.constAllocatedCapacity(); + const auto freeAtBegin = newDataPointer.freeSpaceAtBegin(); + const auto freeAtEnd = newDataPointer.freeSpaceAtEnd(); + + QVERIFY(newAlloc >= oldDataPointer.constAllocatedCapacity()); + QCOMPARE(freeAtBegin + freeAtEnd, newAlloc); + if (GrowthPosition == QArrayData::GrowsAtBeginning) { + QVERIFY(freeAtBegin > 0); + } else if (GrowthPosition & QArrayData::GrowsAtEnd) { + QCOMPARE(freeAtBegin, oldDataPointer.freeSpaceAtBegin()); + QVERIFY(freeAtEnd > 0); + } + }; + + for (size_t n : {10, 512, 1000}) { + RUN_TEST_FUNC(testRealloc, n, n + 1, int(0)); + RUN_TEST_FUNC(testRealloc, n, n + 1, char('a')); + RUN_TEST_FUNC(testRealloc, n, n + 1, char16_t(u'a')); + RUN_TEST_FUNC(testRealloc, n, n + 1, QString("hello, world!")); + RUN_TEST_FUNC(testRealloc, n, n + 1, CountedObject()); + } + + const auto testDetachRealloc = [&] (qsizetype capacity, qsizetype newSize, auto initValue) { + using Type = std::decay_t<decltype(initValue)>; + using DataPointer = QArrayDataPointer<Type>; + + auto oldDataPointer = createDataPointer(capacity, initValue); + oldDataPointer->insert(0, 1, initValue); // trigger prepend + auto oldDataPointerCopy = oldDataPointer; // force detach later + QVERIFY(oldDataPointer.needsDetach()); + + auto newDataPointer = DataPointer::allocateGrow(oldDataPointer, oldDataPointer->detachCapacity(newSize), GrowthPosition); + const auto newAlloc = newDataPointer.constAllocatedCapacity(); + const auto freeAtBegin = newDataPointer.freeSpaceAtBegin(); + const auto freeAtEnd = newDataPointer.freeSpaceAtEnd(); + + QVERIFY(newAlloc > oldDataPointer.constAllocatedCapacity()); + QCOMPARE(freeAtBegin + freeAtEnd, newAlloc); + if (GrowthPosition == QArrayData::GrowsAtBeginning) { + QVERIFY(freeAtBegin > 0); + } else if (GrowthPosition & QArrayData::GrowsAtEnd) { + QCOMPARE(freeAtBegin, oldDataPointer.freeSpaceAtBegin()); + QVERIFY(freeAtEnd > 0); + } + }; + + for (size_t n : {10, 512, 1000}) { + RUN_TEST_FUNC(testDetachRealloc, n, n + 1, int(0)); + RUN_TEST_FUNC(testDetachRealloc, n, n + 1, char('a')); + RUN_TEST_FUNC(testDetachRealloc, n, n + 1, char16_t(u'a')); + RUN_TEST_FUNC(testDetachRealloc, n, n + 1, QString("hello, world!")); + RUN_TEST_FUNC(testDetachRealloc, n, n + 1, CountedObject()); + } +} + +struct MyQStringWrapper : public QString +{ + bool movedTo = false; + bool movedFrom = false; + MyQStringWrapper() = default; + MyQStringWrapper(QChar c) : QString(c) { } + MyQStringWrapper(MyQStringWrapper &&other) : QString(std::move(static_cast<QString &>(other))) + { + movedTo = true; + movedFrom = other.movedFrom; + other.movedFrom = true; + } + MyQStringWrapper &operator=(MyQStringWrapper &&other) + { + QString::operator=(std::move(static_cast<QString &>(other))); + movedTo = true; + movedFrom = other.movedFrom; + other.movedFrom = true; + return *this; + } + MyQStringWrapper(const MyQStringWrapper &) = default; + MyQStringWrapper &operator=(const MyQStringWrapper &) = default; + ~MyQStringWrapper() = default; +}; + +struct MyMovableQString : public MyQStringWrapper +{ + MyMovableQString() = default; + MyMovableQString(QChar c) : MyQStringWrapper(c) { } + +private: + friend bool operator==(const MyMovableQString &a, QChar c) + { + return static_cast<QString>(a) == QString(c); + } + + friend bool operator==(const MyMovableQString &a, const MyMovableQString &b) + { + return static_cast<QString>(a) == static_cast<QString>(b); + } +}; + +QT_BEGIN_NAMESPACE +Q_DECLARE_TYPEINFO(MyMovableQString, Q_RELOCATABLE_TYPE); +QT_END_NAMESPACE +static_assert(QTypeInfo<MyMovableQString>::isComplex); +static_assert(QTypeInfo<MyMovableQString>::isRelocatable); + +struct MyComplexQString : public MyQStringWrapper +{ + MyComplexQString() = default; + MyComplexQString(QChar c) : MyQStringWrapper(c) { } + +private: + friend bool operator==(const MyComplexQString &a, QChar c) + { + return static_cast<QString>(a) == QString(c); + } + + friend bool operator==(const MyComplexQString &a, const MyComplexQString &b) + { + return static_cast<QString>(a) == static_cast<QString>(b); + } +}; +static_assert(QTypeInfo<MyComplexQString>::isComplex); +static_assert(!QTypeInfo<MyComplexQString>::isRelocatable); + +void tst_QArrayData::selfEmplaceBackwards() +{ + const auto createDataPointer = [](qsizetype capacity, int spaceAtEnd, auto dummy) { + using Type = std::decay_t<decltype(dummy)>; + Q_UNUSED(dummy); + auto [header, ptr] = QTypedArrayData<Type>::allocate(capacity, QArrayData::Grow); + // do custom adjustments to make sure there's free space at end + ptr += header->alloc - spaceAtEnd; + return QArrayDataPointer(header, ptr); + }; + + const auto testSelfEmplace = [&](auto dummy, int spaceAtEnd, auto initValues) { + auto adp = createDataPointer(100, spaceAtEnd, dummy); + for (auto v : initValues) { + adp->emplace(adp.size, v); + } + QVERIFY(!adp.freeSpaceAtEnd()); + QVERIFY(adp.freeSpaceAtBegin()); + + adp->emplace(adp.size, adp.data()[0]); + for (qsizetype i = 0; i < adp.size - 1; ++i) { + QCOMPARE(adp.data()[i], initValues[i]); + } + QCOMPARE(adp.data()[adp.size - 1], initValues[0]); + + adp->emplace(adp.size, std::move(adp.data()[0])); + for (qsizetype i = 1; i < adp.size - 2; ++i) { + QCOMPARE(adp.data()[i], initValues[i]); + } + QCOMPARE(adp.data()[adp.size - 2], initValues[0]); + QCOMPARE(adp.data()[0].movedFrom, true); + QCOMPARE(adp.data()[adp.size - 1], initValues[0]); + QCOMPARE(adp.data()[adp.size - 1].movedTo, true); + }; + + QList<QChar> movableObjs { u'a', u'b', u'c', u'd' }; + RUN_TEST_FUNC(testSelfEmplace, MyMovableQString(), 4, movableObjs); + QList<QChar> complexObjs { u'a', u'b', u'c', u'd' }; + RUN_TEST_FUNC(testSelfEmplace, MyComplexQString(), 4, complexObjs); +} + +void tst_QArrayData::selfEmplaceForward() +{ + const auto createDataPointer = [](qsizetype capacity, int spaceAtBegin, auto dummy) { + using Type = std::decay_t<decltype(dummy)>; + Q_UNUSED(dummy); + auto [header, ptr] = QTypedArrayData<Type>::allocate(capacity, QArrayData::Grow); + // do custom adjustments to make sure there's free space at end + ptr += spaceAtBegin; + return QArrayDataPointer(header, ptr); + }; + + const auto testSelfEmplace = [&](auto dummy, int spaceAtBegin, auto initValues) { + // need a -1 below as the first emplace will go towards the end (as the array is still empty) + auto adp = createDataPointer(100, spaceAtBegin - 1, dummy); + auto reversedInitValues = initValues; + std::reverse(reversedInitValues.begin(), reversedInitValues.end()); + for (auto v : reversedInitValues) { + adp->emplace(0, v); + } + QVERIFY(!adp.freeSpaceAtBegin()); + QVERIFY(adp.freeSpaceAtEnd()); + + adp->emplace(0, adp.data()[adp.size - 1]); + for (qsizetype i = 1; i < adp.size; ++i) { + QCOMPARE(adp.data()[i], initValues[i - 1]); + } + QCOMPARE(adp.data()[0], initValues[spaceAtBegin - 1]); + + adp->emplace(0, std::move(adp.data()[adp.size - 1])); + for (qsizetype i = 2; i < adp.size - 1; ++i) { + QCOMPARE(adp.data()[i], initValues[i - 2]); + } + QCOMPARE(adp.data()[1], initValues[spaceAtBegin - 1]); + QCOMPARE(adp.data()[adp.size - 1].movedFrom, true); + QCOMPARE(adp.data()[0], initValues[spaceAtBegin - 1]); + QCOMPARE(adp.data()[0].movedTo, true); + }; + + QList<QChar> movableObjs { u'a', u'b', u'c', u'd' }; + RUN_TEST_FUNC(testSelfEmplace, MyMovableQString(), 4, movableObjs); + QList<QChar> complexObjs { u'a', u'b', u'c', u'd' }; + RUN_TEST_FUNC(testSelfEmplace, MyComplexQString(), 4, complexObjs); +} + +#ifndef QT_NO_EXCEPTIONS +struct ThrowingTypeWatcher +{ + std::vector<void *> destroyedAddrs; + bool watch = false; + + void destroyed(void *addr) + { + if (watch) + destroyedAddrs.push_back(addr); + } +}; + +ThrowingTypeWatcher &throwingTypeWatcher() +{ + static ThrowingTypeWatcher global; + return global; +} + +struct ThrowingType +{ + static unsigned int throwOnce; + static constexpr char throwString[] = "Requested to throw"; + enum MoveCase { + MoveRightNoOverlap, + MoveRightOverlap, + MoveLeftNoOverlap, + MoveLeftOverlap, + }; + enum ThrowCase { + NoThrow, + ThrowInUninitializedRegion, + ThrowInOverlapRegion, + }; + + // reinforce basic checkers with std::shared_ptr which happens to signal + // very explicitly about use-after-free and so on under ASan + std::shared_ptr<int> doubleFreeHelper = std::shared_ptr<int>(new int(42)); + int id = 0; + + void checkThrow() + { + // deferred throw + if (throwOnce > 0) { + --throwOnce; + if (throwOnce == 0) { + throw std::runtime_error(throwString); + } + } + return; + } + + void copy(const ThrowingType &other) noexcept(false) + { + doubleFreeHelper = other.doubleFreeHelper; + id = other.id; + checkThrow(); + } + + ThrowingType(int val = 0) noexcept(false) : id(val) { checkThrow(); } + ThrowingType(const ThrowingType &other) noexcept(false) { copy(other); } + ThrowingType &operator=(const ThrowingType &other) noexcept(false) + { + copy(other); + return *this; + } + ThrowingType(ThrowingType &&other) noexcept(false) { copy(other); } + ThrowingType &operator=(ThrowingType &&other) noexcept(false) + { + copy(other); + return *this; + } + ~ThrowingType() noexcept(true) + { + throwingTypeWatcher().destroyed(this); // notify global watcher + id = -1; + // if we're in dtor but use_count is 0, it's double free + QVERIFY(doubleFreeHelper.use_count() > 0); + } + + friend bool operator==(const ThrowingType &a, const ThrowingType &b) { return a.id == b.id; } +}; + +unsigned int ThrowingType::throwOnce = 0; +static_assert(!QTypeInfo<ThrowingType>::isRelocatable); + +void tst_QArrayData::relocateWithExceptions_data() +{ + QTest::addColumn<ThrowingType::MoveCase>("moveCase"); + QTest::addColumn<ThrowingType::ThrowCase>("throwCase"); + // Not throwing + QTest::newRow("no-throw-move-right-no-overlap") + << ThrowingType::MoveRightNoOverlap << ThrowingType::NoThrow; + QTest::newRow("no-throw-move-right-overlap") + << ThrowingType::MoveRightOverlap << ThrowingType::NoThrow; + QTest::newRow("no-throw-move-left-no-overlap") + << ThrowingType::MoveLeftNoOverlap << ThrowingType::NoThrow; + QTest::newRow("no-throw-move-left-overlap") + << ThrowingType::MoveLeftOverlap << ThrowingType::NoThrow; + // Throwing in uninitialized region + QTest::newRow("throw-in-uninit-region-move-right-no-overlap") + << ThrowingType::MoveRightNoOverlap << ThrowingType::ThrowInUninitializedRegion; + QTest::newRow("throw-in-uninit-region-move-right-overlap") + << ThrowingType::MoveRightOverlap << ThrowingType::ThrowInUninitializedRegion; + QTest::newRow("throw-in-uninit-region-move-left-no-overlap") + << ThrowingType::MoveLeftNoOverlap << ThrowingType::ThrowInUninitializedRegion; + QTest::newRow("throw-in-uninit-region-move-left-overlap") + << ThrowingType::MoveLeftOverlap << ThrowingType::ThrowInUninitializedRegion; + // Throwing in overlap region + QTest::newRow("throw-in-overlap-region-move-right-overlap") + << ThrowingType::MoveRightOverlap << ThrowingType::ThrowInOverlapRegion; + QTest::newRow("throw-in-overlap-region-move-left-overlap") + << ThrowingType::MoveLeftOverlap << ThrowingType::ThrowInOverlapRegion; +} + +void tst_QArrayData::relocateWithExceptions() +{ + // Assume that non-throwing moves perform correctly. Otherwise, all previous + // tests would've failed. Test only what happens when exceptions are thrown. + QFETCH(ThrowingType::MoveCase, moveCase); + QFETCH(ThrowingType::ThrowCase, throwCase); + + struct ThrowingTypeLeakChecker + { + ThrowingType::MoveCase moveCase; + ThrowingType::ThrowCase throwCase; + size_t containerSize = 0; + + ThrowingTypeLeakChecker(ThrowingType::MoveCase mc, ThrowingType::ThrowCase tc) + : moveCase(mc), throwCase(tc) + { + } + + void start(qsizetype size) + { + containerSize = size_t(size); + throwingTypeWatcher().watch = true; + } + + ~ThrowingTypeLeakChecker() + { + const size_t destroyedElementsCount = throwingTypeWatcher().destroyedAddrs.size(); + const size_t destroyedElementsUniqueCount = + std::set<void *>(throwingTypeWatcher().destroyedAddrs.begin(), + throwingTypeWatcher().destroyedAddrs.end()) + .size(); + + // reset the global watcher first and only then verify things + throwingTypeWatcher().watch = false; + throwingTypeWatcher().destroyedAddrs.clear(); + + size_t deletedByRelocate = 0; + switch (throwCase) { + case ThrowingType::NoThrow: + // if no overlap, N elements from old range. otherwise, N - 1 + // elements from old range + if (moveCase == ThrowingType::MoveLeftNoOverlap + || moveCase == ThrowingType::MoveRightNoOverlap) { + deletedByRelocate = containerSize; + } else { + deletedByRelocate = containerSize - 1; + } + break; + case ThrowingType::ThrowInUninitializedRegion: + // 1 relocated element from uninitialized region + deletedByRelocate = 1u; + break; + case ThrowingType::ThrowInOverlapRegion: + // 2 relocated elements from uninitialized region + deletedByRelocate = 2u; + break; + default: + QFAIL("Unknown throwCase"); + } + + QCOMPARE(destroyedElementsCount, deletedByRelocate + containerSize); + QCOMPARE(destroyedElementsUniqueCount, destroyedElementsCount); + } + }; + + const auto setDeferredThrow = [throwCase]() { + switch (throwCase) { + case ThrowingType::NoThrow: + break; // do nothing + case ThrowingType::ThrowInUninitializedRegion: + ThrowingType::throwOnce = 2; + break; + case ThrowingType::ThrowInOverlapRegion: + ThrowingType::throwOnce = 3; + break; + default: + QFAIL("Unknown throwCase"); + } + }; + + const auto createDataPointer = [](qsizetype capacity, qsizetype initSize) { + QArrayDataPointer<ThrowingType> qadp(capacity); + qadp->appendInitialize(initSize); + int i = 0; + std::generate(qadp.begin(), qadp.end(), [&i]() { return ThrowingType(i++); }); + return qadp; + }; + + switch (moveCase) { + case ThrowingType::MoveRightNoOverlap: { + ThrowingTypeLeakChecker watch(moveCase, throwCase); + auto storage = createDataPointer(20, 3); + QVERIFY(storage.freeSpaceAtEnd() > 3); + + watch.start(storage.size); + try { + setDeferredThrow(); + storage->relocate(4); + if (throwCase != ThrowingType::NoThrow) + QFAIL("Unreachable line!"); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + } + break; + } + case ThrowingType::MoveRightOverlap: { + ThrowingTypeLeakChecker watch(moveCase, throwCase); + auto storage = createDataPointer(20, 3); + QVERIFY(storage.freeSpaceAtEnd() > 3); + + watch.start(storage.size); + try { + setDeferredThrow(); + storage->relocate(2); + if (throwCase != ThrowingType::NoThrow) + QFAIL("Unreachable line!"); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + } + break; + } + case ThrowingType::MoveLeftNoOverlap: { + ThrowingTypeLeakChecker watch(moveCase, throwCase); + auto storage = createDataPointer(20, 2); + storage->insert(0, 1, ThrowingType(42)); + QVERIFY(storage.freeSpaceAtBegin() > 3); + + watch.start(storage.size); + try { + setDeferredThrow(); + storage->relocate(-4); + if (throwCase != ThrowingType::NoThrow) + QFAIL("Unreachable line!"); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + } + break; + } + case ThrowingType::MoveLeftOverlap: { + ThrowingTypeLeakChecker watch(moveCase, throwCase); + auto storage = createDataPointer(20, 2); + storage->insert(0, 1, ThrowingType(42)); + QVERIFY(storage.freeSpaceAtBegin() > 3); + + watch.start(storage.size); + try { + setDeferredThrow(); + storage->relocate(-2); + if (throwCase != ThrowingType::NoThrow) + QFAIL("Unreachable line!"); + } catch (const std::runtime_error &e) { + QCOMPARE(std::string(e.what()), ThrowingType::throwString); + } + break; + } + default: + QFAIL("Unknown ThrowingType::MoveCase"); + }; +} +#endif // QT_NO_EXCEPTIONS + QTEST_APPLESS_MAIN(tst_QArrayData) #include "tst_qarraydata.moc" |