yaml_test_helpers.h 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  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. //
  5. // This file provides gmock matchers to support testing YAML output.
  6. //
  7. // A YAML document can be converted into a matchable value using
  8. // Yaml::Value::FromText, and then matched with Yaml::Mapping, Yaml::Sequence,
  9. // or Yaml::Scalar. Scalar values can also be matched directly against strings.
  10. //
  11. // Example usage:
  12. //
  13. // namespace Yaml = Carbon::Testing::Yaml;
  14. // using ::testing::ElementsAre;
  15. // using ::testing::Pair;
  16. // Yaml::Value yaml = Yaml::Value::FromText(R"yaml(
  17. // ---
  18. // fruits:
  19. // - apple
  20. // - orange
  21. // - pear
  22. // ...
  23. // ---
  24. // - [foo: "bar"]: "baz"
  25. // )yaml"),
  26. //
  27. // // Exact values can be matched by constructing the desired value.
  28. // EXPECT_THAT(
  29. // yaml,
  30. // Yaml::IsYaml(ElementsAre(
  31. // Yaml::MappingValue{
  32. // {"fruits", Yaml::SequenceValue{"apple", "orange", "pear"}}},
  33. // Yaml::SequenceValue{Yaml::MappingValue{
  34. // {Yaml::SequenceValue{Yaml::MappingValue{{"foo", "bar"}}},
  35. // "baz"}}})));
  36. //
  37. // // Properties can be checked using Yaml::Mapping or Yaml::Sequence to
  38. // // adapt regular gmock container matchers.
  39. // EXPECT_THAT(
  40. // yaml,
  41. // Yaml::IsYaml(Contains(Yaml::Mapping(
  42. // Contains(Pair("fruits", Yaml::Sequence(Contains("orange"))))))));
  43. //
  44. // On match failure, Yaml::Values are printed as C++ code that can be used to
  45. // recreate the value, for easy copy-pasting into test expectations.
  46. #ifndef CARBON_TOOLCHAIN_TESTING_YAML_TEST_HELPERS_H_
  47. #define CARBON_TOOLCHAIN_TESTING_YAML_TEST_HELPERS_H_
  48. #include <gmock/gmock.h>
  49. #include <gtest/gtest.h>
  50. #include <iostream>
  51. #include <variant>
  52. #include "absl/strings/str_replace.h"
  53. #include "common/error.h"
  54. #include "common/ostream.h"
  55. #include "llvm/ADT/StringRef.h"
  56. namespace Carbon::Testing::Yaml {
  57. // Adds the specified indentation before each newline in the given string.
  58. inline auto IndentString(std::string_view str) -> std::string {
  59. return absl::StrReplaceAll(str, {{"\n", "\n "}});
  60. }
  61. struct EmptyComparable {
  62. friend auto operator==(EmptyComparable /*lhs*/, EmptyComparable /*rhs*/)
  63. -> bool {
  64. return true;
  65. }
  66. };
  67. struct Value;
  68. struct NullValue : EmptyComparable {};
  69. using ScalarValue = std::string;
  70. using MappingValue = std::vector<std::pair<Value, Value>>;
  71. using SequenceValue = std::vector<Value>;
  72. struct AliasValue : EmptyComparable {};
  73. // A thin wrapper around a variant of possible YAML value types. This type
  74. // intentionally provides no additional encapsulation or invariants beyond
  75. // those of the variant.
  76. struct Value : Carbon::Printable<Value>,
  77. std::variant<NullValue, ScalarValue, MappingValue, SequenceValue,
  78. AliasValue> {
  79. using variant::variant;
  80. // Prints the Value in the form of code to recreate the value.
  81. auto Print(llvm::raw_ostream& os) const -> void;
  82. // Parses a sequence of YAML documents from the given YAML text.
  83. static auto FromText(llvm::StringRef text) -> ErrorOr<SequenceValue>;
  84. };
  85. // Used to examine the results of Value::FromText.
  86. // NOLINTNEXTLINE: Expands from GoogleTest.
  87. MATCHER_P(
  88. IsYaml, matcher,
  89. "is yaml root sequence that " +
  90. IndentString(::testing::DescribeMatcher<SequenceValue>(matcher))) {
  91. const ErrorOr<SequenceValue>& yaml = arg;
  92. const ::testing::Matcher<SequenceValue>& typed_matcher = matcher;
  93. if (yaml.ok()) {
  94. // It's hard to intercept printing of the ErrorOr value, so just print it
  95. // here.
  96. *result_listener << "\n which is: " << *yaml << "\n ";
  97. return typed_matcher.MatchAndExplain(*yaml, result_listener);
  98. }
  99. *result_listener << "\n with the error: " << yaml.error() << "\n ";
  100. return false;
  101. }
  102. // Match a Value that is a MappingValue.
  103. // Similar to testing::VariantWith<MappingValue>(matcher), but with better
  104. // descriptions.
  105. // NOLINTNEXTLINE: Expands from GoogleTest.
  106. MATCHER_P(Mapping, matcher,
  107. "is mapping that " +
  108. IndentString(::testing::DescribeMatcher<MappingValue>(matcher))) {
  109. const Value& val = arg;
  110. const ::testing::Matcher<MappingValue>& typed_matcher = matcher;
  111. if (const auto* map = std::get_if<MappingValue>(&val)) {
  112. return typed_matcher.MatchAndExplain(*map, result_listener);
  113. }
  114. *result_listener << "which is not a mapping";
  115. return false;
  116. }
  117. // Match a Value that is a SequenceValue.
  118. // Similar to testing::VariantWith<SequenceValue>(matcher), but with better
  119. // descriptions.
  120. // NOLINTNEXTLINE: Expands from GoogleTest.
  121. MATCHER_P(
  122. Sequence, matcher,
  123. "is sequence that " +
  124. IndentString(::testing::DescribeMatcher<SequenceValue>(matcher))) {
  125. const Value& val = arg;
  126. const ::testing::Matcher<SequenceValue>& typed_matcher = matcher;
  127. if (const auto* map = std::get_if<SequenceValue>(&val)) {
  128. return typed_matcher.MatchAndExplain(*map, result_listener);
  129. }
  130. *result_listener << "which is not a sequence";
  131. return false;
  132. }
  133. // Match a Value that is a ScalarValue.
  134. // Similar to testing::VariantWith<ScalarValue>(matcher), but with better
  135. // descriptions.
  136. // NOLINTNEXTLINE: Expands from GoogleTest.
  137. MATCHER_P(Scalar, matcher,
  138. "has scalar value " +
  139. IndentString(::testing::DescribeMatcher<ScalarValue>(matcher))) {
  140. const Value& val = arg;
  141. const ::testing::Matcher<ScalarValue>& typed_matcher = matcher;
  142. if (const auto* map = std::get_if<ScalarValue>(&val)) {
  143. return typed_matcher.MatchAndExplain(*map, result_listener);
  144. }
  145. *result_listener << "which is not a scalar";
  146. return false;
  147. }
  148. } // namespace Carbon::Testing::Yaml
  149. #endif // CARBON_TOOLCHAIN_TESTING_YAML_TEST_HELPERS_H_