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 <QtTest/qtesteventloop.h>
48#include <QtCore/qvariant.h>
49
50QT_BEGIN_NAMESPACE
51
52
53class QVariant;
54
55class QSignalSpy: public QObject, public QList<QList<QVariant> >
56{
57public:
58 explicit QSignalSpy(const QObject *obj, const char *aSignal)
59 : m_waiting(false)
60 {
61 if (!isObjectValid(obj))
62 return;
63
64 if (!aSignal) {
65 qWarning("QSignalSpy: Null signal name is not valid");
66 return;
67 }
68
69 if (((aSignal[0] - '0') & 0x03) != QSIGNAL_CODE) {
70 qWarning("QSignalSpy: Not a valid signal, use the SIGNAL macro");
71 return;
72 }
73
74 const QByteArray ba = QMetaObject::normalizedSignature(aSignal + 1);
75 const QMetaObject * const mo = obj->metaObject();
76 const int sigIndex = mo->indexOfMethod(ba.constData());
77 if (sigIndex < 0) {
78 qWarning("QSignalSpy: No such signal: '%s'", ba.constData());
79 return;
80 }
81
82 if (!connectToSignal(obj, sigIndex))
83 return;
84
85 sig = ba;
86 initArgs(mo->method(sigIndex), obj);
87 }
88
89#ifdef Q_CLANG_QDOC
90 template <typename PointerToMemberFunction>
91 QSignalSpy(const QObject *object, PointerToMemberFunction signal);
92#else
93 template <typename Func>
94 QSignalSpy(const typename QtPrivate::FunctionPointer<Func>::Object *obj, Func signal0)
95 : m_waiting(false)
96 {
97 if (!isObjectValid(obj))
98 return;
99
100 if (!signal0) {
101 qWarning("QSignalSpy: Null signal name is not valid");
102 return;
103 }
104
105 const QMetaObject * const mo = obj->metaObject();
106 const QMetaMethod signalMetaMethod = QMetaMethod::fromSignal(signal0);
107 const int sigIndex = signalMetaMethod.methodIndex();
108
109 if (!isSignalMetaMethodValid(signalMetaMethod))
110 return;
111
112 if (!connectToSignal(obj, sigIndex))
113 return;
114
115 sig = signalMetaMethod.methodSignature();
116 initArgs(mo->method(sigIndex), obj);
117 }
118#endif // Q_CLANG_QDOC
119
120 QSignalSpy(const QObject *obj, const QMetaMethod &signal)
121 : m_waiting(false)
122 {
123 if (isObjectValid(obj) && isSignalMetaMethodValid(signal) &&
124 connectToSignal(obj, signal.methodIndex())) {
125 sig = signal.methodSignature();
126 initArgs(signal, obj);
127 }
128 }
129
130 inline bool isValid() const { return !sig.isEmpty(); }
131 inline QByteArray signal() const { return sig; }
132
133 bool wait(int timeout = 5000)
134 {
135 Q_ASSERT(!m_waiting);
136 const int origCount = count();
137 m_waiting = true;
138 m_loop.enterLoopMSecs(timeout);
139 m_waiting = false;
140 return count() > origCount;
141 }
142
143 int qt_metacall(QMetaObject::Call call, int methodId, void **a) override
144 {
145 methodId = QObject::qt_metacall(call, methodId, a);
146 if (methodId < 0)
147 return methodId;
148
149 if (call == QMetaObject::InvokeMetaMethod) {
150 if (methodId == 0) {
151 appendArgs(a);
152 }
153 --methodId;
154 }
155 return methodId;
156 }
157
158private:
159 bool connectToSignal(const QObject *sender, int sigIndex)
160 {
161 static const int memberOffset = QObject::staticMetaObject.methodCount();
162 const bool connected = QMetaObject::connect(
163 sender, sigIndex, this, memberOffset, Qt::DirectConnection, nullptr);
164
165 if (!connected)
166 qWarning("QSignalSpy: QMetaObject::connect returned false. Unable to connect.");
167
168 return connected;
169 }
170
171 static bool isSignalMetaMethodValid(const QMetaMethod &signal)
172 {
173 const bool valid = signal.isValid() && signal.methodType() == QMetaMethod::Signal;
174
175 if (!valid)
176 qWarning("QSignalSpy: Not a valid signal: '%s'", signal.methodSignature().constData());
177
178 return valid;
179 }
180
181 static bool isObjectValid(const QObject *object)
182 {
183 const bool valid = !!object;
184
185 if (!valid)
186 qWarning("QSignalSpy: Cannot spy on a null object");
187
188 return valid;
189 }
190
191 void initArgs(const QMetaMethod &member, const QObject *obj)
192 {
193 args.reserve(member.parameterCount());
194 for (int i = 0; i < member.parameterCount(); ++i) {
195 int tp = member.parameterType(i);
196 if (tp == QMetaType::UnknownType && obj) {
197 void *argv[] = { &tp, &i };
198 QMetaObject::metacall(const_cast<QObject*>(obj),
199 QMetaObject::RegisterMethodArgumentMetaType,
200 member.methodIndex(), argv);
201 if (tp == -1)
202 tp = QMetaType::UnknownType;
203 }
204 if (tp == QMetaType::UnknownType) {
205 qWarning("QSignalSpy: Unable to handle parameter '%s' of type '%s' of method '%s',"
206 " use qRegisterMetaType to register it.",
207 member.parameterNames().at(i).constData(),
208 member.parameterTypes().at(i).constData(),
209 member.name().constData());
210 }
211 args << tp;
212 }
213 }
214
215 void appendArgs(void **a)
216 {
217 QList<QVariant> list;
218 list.reserve(args.count());
219 for (int i = 0; i < args.count(); ++i) {
220 const QMetaType::Type type = static_cast<QMetaType::Type>(args.at(i));
221 if (type == QMetaType::QVariant)
222 list << *reinterpret_cast<QVariant *>(a[i + 1]);
223 else
224 list << QVariant(QMetaType(type), a[i + 1]);
225 }
226 append(list);
227
228 if (m_waiting)
229 m_loop.exitLoop();
230 }
231
232 // the full, normalized signal name
233 QByteArray sig;
234 // holds the QMetaType types for the argument list of the signal
235 QList<int> args;
236
237 QTestEventLoop m_loop;
238 bool m_waiting;
239};
240
241QT_END_NAMESPACE
242
243#endif
244