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 constexpr auto tolerance = 0.05;
256 QCOMPARE_GE (executionElapsed, max * (1 - tolerance));
257 const auto linearizedExecTime =
max * (
max + 1) / 2;
258 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
261 void CoroTaskTest::testWaitManyTuple ()
263 auto mkTask = [] (
int delay) -> Task<int>
265 co_await Precisely { std::chrono::milliseconds { delay } };
272 const auto executionElapsed = timer.elapsed ();
274 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
276 QCOMPARE_GE (executionElapsed, 10);
277 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
280 void CoroTaskTest::testEither ()
282 using Result_t = Either<QString, bool>;
284 auto immediatelyFailing = [] () -> Task<Result_t>
286 const auto theInt =
co_await Either<QString, int> {
"meh" };
287 co_return { theInt > 420 };
289 QCOMPARE (
GetTaskResult (immediatelyFailing), Result_t { Left {
"meh" } });
291 auto earlyFailing = [] () -> Task<Result_t>
293 const auto theInt =
co_await Either<QString, int> {
"meh" };
295 co_return { theInt > 420 };
297 QCOMPARE (
GetTaskResult (earlyFailing), Result_t { Left {
"meh" } });
299 auto successful = [] () -> Task<Result_t>
301 const auto theInt =
co_await Either<QString, int> { 42 };
303 co_return { theInt > 420 };
308 void CoroTaskTest::testThrottleSameCoro ()
311 constexpr auto count = 10;
315 auto task = [] (
auto& t) -> Task<int>
318 for (
int i = 0; i <
count; ++i)
326 const auto time = timer.elapsed ();
328 QCOMPARE (result, count * (count - 1) / 2);
329 QCOMPARE_GE (time, count * t.GetInterval ().count ());
332 void CoroTaskTest::testThrottleSameCoroSlow ()
335 constexpr auto count = 10;
336 constexpr static auto intraDelay = 9ms;
340 auto task = [] (
auto& t) -> Task<void>
342 for (
int i = 0; i <
count; ++i)
350 const auto time = timer.elapsed ();
352 const auto expectedMinTime =
count * t.GetInterval ().count ();
353 QCOMPARE_GE (time, expectedMinTime);
355 const auto delaysTime = (
count - 1) * intraDelay.count ();
356 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
359 void CoroTaskTest::testThrottleSameCoroVerySlow ()
362 constexpr auto count = 10;
363 constexpr static auto intraDelay = 20ms;
367 auto task = [] (
auto& t) -> Task<void>
369 for (
int i = 0; i <
count; ++i)
377 const auto time = timer.elapsed ();
379 const auto expectedMinTime = (
count - 1) * intraDelay.count ();
380 QCOMPARE_GE (time, expectedMinTime);
382 const auto throttlesTime =
count * t.GetInterval ().count ();
383 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
386 void CoroTaskTest::testThrottleManyCoros ()
388 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
389 constexpr auto count = 10;
393 auto mkTask = [] (
auto& t) -> Task<void>
395 for (
int i = 0; i <
count; ++i)
398 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
399 for (
auto& task : tasks)
401 const auto time = timer.elapsed ();
403 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
410 void CoroTaskTest::testContextDestrBeforeFinish ()
412 auto context = std::make_unique<QObject> ();
417 co_return context->children ().size ();
424 void CoroTaskTest::testContextDestrAfterFinish ()
426 auto context = std::make_unique<QObject> ();
429 co_await AddContextObject { *context };
431 co_return context->children ().size ();
439 template<
typename... Ts>
440 auto WithContext (
auto&& taskGen, Ts&&... taskArgs)
442 auto context = std::make_unique<QObject> ();
443 auto task = taskGen (&*context, std::forward<Ts> (taskArgs)...);
444 QTimer::singleShot (
ShortDelay, [context = std::move (context)] ()
mutable { context.reset (); });
448 void WithDestroyTimer (
auto task)
452 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
457 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
461 co_await AddContextObject { *context };
466 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
468 const QByteArray data {
"this is some test data" };
469 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
473 if (
const auto reply = nam->GetReply ())
475 reply->setFinished (
true);
476 emit reply->finished ();
482 co_await AddContextObject { *context };
483 const auto reply =
co_await *nam->get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
484 co_return reply.GetReplyData ();
488 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
490 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
492 co_await AddContextObject { *context };
494 const auto process =
new QProcess {};
495 const auto delay = std::chrono::duration_cast<std::chrono::milliseconds> (
LongDelay);
496 process->start (
"sleep", { QString::number (delay.count () / 1000.0) });
500 &QObject::deleteLater);
506 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
508 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
510 co_await AddContextObject { *context };
511 co_await QtConcurrent::run ([] { QThread::sleep (
LongDelay); });
515 void CoroTaskTest::cleanupTestCase ()
518 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