Add missing nullptr check in LogReader::State constructor

This change only affects the code which uses replay filters.
We ran into the path where construction of State happens with no
timestamp_mapper while running log_replayer on single node logs.
The timestamp_mapper does not exist when there are no communications
with other nodes as no filters are needed for logfile sorting. This has been
reproduced by creating a unit test with a single node which has been blocked
of any communication with other nodes and by calling Register on the logged files.

Change-Id: Ie3e2f1742e0480339f572e474f5cb49a4f051525
Signed-off-by: James Kuszmaul <james.kuszmaul@bluerivertech.com>
diff --git a/aos/events/logging/log_reader.cc b/aos/events/logging/log_reader.cc
index 36c8e8a..fa5e5d5 100644
--- a/aos/events/logging/log_reader.cc
+++ b/aos/events/logging/log_reader.cc
@@ -1333,7 +1333,8 @@
                                    RemapConflict conflict_handling) {
   if (replay_channels_ != nullptr) {
     CHECK(std::find(replay_channels_->begin(), replay_channels_->end(),
-                    std::make_pair(std::string{name}, std::string{type})) != replay_channels_->end())
+                    std::make_pair(std::string{name}, std::string{type})) !=
+          replay_channels_->end())
         << "Attempted to remap channel " << name << " " << type
         << " which is not included in the replay channels passed to LogReader.";
   }
@@ -1755,7 +1756,10 @@
       multinode_filters_(multinode_filters),
       threading_(threading),
       replay_channel_indices_(std::move(replay_channel_indices)) {
-  if (replay_channel_indices_ != nullptr) {
+  // If timestamp_mapper_ is nullptr, then there are no log parts associated
+  // with this node. If there are no log parts for the node, there will be no
+  // log data, and so we do not need to worry about the replay channel filters.
+  if (replay_channel_indices_ != nullptr && timestamp_mapper_ != nullptr) {
     timestamp_mapper_->set_replay_channels_callback(
         [filter = replay_channel_indices_.get()](
             const TimestampedMessage &message) -> bool {
diff --git a/aos/events/logging/log_reader_utils_test.cc b/aos/events/logging/log_reader_utils_test.cc
index 680b472..9977be9 100644
--- a/aos/events/logging/log_reader_utils_test.cc
+++ b/aos/events/logging/log_reader_utils_test.cc
@@ -1,6 +1,9 @@
 #include "aos/events/logging/log_reader_utils.h"
 
 #include "aos/events/logging/multinode_logger_test_lib.h"
+#include "aos/events/ping_lib.h"
+#include "aos/events/pong_lib.h"
+#include "aos/testing/tmpdir.h"
 
 namespace aos::logger::testing {
 
@@ -76,4 +79,51 @@
   // There no fetcher channels, check for none
   ASSERT_EQ(channels.fetchers.value().size(), 0);
 }
+
+// Test to run log reader with replay channels via simulated event loop
+TEST_P(MultinodeLoggerOneConfigTest, SingleNodeLogReplay) {
+  time_converter_.StartEqual();
+  std::vector<std::string> actual_filenames;
+  const std::string kLogfile1_1 =
+      aos::testing::TestTmpDir() + "/multi_logfile1/";
+  util::UnlinkRecursive(kLogfile1_1);
+
+  {
+    LoggerState pi1_logger = MakeLoggerState(
+        pi1_, &event_loop_factory_, SupportedCompressionAlgorithms()[0]);
+    pi2_->DisableStatistics();
+    pi2_->Disconnect(pi1_->node());
+    pi1_->Disconnect(pi2_->node());
+    pi1_logger.StartLogger(kLogfile1_1);
+    event_loop_factory_.RunFor(chrono::milliseconds(20000));
+    pi1_logger.AppendAllFilenames(&actual_filenames);
+  }
+
+  ReplayChannels replay_channels{{"/test", "aos.examples.Ping"}};
+  LogReader reader(logger::SortParts(actual_filenames), &config_.message(),
+                   &replay_channels);
+  SimulatedEventLoopFactory log_reader_factory(reader.configuration());
+  int ping_count = 0;
+  int pong_count = 0;
+
+  // This sends out the fetched messages and advances time to the start of the
+  // log file.
+  reader.Register(&log_reader_factory);
+
+  const Node *pi1 =
+      configuration::GetNode(log_reader_factory.configuration(), "pi1");
+
+  std::unique_ptr<EventLoop> pi1_event_loop =
+      log_reader_factory.MakeEventLoop("test", pi1);
+  pi1_event_loop->MakeWatcher(
+      "/test", [&ping_count](const examples::Ping &) { ++ping_count; });
+  pi1_event_loop->MakeWatcher(
+      "/test", [&pong_count](const examples::Pong &) { ++pong_count; });
+
+  int sent_messages = 1999;
+  reader.event_loop_factory()->Run();
+  EXPECT_EQ(ping_count, sent_messages);
+  EXPECT_EQ(pong_count, 0);
+  reader.Deregister();
+}
 }  // namespace aos::logger::testing