arm: add async tests

This commit is contained in:
Paul Mathieu 2022-06-19 09:41:17 +02:00
parent b9a9c2be7b
commit 15488550c0
5 changed files with 363 additions and 4 deletions

287
arm/async_test.cc Normal file
View File

@ -0,0 +1,287 @@
#include "async.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <array>
#include <atomic>
#include <chrono>
#include <cstdio>
#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 != 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<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);
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) {
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();
}

23
arm/filter.sh Normal file
View File

@ -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 '' "$@"

View File

@ -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)

18
arm/test.py Normal file
View File

@ -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')

3
arm/tsan.suppressions Normal file
View File

@ -0,0 +1,3 @@
race:std::__1::basic_ios<char, std::__1::char_traits<char> >::fill()
race:std::__1::ios_base::width()