LeechCraft 0.6.70-18808-g3467692359
Modular cross-platform feature rich live environment.
Loading...
Searching...
No Matches
corotasktest.cpp
Go to the documentation of this file.
1/**********************************************************************
2 * LeechCraft - modular cross-platform feature rich internet client.
3 * Copyright (C) 2006-2014 Georg Rudoy
4 *
5 * Distributed under the Boost Software License, Version 1.0.
6 * (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
7 **********************************************************************/
8
9#include "corotasktest.h"
10#include <QtConcurrentRun>
11#include <QtTest>
12#ifdef QT_DBUS_LIB
13#include <QDBusInterface>
14#endif
15#include <coro/future.h>
16#include <coro.h>
17#include <coro/getresult.h>
18#include <coro/inparallel.h>
19#include <coro/sharedtask.h>
20#include <coro/throttle.h>
21#ifdef QT_DBUS_LIB
22#include <coro/dbus.h>
23#endif
26#include <util/sll/qtutil.h>
27
28QTEST_GUILESS_MAIN (LC::Util::CoroTaskTest)
29
30using namespace std::chrono_literals;
31
32namespace LC::Util
33{
34 void CoroTaskTest::testReturn ()
35 {
36 auto task = [] () -> Task<int> { co_return 42; } ();
37 auto result = GetTaskResult (task);
38 QCOMPARE (result, 42);
39 }
40
41 void CoroTaskTest::testMoveOnlyReturn ()
42 {
43 auto direct = [] () -> Task<std::unique_ptr<int>>
44 {
45 co_return std::make_unique<int> (42);
46 } ();
47 auto directResult = GetTaskResult (direct);
48 QVERIFY (directResult);
49 QCOMPARE (*directResult, 42);
50
51 auto chained = [] () -> Task<std::unique_ptr<int>>
52 {
53 auto inner = [] () -> Task<std::unique_ptr<int>>
54 {
55 co_await 5ms;
56 co_return std::make_unique<int> (7);
57 } ();
58 auto value = co_await inner;
59 *value *= 6;
60 co_return std::move (value);
61 } ();
62 auto chainedResult = GetTaskResult (chained);
63 QVERIFY (chainedResult);
64 QCOMPARE (*chainedResult, 42);
65 }
66
67 void CoroTaskTest::testWait ()
68 {
69 QElapsedTimer timer;
70 timer.start ();
71
72 auto task = [] () -> Task<int>
73 {
74 co_await 50ms;
75 co_await Precisely { 10ms };
76 co_return 42;
77 } ();
78
79 auto result = GetTaskResult (task);
80 QCOMPARE (result, 42);
81 QCOMPARE_GT (timer.elapsed (), 50);
82 }
83
84 void CoroTaskTest::testTaskDestr ()
85 {
86 bool continued = false;
87
88 [] (auto& continued) -> Task<void>
89 {
90 co_await 10ms;
91 continued = true;
92 } (continued);
93
94 QTRY_VERIFY_WITH_TIMEOUT (continued, 20);
95 }
96
97 namespace
98 {
99 // almost the Public Morozov pattern
100 class MockReply : public QNetworkReply
101 {
102 QBuffer Buffer_;
103 public:
104 using QNetworkReply::QNetworkReply;
105
106 using QNetworkReply::setAttribute;
107 using QNetworkReply::setError;
108 using QNetworkReply::setFinished;
109 using QNetworkReply::setHeader;
110 using QNetworkReply::setOperation;
111 using QNetworkReply::setRawHeader;
112 using QNetworkReply::setRequest;
113 using QNetworkReply::setUrl;
114
115 void SetData (const QByteArray& data)
116 {
117 Buffer_.setData (data);
118 Buffer_.open (QIODevice::ReadOnly);
119 open (QIODevice::ReadOnly);
120 }
121 protected:
122 qint64 readData (char *data, qint64 maxSize) override
123 {
124 return Buffer_.read (data, maxSize);
125 }
126
127 void abort () override
128 {
129 }
130 };
131
132 class MockNAM : public QNetworkAccessManager
133 {
134 QPointer<MockReply> Reply_;
135 public:
136 explicit MockNAM (MockReply *reply)
137 : Reply_ { reply }
138 {
139 }
140
141 MockReply* GetReply ()
142 {
143 return Reply_;
144 }
145 protected:
146 QNetworkReply* createRequest (Operation op, const QNetworkRequest& req, QIODevice*) override
147 {
148 Reply_->setUrl (req.url ());
149 Reply_->setOperation (op);
150 Reply_->setRequest (req);
151 return Reply_;
152 }
153 };
154
155 auto MkSuccessfulReply (const QByteArray& data)
156 {
157 auto reply = new MockReply;
158 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 200);
159 reply->SetData (data);
160 return reply;
161 }
162
163 auto MkErrorReply ()
164 {
165 auto reply = new MockReply;
166 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 404);
167 reply->setError (QNetworkReply::NetworkError::ContentAccessDenied, "well, 404!"_qs);
168 return reply;
169 }
170
171 void TestGoodReply (auto finishMarker)
172 {
173 const QByteArray data { "this is some test data" };
174 MockNAM nam { MkSuccessfulReply (data) };
175 finishMarker (*nam.GetReply ());
176
177 auto task = [&nam] () -> Task<QByteArray>
178 {
179 auto reply = co_await *nam.get (QNetworkRequest { QUrl { "http://example.com/foo.txt"_qs } });
180 co_return reply.GetReplyData ();
181 } ();
182
183 auto result = GetTaskResult (task);
184 QCOMPARE (result, data);
185 }
186
187 void TestBadReply (auto finishMarker)
188 {
189 MockNAM nam { MkErrorReply () };
190 finishMarker (*nam.GetReply ());
191
192 auto task = [&nam] () -> Task<QByteArray>
193 {
194 auto reply = co_await *nam.get (QNetworkRequest { QUrl { "http://example.com/foo.txt"_qs } });
195 co_return reply.GetReplyData ();
196 } ();
197
198 QVERIFY_THROWS_EXCEPTION (LC::Util::NetworkReplyErrorException, GetTaskResult (task));
199 }
200
201 void ImmediateFinishMarker (MockReply& reply)
202 {
203 reply.setFinished (true);
204 }
205
206 void DelayedFinishMarker (MockReply& reply)
207 {
208 QTimer::singleShot (10ms,
209 [&]
210 {
211 reply.setFinished (true);
212 emit reply.finished ();
213 });
214 }
215 }
216
217 void CoroTaskTest::testNetworkReplyGoodNoWait ()
218 {
219 TestGoodReply (&ImmediateFinishMarker);
220 }
221
222 void CoroTaskTest::testNetworkReplyGoodWait ()
223 {
224 TestGoodReply (&DelayedFinishMarker);
225 }
226
227 void CoroTaskTest::testNetworkReplyBadNoWait ()
228 {
229 TestBadReply (&ImmediateFinishMarker);
230 }
231
232 void CoroTaskTest::testNetworkReplyBadWait ()
233 {
234 TestBadReply (&DelayedFinishMarker);
235 }
236
237 void CoroTaskTest::testFutureAwaiter ()
238 {
239 auto delayed = [] () -> Task<int>
240 {
241 co_return co_await QtConcurrent::run ([]
242 {
243 QThread::msleep (1);
244 return 42;
245 });
246 } ();
247 QCOMPARE (GetTaskResult (delayed), 42);
248
249 auto immediate = [] () -> Task<int>
250 {
251 co_return co_await QtConcurrent::run ([] { return 42; });
252 } ();
253 QCOMPARE (GetTaskResult (immediate), 42);
254
255 auto ready = [] () -> Task<int>
256 {
257 co_return co_await MakeReadyFuture (42);
258 } ();
259 QCOMPARE (GetTaskResult (ready), 42);
260 }
261
262 void CoroTaskTest::testWaitMany ()
263 {
264 constexpr auto max = 100;
265 auto mkTask = [] (int index) -> Task<int>
266 {
267 co_await Precisely { std::chrono::milliseconds { max - index } };
268 co_return index;
269 };
270
271 QElapsedTimer timer;
272 timer.start ();
273 QVector<Task<int>> tasks;
274 QVector<int> expected;
275 for (int i = 0; i < max; ++i)
276 {
277 tasks << mkTask (i);
278 expected << i;
279 }
280 const auto creationElapsed = timer.elapsed ();
281
282 timer.restart ();
283 auto result = GetTaskResult (InParallel (std::move (tasks)));
284 const auto executionElapsed = timer.elapsed ();
285
286 QCOMPARE (result, expected);
287 QCOMPARE_LT (creationElapsed, 1);
288
289 constexpr auto tolerance = 0.05;
290 QCOMPARE_GE (executionElapsed, max * (1 - tolerance));
291 const auto linearizedExecTime = max * (max + 1) / 2;
292 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
293 }
294
295 void CoroTaskTest::testWaitManyTuple ()
296 {
297 auto mkTask = [] (int delay) -> Task<int>
298 {
299 co_await Precisely { std::chrono::milliseconds { delay } };
300 co_return delay;
301 };
302
303 QElapsedTimer timer;
304 timer.start ();
305 auto result = GetTaskResult (InParallel (mkTask (10), mkTask (9), mkTask (2), mkTask (1)));
306 const auto executionElapsed = timer.elapsed ();
307
308 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
309
310 QCOMPARE_GE (executionElapsed, 10);
311 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
312 }
313
314 void CoroTaskTest::testWaitManyInvoking ()
315 {
316 constexpr auto max = 100;
317 auto mkTask = [] (int index) -> Task<int>
318 {
319 co_await Precisely { std::chrono::milliseconds { max - index } };
320 co_return index;
321 };
322
323 QElapsedTimer timer;
324 timer.start ();
325 QVector<int> inputs;
326 QVector<int> expected;
327 for (int i = 0; i < max; ++i)
328 {
329 inputs << i;
330 expected << i;
331 }
332 const auto creationElapsed = timer.elapsed ();
333
334 timer.restart ();
335 auto result = GetTaskResult (InParallel (inputs, mkTask));
336 const auto executionElapsed = timer.elapsed ();
337
338 QCOMPARE (result, expected);
339 QCOMPARE_LT (creationElapsed, 1);
340
341 constexpr auto tolerance = 0.05;
342 QCOMPARE_GE (executionElapsed, max * (1 - tolerance));
343 const auto linearizedExecTime = max * (max + 1) / 2;
344 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
345 }
346
347 void CoroTaskTest::testSharedTaskManyAwaiters ()
348 {
349 auto shared = [] () -> SharedTask<int>
350 {
351 co_await 10ms;
352 co_return 42;
353 } ();
354
355 auto mkAwaiter = [&shared] (int offset) -> Task<int>
356 {
357 co_return co_await shared + offset;
358 };
359
360 const auto result = GetTaskResult (InParallel (mkAwaiter (0), mkAwaiter (1)));
361 QCOMPARE (result, (std::tuple { 42, 43 }));
362 }
363
364 void CoroTaskTest::testSharedTaskAwaiterRemovedOnOuterDestruction ()
365 {
366 constexpr auto destructionDelay = 10ms;
367 auto shared = [] () -> SharedTask<int>
368 {
369 co_await 500ms;
370 co_return 42;
371 } ();
372
373 auto survivingAwaiter = [&shared] () -> Task<int>
374 {
375 co_return co_await shared;
376 } ();
377
378 auto context = std::make_unique<QObject> ();
379 auto cancelledAwaiter = [&shared] (QObject *context) -> ContextTask<int>
380 {
381 co_await AddContextObject { *context };
382 co_return co_await shared;
383 } (&*context);
384 QTimer::singleShot (destructionDelay, [context = std::move (context)] () mutable { context.reset (); });
385
386 QCOMPARE (GetTaskResult (survivingAwaiter), 42);
387 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (cancelledAwaiter));
388 }
389
390 void CoroTaskTest::testSharedTaskExceptionManyAwaiters ()
391 {
392 struct TestSharedTaskException : std::exception {};
393
394 auto shared = [] () -> SharedTask<>
395 {
396 co_await 10ms;
397 throw TestSharedTaskException {};
398 } ();
399
400 auto mkAwaiter = [&shared] () -> Task<bool>
401 {
402 try
403 {
404 co_await shared;
405 co_return false;
406 }
407 catch (const TestSharedTaskException&)
408 {
409 co_return true;
410 }
411 };
412
413 const auto result = GetTaskResult (InParallel (mkAwaiter (), mkAwaiter ()));
414 QCOMPARE (result, (std::tuple { true, true }));
415 }
416
417 void CoroTaskTest::testSharedTaskLastCopyDestroyedByAwaiter ()
418 {
419 std::optional<SharedTask<int>> shared = [] () -> SharedTask<int>
420 {
421 co_await 10ms;
422 co_return 42;
423 } ();
424
425 auto destructiveAwaiter = [] (std::optional<SharedTask<int>> *shared) -> Task<int>
426 {
427 auto result = co_await **shared;
428 shared->reset ();
429 co_return result;
430 } (&shared);
431
432 auto normalAwaiter = [] (std::optional<SharedTask<int>> *shared) -> Task<int>
433 {
434 co_return co_await **shared;
435 } (&shared);
436
437 const auto result = GetTaskResult (InParallel (destructiveAwaiter, normalAwaiter));
438 QCOMPARE (result, (std::tuple { 42, 42 }));
439 }
440
441 void CoroTaskTest::testSharedTaskNonTriviallyMovableReturn ()
442 {
443 // Long enough that a moved-from QString is different from the real one, avoiding any SSO shenanigans.
444 const auto payload = "0123456789abcdef"_qs.repeated (16);
445
446 auto shared = [] (QString p) -> SharedTask<QString>
447 {
448 co_await 10ms;
449 co_return p;
450 } (payload);
451
452 auto mkAwaiter = [&shared] () -> Task<QString>
453 {
454 co_return co_await shared;
455 };
456
457 const auto result = GetTaskResult (InParallel (mkAwaiter (), mkAwaiter ()));
458 QCOMPARE (result, (std::tuple { payload, payload }));
459 }
460
461 void CoroTaskTest::testSharedContextTaskManyAwaitersContextAlive ()
462 {
463 auto context = std::make_unique<QObject> ();
464 auto shared = [] (QObject *ctx) -> SharedContextTask<int>
465 {
466 co_await AddContextObject { *ctx };
467 co_await 10ms;
468 co_return 42;
469 } (&*context);
470
471 auto mkAwaiter = [&shared] (int offset) -> Task<int>
472 {
473 co_return co_await shared + offset;
474 };
475
476 const auto result = GetTaskResult (InParallel (mkAwaiter (0), mkAwaiter (1)));
477 QCOMPARE (result, (std::tuple { 42, 43 }));
478 }
479
480 void CoroTaskTest::testSharedContextTaskContextDeadFansOutToAll ()
481 {
482 auto context = std::make_unique<QObject> ();
483 auto shared = [] (QObject *ctx) -> SharedContextTask<int>
484 {
485 co_await AddContextObject { *ctx };
486 co_await 500ms;
487 co_return 42;
488 } (&*context);
489
490 auto mkAwaiter = [&shared] () -> Task<bool>
491 {
492 try
493 {
494 co_await shared;
495 co_return false;
496 }
497 catch (const ContextDeadException&)
498 {
499 co_return true;
500 }
501 };
502
503 QTimer::singleShot (10ms, [context = std::move (context)] () mutable { context.reset (); });
504
505 const auto result = GetTaskResult (InParallel (mkAwaiter (), mkAwaiter ()));
506 QCOMPARE (result, (std::tuple { true, true }));
507 }
508
509 void CoroTaskTest::testSharedContextTaskBodyExceptionFansOut ()
510 {
511 struct TestSharedContextException : std::exception {};
512
513 auto context = std::make_unique<QObject> ();
514 auto shared = [] (QObject *ctx) -> SharedContextTask<>
515 {
516 co_await AddContextObject { *ctx };
517 co_await 10ms;
518 throw TestSharedContextException {};
519 } (&*context);
520
521 auto mkAwaiter = [&shared] () -> Task<bool>
522 {
523 try
524 {
525 co_await shared;
526 co_return false;
527 }
528 catch (const TestSharedContextException&)
529 {
530 co_return true;
531 }
532 };
533
534 const auto result = GetTaskResult (InParallel (mkAwaiter (), mkAwaiter ()));
535 QCOMPARE (result, (std::tuple { true, true }));
536 }
537
538 void CoroTaskTest::testSharedContextTaskContextDeadDoesntWaitLong ()
539 {
540 auto context = std::make_unique<QObject> ();
541 auto shared = [] (QObject *ctx) -> SharedContextTask<>
542 {
543 co_await AddContextObject { *ctx };
544 co_await 500ms;
545 } (&*context);
546
547 auto awaiter = [&shared] () -> Task<void>
548 {
549 co_await shared;
550 } ();
551
552 QTimer::singleShot (10ms, [context = std::move (context)] () mutable { context.reset (); });
553
554 QElapsedTimer timer;
555 timer.start ();
556 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (awaiter));
557 QCOMPARE_LT (timer.elapsed (), 255);
558 }
559
560 void CoroTaskTest::testSharedContextTaskMixedAwaitersOwnContextDies ()
561 {
562 auto sharedCtx = std::make_unique<QObject> ();
563 auto aliveCtx = std::make_unique<QObject> ();
564 auto dyingCtx = std::make_unique<QObject> ();
565
566 auto shared = [] (QObject *ctx) -> SharedContextTask<int>
567 {
568 co_await AddContextObject { *ctx };
569 co_await 100ms;
570 co_return 42;
571 } (&*sharedCtx);
572
573 auto aliveAwaiter = [&shared] (QObject *ctx) -> ContextTask<int>
574 {
575 co_await AddContextObject { *ctx };
576 co_return co_await shared;
577 } (&*aliveCtx);
578
579 auto dyingAwaiter = [&shared] (QObject *ctx) -> ContextTask<int>
580 {
581 co_await AddContextObject { *ctx };
582 co_return co_await shared;
583 } (&*dyingCtx);
584
585 QTimer::singleShot (10ms, [dyingCtx = std::move (dyingCtx)] () mutable { dyingCtx.reset (); });
586
587 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (dyingAwaiter));
588 QCOMPARE (GetTaskResult (aliveAwaiter), 42);
589 }
590
591 void CoroTaskTest::testEither ()
592 {
593 using Result_t = Either<QString, bool>;
594
595 auto immediatelyFailing = [] () -> Task<Result_t>
596 {
597 const auto theInt = co_await Either<QString, int> { "meh" };
598 co_return theInt > 420;
599 } ();
600 QCOMPARE (GetTaskResult (immediatelyFailing), Result_t { Left { "meh" } });
601
602 auto earlyFailing = [] () -> Task<Result_t>
603 {
604 const auto theInt = co_await Either<QString, int> { "meh" };
605 co_await 10ms;
606 co_return theInt > 420;
607 } ();
608 QCOMPARE (GetTaskResult (earlyFailing), Result_t { Left { "meh" } });
609
610 auto successful = [] () -> Task<Result_t>
611 {
612 const auto theInt = co_await Either<QString, int> { 42 };
613 co_await 10ms;
614 co_return theInt > 420;
615 } ();
616 QCOMPARE (GetTaskResult (successful), Result_t { false });
617 }
618
619 void CoroTaskTest::testEitherIgnoreLeft ()
620 {
621 auto immediatelyFailing = [] () -> Task<Either<QString, int>>
622 {
623 const auto theInt = co_await WithHandler (Either<QString, int> { "meh" }, IgnoreLeft {});
624 co_return theInt;
625 } ();
626 QCOMPARE (GetTaskResult (immediatelyFailing), (Either<QString, int> { 0 }));
627 }
628
629 void CoroTaskTest::testThrottleSameCoro ()
630 {
631 Throttle t { 10ms };
632 constexpr auto count = 10;
633
634 QElapsedTimer timer;
635 timer.start ();
636 auto task = [] (auto& t) -> Task<int>
637 {
638 int result = 0;
639 for (int i = 0; i < count; ++i)
640 {
641 co_await t;
642 result += i;
643 }
644 co_return result;
645 } (t);
646 const auto result = GetTaskResult (task);
647 const auto time = timer.elapsed ();
648
649 QCOMPARE (result, count * (count - 1) / 2);
650 QCOMPARE_GE (time, count * t.GetInterval ().count ());
651 }
652
653 void CoroTaskTest::testThrottleSameCoroSlow ()
654 {
655 Throttle t { 10ms };
656 constexpr auto count = 10;
657 constexpr static auto intraDelay = 9ms;
658
659 QElapsedTimer timer;
660 timer.start ();
661 auto task = [] (auto& t) -> Task<void>
662 {
663 for (int i = 0; i < count; ++i)
664 {
665 co_await t;
666 if (i != count - 1)
667 co_await Precisely { intraDelay };
668 }
669 } (t);
670 GetTaskResult (task);
671 const auto time = timer.elapsed ();
672
673 const auto expectedMinTime = count * t.GetInterval ().count ();
674 QCOMPARE_GE (time, expectedMinTime);
675
676 const auto delaysTime = (count - 1) * intraDelay.count ();
677 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
678 }
679
680 void CoroTaskTest::testThrottleSameCoroVerySlow ()
681 {
682 Throttle t { 10ms };
683 constexpr auto count = 10;
684 constexpr static auto intraDelay = 20ms;
685
686 QElapsedTimer timer;
687 timer.start ();
688 auto task = [] (auto& t) -> Task<void>
689 {
690 for (int i = 0; i < count; ++i)
691 {
692 co_await t;
693 if (i != count - 1)
694 co_await Precisely { intraDelay };
695 }
696 } (t);
697 GetTaskResult (task);
698 const auto time = timer.elapsed ();
699
700 const auto expectedMinTime = (count - 1) * intraDelay.count ();
701 QCOMPARE_GE (time, expectedMinTime);
702
703 const auto throttlesTime = count * t.GetInterval ().count ();
704 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
705 }
706
707 void CoroTaskTest::testThrottleManyCoros ()
708 {
709 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
710 constexpr auto count = 10;
711
712 QElapsedTimer timer;
713 timer.start ();
714 auto mkTask = [] (auto& t) -> Task<void>
715 {
716 for (int i = 0; i < count; ++i)
717 co_await t;
718 };
719 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
720 for (auto& task : tasks)
721 GetTaskResult (task);
722 const auto time = timer.elapsed ();
723
724 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
725 }
726
727 constexpr auto LongDelay = 500ms;
728 constexpr auto ShortDelay = 10ms;
729 constexpr auto DelayThreshold = std::chrono::duration_cast<std::chrono::milliseconds> ((ShortDelay + LongDelay) / 2);
730
731 void CoroTaskTest::testContextDestrBeforeFinish ()
732 {
733 auto context = std::make_unique<QObject> ();
734 auto task = [] (QObject *context) -> ContextTask<int>
735 {
736 co_await AddContextObject { *context };
737 co_await LongDelay;
738 co_return context->children ().size ();
739 } (&*context);
740 context.reset ();
741
742 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
743 }
744
745 void CoroTaskTest::testContextDestrAfterFinish ()
746 {
747 auto context = std::make_unique<QObject> ();
748 auto task = [] (QObject *context) -> ContextTask<int>
749 {
750 co_await AddContextObject { *context };
751 co_await ShortDelay;
752 co_return context->children ().size ();
753 } (&*context);
754
755 QCOMPARE (GetTaskResult (task), 0);
756 }
757
758 void CoroTaskTest::testContextDestrAwaitedSubtask ()
759 {
760 auto context = std::make_unique<QObject> ();
761 auto task = [] (QObject *context) -> ContextTask<int>
762 {
763 co_await AddContextObject { *context };
764
765 const auto nestedSubtask = [] -> ContextTask<int>
766 {
767 co_await ShortDelay;
768 co_return 42;
769 } ();
770
771 co_await nestedSubtask;
772 co_return context->children ().size ();
773 } (&*context);
774 context.reset ();
775
776 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
777 }
778
779 namespace
780 {
781 template<typename... Ts>
782 auto WithContext (auto&& taskGen, Ts&&... taskArgs)
783 {
784 auto context = std::make_unique<QObject> ();
785 auto task = taskGen (&*context, std::forward<Ts> (taskArgs)...);
786 QTimer::singleShot (ShortDelay, [context = std::move (context)] () mutable { context.reset (); });
787 return task;
788 }
789
790 void WithDestroyTimer (auto task)
791 {
792 QElapsedTimer timer;
793 timer.start ();
794 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
795 QCOMPARE_LT (timer.elapsed (), DelayThreshold.count ());
796 }
797 }
798
799 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
800 {
801 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<void>
802 {
803 co_await AddContextObject { *context };
804 co_await LongDelay;
805 }));
806 }
807
808 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
809 {
810 const QByteArray data { "this is some test data" };
811 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
812 QTimer::singleShot (LongDelay,
813 [nam]
814 {
815 if (const auto reply = nam->GetReply ())
816 {
817 reply->setFinished (true);
818 emit reply->finished ();
819 }
820 });
821
822 WithDestroyTimer (WithContext ([] (QObject *context, QNetworkAccessManager *nam) -> ContextTask<QByteArray>
823 {
824 co_await AddContextObject { *context };
825 const auto reply = co_await *nam->get (QNetworkRequest { QUrl { "http://example.com/foo.txt"_qs } });
826 co_return reply.GetReplyData ();
827 }, &*nam));
828 }
829
830 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
831 {
832 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<>
833 {
834 co_await AddContextObject { *context };
835
836 const auto process = new QProcess {};
837 const auto delay = std::chrono::duration_cast<std::chrono::milliseconds> (LongDelay);
838 process->start ("sleep", { QString::number (delay.count () / 1000.0) });
839 connect (process,
840 &QProcess::finished,
841 process,
842 &QObject::deleteLater);
843
844 co_await *process;
845 }));
846 }
847
848 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
849 {
850 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<>
851 {
852 co_await AddContextObject { *context };
853 co_await QtConcurrent::run ([] { QThread::sleep (LongDelay); });
854 }));
855 }
856
857#ifdef QT_DBUS_LIB
858 void CoroTaskTest::testDBus ()
859 {
860 const auto& bus = QDBusConnection::systemBus ();
861 if (!bus.isConnected ())
862 QSKIP ("D-Bus system bus is not available");
863
864 auto task = [] () -> Task<Either<QDBusError::ErrorType, bool>>
865 {
866 const auto& bus = QDBusConnection::systemBus ();
867 QDBusInterface dbusIface { "org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", bus };
868 const auto result = co_await Typed<bool> (dbusIface.asyncCall ("NameHasOwner", "org.freedesktop.DBus"_qs));
869 co_return result.MapLeft (&QDBusError::type);
870 } ();
871
872 QCOMPARE (GetTaskResult (task), true);
873 }
874#endif
875
876 void CoroTaskTest::cleanupTestCase ()
877 {
878 bool done = false;
879 QTimer::singleShot (LongDelay * 2, [&done] { done = true; });
880 QTRY_VERIFY (done);
881 }
882}
constexpr detail::AggregateType< detail::AggregateFunction::Count, Ptr > count
Definition oral.h:1100
constexpr detail::AggregateType< detail::AggregateFunction::Max, Ptr > max
Definition oral.h:1106
Task< R, ContextExtension > ContextTask
Definition taskfwd.h:20
detail::DBusAwaiter< Rets... > Typed(const QDBusPendingCall &asyncCall)
Definition dbus.h:95
constexpr auto LongDelay
Task< T, SharedTaskExtension > SharedTask
Definition sharedtask.h:51
Task< T, SharedTaskExtension, ContextExtension > SharedContextTask
Definition sharedtask.h:54
detail::EitherAwaiter< L, R, F > WithHandler(const Either< L, R > &either, F &&errorHandler)
Definition either.h:82
constexpr auto ShortDelay
WithPrecision< Qt::PreciseTimer > Precisely
Definition timer.h:43
Task< QVector< T >, Exts... > InParallel(Cont< Task< T, Exts... > > tasks)
Definition inparallel.h:21
T GetTaskResult(Task< T, Extensions... > task)
Definition getresult.h:19
constexpr auto DelayThreshold