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 
51 QT_BEGIN_NAMESPACE
52 
53 
54 class QVariant;
55 
56 class QSignalSpy: public QObject, public QList<QList<QVariant> >
57 {
58 public:
QSignalSpy(const QObject * obj,const char * aSignal)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>
QSignalSpy(const typename QtPrivate::FunctionPointer<Func>::Object * obj,Func signal0)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 
QSignalSpy(const QObject * obj,const QMetaMethod & signal)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 
isValid()131     inline bool isValid() const { return !sig.isEmpty(); }
signal()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 
qt_metacall(QMetaObject::Call call,int methodId,void ** a)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 
159 private:
connectToSignal(const QObject * sender,int sigIndex)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 
isSignalMetaMethodValid(const QMetaMethod & signal)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 
isObjectValid(const QObject * object)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 
initArgs(const QMetaMethod & member,const QObject * obj)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 
appendArgs(void ** a)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 
242 QT_END_NAMESPACE
243 
244 #endif
245