10#include <QtConcurrentRun>
22using namespace std::chrono_literals;
26 void CoroTaskTest::testReturn ()
28 auto task = [] () -> Task<int> {
co_return 42; } ();
30 QCOMPARE (result, 42);
33 void CoroTaskTest::testWait ()
38 auto task = [] () -> Task<int>
46 QCOMPARE (result, 42);
47 QCOMPARE_GT (timer.elapsed (), 50);
50 void CoroTaskTest::testTaskDestr ()
52 bool continued =
false;
54 [] (
auto& continued) -> Task<void>
60 QTRY_VERIFY_WITH_TIMEOUT (continued, 20);
66 class MockReply :
public QNetworkReply
70 using QNetworkReply::QNetworkReply;
72 using QNetworkReply::setAttribute;
73 using QNetworkReply::setError;
74 using QNetworkReply::setFinished;
75 using QNetworkReply::setHeader;
76 using QNetworkReply::setOperation;
77 using QNetworkReply::setRawHeader;
78 using QNetworkReply::setRequest;
79 using QNetworkReply::setUrl;
81 void SetData (
const QByteArray& data)
83 Buffer_.setData (data);
84 Buffer_.open (QIODevice::ReadOnly);
85 open (QIODevice::ReadOnly);
88 qint64 readData (
char *data, qint64 maxSize)
override
90 return Buffer_.read (data, maxSize);
93 void abort ()
override
98 class MockNAM :
public QNetworkAccessManager
100 QPointer<MockReply> Reply_;
102 explicit MockNAM (MockReply *reply)
107 MockReply* GetReply ()
112 QNetworkReply* createRequest (Operation op,
const QNetworkRequest& req, QIODevice*)
override
114 Reply_->setUrl (req.url ());
115 Reply_->setOperation (op);
116 Reply_->setRequest (req);
121 auto MkSuccessfulReply (
const QByteArray& data)
123 auto reply =
new MockReply;
124 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 200);
125 reply->SetData (data);
131 auto reply =
new MockReply;
132 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 404);
133 reply->setError (QNetworkReply::NetworkError::ContentAccessDenied,
"well, 404!"_qs);
137 void TestGoodReply (
auto finishMarker)
139 const QByteArray data {
"this is some test data" };
140 MockNAM nam { MkSuccessfulReply (data) };
141 finishMarker (*nam.GetReply ());
145 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
146 co_return reply.GetReplyData ();
150 QCOMPARE (result, data);
153 void TestBadReply (
auto finishMarker)
155 MockNAM nam { MkErrorReply () };
156 finishMarker (*nam.GetReply ());
160 auto reply =
co_await *nam.get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
161 co_return reply.GetReplyData ();
164 QVERIFY_THROWS_EXCEPTION (LC::Util::NetworkReplyErrorException,
GetTaskResult (task));
167 void ImmediateFinishMarker (MockReply& reply)
169 reply.setFinished (
true);
172 void DelayedFinishMarker (MockReply& reply)
174 QTimer::singleShot (10ms,
177 reply.setFinished (
true);
178 emit reply.finished ();
183 void CoroTaskTest::testNetworkReplyGoodNoWait ()
185 TestGoodReply (&ImmediateFinishMarker);
188 void CoroTaskTest::testNetworkReplyGoodWait ()
190 TestGoodReply (&DelayedFinishMarker);
193 void CoroTaskTest::testNetworkReplyBadNoWait ()
195 TestBadReply (&ImmediateFinishMarker);
198 void CoroTaskTest::testNetworkReplyBadWait ()
200 TestBadReply (&DelayedFinishMarker);
203 void CoroTaskTest::testFutureAwaiter ()
205 auto delayed = [] () -> Task<int>
207 co_return co_await QtConcurrent::run ([]
215 auto immediate = [] () -> Task<int>
217 co_return co_await QtConcurrent::run ([] {
return 42; });
221 auto ready = [] () -> Task<int>
223 co_return co_await MakeReadyFuture (42);
228 void CoroTaskTest::testWaitMany ()
230 constexpr auto max = 100;
231 auto mkTask = [] (
int index) -> Task<int>
233 co_await Precisely { std::chrono::milliseconds {
max - index } };
239 QVector<Task<int>> tasks;
240 QVector<int> expected;
241 for (
int i = 0; i <
max; ++i)
246 const auto creationElapsed = timer.elapsed ();
250 const auto executionElapsed = timer.elapsed ();
252 QCOMPARE (result, expected);
253 QCOMPARE_LT (creationElapsed, 1);
255 QCOMPARE_GE (executionElapsed, max);
256 const auto linearizedExecTime =
max * (
max + 1) / 2;
257 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
260 void CoroTaskTest::testWaitManyTuple ()
262 auto mkTask = [] (
int delay) -> Task<int>
264 co_await Precisely { std::chrono::milliseconds { delay } };
271 const auto executionElapsed = timer.elapsed ();
273 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
275 QCOMPARE_GE (executionElapsed, 10);
276 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
279 void CoroTaskTest::testEither ()
281 using Result_t = Either<QString, bool>;
283 auto immediatelyFailing = [] () -> Task<Result_t>
285 const auto theInt =
co_await Either<QString, int> {
"meh" };
286 co_return { theInt > 420 };
288 QCOMPARE (
GetTaskResult (immediatelyFailing), Result_t { Left {
"meh" } });
290 auto earlyFailing = [] () -> Task<Result_t>
292 const auto theInt =
co_await Either<QString, int> {
"meh" };
294 co_return { theInt > 420 };
296 QCOMPARE (
GetTaskResult (earlyFailing), Result_t { Left {
"meh" } });
298 auto successful = [] () -> Task<Result_t>
300 const auto theInt =
co_await Either<QString, int> { 42 };
302 co_return { theInt > 420 };
307 void CoroTaskTest::testThrottleSameCoro ()
310 constexpr auto count = 10;
314 auto task = [] (
auto& t) -> Task<int>
317 for (
int i = 0; i <
count; ++i)
325 const auto time = timer.elapsed ();
327 QCOMPARE (result, count * (count - 1) / 2);
328 QCOMPARE_GE (time, count * t.GetInterval ().count ());
331 void CoroTaskTest::testThrottleSameCoroSlow ()
334 constexpr auto count = 10;
335 constexpr static auto intraDelay = 9ms;
339 auto task = [] (
auto& t) -> Task<void>
341 for (
int i = 0; i <
count; ++i)
349 const auto time = timer.elapsed ();
351 const auto expectedMinTime =
count * t.GetInterval ().count ();
352 QCOMPARE_GE (time, expectedMinTime);
354 const auto delaysTime = (
count - 1) * intraDelay.count ();
355 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
358 void CoroTaskTest::testThrottleSameCoroVerySlow ()
361 constexpr auto count = 10;
362 constexpr static auto intraDelay = 20ms;
366 auto task = [] (
auto& t) -> Task<void>
368 for (
int i = 0; i <
count; ++i)
376 const auto time = timer.elapsed ();
378 const auto expectedMinTime = (
count - 1) * intraDelay.count ();
379 QCOMPARE_GE (time, expectedMinTime);
381 const auto throttlesTime =
count * t.GetInterval ().count ();
382 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
385 void CoroTaskTest::testThrottleManyCoros ()
387 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
388 constexpr auto count = 10;
392 auto mkTask = [] (
auto& t) -> Task<void>
394 for (
int i = 0; i <
count; ++i)
397 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
398 for (
auto& task : tasks)
400 const auto time = timer.elapsed ();
402 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
409 void CoroTaskTest::testContextDestrBeforeFinish ()
411 auto context = std::make_unique<QObject> ();
416 co_return context->children ().size ();
423 void CoroTaskTest::testContextDestrAfterFinish ()
425 auto context = std::make_unique<QObject> ();
428 co_await AddContextObject { *context };
430 co_return context->children ().size ();
438 template<
typename... Ts>
439 auto WithContext (
auto&& taskGen, Ts&&... taskArgs)
441 auto context = std::make_unique<QObject> ();
442 auto task = taskGen (&*context, std::forward<Ts> (taskArgs)...);
443 QTimer::singleShot (
ShortDelay, [context = std::move (context)] ()
mutable { context.reset (); });
447 void WithDestroyTimer (
auto task)
451 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
456 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
460 co_await AddContextObject { *context };
465 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
467 const QByteArray data {
"this is some test data" };
468 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
472 if (
const auto reply = nam->GetReply ())
474 reply->setFinished (
true);
475 emit reply->finished ();
481 co_await AddContextObject { *context };
482 const auto reply =
co_await *nam->get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
483 co_return reply.GetReplyData ();
487 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
489 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
491 co_await AddContextObject { *context };
493 const auto process =
new QProcess {};
494 const auto delay = std::chrono::duration_cast<std::chrono::milliseconds> (
LongDelay);
495 process->start (
"sleep", { QString::number (delay.count () / 1000.0) });
499 &QObject::deleteLater);
505 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
507 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
509 co_await AddContextObject { *context };
510 co_await QtConcurrent::run ([] { QThread::sleep (
LongDelay); });
514 void CoroTaskTest::cleanupTestCase ()
517 QTimer::singleShot (
LongDelay * 2, [&done] { done =
true; });
constexpr detail::AggregateType< detail::AggregateFunction::Count, Ptr > count
constexpr detail::AggregateType< detail::AggregateFunction::Max, Ptr > max
Task< QVector< T >, Exts... > InParallel(QVector< Task< T, Exts... > > tasks)
Task< R, ContextExtensions > ContextTask
constexpr auto ShortDelay
WithPrecision< Qt::PreciseTimer > Precisely
T GetTaskResult(Task< T, Extensions... > task)
constexpr auto DelayThreshold