10#include <QtConcurrentRun>
13#include <QDBusInterface>
30using namespace std::chrono_literals;
34 void CoroTaskTest::testReturn ()
36 auto task = [] () -> Task<int> {
co_return 42; } ();
38 QCOMPARE (result, 42);
41 void CoroTaskTest::testMoveOnlyReturn ()
43 auto direct = [] () -> Task<std::unique_ptr<int>>
45 co_return std::make_unique<int> (42);
48 QVERIFY (directResult);
49 QCOMPARE (*directResult, 42);
51 auto chained = [] () -> Task<std::unique_ptr<int>>
53 auto inner = [] () -> Task<std::unique_ptr<int>>
56 co_return std::make_unique<int> (7);
58 auto value =
co_await inner;
60 co_return std::move (value);
63 QVERIFY (chainedResult);
64 QCOMPARE (*chainedResult, 42);
67 void CoroTaskTest::testWait ()
72 auto task = [] () -> Task<int>
80 QCOMPARE (result, 42);
81 QCOMPARE_GT (timer.elapsed (), 50);
84 void CoroTaskTest::testTaskDestr ()
86 bool continued =
false;
88 [] (
auto& continued) -> Task<void>
94 QTRY_VERIFY_WITH_TIMEOUT (continued, 20);
100 class MockReply :
public QNetworkReply
104 using QNetworkReply::QNetworkReply;
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;
115 void SetData (
const QByteArray& data)
117 Buffer_.setData (data);
118 Buffer_.open (QIODevice::ReadOnly);
119 open (QIODevice::ReadOnly);
122 qint64 readData (
char *data, qint64 maxSize)
override
124 return Buffer_.read (data, maxSize);
127 void abort ()
override
132 class MockNAM :
public QNetworkAccessManager
134 QPointer<MockReply> Reply_;
136 explicit MockNAM (MockReply *reply)
141 MockReply* GetReply ()
146 QNetworkReply* createRequest (Operation op,
const QNetworkRequest& req, QIODevice*)
override
148 Reply_->setUrl (req.url ());
149 Reply_->setOperation (op);
150 Reply_->setRequest (req);
155 auto MkSuccessfulReply (
const QByteArray& data)
157 auto reply =
new MockReply;
158 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 200);
159 reply->SetData (data);
165 auto reply =
new MockReply;
166 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 404);
167 reply->setError (QNetworkReply::NetworkError::ContentAccessDenied,
"well, 404!"_qs);
171 void TestGoodReply (
auto finishMarker)
173 const QByteArray data {
"this is some test data" };
174 MockNAM nam { MkSuccessfulReply (data) };
175 finishMarker (*nam.GetReply ());
179 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
180 co_return reply.GetReplyData ();
184 QCOMPARE (result, data);
187 void TestBadReply (
auto finishMarker)
189 MockNAM nam { MkErrorReply () };
190 finishMarker (*nam.GetReply ());
194 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
195 co_return reply.GetReplyData ();
198 QVERIFY_THROWS_EXCEPTION (LC::Util::NetworkReplyErrorException,
GetTaskResult (task));
201 void ImmediateFinishMarker (MockReply& reply)
203 reply.setFinished (
true);
206 void DelayedFinishMarker (MockReply& reply)
208 QTimer::singleShot (10ms,
211 reply.setFinished (
true);
212 emit reply.finished ();
217 void CoroTaskTest::testNetworkReplyGoodNoWait ()
219 TestGoodReply (&ImmediateFinishMarker);
222 void CoroTaskTest::testNetworkReplyGoodWait ()
224 TestGoodReply (&DelayedFinishMarker);
227 void CoroTaskTest::testNetworkReplyBadNoWait ()
229 TestBadReply (&ImmediateFinishMarker);
232 void CoroTaskTest::testNetworkReplyBadWait ()
234 TestBadReply (&DelayedFinishMarker);
237 void CoroTaskTest::testFutureAwaiter ()
239 auto delayed = [] () -> Task<int>
241 co_return co_await QtConcurrent::run ([]
249 auto immediate = [] () -> Task<int>
251 co_return co_await QtConcurrent::run ([] {
return 42; });
255 auto ready = [] () -> Task<int>
257 co_return co_await MakeReadyFuture (42);
262 void CoroTaskTest::testWaitMany ()
264 constexpr auto max = 100;
265 auto mkTask = [] (
int index) -> Task<int>
267 co_await Precisely { std::chrono::milliseconds {
max - index } };
273 QVector<Task<int>> tasks;
274 QVector<int> expected;
275 for (
int i = 0; i <
max; ++i)
280 const auto creationElapsed = timer.elapsed ();
284 const auto executionElapsed = timer.elapsed ();
286 QCOMPARE (result, expected);
287 QCOMPARE_LT (creationElapsed, 1);
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);
295 void CoroTaskTest::testWaitManyTuple ()
297 auto mkTask = [] (
int delay) -> Task<int>
299 co_await Precisely { std::chrono::milliseconds { delay } };
306 const auto executionElapsed = timer.elapsed ();
308 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
310 QCOMPARE_GE (executionElapsed, 10);
311 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
314 void CoroTaskTest::testWaitManyInvoking ()
316 constexpr auto max = 100;
317 auto mkTask = [] (
int index) -> Task<int>
319 co_await Precisely { std::chrono::milliseconds {
max - index } };
326 QVector<int> expected;
327 for (
int i = 0; i <
max; ++i)
332 const auto creationElapsed = timer.elapsed ();
336 const auto executionElapsed = timer.elapsed ();
338 QCOMPARE (result, expected);
339 QCOMPARE_LT (creationElapsed, 1);
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);
347 void CoroTaskTest::testSharedTaskManyAwaiters ()
355 auto mkAwaiter = [&shared] (
int offset) -> Task<int>
357 co_return co_await shared + offset;
361 QCOMPARE (result, (std::tuple { 42, 43 }));
364 void CoroTaskTest::testSharedTaskAwaiterRemovedOnOuterDestruction ()
366 constexpr auto destructionDelay = 10ms;
373 auto survivingAwaiter = [&shared] () -> Task<int>
375 co_return co_await shared;
378 auto context = std::make_unique<QObject> ();
381 co_await AddContextObject { *context };
382 co_return co_await shared;
384 QTimer::singleShot (destructionDelay, [context = std::move (context)] ()
mutable { context.reset (); });
387 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (cancelledAwaiter));
390 void CoroTaskTest::testSharedTaskExceptionManyAwaiters ()
392 struct TestSharedTaskException : std::exception {};
397 throw TestSharedTaskException {};
400 auto mkAwaiter = [&shared] () -> Task<bool>
407 catch (
const TestSharedTaskException&)
414 QCOMPARE (result, (std::tuple {
true,
true }));
417 void CoroTaskTest::testSharedTaskLastCopyDestroyedByAwaiter ()
425 auto destructiveAwaiter = [] (std::optional<SharedTask<int>> *shared) -> Task<int>
427 auto result =
co_await **shared;
432 auto normalAwaiter = [] (std::optional<SharedTask<int>> *shared) -> Task<int>
434 co_return co_await **shared;
438 QCOMPARE (result, (std::tuple { 42, 42 }));
441 void CoroTaskTest::testSharedTaskNonTriviallyMovableReturn ()
444 const auto payload =
"0123456789abcdef"_qs.repeated (16);
452 auto mkAwaiter = [&shared] () -> Task<QString>
454 co_return co_await shared;
458 QCOMPARE (result, (std::tuple { payload, payload }));
461 void CoroTaskTest::testSharedContextTaskManyAwaitersContextAlive ()
463 auto context = std::make_unique<QObject> ();
466 co_await AddContextObject { *ctx };
471 auto mkAwaiter = [&shared] (
int offset) -> Task<int>
473 co_return co_await shared + offset;
477 QCOMPARE (result, (std::tuple { 42, 43 }));
480 void CoroTaskTest::testSharedContextTaskContextDeadFansOutToAll ()
482 auto context = std::make_unique<QObject> ();
485 co_await AddContextObject { *ctx };
490 auto mkAwaiter = [&shared] () -> Task<bool>
497 catch (
const ContextDeadException&)
503 QTimer::singleShot (10ms, [context = std::move (context)] ()
mutable { context.reset (); });
506 QCOMPARE (result, (std::tuple {
true,
true }));
509 void CoroTaskTest::testSharedContextTaskBodyExceptionFansOut ()
511 struct TestSharedContextException : std::exception {};
513 auto context = std::make_unique<QObject> ();
516 co_await AddContextObject { *ctx };
518 throw TestSharedContextException {};
521 auto mkAwaiter = [&shared] () -> Task<bool>
528 catch (
const TestSharedContextException&)
535 QCOMPARE (result, (std::tuple {
true,
true }));
538 void CoroTaskTest::testSharedContextTaskContextDeadDoesntWaitLong ()
540 auto context = std::make_unique<QObject> ();
543 co_await AddContextObject { *ctx };
547 auto awaiter = [&shared] () -> Task<void>
552 QTimer::singleShot (10ms, [context = std::move (context)] ()
mutable { context.reset (); });
556 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (awaiter));
557 QCOMPARE_LT (timer.elapsed (), 255);
560 void CoroTaskTest::testSharedContextTaskMixedAwaitersOwnContextDies ()
562 auto sharedCtx = std::make_unique<QObject> ();
563 auto aliveCtx = std::make_unique<QObject> ();
564 auto dyingCtx = std::make_unique<QObject> ();
568 co_await AddContextObject { *ctx };
575 co_await AddContextObject { *ctx };
576 co_return co_await shared;
581 co_await AddContextObject { *ctx };
582 co_return co_await shared;
585 QTimer::singleShot (10ms, [dyingCtx = std::move (dyingCtx)] ()
mutable { dyingCtx.reset (); });
587 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (dyingAwaiter));
591 void CoroTaskTest::testEither ()
593 using Result_t = Either<QString, bool>;
595 auto immediatelyFailing = [] () -> Task<Result_t>
597 const auto theInt =
co_await Either<QString, int> {
"meh" };
598 co_return theInt > 420;
600 QCOMPARE (
GetTaskResult (immediatelyFailing), Result_t { Left {
"meh" } });
602 auto earlyFailing = [] () -> Task<Result_t>
604 const auto theInt =
co_await Either<QString, int> {
"meh" };
606 co_return theInt > 420;
608 QCOMPARE (
GetTaskResult (earlyFailing), Result_t { Left {
"meh" } });
610 auto successful = [] () -> Task<Result_t>
612 const auto theInt =
co_await Either<QString, int> { 42 };
614 co_return theInt > 420;
619 void CoroTaskTest::testEitherIgnoreLeft ()
621 auto immediatelyFailing = [] () -> Task<Either<QString, int>>
623 const auto theInt =
co_await WithHandler (Either<QString, int> {
"meh" }, IgnoreLeft {});
626 QCOMPARE (
GetTaskResult (immediatelyFailing), (Either<QString, int> { 0 }));
629 void CoroTaskTest::testThrottleSameCoro ()
632 constexpr auto count = 10;
636 auto task = [] (
auto& t) -> Task<int>
639 for (
int i = 0; i <
count; ++i)
647 const auto time = timer.elapsed ();
649 QCOMPARE (result, count * (count - 1) / 2);
650 QCOMPARE_GE (time, count * t.GetInterval ().count ());
653 void CoroTaskTest::testThrottleSameCoroSlow ()
656 constexpr auto count = 10;
657 constexpr static auto intraDelay = 9ms;
661 auto task = [] (
auto& t) -> Task<void>
663 for (
int i = 0; i <
count; ++i)
671 const auto time = timer.elapsed ();
673 const auto expectedMinTime =
count * t.GetInterval ().count ();
674 QCOMPARE_GE (time, expectedMinTime);
676 const auto delaysTime = (
count - 1) * intraDelay.count ();
677 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
680 void CoroTaskTest::testThrottleSameCoroVerySlow ()
683 constexpr auto count = 10;
684 constexpr static auto intraDelay = 20ms;
688 auto task = [] (
auto& t) -> Task<void>
690 for (
int i = 0; i <
count; ++i)
698 const auto time = timer.elapsed ();
700 const auto expectedMinTime = (
count - 1) * intraDelay.count ();
701 QCOMPARE_GE (time, expectedMinTime);
703 const auto throttlesTime =
count * t.GetInterval ().count ();
704 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
707 void CoroTaskTest::testThrottleManyCoros ()
709 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
710 constexpr auto count = 10;
714 auto mkTask = [] (
auto& t) -> Task<void>
716 for (
int i = 0; i <
count; ++i)
719 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
720 for (
auto& task : tasks)
722 const auto time = timer.elapsed ();
724 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
731 void CoroTaskTest::testContextDestrBeforeFinish ()
733 auto context = std::make_unique<QObject> ();
738 co_return context->children ().size ();
745 void CoroTaskTest::testContextDestrAfterFinish ()
747 auto context = std::make_unique<QObject> ();
750 co_await AddContextObject { *context };
752 co_return context->children ().size ();
758 void CoroTaskTest::testContextDestrAwaitedSubtask ()
760 auto context = std::make_unique<QObject> ();
763 co_await AddContextObject { *context };
771 co_await nestedSubtask;
772 co_return context->children ().size ();
776 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
781 template<
typename... Ts>
782 auto WithContext (
auto&& taskGen, Ts&&... taskArgs)
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 (); });
790 void WithDestroyTimer (
auto task)
794 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
799 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
803 co_await AddContextObject { *context };
808 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
810 const QByteArray data {
"this is some test data" };
811 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
815 if (
const auto reply = nam->GetReply ())
817 reply->setFinished (
true);
818 emit reply->finished ();
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 ();
830 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
832 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
834 co_await AddContextObject { *context };
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) });
842 &QObject::deleteLater);
848 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
850 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
852 co_await AddContextObject { *context };
853 co_await QtConcurrent::run ([] { QThread::sleep (
LongDelay); });
858 void CoroTaskTest::testDBus ()
860 const auto& bus = QDBusConnection::systemBus ();
861 if (!bus.isConnected ())
862 QSKIP (
"D-Bus system bus is not available");
864 auto task = [] () -> Task<Either<QDBusError::ErrorType, bool>>
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);
876 void CoroTaskTest::cleanupTestCase ()
879 QTimer::singleShot (
LongDelay * 2, [&done] { done =
true; });
constexpr detail::AggregateType< detail::AggregateFunction::Count, Ptr > count
constexpr detail::AggregateType< detail::AggregateFunction::Max, Ptr > max
Task< R, ContextExtension > ContextTask
detail::DBusAwaiter< Rets... > Typed(const QDBusPendingCall &asyncCall)
Task< T, SharedTaskExtension > SharedTask
Task< T, SharedTaskExtension, ContextExtension > SharedContextTask
detail::EitherAwaiter< L, R, F > WithHandler(const Either< L, R > &either, F &&errorHandler)
constexpr auto ShortDelay
WithPrecision< Qt::PreciseTimer > Precisely
Task< QVector< T >, Exts... > InParallel(Cont< Task< T, Exts... > > tasks)
T GetTaskResult(Task< T, Extensions... > task)
constexpr auto DelayThreshold