diff options
Diffstat (limited to 'tests/auto/corelib/tools/qlist/tst_qlist.cpp')
-rw-r--r-- | tests/auto/corelib/tools/qlist/tst_qlist.cpp | 1980 |
1 files changed, 1340 insertions, 640 deletions
diff --git a/tests/auto/corelib/tools/qlist/tst_qlist.cpp b/tests/auto/corelib/tools/qlist/tst_qlist.cpp index 80adb0f6a1..35d69e8433 100644 --- a/tests/auto/corelib/tools/qlist/tst_qlist.cpp +++ b/tests/auto/corelib/tools/qlist/tst_qlist.cpp @@ -1,38 +1,33 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <QTest> #include <QAtomicInt> #include <QThread> #include <QSemaphore> -#include <QScopedValueRollback> +#include <QAtomicScopedValueRollback> #include <qlist.h> + +#ifdef QT_COMPILER_HAS_LWG3346 +# if __has_include(<concepts>) +# include <concepts> +# if defined(__cpp_lib_concepts) && __cpp_lib_concepts >= 202002L + static_assert(std::contiguous_iterator<QList<int>::iterator>); + static_assert(std::contiguous_iterator<QList<int>::const_iterator>); +# endif +# endif +# if __has_include(<ranges>) +# include <ranges> +# if defined(__cpp_lib_ranges) + namespace rns = std::ranges; + + static_assert(rns::contiguous_range<QList<int>>); + static_assert(rns::contiguous_range<const QList<int>>); +# endif +# endif +#endif + struct Movable { Movable(char input = 'j') : i(input) @@ -139,6 +134,7 @@ struct Custom { i = 0; counter.fetchAndAddRelaxed(-1); state = Destructed; + QVERIFY(heapData.use_count() > 0); // otherwise it's double free } bool operator ==(const Custom &other) const @@ -167,6 +163,9 @@ struct Custom { char i; // used to identify orgin of an instance private: Custom *that; // used to check if an instance was moved + // shared_ptr triggers ASan/LSan and can track if double free happens, which + // is convenient to ensure there's no malfunctioning QList APIs + std::shared_ptr<int> heapData = std::shared_ptr<int>(new int(42)); enum State { Constructed = 106, Destructed = 110 }; State state; @@ -192,81 +191,105 @@ static_assert(QTypeInfo<Movable>::isComplex); static_assert(!QTypeInfo<Custom>::isRelocatable); static_assert(QTypeInfo<Custom>::isComplex); +// leak checking utility: +template<typename T> +struct LeakChecker +{ + int instancesCount; + LeakChecker() : instancesCount(T::counter.loadAcquire()) { } + ~LeakChecker() { QCOMPARE(instancesCount, T::counter.loadAcquire()); } +}; +template<> struct LeakChecker<int>{}; +template<> struct LeakChecker<QString>{}; +#define TST_QLIST_CHECK_LEAKS(Type) \ + LeakChecker<Type> checker; \ + Q_UNUSED(checker); class tst_QList : public QObject { Q_OBJECT + private slots: void constructors_empty() const; void constructors_emptyReserveZero() const; void constructors_emptyReserve() const; void constructors_reserveAndInitialize() const; - void copyConstructorInt() const; - void copyConstructorMovable() const; - void copyConstructorCustom() const; - void assignmentInt() const; - void assignmentMovable() const; - void assignmentCustom() const; - void assignFromInitializerListInt() const; - void assignFromInitializerListMovable() const; - void assignFromInitializerListCustom() const; - void addInt() const; - void addMovable() const; - void addCustom() const; - void appendInt() const; - void appendMovable() const; - void appendCustom() const; + void copyConstructorInt() const { copyConstructor<int>(); } + void copyConstructorMovable() const { copyConstructor<Movable>(); } + void copyConstructorCustom() const { copyConstructor<Custom>(); } + void assignmentInt() const { testAssignment<int>(); } + void assignmentMovable() const { testAssignment<Movable>(); } + void assignmentCustom() const { testAssignment<Custom>(); } + void assignFromInitializerListInt() const { assignFromInitializerList<int>(); } + void assignFromInitializerListMovable() const { assignFromInitializerList<Movable>(); } + void assignFromInitializerListCustom() const { assignFromInitializerList<Custom>(); } + void addInt() const { add<int>(); } + void addMovable() const { add<Movable>(); } + void addCustom() const { add<Custom>(); } + void appendInt() const { append<int>(); } + void appendMovable() const { append<Movable>(); } + void appendCustom() const { append<Custom>(); } void appendRvalue() const; void appendList() const; + void assignEmpty() const; + void assignInt() const { assign<int>(); } + void assignMovable() const { assign<Movable>(); } + void assignCustom() const { assign<Custom>(); } + void assignUsesPrependBuffer_int_data() { assignUsesPrependBuffer_data(); } + void assignUsesPrependBuffer_int() const { assignUsesPrependBuffer<int>(); } + void assignUsesPrependBuffer_Movable_data() { assignUsesPrependBuffer_data(); } + void assignUsesPrependBuffer_Movable() const { assignUsesPrependBuffer<Movable>(); } + void assignUsesPrependBuffer_Custom_data() { assignUsesPrependBuffer_data(); } + void assignUsesPrependBuffer_Custom() const { assignUsesPrependBuffer<Custom>(); } void at() const; - void capacityInt() const; - void capacityMovable() const; - void capacityCustom() const; - void clearInt() const; - void clearMovable() const; - void clearCustom() const; + void capacityInt() const { capacity<int>(); } + void capacityMovable() const { capacity<Movable>(); } + void capacityCustom() const { capacity<Custom>(); } + void clearInt() const { clear<int>(); } + void clearMovable() const { clear<Movable>(); } + void clearCustom() const { clear<Custom>(); } void constData() const; void constFirst() const; void constLast() const; void contains() const; - void countInt() const; - void countMovable() const; - void countCustom() const; + void countInt() const { count<int>(); } + void countMovable() const { count<Movable>(); } + void countCustom() const { count<Custom>(); } void cpp17ctad() const; void data() const; - void emptyInt() const; - void emptyMovable() const; - void emptyCustom() const; + void emptyInt() const { empty<int>(); } + void emptyMovable() const { empty<Movable>(); } + void emptyCustom() const { empty<Custom>(); } void endsWith() const; - void eraseEmptyInt() const; - void eraseEmptyMovable() const; - void eraseEmptyCustom() const; - void eraseEmptyReservedInt() const; - void eraseEmptyReservedMovable() const; - void eraseEmptyReservedCustom() const; - void eraseInt() const; - void eraseIntShared() const; - void eraseMovable() const; - void eraseMovableShared() const; - void eraseCustom() const; - void eraseCustomShared() const; - void eraseReservedInt() const; - void eraseReservedMovable() const; - void eraseReservedCustom() const; - void fillInt() const; - void fillMovable() const; - void fillCustom() const; - void fillDetachInt() const; - void fillDetachMovable() const; - void fillDetachCustom() const; + void eraseEmptyInt() const { eraseEmpty<int>(); } + void eraseEmptyMovable() const { eraseEmpty<Movable>(); } + void eraseEmptyCustom() const { eraseEmpty<Custom>(); } + void eraseEmptyReservedInt() const { eraseEmptyReserved<int>(); } + void eraseEmptyReservedMovable() const { eraseEmptyReserved<Movable>(); } + void eraseEmptyReservedCustom() const { eraseEmptyReserved<Custom>(); } + void eraseInt() const { erase<int>(false); } + void eraseIntShared() const { erase<int>(true); } + void eraseMovable() const { erase<Movable>(false); } + void eraseMovableShared() const { erase<Movable>(true); } + void eraseCustom() const { erase<Custom>(false); } + void eraseCustomShared() const { erase<Custom>(true); } + void eraseReservedInt() const { eraseReserved<int>(); } + void eraseReservedMovable() const { eraseReserved<Movable>(); } + void eraseReservedCustom() const { eraseReserved<Custom>(); } + void fillInt() const { fill<int>(); } + void fillMovable() const { fill<Movable>(); } + void fillCustom() const { fill<Custom>(); } + void fillDetachInt() const { fillDetach<int>(); } + void fillDetachMovable() const { fillDetach<Movable>(); } + void fillDetachCustom() const { fillDetach<Custom>(); } void first() const; - void fromListInt() const; - void fromListMovable() const; - void fromListCustom() const; + void fromListInt() const { fromList<int>(); } + void fromListMovable() const { fromList<Movable>(); } + void fromListCustom() const { fromList<Custom>(); } void indexOf() const; - void insertInt() const; - void insertMovable() const; - void insertCustom() const; + void insertInt() const { insert<int>(); } + void insertMovable() const { insert<Movable>(); } + void insertCustom() const { insert<Custom>(); } void insertZeroCount_data(); void insertZeroCount() const; void isEmpty() const; @@ -274,19 +297,20 @@ private slots: void lastIndexOf() const; void mid() const; void sliced() const; - void moveInt() const; - void moveMovable() const; - void moveCustom() const; - void prependInt() const; - void prependMovable() const; - void prependCustom() const; + void moveInt() const { move<int>(); } + void moveMovable() const { move<Movable>(); } + void moveCustom() const { move<Custom>(); } + void prependInt() const { prepend<int>(); } + void prependMovable() const { prepend<Movable>(); } + void prependCustom() const { prepend<Custom>(); } + void prependRvalue() const; void qhashInt() const { qhash<int>(); } void qhashMovable() const { qhash<Movable>(); } void qhashCustom() const { qhash<Custom>(); } void removeAllWithAlias() const; - void removeInt() const; - void removeMovable() const; - void removeCustom() const; + void removeInt() const { remove<int>(); } + void removeMovable() const { remove<Movable>(); } + void removeCustom() const { remove<Custom>(); } void removeFirstLast() const; void resizePOD_data() const; void resizePOD() const; @@ -298,63 +322,94 @@ private slots: void resizeToZero() const; void resizeToTheSameSize_data(); void resizeToTheSameSize() const; + void resizeForOverwrite() const; + void iterators() const; + void constIterators() const; void reverseIterators() const; - void sizeInt() const; - void sizeMovable() const; - void sizeCustom() const; + void sizeInt() const { size<int>(); } + void sizeMovable() const { size<Movable>(); } + void sizeCustom() const { size<Custom>(); } void startsWith() const; - void swapInt() const; - void swapMovable() const; - void swapCustom() const; + void swapInt() const { swap<int>(); } + void swapMovable() const { swap<Movable>(); } + void swapCustom() const { swap<Custom>(); } void toList() const; #if QT_VERSION < QT_VERSION_CHECK(6,0,0) void fromStdVector() const; void toStdVector() const; #endif void value() const; - void testOperators() const; - void reserve(); void reserveZero(); - void initializeListInt(); - void initializeListMovable(); - void initializeListCustom(); - + void initializeListInt() { initializeList<int>(); } + void initializeListMovable() { initializeList<Movable>(); } + void initializeListCustom() { initializeList<Custom>(); } void const_shared_null(); - - void detachInt() const; - void detachMovable() const; - void detachCustom() const; + void detachInt() const { detach<int>(); } + void detachMovable() const { detach<Movable>(); } + void detachCustom() const { detach<Custom>(); } void detachThreadSafetyInt() const; void detachThreadSafetyMovable() const; void detachThreadSafetyCustom() const; - void insertMove() const; - void swapItemsAt() const; - - void emplaceInt(); - void emplaceCustom(); - void emplaceMovable(); - void emplaceConsistentWithStdVectorInt(); - void emplaceConsistentWithStdVectorCustom(); - void emplaceConsistentWithStdVectorMovable(); - void emplaceConsistentWithStdVectorQString(); + void emplaceInt() { emplaceImpl<int>(); } + void emplaceCustom() { emplaceImpl<Custom>(); } + void emplaceMovable() { emplaceImpl<Movable>(); } + void emplaceConsistentWithStdVectorInt() { emplaceConsistentWithStdVectorImpl<int>(); } + void emplaceConsistentWithStdVectorCustom() { emplaceConsistentWithStdVectorImpl<Custom>(); } + void emplaceConsistentWithStdVectorMovable() { emplaceConsistentWithStdVectorImpl<Movable>(); } + void emplaceConsistentWithStdVectorQString() { emplaceConsistentWithStdVectorImpl<QString>(); } void emplaceReturnsIterator(); + void emplaceFront() const; + void emplaceFrontReturnsRef() const; void emplaceBack(); void emplaceBackReturnsRef(); void emplaceWithElementFromTheSameContainer(); void emplaceWithElementFromTheSameContainer_data(); - + void replaceInt() const { replace<int>(); } + void replaceCustom() const { replace<Custom>(); } + void replaceMovable() const { replace<Movable>(); } void fromReadOnlyData() const; - - void qtbug_90359() const; + void reallocateCustomAlignedType_qtbug90359() const; + void reinsertToBeginInt_qtbug91360() const { reinsertToBegin<int>(); } + void reinsertToBeginMovable_qtbug91360() const { reinsertToBegin<Movable>(); } + void reinsertToBeginCustom_qtbug91360() const { reinsertToBegin<Custom>(); } + void reinsertToEndInt_qtbug91360() const { reinsertToEnd<int>(); } + void reinsertToEndMovable_qtbug91360() const { reinsertToEnd<Movable>(); } + void reinsertToEndCustom_qtbug91360() const { reinsertToEnd<Custom>(); } + void reinsertRangeToEndInt_qtbug91360() const { reinsertRangeToEnd<int>(); } + void reinsertRangeToEndMovable_qtbug91360() const { reinsertRangeToEnd<Movable>(); } + void reinsertRangeToEndCustom_qtbug91360() const { reinsertRangeToEnd<Custom>(); } + // QList reference stability tests: + void stability_reserveInt() const { stability_reserve<int>(); } + void stability_reserveMovable() const { stability_reserve<Movable>(); } + void stability_reserveCustom() const { stability_reserve<Custom>(); } + void stability_eraseInt() const { stability_erase<int>(); } + void stability_eraseMovable() const { stability_erase<Movable>(); } + void stability_eraseCustom() const { stability_erase<Custom>(); } + void stability_appendInt() const { stability_append<int>(); } + void stability_appendMovable() const { stability_append<Movable>(); } + void stability_appendCustom() const { stability_append<Custom>(); } + void stability_insertElementInt() const { stability_insertElement<int>(); } + void stability_insertElementMovable() const { stability_insertElement<Movable>(); } + void stability_insertElementCustom() const { stability_insertElement<Custom>(); } + void stability_emplaceInt() const { stability_emplace<int>(); } + void stability_emplaceMovable() const { stability_emplace<Movable>(); } + void stability_emplaceCustom() const { stability_emplace<Custom>(); } + void stability_resizeInt() const { stability_resize<int>(); } + void stability_resizeMovable() const { stability_resize<Movable>(); } + void stability_resizeCustom() const { stability_resize<Custom>(); } private: template<typename T> void copyConstructor() const; + template<typename T> void testAssignment() const; template<typename T> void add() const; template<typename T> void append() const; + template<typename T> void assign() const; + void assignUsesPrependBuffer_data() const; + template<typename T> void assignUsesPrependBuffer() const; template<typename T> void assignFromInitializerList() const; template<typename T> void capacity() const; template<typename T> void clear() const; @@ -379,6 +434,56 @@ private: template<typename T> void detachThreadSafety() const; template<typename T> void emplaceImpl() const; template<typename T> void emplaceConsistentWithStdVectorImpl() const; + template<typename T> void replace() const; + template<typename T, typename Reinsert> + void reinsert(Reinsert op) const; + template<typename T> + void reinsertToBegin() const + { + reinsert<T>([](QList<T> &list) { + list.prepend(list.back()); + list.removeLast(); + }); + } + template<typename T> + void reinsertToEnd() const + { + reinsert<T>([](QList<T> &list) { + list.append(list.front()); + list.removeFirst(); + }); + } + template<typename T> + void reinsertRangeToEnd() const + { + reinsert<T>([](QList<T> &list) { + list.append(list.begin(), list.begin() + 1); + list.removeFirst(); + }); + } + template<typename T> + void stability_reserve() const; + template<typename T> + void stability_erase() const; + template<typename T> + void stability_append() const; + template<typename T, typename Insert> + void stability_insert(Insert op) const; + template<typename T> + void stability_resize() const; + + template<typename T> + void stability_insertElement() const + { + stability_insert<T>( + [](QList<T> &list, int pos, const T &value) { list.insert(pos, 1, value); }); + } + template<typename T> + void stability_emplace() const + { + stability_insert<T>( + [](QList<T> &list, int pos, const T &value) { list.emplace(pos, value); }); + } }; @@ -416,6 +521,18 @@ const Custom SimpleValue<Custom>::Values[] = { 110, 105, 101, 114, 111, 98 }; #define T_DOG SimpleValue<T>::at(4) #define T_BLAH SimpleValue<T>::at(5) +// returns a pair of QList<T> and QList<T *> +template<typename It> +decltype(auto) qlistCopyAndReferenceFromRange(It first, It last) +{ + using T = typename std::iterator_traits<It>::value_type; + QList<T> copy(first, last); + QList<T *> reference; + for (; first != last; ++first) + reference.append(std::addressof(*first)); + return std::make_pair(copy, reference); +} + void tst_QList::constructors_empty() const { QList<int> emptyInt; @@ -445,30 +562,29 @@ void tst_QList::constructors_reserveAndInitialize() const { // default-initialise items - QList<int> myInt(5, 42); + const QList<int> myInt(5, 42); QVERIFY(myInt.capacity() == 5); - foreach (int meaningoflife, myInt) { + for (int meaningoflife : myInt) QCOMPARE(meaningoflife, 42); - } - QList<QString> myString(5, QString::fromLatin1("c++")); + const QList<QString> myString(5, QString::fromLatin1("c++")); QVERIFY(myString.capacity() == 5); // make sure all items are initialised ok - foreach (QString meaningoflife, myString) { + for (const QString &meaningoflife : myString) QCOMPARE(meaningoflife, QString::fromLatin1("c++")); - } - QList<Custom> myCustom(5, Custom('n')); + const QList<Custom> myCustom(5, Custom('n')); QVERIFY(myCustom.capacity() == 5); // make sure all items are initialised ok - foreach (Custom meaningoflife, myCustom) { + for (Custom meaningoflife : myCustom) QCOMPARE(meaningoflife.i, 'n'); - } } template<typename T> void tst_QList::copyConstructor() const { + TST_QLIST_CHECK_LEAKS(T) + T value1(SimpleValue<T>::at(0)); T value2(SimpleValue<T>::at(1)); T value3(SimpleValue<T>::at(2)); @@ -486,28 +602,11 @@ void tst_QList::copyConstructor() const } } -void tst_QList::copyConstructorInt() const -{ - copyConstructor<int>(); -} - -void tst_QList::copyConstructorMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - copyConstructor<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::copyConstructorCustom() const +template<typename T> +void tst_QList::testAssignment() const { - const int instancesCount = Custom::counter.loadAcquire(); - copyConstructor<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} + TST_QLIST_CHECK_LEAKS(T) -template <class T> -static inline void testAssignment() -{ QList<T> v1(5); QCOMPARE(v1.size(), 5); QVERIFY(v1.isDetached()); @@ -536,24 +635,11 @@ static inline void testAssignment() QCOMPARE((void *)v2.constData(), data2); } -void tst_QList::assignmentInt() const -{ - testAssignment<int>(); -} - -void tst_QList::assignmentMovable() const -{ - testAssignment<Movable>(); -} - -void tst_QList::assignmentCustom() const -{ - testAssignment<Custom>(); -} - template<typename T> void tst_QList::assignFromInitializerList() const { + TST_QLIST_CHECK_LEAKS(T) + T val1(SimpleValue<T>::at(1)); T val2(SimpleValue<T>::at(2)); T val3(SimpleValue<T>::at(3)); @@ -566,28 +652,11 @@ void tst_QList::assignFromInitializerList() const QCOMPARE(v1.size(), 0); } -void tst_QList::assignFromInitializerListInt() const -{ - assignFromInitializerList<int>(); -} - -void tst_QList::assignFromInitializerListMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - assignFromInitializerList<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::assignFromInitializerListCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - assignFromInitializerList<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - template<typename T> void tst_QList::add() const { + TST_QLIST_CHECK_LEAKS(T) + { QList<T> empty1; QList<T> empty2; @@ -616,28 +685,11 @@ void tst_QList::add() const } } -void tst_QList::addInt() const -{ - add<int>(); -} - -void tst_QList::addMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - add<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::addCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - add<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - template<typename T> void tst_QList::append() const { + TST_QLIST_CHECK_LEAKS(T) + { QList<T> myvec; myvec.append(SimpleValue<T>::at(0)); @@ -676,25 +728,193 @@ void tst_QList::append() const QCOMPARE(v, combined); } + { + const QList<T> otherVec { SimpleValue<T>::at(0), + SimpleValue<T>::at(1), + SimpleValue<T>::at(2), + SimpleValue<T>::at(3) }; + QList<T> myvec; + myvec.append(otherVec.cbegin(), otherVec.cbegin() + 3); + QCOMPARE(myvec.size(), 3); + QCOMPARE(myvec, QList<T>() << SimpleValue<T>::at(0) + << SimpleValue<T>::at(1) + << SimpleValue<T>::at(2)); + } + { + QList<T> emptyVec; + QList<T> otherEmptyVec; + + emptyVec.append(otherEmptyVec); + + QVERIFY(emptyVec.isEmpty()); + QVERIFY(!emptyVec.isDetached()); + QVERIFY(!otherEmptyVec.isDetached()); + } + { + QList<T> myvec { SimpleValue<T>::at(0), SimpleValue<T>::at(1) }; + QList<T> emptyVec; + + myvec.append(emptyVec); + QVERIFY(emptyVec.isEmpty()); + QVERIFY(!emptyVec.isDetached()); + QCOMPARE(myvec, QList<T>({ SimpleValue<T>::at(0), SimpleValue<T>::at(1) })); + } } -void tst_QList::appendInt() const +void tst_QList::assignEmpty() const { - append<int>(); + // Test that the realloc branch in assign(it, it) doesn't crash. + using T = int; + QList<T> list; + QList<T> ref1 = list; + QVERIFY(list.d.needsDetach()); + list.assign(list.begin(), list.begin()); + +#if !defined Q_OS_QNX // QNX has problems with the empty istream_iterator + auto empty = std::istream_iterator<T>{}; + list.squeeze(); + QCOMPARE_EQ(list.capacity(), 0); + ref1 = list; + QVERIFY(list.d.needsDetach()); + list.assign(empty, empty); +#endif } -void tst_QList::appendMovable() const +template <typename T> +void tst_QList::assign() const { - const int instancesCount = Movable::counter.loadAcquire(); - append<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); + TST_QLIST_CHECK_LEAKS(T) + { + QList<T> myvec; + myvec.assign(2, T_FOO); + QVERIFY(myvec.isDetached()); + QCOMPARE(myvec, QList<T>() << T_FOO << T_FOO); + + QList<T> myvecCopy = myvec; + QVERIFY(!myvec.isDetached()); + QVERIFY(!myvecCopy.isDetached()); + QVERIFY(myvec.isSharedWith(myvecCopy)); + QVERIFY(myvecCopy.isSharedWith(myvec)); + + myvec.assign(3, T_BAR); + QCOMPARE(myvec, QList<T>() << T_BAR << T_BAR << T_BAR); + QVERIFY(myvec.isDetached()); + QVERIFY(myvecCopy.isDetached()); + QVERIFY(!myvec.isSharedWith(myvecCopy)); + QVERIFY(!myvecCopy.isSharedWith(myvec)); + } + { + QList<T> myvec; + myvec.assign(4, T_FOO); + QVERIFY(myvec.isDetached()); + QCOMPARE(myvec, QList<T>() << T_FOO << T_FOO << T_FOO << T_FOO); + + QList<T> myvecCopy = myvec; + QVERIFY(!myvec.isDetached()); + QVERIFY(!myvecCopy.isDetached()); + QVERIFY(myvec.isSharedWith(myvecCopy)); + QVERIFY(myvecCopy.isSharedWith(myvec)); + + myvecCopy.assign(myvec.begin(), myvec.begin() + 2); + QVERIFY(myvec.isDetached()); + QVERIFY(myvecCopy.isDetached()); + QVERIFY(!myvec.isSharedWith(myvecCopy)); + QVERIFY(!myvecCopy.isSharedWith(myvec)); + QCOMPARE(myvecCopy, QList<T>() << T_FOO << T_FOO); + } +} + +inline namespace Scenarios { +Q_NAMESPACE +enum ListState { + UnsharedList, + SharedList, +}; +Q_ENUM_NS(ListState) +enum RelationWithPrependBuffer { + FitsIntoFreeSpaceAtBegin, + FitsFreeSpaceAtBeginExactly, + ExceedsFreeSpaceAtBegin, + FitsFreeSpaceAtBeginPlusSizeExactly, + FullCapacity, +}; +Q_ENUM_NS(RelationWithPrependBuffer) +} // namespace Scenarios + +void tst_QList::assignUsesPrependBuffer_data() const +{ + QTest::addColumn<ListState>("listState"); + QTest::addColumn<RelationWithPrependBuffer>("relationWithPrependBuffer"); + + const auto sme = QMetaEnum::fromType<ListState>(); + const auto rme = QMetaEnum::fromType<RelationWithPrependBuffer>(); + + for (int i = 0, s = sme.value(i); s != -1; s = sme.value(++i)) { + for (int j = 0, r = rme.value(j); r != -1; r = rme.value(++j)) { + QTest::addRow("%s-%s", sme.key(i), rme.key(j)) + << ListState(s) << RelationWithPrependBuffer(r); + } + } } -void tst_QList::appendCustom() const +template <typename T> +void tst_QList::assignUsesPrependBuffer() const { - const int instancesCount = Custom::counter.loadAcquire(); - append<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); + QFETCH(const ListState, listState); + QFETCH(const RelationWithPrependBuffer, relationWithPrependBuffer); + + const auto capBegin = [](const QList<T> &l) { + return l.begin() - l.d.freeSpaceAtBegin(); + }; + const auto capEnd = [](const QList<T> &l) { + return l.end() + l.d.freeSpaceAtEnd(); + }; + + TST_QLIST_CHECK_LEAKS(T) + { + // Test the prepend optimization. + QList<T> withFreeSpaceAtBegin(16, T_FOO); + // try at most 100 times to create freeSpaceAtBegin(): + for (int i = 0; i < 100 && withFreeSpaceAtBegin.d.freeSpaceAtBegin() < 2; ++i) + withFreeSpaceAtBegin.prepend(T_FOO); + QCOMPARE_GT(withFreeSpaceAtBegin.d.freeSpaceAtBegin(), 1); + + auto c = [&] { + switch (listState) { + case UnsharedList: return std::move(withFreeSpaceAtBegin); + case SharedList: return withFreeSpaceAtBegin; + } + Q_UNREACHABLE_RETURN(withFreeSpaceAtBegin); + }(); + + const auto n = [&] () -> qsizetype { + switch (relationWithPrependBuffer) { + case FitsIntoFreeSpaceAtBegin: + return qsizetype(1); + case FitsFreeSpaceAtBeginExactly: + return c.d.freeSpaceAtBegin(); + case ExceedsFreeSpaceAtBegin: + return c.d.freeSpaceAtBegin() + 1; + case FitsFreeSpaceAtBeginPlusSizeExactly: + return c.d.freeSpaceAtBegin() + c.size(); + case FullCapacity: + return c.capacity(); + }; + Q_UNREACHABLE_RETURN(0); + }(); + + const auto oldCapBegin = capBegin(c); + const auto oldCapEnd = capEnd(c); + + const std::vector v(n, T_BAR); + c.assign(v.begin(), v.end()); + QCOMPARE_EQ(c.d.freeSpaceAtBegin(), 0); // we used the prepend-buffer + if (listState != SharedList) { + // check that we didn't reallocate + QCOMPARE_EQ(capBegin(c), oldCapBegin); + QCOMPARE_EQ(capEnd(c), oldCapEnd); + } + } } void tst_QList::appendRvalue() const @@ -703,9 +923,15 @@ void tst_QList::appendRvalue() const v.append("hello"); QString world = "world"; v.append(std::move(world)); - QVERIFY(world.isEmpty()); QCOMPARE(v.front(), QString("hello")); QCOMPARE(v.back(), QString("world")); + + // check append rvalue to empty list + QList<QString> myvec; + QString test = "test"; + myvec.append(std::move(test)); + QCOMPARE(myvec.size(), 1); + QCOMPARE(myvec.front(), QString("test")); } struct ConstructionCounted @@ -882,6 +1108,7 @@ void tst_QList::appendList() const // Using operators // << QList<ConstructionCounted> v6; + v6.reserve(4); v6 << (QList<ConstructionCounted>() << 1 << 2); v6 << (QList<ConstructionCounted>() << 3 << 4); QCOMPARE(v6, expectedFour); @@ -931,11 +1158,14 @@ void tst_QList::at() const template<typename T> void tst_QList::capacity() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> myvec; // TODO: is this guaranteed? seems a safe assumption, but I suppose preallocation of a // few items isn't an entirely unforseeable possibility. QVERIFY(myvec.capacity() == 0); + QVERIFY(!myvec.isDetached()); // test it gets a size myvec << SimpleValue<T>::at(0) << SimpleValue<T>::at(1) << SimpleValue<T>::at(2); @@ -958,29 +1188,15 @@ void tst_QList::capacity() const QVERIFY(myvec.capacity() == 0); } -void tst_QList::capacityInt() const -{ - capacity<int>(); -} - -void tst_QList::capacityMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - capacity<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::capacityCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - capacity<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - template<typename T> void tst_QList::clear() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> myvec; + myvec.clear(); + QVERIFY(!myvec.isDetached()); + myvec << SimpleValue<T>::at(0) << SimpleValue<T>::at(1) << SimpleValue<T>::at(2); const auto oldCapacity = myvec.capacity(); @@ -990,37 +1206,25 @@ void tst_QList::clear() const QCOMPARE(myvec.capacity(), oldCapacity); } -void tst_QList::clearInt() const -{ - clear<int>(); -} - -void tst_QList::clearMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - clear<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::clearCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - clear<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - void tst_QList::constData() const { int arr[] = { 42, 43, 44 }; QList<int> myvec; + QCOMPARE(myvec.constData(), nullptr); + QVERIFY(!myvec.isDetached()); + myvec << 42 << 43 << 44; - QVERIFY(memcmp(myvec.constData(), reinterpret_cast<const int *>(&arr), sizeof(int) * 3) == 0); + QCOMPARE(memcmp(myvec.constData(), reinterpret_cast<const int *>(&arr), sizeof(int) * 3), 0); } void tst_QList::contains() const { QList<QString> myvec; + + QVERIFY(!myvec.contains(QLatin1String("test"))); + QVERIFY(!myvec.isDetached()); + myvec << "aaa" << "bbb" << "ccc"; QVERIFY(myvec.contains(QLatin1String("aaa"))); @@ -1036,28 +1240,34 @@ void tst_QList::contains() const template<typename T> void tst_QList::count() const { + TST_QLIST_CHECK_LEAKS(T) + // total size { // zero size QList<T> myvec; - QVERIFY(myvec.count() == 0); + QVERIFY(myvec.size() == 0); + QVERIFY(!myvec.isDetached()); // grow myvec.append(SimpleValue<T>::at(0)); - QVERIFY(myvec.count() == 1); + QVERIFY(myvec.size() == 1); myvec.append(SimpleValue<T>::at(1)); - QVERIFY(myvec.count() == 2); + QVERIFY(myvec.size() == 2); // shrink myvec.remove(0); - QVERIFY(myvec.count() == 1); + QVERIFY(myvec.size() == 1); myvec.remove(0); - QVERIFY(myvec.count() == 0); + QVERIFY(myvec.size() == 0); } // count of items { QList<T> myvec; + QCOMPARE(myvec.count(SimpleValue<T>::at(0)), 0); + QVERIFY(!myvec.isDetached()); + myvec << SimpleValue<T>::at(0) << SimpleValue<T>::at(1) << SimpleValue<T>::at(2); // initial tests @@ -1074,28 +1284,8 @@ void tst_QList::count() const } } -void tst_QList::countInt() const -{ - count<int>(); -} - -void tst_QList::countMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - count<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::countCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - count<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - void tst_QList::cpp17ctad() const { -#ifdef __cpp_deduction_guides #define QVERIFY_IS_VECTOR_OF(obj, Type) \ QVERIFY2((std::is_same<decltype(obj), QList<Type>>::value), \ QMetaType::fromType<decltype(obj)::value_type>().name()) @@ -1115,14 +1305,13 @@ void tst_QList::cpp17ctad() const CHECK(QString, QStringLiteral("one"), QStringLiteral("two"), QStringLiteral("three")); #undef QVERIFY_IS_VECTOR_OF #undef CHECK -#else - QSKIP("This test requires C++17 Constructor Template Argument Deduction support enabled in the compiler."); -#endif } void tst_QList::data() const { QList<int> myvec; + QCOMPARE(myvec.data(), nullptr); + myvec << 42 << 43 << 44; // make sure it starts off ok @@ -1135,16 +1324,23 @@ void tst_QList::data() const QCOMPARE(*(myvec.data() + 1), 69); int arr[] = { 42, 69, 44 }; - QVERIFY(memcmp(myvec.data(), reinterpret_cast<int *>(&arr), sizeof(int) * 3) == 0); + QCOMPARE(memcmp(myvec.data(), reinterpret_cast<int *>(&arr), sizeof(int) * 3), 0); + + const QList<int> constVec = myvec; + QCOMPARE(memcmp(constVec.data(), reinterpret_cast<const int *>(&arr), sizeof(int) * 3), 0); + QVERIFY(!constVec.isDetached()); // const data() does not detach() } template<typename T> void tst_QList::empty() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> myvec; // starts empty QVERIFY(myvec.empty()); + QVERIFY(!myvec.isDetached()); // not empty myvec.append(SimpleValue<T>::at(2)); @@ -1155,25 +1351,6 @@ void tst_QList::empty() const QVERIFY(myvec.empty()); } -void tst_QList::emptyInt() const -{ - empty<int>(); -} - -void tst_QList::emptyMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - empty<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::emptyCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - empty<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - void tst_QList::endsWith() const { QList<int> myvec; @@ -1197,58 +1374,24 @@ void tst_QList::endsWith() const template<typename T> void tst_QList::eraseEmpty() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> v; v.erase(v.begin(), v.end()); QCOMPARE(v.size(), 0); } -void tst_QList::eraseEmptyInt() const -{ - eraseEmpty<int>(); -} - -void tst_QList::eraseEmptyMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - eraseEmpty<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::eraseEmptyCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - eraseEmpty<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - template<typename T> void tst_QList::eraseEmptyReserved() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> v; v.reserve(10); v.erase(v.begin(), v.end()); QCOMPARE(v.size(), 0); } -void tst_QList::eraseEmptyReservedInt() const -{ - eraseEmptyReserved<int>(); -} - -void tst_QList::eraseEmptyReservedMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - eraseEmptyReserved<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::eraseEmptyReservedCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - eraseEmptyReserved<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - template<typename T> struct SharedVectorChecker { @@ -1279,10 +1422,12 @@ struct SharedVectorChecker template<typename T> void tst_QList::erase(bool shared) const { - // note: remove() is actually more efficient, and more dangerous, because it uses the non-detaching - // begin() / end() internally. you can also use constBegin() and constEnd() with erase(), but only - // using reinterpret_cast... because both iterator types are really just pointers. - // so we use a mix of erase() and remove() to cover more cases. + TST_QLIST_CHECK_LEAKS(T) + + // note: remove() is actually more efficient, and more dangerous, because it uses the + // non-detaching begin() / end() internally. you can also use constBegin() and constEnd() with + // erase(), but only using reinterpret_cast... because both iterator types are really just + // pointers. so we use a mix of erase() and remove() to cover more cases. { QList<T> v = SimpleValue<T>::vector(12); SharedVectorChecker<T> svc(v, shared); @@ -1336,46 +1481,11 @@ void tst_QList::erase(bool shared) const } } -void tst_QList::eraseInt() const -{ - erase<int>(false); -} - -void tst_QList::eraseIntShared() const -{ - erase<int>(true); -} - -void tst_QList::eraseMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - erase<Movable>(false); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::eraseMovableShared() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - erase<Movable>(true); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::eraseCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - erase<Custom>(false); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - -void tst_QList::eraseCustomShared() const +template<typename T> +void tst_QList::eraseReserved() const { - const int instancesCount = Custom::counter.loadAcquire(); - erase<Custom>(true); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} + TST_QLIST_CHECK_LEAKS(T) -template<typename T> void tst_QList::eraseReserved() const -{ { QList<T> v(12); v.reserve(16); @@ -1408,30 +1518,17 @@ template<typename T> void tst_QList::eraseReserved() const } } -void tst_QList::eraseReservedInt() const -{ - eraseReserved<int>(); -} - -void tst_QList::eraseReservedMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - eraseReserved<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::eraseReservedCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - eraseReserved<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - template<typename T> void tst_QList::fill() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> myvec; + // fill an empty list - it should resize + myvec.fill(SimpleValue<T>::at(1), 2); + QCOMPARE(myvec, QList<T>({ SimpleValue<T>::at(1), SimpleValue<T>::at(1) })); + // resize myvec.resize(5); myvec.fill(SimpleValue<T>::at(1)); @@ -1452,28 +1549,11 @@ void tst_QList::fill() const QCOMPARE(myvec, QList<T>() << SimpleValue<T>::at(3) << SimpleValue<T>::at(3)); } -void tst_QList::fillInt() const -{ - fill<int>(); -} - -void tst_QList::fillMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - fill<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::fillCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - fill<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - template<typename T> void tst_QList::fillDetach() const { + TST_QLIST_CHECK_LEAKS(T) + // detaches to the same size { QList<T> original = { SimpleValue<T>::at(1), SimpleValue<T>::at(1), SimpleValue<T>::at(1) }; @@ -1511,25 +1591,6 @@ void tst_QList::fillDetach() const } } -void tst_QList::fillDetachInt() const -{ - fillDetach<int>(); -} - -void tst_QList::fillDetachMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - fillDetach<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::fillDetachCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - fillDetach<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - void tst_QList::first() const { QList<int> myvec; @@ -1627,6 +1688,8 @@ void tst_QList::constFirst() const template<typename T> void tst_QList::fromList() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> list; list << SimpleValue<T>::at(0) << SimpleValue<T>::at(1) << SimpleValue<T>::at(2) << SimpleValue<T>::at(3); @@ -1638,25 +1701,6 @@ void tst_QList::fromList() const QCOMPARE(list, QList<T>() << SimpleValue<T>::at(0) << SimpleValue<T>::at(1) << SimpleValue<T>::at(2) << SimpleValue<T>::at(3)); } -void tst_QList::fromListInt() const -{ - fromList<int>(); -} - -void tst_QList::fromListMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - fromList<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::fromListCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - fromList<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - #if QT_VERSION < QT_VERSION_CHECK(6,0,0) void tst_QList::fromStdVector() const { @@ -1676,6 +1720,11 @@ void tst_QList::fromStdVector() const void tst_QList::indexOf() const { QList<QString> myvec; + + QCOMPARE(myvec.indexOf("A"), -1); + QCOMPARE(myvec.indexOf("A", 5), -1); + QVERIFY(!myvec.isDetached()); + myvec << "A" << "B" << "C" << "B" << "A"; QVERIFY(myvec.indexOf("B") == 1); @@ -1700,6 +1749,8 @@ void tst_QList::indexOf() const template <typename T> void tst_QList::insert() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> myvec; const T tA = SimpleValue<T>::at(0), @@ -1763,21 +1814,52 @@ void tst_QList::insert() const QCOMPARE(myvec, QList<T>() << tB << tB << tX << tZ << ti << ti << tA << tB << tC << tT); QCOMPARE(myvec2, myvec); -} -void tst_QList::insertInt() const -{ - insert<int>(); -} - -void tst_QList::insertMovable() const -{ - insert<Movable>(); -} - -void tst_QList::insertCustom() const -{ - insert<Custom>(); + // Different insert() into empty list overloads + { + QList<T> myvec; + auto it = myvec.insert(0, tA); + QCOMPARE(myvec.size(), 1); + QCOMPARE(myvec.front(), tA); + QCOMPARE(it, myvec.begin()); + } + { + QList<T> myvec; + auto it = myvec.insert(0, 3, tX); + QCOMPARE(myvec.size(), 3); + QCOMPARE(myvec, QList<T>({ tX, tX, tX })); + QCOMPARE(it, myvec.begin()); + } + { + QList<T> myvec; + auto it = myvec.insert(myvec.cbegin(), tA); + QCOMPARE(myvec.size(), 1); + QCOMPARE(myvec.front(), tA); + QCOMPARE(it, myvec.begin()); + } + { + QList<T> myvec; + auto it = myvec.insert(myvec.cbegin(), 3, tX); + QCOMPARE(myvec.size(), 3); + QCOMPARE(myvec, QList<T>({ tX, tX, tX })); + QCOMPARE(it, myvec.begin()); + } + { + QList<QString> myvec; + QString test = "test"; + auto it = myvec.insert(0, std::move(test)); + QCOMPARE(myvec.size(), 1); + QCOMPARE(myvec.front(), u"test"); + QCOMPARE(it, myvec.begin()); + } + { + QList<QString> myvec; + QString test = "test"; + auto it = myvec.insert(myvec.cbegin(), std::move(test)); + QCOMPARE(myvec.size(), 1); + QCOMPARE(myvec.front(), u"test"); + QCOMPARE(it, myvec.begin()); + } } void tst_QList::insertZeroCount_data() @@ -1806,6 +1888,7 @@ void tst_QList::isEmpty() const // starts ok QVERIFY(myvec.isEmpty()); + QVERIFY(!myvec.isDetached()); // not empty now myvec.append(QLatin1String("hello there")); @@ -1911,6 +1994,11 @@ void tst_QList::constLast() const void tst_QList::lastIndexOf() const { QList<QString> myvec; + + QCOMPARE(myvec.lastIndexOf("A"), -1); + QCOMPARE(myvec.lastIndexOf("A", 5), -1); + QVERIFY(!myvec.isDetached()); + myvec << "A" << "B" << "C" << "B" << "A"; QVERIFY(myvec.lastIndexOf("B") == 3); @@ -1934,6 +2022,12 @@ void tst_QList::lastIndexOf() const void tst_QList::mid() const { QList<QString> list; + + QCOMPARE(list.mid(4, 2), QList<QString>()); + QCOMPARE(list.mid(0, 3), QList<QString>()); + QCOMPARE(list.mid(-2, 3), QList<QString>()); + QVERIFY(!list.isDetached()); + list << "foo" << "bar" << "baz" << "bak" << "buck" << "hello" << "kitty"; QCOMPARE(list.mid(3, 3), QList<QString>() << "bak" << "buck" << "hello"); @@ -1961,6 +2055,8 @@ void tst_QList::sliced() const template <typename T> void tst_QList::qhash() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> l1, l2; QCOMPARE(qHash(l1), qHash(l2)); l1 << SimpleValue<T>::at(0); @@ -1971,15 +2067,17 @@ void tst_QList::qhash() const template <typename T> void tst_QList::move() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> list; list << T_FOO << T_BAR << T_BAZ; // move an item - list.move(0, list.count() - 1); + list.move(0, list.size() - 1); QCOMPARE(list, QList<T>() << T_BAR << T_BAZ << T_FOO); // move it back - list.move(list.count() - 1, 0); + list.move(list.size() - 1, 0); QCOMPARE(list, QList<T>() << T_FOO << T_BAR << T_BAZ); // move an item in the middle @@ -1987,34 +2085,25 @@ void tst_QList::move() const QCOMPARE(list, QList<T>() << T_BAR << T_FOO << T_BAZ); } -void tst_QList::moveInt() const -{ - move<int>(); -} - -void tst_QList::moveMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - move<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::moveCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - move<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - template<typename T> void tst_QList::prepend() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> myvec; + T val1 = SimpleValue<T>::at(0); T val2 = SimpleValue<T>::at(1); T val3 = SimpleValue<T>::at(2); T val4 = SimpleValue<T>::at(3); T val5 = SimpleValue<T>::at(4); + + // prepend to default-constructed empty list + myvec.prepend(val1); + QCOMPARE(myvec.size(), 1); + QCOMPARE(myvec.at(0), val1); + myvec.clear(); + myvec << val1 << val2 << val3; // starts ok @@ -2042,23 +2131,19 @@ void tst_QList::prepend() const QCOMPARE(myvec.at(0), val5); } -void tst_QList::prependInt() const +void tst_QList::prependRvalue() const { - prepend<int>(); -} + QList<QString> myvec; -void tst_QList::prependMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - prepend<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} + QString hello = "hello"; + QString world = "world"; -void tst_QList::prependCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - prepend<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); + myvec.prepend(std::move(world)); + QCOMPARE(myvec.size(), 1); + + myvec.prepend(std::move(hello)); + QCOMPARE(myvec.size(), 2); + QCOMPARE(myvec, QList<QString>({ "hello", "world" })); } void tst_QList::removeAllWithAlias() const @@ -2071,61 +2156,61 @@ void tst_QList::removeAllWithAlias() const template<typename T> void tst_QList::remove() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> myvec; T val1 = SimpleValue<T>::at(1); T val2 = SimpleValue<T>::at(2); T val3 = SimpleValue<T>::at(3); T val4 = SimpleValue<T>::at(4); - myvec << val1 << val2 << val3; - myvec << val1 << val2 << val3; - myvec << val1 << val2 << val3; - // remove middle + T val5 = SimpleValue<T>::at(5); + + // some operations on empty list + QVERIFY(!myvec.removeOne(val1)); + QCOMPARE(myvec.removeAll(val2), 0); + auto count = myvec.removeIf([](const T&) { return true; }); + QCOMPARE(count, 0); + + myvec << val1 << val2 << val3 << val4; + myvec << val1 << val2 << val3 << val4; + myvec << val1 << val2 << val3 << val4; + // remove by index myvec.remove(1); - QCOMPARE(myvec, QList<T>() << val1 << val3 << val1 << val2 << val3 << val1 << val2 << val3); + QCOMPARE(myvec, QList<T>({ val1, val3, val4, val1, val2, val3, val4, val1, val2, val3, val4 })); + myvec.removeAt(6); + QCOMPARE(myvec, QList<T>({ val1, val3, val4, val1, val2, val3, val1, val2, val3, val4 })); // removeOne() - QVERIFY(!myvec.removeOne(val4)); + QVERIFY(!myvec.removeOne(val5)); QVERIFY(myvec.removeOne(val2)); - QCOMPARE(myvec, QList<T>() << val1 << val3 << val1 << val3 << val1 << val2 << val3); + QCOMPARE(myvec, QList<T>({ val1, val3, val4, val1, val3, val1, val2, val3, val4 })); QList<T> myvecCopy = myvec; QVERIFY(myvecCopy.isSharedWith(myvec)); // removeAll() - QCOMPARE(myvec.removeAll(val4), 0); + QCOMPARE(myvec.removeAll(val5), 0); QVERIFY(myvecCopy.isSharedWith(myvec)); QCOMPARE(myvec.removeAll(val1), 3); QVERIFY(!myvecCopy.isSharedWith(myvec)); - QCOMPARE(myvec, QList<T>() << val3 << val3 << val2 << val3); + QCOMPARE(myvec, QList<T>({ val3, val4, val3, val2, val3, val4 })); + QCOMPARE(myvecCopy, QList<T>({ val1, val3, val4, val1, val3, val1, val2, val3, val4 })); myvecCopy = myvec; QVERIFY(myvecCopy.isSharedWith(myvec)); QCOMPARE(myvec.removeAll(val2), 1); QVERIFY(!myvecCopy.isSharedWith(myvec)); - QCOMPARE(myvec, QList<T>() << val3 << val3 << val3); + QCOMPARE(myvec, QList<T>({ val3, val4, val3, val3, val4 })); + QCOMPARE(myvecCopy, QList<T>({ val3, val4, val3, val2, val3, val4 })); + + // removeIf + count = myvec.removeIf([&val4](const T &val) { return val == val4; }); + QCOMPARE(count, 2); + QCOMPARE(myvec, QList<T>({ val3, val3, val3 })); // remove rest myvec.remove(0, 3); QCOMPARE(myvec, QList<T>()); } -void tst_QList::removeInt() const -{ - remove<int>(); -} - -void tst_QList::removeMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - remove<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::removeCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - remove<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - struct RemoveLastTestClass { RemoveLastTestClass() { other = 0; deleted = false; } @@ -2245,7 +2330,9 @@ void tst_QList::resizePOD_data() const QVERIFY(nonEmptyReserved.capacity() >= 15); QTest::newRow("null") << null << 10; + QTest::newRow("null and 0 size") << null << 0; QTest::newRow("empty") << empty << 10; + QTest::newRow("empty and 0 size") << empty << 0; QTest::newRow("emptyReserved") << emptyReserved << 10; QTest::newRow("nonEmpty") << nonEmpty << 10; QTest::newRow("nonEmptyReserved") << nonEmptyReserved << 10; @@ -2261,6 +2348,9 @@ void tst_QList::resizePOD() const vector.resize(size); QCOMPARE(vector.size(), size); QVERIFY(vector.capacity() >= size); + if (vector.isEmpty()) + QVERIFY(!vector.isDetached()); + for (int i = oldSize; i < size; ++i) QVERIFY(vector[i] == 0); // check initialization @@ -2293,7 +2383,9 @@ void tst_QList::resizeComplexMovable_data() const QVERIFY(nonEmptyReserved.capacity() >= 15); QTest::newRow("null") << null << 10; + QTest::newRow("null and 0 size") << null << 0; QTest::newRow("empty") << empty << 10; + QTest::newRow("empty and 0 size") << empty << 0; QTest::newRow("emptyReserved") << emptyReserved << 10; QTest::newRow("nonEmpty") << nonEmpty << 10; QTest::newRow("nonEmptyReserved") << nonEmptyReserved << 10; @@ -2311,6 +2403,8 @@ void tst_QList::resizeComplexMovable() const vector.resize(size); QCOMPARE(vector.size(), size); QVERIFY(vector.capacity() >= size); + if (vector.isEmpty()) + QVERIFY(!vector.isDetached()); for (int i = oldSize; i < size; ++i) QVERIFY(vector[i] == 'j'); // check initialization @@ -2345,7 +2439,9 @@ void tst_QList::resizeComplex_data() const QVERIFY(nonEmptyReserved.capacity() >= 15); QTest::newRow("null") << null << 10; + QTest::newRow("null and 0 size") << null << 0; QTest::newRow("empty") << empty << 10; + QTest::newRow("empty and 0 size") << empty << 0; QTest::newRow("emptyReserved") << emptyReserved << 10; QTest::newRow("nonEmpty") << nonEmpty << 10; QTest::newRow("nonEmptyReserved") << nonEmptyReserved << 10; @@ -2362,6 +2458,8 @@ void tst_QList::resizeComplex() const vector.resize(size); QCOMPARE(vector.size(), size); QVERIFY(vector.capacity() >= size); + if (vector.isEmpty()) + QVERIFY(!vector.isDetached()); for (int i = oldSize; i < size; ++i) QVERIFY(vector[i].i == 'j'); // check default initialization @@ -2434,6 +2532,335 @@ void tst_QList::resizeToTheSameSize() const QCOMPARE(y.size(), x.size()); } +void tst_QList::resizeForOverwrite() const +{ + constexpr int BUILD_COUNT = 42; + { + // Smoke test + QList<int> l(BUILD_COUNT, Qt::Uninitialized); + l.resizeForOverwrite(l.size() + BUILD_COUNT); + } + + { + const int beforeCounter = Movable::counter.loadRelaxed(); + QList<Movable> l(BUILD_COUNT, Qt::Uninitialized); + const int after1Counter = Movable::counter.loadRelaxed(); + QCOMPARE(after1Counter, beforeCounter + BUILD_COUNT); + + l.resizeForOverwrite(l.size() + BUILD_COUNT); + const int after2Counter = Movable::counter.loadRelaxed(); + QCOMPARE(after2Counter, after1Counter + BUILD_COUNT); + } + + struct QtInitializationSupport { + bool wasInitialized; + QtInitializationSupport() : wasInitialized(true) {} + explicit QtInitializationSupport(Qt::Initialization) : wasInitialized(false) {} + }; + + { + QList<QtInitializationSupport> l(BUILD_COUNT); + for (const auto &elem : l) + QVERIFY(elem.wasInitialized); + l.resize(l.size() + BUILD_COUNT); + for (const auto &elem : l) + QVERIFY(elem.wasInitialized); + } + + { + QList<QtInitializationSupport> l(BUILD_COUNT, Qt::Uninitialized); + for (const auto &elem : l) + QVERIFY(!elem.wasInitialized); + l.resizeForOverwrite(l.size() + BUILD_COUNT); + for (const auto &elem : l) + QVERIFY(!elem.wasInitialized); + } +} + +void tst_QList::iterators() const +{ + QList<int> v; + + QCOMPARE(v.begin(), v.end()); + QCOMPARE(v.rbegin(), v.rend()); + + qsizetype idx = 0; + for (; idx < 10; ++idx) + v.push_back(idx); + + // stl-style iterators + idx = 0; + auto it = v.begin(); + QCOMPARE(*it, idx); + // idx == 0 + + std::advance(it, 7); + idx += 7; + QCOMPARE(*it, idx); + // idx == 7 + + it++; + idx++; + QCOMPARE(*it, idx); + // idx == 8 + + ++it; + ++idx; + QCOMPARE(*it, idx); + // idx == 9 + + std::advance(it, -3); + idx -= 3; + QCOMPARE(*it, idx); + // idx == 6 + + it--; + idx--; + QCOMPARE(*it, idx); + // idx == 5 + + --it; + --idx; + QCOMPARE(*it, idx); + // idx == 4 + + it = it + 1; + idx = idx + 1; + QCOMPARE(*it, idx); + // idx == 5 + + it = it + ptrdiff_t(1); + idx = idx + 1; + QCOMPARE(*it, idx); + // idx == 6 + + it = it + qsizetype(1); + idx = idx + 1; + QCOMPARE(*it, idx); + // idx == 7 + + it = it - qsizetype(1); + idx = idx - 1; + QCOMPARE(*it, idx); + // idx == 6 + + it = it - ptrdiff_t(1); + idx = idx - 1; + QCOMPARE(*it, idx); + // idx == 5 + + it = it - 1; + idx = idx - 1; + QCOMPARE(*it, idx); + // idx == 4 + + it -= 1; + idx -= 1; + QCOMPARE(*it, idx); + // idx == 3 + + it -= qsizetype(1); + idx -= 1; + QCOMPARE(*it, idx); + // idx == 2 + + it -= ptrdiff_t(1); + idx -= 1; + QCOMPARE(*it, idx); + // idx == 1 + + it += ptrdiff_t(1); + idx += 1; + QCOMPARE(*it, idx); + // idx == 2 + + it += qsizetype(1); + idx += 1; + QCOMPARE(*it, idx); + // idx == 3 + + it += 1; + idx += 1; + QCOMPARE(*it, idx); + // idx == 4 + + *it = idx + 1; + QCOMPARE(*it, idx + 1); + *it = idx; + + // stl-style reverse iterators + idx = v.size() - 1; + auto rit = v.rbegin(); + QCOMPARE(*rit, idx); + + *rit = idx + 1; + QCOMPARE(*rit, idx + 1); + *rit = idx; + + std::advance(rit, 5); + idx -= 5; + QCOMPARE(*rit, idx); + + ++rit; + --idx; + QCOMPARE(*rit, idx); + + rit++; + idx--; + QCOMPARE(*rit, idx); + + std::advance(rit, -4); + idx += 4; + QCOMPARE(*rit, idx); + + --rit; + ++idx; + QCOMPARE(*rit, idx); + + rit--; + idx++; + QCOMPARE(*rit, idx); +} + +void tst_QList::constIterators() const +{ + const QList<int> constEmptyList; + QCOMPARE(constEmptyList.cbegin(), constEmptyList.cend()); + QCOMPARE(constEmptyList.begin(), constEmptyList.cbegin()); + QCOMPARE(constEmptyList.end(), constEmptyList.cend()); + QCOMPARE(constEmptyList.constBegin(), constEmptyList.constEnd()); + QCOMPARE(constEmptyList.constBegin(), constEmptyList.cbegin()); + QCOMPARE(constEmptyList.constEnd(), constEmptyList.cend()); + QVERIFY(!constEmptyList.isDetached()); + + const QList<int> v { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + // stl-style iterators + qsizetype idx = 0; + auto it = v.cbegin(); + QCOMPARE(*it, idx); + // idx == 0 + + std::advance(it, 7); + idx += 7; + QCOMPARE(*it, idx); + // idx == 7 + + it++; + idx++; + QCOMPARE(*it, idx); + // idx == 8 + + ++it; + ++idx; + QCOMPARE(*it, idx); + // idx == 9 + + std::advance(it, -3); + idx -= 3; + QCOMPARE(*it, idx); + // idx == 6 + + it--; + idx--; + QCOMPARE(*it, idx); + // idx == 5 + + --it; + --idx; + QCOMPARE(*it, idx); + // idx == 4 + + it = it + 1; + idx = idx + 1; + QCOMPARE(*it, idx); + // idx == 5 + + it = it + ptrdiff_t(1); + idx = idx + 1; + QCOMPARE(*it, idx); + // idx == 6 + + it = it + qsizetype(1); + idx = idx + 1; + QCOMPARE(*it, idx); + // idx == 7 + + it = it - qsizetype(1); + idx = idx - 1; + QCOMPARE(*it, idx); + // idx == 6 + + it = it - ptrdiff_t(1); + idx = idx - 1; + QCOMPARE(*it, idx); + // idx == 5 + + it = it - 1; + idx = idx - 1; + QCOMPARE(*it, idx); + // idx == 4 + + it -= 1; + idx -= 1; + QCOMPARE(*it, idx); + // idx == 3 + + it -= qsizetype(1); + idx -= 1; + QCOMPARE(*it, idx); + // idx == 2 + + it -= ptrdiff_t(1); + idx -= 1; + QCOMPARE(*it, idx); + // idx == 1 + + it += ptrdiff_t(1); + idx += 1; + QCOMPARE(*it, idx); + // idx == 2 + + it += qsizetype(1); + idx += 1; + QCOMPARE(*it, idx); + // idx == 3 + + it += 1; + idx += 1; + QCOMPARE(*it, idx); + // idx == 4 + + // stl-style reverse iterators + idx = v.size() - 1; + auto rit = v.crbegin(); + QCOMPARE(*rit, idx); + + std::advance(rit, 5); + idx -= 5; + QCOMPARE(*rit, idx); + + ++rit; + --idx; + QCOMPARE(*rit, idx); + + rit++; + idx--; + QCOMPARE(*rit, idx); + + std::advance(rit, -4); + idx += 4; + QCOMPARE(*rit, idx); + + --rit; + ++idx; + QCOMPARE(*rit, idx); + + rit--; + idx++; + QCOMPARE(*rit, idx); +} + void tst_QList::reverseIterators() const { QList<int> v; @@ -2452,40 +2879,31 @@ void tst_QList::reverseIterators() const template<typename T> void tst_QList::size() const { + TST_QLIST_CHECK_LEAKS(T) + + // also verify that length() is an alias to size() + // zero size QList<T> myvec; QVERIFY(myvec.size() == 0); + QCOMPARE(myvec.size(), myvec.size()); + QVERIFY(!myvec.isDetached()); // grow myvec.append(SimpleValue<T>::at(0)); QVERIFY(myvec.size() == 1); + QCOMPARE(myvec.size(), myvec.size()); myvec.append(SimpleValue<T>::at(1)); QVERIFY(myvec.size() == 2); + QCOMPARE(myvec.size(), myvec.size()); // shrink myvec.remove(0); QVERIFY(myvec.size() == 1); + QCOMPARE(myvec.size(), myvec.size()); myvec.remove(0); QVERIFY(myvec.size() == 0); -} - -void tst_QList::sizeInt() const -{ - size<int>(); -} - -void tst_QList::sizeMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - size<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::sizeCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - size<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); + QCOMPARE(myvec.size(), myvec.size()); } // ::squeeze() is tested in ::capacity(). @@ -2513,6 +2931,8 @@ void tst_QList::startsWith() const template<typename T> void tst_QList::swap() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> v1, v2; T val1 = SimpleValue<T>::at(0); T val2 = SimpleValue<T>::at(1); @@ -2528,25 +2948,6 @@ void tst_QList::swap() const QCOMPARE(v2,QList<T>() << val1 << val2 << val3); } -void tst_QList::swapInt() const -{ - swap<int>(); -} - -void tst_QList::swapMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - swap<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::swapCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - swap<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - void tst_QList::toList() const { QList<QString> myvec; @@ -2575,6 +2976,11 @@ void tst_QList::toStdVector() const void tst_QList::value() const { QList<QString> myvec; + + QCOMPARE(myvec.value(1), QString()); + QCOMPARE(myvec.value(-1, QLatin1String("default")), QLatin1String("default")); + QVERIFY(!myvec.isDetached()); + myvec << "A" << "B" << "C"; // valid calls @@ -2691,11 +3097,21 @@ void tst_QList::reserveZero() vec.append(42); QCOMPARE(vec.size(), 1); QVERIFY(vec.capacity() >= 1); + + QList<int> vec2; + vec2.reserve(0); // should not crash either + vec2.reserve(-1); + vec2.squeeze(); + QCOMPARE(vec2.size(), 0); + QCOMPARE(vec2.capacity(), 0); + QVERIFY(!vec2.isDetached()); } template<typename T> void tst_QList::initializeList() { + TST_QLIST_CHECK_LEAKS(T) + T val1(SimpleValue<T>::at(1)); T val2(SimpleValue<T>::at(2)); T val3(SimpleValue<T>::at(3)); @@ -2714,25 +3130,6 @@ void tst_QList::initializeList() QCOMPARE(v4.size(), 0); } -void tst_QList::initializeListInt() -{ - initializeList<int>(); -} - -void tst_QList::initializeListMovable() -{ - const int instancesCount = Movable::counter.loadAcquire(); - initializeList<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::initializeListCustom() -{ - const int instancesCount = Custom::counter.loadAcquire(); - initializeList<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - void tst_QList::const_shared_null() { QList<int> v2; @@ -2742,6 +3139,8 @@ void tst_QList::const_shared_null() template<typename T> void tst_QList::detach() const { + TST_QLIST_CHECK_LEAKS(T) + { // detach an empty vector QList<T> v; @@ -2836,25 +3235,6 @@ void tst_QList::detach() const } } -void tst_QList::detachInt() const -{ - detach<int>(); -} - -void tst_QList::detachMovable() const -{ - const int instancesCount = Movable::counter.loadAcquire(); - detach<Movable>(); - QCOMPARE(instancesCount, Movable::counter.loadAcquire()); -} - -void tst_QList::detachCustom() const -{ - const int instancesCount = Custom::counter.loadAcquire(); - detach<Custom>(); - QCOMPARE(instancesCount, Custom::counter.loadAcquire()); -} - static QAtomicPointer<QList<int> > detachThreadSafetyDataInt; static QAtomicPointer<QList<Movable> > detachThreadSafetyDataMovable; static QAtomicPointer<QList<Custom> > detachThreadSafetyDataCustom; @@ -3003,53 +3383,43 @@ void tst_QList::swapItemsAt() const QCOMPARE(copy.at(2), 2); } -void tst_QList::emplaceInt() +void tst_QList::emplaceReturnsIterator() { - emplaceImpl<int>(); -} + QList<Movable> vec; -void tst_QList::emplaceCustom() -{ - emplaceImpl<Custom>(); -} + vec.emplace(0, 'k')->i = 'p'; -void tst_QList::emplaceMovable() -{ - emplaceImpl<Movable>(); + QCOMPARE(vec[0].i, 'p'); } -void tst_QList::emplaceConsistentWithStdVectorInt() +void tst_QList::emplaceFront() const { - emplaceConsistentWithStdVectorImpl<int>(); -} + QAtomicScopedValueRollback rollback(Movable::counter, 0); -void tst_QList::emplaceConsistentWithStdVectorCustom() -{ - emplaceConsistentWithStdVectorImpl<Custom>(); -} + QList<Movable> vec; + vec.emplaceFront('b'); + QCOMPARE(Movable::counter, 1); -void tst_QList::emplaceConsistentWithStdVectorMovable() -{ - emplaceConsistentWithStdVectorImpl<Movable>(); -} + vec.emplaceFront('a'); + QCOMPARE(Movable::counter, 2); -void tst_QList::emplaceConsistentWithStdVectorQString() -{ - emplaceConsistentWithStdVectorImpl<QString>(); + QCOMPARE(vec, QList<Movable>({ 'a', 'b' })); } -void tst_QList::emplaceReturnsIterator() +void tst_QList::emplaceFrontReturnsRef() const { QList<Movable> vec; - vec.emplace(0, 'k')->i = 'p'; + QCOMPARE(vec.emplaceFront('c').i, 'c'); - QCOMPARE(vec[0].i, 'p'); + vec.emplaceFront('b').i = 'a'; + + QCOMPARE(vec.front().i, 'a'); } void tst_QList::emplaceBack() { - QScopedValueRollback<QAtomicInt> rollback(Movable::counter, 0); + QAtomicScopedValueRollback rollback(Movable::counter, 0); QList<Movable> vec; @@ -3106,11 +3476,48 @@ void tst_QList::emplaceWithElementFromTheSameContainer_data() template<typename T> void tst_QList::emplaceImpl() const { + TST_QLIST_CHECK_LEAKS(T) + QList<T> vec {'a', 'b', 'c', 'd'}; vec.emplace(2, 'k'); + QCOMPARE(vec.size(), 5); // emplace adds new element QCOMPARE(vec[2], T('k')); + + vec.emplace(vec.end(), T('f')); + + QCOMPARE(vec.size(), 6); + QCOMPARE(vec.back(), T('f')); + + // emplace() into empty container + { + QList<T> vec; + vec.emplace(vec.begin(), 'a'); + QCOMPARE(vec.size(), 1); + QCOMPARE(vec.front(), T('a')); + } + { + QList<T> vec; + vec.emplace(0, 'a'); + QCOMPARE(vec.size(), 1); + QCOMPARE(vec.front(), T('a')); + } +} + +template <typename T> +void tst_QList::replace() const +{ + TST_QLIST_CHECK_LEAKS(T) + + QList<T> vec { 'a', 'b', 'c', 'd' }; + T e = 'e'; + vec.replace(0, e); + QCOMPARE(vec[0], T('e')); + + T f = 'f'; + vec.replace(2, std::move(f)); + QCOMPARE(vec[2], T('f')); } template <class T> @@ -3130,6 +3537,8 @@ static void squeezeVec(QList<T> &qVec, std::vector<T> &stdVec) template<typename T> void tst_QList::emplaceConsistentWithStdVectorImpl() const { + TST_QLIST_CHECK_LEAKS(T) + // fast-patch to make QString work with the old logic const auto convert = [] (char i) { if constexpr (std::is_same_v<QString, T>) { @@ -3218,7 +3627,7 @@ void tst_QList::fromReadOnlyData() const QCOMPARE(v.size(), qsizetype(11)); // v.capacity() is unspecified, for now - QCOMPARE((void*)(const char*)(v.constBegin() + v.size()), (void*)(const char*)v.constEnd()); + QCOMPARE((void*)(v.constBegin() + v.size()).operator->(), (void*)v.constEnd().operator->()); for (int i = 0; i < 10; ++i) QCOMPARE(v[i], char('A' + i)); @@ -3247,7 +3656,7 @@ struct alignas(8) CustomAligned friend bool operator==(const CustomAligned &x, const CustomAligned &y) { return x.v == y.v; } }; -void tst_QList::qtbug_90359() const +void tst_QList::reallocateCustomAlignedType_qtbug90359() const { // Note: a very special test that could only fail for specific alignments constexpr bool canFail = (alignof(QArrayData) == 4) && (sizeof(QArrayData) == 12); @@ -3265,5 +3674,296 @@ void tst_QList::qtbug_90359() const QCOMPARE(actual, expected); } +template<typename T, typename Reinsert> +void tst_QList::reinsert(Reinsert op) const +{ + TST_QLIST_CHECK_LEAKS(T) + + QList<T> list(1); + // this constant is big enough for the QList to stop reallocating, after + // all, size is always less than 3 + const int maxIters = 128; + for (int i = 0; i < maxIters; ++i) { + op(list); + } + + // if QList continues to grow, it's an error + qsizetype capacity = list.capacity(); + for (int i = 0, enoughIters = int(capacity) * 2; i < enoughIters; ++i) { + op(list); + QCOMPARE(capacity, list.capacity()); + } +} + +template<typename T> +void tst_QList::stability_reserve() const +{ + TST_QLIST_CHECK_LEAKS(T) + + // NOTE: this test verifies that QList::constData() stays unchanged when + // inserting as much as requested by the reserve. This is specifically + // designed this way as in cases when QTypeInfo<T>::isRelocatable returns + // true, reallocation might use fast ::realloc() path which may in theory + // (and, actually, in practice) just expand the current memory area and thus + // keep QList::constData() unchanged, which means checks like + // QVERIFY(oldConstData != vec.constData()) are flaky. When + // QTypeInfo<T>::isRelocatable returns false, constData() will always change + // if a reallocation happens and this will fail the test. This should be + // sufficient on its own to test the stability requirements. + + { + QList<T> vec; + vec.reserve(64); + const T *ptr = vec.constData(); + vec.append(QList<T>(64)); + QCOMPARE(ptr, vec.constData()); + } + + { + QList<T> vec; + vec.prepend(SimpleValue<T>::at(0)); + vec.removeFirst(); + vec.reserve(64); + const T *ptr = vec.constData(); + vec.append(QList<T>(64)); + QCOMPARE(ptr, vec.constData()); + } + + { + QList<T> vec; + const T *ptr = vec.constData(); + vec.reserve(vec.capacity()); + QCOMPARE(ptr, vec.constData()); + vec.append(QList<T>(vec.capacity())); + QCOMPARE(ptr, vec.constData()); + } + + { + QList<T> vec; + vec.prepend(SimpleValue<T>::at(0)); + vec.removeFirst(); + vec.reserve(vec.capacity()); + const T *ptr = vec.constData(); + vec.append(QList<T>(vec.capacity())); + QCOMPARE(ptr, vec.constData()); + } + + { + QList<T> vec; + vec.append(SimpleValue<T>::at(0)); + vec.reserve(64); + const T *ptr = vec.constData(); + vec.append(QList<T>(64 - vec.size())); // 1 element is already in the container + QCOMPARE(ptr, vec.constData()); + QCOMPARE(vec.size(), 64); + QCOMPARE(vec.capacity(), 64); + const qsizetype oldCapacity = vec.capacity(); + vec.append(SimpleValue<T>::at(1)); // will reallocate as this exceeds 64 + QVERIFY(oldCapacity < vec.capacity()); + } + + { + QList<T> vec; + vec.prepend(SimpleValue<T>::at(0)); + vec.reserve(64); + const T *ptr = vec.constData(); + vec.append(QList<T>(64 - vec.size())); // 1 element is already in the container + QCOMPARE(ptr, vec.constData()); + QCOMPARE(vec.size(), 64); + QCOMPARE(vec.capacity(), 64); + const qsizetype oldCapacity = vec.capacity(); + vec.append(SimpleValue<T>::at(1)); // will reallocate as this exceeds 64 + QVERIFY(oldCapacity < vec.capacity()); + } +} + +template<typename T> +void tst_QList::stability_erase() const +{ + TST_QLIST_CHECK_LEAKS(T) + + // invalidated: [pos, end()) + for (int pos = 1; pos < 10; ++pos) { + QList<T> v(10); + int k = 0; + std::generate(v.begin(), v.end(), [&k]() { return SimpleValue<T>::at(k++); }); + const auto ptr = v.constData(); + + auto [copy, reference] = qlistCopyAndReferenceFromRange(v.begin(), v.begin() + pos); + + v.remove(pos, 1); + QVERIFY(ptr == v.constData()); + for (int i = 0; i < copy.size(); ++i) + QCOMPARE(*reference[i], copy[i]); + } + + // 0 is a special case, because all values get invalidated + { + QList<T> v(10); + const auto ptr = v.constData(); + v.remove(0, 2); + QVERIFY(ptr != v.constData()); // can do fast removal from begin() + } + + // when erasing everything, leave the data pointer in place (not strictly + // required, but this makes more sense in general) + { + QList<T> v(10); + const auto ptr = v.constData(); + v.remove(0, v.size()); + QVERIFY(ptr == v.constData()); + } +} + +template<typename T> +void tst_QList::stability_append() const +{ + TST_QLIST_CHECK_LEAKS(T) + + { + QList<T> v(10); + int k = 0; + std::generate(v.begin(), v.end(), [&k]() { return SimpleValue<T>::at(k++); }); + QList<T> src(1, SimpleValue<T>::at(0)); + v.append(src.begin(), src.end()); + QCOMPARE_LE(v.size(), v.capacity()); + + for (int i = 0; i < v.capacity() - v.size(); ++i) { + auto [copy, reference] = qlistCopyAndReferenceFromRange(v.begin(), v.end()); + v.append(SimpleValue<T>::at(i)); + for (int i = 0; i < copy.size(); ++i) + QCOMPARE(*reference[i], copy[i]); + } + } + + { + QList<T> v; + v.reserve(10); + const qsizetype capacity = v.capacity(); + const T *ptr = v.constData(); + v.prepend(SimpleValue<T>::at(0)); + // here we abuse the internal details of QList. since there's enough + // free space, QList should've only rearranged the data in memory, + // without reallocating. + QCOMPARE(capacity, v.capacity()); // otherwise cannot rely on ptr + const qsizetype freeSpaceAtBegin = v.constData() - ptr; + const qsizetype freeSpaceAtEnd = v.capacity() - v.size() - freeSpaceAtBegin; + QVERIFY(freeSpaceAtEnd > 0); // otherwise this test is useless + QVERIFY(v.size() + freeSpaceAtBegin + freeSpaceAtEnd == v.capacity()); + + for (int i = 0; i < freeSpaceAtEnd; ++i) { + auto [copy, reference] = qlistCopyAndReferenceFromRange(v.begin(), v.end()); + QList<T> src(1, SimpleValue<T>::at(i)); + v.append(src.begin(), src.end()); + for (int i = 0; i < copy.size(); ++i) + QCOMPARE(*reference[i], copy[i]); + } + } +} + +template<typename T, typename Insert> +void tst_QList::stability_insert(Insert op) const +{ + TST_QLIST_CHECK_LEAKS(T) + + // invalidated: [pos, end()) + for (int pos = 1; pos <= 10; ++pos) { + QList<T> v(10); + int k = 0; + std::generate(v.begin(), v.end(), [&k]() { return SimpleValue<T>::at(k++); }); + v.append(SimpleValue<T>::at(0)); // causes growth + v.removeLast(); + QCOMPARE(v.size(), 10); + QVERIFY(v.size() < v.capacity()); + + auto [copy, reference] = qlistCopyAndReferenceFromRange(v.begin(), v.begin() + pos); + op(v, pos, SimpleValue<T>::at(0)); + for (int i = 0; i < pos; ++i) + QCOMPARE(*reference[i], copy[i]); + } + + for (int pos = 1; pos <= 10; ++pos) { + QList<T> v(10); + int k = 0; + std::generate(v.begin(), v.end(), [&k]() { return SimpleValue<T>::at(k++); }); + v.prepend(SimpleValue<T>::at(0)); // causes growth and free space at begin > 0 + v.removeFirst(); + QCOMPARE(v.size(), 10); + QVERIFY(v.size() < v.capacity()); + + auto [copy, reference] = qlistCopyAndReferenceFromRange(v.begin(), v.begin() + pos); + op(v, pos, SimpleValue<T>::at(0)); + for (int i = 0; i < pos; ++i) + QCOMPARE(*reference[i], copy[i]); + } +} + +template<typename T> +void tst_QList::stability_resize() const +{ + TST_QLIST_CHECK_LEAKS(T) + + { + QList<T> v(10); + v.reserve(15); + QVERIFY(v.size() < v.capacity()); + int k = 0; + std::generate(v.begin(), v.end(), [&k]() { return SimpleValue<T>::at(k++); }); + auto [copy, reference] = qlistCopyAndReferenceFromRange(v.begin(), v.end()); + + v.resize(15); + for (int i = 0; i < copy.size(); ++i) + QCOMPARE(*reference[i], copy[i]); + } + + { + QList<T> v(10); + int k = 0; + std::generate(v.begin(), v.end(), [&k]() { return SimpleValue<T>::at(k++); }); + auto [copy, reference] = qlistCopyAndReferenceFromRange(v.begin(), v.end()); + + v.resize(10); + for (int i = 0; i < 10; ++i) + QCOMPARE(*reference[i], copy[i]); + } + + { + QList<T> v(10); + int k = 0; + std::generate(v.begin(), v.end(), [&k]() { return SimpleValue<T>::at(k++); }); + auto [copy, reference] = qlistCopyAndReferenceFromRange(v.begin(), v.end()); + + v.resize(5); + for (int i = 0; i < 5; ++i) + QCOMPARE(*reference[i], copy[i]); + } + + // special case due to prepend: + { + QList<T> v; + v.reserve(20); + const qsizetype capacity = v.capacity(); + const T *ptr = v.constData(); + v.prepend(SimpleValue<T>::at(0)); // now there's free space at begin + v.resize(10); + QVERIFY(v.size() < v.capacity()); + // here we abuse the internal details of QList. since there's enough + // free space, QList should've only rearranged the data in memory, + // without reallocating. + QCOMPARE(capacity, v.capacity()); // otherwise cannot rely on ptr + const qsizetype freeSpaceAtBegin = v.constData() - ptr; + const qsizetype freeSpaceAtEnd = v.capacity() - v.size() - freeSpaceAtBegin; + QVERIFY(freeSpaceAtEnd > 0); // otherwise this test is useless + QVERIFY(v.size() + freeSpaceAtBegin + freeSpaceAtEnd == v.capacity()); + int k = 0; + std::generate(v.begin(), v.end(), [&k]() { return SimpleValue<T>::at(k++); }); + auto [copy, reference] = qlistCopyAndReferenceFromRange(v.begin(), v.end()); + + v.resize(v.size() + freeSpaceAtEnd); + for (int i = 0; i < copy.size(); ++i) + QCOMPARE(*reference[i], copy[i]); + } +} + QTEST_MAIN(tst_QList) #include "tst_qlist.moc" |