18#include <QProcessEnvironment>
19#include <QRegularExpression>
20#include <QtConcurrentRun>
23#include <QDBusInterface>
44using namespace std::chrono_literals;
50 constexpr auto TimerTolerance = 0.05;
52 constexpr auto MinElapsed (std::chrono::milliseconds expected)
54 return expected.count () * (1 - TimerTolerance);
58 void CoroTaskTest::testReturn ()
60 auto task = [] () -> Task<int> {
co_return 42; } ();
62 QCOMPARE (result, 42);
65 void CoroTaskTest::testMoveOnlyReturn ()
67 auto direct = [] () -> Task<std::unique_ptr<int>>
69 co_return std::make_unique<int> (42);
72 QVERIFY (directResult);
73 QCOMPARE (*directResult, 42);
75 auto chained = [] () -> Task<std::unique_ptr<int>>
77 auto inner = [] () -> Task<std::unique_ptr<int>>
80 co_return std::make_unique<int> (7);
82 auto value =
co_await inner;
84 co_return std::move (value);
87 QVERIFY (chainedResult);
88 QCOMPARE (*chainedResult, 42);
91 void CoroTaskTest::testWait ()
96 auto task = [] () -> Task<int>
104 QCOMPARE (result, 42);
105 QCOMPARE_GT (timer.elapsed (), 50);
108 void CoroTaskTest::testTaskDestr ()
110 bool continued =
false;
112 [] (
auto& continued) -> Task<void>
118 QTRY_VERIFY_WITH_TIMEOUT (continued, 20);
124 class MockReply :
public QNetworkReply
128 using QNetworkReply::QNetworkReply;
130 using QNetworkReply::setAttribute;
131 using QNetworkReply::setError;
132 using QNetworkReply::setFinished;
133 using QNetworkReply::setHeader;
134 using QNetworkReply::setOperation;
135 using QNetworkReply::setRawHeader;
136 using QNetworkReply::setRequest;
137 using QNetworkReply::setUrl;
139 void SetData (
const QByteArray& data)
141 Buffer_.setData (data);
142 Buffer_.open (QIODevice::ReadOnly);
143 open (QIODevice::ReadOnly);
146 qint64 readData (
char *data, qint64 maxSize)
override
148 return Buffer_.read (data, maxSize);
151 void abort ()
override
156 class MockNAM :
public QNetworkAccessManager
158 QPointer<MockReply> Reply_;
160 explicit MockNAM (MockReply *reply)
165 MockReply* GetReply ()
170 QNetworkReply* createRequest (Operation op,
const QNetworkRequest& req, QIODevice*)
override
172 Reply_->setUrl (req.url ());
173 Reply_->setOperation (op);
174 Reply_->setRequest (req);
179 auto MkSuccessfulReply (
const QByteArray& data)
181 auto reply =
new MockReply;
182 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 200);
183 reply->SetData (data);
189 auto reply =
new MockReply;
190 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 404);
191 reply->setError (QNetworkReply::NetworkError::ContentAccessDenied,
"well, 404!"_qs);
195 void TestGoodReply (
auto finishMarker)
197 const QByteArray data {
"this is some test data" };
198 MockNAM nam { MkSuccessfulReply (data) };
199 finishMarker (*nam.GetReply ());
201 auto task = [&nam] () -> Task<QByteArray>
203 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
204 co_return reply.GetReplyData ();
208 QCOMPARE (result, data);
211 void TestBadReply (
auto finishMarker)
213 MockNAM nam { MkErrorReply () };
214 finishMarker (*nam.GetReply ());
216 auto task = [&nam] () -> Task<QByteArray>
218 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
219 co_return reply.GetReplyData ();
225 void ImmediateFinishMarker (MockReply& reply)
227 reply.setFinished (
true);
230 void DelayedFinishMarker (MockReply& reply)
232 QTimer::singleShot (10ms,
235 reply.setFinished (
true);
236 emit reply.finished ();
241 void CoroTaskTest::testNetworkReplyGoodNoWait ()
243 TestGoodReply (&ImmediateFinishMarker);
246 void CoroTaskTest::testNetworkReplyGoodWait ()
248 TestGoodReply (&DelayedFinishMarker);
251 void CoroTaskTest::testNetworkReplyBadNoWait ()
253 TestBadReply (&ImmediateFinishMarker);
256 void CoroTaskTest::testNetworkReplyBadWait ()
258 TestBadReply (&DelayedFinishMarker);
261 void CoroTaskTest::testFutureAwaiter ()
263 auto delayed = [] () -> Task<int>
265 co_return co_await QtConcurrent::run ([]
273 auto immediate = [] () -> Task<int>
275 co_return co_await QtConcurrent::run ([] {
return 42; });
279 auto ready = [] () -> Task<int>
281 co_return co_await MakeReadyFuture (42);
286 void CoroTaskTest::testWaitMany ()
288 constexpr auto max = 100;
289 auto mkTask = [] (
int index) -> Task<int>
291 co_await Precisely { std::chrono::milliseconds { max - index } };
297 QVector<Task<int>> tasks;
298 QVector<int> expected;
299 for (
int i = 0; i < max; ++i)
304 const auto creationElapsed = timer.elapsed ();
308 const auto executionElapsed = timer.elapsed ();
310 QCOMPARE (result, expected);
311 QCOMPARE_LT (creationElapsed, 1);
313 QCOMPARE_GE (executionElapsed, MinElapsed (max * 1ms));
314 const auto linearizedExecTime = max * (max + 1) / 2;
315 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
318 void CoroTaskTest::testWaitManyTuple ()
320 auto mkTask = [] (
int delay) -> Task<int>
322 co_await Precisely { std::chrono::milliseconds { delay } };
329 const auto executionElapsed = timer.elapsed ();
331 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
333 QCOMPARE_GE (executionElapsed, 10);
334 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
337 void CoroTaskTest::testWaitManyInvoking ()
339 constexpr auto max = 100;
340 auto mkTask = [] (
int index) -> Task<int>
342 co_await Precisely { std::chrono::milliseconds { max - index } };
349 QVector<int> expected;
350 for (
int i = 0; i < max; ++i)
355 const auto creationElapsed = timer.elapsed ();
359 const auto executionElapsed = timer.elapsed ();
361 QCOMPARE (result, expected);
362 QCOMPARE_LT (creationElapsed, 1);
364 QCOMPARE_GE (executionElapsed, MinElapsed (max * 1ms));
365 const auto linearizedExecTime = max * (max + 1) / 2;
366 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
369 void CoroTaskTest::testNCopies ()
371 constexpr auto count = 10;
372 static constexpr auto unit = 10ms;
374 auto mkTask = [] (
int index) -> Task<int>
376 co_await Precisely { unit * (count - index) };
383 auto task =
NCopies (count, [&] {
return mkTask (created++); });
384 const auto creationElapsed = timer.elapsed ();
386 static_assert (std::is_same_v<
decltype (task), Task<QVector<int>>>);
387 QCOMPARE (created, count);
388 QCOMPARE_LT (creationElapsed, unit.count ());
392 const auto executionElapsed = timer.elapsed ();
394 QVector<int> expected;
395 for (
int i = 0; i < count; ++i)
397 QCOMPARE (result, expected);
399 QCOMPARE_GE (executionElapsed, MinElapsed (count * unit));
400 const auto linearizedExecTime = unit.count () * count * (count + 1) / 2;
401 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
404 void CoroTaskTest::testNCopiesVoid ()
406 constexpr auto count = 5;
407 auto mkTask = [] (
int& finished) -> Task<void>
414 auto task =
NCopies (count, [&] {
return mkTask (finished); });
415 static_assert (std::is_same_v<
decltype (task), Task<void>>);
416 QCOMPARE (finished, 0);
419 QCOMPARE (finished, count);
423 "extensions did not survive the delegation");
426 void CoroTaskTest::testNCopiesZero ()
429 auto mkTask = [] (
int& created) -> Task<int>
436 QVERIFY (result.isEmpty ());
437 QCOMPARE (created, 0);
442 Task<int> SlowSuccess (std::chrono::milliseconds delay,
bool& done)
449 Task<int> SlowFailure (std::chrono::milliseconds delay, std::string what)
452 throw std::runtime_error { what };
455 QString ThrownMessage (
auto&& f)
461 catch (
const std::exception& e)
463 return QString::fromUtf8 (e.what ());
469 void CoroTaskTest::testWaitManyJoinsAfterFailure ()
471 constexpr auto slowDelay = 60ms;
472 bool slowDone =
false;
475 const auto message = ThrownMessage ([&] {
GetTaskResult (
InParallel ({ SlowFailure (10ms,
"early"), SlowSuccess (slowDelay, slowDone) })); });
476 const auto elapsed = timer.elapsed ();
478 QCOMPARE (message,
"early"_qs);
480 QCOMPARE_GE (elapsed, MinElapsed (slowDelay));
483 void CoroTaskTest::testWaitManyVoidJoinsAfterFailure ()
485 auto slowSuccess = [] (
bool& done) -> Task<void>
490 auto earlyFailure = [] () -> Task<void>
493 throw std::runtime_error {
"early" };
496 bool slowDone =
false;
497 const auto message = ThrownMessage ([&] {
GetTaskResult (
InParallel ({ earlyFailure (), slowSuccess (slowDone) })); });
498 QCOMPARE (message,
"early"_qs);
502 void CoroTaskTest::testWaitManyFirstFailureInOrderWins ()
504 QTest::ignoreMessage (QtWarningMsg,
"dropping a subsequent exception: first in time");
506 const auto message = ThrownMessage ([] {
GetTaskResult (
InParallel ({ SlowFailure (40ms,
"first in order"), SlowFailure (10ms,
"first in time") })); });
507 QCOMPARE (message,
"first in order"_qs);
510 void CoroTaskTest::testWaitManyInvokingFactoryThrows ()
512 constexpr auto delay = 40ms;
513 QVector<bool> done (5,
false);
515 auto mkTask = [&] (
int index)
518 throw std::runtime_error {
"factory" };
520 return SlowSuccess (delay, done [index]);
526 const auto elapsed = timer.elapsed ();
528 QCOMPARE (message,
"factory"_qs);
529 QCOMPARE (created, 2);
530 QCOMPARE (done, (QVector {
true,
true,
false,
false,
false }));
531 QCOMPARE_GE (elapsed, MinElapsed (delay));
534 void CoroTaskTest::testNCopiesFactoryThrows ()
538 auto taskFactory = [&]
541 throw std::runtime_error {
"factory" };
542 return SlowSuccess (30ms, done);
546 QCOMPARE (message,
"factory"_qs);
551 void CoroTaskTest::testWaitManyContextDeathSilenced ()
553 QTest::failOnWarning (QRegularExpression {
"dropping a subsequent"_qs });
555 auto context = std::make_unique<CoroContext> ();
558 co_await AddContext { *context };
561 auto task =
InParallel ({ mkTask (context.get ()), mkTask (context.get ()), mkTask (context.get ()) });
562 QTimer::singleShot (10ms, [context = std::move (context)] ()
mutable { context.reset (); });
566 QVERIFY_THROWS_EXCEPTION (ContextDeadException,
GetTaskResult (task));
567 QCOMPARE_LT (timer.elapsed (), 250);
570 void CoroTaskTest::testSharedTaskManyAwaiters ()
578 auto mkAwaiter = [&shared] (
int offset) -> Task<int>
580 co_return co_await shared + offset;
584 QCOMPARE (result, (std::tuple { 42, 43 }));
587 void CoroTaskTest::testSharedTaskAwaiterRemovedOnOuterDestruction ()
589 constexpr auto destructionDelay = 10ms;
596 auto survivingAwaiter = [&shared] () -> Task<int>
598 co_return co_await shared;
601 auto context = std::make_unique<CoroContext> ();
602 auto cancelledAwaiter = [&shared] (CoroContext *context) ->
ContextTask<int>
604 co_await AddContext { *context };
605 co_return co_await shared;
607 QTimer::singleShot (destructionDelay, [context = std::move (context)] ()
mutable { context.reset (); });
613 void CoroTaskTest::testSharedTaskExceptionManyAwaiters ()
615 struct TestSharedTaskException : std::exception {};
620 throw TestSharedTaskException {};
623 auto mkAwaiter = [&shared] () -> Task<bool>
630 catch (
const TestSharedTaskException&)
637 QCOMPARE (result, (std::tuple {
true,
true }));
640 void CoroTaskTest::testSharedTaskLastCopyDestroyedByAwaiter ()
648 auto destructiveAwaiter = [] (std::optional<SharedTask<int>> *shared) -> Task<int>
650 auto result =
co_await **shared;
655 auto normalAwaiter = [] (std::optional<SharedTask<int>> *shared) -> Task<int>
657 co_return co_await **shared;
661 QCOMPARE (result, (std::tuple { 42, 42 }));
664 void CoroTaskTest::testSharedTaskNonTriviallyMovableReturn ()
667 const auto payload =
"0123456789abcdef"_qs.repeated (16);
675 auto mkAwaiter = [&shared] () -> Task<QString>
677 co_return co_await shared;
681 QCOMPARE (result, (std::tuple { payload, payload }));
684 void CoroTaskTest::testSharedContextTaskManyAwaitersContextAlive ()
686 auto context = std::make_unique<CoroContext> ();
689 co_await AddContext { *ctx };
694 auto mkAwaiter = [&shared] (
int offset) -> Task<int>
696 co_return co_await shared + offset;
700 QCOMPARE (result, (std::tuple { 42, 43 }));
703 void CoroTaskTest::testSharedContextTaskContextDeadFansOutToAll ()
705 auto context = std::make_unique<CoroContext> ();
708 co_await AddContext { *ctx };
713 auto mkAwaiter = [&shared] () -> Task<bool>
720 catch (
const ContextDeadException&)
726 QTimer::singleShot (10ms, [context = std::move (context)] ()
mutable { context.reset (); });
729 QCOMPARE (result, (std::tuple {
true,
true }));
732 void CoroTaskTest::testSharedContextTaskBodyExceptionFansOut ()
734 struct TestSharedContextException : std::exception {};
736 auto context = std::make_unique<CoroContext> ();
739 co_await AddContext { *ctx };
741 throw TestSharedContextException {};
744 auto mkAwaiter = [&shared] () -> Task<bool>
751 catch (
const TestSharedContextException&)
758 QCOMPARE (result, (std::tuple {
true,
true }));
761 void CoroTaskTest::testSharedContextTaskContextDeadDoesntWaitLong ()
763 auto context = std::make_unique<CoroContext> ();
766 co_await AddContext { *ctx };
770 auto awaiter = [&shared] () -> Task<void>
775 QTimer::singleShot (10ms, [context = std::move (context)] ()
mutable { context.reset (); });
780 QCOMPARE_LT (timer.elapsed (), 255);
783 void CoroTaskTest::testSharedContextTaskMixedAwaitersOwnContextDies ()
785 auto sharedCtx = std::make_unique<CoroContext> ();
786 auto aliveCtx = std::make_unique<CoroContext> ();
787 auto dyingCtx = std::make_unique<CoroContext> ();
791 co_await AddContext { *ctx };
798 co_await AddContext { *ctx };
799 co_return co_await shared;
804 co_await AddContext { *ctx };
805 co_return co_await shared;
808 QTimer::singleShot (10ms, [dyingCtx = std::move (dyingCtx)] ()
mutable { dyingCtx.reset (); });
814 void CoroTaskTest::testEither ()
816 using Result_t = Either<QString, bool>;
818 auto immediatelyFailing = [] () -> Task<Result_t>
820 const auto theInt =
co_await Either<QString, int> {
"meh" };
821 co_return theInt > 420;
823 QCOMPARE (
GetTaskResult (immediatelyFailing), Result_t { Left {
"meh" } });
825 auto earlyFailing = [] () -> Task<Result_t>
827 const auto theInt =
co_await Either<QString, int> {
"meh" };
829 co_return theInt > 420;
831 QCOMPARE (
GetTaskResult (earlyFailing), Result_t { Left {
"meh" } });
833 auto successful = [] () -> Task<Result_t>
835 const auto theInt =
co_await Either<QString, int> { 42 };
837 co_return theInt > 420;
842 void CoroTaskTest::testEitherIgnoreLeft ()
844 auto immediatelyFailing = [] () -> Task<Either<QString, int>>
846 const auto theInt =
co_await WithHandler (Either<QString, int> {
"meh" }, IgnoreLeft {});
849 QCOMPARE (
GetTaskResult (immediatelyFailing), (Either<QString, int> { 0 }));
852 void CoroTaskTest::testThrottleSameCoro ()
855 constexpr auto count = 10;
859 auto task = [] (
auto& t) -> Task<int>
862 for (
int i = 0; i < count; ++i)
870 const auto time = timer.elapsed ();
872 QCOMPARE (result, count * (count - 1) / 2);
873 QCOMPARE_GE (time, count * t.GetInterval ().count ());
876 void CoroTaskTest::testThrottleSameCoroSlow ()
879 constexpr auto count = 10;
880 constexpr static auto intraDelay = 9ms;
884 auto task = [] (
auto& t) -> Task<void>
886 for (
int i = 0; i < count; ++i)
894 const auto time = timer.elapsed ();
896 const auto expectedMinTime = count * t.GetInterval ().count ();
897 QCOMPARE_GE (time, expectedMinTime);
899 const auto delaysTime = (count - 1) * intraDelay.count ();
900 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
903 void CoroTaskTest::testThrottleSameCoroVerySlow ()
906 constexpr auto count = 10;
907 constexpr static auto intraDelay = 20ms;
911 auto task = [] (
auto& t) -> Task<void>
913 for (
int i = 0; i < count; ++i)
921 const auto time = timer.elapsed ();
923 const auto expectedMinTime = (count - 1) * intraDelay.count ();
924 QCOMPARE_GE (time, expectedMinTime);
926 const auto throttlesTime = count * t.GetInterval ().count ();
927 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
930 void CoroTaskTest::testThrottleManyCoros ()
932 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
933 constexpr auto count = 10;
937 auto mkTask = [] (
auto& t) -> Task<void>
939 for (
int i = 0; i < count; ++i)
942 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
943 for (
auto& task : tasks)
945 const auto time = timer.elapsed ();
947 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
954 void CoroTaskTest::testContextDestrBeforeFinish ()
956 auto context = std::make_unique<CoroContext> ();
961 co_return context->GetName ();
968 void CoroTaskTest::testContextDestrAfterFinish ()
970 const auto ctxName =
"some name"_qs;
971 auto context = std::make_unique<CoroContext> (ctxName);
974 co_await AddContext { *context };
976 co_return context->GetName ();
982 void CoroTaskTest::testContextDestrAwaitedSubtask ()
984 auto context = std::make_unique<CoroContext> ();
987 co_await AddContext { *context };
995 co_await nestedSubtask;
996 co_return context->GetName ();
1000 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
1005 template<
typename... Ts>
1006 auto WithContext (
auto&& taskGen, Ts&&... taskArgs)
1008 auto context = std::make_unique<CoroContext> ();
1009 auto task = taskGen (&*context, std::forward<Ts> (taskArgs)...);
1010 QTimer::singleShot (
ShortDelay, [context = std::move (context)] ()
mutable { context.reset (); });
1014 void WithDestroyTimer (
auto task)
1016 QElapsedTimer timer;
1018 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
1023 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
1027 co_await AddContext { *context };
1032 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
1034 const QByteArray data {
"this is some test data" };
1035 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
1039 if (
const auto reply = nam->GetReply ())
1041 reply->setFinished (
true);
1042 emit reply->finished ();
1046 WithDestroyTimer (WithContext ([] (CoroContext *context, QNetworkAccessManager *nam) ->
ContextTask<QByteArray>
1048 co_await AddContext { *context };
1049 const auto reply =
co_await *nam->get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
1050 co_return reply.GetReplyData ();
1054 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
1056 WithDestroyTimer (WithContext ([] (CoroContext *context) ->
ContextTask<>
1058 co_await AddContext { *context };
1060 const auto process =
new QProcess {};
1061 const auto delay = std::chrono::duration_cast<std::chrono::milliseconds> (
LongDelay);
1062 process->start (
"sleep", { QString::number (delay.count () / 1000.0) });
1064 &QProcess::finished,
1066 &QObject::deleteLater);
1072 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
1074 WithDestroyTimer (WithContext ([] (CoroContext *context) ->
ContextTask<>
1076 co_await AddContext { *context };
1077 co_await QtConcurrent::run ([] { QThread::sleep (
LongDelay); });
1081 void CoroTaskTest::testContextDestrDoesntWaitPlainTask ()
1086 co_await AddContext { *context };
1087 co_await [] () -> Task<void> {
co_await LongDelay; } ();
1096 void CoroTaskTest::testContextDestrParentOutlivesChild ()
1098 QList<Event> destrLog;
1099 auto task = WithContext ([] (CoroContext *context, QList<Event>& log) ->
ContextTask<void>
1101 co_await AddContext { *context };
1106 co_await AddContext { *context };
1110 WithDestroyTimer (task);
1115 void CoroTaskTest::testContextDestrDoesntResumeFinishedSibling ()
1118 auto task = WithContext ([] (CoroContext *context, QList<Event>& log) ->
ContextTask<void>
1120 co_await AddContext { *context };
1123 auto child = [] (CoroContext *context, QList<Event>& log, std::chrono::milliseconds delay,
Event name) ->
ContextTask<void>
1125 const auto cleanup =
MakeScopeGuard ([&log, name] { log << name; });
1126 co_await AddContext { *context };
1131 WithDestroyTimer (task);
1133 std::ranges::sort (log);
1139 struct TouchRecordingAwaiter
1143 bool await_ready () noexcept
1149 void await_suspend (std::coroutine_handle<>)
noexcept {}
1150 void await_resume () noexcept {}
1154 void CoroTaskTest::testContextDestrSwallowedDoesntTouchNextAwaitable ()
1156 bool touched =
false;
1157 auto task = WithContext ([] (CoroContext *context,
bool& touched) ->
ContextTask<void>
1159 co_await AddContext { *context };
1164 catch (
const std::exception&)
1167 co_await TouchRecordingAwaiter { touched };
1169 WithDestroyTimer (task);
1175 using Context_ptr = std::unique_ptr<CoroContext>;
1196 Task<void> PlainMiddle (Context_ptr& context)
1198 co_await DestroyingChild (context);
1201 auto ParallelChildren (Context_ptr& context)
1203 return InParallel ({ DestroyingChild (context), SleepingChild (context) });
1209 co_await mkChild (context);
1213 void RunCoro (
auto mkTask)
1215 auto context = std::make_unique<CoroContext> (
"doomed"_qs);
1219 void RunUnderParent (
auto mkChild)
1221 RunCoro ([&] (Context_ptr& context) {
return Parent (context, mkChild); });
1224 constexpr auto DeathChildEnvVar =
"LC_COROTASKTEST_DEATH_CHILD";
1228 void ExpectFatal (
const QString& message,
auto scenario)
1230 if (qEnvironmentVariableIsSet (DeathChildEnvVar))
1233 QFAIL (
"the scenario was expected to abort");
1236 auto testName = QString::fromLatin1 (QTest::currentTestFunction ());
1237 if (
const auto tag = QTest::currentDataTag ())
1238 testName +=
':' + QString::fromLatin1 (tag);
1240 auto env = QProcessEnvironment::systemEnvironment ();
1241 env.insert (DeathChildEnvVar,
"1"_qs);
1244 child.setProcessEnvironment (env);
1245 child.setProcessChannelMode (QProcess::MergedChannels);
1246 child.start (QCoreApplication::applicationFilePath (), {
"-nocrashhandler"_qs, testName });
1247 QVERIFY (child.waitForFinished ());
1249 const auto output = QString::fromUtf8 (child.readAll ());
1250 QVERIFY2 (child.exitStatus () == QProcess::CrashExit, qPrintable (output));
1251 QVERIFY2 (output.contains (message), qPrintable (output));
1255 void CoroTaskTest::testContextDestrFromRunningCoroAborts_data ()
1257 QTest::addColumn<Scenario> (
"scenario");
1259 QTest::newRow (
"self") <<
Scenario { [] { RunCoro (&DestroyingChild); } };
1260 QTest::newRow (
"child") <<
Scenario { [] { RunUnderParent (&DestroyingChild); } };
1261 QTest::newRow (
"child via plain task") <<
Scenario { [] { RunUnderParent (&PlainMiddle); } };
1262 QTest::newRow (
"child before first await") <<
Scenario { [] { RunUnderParent (&ImmediateDestroyingChild); } };
1263 QTest::newRow (
"child in InParallel") <<
Scenario { [] { RunUnderParent (&ParallelChildren); } };
1266 void CoroTaskTest::testContextDestrFromRunningCoroAborts ()
1269 ExpectFatal (
"destroying the context \"doomed\" while its child coro is running"_qs, scenario);
1272 void CoroTaskTest::testContextOfIsPerObject ()
1280 void CoroTaskTest::testContextOfDiesWithObject ()
1282 auto object = std::make_unique<QObject> ();
1288 QTimer::singleShot (
ShortDelay, [
object = std::move (
object)] ()
mutable {
object.reset (); });
1289 WithDestroyTimer (task);
1292 void CoroTaskTest::testContextOfRejectsForeignThread ()
1295 bool thrown =
false;
1303 catch (
const std::runtime_error&)
1317 process.start (program, args);
1318 co_return co_await process;
1322 void CoroTaskTest::testProcessOutcomeExited ()
1324 const auto outcome =
GetTaskResult (RunProcess (
"sh"_qs, {
"-c"_qs,
"exit 3"_qs }));
1325 QVERIFY (std::holds_alternative<ProcessExited> (outcome));
1326 QCOMPARE (std::get<ProcessExited> (outcome).Code_, 3);
1329 void CoroTaskTest::testProcessOutcomeFailedToStart ()
1331 const auto outcome =
GetTaskResult (RunProcess (
"lc-definitely-not-an-existing-binary"_qs, {}));
1332 QVERIFY (std::holds_alternative<ProcessFailedToStart> (outcome));
1333 QVERIFY (!std::get<ProcessFailedToStart> (outcome).Error_.isEmpty ());
1337 void CoroTaskTest::testProcessOutcomeCrashed ()
1339 const auto outcome =
GetTaskResult (RunProcess (
"sh"_qs, {
"-c"_qs,
"kill -SEGV $$"_qs }));
1340 QVERIFY (std::holds_alternative<ProcessCrashed> (outcome));
1341 QCOMPARE (std::get<ProcessCrashed> (outcome).Code_, SIGSEGV);
1346 void CoroTaskTest::testDBus ()
1348 const auto& bus = QDBusConnection::systemBus ();
1349 if (!bus.isConnected ())
1350 QSKIP (
"D-Bus system bus is not available");
1352 auto task = [] () -> Task<Either<QDBusError::ErrorType, bool>>
1354 const auto& bus = QDBusConnection::systemBus ();
1355 QDBusInterface dbusIface {
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus", bus };
1356 const auto result =
co_await Typed<bool> (dbusIface.asyncCall (
"NameHasOwner",
"org.freedesktop.DBus"_qs));
1357 co_return result.MapLeft (&QDBusError::type);
1364 void CoroTaskTest::cleanupTestCase ()
1367 QTimer::singleShot (
LongDelay * 2, [&done] { done =
true; });
static CoroContext & Of(QObject &obj)
Returns the context tied to the lifetime of obj.
std::function< void()> Scenario
Task< R, ContextExtension > ContextTask
detail::DBusAwaiter< Rets... > Typed(const QDBusPendingCall &asyncCall)
Task< T, SharedTaskExtension > SharedTask
Task< T, SharedTaskExtension, ContextExtension > SharedContextTask
detail::ScopeGuard< F > MakeScopeGuard(const F &f)
Returns an object performing passed function on scope exit.
auto NCopies(qsizetype count, auto taskFactory)
Starts count tasks via taskFactory and awaits them all.
detail::EitherAwaiter< L, R, F > WithHandler(const Either< L, R > &either, F &&errorHandler)
constexpr auto ShortDelay
WithPrecision< Qt::PreciseTimer > Precisely
T GetTaskResult(Task< T, Extensions... > task)
constexpr auto DelayThreshold
auto InParallel(QVector< Task< T, Exts... > > tasks)
Awaits all the tasks, collecting their results in order.
Q_DECLARE_METATYPE(QVariantList *)