From f27474905075c933d85a34acfa4c72501db6a0da Mon Sep 17 00:00:00 2001 From: Paul Mathieu Date: Fri, 2 Jun 2023 23:33:01 -0700 Subject: [PATCH] arm: add host uart driver tests All host tests currently pass Some async refactors may not work well on device, will try later --- arm/async.cc | 92 ++++++++++++--------- arm/async.h | 55 +++++++++--- arm/async_test.cc | 37 ++++----- arm/dev.dockerfile | 5 ++ arm/fake_uart.cc | 202 +++++++++++++++++++++++++++++++++++++++++++++ arm/fake_uart.h | 10 +++ arm/lock.cc | 5 ++ arm/lock.h | 10 +++ arm/main.cc | 11 +-- arm/makefile | 37 +++++---- arm/prog.py | 8 +- arm/ring_buffer.h | 22 ++++- arm/uart.cc | 2 +- arm/uart_test.cc | 95 +++++++++++++++++++++ 14 files changed, 494 insertions(+), 97 deletions(-) create mode 100644 arm/dev.dockerfile create mode 100644 arm/fake_uart.cc create mode 100644 arm/fake_uart.h create mode 100644 arm/lock.cc create mode 100644 arm/uart_test.cc diff --git a/arm/async.cc b/arm/async.cc index 5e2ec74..62cebf8 100644 --- a/arm/async.cc +++ b/arm/async.cc @@ -5,21 +5,8 @@ #include #include -#include "trace.h" - -#ifdef __x86_64__ -#include -struct InterruptLock { - static std::mutex m; - InterruptLock() { m.lock(); } - ~InterruptLock() { m.unlock(); } -}; -std::mutex InterruptLock::m; -#else // __x86_64__ #include "lock.h" - -using tracing::trace; -#endif // __x86_64__ +#include "trace.h" namespace async { namespace { @@ -38,13 +25,14 @@ struct Notification { Stuff* stuff; }; -std::atomic work; +std::atomic work = nullptr; std::array(AwaitableType::kNumTypes)> notifications = {}; } // namespace void schedule(std::coroutine_handle<> h, int ms) { + InterruptLock lock; TRACE(tracing::TraceEvent::kAsyncSchedule); std::chrono::system_clock::time_point exp = std::chrono::system_clock::now() + std::chrono::milliseconds(ms); @@ -70,38 +58,64 @@ void schedule(std::coroutine_handle<> h, int ms) { s->next = news; } +void step() { + Stuff* stuff; + // ensure all previous side effects are visible + { + InterruptLock lock; + stuff = work; + }; + + if (stuff == nullptr) { + return; + } + + auto now = std::chrono::system_clock::now(); + auto dt = stuff->expiration - now; + + if (dt > 0ms) { + return; + } + + int stuffinqueue = 0; + for (Stuff* s = stuff; s; s = s->next) stuffinqueue++; + + TRACE(tracing::TraceEvent::kAsyncTask); + stuff->h(); + TRACE(tracing::TraceEvent::kAsyncTaskDone); + + if (stuff->h.done()) { + stuff->h.destroy(); + } + + { + InterruptLock lock; + work = stuff->next; + } + delete stuff; +} + +void reset() { + Stuff* stuff = work; + while (stuff) { + Stuff* byebye = stuff; + stuff = stuff->next; + + delete byebye; + } + work = nullptr; +} + void main_loop(bool (*idle_function)()) { while (1) { if (idle_function != nullptr) { if (idle_function()) { + reset(); break; }; } - Stuff* stuff = work; - if (stuff == nullptr) { - continue; // busyloop - } - auto now = std::chrono::system_clock::now(); - auto dt = stuff->expiration - now; - - if (dt > 0ms) { - continue; - } - - TRACE(tracing::TraceEvent::kAsyncTask); - stuff->h(); - TRACE(tracing::TraceEvent::kAsyncTaskDone); - - if (stuff->h.done()) { - stuff->h.destroy(); - } - { - InterruptLock lock; - - work = stuff->next; - } - delete stuff; + step(); } } diff --git a/arm/async.h b/arm/async.h index a5b80c4..27877ad 100644 --- a/arm/async.h +++ b/arm/async.h @@ -1,19 +1,10 @@ -#pragma once +#include #include #include #include "trace.h" -#ifdef __clang__ -#include -namespace std { -using namespace experimental; -} -#else // __clang__ -#include -#endif // __clang__ - namespace async { struct task_final_suspend { @@ -23,6 +14,11 @@ struct task_final_suspend { TRACE(tracing::TraceEvent::kAsyncCallParent); parent(); TRACE(tracing::TraceEvent::kAsyncCallParentDone); + + if (parent && parent.done()) { + TRACE(tracing::TraceEvent::kAsyncDestroy); + parent.destroy(); + } } } void await_resume() noexcept(true) {} @@ -30,6 +26,7 @@ struct task_final_suspend { std::coroutine_handle<> parent; }; +#if 0 template struct gimme { // child interface @@ -87,6 +84,40 @@ struct gimme { T stuff; std::coroutine_handle<> parent; }; +#else + +template +struct gimme { + // child interface + bool await_ready() { return false; } + void await_suspend(std::coroutine_handle<> h) { + ha = h; + waiting = true; + TRACE(tracing::TraceEvent::kAsyncGimmeWaiting); + } + T await_resume() { + waiting = false; + TRACE(tracing::TraceEvent::kAsyncGimmeResume); + return std::move(stuff); + } + + // parent interface + void feed(T&& s) { + if (!waiting) { + __builtin_trap(); + } + if (!ha) { + __builtin_trap(); + } + stuff = s; + ha.resume(); + } + + bool waiting = false; + std::coroutine_handle<> ha; + T stuff; +}; +#endif template struct task; @@ -117,7 +148,8 @@ struct task { TRACE(tracing::TraceEvent::kAsyncCoAwait); h(); if (h.done()) { - TRACE(tracing::TraceEvent::kAsyncDestroy); h.destroy(); + TRACE(tracing::TraceEvent::kAsyncDestroy); + h.destroy(); return true; } return false; @@ -206,6 +238,7 @@ void enqueue(std::coroutine_handle<> h, AwaitableType type); void resume(AwaitableType type); // typically called from an ISR void main_loop(bool (*idle_function)()); +void step(); inline auto await(AwaitableType type) { struct awaitable { diff --git a/arm/async_test.cc b/arm/async_test.cc index 31b50dc..db568a2 100644 --- a/arm/async_test.cc +++ b/arm/async_test.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -28,11 +29,14 @@ int uptime() { template void log(int i, const char* fmt, Args... args) { - if (i != 4) { + if (i != 3) { return; } printf("[%10i] ", uptime()); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-security" printf(fmt, args...); +#pragma clang diagnostic pop printf("\n"); } @@ -124,6 +128,7 @@ TEST_F(LoopAsyncTest, ManyEnqueueResume) { } EXPECT_EQ(done, kLoops); + log(3, "got all of em, done"); terminate = true; t.join(); @@ -196,10 +201,14 @@ std::binary_semaphore NestedAsync::next(0); std::binary_semaphore NestedAsync::ready(0); TEST_F(NestedAsync, SimpleNestedTest) { + while (next.try_acquire()); + while (ready.try_acquire()); + async::schedule(nested().h); std::thread t([]() { async::main_loop([]() -> bool { - next.acquire(); + // for some reason the bare `acquire` fails sometimes :/ + while (!next.try_acquire_for(10ms)); return terminate; }); }); @@ -222,39 +231,23 @@ TEST_F(NestedAsync, SimpleNestedTest) { TEST_F(NestedAsync, LessSimpleNestedTest) { async::schedule(othernested().h); - std::thread t([]() { - async::main_loop([]() -> bool { - ready.release(); - log(3, "loop ready"); - next.acquire(); - return terminate; - }); - }); ASSERT_FALSE(voiddone); ASSERT_EQ(result, 0); - log(3, "starting stuff"); - - ASSERT_TRUE(ready.try_acquire_for(1s)); - next.release(); - ASSERT_TRUE(ready.try_acquire_for(1s)); + log(3, "step"); + async::step(); // should proceed until yield ASSERT_FALSE(voiddone); ASSERT_EQ(result, 0); - next.release(); - ASSERT_TRUE(ready.try_acquire_for(1s)); + log(3, "step"); + async::step(); // should proceed until end EXPECT_EQ(voiddone, true); ASSERT_EQ(result, 42); EXPECT_TRUE(done.try_acquire_for(1s)); - - terminate = true; - next.release(); - - t.join(); } TEST_F(NestedAsync, GeneratorTest) { diff --git a/arm/dev.dockerfile b/arm/dev.dockerfile new file mode 100644 index 0000000..b2b23d7 --- /dev/null +++ b/arm/dev.dockerfile @@ -0,0 +1,5 @@ +FROM ubuntu + +RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ + apt-get install -y make clang libgmock-dev gdb && \ + apt-get clean && rm -rf /var/lib/apt/lists diff --git a/arm/fake_uart.cc b/arm/fake_uart.cc new file mode 100644 index 0000000..1d29316 --- /dev/null +++ b/arm/fake_uart.cc @@ -0,0 +1,202 @@ +#include "uart.h" + +#include "async.h" +#include "buffer.h" +#include "ring_buffer.h" + +#include +#include +#include + +namespace { +using async::AwaitableType; + +constexpr size_t kUartTxBufferSize = 256; +std::array tx_buffer = {}; +RingBuffer tx_ring_buffer{.buffer = tx_buffer}; + +uint8_t* tx_buff = nullptr; +uint16_t tx_size = 0; +uint16_t tx_size_orig = 0; + +uint8_t* rx_buff = nullptr; +uint16_t rx_size = 0; +uint16_t rx_size_orig = 0; +std::vector rx_overflow; + +std::mutex interrupt_lock; +std::mutex fu_mutex; +} // namespace + +//// Internal API +void FakeUart_Send(uint8_t* ptr, uint16_t size) { + std::scoped_lock lock(fu_mutex); + + tx_buff = ptr; + tx_size = size; + tx_size_orig = size; +} +size_t FakeUart_Recv(uint8_t* ptr, uint16_t size) { + std::scoped_lock lock(fu_mutex); + + if (size <= rx_overflow.size()) { + std::copy(rx_overflow.begin(), rx_overflow.begin() + size, + std::as_writable_bytes(std::span{ptr, size}).begin()); + std::rotate(rx_overflow.begin(), rx_overflow.begin() + size, rx_overflow.end()); + rx_overflow.resize(rx_overflow.size() - size); + + return size; + } + + rx_buff = ptr; + rx_size = size; + + if (rx_overflow.empty()) { + return 0; + } + + std::copy(rx_overflow.begin(), rx_overflow.end(), + std::as_writable_bytes(std::span{ptr, size}).begin()); + size_t copied = rx_overflow.size(); + rx_overflow.clear(); + + rx_buff += copied; + rx_size -= copied; + + return copied; +} +uint8_t FakeUart_IsSending() { + std::scoped_lock lock(fu_mutex); + return tx_size > 0; +} + +//// Backdoor access API +/// Those do trigger UART interrupts when tx/rx buffer state changes +buffer FakeUart_Drain(uint16_t size) { + std::scoped_lock isr_lock(interrupt_lock); // not really the same, but that'll do + std::scoped_lock lock(fu_mutex); + + size_t drained = std::min(size, tx_size); + + if (drained < 1) { + return buffer{}; + } + + buffer buff = buffer::make(drained); + auto txb = std::as_bytes(std::span{tx_buff, tx_size}); + std::copy(txb.begin(), txb.begin() + drained, buff.data.begin()); + + tx_buff += drained; + tx_size -= drained; + + if (tx_size < 1) { + fu_mutex.unlock(); + HandleUartTxFromIsr(nullptr, tx_size_orig); + fu_mutex.lock(); + } + + return buff; +} +void FakeUart_Feed(std::span data) { + std::scoped_lock lock(fu_mutex); + + if (data.empty()) { + return; + } + + auto rxb = std::as_writable_bytes(std::span{rx_buff, rx_size}); + size_t fed = std::min(static_cast(rx_size), data.size()); + + if (data.size() > fed) { + rx_overflow.insert(rx_overflow.end(), data.begin() + fed, data.end()); + } + + if (fed > 0) { + std::copy(data.begin(), data.begin() + fed, rxb.begin()); + + rx_buff += fed; + rx_size -= fed; + + if (rx_size < 1) { + HandleUartRxFromIsr(nullptr, rx_size_orig); + } + } +} +void FakeUart_Reset() { + rx_overflow.clear(); + + rx_buff = nullptr; + rx_size = 0; + tx_buff = nullptr; + tx_size = 0; +} + +//// Public API +void InitUarts() {} +void UartWriteCrash(std::span) {} +void LogStuff() {} +void UartReadBlocking(std::span) {} +void UartWriteBlocking(std::span) {} +void HandleUartIsr() {} +async::task UartRead(int size) { + co_return buffer{}; +} + +void HandleUartTxFromIsr(void*, unsigned int transmitted) { + tx_ring_buffer.Pop(transmitted); + if (tx_ring_buffer.AvailableData() > 0 && !FakeUart_IsSending()) { + FakeUart_Send(tx_ring_buffer.RawReadPointer(), + tx_ring_buffer.ContiguousAvailableData()); + } + async::resume(AwaitableType::kUartTx); +} + +void HandleUartRxFromIsr(void*, unsigned int) { + async::resume(AwaitableType::kUartRx); +} + +async::task<> UartWrite(std::span data) { + while (!tx_ring_buffer.Store(data)) { + co_await async::await(AwaitableType::kUartTx); + } + + { + std::scoped_lock lock(interrupt_lock); + + if (!FakeUart_IsSending()) { + FakeUart_Send(tx_ring_buffer.RawReadPointer(), + tx_ring_buffer.ContiguousAvailableData()); + } + } +} + +async::task<> UartWriteLoop(async::gimme>& data_gen) { + while (1) { + auto data = co_await data_gen; + while (!tx_ring_buffer.Store(data)) { + co_await async::await(AwaitableType::kUartTx); + } + + { + std::scoped_lock lock(interrupt_lock); + + if (tx_ring_buffer.AvailableData() && !FakeUart_IsSending()) { + FakeUart_Send(tx_ring_buffer.RawReadPointer(), + tx_ring_buffer.ContiguousAvailableData()); + } + } + } +} + +async::task UartReadLoop() { + uint8_t c; + while (1) { + size_t received = FakeUart_Recv(&c, 1); + // some data may already be in the fifo, but if not, wait: + if (received < 1) { + co_await async::await(AwaitableType::kUartRx); + } + + co_yield c; + } +} \ No newline at end of file diff --git a/arm/fake_uart.h b/arm/fake_uart.h new file mode 100644 index 0000000..b679c1d --- /dev/null +++ b/arm/fake_uart.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +#include "buffer.h" + +buffer FakeUart_Drain(uint16_t size); +void FakeUart_Feed(std::span data); +void FakeUart_Reset(); \ No newline at end of file diff --git a/arm/lock.cc b/arm/lock.cc new file mode 100644 index 0000000..87ea6d5 --- /dev/null +++ b/arm/lock.cc @@ -0,0 +1,5 @@ +#include "lock.h" + +#ifdef __x86_64__ +std::recursive_mutex InterruptLock::m; +#endif \ No newline at end of file diff --git a/arm/lock.h b/arm/lock.h index ac06a17..de07444 100644 --- a/arm/lock.h +++ b/arm/lock.h @@ -1,5 +1,6 @@ #pragma once +#ifndef __x86_64__ #include "aum1_cm1.h" struct InterruptLock { @@ -11,3 +12,12 @@ struct InterruptLock { __set_PRIMASK(old_primask); } }; +#else // __x86_64__ +#include + +struct InterruptLock { + static std::recursive_mutex m; + InterruptLock() { m.lock(); } + ~InterruptLock() { m.unlock(); } +}; +#endif // __x86_64__ \ No newline at end of file diff --git a/arm/main.cc b/arm/main.cc index ad2b54f..5c23183 100644 --- a/arm/main.cc +++ b/arm/main.cc @@ -30,7 +30,7 @@ void SetupUart() { } void SetupTimer() { - timer0->SetupAsWdt(100000 * 4000); + timer0->SetupAsWdt(100000 * 1); //4000); timer0->EnableT1(); _vector_table[16 + Timer0_IRQn] = reinterpret_cast(Timer0Isr); @@ -38,7 +38,7 @@ void SetupTimer() { } -#if 0 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101133 +#if 1 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101133 async::task<> echo() { async::task reader = UartReadLoop(); async::gimme> feeder; @@ -52,8 +52,7 @@ async::task<> echo() { co_await feeder.feed(std::as_bytes(std::span{&c, 1})); } } -#endif - +#else async::task<> echo() { async::task reader = UartReadLoop(); while (1) { @@ -64,7 +63,9 @@ async::task<> echo() { co_await UartWrite(std::as_bytes(std::span{&c, 1})); } } +#endif +[[maybe_unused]] async::task<> dump_trace() { while (1) { co_await async::delay(5000); @@ -86,7 +87,7 @@ int main() { UartWriteBlocking("timer setup done\r\n"); async::schedule(echo().h); - async::schedule(dump_trace().h); +// async::schedule(dump_trace().h); tracing::trace(tracing::TraceEvent::kUartIsr); tracing::trace(tracing::TraceEvent::kUartIsr); diff --git a/arm/makefile b/arm/makefile index f2615e2..13be640 100644 --- a/arm/makefile +++ b/arm/makefile @@ -43,7 +43,7 @@ app.elf: $(app_objects) app.ld deps = $(app_objects:.o=.d) $(bootloader_objects:.o=.d) -HOSTCXX = /usr/local/opt/llvm/bin/clang++ +HOSTCXX = clang++ HOSTLDFLAGS = -lgmock -lgtest -lgtest_main -L/usr/local/opt/llvm/lib -L/usr/local/lib HOSTCFLAGS = -std=c++20 -g\ -I/usr/local/opt/llvm/include \ @@ -54,36 +54,39 @@ HOSTCFLAGS = -std=c++20 -g\ TSAN_CFLAGS = $(HOSTCFLAGS) -fsanitize=thread ASAN_CFLAGS = $(HOSTCFLAGS) -fsanitize=address -fsanitize=leak -tests = ring_buffer_test async_test_asan async_test_tsan - -.PRECIOUS: $(tests) +tests = ring_buffer_test.run async_test_asan.run async_test_tsan.run uart_test_tsan.run test: $(tests) -ring_buffer_test: ring_buffer_test.cc - mkdir test - $(HOSTCXX) $(HOSTCFLAGS) -o test/$@ $< $(HOSTLDFLAGS) - ./test/$@ +test/async_test_asan: async_test.cc async.host.o lock.host.o +test/async_test_tsan: async_test.cc async.host.o lock.host.o +test/ring_buffer_test: ring_buffer_test.cc lock.host.o +test/uart_test_tsan: uart_test.cc fake_uart.host.o async.host.o lock.host.o + +%.run: test/% + TSAN_OPTIONS='suppressions=tsan.suppressions' ASAN_OPTIONS=detect_leaks=1 ./$< %.host.o: %.cc $(HOSTCXX) $(HOSTCFLAGS) -c -o $@ $< -async_test_tsan: async_test.cc async.host.o - mkdir -p test - $(HOSTCXX) $(TSAN_CFLAGS) -o test/$@ $^ $(HOSTLDFLAGS) - TSAN_OPTIONS='suppressions=tsan.suppressions' ./test/$@ +test/%_test: | mktest + $(HOSTCXX) $(HOSTCFLAGS) -o $@ $^ $(HOSTLDFLAGS) -async_test_asan: async_test.cc async.host.o +test/%_asan: | mktest + $(HOSTCXX) $(ASAN_CFLAGS) -o $@ $^ $(HOSTLDFLAGS) + +test/%_tsan: | mktest + $(HOSTCXX) $(TSAN_CFLAGS) -o $@ $^ $(HOSTLDFLAGS) + +mktest: mkdir -p test - $(HOSTCXX) $(ASAN_CFLAGS) -o test/$@ $^ $(HOSTLDFLAGS) - ASAN_OPTIONS=detect_leaks=1 ./test/$@ test_deps = async.host.d clean: rm -rf *.elf *.bin $(app_objects) $(bootloader_objects) $(deps) - rm -rf test/ *.dSYM $(tests) $(test_deps) *.o + rm -rf test/ *.dSYM $(test_deps) *.o -include $(deps) --include $(test_deps) +-include $(test_deps) \ No newline at end of file diff --git a/arm/prog.py b/arm/prog.py index d15370d..df231c1 100644 --- a/arm/prog.py +++ b/arm/prog.py @@ -1,6 +1,7 @@ import serial import struct import sys +import time offset = 0x800 tty = 'tty' @@ -41,11 +42,16 @@ def main(): s = serial.Serial(tty, baud, timeout=1) write(s, offset, dat) jump(s, offset) + last_dat = time.time() while True: dat = s.read() if not dat: - break + if time.time() - last_dat > 1.0: + print('Data timeout') + break + continue + last_dat = time.time() sys.stdout.buffer.write(dat) diff --git a/arm/ring_buffer.h b/arm/ring_buffer.h index ca6e368..56a496e 100644 --- a/arm/ring_buffer.h +++ b/arm/ring_buffer.h @@ -3,6 +3,8 @@ #include #include +#include "lock.h" + struct RingBuffer { std::span buffer; @@ -11,6 +13,8 @@ struct RingBuffer { std::atomic full = 0; bool Store(std::span data) { + InterruptLock lock; + if (data.size() > FreeSpace()) { return false; } @@ -26,6 +30,8 @@ struct RingBuffer { } bool Load(std::span out) { + InterruptLock lock; + if (out.size() > AvailableData()) { return false; } @@ -41,6 +47,8 @@ struct RingBuffer { } bool Push(size_t amount) { + InterruptLock lock; + if (amount > FreeSpace()) { return false; } @@ -52,6 +60,8 @@ struct RingBuffer { } bool Pop(size_t amount) { + InterruptLock lock; + if (amount > AvailableData()) { return false; } @@ -62,9 +72,15 @@ struct RingBuffer { return true; } - size_t FreeSpace() const { return buffer.size() - AvailableData(); } + size_t FreeSpace() const { + InterruptLock lock; + + return buffer.size() - AvailableData(); + } size_t AvailableData() const { + InterruptLock lock; + if (read_ptr == write_ptr) { return full ? buffer.size() : 0; } @@ -72,10 +88,14 @@ struct RingBuffer { } uint8_t* RawReadPointer() const { + InterruptLock lock; + return reinterpret_cast(buffer.data() + read_ptr); } size_t ContiguousAvailableData() const { + InterruptLock lock; + if (read_ptr < write_ptr) { return AvailableData(); } diff --git a/arm/uart.cc b/arm/uart.cc index df7c3cf..00b25ed 100644 --- a/arm/uart.cc +++ b/arm/uart.cc @@ -70,7 +70,7 @@ async::task<> UartWrite(std::span data) { } } -#define GCC_HAS_BUG_101133 1 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101133 +#define GCC_HAS_BUG_101133 0 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101133 #if !GCC_HAS_BUG_101133 async::task<> UartWriteLoop(async::gimme>& data_gen) { while (1) { diff --git a/arm/uart_test.cc b/arm/uart_test.cc new file mode 100644 index 0000000..dd9157e --- /dev/null +++ b/arm/uart_test.cc @@ -0,0 +1,95 @@ +#include "uart.h" +#include "uart_async.h" +#include "fake_uart.h" + +#include +#include + +#include +#include +#include +#include + +using namespace ::testing; + +namespace { +std::atomic terminate; +std::counting_semaphore<12345> got_stuff(0); + +buffer rx; + +using namespace std::literals::chrono_literals; +} + +std::string drain_n(size_t n, std::chrono::system_clock::duration timeout) { + std::string txtout; + auto begin = std::chrono::system_clock::now(); + + while (std::chrono::system_clock::now() < begin + timeout) { + auto out = FakeUart_Drain(n); + if (out.data.empty()) { + continue; + } + + auto strout = std::string_view(reinterpret_cast(out.data.data()), + out.data.size()); + n -= out.data.size(); + txtout.append(strout); + if (n < 1) { + return txtout; + } + } + return ""; +} + +TEST(Uart, BasicSend) { + FakeUart_Reset(); + terminate = false; + + constexpr std::string_view kTestData = "blarg"; + auto buff = std::as_bytes(std::span{kTestData}); + + async::schedule(UartWrite(buff).h); + std::thread t( + []() { async::main_loop([]() -> bool { return terminate; }); }); + + std::string strout = drain_n(kTestData.size(), 1s); + EXPECT_EQ(strout, kTestData); + + terminate = true; + t.join(); +} + +async::task<> test_echo() { + async::task reader = UartReadLoop(); + async::gimme> feeder; + async::task<> writer = UartWriteLoop(feeder); + writer.h.resume(); // advance to first yield + while (1) { + uint8_t c = co_await reader; + feeder.feed(std::as_bytes(std::span{&c, 1})); + got_stuff.release(); + } +} + +TEST(Uart, Echo) { + FakeUart_Reset(); + terminate = false; + + constexpr std::string_view kTestData = "blargblaektrkblalasrjkh1!!"; + auto buff = std::as_bytes(std::span{kTestData}); + + async::schedule(test_echo().h); + std::thread t( + []() { async::main_loop([]() -> bool { return terminate; }); }); + + for (int j = 0; j < 100; j++) { + FakeUart_Feed(buff); + std::string txtout = drain_n(kTestData.size(), 1s); + + ASSERT_EQ(txtout, kTestData); + } + + terminate = true; + t.join(); +} \ No newline at end of file