LeechCraft 0.6.70-19214-g121e5318a7
Modular cross-platform feature rich live environment.
Loading...
Searching...
No Matches
corosignaltest.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 "corosignaltest.h"
10#include <algorithm>
11#include <functional>
12#include <memory>
13#include <optional>
14#include <stdexcept>
15#include <tuple>
16#include <type_traits>
17#include <QThread>
18#include <QTimer>
19#include <QtTest>
20#include <coro.h>
21#include <coro/corocontext.h>
22#include <coro/getresult.h>
23#include <coro/signal.h>
24#include <util/sll/qtutil.h>
25
26QTEST_GUILESS_MAIN (LC::Util::CoroSignalTest)
27
28namespace LC::Util
29{
30 void CoroSignalTest::testNoArgs ()
31 {
32 SignalEmitter emitter;
33 auto task = [] (SignalEmitter& emitter) -> Task<bool>
34 {
35 co_return co_await Signal { emitter, &SignalEmitter::NoArgs };
36 } (emitter);
37
38 emit emitter.NoArgs ();
39 QVERIFY (GetTaskResult (task));
40 }
41
42 void CoroSignalTest::testOneArg ()
43 {
44 SignalEmitter emitter;
45 auto task = [] (SignalEmitter& emitter) -> Task<int>
46 {
47 auto value = co_await Signal { emitter, &SignalEmitter::OneArg };
48 static_assert (std::is_same_v<decltype (value), std::optional<int>>);
49 co_return value.value ();
50 } (emitter);
51
52 emit emitter.OneArg (42);
53 QCOMPARE (GetTaskResult (task), 42);
54 }
55
56 void CoroSignalTest::testTwoArgsYieldTuple ()
57 {
58 SignalEmitter emitter;
59 auto task = [] (SignalEmitter& emitter) -> Task<std::tuple<int, QString>>
60 {
61 auto value = co_await Signal { emitter, &SignalEmitter::TwoArgs };
62 static_assert (std::is_same_v<decltype (value), std::optional<std::tuple<int, QString>>>);
63 co_return value.value ();
64 } (emitter);
65
66 emit emitter.TwoArgs (7, "seven"_qs);
67 const auto& [number, name] = GetTaskResult (task);
68 QCOMPARE (number, 7);
69 QCOMPARE (name, "seven"_qs);
70 }
71
72 void CoroSignalTest::testRefArgYieldsOwnedValue ()
73 {
74 SignalEmitter emitter;
75 auto task = [] (SignalEmitter& emitter) -> Task<QString>
76 {
77 auto value = co_await Signal { emitter, &SignalEmitter::RefArg };
78 static_assert (std::is_same_v<decltype (value), std::optional<QString>>);
79 co_return value.value ();
80 } (emitter);
81
82 emit emitter.RefArg (QString::number (42));
83 QCOMPARE (GetTaskResult (task), "42"_qs);
84 }
85
86 void CoroSignalTest::testOverloaded ()
87 {
88 SignalEmitter emitter;
89 auto task = [] (SignalEmitter& emitter) -> Task<QString>
90 {
91 co_return (co_await Signal { emitter, qOverload<const QString&> (&SignalEmitter::Overloaded) }).value ();
92 } (emitter);
93
94 emit emitter.Overloaded (1);
95 QVERIFY (emitter.IsConnected (qOverload<const QString&> (&SignalEmitter::Overloaded)));
96 emit emitter.Overloaded ("string"_qs);
97 QCOMPARE (GetTaskResult (task), "string"_qs);
98 }
99
100 void CoroSignalTest::testPrivateTagStripped ()
101 {
102 QTimer timer;
103 timer.setSingleShot (true);
104 auto timedOut = [] (QTimer& timer) -> Task<bool>
105 {
106 co_return co_await Signal { timer, &QTimer::timeout };
107 } (timer);
108 timer.start (0);
109 QVERIFY (GetTaskResult (timedOut));
110
111 QObject object;
112 auto renamed = [] (QObject& object) -> Task<QString>
113 {
114 auto name = co_await Signal { object, &QObject::objectNameChanged };
115 static_assert (std::is_same_v<decltype (name), std::optional<QString>>);
116 co_return name.value ();
117 } (object);
118 object.setObjectName ("renamed"_qs);
119 QCOMPARE (GetTaskResult (renamed), "renamed"_qs);
120 }
121
122 void CoroSignalTest::testBaseClassThroughDerived ()
123 {
124 auto emitter = std::make_unique<SignalEmitter> ();
125 auto task = [] (SignalEmitter& emitter) -> Task<std::optional<QObject*>>
126 {
127 co_return co_await Signal { emitter, &QObject::destroyed };
128 } (*emitter);
129
130 QObject *expected = emitter.get ();
131 emitter.reset ();
132 const auto result = GetTaskResult (task);
133 QVERIFY (result.has_value ());
134 QCOMPARE (*result, expected);
135 }
136
137 void CoroSignalTest::testResumesAfterEmitReturns ()
138 {
139 SignalEmitter emitter;
140 int stage = 0;
141 int stageAtResume = -1;
142 auto task = [] (SignalEmitter& emitter, const int& stage, int& stageAtResume) -> Task<void>
143 {
144 co_await Signal { emitter, &SignalEmitter::NoArgs };
145 stageAtResume = stage;
146 } (emitter, stage, stageAtResume);
147
148 emit emitter.NoArgs ();
149 stage = 1;
150 GetTaskResult (task);
151 QCOMPARE (stageAtResume, 1);
152 }
153
154 void CoroSignalTest::testContinuationMayDestroySender ()
155 {
156 auto emitter = std::make_unique<SignalEmitter> ();
157 auto task = [] (std::unique_ptr<SignalEmitter>& emitter) -> Task<void>
158 {
159 co_await Signal { *emitter, &SignalEmitter::NoArgs };
160 emitter.reset ();
161 } (emitter);
162
163 emit emitter->NoArgs ();
164 QVERIFY (emitter);
165 GetTaskResult (task);
166 QVERIFY (!emitter);
167 }
168
169 void CoroSignalTest::testFirstEmissionWins ()
170 {
171 SignalEmitter emitter;
172 int resumes = 0;
173 auto task = [] (SignalEmitter& emitter, int& resumes) -> Task<int>
174 {
175 const auto value = (co_await Signal { emitter, &SignalEmitter::OneArg }).value ();
176 ++resumes;
177 co_return value;
178 } (emitter, resumes);
179
180 emit emitter.OneArg (1);
181 emit emitter.OneArg (2);
182 QCOMPARE (GetTaskResult (task), 1);
183
184 emit emitter.OneArg (3);
185 QTest::qWait (10);
186 QCOMPARE (resumes, 1);
187 }
188
189 void CoroSignalTest::testConnectedOnlyWhileSuspended ()
190 {
191 SignalEmitter emitter;
192 QVERIFY (!emitter.IsConnected (&SignalEmitter::OneArg));
193 QVERIFY (!emitter.IsConnected (&QObject::destroyed));
194
195 auto task = [] (SignalEmitter& emitter) -> Task<int>
196 {
197 co_return (co_await Signal { emitter, &SignalEmitter::OneArg }).value ();
198 } (emitter);
199 QVERIFY (emitter.IsConnected (&SignalEmitter::OneArg));
200 QVERIFY (emitter.IsConnected (&QObject::destroyed));
201
202 emit emitter.OneArg (5);
203 QCOMPARE (GetTaskResult (task), 5);
204 QVERIFY (!emitter.IsConnected (&SignalEmitter::OneArg));
205 QVERIFY (!emitter.IsConnected (&QObject::destroyed));
206 }
207
208 void CoroSignalTest::testManyAwaitersOneEmission ()
209 {
210 SignalEmitter emitter;
211 const auto awaitOneArg = [] (SignalEmitter& emitter) -> Task<int>
212 {
213 co_return (co_await Signal { emitter, &SignalEmitter::OneArg }).value ();
214 };
215 auto first = awaitOneArg (emitter);
216 auto second = awaitOneArg (emitter);
217
218 emit emitter.OneArg (9);
219 QCOMPARE (GetTaskResult (first), 9);
220 QCOMPARE (GetTaskResult (second), 9);
221 }
222
223 void CoroSignalTest::testReawaitSame ()
224 {
225 SignalEmitter emitter;
226 int counter = 0;
227 QTimer ticker;
228 ticker.callOnTimeout ([&] { emit emitter.OneArg (++counter); });
229 ticker.start (0);
230
231 auto task = [] (SignalEmitter& emitter) -> Task<QList<int>>
232 {
233 const Signal signal { emitter, &SignalEmitter::OneArg };
234 QList<int> values;
235 while (values.size () < 3)
236 values << (co_await signal).value ();
237 co_return values;
238 } (emitter);
239
240 const auto values = GetTaskResult (task);
241 ticker.stop ();
242 QCOMPARE (values.size (), 3);
243 QVERIFY (std::ranges::adjacent_find (values, std::greater_equal {}) == values.end ());
244 }
245
246 void CoroSignalTest::testHandlerResultIsAwaited ()
247 {
248 SignalEmitter emitter;
249 auto task = [] (SignalEmitter& emitter) -> Task<double>
250 {
251 auto value = co_await Signal { emitter, &SignalEmitter::OneArg, [] (int value) { return value / 2.0; } };
252 static_assert (std::is_same_v<decltype (value), std::optional<double>>);
253 co_return value.value ();
254 } (emitter);
255
256 emit emitter.OneArg (21);
257 QCOMPARE (GetTaskResult (task), 10.5);
258 }
259
260 void CoroSignalTest::testHandlerRunsInsideEmit ()
261 {
262 SignalEmitter emitter;
263 int stage = 0;
264 int stageInHandler = -1;
265 int stageAtResume = -1;
266 auto task = [] (SignalEmitter& emitter, const int& stage, int& stageInHandler, int& stageAtResume) -> Task<void>
267 {
268 co_await Signal { emitter, &SignalEmitter::NoArgs, [&] { stageInHandler = stage; return true; } };
269 stageAtResume = stage;
270 } (emitter, stage, stageInHandler, stageAtResume);
271
272 emit emitter.NoArgs ();
273 stage = 1;
274 GetTaskResult (task);
275 QCOMPARE (stageInHandler, 0);
276 QCOMPARE (stageAtResume, 1);
277 }
278
279 void CoroSignalTest::testHandlerSnapshotsDyingSender ()
280 {
281 auto emitter = std::make_unique<SignalEmitter> ();
282 emitter->setObjectName ("snapshot"_qs);
283 auto task = [] (SignalEmitter& emitter) -> Task<std::optional<QString>>
284 {
285 co_return co_await Signal { emitter, &SignalEmitter::NoArgs, [&emitter] { return emitter.objectName (); } };
286 } (*emitter);
287
288 emit emitter->NoArgs ();
289 emitter.reset ();
290 const auto result = GetTaskResult (task);
291 QVERIFY (result.has_value ());
292 QCOMPARE (*result, "snapshot"_qs);
293 }
294
295 void CoroSignalTest::testHandlerTupleArgs ()
296 {
297 SignalEmitter emitter;
298 auto task = [] (SignalEmitter& emitter) -> Task<QString>
299 {
300 const auto handler = [] (int number, const QString& name) { return name + u':' + QString::number (number); };
301 co_return (co_await Signal { emitter, &SignalEmitter::TwoArgs, handler }).value ();
302 } (emitter);
303
304 emit emitter.TwoArgs (7, "seven"_qs);
305 QCOMPARE (GetTaskResult (task), "seven:7"_qs);
306 }
307
308 void CoroSignalTest::testHandlerPrivateTagStripped ()
309 {
310 QTimer timer;
311 timer.setSingleShot (true);
312 auto timedOut = [] (QTimer& timer) -> Task<std::optional<int>>
313 {
314 co_return co_await Signal { timer, &QTimer::timeout, [] { return 42; } };
315 } (timer);
316 timer.start (0);
317 QCOMPARE (GetTaskResult (timedOut).value (), 42);
318
319 QObject object;
320 auto renamed = [] (QObject& object) -> Task<QString>
321 {
322 co_return (co_await Signal { object, &QObject::objectNameChanged, [] (const QString& name) { return name.toUpper (); } }).value ();
323 } (object);
324 object.setObjectName ("renamed"_qs);
325 QCOMPARE (GetTaskResult (renamed), "RENAMED"_qs);
326 }
327
328 void CoroSignalTest::testHandlerRunsOnce ()
329 {
330 SignalEmitter emitter;
331 int calls = 0;
332 auto task = [] (SignalEmitter& emitter, int& calls) -> Task<int>
333 {
334 co_return (co_await Signal { emitter, &SignalEmitter::OneArg, [&calls] (int value) { ++calls; return value; } }).value ();
335 } (emitter, calls);
336
337 emit emitter.OneArg (1);
338 emit emitter.OneArg (2);
339 QCOMPARE (GetTaskResult (task), 1);
340 QCOMPARE (calls, 1);
341 }
342
343 void CoroSignalTest::testHandlerSenderDeathYieldsNullopt ()
344 {
345 auto emitter = std::make_unique<SignalEmitter> ();
346 bool handlerRan = false;
347 auto task = [] (SignalEmitter& emitter, bool& handlerRan) -> Task<std::optional<int>>
348 {
349 co_return co_await Signal { emitter, &SignalEmitter::OneArg, [&handlerRan] (int value) { handlerRan = true; return value; } };
350 } (*emitter, handlerRan);
351
352 emitter.reset ();
353 QVERIFY (!GetTaskResult (task).has_value ());
354 QVERIFY (!handlerRan);
355 }
356
357 void CoroSignalTest::testHandlerExceptionRethrownAtAwait ()
358 {
359 auto emitter = std::make_unique<SignalEmitter> ();
360 int resumes = 0;
361 QString caught;
362 auto task = [] (SignalEmitter& emitter, int& resumes, QString& caught) -> Task<void>
363 {
364 try
365 {
366 co_await Signal { emitter, &SignalEmitter::OneArg, [] (int) -> int { throw std::runtime_error { "boom" }; } };
367 }
368 catch (const std::runtime_error& e)
369 {
370 caught = QString::fromUtf8 (e.what ());
371 }
372 ++resumes;
373 } (*emitter, resumes, caught);
374
375 emit emitter->OneArg (1);
376 emitter.reset ();
377 GetTaskResult (task);
378 QCOMPARE (caught, "boom"_qs);
379 QTest::qWait (10);
380 QCOMPARE (resumes, 1);
381 }
382
383 void CoroSignalTest::testHandlerCopiedPerAwait ()
384 {
385 SignalEmitter emitter;
386 QTimer ticker;
387 ticker.callOnTimeout ([&] { emit emitter.OneArg (10); });
388 ticker.start (0);
389
390 auto task = [] (SignalEmitter& emitter) -> Task<QList<int>>
391 {
392 const Signal signal { emitter, &SignalEmitter::OneArg, [calls = 0] (int value) mutable { return value + calls++; } };
393 QList<int> values;
394 while (values.size () < 3)
395 values << (co_await signal).value ();
396 co_return values;
397 } (emitter);
398
399 const auto values = GetTaskResult (task);
400 ticker.stop ();
401 QCOMPARE (values, (QList<int> { 10, 10, 10 }));
402 }
403
404 void CoroSignalTest::testSenderDeathYieldsNullopt ()
405 {
406 auto emitter = std::make_unique<SignalEmitter> ();
407 bool resumed = false;
408 auto noArgs = [] (SignalEmitter& emitter) -> Task<bool>
409 {
410 co_return co_await Signal { emitter, &SignalEmitter::NoArgs };
411 } (*emitter);
412 auto oneArg = [] (SignalEmitter& emitter, bool& resumed) -> Task<std::optional<int>>
413 {
414 const auto value = co_await Signal { emitter, &SignalEmitter::OneArg };
415 resumed = true;
416 co_return value;
417 } (*emitter, resumed);
418 auto twoArgs = [] (SignalEmitter& emitter) -> Task<std::optional<std::tuple<int, QString>>>
419 {
420 co_return co_await Signal { emitter, &SignalEmitter::TwoArgs };
421 } (*emitter);
422
423 emitter.reset ();
424 QVERIFY (!resumed);
425
426 QVERIFY (!GetTaskResult (noArgs));
427 QVERIFY (!GetTaskResult (oneArg).has_value ());
428 QVERIFY (!GetTaskResult (twoArgs).has_value ());
429 }
430
431 void CoroSignalTest::testEmissionBeforeDeathKeepsValue ()
432 {
433 auto emitter = std::make_unique<SignalEmitter> ();
434 int resumes = 0;
435 auto task = [] (SignalEmitter& emitter, int& resumes) -> Task<std::optional<int>>
436 {
437 const auto value = co_await Signal { emitter, &SignalEmitter::OneArg };
438 ++resumes;
439 co_return value;
440 } (*emitter, resumes);
441
442 emit emitter->OneArg (5);
443 emitter.reset ();
444
445 const auto result = GetTaskResult (task);
446 QVERIFY (result.has_value ());
447 QCOMPARE (*result, 5);
448 QTest::qWait (10);
449 QCOMPARE (resumes, 1);
450 }
451
452 void CoroSignalTest::testContextDeathDisconnects ()
453 {
454 SignalEmitter emitter;
455 auto context = std::make_unique<CoroContext> ();
456 auto task = [] (CoroContext *context, SignalEmitter& emitter) -> ContextTask<int>
457 {
458 co_await AddContext { *context };
459 co_return (co_await Signal { emitter, &SignalEmitter::OneArg }).value ();
460 } (&*context, emitter);
461 QVERIFY (emitter.IsConnected (&SignalEmitter::OneArg));
462
463 context.reset ();
464 QVERIFY (!emitter.IsConnected (&SignalEmitter::OneArg));
465 QVERIFY (!emitter.IsConnected (&QObject::destroyed));
466
467 emit emitter.OneArg (42);
468 QVERIFY_THROWS_EXCEPTION (ContextDeadException, GetTaskResult (task));
469 }
470
471 void CoroSignalTest::testEmittedFromAnotherThread ()
472 {
473 QThread worker;
474 worker.start ();
475
476 SignalEmitter emitter;
477 emitter.moveToThread (&worker);
478
479 QThread *resumedOn = nullptr;
480 auto task = [] (SignalEmitter& emitter, QThread*& resumedOn) -> Task<int>
481 {
482 const auto value = (co_await Signal { emitter, &SignalEmitter::OneArg }).value ();
483 resumedOn = QThread::currentThread ();
484 co_return value;
485 } (emitter, resumedOn);
486
487 const auto mainThread = QThread::currentThread ();
488 QThread *emittedOn = nullptr;
489 QMetaObject::invokeMethod (&emitter,
490 [&]
491 {
492 emittedOn = QThread::currentThread ();
493 emit emitter.OneArg (11);
494 emitter.moveToThread (mainThread);
495 },
496 Qt::QueuedConnection);
497
498 QCOMPARE (GetTaskResult (task), 11);
499 QCOMPARE (resumedOn, mainThread);
500
501 worker.quit ();
502 QVERIFY (worker.wait ());
503 QCOMPARE (emittedOn, &worker);
504 }
505}
void RefArg(const QString &)
void TwoArgs(int, QString)
Task< R, ContextExtension > ContextTask
Definition taskfwd.h:20
Signal(Derived &, void(Base::*)(SignalArgs...)) -> Signal< Base, detail::TuplePacker, void(Base::*)(SignalArgs...), Typelist< SignalArgs... > >
T GetTaskResult(Task< T, Extensions... > task)
Definition getresult.h:19