Create MockStarter in aos/starter

Change-Id: I58b36a8c0cfa9c6262ce9e1222cd6456dfb5ca1a
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/starter/BUILD b/aos/starter/BUILD
index 8a30408..9068caa 100644
--- a/aos/starter/BUILD
+++ b/aos/starter/BUILD
@@ -247,3 +247,16 @@
         "//aos/testing:googletest",
     ],
 )
+
+cc_library(
+    name = "mock_starter",
+    srcs = ["mock_starter.cc"],
+    hdrs = ["mock_starter.h"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos/events:simulated_event_loop",
+        "//aos/starter:starter_fbs",
+        "//aos/starter:starter_rpc_fbs",
+        "//aos/starter:starter_rpc_lib",
+    ],
+)
diff --git a/aos/starter/mock_starter.cc b/aos/starter/mock_starter.cc
new file mode 100644
index 0000000..5908abe
--- /dev/null
+++ b/aos/starter/mock_starter.cc
@@ -0,0 +1,115 @@
+#include "aos/starter/mock_starter.h"
+
+namespace aos {
+namespace starter {
+
+MockStarter::MockStarter(aos::EventLoop *event_loop)
+    : event_loop_(event_loop),
+      status_sender_(event_loop_->MakeSender<aos::starter::Status>("/aos")) {
+  aos::TimerHandler *send_timer =
+      event_loop_->AddTimer([this]() { SendStatus(); });
+
+  CHECK(aos::configuration::MultiNode(event_loop_->configuration()));
+
+  for (const aos::Node *node :
+       aos::configuration::GetNodes(event_loop_->configuration())) {
+    const aos::Channel *channel = aos::starter::StarterRpcChannelForNode(
+        event_loop_->configuration(), node);
+    if (aos::configuration::ChannelIsReadableOnNode(channel,
+                                                    event_loop_->node())) {
+      std::string_view channel_name = channel->name()->string_view();
+      event_loop_->MakeWatcher(
+          channel_name, [this](const aos::starter::StarterRpc &command) {
+            for (const flatbuffers::String *node : *command.nodes()) {
+              if (node->string_view() ==
+                  event_loop_->node()->name()->string_view()) {
+                CHECK(statuses_.count(command.name()->str()) > 0)
+                    << "Unable to find " << command.name()->string_view()
+                    << " in our list of applications.";
+                ApplicationStatus &status = statuses_[command.name()->str()];
+                switch (command.command()) {
+                  case aos::starter::Command::START:
+                    if (!status.running) {
+                      status.running = true;
+                      status.start_time = event_loop_->monotonic_now();
+                      status.id = next_id_++;
+                    }
+                    break;
+                  case aos::starter::Command::STOP:
+                    status.running = false;
+                    break;
+                  case aos::starter::Command::RESTART:
+                    status.running = true;
+                    status.start_time = event_loop_->monotonic_now();
+                    status.id = next_id_++;
+                }
+                SendStatus();
+              }
+            }
+          });
+    }
+  }
+
+  event_loop_->OnRun([this, send_timer]() {
+    send_timer->Setup(event_loop_->monotonic_now(), std::chrono::seconds(1));
+
+    for (const aos::Application *application :
+         *event_loop_->configuration()->applications()) {
+      if (aos::configuration::ApplicationShouldStart(
+              event_loop_->configuration(), event_loop_->node(), application)) {
+        statuses_[application->name()->str()] = ApplicationStatus{
+            next_id_++, application->autostart(), event_loop_->monotonic_now()};
+      }
+    }
+  });
+}
+
+void MockStarter::SendStatus() {
+  aos::Sender<aos::starter::Status>::Builder builder =
+      status_sender_.MakeBuilder();
+  std::vector<flatbuffers::Offset<aos::starter::ApplicationStatus>>
+      status_offsets;
+  for (const std::pair<const std::string, ApplicationStatus> &pair :
+       statuses_) {
+    const flatbuffers::Offset<flatbuffers::String> name_offset =
+        builder.fbb()->CreateString(pair.first);
+    aos::starter::ApplicationStatus::Builder status_builder =
+        builder.MakeBuilder<aos::starter::ApplicationStatus>();
+    status_builder.add_name(name_offset);
+    status_builder.add_state(pair.second.running
+                                 ? aos::starter::State::RUNNING
+                                 : aos::starter::State::STOPPED);
+    status_builder.add_last_exit_code(0);
+    status_builder.add_id(pair.second.id);
+    status_builder.add_last_stop_reason(
+        aos::starter::LastStopReason::STOP_REQUESTED);
+    status_builder.add_last_start_time(
+        pair.second.start_time.time_since_epoch().count());
+    if (pair.second.running) {
+      status_builder.add_pid(pair.second.id);
+    }
+    status_offsets.push_back(status_builder.Finish());
+  }
+  const flatbuffers::Offset<
+      flatbuffers::Vector<flatbuffers::Offset<aos::starter::ApplicationStatus>>>
+      statuses_offset = builder.fbb()->CreateVector(status_offsets);
+  aos::starter::Status::Builder status_builder =
+      builder.MakeBuilder<aos::starter::Status>();
+  status_builder.add_statuses(statuses_offset);
+  builder.CheckOk(builder.Send(status_builder.Finish()));
+}
+
+MockStarters::MockStarters(aos::SimulatedEventLoopFactory *event_loop_factory) {
+  CHECK(aos::configuration::MultiNode(event_loop_factory->configuration()));
+  for (const aos::Node *node :
+       aos::configuration::GetNodes(event_loop_factory->configuration())) {
+    event_loops_.emplace_back(
+        event_loop_factory->GetNodeEventLoopFactory(node)->MakeEventLoop(
+            "starterd"));
+    mock_starters_.emplace_back(
+        std::make_unique<MockStarter>(event_loops_.back().get()));
+  }
+}
+
+}  // namespace starter
+}  // namespace aos
diff --git a/aos/starter/mock_starter.h b/aos/starter/mock_starter.h
new file mode 100644
index 0000000..a0c1b76
--- /dev/null
+++ b/aos/starter/mock_starter.h
@@ -0,0 +1,54 @@
+#include <map>
+
+#include "aos/events/event_loop.h"
+#include "aos/events/simulated_event_loop.h"
+#include "aos/starter/starter_generated.h"
+#include "aos/starter/starter_rpc_generated.h"
+#include "aos/starter/starterd_lib.h"
+
+namespace aos {
+namespace starter {
+
+// Simple mock of starterd that updates the starter status message to act as
+// though applications are started and stopped when requested.
+// TODO(james.kuszmaul): Consider integrating with SimulatedEventLoopFactory.
+class MockStarter {
+ public:
+  struct ApplicationStatus {
+    int id;
+    bool running;
+    aos::monotonic_clock::time_point start_time;
+  };
+
+  MockStarter(aos::EventLoop *event_loop);
+
+  const aos::Node *node() const { return event_loop_->node(); }
+
+  const std::map<std::string, ApplicationStatus> &statuses() const {
+    return statuses_;
+  }
+
+ private:
+  void SendStatus();
+
+  aos::EventLoop *event_loop_;
+  aos::Sender<aos::starter::Status> status_sender_;
+  std::map<std::string, ApplicationStatus> statuses_;
+  int next_id_ = 0;
+};
+
+// Spins up MockStarter's for each node.
+class MockStarters {
+ public:
+  MockStarters(aos::SimulatedEventLoopFactory *event_loop_factory);
+  const std::vector<std::unique_ptr<MockStarter>> &starters() const {
+    return mock_starters_;
+  }
+
+ private:
+  std::vector<std::unique_ptr<aos::EventLoop>> event_loops_;
+  std::vector<std::unique_ptr<MockStarter>> mock_starters_;
+};
+
+}  // namespace starter
+}  // namespace aos