All host tests currently pass Some async refactors may not work well on device, will try later
		
			
				
	
	
		
			281 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			281 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "async.h"
 | |
| 
 | |
| #include <gmock/gmock.h>
 | |
| #include <gtest/gtest.h>
 | |
| 
 | |
| #include <array>
 | |
| #include <atomic>
 | |
| #include <chrono>
 | |
| #include <cstdio>
 | |
| #include <mutex>
 | |
| #include <semaphore>
 | |
| #include <thread>
 | |
| 
 | |
| #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::microseconds>(
 | |
|                std::chrono::system_clock::now() - start)
 | |
|         .count();
 | |
| }
 | |
| 
 | |
| template <typename... Args>
 | |
| 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<bool> terminate;
 | |
| };
 | |
| 
 | |
| std::atomic<bool> 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<bool> terminate;
 | |
| };
 | |
| std::atomic<bool> 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<int> interior() { co_return 42; }
 | |
|     async::task<int> interior_yield() {
 | |
|         co_await async::delay(0);
 | |
|         co_return 42;
 | |
|     }
 | |
| 
 | |
|     async::task<int> generator() {
 | |
|         int i = 41;
 | |
|         while (true) {
 | |
|             co_yield i++;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     std::atomic<bool> voiddone = false;
 | |
|     std::atomic<int> 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();
 | |
| }
 |