Add test case for ping and pong

This provides a simple example for how to test, and also provides a
staring point for the tests for the logger.

Change-Id: Ieac6858156627c0efcdbb739612e6a4efaf19b80
diff --git a/aos/BUILD b/aos/BUILD
index a2c7134..4f87873 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -442,7 +442,8 @@
         "testdata/config3.json",
         "testdata/expected.json",
         "//aos/events:config.fb.json",
-        "//aos/events:pingpong.bfbs",
+        "//aos/events:ping.bfbs",
+        "//aos/events:pong.bfbs",
     ],
     deps = [
         ":configuration",
diff --git a/aos/events/BUILD b/aos/events/BUILD
index 17ed688..3b3fb52 100644
--- a/aos/events/BUILD
+++ b/aos/events/BUILD
@@ -1,4 +1,5 @@
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+load("//aos:config.bzl", "aos_config")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -9,8 +10,14 @@
 )
 
 flatbuffer_cc_library(
-    name = "pingpong_fbs",
-    srcs = ["pingpong.fbs"],
+    name = "ping_fbs",
+    srcs = ["ping.fbs"],
+    gen_reflections = 1,
+)
+
+flatbuffer_cc_library(
+    name = "pong_fbs",
+    srcs = ["pong.fbs"],
     gen_reflections = 1,
 )
 
@@ -41,14 +48,31 @@
     ],
 )
 
+cc_library(
+    name = "ping_lib",
+    srcs = [
+        "ping_lib.cc",
+    ],
+    hdrs = [
+        "ping_lib.h",
+    ],
+    deps = [
+        ":event_loop",
+        ":ping_fbs",
+        ":pong_fbs",
+        "//aos:json_to_flatbuffer",
+        "@com_github_google_glog//:glog",
+    ],
+)
+
 cc_binary(
     name = "ping",
     srcs = [
         "ping.cc",
     ],
-    data = ["config.fb.json"],
+    data = ["pingpong_config.json"],
     deps = [
-        ":pingpong_fbs",
+        ":ping_lib",
         ":shm_event_loop",
         "//aos:configuration",
         "//aos:init",
@@ -57,14 +81,41 @@
     ],
 )
 
+aos_config(
+    name = "pingpong_config",
+    src = "config.fb.json",
+    flatbuffers = [
+        ":ping_fbs",
+        ":pong_fbs",
+    ],
+)
+
+cc_library(
+    name = "pong_lib",
+    srcs = [
+        "pong_lib.cc",
+    ],
+    hdrs = [
+        "pong_lib.h",
+    ],
+    deps = [
+        ":event_loop",
+        ":ping_fbs",
+        ":pong_fbs",
+        "@com_github_google_glog//:glog",
+    ],
+)
+
 cc_binary(
     name = "pong",
     srcs = [
         "pong.cc",
     ],
-    data = ["config.fb.json"],
+    data = ["pingpong_config.json"],
     deps = [
-        ":pingpong_fbs",
+        ":ping_fbs",
+        ":pong_fbs",
+        ":pong_lib",
         ":shm_event_loop",
         "//aos:configuration",
         "//aos:init",
@@ -73,6 +124,20 @@
     ],
 )
 
+cc_test(
+    name = "pingpong_test",
+    srcs = ["pingpong_test.cc"],
+    data = [":pingpong_config.json"],
+    deps = [
+        ":ping_lib",
+        ":pong_lib",
+        ":simulated_event_loop",
+        "//aos:configuration",
+        "//aos:flatbuffers",
+        "//aos/testing:googletest",
+    ],
+)
+
 cc_library(
     name = "shm_event_loop",
     srcs = ["shm_event_loop.cc"],
diff --git a/aos/events/ping.cc b/aos/events/ping.cc
index b904cb2..66c8f12 100644
--- a/aos/events/ping.cc
+++ b/aos/events/ping.cc
@@ -1,81 +1,18 @@
-#include <chrono>
-
 #include "aos/configuration.h"
-#include "aos/events/pingpong_generated.h"
+#include "aos/events/ping_lib.h"
 #include "aos/events/shm_event_loop.h"
 #include "aos/init.h"
 #include "aos/json_to_flatbuffer.h"
 #include "gflags/gflags.h"
 #include "glog/logging.h"
 
-DEFINE_int32(sleep_ms, 10, "Time to sleep between pings");
-
-namespace aos {
-
-namespace chrono = std::chrono;
-
-class Ping {
- public:
-  Ping(EventLoop *event_loop)
-      : event_loop_(event_loop),
-        sender_(event_loop_->MakeSender<examples::Ping>("/test")) {
-    timer_handle_ = event_loop_->AddTimer([this]() { SendPing(); });
-
-    event_loop_->MakeWatcher(
-        "/test", [this](const examples::Pong &pong) { HandlePong(pong); });
-
-    event_loop_->OnRun([this]() {
-      timer_handle_->Setup(event_loop_->monotonic_now(),
-                           chrono::milliseconds(FLAGS_sleep_ms));
-    });
-
-    event_loop_->SetRuntimeRealtimePriority(5);
-  }
-
-  void SendPing() {
-    ++count_;
-    aos::Sender<examples::Ping>::Builder msg = sender_.MakeBuilder();
-    examples::Ping::Builder builder = msg.MakeBuilder<examples::Ping>();
-    builder.add_value(count_);
-    builder.add_send_time(
-        event_loop_->monotonic_now().time_since_epoch().count());
-    CHECK(msg.Send(builder.Finish()));
-    VLOG(2) << "Sending ping";
-  }
-
-  void HandlePong(const examples::Pong &pong) {
-    const aos::monotonic_clock::time_point monotonic_send_time(
-        chrono::nanoseconds(pong.initial_send_time()));
-    const aos::monotonic_clock::time_point monotonic_now =
-        event_loop_->monotonic_now();
-
-    const chrono::nanoseconds round_trip_time =
-        monotonic_now - monotonic_send_time;
-
-    if (pong.value() == count_) {
-      VLOG(1) << "Elapsed time " << round_trip_time.count() << " ns "
-              << FlatbufferToJson(&pong);
-    } else {
-      VLOG(1) << "Missmatched pong message";
-    }
-  }
-
- private:
-  EventLoop *event_loop_;
-  aos::Sender<examples::Ping> sender_;
-  TimerHandler *timer_handle_;
-  int count_ = 0;
-};
-
-}  // namespace aos
-
 int main(int argc, char **argv) {
   FLAGS_logtostderr = true;
   google::InitGoogleLogging(argv[0]);
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
   aos::FlatbufferDetachedBuffer<aos::Configuration> config =
-      aos::configuration::ReadConfig("aos/events/config.fb.json");
+      aos::configuration::ReadConfig("aos/events/pingpong_config.json");
 
   ::aos::ShmEventLoop event_loop(&config.message());
 
diff --git a/aos/events/ping.fbs b/aos/events/ping.fbs
new file mode 100644
index 0000000..c41498b
--- /dev/null
+++ b/aos/events/ping.fbs
@@ -0,0 +1,8 @@
+namespace aos.examples;
+
+table Ping {
+  value:int;
+  send_time:long;
+}
+
+root_type Ping;
diff --git a/aos/events/ping_lib.cc b/aos/events/ping_lib.cc
new file mode 100644
index 0000000..31c7b18
--- /dev/null
+++ b/aos/events/ping_lib.cc
@@ -0,0 +1,58 @@
+#include "aos/events/ping_lib.h"
+
+#include "aos/events/pong_generated.h"
+#include "aos/events/ping_generated.h"
+#include "aos/json_to_flatbuffer.h"
+#include "gflags/gflags.h"
+#include "glog/logging.h"
+
+DEFINE_int32(sleep_ms, 10, "Time to sleep between pings");
+
+namespace aos {
+
+namespace chrono = std::chrono;
+
+Ping::Ping(EventLoop *event_loop)
+    : event_loop_(event_loop),
+      sender_(event_loop_->MakeSender<examples::Ping>("/test")) {
+  timer_handle_ = event_loop_->AddTimer([this]() { SendPing(); });
+
+  event_loop_->MakeWatcher(
+      "/test", [this](const examples::Pong &pong) { HandlePong(pong); });
+
+  event_loop_->OnRun([this]() {
+    timer_handle_->Setup(event_loop_->monotonic_now(),
+                         chrono::milliseconds(FLAGS_sleep_ms));
+  });
+
+  event_loop_->SetRuntimeRealtimePriority(5);
+}
+
+void Ping::SendPing() {
+  ++count_;
+  aos::Sender<examples::Ping>::Builder msg = sender_.MakeBuilder();
+  examples::Ping::Builder builder = msg.MakeBuilder<examples::Ping>();
+  builder.add_value(count_);
+  builder.add_send_time(
+      event_loop_->monotonic_now().time_since_epoch().count());
+  CHECK(msg.Send(builder.Finish()));
+  VLOG(2) << "Sending ping";
+}
+
+void Ping::HandlePong(const examples::Pong &pong) {
+  const aos::monotonic_clock::time_point monotonic_send_time(
+      chrono::nanoseconds(pong.initial_send_time()));
+  const aos::monotonic_clock::time_point monotonic_now =
+      event_loop_->monotonic_now();
+
+  const chrono::nanoseconds round_trip_time =
+      monotonic_now - monotonic_send_time;
+
+  if (pong.value() == count_) {
+    VLOG(1) << "Elapsed time " << round_trip_time.count() << " ns "
+            << FlatbufferToJson(&pong);
+  } else {
+    VLOG(1) << "Missmatched pong message";
+  }
+}
+}  // namespace aos
diff --git a/aos/events/ping_lib.h b/aos/events/ping_lib.h
new file mode 100644
index 0000000..2cc4f5e
--- /dev/null
+++ b/aos/events/ping_lib.h
@@ -0,0 +1,34 @@
+#ifndef AOS_EVENTS_PING_LIB_H_
+#define AOS_EVENTS_PING_LIB_H_
+
+#include <chrono>
+
+#include "aos/events/event_loop.h"
+#include "aos/events/pong_generated.h"
+#include "aos/events/ping_generated.h"
+
+namespace aos {
+
+// Class which sends out a Ping message every X ms, and times the response.
+class Ping {
+ public:
+  Ping(EventLoop *event_loop);
+
+ private:
+  // Sends out the ping message with an incrementing count.
+  void SendPing();
+
+  // Receives the reply and measures the latency.
+  void HandlePong(const examples::Pong &pong);
+
+  aos::EventLoop *event_loop_;
+  aos::Sender<examples::Ping> sender_;
+  // Timer handle which sends the Ping message.
+  aos::TimerHandler *timer_handle_;
+  // Number of pings sent.
+  int count_ = 0;
+};
+
+}  // namespace aos
+
+#endif  // AOS_EVENTS_PING_LIB_H_
diff --git a/aos/events/pingpong_test.cc b/aos/events/pingpong_test.cc
new file mode 100644
index 0000000..0bc1f88
--- /dev/null
+++ b/aos/events/pingpong_test.cc
@@ -0,0 +1,80 @@
+#include "aos/events/ping_lib.h"
+#include "aos/events/pong_lib.h"
+#include "aos/events/simulated_event_loop.h"
+#include "aos/json_to_flatbuffer.h"
+#include "glog/logging.h"
+#include "gtest/gtest.h"
+
+namespace aos {
+namespace testing {
+
+namespace chrono = std::chrono;
+
+class PingPongTest : public ::testing::Test {
+ public:
+  PingPongTest()
+      : config_(aos::configuration::ReadConfig(
+            "aos/events/pingpong_config.json")),
+        event_loop_factory_(&config_.message()),
+        ping_event_loop_(event_loop_factory_.MakeEventLoop()),
+        ping_(ping_event_loop_.get()),
+        pong_event_loop_(event_loop_factory_.MakeEventLoop()),
+        pong_(pong_event_loop_.get()) {}
+
+  // Config and factory.
+  // The factory is a factory for connected event loops.  Each application needs
+  // a separate event loop (because you can't send a message to yourself in a
+  // single event loop).  The created event loops can then send messages to each
+  // other and trigger callbacks to be called, or fetchers to receive data.
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config_;
+  SimulatedEventLoopFactory event_loop_factory_;
+
+  // Event loop and app for Ping
+  std::unique_ptr<EventLoop> ping_event_loop_;
+  Ping ping_;
+
+  // Event loop and app for Pong
+  std::unique_ptr<EventLoop> pong_event_loop_;
+  Pong pong_;
+};
+
+// Tests that we can startup at all.  This confirms that the channels are all in
+// the config.
+TEST_F(PingPongTest, Starts) {
+  // RunFor lives in the factory because all the loops need to run together, and
+  // the factory is the only object that conceptually knows about all of the
+  // loops at once.
+  event_loop_factory_.RunFor(chrono::seconds(10));
+}
+
+// Tests that the number of pong messages matches the number of ping messages.
+TEST_F(PingPongTest, AlwaysReplies) {
+  std::unique_ptr<EventLoop> test_event_loop =
+      event_loop_factory_.MakeEventLoop();
+
+  int ping_count = 0;
+  int pong_count = 0;
+
+  // Confirm that the ping value matches.
+  test_event_loop->MakeWatcher("/test",
+                               [&ping_count](const examples::Ping &ping) {
+                                 EXPECT_EQ(ping.value(), ping_count + 1);
+                                 ++ping_count;
+                               });
+  // Confirm that the ping and pong counts both match, and the value also
+  // matches.
+  test_event_loop->MakeWatcher(
+      "/test", [&pong_count, &ping_count](const examples::Pong &pong) {
+        EXPECT_EQ(pong.value(), pong_count + 1);
+        ++pong_count;
+        EXPECT_EQ(ping_count, pong_count);
+      });
+
+  event_loop_factory_.RunFor(chrono::seconds(10));
+
+  // We run at t=0 and t=10 seconds, which means we run 1 extra time.
+  EXPECT_EQ(ping_count, 1001);
+}
+
+}  // namespace testing
+}  // namespace aos
diff --git a/aos/events/pong.cc b/aos/events/pong.cc
index 1504bfb..339d4d7 100644
--- a/aos/events/pong.cc
+++ b/aos/events/pong.cc
@@ -1,47 +1,18 @@
-#include <chrono>
-
 #include "aos/configuration.h"
-#include "aos/events/pingpong_generated.h"
+#include "aos/events/ping_generated.h"
+#include "aos/events/pong_generated.h"
+#include "aos/events/pong_lib.h"
 #include "aos/events/shm_event_loop.h"
 #include "aos/init.h"
-#include "aos/json_to_flatbuffer.h"
-#include "gflags/gflags.h"
 #include "glog/logging.h"
 
-namespace aos {
-
-namespace chrono = std::chrono;
-
-class Pong {
- public:
-  Pong(EventLoop *event_loop)
-      : event_loop_(event_loop),
-        sender_(event_loop_->MakeSender<examples::Pong>("/test")) {
-    event_loop_->MakeWatcher("/test", [this](const examples::Ping &ping) {
-      aos::Sender<examples::Pong>::Builder msg = sender_.MakeBuilder();
-      examples::Pong::Builder builder = msg.MakeBuilder<examples::Pong>();
-      builder.add_value(ping.value());
-      builder.add_initial_send_time(ping.send_time());
-      CHECK(msg.Send(builder.Finish()));
-    });
-
-    event_loop_->SetRuntimeRealtimePriority(5);
-  }
-
- private:
-  EventLoop *event_loop_;
-  aos::Sender<examples::Pong> sender_;
-};
-
-}  // namespace aos
-
 int main(int argc, char **argv) {
   FLAGS_logtostderr = true;
   google::InitGoogleLogging(argv[0]);
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
   aos::FlatbufferDetachedBuffer<aos::Configuration> config =
-      aos::configuration::ReadConfig("aos/events/config.fb.json");
+      aos::configuration::ReadConfig("aos/events/pingpong_config.json");
 
   ::aos::ShmEventLoop event_loop(&config.message());
 
diff --git a/aos/events/pong.fbs b/aos/events/pong.fbs
new file mode 100644
index 0000000..115dbbe
--- /dev/null
+++ b/aos/events/pong.fbs
@@ -0,0 +1,8 @@
+namespace aos.examples;
+
+table Pong {
+  value:int;
+  initial_send_time:long;
+}
+
+root_type Pong;
diff --git a/aos/events/pong_lib.cc b/aos/events/pong_lib.cc
new file mode 100644
index 0000000..5ff38b9
--- /dev/null
+++ b/aos/events/pong_lib.cc
@@ -0,0 +1,24 @@
+#include "aos/events/pong_lib.h"
+
+#include "aos/events/event_loop.h"
+#include "aos/events/pong_generated.h"
+#include "aos/events/ping_generated.h"
+#include "glog/logging.h"
+
+namespace aos {
+
+Pong::Pong(EventLoop *event_loop)
+    : event_loop_(event_loop),
+      sender_(event_loop_->MakeSender<examples::Pong>("/test")) {
+  event_loop_->MakeWatcher("/test", [this](const examples::Ping &ping) {
+    aos::Sender<examples::Pong>::Builder msg = sender_.MakeBuilder();
+    examples::Pong::Builder builder = msg.MakeBuilder<examples::Pong>();
+    builder.add_value(ping.value());
+    builder.add_initial_send_time(ping.send_time());
+    CHECK(msg.Send(builder.Finish()));
+  });
+
+  event_loop_->SetRuntimeRealtimePriority(5);
+}
+
+}  // namespace aos
diff --git a/aos/events/pong_lib.h b/aos/events/pong_lib.h
new file mode 100644
index 0000000..17d779c
--- /dev/null
+++ b/aos/events/pong_lib.h
@@ -0,0 +1,22 @@
+#ifndef AOS_EVENTS_PONG_LIB_H_
+#define AOS_EVENTS_PONG_LIB_H_
+
+#include "aos/events/event_loop.h"
+#include "aos/events/pong_generated.h"
+#include "aos/events/ping_generated.h"
+
+namespace aos {
+
+// Class which replies to a Ping message with a Pong message immediately.
+class Pong {
+ public:
+  Pong(EventLoop *event_loop);
+
+ private:
+  EventLoop *event_loop_;
+  aos::Sender<examples::Pong> sender_;
+};
+
+}  // namespace aos
+
+#endif  // AOS_EVENTS_PONG_LIB_H_