• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 27473096046

13 Jun 2026 04:56PM UTC coverage: 55.323% (-0.05%) from 55.376%
27473096046

Pull #1520

github

web-flow
Merge 0300946d3 into 20a223634
Pull Request #1520: refactor: dedup executeBlocking + clarify isPathInStore guard (#1510)

15 of 15 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

3731 of 6744 relevant lines covered (55.32%)

36.73 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.58
/src/executor.cpp
1
// SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "executor.h"
4
#include <QCoreApplication>
5
#include <QDir>
6
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
7
#include <QTextCodec>
8
#endif
9
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
10
#include <QStringDecoder>
11
#endif
12
#include <utility>
13

14
#ifdef QT_DEBUG
15
#include "debughelper.h"
16
#endif
17

18
/**
19
 * @brief Executor::Executor executes external applications
20
 * @param parent
21
 */
22
Executor::Executor(QObject *parent) : QObject(parent) {
49✔
23
  connect(&m_process,
49✔
24
          static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
25
              &QProcess::finished),
26
          this,
27
          static_cast<void (Executor::*)(int, QProcess::ExitStatus)>(
49✔
28
              &Executor::finished));
29
  connect(&m_process, &QProcess::started, this, &Executor::starting);
49✔
30
}
49✔
31

32
/**
33
 * @brief Executor::startProcess starts the internal process, handling WSL
34
 * prefixes.
35
 * @param app Executable path (may start with "wsl ").
36
 * @param args Arguments to pass to the executable.
37
 */
38
void Executor::startProcess(const QString &app, const QStringList &args) {
40✔
39
  if (app.startsWith("wsl ")) {
80✔
40
    QStringList wslArgs = args;
41
    QString actualApp = app;
42
    wslArgs.prepend(actualApp.remove(0, 4));
×
43
    m_process.start("wsl", wslArgs);
×
44
  } else {
45
    m_process.start(app, args);
40✔
46
  }
47
}
40✔
48

49
/**
50
 * @brief Executor::startProcessBlocking starts a given process, handling WSL
51
 * prefixes.
52
 * @param internal QProcess reference to start.
53
 * @param app Executable path (may start with "wsl ").
54
 * @param args Arguments to pass to the executable.
55
 */
56
void Executor::startProcessBlocking(QProcess &internal, const QString &app,
51✔
57
                                    const QStringList &args) {
58
  if (app.startsWith("wsl ")) {
102✔
59
    QStringList wslArgs = args;
60
    QString actualApp = app;
61
    wslArgs.prepend(actualApp.remove(0, 4));
×
62
    internal.start("wsl", wslArgs);
×
63
  } else {
64
    internal.start(app, args);
51✔
65
  }
66
}
51✔
67

68
/**
69
 * @brief Executor::executeNext consumes executable tasks from the queue
70
 */
71
void Executor::executeNext() {
79✔
72
  if (!running) {
79✔
73
    if (!m_execQueue.isEmpty()) {
75✔
74
      const execQueueItem &i = m_execQueue.head();
75
      running = true;
40✔
76
      if (!i.workingDir.isEmpty()) {
40✔
77
        m_process.setWorkingDirectory(i.workingDir);
32✔
78
      }
79
      startProcess(i.app, i.args);
40✔
80
      if (!i.input.isEmpty()) {
40✔
81
        if (!m_process.waitForStarted(-1)) {
19✔
82
#ifdef QT_DEBUG
83
          dbg() << "Process failed to start:" << i.id << " " << i.app;
84
#endif
85
          m_process.closeWriteChannel();
×
86
          running = false;
×
87
          m_execQueue.dequeue();
×
88
          executeNext();
×
89
          return;
×
90
        }
91
        QByteArray data = i.input.toUtf8();
19✔
92
        if (m_process.write(data) != data.length()) {
19✔
93
#ifdef QT_DEBUG
94
          dbg() << "Not all data written to process:" << i.id << " " << i.app;
95
#endif
96
        }
97
      }
98
      m_process.closeWriteChannel();
40✔
99
    }
100
  }
101
}
102

103
/**
104
 * @brief Executor::execute execute an app
105
 * @param id
106
 * @param app
107
 * @param args
108
 * @param readStdout
109
 * @param readStderr
110
 */
111
void Executor::execute(int id, const QString &app, const QStringList &args,
8✔
112
                       bool readStdout, bool readStderr) {
113
  execute(id, QString(), app, args, QString(), readStdout, readStderr);
16✔
114
}
8✔
115

116
/**
117
 * @brief Executor::execute executes an app from a workDir
118
 * @param id
119
 * @param workDir
120
 * @param app
121
 * @param args
122
 * @param readStdout
123
 * @param readStderr
124
 */
125
void Executor::execute(int id, const QString &workDir, const QString &app,
1✔
126
                       const QStringList &args, bool readStdout,
127
                       bool readStderr) {
128
  execute(id, workDir, app, args, QString(), readStdout, readStderr);
1✔
129
}
1✔
130

131
/**
132
 * @brief Executor::execute an app, takes input and presents it as stdin
133
 * @param id
134
 * @param app
135
 * @param args
136
 * @param input
137
 * @param readStdout
138
 * @param readStderr
139
 */
140
void Executor::execute(int id, const QString &app, const QStringList &args,
×
141
                       QString input, bool readStdout, bool readStderr) {
142
  execute(id, QString(), app, args, std::move(input), readStdout, readStderr);
×
143
}
×
144

145
/**
146
 * @brief Executor::execute  executes an app from a workDir, takes input and
147
 * presents it as stdin
148
 * @param id
149
 * @param workDir
150
 * @param app
151
 * @param args
152
 * @param input
153
 * @param readStdout
154
 * @param readStderr
155
 */
156
void Executor::execute(int id, const QString &workDir, const QString &app,
40✔
157
                       const QStringList &args, QString input, bool readStdout,
158
                       bool readStderr) {
159
  // Happens a lot if e.g. git binary is not set.
160
  // This will result in bogus "QProcess::FailedToStart" messages,
161
  // also hiding legitimate errors from the gpg commands.
162
  if (app.isEmpty()) {
40✔
163
#ifdef QT_DEBUG
164
    dbg() << "Trying to execute nothing...";
165
#endif
166
    return;
×
167
  }
168
  QString appPath = app;
169
  if (!appPath.startsWith("wsl ")) {
80✔
170
    appPath =
171
        QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(app);
120✔
172
  }
173
  m_execQueue.push_back(
40✔
174
      {id, appPath, args, std::move(input), readStdout, readStderr, workDir});
175
  executeNext();
40✔
176
}
80✔
177

178
/**
179
 * @brief decodes the input into a string assuming UTF-8 encoding.
180
 * If this fails (which is likely if it is not actually UTF-8)
181
 * it will then fall back to Qt's decoding function, which
182
 * will try based on BOM and if that fails fall back to local encoding.
183
 * This should not be needed in Qt6
184
 *
185
 * @param in input data
186
 * @return Input bytes decoded to string
187
 */
188
static auto decodeAssumingUtf8(const QByteArray &in) -> QString {
114✔
189
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
190
  QTextCodec *codec = QTextCodec::codecForName("UTF-8");
191
  QTextCodec::ConverterState state;
192
  QString out = codec->toUnicode(in.constData(), in.size(), &state);
193
  if (state.invalidChars == 0) {
194
    return out;
195
  }
196
  codec = QTextCodec::codecForUtfText(in);
197
  return codec->toUnicode(in);
198
#else
199
  auto converter = QStringDecoder(QStringDecoder::Utf8);
200
  QString out = converter(in);
114✔
201
  if (!converter.hasError()) {
114✔
202
    return out;
203
  }
204
  // Fallback if UTF-8 decoding failed - try system encoding
205
  auto fallback = QStringDecoder(QStringDecoder::System);
206
  return fallback(in);
×
207
#endif
208
}
209

210
/**
211
 * @brief Executor::executeBlocking blocking version of the executor,
212
 * takes input and presents it as stdin
213
 * @param app
214
 * @param args
215
 * @param input
216
 * @param process_out
217
 * @param process_err
218
 * @return
219
 *
220
 * Note: Returning error code instead of throwing to maintain compatibility
221
 * with the existing error handling pattern used throughout QtPass.
222
 */
223
auto Executor::runBlocking(QProcess &process, const QString &app,
51✔
224
                           const QStringList &args, const QString &input,
225
                           QString *process_out, QString *process_err) -> int {
226
  startProcessBlocking(process, app, args);
51✔
227
  if (!process.waitForStarted(-1)) {
51✔
228
#ifdef QT_DEBUG
229
    dbg() << "Process failed to start:" << app;
230
#endif
231
    return -1;
232
  }
233
  if (!input.isEmpty()) {
36✔
234
    QByteArray data = input.toUtf8();
235
    if (process.write(data) != data.length()) {
3✔
236
#ifdef QT_DEBUG
237
      dbg() << "Not all input written:" << app;
238
#endif
239
    }
240
    process.closeWriteChannel();
3✔
241
  }
242
  process.waitForFinished(-1);
36✔
243
  if (process.exitStatus() != QProcess::NormalExit) {
36✔
244
    // Process failed to start or crashed; return -1 to indicate error.
245
    // The calling code checks for non-zero exit codes for error handling.
246
    return -1;
247
  }
248
  if (process_out != nullptr) {
36✔
249
    *process_out = decodeAssumingUtf8(process.readAllStandardOutput());
68✔
250
  }
251
  if (process_err != nullptr) {
36✔
252
    *process_err = decodeAssumingUtf8(process.readAllStandardError());
24✔
253
  }
254
  return process.exitCode();
36✔
255
}
256

257
auto Executor::executeBlocking(const QString &app, const QStringList &args,
36✔
258
                               const QString &input, QString *process_out,
259
                               QString *process_err) -> int {
260
  QProcess internal;
36✔
261
  return runBlocking(internal, app, args, input, process_out, process_err);
72✔
262
}
36✔
263

264
/**
265
 * @brief Executor::executeBlocking blocking version of the executor
266
 * @param app
267
 * @param args
268
 * @param process_out
269
 * @param process_err
270
 * @return
271
 */
272
auto Executor::executeBlocking(const QString &app, const QStringList &args,
21✔
273
                               QString *process_out, QString *process_err)
274
    -> int {
275
  return executeBlocking(app, args, QString(), process_out, process_err);
42✔
276
}
277

278
/**
279
 * @brief Executor::executeBlocking blocking version with custom environment
280
 * @param env Environment variables to set
281
 * @param app Executable path
282
 * @param args Arguments
283
 * @param process_out Standard output
284
 * @param process_err Standard error
285
 * @return Exit code
286
 */
287
auto Executor::executeBlocking(const QStringList &env, const QString &app,
15✔
288
                               const QStringList &args, QString *process_out,
289
                               QString *process_err) -> int {
290
  QProcess process;
15✔
291
  QProcessEnvironment penv;
15✔
292
  for (const QString &var : env) {
1,350✔
293
    qsizetype idx = var.indexOf('=');
1,335✔
294
    if (idx > 0) {
1,335✔
295
      penv.insert(var.left(idx), var.mid(idx + 1));
2,670✔
296
    }
297
  }
298
  process.setProcessEnvironment(penv);
15✔
299
  return runBlocking(process, app, args, QString(), process_out, process_err);
30✔
300
}
15✔
301

302
/**
303
 * @brief Executor::setEnvironment set environment variables
304
 * for executor processes
305
 * @param env
306
 */
307
void Executor::setEnvironment(const QStringList &env) {
36✔
308
  m_process.setEnvironment(env);
36✔
309
}
36✔
310

311
auto Executor::environment() const -> QStringList {
14✔
312
  return m_process.environment();
14✔
313
}
314

315
/**
316
 * @brief Executor::cancelNext  cancels execution of first process in queue
317
 *                              if it's not already running
318
 *
319
 * @return  id of the cancelled process or -1 on error
320
 */
321
auto Executor::cancelNext() -> int {
2✔
322
  if (running || m_execQueue.isEmpty()) {
2✔
323
    return -1; // Return -1 to indicate no process was cancelled
324
               // (queue empty or currently executing).
325
  }
UNCOV
326
  return m_execQueue.dequeue().id;
×
327
}
328

329
/**
330
 * @brief Executor::finished called when an executed process finishes
331
 * @param exitCode
332
 * @param exitStatus
333
 */
334
void Executor::finished(int exitCode, QProcess::ExitStatus exitStatus) {
39✔
335
  execQueueItem i = m_execQueue.dequeue();
336
  running = false;
39✔
337
  if (exitStatus == QProcess::NormalExit) {
39✔
338
    QString output;
38✔
339
    QString err;
38✔
340
    if (i.readStdout) {
38✔
341
      output = decodeAssumingUtf8(m_process.readAllStandardOutput());
72✔
342
    }
343
    if (i.readStderr || exitCode != 0) {
38✔
344
      err = decodeAssumingUtf8(m_process.readAllStandardError());
64✔
345
      if (exitCode != 0) {
346
#ifdef QT_DEBUG
347
        dbg() << exitCode << err;
348
#endif
349
      }
350
    }
351
    emit finished(i.id, exitCode, output, err);
38✔
352
  }
353
  //  else: emit crashed with ID, which may give a chance to recover ?
354
  executeNext();
39✔
355
}
39✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc