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/** 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} 282