407 lines
8.4 KiB
C++
407 lines
8.4 KiB
C++
//
|
|
// Created by erki on 30/01/25.
|
|
//
|
|
|
|
#include <catch2/catch.hpp>
|
|
|
|
#include "skullc/coro/scheduler.hpp"
|
|
#include "skullc/coro/semaphore.hpp"
|
|
#include "skullc/coro/signal.hpp"
|
|
#include "skullc/coro/sleep.hpp"
|
|
#include "skullc/coro/task.hpp"
|
|
#include "skullc/coro/this_coro.hpp"
|
|
#include "skullc/coro/composition.hpp"
|
|
|
|
#include <semaphore>
|
|
|
|
namespace
|
|
{
|
|
|
|
int test_coro_called = 0;
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
skullc::coro::Task<> test_coro(const int expected)
|
|
{
|
|
REQUIRE(expected == test_coro_called);
|
|
test_coro_called++;
|
|
co_return;
|
|
}
|
|
|
|
skullc::coro::Task<> test_sleepy_coro(const int expected, const std::chrono::duration<uint32_t, std::milli>& duration = 10ms)
|
|
{
|
|
co_await skullc::coro::sleep(0ms, duration);
|
|
REQUIRE(expected == test_coro_called);
|
|
test_coro_called++;
|
|
co_return;
|
|
}
|
|
|
|
template<typename T>
|
|
skullc::coro::Task<> test_semaphore_coro(T* semaphore, const int expected)
|
|
{
|
|
co_await skullc::coro::await_semaphore(*semaphore);
|
|
REQUIRE(expected == test_coro_called);
|
|
test_coro_called++;
|
|
co_return;
|
|
}
|
|
|
|
}// namespace
|
|
|
|
TEST_CASE("Scheduler runs coroutines.", "[coro]")
|
|
{
|
|
using namespace skullc::coro;
|
|
|
|
|
|
Scheduler<std::vector> scheduler;
|
|
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
|
|
|
test_coro_called = 0;
|
|
|
|
SECTION("With a single coroutine.")
|
|
{
|
|
scheduler.start_tasks(test_coro(0));
|
|
scheduler.loop(0);
|
|
|
|
REQUIRE(test_coro_called == 1);
|
|
}
|
|
|
|
|
|
SECTION("With two coroutines.")
|
|
{
|
|
scheduler.start_tasks(test_coro(0), test_coro(1));
|
|
scheduler.loop(0);
|
|
|
|
REQUIRE(test_coro_called == 2);
|
|
}
|
|
|
|
SECTION("With a single sleepy coroutine.")
|
|
{
|
|
using namespace std::chrono_literals;
|
|
|
|
scheduler.start_tasks(test_sleepy_coro(0));
|
|
scheduler.loop(0);
|
|
|
|
REQUIRE(test_coro_called == 0);
|
|
|
|
const std::chrono::duration<uint32_t, std::milli> half_sleep = 5ms;
|
|
scheduler.loop(half_sleep.count());
|
|
REQUIRE(test_coro_called == 0);
|
|
|
|
const std::chrono::duration<uint32_t, std::milli> full_sleep = 11ms;
|
|
scheduler.loop(full_sleep.count());
|
|
REQUIRE(test_coro_called == 1);
|
|
}
|
|
|
|
SECTION("With a semaphore awaiter.")
|
|
{
|
|
std::binary_semaphore semaphore{0};
|
|
std::counting_semaphore<10> counting_semaphore{0};
|
|
|
|
scheduler.start_tasks(
|
|
test_semaphore_coro(&semaphore, 0),
|
|
test_semaphore_coro(&counting_semaphore, 1));
|
|
scheduler.loop(0);
|
|
|
|
REQUIRE(test_coro_called == 0);
|
|
|
|
semaphore.release(1);
|
|
scheduler.loop(1);
|
|
scheduler.loop(2);
|
|
|
|
REQUIRE(test_coro_called == 1);
|
|
|
|
counting_semaphore.release(1);
|
|
scheduler.loop(3);
|
|
scheduler.loop(4);
|
|
|
|
REQUIRE(test_coro_called == 2);
|
|
}
|
|
|
|
SECTION("Coros scheduled for the same millisecond get executed with the same time.")
|
|
{
|
|
scheduler.start_tasks(
|
|
test_sleepy_coro(0, std::chrono::milliseconds{2}),
|
|
test_sleepy_coro(1, std::chrono::milliseconds{2}));
|
|
|
|
scheduler.loop(0);
|
|
scheduler.loop(0);
|
|
scheduler.loop(0);
|
|
|
|
REQUIRE(test_coro_called == 0);
|
|
|
|
scheduler.loop(2);
|
|
scheduler.loop(2);
|
|
|
|
REQUIRE(test_coro_called == 2);
|
|
}
|
|
}
|
|
|
|
#include "skullc/coro/peripheral_awaiters.hpp"
|
|
|
|
namespace
|
|
{
|
|
|
|
struct TestGpio
|
|
{
|
|
bool is_set = false;
|
|
|
|
void set(const bool value)
|
|
{
|
|
is_set = value;
|
|
}
|
|
|
|
void toggle()
|
|
{
|
|
is_set = !is_set;
|
|
}
|
|
|
|
bool read() const
|
|
{
|
|
return is_set;
|
|
}
|
|
};
|
|
|
|
skullc::coro::Task<> await_edge(TestGpio* pin, const Peripherals::Hal::Edge edge)
|
|
{
|
|
co_await skullc::coro::PinEdgeAwaiter(pin, edge);
|
|
test_coro_called = 1;
|
|
|
|
co_return;
|
|
}
|
|
|
|
}// namespace
|
|
|
|
TEST_CASE("Peripheral awaiters work.", "[coro],[awaiters]")
|
|
{
|
|
using namespace skullc::coro;
|
|
Scheduler<std::vector> scheduler;
|
|
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
|
|
|
test_coro_called = 0;
|
|
|
|
SECTION("Pin edge awaiter works with rising edge.")
|
|
{
|
|
TestGpio gpio{false};
|
|
scheduler.start_tasks(await_edge(&gpio, Peripherals::Hal::Edge::Rising));
|
|
scheduler.loop(0);
|
|
|
|
REQUIRE(test_coro_called == 0);
|
|
|
|
SECTION("Keeping low generates no edge.")
|
|
{
|
|
scheduler.loop(1);
|
|
scheduler.loop(2);
|
|
scheduler.loop(3);
|
|
|
|
REQUIRE(test_coro_called == 0);
|
|
}
|
|
|
|
SECTION("Raising generated a rising edge.")
|
|
{
|
|
gpio.is_set = true;
|
|
scheduler.loop(1);
|
|
scheduler.loop(2);
|
|
|
|
REQUIRE(test_coro_called == 1);
|
|
}
|
|
}
|
|
|
|
SECTION("Pin edge awaiter works with falling edge.")
|
|
{
|
|
TestGpio gpio{true};
|
|
scheduler.start_tasks(await_edge(&gpio, Peripherals::Hal::Edge::Falling));
|
|
scheduler.loop(0);
|
|
|
|
REQUIRE(test_coro_called == 0);
|
|
|
|
SECTION("Keeping high generates no edge.")
|
|
{
|
|
scheduler.loop(1);
|
|
scheduler.loop(2);
|
|
scheduler.loop(3);
|
|
|
|
REQUIRE(test_coro_called == 0);
|
|
}
|
|
|
|
SECTION("Falling generated a falling edge.")
|
|
{
|
|
gpio.is_set = false;
|
|
scheduler.loop(1);
|
|
scheduler.loop(2);
|
|
|
|
REQUIRE(test_coro_called == 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
skullc::coro::Task<> await_signal(const int expected, skullc::coro::Signal<int>* signal)
|
|
{
|
|
const int value = co_await signal->receive();
|
|
REQUIRE(value == expected);
|
|
test_coro_called = 1;
|
|
co_return;
|
|
}
|
|
|
|
skullc::coro::Task<> await_signal_n(const auto& expecteds, skullc::coro::Signal<int>* signal)
|
|
{
|
|
for (const int e : expecteds)
|
|
{
|
|
const int value = co_await signal->receive();
|
|
REQUIRE(value == e);
|
|
test_coro_called++;
|
|
}
|
|
co_return;
|
|
}
|
|
|
|
skullc::coro::Task<> send_signal(const int value, skullc::coro::Signal<int>* signal)
|
|
{
|
|
co_await skullc::coro::sleep(0ms, 10ms);
|
|
signal->send(value);
|
|
|
|
co_return;
|
|
}
|
|
|
|
}// namespace
|
|
|
|
TEST_CASE("Signal awaiters work.", "[coro],[signal]")
|
|
{
|
|
using namespace skullc::coro;
|
|
Scheduler<std::vector> scheduler;
|
|
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
|
|
|
test_coro_called = 0;
|
|
|
|
Signal<int> signal;
|
|
|
|
SECTION("Sending from main thread.")
|
|
{
|
|
scheduler.start_tasks(await_signal(10, &signal));
|
|
scheduler.loop(0);
|
|
scheduler.loop(1);
|
|
REQUIRE(test_coro_called == 0);
|
|
|
|
signal.send(10);
|
|
scheduler.loop(2);
|
|
REQUIRE(test_coro_called == 1);
|
|
}
|
|
|
|
SECTION("Sending from another coroutine.")
|
|
{
|
|
scheduler.start_tasks(await_signal(10, &signal), send_signal(10, &signal));
|
|
scheduler.loop(0);
|
|
scheduler.loop(1);
|
|
REQUIRE(test_coro_called == 0);
|
|
|
|
scheduler.loop(10);
|
|
REQUIRE(test_coro_called == 0);
|
|
scheduler.loop(10);
|
|
REQUIRE(test_coro_called == 1);
|
|
}
|
|
|
|
const std::vector<int> values = {10, 11, 13};
|
|
|
|
SECTION("Sending multiple values.")
|
|
{
|
|
scheduler.start_tasks(await_signal_n(values, &signal));
|
|
scheduler.loop(0);
|
|
scheduler.loop(1);
|
|
REQUIRE(test_coro_called == 0);
|
|
|
|
for (int i = 0; i < values.size(); i++)
|
|
{
|
|
signal.send(values[i]);
|
|
scheduler.loop(i + 1);
|
|
REQUIRE(test_coro_called == i + 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
|
|
skullc::coro::Task<> test_wait_all()
|
|
{
|
|
co_await skullc::coro::wait_all(
|
|
test_sleepy_coro(0, 10ms),
|
|
test_sleepy_coro(1, 20ms)
|
|
);
|
|
|
|
co_return;
|
|
}
|
|
|
|
skullc::coro::Task<int> test_sleepy_coro_return(const int expected, const std::chrono::duration<uint32_t, std::milli>& duration = 10ms)
|
|
{
|
|
co_await skullc::coro::sleep(0ms, duration);
|
|
REQUIRE(expected == test_coro_called);
|
|
test_coro_called++;
|
|
co_return expected;
|
|
}
|
|
|
|
skullc::coro::Task<> testwait_first()
|
|
{
|
|
auto val = co_await skullc::coro::wait_first(
|
|
test_sleepy_coro_return(0, 10ms),
|
|
test_sleepy_coro_return(1, 20ms)
|
|
);
|
|
|
|
REQUIRE(std::get<0>(val) == 0);
|
|
REQUIRE(std::get<1>(val) == std::nullopt);
|
|
|
|
co_return;
|
|
}
|
|
|
|
}
|
|
|
|
TEST_CASE("Wait all awaiter works.", "[coro],[wait_all]")
|
|
{
|
|
using namespace skullc::coro;
|
|
Scheduler<std::vector> scheduler;
|
|
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
|
|
|
test_coro_called = 0;
|
|
|
|
scheduler.start_tasks(test_wait_all());
|
|
scheduler.loop(0);
|
|
scheduler.loop(0);
|
|
scheduler.loop(0);
|
|
|
|
REQUIRE(test_coro_called == 0);
|
|
scheduler.loop(10);
|
|
scheduler.loop(10);
|
|
|
|
REQUIRE(test_coro_called == 1);
|
|
scheduler.loop(20);
|
|
scheduler.loop(20);
|
|
scheduler.loop(20);
|
|
|
|
REQUIRE(test_coro_called == 2);
|
|
}
|
|
|
|
TEST_CASE("Wait one awaiter works.", "[coro],[wait_first]")
|
|
{
|
|
using namespace skullc::coro;
|
|
Scheduler<std::vector> scheduler;
|
|
skullc::this_coro::scheduler.register_scheduler(scheduler);
|
|
|
|
test_coro_called = 0;
|
|
|
|
scheduler.start_tasks(testwait_first());
|
|
scheduler.loop(0);
|
|
scheduler.loop(0);
|
|
scheduler.loop(0);
|
|
|
|
REQUIRE(test_coro_called == 0);
|
|
scheduler.loop(10);
|
|
scheduler.loop(10);
|
|
|
|
REQUIRE(test_coro_called == 1);
|
|
scheduler.loop(20);
|
|
scheduler.loop(20);
|
|
scheduler.loop(20);
|
|
|
|
REQUIRE(test_coro_called == 1);
|
|
}
|