LeechCraft 0.6.70-19214-g121e5318a7
Modular cross-platform feature rich live environment.
Loading...
Searching...
No Matches
corochanneltest.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 "corochanneltest.h"
10#include <memory>
11#include <QtConcurrentRun>
12#include <QtTest>
13#include "coro.h"
14#include "coro/channel.h"
15#include "coro/channelutils.h"
16#include "coro/getresult.h"
17#include "coro/threadsafety.h"
18
19QTEST_GUILESS_MAIN (LC::Util::CoroChannelTest)
20
21namespace LC::Util
22{
23 void CoroChannelTest::testSingleRecv ()
24 {
25 using namespace std::chrono_literals;
26
27 constexpr auto producersCount = 32;
28 constexpr auto repCount = 100;
29 constexpr auto sleepLength = 1ms;
30
31 Channel<int> ch;
32
33 std::vector<std::thread> threads;
34 std::atomic_int expected;
35 for (int i = 0; i < producersCount; ++i)
36 threads.emplace_back ([&, i]
37 {
38 for (int j = 0; j < repCount; ++j)
39 {
40 const auto val = j * producersCount + i;
41 ch.Send (val);
42 expected.fetch_add (val, std::memory_order::relaxed);
43 std::this_thread::sleep_for (sleepLength);
44 }
45 });
46
47 auto mainThread = std::this_thread::get_id ();
48 auto reader = [] (auto mainThread, Channel<int> *ch) -> Task<int, ThreadSafetyExtension>
49 {
50 int sum = 0;
51 while (auto next = co_await ch->Receive ())
52 {
53 [=]
54 {
55 auto thisThread = std::this_thread::get_id ();
56 QCOMPARE (thisThread, mainThread);
57 } ();
58
59 sum += *next;
60 }
61 co_return sum;
62 } (mainThread, &ch);
63
64 for (auto& thread : threads)
65 thread.join ();
66
67 ch.Close ();
68
69 auto result = GetTaskResult (reader);
70 QCOMPARE (result, expected);
71 }
72
73 void CoroChannelTest::testManyRecvs ()
74 {
75 using namespace std::chrono_literals;
76
77 constexpr auto producersCount = 32;
78 constexpr auto consumersCount = 8;
79 constexpr auto repCount = 100;
80 constexpr auto sleepLength = 1ms;
81
82 Channel<int> ch;
83
84 std::vector<std::thread> producers;
85 std::atomic_int expected;
86 for (int i = 0; i < producersCount; ++i)
87 producers.emplace_back ([&, i]
88 {
89 for (int j = 0; j < repCount; ++j)
90 {
91 const auto val = j * producersCount + i;
92 ch.Send (val);
93 expected.fetch_add (val, std::memory_order::relaxed);
94 std::this_thread::sleep_for (sleepLength);
95 }
96 });
97
98 std::atomic_int sum;
99 std::vector<std::thread> consumers;
100 for (int i = 0; i < consumersCount; ++i)
101 consumers.emplace_back ([&]
102 {
103 auto reader = [] (Channel<int> *ch) -> Task<int, ThreadSafetyExtension>
104 {
105 int sum = 0;
106 while (auto next = co_await ch->Receive ())
107 sum += *next;
108 co_return sum;
109 } (&ch);
110 sum.fetch_add (GetTaskResult (reader));
111 });
112
113 for (auto& producer : producers)
114 producer.join ();
115
116 ch.Close ();
117
118 for (auto& consumer : consumers)
119 consumer.join ();
120
121 QCOMPARE (sum, expected);
122 }
123
124 void CoroChannelTest::testSingleThreaded ()
125 {
126 constexpr auto iterations = 1000;
127
128 Channel<int> ch;
129
130 auto reader = [] (Channel<int> *ch) -> Task<int, ThreadSafetyExtension>
131 {
132 int sum = 0;
133 while (auto next = co_await ch->Receive ())
134 sum += *next;
135 co_return sum;
136 } (&ch);
137
138 int expected = 0;
139 for (int i = 0; i < iterations; ++i)
140 {
141 expected += i;
142 ch.Send (i);
143 }
144
145 ch.Close ();
146
147 const auto result = GetTaskResult (reader);
148 QCOMPARE (result, expected);
149 }
150
151 void CoroChannelTest::testSingleThreadedTimered ()
152 {
153 using namespace std::chrono_literals;
154
155 constexpr auto iterations = 100;
156 constexpr auto interval = 1ms;
157
158 Channel<int> ch;
159
160 auto reader = [] (Channel<int> *ch) -> Task<int, ThreadSafetyExtension>
161 {
162 int sum = 0;
163 while (auto next = co_await ch->Receive ())
164 sum += *next;
165 co_return sum;
166 } (&ch);
167
168 int expected = 0;
169
170 QTimer timer;
171 timer.callOnTimeout ([&, i = 0] mutable
172 {
173 expected += i;
174 ch.Send (i);
175 if (++i == iterations)
176 {
177 timer.stop ();
178 ch.Close ();
179 }
180 });
181 timer.start (interval);
182
183 const auto result = GetTaskResult (reader);
184 QCOMPARE (result, expected);
185 }
186
187 void CoroChannelTest::testMerge ()
188 {
189 using namespace std::chrono_literals;
190
191 constexpr auto numChannels = 100;
192 constexpr auto iterations = 100;
193 constexpr auto interval = 1ms;
194
195 QVector<Channel_ptr<int>> channels;
196 std::generate_n (std::back_inserter (channels), numChannels, [] { return std::make_shared<Channel<int>> (); });
197
198 auto merged = MergeChannels (channels);
199
200 auto reader = [] (Channel<int> *ch) -> Task<int, ThreadSafetyExtension>
201 {
202 int sum = 0;
203 while (auto next = co_await ch->Receive ())
204 sum += *next;
205 co_return sum;
206 } (merged.get ());
207
208 int expected = 0;
209
210 QTimer timer;
211 timer.callOnTimeout ([&, i = 0] mutable
212 {
213 for (int j = 0; j < numChannels; ++j)
214 {
215 auto value = i * numChannels + j;
216 expected += value;
217 channels [j]->Send (value);
218 }
219 if (++i == iterations)
220 {
221 timer.stop ();
222 for (auto chan : channels)
223 chan->Close ();
224 }
225 });
226 timer.start (interval);
227
228 const auto result = GetTaskResult (reader);
229 QCOMPARE (result, expected);
230 }
231
232 namespace
233 {
234 using Event = CoroChannelTest::Event;
235
236 Task<void> Receiver (Channel<int>& ch, QList<Event>& log)
237 {
238 log << (co_await ch ? Event::Received : Event::ReceivedEnd);
239 }
240 }
241
242 void CoroChannelTest::testSendDefersWakeup ()
243 {
244 Channel<int> ch;
245 QList<Event> log;
246 auto reader = Receiver (ch, log);
247 ch.Send (42);
248 log << Event::Sent;
249 GetTaskResult (reader);
250 QCOMPARE (log, (QList { Event::Sent, Event::Received }));
251 }
252
253 void CoroChannelTest::testCloseDefersWakeup ()
254 {
255 Channel<int> ch;
256 QList<Event> log;
257 auto reader = Receiver (ch, log);
258 ch.Close ();
259 log << Event::Closed;
260 GetTaskResult (reader);
261 QCOMPARE (log, (QList { Event::Closed, Event::ReceivedEnd }));
262 }
263
264 namespace
265 {
266 ContextTask<void> Doomed (CoroContext& context, Channel<int>& ch, QList<int>& received)
267 {
268 co_await AddContext { context };
269 if (const auto value = co_await ch)
270 received << *value;
271 }
272
273 Task<void> Survivor (Channel<int>& ch, QList<int>& received)
274 {
275 while (const auto value = co_await ch)
276 received << *value;
277 }
278 }
279
280 void CoroChannelTest::testCancelledReceiverDoesntTakeValue ()
281 {
282 Channel<int> ch;
283 auto context = std::make_unique<CoroContext> ();
284 QList<int> doomedReceived;
285 auto doomed = Doomed (*context, ch, doomedReceived);
286 ch.Send (42);
287 context.reset ();
288
289 QList<int> survivorReceived;
290 auto survivor = Survivor (ch, survivorReceived);
291 ch.Close ();
292
293 QVERIFY_THROWS_EXCEPTION (ContextDeadException, GetTaskResult (doomed));
294 GetTaskResult (survivor);
295 QVERIFY (doomedReceived.isEmpty ());
296 QCOMPARE (survivorReceived, (QList { 42 }));
297 }
298
299 void CoroChannelTest::testCancelledReceiverDoesntStrandValueBehindClose ()
300 {
301 Channel<int> ch;
302 auto context = std::make_unique<CoroContext> ();
303 QList<int> doomedReceived;
304 QList<int> survivorReceived;
305 auto doomed = Doomed (*context, ch, doomedReceived);
306 auto survivor = Survivor (ch, survivorReceived);
307 ch.Send (42);
308 ch.Close ();
309 context.reset ();
310
311 QVERIFY_THROWS_EXCEPTION (ContextDeadException, GetTaskResult (doomed));
312 GetTaskResult (survivor);
313 QVERIFY (doomedReceived.isEmpty ());
314 QCOMPARE (survivorReceived, (QList { 42 }));
315 }
316
317 namespace
318 {
319 struct Producer : QObject
320 {
321 Channel<int> Ch_;
322 bool InCall_ = false;
323
324 ContextTask<void> SendOne ()
325 {
326 co_await AddContext { CoroContext_ };
327 InCall_ = true;
328 Ch_.Send (42);
329 InCall_ = false;
330 }
331
332 ContextTask<void> CloseChannel ()
333 {
334 co_await AddContext { CoroContext_ };
335 InCall_ = true;
336 Ch_.Close ();
337 InCall_ = false;
338 }
339
341 };
342
343 Task<bool> DestroyingReceiver (std::unique_ptr<Producer>& producer)
344 {
345 co_await producer->Ch_;
346 const auto inCall = producer->InCall_;
347 if (!inCall)
348 producer.reset ();
349 co_return inCall;
350 }
351 }
352
353 void CoroChannelTest::testReceiverMayDestroyProducerOnSend ()
354 {
355 auto producer = std::make_unique<Producer> ();
356 auto reader = DestroyingReceiver (producer);
357 producer->SendOne ();
358 QVERIFY (!GetTaskResult (reader));
359 QVERIFY (!producer);
360 }
361
362 void CoroChannelTest::testReceiverMayDestroyProducerOnClose ()
363 {
364 auto producer = std::make_unique<Producer> ();
365 auto reader = DestroyingReceiver (producer);
366 producer->CloseChannel ();
367 QVERIFY (!GetTaskResult (reader));
368 QVERIFY (!producer);
369 }
370}
#define LC_CORO_CONTEXT
Declares the coroutine context of the enclosing QObject-derived class.
Definition corocontext.h:93
Q_DECL_IMPORT const QString Receiver
T GetTaskResult(Task< T, Extensions... > task)
Definition getresult.h:19
Channel_ptr< T > MergeChannels(QVector< Channel_ptr< T > > channels)