Merge "Fix merging schemas in aos configs"
diff --git a/aos/configuration.fbs b/aos/configuration.fbs
index bfac6a2..3401b43 100644
--- a/aos/configuration.fbs
+++ b/aos/configuration.fbs
@@ -143,6 +143,9 @@
 
   // List of arguments to be passed to application
   args:[string] (id: 4);
+
+  // Indicates that application should be executed on boot.
+  autostart:bool = true (id: 6);
 }
 
 // Per node data and connection information.
diff --git a/aos/events/event_loop_param_test.cc b/aos/events/event_loop_param_test.cc
index 1460663..b976d8f 100644
--- a/aos/events/event_loop_param_test.cc
+++ b/aos/events/event_loop_param_test.cc
@@ -780,11 +780,23 @@
 
 // Verify that SetRuntimeAffinity fails while running.
 TEST_P(AbstractEventLoopDeathTest, SetRuntimeAffinity) {
+  const cpu_set_t available = GetCurrentThreadAffinity();
+  int first_cpu = -1;
+  for (int i = 0; i < CPU_SETSIZE; ++i) {
+    if (CPU_ISSET(i, &available)) {
+      first_cpu = i;
+      break;
+      continue;
+    }
+  }
+  CHECK_NE(first_cpu, -1) << ": Default affinity has no CPUs?";
+
   auto loop = MakePrimary();
   // Confirm that runtime priority calls work when not running.
-  loop->SetRuntimeAffinity(MakeCpusetFromCpus({0}));
+  loop->SetRuntimeAffinity(MakeCpusetFromCpus({first_cpu}));
 
-  loop->OnRun([&]() { loop->SetRuntimeAffinity(MakeCpusetFromCpus({1})); });
+  loop->OnRun(
+      [&]() { loop->SetRuntimeAffinity(MakeCpusetFromCpus({first_cpu})); });
 
   EXPECT_DEATH(Run(), "Cannot set affinity while running");
 }
@@ -937,7 +949,7 @@
 
 // Verify that timer intervals and duration function properly.
 TEST_P(AbstractEventLoopTest, TimerIntervalAndDuration) {
-  // Force a slower rate so we are guarenteed to have reports for our timer.
+  // Force a slower rate so we are guaranteed to have reports for our timer.
   FLAGS_timing_report_ms = 2000;
 
   const int kCount = 5;
@@ -977,9 +989,9 @@
   Run();
 
   // Confirm that we got both the right number of samples, and it's odd.
-  EXPECT_EQ(times.size(), static_cast<size_t>(kCount));
-  EXPECT_EQ(times.size(), expected_times.size());
-  EXPECT_EQ((times.size() % 2), 1);
+  ASSERT_EQ(times.size(), static_cast<size_t>(kCount));
+  ASSERT_EQ(times.size(), expected_times.size());
+  ASSERT_EQ((times.size() % 2), 1);
 
   // Grab the middle sample.
   ::aos::monotonic_clock::time_point average_time = times[times.size() / 2];
@@ -1064,6 +1076,7 @@
 // Verify that we can change a timer's parameters during execution.
 TEST_P(AbstractEventLoopTest, TimerChangeParameters) {
   auto loop = MakePrimary();
+  loop->SetRuntimeRealtimePriority(1);
   std::vector<monotonic_clock::time_point> iteration_list;
 
   auto test_timer = loop->AddTimer([&iteration_list, &loop]() {
@@ -1072,42 +1085,45 @@
 
   monotonic_clock::time_point s;
   auto modifier_timer = loop->AddTimer([&test_timer, &s]() {
-    test_timer->Setup(s + chrono::milliseconds(45), chrono::milliseconds(30));
+    test_timer->Setup(s + chrono::milliseconds(1750),
+                      chrono::milliseconds(600));
   });
 
   s = loop->monotonic_now();
-  test_timer->Setup(s, chrono::milliseconds(20));
-  modifier_timer->Setup(s + chrono::milliseconds(45));
-  EndEventLoop(loop.get(), chrono::milliseconds(150));
+  test_timer->Setup(s, chrono::milliseconds(500));
+  modifier_timer->Setup(s + chrono::milliseconds(1250));
+  EndEventLoop(loop.get(), chrono::milliseconds(3950));
   Run();
 
-  EXPECT_EQ(iteration_list.size(), 7);
-  EXPECT_EQ(iteration_list[0], s);
-  EXPECT_EQ(iteration_list[1], s + chrono::milliseconds(20));
-  EXPECT_EQ(iteration_list[2], s + chrono::milliseconds(40));
-  EXPECT_EQ(iteration_list[3], s + chrono::milliseconds(45));
-  EXPECT_EQ(iteration_list[4], s + chrono::milliseconds(75));
-  EXPECT_EQ(iteration_list[5], s + chrono::milliseconds(105));
-  EXPECT_EQ(iteration_list[6], s + chrono::milliseconds(135));
+  EXPECT_THAT(
+      iteration_list,
+      ::testing::ElementsAre(
+          s, s + chrono::milliseconds(500), s + chrono::milliseconds(1000),
+          s + chrono::milliseconds(1750), s + chrono::milliseconds(2350),
+          s + chrono::milliseconds(2950), s + chrono::milliseconds(3550)));
 }
 
 // Verify that we can disable a timer during execution.
 TEST_P(AbstractEventLoopTest, TimerDisable) {
   auto loop = MakePrimary();
+  loop->SetRuntimeRealtimePriority(1);
   ::std::vector<::aos::monotonic_clock::time_point> iteration_list;
 
   auto test_timer = loop->AddTimer([&iteration_list, &loop]() {
-    iteration_list.push_back(loop->monotonic_now());
+    iteration_list.push_back(loop->context().monotonic_event_time);
   });
 
   auto ender_timer = loop->AddTimer([&test_timer]() { test_timer->Disable(); });
 
-  test_timer->Setup(loop->monotonic_now(), ::std::chrono::milliseconds(20));
-  ender_timer->Setup(loop->monotonic_now() + ::std::chrono::milliseconds(45));
-  EndEventLoop(loop.get(), ::std::chrono::milliseconds(150));
+  monotonic_clock::time_point s = loop->monotonic_now();
+  test_timer->Setup(s, ::std::chrono::milliseconds(70));
+  ender_timer->Setup(s + ::std::chrono::milliseconds(200));
+  EndEventLoop(loop.get(), ::std::chrono::milliseconds(350));
   Run();
 
-  EXPECT_EQ(iteration_list.size(), 3);
+  EXPECT_THAT(iteration_list,
+              ::testing::ElementsAre(s, s + chrono::milliseconds(70),
+                                     s + chrono::milliseconds(140)));
 }
 
 // Verify that a timer can disable itself.
@@ -1175,7 +1191,7 @@
 }
 
 // Verifies that the event loop implementations detect when Channel is not a
-// pointer into confguration()
+// pointer into configuration()
 TEST_P(AbstractEventLoopDeathTest, InvalidChannel) {
   auto loop = MakePrimary();
 
@@ -1417,7 +1433,7 @@
 // Tests that a couple phased loops run in a row result in the correct offset
 // and period.
 TEST_P(AbstractEventLoopTest, PhasedLoopTest) {
-  // Force a slower rate so we are guarenteed to have reports for our phased
+  // Force a slower rate so we are guaranteed to have reports for our phased
   // loop.
   FLAGS_timing_report_ms = 2000;
 
@@ -1470,9 +1486,9 @@
   Run();
 
   // Confirm that we got both the right number of samples, and it's odd.
-  EXPECT_EQ(times.size(), static_cast<size_t>(kCount));
-  EXPECT_EQ(times.size(), expected_times.size());
-  EXPECT_EQ((times.size() % 2), 1);
+  ASSERT_EQ(times.size(), static_cast<size_t>(kCount));
+  ASSERT_EQ(times.size(), expected_times.size());
+  ASSERT_EQ((times.size() % 2), 1);
 
   // Grab the middle sample.
   ::aos::monotonic_clock::time_point average_time = times[times.size() / 2];
diff --git a/aos/events/logging/log_reader.cc b/aos/events/logging/log_reader.cc
index 169311e..d6213ae 100644
--- a/aos/events/logging/log_reader.cc
+++ b/aos/events/logging/log_reader.cc
@@ -49,6 +49,24 @@
 namespace logger {
 namespace {
 
+bool CompareChannels(const Channel *c,
+                     ::std::pair<std::string_view, std::string_view> p) {
+  int name_compare = c->name()->string_view().compare(p.first);
+  if (name_compare == 0) {
+    return c->type()->string_view() < p.second;
+  } else if (name_compare < 0) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+bool EqualsChannels(const Channel *c,
+                    ::std::pair<std::string_view, std::string_view> p) {
+  return c->name()->string_view() == p.first &&
+         c->type()->string_view() == p.second;
+}
+
 // Copies the channel, removing the schema as we go.  If new_name is provided,
 // it is used instead of the name inside the channel.  If new_type is provided,
 // it is used instead of the type in the channel.
@@ -1231,6 +1249,30 @@
   // TODO(austin): Lazily re-build to save CPU?
 }
 
+std::vector<const Channel *> LogReader::RemappedChannels() const {
+  std::vector<const Channel *> result;
+  result.reserve(remapped_channels_.size());
+  for (auto &pair : remapped_channels_) {
+    const Channel *const logged_channel =
+        CHECK_NOTNULL(logged_configuration()->channels()->Get(pair.first));
+
+    auto channel_iterator = std::lower_bound(
+        remapped_configuration_->channels()->cbegin(),
+        remapped_configuration_->channels()->cend(),
+        std::make_pair(std::string_view(pair.second.remapped_name),
+                       logged_channel->type()->string_view()),
+        CompareChannels);
+
+    CHECK(channel_iterator != remapped_configuration_->channels()->cend());
+    CHECK(EqualsChannels(
+        *channel_iterator,
+        std::make_pair(std::string_view(pair.second.remapped_name),
+                       logged_channel->type()->string_view())));
+    result.push_back(*channel_iterator);
+  }
+  return result;
+}
+
 const Channel *LogReader::RemapChannel(const EventLoop *event_loop,
                                        const Node *node,
                                        const Channel *channel) {
diff --git a/aos/events/logging/log_reader.h b/aos/events/logging/log_reader.h
index 8293956..5dfaeea 100644
--- a/aos/events/logging/log_reader.h
+++ b/aos/events/logging/log_reader.h
@@ -173,6 +173,9 @@
     return channel->logger() != LoggerConfig::NOT_LOGGED;
   }
 
+  // Returns a list of all the original channels from remapping.
+  std::vector<const Channel *> RemappedChannels() const;
+
   SimulatedEventLoopFactory *event_loop_factory() {
     return event_loop_factory_;
   }
diff --git a/aos/events/logging/logger_test.cc b/aos/events/logging/logger_test.cc
index 8d8a2a6..0be64bb 100644
--- a/aos/events/logging/logger_test.cc
+++ b/aos/events/logging/logger_test.cc
@@ -1774,6 +1774,11 @@
   SimulatedEventLoopFactory log_reader_factory(reader.configuration());
   log_reader_factory.set_send_delay(chrono::microseconds(0));
 
+  std::vector<const Channel *> remapped_channels = reader.RemappedChannels();
+  ASSERT_EQ(remapped_channels.size(), 1u);
+  EXPECT_EQ(remapped_channels[0]->name()->string_view(), "/original/pi1/aos");
+  EXPECT_EQ(remapped_channels[0]->type()->string_view(), "aos.timing.Report");
+
   reader.Register(&log_reader_factory);
 
   const Node *pi1 =
@@ -2549,9 +2554,9 @@
 }
 
 constexpr std::string_view kCombinedConfigSha1(
-    "4503751edc96327493562f0376f0d6daac172927c0fd64d04ce5d67505186c0b");
+    "cad3b6838a518ab29470771a959b89945ee034bc7a738080fd1713a1dce51b1f");
 constexpr std::string_view kSplitConfigSha1(
-    "918a748432c5e70a971dfd8934968378bed04ab61cf2efcd35b7f6224053c247");
+    "aafdd7e43d1942cce5b3e2dd8c6b9706abf7068a43501625a33b7cdfddf6c932");
 
 INSTANTIATE_TEST_SUITE_P(
     All, MultinodeLoggerTest,
diff --git a/aos/realtime.cc b/aos/realtime.cc
index dc53a37..c58e2e6 100644
--- a/aos/realtime.cc
+++ b/aos/realtime.cc
@@ -176,6 +176,12 @@
   }
 }
 
+cpu_set_t GetCurrentThreadAffinity() {
+  cpu_set_t result;
+  PCHECK(sched_getaffinity(0, sizeof(result), &result) == 0);
+  return result;
+}
+
 void SetCurrentThreadRealtimePriority(int priority) {
   if (FLAGS_skip_realtime_scheduler) {
     LOG(WARNING) << "Ignoring request to switch to the RT scheduler due to "
diff --git a/aos/realtime.h b/aos/realtime.h
index fb49cac..be28e85 100644
--- a/aos/realtime.h
+++ b/aos/realtime.h
@@ -2,6 +2,7 @@
 #define AOS_REALTIME_H_
 
 #include <sched.h>
+
 #include <string_view>
 
 #include "glog/logging.h"
@@ -37,6 +38,9 @@
   return result;
 }
 
+// Returns the current thread's CPU affinity.
+cpu_set_t GetCurrentThreadAffinity();
+
 // Sets the current thread's scheduling affinity.
 void SetCurrentThreadAffinity(const cpu_set_t &cpuset);
 
diff --git a/aos/starter/BUILD b/aos/starter/BUILD
index 5cff4cc..cc7ea04 100644
--- a/aos/starter/BUILD
+++ b/aos/starter/BUILD
@@ -34,6 +34,7 @@
         "//aos/events:pingpong_config",
         "//aos/events:pong",
     ],
+    shard_count = 3,
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
         ":starter_rpc_lib",
diff --git a/aos/starter/starter_test.cc b/aos/starter/starter_test.cc
index dea27e9..f174032 100644
--- a/aos/starter/starter_test.cc
+++ b/aos/starter/starter_test.cc
@@ -12,6 +12,9 @@
 
 using aos::testing::ArtifactPath;
 
+namespace aos {
+namespace starter {
+
 TEST(StarterdTest, StartStopTest) {
   const std::string config_file =
       ArtifactPath("aos/events/pingpong_config.json");
@@ -69,7 +72,8 @@
               ASSERT_TRUE(aos::starter::SendCommandBlocking(
                   aos::starter::Command::STOP, "ping", config_msg,
                   std::chrono::seconds(3)));
-            }).detach();
+            })
+                .detach();
             test_stage = 3;
             break;
           }
@@ -196,3 +200,78 @@
   starter.Cleanup();
   starterd_thread.join();
 }
+
+TEST(StarterdTest, Autostart) {
+  const std::string config_file =
+      ArtifactPath("aos/events/pingpong_config.json");
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(config_file);
+
+  const std::string test_dir = aos::testing::TestTmpDir();
+
+  auto new_config = aos::configuration::MergeWithConfig(
+      &config.message(), absl::StrFormat(
+                             R"({"applications": [
+                                  {
+                                    "name": "ping",
+                                    "executable_name": "%s",
+                                    "args": ["--shm_base", "%s/aos"],
+                                    "autostart": false
+                                  },
+                                  {
+                                    "name": "pong",
+                                    "executable_name": "%s",
+                                    "args": ["--shm_base", "%s/aos"]
+                                  }
+                                ]})",
+                             ArtifactPath("aos/events/ping"), test_dir,
+                             ArtifactPath("aos/events/pong"), test_dir));
+
+  const aos::Configuration *config_msg = &new_config.message();
+
+  // Set up starter with config file
+  aos::starter::Starter starter(config_msg);
+
+  // Create an event loop to watch for the application starting up.
+  aos::ShmEventLoop watcher_loop(config_msg);
+  watcher_loop.SkipAosLog();
+
+  watcher_loop
+      .AddTimer([&watcher_loop] {
+        watcher_loop.Exit();
+        FAIL();
+      })
+      ->Setup(watcher_loop.monotonic_now() + std::chrono::seconds(7));
+
+  watcher_loop.MakeWatcher(
+      "/aos", [&watcher_loop](const aos::starter::Status &status) {
+        const aos::starter::ApplicationStatus *ping_app_status =
+            FindApplicationStatus(status, "ping");
+        const aos::starter::ApplicationStatus *pong_app_status =
+            FindApplicationStatus(status, "pong");
+        if (ping_app_status == nullptr || pong_app_status == nullptr) {
+          return;
+        }
+
+        if (ping_app_status->has_state() &&
+            ping_app_status->state() != aos::starter::State::STOPPED) {
+          watcher_loop.Exit();
+          FAIL();
+        }
+        if (pong_app_status->has_state() &&
+            pong_app_status->state() == aos::starter::State::RUNNING) {
+          watcher_loop.Exit();
+          SUCCEED();
+        }
+      });
+
+  std::thread starterd_thread([&starter] { starter.Run(); });
+  watcher_loop.Run();
+
+  starter.Cleanup();
+  starterd_thread.join();
+}
+
+}  // namespace starter
+}  // namespace aos
diff --git a/aos/starter/starterd_lib.cc b/aos/starter/starterd_lib.cc
index 1c32be3..ff8bff9 100644
--- a/aos/starter/starterd_lib.cc
+++ b/aos/starter/starterd_lib.cc
@@ -23,6 +23,7 @@
       args_(1),
       user_(application->has_user() ? FindUid(application->user()->c_str())
                                     : std::nullopt),
+      autostart_(application->autostart()),
       event_loop_(event_loop),
       start_timer_(event_loop_->AddTimer([this] {
         status_ = aos::starter::State::RUNNING;
@@ -33,9 +34,7 @@
         if (kill(pid_, SIGKILL) == 0) {
           LOG(WARNING) << "Sent SIGKILL to " << name_ << " pid: " << pid_;
         }
-      }))
-
-{}
+      })) {}
 
 void Application::DoStart() {
   if (status_ != aos::starter::State::WAITING) {
@@ -481,7 +480,9 @@
 #endif
 
   for (auto &application : applications_) {
-    application.second.Start();
+    if (application.second.autostart()) {
+      application.second.Start();
+    }
   }
 
   event_loop_.Run();
diff --git a/aos/starter/starterd_lib.h b/aos/starter/starterd_lib.h
index 38a11d6..cd4b40a 100644
--- a/aos/starter/starterd_lib.h
+++ b/aos/starter/starterd_lib.h
@@ -95,6 +95,8 @@
       const flatbuffers::Vector<flatbuffers::Offset<flatbuffers::String>>
           &args);
 
+  bool autostart() const { return autostart_; }
+
  private:
   void DoStart();
 
@@ -124,6 +126,7 @@
   aos::monotonic_clock::time_point start_time_, exit_time_;
   bool queue_restart_ = false;
   bool terminating_ = false;
+  bool autostart_ = true;
 
   aos::starter::State status_ = aos::starter::State::STOPPED;
   aos::starter::LastStopReason stop_reason_ =