1/****************************************************************************
2**
3** Copyright (C) 2017 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 "qqmldebugprocess_p.h"
30
31#include <QtCore/qdebug.h>
32#include <QtCore/qfileinfo.h>
33#include <QtCore/qdir.h>
34
35QQmlDebugProcess::QQmlDebugProcess(const QString &executable, QObject *parent)
36 : QObject(parent)
37 , m_executable(executable)
38 , m_state(SessionUnknown)
39 , m_port(0)
40 , m_maximumBindErrors(0)
41 , m_receivedBindErrors(0)
42{
43 m_process.setProcessChannelMode(QProcess::MergedChannels);
44 m_timer.setInterval(15000);
45 connect(sender: &m_process, signal: &QProcess::readyReadStandardOutput,
46 receiver: this, slot: &QQmlDebugProcess::processAppOutput);
47 connect(sender: &m_process, signal: &QProcess::errorOccurred,
48 receiver: this, slot: &QQmlDebugProcess::processError);
49 connect(sender: &m_process, signal: QOverload<int, QProcess::ExitStatus>::of(ptr: &QProcess::finished),
50 context: this, slot: [this]() {
51 m_timer.stop();
52 m_eventLoop.quit();
53 emit finished();
54 });
55 connect(sender: &m_timer, signal: &QTimer::timeout,
56 receiver: this, slot: &QQmlDebugProcess::timeout);
57}
58
59QQmlDebugProcess::~QQmlDebugProcess()
60{
61 stop();
62}
63
64QString QQmlDebugProcess::stateString() const
65{
66 QString stateStr;
67 switch (m_process.state()) {
68 case QProcess::NotRunning: {
69 stateStr = "not running";
70 if (m_process.exitStatus() == QProcess::CrashExit)
71 stateStr += " (crashed!)";
72 else
73 stateStr += ", return value " + QString::number(m_process.exitCode());
74 break;
75 }
76 case QProcess::Starting:
77 stateStr = "starting";
78 break;
79 case QProcess::Running:
80 stateStr = "running";
81 break;
82 }
83 return stateStr;
84}
85
86void QQmlDebugProcess::start(const QStringList &arguments)
87{
88#ifdef Q_OS_MAC
89 // make sure m_executable points to the actual binary even if it's inside an app bundle
90 QFileInfo binFile(m_executable);
91 if (!binFile.isExecutable()) {
92 QDir bundleDir(m_executable + ".app");
93 if (bundleDir.exists()) {
94 m_executable = bundleDir.absoluteFilePath("Contents/MacOS/" + binFile.baseName());
95 //qDebug() << Q_FUNC_INFO << "found bundled binary" << m_executable;
96 }
97 }
98#endif
99 m_mutex.lock();
100 m_port = 0;
101 m_process.setEnvironment(QProcess::systemEnvironment() + m_environment);
102 m_process.start(program: m_executable, arguments);
103 if (!m_process.waitForStarted()) {
104 qWarning() << "QML Debug Client: Could not launch app " << m_executable
105 << ": " << m_process.errorString();
106 m_eventLoop.quit();
107 }
108 m_mutex.unlock();
109}
110
111void QQmlDebugProcess::stop()
112{
113 if (m_process.state() != QProcess::NotRunning) {
114 disconnect(sender: &m_process, signal: &QProcess::errorOccurred, receiver: this, slot: &QQmlDebugProcess::processError);
115 m_process.kill();
116 m_process.waitForFinished(msecs: 5000);
117 }
118}
119
120void QQmlDebugProcess::setMaximumBindErrors(int ignore)
121{
122 m_maximumBindErrors = ignore;
123}
124
125void QQmlDebugProcess::timeout()
126{
127 qWarning() << "Timeout while waiting for QML debugging messages "
128 "in application output. Process is in state" << m_process.state()
129 << ", Output:" << m_output << ".";
130}
131
132bool QQmlDebugProcess::waitForSessionStart()
133{
134 if (m_process.state() != QProcess::Running) {
135 qWarning() << "Could not start up " << m_executable;
136 return false;
137 } else if (m_state == SessionStarted) {
138 return true;
139 } else if (m_state == SessionFailed) {
140 return false;
141 }
142
143 m_timer.start();
144 m_eventLoop.exec();
145
146 return m_state == SessionStarted;
147}
148
149int QQmlDebugProcess::debugPort() const
150{
151 return m_port;
152}
153
154bool QQmlDebugProcess::waitForFinished()
155{
156 return m_process.waitForFinished();
157}
158
159QProcess::ProcessState QQmlDebugProcess::state() const
160{
161 return m_process.state();
162}
163
164QProcess::ExitStatus QQmlDebugProcess::exitStatus() const
165{
166 return m_process.exitStatus();
167}
168
169void QQmlDebugProcess::addEnvironment(const QString &environment)
170{
171 m_environment.append(t: environment);
172}
173
174QString QQmlDebugProcess::output() const
175{
176 return m_output;
177}
178
179void QQmlDebugProcess::processAppOutput()
180{
181 m_mutex.lock();
182
183 bool outputFromAppItself = false;
184
185 QString newOutput = m_process.readAll();
186 m_output.append(s: newOutput);
187 m_outputBuffer.append(s: newOutput);
188
189 while (true) {
190 const int nlIndex = m_outputBuffer.indexOf(c: QLatin1Char('\n'));
191 if (nlIndex < 0) // no further complete lines
192 break;
193 const QString line = m_outputBuffer.left(n: nlIndex);
194 m_outputBuffer = m_outputBuffer.right(n: m_outputBuffer.size() - nlIndex - 1);
195
196 if (line.contains(s: "QML Debugger:")) {
197 const QRegExp portRx("Waiting for connection on port (\\d+)");
198 if (portRx.indexIn(str: line) != -1) {
199 m_port = portRx.cap(nth: 1).toInt();
200 m_timer.stop();
201 m_state = SessionStarted;
202 m_eventLoop.quit();
203 continue;
204 }
205 if (line.contains(s: "Unable to listen")) {
206 if (++m_receivedBindErrors >= m_maximumBindErrors) {
207 if (m_maximumBindErrors == 0)
208 qWarning() << "App was unable to bind to port!";
209 m_timer.stop();
210 m_state = SessionFailed;
211 m_eventLoop.quit();
212 }
213 continue;
214 }
215 }
216
217 // set to true if there is output not coming from the debugger or we don't understand it
218 outputFromAppItself = true;
219 }
220 m_mutex.unlock();
221
222 if (outputFromAppItself)
223 emit readyReadStandardOutput();
224}
225
226void QQmlDebugProcess::processError(QProcess::ProcessError error)
227{
228 qDebug() << "An error occurred while waiting for debug process to become available:";
229 switch (error) {
230 case QProcess::FailedToStart:
231 qDebug() << "Process failed to start.";
232 break;
233 case QProcess::Crashed:
234 qDebug() << "Process crashed.";
235 break;
236 case QProcess::Timedout:
237 qDebug() << "Process timed out.";
238 break;
239 case QProcess::WriteError:
240 qDebug() << "Error while writing to process.";
241 break;
242 case QProcess::ReadError:
243 qDebug() << "Error while reading from process.";
244 break;
245 case QProcess::UnknownError:
246 qDebug() << "Unknown process error.";
247 break;
248 }
249}
250

source code of qtdeclarative/tests/auto/qml/debugger/shared/qqmldebugprocess.cpp