diff --git a/arm/async_test.cc b/arm/async_test.cc new file mode 100644 index 0000000..31b50dc --- /dev/null +++ b/arm/async_test.cc @@ -0,0 +1,287 @@ +#include "async.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "trace.h" + +using namespace ::testing; + +using async::AwaitableType; +using namespace std::literals::chrono_literals; + +int uptime() { + static std::chrono::system_clock::time_point start = + std::chrono::system_clock::now(); + + return std::chrono::duration_cast( + std::chrono::system_clock::now() - start) + .count(); +} + +template +void log(int i, const char* fmt, Args... args) { + if (i != 4) { + return; + } + printf("[%10i] ", uptime()); + printf(fmt, args...); + printf("\n"); +} + +namespace tracing { +void trace(TraceEvent) {} +} // namespace tracing + +struct SimpleAsyncTest : public Test { + SimpleAsyncTest() : done(0) {} + + std::binary_semaphore done; + + async::task<> test_task() { + co_await async::await(AwaitableType::kUartRx); + done.release(); + } + + void SetUp() { terminate = false; } + + static std::atomic terminate; +}; + +std::atomic SimpleAsyncTest::terminate; + +TEST_F(SimpleAsyncTest, EnqueueResume) { + async::schedule(test_task().h); + std::thread t( + []() { async::main_loop([]() -> bool { return terminate; }); }); + + async::resume(AwaitableType::kUartRx); + EXPECT_TRUE(done.try_acquire_for(1s)); + terminate = true; + + t.join(); +} + +struct LoopAsyncTest : public Test { + int in_isr = 0; + std::mutex m; + std::counting_semaphore<123456> processed; + + LoopAsyncTest() : processed(0) {} + + async::task<> loop_task() { + while (!terminate) { + co_await async::await(AwaitableType::kUartRx); + { + std::scoped_lock lock(m); + while (in_isr > 0) { + processed.release(); + log(1, "processed.release() in_isr: %i", in_isr); + in_isr--; + } + } + } + } + + void SetUp() { terminate = false; } + + static std::atomic terminate; +}; +std::atomic LoopAsyncTest::terminate; + +TEST_F(LoopAsyncTest, ManyEnqueueResume) { + constexpr int kLoops = 100; + + async::schedule(loop_task().h); + std::thread t( + []() { async::main_loop([]() -> bool { return terminate; }); }); + + for (int i = 0; i < kLoops; i++) { + std::scoped_lock lock(m); + in_isr++; + async::resume(AwaitableType::kUartRx); + log(1, "async::resume() in_isr: %i", in_isr); + } + int done = 0; + for (int i = 0; i < kLoops; i++) { + if (processed.try_acquire_for(2s)) { + { + std::scoped_lock lock(m); + log(1, "processed acquired"); + } + done++; + } else { + log(1, "skipping %i", i); + break; + } + } + + EXPECT_EQ(done, kLoops); + + terminate = true; + t.join(); +} + +struct NestedAsync : SimpleAsyncTest { + async::task interior() { co_return 42; } + async::task interior_yield() { + co_await async::delay(0); + co_return 42; + } + + async::task generator() { + int i = 41; + while (true) { + co_yield i++; + } + } + + std::atomic voiddone = false; + std::atomic result = 0; + + async::task<> voidstuff() { + voiddone = true; + log(3, "voidstuff()"); + co_return; + } + + async::task<> nested() { + log(3, "nested()"); + result = co_await interior(); + log(3, "got result"); + co_await voidstuff(); + log(3, "voidstuff returned"); + done.release(); + } + + async::task<> othernested() { + log(3, "othernested()"); + result = co_await interior_yield(); + log(3, "got result"); + co_await voidstuff(); + log(3, "voidstuff returned"); + done.release(); + } + + async::task<> call_generator() { + log(4, "call_generator()"); + auto g = generator(); + log(4, "generator()"); + + int a0 = co_await g; + log(4, "a0: %d", a0); + + int a1 = co_await g; + log(4, "a1: %d", a1); + + result = a1; + + g.h.destroy(); + + done.release(); + } + + static std::binary_semaphore next; + static std::binary_semaphore ready; +}; + +std::binary_semaphore NestedAsync::next(0); +std::binary_semaphore NestedAsync::ready(0); + +TEST_F(NestedAsync, SimpleNestedTest) { + async::schedule(nested().h); + std::thread t([]() { + async::main_loop([]() -> bool { + next.acquire(); + return terminate; + }); + }); + + ASSERT_FALSE(voiddone); + ASSERT_EQ(result, 0); + + next.release(); + + EXPECT_TRUE(done.try_acquire_for(1s)); + + ASSERT_TRUE(voiddone); + ASSERT_EQ(result, 42); + + terminate = true; + next.release(); + + t.join(); +} + +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)); + + ASSERT_FALSE(voiddone); + ASSERT_EQ(result, 0); + + next.release(); + ASSERT_TRUE(ready.try_acquire_for(1s)); + + 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) { + async::schedule(call_generator().h); + std::thread t([]() { + async::main_loop([]() -> bool { + ready.release(); + log(3, "loop ready"); + next.acquire(); + return terminate; + }); + }); + + ASSERT_EQ(result, 0); + + log(4, "starting stuff"); + + ASSERT_TRUE(ready.try_acquire_for(1s)); + next.release(); + ASSERT_TRUE(ready.try_acquire_for(1s)); + + ASSERT_EQ(result, 42); + + EXPECT_TRUE(done.try_acquire_for(1s)); + + terminate = true; + next.release(); + + t.join(); +} diff --git a/arm/filter.sh b/arm/filter.sh new file mode 100644 index 0000000..0954fff --- /dev/null +++ b/arm/filter.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +dos2unix "$@" + +sed \ + -e 's,02$,02 rx,' \ + -e 's,03$,03 tx,' \ + -e 's,04$,04 resume,' \ + -e 's,05$,05 enqueue,' \ + -e 's,06$,06 run task,' \ + -e 's,09$,09 schedule,' \ + -e 's,0a$,0a send,' \ + -e 's,0b$,0b recv,' \ + -e 's,0e$,0e task done,' \ + -e 's,10$,10 run parent,' \ + -e 's,11$,11 parent done,' \ + -e 's,12$,12 co_await task,' \ + -e 's,13$,13 suspend,' \ + -e 's,14$,14 UartWrite done,' \ + -e 's,15$,15 coroutine destroy,' \ + -e 's,16$,16 gimme waiting,' \ + -e 's,17$,17 gimme resume,' \ + -i '' "$@" diff --git a/arm/makefile b/arm/makefile index 0836039..b1b0f5e 100644 --- a/arm/makefile +++ b/arm/makefile @@ -46,14 +46,42 @@ deps = $(app_objects:.o=.d) $(bootloader_objects:.o=.d) clean: rm -rf *.elf *.bin $(tests) $(app_objects) $(bootloader_objects) $(deps) -HOSTCXX = clang++ +HOSTCXX = /usr/local/opt/llvm/bin/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 \ + -I/usr/local/include \ + -I/usr/local/include \ + -MP -MD -tests = ring_buffer_test +TSAN_CFLAGS = $(HOSTCFLAGS) -fsanitize=thread +ASAN_CFLAGS = $(HOSTCFLAGS) -fsanitize=address -fsanitize=leak + +tests = ring_buffer_test async_test_asan async_test_tsan + +.PRECIOUS: $(tests) test: $(tests) ring_buffer_test: ring_buffer_test.cc - $(HOSTCXX) -std=c++2a -o $@ $< -lgmock -lgtest -lgtest_main - ./$@ + mkdir test + $(HOSTCXX) $(HOSTCFLAGS) -o test/$@ $< $(HOSTLDFLAGS) + ./test/$@ + +%.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/$@ + +async_test_asan: async_test.cc async.host.o + mkdir -p test + $(HOSTCXX) $(ASAN_CFLAGS) -o test/$@ $^ $(HOSTLDFLAGS) + ASAN_OPTIONS=detect_leaks=1 ./test/$@ + +test_deps = async.host.d -include $(deps) +-include $(test_deps) diff --git a/arm/test.py b/arm/test.py new file mode 100644 index 0000000..b88acf9 --- /dev/null +++ b/arm/test.py @@ -0,0 +1,18 @@ +import serial + +s = serial.Serial('tty', 115200, timeout=6) +length = 1 + +log = open('test.log', 'wb') + +while True: + print(f'Writing {length} bytes') + s.write(b'b' * length); + data = s.read(length) + log.write(data) + if not data: + break + + length += 1 + +print(f'Read back 0 bytes') diff --git a/arm/tsan.suppressions b/arm/tsan.suppressions new file mode 100644 index 0000000..2734943 --- /dev/null +++ b/arm/tsan.suppressions @@ -0,0 +1,3 @@ +race:std::__1::basic_ios >::fill() + +race:std::__1::ios_base::width()