diff options
Diffstat (limited to 'tests')
30 files changed, 1942 insertions, 53 deletions
diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index ac8aece707b..7dd9340f51b 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -4,6 +4,9 @@ # Order by dependency [*], then alphabetic. [*] If bugs in part A of # our source would break tests of part B, then test A before B. + +add_subdirectory(util/testrunner) + set(run_dbus_tests OFF) if (QT_FEATURE_dbus) set(run_dbus_tests ON) diff --git a/tests/auto/cmake/mockplugins/.cmake.conf b/tests/auto/cmake/mockplugins/.cmake.conf index be788d10f8e..846c4f3b923 100644 --- a/tests/auto/cmake/mockplugins/.cmake.conf +++ b/tests/auto/cmake/mockplugins/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.12.0") diff --git a/tests/auto/cmake/test_generating_cpp_exports/.cmake.conf b/tests/auto/cmake/test_generating_cpp_exports/.cmake.conf index be788d10f8e..846c4f3b923 100644 --- a/tests/auto/cmake/test_generating_cpp_exports/.cmake.conf +++ b/tests/auto/cmake/test_generating_cpp_exports/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.12.0") diff --git a/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECT.json b/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECT.json index 9bd20506429..28be7330cc1 100644 --- a/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECT.json +++ b/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECT.json @@ -14,6 +14,9 @@ ] } ], + "hashes": { + "MetaType": "0$swya0mP+olQ6EImtfZ4HW3dVkKs" + }, "inputFile": "MetaType.h", "outputRevision": 69 } diff --git a/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECTandQ_PROPERTY.json b/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECTandQ_PROPERTY.json index fe80985f796..576668df12f 100644 --- a/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECTandQ_PROPERTY.json +++ b/tests/auto/cmake/test_qt_extract_metatypes/test_qt_extract_metatypes_project/testdata/qt6metatypetest_metatypesQ_OBJECTandQ_PROPERTY.json @@ -30,6 +30,9 @@ ] } ], + "hashes": { + "MetaType": "0$NMxUTKrEcV2vk8Gr4Jl/SR4Q7/c" + }, "inputFile": "MetaType.h", "outputRevision": 69 } diff --git a/tests/auto/cmake/test_static_resources/.cmake.conf b/tests/auto/cmake/test_static_resources/.cmake.conf index be788d10f8e..846c4f3b923 100644 --- a/tests/auto/cmake/test_static_resources/.cmake.conf +++ b/tests/auto/cmake/test_static_resources/.cmake.conf @@ -1 +1 @@ -set(QT_REPO_MODULE_VERSION "6.11.0") +set(QT_REPO_MODULE_VERSION "6.12.0") diff --git a/tests/auto/corelib/animation/qparallelanimationgroup/tst_qparallelanimationgroup.cpp b/tests/auto/corelib/animation/qparallelanimationgroup/tst_qparallelanimationgroup.cpp index c2c09a234c8..d134afc4323 100644 --- a/tests/auto/corelib/animation/qparallelanimationgroup/tst_qparallelanimationgroup.cpp +++ b/tests/auto/corelib/animation/qparallelanimationgroup/tst_qparallelanimationgroup.cpp @@ -463,9 +463,7 @@ void tst_QParallelAnimationGroup::deleteChildrenWithRunningGroup() QCOMPARE(group.state(), QAnimationGroup::Running); QCOMPARE(anim1->state(), QAnimationGroup::Running); - QTest::qWaitFor([&]{ - return group.currentLoopTime() > 0; - }, 200ms); + QVERIFY(QTest::qWaitFor([&] { return group.currentLoopTime() > 0; }, 200ms)); delete anim1; QCOMPARE(group.animationCount(), 0); diff --git a/tests/auto/corelib/io/CMakeLists.txt b/tests/auto/corelib/io/CMakeLists.txt index c0d5ea3136e..10327ceaefb 100644 --- a/tests/auto/corelib/io/CMakeLists.txt +++ b/tests/auto/corelib/io/CMakeLists.txt @@ -9,7 +9,7 @@ endif() if(QT_FEATURE_private_tests) add_subdirectory(qabstractfileengine) add_subdirectory(qfileinfo) - if(LINUX AND QT_FEATURE_liburing) + if((LINUX AND QT_FEATURE_liburing) OR (WIN32 AND QT_FEATURE_windows_ioring)) add_subdirectory(qioring) endif() add_subdirectory(qipaddress) diff --git a/tests/auto/corelib/io/qioring/tst_qioring.cpp b/tests/auto/corelib/io/qioring/tst_qioring.cpp index 1128bcd7979..75d4fe68c55 100644 --- a/tests/auto/corelib/io/qioring/tst_qioring.cpp +++ b/tests/auto/corelib/io/qioring/tst_qioring.cpp @@ -5,7 +5,12 @@ #include <QtCore/private/qioring_p.h> +#ifdef Q_OS_WIN +#include <QtCore/qt_windows.h> +#include <io.h> +#else #include <QtCore/private/qcore_unix_p.h> +#endif using namespace Qt::StringLiterals; using namespace std::chrono_literals; @@ -30,7 +35,13 @@ private: void tst_QIORing::closeFile(qintptr fd) { +#ifdef Q_OS_WIN + // NOLINTNEXTLINE(performance-no-int-to-ptr) + HANDLE h = HANDLE(fd); + CloseHandle(h); +#else QT_CLOSE(fd); +#endif } qintptr tst_QIORing::openHelper(QIORing *ring, const QString &path, QIODevice::OpenMode flags) @@ -109,7 +120,11 @@ void tst_QIORing::read() QFile file(QFINDTESTDATA("data/input.txt")); QVERIFY(file.open(QIODevice::ReadOnly)); int fd = file.handle(); +#ifdef Q_OS_WIN + qintptr nativeFd = _get_osfhandle(fd); +#else qintptr nativeFd = fd; +#endif QIORing ring; QVERIFY(ring.ensureInitialized()); diff --git a/tests/auto/corelib/io/qurl/tst_qurl.cpp b/tests/auto/corelib/io/qurl/tst_qurl.cpp index 71389abc976..a4ef698d380 100644 --- a/tests/auto/corelib/io/qurl/tst_qurl.cpp +++ b/tests/auto/corelib/io/qurl/tst_qurl.cpp @@ -925,7 +925,7 @@ void tst_QUrl::resolving_data() QTest::newRow("/-on-empty-no-authority") << "scheme:" << "/" << "scheme:/"; QTest::newRow(".-on-empty-no-authority") << "scheme:" << "." << "scheme:"; QTest::newRow("./-on-empty-no-authority") << "scheme:" << "./" << "scheme:"; - QTest::newRow(".//-on-empty-no-authority") << "scheme:" << "./" << "scheme:"; + QTest::newRow(".//-on-empty-no-authority") << "scheme:" << ".//" << "scheme:"; QTest::newRow("..-on-empty-no-authority") << "scheme:" << ".." << "scheme:"; QTest::newRow("../-on-empty-no-authority") << "scheme:" << "../" << "scheme:"; diff --git a/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp b/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp index eab791e57eb..29e26f99bdd 100644 --- a/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp +++ b/tests/auto/corelib/itemmodels/qrangemodeladapter/tst_qrangemodeladapter.cpp @@ -220,8 +220,8 @@ API_TEST(moveRows, moveRows(0, 0, 0)) API_TEST(moveTreeRows, moveRows(QList<int>{0, 0}, 0, QList<int>{0, 0})) API_TEST(insertColumn, insertColumn(0)) -API_TEST(insertColumnWithData, insertColumn(0, {})) -API_TEST(insertColumns, insertColumns(0, std::declval<Range&>())) +API_TEST(insertColumnWithData, insertColumn(0, QList<int>{0})) +API_TEST(insertColumns, insertColumns(0, QList<int>{0})) API_TEST(removeColumn, removeColumn(0)) API_TEST(removeColumns, removeColumns(0, 0)) API_TEST(moveColumn, moveColumn(0, 0)) @@ -849,7 +849,7 @@ void tst_QRangeModelAdapter::insertColumn_API() static_assert(has_insertColumnWithData(d.tableOfNumbers)); static_assert(!has_insertColumnWithData(d.constTableOfNumbers)); - static_assert(has_insertColumnWithData(d.tableOfPointers)); + static_assert(!has_insertColumnWithData(d.tableOfPointers)); } void tst_QRangeModelAdapter::insertColumns_API() @@ -863,7 +863,7 @@ void tst_QRangeModelAdapter::insertColumns_API() static_assert(has_insertColumns(d.tableOfNumbers)); static_assert(!has_insertColumns(d.constTableOfNumbers)); - static_assert(has_insertColumns(d.tableOfPointers)); + static_assert(!has_insertColumns(d.tableOfPointers)); static_assert(!has_insertColumns(d.tableOfRowPointers)); static_assert(!has_insertColumns(d.listOfNamedRoles)); static_assert(!has_insertColumns(d.m_tree)); @@ -1025,8 +1025,18 @@ void tst_QRangeModelAdapter::modelReset() QCOMPARE(adapter[0], 3); QCOMPARE(adapter, (std::vector<int>{3, 2, 1})); + modelAboutToBeResetSpy.clear(); + modelResetSpy.clear(); std::vector<int> modifiedData = adapter; + + adapter.assign(modifiedData.begin(), modifiedData.end()); + QCOMPARE(modelResetSpy.count(), 1); + adapter.setRange(std::vector<int>{3, 2, 1}); + QCOMPARE(modelResetSpy.count(), 2); + std::vector<short> shorts = {10, 11, 12}; + adapter.assign(shorts.begin(), shorts.end()); + QCOMPARE(modelResetSpy.count(), 3); } { @@ -1533,7 +1543,7 @@ void tst_QRangeModelAdapter::tableWriteAccess() QTest::ignoreMessage(QtCriticalMsg, QRegularExpression("Not able to assign QVariant")); QTest::ignoreMessage(QtWarningMsg, QRegularExpression("Writing value of type Object\\* to " - "role Qt::RangeModelAdapterRole at index .* of the model failed")); + "role Qt::RangeModelAdapterRole at index .* failed")); #endif adapter.at(0, 0) = new Object; QCOMPARE(dataChangedSpy.count(), 0); @@ -2620,19 +2630,44 @@ using ObjectTree = std::vector<ObjectTreeItem>; class ObjectTreeItem : public ObjectRow { public: - ObjectTreeItem(Object *item = nullptr) + ObjectTreeItem() = default; + + explicit ObjectTreeItem(Object *item) { m_objects[0] = item; } + ObjectTreeItem(const ObjectTreeItem &other) = delete; + ObjectTreeItem &operator=(const ObjectTreeItem &other) = delete; + ObjectTreeItem(ObjectTreeItem &&other) noexcept + { + m_children = std::move(other.m_children); + m_objects = std::move(other.m_objects); + other.m_objects = {}; + } + + ObjectTreeItem &operator=(ObjectTreeItem &&other) noexcept + { + m_children = std::move(other.m_children); + m_objects = std::move(other.m_objects); + other.m_objects = {}; + return *this; + } + + ~ObjectTreeItem() + { + qDeleteAll(m_objects); + } + ObjectTreeItem *parentRow() const { return m_parentRow; } void setParentRow(ObjectTreeItem *parentRow) { m_parentRow = parentRow; } const auto &childRows() const { return m_children; } auto &childRows() { return m_children; } private: - template <std::size_t I> // read-only is enough for this - friend decltype(auto) get(const ObjectTreeItem &row) { return row.m_objects[I]; } + template <std::size_t I, typename Item, + std::enable_if_t<std::is_same_v<q20::remove_cvref_t<Item>, ObjectTreeItem>, bool> = true> + friend decltype(auto) get(Item &&row) { return q23::forward_like<Item>(row.m_objects[I]); } ObjectTreeItem *m_parentRow = nullptr; std::optional<ObjectTree> m_children = std::nullopt; @@ -2645,9 +2680,7 @@ namespace std { void tst_QRangeModelAdapter::insertAutoConnectObjects() { - ObjectTree emptyTree; - - QRangeModelAdapter adapter(emptyTree); + QRangeModelAdapter adapter(ObjectTree{}); QSignalSpy dataChangedSpy(adapter.model(), &QAbstractItemModel::dataChanged); adapter.model()->setAutoConnectPolicy(QRangeModel::AutoConnectPolicy::Full); @@ -2661,11 +2694,11 @@ void tst_QRangeModelAdapter::insertAutoConnectObjects() Object *newChild = new Object; auto firstRow = adapter.begin(); - (*firstRow).children() = ObjectTree{ - ObjectTreeItem(newChild), - ObjectTreeItem(), - ObjectTreeItem() - }; + { + ObjectTree children(3); + children[0] = ObjectTreeItem(newChild); + (*firstRow).children() = std::move(children); + } QCOMPARE(dataChangedSpy.count(), 0); QVERIFY(adapter.hasChildren(0)); newChild->setString("0.0"); @@ -2683,12 +2716,14 @@ void tst_QRangeModelAdapter::insertAutoConnectObjects() newChild = new Object; Object *newGrandChild = new Object; ObjectTreeItem newBranch(newChild); - newBranch.childRows() = ObjectTree{ - ObjectTreeItem(), // skip the first column - ObjectTreeItem(newGrandChild), - ObjectTreeItem() - }; - adapter.at({0, 2}) = newBranch; + { + ObjectTree children(3); + // skip the first row to verify that we continue through nullptr + children[1] = ObjectTreeItem(newGrandChild); + newBranch.childRows() = std::move(children); + } + adapter.at({0, 2}) = std::move(newBranch); + QCOMPARE(adapter.rowCount({0, 2}), 3); QCOMPARE(dataChangedSpy.count(), 1); newChild->setNumber(1); QCOMPARE(dataChangedSpy.count(), 2); @@ -2698,9 +2733,11 @@ void tst_QRangeModelAdapter::insertAutoConnectObjects() QCOMPARE(dataChangedSpy.count(), 1); dataChangedSpy.clear(); - // newGrandChild = new Object; - // adapter.at({0, 2, 0}, 0) = newGrandChild; - // newGrandChild->setString("0.2.0"); + newGrandChild = new Object; + adapter.at({0, 2, 0}, 0) = newGrandChild; + QCOMPARE(dataChangedSpy.count(), 1); + newGrandChild->setString("0.2.0"); + QCOMPARE(dataChangedSpy.count(), 2); } QTEST_MAIN(tst_QRangeModelAdapter) diff --git a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp index 696bcdc07d7..0fc7538c515 100644 --- a/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp +++ b/tests/auto/corelib/kernel/qmetaobject/tst_qmetaobject.cpp @@ -291,6 +291,8 @@ class tst_QMetaObject : public QObject Q_PROPERTY(int value8 READ value8) Q_PROPERTY(int value9 READ value9 CONSTANT) Q_PROPERTY(int value10 READ value10 FINAL) + Q_PROPERTY(int value11 READ value10 VIRTUAL) + Q_PROPERTY(int value12 READ value10 OVERRIDE) public: enum EnumType { EnumType1 }; @@ -358,6 +360,8 @@ private slots: void propertyNotify(); void propertyConstant(); void propertyFinal(); + void propertyVirtual(); + void propertyOverride(); void metaType(); @@ -2727,6 +2731,32 @@ void tst_QMetaObject::propertyFinal() QVERIFY(!prop.isFinal()); } +void tst_QMetaObject::propertyVirtual() +{ + const QMetaObject *mo = metaObject(); + + QMetaProperty prop = mo->property(mo->indexOfProperty("value11")); + QVERIFY(prop.isValid()); + QVERIFY(prop.isVirtual()); + + prop = mo->property(mo->indexOfProperty("value9")); + QVERIFY(prop.isValid()); + QVERIFY(!prop.isVirtual()); +} + +void tst_QMetaObject::propertyOverride() +{ + const QMetaObject *mo = metaObject(); + + QMetaProperty prop = mo->property(mo->indexOfProperty("value12")); + QVERIFY(prop.isValid()); + QVERIFY(prop.isOverride()); + + prop = mo->property(mo->indexOfProperty("value9")); + QVERIFY(prop.isValid()); + QVERIFY(!prop.isOverride()); +} + void tst_QMetaObject::metaType() { QCOMPARE(QObject::staticMetaObject.metaType(), QMetaType::fromType<QObject>()); diff --git a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp index 67643606fa3..a441ed8f7ee 100644 --- a/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp +++ b/tests/auto/corelib/kernel/qmetaobjectbuilder/tst_qmetaobjectbuilder.cpp @@ -77,6 +77,9 @@ class SomethingOfEverything : public QObject Q_PROPERTY(SomethingEnum eprop READ eprop) Q_PROPERTY(SomethingFlagEnum fprop READ fprop) Q_PROPERTY(QLocale::Language language READ language) + Q_PROPERTY(QString virtualP READ prop VIRTUAL) + // Doesn't override anything, used only to verify MOC handling of OVERRIDE keyword + Q_PROPERTY(QString overrideP READ prop OVERRIDE) public: Q_INVOKABLE SomethingOfEverything() {} ~SomethingOfEverything() {} @@ -577,6 +580,8 @@ void tst_QMetaObjectBuilder::property() QVERIFY(!nullProp.isEnumOrFlag()); QVERIFY(!nullProp.isConstant()); QVERIFY(!nullProp.isFinal()); + QVERIFY(!nullProp.isVirtual()); + QVERIFY(!nullProp.isOverride()); QCOMPARE(nullProp.index(), 0); QCOMPARE(nullProp.revision(), 0); @@ -596,6 +601,8 @@ void tst_QMetaObjectBuilder::property() QVERIFY(!prop1.isEnumOrFlag()); QVERIFY(!prop1.isConstant()); QVERIFY(!prop1.isFinal()); + QVERIFY(!prop1.isVirtual()); + QVERIFY(!prop1.isOverride()); QCOMPARE(prop1.revision(), 0); QCOMPARE(prop1.index(), 0); QCOMPARE(builder.propertyCount(), 1); @@ -616,6 +623,8 @@ void tst_QMetaObjectBuilder::property() QVERIFY(!prop2.isEnumOrFlag()); QVERIFY(!prop2.isConstant()); QVERIFY(!prop2.isFinal()); + QVERIFY(!prop2.isVirtual()); + QVERIFY(!prop2.isOverride()); QCOMPARE(prop2.revision(), 0); QCOMPARE(prop2.index(), 1); QCOMPARE(builder.propertyCount(), 2); @@ -669,6 +678,8 @@ void tst_QMetaObjectBuilder::property() QVERIFY(!prop2.isEnumOrFlag()); QVERIFY(!prop2.isConstant()); QVERIFY(!prop2.isFinal()); + QVERIFY(!prop2.isVirtual()); + QVERIFY(!prop2.isOverride()); QCOMPARE(prop2.revision(), 0); // Remove prop1 and check that prop2 becomes index 0. @@ -686,6 +697,8 @@ void tst_QMetaObjectBuilder::property() QVERIFY(!prop2.isEnumOrFlag()); QVERIFY(!prop2.isConstant()); QVERIFY(!prop2.isFinal()); + QVERIFY(!prop2.isVirtual()); + QVERIFY(!prop2.isOverride()); QCOMPARE(prop2.revision(), 0); QCOMPARE(prop2.index(), 0); @@ -711,6 +724,8 @@ void tst_QMetaObjectBuilder::property() prop2.setEnumOrFlag(false); \ prop2.setConstant(false); \ prop2.setFinal(false); \ + prop2.setVirtual(false); \ + prop2.setOverride(false); \ prop2.setBindable(false); \ prop2.setRequired(false); \ } while (0) @@ -727,6 +742,8 @@ void tst_QMetaObjectBuilder::property() prop2.setEnumOrFlag(true); \ prop2.setConstant(true); \ prop2.setFinal(true); \ + prop2.setVirtual(true); \ + prop2.setOverride(true); \ prop2.setBindable(true); \ prop2.setRequired(true); \ } while (0) @@ -742,6 +759,8 @@ void tst_QMetaObjectBuilder::property() (prop2.isEnumOrFlag() ? 1 : 0) + \ (prop2.isConstant() ? 1 : 0) + \ (prop2.isFinal() ? 1 : 0) + \ + (prop2.isVirtual() ? 1 : 0) + \ + (prop2.isOverride() ? 1 : 0) + \ (prop2.isBindable() ? 1 : 0) + \ (prop2.isRequired() ? 1 : 0)) #define CHECK_FLAG(setFunc,isFunc) \ @@ -766,6 +785,8 @@ void tst_QMetaObjectBuilder::property() CHECK_FLAG(setConstant, isConstant); CHECK_FLAG(setBindable, isBindable); CHECK_FLAG(setFinal, isFinal); + CHECK_FLAG(setVirtual, isVirtual); + CHECK_FLAG(setOverride, isOverride); CHECK_FLAG(setRequired, isRequired); SET_ALL_FLAGS(); QCOMPARE(COUNT_FLAGS(), flagCounter); @@ -782,6 +803,22 @@ void tst_QMetaObjectBuilder::property() QCOMPARE(prototypeProp.notifySignal().signature(), QByteArray("propChanged(QString)")); QCOMPARE(builder.methodCount(), 1); QCOMPARE(builder.method(0).signature(), QByteArray("propChanged(QString)")); + + // virt specifiers + { //Q_PROPERTY(int virtualP READ prop VIRTUAL) + QMetaProperty prototype = SomethingOfEverything::staticMetaObject.property(7); + QMetaPropertyBuilder prototypeProp = builder.addProperty(prototype); + QCOMPARE(prototypeProp.isVirtual(), true); + QCOMPARE(prototypeProp.isOverride(), false); + QCOMPARE(prototypeProp.isFinal(), false); + } + { // Q_PROPERTY(int overrideP READ prop OVERRIDE) + QMetaProperty prototype = SomethingOfEverything::staticMetaObject.property(8); + QMetaPropertyBuilder prototypeProp = builder.addProperty(prototype); + QCOMPARE(prototypeProp.isVirtual(), false); + QCOMPARE(prototypeProp.isOverride(), true); + QCOMPARE(prototypeProp.isFinal(), false); + } } void tst_QMetaObjectBuilder::variantProperty() diff --git a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp index 2fcfd056882..a30ed901fe6 100644 --- a/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp +++ b/tests/auto/corelib/kernel/qvariant/tst_qvariant.cpp @@ -6,6 +6,8 @@ #include <qvariant.h> #include <QtCore/qttypetraits.h> +#include <QtCore/qsequentialiterable.h> +#include <QtCore/qassociativeiterable.h> // don't assume <type_traits> template <typename T, typename U> @@ -5025,7 +5027,7 @@ void sortIterable(QMetaSequence::Iterable *iterable) } template<typename Container> -static void testSequentialIteration() +static void testMetaSequenceIteration() { QFETCH(bool, hasSizeAccessor); QFETCH(bool, hasIndexedAccessors); @@ -5157,7 +5159,7 @@ static void testSequentialIteration() } template<typename Container> -static void testAssociativeIteration() +static void testMetaAssociationIteration() { using Key = typename Container::key_type; using Mapped = typename Container::mapped_type; @@ -5228,12 +5230,268 @@ static void testAssociativeIteration() QCOMPARE(f, iter.constEnd()); } +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) && QT_DEPRECATED_SINCE(6, 15) +QT_WARNING_PUSH +QT_WARNING_DISABLE_DEPRECATED + +template<typename Iterator> +void sortIterable(QSequentialIterable *iterable) +{ + std::sort(Iterator(iterable->mutableBegin()), Iterator(iterable->mutableEnd()), + [&](const QVariant &a, const QVariant &b) { + return a.toInt() < b.toInt(); + }); +} + +template<typename Container> +static void testSequentialIteration() +{ + QFETCH(bool, hasSizeAccessor); + const auto ignoreSizeWarning = [hasSizeAccessor]() { + if (hasSizeAccessor) + return; + QTest::ignoreMessage( + QtWarningMsg, + "size() called on an iterable without native size accessor. This is slow"); + }; + QTest::failOnWarning(); + + int numSeen = 0; + Container sequence; + ContainerAPI<Container>::insert(sequence, 1); + ContainerAPI<Container>::insert(sequence, 2); + ContainerAPI<Container>::insert(sequence, 3); + + QVariant listVariant = QVariant::fromValue(sequence); + QVERIFY(listVariant.canConvert<QVariantList>()); + QVariantList varList = listVariant.value<QVariantList>(); + ignoreSizeWarning(); + QCOMPARE(varList.size(), (int)std::distance(sequence.begin(), sequence.end())); + QSequentialIterable listIter = listVariant.view<QSequentialIterable>(); + ignoreSizeWarning(); + QCOMPARE(varList.size(), listIter.size()); + + typename Container::iterator containerIter = sequence.begin(); + const typename Container::iterator containerEnd = sequence.end(); + ignoreSizeWarning(); + for (int i = 0, end = listIter.size(); i < end; ++i, ++containerIter, ++numSeen) + { + QVERIFY(ContainerAPI<Container >::compare(listIter.at(i), *containerIter)); + QVERIFY(ContainerAPI<Container >::compare(listIter.at(i), varList.at(i))); + } + QCOMPARE(numSeen, (int)std::distance(sequence.begin(), sequence.end())); + QCOMPARE(containerIter, containerEnd); + + numSeen = 0; + containerIter = sequence.begin(); + for (QVariant v : listIter) { + QVERIFY(ContainerAPI<Container>::compare(v, *containerIter)); + QVERIFY(ContainerAPI<Container>::compare(v, varList.at(numSeen))); + ++containerIter; + ++numSeen; + } + QCOMPARE(numSeen, (int)std::distance(sequence.begin(), sequence.end())); + + auto compareLists = [&]() { + int numSeen = 0; + auto varList = listVariant.value<QVariantList>(); + auto varIter = varList.begin(); + for (const QVariant &v : std::as_const(listIter)) { + QVERIFY(ContainerAPI<Container>::compare(v, *varIter)); + ++varIter; + ++numSeen; + } + QCOMPARE(varIter, varList.end()); + numSeen = 0; + auto constVarIter = varList.constBegin(); + for (QVariant v : listIter) { + QVERIFY(ContainerAPI<Container>::compare(v, *constVarIter)); + ++constVarIter; + ++numSeen; + } + QCOMPARE(numSeen, (int)std::distance(varList.begin(), varList.end())); + }; + compareLists(); + + QVariant first = listIter.at(0); + QVariant second = listIter.at(1); + QVariant third = listIter.at(2); + compareLists(); + listIter.addValue(third); + compareLists(); + listIter.addValue(second); + compareLists(); + listIter.addValue(first); + compareLists(); + + QCOMPARE(listIter.size(), 6); + + if (listIter.canRandomAccessIterate()) + sortIterable<QSequentialIterable::RandomAccessIterator>(&listIter); + else if (listIter.canReverseIterate()) + sortIterable<QSequentialIterable::BidirectionalIterator>(&listIter); + else if (listIter.canForwardIterate()) + return; // std::sort cannot sort with only forward iterators. + else + QFAIL("The container has no meaningful iterators"); + + compareLists(); + ignoreSizeWarning(); + QCOMPARE(listIter.size(), 6); + QCOMPARE(listIter.at(0), first); + QCOMPARE(listIter.at(1), first); + QCOMPARE(listIter.at(2), second); + QCOMPARE(listIter.at(3), second); + QCOMPARE(listIter.at(4), third); + QCOMPARE(listIter.at(5), third); + + if (listIter.metaContainer().canRemoveValue()) { + listIter.removeValue(); + compareLists(); + ignoreSizeWarning(); + QCOMPARE(listIter.size(), 5); + QCOMPARE(listIter.at(0), first); + QCOMPARE(listIter.at(1), first); + QCOMPARE(listIter.at(2), second); + QCOMPARE(listIter.at(3), second); + QCOMPARE(listIter.at(4), third); + } else { + // QString and QByteArray have no pop_back or pop_front and it's unclear what other + // method we should use to remove an item. + QVERIFY((std::is_same_v<Container, QString> || std::is_same_v<Container, QByteArray>)); + } + + auto i = listIter.mutableBegin(); + QVERIFY(i != listIter.mutableEnd()); + + *i = QStringLiteral("17"); + if (listIter.metaContainer().valueMetaType() == QMetaType::fromType<int>()) + QCOMPARE(listIter.at(0).toInt(), 17); + else if (listIter.metaContainer().valueMetaType() == QMetaType::fromType<bool>()) + QCOMPARE(listIter.at(0).toBool(), false); + + *i = QStringLiteral("true"); + if (listIter.metaContainer().valueMetaType() == QMetaType::fromType<int>()) + QCOMPARE(listIter.at(0).toInt(), 0); + else if (listIter.metaContainer().valueMetaType() == QMetaType::fromType<bool>()) + QCOMPARE(listIter.at(0).toBool(), true); +} + +template<typename Container> +static void testAssociativeIteration() +{ + using Key = typename Container::key_type; + using Mapped = typename Container::mapped_type; + + int numSeen = 0; + Container mapping; + mapping[5] = true; + mapping[15] = false; + + QVariant mappingVariant = QVariant::fromValue(mapping); + QVariantMap varMap = mappingVariant.value<QVariantMap>(); + QVariantMap varHash = mappingVariant.value<QVariantMap>(); + QAssociativeIterable mappingIter = mappingVariant.view<QAssociativeIterable>(); + + typename Container::const_iterator containerIter = mapping.begin(); + const typename Container::const_iterator containerEnd = mapping.end(); + for ( ;containerIter != containerEnd; ++containerIter, ++numSeen) + { + Mapped expected = KeyGetter<Container>::value(containerIter); + Key key = KeyGetter<Container>::get(containerIter); + Mapped actual = qvariant_cast<Mapped>(mappingIter.value(key)); + QCOMPARE(qvariant_cast<Mapped>(varMap.value(QString::number(key))), expected); + QCOMPARE(qvariant_cast<Mapped>(varHash.value(QString::number(key))), expected); + QCOMPARE(actual, expected); + const QAssociativeIterable::const_iterator it = mappingIter.find(key); + QVERIFY(it != mappingIter.end()); + QCOMPARE(it.value().value<Mapped>(), expected); + } + QCOMPARE(numSeen, (int)std::distance(mapping.begin(), mapping.end())); + QCOMPARE(containerIter, containerEnd); + QVERIFY(mappingIter.find(10) == mappingIter.end()); + + auto i = mappingIter.mutableFind(QStringLiteral("nonono")); + QCOMPARE(i, mappingIter.mutableEnd()); + i = mappingIter.mutableFind(QStringLiteral("5")); + QVERIFY(i != mappingIter.mutableEnd()); + + *i = QStringLiteral("17"); + + if (mappingIter.metaContainer().mappedMetaType() == QMetaType::fromType<int>()) + QCOMPARE(mappingIter.value(5).toInt(), 17); + else if (mappingIter.metaContainer().mappedMetaType() == QMetaType::fromType<bool>()) + QCOMPARE(mappingIter.value(5).toBool(), true); + + *i = QStringLiteral("true"); + if (mappingIter.metaContainer().mappedMetaType() == QMetaType::fromType<int>()) + QCOMPARE(mappingIter.value(5).toInt(), 0); + else if (mappingIter.metaContainer().mappedMetaType() == QMetaType::fromType<bool>()) + QCOMPARE(mappingIter.value(5).toBool(), true); + + QVERIFY(mappingIter.containsKey("5")); + mappingIter.removeKey(QStringLiteral("5")); + QCOMPARE(mappingIter.find(5), mappingIter.end()); + + mappingIter.setValue(5, 44); + if (mappingIter.metaContainer().mappedMetaType() == QMetaType::fromType<int>()) + QCOMPARE(mappingIter.value(5).toInt(), 44); + else if (mappingIter.metaContainer().mappedMetaType() == QMetaType::fromType<bool>()) + QCOMPARE(mappingIter.value(5).toBool(), true); + + // Test that find() does not coerce + auto container = Container(); + container[0] = true; + + QVariant containerVariant = QVariant::fromValue(container); + QAssociativeIterable iter = containerVariant.value<QAssociativeIterable>(); + auto f = iter.constFind(QStringLiteral("anything")); + QCOMPARE(f, iter.constEnd()); +} + +template<typename T> +static void addRowSequential(const char *name, bool hasSizeAccessor, bool hasIndexedAccessor) +{ + QTest::newRow(name) + << &testMetaSequenceIteration<T> << hasSizeAccessor << hasIndexedAccessor; + QTest::addRow("%s_old", name) + << &testSequentialIteration<T> << hasSizeAccessor << hasIndexedAccessor; +} + +template<typename C> +static void addRowAssociative(const char *name) +{ + QTest::newRow(name) + << &testMetaAssociationIteration<C>; + QTest::addRow("%s_old", name) + << &testAssociativeIteration<C>; +} + +QT_WARNING_POP +#else + +template<typename T> +static void addRowSequential(const char *name, bool hasSizeAccessor, bool hasIndexedAccessor) +{ + QTest::newRow(name) + << &testMetaSequenceIteration<T> << hasSizeAccessor << hasIndexedAccessor; +} + +template<typename C> +static void addRowAssociative(const char *name) +{ + QTest::newRow(name) + << &testMetaAssociationIteration<C>; +} + +#endif // QT_VERSION < QT_VERSION_CHECK(7, 0, 0) && QT_DEPRECATED_SINCE(6, 15) + void tst_QVariant::iterateSequentialContainerElements_data() { QTest::addColumn<QFunctionPointer>("testFunction"); QTest::addColumn<bool>("hasSizeAccessor"); QTest::addColumn<bool>("hasIndexedAccessors"); -#define ADD(T) QTest::newRow(#T) << &testSequentialIteration<T> << true << true +#define ADD(T) addRowSequential<T>(#T, true, true) ADD(QQueue<int>); ADD(QQueue<QVariant>); ADD(QQueue<QString>); @@ -5253,13 +5511,13 @@ void tst_QVariant::iterateSequentialContainerElements_data() ADD(QByteArray); #undef ADD -#define ADD(T) QTest::newRow(#T) << &testSequentialIteration<T> << true << false +#define ADD(T) addRowSequential<T>(#T, true, false) ADD(std::list<int>); ADD(std::list<QVariant>); ADD(std::list<QString>); #undef ADD -#define ADD(T) QTest::newRow(#T) << &testSequentialIteration<T> << false << false +#define ADD(T) addRowSequential<T>(#T, false, false) #ifdef TEST_FORWARD_LIST ADD(std::forward_list<int>); ADD(std::forward_list<QVariant>); @@ -5271,7 +5529,7 @@ void tst_QVariant::iterateSequentialContainerElements_data() void tst_QVariant::iterateAssociativeContainerElements_data() { QTest::addColumn<QFunctionPointer>("testFunction"); -#define ADD(C, K, V) QTest::newRow(#C #K #V) << &testAssociativeIteration<C<K, V>>; +#define ADD(C, K, V) addRowAssociative<C<K, V>>(#C "<" #K "," #V ">"); ADD(QHash, int, bool); ADD(QHash, int, int); ADD(QMap, int, bool); @@ -6448,15 +6706,15 @@ void tst_QVariant::get_NonDefaultConstructible() struct QVariantWrapper { public: - static constexpr bool canNoexceptConvertToQVariant + static constexpr bool CanNoexceptConvertToQVariant = std::is_nothrow_copy_constructible_v<QVariant>; - static constexpr bool canNoexceptAssignQVariant + static constexpr bool CanNoexceptAssignQVariant = std::is_nothrow_copy_assignable_v<QVariant>; QVariantWrapper(QVariant *content = nullptr) noexcept : m_content(content) {} - QVariant content() const noexcept(canNoexceptConvertToQVariant) { return *m_content; } - void setContent(const QVariant &content) noexcept(canNoexceptAssignQVariant) + QVariant content() const noexcept(CanNoexceptConvertToQVariant) { return *m_content; } + void setContent(const QVariant &content) noexcept(CanNoexceptAssignQVariant) { *m_content = content; } @@ -6468,14 +6726,14 @@ private: QT_BEGIN_NAMESPACE template<> QVariant::ConstReference<QVariantWrapper>::operator QVariant() const - noexcept(QVariantWrapper::canNoexceptConvertToQVariant) + noexcept(QVariantWrapper::CanNoexceptConvertToQVariant) { return m_referred.content(); } template<> QVariant::Reference<QVariantWrapper> &QVariant::Reference<QVariantWrapper>::operator=( - const QVariant &content) noexcept(QVariantWrapper::canNoexceptAssignQVariant) + const QVariant &content) noexcept(QVariantWrapper::CanNoexceptAssignQVariant) { m_referred.setContent(content); return *this; diff --git a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp index 2d9d351286f..f1f88891015 100644 --- a/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp +++ b/tests/auto/corelib/plugin/qpluginloader/tst_qpluginloader.cpp @@ -63,11 +63,11 @@ using namespace Qt::StringLiterals; #elif defined(Q_OS_WIN) # undef dll_VALID # define dll_VALID true -//# ifdef QT_NO_DEBUG +# if !defined(QT_NO_DEBUG) && defined(Q_CC_MSVC) +# define SUFFIX "d.dll" +# else # define SUFFIX ".dll" -//# else -//# define SUFFIX "d.dll" -//# endif +# endif # define PREFIX "" #else // all other Unix diff --git a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp index 806b6b43161..f7d98eb15e2 100644 --- a/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp +++ b/tests/auto/corelib/thread/qfuture/tst_qfuture.cpp @@ -5656,6 +5656,12 @@ void tst_QFuture::cancelChainWithContext() int onCancelCnt = 0; bool unexpectedThread = false; + // For in-other-thread case we need a semaphore to make sure that + // the needed continuation is executed. + // Fot the in-main-thread case it's guaranteed by the unwrap() + // implementation. + QSemaphore sem; + auto f = p1.future() .then(context, [&]() { if (QThread::currentThread() != thread) @@ -5666,6 +5672,8 @@ void tst_QFuture::cancelChainWithContext() if (QThread::currentThread() != thread) unexpectedThread = true; ++thenCnt; + if (inOtherThread) + sem.release(); return f; }).unwrap() .then(context, [&]{ @@ -5685,6 +5693,8 @@ void tst_QFuture::cancelChainWithContext() }); p1.finish(); + if (inOtherThread) + sem.acquire(); f.cancelChain(); p2.finish(); f.waitForFinished(); @@ -5737,6 +5747,75 @@ void tst_QFuture::cancelChainWithContext() QCOMPARE_EQ(thenCnt, 4); QCOMPARE_EQ(onCancelCnt, 0); } + // cancelling propagates through unwrap() + { + QPromise<void> p1, p2; + p1.start(); + p2.start(); + + int thenCnt = 0; + int onCancelCnt = 0; + bool unexpectedThread = false; + + // For in-other-thread case we need a semaphore to make sure that + // the first continuation is executed. + // Fot the in-main-thread case it's guaranteed by the unwrap() + // implementation. + QSemaphore sem; + + auto f = p1.future() + .then(context, [&, f2 = p2.future()]() { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++thenCnt; + if (inOtherThread) + sem.release(); + return f2; + }).unwrap() + .onCanceled(context, [&] { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++onCancelCnt; + }) + .then([&]() { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++thenCnt; + return QtFuture::makeReadyVoidFuture(); + }).unwrap() + .onCanceled([&] { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++onCancelCnt; + }) + .then(context, [&]{ + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++thenCnt; + return QtFuture::makeReadyVoidFuture(); + }).unwrap() + .onCanceled([&] { + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++onCancelCnt; + }) + .then(context, [&]{ + if (QThread::currentThread() != thread) + unexpectedThread = true; + ++thenCnt; + }); + + p1.finish(); + if (inOtherThread) + sem.acquire(); + f.cancelChain(); + p2.finish(); + f.waitForFinished(); + + QVERIFY(!unexpectedThread); + QCOMPARE_EQ(thenCnt, 1); + QCOMPARE_EQ(onCancelCnt, 3); + } } void tst_QFuture::cancelChainOnAnOverwrittenFuture() diff --git a/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp b/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp index 86dfa5faffc..4c089091f8d 100644 --- a/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp +++ b/tests/auto/corelib/thread/qreadwritelock/tst_qreadwritelock.cpp @@ -57,6 +57,7 @@ private slots: void multipleReadersLoop(); void multipleWritersLoop(); void multipleReadersWritersLoop(); + void heavyLoadLocks(); void countingTest(); void limitedReaders(); void deleteOnUnlock(); @@ -603,6 +604,111 @@ public: } }; +class HeavyLoadLockThread : public QThread +{ +public: + QReadWriteLock &testRwlock; + const qsizetype iterations; + const int numThreads; + inline HeavyLoadLockThread(QReadWriteLock &l, qsizetype iters, int numThreads, QVector<QAtomicInt *> &counters): + testRwlock(l), + iterations(iters), + numThreads(numThreads), + counters(counters) + { } + +private: + QVector<QAtomicInt *> &counters; + QAtomicInt *getCounter(qsizetype index) + { + QReadLocker locker(&testRwlock); + /* + The index is increased monotonically, so the index + being requested should be always within or at the end of the + counters vector. + */ + Q_ASSERT(index <= counters.size()); + if (counters.size() <= index || counters[index] == nullptr) { + locker.unlock(); + QWriteLocker wlocker(&testRwlock); + if (counters.size() <= index) + counters.resize(index + 1, nullptr); + if (counters[index] == nullptr) + counters[index] = new QAtomicInt(0); + return counters[index]; + } + return counters[index]; + } + void releaseCounter(qsizetype index) + { + QWriteLocker locker(&testRwlock); + delete counters[index]; + counters[index] = nullptr; + } + +public: + void run() override + { + for (qsizetype i = 0; i < iterations; ++i) { + QAtomicInt *counter = getCounter(i); + /* + Here each counter is accessed by each thread + and increaed only once. As a result, when the + counter reaches numThreads, i.e. the fetched + value before the increment is numThreads-1, + we know all threads have accessed this counter + and we can delete it safely. + */ + int prev = counter->fetchAndAddRelaxed(1); + if (prev == numThreads - 1) { +#ifdef QT_BUILDING_UNDER_TSAN + /* + Under TSAN, deleting and freeing an object + will trigger a write operation on the memory + of the object. Since we used fetchAndAddRelaxed + to update the counter, TSAN will report a data + race when deleting the counter here. To avoid + the false positive, we simply reset the counter + to 0 here, with ordered semantics to establish + the sequence to ensure the the free-ing option + happens after all fetchAndAddRelaxed operations + in other threads. + + When not building under TSAN, deleting the counter + will not result in any data read or written to the + memory region of the counter, so no data race will + happen. + */ + counter->fetchAndStoreOrdered(0); +#endif + releaseCounter(i); + } + } + } +}; + +/* + Multiple threads racing acquiring and releasing + locks on the same indices. +*/ + +void tst_QReadWriteLock::heavyLoadLocks() +{ + constexpr qsizetype iterations = 65536 * 4; + constexpr int numThreads = 8; + QVector<QAtomicInt *> counters; + QReadWriteLock testLock; + std::array<std::unique_ptr<HeavyLoadLockThread>, numThreads> threads; + for (auto &thread : threads) + thread = std::make_unique<HeavyLoadLockThread>(testLock, iterations, numThreads, counters); + for (auto &thread : threads) + thread->start(); + for (auto &thread : threads) + thread->wait(); + QVERIFY(counters.size() == iterations); + for (qsizetype i = 0; i < iterations; ++i) + QVERIFY(counters[i] == nullptr); +} /* A writer acquires a read-lock, a reader locks diff --git a/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp b/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp index 3272ffac0ee..18d8b604dff 100644 --- a/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp +++ b/tests/auto/gui/math3d/qvectornd/tst_qvectornd.cpp @@ -4,6 +4,9 @@ #include <QVector2D> #include <QVector3D> #include <QVector4D> + +#include <QtCore/qdatastream.h> + #ifdef QVARIANT_H # error "This test requires qvector{2,3,4}d.h to not include qvariant.h" #endif @@ -162,6 +165,8 @@ private slots: void metaTypes(); void structuredBinding(); + void nonFiniteValuesStreamingRoundTrip_data(); + void nonFiniteValuesStreamingRoundTrip(); }; // Test the creation of QVector2D objects in various ways: @@ -2759,6 +2764,78 @@ void tst_QVectorND::structuredBinding() } } +void tst_QVectorND::nonFiniteValuesStreamingRoundTrip_data() +{ + QTest::addColumn<float>("value"); + + constexpr auto inf = std::numeric_limits<float>::infinity(); + constexpr auto NaN = std::numeric_limits<float>::quiet_NaN(); + + QTest::addRow("+∞") << +inf; + QTest::addRow("-∞") << -inf; + QTest::addRow("NaN") << NaN; + +} + +void tst_QVectorND::nonFiniteValuesStreamingRoundTrip() +{ + QFETCH(const float, value); + + const QVector2D i2{value, value}; + const QVector3D i3{value, value, value}; + const QVector4D i4{value, value, value, value}; + + QByteArray buffer; + + { + QDataStream s(&buffer, QIODevice::WriteOnly); + s << i2 << i3 << i4; + QCOMPARE(s.status(), QDataStream::Status::Ok); + } + + { + QVector2D o2 = {0, 0}; + QVector3D o3 = {1, 0, -1}; + QVector4D o4 = {0, 1, 2, 3}; + + QDataStream s(&buffer, QIODevice::ReadOnly); + s >> o2; + QCOMPARE(s.status(), QDataStream::Status::Ok); + s >> o3; + QCOMPARE(s.status(), QDataStream::Status::Ok); + s >> o4; + QCOMPARE(s.status(), QDataStream::Status::Ok); + + constexpr auto convert_to_binary = [](float v) { + uint r; + static_assert(sizeof v == sizeof r); + memcpy(&r, &v, sizeof v); + return r; + }; + + #define CHECK(n, what) \ + do { \ + const auto i ## n ## what = convert_to_binary(i ## n . what ()); \ + const auto o ## n ## what = convert_to_binary(o ## n . what ()); \ + QCOMPARE(i ## n ## what, o ## n ## what); \ + } while (false) + + CHECK(2, x); + CHECK(2, y); + + CHECK(3, x); + CHECK(3, y); + CHECK(3, z); + + CHECK(4, x); + CHECK(4, y); + CHECK(4, z); + CHECK(4, w); + + #undef CHECK + } +} + QTEST_APPLESS_MAIN(tst_QVectorND) #include "tst_qvectornd.moc" diff --git a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp index 305f48c95ee..4fbaed0d7b3 100644 --- a/tests/auto/other/qaccessibility/tst_qaccessibility.cpp +++ b/tests/auto/other/qaccessibility/tst_qaccessibility.cpp @@ -2988,6 +2988,10 @@ void tst_QAccessibility::listTest() model->appendRow({new QStandardItem("Norway"), new QStandardItem("Oslo"), new QStandardItem("NOK")}); model->appendRow({new QStandardItem("Germany"), new QStandardItem("Berlin"), new QStandardItem("EUR")}); model->appendRow({new QStandardItem("Australia"), new QStandardItem("Brisbane"), new QStandardItem("AUD")}); + model->item(0, 1)->setCheckable(true); + model->item(1, 1)->setCheckable(true); + model->item(2, 1)->setCheckable(true); + model->item(2, 1)->setCheckState(Qt::Checked); auto lvHolder = std::make_unique<QListView>(); auto listView = lvHolder.get(); listView->setModel(model); @@ -3016,16 +3020,19 @@ void tst_QAccessibility::listTest() QCOMPARE(iface->indexOfChild(child1), 0); QCOMPARE(child1->text(QAccessible::Name), QString("Oslo")); QCOMPARE(child1->role(), QAccessible::ListItem); + QVERIFY(child1->state().checkable); QAccessibleInterface *child2 = iface->child(1); QVERIFY(child2); QCOMPARE(iface->indexOfChild(child2), 1); QCOMPARE(child2->text(QAccessible::Name), QString("Berlin")); + QVERIFY(child2->state().checkable); QAccessibleInterface *child3 = iface->child(2); QVERIFY(child3); QCOMPARE(iface->indexOfChild(child3), 2); QCOMPARE(child3->text(QAccessible::Name), QString("Brisbane")); + QVERIFY(child3->state().checkable); } // Check that application is accessible parent, since it's a top-level widget @@ -3044,9 +3051,21 @@ void tst_QAccessibility::listTest() // skip focus event tests on platforms where window focus cannot be ensured const bool checkFocus = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation); if (checkFocus) { + QVERIFY(QTest::qWaitForWindowActive(listView)); + QAccessibleEvent focusEvent(listView, QAccessible::Focus); focusEvent.setChild(1); QVERIFY(QTestAccessibility::containsEvent(&focusEvent)); + + // check the item + QVERIFY(!iface->child(1)->state().checked); + QTest::keyClick(listView, Qt::Key_Space); + QVERIFY(iface->child(1)->state().checked); + QAccessible::State s; + s.checked = true; + QAccessibleStateChangeEvent checkedStateChangedEvent(listView, s); + checkedStateChangedEvent.setChild(1); + QVERIFY(QTestAccessibility::containsEvent(&checkedStateChangedEvent)); } QTest::mouseClick(listView->viewport(), Qt::LeftButton, { }, listView->visualRect(model->index(2, listView->modelColumn())).center()); @@ -3058,6 +3077,16 @@ void tst_QAccessibility::listTest() QAccessibleEvent focusEvent2(listView, QAccessible::Focus); focusEvent2.setChild(2); QVERIFY(QTestAccessibility::containsEvent(&focusEvent2)); + + // uncheck the item + QVERIFY(iface->child(2)->state().checked); + QTest::keyClick(listView, Qt::Key_Space); + QVERIFY(!iface->child(2)->state().checked); + QAccessible::State s; + s.checked = true; + QAccessibleStateChangeEvent checkedStateChangedEvent(listView, s); + checkedStateChangedEvent.setChild(2); + QVERIFY(QTestAccessibility::containsEvent(&checkedStateChangedEvent)); } QAccessibleTableInterface *table = iface->tableInterface(); @@ -3174,6 +3203,7 @@ void tst_QAccessibility::treeTest() QTreeWidgetItem *item1 = new QTreeWidgetItem; item1->setText(0, "Picasso"); item1->setText(1, "Guernica"); + item1->setCheckState(0, Qt::Unchecked); root1->addChild(item1); QTreeWidgetItem *item2 = new QTreeWidgetItem; @@ -3188,6 +3218,7 @@ void tst_QAccessibility::treeTest() QTreeWidgetItem *item3 = new QTreeWidgetItem; item3->setText(0, "Klimt"); item3->setText(1, "The Kiss"); + item3->setCheckState(0, Qt::Checked); root2->addChild(item3); treeView->resize(400,400); @@ -3291,6 +3322,40 @@ void tst_QAccessibility::treeTest() QCOMPARE(table2->columnDescription(0), QString("Artist")); QCOMPARE(table2->columnDescription(1), QString("Work")); + // skip accessible state change event tests on platforms where window focus cannot be ensured + if (QGuiApplicationPrivate::platformIntegration()->hasCapability( + QPlatformIntegration::WindowActivation)) { + QVERIFY(QTest::qWaitForWindowActive(treeView.get())); + + // check item1 (Picasso) + QVERIFY(cell1->state().checkable); + QVERIFY(!cell1->state().checked); + treeView->setCurrentItem(item1); + QTest::keyClick(treeView.get(), Qt::Key_Space); + QVERIFY(cell1->state().checked); + { + QAccessible::State s; + s.checked = true; + QAccessibleStateChangeEvent checkedStateChangedEvent(treeView.get(), s); + checkedStateChangedEvent.setChild(iface->indexOfChild(cell1)); + QVERIFY(QTestAccessibility::containsEvent(&checkedStateChangedEvent)); + } + + // uncheck item3 (Klimt) + QVERIFY(cell2->state().checkable); + QVERIFY(cell2->state().checked); + treeView->setCurrentItem(item3); + QTest::keyClick(treeView.get(), Qt::Key_Space); + QVERIFY(!cell2->state().checked); + { + QAccessible::State s; + s.checked = true; + QAccessibleStateChangeEvent checkedStateChangedEvent(treeView.get(), s); + checkedStateChangedEvent.setChild(iface->indexOfChild(cell2)); + QVERIFY(QTestAccessibility::containsEvent(&checkedStateChangedEvent)); + } + } + QTestAccessibility::clearEvents(); } @@ -3315,6 +3380,7 @@ void tst_QAccessibility::tableTest() for (int i = 0; i<9; ++i) { QTableWidgetItem *item = new QTableWidgetItem; item->setText(QString::number(i/3) + QString(".") + QString::number(i%3)); + item->setCheckState(Qt::Unchecked); tableView->setItem(i/3, i%3, item); } @@ -3582,6 +3648,56 @@ void tst_QAccessibility::tableTest() tableView->horizontalHeader()->setVisible(false); } + + // skip accessible state change event tests on platforms where window focus cannot be ensured + if (QGuiApplicationPrivate::platformIntegration()->hasCapability( + QPlatformIntegration::WindowActivation)) { + QVERIFY(QTest::qWaitForWindowActive(tableView)); + + // check cell (0, 0) + tableView->setCurrentItem(tableView->item(0, 0)); + QVERIFY(cell00->state().checkable); + QVERIFY(!cell00->state().checked); + QTest::keyClick(tableView, Qt::Key_Space); + QVERIFY(cell00->state().checked); + { + QAccessible::State s; + s.checked = true; + QAccessibleStateChangeEvent checkedStateChangedEvent(tableView, s); + checkedStateChangedEvent.setChild(iface->indexOfChild(cell00)); + QVERIFY(QTestAccessibility::containsEvent(&checkedStateChangedEvent)); + } + + QTestAccessibility::clearEvents(); + + // uncheck cell (0, 0) again + QTest::keyClick(tableView, Qt::Key_Space); + QVERIFY(!cell00->state().checked); + { + QAccessible::State s; + s.checked = true; + QAccessibleStateChangeEvent checkedStateChangedEvent(tableView, s); + checkedStateChangedEvent.setChild(iface->indexOfChild(cell00)); + QVERIFY(QTestAccessibility::containsEvent(&checkedStateChangedEvent)); + } + } + + { + QTestAccessibility::clearEvents(); + auto cell0 = table2->cellAt(0, 2); + auto cell1 = table2->cellAt(1, 2); + auto cell2 = table2->cellAt(2, 2); + auto cell3 = table2->cellAt(3, 2); + QAccessibleObjectDestroyedEvent event0(cell0); + QAccessibleObjectDestroyedEvent event1(cell1); + QAccessibleObjectDestroyedEvent event2(cell2); + QAccessibleObjectDestroyedEvent event3(cell3); + tableView->removeColumn(2); + QVERIFY_EVENT(&event0); + QVERIFY_EVENT(&event1); + QVERIFY_EVENT(&event2); + QVERIFY_EVENT(&event3); + } tvHolder.reset(); QVERIFY(!QAccessible::accessibleInterface(id00)); QTestAccessibility::clearEvents(); diff --git a/tests/auto/tools/moc/allmocs_baseline_in.json b/tests/auto/tools/moc/allmocs_baseline_in.json index d8e6c4df538..d36bdb907ae 100644 --- a/tests/auto/tools/moc/allmocs_baseline_in.json +++ b/tests/auto/tools/moc/allmocs_baseline_in.json @@ -23,6 +23,9 @@ ] } ], + "hashes": { + "BackslashNewlines": "0$VAkStqKr6pw5W81yZ3rCFn98h9U" + }, "inputFile": "backslash-newlines.h", "outputRevision": 69 }, @@ -41,6 +44,9 @@ ] } ], + "hashes": { + "IfdefedClass": "0$OochknExuVUAqq2zc5gt2Ldj4+4" + }, "inputFile": "c-comments.h", "outputRevision": 69 }, @@ -75,6 +81,9 @@ "qualifiedClassName": "CStyleEnums" } ], + "hashes": { + "CStyleEnums": "0$fqbHabuJUT79FJr/S/6WsAiFcJI" + }, "inputFile": "cstyle-enums.h", "outputRevision": 69 }, @@ -325,6 +334,11 @@ ] } ], + "hashes": { + "CXX11Enums": "0$SPzFG1raX8GCqiq6g21PoU/7ixs", + "CXX11Enums2": "0$uYFCI2+nT8TI8jLcKAvO8vp9OYA", + "CXX11Enums3": "0$bRezVo4UbDNuGatmLEd0/n8K8FA" + }, "inputFile": "cxx11-enums.h", "outputRevision": 69 }, @@ -727,6 +741,17 @@ ] } ], + "hashes": { + "ExplicitOverrideControlBase": "0$PFsRqLGh0wP5gfqwF9CVeUi9EVU", + "ExplicitOverrideControlFinalCxx11": "0$RQTN9wEJCVZ1N4IcieHs2LuRKY8", + "ExplicitOverrideControlFinalCxx11OverrideCxx11": "0$ZEBCUhi5oEGiDVdmoqD+x4n1KJk", + "ExplicitOverrideControlFinalQt": "0$GuS6YD8Kcf/9KyehmFsrmDWW42M", + "ExplicitOverrideControlFinalQtOverrideQt": "0$q48mSBftA2LJFVSB4uqTmKs2gOc", + "ExplicitOverrideControlOverrideCxx11": "0$he1BJyFAtIcrEf74bZY1d54aCbc", + "ExplicitOverrideControlOverrideQt": "0$dWQ+iDw9oKEYXiSgy2izIISyRMg", + "ExplicitOverrideControlSealed": "0$3nq5psg7nFNDlPnBpaYraCr3CHQ", + "ExplicitOverrideControlSealedOverride": "0$I7ySe3SgUSARroswRh74wJoZkkA" + }, "inputFile": "cxx11-explicit-override-control.h", "outputRevision": 69 }, @@ -850,6 +875,17 @@ ] } ], + "hashes": { + "ExportedFinalTestClassCpp11": "0$eUEIDQi+/lB9KOjB+oul4rZe9xE", + "ExportedFinalTestClassCpp11X": "0$g03sLKAEeX8BLjKjFsG3MMfunaY", + "ExportedFinalTestClassQt": "0$fYFeeRSmEnokVTW4eIiCU6PR5HM", + "ExportedFinalTestClassQtX": "0$7+xgvb70xVV3Xot9q7FhSm8mUew", + "ExportedSealedTestClass": "0$jzcARHlbTm1whxXOtg9edtN1ctg", + "ExportedSealedTestClassX": "0$tEmYiad6hqJMlGaCnNc2GhZtDgM", + "FinalTestClassCpp11": "0$Opb/8DnbfVjpH2CZvHo9Rv93iV8", + "FinalTestClassQt": "0$DtnHOKRNbQ5Y3QbO5AeLM4qYDW0", + "SealedTestClass": "0$VUU19XJgT+KhqRBlwQmXkd0yVgQ" + }, "inputFile": "cxx11-final-classes.h", "outputRevision": 69 }, @@ -937,6 +973,9 @@ ] } ], + "hashes": { + "CXX11TrailingReturn": "0$UrmUM8dix1r2pcj94cUzTALq0x8" + }, "inputFile": "cxx11-trailing-return.h", "outputRevision": 69 }, @@ -977,6 +1016,10 @@ "qualifiedClassName": "CXX17Namespace::A::B::C::D" } ], + "hashes": { + "CXX17Namespace::A::B::C::D": "0$WwEpzp6jKMGQfcvBrtsAJOy01KM", + "CXX17Namespace::A::B::C::D::ClassInNamespace": "0$/g09nq8R/tK4tC37pIgfRw/mBog" + }, "inputFile": "cxx17-namespaces.h", "outputRevision": 69 }, @@ -1007,6 +1050,9 @@ ] } ], + "hashes": { + "DirInIncludePath": "0$Yj0uFD5gTp9Ie67oMAyjAEUetbo" + }, "inputFile": "dir-in-include-path.h", "outputRevision": 69 }, @@ -1025,6 +1071,9 @@ ] } ], + "hashes": { + "Foo": "0$CB6VdqlszayDKadxVcYxPz1KzvI" + }, "inputFile": "enum_with_include.h", "outputRevision": 69 }, @@ -1057,6 +1106,9 @@ ] } ], + "hashes": { + "StringLiterals": "0$ZMhjewm0f9PVVN6Nf31Slot+IRo" + }, "inputFile": "escapes-in-string-literals.h", "outputRevision": 69 }, @@ -1295,6 +1347,9 @@ ] } ], + "hashes": { + "ForwardDeclaredParamClass": "0$1v+gvQyQz9IWyRMSBZJfmhZpCw4" + }, "inputFile": "forward-declared-param.h", "outputRevision": 69 }, @@ -1329,6 +1384,9 @@ ] } ], + "hashes": { + "FunctionWithAttributes": "0$PHwt9wN4wGfmeCWQSXJ4CFyLKCk" + }, "inputFile": "function-with-attributes.h", "outputRevision": 69 }, @@ -1364,6 +1422,10 @@ ] } ], + "hashes": { + "DerivedGadgetWithEnums": "0$ziLf2XE3jIzXb4mHc1AtXn2Pr+w", + "GadgetWithNoEnums": "0$Jahs+W0ABVb+g0vSoUQ80hLQCzk" + }, "inputFile": "gadgetwithnoenums.h", "outputRevision": 69 }, @@ -1400,6 +1462,11 @@ ] } ], + "hashes": { + "GrandParentGadget::BaseGadget": "0$edRI9jp0xvXAhzuu71v5ByBmvbw", + "GrandParentGadget::CRTPDerivedGadget": "0$Y3W4552o/BUqjdHf2NxgqKGkRmI", + "GrandParentGadget::DerivedGadget": "0$AhUcr6i4Hne/y216sE3UnzvaBks" + }, "inputFile": "grand-parent-gadget-class.h", "outputRevision": 69 }, @@ -1471,6 +1538,10 @@ "qualifiedClassName": "SomeRandomNamespace" } ], + "hashes": { + "SomeRandomNamespace": "0$haTJ0XROzXzF0eXH4sreUExdPxg", + "TestFwdProperties": "0$3+84uwm9jhX5WgZzqClAZM6h7Hg" + }, "inputFile": "moc_include.h", "outputRevision": 69 }, @@ -1541,6 +1612,11 @@ "qualifiedClassName": "FooNamespace::FooNestedNamespace::FooMoreNestedNamespace" } ], + "hashes": { + "FooNamespace": "0$Z0uaI0MMFqu0YzU7vExYNRUcBhs", + "FooNamespace::FooNestedNamespace": "0$c0mGYICDOoC4QPdVm5HRH9ApH68", + "FooNamespace::FooNestedNamespace::FooMoreNestedNamespace": "0$FHRRWVs1L+xEcXWY318xVxYlx+Y" + }, "inputFile": "namespace.h", "outputRevision": 69 }, @@ -1572,6 +1648,10 @@ ] } ], + "hashes": { + "QTBUG_101141::Base": "0$U366MOIqv3M+uAirFGzc/YkYdoA", + "QTBUG_101141::Derived": "0$RhyA1CgCTHJhP+oLRVIDYv85oNQ" + }, "inputFile": "namespaced-base-class.h", "outputRevision": 69 }, @@ -1664,6 +1744,10 @@ ] } ], + "hashes": { + "Foo::Bar": "0$bYnwxHCP+WvK/RySc0Cz7hJ/Mv4", + "Foo::Baz": "0$/xEtUkDT353vJHNrTTLCovKNWyo" + }, "inputFile": "namespaced-flags.h", "outputRevision": 69 }, @@ -1700,6 +1784,9 @@ ] } ], + "hashes": { + "MyBooooooostishClass": "0$RDRAZTtVcMrrVnaW3ccG5tm5kvU" + }, "inputFile": "no-keywords.h", "outputRevision": 69 }, @@ -1718,6 +1805,9 @@ ] } ], + "hashes": { + "NonGadgetParent::Derived": "0$3bsnxOwlHgO7cAngQLFI5JRtpDE" + }, "inputFile": "non-gadget-parent-class.h", "outputRevision": 69 }, @@ -1784,6 +1874,9 @@ ] } ], + "hashes": { + "OldStyleCast": "0$tg567Jxb/wczCGYgDo7CHjLQaF8" + }, "inputFile": "oldstyle-casts.h", "outputRevision": 69 }, @@ -2027,6 +2120,9 @@ ] } ], + "hashes": { + "PD::ParseDefine": "0$gtPGvhjjReF7/9ujAvwWNaRWEdk" + }, "inputFile": "parse-defines.h", "outputRevision": 69 }, @@ -2045,6 +2141,9 @@ ] } ], + "hashes": { + "TestPluginMetaData": "0$D43pha4BOvdLMUbKSF0Pw0b2rfQ" + }, "inputFile": "plugin_metadata.h", "outputRevision": 69 }, @@ -2149,6 +2248,9 @@ ] } ], + "hashes": { + "TestPointeeCanBeIncomplete": "0$BXzN6PCuKk/qhZeYZaaNyUNKW/8" + }, "inputFile": "pointery_to_incomplete.h", "outputRevision": 69 }, @@ -2230,6 +2332,10 @@ ] } ], + "hashes": { + "PureVirtualSignalsImpl": "0$+RHy7vy0RgivuAtQW3Dohe0R+qk", + "PureVirtualSignalsTest": "0$31whJazqnFwWMc3cO9MaT9wBSy8" + }, "inputFile": "pure-virtual-signals.h", "outputRevision": 69 }, @@ -2302,6 +2408,10 @@ ] } ], + "hashes": { + "QEnum64Object": "0$mPIUjAUmfYvqxkkkXghMo9RAiZA", + "QFlags64Object": "0$o86jnlU0tZ0uyjOrEZ4/oOXAKfk" + }, "inputFile": "qflags64object.h", "outputRevision": 69 }, @@ -2358,6 +2468,10 @@ ] } ], + "hashes": { + "InvokableBeforeInline": "0$t5nSESnZz1Liw62inFXd2SZTQTg", + "InvokableBeforeReturnType": "0$q7unyP9EifVhdxdSVIaFweQMDs4" + }, "inputFile": "qinvokable.h", "outputRevision": 69 }, @@ -2397,6 +2511,9 @@ ] } ], + "hashes": { + "QmlMacro": "0$ohmDXSSvwPL9cUf7TItBqGRGOXM" + }, "inputFile": "qmlmacro.h", "outputRevision": 69 }, @@ -2433,6 +2550,9 @@ ] } ], + "hashes": { + "TestQPrivateSlots": "0$akM0QTpV2o3AF3v6YBUh+iY1NsM" + }, "inputFile": "qprivateslots.h", "outputRevision": 69 }, @@ -2456,6 +2576,9 @@ "qualifiedClassName": "QTBUG_35657::A" } ], + "hashes": { + "QTBUG_35657::A": "0$ExnqnOcM0keSitP69QEKBZXt4Es" + }, "inputFile": "qtbug-35657-gadget.h", "outputRevision": 69 }, @@ -2490,6 +2613,9 @@ ] } ], + "hashes": { + "QTBUG_35657::B": "0$72RlEvuVajxk55rzeym5e5E7LmA" + }, "inputFile": "related-metaobjects-in-gadget.h", "outputRevision": 69 }, @@ -2547,6 +2673,10 @@ ] } ], + "hashes": { + "QTBUG_2151::A": "0$wwzOwB54lloQnRalZ9IiAf2oCII", + "QTBUG_2151::B": "0$b4fk7Aaf9GubBohh4VCfABMTEJM" + }, "inputFile": "related-metaobjects-in-namespaces.h", "outputRevision": 69 }, @@ -3029,6 +3159,28 @@ ] } ], + "hashes": { + "NS1::DependingNestedGadget": "0$AjMR5Q0D2DupU0l/8Fl2wE0HX/w", + "NS1::DependingNestedObject": "0$DopB5FLdxxGgVBjg2pb8LDui8hA", + "NS1::DependingObject": "0$NBnkXMTHy3+HN77VtSKqsQrn2Ds", + "NS1::Gadget": "0$x+7FBtXOnCa+gBCDQYYJELL4q6I", + "NS1::Nested::Gadget": "0$QrFH5wzXn6x40CgbEt6o8+GwAEw", + "NS1::Nested::Object": "0$pMIVvuQb0tyLZ5mwRFw8QDUTsPE", + "NS1::NestedUnsused::Gadget": "0$ksUC+FXicub96bqLdCnYlMjTIQg", + "NS1::NestedUnsused::Object": "0$Cu9dTusUon5OJbsbCNDszsTWsk4", + "NS1::Object": "0$FkizYy4XPEX9QPIyL2eE9bFYc5E", + "NS2::DependingNestedGadget": "0$DMnk8/Na+tVbOCW2bDplJtp+qfI", + "NS2::DependingNestedObject": "0$us8HSPRnoTuPP+yEIj9VoF0+wgY", + "NS2::DependingObject": "0$Y9OXadBw3CKbCLiiIcwarWKy4Ko", + "NS2::Gadget": "0$1c2uSbAFT5taj/PGDXyGdXgWhk0", + "NS2::Nested::Gadget": "0$1Xmv0ccjZFLq/Y68TxyTQNopa8E", + "NS2::Nested::Object": "0$usRIIUM8ix6bMuqFyxtLIGnTAew", + "NS2::NestedUnsused::Gadget": "0$V8PumQY6aJmatQ/V72bPVRUyPtM", + "NS2::NestedUnsused::Object": "0$HJl5p4/beto2yDugYIrnCfTIRm0", + "NS2::Object": "0$qx8DIVSOVAgRXwqE1JjvDXnuM44", + "Unsused::Gadget": "0$9DYMYjqKdVlC6OknSGIF2TCoZJo", + "Unsused::Object": "0$KW85gaAnMYh4a0phjBcM1vGAyKw" + }, "inputFile": "related-metaobjects-name-conflict.h", "outputRevision": 69 }, @@ -3070,6 +3222,9 @@ ] } ], + "hashes": { + "SignalWithDefaultArg": "0$nxiVjK8ieRlRO/uDdq0gKtPSgTU" + }, "inputFile": "signal-with-default-arg.h", "outputRevision": 69 }, @@ -3099,6 +3254,9 @@ ] } ], + "hashes": { + "KDAB": "0$grAoknUYC0BkMxt4HLH2mGR4Tu8" + }, "inputFile": "single-quote-digit-separator-n3781.h", "outputRevision": 69 }, @@ -3180,6 +3338,9 @@ ] } ], + "hashes": { + "SlotsWithVoidTemplateTest": "0$/08YvkDzSSJmzKO0L6t3PWzQTpA" + }, "inputFile": "slots-with-void-template.h", "outputRevision": 69 }, @@ -3198,6 +3359,9 @@ ] } ], + "hashes": { + "Task192552": "0$QGM+ZcuSQWGxY2IolngyVZy8sy4" + }, "inputFile": "task192552.h", "outputRevision": 69 }, @@ -3228,6 +3392,10 @@ ] } ], + "hashes": { + "NS_A::NS_B::TestObject": "0$IMqa1PfGS0EaXXK+9Ch24g/nP2A", + "NS_A::NS_Main::TestMain": "0$0z3ZNVKocxpdZHzRFtoTKQimUm0" + }, "inputFile": "task234909.h", "outputRevision": 69 }, @@ -3403,6 +3571,9 @@ ] } ], + "hashes": { + "TypenameWithUnsigned": "0$jwklF5QEzeV58Ui5C0aT9ZFwDVo" + }, "inputFile": "task240368.h", "outputRevision": 69 }, @@ -3421,6 +3592,9 @@ ] } ], + "hashes": { + "Task87883": "0$wz5QuoSTJ+0peRmMDYqL9p8q5q4" + }, "inputFile": "task87883.h", "outputRevision": 69 }, @@ -3507,6 +3681,9 @@ ] } ], + "hashes": { + "MyTechPreviewObject": "0$70fB9sh1BICrgFbeYIYRC8QX+PQ" + }, "inputFile": "tech-preview.h", "outputRevision": 69 }, @@ -3591,6 +3768,9 @@ ] } ], + "hashes": { + "BBB::Foo": "0$QBU7ysZZuCx+IZ4+vUQEQ84tqu8" + }, "inputFile": "trigraphs.h", "outputRevision": 69 }, diff --git a/tests/auto/tools/moc/tst_moc.cpp b/tests/auto/tools/moc/tst_moc.cpp index fb30d66e6ec..7001d676878 100644 --- a/tests/auto/tools/moc/tst_moc.cpp +++ b/tests/auto/tools/moc/tst_moc.cpp @@ -2576,6 +2576,26 @@ void tst_Moc::warnings_data() << QString() << u"standard input:2:1: error: Parse error at \"NONSENSE\""_s; + QTest::newRow("VIRTUAL FINAL property") + << "class X { \n Q_PROPERTY(int p READ p VIRTUAL FINAL) \n };"_ba << QStringList() << 1 + << QString() + << u"standard input:2:1: error: Issue with property declaration p: " + u"The VIRTUAL cannot be combined with FINAL, as these attributes are mutually exclusive"_s; + + QTest::newRow("FINAL OVERRIDE property") + << "class X { \n Q_PROPERTY(int p READ p FINAL OVERRIDE) \n };"_ba << QStringList() << 1 + << QString() + << u"standard input:2:1: error: Issue with property declaration p: " + u"OVERRIDE is redundant when property is marked FINAL"_s; + + QTest::newRow("VIRTUAL OVERRIDE property") + << "class X { \n Q_PROPERTY(int p READ p VIRTUAL OVERRIDE) \n };"_ba << QStringList() + << 1 << QString() + << u"standard input:2:1: error: Issue with property declaration p: VIRTUAL is " + u"redundant when overriding a property." + u" The OVERRIDE must only be used when actually overriding an existing property;" + u" using it on a new property is an error."_s; + #ifdef Q_OS_UNIX // Limit to Unix because the error message is platform-dependent QTest::newRow("Q_PLUGIN_METADATA: unreadable file") << QByteArray("class X { \n Q_PLUGIN_METADATA(FILE \".\") \n };") diff --git a/tests/auto/tools/mochelpers/tst_mochelpers.cpp b/tests/auto/tools/mochelpers/tst_mochelpers.cpp index 7e5d18d160e..ae80f0c2b58 100644 --- a/tests/auto/tools/mochelpers/tst_mochelpers.cpp +++ b/tests/auto/tools/mochelpers/tst_mochelpers.cpp @@ -104,7 +104,7 @@ void tst_MocHelpers::classinfoDataGroup() { constexpr auto data = QtMocHelpers::metaObjectData<void, void>(0, dummyStringData, QtMocHelpers::UintData{}, QtMocHelpers::UintData{}, - QtMocHelpers::UintData{}, QtMocHelpers::UintData{}, + QtMocHelpers::UintData{}, -1, QtMocHelpers::UintData{}, QtMocHelpers::ClassInfos({{1, 2}, {3, 4}})); checkClassInfos(data.staticData.data); } @@ -612,7 +612,7 @@ void tst_MocHelpers::constructorUintGroup() constexpr auto data = QtMocHelpers::metaObjectData<void, void>(0, dummyStringData, QtMocHelpers::UintData{}, QtMocHelpers::UintData{}, - QtMocHelpers::UintData{}, constructors); + QtMocHelpers::UintData{}, -1, constructors); checkConstructors(data.staticData.data, data.relocatingData.metaTypes); } @@ -676,7 +676,7 @@ void tst_MocHelpers::uintArrayNoMethods() QtMocHelpers::EnumData<E2>(7, 6, EnumIsFlag | EnumIsScoped) .add({ { 7, E2::V0 }, { 10, E2::V1 }, }), QtMocHelpers::EnumData<QFlags<E1>>(11, 1, EnumIsFlag).add({ { 3, E1::AnEnumValue } }), - }, QtMocHelpers::UintData{}, QtMocHelpers::ClassInfos({{1, 2}, {3, 4}})); + }, -1, QtMocHelpers::UintData{}, QtMocHelpers::ClassInfos({{1, 2}, {3, 4}})); auto &data = mo.staticData.data; auto &metaTypes = mo.relocatingData.metaTypes; @@ -724,6 +724,7 @@ void tst_MocHelpers::uintArray() .add({ { 7, E2::V0 }, { 10, E2::V1 }, }), QtMocHelpers::EnumData<QFlags<E1>>(11, 1, EnumIsFlag).add({ { 3, E1::AnEnumValue } }), }, + -1, QtMocHelpers::UintData{ QtMocHelpers::ConstructorData<NoType(QObject *)>(1, QtMocConstants::AccessPublic, {{ { QMetaType::QObjectStar, 2 } }} diff --git a/tests/auto/tools/rcc/data/legal/rcc_legal.cpp b/tests/auto/tools/rcc/data/legal/rcc_legal.cpp index 248ab2e3b48..96f87d192e7 100644 --- a/tests/auto/tools/rcc/data/legal/rcc_legal.cpp +++ b/tests/auto/tools/rcc/data/legal/rcc_legal.cpp @@ -3,7 +3,7 @@ ** Copyright (C) 2024 Intel Corporation. ** SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only ** -** Created by: The Resource Compiler for Qt version 6.11.0 +** Created by: The Resource Compiler for Qt version 6.12.0 ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ diff --git a/tests/auto/util/testrunner/CMakeLists.txt b/tests/auto/util/testrunner/CMakeLists.txt new file mode 100644 index 00000000000..5ca8406f854 --- /dev/null +++ b/tests/auto/util/testrunner/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (C) 2025 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +# Run the qt-testrunner test only inside the CI. +if(DEFINED ENV{COIN_UNIQUE_JOB_ID} AND NOT IOS) + qt_internal_create_test_script( + NAME tst_qt_testrunner + COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/tst_qt_testrunner.py" ARGS -v + WORKING_DIRECTORY "${test_working_dir}" + OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/tst_qt_testrunner_Wrapper$<CONFIG>.cmake" + ENVIRONMENT "TESTRUNNER" "" + ) +endif() diff --git a/tests/auto/util/testrunner/qt_mock_test-log.xml b/tests/auto/util/testrunner/qt_mock_test-log.xml new file mode 100644 index 00000000000..a164bec9f9c --- /dev/null +++ b/tests/auto/util/testrunner/qt_mock_test-log.xml @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="UTF-8"?> +<TestCase name="qt_mock_test"> + <Environment> + <QtVersion>MOCK</QtVersion> + <QtBuild>MOCK</QtBuild> + <QTestVersion>6.3.0</QTestVersion> + </Environment> + <TestFunction name="initTestCase"> + <Incident type="{{initTestCase_result}}" file="" line="0" /> + <Duration msecs="0.00004"/> + </TestFunction> + <TestFunction name="always_pass"> + <Incident type="{{always_pass_result}}" file="" line="0" /> + <Duration msecs="0.71704"/> + </TestFunction> + <TestFunction name="always_fail"> + <Incident type="{{always_fail_result}}" file="" line="0" /> + <Duration msecs="0.828272"/> + </TestFunction> + <TestFunction name="always_crash"> + <Incident type="{{always_crash_result}}" file="" line="0" /> + <Duration msecs="0.828272"/> + </TestFunction> + + <!-- The strings like this one "{{fail_then_pass:2_result}}" + are just template strings that will be replaced by the test driver + before each test. The colon doesn't have a special meaning. + The datatags in the following tests are just "2", "5", "6". + We don't strictly need datatags because the tests don't include + specific testing for datatags. It's just that adding a couple + of datatags to this XML template, complicates it a bit and + tests somewhat that functionality as a side-effect. + --> + <TestFunction name="fail_then_pass"> + <Incident type="{{fail_then_pass:2_result}}" file="" line="0"> + <DataTag><![CDATA[2]]></DataTag> + </Incident> + <Incident type="{{fail_then_pass:5_result}}" file="" line="0"> + <DataTag><![CDATA[5]]></DataTag> + </Incident> + <Incident type="{{fail_then_pass:6_result}}" file="" line="0"> + <DataTag><![CDATA[6]]></DataTag> + </Incident> + </TestFunction> + <TestFunction name="fail_then_crash"> + <Incident type="{{fail_then_crash_result}}" file="" line="0" /> + <Duration msecs="0.828272"/> + </TestFunction> + <Duration msecs="1904.9"/> +</TestCase> diff --git a/tests/auto/util/testrunner/qt_mock_test.py b/tests/auto/util/testrunner/qt_mock_test.py new file mode 100755 index 00000000000..af8fdf24509 --- /dev/null +++ b/tests/auto/util/testrunner/qt_mock_test.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +# This is an artificial test, mimicking the Qt tests, for example tst_whatever. +# Its purpose is to assist in testing qt-testrunner.py. +# +# Mode A: +# +# If invoked with a test function argument, it runs that test function. +# +# Usage: +# +# $0 always_pass +# $0 always_fail +# $0 always_crash +# $0 fail_then_pass:N # where N is the number of failing runs before passing +# +# Needs environment variable: +# + QT_MOCK_TEST_STATE_FILE :: points to a unique filename, to be written +# for keeping the state of the fail_then_pass:N tests. +# +# Mode B: +# +# If invoked without any argument, it runs the tests listed in the +# variable QT_MOCK_TEST_RUN_LIST. If variable is empty it just runs +# the always_pass test. It also understands qtestlib's `-o outfile.xml,xml` +# option for writing a mock testlog in a file. Requires environment variables: +# + QT_MOCK_TEST_STATE_FILE :: See above +# + QT_MOCK_TEST_XML_TEMPLATE_FILE :: may point to the template XML file +# located in the same source directory. Without this variable, the +# option `-o outfile.xml,xml` will be ignored. +# + QT_MOCK_TEST_RUN_LIST :: may contain a comma-separated list of test +# that should run. +# + QT_MOCK_TEST_CRASH_CLEANLY :: if set to 1, then the executable will +# crash (exit with a high exit code) +# after successfully running the given tests and writing the XML logfile. + + + +import sys +import os +import traceback +from tst_qt_testrunner import write_xml_log + + +MY_NAME = os.path.basename(sys.argv[0]) +STATE_FILE = None +XML_TEMPLATE = None +XML_OUTPUT_FILE = None +CRASH_CLEANLY = False + + +def crash(): + sys.exit(131) + +def put_failure(test_name): + with open(STATE_FILE, "a") as f: + f.write(test_name + "\n") +def get_failures(test_name): + n = 0 + try: + with open(STATE_FILE) as f: + for line in f: + if line.strip() == test_name: + n += 1 + except FileNotFoundError: + return 0 + return n + +# Only care about the XML log output file. +def parse_output_argument(a): + global XML_OUTPUT_FILE + if a.endswith(",xml"): + XML_OUTPUT_FILE = a[:-4] + +# Strip qtestlib specific arguments. +# Only care about the "-o ...,xml" argument. +def clean_cmdline(): + args = [] + prev_arg = None + skip_next_arg = True # Skip argv[0] + for a in sys.argv: + if skip_next_arg: + if prev_arg == "-o": + parse_output_argument(a) + prev_arg = None + skip_next_arg = False + continue + if a in ("-o", "-maxwarnings"): + skip_next_arg = True + prev_arg = a + continue + if a in ("-v1", "-v2", "-vs"): + print("VERBOSE RUN") + if "QT_LOGGING_RULES" in os.environ: + print("Environment has QT_LOGGING_RULES:", + os.environ["QT_LOGGING_RULES"]) + continue + args.append(a) + return args + + +def log_test(testcase, result, + testsuite=MY_NAME.rpartition(".")[0]): + print("%-7s: %s::%s()" % (result, testsuite, testcase)) + +def log_xml(fail_list): + if XML_OUTPUT_FILE and XML_TEMPLATE is not None: + if XML_TEMPLATE == "": + # If the template is an empty file, then write an empty output file + with open(XML_OUTPUT_FILE, "w"): + pass + else: + write_xml_log(XML_OUTPUT_FILE, failure=fail_list) + +# Return the exit code +def run_test(testname): + if testname == "initTestCase": + exit_code = 1 # specifically test that initTestCase fails + elif testname == "always_pass": + exit_code = 0 + elif testname == "always_fail": + exit_code = 1 + elif testname == "always_crash": + exit_code = 131 + elif testname == "fail_then_crash": + previous_fails = get_failures(testname) + if previous_fails == 0: + put_failure(testname) + exit_code = 1 + else: + exit_code = 131 + elif testname.startswith("fail_then_pass"): + wanted_fails = int(testname.partition(":")[2]) + previous_fails = get_failures(testname) + if previous_fails < wanted_fails: + put_failure(testname) + exit_code = 1 + else: + exit_code = 0 + else: + assert False, "Unknown argument: %s" % testname + + if exit_code == 0: + log_test(testname, "PASS") + elif exit_code == 1: + log_test(testname, "FAIL!") + else: + log_test(testname, "CRASH!") + + return exit_code + +def no_args_run(): + try: + run_list = os.environ["QT_MOCK_TEST_RUN_LIST"].split(",") + except KeyError: + run_list = ["always_pass"] + + total_result = True + fail_list = [] + for test in run_list: + test_exit_code = run_test(test) + if test_exit_code not in (0, 1): + crash() + if test_exit_code != 0: + fail_list.append(test) + total_result = total_result and (test_exit_code == 0) + + log_xml(fail_list) + + if CRASH_CLEANLY: + # Crash despite all going well and writing all output files cleanly. + crash() + + if total_result: + sys.exit(0) + else: + sys.exit(1) + + +def main(): + global STATE_FILE + # Will fail if env var is not set. + STATE_FILE = os.environ["QT_MOCK_TEST_STATE_FILE"] + + global XML_TEMPLATE + if "QT_MOCK_TEST_XML_TEMPLATE_FILE" in os.environ: + with open(os.environ["QT_MOCK_TEST_XML_TEMPLATE_FILE"]) as f: + XML_TEMPLATE = f.read() + + global CRASH_CLEANLY + if ("QT_MOCK_TEST_CRASH_CLEANLY" in os.environ + and os.environ["QT_MOCK_TEST_CRASH_CLEANLY"] == "1" + ): + CRASH_CLEANLY = True + + args = clean_cmdline() + + if len(args) == 0: + no_args_run() + assert False, "Unreachable!" + else: # run single test function + exit_code = run_test(args[0]) + # Write "fail" in the XML log only if the specific run has failed. + if exit_code != 0: + log_xml([args[0]]) + else: + log_xml([]) + sys.exit(exit_code) + + +# TODO write XPASS test that does exit(1) + +if __name__ == "__main__": + try: + main() + except Exception as e: + traceback.print_exc() + exit(128) # Something went wrong with this script diff --git a/tests/auto/util/testrunner/tst_qt_testrunner.py b/tests/auto/util/testrunner/tst_qt_testrunner.py new file mode 100755 index 00000000000..1134fa0427f --- /dev/null +++ b/tests/auto/util/testrunner/tst_qt_testrunner.py @@ -0,0 +1,581 @@ +#!/usr/bin/env python3 +# Copyright (C) 2021 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +import sys +import os +import re +import glob +import subprocess + +from subprocess import STDOUT, PIPE +from tempfile import TemporaryDirectory, mkstemp + +MY_NAME = os.path.basename(__file__) +my_dir = os.path.dirname(__file__) +testrunner = os.path.join(my_dir, "..", "..", "..", "..", + "util", "testrunner", "qt-testrunner.py") +mock_test = os.path.join(my_dir, "qt_mock_test.py") +xml_log_template = os.path.join(my_dir, "qt_mock_test-log.xml") + +with open(xml_log_template) as f: + XML_TEMPLATE = f.read() + + +import unittest + +def setUpModule(): + global TEMPDIR + TEMPDIR = TemporaryDirectory(prefix="tst_qt_testrunner-") + + global EMPTY_FILE + EMPTY_FILE = os.path.join(TEMPDIR.name, "EMPTY") + with open(EMPTY_FILE, "w") as f: + pass + + filename = os.path.join(TEMPDIR.name, "file_1") + print("setUpModule(): setting up temporary directory and env var" + " QT_MOCK_TEST_STATE_FILE=" + filename + " and" + " QT_MOCK_TEST_XML_TEMPLATE_FILE=" + xml_log_template) + + os.environ["QT_MOCK_TEST_STATE_FILE"] = filename + os.environ["QT_MOCK_TEST_XML_TEMPLATE_FILE"] = xml_log_template + os.environ["QT_TESTRUNNER_TESTING"] = "1" + +def tearDownModule(): + print("\ntearDownModule(): Cleaning up temporary directory:", + TEMPDIR.name) + del os.environ["QT_MOCK_TEST_STATE_FILE"] + TEMPDIR.cleanup() + + +# Helper to run a command and always capture output +def run(args : list, **kwargs): + if args[0].endswith(".py"): + # Make sure we run python executables with the same python version. + # It also helps on Windows, that .py files are not directly executable. + args = [ sys.executable, *args ] + if DEBUG: + print("Running: ", args, flush=True) + proc = subprocess.run(args, stdout=PIPE, stderr=STDOUT, **kwargs) + if DEBUG and proc.stdout: + print(proc.stdout.decode(), flush=True) + return proc + +# Helper to run qt-testrunner.py with proper testing arguments. +# Always append --log-dir=TEMPDIR unless specifically told not to. +def run_testrunner(xml_filename=None, log_dir=None, + testrunner_args=None, + wrapper_script=None, wrapper_args=None, + qttest_args=None, env=None): + + args = [ testrunner ] + if xml_filename: + args += [ "--parse-xml-testlog", xml_filename ] + if log_dir == None: + args += [ "--log-dir", TEMPDIR.name ] + elif log_dir != "": + args += [ "--log-dir", log_dir ] + if testrunner_args: + args += testrunner_args + + if wrapper_script: + args += [ wrapper_script ] + if wrapper_args: + args += wrapper_args + + args += [ mock_test ] + if qttest_args: + args += qttest_args + + return run(args, env=env) + +# Write the XML_TEMPLATE to filename, replacing the templated results. +def write_xml_log(filename, failure=None, inject_message=None): + data = XML_TEMPLATE + if failure is None: + failure = [] + elif isinstance(failure, str): + failure = [ failure ] + # Replace what was asked to fail with "fail" + for x in failure: + data = data.replace("{{" + x + "_result}}", "fail") + # Replace the rest with "pass" + data = re.sub(r"{{[^}]+}}", "pass", data) + # Inject possible <Message> tags inside the first <TestFunction> + if inject_message: + i = data.index("</TestFunction>") + data = data[:i] + inject_message + data[i:] + with open(filename, "w") as f: + f.write(data) + + +# Test that qt_mock_test.py behaves well. This is necessary to properly +# test qt-testrunner. +class Test_qt_mock_test(unittest.TestCase): + def setUp(self): + state_file = os.environ["QT_MOCK_TEST_STATE_FILE"] + if os.path.exists(state_file): + os.remove(state_file) + def assertProcessCrashed(self, proc): + if DEBUG: + print("process returncode is:", proc.returncode) + self.assertTrue(proc.returncode < 0 or + proc.returncode >= 128) + + def test_always_pass(self): + proc = run([mock_test, "always_pass"]) + self.assertEqual(proc.returncode, 0) + def test_always_fail(self): + proc = run([mock_test, "always_fail"]) + self.assertEqual(proc.returncode, 1) + def test_fail_then_pass_2(self): + proc = run([mock_test, "fail_then_pass:2"]) + self.assertEqual(proc.returncode, 1) + proc = run([mock_test, "fail_then_pass:2"]) + self.assertEqual(proc.returncode, 1) + proc = run([mock_test, "fail_then_pass:2"]) + self.assertEqual(proc.returncode, 0) + def test_fail_then_pass_1(self): + proc = run([mock_test, "fail_then_pass:1"]) + self.assertEqual(proc.returncode, 1) + proc = run([mock_test, "fail_then_pass:1"]) + self.assertEqual(proc.returncode, 0) + def test_fail_then_pass_many_tests(self): + proc = run([mock_test, "fail_then_pass:1"]) + self.assertEqual(proc.returncode, 1) + proc = run([mock_test, "fail_then_pass:2"]) + self.assertEqual(proc.returncode, 1) + proc = run([mock_test, "fail_then_pass:1"]) + self.assertEqual(proc.returncode, 0) + proc = run([mock_test, "fail_then_pass:2"]) + self.assertEqual(proc.returncode, 1) + proc = run([mock_test, "fail_then_pass:2"]) + self.assertEqual(proc.returncode, 0) + def test_fail_then_crash(self): + proc = run([mock_test, "fail_then_crash"]) + self.assertEqual(proc.returncode, 1) + proc = run([mock_test, "fail_then_crash"]) + self.assertProcessCrashed(proc) + def test_xml_file_is_written(self): + filename = os.path.join(TEMPDIR.name, "testlog.xml") + proc = run([mock_test, "-o", filename+",xml"]) + self.assertEqual(proc.returncode, 0) + self.assertTrue(os.path.exists(filename)) + self.assertGreater(os.path.getsize(filename), 0) + os.remove(filename) + # Test it will write an empty XML file if template is empty + def test_empty_xml_file_is_written(self): + my_env = { + **os.environ, + "QT_MOCK_TEST_XML_TEMPLATE_FILE": EMPTY_FILE + } + filename = os.path.join(TEMPDIR.name, "testlog.xml") + proc = run([mock_test, "-o", filename+",xml"], + env=my_env) + self.assertEqual(proc.returncode, 0) + self.assertTrue(os.path.exists(filename)) + self.assertEqual(os.path.getsize(filename), 0) + os.remove(filename) + def test_crash_cleanly(self): + proc = run([mock_test], + env={ **os.environ, "QT_MOCK_TEST_CRASH_CLEANLY":"1" }) + if DEBUG: + print("returncode:", proc.returncode) + self.assertProcessCrashed(proc) + + +# Find in @path, files that start with @testname and end with @pattern, +# where @pattern is a glob-like string. +def find_test_logs(testname=None, path=None, pattern="-*[0-9].xml"): + if testname is None: + testname = os.path.basename(mock_test) + if path is None: + path = TEMPDIR.name + pattern = os.path.join(path, testname + pattern) + logfiles = glob.glob(pattern) + if DEBUG: + print(f"Test ({testname}) logfiles found: ", logfiles) + return logfiles + +# Test regular invocations of qt-testrunner. +class Test_testrunner(unittest.TestCase): + def setUp(self): + state_file = os.environ["QT_MOCK_TEST_STATE_FILE"] + if os.path.exists(state_file): + os.remove(state_file) + # The mock_test honors only the XML output arguments, the rest are ignored. + old_logfiles = find_test_logs(pattern="*.xml") + for fname in old_logfiles: + os.remove(os.path.join(TEMPDIR.name, fname)) + self.env = dict(os.environ) + self.testrunner_args = [] + def prepare_env(self, run_list=None): + if run_list is not None: + self.env['QT_MOCK_TEST_RUN_LIST'] = ",".join(run_list) + def run2(self): + return run_testrunner(testrunner_args=self.testrunner_args, env=self.env) + + def test_simple_invocation(self): + # All tests pass. + proc = self.run2() + self.assertEqual(proc.returncode, 0) + def test_always_pass(self): + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 0) + def test_output_files_are_generated(self): + proc = self.run2() + xml_output_files = find_test_logs() + self.assertEqual(len(xml_output_files), 1) + def test_always_fail(self): + self.prepare_env(run_list=["always_fail"]) + proc = self.run2() + # TODO verify that re-runs==max_repeats + self.assertEqual(proc.returncode, 2) + def test_flaky_pass_1(self): + self.prepare_env(run_list=["always_pass,fail_then_pass:1"]) + proc = self.run2() + self.assertEqual(proc.returncode, 0) + def test_flaky_pass_5(self): + self.prepare_env(run_list=["always_pass,fail_then_pass:1,fail_then_pass:5"]) + proc = self.run2() + self.assertEqual(proc.returncode, 0) + def test_flaky_fail(self): + self.prepare_env(run_list=["always_pass,fail_then_pass:6"]) + proc = self.run2() + self.assertEqual(proc.returncode, 2) + def test_flaky_pass_fail(self): + self.prepare_env(run_list=["always_pass,fail_then_pass:1,fail_then_pass:6"]) + proc = self.run2() + # TODO verify that one func was re-run and passed but the other failed. + self.assertEqual(proc.returncode, 2) + def test_initTestCase_fail_crash(self): + self.prepare_env(run_list=["initTestCase,always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + def test_fail_then_crash(self): + self.prepare_env(run_list=["fail_then_crash"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + + # By testing --no-extra-args, we ensure qt-testrunner works for + # tst_selftests and the other NON_XML_GENERATING_TESTS. + def test_no_extra_args_pass(self): + self.testrunner_args += ["--no-extra-args"] + proc = self.run2() + self.assertEqual(proc.returncode, 0) + def test_no_extra_args_fail(self): + self.prepare_env(run_list=["always_fail"]) + self.testrunner_args += ["--no-extra-args"] + proc = self.run2() + self.assertEqual(proc.returncode, 3) + def test_no_extra_args_reruns_only_once_1(self): + self.prepare_env(run_list=["fail_then_pass:1"]) + self.testrunner_args += ["--no-extra-args"] + proc = self.run2() + # The 1st rerun PASSed. + self.assertEqual(proc.returncode, 0) + def test_no_extra_args_reruns_only_once_2(self): + self.prepare_env(run_list=["fail_then_pass:2"]) + self.testrunner_args += ["--no-extra-args"] + proc = self.run2() + # We never re-run more than once, so the exit code shows FAIL. + self.assertEqual(proc.returncode, 3) + + # If no XML file is found by qt-testrunner, it is usually considered a + # CRASH and the whole test is re-run. Even when the return code is zero. + # It is a PASS only if the test is not capable of XML output (see no_extra_args above). + def test_no_xml_log_written_pass_crash(self): + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + # On the 2nd iteration of the full test, both of the tests pass. + # Still it's a CRASH because no XML file was found. + def test_no_xml_log_written_fail_then_pass_crash(self): + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.prepare_env(run_list=["always_pass,fail_then_pass:1"]) + proc = self.run2() + # TODO verify that the whole test has run twice. + self.assertEqual(proc.returncode, 3) + # Even after 2 iterations of the full test we still get failures but no XML file, + # and this is considered a CRASH. + def test_no_xml_log_written_crash(self): + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.prepare_env(run_list=["fail_then_pass:2"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + + def test_empty_xml_crash_1(self): + self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] = EMPTY_FILE + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + def test_empty_xml_crash_2(self): + self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] = EMPTY_FILE + self.prepare_env(run_list=["always_fail"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + + # test qFatal should be a crash in all cases. + def test_qfatal_crash_1(self): + fatal_xml_message = """ + <Message type="qfatal" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to initialize graphics backend for OpenGL.]]></Description> + </Message> + """ + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile, failure=None, inject_message=fatal_xml_message) + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + def test_qfatal_crash_2(self): + fatal_xml_message = """ + <Message type="qfatal" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to initialize graphics backend for OpenGL.]]></Description> + </Message> + """ + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile, failure="always_fail", inject_message=fatal_xml_message) + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_pass,always_fail"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + + def test_qwarn_is_ignored_1(self): + qwarn_xml_message = """ + <Message type="qwarn" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to create RHI (backend 2)]]></Description> + </Message> + """ + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile, failure=None, inject_message=qwarn_xml_message) + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 0) + def test_qwarn_is_ignored_2(self): + fatal_xml_message = """ + <Message type="qfatal" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to initialize graphics backend for OpenGL.]]></Description> + </Message> + <Message type="qwarn" file="" line="0"> + <DataTag><![CDATA[modal]]></DataTag> + <Description><![CDATA[Failed to create RHI (backend 2)]]></Description> + </Message> + """ + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile, failure=None, inject_message=fatal_xml_message) + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + + # If a test returns success but XML contains failures, it's a CRASH. + def test_wrong_xml_log_written_1_crash(self): + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile, failure="always_fail") + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_pass"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + # If a test returns failure but XML contains only pass, it's a CRASH. + def test_wrong_xml_log_written_2_crash(self): + logfile = os.path.join(TEMPDIR.name, os.path.basename(mock_test) + ".xml") + write_xml_log(logfile) + del self.env["QT_MOCK_TEST_XML_TEMPLATE_FILE"] + self.env["QT_TESTRUNNER_DEBUG_NO_UNIQUE_OUTPUT_FILENAME"] = "1" + self.prepare_env(run_list=["always_fail"]) + proc = self.run2() + self.assertEqual(proc.returncode, 3) + + def create_wrapper(self, filename, content=None): + if not content: + content='exec "$@"' + filename = os.path.join(TEMPDIR.name, filename) + with open(filename, "w") as f: + f.write(f'#!/bin/sh\n{content}\n') + self.wrapper_script = f.name + os.chmod(self.wrapper_script, 0o700) + + # Test that it re-runs the full executable in case of crash, even if the + # XML file is valid and shows one specific test failing. + def test_crash_reruns_full_QTQAINFRA_5226(self): + self.env["QT_MOCK_TEST_RUN_LIST"] = "always_fail" + # Tell qt_mock_test to crash after writing a proper XML file. + self.env["QT_MOCK_TEST_CRASH_CLEANLY"] = "1" + proc = self.run2() + # Verify qt-testrunner exited with 3 which means CRASH. + self.assertEqual(proc.returncode, 3) + # Verify that a full executable re-run happened that re-runs 2 times, + # instead of individual functions that re-run 5 times. + xml_output_files = find_test_logs() + self.assertEqual(len(xml_output_files), 2) + + # Test that qt-testrunner detects the correct executable name even if we + # use a special wrapper script, and that it uses that in the XML log filename. + @unittest.skipUnless(os.name == "posix", "Wrapper script needs POSIX shell") + def test_wrapper(self): + self.create_wrapper("coin_vxworks_qemu_runner.sh") + proc = run_testrunner(wrapper_script=self.wrapper_script, + env=self.env) + self.assertEqual(proc.returncode, 0) + xml_output_files = find_test_logs() + self.assertEqual(len(xml_output_files), 1) + + # The "androidtestrunner" wrapper is special. It expects the QTest arguments after "--". + # So our mock androidtestrunner wrapper ignores everything before "--" + # and executes our hardcoded mock_test with the arguments that follow. + def create_mock_anroidtestrunner_wrapper(self): + self.create_wrapper("androidtestrunner", content= + 'while [ "$1" != "--" ]; do shift; done; shift; exec {} "$@"'.format(mock_test)) + + @unittest.skipUnless(os.name == "posix", "Wrapper script needs POSIX shell") + def test_androidtestrunner_with_aab(self): + self.create_mock_anroidtestrunner_wrapper() + # Copied from our CI logs. The only relevant option is --aab. + androidtestrunner_args= [ + '--path', '/home/qt/work/qt/qtdeclarative_standalone_tests/tests/auto/quickcontrols/qquickpopup/android-build-tst_qquickpopup', + '--adb', '/opt/android/sdk/platform-tools/adb', '--skip-install-root', + '--ndk-stack', '/opt/android/android-ndk-r27c/ndk-stack', + '--manifest', '/home/qt/work/qt/qtdeclarative_standalone_tests/tests/auto/quickcontrols/qquickpopup/android-build-tst_qquickpopup/app/AndroidManifest.xml', + '--make', '"/opt/cmake-3.30.5/bin/cmake" --build /home/qt/work/qt/qtdeclarative_standalone_tests --target tst_qquickpopup_make_aab', + '--aab', '/home/qt/work/qt/qtdeclarative_standalone_tests/tests/auto/quickcontrols/qquickpopup/android-build-tst_qquickpopup/tst_qquickpopup.aab', + '--bundletool', '/opt/bundletool/bundletool', '--timeout', '1425' + ] + # In COIN CI, TESTRUNNER="qt-testrunner.py --". That's why we append "--". + proc = run_testrunner(testrunner_args=["--"], + wrapper_script=self.wrapper_script, + wrapper_args=androidtestrunner_args, + env=self.env) + self.assertEqual(proc.returncode, 0) + xml_output_files = find_test_logs("tst_qquickpopup") + self.assertEqual(len(xml_output_files), 1) + # similar to above but with "--apk" + @unittest.skipUnless(os.name == "posix", "Wrapper script needs POSIX shell") + def test_androidtestrunner_with_apk(self): + self.create_mock_anroidtestrunner_wrapper() + androidtestrunner_args= ['--blah', '--apk', '/whatever/waza.apk', 'blue'] + proc = run_testrunner(testrunner_args=["--"], + wrapper_script=self.wrapper_script, + wrapper_args=androidtestrunner_args, + env=self.env) + self.assertEqual(proc.returncode, 0) + xml_output_files = find_test_logs("waza") + self.assertEqual(len(xml_output_files), 1) + # similar to above but with neither "--apk" nor "--aab". qt-testrunner throws error. + @unittest.skipUnless(os.name == "posix", "Wrapper script needs POSIX shell") + def test_androidtestrunner_fail_to_detect_filename(self): + self.create_mock_anroidtestrunner_wrapper() + androidtestrunner_args= ['--blah', '--argh', '/whatever/waza.apk', 'waza.aab'] + proc = run_testrunner(testrunner_args=["--"], + wrapper_script=self.wrapper_script, + wrapper_args=androidtestrunner_args, + env=self.env) + self.assertEqual(proc.returncode, 1) + xml_output_files = find_test_logs("waza") + self.assertEqual(len(xml_output_files), 0) + + +# Test qt-testrunner script with an existing XML log file: +# qt-testrunner.py qt_mock_test.py --parse-xml-testlog file.xml +# qt-testrunner should repeat the testcases that are logged as +# failures and fail or pass depending on how the testcases behave. +# Different XML files are generated for the following test cases. +# + No failure logged. qt-testrunner should exit(0) +# + The "always_pass" test has failed. qt-testrunner should exit(0). +# + The "always_fail" test has failed. qt-testrunner should exit(2). +# + The "always_crash" test has failed. qt-testrunner should exit(3) +# since the re-run will crash. +# + The "fail_then_pass:2" test failed. qt-testrunner should exit(0). +# + The "fail_then_pass:5" test failed. qt-testrunner should exit(2). +# + The "initTestCase" failed which is listed as NO_RERUN thus +# qt-testrunner should exit(3). +class Test_testrunner_with_xml_logfile(unittest.TestCase): + # Runs before every single test function, creating a unique temp file. + def setUp(self): + (_handle, self.xml_file) = mkstemp( + suffix=".xml", prefix="qt_mock_test-log-", + dir=TEMPDIR.name) + os.close(_handle) + if os.path.exists(os.environ["QT_MOCK_TEST_STATE_FILE"]): + os.remove(os.environ["QT_MOCK_TEST_STATE_FILE"]) + def tearDown(self): + os.remove(self.xml_file) + del self.xml_file + # Run testrunner specifically for the tests here, with --parse-xml-testlog. + def run3(self, testrunner_args=None): + return run_testrunner(self.xml_file, + testrunner_args=testrunner_args) + + def test_no_failure(self): + write_xml_log(self.xml_file, failure=None) + proc = self.run3() + self.assertEqual(proc.returncode, 0) + def test_always_pass_failed(self): + write_xml_log(self.xml_file, failure="always_pass") + proc = self.run3() + self.assertEqual(proc.returncode, 0) + def test_always_pass_failed_max_repeats_0(self): + write_xml_log(self.xml_file, failure="always_pass") + proc = self.run3(testrunner_args=["--max-repeats", "0"]) + self.assertEqual(proc.returncode, 2) + def test_always_fail_failed(self): + write_xml_log(self.xml_file, failure="always_fail") + proc = self.run3() + self.assertEqual(proc.returncode, 2) + # Assert that one of the re-runs was in verbose mode + matches = re.findall("VERBOSE RUN", + proc.stdout.decode()) + self.assertEqual(len(matches), 1) + # Assert that the environment was altered too + self.assertIn("QT_LOGGING_RULES", proc.stdout.decode()) + def test_always_crash_crashed(self): + write_xml_log(self.xml_file, failure="always_crash") + proc = self.run3() + self.assertEqual(proc.returncode, 3) + def test_fail_then_pass_2_failed(self): + write_xml_log(self.xml_file, failure="fail_then_pass:2") + proc = self.run3() + self.assertEqual(proc.returncode, 0) + def test_fail_then_pass_5_failed(self): + write_xml_log(self.xml_file, failure="fail_then_pass:5") + proc = self.run3() + self.assertEqual(proc.returncode, 2) + def test_with_two_failures(self): + write_xml_log(self.xml_file, + failure=["always_pass", "fail_then_pass:2"]) + proc = self.run3() + self.assertEqual(proc.returncode, 0) + # Check that test output is properly interleaved with qt-testrunner's logging. + matches = re.findall(r"(PASS|FAIL!).*\n.*Test process exited with code", + proc.stdout.decode()) + self.assertEqual(len(matches), 4) + def test_initTestCase_fail_crash(self): + write_xml_log(self.xml_file, failure="initTestCase") + proc = self.run3() + self.assertEqual(proc.returncode, 3) + + +if __name__ == "__main__": + + DEBUG = False + if "--debug" in sys.argv: + sys.argv.remove("--debug") + DEBUG = True + + # We set failfast=True as we do not want the test suite to continue if the + # tests of qt_mock_test failed. The next ones depend on it. + unittest.main(failfast=True) diff --git a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp index 10a67daa02a..0f8a6da823e 100644 --- a/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp +++ b/tests/auto/widgets/widgets/qcombobox/tst_qcombobox.cpp @@ -3572,6 +3572,7 @@ void tst_QComboBox::popupPositionAfterStyleChange() QFrame *container = box.findChild<QComboBoxPrivateContainer *>(); QVERIFY(container); QVERIFY(QTest::qWaitForWindowExposed(container)); + container->resize(80, 80); // Select the last menu item, which will close the popup. This item is then expected // to be centered on top of the combobox the next time the popup opens. diff --git a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp index 1f57ae43fae..50e8646499d 100644 --- a/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp +++ b/tests/auto/widgets/widgets/qmenu/tst_qmenu.cpp @@ -127,6 +127,7 @@ private slots: #endif void invisibleActions(); + void execReturnsWidgetAction(); protected slots: void onActivated(QAction*); @@ -2335,5 +2336,26 @@ void tst_QMenu::dontSelectDisabledActionByShortcut() } #endif +void tst_QMenu::execReturnsWidgetAction() +{ + QWidget window; + + QMenu menu(&window); + QWidgetAction *widgetAction = new QWidgetAction(&menu); + QPushButton *menuButton = new QPushButton("Button", &menu); + widgetAction->setDefaultWidget(menuButton); + QObject::connect(menuButton, &QPushButton::clicked, widgetAction, &QAction::trigger); + + menu.addAction("First"); + menu.addAction(widgetAction); + menu.addAction("Last"); + + window.show(); + QVERIFY(QTest::qWaitForWindowActive(&window)); + + QTimer::singleShot(0, menuButton, &QPushButton::click); + QCOMPARE(menu.exec(window.geometry().center()), widgetAction); +} + QTEST_MAIN(tst_QMenu) #include "tst_qmenu.moc" diff --git a/tests/benchmarks/gui/painting/qcolor/tst_qcolor.cpp b/tests/benchmarks/gui/painting/qcolor/tst_qcolor.cpp index 02fe3f986e9..54ba45c1256 100644 --- a/tests/benchmarks/gui/painting/qcolor/tst_qcolor.cpp +++ b/tests/benchmarks/gui/painting/qcolor/tst_qcolor.cpp @@ -2,8 +2,24 @@ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <qtest.h> + #include <QColor> +#include <vector> + +static std::vector<QColor> all_rgb_colors() +{ + std::vector<QColor> colors; + colors.reserve(256 * 256 * 256); + for (int r = 0; r < 256; ++r) { + for (int g = 0; g < 256; ++g) { + for (int b = 0; b < 256; ++b) + colors.emplace_back(r, g, b); + } + } + return colors; +} + class tst_QColor : public QObject { @@ -12,6 +28,11 @@ class tst_QColor : public QObject private slots: void nameRgb(); void nameArgb(); + void toHsl(); + void toHsv(); + +private: + const std::vector<QColor> m_all_rgb = all_rgb_colors(); }; void tst_QColor::nameRgb() @@ -32,6 +53,22 @@ void tst_QColor::nameArgb() } } +void tst_QColor::toHsl() +{ + QBENCHMARK { + for (const QColor &c : m_all_rgb) + [[maybe_unused]] const auto r = c.toHsl(); + } +} + +void tst_QColor::toHsv() +{ + QBENCHMARK { + for (const QColor &c : m_all_rgb) + [[maybe_unused]] const auto r = c.toHsv(); + } +} + QTEST_MAIN(tst_QColor) #include "tst_qcolor.moc" |
