coroutine.test.cc (13465:dee578a46d87) coroutine.test.cc (14067:2c3667b32607)
1/*
2 * Copyright (c) 2018 ARM Limited
3 * All rights reserved
4 *
5 * The license below extends only to copyright in the software and shall
6 * not be construed as granting a license to any other intellectual
7 * property including but not limited to intellectual property relating
8 * to a hardware implementation of the functionality of the software
9 * licensed hereunder. You may use the software subject to the license
10 * terms below provided that you ensure that this notice is replicated
11 * unmodified and in its entirety in all distributions of the software,
12 * modified or unmodified, in source code or in binary form.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are
16 * met: redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer;
18 * redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution;
21 * neither the name of the copyright holders nor the names of its
22 * contributors may be used to endorse or promote products derived from
23 * this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 *
37 * Authors: Giacomo Travaglini
38 */
39
40#include <gtest/gtest.h>
41
42#include "base/coroutine.hh"
43
44using namespace m5;
45
46/**
1/*
2 * Copyright (c) 2018 ARM Limited
3 * All rights reserved
4 *
5 * The license below extends only to copyright in the software and shall
6 * not be construed as granting a license to any other intellectual
7 * property including but not limited to intellectual property relating
8 * to a hardware implementation of the functionality of the software
9 * licensed hereunder. You may use the software subject to the license
10 * terms below provided that you ensure that this notice is replicated
11 * unmodified and in its entirety in all distributions of the software,
12 * modified or unmodified, in source code or in binary form.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are
16 * met: redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer;
18 * redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution;
21 * neither the name of the copyright holders nor the names of its
22 * contributors may be used to endorse or promote products derived from
23 * this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 *
37 * Authors: Giacomo Travaglini
38 */
39
40#include <gtest/gtest.h>
41
42#include "base/coroutine.hh"
43
44using namespace m5;
45
46/**
47 * This test is checking if the Coroutine, once it's created
48 * it doesn't start since the second argument of the constructor
49 * (run_coroutine) is set to false
50 */
51TEST(Coroutine, Unstarted)
52{
53 auto yielding_task =
54 [] (Coroutine<void, void>::CallerType& yield)
55 {
56 yield();
57 };
58
59 const bool start_upon_creation = false;
60 Coroutine<void, void> coro(yielding_task, start_upon_creation);
61
62 ASSERT_FALSE(coro.started());
63}
64
65/**
47 * This test is checking if the Coroutine, once it yields
48 * back to the caller, it is still marked as not finished.
49 */
50TEST(Coroutine, Unfinished)
51{
52 auto yielding_task =
53 [] (Coroutine<void, void>::CallerType& yield)
54 {
55 yield();
56 };
57
58 Coroutine<void, void> coro(yielding_task);
59 ASSERT_TRUE(coro);
60}
61
62/**
63 * This test is checking the parameter passing interface of a
64 * coroutine which takes an integer as an argument.
65 * Coroutine::operator() and CallerType::get() are the tested
66 * APIS.
67 */
68TEST(Coroutine, Passing)
69{
70 const std::vector<int> input{ 1, 2, 3 };
71 const std::vector<int> expected_values = input;
72
73 auto passing_task =
74 [&expected_values] (Coroutine<int, void>::CallerType& yield)
75 {
76 int argument;
77
78 for (const auto expected : expected_values) {
79 argument = yield.get();
80 ASSERT_EQ(argument, expected);
81 }
82 };
83
84 Coroutine<int, void> coro(passing_task);
85 ASSERT_TRUE(coro);
86
87 for (const auto val : input) {
88 coro(val);
89 }
90}
91
92/**
93 * This test is checking the yielding interface of a coroutine
94 * which takes no argument and returns integers.
95 * Coroutine::get() and CallerType::operator() are the tested
96 * APIS.
97 */
98TEST(Coroutine, Returning)
99{
100 const std::vector<int> output{ 1, 2, 3 };
101 const std::vector<int> expected_values = output;
102
103 auto returning_task =
104 [&output] (Coroutine<void, int>::CallerType& yield)
105 {
106 for (const auto ret : output) {
107 yield(ret);
108 }
109 };
110
111 Coroutine<void, int> coro(returning_task);
112 ASSERT_TRUE(coro);
113
114 for (const auto expected : expected_values) {
115 int returned = coro.get();
116 ASSERT_EQ(returned, expected);
117 }
118}
119
120/**
121 * This test is still supposed to test the returning interface
122 * of the the Coroutine, proving how coroutine can be used
123 * for generators.
124 * The coroutine is computing the first #steps of the fibonacci
125 * sequence and it is yielding back results one number per time.
126 */
127TEST(Coroutine, Fibonacci)
128{
129 const std::vector<int> expected_values{
130 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 };
131
132 const int steps = expected_values.size();
133
134 auto fibonacci_task =
135 [steps] (Coroutine<void, int>::CallerType& yield)
136 {
137 int prev = 0;
138 int current = 1;
139
140 for (auto iter = 0; iter < steps; iter++) {
141 int sum = prev + current;
142 yield(sum);
143
144 prev = current;
145 current = sum;
146 }
147 };
148
149 Coroutine<void, int> coro(fibonacci_task);
150 ASSERT_TRUE(coro);
151
152 for (const auto expected : expected_values) {
153 ASSERT_TRUE(coro);
154 int returned = coro.get();
155 ASSERT_EQ(returned, expected);
156 }
157}
158
159/**
160 * This test is using a bi-channel coroutine (accepting and
161 * yielding values) for testing a cooperative task.
162 * The caller and the coroutine have a string each; they are
163 * composing a new string by merging the strings together one
164 * character per time.
165 * The result string is hence passed back and forth between the
166 * coroutine and the caller.
167 */
168TEST(Coroutine, Cooperative)
169{
170 const std::string caller_str("HloWrd");
171 const std::string coro_str("el ol!");
172 const std::string expected("Hello World!");
173
174 auto cooperative_task =
175 [&coro_str] (Coroutine<std::string, std::string>::CallerType& yield)
176 {
177 for (auto& appended_c : coro_str) {
178 auto old_str = yield.get();
179 yield(old_str + appended_c);
180 }
181 };
182
183 Coroutine<std::string, std::string> coro(cooperative_task);
184
185 std::string result;
186 for (auto& c : caller_str) {
187 ASSERT_TRUE(coro);
188 result += c;
189 result = coro(result).get();
190 }
191
192 ASSERT_EQ(result, expected);
193}
194
195/**
196 * This test is testing nested coroutines by using one inner and one
197 * outer coroutine. It basically ensures that yielding from the inner
198 * coroutine returns to the outer coroutine (mid-layer of execution) and
199 * not to the outer caller.
200 */
201TEST(Coroutine, Nested)
202{
203 const std::string wrong("Inner");
204 const std::string expected("Inner + Outer");
205
206 auto inner_task =
207 [] (Coroutine<void, std::string>::CallerType& yield)
208 {
209 std::string inner_string("Inner");
210 yield(inner_string);
211 };
212
213 auto outer_task =
214 [&inner_task] (Coroutine<void, std::string>::CallerType& yield)
215 {
216 Coroutine<void, std::string> coro(inner_task);
217 std::string inner_string = coro.get();
218
219 std::string outer_string("Outer");
220 yield(inner_string + " + " + outer_string);
221 };
222
223
224 Coroutine<void, std::string> coro(outer_task);
225 ASSERT_TRUE(coro);
226
227 std::string result = coro.get();
228
229 ASSERT_NE(result, wrong);
230 ASSERT_EQ(result, expected);
231}
232
233/**
234 * This test is stressing the scenario where two distinct fibers are
235 * calling the same coroutine. First the test instantiates (and runs) a
236 * coroutine, then spawns another one and it passes it a reference to
237 * the first coroutine. Once the new coroutine calls the first coroutine
238 * and the first coroutine yields, we are expecting execution flow to
239 * be yielded to the second caller (the second coroutine) and not the
240 * original caller (the test itself)
241 */
242TEST(Coroutine, TwoCallers)
243{
244 bool valid_return = false;
245
246 Coroutine<void, void> callee{[]
247 (Coroutine<void, void>::CallerType& yield)
248 {
249 yield();
250 yield();
251 }};
252
253 Coroutine<void, void> other_caller{[&callee, &valid_return]
254 (Coroutine<void, void>::CallerType& yield)
255 {
256 callee();
257 valid_return = true;
258 yield();
259 }};
260
261 ASSERT_TRUE(valid_return);
262}
66 * This test is checking if the Coroutine, once it yields
67 * back to the caller, it is still marked as not finished.
68 */
69TEST(Coroutine, Unfinished)
70{
71 auto yielding_task =
72 [] (Coroutine<void, void>::CallerType& yield)
73 {
74 yield();
75 };
76
77 Coroutine<void, void> coro(yielding_task);
78 ASSERT_TRUE(coro);
79}
80
81/**
82 * This test is checking the parameter passing interface of a
83 * coroutine which takes an integer as an argument.
84 * Coroutine::operator() and CallerType::get() are the tested
85 * APIS.
86 */
87TEST(Coroutine, Passing)
88{
89 const std::vector<int> input{ 1, 2, 3 };
90 const std::vector<int> expected_values = input;
91
92 auto passing_task =
93 [&expected_values] (Coroutine<int, void>::CallerType& yield)
94 {
95 int argument;
96
97 for (const auto expected : expected_values) {
98 argument = yield.get();
99 ASSERT_EQ(argument, expected);
100 }
101 };
102
103 Coroutine<int, void> coro(passing_task);
104 ASSERT_TRUE(coro);
105
106 for (const auto val : input) {
107 coro(val);
108 }
109}
110
111/**
112 * This test is checking the yielding interface of a coroutine
113 * which takes no argument and returns integers.
114 * Coroutine::get() and CallerType::operator() are the tested
115 * APIS.
116 */
117TEST(Coroutine, Returning)
118{
119 const std::vector<int> output{ 1, 2, 3 };
120 const std::vector<int> expected_values = output;
121
122 auto returning_task =
123 [&output] (Coroutine<void, int>::CallerType& yield)
124 {
125 for (const auto ret : output) {
126 yield(ret);
127 }
128 };
129
130 Coroutine<void, int> coro(returning_task);
131 ASSERT_TRUE(coro);
132
133 for (const auto expected : expected_values) {
134 int returned = coro.get();
135 ASSERT_EQ(returned, expected);
136 }
137}
138
139/**
140 * This test is still supposed to test the returning interface
141 * of the the Coroutine, proving how coroutine can be used
142 * for generators.
143 * The coroutine is computing the first #steps of the fibonacci
144 * sequence and it is yielding back results one number per time.
145 */
146TEST(Coroutine, Fibonacci)
147{
148 const std::vector<int> expected_values{
149 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 };
150
151 const int steps = expected_values.size();
152
153 auto fibonacci_task =
154 [steps] (Coroutine<void, int>::CallerType& yield)
155 {
156 int prev = 0;
157 int current = 1;
158
159 for (auto iter = 0; iter < steps; iter++) {
160 int sum = prev + current;
161 yield(sum);
162
163 prev = current;
164 current = sum;
165 }
166 };
167
168 Coroutine<void, int> coro(fibonacci_task);
169 ASSERT_TRUE(coro);
170
171 for (const auto expected : expected_values) {
172 ASSERT_TRUE(coro);
173 int returned = coro.get();
174 ASSERT_EQ(returned, expected);
175 }
176}
177
178/**
179 * This test is using a bi-channel coroutine (accepting and
180 * yielding values) for testing a cooperative task.
181 * The caller and the coroutine have a string each; they are
182 * composing a new string by merging the strings together one
183 * character per time.
184 * The result string is hence passed back and forth between the
185 * coroutine and the caller.
186 */
187TEST(Coroutine, Cooperative)
188{
189 const std::string caller_str("HloWrd");
190 const std::string coro_str("el ol!");
191 const std::string expected("Hello World!");
192
193 auto cooperative_task =
194 [&coro_str] (Coroutine<std::string, std::string>::CallerType& yield)
195 {
196 for (auto& appended_c : coro_str) {
197 auto old_str = yield.get();
198 yield(old_str + appended_c);
199 }
200 };
201
202 Coroutine<std::string, std::string> coro(cooperative_task);
203
204 std::string result;
205 for (auto& c : caller_str) {
206 ASSERT_TRUE(coro);
207 result += c;
208 result = coro(result).get();
209 }
210
211 ASSERT_EQ(result, expected);
212}
213
214/**
215 * This test is testing nested coroutines by using one inner and one
216 * outer coroutine. It basically ensures that yielding from the inner
217 * coroutine returns to the outer coroutine (mid-layer of execution) and
218 * not to the outer caller.
219 */
220TEST(Coroutine, Nested)
221{
222 const std::string wrong("Inner");
223 const std::string expected("Inner + Outer");
224
225 auto inner_task =
226 [] (Coroutine<void, std::string>::CallerType& yield)
227 {
228 std::string inner_string("Inner");
229 yield(inner_string);
230 };
231
232 auto outer_task =
233 [&inner_task] (Coroutine<void, std::string>::CallerType& yield)
234 {
235 Coroutine<void, std::string> coro(inner_task);
236 std::string inner_string = coro.get();
237
238 std::string outer_string("Outer");
239 yield(inner_string + " + " + outer_string);
240 };
241
242
243 Coroutine<void, std::string> coro(outer_task);
244 ASSERT_TRUE(coro);
245
246 std::string result = coro.get();
247
248 ASSERT_NE(result, wrong);
249 ASSERT_EQ(result, expected);
250}
251
252/**
253 * This test is stressing the scenario where two distinct fibers are
254 * calling the same coroutine. First the test instantiates (and runs) a
255 * coroutine, then spawns another one and it passes it a reference to
256 * the first coroutine. Once the new coroutine calls the first coroutine
257 * and the first coroutine yields, we are expecting execution flow to
258 * be yielded to the second caller (the second coroutine) and not the
259 * original caller (the test itself)
260 */
261TEST(Coroutine, TwoCallers)
262{
263 bool valid_return = false;
264
265 Coroutine<void, void> callee{[]
266 (Coroutine<void, void>::CallerType& yield)
267 {
268 yield();
269 yield();
270 }};
271
272 Coroutine<void, void> other_caller{[&callee, &valid_return]
273 (Coroutine<void, void>::CallerType& yield)
274 {
275 callee();
276 valid_return = true;
277 yield();
278 }};
279
280 ASSERT_TRUE(valid_return);
281}