Add gmock matcher support for comparing flatbuffers

This makes it easier to compare flatbuffers in tests.

Change-Id: Iabd1038f7ce9dfc3edf1162f2d7b0485cdda3b90
diff --git a/aos/BUILD b/aos/BUILD
index 86aa433..790f845 100644
--- a/aos/BUILD
+++ b/aos/BUILD
@@ -463,6 +463,7 @@
     target_compatible_with = ["@platforms//os:linux"],
     deps = [
         ":configuration",
+        "//aos/testing:flatbuffer_eq",
         "//aos/testing:googletest",
         "//aos/testing:test_logging",
     ],
diff --git a/aos/configuration_test.cc b/aos/configuration_test.cc
index a5b3b59..e0c4dc3 100644
--- a/aos/configuration_test.cc
+++ b/aos/configuration_test.cc
@@ -2,12 +2,13 @@
 
 #include "absl/strings/strip.h"
 #include "aos/json_to_flatbuffer.h"
+#include "aos/testing/flatbuffer_eq.h"
 #include "aos/testing/test_logging.h"
 #include "aos/util/file.h"
 #include "flatbuffers/reflection.h"
 #include "glog/logging.h"
-#include "gtest/gtest.h"
 #include "gmock/gmock.h"
+#include "gtest/gtest.h"
 
 namespace aos {
 namespace configuration {
@@ -23,12 +24,17 @@
 typedef ConfigurationTest ConfigurationDeathTest;
 
 // *the* expected location for all working tests.
-const char *kExpectedLocation =
-    "{ \"name\": \"/foo\", \"type\": \".aos.bar\", \"max_size\": 5 }";
+aos::FlatbufferDetachedBuffer<Channel> ExpectedLocation() {
+  return JsonToFlatbuffer<Channel>(
+      "{ \"name\": \"/foo\", \"type\": \".aos.bar\", \"max_size\": 5 }");
+}
+
 // And for multinode setups
-const char *kExpectedMultinodeLocation =
-    "{ \"name\": \"/foo\", \"type\": \".aos.bar\", \"max_size\": 5, "
-    "\"source_node\": \"pi1\" }";
+aos::FlatbufferDetachedBuffer<Channel> ExpectedMultinodeLocation() {
+  return JsonToFlatbuffer<Channel>(
+      "{ \"name\": \"/foo\", \"type\": \".aos.bar\", \"max_size\": 5, "
+      "\"source_node\": \"pi1\" }");
+}
 
 // Tests that we can read and merge a configuration.
 TEST_F(ConfigurationTest, ConfigMerge) {
@@ -143,9 +149,8 @@
       ReadConfig(kConfigPrefix + "config1.json");
 
   // Test a basic lookup first.
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/foo", ".aos.bar", "app1", nullptr)),
-      kExpectedLocation);
+  EXPECT_THAT(GetChannel(config, "/foo", ".aos.bar", "app1", nullptr),
+              aos::testing::FlatbufferEq(ExpectedLocation()));
 
   // Test that an invalid name results in nullptr back.
   EXPECT_EQ(GetChannel(config, "/invalid_name", ".aos.bar", "app1", nullptr),
@@ -153,22 +158,18 @@
 
   // Tests that a root map/rename works. And that they get processed from the
   // bottom up.
-  EXPECT_EQ(FlatbufferToJson(
-                GetChannel(config, "/batman", ".aos.bar", "app1", nullptr)),
-            kExpectedLocation);
+  EXPECT_THAT(GetChannel(config, "/batman", ".aos.bar", "app1", nullptr),
+              aos::testing::FlatbufferEq(ExpectedLocation()));
 
   // And then test that an application specific map/rename works.
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/bar", ".aos.bar", "app1", nullptr)),
-      kExpectedLocation);
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/baz", ".aos.bar", "app2", nullptr)),
-      kExpectedLocation);
+  EXPECT_THAT(GetChannel(config, "/bar", ".aos.bar", "app1", nullptr),
+              aos::testing::FlatbufferEq(ExpectedLocation()));
+  EXPECT_THAT(GetChannel(config, "/baz", ".aos.bar", "app2", nullptr),
+              aos::testing::FlatbufferEq(ExpectedLocation()));
 
   // And then test that an invalid application name gets properly ignored.
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/foo", ".aos.bar", "app3", nullptr)),
-      kExpectedLocation);
+  EXPECT_THAT(GetChannel(config, "/foo", ".aos.bar", "app3", nullptr),
+              aos::testing::FlatbufferEq(ExpectedLocation()));
 }
 
 // Tests that we can lookup a location with node specific maps.
@@ -179,29 +180,24 @@
   const Node *pi2 = GetNode(&config.message(), "pi2");
 
   // Test a basic lookup first.
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/foo", ".aos.bar", "app1", pi1)),
-      kExpectedMultinodeLocation);
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/foo", ".aos.bar", "app1", pi2)),
-      kExpectedMultinodeLocation);
+  EXPECT_THAT(GetChannel(config, "/foo", ".aos.bar", "app1", pi1),
+              aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
+  EXPECT_THAT(GetChannel(config, "/foo", ".aos.bar", "app1", pi2),
+              aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
 
   // Tests that a root map/rename works with a node specific map.
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/batman", ".aos.bar", "app1", pi1)),
-      kExpectedMultinodeLocation);
+  EXPECT_THAT(GetChannel(config, "/batman", ".aos.bar", "app1", pi1),
+              aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
 
   // Tests that a root map/rename fails with a node specific map for the wrong
   // node.
   EXPECT_EQ(GetChannel(config, "/batman", ".aos.bar", "app1", pi2), nullptr);
 
   // And then test that an application specific map/rename works.
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/batman2", ".aos.bar", "app1", pi1)),
-      kExpectedMultinodeLocation);
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/batman3", ".aos.bar", "app1", pi1)),
-      kExpectedMultinodeLocation);
+  EXPECT_THAT(GetChannel(config, "/batman2", ".aos.bar", "app1", pi1),
+              aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
+  EXPECT_THAT(GetChannel(config, "/batman3", ".aos.bar", "app1", pi1),
+              aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
 
   // And then that it fails when the node changes.
   EXPECT_EQ(GetChannel(config, "/batman3", ".aos.bar", "app1", pi2), nullptr);
@@ -214,9 +210,8 @@
   const Node *pi1 = GetNode(&config.message(), "pi1");
 
   // Test a basic lookup first.
-  EXPECT_EQ(
-      FlatbufferToJson(GetChannel(config, "/batman", ".aos.bar", "app1", pi1)),
-      kExpectedMultinodeLocation);
+  EXPECT_THAT(GetChannel(config, "/batman", ".aos.bar", "app1", pi1),
+              aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
 
   // Now confirm that a second message on the same name doesn't get remapped.
   const char *kExpectedBazMultinodeLocation =
@@ -234,12 +229,8 @@
   const Node *pi1 = GetNode(&config.message(), "pi1");
 
   // Confirm that a glob with nothing after it matches.
-  const char *kExpectedMultinodeLocation =
-      "{ \"name\": \"/foo\", \"type\": \".aos.bar\", \"max_size\": 5, "
-      "\"source_node\": \"pi1\" }";
-  EXPECT_EQ(FlatbufferToJson(
-                GetChannel(config, "/magic/string", ".aos.bar", "app7", pi1)),
-            kExpectedMultinodeLocation);
+  EXPECT_THAT(GetChannel(config, "/magic/string", ".aos.bar", "app7", pi1),
+              aos::testing::FlatbufferEq(ExpectedMultinodeLocation()));
 
   // Now confirm that glob with something following it matches and renames
   // correctly.
diff --git a/aos/testing/BUILD b/aos/testing/BUILD
index 116acc0..d3287e3 100644
--- a/aos/testing/BUILD
+++ b/aos/testing/BUILD
@@ -102,3 +102,18 @@
     target_compatible_with = ["@platforms//os:linux"],
     visibility = ["//visibility:public"],
 )
+
+cc_library(
+    name = "flatbuffer_eq",
+    testonly = True,
+    hdrs = [
+        "flatbuffer_eq.h",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos:flatbuffer_merge",
+        "//aos:flatbuffers",
+        "//aos:json_to_flatbuffer",
+        "@com_google_googletest//:gtest",
+    ],
+)
diff --git a/aos/testing/flatbuffer_eq.h b/aos/testing/flatbuffer_eq.h
new file mode 100644
index 0000000..03f3241
--- /dev/null
+++ b/aos/testing/flatbuffer_eq.h
@@ -0,0 +1,57 @@
+#ifndef AOS_TESTING_FLATBUFFER_EQ_H_
+#define AOS_TESTING_FLATBUFFER_EQ_H_
+
+#include "aos/flatbuffer_merge.h"
+#include "aos/flatbuffers.h"
+#include "aos/json_to_flatbuffer.h"
+#include "gmock/gmock.h"
+
+namespace aos {
+namespace testing {
+
+// Use FlatbufferEq to instantiate this.
+template <typename T>
+class FlatbufferEqMatcher {
+ public:
+  FlatbufferEqMatcher(aos::FlatbufferString<T> expected)
+      : expected_(std::move(expected)) {}
+
+  bool MatchAndExplain(const T *t,
+                       ::testing::MatchResultListener *listener) const {
+    *listener << "is " << aos::FlatbufferToJson(t);
+    return aos::CompareFlatBuffer(t, &expected_.message());
+  }
+
+  bool MatchAndExplain(const aos::Flatbuffer<T> &t,
+                       ::testing::MatchResultListener *listener) const {
+    return MatchAndExplain(&t.message(), listener);
+  }
+
+  void DescribeTo(std::ostream *os) const {
+    *os << "is equal to " << aos::FlatbufferToJson(&expected_.message());
+  }
+
+  void DescribeNegationTo(std::ostream *os) const {
+    *os << "is not equal to " << aos::FlatbufferToJson(&expected_.message());
+  }
+
+ private:
+  const aos::FlatbufferString<T> expected_;
+};
+
+// Returns a googlemock matcher which will compare a `const T *` or a `const
+// aos::Flatbuffer<T> &` against expected. This will automatically give nice
+// error messages if they don't match.
+//
+// T must be a flatbuffer table type.
+template <typename T>
+::testing::PolymorphicMatcher<FlatbufferEqMatcher<T>> FlatbufferEq(
+    const aos::NonSizePrefixedFlatbuffer<T> &expected) {
+  return ::testing::MakePolymorphicMatcher(
+      FlatbufferEqMatcher(aos::FlatbufferString<T>(expected)));
+}
+
+}  // namespace testing
+}  // namespace aos
+
+#endif  // AOS_TESTING_FLATBUFFER_EQ_H_