Skip to content

Commit 29cf0d5

Browse files
nogeenharrieclaude
andauthored
test(executor): add async execute() and environment API tests (#1497)
Adds 10 new tests covering the async execute() path, setEnvironment(), environment(), and cancelNext() — areas previously untested. Async tests use QStandardPaths::findExecutable("sh") for the binary path because Executor::execute() resolves names relative to the application directory rather than PATH. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b2df519 commit 29cf0d5

1 file changed

Lines changed: 140 additions & 0 deletions

File tree

tests/auto/executor/tst_executor.cpp

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
// SPDX-FileCopyrightText: 2026 Anne Jan Brouwer
22
// SPDX-License-Identifier: GPL-3.0-or-later
3+
#include <QStandardPaths>
34
#include <QtTest>
45

56
#include "../../../src/executor.h"
@@ -24,6 +25,13 @@ private Q_SLOTS:
2425
void executeBlockingWithEnvSetsVariable();
2526
void executeBlockingTwoArgOverload();
2627
void executeBlockingConstQStringRef();
28+
void executeAsyncFinishedSignal();
29+
void executeAsyncCapturesStdout();
30+
void executeAsyncNonZeroExitCode();
31+
void executeAsyncStartingSignal();
32+
void executeAsyncMultipleSequential();
33+
void executeAsyncWithWorkDir();
34+
void cancelNextWhileRunningReturnsMinusOne();
2735
#endif
2836
void executeBlockingNotFound();
2937
void executeBlockingGpgVersion();
@@ -32,6 +40,9 @@ private Q_SLOTS:
3240
void executeBlockingGpgKillAgent();
3341
void resolveGpgconfCommand();
3442
void executeBlockingWithEnvNotFound();
43+
void environmentDefaultsEmpty();
44+
void setAndGetEnvironment();
45+
void cancelNextEmptyReturnsMinusOne();
3546
};
3647

3748
#ifndef Q_OS_WIN
@@ -293,5 +304,134 @@ void tst_executor::resolveGpgconfCommand() {
293304
}
294305
}
295306

307+
void tst_executor::environmentDefaultsEmpty() {
308+
Executor exec;
309+
QVERIFY2(exec.environment().isEmpty(),
310+
"default executor environment must be empty");
311+
}
312+
313+
void tst_executor::setAndGetEnvironment() {
314+
Executor exec;
315+
QStringList env = {"QTPASS_FOO=bar", "QTPASS_BAZ=qux"};
316+
exec.setEnvironment(env);
317+
QStringList expected = env;
318+
expected.sort();
319+
QStringList actual = exec.environment();
320+
actual.sort();
321+
QCOMPARE(actual, expected);
322+
}
323+
324+
void tst_executor::cancelNextEmptyReturnsMinusOne() {
325+
Executor exec;
326+
QCOMPARE(exec.cancelNext(), -1);
327+
}
328+
329+
#ifndef Q_OS_WIN
330+
void tst_executor::executeAsyncFinishedSignal() {
331+
const QString sh = QStandardPaths::findExecutable("sh");
332+
if (sh.isEmpty())
333+
QSKIP("sh not found in PATH");
334+
Executor exec;
335+
QSignalSpy spy(&exec, qOverload<int, int, const QString &, const QString &>(
336+
&Executor::finished));
337+
QVERIFY2(spy.isValid(),
338+
"spy must connect to Executor::finished(int,int,...) signal");
339+
exec.execute(42, sh, {"-c", "echo async-hello"}, true, false);
340+
QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 5000);
341+
const QList<QVariant> args = spy.first();
342+
QCOMPARE(args.at(0).toInt(), 42);
343+
QCOMPARE(args.at(1).toInt(), 0);
344+
QVERIFY2(args.at(2).toString().contains("async-hello"),
345+
"stdout must contain 'async-hello'");
346+
}
347+
348+
void tst_executor::executeAsyncCapturesStdout() {
349+
const QString sh = QStandardPaths::findExecutable("sh");
350+
if (sh.isEmpty())
351+
QSKIP("sh not found in PATH");
352+
Executor exec;
353+
QSignalSpy spy(&exec, qOverload<int, int, const QString &, const QString &>(
354+
&Executor::finished));
355+
QVERIFY2(spy.isValid(), "spy must connect to Executor::finished signal");
356+
exec.execute(1, sh, {"-c", "echo captured-output"}, true, false);
357+
QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 5000);
358+
const QString output = spy.first().at(2).toString();
359+
QVERIFY2(output.contains("captured-output"),
360+
"stdout must contain 'captured-output'");
361+
}
362+
363+
void tst_executor::executeAsyncNonZeroExitCode() {
364+
const QString sh = QStandardPaths::findExecutable("sh");
365+
if (sh.isEmpty())
366+
QSKIP("sh not found in PATH");
367+
Executor exec;
368+
QSignalSpy spy(&exec, qOverload<int, int, const QString &, const QString &>(
369+
&Executor::finished));
370+
QVERIFY2(spy.isValid(), "spy must connect to Executor::finished signal");
371+
exec.execute(7, sh, {"-c", "exit 1"}, false, false);
372+
QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 5000);
373+
const int exitCode = spy.first().at(1).toInt();
374+
QVERIFY2(exitCode != 0, "sh -c 'exit 1' must exit with non-zero code");
375+
}
376+
377+
void tst_executor::executeAsyncStartingSignal() {
378+
const QString sh = QStandardPaths::findExecutable("sh");
379+
if (sh.isEmpty())
380+
QSKIP("sh not found in PATH");
381+
Executor exec;
382+
QSignalSpy startSpy(&exec, &Executor::starting);
383+
QSignalSpy doneSpy(&exec,
384+
qOverload<int, int, const QString &, const QString &>(
385+
&Executor::finished));
386+
QVERIFY2(doneSpy.isValid(),
387+
"doneSpy must connect to Executor::finished signal");
388+
exec.execute(3, sh, {"-c", "echo starting-test"}, false, false);
389+
QTRY_COMPARE_WITH_TIMEOUT(doneSpy.count(), 1, 5000);
390+
QVERIFY2(startSpy.count() >= 1, "starting signal must have been emitted");
391+
}
392+
393+
void tst_executor::executeAsyncMultipleSequential() {
394+
const QString sh = QStandardPaths::findExecutable("sh");
395+
if (sh.isEmpty())
396+
QSKIP("sh not found in PATH");
397+
Executor exec;
398+
QSignalSpy spy(&exec, qOverload<int, int, const QString &, const QString &>(
399+
&Executor::finished));
400+
QVERIFY2(spy.isValid(), "spy must connect to Executor::finished signal");
401+
exec.execute(10, sh, {"-c", "echo first"}, true, false);
402+
exec.execute(11, sh, {"-c", "echo second"}, true, false);
403+
QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 2, 5000);
404+
QCOMPARE(spy.at(0).at(0).toInt(), 10);
405+
QCOMPARE(spy.at(1).at(0).toInt(), 11);
406+
}
407+
408+
void tst_executor::executeAsyncWithWorkDir() {
409+
const QString sh = QStandardPaths::findExecutable("sh");
410+
if (sh.isEmpty())
411+
QSKIP("sh not found in PATH");
412+
QTemporaryDir tmp;
413+
QVERIFY2(tmp.isValid(), "temp dir must be valid");
414+
Executor exec;
415+
QSignalSpy spy(&exec, qOverload<int, int, const QString &, const QString &>(
416+
&Executor::finished));
417+
QVERIFY2(spy.isValid(), "spy must connect to Executor::finished signal");
418+
exec.execute(5, tmp.path(), sh, {"-c", "pwd"}, true, false);
419+
QTRY_COMPARE_WITH_TIMEOUT(spy.count(), 1, 5000);
420+
const QString output = spy.first().at(2).toString().trimmed();
421+
QVERIFY2(QDir::cleanPath(output) == QDir::cleanPath(tmp.path()),
422+
"working directory must match the tmp path");
423+
}
424+
425+
void tst_executor::cancelNextWhileRunningReturnsMinusOne() {
426+
const QString sh = QStandardPaths::findExecutable("sh");
427+
if (sh.isEmpty())
428+
QSKIP("sh not found in PATH");
429+
Executor exec;
430+
exec.execute(1, sh, {"-c", "sleep 2"}, false, false);
431+
exec.execute(2, sh, {"-c", "echo queued"}, false, false);
432+
QCOMPARE(exec.cancelNext(), -1);
433+
}
434+
#endif
435+
296436
QTEST_MAIN(tst_executor)
297437
#include "tst_executor.moc"

0 commit comments

Comments
 (0)