LeechCraft  0.6.70-16373-g319c272718
Modular cross-platform feature rich live environment.
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 <QtTest>
11 #include <coro.h>
12 #include <coro/context.h>
13 #include <coro/either.h>
14 #include <coro/getresult.h>
15 #include <coro/inparallel.h>
16 #include <coro/networkresult.h>
17 #include <coro/throttle.h>
18 #include <util/sll/qtutil.h>
19 
20 QTEST_GUILESS_MAIN (LC::Util::CoroTaskTest)
21 
22 using namespace std::chrono_literals;
23 
24 namespace 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  QVERIFY (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  std::unique_ptr<MockReply> Reply_;
101  public:
102  explicit MockNAM (std::unique_ptr<MockReply> reply)
103  : Reply_ { std::move (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_.get ();
118  }
119  };
120 
121  auto MkSuccessfulReply (const QByteArray& data)
122  {
123  auto reply = std::make_unique<MockReply> ();
124  reply->setAttribute (QNetworkRequest::HttpStatusCodeAttribute, 200);
125  reply->SetData (data);
126  return reply;
127  }
128 
129  auto MkErrorReply ()
130  {
131  auto reply = std::make_unique<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_EXCEPTION_THROWN (GetTaskResult (task), LC::Util::NetworkReplyErrorException);
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::testContextDestrBeforeFinish ()
204  {
205  auto context = std::make_unique<QObject> ();
206  auto task = [] (QObject *context) -> Task<int, ContextExtensions>
207  {
208  co_await AddContextObject { *context };
209  co_await 10ms;
210  co_return context->children ().size ();
211  } (&*context);
212  context.reset ();
213 
214  QVERIFY_EXCEPTION_THROWN (GetTaskResult (task), LC::Util::ContextDeadException);
215  }
216 
217  void CoroTaskTest::testContextDestrAfterFinish ()
218  {
219  auto context = std::make_unique<QObject> ();
220  auto task = [] (QObject *context) -> Task<int, ContextExtensions>
221  {
222  co_await AddContextObject { *context };
223  co_await 10ms;
224  co_return context->children ().size ();
225  } (&*context);
226 
227  QCOMPARE (GetTaskResult (task), 0);
228  }
229 
230  void CoroTaskTest::testWaitMany ()
231  {
232  constexpr auto max = 100;
233  auto mkTask = [] (int index) -> Task<int>
234  {
235  co_await Precisely { std::chrono::milliseconds { max - index } };
236  co_return index;
237  };
238 
239  QElapsedTimer timer;
240  timer.start ();
241  QVector<Task<int>> tasks;
242  QVector<int> expected;
243  for (int i = 0; i < max; ++i)
244  {
245  tasks << mkTask (i);
246  expected << i;
247  }
248  const auto creationElapsed = timer.elapsed ();
249 
250  timer.restart ();
251  auto result = GetTaskResult (InParallel (std::move (tasks)));
252  const auto executionElapsed = timer.elapsed ();
253 
254  QCOMPARE (result, expected);
255  QVERIFY (creationElapsed < 1);
256  constexpr auto tolerance = 1.05;
257  QVERIFY (executionElapsed < max * tolerance);
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  constexpr auto tolerance = 1.05;
275  QVERIFY (executionElapsed < 10 * tolerance);
276  }
277 
278  void CoroTaskTest::testEither ()
279  {
280  using Result_t = Either<QString, bool>;
281 
282  auto immediatelyFailing = [] () -> Task<Result_t>
283  {
284  const auto theInt = co_await Either<QString, int>::Left ("meh");
285  co_return Result_t::Right (theInt > 420);
286  } ();
287  QCOMPARE (GetTaskResult (immediatelyFailing), Result_t::Left ("meh"));
288 
289  auto earlyFailing = [] () -> Task<Result_t>
290  {
291  const auto theInt = co_await Either<QString, int>::Left ("meh");
292  co_await 10ms;
293  co_return Result_t::Right (theInt > 420);
294  } ();
295  QCOMPARE (GetTaskResult (earlyFailing), Result_t::Left ("meh"));
296 
297  auto successful = [] () -> Task<Result_t>
298  {
299  const auto theInt = co_await Either<QString, int>::Right (42);
300  co_await 10ms;
301  co_return Result_t::Right (theInt > 420);
302  } ();
303  QCOMPARE (GetTaskResult (successful), Result_t::Right (false));
304  }
305 
306  void CoroTaskTest::testThrottleSameCoro ()
307  {
308  Throttle t { 10ms };
309  constexpr auto count = 10;
310 
311  QElapsedTimer timer;
312  timer.start ();
313  auto task = [] (auto& t) -> Task<int>
314  {
315  int result = 0;
316  for (int i = 0; i < count; ++i)
317  {
318  co_await t;
319  result += i;
320  }
321  co_return result;
322  } (t);
323  const auto result = GetTaskResult (task);
324  const auto time = timer.elapsed ();
325 
326  QCOMPARE (result, count * (count - 1) / 2);
327  QVERIFY (time >= count * t.GetInterval ().count ());
328  }
329 
330  void CoroTaskTest::testThrottleSameCoroSlow ()
331  {
332  Throttle t { 10ms };
333  constexpr auto count = 10;
334 
335  QElapsedTimer timer;
336  timer.start ();
337  auto task = [] (auto& t) -> Task<void>
338  {
339  for (int i = 0; i < count; ++i)
340  {
341  co_await t;
342  if (i != count - 1)
343  co_await Precisely { 9ms };
344  }
345  } (t);
346  GetTaskResult (task);
347  const auto time = timer.elapsed ();
348 
349  const auto expectedMinTime = count * t.GetInterval ().count ();
350  QVERIFY (time >= expectedMinTime);
351  QVERIFY (time - expectedMinTime <= expectedMinTime * 0.05);
352  }
353 
354  void CoroTaskTest::testThrottleManyCoros ()
355  {
356  Throttle t { 1ms, Qt::TimerType::PreciseTimer };
357  constexpr auto count = 10;
358 
359  QElapsedTimer timer;
360  timer.start ();
361  auto mkTask = [] (auto& t) -> Task<void>
362  {
363  for (int i = 0; i < count; ++i)
364  co_await t;
365  };
366  QVector tasks { mkTask (t), mkTask (t), mkTask (t) };
367  for (auto& task : tasks)
368  GetTaskResult (task);
369  const auto time = timer.elapsed ();
370 
371  QVERIFY (time >= count * tasks.size () * t.GetInterval ().count ());
372  }
373 }
constexpr detail::AggregateType< detail::AggregateFunction::Count, Ptr > count
Definition: oral.h:965
WithPrecision< Qt::PreciseTimer > Precisely
Definition: timer.h:42
constexpr detail::AggregateType< detail::AggregateFunction::Max, Ptr > max
Definition: oral.h:971
Task< QVector< T >, Exts... > InParallel(QVector< Task< T, Exts... >> tasks)
Definition: inparallel.h:17
T GetTaskResult(Task< T, Extensions... > task)
Definition: getresult.h:18