Improve EXPECT_EQ diff printing

It now supports printing diffs for non-string formats via the function `Pretty`.
You can implement it for a custom type to easily allow comparisons. For
example, you can compare a commonly used type such as std::vector<const Target*>.

*** FAILURE ../src/gn/command_suggest_unittest.cc:57: all_targets == no_targets
diff --git a/tmp/.org.chromium.Chromium.rb8DZb/expected.txt b/tmp/.org.chromium.Chromium.rb8DZb/actual.txt
index 257b0ee5a..0d4f101c7 100644
--- a/tmp/.org.chromium.Chromium.rb8DZb/expected.txt
+++ b/tmp/.org.chromium.Chromium.rb8DZb/actual.txt
@@ -1,3 +1,2 @@
 [
-  0x7ffe03304ea8 -> Target for //foo:bar(),
 ]

Change-Id: I226de16e65407a8c460d023ce466c0b36a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/22020
Commit-Queue: Matt Stark <msta@google.com>
Reviewed-by: Takuto Ikuta <tikuta@google.com>
diff --git a/src/gn/label.cc b/src/gn/label.cc
index 6914962..93f3505 100644
--- a/src/gn/label.cc
+++ b/src/gn/label.cc
@@ -330,3 +330,7 @@
                            default_toolchain.name_atom() != toolchain_name_;
   return GetUserVisibleName(include_toolchain);
 }
+
+std::string Pretty(const Label& label) {
+  return label.GetUserVisibleName(true);
+}
diff --git a/src/gn/label.h b/src/gn/label.h
index e2a0281..de0b32c 100644
--- a/src/gn/label.h
+++ b/src/gn/label.h
@@ -132,6 +132,8 @@
   // NOTE: Must be initialized by constructors with ComputeHash() value.
 };
 
+std::string Pretty(const Label& label);
+
 namespace std {
 
 template <>
diff --git a/src/gn/target.cc b/src/gn/target.cc
index fbb440c..f170c5d 100644
--- a/src/gn/target.cc
+++ b/src/gn/target.cc
@@ -1435,3 +1435,7 @@
   }
   return &private_modulemap_file_;
 }
+
+std::string Pretty(const Target& target) {
+  return "Target for " + target.label().GetUserVisibleName(true);
+}
\ No newline at end of file
diff --git a/src/gn/target.h b/src/gn/target.h
index c06f5fc..83b1e37 100644
--- a/src/gn/target.h
+++ b/src/gn/target.h
@@ -609,6 +609,8 @@
   Target& operator=(const Target&) = delete;
 };
 
+std::string Pretty(const Target& target);
+
 extern const char kExecution_Help[];
 
 #endif  // TOOLS_GN_TARGET_H_
diff --git a/src/util/test/gn_test.cc b/src/util/test/gn_test.cc
index 069fad2..4941506 100644
--- a/src/util/test/gn_test.cc
+++ b/src/util/test/gn_test.cc
@@ -34,7 +34,31 @@
 namespace testing {
 Test* g_current_test;
 
+std::string Pretty(bool value) {
+  return value ? "true" : "false";
+}
+
+std::string Indent(std::string_view value) {
+  std::stringstream ss;
+  ss << "  ";
+  for (auto c : value) {
+    switch (c) {
+      case '\n':
+        ss << c;
+        ss << "  ";
+        break;
+      default:
+        ss << c;
+    }
+  }
+  return ss.str();
+}
+
 std::string DiffStrings(std::string_view expected, std::string_view actual) {
+  if (expected == actual) {
+    return "Different values with the same representation:\n" +
+           std::string(expected);
+  }
   base::ScopedTempDir temp_dir;
   auto fallback = [&]() {
     return base::StringPrintf(
diff --git a/src/util/test/test.h b/src/util/test/test.h
index 94d7129..4c45db8 100644
--- a/src/util/test/test.h
+++ b/src/util/test/test.h
@@ -7,10 +7,17 @@
 
 #include <string.h>
 
+#include <concepts>
+#include <memory>
+#include <optional>
 #include <sstream>
 #include <string>
 #include <string_view>
 #include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "base/strings/stringprintf.h"
 
 // This is a minimal googletest-like testing framework. It's originally derived
 // from Ninja's src/test.h. You might prefer that one if you have different
@@ -82,14 +89,104 @@
   const char* error_;
 };
 
+std::string Indent(std::string_view value);
 std::string DiffStrings(std::string_view expected, std::string_view actual);
 
+std::string Pretty(bool value);
+
+// Explicitly write this for enum, because otherwise it tries to cast enums
+// to bools.
+template <typename T>
+  requires std::is_enum_v<T>
+std::string Pretty(T value) {
+  return "Enum value " +
+         std::to_string(static_cast<std::underlying_type_t<T>>(value));
+}
+
+template <typename T>
+  requires std::is_convertible_v<T, std::string>
+std::string Pretty(const T& value) {
+  return value;
+}
+
+template <typename T>
+  requires std::is_arithmetic_v<T>
+std::string Pretty(T value) {
+  return std::to_string(value);
+}
+
+// Order matters for these templates.
+// The requires clause requires that the clause can already be met.
+// The TLDR is that you must order them so that your inner types come first.
+// For example, since we put T* before vector<T>, we support vector<T*> but not
+// vector<T>*.
+
+// Pointer and options should come first because we pretty much universally
+// point to fixed types rather than templates.
+template <typename T>
+  requires requires(T t) { Pretty(t); }
+std::string Pretty(const T* value) {
+  if (value == nullptr) {
+    return "NULL";
+  }
+  auto pretty = Pretty(*value);
+  return base::StringPrintf("%p -> %.*s", value,
+                            static_cast<int>(pretty.size()), pretty.data());
+}
+
+template <typename T>
+  requires requires(T t) { Pretty(t); }
+std::string Pretty(const std::unique_ptr<T>& value) {
+  return "unique_ptr " + Pretty(value.get());
+}
+
+template <typename T>
+  requires requires(T t) { Pretty(t); }
+std::string Pretty(const std::optional<T>& value) {
+  if (!value) {
+    return "nullopt";
+  }
+  return "Optional(" + Pretty(*value) + ")";
+}
+
+// Containers of pairs are far more common than pairs of containers.
+template <typename T, typename U>
+  requires requires(T t) { Pretty(t); } && requires(U u) { Pretty(u); }
+std::string Pretty(const std::pair<T, U>& value) {
+  std::stringstream ss;
+  ss << "Pair(\n";
+  ss << Indent(Pretty(value.first)) << ",\n";
+  ss << Indent(Pretty(value.second)) << ",\n";
+  ss << ")";
+  return ss.str();
+}
+
+// Containers are usually the outer layer, so they come last.
+template <typename T>
+  requires requires(T t) { Pretty(t); }
+std::string Pretty(const std::vector<T>& value) {
+  std::stringstream ss;
+  ss << "[\n";
+  for (const auto& v : value) {
+    ss << Indent(Pretty(v)) << ",\n";
+  }
+  ss << "]";
+  return ss.str();
+}
+
 template <typename T, typename U>
 std::string TryDiffStrings(const T& expected, const U& actual) {
-  // Try to diff if they are strings
-  if constexpr (std::is_convertible_v<T, std::string_view> &&
-                std::is_convertible_v<U, std::string_view>) {
-    return DiffStrings(expected, actual);
+  if constexpr (requires {
+                  Pretty(expected);
+                  Pretty(actual);
+                }) {
+    auto expected_pretty = Pretty(expected);
+    auto actual_pretty = Pretty(actual);
+    // Ensure git diff doesn't complain about missing newlines.
+    if (!expected_pretty.ends_with("\n") && !actual_pretty.ends_with("\n")) {
+      return DiffStrings(expected_pretty + "\n", actual_pretty + "\n");
+    }
+    return DiffStrings(expected_pretty, actual_pretty);
   } else {
     return "";
   }