18#include <QNetworkReply>
21#include <QProcessEnvironment>
22#include <QRegularExpression>
24#include <QtConcurrentRun>
26#include <QDBusInterface>
47using namespace std::chrono_literals;
53 constexpr auto TimerTolerance = 0.05;
55 constexpr auto MinElapsed (std::chrono::milliseconds expected)
57 return expected.count () * (1 - TimerTolerance);
61 void CoroTaskTest::testReturn ()
63 auto task = [] () -> Task<int> {
co_return 42; } ();
65 QCOMPARE (result, 42);
68 void CoroTaskTest::testMoveOnlyReturn ()
70 auto direct = [] () -> Task<std::unique_ptr<int>>
72 co_return std::make_unique<int> (42);
75 QVERIFY (directResult);
76 QCOMPARE (*directResult, 42);
78 auto chained = [] () -> Task<std::unique_ptr<int>>
80 auto inner = [] () -> Task<std::unique_ptr<int>>
83 co_return std::make_unique<int> (7);
85 auto value =
co_await inner;
87 co_return std::move (value);
90 QVERIFY (chainedResult);
91 QCOMPARE (*chainedResult, 42);
94 void CoroTaskTest::testWait ()
99 auto task = [] () -> Task<int>
107 QCOMPARE (result, 42);
108 QCOMPARE_GT (timer.elapsed (), 50);
111 void CoroTaskTest::testTaskDestr ()
113 bool continued =
false;
115 [] (
auto& continued) -> Task<void>
121 QTRY_VERIFY_WITH_TIMEOUT (continued, 20);
127 class MockReply :
public QNetworkReply
131 using QNetworkReply::QNetworkReply;
133 using QNetworkReply::setAttribute;
134 using QNetworkReply::setError;
135 using QNetworkReply::setFinished;
136 using QNetworkReply::setHeader;
137 using QNetworkReply::setOperation;
138 using QNetworkReply::setRawHeader;
139 using QNetworkReply::setRequest;
140 using QNetworkReply::setUrl;
142 void SetData (
const QByteArray& data)
144 Buffer_.setData (data);
145 Buffer_.open (QIODevice::ReadOnly);
146 open (QIODevice::ReadOnly);
149 qint64 readData (
char *data, qint64 maxSize)
override
151 return Buffer_.read (data, maxSize);
154 void abort ()
override
159 class MockNAM :
public QNetworkAccessManager
161 QPointer<MockReply> Reply_;
163 explicit MockNAM (MockReply *reply)
168 MockReply* GetReply ()
173 QNetworkReply* createRequest (Operation op,
const QNetworkRequest& req, QIODevice*)
override
175 Reply_->setUrl (req.url ());
176 Reply_->setOperation (op);
177 Reply_->setRequest (req);
182 auto MkSuccessfulReply (
const QByteArray& data)
184 auto reply =
new MockReply;
185 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 200);
186 reply->SetData (data);
192 auto reply =
new MockReply;
193 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 404);
194 reply->setError (QNetworkReply::NetworkError::ContentAccessDenied,
"well, 404!"_qs);
198 void TestGoodReply (
auto finishMarker)
200 const QByteArray data {
"this is some test data" };
201 MockNAM nam { MkSuccessfulReply (data) };
202 finishMarker (*nam.GetReply ());
204 auto task = [&nam] () -> Task<QByteArray>
206 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
207 co_return reply.GetReplyData ();
211 QCOMPARE (result, data);
214 void TestBadReply (
auto finishMarker)
216 MockNAM nam { MkErrorReply () };
217 finishMarker (*nam.GetReply ());
219 auto task = [&nam] () -> Task<QByteArray>
221 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
222 co_return reply.GetReplyData ();
228 void ImmediateFinishMarker (MockReply& reply)
230 reply.setFinished (
true);
233 void DelayedFinishMarker (MockReply& reply)
235 QTimer::singleShot (10ms,
238 reply.setFinished (
true);
239 emit reply.finished ();
244 void CoroTaskTest::testNetworkReplyGoodNoWait ()
246 TestGoodReply (&ImmediateFinishMarker);
249 void CoroTaskTest::testNetworkReplyGoodWait ()
251 TestGoodReply (&DelayedFinishMarker);
254 void CoroTaskTest::testNetworkReplyBadNoWait ()
256 TestBadReply (&ImmediateFinishMarker);
259 void CoroTaskTest::testNetworkReplyBadWait ()
261 TestBadReply (&DelayedFinishMarker);
264 void CoroTaskTest::testFutureAwaiter ()
266 auto delayed = [] () -> Task<int>
268 co_return co_await QtConcurrent::run ([]
276 auto immediate = [] () -> Task<int>
278 co_return co_await QtConcurrent::run ([] {
return 42; });
282 auto ready = [] () -> Task<int>
284 co_return co_await MakeReadyFuture (42);
289 void CoroTaskTest::testWaitMany ()
291 constexpr auto max = 100;
292 auto mkTask = [] (
int index) -> Task<int>
294 co_await Precisely { std::chrono::milliseconds { max - index } };
300 QVector<Task<int>> tasks;
301 QVector<int> expected;
302 for (
int i = 0; i < max; ++i)
307 const auto creationElapsed = timer.elapsed ();
311 const auto executionElapsed = timer.elapsed ();
313 QCOMPARE (result, expected);
314 QCOMPARE_LT (creationElapsed, 1);
316 QCOMPARE_GE (executionElapsed, MinElapsed (max * 1ms));
317 const auto linearizedExecTime = max * (max + 1) / 2;
318 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
321 void CoroTaskTest::testWaitManyTuple ()
323 auto mkTask = [] (
int delay) -> Task<int>
325 co_await Precisely { std::chrono::milliseconds { delay } };
332 const auto executionElapsed = timer.elapsed ();
334 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
336 QCOMPARE_GE (executionElapsed, 10);
337 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
340 void CoroTaskTest::testWaitManyInvoking ()
342 constexpr auto max = 100;
343 auto mkTask = [] (
int index) -> Task<int>
345 co_await Precisely { std::chrono::milliseconds { max - index } };
352 QVector<int> expected;
353 for (
int i = 0; i < max; ++i)
358 const auto creationElapsed = timer.elapsed ();
362 const auto executionElapsed = timer.elapsed ();
364 QCOMPARE (result, expected);
365 QCOMPARE_LT (creationElapsed, 1);
367 QCOMPARE_GE (executionElapsed, MinElapsed (max * 1ms));
368 const auto linearizedExecTime = max * (max + 1) / 2;
369 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
372 void CoroTaskTest::testNCopies ()
374 constexpr auto count = 10;
375 static constexpr auto unit = 10ms;
377 auto mkTask = [] (
int index) -> Task<int>
379 co_await Precisely { unit * (count - index) };
386 auto task =
NCopies (count, [&] {
return mkTask (created++); });
387 const auto creationElapsed = timer.elapsed ();
389 static_assert (std::is_same_v<
decltype (task), Task<QVector<int>>>);
390 QCOMPARE (created, count);
391 QCOMPARE_LT (creationElapsed, unit.count ());
395 const auto executionElapsed = timer.elapsed ();
397 QVector<int> expected;
398 for (
int i = 0; i < count; ++i)
400 QCOMPARE (result, expected);
402 QCOMPARE_GE (executionElapsed, MinElapsed (count * unit));
403 const auto linearizedExecTime = unit.count () * count * (count + 1) / 2;
404 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
407 void CoroTaskTest::testNCopiesVoid ()
409 constexpr auto count = 5;
410 auto mkTask = [] (
int& finished) -> Task<void>
417 auto task =
NCopies (count, [&] {
return mkTask (finished); });
418 static_assert (std::is_same_v<
decltype (task), Task<void>>);
419 QCOMPARE (finished, 0);
422 QCOMPARE (finished, count);
426 "extensions did not survive the delegation");
429 void CoroTaskTest::testNCopiesZero ()
432 auto mkTask = [] (
int& created) -> Task<int>
439 QVERIFY (result.isEmpty ());
440 QCOMPARE (created, 0);
445 Task<int> SlowSuccess (std::chrono::milliseconds delay,
bool& done)
452 Task<int> SlowFailure (std::chrono::milliseconds delay, std::string what)
455 throw std::runtime_error { what };
458 QString ThrownMessage (
auto&& f)
464 catch (
const std::exception& e)
466 return QString::fromUtf8 (e.what ());
472 void CoroTaskTest::testWaitManyJoinsAfterFailure ()
474 constexpr auto slowDelay = 60ms;
475 bool slowDone =
false;
478 const auto message = ThrownMessage ([&] {
GetTaskResult (
InParallel ({ SlowFailure (10ms,
"early"), SlowSuccess (slowDelay, slowDone) })); });
479 const auto elapsed = timer.elapsed ();
481 QCOMPARE (message,
"early"_qs);
483 QCOMPARE_GE (elapsed, MinElapsed (slowDelay));
486 void CoroTaskTest::testWaitManyVoidJoinsAfterFailure ()
488 auto slowSuccess = [] (
bool& done) -> Task<void>
493 auto earlyFailure = [] () -> Task<void>
496 throw std::runtime_error {
"early" };
499 bool slowDone =
false;
500 const auto message = ThrownMessage ([&] {
GetTaskResult (
InParallel ({ earlyFailure (), slowSuccess (slowDone) })); });
501 QCOMPARE (message,
"early"_qs);
505 void CoroTaskTest::testWaitManyFirstFailureInOrderWins ()
507 QTest::ignoreMessage (QtWarningMsg,
"dropping a subsequent exception: \"first in time\"");
509 const auto message = ThrownMessage ([] {
GetTaskResult (
InParallel ({ SlowFailure (40ms,
"first in order"), SlowFailure (10ms,
"first in time") })); });
510 QCOMPARE (message,
"first in order"_qs);
513 void CoroTaskTest::testWaitManyInvokingFactoryThrows ()
515 constexpr auto delay = 40ms;
516 QVector<bool> done (5,
false);
518 auto mkTask = [&] (
int index)
521 throw std::runtime_error {
"factory" };
523 return SlowSuccess (delay, done [index]);
529 const auto elapsed = timer.elapsed ();
531 QCOMPARE (message,
"factory"_qs);
532 QCOMPARE (created, 2);
533 QCOMPARE (done, (QVector {
true,
true,
false,
false,
false }));
534 QCOMPARE_GE (elapsed, MinElapsed (delay));
537 void CoroTaskTest::testNCopiesFactoryThrows ()
541 auto taskFactory = [&]
544 throw std::runtime_error {
"factory" };
545 return SlowSuccess (30ms, done);
549 QCOMPARE (message,
"factory"_qs);
554 void CoroTaskTest::testWaitManyContextDeathSilenced ()
556 QTest::failOnWarning (QRegularExpression {
"dropping a subsequent"_qs });
558 auto context = std::make_unique<CoroContext> ();
561 co_await AddContext { *context };
564 auto task =
InParallel ({ mkTask (context.get ()), mkTask (context.get ()), mkTask (context.get ()) });
565 QTimer::singleShot (10ms, [context = std::move (context)] ()
mutable { context.reset (); });
569 QVERIFY_THROWS_EXCEPTION (ContextDeadException,
GetTaskResult (task));
570 QCOMPARE_LT (timer.elapsed (), 250);
573 void CoroTaskTest::testSharedTaskManyAwaiters ()
581 auto mkAwaiter = [&shared] (
int offset) -> Task<int>
583 co_return co_await shared + offset;
587 QCOMPARE (result, (std::tuple { 42, 43 }));
590 void CoroTaskTest::testSharedTaskAwaiterRemovedOnOuterDestruction ()
592 constexpr auto destructionDelay = 10ms;
599 auto survivingAwaiter = [&shared] () -> Task<int>
601 co_return co_await shared;
604 auto context = std::make_unique<CoroContext> ();
605 auto cancelledAwaiter = [&shared] (CoroContext *context) ->
ContextTask<int>
607 co_await AddContext { *context };
608 co_return co_await shared;
610 QTimer::singleShot (destructionDelay, [context = std::move (context)] ()
mutable { context.reset (); });
616 void CoroTaskTest::testSharedTaskExceptionManyAwaiters ()
618 struct TestSharedTaskException : std::exception {};
623 throw TestSharedTaskException {};
626 auto mkAwaiter = [&shared] () -> Task<bool>
633 catch (
const TestSharedTaskException&)
640 QCOMPARE (result, (std::tuple {
true,
true }));
643 void CoroTaskTest::testSharedTaskLastCopyDestroyedByAwaiter ()
651 auto destructiveAwaiter = [] (std::optional<SharedTask<int>> *shared) -> Task<int>
653 auto result =
co_await **shared;
658 auto normalAwaiter = [] (std::optional<SharedTask<int>> *shared) -> Task<int>
660 co_return co_await **shared;
664 QCOMPARE (result, (std::tuple { 42, 42 }));
667 void CoroTaskTest::testSharedTaskNonTriviallyMovableReturn ()
670 const auto payload =
"0123456789abcdef"_qs.repeated (16);
678 auto mkAwaiter = [&shared] () -> Task<QString>
680 co_return co_await shared;
684 QCOMPARE (result, (std::tuple { payload, payload }));
687 void CoroTaskTest::testSharedContextTaskManyAwaitersContextAlive ()
689 auto context = std::make_unique<CoroContext> ();
692 co_await AddContext { *ctx };
697 auto mkAwaiter = [&shared] (
int offset) -> Task<int>
699 co_return co_await shared + offset;
703 QCOMPARE (result, (std::tuple { 42, 43 }));
706 void CoroTaskTest::testSharedContextTaskContextDeadFansOutToAll ()
708 auto context = std::make_unique<CoroContext> ();
711 co_await AddContext { *ctx };
716 auto mkAwaiter = [&shared] () -> Task<bool>
723 catch (
const ContextDeadException&)
729 QTimer::singleShot (10ms, [context = std::move (context)] ()
mutable { context.reset (); });
732 QCOMPARE (result, (std::tuple {
true,
true }));
735 void CoroTaskTest::testSharedContextTaskBodyExceptionFansOut ()
737 struct TestSharedContextException : std::exception {};
739 auto context = std::make_unique<CoroContext> ();
742 co_await AddContext { *ctx };
744 throw TestSharedContextException {};
747 auto mkAwaiter = [&shared] () -> Task<bool>
754 catch (
const TestSharedContextException&)
761 QCOMPARE (result, (std::tuple {
true,
true }));
764 void CoroTaskTest::testSharedContextTaskContextDeadDoesntWaitLong ()
766 auto context = std::make_unique<CoroContext> ();
769 co_await AddContext { *ctx };
773 auto awaiter = [&shared] () -> Task<void>
778 QTimer::singleShot (10ms, [context = std::move (context)] ()
mutable { context.reset (); });
783 QCOMPARE_LT (timer.elapsed (), 255);
786 void CoroTaskTest::testSharedContextTaskMixedAwaitersOwnContextDies ()
788 auto sharedCtx = std::make_unique<CoroContext> ();
789 auto aliveCtx = std::make_unique<CoroContext> ();
790 auto dyingCtx = std::make_unique<CoroContext> ();
794 co_await AddContext { *ctx };
801 co_await AddContext { *ctx };
802 co_return co_await shared;
807 co_await AddContext { *ctx };
808 co_return co_await shared;
811 QTimer::singleShot (10ms, [dyingCtx = std::move (dyingCtx)] ()
mutable { dyingCtx.reset (); });
817 void CoroTaskTest::testEither ()
819 using Result_t = Either<QString, bool>;
821 auto immediatelyFailing = [] () -> Task<Result_t>
823 const auto theInt =
co_await Either<QString, int> {
"meh" };
824 co_return theInt > 420;
826 QCOMPARE (
GetTaskResult (immediatelyFailing), Result_t { Left {
"meh" } });
828 auto earlyFailing = [] () -> Task<Result_t>
830 const auto theInt =
co_await Either<QString, int> {
"meh" };
832 co_return theInt > 420;
834 QCOMPARE (
GetTaskResult (earlyFailing), Result_t { Left {
"meh" } });
836 auto successful = [] () -> Task<Result_t>
838 const auto theInt =
co_await Either<QString, int> { 42 };
840 co_return theInt > 420;
845 void CoroTaskTest::testEitherIgnoreLeft ()
847 auto immediatelyFailing = [] () -> Task<Either<QString, int>>
849 const auto theInt =
co_await WithHandler (Either<QString, int> {
"meh" }, IgnoreLeft {});
852 QCOMPARE (
GetTaskResult (immediatelyFailing), (Either<QString, int> { 0 }));
855 void CoroTaskTest::testThrottleSameCoro ()
858 constexpr auto count = 10;
862 auto task = [] (
auto& t) -> Task<int>
865 for (
int i = 0; i < count; ++i)
873 const auto time = timer.elapsed ();
875 QCOMPARE (result, count * (count - 1) / 2);
876 QCOMPARE_GE (time, count * t.GetInterval ().count ());
879 void CoroTaskTest::testThrottleSameCoroSlow ()
882 constexpr auto count = 10;
883 constexpr static auto intraDelay = 9ms;
887 auto task = [] (
auto& t) -> Task<void>
889 for (
int i = 0; i < count; ++i)
897 const auto time = timer.elapsed ();
899 const auto expectedMinTime = count * t.GetInterval ().count ();
900 QCOMPARE_GE (time, expectedMinTime);
902 const auto delaysTime = (count - 1) * intraDelay.count ();
903 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
906 void CoroTaskTest::testThrottleSameCoroVerySlow ()
909 constexpr auto count = 10;
910 constexpr static auto intraDelay = 20ms;
914 auto task = [] (
auto& t) -> Task<void>
916 for (
int i = 0; i < count; ++i)
924 const auto time = timer.elapsed ();
926 const auto expectedMinTime = (count - 1) * intraDelay.count ();
927 QCOMPARE_GE (time, expectedMinTime);
929 const auto throttlesTime = count * t.GetInterval ().count ();
930 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
933 void CoroTaskTest::testThrottleManyCoros ()
935 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
936 constexpr auto count = 10;
940 auto mkTask = [] (
auto& t) -> Task<void>
942 for (
int i = 0; i < count; ++i)
945 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
946 for (
auto& task : tasks)
948 const auto time = timer.elapsed ();
950 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
957 void CoroTaskTest::testContextDestrBeforeFinish ()
959 auto context = std::make_unique<CoroContext> ();
964 co_return context->GetName ();
971 void CoroTaskTest::testContextDestrAfterFinish ()
973 const auto ctxName =
"some name"_qs;
974 auto context = std::make_unique<CoroContext> (ctxName);
977 co_await AddContext { *context };
979 co_return context->GetName ();
985 void CoroTaskTest::testContextDestrAwaitedSubtask ()
987 auto context = std::make_unique<CoroContext> ();
990 co_await AddContext { *context };
998 co_await nestedSubtask;
999 co_return context->GetName ();
1003 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
1008 template<
typename... Ts>
1009 auto WithContext (
auto&& taskGen, Ts&&... taskArgs)
1011 auto context = std::make_unique<CoroContext> ();
1012 auto task = taskGen (&*context, std::forward<Ts> (taskArgs)...);
1013 QTimer::singleShot (
ShortDelay, [context = std::move (context)] ()
mutable { context.reset (); });
1017 void WithDestroyTimer (
auto task)
1019 QElapsedTimer timer;
1021 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
1026 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
1030 co_await AddContext { *context };
1035 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
1037 const QByteArray data {
"this is some test data" };
1038 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
1042 if (
const auto reply = nam->GetReply ())
1044 reply->setFinished (
true);
1045 emit reply->finished ();
1049 WithDestroyTimer (WithContext ([] (CoroContext *context, QNetworkAccessManager *nam) ->
ContextTask<QByteArray>
1051 co_await AddContext { *context };
1052 const auto reply =
co_await *nam->get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
1053 co_return reply.GetReplyData ();
1057 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
1059 WithDestroyTimer (WithContext ([] (CoroContext *context) ->
ContextTask<>
1061 co_await AddContext { *context };
1063 const auto process =
new QProcess {};
1064 const auto delay = std::chrono::duration_cast<std::chrono::milliseconds> (
LongDelay);
1065 process->start (
"sleep", { QString::number (delay.count () / 1000.0) });
1067 &QProcess::finished,
1069 &QObject::deleteLater);
1075 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
1077 WithDestroyTimer (WithContext ([] (CoroContext *context) ->
ContextTask<>
1079 co_await AddContext { *context };
1080 co_await QtConcurrent::run ([] { QThread::sleep (
LongDelay); });
1084 void CoroTaskTest::testContextDestrDoesntWaitPlainTask ()
1089 co_await AddContext { *context };
1090 co_await [] () -> Task<void> {
co_await LongDelay; } ();
1099 void CoroTaskTest::testContextDestrParentOutlivesChild ()
1101 QList<Event> destrLog;
1102 auto task = WithContext ([] (CoroContext *context, QList<Event>& log) ->
ContextTask<void>
1104 co_await AddContext { *context };
1109 co_await AddContext { *context };
1113 WithDestroyTimer (task);
1118 void CoroTaskTest::testContextDestrDoesntResumeFinishedSibling ()
1121 auto task = WithContext ([] (CoroContext *context, QList<Event>& log) ->
ContextTask<void>
1123 co_await AddContext { *context };
1126 auto child = [] (CoroContext *context, QList<Event>& log, std::chrono::milliseconds delay,
Event name) ->
ContextTask<void>
1128 const auto cleanup =
MakeScopeGuard ([&log, name] { log << name; });
1129 co_await AddContext { *context };
1134 WithDestroyTimer (task);
1136 std::ranges::sort (log);
1142 struct TouchRecordingAwaiter
1146 bool await_ready () noexcept
1152 void await_suspend (std::coroutine_handle<>)
noexcept {}
1153 void await_resume () noexcept {}
1157 void CoroTaskTest::testContextDestrSwallowedDoesntTouchNextAwaitable ()
1159 bool touched =
false;
1160 auto task = WithContext ([] (CoroContext *context,
bool& touched) ->
ContextTask<void>
1162 co_await AddContext { *context };
1167 catch (
const std::exception&)
1170 co_await TouchRecordingAwaiter { touched };
1172 WithDestroyTimer (task);
1178 using Context_ptr = std::unique_ptr<CoroContext>;
1199 Task<void> PlainMiddle (Context_ptr& context)
1201 co_await DestroyingChild (context);
1204 auto ParallelChildren (Context_ptr& context)
1206 return InParallel ({ DestroyingChild (context), SleepingChild (context) });
1212 co_await mkChild (context);
1216 void RunCoro (
auto mkTask)
1218 auto context = std::make_unique<CoroContext> (
"doomed"_qs);
1222 void RunUnderParent (
auto mkChild)
1224 RunCoro ([&] (Context_ptr& context) {
return Parent (context, mkChild); });
1227 constexpr auto DeathChildEnvVar =
"LC_COROTASKTEST_DEATH_CHILD";
1231 void ExpectFatal (
const QString& message,
auto scenario)
1233 if (qEnvironmentVariableIsSet (DeathChildEnvVar))
1236 QFAIL (
"the scenario was expected to abort");
1239 auto testName = QString::fromLatin1 (QTest::currentTestFunction ());
1240 if (
const auto tag = QTest::currentDataTag ())
1241 testName +=
':' + QString::fromLatin1 (tag);
1243 auto env = QProcessEnvironment::systemEnvironment ();
1244 env.insert (DeathChildEnvVar,
"1"_qs);
1247 child.setProcessEnvironment (env);
1248 child.setProcessChannelMode (QProcess::MergedChannels);
1249 child.start (QCoreApplication::applicationFilePath (), {
"-nocrashhandler"_qs, testName });
1250 QVERIFY (child.waitForFinished ());
1252 const auto output = QString::fromUtf8 (child.readAll ());
1253 QVERIFY2 (child.exitStatus () == QProcess::CrashExit, qPrintable (output));
1254 QVERIFY2 (output.contains (message), qPrintable (output));
1258 void CoroTaskTest::testContextDestrFromRunningCoroAborts_data ()
1260 QTest::addColumn<Scenario> (
"scenario");
1262 QTest::newRow (
"self") <<
Scenario { [] { RunCoro (&DestroyingChild); } };
1263 QTest::newRow (
"child") <<
Scenario { [] { RunUnderParent (&DestroyingChild); } };
1264 QTest::newRow (
"child via plain task") <<
Scenario { [] { RunUnderParent (&PlainMiddle); } };
1265 QTest::newRow (
"child before first await") <<
Scenario { [] { RunUnderParent (&ImmediateDestroyingChild); } };
1266 QTest::newRow (
"child in InParallel") <<
Scenario { [] { RunUnderParent (&ParallelChildren); } };
1269 void CoroTaskTest::testContextDestrFromRunningCoroAborts ()
1272 ExpectFatal (
"destroying the context \"doomed\" while its child coro is running"_qs, scenario);
1275 void CoroTaskTest::testContextOfIsPerObject ()
1283 void CoroTaskTest::testContextOfDiesWithObject ()
1285 auto object = std::make_unique<QObject> ();
1291 QTimer::singleShot (
ShortDelay, [
object = std::move (
object)] ()
mutable {
object.reset (); });
1292 WithDestroyTimer (task);
1295 void CoroTaskTest::testContextOfRejectsForeignThread ()
1298 bool thrown =
false;
1306 catch (
const std::runtime_error&)
1320 process.start (program, args);
1321 co_return co_await process;
1325 void CoroTaskTest::testProcessOutcomeExited ()
1327 const auto outcome =
GetTaskResult (RunProcess (
"sh"_qs, {
"-c"_qs,
"exit 3"_qs }));
1328 QVERIFY (std::holds_alternative<ProcessExited> (outcome));
1329 QCOMPARE (std::get<ProcessExited> (outcome).Code_, 3);
1332 void CoroTaskTest::testProcessOutcomeFailedToStart ()
1334 const auto outcome =
GetTaskResult (RunProcess (
"lc-definitely-not-an-existing-binary"_qs, {}));
1335 QVERIFY (std::holds_alternative<ProcessFailedToStart> (outcome));
1336 QVERIFY (!std::get<ProcessFailedToStart> (outcome).Error_.isEmpty ());
1340 void CoroTaskTest::testProcessOutcomeCrashed ()
1342 const auto outcome =
GetTaskResult (RunProcess (
"sh"_qs, {
"-c"_qs,
"kill -SEGV $$"_qs }));
1343 QVERIFY (std::holds_alternative<ProcessCrashed> (outcome));
1344 QCOMPARE (std::get<ProcessCrashed> (outcome).Code_, SIGSEGV);
1349 void CoroTaskTest::testDBus ()
1351 const auto& bus = QDBusConnection::systemBus ();
1352 if (!bus.isConnected ())
1353 QSKIP (
"D-Bus system bus is not available");
1355 auto task = [] () -> Task<Either<QDBusError::ErrorType, bool>>
1357 const auto& bus = QDBusConnection::systemBus ();
1358 QDBusInterface dbusIface {
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus", bus };
1359 const auto result =
co_await Typed<bool> (dbusIface.asyncCall (
"NameHasOwner",
"org.freedesktop.DBus"_qs));
1360 co_return result.MapLeft (&QDBusError::type);
1367 void CoroTaskTest::cleanupTestCase ()
1370 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 *)