Add and test TryMakeFetcher, TryMakeSender, and NodeHasTag helpers

Using a Fetcher for a channel on nodes where it exists and triggering
logic to handle other nodes based on whether the fetcher is valid works
well for some use cases. Add a method to EventLoop to support that
directly, instead of forcing the caller to redo the logic around whether
a channel exists and is readable. Basing similar logic off whether the
node has a given tag is also fairly common.

In the process, fill out the EventLoop tests around channels that don't
exist or aren't readable/writable a bit better.

Change-Id: I168e3c1e98adc4461a01d98360f0f0a78d24bbb5
Signed-off-by: Brian Silverman <brian.silverman@bluerivertech.com>
diff --git a/aos/events/event_loop.h b/aos/events/event_loop.h
index 6f51fb0..42489e5 100644
--- a/aos/events/event_loop.h
+++ b/aos/events/event_loop.h
@@ -517,31 +517,62 @@
     return GetChannel<T>(channel_name) != nullptr;
   }
 
+  // Like MakeFetcher, but returns an invalid fetcher if the given channel is
+  // not readable on this node or does not exist.
+  template <typename T>
+  Fetcher<T> TryMakeFetcher(const std::string_view channel_name) {
+    const Channel *const channel = GetChannel<T>(channel_name);
+    if (channel == nullptr) {
+      return Fetcher<T>();
+    }
+
+    if (!configuration::ChannelIsReadableOnNode(channel, node())) {
+      return Fetcher<T>();
+    }
+
+    return Fetcher<T>(MakeRawFetcher(channel));
+  }
+
   // Makes a class that will always fetch the most recent value
   // sent to the provided channel.
   template <typename T>
   Fetcher<T> MakeFetcher(const std::string_view channel_name) {
-    const Channel *channel = GetChannel<T>(channel_name);
-    CHECK(channel != nullptr)
+    CHECK(HasChannel<T>(channel_name))
         << ": Channel { \"name\": \"" << channel_name << "\", \"type\": \""
         << T::GetFullyQualifiedName() << "\" } not found in config.";
 
-    if (!configuration::ChannelIsReadableOnNode(channel, node())) {
+    Fetcher<T> result = TryMakeFetcher<T>(channel_name);
+    if (!result.valid()) {
       LOG(FATAL) << "Channel { \"name\": \"" << channel_name
                  << "\", \"type\": \"" << T::GetFullyQualifiedName()
                  << "\" } is not able to be fetched on this node.  Check your "
                     "configuration.";
     }
 
-    return Fetcher<T>(MakeRawFetcher(channel));
+    return result;
+  }
+
+  // Like MakeSender, but returns an invalid sender if the given channel is
+  // not readable on this node or does not exist.
+  template <typename T>
+  Sender<T> TryMakeSender(const std::string_view channel_name) {
+    const Channel *channel = GetChannel<T>(channel_name);
+    if (channel == nullptr) {
+      return Sender<T>();
+    }
+
+    if (!configuration::ChannelIsSendableOnNode(channel, node())) {
+      return Sender<T>();
+    }
+
+    return Sender<T>(MakeRawSender(channel));
   }
 
   // Makes class that allows constructing and sending messages to
   // the provided channel.
   template <typename T>
   Sender<T> MakeSender(const std::string_view channel_name) {
-    const Channel *channel = GetChannel<T>(channel_name);
-    CHECK(channel != nullptr)
+    CHECK(HasChannel<T>(channel_name))
         << ": Channel { \"name\": \"" << channel_name << "\", \"type\": \""
         << T::GetFullyQualifiedName() << "\" } not found in config for "
         << name()
@@ -549,14 +580,15 @@
                 ? absl::StrCat(" on node ", node()->name()->string_view())
                 : ".");
 
-    if (!configuration::ChannelIsSendableOnNode(channel, node())) {
+    Sender<T> result = TryMakeSender<T>(channel_name);
+    if (!result) {
       LOG(FATAL) << "Channel { \"name\": \"" << channel_name
                  << "\", \"type\": \"" << T::GetFullyQualifiedName()
                  << "\" } is not able to be sent on this node.  Check your "
                     "configuration.";
     }
 
-    return Sender<T>(MakeRawSender(channel));
+    return result;
   }
 
   // This will watch messages sent to the provided channel.