diff options
Diffstat (limited to 'tests/auto/corelib/kernel/qobject')
-rw-r--r-- | tests/auto/corelib/kernel/qobject/CMakeLists.txt | 26 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qobject/qobject.pro | 4 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qobject/signalbug/CMakeLists.txt | 11 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qobject/signalbug/signalbug.cpp | 29 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qobject/signalbug/signalbug.h | 29 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qobject/signalbug/signalbug.pro | 6 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qobject/test.pro | 10 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qobject/tst_qobject.cpp | 2037 |
8 files changed, 1676 insertions, 476 deletions
diff --git a/tests/auto/corelib/kernel/qobject/CMakeLists.txt b/tests/auto/corelib/kernel/qobject/CMakeLists.txt new file mode 100644 index 0000000000..46b8e2d238 --- /dev/null +++ b/tests/auto/corelib/kernel/qobject/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## tst_qobject Test: +##################################################################### + +if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT) + cmake_minimum_required(VERSION 3.16) + project(tst_qobject LANGUAGES CXX) + find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST) +endif() + +qt_internal_add_test(tst_qobject + SOURCES + tst_qobject.cpp + LIBRARIES + Qt::CorePrivate + Qt::Network + Qt::TestPrivate +) + +## Scopes: +##################################################################### +add_subdirectory(signalbug) +add_dependencies(tst_qobject signalbug_helper) diff --git a/tests/auto/corelib/kernel/qobject/qobject.pro b/tests/auto/corelib/kernel/qobject/qobject.pro deleted file mode 100644 index 75ad7b5f14..0000000000 --- a/tests/auto/corelib/kernel/qobject/qobject.pro +++ /dev/null @@ -1,4 +0,0 @@ -TEMPLATE = subdirs - -SUBDIRS += test.pro -!winrt: SUBDIRS += signalbug diff --git a/tests/auto/corelib/kernel/qobject/signalbug/CMakeLists.txt b/tests/auto/corelib/kernel/qobject/signalbug/CMakeLists.txt new file mode 100644 index 0000000000..aefa1554b6 --- /dev/null +++ b/tests/auto/corelib/kernel/qobject/signalbug/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## signalbug_helper Binary: +##################################################################### + +qt_internal_add_test_helper(signalbug_helper + SOURCES + signalbug.cpp signalbug.h +) diff --git a/tests/auto/corelib/kernel/qobject/signalbug/signalbug.cpp b/tests/auto/corelib/kernel/qobject/signalbug/signalbug.cpp index 57fb1eb836..bb8d7984f4 100644 --- a/tests/auto/corelib/kernel/qobject/signalbug/signalbug.cpp +++ b/tests/auto/corelib/kernel/qobject/signalbug/signalbug.cpp @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include "signalbug.h" diff --git a/tests/auto/corelib/kernel/qobject/signalbug/signalbug.h b/tests/auto/corelib/kernel/qobject/signalbug/signalbug.h index e6e40c35d9..ac124c2a1c 100644 --- a/tests/auto/corelib/kernel/qobject/signalbug/signalbug.h +++ b/tests/auto/corelib/kernel/qobject/signalbug/signalbug.h @@ -1,30 +1,5 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #ifndef SIGNAL_BUG_H #define SIGNAL_BUG_H diff --git a/tests/auto/corelib/kernel/qobject/signalbug/signalbug.pro b/tests/auto/corelib/kernel/qobject/signalbug/signalbug.pro deleted file mode 100644 index d21b3a62a9..0000000000 --- a/tests/auto/corelib/kernel/qobject/signalbug/signalbug.pro +++ /dev/null @@ -1,6 +0,0 @@ -QT = core - -HEADERS += signalbug.h -SOURCES += signalbug.cpp - -load(qt_test_helper) diff --git a/tests/auto/corelib/kernel/qobject/test.pro b/tests/auto/corelib/kernel/qobject/test.pro deleted file mode 100644 index af5203e152..0000000000 --- a/tests/auto/corelib/kernel/qobject/test.pro +++ /dev/null @@ -1,10 +0,0 @@ -CONFIG += testcase console - -QT = core-private network testlib -TARGET = tst_qobject -SOURCES = tst_qobject.cpp - -# Force C++17 if available (needed due to P0012R1) -contains(QT_CONFIG, c++1z): CONFIG += c++1z - -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp index 5ce70f7a0e..6c387fde96 100644 --- a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp +++ b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp @@ -1,36 +1,22 @@ -/**************************************************************************** -** -** Copyright (C) 2016 The Qt Company Ltd. -** Copyright (C) 2015 Olivier Goffart <ogoffart@woboq.com> -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QtTest/QtTest> +// Copyright (C) 2021 The Qt Company Ltd. +// Copyright (C) 2020 Olivier Goffart <ogoffart@woboq.com> +// Copyright (C) 2021 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only + +// This test actually wants to practice narrowing conditions, so never define this. +#ifdef QT_NO_NARROWING_CONVERSIONS_IN_CONNECT +#undef QT_NO_NARROWING_CONVERSIONS_IN_CONNECT +#endif + +#include <QTest> +#include <QtTest/private/qpropertytesthelper_p.h> +#include <QStringListModel> +#include <QAbstractEventDispatcher> +#include <QScopedValueRollback> #include <qcoreapplication.h> #include <qpointer.h> +#include <qproperty.h> #include <qtimer.h> #include <qregularexpression.h> #include <qmetaobject.h> @@ -49,8 +35,12 @@ #include <private/qobject_p.h> #endif +#include <functional> + #include <math.h> +using namespace Qt::StringLiterals; + class tst_QObject : public QObject { Q_OBJECT @@ -69,6 +59,8 @@ private slots: void disconnectNotify_metaObjConnection(); void connectNotify_connectSlotsByName(); void connectDisconnectNotify_shadowing(); + void connectReferenceToIncompleteTypes(); + void connectAutoQueuedIncomplete(); void emitInDefinedOrder(); void customTypes(); void streamCustomTypes(); @@ -90,10 +82,13 @@ private slots: void signalBlocking(); void blockingQueuedConnection(); void childEvents(); + void parentEvents(); void installEventFilter(); + void installEventFilterOrder(); void deleteSelfInSlot(); void disconnectSelfInSlotAndDeleteAfterEmit(); void dumpObjectInfo(); + void dumpObjectTree(); void connectToSender(); void qobjectConstCast(); void uniqConnection(); @@ -146,14 +141,19 @@ private slots: void connectBase(); void connectWarnings(); void qmlConnect(); + void qmlConnectToQObjectReceiver(); void exceptions(); - void noDeclarativeParentChangedOnDestruction(); void deleteLaterInAboutToBlockHandler(); void mutableFunctor(); void checkArgumentsForNarrowing(); void nullReceiver(); void functorReferencesConnection(); void disconnectDisconnects(); + void singleShotConnection(); + void objectNameBinding(); + void emitToDestroyedClass(); + void declarativeData(); + void asyncCallbackHelper(); }; struct QObjectCreatedOnShutdown @@ -207,7 +207,11 @@ protected: Q_INVOKABLE QT_MOC_COMPAT void invoke2(int){} Q_SCRIPTABLE QT_MOC_COMPAT void sinvoke2(){} private: - Q_INVOKABLE void invoke3(int hinz = 0, int kunz = 0){Q_UNUSED(hinz) Q_UNUSED(kunz)} + Q_INVOKABLE void invoke3(int hinz = 0, int kunz = 0) + { + Q_UNUSED(hinz); + Q_UNUSED(kunz); + } Q_SCRIPTABLE void sinvoke3(){} int recursionCount; @@ -426,15 +430,12 @@ public: public slots: void on_Sender_signalNoParams() { called_slots << 1; } - void on_Sender_signalWithParams(int i = 0) { called_slots << 2; Q_UNUSED(i); } - void on_Sender_signalWithParams(int i, QString string) { called_slots << 3; Q_UNUSED(i);Q_UNUSED(string); } + void on_Sender_signalWithParams(int = 0) { called_slots << 2; } + void on_Sender_signalWithParams(int, QString) { called_slots << 3; } void on_Sender_signalManyParams() { called_slots << 4; } - void on_Sender_signalManyParams(int i1, int i2, int i3, QString string, bool onoff) - { called_slots << 5; Q_UNUSED(i1);Q_UNUSED(i2);Q_UNUSED(i3);Q_UNUSED(string);Q_UNUSED(onoff); } - void on_Sender_signalManyParams(int i1, int i2, int i3, QString string, bool onoff, bool dummy) - { called_slots << 6; Q_UNUSED(i1);Q_UNUSED(i2);Q_UNUSED(i3);Q_UNUSED(string);Q_UNUSED(onoff); Q_UNUSED(dummy);} - void on_Sender_signalManyParams2(int i1, int i2, int i3, QString string, bool onoff) - { called_slots << 7; Q_UNUSED(i1);Q_UNUSED(i2);Q_UNUSED(i3);Q_UNUSED(string);Q_UNUSED(onoff); } + void on_Sender_signalManyParams(int, int, int, QString, bool) { called_slots << 5; } + void on_Sender_signalManyParams(int, int, int, QString, bool, bool) { called_slots << 6; } + void on_Sender_signalManyParams2(int, int, int, QString, bool) { called_slots << 7; } void slotLoopBack() { called_slots << 8; } void on_Receiver_signalNoParams() { called_slots << 9; } void on_Receiver_signal_with_underscore() { called_slots << 10; } @@ -460,7 +461,7 @@ void tst_QObject::connectSlotsByName() sender.setObjectName("Sender"); QTest::ignoreMessage(QtWarningMsg, "QMetaObject::connectSlotsByName: No matching signal for on_child_signal()"); - QTest::ignoreMessage(QtWarningMsg, "QMetaObject::connectSlotsByName: Connecting slot on_Sender_signalManyParams() with the first of the following compatible signals: QVector(\"signalManyParams(int,int,int,QString,bool)\", \"signalManyParams(int,int,int,QString,bool,bool)\")"); + QTest::ignoreMessage(QtWarningMsg, "QMetaObject::connectSlotsByName: Connecting slot on_Sender_signalManyParams() with the first of the following compatible signals: QList(\"signalManyParams(int,int,int,QString,bool)\", \"signalManyParams(int,int,int,QString,bool,bool)\")"); QMetaObject::connectSlotsByName(&receiver); receiver.called_slots.clear(); @@ -510,6 +511,12 @@ void tst_QObject::qobject_castTemplate() QVERIFY(!::qobject_cast<ReceiverObject*>(o.data())); } +class DerivedObj : public QObject { + Q_OBJECT +public: + using QObject::QObject; +}; + void tst_QObject::findChildren() { QObject o; @@ -522,6 +529,10 @@ void tst_QObject::findChildren() QTimer t1(&o); QTimer t121(&o12); QTimer emptyname(&o); + QObject oo; + QObject o21(&oo); + QObject o22(&oo); + QObject o23(&oo); Q_SET_OBJECT_NAME(o); Q_SET_OBJECT_NAME(o1); @@ -532,8 +543,15 @@ void tst_QObject::findChildren() Q_SET_OBJECT_NAME(t1); Q_SET_OBJECT_NAME(t121); emptyname.setObjectName(""); + Q_SET_OBJECT_NAME(oo); + const QUtf8StringView utf8_name = u8"utf8 ⁎ obj"; + o21.setObjectName(utf8_name); + const QStringView utf16_name = u"utf16 ⁎ obj"; + o22.setObjectName(utf16_name); + constexpr QLatin1StringView L1_name("L1 ⁎ obj"); + o23.setObjectName(L1_name); - QObject *op = 0; + QObject *op = nullptr; op = o.findChild<QObject*>("o1"); QCOMPARE(op, &o1); @@ -562,6 +580,27 @@ void tst_QObject::findChildren() op = o.findChild<QObject*>("o1"); QCOMPARE(op, &o1); + op = oo.findChild<QObject*>(utf8_name); + QCOMPARE(op, &o21); + op = oo.findChild<QObject*>(utf8_name.chopped(1)); + QCOMPARE(op, nullptr); + const QUtf8StringView utf8_name_with_trailing_data = u8"utf8 ⁎ obj_data"; + op = oo.findChild<QObject*>(utf8_name_with_trailing_data.chopped(5)); + QCOMPARE(op, &o21); + op = oo.findChild<QObject*>(utf16_name); + QCOMPARE(op, &o22); + op = oo.findChild<QObject*>(utf16_name.chopped(1)); + QCOMPARE(op, nullptr); + const QStringView utf16_name_with_trailing_data = u"utf16 ⁎ obj_data"; + op = oo.findChild<QObject*>(utf16_name_with_trailing_data.chopped(5)); + QCOMPARE(op, &o22); + op = oo.findChild<QObject*>(L1_name); + QCOMPARE(op, &o23); + op = oo.findChild<QObject*>(L1_name.chopped(1)); + QCOMPARE(op, nullptr); + op = oo.findChild<QObject*>((L1_name + "_data"_L1).chopped(5)); + QCOMPARE(op, &o23); + QList<QObject*> l; QList<QTimer*> tl; @@ -737,7 +776,20 @@ void tst_QObject::findChildren() l = o.findChildren<QObject*>(QRegularExpression("^harry$"), Qt::FindDirectChildrenOnly); QCOMPARE(l.size(), 0); + DerivedObj dr1(&o111); + DerivedObj dr2(&o111); + Q_SET_OBJECT_NAME(dr1); + Q_SET_OBJECT_NAME(dr2); + // empty and null string check + op = o.findChild<QObject*>(Qt::FindDirectChildrenOnly); + QCOMPARE(op, &o1); + op = o.findChild<QTimer*>(Qt::FindDirectChildrenOnly); + QCOMPARE(op, &t1); + op = o.findChild<DerivedObj*>(Qt::FindDirectChildrenOnly); + QCOMPARE(op, nullptr); + op = o.findChild<DerivedObj*>(Qt::FindChildrenRecursively); + QCOMPARE(op, &dr1); op = o.findChild<QObject*>(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(op, &o1); op = o.findChild<QObject*>("", Qt::FindDirectChildrenOnly); @@ -745,6 +797,8 @@ void tst_QObject::findChildren() op = o.findChild<QObject*>("unnamed", Qt::FindDirectChildrenOnly); QCOMPARE(op, static_cast<QObject *>(0)); + l = o.findChildren<QObject*>(Qt::FindDirectChildrenOnly); + QCOMPARE(l.size(), 5); l = o.findChildren<QObject*>(QString(), Qt::FindDirectChildrenOnly); QCOMPARE(l.size(), 5); l = o.findChildren<QObject*>("", Qt::FindDirectChildrenOnly); @@ -772,9 +826,9 @@ public: disconnectedSignals.clear(); } protected: - void connectNotify(const QMetaMethod &signal) + void connectNotify(const QMetaMethod &signal) override { connectedSignals.append(signal); } - void disconnectNotify(const QMetaMethod &signal) + void disconnectNotify(const QMetaMethod &signal) override { disconnectedSignals.append(signal); } }; @@ -877,6 +931,61 @@ void tst_QObject::connectDisconnectNotify() QCOMPARE(s.connectedSignals.size(), 1); } +struct Incomplete; +class QObjectWithIncomplete : public QObject { + Q_OBJECT + +public: + QObjectWithIncomplete(QObject *parent=nullptr) : QObject(parent) {} +signals: + void signalWithIncomplete(const Incomplete &); +public slots: + void slotWithIncomplete(const Incomplete &) {} +}; + +void tst_QObject::connectReferenceToIncompleteTypes() { + QObjectWithIncomplete withIncomplete; + auto connection = QObject::connect(&withIncomplete, &QObjectWithIncomplete::signalWithIncomplete, + &withIncomplete, &QObjectWithIncomplete::slotWithIncomplete); + QVERIFY(connection); +} + +struct Incomplete2; +class QObjectWithIncomplete2 : public QObject { + Q_OBJECT + +public: + QObjectWithIncomplete2(QObject *parent=nullptr) : QObject(parent) {} +signals: + void signalWithIncomplete(Incomplete2 *ptr); +public slots: + void slotWithIncomplete(Incomplete2 *) { calledSlot = true; } + void run() { Q_EMIT signalWithIncomplete(nullptr); } +public: + bool calledSlot = false; +}; + +void tst_QObject::connectAutoQueuedIncomplete() +{ + auto objectWithIncomplete1 = new QObjectWithIncomplete2(); + auto objectWithIncomplete2 = new QObjectWithIncomplete2(); + auto t = new QThread(this); + auto cleanup = qScopeGuard([&](){ + t->quit(); + QVERIFY(t->wait()); + delete objectWithIncomplete1; + delete objectWithIncomplete2; + }); + + t->start(); + objectWithIncomplete2->moveToThread(t); + + connect(objectWithIncomplete2, &QObjectWithIncomplete2::signalWithIncomplete, + objectWithIncomplete1, &QObjectWithIncomplete2::slotWithIncomplete); + QMetaObject::invokeMethod(objectWithIncomplete2, "run", Qt::QueuedConnection); + QTRY_VERIFY(objectWithIncomplete1->calledSlot); +} + static void connectDisconnectNotifyTestSlot() {} void tst_QObject::connectDisconnectNotifyPMF() @@ -944,7 +1053,7 @@ void tst_QObject::disconnectNotify_receiverDestroyed() QVERIFY(QObject::connect((SenderObject *)&s, SIGNAL(signal1()), (ReceiverObject *)&r, SLOT(slot1()))); } - QCOMPARE(s.disconnectedSignals.count(), 1); + QCOMPARE(s.disconnectedSignals.size(), 1); QCOMPARE(s.disconnectedSignals.at(0), QMetaMethod::fromSignal(&SenderObject::signal1)); s.disconnectedSignals.clear(); @@ -955,7 +1064,7 @@ void tst_QObject::disconnectNotify_receiverDestroyed() (ReceiverObject *)&r, SLOT(slot3()))); } - QCOMPARE(s.disconnectedSignals.count(), 1); + QCOMPARE(s.disconnectedSignals.size(), 1); QCOMPARE(s.disconnectedSignals.at(0), QMetaMethod::fromSignal(&SenderObject::signal3)); s.disconnectedSignals.clear(); @@ -965,7 +1074,7 @@ void tst_QObject::disconnectNotify_receiverDestroyed() QVERIFY(QObject::connect((SenderObject *)&s, SIGNAL(destroyed()), (ReceiverObject *)&r, SLOT(slot3()))); } - QCOMPARE(s.disconnectedSignals.count(), 1); + QCOMPARE(s.disconnectedSignals.size(), 1); QCOMPARE(s.disconnectedSignals.at(0), QMetaMethod::fromSignal(&QObject::destroyed)); } @@ -980,10 +1089,10 @@ void tst_QObject::disconnectNotify_metaObjConnection() QVERIFY(c); QVERIFY(QObject::disconnect(c)); - QCOMPARE(s.disconnectedSignals.count(), 1); + QCOMPARE(s.disconnectedSignals.size(), 1); QCOMPARE(s.disconnectedSignals.at(0), QMetaMethod::fromSignal(&SenderObject::signal1)); - QCOMPARE(s.disconnectedSignals.count(), 1); + QCOMPARE(s.disconnectedSignals.size(), 1); } } @@ -999,9 +1108,9 @@ public: disconnectedSignals.clear(); } protected: - void connectNotify(const QMetaMethod &signal) + void connectNotify(const QMetaMethod &signal) override { connectedSignals.append(signal); } - void disconnectNotify(const QMetaMethod &signal) + void disconnectNotify(const QMetaMethod &signal) override { disconnectedSignals.append(signal); } Q_SIGNALS: void signal1(); @@ -1404,6 +1513,20 @@ struct CustomType int value() { return i1 + i2 + i3; } }; +QDataStream &operator<<(QDataStream &stream, const CustomType &ct) +{ + stream << ct.i1 << ct.i2 << ct.i3; + return stream; +} + +QDataStream &operator>>(QDataStream &stream, CustomType &ct) +{ + stream >> ct.i1; + stream >> ct.i2; + stream >> ct.i3; + return stream; +} + Q_DECLARE_METATYPE(CustomType*) Q_DECLARE_METATYPE(CustomType) @@ -1412,7 +1535,7 @@ class QCustomTypeChecker: public QObject Q_OBJECT public: - QCustomTypeChecker(QObject *parent = 0): QObject(parent) {} + QCustomTypeChecker(QObject *parent = nullptr): QObject(parent) {} void doEmit(CustomType ct) { emit signal1(ct); } @@ -1451,8 +1574,7 @@ void tst_QObject::customTypes() QCOMPARE(checker.received.value(), t1.value()); checker.received = t0; - int idx = qRegisterMetaType<CustomType>("CustomType"); - QCOMPARE(QMetaType::type("CustomType"), idx); + qRegisterMetaType<CustomType>(); checker.disconnect(); connect(&checker, SIGNAL(signal1(CustomType)), &checker, SLOT(slot1(CustomType)), @@ -1465,41 +1587,23 @@ void tst_QObject::customTypes() QCoreApplication::processEvents(); QCOMPARE(checker.received.value(), t2.value()); QCOMPARE(instanceCount, 4); - - QVERIFY(QMetaType::isRegistered(idx)); - QCOMPARE(qRegisterMetaType<CustomType>("CustomType"), idx); - QCOMPARE(QMetaType::type("CustomType"), idx); - QVERIFY(QMetaType::isRegistered(idx)); } QCOMPARE(instanceCount, 3); } -QDataStream &operator<<(QDataStream &stream, const CustomType &ct) -{ - stream << ct.i1 << ct.i2 << ct.i3; - return stream; -} - -QDataStream &operator>>(QDataStream &stream, CustomType &ct) -{ - stream >> ct.i1; - stream >> ct.i2; - stream >> ct.i3; - return stream; -} - void tst_QObject::streamCustomTypes() { QByteArray ba; - int idx = qRegisterMetaType<CustomType>("CustomType"); - qRegisterMetaTypeStreamOperators<CustomType>("CustomType"); + qRegisterMetaType<CustomType>(); + + QMetaType metaType = QMetaType::fromType<CustomType>(); { CustomType t1(1, 2, 3); QCOMPARE(instanceCount, 1); QDataStream stream(&ba, (QIODevice::OpenMode)QIODevice::WriteOnly); - QMetaType::save(stream, idx, &t1); + metaType.save(stream, &t1); } QCOMPARE(instanceCount, 0); @@ -1508,7 +1612,7 @@ void tst_QObject::streamCustomTypes() CustomType t2; QCOMPARE(instanceCount, 1); QDataStream stream(&ba, (QIODevice::OpenMode)QIODevice::ReadOnly); - QMetaType::load(stream, idx, &t2); + metaType.load(stream, &t2); QCOMPARE(instanceCount, 1); QCOMPARE(t2.i1, 1); QCOMPARE(t2.i2, 2); @@ -1603,7 +1707,7 @@ class TestThread : public QThread { Q_OBJECT public: - inline void run() + inline void run() override { *object = new QObject; *child = new QObject(*object); @@ -1635,8 +1739,8 @@ void tst_QObject::thread() QCOMPARE(child.thread(), object.thread()); } - QObject *object = 0; - QObject *child = 0; + QObject *object = nullptr; + QObject *child = nullptr; { TestThread thr; @@ -1678,15 +1782,15 @@ class MoveToThreadObject : public QObject { Q_OBJECT public: - QThread *timerEventThread; - QThread *customEventThread; - QThread *slotThread; + QThread *timerEventThread = nullptr; + QThread *customEventThread = nullptr; + QThread *slotThread = nullptr; - MoveToThreadObject(QObject *parent = 0) - : QObject(parent), timerEventThread(0), customEventThread(0), slotThread(0) + MoveToThreadObject(QObject *parent = nullptr) + : QObject(parent) { } - void customEvent(QEvent *) + void customEvent(QEvent *) override { if (customEventThread) qFatal("%s: customEventThread should be null", Q_FUNC_INFO); @@ -1694,7 +1798,7 @@ public: emit theSignal(); } - void timerEvent(QTimerEvent *) + void timerEvent(QTimerEvent *) override { if (timerEventThread) qFatal("%s: timerEventThread should be null", Q_FUNC_INFO); @@ -1733,7 +1837,7 @@ public: // wait for thread to start (void) eventLoop.exec(); } - void run() + void run() override { (void) exec(); } }; @@ -1767,13 +1871,15 @@ void tst_QObject::moveToThread() QObject *child = new QObject(object); QCOMPARE(object->thread(), currentThread); QCOMPARE(child->thread(), currentThread); - object->moveToThread(0); + QVERIFY(object->moveToThread(nullptr)); QCOMPARE(object->thread(), (QThread *)0); QCOMPARE(child->thread(), (QThread *)0); - object->moveToThread(currentThread); + QVERIFY(object->moveToThread(currentThread)); QCOMPARE(object->thread(), currentThread); QCOMPARE(child->thread(), currentThread); - object->moveToThread(0); + QTest::ignoreMessage(QtWarningMsg, "QObject::moveToThread: Cannot move objects with a parent"); + QVERIFY(!child->moveToThread(nullptr)); + QVERIFY(object->moveToThread(nullptr)); QCOMPARE(object->thread(), (QThread *)0); QCOMPARE(child->thread(), (QThread *)0); // can delete an object with no thread anywhere @@ -1862,8 +1968,6 @@ void tst_QObject::moveToThread() thread.wait(); } - // WinRT does not allow connection to localhost -#ifndef Q_OS_WINRT { // make sure socket notifiers are moved with the object MoveToThreadThread thread; @@ -1899,7 +2003,6 @@ void tst_QObject::moveToThread() QMetaObject::invokeMethod(socket, "deleteLater", Qt::QueuedConnection); thread.wait(); } -#endif } @@ -1913,8 +2016,8 @@ void tst_QObject::property() QVERIFY(mo->indexOfProperty("alpha") != -1); property = mo->property(mo->indexOfProperty("alpha")); QVERIFY(property.isEnumType()); - QCOMPARE(property.typeName(), "Alpha"); - QCOMPARE(property.type(), QVariant::Int); + QCOMPARE(property.typeName(), "PropertyObject::Alpha"); + QCOMPARE(property.userType(), QMetaType::fromType<PropertyObject::Alpha>().id()); QVariant var = object.property("alpha"); QVERIFY(!var.isNull()); @@ -1925,7 +2028,8 @@ void tst_QObject::property() QCOMPARE(object.property("alpha").toInt(), int(PropertyObject::Alpha2)); QVERIFY(object.setProperty("alpha", "Alpha1")); QCOMPARE(object.property("alpha").toInt(), int(PropertyObject::Alpha1)); - QVERIFY(!object.setProperty("alpha", QVariant())); + QVERIFY(object.setProperty("alpha", QVariant())); + QCOMPARE(object.property("alpha").toInt(), 0); QVERIFY(mo->indexOfProperty("number") != -1); QCOMPARE(object.property("number").toInt(), 0); @@ -1946,7 +2050,7 @@ void tst_QObject::property() const int idx = mo->indexOfProperty("variant"); QVERIFY(idx != -1); - QCOMPARE(QMetaType::Type(mo->property(idx).type()), QMetaType::QVariant); + QCOMPARE(mo->property(idx).userType(), QMetaType::QVariant); QCOMPARE(object.property("variant"), QVariant()); QVariant variant1(42); QVariant variant2("string"); @@ -1965,10 +2069,10 @@ void tst_QObject::property() QVERIFY(!property.isEnumType()); QCOMPARE(property.typeName(), "CustomType*"); qRegisterMetaType<CustomType*>(); - QCOMPARE(property.type(), QVariant::UserType); + QCOMPARE_GE(property.typeId(), QMetaType::User); QCOMPARE(property.userType(), qMetaTypeId<CustomType*>()); - CustomType *customPointer = 0; + CustomType *customPointer = nullptr; QVariant customVariant = object.property("custom"); customPointer = qvariant_cast<CustomType *>(customVariant); QCOMPARE(customPointer, object.custom()); @@ -1980,7 +2084,7 @@ void tst_QObject::property() property = mo->property(mo->indexOfProperty("custom")); QVERIFY(property.isWritable()); QCOMPARE(property.typeName(), "CustomType*"); - QCOMPARE(property.type(), QVariant::UserType); + QCOMPARE_GE(property.typeId(), QMetaType::User); QCOMPARE(property.userType(), qMetaTypeId<CustomType*>()); QVERIFY(object.setProperty("custom", customVariant)); @@ -1994,8 +2098,8 @@ void tst_QObject::property() QVERIFY(mo->indexOfProperty("priority") != -1); property = mo->property(mo->indexOfProperty("priority")); QVERIFY(property.isEnumType()); - QCOMPARE(property.typeName(), "Priority"); - QCOMPARE(property.type(), QVariant::Int); + QCOMPARE(property.typeName(), "PropertyObject::Priority"); + QCOMPARE(property.userType(), QMetaType::fromType<PropertyObject::Priority>().id()); var = object.property("priority"); QVERIFY(!var.isNull()); @@ -2006,16 +2110,17 @@ void tst_QObject::property() QCOMPARE(object.property("priority").toInt(), int(PropertyObject::VeryHigh)); QVERIFY(object.setProperty("priority", "High")); QCOMPARE(object.property("priority").toInt(), int(PropertyObject::High)); - QVERIFY(!object.setProperty("priority", QVariant())); + QVERIFY(object.setProperty("priority", QVariant())); + QCOMPARE(object.property("priority").toInt(), 0); // now it's registered, so it works as expected - int priorityMetaTypeId = qRegisterMetaType<PropertyObject::Priority>("PropertyObject::Priority"); + int priorityMetaTypeId = qRegisterMetaType<PropertyObject::Priority>(); QVERIFY(mo->indexOfProperty("priority") != -1); property = mo->property(mo->indexOfProperty("priority")); QVERIFY(property.isEnumType()); - QCOMPARE(property.typeName(), "Priority"); - QCOMPARE(property.type(), QVariant::UserType); + QCOMPARE(property.typeName(), "PropertyObject::Priority"); + QCOMPARE_GE(property.typeId(), QMetaType::User); QCOMPARE(property.userType(), priorityMetaTypeId); var = object.property("priority"); @@ -2028,7 +2133,8 @@ void tst_QObject::property() QCOMPARE(qvariant_cast<PropertyObject::Priority>(object.property("priority")), PropertyObject::VeryHigh); QVERIFY(object.setProperty("priority", "High")); QCOMPARE(qvariant_cast<PropertyObject::Priority>(object.property("priority")), PropertyObject::High); - QVERIFY(!object.setProperty("priority", QVariant())); + QVERIFY(object.setProperty("priority", QVariant())); + QCOMPARE(object.property("priority").toInt(), 0); var = object.property("priority"); QCOMPARE(qvariant_cast<PropertyObject::Priority>(var), PropertyObject::High); @@ -2037,7 +2143,7 @@ void tst_QObject::property() object.setProperty("priority", var); QCOMPARE(qvariant_cast<PropertyObject::Priority>(object.property("priority")), PropertyObject::High); - qRegisterMetaType<CustomString>("CustomString"); + qRegisterMetaType<CustomString>(); QVERIFY(mo->indexOfProperty("customString") != -1); QCOMPARE(object.property("customString").toString(), QString()); object.setCustomString("String1"); @@ -2110,18 +2216,18 @@ void tst_QObject::metamethod() QVERIFY(!(m.attributes() & QMetaMethod::Compatibility)); m = mobj->method(mobj->indexOfMethod("invoke1()")); - QCOMPARE(m.parameterNames().count(), 0); - QCOMPARE(m.parameterTypes().count(), 0); + QCOMPARE(m.parameterNames().size(), 0); + QCOMPARE(m.parameterTypes().size(), 0); m = mobj->method(mobj->indexOfMethod("invoke2(int)")); - QCOMPARE(m.parameterNames().count(), 1); - QCOMPARE(m.parameterTypes().count(), 1); + QCOMPARE(m.parameterNames().size(), 1); + QCOMPARE(m.parameterTypes().size(), 1); QCOMPARE(m.parameterTypes().at(0), QByteArray("int")); QVERIFY(m.parameterNames().at(0).isEmpty()); m = mobj->method(mobj->indexOfMethod("invoke3(int,int)")); - QCOMPARE(m.parameterNames().count(), 2); - QCOMPARE(m.parameterTypes().count(), 2); + QCOMPARE(m.parameterNames().size(), 2); + QCOMPARE(m.parameterTypes().size(), 2); QCOMPARE(m.parameterTypes().at(0), QByteArray("int")); QCOMPARE(m.parameterNames().at(0), QByteArray("hinz")); QCOMPARE(m.parameterTypes().at(1), QByteArray("int")); @@ -2163,7 +2269,7 @@ public: SuperObject() { - theSender = 0; + theSender = nullptr; theSignalId = 0; } @@ -2227,7 +2333,7 @@ void tst_QObject::senderTest() QCOMPARE(receiver->sender(), (QObject *)0); QCOMPARE(receiver->senderSignalIndex(), -1); - receiver->theSender = 0; + receiver->theSender = nullptr; receiver->theSignalId = -1; thread.start(); emit sender->theSignal(); @@ -2308,7 +2414,7 @@ class FooObject: public QObject, public Foo::Bar Q_OBJECT Q_INTERFACES(Foo::Bar) public: - int rtti() const { return 42; } + int rtti() const override { return 42; } }; class BlehObject : public QObject, public Foo::Bleh @@ -2316,7 +2422,7 @@ class BlehObject : public QObject, public Foo::Bleh Q_OBJECT Q_INTERFACES(Foo::Bleh) public: - int rtti() const { return 43; } + int rtti() const override { return 43; } }; void tst_QObject::declareInterface() @@ -2876,7 +2982,7 @@ void tst_QObject::floatProperty() QVERIFY(idx > 0); QMetaProperty prop = obj.metaObject()->property(idx); QVERIFY(prop.isValid()); - QCOMPARE(int(prop.type()), QMetaType::type("float")); + QCOMPARE(prop.typeId(), QMetaType::fromType<float>().id()); QVERIFY(!prop.write(&obj, QVariant("Hello"))); QVERIFY(prop.write(&obj, QVariant::fromValue(128.0f))); QVariant v = prop.read(&obj); @@ -2891,7 +2997,7 @@ void tst_QObject::qrealProperty() QVERIFY(idx > 0); QMetaProperty prop = obj.metaObject()->property(idx); QVERIFY(prop.isValid()); - QCOMPARE(int(prop.type()), QMetaType::type("qreal")); + QCOMPARE(prop.typeId(), QMetaType::fromType<qreal>().id()); QVERIFY(!prop.write(&obj, QVariant("Hello"))); QVERIFY(prop.write(&obj, QVariant::fromValue(128.0f))); @@ -2910,7 +3016,7 @@ class DynamicPropertyObject : public PropertyObject public: inline DynamicPropertyObject() {} - inline virtual bool event(QEvent *e) { + inline virtual bool event(QEvent *e) override { if (e->type() == QEvent::DynamicPropertyChange) { changedDynamicProperties.append(static_cast<QDynamicPropertyChangeEvent *>(e)->propertyName()); } @@ -2936,31 +3042,31 @@ void tst_QObject::dynamicProperties() // set a dynamic property QVERIFY(!obj.setProperty("myuserproperty", "Hello")); - QCOMPARE(obj.changedDynamicProperties.count(), 1); + QCOMPARE(obj.changedDynamicProperties.size(), 1); QCOMPARE(obj.changedDynamicProperties.first(), QByteArray("myuserproperty")); //check if there is no redundant DynamicPropertyChange events QVERIFY(!obj.setProperty("myuserproperty", "Hello")); - QCOMPARE(obj.changedDynamicProperties.count(), 1); + QCOMPARE(obj.changedDynamicProperties.size(), 1); - QCOMPARE(obj.property("myuserproperty").type(), QVariant::String); + QCOMPARE(obj.property("myuserproperty").typeId(), QMetaType::QString); QCOMPARE(obj.property("myuserproperty").toString(), QString("Hello")); - QCOMPARE(obj.dynamicPropertyNames().count(), 1); + QCOMPARE(obj.dynamicPropertyNames().size(), 1); QCOMPARE(obj.dynamicPropertyNames().first(), QByteArray("myuserproperty")); // change type of the dynamic property obj.changedDynamicProperties.clear(); QVERIFY(!obj.setProperty("myuserproperty", QByteArray("Hello"))); - QCOMPARE(obj.changedDynamicProperties.count(), 1); + QCOMPARE(obj.changedDynamicProperties.size(), 1); QCOMPARE(obj.changedDynamicProperties.first(), QByteArray("myuserproperty")); - QCOMPARE(obj.property("myuserproperty").type(), QVariant::ByteArray); + QCOMPARE(obj.property("myuserproperty").typeId(), QMetaType::QByteArray); QCOMPARE(obj.property("myuserproperty").toString(), QByteArray("Hello")); // unset the property obj.changedDynamicProperties.clear(); QVERIFY(!obj.setProperty("myuserproperty", QVariant())); - QCOMPARE(obj.changedDynamicProperties.count(), 1); + QCOMPARE(obj.changedDynamicProperties.size(), 1); QCOMPARE(obj.changedDynamicProperties.first(), QByteArray("myuserproperty")); obj.changedDynamicProperties.clear(); @@ -2976,7 +3082,7 @@ void tst_QObject::recursiveSignalEmission() #else QProcess proc; // signalbug helper app should always be next to this test binary - const QString path = QStringLiteral("signalbug_helper"); + const QString path = QCoreApplication::applicationDirPath() + QDir::separator() + QStringLiteral("signalbug_helper"); proc.start(path); QVERIFY2(proc.waitForStarted(), qPrintable(QString::fromLatin1("Cannot start '%1': %2").arg(path, proc.errorString()))); QVERIFY(proc.waitForFinished()); @@ -3036,6 +3142,8 @@ void tst_QObject::blockingQueuedConnection() } } +static int s_eventSpyCounter = -1; + class EventSpy : public QObject { Q_OBJECT @@ -3043,7 +3151,7 @@ class EventSpy : public QObject public: typedef QList<QPair<QObject *, QEvent::Type> > EventList; - EventSpy(QObject *parent = 0) + EventSpy(QObject *parent = nullptr) : QObject(parent) { } @@ -3055,14 +3163,17 @@ public: void clear() { events.clear(); + thisCounter = -1; } - bool eventFilter(QObject *object, QEvent *event) + bool eventFilter(QObject *object, QEvent *event) override { events.append(qMakePair(object, event->type())); + thisCounter = ++s_eventSpyCounter; return false; } + int thisCounter = -1; private: EventList events; }; @@ -3151,6 +3262,78 @@ void tst_QObject::childEvents() } } +void tst_QObject::parentEvents() +{ +#ifdef QT_BUILD_INTERNAL + EventSpy::EventList expected; + + { + // Parent events not enabled + QObject parent; + QObject child; + + EventSpy spy; + child.installEventFilter(&spy); + + QCoreApplication::postEvent(&child, new QEvent(QEvent::Type(QEvent::User + 1))); + + child.setParent(&parent); + + QCoreApplication::postEvent(&child, new QEvent(QEvent::Type(QEvent::User + 2))); + + expected = + EventSpy::EventList(); + QCOMPARE(spy.eventList(), expected); + spy.clear(); + + QCoreApplication::processEvents(); + + expected = + EventSpy::EventList() + << qMakePair(&child, QEvent::Type(QEvent::User + 1)) + << qMakePair(&child, QEvent::Type(QEvent::User + 2)); + QCOMPARE(spy.eventList(), expected); + } + + { + // Parent events enabled + QObject parent; + QObject child; + auto *childPrivate = QObjectPrivate::get(&child); + childPrivate->receiveParentEvents = true; + + EventSpy spy; + child.installEventFilter(&spy); + + QCoreApplication::postEvent(&child, new QEvent(QEvent::Type(QEvent::User + 1))); + + child.setParent(&parent); + child.setParent(nullptr); + + QCoreApplication::postEvent(&child, new QEvent(QEvent::Type(QEvent::User + 2))); + + expected = + EventSpy::EventList() + << qMakePair(&child, QEvent::ParentAboutToChange) + << qMakePair(&child, QEvent::ParentChange) + << qMakePair(&child, QEvent::ParentAboutToChange) + << qMakePair(&child, QEvent::ParentChange); + QCOMPARE(spy.eventList(), expected); + spy.clear(); + + QCoreApplication::processEvents(); + + expected = + EventSpy::EventList() + << qMakePair(&child, QEvent::Type(QEvent::User + 1)) + << qMakePair(&child, QEvent::Type(QEvent::User + 2)); + QCOMPARE(spy.eventList(), expected); + } +#else + QSKIP("Needs QT_BUILD_INTERNAL"); +#endif +} + void tst_QObject::installEventFilter() { QEvent event(QEvent::User); @@ -3192,10 +3375,74 @@ void tst_QObject::installEventFilter() QVERIFY(spy.eventList().isEmpty()); } +#define CHECK_FAIL(message) \ +do {\ + if (QTest::currentTestFailed())\ + QFAIL("failed one line above on " message);\ +} while (false) + +void tst_QObject::installEventFilterOrder() +{ + // installEventFilter() adds new objects to d_func()->extraData->eventFilters, which + // affects the order of calling each object's eventFilter() when processing the events. + + QObject object; + EventSpy spy1, spy2, spy3; + + auto clearSignalSpies = [&] { + for (auto *s : {&spy1, &spy2, &spy3}) + s->clear(); + s_eventSpyCounter = -1; + }; + + const EventSpy::EventList expected = { { &object, QEvent::Type(QEvent::User + 1) } }; + + // Call Order: from first to last + auto checkCallOrder = [&expected](const QList<EventSpy *> &spies) { + for (int i = 0; i < spies.size(); ++i) { + EventSpy *spy = spies.at(i); + QVERIFY2(spy->eventList() == expected, + QString("The spy %1 wasn't triggered exactly once.").arg(i).toLatin1()); + QCOMPARE(spy->thisCounter, i); + } + }; + + // Install event filters and check the order of invocations: + // The last installed = the first called. + object.installEventFilter(&spy1); + object.installEventFilter(&spy2); + object.installEventFilter(&spy3); + clearSignalSpies(); + QCoreApplication::postEvent(&object, new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + checkCallOrder({ &spy3, &spy2, &spy1 }); + CHECK_FAIL("checkCallOrder() - 1st round"); + + // Install event filter for `spy1` again, which reorders spy1 in `eventFilters` + // (the list doesn't have duplicates). + object.installEventFilter(&spy1); + clearSignalSpies(); + QCoreApplication::postEvent(&object, new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + checkCallOrder({ &spy1, &spy3, &spy2 }); + CHECK_FAIL("checkCallOrder() - 2nd round"); + + // Remove event filter for `spy3`, ensure it's not called anymore and the + // existing filters order is preserved. + object.removeEventFilter(&spy3); + clearSignalSpies(); + QCoreApplication::postEvent(&object, new QEvent(QEvent::Type(QEvent::User + 1))); + QCoreApplication::processEvents(); + checkCallOrder({ &spy1, &spy2 }); + CHECK_FAIL("checkCallOrder() - 3rd round"); + QVERIFY(spy3.eventList().isEmpty()); + QCOMPARE(spy3.thisCounter, -1); +} + class EmitThread : public QThread { Q_OBJECT public: - void run(void) { + void run(void) override { emit work(); } signals: @@ -3375,6 +3622,32 @@ void tst_QObject::dumpObjectInfo() a.dumpObjectInfo(); // should not crash } +void tst_QObject::dumpObjectTree() +{ + QObject a; + Q_SET_OBJECT_NAME(a); + + QTimer b(&a); + Q_SET_OBJECT_NAME(b); + + QObject c(&b); + Q_SET_OBJECT_NAME(c); + + QFile f(&a); + Q_SET_OBJECT_NAME(f); + + const char * const output[] = { + "QObject::a ", + " QTimer::b ", + " QObject::c ", + " QFile::f ", + }; + for (const char *line : output) + QTest::ignoreMessage(QtDebugMsg, line); + + a.dumpObjectTree(); +} + class ConnectToSender : public QObject { Q_OBJECT public slots: @@ -3552,8 +3825,6 @@ void tst_QObject::interfaceIid() QByteArray(Bleh_iid)); QCOMPARE(QByteArray(qobject_interface_iid<Foo::Bar *>()), QByteArray("com.qtest.foobar")); - QCOMPARE(QByteArray(qobject_interface_iid<FooObject *>()), - QByteArray()); } void tst_QObject::deleteQObjectWhenDeletingEvent() @@ -3579,9 +3850,9 @@ class OverloadObject : public QObject signals: void sig(int i, char c, qreal m = 12); void sig(int i, int j = 12); - void sig(QObject *o, QObject *p, QObject *q = 0, QObject *r = 0) const; + void sig(QObject *o, QObject *p, QObject *q = nullptr, QObject *r = nullptr) const; void other(int a = 0); - void sig(QObject *o, OverloadObject *p = 0, QObject *q = 0, QObject *r = nullptr); + void sig(QObject *o, OverloadObject *p = nullptr, QObject *q = nullptr, QObject *r = nullptr); void sig(double r = 0.5); public slots: void slo(int i, int j = 43) @@ -4289,7 +4560,7 @@ public: ThreadAffinityThread(SenderObject *sender) : sender(sender) { } - void run() + void run() override { sender->emitSignal1(); } @@ -4470,6 +4741,17 @@ void tst_QObject::pointerConnect() con = connect(&r1, &ReceiverObject::slot4 , &s, &SenderObject::signal4); QVERIFY(!con); QVERIFY(!QObject::disconnect(con)); + + //connect an arbitrary PMF to a slot + QTest::ignoreMessage(QtWarningMsg, "QObject::connect: signal not found in ReceiverObject"); + con = connect(&r1, &ReceiverObject::reset, &r1, &ReceiverObject::slot1); + QVERIFY(!con); + QVERIFY(!QObject::disconnect(con)); + + QTest::ignoreMessage(QtWarningMsg, "QObject::connect: signal not found in ReceiverObject"); + con = connect(&r1, &ReceiverObject::reset, &r1, [](){}); + QVERIFY(!con); + QVERIFY(!QObject::disconnect(con)); } void tst_QObject::pointerDisconnect() @@ -4652,8 +4934,7 @@ void tst_QObject::customTypesPointer() checker.disconnect(); - int idx = qRegisterMetaType<CustomType>("CustomType"); - QCOMPARE(QMetaType::type("CustomType"), idx); + qRegisterMetaType<CustomType>(); connect(&checker, &QCustomTypeChecker::signal1, &checker, &QCustomTypeChecker::slot1, Qt::QueuedConnection); @@ -4666,11 +4947,6 @@ void tst_QObject::customTypesPointer() QCOMPARE(checker.received.value(), t2.value()); QCOMPARE(instanceCount, 4); - QVERIFY(QMetaType::isRegistered(idx)); - QCOMPARE(qRegisterMetaType<CustomType>("CustomType"), idx); - QCOMPARE(QMetaType::type("CustomType"), idx); - QVERIFY(QMetaType::isRegistered(idx)); - // Test auto registered type (QList<CustomType>) QList<CustomType> list; QCOMPARE(instanceCount, 4); @@ -5191,7 +5467,7 @@ namespace ManyArgumentNamespace { } }; - struct Funct6 { + struct Funct6 final { void operator()(const QString &a, const QString &b, const QString &c, const QString&d, const QString&e, const QString&f) { MANYARGUMENT_COMPARE(a); MANYARGUMENT_COMPARE(b); MANYARGUMENT_COMPARE(c); MANYARGUMENT_COMPARE(d); MANYARGUMENT_COMPARE(e); MANYARGUMENT_COMPARE(f); @@ -5275,6 +5551,8 @@ void tst_QObject::connectForwardDeclare() QVERIFY(connect(&ob, &ForwardDeclareArguments::mySignal, &ob, &ForwardDeclareArguments::mySlot, Qt::QueuedConnection)); } +class ForwardDeclared {}; // complete definition for moc + class NoDefaultConstructor { Q_GADGET @@ -5650,7 +5928,7 @@ signals: class VirtualSlotsObject : public VirtualSlotsObjectBase { Q_OBJECT public slots: - virtual void slot1() { + virtual void slot1() override { derived_counter1++; } public: @@ -5699,8 +5977,8 @@ public: public slots: void regularSlot() { ++regular_call_count; } - virtual void slot1() { ++derived_counter2; } - virtual void slot2() { ++virtual_base_count; } + virtual void slot1() override { ++derived_counter2; } + virtual void slot2() override { ++virtual_base_count; } }; struct NormalBase @@ -5903,10 +6181,10 @@ class ConnectToPrivateSlotPrivate; class ConnectToPrivateSlot :public QObject { Q_OBJECT + Q_DECLARE_PRIVATE(ConnectToPrivateSlot) public: ConnectToPrivateSlot(); void test(SenderObject *obj1) ; - Q_DECLARE_PRIVATE(ConnectToPrivateSlot) }; class ConnectToPrivateSlotPrivate : public QObjectPrivate { @@ -5923,6 +6201,8 @@ public: receivedCount++; receivedValue = v; }; + + void testFromPrivate(SenderObject *obj); }; ConnectToPrivateSlot::ConnectToPrivateSlot(): QObject(*new ConnectToPrivateSlotPrivate) {} @@ -5949,6 +6229,14 @@ void ConnectToPrivateSlot::test(SenderObject* obj1) { QVERIFY(!QObjectPrivate::disconnect(obj1, &SenderObject::signal2, d, &ConnectToPrivateSlotPrivate::thisIsAPrivateSlot)); } +// Compile test to verify that we can use QObjectPrivate::connect in +// the code of the private class, even if Q_DECLARE_PUBLIC is used in the +// private section of the private class. +void ConnectToPrivateSlotPrivate::testFromPrivate(SenderObject *obj) +{ + QVERIFY(QObjectPrivate::connect(obj, &SenderObject::signal1, this, &ConnectToPrivateSlotPrivate::thisIsAPrivateSlot)); +} + void tst_QObject::connectPrivateSlots() { SenderObject sender; @@ -5985,6 +6273,7 @@ void tst_QObject::connectFunctorArgDifference() connect(&timer, &QTimer::timeout, [=](){}); connect(&timer, &QTimer::objectNameChanged, [=](const QString &){}); + connect(&timer, &QTimer::objectNameChanged, this, [](){}); connect(qApp, &QCoreApplication::aboutToQuit, [=](){}); connect(&timer, &QTimer::objectNameChanged, [=](){}); @@ -6052,13 +6341,41 @@ void tst_QObject::connectFunctorWithContext() connect(context, &QObject::destroyed, &obj, &SenderObject::signal1, Qt::QueuedConnection); context->deleteLater(); - QCOMPARE(status, 1); + obj.emitSignal1(); + QCOMPARE(status, 2); e.exec(); - QCOMPARE(status, 1); + QCOMPARE(status, 2); + + // Check disconnect with the context object as "receiver" argument, all signals + context = new ContextObject; + status = 1; + connect(&obj, &SenderObject::signal1, context, SlotArgFunctor(&status)); + + obj.emitSignal1(); + QCOMPARE(status, 2); + QObject::disconnect(&obj, nullptr, context, nullptr); + obj.emitSignal1(); + QCOMPARE(status, 2); + + delete context; + + // Check disconnect with the context object as "receiver" argument, specific signal + context = new ContextObject; + status = 1; + connect(&obj, &SenderObject::signal1, context, SlotArgFunctor(&status)); + + obj.emitSignal1(); + QCOMPARE(status, 2); + QObject::disconnect(&obj, &SenderObject::signal1, context, nullptr); + obj.emitSignal1(); + QCOMPARE(status, 2); + + delete context; // Check the sender arg is set correctly in the context context = new ContextObject; + status = 1; connect(&obj, &SenderObject::signal1, context, SlotArgFunctor(context, &obj, &status), Qt::QueuedConnection); @@ -6075,8 +6392,7 @@ void tst_QObject::connectFunctorWithContext() e.exec(); QCOMPARE(status, 2); - // Free - context->deleteLater(); + delete context; } class StatusChanger : public QObject @@ -6128,7 +6444,7 @@ public slots: { if (abouttoblock) { abouttoblock->deleteLater(); - abouttoblock = 0; + abouttoblock = nullptr; } ++m_aboutToBlocks; } @@ -6136,7 +6452,7 @@ public slots: { if (awake) { awake->deleteLater(); - awake = 0; + awake = nullptr; } ++m_awakes; @@ -6182,13 +6498,49 @@ void tst_QObject::connectFunctorWithContextUnique() SenderObject sender; ReceiverObject receiver; - QObject::connect(&sender, &SenderObject::signal1, &receiver, &ReceiverObject::slot1); + QVERIFY(QObject::connect(&sender, &SenderObject::signal1, &receiver, &ReceiverObject::slot1)); receiver.count_slot1 = 0; - QObject::connect(&sender, &SenderObject::signal1, &receiver, SlotFunctor(), Qt::UniqueConnection); + QVERIFY(QObject::connect(&sender, &SenderObject::signal2, &receiver, &ReceiverObject::slot2)); + receiver.count_slot2 = 0; + + const auto oredType = Qt::ConnectionType(Qt::DirectConnection | Qt::UniqueConnection); + + // Will assert in debug builds, so only test in release builds +#if defined(QT_NO_DEBUG) && !defined(QT_FORCE_ASSERTS) + auto ignoreMsg = [] { + QTest::ignoreMessage(QtWarningMsg, + "QObject::connect(SenderObject, ReceiverObject): unique connections " + "require a pointer to member function of a QObject subclass"); + }; + + ignoreMsg(); + QVERIFY(!QObject::connect(&sender, &SenderObject::signal1, &receiver, [&](){ receiver.slot1(); }, Qt::UniqueConnection)); + + ignoreMsg(); + QVERIFY(!QObject::connect( + &sender, &SenderObject::signal2, &receiver, [&]() { receiver.slot2(); }, oredType)); +#endif sender.emitSignal1(); QCOMPARE(receiver.count_slot1, 1); + + sender.emitSignal2(); + QCOMPARE(receiver.count_slot2, 1); + + // Check connecting to PMF doesn't hit the assert + + QVERIFY(QObject::connect(&sender, &SenderObject::signal3, &receiver, &ReceiverObject::slot3, + Qt::UniqueConnection)); + receiver.count_slot3 = 0; + sender.emitSignal3(); + QCOMPARE(receiver.count_slot3, 1); + + QVERIFY(QObject::connect(&sender, &SenderObject::signal4, &receiver, &ReceiverObject::slot4, + oredType)); + receiver.count_slot4 = 0; + sender.emitSignal4(); + QCOMPARE(receiver.count_slot4, 1); } class MyFunctor @@ -6695,7 +7047,11 @@ struct QmlReceiver : public QtPrivate::QSlotObjectBase , magic(0) {} +#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0) static void impl(int which, QSlotObjectBase *this_, QObject *, void **metaArgs, bool *ret) +#else + static void impl(QSlotObjectBase *this_, QObject *, void **metaArgs, int which, bool *ret) +#endif { switch (which) { case Destroy: delete static_cast<QmlReceiver*>(this_); return; @@ -6735,6 +7091,33 @@ void tst_QObject::qmlConnect() #endif } +void tst_QObject::qmlConnectToQObjectReceiver() +{ +#ifdef QT_BUILD_INTERNAL + SenderObject sender; + QScopedPointer<QObject> receiver(new QObject); + QmlReceiver *slotObject = new QmlReceiver; + slotObject->magic = slotObject; + slotObject->ref(); // extra ref so that slot object is not implicitly deleted + + QVERIFY(QObjectPrivate::connect(&sender, sender.metaObject()->indexOfSignal("signal1()"), + receiver.get(), slotObject, Qt::AutoConnection)); + + QCOMPARE(slotObject->callCount, 0); + sender.emitSignal1(); + QCOMPARE(slotObject->callCount, 1); + + receiver.reset(); // this should disconnect the slotObject + + sender.emitSignal1(); + QCOMPARE(slotObject->callCount, 1); + + slotObject->destroyIfLastRef(); +#else + QSKIP("Needs QT_BUILD_INTERNAL"); +#endif +} + #ifndef QT_NO_EXCEPTIONS class ObjectException : public std::exception { }; @@ -6772,8 +7155,11 @@ public: explicit CountedExceptionThrower(bool throwException, QObject *parent = nullptr) : QObject(parent) { + Q_UNUSED(throwException); +#ifndef QT_NO_EXCEPTIONS if (throwException) throw ObjectException(); +#endif ++counter; } @@ -6907,42 +7293,6 @@ void tst_QObject::exceptions() #endif } -#ifdef QT_BUILD_INTERNAL -static bool parentChangeCalled = false; - -static void testParentChanged(QAbstractDeclarativeData *, QObject *, QObject *) -{ - parentChangeCalled = true; -} -#endif - -void tst_QObject::noDeclarativeParentChangedOnDestruction() -{ -#ifdef QT_BUILD_INTERNAL - typedef void (*ParentChangedCallback)(QAbstractDeclarativeData *, QObject *, QObject *); - QScopedValueRollback<ParentChangedCallback> rollback(QAbstractDeclarativeData::parentChanged); - QAbstractDeclarativeData::parentChanged = testParentChanged; - - QObject *parent = new QObject; - QObject *child = new QObject; - - QAbstractDeclarativeData dummy; - QObjectPrivate::get(child)->declarativeData = &dummy; - - parentChangeCalled = false; - child->setParent(parent); - - QVERIFY(parentChangeCalled); - parentChangeCalled = false; - - delete child; - QVERIFY(!parentChangeCalled); - - delete parent; -#else - QSKIP("Needs QT_BUILD_INTERNAL"); -#endif -} struct MutableFunctor { int count; @@ -6964,24 +7314,34 @@ void tst_QObject::mutableFunctor() void tst_QObject::checkArgumentsForNarrowing() { - enum UnscopedEnum {}; - enum SignedUnscopedEnum { SignedUnscopedEnumV1 = -1, SignedUnscopedEnumV2 = 1 }; + // Clang and ICC masquerade as GCC, so introduce a more strict define + // for exactly GCC (to exclude/include it from some tests). +#if defined(Q_CC_GNU) && !defined(Q_CC_CLANG) +#define Q_CC_EXACTLY_GCC Q_CC_GNU +#endif - // a constexpr would suffice, but MSVC2013 RTM doesn't support them... -#define IS_UNSCOPED_ENUM_SIGNED (std::is_signed<typename std::underlying_type<UnscopedEnum>::type>::value) + enum UnscopedEnum { UnscopedEnumV1 = INT_MAX, UnscopedEnumV2 }; + enum SignedUnscopedEnum { SignedUnscopedEnumV1 = INT_MIN, SignedUnscopedEnumV2 = INT_MAX }; -#define NARROWS_IF(x, y, test) Q_STATIC_ASSERT((QtPrivate::AreArgumentsNarrowedBase<x, y>::value) == (test)) -#define FITS_IF(x, y, test) Q_STATIC_ASSERT((QtPrivate::AreArgumentsNarrowedBase<x, y>::value) != (test)) + static constexpr bool IsUnscopedEnumSigned = std::is_signed_v<std::underlying_type_t<UnscopedEnum>>; + +#define NARROWS_IF(x, y, test) static_assert((QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase<x, y>::value) != (test)) +#define FITS_IF(x, y, test) static_assert((QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase<x, y>::value) == (test)) #define NARROWS(x, y) NARROWS_IF(x, y, true) #define FITS(x, y) FITS_IF(x, y, true) - Q_STATIC_ASSERT(sizeof(UnscopedEnum) <= sizeof(int)); - Q_STATIC_ASSERT(sizeof(SignedUnscopedEnum) <= sizeof(int)); + static_assert(sizeof(UnscopedEnum) <= sizeof(int)); + static_assert(sizeof(SignedUnscopedEnum) <= sizeof(int)); // floating point to integral + + // GCC < 9 does not consider floating point to bool to be narrowing, + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65043 +#if !defined(Q_CC_EXACTLY_GCC) || Q_CC_EXACTLY_GCC >= 900 NARROWS(float, bool); NARROWS(double, bool); NARROWS(long double, bool); +#endif NARROWS(float, char); NARROWS(double, char); @@ -7005,12 +7365,19 @@ void tst_QObject::checkArgumentsForNarrowing() // floating point to a smaller floating point - NARROWS_IF(double, float, (sizeof(double) > sizeof(float))); - NARROWS_IF(long double, float, (sizeof(long double) > sizeof(float))); + NARROWS(double, float); + NARROWS(long double, float); FITS(float, double); FITS(float, long double); - NARROWS_IF(long double, double, (sizeof(long double) > sizeof(double))); + // GCC < 11 thinks this is narrowing only on architectures where + // sizeof(long double) > sizeof(double) + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94590 +#if defined(Q_CC_EXACTLY_GCC) && (Q_CC_EXACTLY_GCC < 1100) + NARROWS_IF(long double, double, sizeof(long double) > sizeof(double)); +#else + NARROWS(long double, double); +#endif FITS(double, long double); @@ -7156,11 +7523,14 @@ void tst_QObject::checkArgumentsForNarrowing() // integral to integral with different signedness. smaller ones tested above NARROWS(signed char, unsigned char); + + // Issue is reported to Green Hills, 2021-09-14. +#if !defined(Q_CC_GHS) NARROWS(signed char, unsigned short); NARROWS(signed char, unsigned int); NARROWS(signed char, unsigned long); NARROWS(signed char, unsigned long long); - +#endif // Q_CC_GHS NARROWS(unsigned char, signed char); FITS(unsigned char, short); FITS(unsigned char, int); @@ -7168,19 +7538,23 @@ void tst_QObject::checkArgumentsForNarrowing() FITS(unsigned char, long long); NARROWS(short, unsigned short); +#if !defined(Q_CC_GHS) NARROWS(short, unsigned int); NARROWS(short, unsigned long); - NARROWS(short, unsigned long long); + NARROWS(short, unsigned long long); +#endif // Q_CC_GHS NARROWS(unsigned short, short); + FITS(unsigned short, int); FITS(unsigned short, long); FITS(unsigned short, long long); NARROWS(int, unsigned int); +#if !defined(Q_CC_GHS) NARROWS(int, unsigned long); NARROWS(int, unsigned long long); - +#endif // Q_CC_GHS NARROWS(unsigned int, int); NARROWS_IF(unsigned int, long, (sizeof(int) >= sizeof(long))); FITS(unsigned int, long long); @@ -7199,23 +7573,23 @@ void tst_QObject::checkArgumentsForNarrowing() FITS(UnscopedEnum, UnscopedEnum); FITS(SignedUnscopedEnum, SignedUnscopedEnum); - NARROWS_IF(UnscopedEnum, char, ((sizeof(UnscopedEnum) > sizeof(char)) || (sizeof(UnscopedEnum) == sizeof(char) && IS_UNSCOPED_ENUM_SIGNED == std::is_signed<char>::value))); - NARROWS_IF(UnscopedEnum, signed char, ((sizeof(UnscopedEnum) > sizeof(char)) || (sizeof(UnscopedEnum) == sizeof(char) && !IS_UNSCOPED_ENUM_SIGNED))); - NARROWS_IF(UnscopedEnum, unsigned char, ((sizeof(UnscopedEnum) > sizeof(char)) || IS_UNSCOPED_ENUM_SIGNED)); + NARROWS_IF(UnscopedEnum, char, ((sizeof(UnscopedEnum) > sizeof(char)) || (sizeof(UnscopedEnum) == sizeof(char) && IsUnscopedEnumSigned == std::is_signed<char>::value))); + NARROWS_IF(UnscopedEnum, signed char, ((sizeof(UnscopedEnum) > sizeof(char)) || (sizeof(UnscopedEnum) == sizeof(char) && !IsUnscopedEnumSigned))); + NARROWS_IF(UnscopedEnum, unsigned char, ((sizeof(UnscopedEnum) > sizeof(char)) || IsUnscopedEnumSigned)); - NARROWS_IF(UnscopedEnum, short, ((sizeof(UnscopedEnum) > sizeof(short)) || (sizeof(UnscopedEnum) == sizeof(short) && !IS_UNSCOPED_ENUM_SIGNED))); - NARROWS_IF(UnscopedEnum, unsigned short, ((sizeof(UnscopedEnum) > sizeof(short)) || IS_UNSCOPED_ENUM_SIGNED)); + NARROWS_IF(UnscopedEnum, short, ((sizeof(UnscopedEnum) > sizeof(short)) || (sizeof(UnscopedEnum) == sizeof(short) && !IsUnscopedEnumSigned))); + NARROWS_IF(UnscopedEnum, unsigned short, ((sizeof(UnscopedEnum) > sizeof(short)) || IsUnscopedEnumSigned)); - NARROWS_IF(UnscopedEnum, int, (sizeof(UnscopedEnum) == sizeof(int) && !IS_UNSCOPED_ENUM_SIGNED)); - NARROWS_IF(UnscopedEnum, unsigned int, IS_UNSCOPED_ENUM_SIGNED); + NARROWS_IF(UnscopedEnum, int, sizeof(UnscopedEnum) > sizeof(int) || (sizeof(UnscopedEnum) == sizeof(int) && !IsUnscopedEnumSigned)); + NARROWS_IF(UnscopedEnum, unsigned int, IsUnscopedEnumSigned); - NARROWS_IF(UnscopedEnum, long, (sizeof(UnscopedEnum) == sizeof(long) && !IS_UNSCOPED_ENUM_SIGNED)); - NARROWS_IF(UnscopedEnum, unsigned long, IS_UNSCOPED_ENUM_SIGNED); + NARROWS_IF(UnscopedEnum, long, sizeof(UnscopedEnum) > sizeof(long) || (sizeof(UnscopedEnum) == sizeof(long) && !IsUnscopedEnumSigned)); + NARROWS_IF(UnscopedEnum, unsigned long, IsUnscopedEnumSigned); - NARROWS_IF(UnscopedEnum, long long, (sizeof(UnscopedEnum) == sizeof(long long) && !IS_UNSCOPED_ENUM_SIGNED)); - NARROWS_IF(UnscopedEnum, unsigned long long, IS_UNSCOPED_ENUM_SIGNED); + NARROWS_IF(UnscopedEnum, long long, sizeof(UnscopedEnum) > sizeof(long long) || (sizeof(UnscopedEnum) == sizeof(long long) && !IsUnscopedEnumSigned)); + NARROWS_IF(UnscopedEnum, unsigned long long, IsUnscopedEnumSigned); - Q_STATIC_ASSERT(std::is_signed<typename std::underlying_type<SignedUnscopedEnum>::type>::value); + static_assert(std::is_signed<typename std::underlying_type<SignedUnscopedEnum>::type>::value); NARROWS_IF(SignedUnscopedEnum, signed char, (sizeof(SignedUnscopedEnum) > sizeof(char))); NARROWS_IF(SignedUnscopedEnum, short, (sizeof(SignedUnscopedEnum) > sizeof(short))); @@ -7223,202 +7597,73 @@ void tst_QObject::checkArgumentsForNarrowing() FITS(SignedUnscopedEnum, long); FITS(SignedUnscopedEnum, long long); - - enum class ScopedEnumBackedBySChar : signed char { A }; - enum class ScopedEnumBackedByUChar : unsigned char { A }; - enum class ScopedEnumBackedByShort : short { A }; - enum class ScopedEnumBackedByUShort : unsigned short { A }; - enum class ScopedEnumBackedByInt : int { A }; - enum class ScopedEnumBackedByUInt : unsigned int { A }; - enum class ScopedEnumBackedByLong : long { A }; - enum class ScopedEnumBackedByULong : unsigned long { A }; - enum class ScopedEnumBackedByLongLong : long long { A }; - enum class ScopedEnumBackedByULongLong : unsigned long long { A }; - - FITS(ScopedEnumBackedBySChar, ScopedEnumBackedBySChar); - FITS(ScopedEnumBackedByUChar, ScopedEnumBackedByUChar); - FITS(ScopedEnumBackedByShort, ScopedEnumBackedByShort); - FITS(ScopedEnumBackedByUShort, ScopedEnumBackedByUShort); - FITS(ScopedEnumBackedByInt, ScopedEnumBackedByInt); - FITS(ScopedEnumBackedByUInt, ScopedEnumBackedByUInt); - FITS(ScopedEnumBackedByLong, ScopedEnumBackedByLong); - FITS(ScopedEnumBackedByULong, ScopedEnumBackedByULong); - FITS(ScopedEnumBackedByLongLong, ScopedEnumBackedByLongLong); - FITS(ScopedEnumBackedByULongLong, ScopedEnumBackedByULongLong); - - FITS(ScopedEnumBackedBySChar, signed char); - FITS(ScopedEnumBackedByUChar, unsigned char); - FITS(ScopedEnumBackedByShort, short); - FITS(ScopedEnumBackedByUShort, unsigned short); - FITS(ScopedEnumBackedByInt, int); - FITS(ScopedEnumBackedByUInt, unsigned int); - FITS(ScopedEnumBackedByLong, long); - FITS(ScopedEnumBackedByULong, unsigned long); - FITS(ScopedEnumBackedByLongLong, long long); - FITS(ScopedEnumBackedByULongLong, unsigned long long); - - FITS(ScopedEnumBackedBySChar, signed char); - FITS(ScopedEnumBackedBySChar, short); - FITS(ScopedEnumBackedBySChar, int); - FITS(ScopedEnumBackedBySChar, long); - FITS(ScopedEnumBackedBySChar, long long); - - FITS(ScopedEnumBackedByUChar, unsigned char); - FITS(ScopedEnumBackedByUChar, unsigned short); - FITS(ScopedEnumBackedByUChar, unsigned int); - FITS(ScopedEnumBackedByUChar, unsigned long); - FITS(ScopedEnumBackedByUChar, unsigned long long); - - NARROWS_IF(ScopedEnumBackedByShort, char, (sizeof(short) > sizeof(char) || std::is_unsigned<char>::value)); - NARROWS_IF(ScopedEnumBackedByUShort, char, (sizeof(short) > sizeof(char) || std::is_signed<char>::value)); - NARROWS_IF(ScopedEnumBackedByInt, char, (sizeof(int) > sizeof(char) || std::is_unsigned<char>::value)); - NARROWS_IF(ScopedEnumBackedByUInt, char, (sizeof(int) > sizeof(char) || std::is_signed<char>::value)); - NARROWS_IF(ScopedEnumBackedByLong, char, (sizeof(long) > sizeof(char) || std::is_unsigned<char>::value)); - NARROWS_IF(ScopedEnumBackedByULong, char, (sizeof(long) > sizeof(char) || std::is_signed<char>::value)); - NARROWS_IF(ScopedEnumBackedByLongLong, char, (sizeof(long long) > sizeof(char) || std::is_unsigned<char>::value)); - NARROWS_IF(ScopedEnumBackedByULongLong, char, (sizeof(long long) > sizeof(char) || std::is_signed<char>::value)); - - NARROWS_IF(ScopedEnumBackedByShort, signed char, (sizeof(short) > sizeof(char))); - NARROWS(ScopedEnumBackedByUShort, signed char); - NARROWS_IF(ScopedEnumBackedByInt, signed char, (sizeof(int) > sizeof(char))); - NARROWS(ScopedEnumBackedByUInt, signed char); - NARROWS_IF(ScopedEnumBackedByLong, signed char, (sizeof(long) > sizeof(char))); - NARROWS(ScopedEnumBackedByULong, signed char); - NARROWS_IF(ScopedEnumBackedByLongLong, signed char, (sizeof(long long) > sizeof(char))); - NARROWS(ScopedEnumBackedByULongLong, signed char); - - NARROWS(ScopedEnumBackedByShort, unsigned char); - NARROWS_IF(ScopedEnumBackedByUShort, unsigned char, (sizeof(short) > sizeof(char))); - NARROWS(ScopedEnumBackedByInt, unsigned char); - NARROWS_IF(ScopedEnumBackedByUInt, unsigned char, (sizeof(int) > sizeof(char))); - NARROWS(ScopedEnumBackedByLong, unsigned char); - NARROWS_IF(ScopedEnumBackedByULong, unsigned char, (sizeof(long) > sizeof(char))); - NARROWS(ScopedEnumBackedByLongLong, unsigned char); - NARROWS_IF(ScopedEnumBackedByULongLong, unsigned char, (sizeof(long long) > sizeof(char))); - - NARROWS_IF(ScopedEnumBackedByInt, short, (sizeof(int) > sizeof(short))); - NARROWS(ScopedEnumBackedByUInt, short); - NARROWS_IF(ScopedEnumBackedByLong, short, (sizeof(long) > sizeof(short))); - NARROWS(ScopedEnumBackedByULong, short); - NARROWS_IF(ScopedEnumBackedByLongLong, short, (sizeof(long long) > sizeof(short))); - NARROWS(ScopedEnumBackedByULongLong, short); - - NARROWS(ScopedEnumBackedByInt, unsigned short); - NARROWS_IF(ScopedEnumBackedByUInt, unsigned short, (sizeof(int) > sizeof(short))); - NARROWS(ScopedEnumBackedByLong, unsigned short); - NARROWS_IF(ScopedEnumBackedByULong, unsigned short, (sizeof(long) > sizeof(short))); - NARROWS(ScopedEnumBackedByLongLong, unsigned short); - NARROWS_IF(ScopedEnumBackedByULongLong, unsigned short, (sizeof(long long) > sizeof(short))); - - NARROWS_IF(ScopedEnumBackedByLong, int, (sizeof(long) > sizeof(int))); - NARROWS(ScopedEnumBackedByULong, int); - NARROWS_IF(ScopedEnumBackedByLongLong, int, (sizeof(long long) > sizeof(int))); - NARROWS(ScopedEnumBackedByULongLong, int); - - NARROWS(ScopedEnumBackedByLong, unsigned int); - NARROWS_IF(ScopedEnumBackedByULong, unsigned int, (sizeof(long) > sizeof(int))); - NARROWS(ScopedEnumBackedByLongLong, unsigned int); - NARROWS_IF(ScopedEnumBackedByULongLong, unsigned int, (sizeof(long long) > sizeof(int))); - - NARROWS_IF(ScopedEnumBackedByLongLong, long, (sizeof(long long) > sizeof(long))); - NARROWS(ScopedEnumBackedByULongLong, long); - - NARROWS(ScopedEnumBackedByLongLong, unsigned long); - NARROWS_IF(ScopedEnumBackedByULongLong, unsigned long, (sizeof(long long) > sizeof(long))); - - // different signedness of the underlying type - NARROWS(SignedUnscopedEnum, unsigned char); - NARROWS(SignedUnscopedEnum, unsigned short); - NARROWS(SignedUnscopedEnum, unsigned int); - NARROWS(SignedUnscopedEnum, unsigned long); - NARROWS(SignedUnscopedEnum, unsigned long long); - - NARROWS(ScopedEnumBackedBySChar, unsigned char); - NARROWS(ScopedEnumBackedBySChar, unsigned short); - NARROWS(ScopedEnumBackedBySChar, unsigned int); - NARROWS(ScopedEnumBackedBySChar, unsigned long); - NARROWS(ScopedEnumBackedBySChar, unsigned long long); - - NARROWS(ScopedEnumBackedByShort, unsigned char); - NARROWS(ScopedEnumBackedByShort, unsigned short); - NARROWS(ScopedEnumBackedByShort, unsigned int); - NARROWS(ScopedEnumBackedByShort, unsigned long); - NARROWS(ScopedEnumBackedByShort, unsigned long long); - - NARROWS(ScopedEnumBackedByInt, unsigned char); - NARROWS(ScopedEnumBackedByInt, unsigned short); - NARROWS(ScopedEnumBackedByInt, unsigned int); - NARROWS(ScopedEnumBackedByInt, unsigned long); - NARROWS(ScopedEnumBackedByInt, unsigned long long); - - NARROWS(ScopedEnumBackedByLong, unsigned char); - NARROWS(ScopedEnumBackedByLong, unsigned short); - NARROWS(ScopedEnumBackedByLong, unsigned int); - NARROWS(ScopedEnumBackedByLong, unsigned long); - NARROWS(ScopedEnumBackedByLong, unsigned long long); - - NARROWS(ScopedEnumBackedByLongLong, unsigned char); - NARROWS(ScopedEnumBackedByLongLong, unsigned short); - NARROWS(ScopedEnumBackedByLongLong, unsigned int); - NARROWS(ScopedEnumBackedByLongLong, unsigned long); - NARROWS(ScopedEnumBackedByLongLong, unsigned long long); - - NARROWS(ScopedEnumBackedByUChar, signed char); - FITS_IF(ScopedEnumBackedByUChar, short, (sizeof(char) < sizeof(short))); - FITS_IF(ScopedEnumBackedByUChar, int, (sizeof(char) < sizeof(int))); - FITS_IF(ScopedEnumBackedByUChar, long, (sizeof(char) < sizeof(long))); - FITS_IF(ScopedEnumBackedByUChar, long long, (sizeof(char) < sizeof(long long))); - - NARROWS(ScopedEnumBackedByUShort, signed char); - NARROWS(ScopedEnumBackedByUShort, short); - FITS_IF(ScopedEnumBackedByUShort, int, (sizeof(short) < sizeof(int))); - FITS_IF(ScopedEnumBackedByUShort, long, (sizeof(short) < sizeof(long))); - FITS_IF(ScopedEnumBackedByUShort, long long, (sizeof(short) < sizeof(long long))); - - NARROWS(ScopedEnumBackedByUInt, signed char); - NARROWS(ScopedEnumBackedByUInt, short); - NARROWS(ScopedEnumBackedByUInt, int); - FITS_IF(ScopedEnumBackedByUInt, long, (sizeof(ScopedEnumBackedByUInt) < sizeof(long))); - FITS(ScopedEnumBackedByUInt, long long); - - NARROWS(ScopedEnumBackedByULong, signed char); - NARROWS(ScopedEnumBackedByULong, short); - NARROWS(ScopedEnumBackedByULong, int); - NARROWS(ScopedEnumBackedByULong, long); - FITS_IF(ScopedEnumBackedByULong, long long, (sizeof(ScopedEnumBackedByULong) < sizeof(long long))); - - NARROWS(ScopedEnumBackedByULongLong, signed char); - NARROWS(ScopedEnumBackedByULongLong, short); - NARROWS(ScopedEnumBackedByULongLong, int); - NARROWS(ScopedEnumBackedByULongLong, long); - NARROWS(ScopedEnumBackedByULongLong, long long); - // other types which should be always unaffected FITS(void *, void *); FITS(QString, QString); - FITS(QString &, QString &); - FITS(const QString &, const QString &); - FITS(QObject, QObject); FITS(QObject *, QObject *); FITS(const QObject *, const QObject *); FITS(std::nullptr_t, std::nullptr_t); - FITS(QString, QObject); - FITS(QString, QVariant); - FITS(QString, void *); - FITS(QString, long long); - FITS(bool, const QObject *&); - FITS(int (*)(bool), void (QObject::*)()); + // classes with conversion operators undergoing implicit conversions + struct ConvertingToDouble { + /* implicit */ operator double() const { return 42.0; } + }; -#undef IS_UNSCOPED_ENUM_SIGNED +#if !defined(Q_CC_GHS) + NARROWS(ConvertingToDouble, char); + NARROWS(ConvertingToDouble, short); + NARROWS(ConvertingToDouble, int); + NARROWS(ConvertingToDouble, long); + NARROWS(ConvertingToDouble, long long); + NARROWS(ConvertingToDouble, float); +#endif // Q_CC_GHS + FITS(ConvertingToDouble, double); + FITS(ConvertingToDouble, long double); + + + // GCC, GHS and clang don't implement this properly yet: + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99625 + // https://bugs.llvm.org/show_bug.cgi?id=49676 +#if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) // at least since VS2017 + struct ConstructibleFromInt { + /* implicit */ ConstructibleFromInt(int) {} + }; + + FITS(char, ConstructibleFromInt); + FITS(short, ConstructibleFromInt); + FITS(int, ConstructibleFromInt); + NARROWS(unsigned int, ConstructibleFromInt); + NARROWS_IF(long, ConstructibleFromInt, sizeof(long) > sizeof(int)); + NARROWS_IF(long long, ConstructibleFromInt, sizeof(long long) > sizeof(int)); + NARROWS(float, ConstructibleFromInt); + NARROWS(double, ConstructibleFromInt); +#endif + + // forward declared classes must work + class ForwardDeclared; + FITS(ForwardDeclared, ForwardDeclared); + +#if (defined(Q_CC_EXACTLY_GCC) && Q_CC_EXACTLY_GCC >= 1100) \ + || (defined(Q_CC_CLANG) && Q_CC_CLANG >= 1100) \ + || defined(Q_CC_MSVC) // at least since VS2017 + { + // wg21.link/P1957 + NARROWS(char*, bool); + NARROWS(void (QObject::*)(), bool); + } +#endif #undef NARROWS_IF #undef FITS_IF #undef NARROWS #undef FITS + +#ifdef Q_CC_EXACTLY_GCC +#undef Q_CC_EXACTLY_GCC +#endif } void tst_QObject::nullReceiver() @@ -7592,9 +7837,997 @@ void tst_QObject::disconnectDisconnects() QCOMPARE(count, 3); // + δ } +class ReceiverDisconnecting : public QObject +{ + Q_OBJECT + +public: + SenderObject *sender; + int slotCalledCount = 0; + +public slots: + void aSlotByName() + { + ++slotCalledCount; + QVERIFY(!disconnect(sender, SIGNAL(signal1()), this, SLOT(aSlotByName()))); + } + + void aSlotByPtr() + { + ++slotCalledCount; + QVERIFY(!disconnect(sender, &SenderObject::signal1, this, &ReceiverDisconnecting::aSlotByPtr)); + } +}; + +class DeleteThisReceiver : public QObject +{ + Q_OBJECT + +public: + static int counter; + +public slots: + void deleteThis() + { + ++counter; + delete this; + } +}; + +int DeleteThisReceiver::counter = 0; + +void tst_QObject::singleShotConnection() +{ + { + // Non single shot behavior: slot called every time the signal is emitted + SenderObject sender; + QMetaObject::Connection c = connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot); + QVERIFY(c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 2); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 3); + } + + { + // Non single shot behavior: multiple connections cause multiple invocations + SenderObject sender; + QMetaObject::Connection c = connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot); + QVERIFY(c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QVERIFY(c); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QVERIFY(c); + QCOMPARE(sender.aPublicSlotCalled, 2); + + QMetaObject::Connection c2 = connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot); + QVERIFY(c); + QVERIFY(c2); + QCOMPARE(sender.aPublicSlotCalled, 2); + + sender.emitSignal1(); + QVERIFY(c); + QVERIFY(c2); + QCOMPARE(sender.aPublicSlotCalled, 4); + + sender.emitSignal1(); + QVERIFY(c); + QVERIFY(c2); + QCOMPARE(sender.aPublicSlotCalled, 6); + } + + { + // Single shot behavior: slot called only once + SenderObject sender; + QMetaObject::Connection c = connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + QVERIFY(c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 1); + } + + { + // Same, without holding a Connection object + SenderObject sender; + bool ok = connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + QVERIFY(ok); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + } + + { + // Single shot, disconnect before emitting + SenderObject sender; + QMetaObject::Connection c = connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + QVERIFY(c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + QVERIFY(QObject::disconnect(c)); + QVERIFY(!c); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 0); + } + + { + // Single shot together with another connection + SenderObject sender; + QVERIFY(connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot)); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 2); + + QVERIFY(connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection))); + QCOMPARE(sender.aPublicSlotCalled, 2); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 4); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 5); + } + + { + // Two single shot, from the same signal, to the same slot + SenderObject sender; + QVERIFY(connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection))); + QVERIFY(connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection))); + + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 2); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 2); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 2); + } + + { + // Two single shot, from different signals, to the same slot + SenderObject sender; + QVERIFY(connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection))); + QVERIFY(connect(&sender, &SenderObject::signal2, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection))); + + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal2(); + QCOMPARE(sender.aPublicSlotCalled, 2); + + sender.emitSignal2(); + QCOMPARE(sender.aPublicSlotCalled, 2); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 2); + } + + { + // Same signal, different connections + SenderObject sender; + ReceiverObject receiver1, receiver2; + receiver1.reset(); + receiver2.reset(); + + QVERIFY(connect(&sender, &SenderObject::signal1, + &receiver1, &ReceiverObject::slot1)); + QVERIFY(connect(&sender, &SenderObject::signal1, + &receiver2, &ReceiverObject::slot1, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection))); + QCOMPARE(receiver1.count_slot1, 0); + QCOMPARE(receiver2.count_slot1, 0); + + sender.emitSignal1(); + QCOMPARE(receiver1.count_slot1, 1); + QCOMPARE(receiver2.count_slot1, 1); + + sender.emitSignal1(); + QCOMPARE(receiver1.count_slot1, 2); + QCOMPARE(receiver2.count_slot1, 1); + + sender.emitSignal1(); + QCOMPARE(receiver1.count_slot1, 3); + QCOMPARE(receiver2.count_slot1, 1); + + // Reestablish a single shot + QVERIFY(connect(&sender, &SenderObject::signal1, + &receiver2, &ReceiverObject::slot1, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection))); + QCOMPARE(receiver1.count_slot1, 3); + QCOMPARE(receiver2.count_slot1, 1); + + sender.emitSignal1(); + QCOMPARE(receiver1.count_slot1, 4); + QCOMPARE(receiver2.count_slot1, 2); + + sender.emitSignal1(); + QCOMPARE(receiver1.count_slot1, 5); + QCOMPARE(receiver2.count_slot1, 2); + } + + { + // Check that the slot is invoked with the connection already disconnected + SenderObject sender; + QMetaObject::Connection c; + auto breakSlot = [&]() { + QVERIFY(!c); + ++sender.aPublicSlotCalled; + }; + + c = connect(&sender, &SenderObject::signal1, + &sender, breakSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + + QVERIFY(c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + QVERIFY(!c); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + QVERIFY(!c); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + QVERIFY(!c); + } + + { + // Same + SenderObject sender; + ReceiverDisconnecting receiver; + receiver.sender = &sender; + bool ok = connect(&sender, SIGNAL(signal1()), + &receiver, SLOT(aSlotByName()), + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + QVERIFY(ok); + QCOMPARE(receiver.slotCalledCount, 0); + + sender.emitSignal1(); + QCOMPARE(receiver.slotCalledCount, 1); + + sender.emitSignal1(); + QCOMPARE(receiver.slotCalledCount, 1); + + // reconnect + ok = connect(&sender, SIGNAL(signal1()), + &receiver, SLOT(aSlotByName()), + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + QVERIFY(ok); + QCOMPARE(receiver.slotCalledCount, 1); + + sender.emitSignal1(); + QCOMPARE(receiver.slotCalledCount, 2); + + sender.emitSignal1(); + QCOMPARE(receiver.slotCalledCount, 2); + } + + { + // Same + SenderObject sender; + ReceiverDisconnecting receiver; + receiver.sender = &sender; + bool ok = connect(&sender, &SenderObject::signal1, + &receiver, &ReceiverDisconnecting::aSlotByPtr, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + + QVERIFY(ok); + QCOMPARE(receiver.slotCalledCount, 0); + + sender.emitSignal1(); + QCOMPARE(receiver.slotCalledCount, 1); + + sender.emitSignal1(); + QCOMPARE(receiver.slotCalledCount, 1); + + // reconnect + ok = connect(&sender, &SenderObject::signal1, + &receiver, &ReceiverDisconnecting::aSlotByPtr, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + QVERIFY(ok); + QCOMPARE(receiver.slotCalledCount, 1); + + sender.emitSignal1(); + QCOMPARE(receiver.slotCalledCount, 2); + + sender.emitSignal1(); + QCOMPARE(receiver.slotCalledCount, 2); + } + + { + // Reconnect from inside the slot + SenderObject sender; + std::function<void()> reconnectingSlot; + bool reconnect = false; + reconnectingSlot = [&]() { + ++sender.aPublicSlotCalled; + if (reconnect) { + QObject::connect(&sender, &SenderObject::signal1, + &sender, reconnectingSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + } + }; + + bool ok = connect(&sender, &SenderObject::signal1, + &sender, reconnectingSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + QVERIFY(ok); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 1); + + reconnect = true; + ok = connect(&sender, &SenderObject::signal1, + &sender, reconnectingSlot, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)); + QVERIFY(ok); + QCOMPARE(sender.aPublicSlotCalled, 1); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 2); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 3); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 4); + + reconnect = false; + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 5); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 5); + } + + { + // Delete the receiver from inside the slot + SenderObject sender; + QPointer<DeleteThisReceiver> p = new DeleteThisReceiver; + DeleteThisReceiver::counter = 0; + + QVERIFY(connect(&sender, &SenderObject::signal1, + p.get(), &DeleteThisReceiver::deleteThis, + static_cast<Qt::ConnectionType>(Qt::SingleShotConnection))); + + QVERIFY(p); + QCOMPARE(DeleteThisReceiver::counter, 0); + + sender.emitSignal1(); + QCOMPARE(DeleteThisReceiver::counter, 1); + QVERIFY(!p); + + sender.emitSignal1(); + QCOMPARE(DeleteThisReceiver::counter, 1); + QVERIFY(!p); + } + + { + // Queued, non single shot + SenderObject sender; + QVERIFY(connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::QueuedConnection))); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 0); + + QTRY_COMPARE(sender.aPublicSlotCalled, 3); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 3); + + QTRY_COMPARE(sender.aPublicSlotCalled, 4); + } + + { + // Queued, single shot + SenderObject sender; + QVERIFY(connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection))); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 0); + + QTRY_COMPARE(sender.aPublicSlotCalled, 1); + QTest::qWait(0); + QCOMPARE(sender.aPublicSlotCalled, 1); + } + + { + // Queued, single shot, checking the connection handle + SenderObject sender; + QMetaObject::Connection c = connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)); + QVERIFY(c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + QTRY_COMPARE(sender.aPublicSlotCalled, 1); + QVERIFY(!c); + QTest::qWait(0); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 1); + } + + { + // Queued, single shot, disconnect before emitting + SenderObject sender; + QVERIFY(connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection))); + QCOMPARE(sender.aPublicSlotCalled, 0); + + QVERIFY(QObject::disconnect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot)); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QCOMPARE(sender.aPublicSlotCalled, 0); + + QTest::qWait(0); + QCOMPARE(sender.aPublicSlotCalled, 0); + } + + { + // Queued, single shot, disconnect before emitting by using the connection handle + SenderObject sender; + QMetaObject::Connection c = connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)); + QVERIFY(c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + QVERIFY(QObject::disconnect(c)); + QVERIFY(!c); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + sender.emitSignal1(); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 0); + + QTest::qWait(0); + QVERIFY(!c); + QCOMPARE(sender.aPublicSlotCalled, 0); + } + + { + // Queued, single shot, delete the receiver from inside the slot + SenderObject sender; + QPointer<DeleteThisReceiver> p = new DeleteThisReceiver; + DeleteThisReceiver::counter = 0; + + QVERIFY(connect(&sender, &SenderObject::signal1, + p.get(), &DeleteThisReceiver::deleteThis, + static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection))); + QCOMPARE(DeleteThisReceiver::counter, 0); + + sender.emitSignal1(); + QVERIFY(p); + QCOMPARE(DeleteThisReceiver::counter, 0); + + sender.emitSignal1(); + QVERIFY(p); + QCOMPARE(DeleteThisReceiver::counter, 0); + + sender.emitSignal1(); + QVERIFY(p); + QCOMPARE(DeleteThisReceiver::counter, 0); + + QTRY_COMPARE(DeleteThisReceiver::counter, 1); + QVERIFY(!p); + QTest::qWait(0); + QCOMPARE(DeleteThisReceiver::counter, 1); + QVERIFY(!p); + } +} + +void tst_QObject::objectNameBinding() +{ + QObject obj; + QTestPrivate::testReadWritePropertyBasics<QObject, QString>(obj, "test1", "test2", + "objectName"); +} + +namespace EmitToDestroyedClass { +static int assertionCallCount = 0; +static int wouldHaveAssertedCount = 0; +struct WouldAssert : std::exception {}; +class Base : public QObject +{ + Q_OBJECT +public: + ~Base() + { + try { + emit theSignal(); + } catch (const WouldAssert &) { + ++wouldHaveAssertedCount; + } + } + +signals: + void theSignal(); +}; + +class Derived : public Base +{ + Q_OBJECT +public: + ~Derived() { } + +public slots: + void doNothing() {} +}; +} // namespace EmitToDestroyedClass + +QT_BEGIN_NAMESPACE +namespace QtPrivate { +template<> void assertObjectType<EmitToDestroyedClass::Derived>(QObject *o) +{ + // override the assertion so we don't assert and so something does happen + // when assertions are disabled. By throwing, we also prevent the UB from + // happening. + using namespace EmitToDestroyedClass; + ++assertionCallCount; + if (!qobject_cast<Derived *>(o)) + throw WouldAssert(); +} +} +QT_END_NAMESPACE + +void tst_QObject::emitToDestroyedClass() +{ + using namespace EmitToDestroyedClass; + std::unique_ptr ptr = std::make_unique<Derived>(); + QObject::connect(ptr.get(), &Base::theSignal, ptr.get(), &Derived::doNothing); + QCOMPARE(assertionCallCount, 0); + QCOMPARE(wouldHaveAssertedCount, 0); + + // confirm our replacement function did get called + emit ptr->theSignal(); + QCOMPARE(assertionCallCount, 1); + QCOMPARE(wouldHaveAssertedCount, 0); + + ptr.reset(); + QCOMPARE(assertionCallCount, 2); + QCOMPARE(wouldHaveAssertedCount, 1); +} + // Test for QtPrivate::HasQ_OBJECT_Macro -Q_STATIC_ASSERT(QtPrivate::HasQ_OBJECT_Macro<tst_QObject>::Value); -Q_STATIC_ASSERT(!QtPrivate::HasQ_OBJECT_Macro<SiblingDeleter>::Value); +static_assert(QtPrivate::HasQ_OBJECT_Macro<tst_QObject>::Value); +static_assert(!QtPrivate::HasQ_OBJECT_Macro<SiblingDeleter>::Value); + +Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr) +Q_DECLARE_SMART_POINTER_METATYPE(std::unique_ptr) + + +// QTBUG-103741: OK to use smart pointers to const QObject in signals/slots +class SenderWithSharedPointerConstQObject : public QObject +{ + Q_OBJECT + +signals: + void aSignal1(const QSharedPointer<const QObject> &); + void aSignal2(const QWeakPointer<const QObject> &); + void aSignal3(const QPointer<const QObject> &); + void aSignal4(const std::shared_ptr<const QObject> &); + void aSignal5(const std::unique_ptr<const QObject> &); +}; + +#ifdef QT_BUILD_INTERNAL +/* + Since QObjectPrivate stores the declarativeData pointer in a union with the pointer + to the currently destroyed child, calls to the QtDeclarative handlers need to be + correctly guarded. QTBUG-105286 +*/ +namespace QtDeclarative { +static QAbstractDeclarativeData *theData; + +static void destroyed(QAbstractDeclarativeData *data, QObject *) +{ + QCOMPARE(data, theData); +} +static void signalEmitted(QAbstractDeclarativeData *data, QObject *, int, void **) +{ + QCOMPARE(data, theData); +} +// we can't use QCOMPARE in the next two functions, as they don't return void +static int receivers(QAbstractDeclarativeData *data, const QObject *, int) +{ + QTest::qCompare(data, theData, "data", "theData", __FILE__, __LINE__); + return 0; +} +static bool isSignalConnected(QAbstractDeclarativeData *data, const QObject *, int) +{ + QTest::qCompare(data, theData, "data", "theData", __FILE__, __LINE__); + return true; +} + +class Object : public QObject +{ + Q_OBJECT +public: + using QObject::QObject; + ~Object() + { + if (Object *p = static_cast<Object *>(parent())) + p->emitSignal(); + } + + void emitSignal() + { + emit theSignal(); + } + +signals: + void theSignal(); +}; + +} +#endif + +void tst_QObject::declarativeData() +{ +#ifdef QT_BUILD_INTERNAL + QScopedValueRollback destroyed(QAbstractDeclarativeData::destroyed, + QtDeclarative::destroyed); + QScopedValueRollback signalEmitted(QAbstractDeclarativeData::signalEmitted, + QtDeclarative::signalEmitted); + QScopedValueRollback receivers(QAbstractDeclarativeData::receivers, + QtDeclarative::receivers); + QScopedValueRollback isSignalConnected(QAbstractDeclarativeData::isSignalConnected, + QtDeclarative::isSignalConnected); + + QtDeclarative::Object p; + QObjectPrivate *priv = QObjectPrivate::get(&p); + priv->declarativeData = QtDeclarative::theData = new QAbstractDeclarativeData; + + connect(&p, &QtDeclarative::Object::theSignal, &p, []{ + }); + + QtDeclarative::Object *child = new QtDeclarative::Object; + child->setParent(&p); +#endif +} + +/* + Compile-time test for the helpers in qobjectdefs_impl.h. +*/ +class AsyncCaller : public QObject +{ + Q_OBJECT +public: + ~AsyncCaller() + { + if (slotObject) + slotObject->destroyIfLastRef(); + } + void callback0() {} + void callback1(const QString &) {} + void callbackInt(int) {} + int returnInt() const { return 0; } + + static int staticCallback0() { return 0; } + static void staticCallback1(const QString &) {} + + using Prototype0 = int(*)(); + using Prototype1 = void(*)(QString); + + template<typename Functor> + bool callMe0(const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *, Functor &&func) + { + if (slotObject) { + slotObject->destroyIfLastRef(); + slotObject = nullptr; + } + QtPrivate::AssertCompatibleFunctions<Prototype0, Functor>(); + slotObject = QtPrivate::makeCallableObject<Prototype0>(std::forward<Functor>(func)); + return true; + } + + template<typename Functor> + bool callMe0(Functor &&func) + { + return callMe0(nullptr, std::forward<Functor>(func)); + } + + template<typename Functor> + bool callMe1(const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *, Functor &&func) + { + if (slotObject) { + slotObject->destroyIfLastRef(); + slotObject = nullptr; + } + QtPrivate::AssertCompatibleFunctions<Prototype1, Functor>(); + slotObject = QtPrivate::makeCallableObject<Prototype1>(std::forward<Functor>(func)); + return true; + } + + template<typename Functor> + bool callMe1(Functor &&func) + { + return callMe1(nullptr, std::forward<Functor>(func)); + } + + QtPrivate::QSlotObjectBase *slotObject = nullptr; +}; + +static void freeFunction0() {} +static void freeFunction1(QString) {} +static void freeFunctionVariant(QVariant) {} + +template<typename Prototype, typename Functor> +inline constexpr bool compiles(Functor &&) { + return QtPrivate::AreFunctionsCompatible<Prototype, Functor>::value; +} + +void tst_QObject::asyncCallbackHelper() +{ + int result = 0; + QString arg1 = "Parameter"; + void *argv[] = { &result, &arg1 }; + + auto lambda0 = []{}; + auto lambda1 = [](const QString &) {}; + auto lambda2 = [](const QString &, int) {}; + const auto constLambda = [](const QString &) {}; + auto moveOnlyLambda = [u = std::unique_ptr<int>()]{}; + auto moveOnlyLambda1 = [u = std::unique_ptr<int>()](const QString &){}; + + SlotFunctor functor0; + SlotFunctorString functor1; + + // no parameters provided or needed + static_assert(compiles<AsyncCaller::Prototype0>(&AsyncCaller::callback0)); + static_assert(compiles<AsyncCaller::Prototype0>(&AsyncCaller::staticCallback0)); + static_assert(compiles<AsyncCaller::Prototype0>(lambda0)); + static_assert(compiles<AsyncCaller::Prototype0>(std::move(moveOnlyLambda))); + static_assert(compiles<AsyncCaller::Prototype0>(freeFunction0)); + static_assert(compiles<AsyncCaller::Prototype0>(functor0)); + + // more parameters than needed + static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::callback0)); + static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::staticCallback0)); + static_assert(compiles<AsyncCaller::Prototype1>(lambda0)); + static_assert(compiles<AsyncCaller::Prototype1>(freeFunction0)); + static_assert(compiles<AsyncCaller::Prototype1>(functor0)); + + // matching parameter + static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::callback1)); + static_assert(compiles<AsyncCaller::Prototype1>(&AsyncCaller::staticCallback1)); + static_assert(compiles<AsyncCaller::Prototype1>(lambda1)); + static_assert(compiles<AsyncCaller::Prototype1>(std::move(moveOnlyLambda1))); + static_assert(compiles<AsyncCaller::Prototype1>(constLambda)); + static_assert(compiles<AsyncCaller::Prototype1>(freeFunction1)); + static_assert(compiles<AsyncCaller::Prototype1>(functor1)); + + // not enough parameters + static_assert(!compiles<AsyncCaller::Prototype0>(&AsyncCaller::callback1)); + static_assert(!compiles<AsyncCaller::Prototype0>(&AsyncCaller::staticCallback1)); + static_assert(!compiles<AsyncCaller::Prototype0>(lambda1)); + static_assert(!compiles<AsyncCaller::Prototype0>(constLambda)); + static_assert(!compiles<AsyncCaller::Prototype0>(lambda2)); + static_assert(!compiles<AsyncCaller::Prototype0>(freeFunction1)); + static_assert(!compiles<AsyncCaller::Prototype0>(functor1)); + + // wrong parameter type + static_assert(!compiles<AsyncCaller::Prototype1>(&AsyncCaller::callbackInt)); + + // old-style slot name + static_assert(!compiles<AsyncCaller::Prototype0>("callback1")); + + // slot with return value is ok, we just don't pass + // the return value through to anything. + static_assert(compiles<AsyncCaller::Prototype0>(&AsyncCaller::returnInt)); + + static_assert(compiles<AsyncCaller::Prototype1>(freeFunctionVariant)); + + std::function<int()> stdFunction0(&AsyncCaller::staticCallback0); + std::function<void(QString)> stdFunction1(&AsyncCaller::staticCallback1); + static_assert(compiles<AsyncCaller::Prototype0>(stdFunction0)); + static_assert(compiles<AsyncCaller::Prototype1>(stdFunction1)); + + AsyncCaller caller; + // with context + QVERIFY(caller.callMe0(&caller, &AsyncCaller::callback0)); + QVERIFY(caller.callMe0(&caller, &AsyncCaller::returnInt)); + QVERIFY(caller.callMe0(&caller, &AsyncCaller::staticCallback0)); + QVERIFY(caller.callMe0(&caller, lambda0)); + QVERIFY(caller.callMe0(&caller, freeFunction0)); + QVERIFY(caller.callMe0(&caller, std::move(moveOnlyLambda))); + QVERIFY(caller.callMe0(&caller, stdFunction0)); + + QVERIFY(caller.callMe1(&caller, &AsyncCaller::callback1)); + QVERIFY(caller.callMe1(&caller, &AsyncCaller::staticCallback1)); + QVERIFY(caller.callMe1(&caller, lambda1)); + QVERIFY(caller.callMe1(&caller, freeFunction1)); + QVERIFY(caller.callMe1(&caller, constLambda)); + QVERIFY(caller.callMe1(&caller, stdFunction1)); + + // without context + QVERIFY(caller.callMe0(&AsyncCaller::staticCallback0)); + QVERIFY(caller.callMe0(lambda0)); + QVERIFY(caller.callMe0(freeFunction0)); + QVERIFY(caller.callMe0(stdFunction0)); + + QVERIFY(caller.callMe1(&AsyncCaller::staticCallback1)); + QVERIFY(caller.callMe1(lambda1)); + QVERIFY(caller.callMe1(constLambda)); + QVERIFY(caller.callMe1(std::move(moveOnlyLambda1))); + QVERIFY(caller.callMe1(freeFunction1)); + QVERIFY(caller.callMe1(stdFunction1)); + + static const char *expectedPayload = "Hello World!"; + { + struct MoveOnlyFunctor { + MoveOnlyFunctor() = default; + MoveOnlyFunctor(MoveOnlyFunctor &&) = default; + MoveOnlyFunctor(const MoveOnlyFunctor &) = delete; + ~MoveOnlyFunctor() = default; + + int operator()() const { + qDebug().noquote() << payload; + return int(payload.length()); + } + QString payload = expectedPayload; + } moveOnlyFunctor; + QVERIFY(caller.callMe0(std::move(moveOnlyFunctor))); + } + QTest::ignoreMessage(QtDebugMsg, expectedPayload); + caller.slotObject->call(nullptr, argv); + QCOMPARE(result, QLatin1String(expectedPayload).length()); + + // mutable lambda; same behavior as mutableFunctor - we copy the functor + // in the QCallableObject, so the original is not modified + int status = 0; + auto mutableLambda1 = [&status, calls = 0]() mutable { status = ++calls; }; + + mutableLambda1(); + QCOMPARE(status, 1); + QVERIFY(caller.callMe0(mutableLambda1)); // this copies the lambda with count == 1 + caller.slotObject->call(nullptr, argv); // this doesn't change mutableLambda1, but the copy + QCOMPARE(status, 2); + mutableLambda1(); + QCOMPARE(status, 2); // and we are still at two + + auto mutableLambda2 = [calls = 0]() mutable { return ++calls; }; + QCOMPARE(mutableLambda2(), 1); + QVERIFY(caller.callMe0(mutableLambda2)); // this copies the lambda + caller.slotObject->call(nullptr, argv); // this call doesn't change mutableLambda2 + QCOMPARE(mutableLambda2(), 2); // so we are still at 2 + + { + int called = -1; + struct MutableFunctor { + void operator()() { called = 0; } + int &called; + }; + struct ConstFunctor + { + void operator()() const { called = 1; } + int &called; + }; + + MutableFunctor mf{called}; + QMetaObject::invokeMethod(this, mf); + QCOMPARE(called, 0); + ConstFunctor cf{called}; + QMetaObject::invokeMethod(this, cf); + QCOMPARE(called, 1); + QMetaObject::invokeMethod(this, [&called, u = std::unique_ptr<int>()]{ called = 2; }); + QCOMPARE(called, 2); + QMetaObject::invokeMethod(this, [&called, count = 0]() mutable { + if (!count) + called = 3; + ++count; + }); + QCOMPARE(called, 3); + } +} QTEST_MAIN(tst_QObject) #include "tst_qobject.moc" |