#include "async.h" #include #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 != 3) { return; } printf("[%10i] ", uptime()); #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wformat-security" printf(fmt, args...); #pragma clang diagnostic pop 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); log(3, "got all of em, done"); 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) { while (next.try_acquire()); while (ready.try_acquire()); async::schedule(nested().h); std::thread t([]() { async::main_loop([]() -> bool { // for some reason the bare `acquire` fails sometimes :/ while (!next.try_acquire_for(10ms)); 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); ASSERT_FALSE(voiddone); ASSERT_EQ(result, 0); log(3, "step"); async::step(); // should proceed until yield ASSERT_FALSE(voiddone); ASSERT_EQ(result, 0); log(3, "step"); async::step(); // should proceed until end EXPECT_EQ(voiddone, true); ASSERT_EQ(result, 42); EXPECT_TRUE(done.try_acquire_for(1s)); } 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(); }