arm: add async tests
This commit is contained in:
parent
b9a9c2be7b
commit
15488550c0
287
arm/async_test.cc
Normal file
287
arm/async_test.cc
Normal 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
23
arm/filter.sh
Normal 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 '' "$@"
|
36
arm/makefile
36
arm/makefile
@ -46,14 +46,42 @@ deps = $(app_objects:.o=.d) $(bootloader_objects:.o=.d)
|
|||||||
clean:
|
clean:
|
||||||
rm -rf *.elf *.bin $(tests) $(app_objects) $(bootloader_objects) $(deps)
|
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)
|
test: $(tests)
|
||||||
|
|
||||||
ring_buffer_test: ring_buffer_test.cc
|
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 $(deps)
|
||||||
|
-include $(test_deps)
|
||||||
|
18
arm/test.py
Normal file
18
arm/test.py
Normal 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
3
arm/tsan.suppressions
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
race:std::__1::basic_ios<char, std::__1::char_traits<char> >::fill()
|
||||||
|
|
||||||
|
race:std::__1::ios_base::width()
|
Loading…
Reference in New Issue
Block a user