misc: Replace enable_if<>::type with enable_if_t<>.
[gem5.git] / src / base / coroutine.test.cc
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
38 #include <gtest/gtest.h>
39
40 #include "base/coroutine.hh"
41
42 using namespace m5;
43
44 /**
45 * This test is checking if the Coroutine, once it's created
46 * it doesn't start since the second argument of the constructor
47 * (run_coroutine) is set to false
48 */
49 TEST(Coroutine, Unstarted)
50 {
51 auto yielding_task =
52 [] (Coroutine<void, void>::CallerType& yield)
53 {
54 yield();
55 };
56
57 const bool start_upon_creation = false;
58 Coroutine<void, void> coro(yielding_task, start_upon_creation);
59
60 ASSERT_FALSE(coro.started());
61 }
62
63 /**
64 * This test is checking if the Coroutine, once it yields
65 * back to the caller, it is still marked as not finished.
66 */
67 TEST(Coroutine, Unfinished)
68 {
69 auto yielding_task =
70 [] (Coroutine<void, void>::CallerType& yield)
71 {
72 yield();
73 };
74
75 Coroutine<void, void> coro(yielding_task);
76 ASSERT_TRUE(coro);
77 }
78
79 /**
80 * This test is checking the parameter passing interface of a
81 * coroutine which takes an integer as an argument.
82 * Coroutine::operator() and CallerType::get() are the tested
83 * APIS.
84 */
85 TEST(Coroutine, Passing)
86 {
87 const std::vector<int> input{ 1, 2, 3 };
88 const std::vector<int> expected_values = input;
89
90 auto passing_task =
91 [&expected_values] (Coroutine<int, void>::CallerType& yield)
92 {
93 int argument;
94
95 for (const auto expected : expected_values) {
96 argument = yield.get();
97 ASSERT_EQ(argument, expected);
98 }
99 };
100
101 Coroutine<int, void> coro(passing_task);
102 ASSERT_TRUE(coro);
103
104 for (const auto val : input) {
105 coro(val);
106 }
107 }
108
109 /**
110 * This test is checking the yielding interface of a coroutine
111 * which takes no argument and returns integers.
112 * Coroutine::get() and CallerType::operator() are the tested
113 * APIS.
114 */
115 TEST(Coroutine, Returning)
116 {
117 const std::vector<int> output{ 1, 2, 3 };
118 const std::vector<int> expected_values = output;
119
120 auto returning_task =
121 [&output] (Coroutine<void, int>::CallerType& yield)
122 {
123 for (const auto ret : output) {
124 yield(ret);
125 }
126 };
127
128 Coroutine<void, int> coro(returning_task);
129 ASSERT_TRUE(coro);
130
131 for (const auto expected : expected_values) {
132 int returned = coro.get();
133 ASSERT_EQ(returned, expected);
134 }
135 }
136
137 /**
138 * This test is still supposed to test the returning interface
139 * of the the Coroutine, proving how coroutine can be used
140 * for generators.
141 * The coroutine is computing the first #steps of the fibonacci
142 * sequence and it is yielding back results one number per time.
143 */
144 TEST(Coroutine, Fibonacci)
145 {
146 const std::vector<int> expected_values{
147 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 };
148
149 const int steps = expected_values.size();
150
151 auto fibonacci_task =
152 [steps] (Coroutine<void, int>::CallerType& yield)
153 {
154 int prev = 0;
155 int current = 1;
156
157 for (auto iter = 0; iter < steps; iter++) {
158 int sum = prev + current;
159 yield(sum);
160
161 prev = current;
162 current = sum;
163 }
164 };
165
166 Coroutine<void, int> coro(fibonacci_task);
167 ASSERT_TRUE(coro);
168
169 for (const auto expected : expected_values) {
170 ASSERT_TRUE(coro);
171 int returned = coro.get();
172 ASSERT_EQ(returned, expected);
173 }
174 }
175
176 /**
177 * This test is using a bi-channel coroutine (accepting and
178 * yielding values) for testing a cooperative task.
179 * The caller and the coroutine have a string each; they are
180 * composing a new string by merging the strings together one
181 * character per time.
182 * The result string is hence passed back and forth between the
183 * coroutine and the caller.
184 */
185 TEST(Coroutine, Cooperative)
186 {
187 const std::string caller_str("HloWrd");
188 const std::string coro_str("el ol!");
189 const std::string expected("Hello World!");
190
191 auto cooperative_task =
192 [&coro_str] (Coroutine<std::string, std::string>::CallerType& yield)
193 {
194 for (auto& appended_c : coro_str) {
195 auto old_str = yield.get();
196 yield(old_str + appended_c);
197 }
198 };
199
200 Coroutine<std::string, std::string> coro(cooperative_task);
201
202 std::string result;
203 for (auto& c : caller_str) {
204 ASSERT_TRUE(coro);
205 result += c;
206 result = coro(result).get();
207 }
208
209 ASSERT_EQ(result, expected);
210 }
211
212 /**
213 * This test is testing nested coroutines by using one inner and one
214 * outer coroutine. It basically ensures that yielding from the inner
215 * coroutine returns to the outer coroutine (mid-layer of execution) and
216 * not to the outer caller.
217 */
218 TEST(Coroutine, Nested)
219 {
220 const std::string wrong("Inner");
221 const std::string expected("Inner + Outer");
222
223 auto inner_task =
224 [] (Coroutine<void, std::string>::CallerType& yield)
225 {
226 std::string inner_string("Inner");
227 yield(inner_string);
228 };
229
230 auto outer_task =
231 [&inner_task] (Coroutine<void, std::string>::CallerType& yield)
232 {
233 Coroutine<void, std::string> coro(inner_task);
234 std::string inner_string = coro.get();
235
236 std::string outer_string("Outer");
237 yield(inner_string + " + " + outer_string);
238 };
239
240
241 Coroutine<void, std::string> coro(outer_task);
242 ASSERT_TRUE(coro);
243
244 std::string result = coro.get();
245
246 ASSERT_NE(result, wrong);
247 ASSERT_EQ(result, expected);
248 }
249
250 /**
251 * This test is stressing the scenario where two distinct fibers are
252 * calling the same coroutine. First the test instantiates (and runs) a
253 * coroutine, then spawns another one and it passes it a reference to
254 * the first coroutine. Once the new coroutine calls the first coroutine
255 * and the first coroutine yields, we are expecting execution flow to
256 * be yielded to the second caller (the second coroutine) and not the
257 * original caller (the test itself)
258 */
259 TEST(Coroutine, TwoCallers)
260 {
261 bool valid_return = false;
262
263 Coroutine<void, void> callee{[]
264 (Coroutine<void, void>::CallerType& yield)
265 {
266 yield();
267 yield();
268 }};
269
270 Coroutine<void, void> other_caller{[&callee, &valid_return]
271 (Coroutine<void, void>::CallerType& yield)
272 {
273 callee();
274 valid_return = true;
275 yield();
276 }};
277
278 ASSERT_TRUE(valid_return);
279 }