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 test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include "debugutil_p.h" |
30 | #include "qqmldebugprocess_p.h" |
31 | |
32 | #include <private/qqmldebugconnection_p.h> |
33 | |
34 | #include <QtCore/qeventloop.h> |
35 | #include <QtCore/qtimer.h> |
36 | |
37 | bool QQmlDebugTest::waitForSignal(QObject *receiver, const char *member, int timeout) { |
38 | QEventLoop loop; |
39 | QTimer timer; |
40 | timer.setSingleShot(true); |
41 | QObject::connect(sender: &timer, SIGNAL(timeout()), receiver: &loop, SLOT(quit())); |
42 | QObject::connect(sender: receiver, signal: member, receiver: &loop, SLOT(quit())); |
43 | timer.start(msec: timeout); |
44 | loop.exec(); |
45 | if (!timer.isActive()) |
46 | qWarning(msg: "waitForSignal %s timed out after %d ms" , member, timeout); |
47 | return timer.isActive(); |
48 | } |
49 | |
50 | QList<QQmlDebugClient *> QQmlDebugTest::createOtherClients(QQmlDebugConnection *connection) |
51 | { |
52 | QList<QQmlDebugClient *> ret; |
53 | |
54 | static const auto debuggerServices |
55 | = QStringList({"V8Debugger" , "QmlDebugger" , "DebugMessages" }); |
56 | static const auto inspectorServices |
57 | = QStringList({"QmlInspector" }); |
58 | static const auto profilerServices |
59 | = QStringList({"CanvasFrameRate" , "EngineControl" , "DebugMessages" }); |
60 | |
61 | for (const QString &service : debuggerServices) { |
62 | if (!connection->client(name: service)) |
63 | ret << new QQmlDebugClient(service, connection); |
64 | } |
65 | for (const QString &service : inspectorServices) { |
66 | if (!connection->client(name: service)) |
67 | ret << new QQmlDebugClient(service, connection); |
68 | } |
69 | for (const QString &service : profilerServices) { |
70 | if (!connection->client(name: service)) |
71 | ret << new QQmlDebugClient(service, connection); |
72 | } |
73 | return ret; |
74 | } |
75 | |
76 | QString QQmlDebugTest::clientStateString(const QQmlDebugClient *client) |
77 | { |
78 | if (!client) |
79 | return QLatin1String("null" ); |
80 | |
81 | switch (client->state()) { |
82 | case QQmlDebugClient::NotConnected: return QLatin1String("Not connected" ); |
83 | case QQmlDebugClient::Unavailable: return QLatin1String("Unavailable" ); |
84 | case QQmlDebugClient::Enabled: return QLatin1String("Enabled" ); |
85 | default: return QLatin1String("Invalid" ); |
86 | } |
87 | |
88 | } |
89 | |
90 | QString QQmlDebugTest::connectionStateString(const QQmlDebugConnection *connection) |
91 | { |
92 | if (!connection) |
93 | return QLatin1String("null" ); |
94 | |
95 | return connection->isConnected() ? QLatin1String("connected" ) : QLatin1String("not connected" ); |
96 | } |
97 | |
98 | QQmlDebugTestClient::QQmlDebugTestClient(const QString &s, QQmlDebugConnection *c) |
99 | : QQmlDebugClient(s, c) |
100 | { |
101 | connect(sender: this, signal: &QQmlDebugClient::stateChanged, context: this, slot: [this](QQmlDebugClient::State newState) { |
102 | QCOMPARE(newState, state()); |
103 | }); |
104 | } |
105 | |
106 | QByteArray QQmlDebugTestClient::waitForResponse() |
107 | { |
108 | lastMsg.clear(); |
109 | QQmlDebugTest::waitForSignal(receiver: this, SIGNAL(serverMessage(QByteArray))); |
110 | if (lastMsg.isEmpty()) { |
111 | qWarning() << "no response from server!" ; |
112 | return QByteArray(); |
113 | } |
114 | return lastMsg; |
115 | } |
116 | |
117 | void QQmlDebugTestClient::messageReceived(const QByteArray &ba) |
118 | { |
119 | lastMsg = ba; |
120 | emit serverMessage(ba); |
121 | } |
122 | |
123 | QQmlDebugTest::ConnectResult QQmlDebugTest::connectTo( |
124 | const QString &executable, const QString &services, const QString &, |
125 | bool block) |
126 | { |
127 | QStringList arguments; |
128 | arguments << QString::fromLatin1(str: "-qmljsdebugger=port:13773,13783%3%4" ) |
129 | .arg(a: block ? QStringLiteral(",block" ) : QString()) |
130 | .arg(a: services.isEmpty() ? services : (QStringLiteral(",services:" ) + services)) |
131 | << extraArgs; |
132 | |
133 | m_process = createProcess(executable); |
134 | if (!m_process) |
135 | return ProcessFailed; |
136 | |
137 | m_process->start(arguments: QStringList() << arguments); |
138 | if (!m_process->waitForSessionStart()) |
139 | return SessionFailed; |
140 | |
141 | m_connection = createConnection(); |
142 | if (!m_connection) |
143 | return ConnectionFailed; |
144 | |
145 | m_clients = createClients(); |
146 | if (m_clients.contains(t: nullptr)) |
147 | return ClientsFailed; |
148 | |
149 | ClientStateHandler stateHandler(m_clients, createOtherClients(connection: m_connection), services.isEmpty() |
150 | ? QQmlDebugClient::Enabled : QQmlDebugClient::Unavailable); |
151 | |
152 | |
153 | const int port = m_process->debugPort(); |
154 | m_connection->connectToHost(hostName: QLatin1String("127.0.0.1" ), port); |
155 | |
156 | QEventLoop loop; |
157 | QTimer timer; |
158 | QObject::connect(sender: &stateHandler, signal: &ClientStateHandler::allOk, receiver: &loop, slot: &QEventLoop::quit); |
159 | QObject::connect(sender: m_connection, signal: &QQmlDebugConnection::disconnected, receiver: &loop, slot: &QEventLoop::quit); |
160 | QObject::connect(sender: &timer, signal: &QTimer::timeout, receiver: &loop, slot: &QEventLoop::quit); |
161 | |
162 | timer.start(msec: 5000); |
163 | loop.exec(); |
164 | |
165 | if (!stateHandler.allEnabled()) |
166 | return EnableFailed; |
167 | |
168 | if (!stateHandler.othersAsExpected()) |
169 | return RestrictFailed; |
170 | |
171 | return ConnectSuccess; |
172 | } |
173 | |
174 | QList<QQmlDebugClient *> QQmlDebugTest::createClients() |
175 | { |
176 | return QList<QQmlDebugClient *>(); |
177 | } |
178 | |
179 | QQmlDebugProcess *QQmlDebugTest::createProcess(const QString &executable) |
180 | { |
181 | return new QQmlDebugProcess(executable, this); |
182 | } |
183 | |
184 | QQmlDebugConnection *QQmlDebugTest::createConnection() |
185 | { |
186 | return new QQmlDebugConnection(this); |
187 | } |
188 | |
189 | void QQmlDebugTest::cleanup() |
190 | { |
191 | if (QTest::currentTestFailed()) { |
192 | const QString null = QStringLiteral("null" ); |
193 | |
194 | qDebug() << "Process State:" << (m_process ? m_process->stateString() : null); |
195 | qDebug() << "Application Output:" << (m_process ? m_process->output() : null); |
196 | qDebug() << "Connection State:" << QQmlDebugTest::connectionStateString(connection: m_connection); |
197 | for (QQmlDebugClient *client : m_clients) { |
198 | if (client) |
199 | qDebug() << client->name() << "State:" << QQmlDebugTest::clientStateString(client); |
200 | else |
201 | qDebug() << "Failed Client:" << null; |
202 | } |
203 | } |
204 | |
205 | qDeleteAll(c: m_clients); |
206 | m_clients.clear(); |
207 | |
208 | delete m_connection; |
209 | m_connection = nullptr; |
210 | |
211 | if (m_process) { |
212 | m_process->stop(); |
213 | delete m_process; |
214 | m_process = nullptr; |
215 | } |
216 | } |
217 | |
218 | ClientStateHandler::ClientStateHandler(const QList<QQmlDebugClient *> &clients, |
219 | const QList<QQmlDebugClient *> &others, |
220 | QQmlDebugClient::State expectedOthers) : |
221 | m_clients(clients), m_others(others), m_expectedOthers(expectedOthers) |
222 | { |
223 | for (QQmlDebugClient *client : m_clients) { |
224 | QObject::connect(sender: client, signal: &QQmlDebugClient::stateChanged, |
225 | receiver: this, slot: &ClientStateHandler::checkStates); |
226 | } |
227 | for (QQmlDebugClient *client : m_others) { |
228 | QObject::connect(sender: client, signal: &QQmlDebugClient::stateChanged, |
229 | receiver: this, slot: &ClientStateHandler::checkStates); |
230 | } |
231 | } |
232 | |
233 | ClientStateHandler::~ClientStateHandler() |
234 | { |
235 | qDeleteAll(c: m_others); |
236 | } |
237 | |
238 | void ClientStateHandler::checkStates() |
239 | { |
240 | for (QQmlDebugClient *client : m_clients) { |
241 | if (client->state() != QQmlDebugClient::Enabled) |
242 | return; |
243 | } |
244 | |
245 | m_allEnabled = true; |
246 | |
247 | for (QQmlDebugClient *other : m_others) { |
248 | if (other->state() != m_expectedOthers) |
249 | return; |
250 | } |
251 | |
252 | m_othersAsExpected = true; |
253 | emit allOk(); |
254 | } |
255 | |
256 | QString debugJsServerPath(const QString &selfPath) |
257 | { |
258 | static const char *debugserver = "qqmldebugjsserver" ; |
259 | QString appPath = QCoreApplication::applicationDirPath(); |
260 | const int position = appPath.lastIndexOf(s: selfPath); |
261 | return (position == -1 ? appPath : appPath.replace(i: position, len: selfPath.length(), after: debugserver)) |
262 | + "/" + debugserver; |
263 | } |
264 | |