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

IJHack / QtPass / 24083498890

07 Apr 2026 01:19PM UTC coverage: 20.115%. First build
24083498890

Pull #895

github

web-flow
Merge ab373250b into e7f408717
Pull Request #895: fix: kill stale GPG agents before key generation

22 of 45 new or added lines in 3 files covered. (48.89%)

1052 of 5230 relevant lines covered (20.11%)

7.76 hits per line

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

28.83
/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), running(false) {
11✔
23
  connect(&m_process,
11✔
24
          static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(
25
              &QProcess::finished),
26
          this,
27
          static_cast<void (Executor::*)(int, QProcess::ExitStatus)>(
11✔
28
              &Executor::finished));
29
  connect(&m_process, &QProcess::started, this, &Executor::starting);
11✔
30
}
11✔
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) {
×
39
  if (app.startsWith("wsl ")) {
×
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);
×
46
  }
47
}
×
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,
14✔
57
                                    const QStringList &args) {
58
  if (app.startsWith("wsl ")) {
28✔
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);
14✔
65
  }
66
}
14✔
67

68
/**
69
 * @brief Executor::executeNext consumes executable tasks from the queue
70
 */
71
void Executor::executeNext() {
×
72
  if (!running) {
×
73
    if (!m_execQueue.isEmpty()) {
×
74
      const execQueueItem &i = m_execQueue.head();
75
      running = true;
×
76
      if (!i.workingDir.isEmpty()) {
×
77
        m_process.setWorkingDirectory(i.workingDir);
×
78
      }
79
      startProcess(i.app, i.args);
×
80
      if (!i.input.isEmpty()) {
×
81
        if (!m_process.waitForStarted(-1)) {
×
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();
×
92
        if (m_process.write(data) != data.length()) {
×
93
#ifdef QT_DEBUG
94
          dbg() << "Not all data written to process:" << i.id << " " << i.app;
95
#endif
96
        }
97
      }
98
      m_process.closeWriteChannel();
×
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,
×
112
                       bool readStdout, bool readStderr) {
113
  execute(id, QString(), app, args, QString(), readStdout, readStderr);
×
114
}
×
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,
×
126
                       const QStringList &args, bool readStdout,
127
                       bool readStderr) {
128
  execute(id, workDir, app, args, QString(), readStdout, readStderr);
×
129
}
×
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,
×
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()) {
×
163
#ifdef QT_DEBUG
164
    dbg() << "Trying to execute nothing...";
165
#endif
166
    return;
×
167
  }
168
  QString appPath = app;
169
  if (!appPath.startsWith("wsl ")) {
×
170
    appPath =
171
        QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(app);
×
172
  }
173
  m_execQueue.push_back(
×
174
      {id, appPath, args, std::move(input), readStdout, readStderr, workDir});
175
  executeNext();
×
176
}
×
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 {
20✔
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);
20✔
201
  if (!converter.hasError()) {
20✔
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::executeBlocking(QString app, const QStringList &args,
14✔
224
                               const QString &input, QString *process_out,
225
                               QString *process_err) -> int {
226
  QProcess internal;
14✔
227
  startProcessBlocking(internal, app, args);
14✔
228
  if (!internal.waitForStarted(-1)) {
14✔
229
#ifdef QT_DEBUG
230
    dbg() << "Process failed to start:" << app;
231
#endif
232
    return -1;
233
  }
234
  if (!input.isEmpty()) {
10✔
235
    QByteArray data = input.toUtf8();
236
    if (internal.write(data) != data.length()) {
1✔
237
#ifdef QT_DEBUG
238
      dbg() << "Not all input written:" << app;
239
#endif
240
    }
241
    internal.closeWriteChannel();
1✔
242
  }
243
  internal.waitForFinished(-1);
10✔
244
  if (internal.exitStatus() == QProcess::NormalExit) {
10✔
245
    QString pout = decodeAssumingUtf8(internal.readAllStandardOutput());
10✔
246
    QString perr = decodeAssumingUtf8(internal.readAllStandardError());
10✔
247
    if (process_out != nullptr) {
10✔
248
      *process_out = pout;
10✔
249
    }
250
    if (process_err != nullptr) {
10✔
251
      *process_err = perr;
3✔
252
    }
253
    return internal.exitCode();
10✔
254
  }
255
  // Process failed to start or crashed; return -1 to indicate error.
256
  // The calling code checks for non-zero exit codes for error handling.
257
  return -1;
258
}
14✔
259

260
/**
261
 * @brief Executor::executeBlocking blocking version of the executor
262
 * @param app
263
 * @param args
264
 * @param process_out
265
 * @param process_err
266
 * @return
267
 */
268
auto Executor::executeBlocking(QString app, const QStringList &args,
3✔
269
                               QString *process_out, QString *process_err)
270
    -> int {
271
  return executeBlocking(std::move(app), args, QString(), process_out,
6✔
272
                         process_err);
3✔
273
}
274

275
/**
276
 * @brief Executor::executeBlocking blocking version with custom environment
277
 * @param env Environment variables to set
278
 * @param app Executable path
279
 * @param args Arguments
280
 * @param process_out Standard output
281
 * @param process_err Standard error
282
 * @return Exit code
283
 */
NEW
284
auto Executor::executeBlocking(const QStringList &env, QString app,
×
285
                               const QStringList &args, QString *process_out,
286
                               QString *process_err) -> int {
NEW
287
  QProcess process;
×
NEW
288
  QProcessEnvironment penv;
×
NEW
289
  for (const QString &var : env) {
×
NEW
290
    int idx = var.indexOf('=');
×
NEW
291
    if (idx > 0) {
×
NEW
292
      penv.insert(var.left(idx), var.mid(idx + 1));
×
293
    }
294
  }
NEW
295
  process.setProcessEnvironment(penv);
×
NEW
296
  startProcessBlocking(process, app, args);
×
NEW
297
  if (!process.waitForStarted(-1)) {
×
298
#ifdef QT_DEBUG
299
    dbg() << "Process failed to start:" << app;
300
#endif
301
    return -1;
302
  }
NEW
303
  process.waitForFinished(-1);
×
NEW
304
  if (process.exitStatus() != QProcess::NormalExit) {
×
305
    return -1;
306
  }
NEW
307
  if (process_out) {
×
NEW
308
    *process_out = decodeAssumingUtf8(process.readAllStandardOutput());
×
309
  }
NEW
310
  if (process_err) {
×
NEW
311
    *process_err = decodeAssumingUtf8(process.readAllStandardError());
×
312
  }
NEW
313
  return process.exitCode();
×
NEW
314
}
×
315

316
/**
317
 * @brief Executor::setEnvironment set environment variables
318
 * for executor processes
319
 * @param env
320
 */
321
void Executor::setEnvironment(const QStringList &env) {
×
322
  m_process.setEnvironment(env);
×
323
}
×
324

325
/**
326
 * @brief Executor::cancelNext  cancels execution of first process in queue
327
 *                              if it's not already running
328
 *
329
 * @return  id of the cancelled process or -1 on error
330
 */
331
auto Executor::cancelNext() -> int {
×
332
  if (running || m_execQueue.isEmpty()) {
×
333
    return -1; // Return -1 to indicate no process was cancelled
334
               // (queue empty or currently executing).
335
  }
336
  return m_execQueue.dequeue().id;
×
337
}
338

339
/**
340
 * @brief Executor::finished called when an executed process finishes
341
 * @param exitCode
342
 * @param exitStatus
343
 */
344
void Executor::finished(int exitCode, QProcess::ExitStatus exitStatus) {
×
345
  execQueueItem i = m_execQueue.dequeue();
346
  running = false;
×
347
  if (exitStatus == QProcess::NormalExit) {
×
348
    QString output;
×
349
    QString err;
×
350
    if (i.readStdout) {
×
351
      output = decodeAssumingUtf8(m_process.readAllStandardOutput());
×
352
    }
353
    if (i.readStderr || exitCode != 0) {
×
354
      err = decodeAssumingUtf8(m_process.readAllStandardError());
×
355
      if (exitCode != 0) {
356
#ifdef QT_DEBUG
357
        dbg() << exitCode << err;
358
#endif
359
      }
360
    }
361
    emit finished(i.id, exitCode, output, err);
×
362
  }
363
  //  else: emit crashed with ID, which may give a chance to recover ?
364
  executeNext();
×
365
}
×
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