1 | // Copyright (C) 2016 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 <QtTest/private/qsignaldumper_p.h> |
5 | |
6 | #include <QtCore/qlist.h> |
7 | #include <QtCore/qmetaobject.h> |
8 | #include <QtCore/qmetatype.h> |
9 | #include <QtCore/qobject.h> |
10 | #include <QtCore/qvariant.h> |
11 | |
12 | #include <QtTest/private/qtestlog_p.h> |
13 | |
14 | #include <QtCore/private/qmetaobject_p.h> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | namespace QTest |
19 | { |
20 | |
21 | inline static void qPrintMessage(const QByteArray &ba) |
22 | { |
23 | QTestLog::info(msg: ba.constData(), file: nullptr, line: 0); |
24 | } |
25 | |
26 | Q_GLOBAL_STATIC(QList<QByteArray>, ignoreClasses) |
27 | static int iLevel = 0; |
28 | static int ignoreLevel = 0; |
29 | enum { IndentSpacesCount = 4 }; |
30 | |
31 | static void qSignalDumperCallback(QObject *caller, int signal_index, void **argv) |
32 | { |
33 | Q_ASSERT(caller); |
34 | Q_ASSERT(argv); |
35 | Q_UNUSED(argv); |
36 | const QMetaObject *mo = caller->metaObject(); |
37 | Q_ASSERT(mo); |
38 | QMetaMethod member = QMetaObjectPrivate::signal(m: mo, signal_index); |
39 | Q_ASSERT(member.isValid()); |
40 | |
41 | if (QTest::ignoreClasses() && QTest::ignoreClasses()->contains(t: mo->className())) { |
42 | ++QTest::ignoreLevel; |
43 | return; |
44 | } |
45 | |
46 | QByteArray str; |
47 | str.fill(c: ' ', size: QTest::iLevel++ * QTest::IndentSpacesCount); |
48 | str += "Signal: " ; |
49 | str += mo->className(); |
50 | str += '('; |
51 | |
52 | QString objname = caller->objectName(); |
53 | str += objname.toLocal8Bit(); |
54 | if (!objname.isEmpty()) |
55 | str += ' '; |
56 | str += QByteArray::number(quintptr(caller), base: 16).rightJustified(width: 8, fill: '0'); |
57 | |
58 | str += ") " ; |
59 | str += member.name(); |
60 | str += " (" ; |
61 | |
62 | QList<QByteArray> args = member.parameterTypes(); |
63 | for (int i = 0; i < args.size(); ++i) { |
64 | const QByteArray &arg = args.at(i); |
65 | int typeId = QMetaType::fromName(name: args.at(i).constData()).id(); |
66 | if (arg.endsWith(c: '*') || arg.endsWith(c: '&')) { |
67 | str += '('; |
68 | str += arg; |
69 | str += ')'; |
70 | if (arg.endsWith(c: '&')) |
71 | str += '@'; |
72 | |
73 | quintptr addr = quintptr(*reinterpret_cast<void **>(argv[i + 1])); |
74 | str.append(a: QByteArray::number(addr, base: 16).rightJustified(width: 8, fill: '0')); |
75 | } else if (typeId != QMetaType::UnknownType) { |
76 | Q_ASSERT(typeId != QMetaType::Void); // void parameter => metaobject is corrupt |
77 | str.append(a: arg) |
78 | .append(c: '(') |
79 | .append(a: QVariant(QMetaType(typeId), argv[i + 1]).toString().toLocal8Bit()) |
80 | .append(c: ')'); |
81 | } |
82 | str.append(s: ", " ); |
83 | } |
84 | if (str.endsWith(bv: ", " )) |
85 | str.chop(n: 2); |
86 | str.append(c: ')'); |
87 | qPrintMessage(ba: str); |
88 | } |
89 | |
90 | static void qSignalDumperCallbackSlot(QObject *caller, int method_index, void **argv) |
91 | { |
92 | Q_ASSERT(caller); |
93 | Q_ASSERT(argv); |
94 | Q_UNUSED(argv); |
95 | const QMetaObject *mo = caller->metaObject(); |
96 | Q_ASSERT(mo); |
97 | QMetaMethod member = mo->method(index: method_index); |
98 | if (!member.isValid()) |
99 | return; |
100 | |
101 | if (QTest::ignoreLevel || |
102 | (QTest::ignoreClasses() && QTest::ignoreClasses()->contains(t: mo->className()))) |
103 | return; |
104 | |
105 | QByteArray str; |
106 | str.fill(c: ' ', size: QTest::iLevel * QTest::IndentSpacesCount); |
107 | str += "Slot: " ; |
108 | str += mo->className(); |
109 | str += '('; |
110 | |
111 | QString objname = caller->objectName(); |
112 | str += objname.toLocal8Bit(); |
113 | if (!objname.isEmpty()) |
114 | str += ' '; |
115 | str += QByteArray::number(quintptr(caller), base: 16).rightJustified(width: 8, fill: '0'); |
116 | |
117 | str += ") " ; |
118 | str += member.methodSignature(); |
119 | qPrintMessage(ba: str); |
120 | } |
121 | |
122 | static void qSignalDumperCallbackEndSignal(QObject *caller, int /*signal_index*/) |
123 | { |
124 | Q_ASSERT(caller); Q_ASSERT(caller->metaObject()); |
125 | if (QTest::ignoreClasses() |
126 | && QTest::ignoreClasses()->contains(t: caller->metaObject()->className())) { |
127 | --QTest::ignoreLevel; |
128 | Q_ASSERT(QTest::ignoreLevel >= 0); |
129 | return; |
130 | } |
131 | --QTest::iLevel; |
132 | Q_ASSERT(QTest::iLevel >= 0); |
133 | } |
134 | |
135 | } |
136 | |
137 | void QSignalDumper::setEnabled(bool enabled) |
138 | { |
139 | s_isEnabled = enabled; |
140 | } |
141 | |
142 | void QSignalDumper::startDump() |
143 | { |
144 | if (!s_isEnabled) |
145 | return; |
146 | |
147 | static QSignalSpyCallbackSet set = { .signal_begin_callback: QTest::qSignalDumperCallback, |
148 | .slot_begin_callback: QTest::qSignalDumperCallbackSlot, .signal_end_callback: QTest::qSignalDumperCallbackEndSignal, .slot_end_callback: nullptr }; |
149 | qt_register_signal_spy_callbacks(callback_set: &set); |
150 | } |
151 | |
152 | void QSignalDumper::endDump() |
153 | { |
154 | qt_register_signal_spy_callbacks(callback_set: nullptr); |
155 | } |
156 | |
157 | void QSignalDumper::ignoreClass(const QByteArray &klass) |
158 | { |
159 | if (QTest::ignoreClasses()) |
160 | QTest::ignoreClasses()->append(t: klass); |
161 | } |
162 | |
163 | void QSignalDumper::clearIgnoredClasses() |
164 | { |
165 | if (QTest::ignoreClasses()) |
166 | QTest::ignoreClasses()->clear(); |
167 | } |
168 | |
169 | bool QSignalDumper::s_isEnabled = false; |
170 | |
171 | QT_END_NAMESPACE |
172 | |