1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtTest module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#ifndef QSIGNALSPY_H
41#define QSIGNALSPY_H
42
43#include <QtCore/qbytearray.h>
44#include <QtCore/qlist.h>
45#include <QtCore/qobject.h>
46#include <QtCore/qmetaobject.h>
47#include <QtCore/qvariant.h>
48#include <QtCore/qvector.h>
49#include <QtTest/qtesteventloop.h>
50
51QT_BEGIN_NAMESPACE
52
53
54class QVariant;
55
56class QSignalSpy: public QObject, public QList<QList<QVariant> >
57{
58public:
59 explicit QSignalSpy(const QObject *obj, const char *aSignal)
60 : m_waiting(false)
61 {
62 if (!isObjectValid(obj))
63 return;
64
65 if (!aSignal) {
66 qWarning("QSignalSpy: Null signal name is not valid");
67 return;
68 }
69
70 if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
71 qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro");
72 return;
73 }
74
75 const QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
76 const QMetaObject * const mo = obj->metaObject();
77 const int sigIndex = mo->indexOfMethod(ba.constData());
78 if (sigIndex < 0) {
79 qWarning("QSignalSpy: No such signal: '%s'", ba.constData());
80 return;
81 }
82
83 if (!connectToSignal(obj, sigIndex))
84 return;
85
86 sig = ba;
87 initArgs(mo->method(sigIndex), obj);
88 }
89
90#ifdef Q_CLANG_QDOC
91 template <typename PointerToMemberFunction>
92 QSignalSpy(const QObject *object, PointerToMemberFunction signal);
93#else
94 template <typename Func>
95 QSignalSpy(const typename QtPrivate::FunctionPointer<Func>::Object *obj, Func signal0)
96 : m_waiting(false)
97 {
98 if (!isObjectValid(obj))
99 return;
100
101 if (!signal0) {
102 qWarning("QSignalSpy: Null signal name is not valid");
103 return;
104 }
105
106 const QMetaObject * const mo = obj->metaObject();
107 const QMetaMethod signalMetaMethod = QMetaMethod::fromSignal(signal0);
108 const int sigIndex = signalMetaMethod.methodIndex();
109
110 if (!isSignalMetaMethodValid(signalMetaMethod))
111 return;
112
113 if (!connectToSignal(obj, sigIndex))
114 return;
115
116 sig = signalMetaMethod.methodSignature();
117 initArgs(mo->method(sigIndex), obj);
118 }
119#endif // Q_CLANG_QDOC
120
121 QSignalSpy(const QObject *obj, const QMetaMethod &signal)
122 : m_waiting(false)
123 {
124 if (isObjectValid(obj) && isSignalMetaMethodValid(signal) &&
125 connectToSignal(obj, signal.methodIndex())) {
126 sig = signal.methodSignature();
127 initArgs(signal, obj);
128 }
129 }
130
131 inline bool isValid() const { return !sig.isEmpty(); }
132 inline QByteArray signal() const { return sig; }
133
134 bool wait(int timeout = 5000)
135 {
136 Q_ASSERT(!m_waiting);
137 const int origCount = count();
138 m_waiting = true;
139 m_loop.enterLoopMSecs(timeout);
140 m_waiting = false;
141 return count() > origCount;
142 }
143
144 int qt_metacall(QMetaObject::Call call, int methodId, void **a) override
145 {
146 methodId = QObject::qt_metacall(call, methodId, a);
147 if (methodId < 0)
148 return methodId;
149
150 if (call == QMetaObject::InvokeMetaMethod) {
151 if (methodId == 0) {
152 appendArgs(a);
153 }
154 --methodId;
155 }
156 return methodId;
157 }
158
159private:
160 bool connectToSignal(const QObject *sender, int sigIndex)
161 {
162 static const int memberOffset = QObject::staticMetaObject.methodCount();
163 const bool connected = QMetaObject::connect(
164 sender, sigIndex, this, memberOffset, Qt::DirectConnection, nullptr);
165
166 if (!connected)
167 qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
168
169 return connected;
170 }
171
172 static bool isSignalMetaMethodValid(const QMetaMethod &signal)
173 {
174 const bool valid = signal.isValid() && signal.methodType() == QMetaMethod::Signal;
175
176 if (!valid)
177 qWarning("QSignalSpy: Not a valid signal: '%s'", signal.methodSignature().constData());
178
179 return valid;
180 }
181
182 static bool isObjectValid(const QObject *object)
183 {
184 const bool valid = !!object;
185
186 if (!valid)
187 qWarning("QSignalSpy: Cannot spy on a null object");
188
189 return valid;
190 }
191
192 void initArgs(const QMetaMethod &member, const QObject *obj)
193 {
194 args.reserve(member.parameterCount());
195 for (int i = 0; i < member.parameterCount(); ++i) {
196 int tp = member.parameterType(i);
197 if (tp == QMetaType::UnknownType && obj) {
198 void *argv[] = { &tp, &i };
199 QMetaObject::metacall(const_cast<QObject*>(obj),
200 QMetaObject::RegisterMethodArgumentMetaType,
201 member.methodIndex(), argv);
202 if (tp == -1)
203 tp = QMetaType::UnknownType;
204 }
205 if (tp == QMetaType::UnknownType) {
206 qWarning("QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s',"
207 " use qRegisterMetaType to register it.",
208 member.parameterNames().at(i).constData(),
209 member.parameterTypes().at(i).constData(),
210 member.name().constData());
211 }
212 args << tp;
213 }
214 }
215
216 void appendArgs(void **a)
217 {
218 QList<QVariant> list;
219 list.reserve(args.count());
220 for (int i = 0; i < args.count(); ++i) {
221 const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
222 if (type == QMetaType::QVariant)
223 list << *reinterpret_cast<QVariant *>(a[i + 1]);
224 else
225 list << QVariant(type, a[i + 1]);
226 }
227 append(list);
228
229 if (m_waiting)
230 m_loop.exitLoop();
231 }
232
233 // the full, normalized signal name
234 QByteArray sig;
235 // holds the QMetaType types for the argument list of the signal
236 QVector<int> args;
237
238 QTestEventLoop m_loop;
239 bool m_waiting;
240};
241
242QT_END_NAMESPACE
243
244#endif
245

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