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
16QT_BEGIN_NAMESPACE
17
18namespace QTest
19{
20
21inline static void qPrintMessage(const QByteArray &ba)
22{
23 QTestLog::info(msg: ba.constData(), file: nullptr, line: 0);
24}
25
26Q_GLOBAL_STATIC(QList<QByteArray>, ignoreClasses)
27static int iLevel = 0;
28static int ignoreLevel = 0;
29enum { IndentSpacesCount = 4 };
30
31static 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
90static 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
122static 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
137void QSignalDumper::setEnabled(bool enabled)
138{
139 s_isEnabled = enabled;
140}
141
142void 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
152void QSignalDumper::endDump()
153{
154 qt_register_signal_spy_callbacks(callback_set: nullptr);
155}
156
157void QSignalDumper::ignoreClass(const QByteArray &klass)
158{
159 if (QTest::ignoreClasses())
160 QTest::ignoreClasses()->append(t: klass);
161}
162
163void QSignalDumper::clearIgnoredClasses()
164{
165 if (QTest::ignoreClasses())
166 QTest::ignoreClasses()->clear();
167}
168
169bool QSignalDumper::s_isEnabled = false;
170
171QT_END_NAMESPACE
172

source code of qtbase/src/testlib/qsignaldumper.cpp