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);
230 void CompareDouble (
double actual,
double expected,
double delta)
232 const auto diff = std::abs (actual - expected);
233 const auto midpoint = (actual + expected) / 2;
234 if (diff / midpoint >= delta)
236 auto message = std::to_string (actual) +
" is too different from the expected " + std::to_string (expected);
237 QFAIL (message.c_str ());
242 void CoroTaskTest::testWaitMany ()
244 constexpr auto max = 100;
245 auto mkTask = [] (
int index) -> Task<int>
247 co_await Precisely { std::chrono::milliseconds {
max - index } };
253 QVector<Task<int>> tasks;
254 QVector<int> expected;
255 for (
int i = 0; i <
max; ++i)
260 const auto creationElapsed = timer.elapsed ();
264 const auto executionElapsed = timer.elapsed ();
266 QCOMPARE (result, expected);
267 QCOMPARE_LT (creationElapsed, 1);
268 CompareDouble (executionElapsed, max, 0.05);
271 void CoroTaskTest::testWaitManyTuple ()
273 auto mkTask = [] (
int delay) -> Task<int>
275 co_await Precisely { std::chrono::milliseconds { delay } };
282 const auto executionElapsed = timer.elapsed ();
284 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
285 CompareDouble (executionElapsed, 10, 0.05);
288 void CoroTaskTest::testEither ()
290 using Result_t = Either<QString, bool>;
292 auto immediatelyFailing = [] () -> Task<Result_t>
294 const auto theInt =
co_await Either<QString, int> {
"meh" };
295 co_return { theInt > 420 };
297 QCOMPARE (
GetTaskResult (immediatelyFailing), Result_t { Left {
"meh" } });
299 auto earlyFailing = [] () -> Task<Result_t>
301 const auto theInt =
co_await Either<QString, int> {
"meh" };
303 co_return { theInt > 420 };
305 QCOMPARE (
GetTaskResult (earlyFailing), Result_t { Left {
"meh" } });
307 auto successful = [] () -> Task<Result_t>
309 const auto theInt =
co_await Either<QString, int> { 42 };
311 co_return { theInt > 420 };
316 void CoroTaskTest::testThrottleSameCoro ()
319 constexpr auto count = 10;
323 auto task = [] (
auto& t) -> Task<int>
326 for (
int i = 0; i <
count; ++i)
334 const auto time = timer.elapsed ();
336 QCOMPARE (result, count * (count - 1) / 2);
337 QCOMPARE_GE (time, count * t.GetInterval ().count ());
340 void CoroTaskTest::testThrottleSameCoroSlow ()
343 constexpr auto count = 10;
347 auto task = [] (
auto& t) -> Task<void>
349 for (
int i = 0; i <
count; ++i)
357 const auto time = timer.elapsed ();
359 const auto expectedMinTime =
count * t.GetInterval ().count ();
360 QCOMPARE_GE (time, expectedMinTime);
361 QCOMPARE_LE (time - expectedMinTime, expectedMinTime * 0.05);
364 void CoroTaskTest::testThrottleManyCoros ()
366 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
367 constexpr auto count = 10;
371 auto mkTask = [] (
auto& t) -> Task<void>
373 for (
int i = 0; i <
count; ++i)
376 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
377 for (
auto& task : tasks)
379 const auto time = timer.elapsed ();
381 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
388 void CoroTaskTest::testContextDestrBeforeFinish ()
390 auto context = std::make_unique<QObject> ();
395 co_return context->children ().size ();
402 void CoroTaskTest::testContextDestrAfterFinish ()
404 auto context = std::make_unique<QObject> ();
407 co_await AddContextObject { *context };
409 co_return context->children ().size ();
417 template<
typename... Ts>
418 auto WithContext (
auto&& taskGen, Ts&&... taskArgs)
420 auto context = std::make_unique<QObject> ();
421 auto task = taskGen (&*context, std::forward<Ts> (taskArgs)...);
422 QTimer::singleShot (
ShortDelay, [context = std::move (context)] ()
mutable { context.reset (); });
426 void WithDestroyTimer (
auto task)
430 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException,
GetTaskResult (task));
435 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
439 co_await AddContextObject { *context };
444 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
446 const QByteArray data {
"this is some test data" };
447 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
451 if (
const auto reply = nam->GetReply ())
453 reply->setFinished (
true);
454 emit reply->finished ();
460 co_await AddContextObject { *context };
461 const auto reply =
co_await *nam->get (QNetworkRequest { QUrl {
"http://example.com/foo.txt"_qs } });
462 co_return reply.GetReplyData ();
466 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
468 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
470 co_await AddContextObject { *context };
472 const auto process =
new QProcess {};
473 const auto delay = std::chrono::duration_cast<std::chrono::milliseconds> (
LongDelay);
474 process->start (
"sleep", { QString::number (delay.count () / 1000.0) });
478 &QObject::deleteLater);
484 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
486 WithDestroyTimer (WithContext ([] (QObject *context) ->
ContextTask<>
488 co_await AddContextObject { *context };
489 co_await QtConcurrent::run ([] { QThread::sleep (
LongDelay); });
493 void CoroTaskTest::cleanupTestCase ()
496 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