1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#ifndef QSIGNALSPY_H
5#define QSIGNALSPY_H
6
7#include <QtCore/qbytearray.h>
8#include <QtCore/qlist.h>
9#include <QtCore/qobject.h>
10#include <QtCore/qmetaobject.h>
11#include <QtTest/qtesteventloop.h>
12#include <QtCore/qvariant.h>
13
14QT_BEGIN_NAMESPACE
15
16
17class QVariant;
18
19class QSignalSpy: public QObject, public QList<QList<QVariant> >
20{
21public:
22 explicit QSignalSpy(const QObject *obj, const char *aSignal)
23 : m_waiting(false)
24 {
25 if (!isObjectValid(object: obj))
26 return;
27
28 if (!aSignal) {
29 qWarning(msg: "QSignalSpy: Null signal name is not valid");
30 return;
31 }
32
33 if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
34 qWarning(msg: "QSignalSpy: Not a valid signal, use the SIGNAL macro");
35 return;
36 }
37
38 const QByteArray ba = QMetaObject::normalizedSignature(method: aSignal + 1);
39 const QMetaObject * const mo = obj->metaObject();
40 const int sigIndex = mo->indexOfMethod(method: ba.constData());
41 if (sigIndex < 0) {
42 qWarning(msg: "QSignalSpy: No such signal: '%s'", ba.constData());
43 return;
44 }
45
46 if (!connectToSignal(sender: obj, sigIndex))
47 return;
48
49 sig = ba;
50 initArgs(member: mo->method(index: sigIndex), obj);
51 }
52
53#ifdef Q_QDOC
54 template <typename PointerToMemberFunction>
55 QSignalSpy(const QObject *object, PointerToMemberFunction signal);
56#else
57 template <typename Func>
58 QSignalSpy(const typename QtPrivate::FunctionPointer<Func>::Object *obj, Func signal0)
59 : m_waiting(false)
60 {
61 if (!isObjectValid(object: obj))
62 return;
63
64 if (!signal0) {
65 qWarning(msg: "QSignalSpy: Null signal name is not valid");
66 return;
67 }
68
69 const QMetaObject * const mo = obj->metaObject();
70 const QMetaMethod signalMetaMethod = QMetaMethod::fromSignal(signal0);
71 const int sigIndex = signalMetaMethod.methodIndex();
72
73 if (!isSignalMetaMethodValid(signal: signalMetaMethod))
74 return;
75
76 if (!connectToSignal(sender: obj, sigIndex))
77 return;
78
79 sig = signalMetaMethod.methodSignature();
80 initArgs(member: mo->method(index: sigIndex), obj);
81 }
82#endif // Q_QDOC
83
84 QSignalSpy(const QObject *obj, const QMetaMethod &signal)
85 : m_waiting(false)
86 {
87 if (isObjectValid(object: obj) && isSignalMetaMethodValid(signal) &&
88 connectToSignal(sender: obj, sigIndex: signal.methodIndex())) {
89 sig = signal.methodSignature();
90 initArgs(member: signal, obj);
91 }
92 }
93
94 inline bool isValid() const { return !sig.isEmpty(); }
95 inline QByteArray signal() const { return sig; }
96
97 bool wait(int timeout)
98 { return wait(timeout: std::chrono::milliseconds{timeout}); }
99
100 bool wait(std::chrono::milliseconds timeout = std::chrono::seconds{5})
101 {
102 Q_ASSERT(!m_waiting);
103 const qsizetype origCount = size();
104 m_waiting = true;
105 m_loop.enterLoop(msecs: timeout);
106 m_waiting = false;
107 return size() > origCount;
108 }
109
110 int qt_metacall(QMetaObject::Call call, int methodId, void **a) override
111 {
112 methodId = QObject::qt_metacall(call, methodId, a);
113 if (methodId < 0)
114 return methodId;
115
116 if (call == QMetaObject::InvokeMetaMethod) {
117 if (methodId == 0) {
118 appendArgs(a);
119 }
120 --methodId;
121 }
122 return methodId;
123 }
124
125private:
126 bool connectToSignal(const QObject *sender, int sigIndex)
127 {
128 static const int memberOffset = QObject::staticMetaObject.methodCount();
129 const bool connected = QMetaObject::connect(
130 sender, signal_index: sigIndex, receiver: this, method_index: memberOffset, type: Qt::DirectConnection, types: nullptr);
131
132 if (!connected)
133 qWarning(msg: "QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
134
135 return connected;
136 }
137
138 static bool isSignalMetaMethodValid(const QMetaMethod &signal)
139 {
140 const bool valid = signal.isValid() && signal.methodType() == QMetaMethod::Signal;
141
142 if (!valid)
143 qWarning(msg: "QSignalSpy: Not a valid signal: '%s'", signal.methodSignature().constData());
144
145 return valid;
146 }
147
148 static bool isObjectValid(const QObject *object)
149 {
150 const bool valid = !!object;
151
152 if (!valid)
153 qWarning(msg: "QSignalSpy: Cannot spy on a null object");
154
155 return valid;
156 }
157
158 void initArgs(const QMetaMethod &member, const QObject *obj)
159 {
160 args.reserve(asize: member.parameterCount());
161 for (int i = 0; i < member.parameterCount(); ++i) {
162 QMetaType tp = member.parameterMetaType(index: i);
163 if (!tp.isValid() && obj) {
164 void *argv[] = { &tp, &i };
165 QMetaObject::metacall(const_cast<QObject*>(obj),
166 QMetaObject::RegisterMethodArgumentMetaType,
167 member.methodIndex(), argv);
168 }
169 if (!tp.isValid()) {
170 qWarning(msg: "QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s',"
171 " use qRegisterMetaType to register it.",
172 member.parameterNames().at(i).constData(),
173 member.parameterTypes().at(i).constData(),
174 member.name().constData());
175 }
176 args << tp.id();
177 }
178 }
179
180 void appendArgs(void **a)
181 {
182 QList<QVariant> list;
183 list.reserve(asize: args.size());
184 for (int i = 0; i < args.size(); ++i) {
185 const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
186 if (type == QMetaType::QVariant)
187 list << *reinterpret_cast<QVariant *>(a[i + 1]);
188 else
189 list << QVariant(QMetaType(type), a[i + 1]);
190 }
191 append(t: list);
192
193 if (m_waiting)
194 m_loop.exitLoop();
195 }
196
197 // the full, normalized signal name
198 QByteArray sig;
199 // holds the QMetaType types for the argument list of the signal
200 QList<int> args;
201
202 QTestEventLoop m_loop;
203 bool m_waiting;
204};
205
206QT_END_NAMESPACE
207
208#endif
209

source code of qtbase/src/testlib/qsignalspy.h