diff options
Diffstat (limited to 'src/base/coroutinetest.cc')
-rw-r--r-- | src/base/coroutinetest.cc | 262 |
1 files changed, 262 insertions, 0 deletions
diff --git a/src/base/coroutinetest.cc b/src/base/coroutinetest.cc new file mode 100644 index 000000000..655bc254a --- /dev/null +++ b/src/base/coroutinetest.cc @@ -0,0 +1,262 @@ +/* + * 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 <gtest/gtest.h> + +#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<void, void>::CallerType& yield) + { + yield(); + }; + + Coroutine<void, void> 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<int> input{ 1, 2, 3 }; + const std::vector<int> expected_values = input; + + auto passing_task = + [&expected_values] (Coroutine<int, void>::CallerType& yield) + { + int argument; + + for (const auto expected : expected_values) { + argument = yield.get(); + ASSERT_EQ(argument, expected); + } + }; + + Coroutine<int, void> 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<int> output{ 1, 2, 3 }; + const std::vector<int> expected_values = output; + + auto returning_task = + [&output] (Coroutine<void, int>::CallerType& yield) + { + for (const auto ret : output) { + yield(ret); + } + }; + + Coroutine<void, int> 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<int> 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<void, int>::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<void, int> 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<std::string, std::string>::CallerType& yield) + { + for (auto& appended_c : coro_str) { + auto old_str = yield.get(); + yield(old_str + appended_c); + } + }; + + Coroutine<std::string, std::string> 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<void, std::string>::CallerType& yield) + { + std::string inner_string("Inner"); + yield(inner_string); + }; + + auto outer_task = + [&inner_task] (Coroutine<void, std::string>::CallerType& yield) + { + Coroutine<void, std::string> coro(inner_task); + std::string inner_string = coro.get(); + + std::string outer_string("Outer"); + yield(inner_string + " + " + outer_string); + }; + + + Coroutine<void, std::string> 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<void, void> callee{[] + (Coroutine<void, void>::CallerType& yield) + { + yield(); + yield(); + }}; + + Coroutine<void, void> other_caller{[&callee, &valid_return] + (Coroutine<void, void>::CallerType& yield) + { + callee(); + valid_return = true; + yield(); + }}; + + ASSERT_TRUE(valid_return); +} |