yaml_test_helpers.h 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  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. // 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. // 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 TOOLCHAIN_COMMON_YAML_TEST_HELPERS_H_
  47. #define TOOLCHAIN_COMMON_YAML_TEST_HELPERS_H_
  48. #include <iomanip>
  49. #include <iostream>
  50. #include <sstream>
  51. #include <variant>
  52. #include "gmock/gmock.h"
  53. #include "gtest/gtest.h"
  54. namespace Carbon {
  55. namespace Testing {
  56. namespace Yaml {
  57. struct EmptyComparable {
  58. friend auto operator==(EmptyComparable, EmptyComparable) -> bool {
  59. return true;
  60. }
  61. friend auto operator!=(EmptyComparable, EmptyComparable) -> bool {
  62. return false;
  63. }
  64. };
  65. struct Value;
  66. struct NullValue : EmptyComparable {};
  67. using ScalarValue = std::string;
  68. using MappingValue = std::vector<std::pair<Value, Value>>;
  69. using SequenceValue = std::vector<Value>;
  70. struct AliasValue : EmptyComparable {};
  71. struct ErrorValue : EmptyComparable {};
  72. // A thin wrapper around a variant of possible YAML value types. This type
  73. // intentionally provides no additional encapsulation or invariants beyond
  74. // those of the variant.
  75. struct Value : std::variant<NullValue, ScalarValue, MappingValue, SequenceValue,
  76. AliasValue, ErrorValue> {
  77. using variant::variant;
  78. // Prints the Value in the form of code to recreate the value.
  79. friend auto operator<<(std::ostream& os, const Value& v) -> std::ostream&;
  80. // Parses a sequence of YAML documents from the given YAML text.
  81. static auto FromText(llvm::StringRef text) -> SequenceValue;
  82. };
  83. template <typename T>
  84. auto DescribeMatcher(::testing::Matcher<T> matcher) -> std::string {
  85. std::ostringstream out;
  86. matcher.DescribeTo(&out);
  87. return out.str();
  88. }
  89. // Match a Value that is a MappingValue.
  90. // Same as testing::VariantWith<MappingValue>(contents).
  91. // NOLINTNEXTLINE: Expands from GoogleTest.
  92. MATCHER_P(Mapping, contents,
  93. "is mapping that " + DescribeMatcher<MappingValue>(contents)) {
  94. ::testing::Matcher<MappingValue> contents_matcher = contents;
  95. if (auto* map = std::get_if<MappingValue>(&arg)) {
  96. return contents_matcher.MatchAndExplain(*map, result_listener);
  97. }
  98. *result_listener << "which is not a mapping";
  99. return false;
  100. }
  101. // Match a Value that is a SequenceValue.
  102. // Same as testing::VariantWith<SequenceValue>(contents).
  103. // NOLINTNEXTLINE: Expands from GoogleTest.
  104. MATCHER_P(Sequence, contents,
  105. "is mapping that " + DescribeMatcher<SequenceValue>(contents)) {
  106. ::testing::Matcher<SequenceValue> contents_matcher = contents;
  107. if (auto* map = std::get_if<SequenceValue>(&arg)) {
  108. return contents_matcher.MatchAndExplain(*map, result_listener);
  109. }
  110. *result_listener << "which is not a sequence";
  111. return false;
  112. }
  113. // Match a Value that is a ScalarValue.
  114. // Same as testing::VariantWith<ScalarValue>(contents).
  115. // NOLINTNEXTLINE: Expands from GoogleTest.
  116. MATCHER_P(Scalar, value,
  117. "has scalar value " + ::testing::PrintToString(value)) {
  118. ::testing::Matcher<ScalarValue> value_matcher = value;
  119. if (auto* map = std::get_if<ScalarValue>(&arg)) {
  120. return value_matcher.MatchAndExplain(*map, result_listener);
  121. }
  122. *result_listener << "which is not a scalar";
  123. return false;
  124. }
  125. } // namespace Yaml
  126. } // namespace Testing
  127. } // namespace Carbon
  128. #endif // TOOLCHAIN_COMMON_YAML_TEST_HELPERS_H_