diff options
Diffstat (limited to 'src/testlib/qsignalspy.cpp')
-rw-r--r-- | src/testlib/qsignalspy.cpp | 179 |
1 files changed, 173 insertions, 6 deletions
diff --git a/src/testlib/qsignalspy.cpp b/src/testlib/qsignalspy.cpp index 13c34c7751..116ce87c3e 100644 --- a/src/testlib/qsignalspy.cpp +++ b/src/testlib/qsignalspy.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2019 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qsignalspy.h" @@ -110,10 +110,6 @@ QT_BEGIN_NAMESPACE Returns the normalized signal the spy is currently listening to. */ -/*! \fn int QSignalSpy::qt_metacall(QMetaObject::Call call, int id, void **a) - \internal -*/ - /*! \fn bool QSignalSpy::wait(int timeout) \since 5.0 @@ -127,7 +123,7 @@ QT_BEGIN_NAMESPACE otherwise returns \c false. */ -/*! \fn bool QSignalSpy::wait(std::chrono::milliseconds timeout) +/*! \since 6.6 Starts an event loop that runs until the given signal is received @@ -146,5 +142,176 @@ QT_BEGIN_NAMESPACE spy.wait(2s); \endcode */ +bool QSignalSpy::wait(std::chrono::milliseconds timeout) +{ + QMutexLocker locker(&m_mutex); + Q_ASSERT(!m_waiting); + const qsizetype origCount = size(); + m_waiting = true; + locker.unlock(); + + m_loop.enterLoop(timeout); + + locker.relock(); + m_waiting = false; + return size() > origCount; +} + +static bool isSignalMetaMethodValid(QMetaMethod signal) +{ + if (!signal.isValid()) { + qWarning("QSignalSpy: Null signal is not valid"); + return false; + } + + if (signal.methodType() != QMetaMethod::Signal) { + qWarning("QSignalSpy: Not a signal: '%s'", signal.methodSignature().constData()); + return false; + } + + return true; +} + +static bool isObjectValid(const QObject *object) +{ + const bool valid = !!object; + + if (!valid) + qWarning("QSignalSpy: Cannot spy on a null object"); + + return valid; +} + +QSignalSpy::ObjectSignal QSignalSpy::verify(const QObject *obj, const char *aSignal) +{ + if (!isObjectValid(obj)) + return {}; + + if (!aSignal) { + qWarning("QSignalSpy: Null signal name is not valid"); + return {}; + } + + if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) { + qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro"); + return {}; + } + + const QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1); + const QMetaObject * const mo = obj->metaObject(); + const int sigIndex = mo->indexOfMethod(ba.constData()); + if (sigIndex < 0) { + qWarning("QSignalSpy: No such signal: '%s'", ba.constData()); + return {}; + } + + return verify(obj, mo->method(sigIndex)); +} + +QSignalSpy::ObjectSignal QSignalSpy::verify(const QObject *obj, QMetaMethod signal) +{ + if (isObjectValid(obj) && isSignalMetaMethodValid(signal)) + return {obj, signal}; + else + return {}; +} + +static QList<int> makeArgs(QMetaMethod member, const QObject *obj) +{ + QList<int> result; + result.reserve(member.parameterCount()); + for (int i = 0; i < member.parameterCount(); ++i) { + QMetaType tp = member.parameterMetaType(i); + if (!tp.isValid() && obj) { + void *argv[] = { &tp, &i }; + QMetaObject::metacall(const_cast<QObject*>(obj), + QMetaObject::RegisterMethodArgumentMetaType, + member.methodIndex(), argv); + } + if (!tp.isValid()) { + qWarning("QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s'," + " use qRegisterMetaType to register it.", + member.parameterNames().at(i).constData(), + member.parameterTypes().at(i).constData(), + member.name().constData()); + } + result.append(tp.id()); + } + return result; +} + +class QSignalSpyPrivate : public QObject +{ + QSignalSpy * const q; +public: + explicit QSignalSpyPrivate(QSignalSpy *qq) : q(qq) {} + + int qt_metacall(QMetaObject::Call call, int methodId, void **a) override; +}; + +QSignalSpy::QSignalSpy(ObjectSignal os) + : sig(os.sig.methodSignature()), + args(os.obj ? makeArgs(os.sig, os.obj) : QList<int>{}) +{ + if (!os.obj) + return; + + auto i = std::make_unique<QSignalSpyPrivate>(this); + + const auto signalIndex = os.sig.methodIndex(); + const auto slotIndex = QObject::staticMetaObject.methodCount(); + if (!QMetaObject::connect(os.obj, signalIndex, + i.get(), slotIndex, Qt::DirectConnection)) { + qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect."); + return; + } + + d_ptr = std::move(i); +} + +/*! + Destructor. +*/ +QSignalSpy::~QSignalSpy() + = default; + +void QSignalSpy::appendArgs(void **a) +{ + QList<QVariant> list; + list.reserve(args.size()); + for (qsizetype i = 0; i < args.size(); ++i) { + const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i)); + if (type == QMetaType::QVariant) + list << *reinterpret_cast<QVariant *>(a[i + 1]); + else + list << QVariant(QMetaType(type), a[i + 1]); + } + QMutexLocker locker(&m_mutex); + append(std::move(list)); + + if (m_waiting) { + locker.unlock(); + m_loop.exitLoop(); + } +} + +/*! + \reimp + \internal +*/ +int QSignalSpyPrivate::qt_metacall(QMetaObject::Call call, int methodId, void **a) +{ + methodId = QObject::qt_metacall(call, methodId, a); + if (methodId < 0) + return methodId; + + if (call == QMetaObject::InvokeMetaMethod) { + if (methodId == 0) { + q->appendArgs(a); + } + --methodId; + } + return methodId; +} QT_END_NAMESPACE |