Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsignalspy.cpp
Go to the documentation of this file.
1// Copyright (C) 2019 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#include "qsignalspy.h"
5
7
8/*!
9 \class QSignalSpy
10 \inmodule QtTest
11
12 \brief The QSignalSpy class enables introspection of signal emission.
13
14 QSignalSpy can connect to any signal of any object and records its emission.
15 QSignalSpy itself is a list of QVariant lists. Each emission of the signal
16 will append one item to the list, containing the arguments of the signal.
17
18 The following example records all signal emissions for the \c clicked() signal
19 of a QCheckBox:
20
21 \snippet code/doc_src_qsignalspy.cpp 0
22
23 \c{spy.takeFirst()} returns the arguments for the first emitted signal, as a
24 list of QVariant objects. The \c clicked() signal has a single bool argument,
25 which is stored as the first entry in the list of arguments.
26
27 The example below catches a signal from a custom object:
28
29 \snippet code/doc_src_qsignalspy.cpp 1
30
31 \note Non-standard data types need to be registered, using
32 the qRegisterMetaType() function, before you can create a
33 QSignalSpy. For example:
34
35 \snippet code/doc_src_qsignalspy.cpp 2
36
37 To retrieve the instance, you can use qvariant_cast:
38
39 \snippet code/doc_src_qsignalspy.cpp 3
40
41 \section1 Verifying Signal Emissions
42
43 The QSignalSpy class provides an elegant mechanism for capturing the list
44 of signals emitted by an object. However, you should verify its validity
45 after construction. The constructor does a number of sanity checks, such as
46 verifying that the signal to be spied upon actually exists. To make the
47 diagnosis of test failures easier, the results of these checks should be
48 checked by calling \c QVERIFY(spy.isValid()) before proceeding further with
49 a test.
50
51 \sa QVERIFY()
52 */
53
54/*! \fn QSignalSpy::QSignalSpy(const QObject *object, const char *signal)
55
56 Constructs a new QSignalSpy that listens for emissions of the \a signal
57 from the QObject \a object. If QSignalSpy is not able to listen for a
58 valid signal (for example, because \a object is \nullptr or \a signal does
59 not denote a valid signal of \a object), an explanatory warning message
60 will be output using qWarning() and subsequent calls to \c isValid() will
61 return false.
62
63 Example:
64 \snippet code/doc_src_qsignalspy.cpp 4
65*/
66
67/*! \fn template <typename PointerToMemberFunction> QSignalSpy::QSignalSpy(const QObject *object, PointerToMemberFunction signal)
68 \since 5.4
69
70 Constructs a new QSignalSpy that listens for emissions of the \a signal
71 from the QObject \a object. If QSignalSpy is not able to listen for a
72 valid signal (for example, because \a object is \nullptr or \a signal does
73 not denote a valid signal of \a object), an explanatory warning message
74 will be output using qWarning() and subsequent calls to \c isValid() will
75 return false.
76
77 Example:
78 \snippet code/doc_src_qsignalspy.cpp 6
79*/
80
81/*! \fn QSignalSpy::QSignalSpy(const QObject *obj, QMetaMethod signal)
82 \since 5.14
83
84 Constructs a new QSignalSpy that listens for emissions of the \a signal
85 from the QObject \a obj. If QSignalSpy is not able to listen for a
86 valid signal (for example, because \a obj is \nullptr or \a signal does
87 not denote a valid signal of \a obj), an explanatory warning message
88 will be output using qWarning() and subsequent calls to \c isValid() will
89 return false.
90
91 This constructor is convenient to use when Qt's meta-object system is
92 heavily used in a test.
93
94 Basic usage example:
95 \snippet code/doc_src_qsignalspy.cpp 7
96
97 Imagine we need to check whether all properties of the QWindow class
98 that represent minimum and maximum dimensions are properly writable.
99 The following example demonstrates one of the approaches:
100 \snippet code/doc_src_qsignalspy.cpp 8
101*/
102
103/*! \fn QSignalSpy::isValid() const
104
105 Returns \c true if the signal spy listens to a valid signal, otherwise false.
106*/
107
108/*! \fn QSignalSpy::signal() const
109
110 Returns the normalized signal the spy is currently listening to.
111*/
112
113/*! \fn bool QSignalSpy::wait(int timeout)
114 \since 5.0
115
116 This is an overloaded function, equivalent passing \a timeout to the
117 chrono overload:
118 \code
119 wait(std::chrono::milliseconds{timeout});
120 \endcode
121
122 Returns \c true if the signal was emitted at least once in \a timeout,
123 otherwise returns \c false.
124*/
125
126/*!
127 \since 6.6
128
129 Starts an event loop that runs until the given signal is received
130 or \a timeout has passed, whichever happens first.
131
132 \a timeout is any valid std::chrono::duration (std::chrono::seconds,
133 std::chrono::milliseconds ...etc).
134
135 Returns \c true if the signal was emitted at least once in \a timeout,
136 otherwise returns \c false.
137
138 Example:
139 \code
140 using namespace std::chrono_literals;
141 QSignalSpy spy(object, signal);
142 spy.wait(2s);
143 \endcode
144*/
145bool QSignalSpy::wait(std::chrono::milliseconds timeout)
146{
147 QMutexLocker locker(&m_mutex);
148 Q_ASSERT(!m_waiting);
149 const qsizetype origCount = size();
150 m_waiting = true;
151 locker.unlock();
152
153 m_loop.enterLoop(timeout);
154
155 locker.relock();
156 m_waiting = false;
157 return size() > origCount;
158}
159
160static bool isSignalMetaMethodValid(QMetaMethod signal)
161{
162 if (!signal.isValid()) {
163 qWarning("QSignalSpy: Null signal is not valid");
164 return false;
165 }
166
167 if (signal.methodType() != QMetaMethod::Signal) {
168 qWarning("QSignalSpy: Not a signal: '%s'", signal.methodSignature().constData());
169 return false;
170 }
171
172 return true;
173}
174
175static bool isObjectValid(const QObject *object)
176{
177 const bool valid = !!object;
178
179 if (!valid)
180 qWarning("QSignalSpy: Cannot spy on a null object");
181
182 return valid;
183}
184
185QSignalSpy::ObjectSignal QSignalSpy::verify(const QObject *obj, const char *aSignal)
186{
187 if (!isObjectValid(obj))
188 return {};
189
190 if (!aSignal) {
191 qWarning("QSignalSpy: Null signal name is not valid");
192 return {};
193 }
194
195 if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
196 qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro");
197 return {};
198 }
199
200 const QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
201 const QMetaObject * const mo = obj->metaObject();
202 const int sigIndex = mo->indexOfMethod(ba.constData());
203 if (sigIndex < 0) {
204 qWarning("QSignalSpy: No such signal: '%s'", ba.constData());
205 return {};
206 }
207
208 return verify(obj, mo->method(sigIndex));
209}
210
211QSignalSpy::ObjectSignal QSignalSpy::verify(const QObject *obj, QMetaMethod signal)
212{
213 if (isObjectValid(obj) && isSignalMetaMethodValid(signal))
214 return {obj, signal};
215 else
216 return {};
217}
218
219static QList<int> makeArgs(QMetaMethod member)
220{
221 QList<int> result;
222 result.reserve(member.parameterCount());
223 for (int i = 0; i < member.parameterCount(); ++i) {
224 QMetaType tp = member.parameterMetaType(i);
225 if (!tp.isValid()) {
226 qWarning("QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s',"
227 " use qRegisterMetaType to register it.",
228 member.parameterNames().at(i).constData(),
229 member.parameterTypes().at(i).constData(),
230 member.name().constData());
231 }
232 result.append(tp.id());
233 }
234 return result;
235}
236
238{
239 QSignalSpy * const q;
240public:
241 explicit QSignalSpyPrivate(QSignalSpy *qq) : q(qq) {}
242
243 int qt_metacall(QMetaObject::Call call, int methodId, void **a) override;
244};
245
246QSignalSpy::QSignalSpy(ObjectSignal os)
247 : sig(os.sig.methodSignature()),
248 args(os.obj ? makeArgs(os.sig) : QList<int>{})
249{
250 if (!os.obj)
251 return;
252
253 auto i = std::make_unique<QSignalSpyPrivate>(this);
254
255 const auto signalIndex = os.sig.methodIndex();
256 const auto slotIndex = QObject::staticMetaObject.methodCount();
257 if (!QMetaObject::connect(os.obj, signalIndex,
258 i.get(), slotIndex, Qt::DirectConnection)) {
259 qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
260 return;
261 }
262
263 d_ptr = std::move(i);
264}
265
266/*!
267 Destructor.
268*/
270 = default;
271
272void QSignalSpy::appendArgs(void **a)
273{
274 QList<QVariant> list;
275 list.reserve(args.size());
276 for (qsizetype i = 0; i < args.size(); ++i) {
277 const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
278 if (type == QMetaType::QVariant)
279 list << *reinterpret_cast<QVariant *>(a[i + 1]);
280 else
281 list << QVariant(QMetaType(type), a[i + 1]);
282 }
283 QMutexLocker locker(&m_mutex);
284 append(std::move(list));
285
286 if (m_waiting) {
287 locker.unlock();
288 m_loop.exitLoop();
289 }
290}
291
292/*!
293 \reimp
294 \internal
295*/
296int QSignalSpyPrivate::qt_metacall(QMetaObject::Call call, int methodId, void **a)
297{
298 methodId = QObject::qt_metacall(call, methodId, a);
299 if (methodId < 0)
300 return methodId;
301
302 if (call == QMetaObject::InvokeMetaMethod) {
303 if (methodId == 0) {
304 q->appendArgs(a);
305 }
306 --methodId;
307 }
308 return methodId;
309}
310
311QT_END_NAMESPACE
QSignalSpyPrivate(QSignalSpy *qq)
int qt_metacall(QMetaObject::Call call, int methodId, void **a) override
\reimp
\inmodule QtTest
Definition qsignalspy.h:22
bool wait(int timeout)
Definition qsignalspy.h:47
Q_TESTLIB_EXPORT ~QSignalSpy()
Destructor.
Combined button and popup list for selecting options.
static bool isSignalMetaMethodValid(QMetaMethod signal)
static bool isObjectValid(const QObject *object)
static QList< int > makeArgs(QMetaMethod member)