/* * Copyright (c) 2018 ARM Limited * All rights reserved * * The license below extends only to copyright in the software and shall * not be construed as granting a license to any other intellectual * property including but not limited to intellectual property relating * to a hardware implementation of the functionality of the software * licensed hereunder. You may use the software subject to the license * terms below provided that you ensure that this notice is replicated * unmodified and in its entirety in all distributions of the software, * modified or unmodified, in source code or in binary form. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer; * redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution; * neither the name of the copyright holders nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * Authors: Giacomo Travaglini */ #include #include "base/coroutine.hh" using namespace m5; /** * This test is checking if the Coroutine, once it yields * back to the caller, it is still marked as not finished. */ TEST(Coroutine, Unfinished) { auto yielding_task = [] (Coroutine::CallerType& yield) { yield(); }; Coroutine coro(yielding_task); ASSERT_TRUE(coro); } /** * This test is checking the parameter passing interface of a * coroutine which takes an integer as an argument. * Coroutine::operator() and CallerType::get() are the tested * APIS. */ TEST(Coroutine, Passing) { const std::vector input{ 1, 2, 3 }; const std::vector expected_values = input; auto passing_task = [&expected_values] (Coroutine::CallerType& yield) { int argument; for (const auto expected : expected_values) { argument = yield.get(); ASSERT_EQ(argument, expected); } }; Coroutine coro(passing_task); ASSERT_TRUE(coro); for (const auto val : input) { coro(val); } } /** * This test is checking the yielding interface of a coroutine * which takes no argument and returns integers. * Coroutine::get() and CallerType::operator() are the tested * APIS. */ TEST(Coroutine, Returning) { const std::vector output{ 1, 2, 3 }; const std::vector expected_values = output; auto returning_task = [&output] (Coroutine::CallerType& yield) { for (const auto ret : output) { yield(ret); } }; Coroutine coro(returning_task); ASSERT_TRUE(coro); for (const auto expected : expected_values) { int returned = coro.get(); ASSERT_EQ(returned, expected); } } /** * This test is still supposed to test the returning interface * of the the Coroutine, proving how coroutine can be used * for generators. * The coroutine is computing the first #steps of the fibonacci * sequence and it is yielding back results one number per time. */ TEST(Coroutine, Fibonacci) { const std::vector expected_values{ 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233 }; const int steps = expected_values.size(); auto fibonacci_task = [steps] (Coroutine::CallerType& yield) { int prev = 0; int current = 1; for (auto iter = 0; iter < steps; iter++) { int sum = prev + current; yield(sum); prev = current; current = sum; } }; Coroutine coro(fibonacci_task); ASSERT_TRUE(coro); for (const auto expected : expected_values) { ASSERT_TRUE(coro); int returned = coro.get(); ASSERT_EQ(returned, expected); } } /** * This test is using a bi-channel coroutine (accepting and * yielding values) for testing a cooperative task. * The caller and the coroutine have a string each; they are * composing a new string by merging the strings together one * character per time. * The result string is hence passed back and forth between the * coroutine and the caller. */ TEST(Coroutine, Cooperative) { const std::string caller_str("HloWrd"); const std::string coro_str("el ol!"); const std::string expected("Hello World!"); auto cooperative_task = [&coro_str] (Coroutine::CallerType& yield) { for (auto& appended_c : coro_str) { auto old_str = yield.get(); yield(old_str + appended_c); } }; Coroutine coro(cooperative_task); std::string result; for (auto& c : caller_str) { ASSERT_TRUE(coro); result += c; result = coro(result).get(); } ASSERT_EQ(result, expected); } /** * This test is testing nested coroutines by using one inner and one * outer coroutine. It basically ensures that yielding from the inner * coroutine returns to the outer coroutine (mid-layer of execution) and * not to the outer caller. */ TEST(Coroutine, Nested) { const std::string wrong("Inner"); const std::string expected("Inner + Outer"); auto inner_task = [] (Coroutine::CallerType& yield) { std::string inner_string("Inner"); yield(inner_string); }; auto outer_task = [&inner_task] (Coroutine::CallerType& yield) { Coroutine coro(inner_task); std::string inner_string = coro.get(); std::string outer_string("Outer"); yield(inner_string + " + " + outer_string); }; Coroutine coro(outer_task); ASSERT_TRUE(coro); std::string result = coro.get(); ASSERT_NE(result, wrong); ASSERT_EQ(result, expected); } /** * This test is stressing the scenario where two distinct fibers are * calling the same coroutine. First the test instantiates (and runs) a * coroutine, then spawns another one and it passes it a reference to * the first coroutine. Once the new coroutine calls the first coroutine * and the first coroutine yields, we are expecting execution flow to * be yielded to the second caller (the second coroutine) and not the * original caller (the test itself) */ TEST(Coroutine, TwoCallers) { bool valid_return = false; Coroutine callee{[] (Coroutine::CallerType& yield) { yield(); yield(); }}; Coroutine other_caller{[&callee, &valid_return] (Coroutine::CallerType& yield) { callee(); valid_return = true; yield(); }}; ASSERT_TRUE(valid_return); }