Erki acbdbf4f7f
Some checks failed
CI & Unit Tests / Unit-Tests (push) Failing after 8s
CI & Unit Tests / Docs (push) Successful in 10s
WIP9: some improvements
2025-02-28 08:51:05 +02:00

410 lines
8.5 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<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<> test_wait_all()
{
auto val = co_await skullc::coro::wait_all(
test_sleepy_coro(0, 10ms),
test_sleepy_coro_return(1, 20ms)
);
REQUIRE(std::get<0>(val) == std::monostate{});
REQUIRE(std::get<1>(val) == 1);
co_return;
}
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);
}