LeechCraft 0.6.70-17739-g0d7a960ef4
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 QCOMPARE_GE (executionElapsed, max);
256 const auto linearizedExecTime = max * (max + 1) / 2;
257 QCOMPARE_LT (executionElapsed, linearizedExecTime / 2);
258 }
259
260 void CoroTaskTest::testWaitManyTuple ()
261 {
262 auto mkTask = [] (int delay) -> Task<int>
263 {
264 co_await Precisely { std::chrono::milliseconds { delay } };
265 co_return delay;
266 };
267
268 QElapsedTimer timer;
269 timer.start ();
270 auto result = GetTaskResult (InParallel (mkTask (10), mkTask (9), mkTask (2), mkTask (1)));
271 const auto executionElapsed = timer.elapsed ();
272
273 QCOMPARE (result, (std::tuple { 10, 9, 2, 1 }));
274
275 QCOMPARE_GE (executionElapsed, 10);
276 QCOMPARE_LT (executionElapsed, (10 + 9 + 2 + 1) / 2);
277 }
278
279 void CoroTaskTest::testEither ()
280 {
281 using Result_t = Either<QString, bool>;
282
283 auto immediatelyFailing = [] () -> Task<Result_t>
284 {
285 const auto theInt = co_await Either<QString, int> { "meh" };
286 co_return { theInt > 420 };
287 } ();
288 QCOMPARE (GetTaskResult (immediatelyFailing), Result_t { Left { "meh" } });
289
290 auto earlyFailing = [] () -> Task<Result_t>
291 {
292 const auto theInt = co_await Either<QString, int> { "meh" };
293 co_await 10ms;
294 co_return { theInt > 420 };
295 } ();
296 QCOMPARE (GetTaskResult (earlyFailing), Result_t { Left { "meh" } });
297
298 auto successful = [] () -> Task<Result_t>
299 {
300 const auto theInt = co_await Either<QString, int> { 42 };
301 co_await 10ms;
302 co_return { theInt > 420 };
303 } ();
304 QCOMPARE (GetTaskResult (successful), Result_t { false });
305 }
306
307 void CoroTaskTest::testThrottleSameCoro ()
308 {
309 Throttle t { 10ms };
310 constexpr auto count = 10;
311
312 QElapsedTimer timer;
313 timer.start ();
314 auto task = [] (auto& t) -> Task<int>
315 {
316 int result = 0;
317 for (int i = 0; i < count; ++i)
318 {
319 co_await t;
320 result += i;
321 }
322 co_return result;
323 } (t);
324 const auto result = GetTaskResult (task);
325 const auto time = timer.elapsed ();
326
327 QCOMPARE (result, count * (count - 1) / 2);
328 QCOMPARE_GE (time, count * t.GetInterval ().count ());
329 }
330
331 void CoroTaskTest::testThrottleSameCoroSlow ()
332 {
333 Throttle t { 10ms };
334 constexpr auto count = 10;
335 constexpr static auto intraDelay = 9ms;
336
337 QElapsedTimer timer;
338 timer.start ();
339 auto task = [] (auto& t) -> Task<void>
340 {
341 for (int i = 0; i < count; ++i)
342 {
343 co_await t;
344 if (i != count - 1)
345 co_await Precisely { intraDelay };
346 }
347 } (t);
348 GetTaskResult (task);
349 const auto time = timer.elapsed ();
350
351 const auto expectedMinTime = count * t.GetInterval ().count ();
352 QCOMPARE_GE (time, expectedMinTime);
353
354 const auto delaysTime = (count - 1) * intraDelay.count ();
355 QCOMPARE_LE (time - expectedMinTime, delaysTime / 2);
356 }
357
358 void CoroTaskTest::testThrottleSameCoroVerySlow ()
359 {
360 Throttle t { 10ms };
361 constexpr auto count = 10;
362 constexpr static auto intraDelay = 20ms;
363
364 QElapsedTimer timer;
365 timer.start ();
366 auto task = [] (auto& t) -> Task<void>
367 {
368 for (int i = 0; i < count; ++i)
369 {
370 co_await t;
371 if (i != count - 1)
372 co_await Precisely { intraDelay };
373 }
374 } (t);
375 GetTaskResult (task);
376 const auto time = timer.elapsed ();
377
378 const auto expectedMinTime = (count - 1) * intraDelay.count ();
379 QCOMPARE_GE (time, expectedMinTime);
380
381 const auto throttlesTime = count * t.GetInterval ().count ();
382 QCOMPARE_LE (time - expectedMinTime, throttlesTime / 2);
383 }
384
385 void CoroTaskTest::testThrottleManyCoros ()
386 {
387 Throttle t { 1ms, Qt::TimerType::PreciseTimer };
388 constexpr auto count = 10;
389
390 QElapsedTimer timer;
391 timer.start ();
392 auto mkTask = [] (auto& t) -> Task<void>
393 {
394 for (int i = 0; i < count; ++i)
395 co_await t;
396 };
397 QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
398 for (auto& task : tasks)
399 GetTaskResult (task);
400 const auto time = timer.elapsed ();
401
402 QCOMPARE_GE (time, count * tasks.size () * t.GetInterval ().count ());
403 }
404
405 constexpr auto LongDelay = 500ms;
406 constexpr auto ShortDelay = 10ms;
407 constexpr auto DelayThreshold = std::chrono::duration_cast<std::chrono::milliseconds> ((ShortDelay + LongDelay) / 2);
408
409 void CoroTaskTest::testContextDestrBeforeFinish ()
410 {
411 auto context = std::make_unique<QObject> ();
412 auto task = [] (QObject *context) -> ContextTask<int>
413 {
414 co_await AddContextObject { *context };
415 co_await LongDelay;
416 co_return context->children ().size ();
417 } (&*context);
418 context.reset ();
419
420 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
421 }
422
423 void CoroTaskTest::testContextDestrAfterFinish ()
424 {
425 auto context = std::make_unique<QObject> ();
426 auto task = [] (QObject *context) -> ContextTask<int>
427 {
428 co_await AddContextObject { *context };
429 co_await ShortDelay;
430 co_return context->children ().size ();
431 } (&*context);
432
433 QCOMPARE (GetTaskResult (task), 0);
434 }
435
436 namespace
437 {
438 template<typename... Ts>
439 auto WithContext (auto&& taskGen, Ts&&... taskArgs)
440 {
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 (); });
444 return task;
445 }
446
447 void WithDestroyTimer (auto task)
448 {
449 QElapsedTimer timer;
450 timer.start ();
451 QVERIFY_THROWS_EXCEPTION (LC::Util::ContextDeadException, GetTaskResult (task));
452 QCOMPARE_LT (timer.elapsed (), DelayThreshold.count ());
453 }
454 }
455
456 void CoroTaskTest::testContextDestrDoesntWaitTimer ()
457 {
458 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<void>
459 {
460 co_await AddContextObject { *context };
461 co_await LongDelay;
462 }));
463 }
464
465 void CoroTaskTest::testContextDestrDoesntWaitNetwork ()
466 {
467 const QByteArray data { "this is some test data" };
468 auto nam = std::make_shared<MockNAM> (MkSuccessfulReply (data));
469 QTimer::singleShot (LongDelay,
470 [nam]
471 {
472 if (const auto reply = nam->GetReply ())
473 {
474 reply->setFinished (true);
475 emit reply->finished ();
476 }
477 });
478
479 WithDestroyTimer (WithContext ([] (QObject *context, QNetworkAccessManager *nam) -> ContextTask<QByteArray>
480 {
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 ();
484 }, &*nam));
485 }
486
487 void CoroTaskTest::testContextDestrDoesntWaitProcess ()
488 {
489 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<>
490 {
491 co_await AddContextObject { *context };
492
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) });
496 connect (process,
497 &QProcess::finished,
498 process,
499 &QObject::deleteLater);
500
501 co_await *process;
502 }));
503 }
504
505 void CoroTaskTest::testContextDestrDoesntWaitFuture ()
506 {
507 WithDestroyTimer (WithContext ([] (QObject *context) -> ContextTask<>
508 {
509 co_await AddContextObject { *context };
510 co_await QtConcurrent::run ([] { QThread::sleep (LongDelay); });
511 }));
512 }
513
514 void CoroTaskTest::cleanupTestCase ()
515 {
516 bool done = false;
517 QTimer::singleShot (LongDelay * 2, [&done] { done = true; });
518 QTRY_VERIFY (done);
519 }
520}
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