summaryrefslogtreecommitdiff
path: root/src/base/coroutine.test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/base/coroutine.test.cc')
-rw-r--r--src/base/coroutine.test.cc262
1 files changed, 262 insertions, 0 deletions
diff --git a/src/base/coroutine.test.cc b/src/base/coroutine.test.cc
new file mode 100644
index 000000000..655bc254a
--- /dev/null
+++ b/src/base/coroutine.test.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);
+}