Add Rust ping/pong test
Basically a clone of pingpong_test.cc but for Rust.
Change-Id: Ie04b90eeb6a399379115dcc15173e09665d569e3
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/events/BUILD b/aos/events/BUILD
index 4d5428b..dc6fa8c 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -259,6 +259,38 @@
],
)
+rust_test(
+ name = "pingpong_test_rs",
+ srcs = [
+ "pingpong_test.rs",
+ ],
+ data = [":pingpong_config"],
+ # TODO(adam.snaider): Remove later. For now we need this because when
+ # a rust test crashes inside of C++, the output gets captured which makes
+ # it pretty much impossible to figure out what happened.
+ env = {"RUST_TEST_NOCAPTURE": "1"},
+ rustc_flags = ["-Crelocation-model=static"],
+ target_compatible_with = select({
+ "//conditions:default": ["//tools/platforms/rust:has_support"],
+ "//tools:has_msan": ["@platforms//:incompatible"],
+ }),
+ deps = [
+ ":event_loop_runtime",
+ ":ping_lib_rs",
+ ":ping_rust_fbs",
+ ":pong_lib_rs",
+ ":pong_rust_fbs",
+ ":simulated_event_loop_rs",
+ "//aos:configuration_rs",
+ "//aos:configuration_rust_fbs",
+ "//aos:flatbuffers_rs",
+ "//aos:test_init_rs",
+ "@com_github_google_flatbuffers//rust",
+ "@crate_index//:futures",
+ "@rules_rust//tools/runfiles",
+ ],
+)
+
rust_binary(
name = "ping_rs",
srcs = [
diff --git a/aos/events/pingpong_test.rs b/aos/events/pingpong_test.rs
new file mode 100644
index 0000000..a20f66f
--- /dev/null
+++ b/aos/events/pingpong_test.rs
@@ -0,0 +1,108 @@
+#[cfg(test)]
+mod tests {
+ use std::{cell::Cell, time::Duration};
+
+ use aos_configuration::read_config_from;
+ use aos_events_event_loop_runtime::{EventLoopRuntimeHolder, Watcher};
+ use aos_events_simulated_event_loop::{SimulatedEventLoopFactory, SimulatedEventLoopHolder};
+ use aos_test_init::test_init;
+ use ping_lib::PingTask;
+ use ping_rust_fbs::aos::examples as ping;
+ use pong_rust_fbs::aos::examples as pong;
+ use runfiles::Runfiles;
+
+ // We use this trait to simplify leaking memory. For now, the event loop only allows
+ // data with a `'static` lifetime. Until that restriction is lifted, we may leak
+ // some memory in tests.
+ trait Leak: Sized {
+ fn leak(self) -> &'static mut Self {
+ Box::leak(Box::new(self))
+ }
+ }
+
+ impl<T> Leak for T {}
+
+ #[allow(unused)]
+ struct PingPongTest {
+ ping_event_loop: EventLoopRuntimeHolder<SimulatedEventLoopHolder>,
+ pong_event_loop: EventLoopRuntimeHolder<SimulatedEventLoopHolder>,
+ event_loop_factory: SimulatedEventLoopFactory<'static>,
+ }
+
+ impl PingPongTest {
+ pub fn init() -> PingPongTest {
+ test_init();
+ let r = Runfiles::create().unwrap();
+ let config =
+ read_config_from(&r.rlocation("org_frc971/aos/events/pingpong_config.json"))
+ .unwrap()
+ .leak();
+ let mut event_loop_factory = SimulatedEventLoopFactory::new(config);
+
+ let ping_event_loop = event_loop_factory.make_runtime("ping", None, |runtime| {
+ let ping = PingTask::new();
+ runtime.spawn(async move { ping.tasks(runtime, 10000).await });
+ });
+
+ let pong_event_loop = event_loop_factory.make_runtime("pong", None, |runtime| {
+ runtime.spawn(async move { pong_lib::pong(runtime).await })
+ });
+ PingPongTest {
+ event_loop_factory,
+ ping_event_loop,
+ pong_event_loop,
+ }
+ }
+
+ pub fn event_loop(&mut self) -> &mut SimulatedEventLoopFactory<'static> {
+ &mut self.event_loop_factory
+ }
+ }
+
+ #[test]
+ fn starts() {
+ let mut pingpong = PingPongTest::init();
+ pingpong.event_loop().run_for(Duration::from_secs(10));
+ }
+
+ #[test]
+ fn always_replies() {
+ let mut pingpong = PingPongTest::init();
+
+ // For now, the simulated event loop requires all references in the tasks
+ // to be `'static`, so we leak them for now until the restriction is lifted.
+ let ping_count: &Cell<i32> = Cell::new(1).leak();
+ let pong_count: &Cell<i32> = Cell::new(1).leak();
+
+ let _test_runtime = pingpong.event_loop().make_runtime("test", None, |runtime| {
+ let count_pings = async move {
+ let mut ping_watcher: Watcher<ping::Ping> = runtime.make_watcher("/test").unwrap();
+ loop {
+ let ping = ping_watcher.next().await;
+ assert_eq!(ping.message().unwrap().value(), ping_count.get());
+ ping_count.set(ping_count.get() + 1);
+ }
+ };
+ let count_pongs = async move {
+ let mut pong_watcher: Watcher<pong::Pong> = runtime.make_watcher("/test").unwrap();
+ loop {
+ let pong = pong_watcher.next().await;
+ assert_eq!(pong.message().unwrap().value(), pong_count.get());
+ pong_count.set(pong_count.get() + 1);
+ }
+ };
+
+ runtime.spawn(async move {
+ futures::join!(count_pings, count_pongs);
+ unreachable!();
+ });
+ });
+
+ pingpong.event_loop().run_for(Duration::from_secs(10));
+
+ // We run at t=0 and t=10 seconds, which means we run 1 extra time (Note that we started
+ // the count at 1, not 0).
+ assert_eq!(ping_count.get(), 1002);
+ assert_eq!(pong_count.get(), 1002);
+ }
+}