10#include <QtConcurrentRun>
13#include <QDBusInterface>
29using namespace std::chrono_literals;
33 void CoroTaskTest::testReturn ()
35 auto task = [] () -> Task<int> {
co_return 42; } ();
37 QCOMPARE (result, 42);
40 void CoroTaskTest::testWait ()
45 auto task = [] () -> Task<int>
53 QCOMPARE (result, 42);
54 QCOMPARE_GT (timer.elapsed (), 50);
57 void CoroTaskTest::testTaskDestr ()
59 bool continued =
false;
61 [] (
auto& continued) -> Task<void>
67 QTRY_VERIFY_WITH_TIMEOUT (continued, 20);
73 class MockReply :
public QNetworkReply
77 using QNetworkReply::QNetworkReply;
79 using QNetworkReply::setAttribute;
80 using QNetworkReply::setError;
81 using QNetworkReply::setFinished;
82 using QNetworkReply::setHeader;
83 using QNetworkReply::setOperation;
84 using QNetworkReply::setRawHeader;
85 using QNetworkReply::setRequest;
86 using QNetworkReply::setUrl;
88 void SetData (
const QByteArray& data)
90 Buffer_.setData (data);
91 Buffer_.open (QIODevice::ReadOnly);
92 open (QIODevice::ReadOnly);
95 qint64 readData (
char *data, qint64 maxSize)
override
97 return Buffer_.read (data, maxSize);
100 void abort ()
override
105 class MockNAM :
public QNetworkAccessManager
107 QPointer<MockReply> Reply_;
109 explicit MockNAM (MockReply *reply)
114 MockReply* GetReply ()
119 QNetworkReply* createRequest (Operation op,
const QNetworkRequest& req, QIODevice*)
override
121 Reply_->setUrl (req.url ());
122 Reply_->setOperation (op);
123 Reply_->setRequest (req);
128 auto MkSuccessfulReply (
const QByteArray& data)
130 auto reply =
new MockReply;
131 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 200);
132 reply->SetData (data);
138 auto reply =
new MockReply;
139 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 404);
140 reply->setError (QNetworkReply::NetworkError::ContentAccessDenied,
"well, 404!"_qs);
144 void TestGoodReply (
auto finishMarker)
146 const QByteArray data {
"this is some test data" };
147 MockNAM nam { MkSuccessfulReply (data) };
148 finishMarker (*nam.GetReply ());
152 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
153 co_return reply.GetReplyData ();
157 QCOMPARE (result, data);
160 void TestBadReply (
auto finishMarker)
162 MockNAM nam { MkErrorReply () };
163 finishMarker (*nam.GetReply ());
167 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
168 co_return reply.GetReplyData ();
171 QVERIFY_THROWS_EXCEPTION (LC::Util::NetworkReplyErrorException,
GetTaskResult (task));
174 void ImmediateFinishMarker (MockReply& reply)
176 reply.setFinished (
true);
179 void DelayedFinishMarker (MockReply& reply)
181 QTimer::singleShot (10ms,
184 reply.setFinished (
true);
185 emit reply.finished ();
190 void CoroTaskTest::testNetworkReplyGoodNoWait ()
192 TestGoodReply (&ImmediateFinishMarker);
195 void CoroTaskTest::testNetworkReplyGoodWait ()
197 TestGoodReply (&DelayedFinishMarker);
200 void CoroTaskTest::testNetworkReplyBadNoWait ()
202 TestBadReply (&ImmediateFinishMarker);
205 void CoroTaskTest::testNetworkReplyBadWait ()
207 TestBadReply (&DelayedFinishMarker);
210 void CoroTaskTest::testFutureAwaiter ()
212 auto delayed = [] () -> Task<int>
214 co_return co_await QtConcurrent::run ([]
222 auto immediate = [] () -> Task<int>
224 co_return co_await QtConcurrent::run ([] {
return 42; });
228 auto ready = [] () -> Task<int>
230 co_return co_await MakeReadyFuture (42);
235 void CoroTaskTest::testWaitMany ()
237 constexpr auto max = 100;
238 auto mkTask = [] (
int index) -> Task<int>
240 co_await Precisely { std::chrono::milliseconds {
max - index } };
246 QVector<Task<int>> tasks;
247 QVector<int> expected;
248 for (
int i = 0; i <
max; ++i)
253 const auto creationElapsed = timer.elapsed ();
257 const auto executionElapsed = timer.elapsed ();
259 QCOMPARE (result, expected);
260 QCOMPARE_LT (creationElapsed, 1);
262 constexpr auto tolerance = 0.05;
263 QCOMPARE_GE (executionElapsed, max * (1 - tolerance));
264 const auto linearizedExecTime =
max * (
max + 1) / 2;
265 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
268 void CoroTaskTest::testWaitManyTuple ()
270 auto mkTask = [] (
int delay) -> Task<int>
272 co_await Precisely { std::chrono::milliseconds { delay } };
279 const auto executionElapsed = timer.elapsed ();
281 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
283 QCOMPARE_GE (executionElapsed, 10);
284 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
287 void CoroTaskTest::testWaitManyInvoking ()
289 constexpr auto max = 100;
290 auto mkTask = [] (
int index) -> Task<int>
292 co_await Precisely { std::chrono::milliseconds {
max - index } };
299 QVector<int> expected;
300 for (
int i = 0; i <
max; ++i)
305 const auto creationElapsed = timer.elapsed ();
309 const auto executionElapsed = timer.elapsed ();
311 QCOMPARE (result, expected);
312 QCOMPARE_LT (creationElapsed, 1);
314 constexpr auto tolerance = 0.05;
315 QCOMPARE_GE (executionElapsed, max * (1 - tolerance));
316 const auto linearizedExecTime =
max * (
max + 1) / 2;
317 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
320 void CoroTaskTest::testEither ()
322 using Result_t = Either<QString, bool>;
324 auto immediatelyFailing = [] () -> Task<Result_t>
326 const auto theInt =
co_await Either<QString, int> {
"meh" };
327 co_return theInt > 420;
329 QCOMPARE (
GetTaskResult (immediatelyFailing), Result_t { Left {
"meh" } });
331 auto earlyFailing = [] () -> Task<Result_t>
333 const auto theInt =
co_await Either<QString, int> {
"meh" };
335 co_return theInt > 420;
337 QCOMPARE (
GetTaskResult (earlyFailing), Result_t { Left {
"meh" } });
339 auto successful = [] () -> Task<Result_t>
341 const auto theInt =
co_await Either<QString, int> { 42 };
343 co_return theInt > 420;
348 void CoroTaskTest::testEitherIgnoreLeft ()
350 auto immediatelyFailing = [] () -> Task<Either<QString, int>>
352 const auto theInt =
co_await WithHandler (Either<QString, int> {
"meh" }, IgnoreLeft {});
355 QCOMPARE (
GetTaskResult (immediatelyFailing), (Either<QString, int> { 0 }));
358 void CoroTaskTest::testThrottleSameCoro ()
361 constexpr auto count = 10;
365 auto task = [] (
auto& t) -> Task<int>
368 for (
int i = 0; i <
count; ++i)
376 const auto time = timer.elapsed ();
378 QCOMPARE (result, count * (count - 1) / 2);
379 QCOMPARE_GE (time, count * t.GetInterval ().count ());
382 void CoroTaskTest::testThrottleSameCoroSlow ()
385 constexpr auto count = 10;
386 constexpr static auto intraDelay = 9ms;
390 auto task = [] (
auto& t) -> Task<void>
392 for (
int i = 0; i <
count; ++i)
400 const auto time = timer.elapsed ();
402 const auto expectedMinTime =
count * t.GetInterval ().count ();
403 QCOMPARE_GE (time, expectedMinTime);
405 const auto delaysTime = (
count - 1) * intraDelay.count ();
406 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
409 void CoroTaskTest::testThrottleSameCoroVerySlow ()
412 constexpr auto count = 10;
413 constexpr static auto intraDelay = 20ms;
417 auto task = [] (
auto& t) -> Task<void>
419 for (
int i = 0; i <
count; ++i)
427 const auto time = timer.elapsed ();
429 const auto expectedMinTime = (
count - 1) * intraDelay.count ();
430 QCOMPARE_GE (time, expectedMinTime);
432 const auto throttlesTime =
count * t.GetInterval ().count ();
433 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
436 void CoroTaskTest::testThrottleManyCoros ()
438 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
439 constexpr auto count = 10;
443 auto mkTask = [] (
auto& t) -> Task<void>
445 for (
int i = 0; i <
count; ++i)
448 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
449 for (
auto& task : tasks)
451 const auto time = timer.elapsed ();
453 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
460 void CoroTaskTest::testContextDestrBeforeFinish ()
462 auto context = std::make_unique<QObject> ();
467 co_return context->children ().size ();
474 void CoroTaskTest::testContextDestrAfterFinish ()
476 auto context = std::make_unique<QObject> ();
479 co_await AddContextObject { *context };
481 co_return context->children ().size ();
487 void CoroTaskTest::testContextDestrAwaitedSubtask ()
489 auto context = std::make_unique<QObject> ();
492 co_await AddContextObject { *context };
500 co_await nestedSubtask;
501 co_return context->children ().size ();
505 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
510 template<
typename... Ts>
511 auto WithContext (
auto&& taskGen, Ts&&... taskArgs)
513 auto context = std::make_unique<QObject> ();
514 auto task = taskGen (&*context, std::forward<Ts> (taskArgs)...);
515 QTimer::singleShot (
ShortDelay, [context = std::move (context)] ()
mutable { context.reset (); });
519 void WithDestroyTimer (
auto task)
523 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
528 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
532 co_await AddContextObject { *context };
537 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
539 const QByteArray data {
"this is some test data" };
540 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
544 if (
const auto reply = nam->GetReply ())
546 reply->setFinished (
true);
547 emit reply->finished ();
553 co_await AddContextObject { *context };
554 const auto reply =
co_await *nam->get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
555 co_return reply.GetReplyData ();
559 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
561 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
563 co_await AddContextObject { *context };
565 const auto process =
new QProcess {};
566 const auto delay = std::chrono::duration_cast<std::chrono::milliseconds> (
LongDelay);
567 process->start (
"sleep", { QString::number (delay.count () / 1000.0) });
571 &QObject::deleteLater);
577 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
579 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
581 co_await AddContextObject { *context };
582 co_await QtConcurrent::run ([] { QThread::sleep (
LongDelay); });
587 void CoroTaskTest::testDBus ()
589 const auto& bus = QDBusConnection::systemBus ();
590 if (!bus.isConnected ())
591 QSKIP (
"D-Bus system bus is not available");
593 auto task = [] () -> Task<Either<QDBusError::ErrorType, bool>>
595 const auto& bus = QDBusConnection::systemBus ();
596 QDBusInterface dbusIface {
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus", bus };
597 const auto result =
co_await Typed<bool> (dbusIface.asyncCall (
"NameHasOwner",
"org.freedesktop.DBus"_qs));
598 co_return result.MapLeft (&QDBusError::type);
605 void CoroTaskTest::cleanupTestCase ()
608 QTimer::singleShot (
LongDelay * 2, [&done] { done =
true; });
constexpr detail::AggregateType< detail::AggregateFunction::Count, Ptr > count
constexpr detail::AggregateType< detail::AggregateFunction::Max, Ptr > max
detail::DBusAwaiter< Rets... > Typed(const QDBusPendingCall &asyncCall)
Task< R, ContextExtensions > ContextTask
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