diff options
Diffstat (limited to 'tests/auto/corelib/kernel/qobject/tst_qobject.cpp')
-rw-r--r-- | tests/auto/corelib/kernel/qobject/tst_qobject.cpp | 873 |
1 files changed, 787 insertions, 86 deletions
diff --git a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp index f5c16faa56..6c387fde96 100644 --- a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp +++ b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp @@ -1,31 +1,12 @@ -/**************************************************************************** -** -** Copyright (C) 2021 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$ -** -****************************************************************************/ +// 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> @@ -35,6 +16,7 @@ #include <qcoreapplication.h> #include <qpointer.h> +#include <qproperty.h> #include <qtimer.h> #include <qregularexpression.h> #include <qmetaobject.h> @@ -57,6 +39,8 @@ #include <math.h> +using namespace Qt::StringLiterals; + class tst_QObject : public QObject { Q_OBJECT @@ -76,6 +60,7 @@ private slots: void connectNotify_connectSlotsByName(); void connectDisconnectNotify_shadowing(); void connectReferenceToIncompleteTypes(); + void connectAutoQueuedIncomplete(); void emitInDefinedOrder(); void customTypes(); void streamCustomTypes(); @@ -97,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(); @@ -163,6 +151,9 @@ private slots: void disconnectDisconnects(); void singleShotConnection(); void objectNameBinding(); + void emitToDestroyedClass(); + void declarativeData(); + void asyncCallbackHelper(); }; struct QObjectCreatedOnShutdown @@ -520,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; @@ -532,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); @@ -542,6 +543,13 @@ 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 = nullptr; @@ -572,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; @@ -747,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); @@ -755,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); @@ -906,6 +950,42 @@ void tst_QObject::connectReferenceToIncompleteTypes() { 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() @@ -973,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(); @@ -984,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(); @@ -994,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)); } @@ -1009,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); } } @@ -1494,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)), @@ -1508,11 +1587,6 @@ 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); } @@ -1521,13 +1595,15 @@ void tst_QObject::streamCustomTypes() { QByteArray ba; - int idx = qRegisterMetaType<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); @@ -1536,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); @@ -1795,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 @@ -1972,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"); @@ -1991,7 +2069,7 @@ 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 = nullptr; @@ -2006,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)); @@ -2036,13 +2114,13 @@ void tst_QObject::property() 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(), "PropertyObject::Priority"); - QCOMPARE(property.type(), QVariant::UserType); + QCOMPARE_GE(property.typeId(), QMetaType::User); QCOMPARE(property.userType(), priorityMetaTypeId); var = object.property("priority"); @@ -2065,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"); @@ -2138,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")); @@ -2904,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); @@ -2919,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))); @@ -2964,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(); @@ -3064,6 +3142,8 @@ void tst_QObject::blockingQueuedConnection() } } +static int s_eventSpyCounter = -1; + class EventSpy : public QObject { Q_OBJECT @@ -3083,14 +3163,17 @@ public: void clear() { events.clear(); + thisCounter = -1; } bool eventFilter(QObject *object, QEvent *event) override { events.append(qMakePair(object, event->type())); + thisCounter = ++s_eventSpyCounter; return false; } + int thisCounter = -1; private: EventList events; }; @@ -3179,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); @@ -3220,6 +3375,70 @@ 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: @@ -3403,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: @@ -3580,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() @@ -4498,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() @@ -4680,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); @@ -4694,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); @@ -5219,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); @@ -5933,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 { @@ -5953,6 +6201,8 @@ public: receivedCount++; receivedValue = v; }; + + void testFromPrivate(SenderObject *obj); }; ConnectToPrivateSlot::ConnectToPrivateSlot(): QObject(*new ConnectToPrivateSlotPrivate) {} @@ -5979,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; @@ -6015,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, [=](){}); @@ -6239,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 @@ -6752,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; @@ -7017,7 +7316,7 @@ void tst_QObject::checkArgumentsForNarrowing() { // 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) && !defined(Q_CC_INTEL) +#if defined(Q_CC_GNU) && !defined(Q_CC_CLANG) #define Q_CC_EXACTLY_GCC Q_CC_GNU #endif @@ -7224,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); @@ -7236,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); @@ -7306,20 +7612,22 @@ void tst_QObject::checkArgumentsForNarrowing() /* implicit */ operator double() const { return 42.0; } }; +#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 and clang don't implement this properly yet: + // 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) // at least since VS2017 +#if defined(Q_CC_MSVC) && !defined(Q_CC_CLANG) // at least since VS2017 struct ConstructibleFromInt { /* implicit */ ConstructibleFromInt(int) {} }; @@ -8124,9 +8432,402 @@ void tst_QObject::objectNameBinding() "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 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" |