unified_diff_matcher.h 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #ifndef CARBON_TESTING_BASE_UNIFIED_DIFF_MATCHER_H_
  5. #define CARBON_TESTING_BASE_UNIFIED_DIFF_MATCHER_H_
  6. #include <gmock/gmock.h>
  7. #include <algorithm>
  8. #include <optional>
  9. #include <string>
  10. #include <utility>
  11. #include "common/check.h"
  12. #include "llvm/ADT/STLExtras.h"
  13. #include "llvm/ADT/Sequence.h"
  14. #include "llvm/ADT/SmallVector.h"
  15. namespace Carbon::Testing {
  16. // Matcher that compares the elements of two containers and produces a unified
  17. // diff on failure.
  18. template <typename Container>
  19. class UnifiedDiffMatcher {
  20. public:
  21. explicit UnifiedDiffMatcher(Container expected)
  22. : expected_(std::move(expected)) {}
  23. // Matches `actual` against `expected_`. Returns true on a match; returns
  24. // false and prints a unified diff to `listener` on a mismatch.
  25. template <typename ActualContainer>
  26. auto MatchAndExplain(const ActualContainer& actual,
  27. testing::MatchResultListener* listener) const -> bool;
  28. auto DescribeTo(std::ostream* os) const -> void {
  29. *os << "matches elements with unified diff";
  30. }
  31. auto DescribeNegationTo(std::ostream* os) const -> void {
  32. *os << "does not match elements with unified diff";
  33. }
  34. private:
  35. // A 2D array, stored contiguously. Rows correspond to `expected_`'s elements,
  36. // and columns correspond to the actual container's elements.
  37. template <typename T>
  38. class Table;
  39. // The result of a `Matches` check between an expected and actual element.
  40. enum class MatchResult : uint8_t { Unknown, Matches, DoesNotMatch };
  41. // Checks whether `actual_element` matches `expected_[expected_index]`. It
  42. // first checks whether a cached result exists. If not, it evaluates the
  43. // match and stores the result in `match_results`.
  44. template <typename ActualElement>
  45. auto IsElementMatch(size_t expected_index, size_t actual_index,
  46. const ActualElement& actual_element,
  47. Table<MatchResult>& match_results) const -> bool {
  48. MatchResult cached_result = match_results.Get(expected_index, actual_index);
  49. if (cached_result != MatchResult::Unknown) {
  50. return cached_result == MatchResult::Matches;
  51. }
  52. bool is_match =
  53. testing::MatcherCast<const ActualElement&>(expected_[expected_index])
  54. .Matches(actual_element);
  55. match_results.Set(
  56. expected_index, actual_index,
  57. is_match ? MatchResult::Matches : MatchResult::DoesNotMatch);
  58. return is_match;
  59. }
  60. // Returns true if every element in `expected_` matches the corresponding
  61. // element in `actual`. Stores comparisons in `match_results`.
  62. template <typename ActualContainer>
  63. auto IsEqual(const ActualContainer& actual,
  64. Table<MatchResult>& match_results) const -> bool;
  65. // Populates `subsequences` with the longest common matching subsequences
  66. // found when comparing `actual` and `expected_`. Stores comparisons in
  67. // `match_results`.
  68. template <typename ActualContainer>
  69. auto GetLongestCommonSubsequences(const ActualContainer& actual,
  70. Table<MatchResult>& match_results,
  71. Table<int>& subsequences) const -> void;
  72. // Prints the unified diff.
  73. template <typename ActualContainer>
  74. auto PrintDiff(const ActualContainer& actual,
  75. Table<MatchResult>& match_results,
  76. const Table<int>& subsequences,
  77. testing::MatchResultListener* listener) const -> void;
  78. // The expected elements.
  79. Container expected_;
  80. };
  81. // Returns a polymorphic matcher that acts similarly to
  82. // ElementsAreArray but produces a unified diff on failure.
  83. template <typename Container>
  84. auto ElementsAreArrayWithUnifiedDiff(Container expected) {
  85. return testing::MakePolymorphicMatcher(
  86. UnifiedDiffMatcher<Container>(std::move(expected)));
  87. }
  88. // -----------------------------------------------------------------------------
  89. // Internal implementation details follow.
  90. // -----------------------------------------------------------------------------
  91. template <typename Container>
  92. template <typename T>
  93. class UnifiedDiffMatcher<Container>::Table {
  94. public:
  95. // Constructs a table with dimensions of expected_size and actual_size,
  96. // corresponding to the containers being compared.
  97. Table(int expected_size, int actual_size, T default_value)
  98. : actual_size_(actual_size),
  99. data_(expected_size * actual_size, default_value) {}
  100. // Sets the value at the given expected_index and actual_index.
  101. auto Set(int expected_index, int actual_index, T value) -> void {
  102. data_[expected_index * actual_size_ + actual_index] = std::move(value);
  103. }
  104. // Gets the value at the given expected_index and actual_index.
  105. auto Get(int expected_index, int actual_index) const -> T {
  106. return data_[expected_index * actual_size_ + actual_index];
  107. }
  108. private:
  109. // The actual_size of the table.
  110. int actual_size_;
  111. // The contiguous data storage for the table.
  112. llvm::SmallVector<T> data_;
  113. };
  114. template <typename Container>
  115. template <typename ActualContainer>
  116. auto UnifiedDiffMatcher<Container>::MatchAndExplain(
  117. const ActualContainer& actual, testing::MatchResultListener* listener) const
  118. -> bool {
  119. Table<MatchResult> match_results(expected_.size(), std::size(actual),
  120. MatchResult::Unknown);
  121. if (IsEqual(actual, match_results)) {
  122. return true;
  123. }
  124. if (listener->IsInterested()) {
  125. Table<int> subsequences(expected_.size() + 1, std::size(actual) + 1, 0);
  126. GetLongestCommonSubsequences(actual, match_results, subsequences);
  127. PrintDiff(actual, match_results, subsequences, listener);
  128. }
  129. return false;
  130. }
  131. template <typename Container>
  132. template <typename ActualContainer>
  133. auto UnifiedDiffMatcher<Container>::IsEqual(
  134. const ActualContainer& actual, Table<MatchResult>& match_results) const
  135. -> bool {
  136. if (expected_.size() != std::size(actual)) {
  137. return false;
  138. }
  139. for (auto [i, actual_element] : llvm::enumerate(actual)) {
  140. if (!IsElementMatch(i, i, actual_element, match_results)) {
  141. return false;
  142. }
  143. }
  144. return true;
  145. }
  146. template <typename Container>
  147. template <typename ActualContainer>
  148. auto UnifiedDiffMatcher<Container>::GetLongestCommonSubsequences(
  149. const ActualContainer& actual, Table<MatchResult>& match_results,
  150. Table<int>& subsequences) const -> void {
  151. for (auto expected_index : llvm::seq(expected_.size())) {
  152. for (auto [actual_index, actual_element] : llvm::enumerate(actual)) {
  153. int subsequence_value;
  154. if (IsElementMatch(expected_index, actual_index, actual_element,
  155. match_results)) {
  156. // If the elements match, the LCS length increases by 1 relative to
  157. // the prefixes where both elements are excluded.
  158. subsequence_value = subsequences.Get(expected_index, actual_index) + 1;
  159. } else {
  160. // Otherwise, the LCS length is the maximum of the LCS lengths
  161. // relative to the prefixes where one element is excluded.
  162. subsequence_value =
  163. std::max(subsequences.Get(expected_index, actual_index + 1),
  164. subsequences.Get(expected_index + 1, actual_index));
  165. }
  166. subsequences.Set(expected_index + 1, actual_index + 1, subsequence_value);
  167. }
  168. }
  169. }
  170. template <typename Container>
  171. template <typename ActualContainer>
  172. auto UnifiedDiffMatcher<Container>::PrintDiff(
  173. const ActualContainer& actual, Table<MatchResult>& match_results,
  174. const Table<int>& subsequences,
  175. testing::MatchResultListener* listener) const -> void {
  176. // A line in the diff output.
  177. struct DiffLine {
  178. enum class Kind { Match, ActualOnly, ExpectedOnly };
  179. Kind kind;
  180. // Only used for `Match` and `ActualOnly`.
  181. const ActualContainer::value_type* actual_value;
  182. int expected_index;
  183. };
  184. llvm::SmallVector<DiffLine> diff;
  185. // Reserve a quick upper bound of the size.
  186. diff.reserve(expected_.size() + std::size(actual));
  187. // Reconstruct the diff by backtracking from the end of the table.
  188. int expected_index = expected_.size() - 1;
  189. int actual_index = std::size(actual) - 1;
  190. auto actual_it = std::end(actual) - 1;
  191. while (expected_index >= 0 || actual_index >= 0) {
  192. auto match_result = (expected_index >= 0 && actual_index >= 0)
  193. ? match_results.Get(expected_index, actual_index)
  194. : MatchResult::DoesNotMatch;
  195. CARBON_CHECK(match_result != MatchResult::Unknown);
  196. if (match_result == MatchResult::Matches) {
  197. // The element is in both lists for the diff.
  198. diff.push_back({.kind = DiffLine::Kind::Match,
  199. .actual_value = &*actual_it,
  200. .expected_index = expected_index});
  201. --expected_index;
  202. --actual_index;
  203. --actual_it;
  204. } else if (actual_index >= 0 &&
  205. (expected_index < 0 ||
  206. subsequences.Get(expected_index + 1, actual_index) >=
  207. subsequences.Get(expected_index, actual_index + 1))) {
  208. // Dropping an element from `actual` preserves the LCS length, so treat it
  209. // as an insertion.
  210. diff.push_back({.kind = DiffLine::Kind::ActualOnly,
  211. .actual_value = &*actual_it,
  212. .expected_index = std::max(0, expected_index)});
  213. --actual_index;
  214. --actual_it;
  215. } else {
  216. // Otherwise, treat it as a deletion from `expected`.
  217. diff.push_back({.kind = DiffLine::Kind::ExpectedOnly,
  218. .actual_value = nullptr,
  219. .expected_index = expected_index});
  220. --expected_index;
  221. }
  222. }
  223. struct PrintRange {
  224. int begin;
  225. int end;
  226. };
  227. llvm::SmallVector<PrintRange> print_ranges;
  228. constexpr int ContextLines = 3;
  229. for (auto [i, line] :
  230. llvm::reverse(llvm::zip_equal(llvm::seq<int>(diff.size()), diff))) {
  231. if (line.kind != DiffLine::Kind::Match) {
  232. PrintRange range = {
  233. .begin = std::max(0, i - ContextLines),
  234. .end = std::min<int>(diff.size() - 1, i + ContextLines)};
  235. if (print_ranges.empty() || print_ranges.back().begin > range.end + 1) {
  236. print_ranges.push_back(range);
  237. } else {
  238. // Merge diffs with overlapping context.
  239. print_ranges.back().begin = range.begin;
  240. }
  241. }
  242. }
  243. *listener << "unified diff (- expected, + actual):\n";
  244. for (const auto& range : print_ranges) {
  245. *listener << "=== diff in expected elements "
  246. << diff[range.end].expected_index + 1 << " to "
  247. << diff[range.begin].expected_index + 1 << " (1-based index):\n";
  248. for (auto i : llvm::reverse(llvm::seq_inclusive(range.begin, range.end))) {
  249. const auto& line = diff[i];
  250. if (line.kind == DiffLine::Kind::Match) {
  251. *listener << " " << *line.actual_value << "\n";
  252. } else if (line.kind == DiffLine::Kind::ActualOnly) {
  253. *listener << "+ " << *line.actual_value << "\n";
  254. } else {
  255. *listener << "- ";
  256. expected_[line.expected_index].DescribeTo(listener->stream());
  257. *listener << "\n";
  258. }
  259. }
  260. }
  261. *listener << "=== diff end\n";
  262. }
  263. } // namespace Carbon::Testing
  264. #endif // CARBON_TESTING_BASE_UNIFIED_DIFF_MATCHER_H_