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 ""; }