diff options
Diffstat (limited to 'tests/auto/corelib/kernel/qobject/tst_qobject.cpp')
-rw-r--r-- | tests/auto/corelib/kernel/qobject/tst_qobject.cpp | 688 |
1 files changed, 638 insertions, 50 deletions
diff --git a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp index f765440e71..6c387fde96 100644 --- a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp +++ b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp @@ -1,7 +1,7 @@ // 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 WITH Qt-GPL-exception-1.0 +// 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 @@ -39,6 +39,8 @@ #include <math.h> +using namespace Qt::StringLiterals; + class tst_QObject : public QObject { Q_OBJECT @@ -58,6 +60,7 @@ private slots: void connectNotify_connectSlotsByName(); void connectDisconnectNotify_shadowing(); void connectReferenceToIncompleteTypes(); + void connectAutoQueuedIncomplete(); void emitInDefinedOrder(); void customTypes(); void streamCustomTypes(); @@ -79,7 +82,9 @@ private slots: void signalBlocking(); void blockingQueuedConnection(); void childEvents(); + void parentEvents(); void installEventFilter(); + void installEventFilterOrder(); void deleteSelfInSlot(); void disconnectSelfInSlotAndDeleteAfterEmit(); void dumpObjectInfo(); @@ -147,6 +152,8 @@ private slots: void singleShotConnection(); void objectNameBinding(); void emitToDestroyedClass(); + void declarativeData(); + void asyncCallbackHelper(); }; struct QObjectCreatedOnShutdown @@ -504,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; @@ -516,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); @@ -526,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; @@ -556,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; @@ -731,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); @@ -892,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() @@ -959,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(); @@ -970,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(); @@ -980,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)); } @@ -995,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); } } @@ -1480,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)), @@ -1494,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); } @@ -1507,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); @@ -1522,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); @@ -1781,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 @@ -1958,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"); @@ -1977,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; @@ -1992,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)); @@ -2022,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"); @@ -2051,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"); @@ -2124,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")); @@ -2890,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); @@ -2905,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))); @@ -2950,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(); @@ -3050,6 +3142,8 @@ void tst_QObject::blockingQueuedConnection() } } +static int s_eventSpyCounter = -1; + class EventSpy : public QObject { Q_OBJECT @@ -3069,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; }; @@ -3165,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); @@ -3206,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: @@ -4701,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); @@ -4715,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); @@ -5954,15 +6181,15 @@ 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 { -public: Q_DECLARE_PUBLIC(ConnectToPrivateSlot) +public: int receivedCount; QVariant receivedValue; @@ -5974,6 +6201,8 @@ public: receivedCount++; receivedValue = v; }; + + void testFromPrivate(SenderObject *obj); }; ConnectToPrivateSlot::ConnectToPrivateSlot(): QObject(*new ConnectToPrivateSlotPrivate) {} @@ -6000,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; @@ -6036,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, [=](){}); @@ -6263,11 +6501,46 @@ void tst_QObject::connectFunctorWithContextUnique() QVERIFY(QObject::connect(&sender, &SenderObject::signal1, &receiver, &ReceiverObject::slot1)); receiver.count_slot1 = 0; - QTest::ignoreMessage(QtWarningMsg, "QObject::connect(SenderObject, ReceiverObject): unique connections require a pointer to member function of a QObject subclass"); + 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 @@ -6774,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; @@ -8241,5 +8518,316 @@ signals: 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" |