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+
296436QTEST_MAIN (tst_executor)
297437#include " tst_executor.moc"
0 commit comments