LeechCraft 0.6.70-17793-g6e56308e78
Modular cross-platform feature rich live environment.
Loading...
Searching...
No Matches
corotasktest.cpp
Go to the documentation of this file.
1/**********************************************************************
2 * LeechCraft - modular cross-platform feature rich internet client.
3 * Copyright (C) 2006-2014 Georg Rudoy
4 *
5 * Distributed under the Boost Software License, Version 1.0.
6 * (See accompanying file LICENSE or copy at https://www.boost.org/LICENSE_1_0.txt)
7 **********************************************************************/
8
9#include "corotasktest.h"
10#include <QtConcurrentRun>
11#include <QtTest>
12#include <coro/future.h>
13#include <coro.h>
14#include <coro/getresult.h>
15#include <coro/inparallel.h>
16#include <coro/throttle.h>
18#include <util/sll/qtutil.h>
19
20QTEST_GUILESS_MAIN (LC::Util::CoroTaskTest)
21
22using namespace std::chrono_literals;
23
24namespace LC::Util
25{
26 void CoroTaskTest::testReturn ()
27 {
28 auto task = [] () -> Task<int> { co_return 42; } ();
29 auto result = GetTaskResult (task);
30 QCOMPARE (result, 42);
31 }
32
33 void CoroTaskTest::testWait ()
34 {
35 QElapsedTimer timer;
36 timer.start ();
37
38 auto task = [] () -> Task<int>
39 {
40 co_await 50ms;
41 co_await Precisely { 10ms };
42 co_return 42;
43 } ();
44
45 auto result = GetTaskResult (task);
46 QCOMPARE (result, 42);
47 QCOMPARE_GT (timer.elapsed (), 50);
48 }
49
50 void CoroTaskTest::testTaskDestr ()
51 {
52 bool continued = false;
53
54 [] (auto& continued) -> Task<void>
55 {
56 co_await 10ms;
57 continued = true;
58 } (continued);
59
60 QTRY_VERIFY_WITH_TIMEOUT (continued, 20);
61 }
62
63 namespace
64 {
65 // almost the Public Morozov pattern
66 class MockReply : public QNetworkReply
67 {
68 QBuffer Buffer_;
69 public:
70 using QNetworkReply::QNetworkReply;
71
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;
80
81 void SetData (const QByteArray& data)
82 {
83 Buffer_.setData (data);
84 Buffer_.open (QIODevice::ReadOnly);
85 open (QIODevice::ReadOnly);
86 }
87 protected:
88 qint64 readData (char *data, qint64 maxSize) override
89 {
90 return Buffer_.read (data, maxSize);
91 }
92
93 void abort () override
94 {
95 }
96 };
97
98 class MockNAM : public QNetworkAccessManager
99 {
100 QPointer<MockReply> Reply_;
101 public:
102 explicit MockNAM (MockReply *reply)
103 : Reply_ { reply }
104 {
105 }
106
107 MockReply* GetReply ()
108 {
109 return Reply_;
110 }
111 protected:
112 QNetworkReply* createRequest (Operation op, const QNetworkRequest& req, QIODevice*) override
113 {
114 Reply_->setUrl (req.url ());
115 Reply_->setOperation (op);
116 Reply_->setRequest (req);
117 return Reply_;
118 }
119 };
120
121 auto MkSuccessfulReply (const QByteArray& data)
122 {
123 auto reply = new MockReply;
124 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 200);
125 reply->SetData (data);
126 return reply;
127 }
128
129 auto MkErrorReply ()
130 {
131 auto reply = new MockReply;
132 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 404);
133 reply->setError (QNetworkReply::NetworkError::ContentAccessDenied, "well, 404!"_qs);
134 return reply;
135 }
136
137 void TestGoodReply (auto finishMarker)
138 {
139 const QByteArray data { "this is some test data" };
140 MockNAM nam { MkSuccessfulReply (data) };
141 finishMarker (*nam.GetReply ());
142
143 auto task = [&nam] () -> Task<QByteArray>
144 {
145 auto reply = co_await *nam.get (QNetworkRequest { QUrl { "http://example.com/foo.txt"_qs } });
146 co_return reply.GetReplyData ();
147 } ();
148
149 auto result = GetTaskResult (task);
150 QCOMPARE (result, data);
151 }
152
153 void TestBadReply (auto finishMarker)
154 {
155 MockNAM nam { MkErrorReply () };
156 finishMarker (*nam.GetReply ());
157
158 auto task = [&nam] () -> Task<QByteArray>
159 {
160 auto reply = co_await *nam.get (QNetworkRequest { QUrl { "http://example.com/foo.txt"_qs } });
161 co_return reply.GetReplyData ();
162 } ();
163
164 QVERIFY_THROWS_EXCEPTION (LC::Util::NetworkReplyErrorException, GetTaskResult (task));
165 }
166
167 void ImmediateFinishMarker (MockReply& reply)
168 {
169 reply.setFinished (true);
170 }
171
172 void DelayedFinishMarker (MockReply& reply)
173 {
174 QTimer::singleShot (10ms,
175 [&]
176 {
177 reply.setFinished (true);
178 emit reply.finished ();
179 });
180 }
181 }
182
183 void CoroTaskTest::testNetworkReplyGoodNoWait ()
184 {
185 TestGoodReply (&ImmediateFinishMarker);
186 }
187
188 void CoroTaskTest::testNetworkReplyGoodWait ()
189 {
190 TestGoodReply (&DelayedFinishMarker);
191 }
192
193 void CoroTaskTest::testNetworkReplyBadNoWait ()
194 {
195 TestBadReply (&ImmediateFinishMarker);
196 }
197
198 void CoroTaskTest::testNetworkReplyBadWait ()
199 {
200 TestBadReply (&DelayedFinishMarker);
201 }
202
203 void CoroTaskTest::testFutureAwaiter ()
204 {
205 auto delayed = [] () -> Task<int>
206 {
207 co_return co_await QtConcurrent::run ([]
208 {
209 QThread::msleep (1);
210 return 42;
211 });
212 } ();
213 QCOMPARE (GetTaskResult (delayed), 42);
214
215 auto immediate = [] () -> Task<int>
216 {
217 co_return co_await QtConcurrent::run ([] { return 42; });
218 } ();
219 QCOMPARE (GetTaskResult (immediate), 42);
220
221 auto ready = [] () -> Task<int>
222 {
223 co_return co_await MakeReadyFuture (42);
224 } ();
225 QCOMPARE (GetTaskResult (ready), 42);
226 }
227
228 void CoroTaskTest::testWaitMany ()
229 {
230 constexpr auto max = 100;
231 auto mkTask = [] (int index) -> Task<int>
232 {
233 co_await Precisely { std::chrono::milliseconds { max - index } };
234 co_return index;
235 };
236
237 QElapsedTimer timer;
238 timer.start ();
239 QVector<Task<int>> tasks;
240 QVector<int> expected;
241 for (int i = 0; i < max; ++i)
242 {
243 tasks << mkTask (i);
244 expected << i;
245 }
246 const auto creationElapsed = timer.elapsed ();
247
248 timer.restart ();
249 auto result = GetTaskResult (InParallel (std::move (tasks)));
250 const auto executionElapsed = timer.elapsed ();
251
252 QCOMPARE (result, expected);
253 QCOMPARE_LT (creationElapsed, 1);
254
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);
259 }
260
261 void CoroTaskTest::testWaitManyTuple ()
262 {
263 auto mkTask = [] (int delay) -> Task<int>
264 {
265 co_await Precisely { std::chrono::milliseconds { delay } };
266 co_return delay;
267 };
268
269 QElapsedTimer timer;
270 timer.start ();
271 auto result = GetTaskResult (InParallel (mkTask (10), mkTask (9), mkTask (2), mkTask (1)));
272 const auto executionElapsed = timer.elapsed ();
273
274 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
275
276 QCOMPARE_GE (executionElapsed, 10);
277 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
278 }
279
280 void CoroTaskTest::testEither ()
281 {
282 using Result_t = Either<QString, bool>;
283
284 auto immediatelyFailing = [] () -> Task<Result_t>
285 {
286 const auto theInt = co_await Either<QString, int> { "meh" };
287 co_return { theInt > 420 };
288 } ();
289 QCOMPARE (GetTaskResult (immediatelyFailing), Result_t { Left { "meh" } });
290
291 auto earlyFailing = [] () -> Task<Result_t>
292 {
293 const auto theInt = co_await Either<QString, int> { "meh" };
294 co_await 10ms;
295 co_return { theInt > 420 };
296 } ();
297 QCOMPARE (GetTaskResult (earlyFailing), Result_t { Left { "meh" } });
298
299 auto successful = [] () -> Task<Result_t>
300 {
301 const auto theInt = co_await Either<QString, int> { 42 };
302 co_await 10ms;
303 co_return { theInt > 420 };
304 } ();
305 QCOMPARE (GetTaskResult (successful), Result_t { false });
306 }
307
308 void CoroTaskTest::testThrottleSameCoro ()
309 {
310 Throttle t { 10ms };
311 constexpr auto count = 10;
312
313 QElapsedTimer timer;
314 timer.start ();
315 auto task = [] (auto& t) -> Task<int>
316 {
317 int result = 0;
318 for (int i = 0; i < count; ++i)
319 {
320 co_await t;
321 result += i;
322 }
323 co_return result;
324 } (t);
325 const auto result = GetTaskResult (task);
326 const auto time = timer.elapsed ();
327
328 QCOMPARE (result, count * (count - 1) / 2);
329 QCOMPARE_GE (time, count * t.GetInterval ().count ());
330 }
331
332 void CoroTaskTest::testThrottleSameCoroSlow ()
333 {
334 Throttle t { 10ms };
335 constexpr auto count = 10;
336 constexpr static auto intraDelay = 9ms;
337
338 QElapsedTimer timer;
339 timer.start ();
340 auto task = [] (auto& t) -> Task<void>
341 {
342 for (int i = 0; i < count; ++i)
343 {
344 co_await t;
345 if (i != count - 1)
346 co_await Precisely { intraDelay };
347 }
348 } (t);
349 GetTaskResult (task);
350 const auto time = timer.elapsed ();
351
352 const auto expectedMinTime = count * t.GetInterval ().count ();
353 QCOMPARE_GE (time, expectedMinTime);
354
355 const auto delaysTime = (count - 1) * intraDelay.count ();
356 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
357 }
358
359 void CoroTaskTest::testThrottleSameCoroVerySlow ()
360 {
361 Throttle t { 10ms };
362 constexpr auto count = 10;
363 constexpr static auto intraDelay = 20ms;
364
365 QElapsedTimer timer;
366 timer.start ();
367 auto task = [] (auto& t) -> Task<void>
368 {
369 for (int i = 0; i < count; ++i)
370 {
371 co_await t;
372 if (i != count - 1)
373 co_await Precisely { intraDelay };
374 }
375 } (t);
376 GetTaskResult (task);
377 const auto time = timer.elapsed ();
378
379 const auto expectedMinTime = (count - 1) * intraDelay.count ();
380 QCOMPARE_GE (time, expectedMinTime);
381
382 const auto throttlesTime = count * t.GetInterval ().count ();
383 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
384 }
385
386 void CoroTaskTest::testThrottleManyCoros ()
387 {
388 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
389 constexpr auto count = 10;
390
391 QElapsedTimer timer;
392 timer.start ();
393 auto mkTask = [] (auto& t) -> Task<void>
394 {
395 for (int i = 0; i < count; ++i)
396 co_await t;
397 };
398 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
399 for (auto& task : tasks)
400 GetTaskResult (task);
401 const auto time = timer.elapsed ();
402
403 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
404 }
405
406 constexpr auto LongDelay = 500ms;
407 constexpr auto ShortDelay = 10ms;
408 constexpr auto DelayThreshold = std::chrono::duration_cast<std::chrono::milliseconds> ((ShortDelay + LongDelay) / 2);
409
410 void CoroTaskTest::testContextDestrBeforeFinish ()
411 {
412 auto context = std::make_unique<QObject> ();
413 auto task = [] (QObject *context) -> ContextTask<int>
414 {
415 co_await AddContextObject { *context };
416 co_await LongDelay;
417 co_return context->children ().size ();
418 } (&*context);
419 context.reset ();
420
421 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
422 }
423
424 void CoroTaskTest::testContextDestrAfterFinish ()
425 {
426 auto context = std::make_unique<QObject> ();
427 auto task = [] (QObject *context) -> ContextTask<int>
428 {
429 co_await AddContextObject { *context };
430 co_await ShortDelay;
431 co_return context->children ().size ();
432 } (&*context);
433
434 QCOMPARE (GetTaskResult (task), 0);
435 }
436
437 namespace
438 {
439 template<typename... Ts>
440 auto WithContext (auto&& taskGen, Ts&&... taskArgs)
441 {
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 (); });
445 return task;
446 }
447
448 void WithDestroyTimer (auto task)
449 {
450 QElapsedTimer timer;
451 timer.start ();
452 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
453 QCOMPARE_LT (timer.elapsed (), DelayThreshold.count ());
454 }
455 }
456
457 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
458 {
459 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<void>
460 {
461 co_await AddContextObject { *context };
462 co_await LongDelay;
463 }));
464 }
465
466 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
467 {
468 const QByteArray data { "this is some test data" };
469 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
470 QTimer::singleShot (LongDelay,
471 [nam]
472 {
473 if (const auto reply = nam->GetReply ())
474 {
475 reply->setFinished (true);
476 emit reply->finished ();
477 }
478 });
479
480 WithDestroyTimer (WithContext ([] (QObject *context, QNetworkAccessManager *nam) -> ContextTask<QByteArray>
481 {
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 ();
485 }, &*nam));
486 }
487
488 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
489 {
490 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<>
491 {
492 co_await AddContextObject { *context };
493
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) });
497 connect (process,
498 &QProcess::finished,
499 process,
500 &QObject::deleteLater);
501
502 co_await *process;
503 }));
504 }
505
506 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
507 {
508 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<>
509 {
510 co_await AddContextObject { *context };
511 co_await QtConcurrent::run ([] { QThread::sleep (LongDelay); });
512 }));
513 }
514
515 void CoroTaskTest::cleanupTestCase ()
516 {
517 bool done = false;
518 QTimer::singleShot (LongDelay * 2, [&done] { done = true; });
519 QTRY_VERIFY (done);
520 }
521}
constexpr detail::AggregateType< detail::AggregateFunction::Count, Ptr > count
Definition oral.h:962
constexpr detail::AggregateType< detail::AggregateFunction::Max, Ptr > max
Definition oral.h:968
Task< QVector< T >, Exts... > InParallel(QVector< Task< T, Exts... > > tasks)
Definition inparallel.h:17
constexpr auto LongDelay
Task< R, ContextExtensions > ContextTask
Definition taskfwd.h:20
constexpr auto ShortDelay
WithPrecision< Qt::PreciseTimer > Precisely
Definition timer.h:43
T GetTaskResult(Task< T, Extensions... > task)
Definition getresult.h:18
constexpr auto DelayThreshold