forked from luncliff/coroutine
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathc2_yield.cpp
More file actions
263 lines (233 loc) · 8.13 KB
/
c2_yield.cpp
File metadata and controls
263 lines (233 loc) · 8.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
//
// Author : github.com/luncliff (luncliff@gmail.com)
// License : CC BY 4.0
//
#include <coroutine/return.h>
#include <coroutine/yield.hpp>
#include <catch2/catch.hpp>
#include <gsl/gsl>
using namespace std;
using namespace std::experimental;
using namespace coro;
auto enumerable_yield_never() -> enumerable<int> {
// co_return is necessary so compiler can notice that
// this is a coroutine when there is no co_yield.
co_return;
};
TEST_CASE("generator yield never", "[generic]") {
auto count = 0u;
for (auto v : enumerable_yield_never())
count += 1;
REQUIRE(count == 0);
}
auto enumerable_yield_once(int value = 0) -> enumerable<int> {
// co_return is necessary so compiler can notice that
// this is a coroutine when there is no co_yield.
co_yield value;
co_return;
};
TEST_CASE("generator yield once", "[generic]") {
auto count = 0u;
SECTION("range for") {
for (auto v : enumerable_yield_once()) {
REQUIRE(v == 0);
count += 1;
}
}
SECTION("iterator") {
auto g = enumerable_yield_once();
auto b = g.begin();
auto e = g.end();
for (auto it = b; it != e; ++it) {
REQUIRE(*it == 0);
count += 1;
}
}
SECTION("move semantics") {
auto g = enumerable_yield_once();
auto m = std::move(g);
// g lost its handle. so it is not iterable anymore
REQUIRE(g.begin() == g.end());
for (auto v : g)
FAIL("null generator won't go into loop");
for (auto v : m) {
REQUIRE(v == 0);
count += 1;
}
}
REQUIRE(count > 0);
}
auto enumerable_yield_until_zero(int n) -> enumerable<int> {
while (n-- > 0)
co_yield n;
};
TEST_CASE("generator iterator accumulate", "[generic]") {
auto g = enumerable_yield_until_zero(10);
auto total = accumulate(g.begin(), g.end(), 0u);
REQUIRE(total == 45); // 0 - 10
}
template <typename T, size_t Sz>
auto enumerable_yield_with_container(array<T, Sz>& storage) -> enumerable<T> {
for (auto& ref : storage)
co_yield ref;
};
TEST_CASE("generator iterator max_element", "[generic]") {
// since generator is not a container,
// using max_element (or min_element) function on it
// will return invalid iterator
auto id = 15u;
array<uint32_t, 10> storage{};
for (auto& e : storage)
e = id--; // [15, 14, 13 ...
// so the first element will hold the largest number
auto g = enumerable_yield_with_container(storage);
auto it = max_element(g.begin(), g.end());
// after iteration is finished (`co_return`),
// the iterator will hold nullptr.
REQUIRE(it.operator->() == nullptr);
// so referencing it will lead to access violation
// REQUIRE(*it == 15);
}
auto sequence_yield_never() -> sequence<int> {
co_return;
}
TEST_CASE("async generator yield never", "[generic]") {
auto use_async_gen = [](int& ref) -> frame {
// clang-format off
for co_await(auto v : sequence_yield_never())
ref = v;
// clang-format on
};
frame f1{}; // frame holder
auto no_frame_leak = gsl::finally([&]() {
if (f1.address() != nullptr)
f1.destroy();
});
int storage = -1;
// since there was no yield, it will remain unchanged
f1 = use_async_gen(storage);
REQUIRE(storage == -1);
}
auto sequence_suspend_and_return(frame& fh) -> sequence<int> {
co_yield fh; // save current coroutine to the `frame`
// the `frame` type inherits `suspend_always`
co_return; // return without yield
}
TEST_CASE("async generator suspend and return", "[generic]") {
auto use_async_gen = [](int& ref, frame& fs) -> frame {
// clang-format off
for co_await(auto v : sequence_suspend_and_return(fs))
ref = v;
// clang-format on
ref = 2; // change the value after iteration
};
frame fc{}, fs{}; // frame of caller, frame of sequence
auto no_frame_leak = gsl::finally([&]() {
// notice that `sequence<int>` is in the caller's frame,
// therefore, by destroying caller, sequence<int> will be destroyed
// automatically. so accessing to the frame will lead to violation
// just destroy caller coroutine's frame
fc.destroy();
});
int storage = -1;
// since there was no yield, it will remain unchanged
fc = use_async_gen(storage, fs);
REQUIRE(storage == -1);
// however, when the sequence coroutine is resumed,
// its caller will continue on ...
REQUIRE(fs.address() != nullptr);
fs.resume(); // sequence coroutine will be resumed
// and it will resume `use_async_gen`.
REQUIRE(fs.done()); // since `use_async_gen` will return after that,
REQUIRE(fc.done()); // both coroutine will reach *final suspended* state
REQUIRE(storage == 2);
}
auto sequence_yield_once(int value = 0) -> sequence<int> {
co_yield value;
co_return;
}
TEST_CASE("async generator yield once", "[generic]") {
auto use_async_gen = [](int& ref) -> frame {
// clang-format off
for co_await(auto v : sequence_yield_once(0xBEE))
ref = v;
// clang-format on
};
frame fc{}; // frame of caller
auto no_frame_leak = gsl::finally([&]() {
if (fc.address() != nullptr)
fc.destroy();
});
int storage = -1;
fc = use_async_gen(storage);
REQUIRE(storage == 0xBEE); // storage will receive value
}
auto sequence_yield_suspend_yield_1(frame& manual_resume) -> sequence<int> {
auto value = 0;
co_yield value = 1;
co_await manual_resume;
co_yield value = 2;
}
auto sequence_yield_suspend_yield_2(frame& manual_resume) -> sequence<int> {
auto value = 0;
co_yield value = 1;
co_yield manual_resume; // use `co_yield` instead of `co_await`
co_yield value = 2;
}
TEST_CASE("async generator yield suspend yield", "[generic]") {
frame fc{}, fs{}; // frame of caller, frame of sequence
auto no_frame_leak = gsl::finally([&]() {
// see `sequence_suspend_and_return`'s test case
if (fc.address() != nullptr)
fc.destroy();
});
int storage = -1;
SECTION("case 1") {
auto use_async_gen = [](int& ref, frame& fh) -> frame {
// clang-format off
for co_await(auto v : sequence_yield_suspend_yield_1(fh))
ref = v;
// clang-format on
};
fc = use_async_gen(storage, fs);
}
SECTION("case 2") {
auto use_async_gen = [](int& ref, frame& fh) -> frame {
// clang-format off
for co_await(auto v : sequence_yield_suspend_yield_2(fh))
ref = v;
// clang-format on
};
fc = use_async_gen(storage, fs);
}
REQUIRE(fc.done() == false); // we didn't finished iteration
REQUIRE(fs.done() == false); // co_await manual_resume
REQUIRE(storage == 1); // co_yield value = 1
fs.resume(); // continue after co_await manual_resume;
REQUIRE(storage == 2); // co_yield value = 2;
REQUIRE(fc.done()); // both are finished
REQUIRE(fs.done());
}
TEST_CASE("async generator destroy when suspended", "[generic]") {
auto use_async_gen = [](int* ptr, frame& fh) -> frame {
auto defer = gsl::finally([=]() {
*ptr = 0xDEAD; // set the value in destruction phase
});
// clang-format off
for co_await(auto v : sequence_yield_suspend_yield_1(fh))
*ptr = v;
// clang-format on
};
int storage = -1;
frame fs{}; // frame of sequence
auto fc = use_async_gen(&storage, fs);
REQUIRE(fs.done() == false); // it is suspended. of course false !
REQUIRE(storage == 1); // co_yield value = 1
fc.destroy();
// as mentioned in `sequence_suspend_and_return`, destruction of `fs`
// will lead access violation in caller frame destruction.
// however, it is still safe to destroy caller frame
// and it won't be a resource leak
// since the step also destroys frame of sequence
REQUIRE(storage == 0xDEAD); // see gsl::finally
}