summaryrefslogtreecommitdiffstats
path: root/src/testlib/qsignalspy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/testlib/qsignalspy.cpp')
-rw-r--r--src/testlib/qsignalspy.cpp179
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