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