LeechCraft 0.6.70-18450-gabe19ee3b0
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#ifdef QT_DBUS_LIB
13#include <QDBusInterface>
14#endif
15#include <coro/future.h>
16#include <coro.h>
17#include <coro/getresult.h>
18#include <coro/inparallel.h>
19#include <coro/throttle.h>
20#ifdef QT_DBUS_LIB
21#include <coro/dbus.h>
22#endif
25#include <util/sll/qtutil.h>
26
27QTEST_GUILESS_MAIN (LC::Util::CoroTaskTest)
28
29using namespace std::chrono_literals;
30
31namespace LC::Util
32{
33 void CoroTaskTest::testReturn ()
34 {
35 auto task = [] () -> Task<int> { co_return 42; } ();
36 auto result = GetTaskResult (task);
37 QCOMPARE (result, 42);
38 }
39
40 void CoroTaskTest::testWait ()
41 {
42 QElapsedTimer timer;
43 timer.start ();
44
45 auto task = [] () -> Task<int>
46 {
47 co_await 50ms;
48 co_await Precisely { 10ms };
49 co_return 42;
50 } ();
51
52 auto result = GetTaskResult (task);
53 QCOMPARE (result, 42);
54 QCOMPARE_GT (timer.elapsed (), 50);
55 }
56
57 void CoroTaskTest::testTaskDestr ()
58 {
59 bool continued = false;
60
61 [] (auto& continued) -> Task<void>
62 {
63 co_await 10ms;
64 continued = true;
65 } (continued);
66
67 QTRY_VERIFY_WITH_TIMEOUT (continued, 20);
68 }
69
70 namespace
71 {
72 // almost the Public Morozov pattern
73 class MockReply : public QNetworkReply
74 {
75 QBuffer Buffer_;
76 public:
77 using QNetworkReply::QNetworkReply;
78
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;
87
88 void SetData (const QByteArray& data)
89 {
90 Buffer_.setData (data);
91 Buffer_.open (QIODevice::ReadOnly);
92 open (QIODevice::ReadOnly);
93 }
94 protected:
95 qint64 readData (char *data, qint64 maxSize) override
96 {
97 return Buffer_.read (data, maxSize);
98 }
99
100 void abort () override
101 {
102 }
103 };
104
105 class MockNAM : public QNetworkAccessManager
106 {
107 QPointer<MockReply> Reply_;
108 public:
109 explicit MockNAM (MockReply *reply)
110 : Reply_ { reply }
111 {
112 }
113
114 MockReply* GetReply ()
115 {
116 return Reply_;
117 }
118 protected:
119 QNetworkReply* createRequest (Operation op, const QNetworkRequest& req, QIODevice*) override
120 {
121 Reply_->setUrl (req.url ());
122 Reply_->setOperation (op);
123 Reply_->setRequest (req);
124 return Reply_;
125 }
126 };
127
128 auto MkSuccessfulReply (const QByteArray& data)
129 {
130 auto reply = new MockReply;
131 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 200);
132 reply->SetData (data);
133 return reply;
134 }
135
136 auto MkErrorReply ()
137 {
138 auto reply = new MockReply;
139 reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 404);
140 reply->setError (QNetworkReply::NetworkError::ContentAccessDenied, "well, 404!"_qs);
141 return reply;
142 }
143
144 void TestGoodReply (auto finishMarker)
145 {
146 const QByteArray data { "this is some test data" };
147 MockNAM nam { MkSuccessfulReply (data) };
148 finishMarker (*nam.GetReply ());
149
150 auto task = [&nam] () -> Task<QByteArray>
151 {
152 auto reply = co_await *nam.get (QNetworkRequest { QUrl { "http://example.com/foo.txt"_qs } });
153 co_return reply.GetReplyData ();
154 } ();
155
156 auto result = GetTaskResult (task);
157 QCOMPARE (result, data);
158 }
159
160 void TestBadReply (auto finishMarker)
161 {
162 MockNAM nam { MkErrorReply () };
163 finishMarker (*nam.GetReply ());
164
165 auto task = [&nam] () -> Task<QByteArray>
166 {
167 auto reply = co_await *nam.get (QNetworkRequest { QUrl { "http://example.com/foo.txt"_qs } });
168 co_return reply.GetReplyData ();
169 } ();
170
171 QVERIFY_THROWS_EXCEPTION (LC::Util::NetworkReplyErrorException, GetTaskResult (task));
172 }
173
174 void ImmediateFinishMarker (MockReply& reply)
175 {
176 reply.setFinished (true);
177 }
178
179 void DelayedFinishMarker (MockReply& reply)
180 {
181 QTimer::singleShot (10ms,
182 [&]
183 {
184 reply.setFinished (true);
185 emit reply.finished ();
186 });
187 }
188 }
189
190 void CoroTaskTest::testNetworkReplyGoodNoWait ()
191 {
192 TestGoodReply (&ImmediateFinishMarker);
193 }
194
195 void CoroTaskTest::testNetworkReplyGoodWait ()
196 {
197 TestGoodReply (&DelayedFinishMarker);
198 }
199
200 void CoroTaskTest::testNetworkReplyBadNoWait ()
201 {
202 TestBadReply (&ImmediateFinishMarker);
203 }
204
205 void CoroTaskTest::testNetworkReplyBadWait ()
206 {
207 TestBadReply (&DelayedFinishMarker);
208 }
209
210 void CoroTaskTest::testFutureAwaiter ()
211 {
212 auto delayed = [] () -> Task<int>
213 {
214 co_return co_await QtConcurrent::run ([]
215 {
216 QThread::msleep (1);
217 return 42;
218 });
219 } ();
220 QCOMPARE (GetTaskResult (delayed), 42);
221
222 auto immediate = [] () -> Task<int>
223 {
224 co_return co_await QtConcurrent::run ([] { return 42; });
225 } ();
226 QCOMPARE (GetTaskResult (immediate), 42);
227
228 auto ready = [] () -> Task<int>
229 {
230 co_return co_await MakeReadyFuture (42);
231 } ();
232 QCOMPARE (GetTaskResult (ready), 42);
233 }
234
235 void CoroTaskTest::testWaitMany ()
236 {
237 constexpr auto max = 100;
238 auto mkTask = [] (int index) -> Task<int>
239 {
240 co_await Precisely { std::chrono::milliseconds { max - index } };
241 co_return index;
242 };
243
244 QElapsedTimer timer;
245 timer.start ();
246 QVector<Task<int>> tasks;
247 QVector<int> expected;
248 for (int i = 0; i < max; ++i)
249 {
250 tasks << mkTask (i);
251 expected << i;
252 }
253 const auto creationElapsed = timer.elapsed ();
254
255 timer.restart ();
256 auto result = GetTaskResult (InParallel (std::move (tasks)));
257 const auto executionElapsed = timer.elapsed ();
258
259 QCOMPARE (result, expected);
260 QCOMPARE_LT (creationElapsed, 1);
261
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);
266 }
267
268 void CoroTaskTest::testWaitManyTuple ()
269 {
270 auto mkTask = [] (int delay) -> Task<int>
271 {
272 co_await Precisely { std::chrono::milliseconds { delay } };
273 co_return delay;
274 };
275
276 QElapsedTimer timer;
277 timer.start ();
278 auto result = GetTaskResult (InParallel (mkTask (10), mkTask (9), mkTask (2), mkTask (1)));
279 const auto executionElapsed = timer.elapsed ();
280
281 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
282
283 QCOMPARE_GE (executionElapsed, 10);
284 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
285 }
286
287 void CoroTaskTest::testWaitManyInvoking ()
288 {
289 constexpr auto max = 100;
290 auto mkTask = [] (int index) -> Task<int>
291 {
292 co_await Precisely { std::chrono::milliseconds { max - index } };
293 co_return index;
294 };
295
296 QElapsedTimer timer;
297 timer.start ();
298 QVector<int> inputs;
299 QVector<int> expected;
300 for (int i = 0; i < max; ++i)
301 {
302 inputs << i;
303 expected << i;
304 }
305 const auto creationElapsed = timer.elapsed ();
306
307 timer.restart ();
308 auto result = GetTaskResult (InParallel (inputs, mkTask));
309 const auto executionElapsed = timer.elapsed ();
310
311 QCOMPARE (result, expected);
312 QCOMPARE_LT (creationElapsed, 1);
313
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);
318 }
319
320 void CoroTaskTest::testEither ()
321 {
322 using Result_t = Either<QString, bool>;
323
324 auto immediatelyFailing = [] () -> Task<Result_t>
325 {
326 const auto theInt = co_await Either<QString, int> { "meh" };
327 co_return theInt > 420;
328 } ();
329 QCOMPARE (GetTaskResult (immediatelyFailing), Result_t { Left { "meh" } });
330
331 auto earlyFailing = [] () -> Task<Result_t>
332 {
333 const auto theInt = co_await Either<QString, int> { "meh" };
334 co_await 10ms;
335 co_return theInt > 420;
336 } ();
337 QCOMPARE (GetTaskResult (earlyFailing), Result_t { Left { "meh" } });
338
339 auto successful = [] () -> Task<Result_t>
340 {
341 const auto theInt = co_await Either<QString, int> { 42 };
342 co_await 10ms;
343 co_return theInt > 420;
344 } ();
345 QCOMPARE (GetTaskResult (successful), Result_t { false });
346 }
347
348 void CoroTaskTest::testEitherIgnoreLeft ()
349 {
350 auto immediatelyFailing = [] () -> Task<Either<QString, int>>
351 {
352 const auto theInt = co_await WithHandler (Either<QString, int> { "meh" }, IgnoreLeft {});
353 co_return theInt;
354 } ();
355 QCOMPARE (GetTaskResult (immediatelyFailing), (Either<QString, int> { 0 }));
356 }
357
358 void CoroTaskTest::testThrottleSameCoro ()
359 {
360 Throttle t { 10ms };
361 constexpr auto count = 10;
362
363 QElapsedTimer timer;
364 timer.start ();
365 auto task = [] (auto& t) -> Task<int>
366 {
367 int result = 0;
368 for (int i = 0; i < count; ++i)
369 {
370 co_await t;
371 result += i;
372 }
373 co_return result;
374 } (t);
375 const auto result = GetTaskResult (task);
376 const auto time = timer.elapsed ();
377
378 QCOMPARE (result, count * (count - 1) / 2);
379 QCOMPARE_GE (time, count * t.GetInterval ().count ());
380 }
381
382 void CoroTaskTest::testThrottleSameCoroSlow ()
383 {
384 Throttle t { 10ms };
385 constexpr auto count = 10;
386 constexpr static auto intraDelay = 9ms;
387
388 QElapsedTimer timer;
389 timer.start ();
390 auto task = [] (auto& t) -> Task<void>
391 {
392 for (int i = 0; i < count; ++i)
393 {
394 co_await t;
395 if (i != count - 1)
396 co_await Precisely { intraDelay };
397 }
398 } (t);
399 GetTaskResult (task);
400 const auto time = timer.elapsed ();
401
402 const auto expectedMinTime = count * t.GetInterval ().count ();
403 QCOMPARE_GE (time, expectedMinTime);
404
405 const auto delaysTime = (count - 1) * intraDelay.count ();
406 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
407 }
408
409 void CoroTaskTest::testThrottleSameCoroVerySlow ()
410 {
411 Throttle t { 10ms };
412 constexpr auto count = 10;
413 constexpr static auto intraDelay = 20ms;
414
415 QElapsedTimer timer;
416 timer.start ();
417 auto task = [] (auto& t) -> Task<void>
418 {
419 for (int i = 0; i < count; ++i)
420 {
421 co_await t;
422 if (i != count - 1)
423 co_await Precisely { intraDelay };
424 }
425 } (t);
426 GetTaskResult (task);
427 const auto time = timer.elapsed ();
428
429 const auto expectedMinTime = (count - 1) * intraDelay.count ();
430 QCOMPARE_GE (time, expectedMinTime);
431
432 const auto throttlesTime = count * t.GetInterval ().count ();
433 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
434 }
435
436 void CoroTaskTest::testThrottleManyCoros ()
437 {
438 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
439 constexpr auto count = 10;
440
441 QElapsedTimer timer;
442 timer.start ();
443 auto mkTask = [] (auto& t) -> Task<void>
444 {
445 for (int i = 0; i < count; ++i)
446 co_await t;
447 };
448 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
449 for (auto& task : tasks)
450 GetTaskResult (task);
451 const auto time = timer.elapsed ();
452
453 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
454 }
455
456 constexpr auto LongDelay = 500ms;
457 constexpr auto ShortDelay = 10ms;
458 constexpr auto DelayThreshold = std::chrono::duration_cast<std::chrono::milliseconds> ((ShortDelay + LongDelay) / 2);
459
460 void CoroTaskTest::testContextDestrBeforeFinish ()
461 {
462 auto context = std::make_unique<QObject> ();
463 auto task = [] (QObject *context) -> ContextTask<int>
464 {
465 co_await AddContextObject { *context };
466 co_await LongDelay;
467 co_return context->children ().size ();
468 } (&*context);
469 context.reset ();
470
471 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
472 }
473
474 void CoroTaskTest::testContextDestrAfterFinish ()
475 {
476 auto context = std::make_unique<QObject> ();
477 auto task = [] (QObject *context) -> ContextTask<int>
478 {
479 co_await AddContextObject { *context };
480 co_await ShortDelay;
481 co_return context->children ().size ();
482 } (&*context);
483
484 QCOMPARE (GetTaskResult (task), 0);
485 }
486
487 void CoroTaskTest::testContextDestrAwaitedSubtask ()
488 {
489 auto context = std::make_unique<QObject> ();
490 auto task = [] (QObject *context) -> ContextTask<int>
491 {
492 co_await AddContextObject { *context };
493
494 const auto nestedSubtask = [] -> ContextTask<int>
495 {
496 co_await ShortDelay;
497 co_return 42;
498 } ();
499
500 co_await nestedSubtask;
501 co_return context->children ().size ();
502 } (&*context);
503 context.reset ();
504
505 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
506 }
507
508 namespace
509 {
510 template<typename... Ts>
511 auto WithContext (auto&& taskGen, Ts&&... taskArgs)
512 {
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 (); });
516 return task;
517 }
518
519 void WithDestroyTimer (auto task)
520 {
521 QElapsedTimer timer;
522 timer.start ();
523 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
524 QCOMPARE_LT (timer.elapsed (), DelayThreshold.count ());
525 }
526 }
527
528 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
529 {
530 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<void>
531 {
532 co_await AddContextObject { *context };
533 co_await LongDelay;
534 }));
535 }
536
537 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
538 {
539 const QByteArray data { "this is some test data" };
540 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
541 QTimer::singleShot (LongDelay,
542 [nam]
543 {
544 if (const auto reply = nam->GetReply ())
545 {
546 reply->setFinished (true);
547 emit reply->finished ();
548 }
549 });
550
551 WithDestroyTimer (WithContext ([] (QObject *context, QNetworkAccessManager *nam) -> ContextTask<QByteArray>
552 {
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 ();
556 }, &*nam));
557 }
558
559 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
560 {
561 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<>
562 {
563 co_await AddContextObject { *context };
564
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) });
568 connect (process,
569 &QProcess::finished,
570 process,
571 &QObject::deleteLater);
572
573 co_await *process;
574 }));
575 }
576
577 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
578 {
579 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<>
580 {
581 co_await AddContextObject { *context };
582 co_await QtConcurrent::run ([] { QThread::sleep (LongDelay); });
583 }));
584 }
585
586#ifdef QT_DBUS_LIB
587 void CoroTaskTest::testDBus ()
588 {
589 const auto& bus = QDBusConnection::systemBus ();
590 if (!bus.isConnected ())
591 QSKIP ("D-Bus system bus is not available");
592
593 auto task = [] () -> Task<Either<QDBusError::ErrorType, bool>>
594 {
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);
599 } ();
600
601 QCOMPARE (GetTaskResult (task), true);
602 }
603#endif
604
605 void CoroTaskTest::cleanupTestCase ()
606 {
607 bool done = false;
608 QTimer::singleShot (LongDelay * 2, [&done] { done = true; });
609 QTRY_VERIFY (done);
610 }
611}
constexpr detail::AggregateType< detail::AggregateFunction::Count, Ptr > count
Definition oral.h:1104
constexpr detail::AggregateType< detail::AggregateFunction::Max, Ptr > max
Definition oral.h:1110
detail::DBusAwaiter< Rets... > Typed(const QDBusPendingCall &asyncCall)
Definition dbus.h:89
constexpr auto LongDelay
Task< R, ContextExtensions > ContextTask
Definition taskfwd.h:20
detail::EitherAwaiter< L, R, F > WithHandler(const Either< L, R > &either, F &&errorHandler)
Definition either.h:82
constexpr auto ShortDelay
WithPrecision< Qt::PreciseTimer > Precisely
Definition timer.h:43
Task< QVector< T >, Exts... > InParallel(Cont< Task< T, Exts... > > tasks)
Definition inparallel.h:21
T GetTaskResult(Task< T, Extensions... > task)
Definition getresult.h:19
constexpr auto DelayThreshold