Просмотр исходного кода

[toolchain] Add simpler mechanism for matching YAML output in tests. (#583)

Richard Smith 4 лет назад
Родитель
Сommit
6a4c8a5186

+ 18 - 0
toolchain/common/BUILD

@@ -0,0 +1,18 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
+
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+    name = "yaml_test_helpers",
+    testonly = 1,
+    srcs = ["yaml_test_helpers.cpp"],
+    hdrs = ["yaml_test_helpers.h"],
+    deps = [
+        "@llvm-project//llvm:gmock",
+        "@llvm-project//llvm:gtest",
+    ],
+)

+ 110 - 0
toolchain/common/yaml_test_helpers.cpp

@@ -0,0 +1,110 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "toolchain/common/yaml_test_helpers.h"
+
+#include "llvm/Support/YAMLParser.h"
+
+namespace Carbon::Testing::Yaml {
+
+static auto Parse(llvm::yaml::Node* node) -> Value {
+  if (!node) {
+    return Value{ErrorValue()};
+  }
+
+  switch (node->getType()) {
+    case llvm::yaml::Node::NK_Null:
+      return Value{NullValue()};
+
+    case llvm::yaml::Node::NK_Scalar: {
+      llvm::SmallString<128> storage;
+      return Value{
+          llvm::cast<llvm::yaml::ScalarNode>(*node).getValue(storage).str()};
+    }
+
+    case llvm::yaml::Node::NK_BlockScalar:
+      return Value{
+          llvm::cast<llvm::yaml::BlockScalarNode>(*node).getValue().str()};
+
+    case llvm::yaml::Node::NK_Mapping: {
+      MappingValue v;
+      for (llvm::yaml::KeyValueNode& kv :
+           llvm::cast<llvm::yaml::MappingNode>(*node)) {
+        Value key = Parse(kv.getKey());
+        Value value = Parse(kv.getValue());
+        v.emplace_back(std::move(key), std::move(value));
+      }
+      return Value{std::move(v)};
+    }
+
+    case llvm::yaml::Node::NK_Sequence: {
+      SequenceValue v;
+      for (llvm::yaml::Node& n : llvm::cast<llvm::yaml::SequenceNode>(*node)) {
+        v.push_back(Parse(&n));
+      }
+      return Value{std::move(v)};
+    }
+
+    case llvm::yaml::Node::NK_Alias:
+      return Value{AliasValue()};
+
+    case llvm::yaml::Node::NK_KeyValue:
+      llvm_unreachable("should only exist as child of mapping");
+  }
+
+  llvm_unreachable("unknown yaml node kind");
+}
+
+auto Value::FromText(llvm::StringRef text) -> SequenceValue {
+  llvm::SourceMgr sm;
+  llvm::yaml::Stream yaml_stream(text, sm);
+
+  SequenceValue result;
+  for (llvm::yaml::Document& document : yaml_stream) {
+    result.push_back(Parse(document.getRoot()));
+  }
+  return result;
+}
+
+auto operator<<(std::ostream& os, const Value& v) -> std::ostream& {
+  // Variant visitor that prints the value in the form of code to recreate the
+  // value.
+  struct Printer {
+    std::ostream& out;
+    auto operator()(NullValue) -> void { out << "Yaml::NullValue()"; }
+    auto operator()(AliasValue) -> void { out << "Yaml::AliasValue()"; }
+    auto operator()(ErrorValue) -> void { out << "Yaml::ErrorValue()"; }
+    auto operator()(const ScalarValue& v) -> void { out << std::quoted(v); }
+    auto operator()(const MappingValue& v) -> void {
+      out << "Yaml::MappingValue{";
+      bool first = true;
+      for (auto& [key, value] : v) {
+        if (first) {
+          first = false;
+        } else {
+          out << ", ";
+        }
+        out << "{" << key << ", " << value << "}";
+      }
+      out << "}";
+    }
+    auto operator()(const SequenceValue& v) -> void {
+      out << "Yaml::SequenceValue{";
+      bool first = true;
+      for (auto& value : v) {
+        if (first) {
+          first = false;
+        } else {
+          out << ", ";
+        }
+        out << value;
+      }
+      out << "}";
+    }
+  };
+  std::visit(Printer{os}, v);
+  return os;
+}
+
+}  // namespace Carbon::Testing::Yaml

+ 149 - 0
toolchain/common/yaml_test_helpers.h

@@ -0,0 +1,149 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+// This file provides gmock matchers to support testing YAML output.
+//
+// A YAML document can be converted into a matchable value using
+// Yaml::Value::FromText, and then matched with Yaml::Mapping, Yaml::Sequence,
+// or Yaml::Scalar. Scalar values can also be matched directly against strings.
+//
+// Example usage:
+//
+//     namespace Yaml = Carbon::Testing::Yaml;
+//     using ::testing::ElementsAre;
+//     using ::testing::Pair;
+//     Yaml::Value yaml = Yaml::Value::FromText(R"yaml(
+//     ---
+//     fruits:
+//       - apple
+//       - orange
+//       - pear
+//     ...
+//     ---
+//     - [foo: "bar"]: "baz"
+//     )yaml"),
+//
+//     // Exact values can be matched by constructing the desired value.
+//     EXPECT_THAT(
+//         yaml,
+//         ElementsAre(
+//             Yaml::MappingValue{
+//                 {"fruits", Yaml::SequenceValue{"apple", "orange", "pear"}}},
+//             Yaml::SequenceValue{Yaml::MappingValue{
+//                 {Yaml::SequenceValue{Yaml::MappingValue{{"foo", "bar"}}},
+//                  "baz"}}}));
+//
+//     // Properties can be checked using Yaml::Mapping or Yaml::Sequence to
+//     // adapt regular gmock container matchers.
+//     EXPECT_THAT(
+//         yaml,
+//         Contains(Yaml::Mapping(
+//             Contains(Pair("fruits", Yaml::Sequence(Contains("orange")))))));
+//
+// On match failure, Yaml::Values are printed as C++ code that can be used to
+// recreate the value, for easy copy-pasting into test expectations.
+
+#ifndef TOOLCHAIN_COMMON_YAML_TEST_HELPERS_H_
+#define TOOLCHAIN_COMMON_YAML_TEST_HELPERS_H_
+
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <variant>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace Carbon {
+namespace Testing {
+namespace Yaml {
+
+struct EmptyComparable {
+  friend auto operator==(EmptyComparable, EmptyComparable) -> bool {
+    return true;
+  }
+  friend auto operator!=(EmptyComparable, EmptyComparable) -> bool {
+    return false;
+  }
+};
+
+struct Value;
+struct NullValue : EmptyComparable {};
+using ScalarValue = std::string;
+using MappingValue = std::vector<std::pair<Value, Value>>;
+using SequenceValue = std::vector<Value>;
+struct AliasValue : EmptyComparable {};
+struct ErrorValue : EmptyComparable {};
+
+// A thin wrapper around a variant of possible YAML value types. This type
+// intentionally provides no additional encapsulation or invariants beyond
+// those of the variant.
+struct Value : std::variant<NullValue, ScalarValue, MappingValue, SequenceValue,
+                            AliasValue, ErrorValue> {
+  using variant::variant;
+
+  // Prints the Value in the form of code to recreate the value.
+  friend auto operator<<(std::ostream& os, const Value& v) -> std::ostream&;
+
+  // Parses a sequence of YAML documents from the given YAML text.
+  static auto FromText(llvm::StringRef text) -> SequenceValue;
+};
+
+template <typename T>
+auto DescribeMatcher(::testing::Matcher<T> matcher) -> std::string {
+  std::ostringstream out;
+  matcher.DescribeTo(&out);
+  return out.str();
+}
+
+// Match a Value that is a MappingValue.
+// Same as testing::VariantWith<MappingValue>(contents).
+// NOLINTNEXTLINE: Expands from GoogleTest.
+MATCHER_P(Mapping, contents,
+          "is mapping that " + DescribeMatcher<MappingValue>(contents)) {
+  ::testing::Matcher<MappingValue> contents_matcher = contents;
+
+  if (auto* map = std::get_if<MappingValue>(&arg)) {
+    return contents_matcher.MatchAndExplain(*map, result_listener);
+  }
+
+  *result_listener << "which is not a mapping";
+  return false;
+}
+
+// Match a Value that is a SequenceValue.
+// Same as testing::VariantWith<SequenceValue>(contents).
+// NOLINTNEXTLINE: Expands from GoogleTest.
+MATCHER_P(Sequence, contents,
+          "is mapping that " + DescribeMatcher<SequenceValue>(contents)) {
+  ::testing::Matcher<SequenceValue> contents_matcher = contents;
+
+  if (auto* map = std::get_if<SequenceValue>(&arg)) {
+    return contents_matcher.MatchAndExplain(*map, result_listener);
+  }
+
+  *result_listener << "which is not a sequence";
+  return false;
+}
+
+// Match a Value that is a ScalarValue.
+// Same as testing::VariantWith<ScalarValue>(contents).
+// NOLINTNEXTLINE: Expands from GoogleTest.
+MATCHER_P(Scalar, value,
+          "has scalar value " + ::testing::PrintToString(value)) {
+  ::testing::Matcher<ScalarValue> value_matcher = value;
+
+  if (auto* map = std::get_if<ScalarValue>(&arg)) {
+    return value_matcher.MatchAndExplain(*map, result_listener);
+  }
+
+  *result_listener << "which is not a scalar";
+  return false;
+}
+
+}  // namespace Yaml
+}  // namespace Testing
+}  // namespace Carbon
+
+#endif  // TOOLCHAIN_COMMON_YAML_TEST_HELPERS_H_

+ 1 - 0
toolchain/driver/BUILD

@@ -25,6 +25,7 @@ cc_test(
     srcs = ["driver_test.cpp"],
     deps = [
         ":driver",
+        "//toolchain/common:yaml_test_helpers",
         "//toolchain/lexer:tokenized_buffer_test_helpers",
         "@llvm-project//llvm:Support",
         "@llvm-project//llvm:gmock",

+ 27 - 93
toolchain/driver/driver_test.cpp

@@ -9,17 +9,17 @@
 #include "llvm/ADT/SmallString.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/SourceMgr.h"
-#include "llvm/Support/YAMLParser.h"
-#include "toolchain/lexer/tokenized_buffer_test_helpers.h"
+#include "toolchain/common/yaml_test_helpers.h"
 
 namespace Carbon {
 namespace {
 
-using Carbon::Testing::IsKeyValueScalars;
+using ::testing::ElementsAre;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::NotNull;
 using ::testing::StrEq;
+namespace Yaml = Carbon::Testing::Yaml;
 
 /// A raw_ostream that makes it easy to repeatedly check streamed output.
 class RawTestOstream : public llvm::raw_ostream {
@@ -130,96 +130,30 @@ TEST(DriverTest, DumpTokens) {
   EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
   auto tokenized_text = test_output_stream.TakeStr();
 
-  // Parse the output into a YAML stream. This will print errors to stderr and
-  // is the most stable view of the textual dumping API.
-  llvm::SourceMgr sm;
-  llvm::yaml::Stream yaml_stream(tokenized_text, sm);
-  auto yaml_it = yaml_stream.begin();
-  auto* root_node = llvm::dyn_cast<llvm::yaml::MappingNode>(yaml_it->getRoot());
-  ASSERT_THAT(root_node, NotNull());
-
-  // Walk the top-level mapping of tokens, dig out the sub-mapping of data for
-  // each taken, and then verify those entries.
-  auto mapping_it = llvm::cast<llvm::yaml::MappingNode>(root_node)->begin();
-  auto* token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
-  ASSERT_THAT(token_node, NotNull());
-  auto* token_key_node =
-      llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
-  ASSERT_THAT(token_key_node, NotNull());
-  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
-  auto* token_value_node =
-      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
-  ASSERT_THAT(token_value_node, NotNull());
-  auto token_it = token_value_node->begin();
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "0"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "Identifier"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", "Hello"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("identifier", "0"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("has_trailing_space", "true"));
-  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
-
-  ++mapping_it;
-  token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
-  ASSERT_THAT(token_node, NotNull());
-  token_key_node = llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
-  ASSERT_THAT(token_key_node, NotNull());
-  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
-  token_value_node =
-      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
-  ASSERT_THAT(token_value_node, NotNull());
-  token_it = token_value_node->begin();
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "Identifier"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "7"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", "World"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("identifier", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("has_trailing_space", "true"));
-  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
-
-  ++mapping_it;
-  token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
-  ASSERT_THAT(token_node, NotNull());
-  token_key_node = llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
-  ASSERT_THAT(token_key_node, NotNull());
-  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
-  token_value_node =
-      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
-  ASSERT_THAT(token_value_node, NotNull());
-  token_it = token_value_node->begin();
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "2"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "EndOfFile"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "12"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", ""));
-  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
-
-  ASSERT_THAT(++mapping_it, Eq(root_node->end()));
-  ASSERT_THAT(++yaml_it, Eq(yaml_stream.end()));
+  EXPECT_THAT(Yaml::Value::FromText(tokenized_text),
+              ElementsAre(Yaml::MappingValue{
+                  {"token", Yaml::MappingValue{{"index", "0"},
+                                               {"kind", "Identifier"},
+                                               {"line", "1"},
+                                               {"column", "1"},
+                                               {"indent", "1"},
+                                               {"spelling", "Hello"},
+                                               {"identifier", "0"},
+                                               {"has_trailing_space", "true"}}},
+                  {"token", Yaml::MappingValue{{"index", "1"},
+                                               {"kind", "Identifier"},
+                                               {"line", "1"},
+                                               {"column", "7"},
+                                               {"indent", "1"},
+                                               {"spelling", "World"},
+                                               {"identifier", "1"},
+                                               {"has_trailing_space", "true"}}},
+                  {"token", Yaml::MappingValue{{"index", "2"},
+                                               {"kind", "EndOfFile"},
+                                               {"line", "1"},
+                                               {"column", "12"},
+                                               {"indent", "1"},
+                                               {"spelling", ""}}}}));
 
   // Check that the subcommand dispatch works.
   EXPECT_TRUE(driver.RunFullCommand({"dump-tokens", test_file_path}));

+ 1 - 0
toolchain/lexer/BUILD

@@ -148,6 +148,7 @@ cc_test(
     deps = [
         ":tokenized_buffer",
         ":tokenized_buffer_test_helpers",
+        "//toolchain/common:yaml_test_helpers",
         "//toolchain/diagnostics:diagnostic_emitter",
         "//toolchain/diagnostics:mocks",
         "@llvm-project//llvm:Support",

+ 32 - 112
toolchain/lexer/tokenized_buffer_test.cpp

@@ -14,8 +14,8 @@
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/Twine.h"
 #include "llvm/Support/SourceMgr.h"
-#include "llvm/Support/YAMLParser.h"
 #include "llvm/Support/raw_ostream.h"
+#include "toolchain/common/yaml_test_helpers.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/diagnostics/mocks.h"
 #include "toolchain/lexer/tokenized_buffer_test_helpers.h"
@@ -27,11 +27,12 @@ using ::Carbon::Testing::DiagnosticAt;
 using ::Carbon::Testing::DiagnosticMessage;
 using ::Carbon::Testing::ExpectedToken;
 using ::Carbon::Testing::HasTokens;
-using ::Carbon::Testing::IsKeyValueScalars;
+using ::testing::ElementsAre;
 using ::testing::Eq;
 using ::testing::HasSubstr;
 using ::testing::NotNull;
 using ::testing::StrEq;
+namespace Yaml = Carbon::Testing::Yaml;
 
 struct LexerTest : ::testing::Test {
   llvm::SmallVector<SourceBuffer, 16> source_storage;
@@ -962,116 +963,35 @@ TEST_F(LexerTest, PrintingAsYaml) {
   buffer.Print(print_stream);
   print_stream.flush();
 
-  // Parse the output into a YAML stream. This will print errors to stderr.
-  llvm::SourceMgr source_manager;
-  llvm::yaml::Stream yaml_stream(print_output, source_manager);
-  auto yaml_it = yaml_stream.begin();
-  auto* root_node = llvm::dyn_cast<llvm::yaml::MappingNode>(yaml_it->getRoot());
-  ASSERT_THAT(root_node, NotNull());
-
-  // Walk the top-level mapping of tokens, dig out the sub-mapping of data for
-  // each taken, and then verify those entries.
-  auto mapping_it = llvm::cast<llvm::yaml::MappingNode>(root_node)->begin();
-  auto* token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
-  ASSERT_THAT(token_node, NotNull());
-  auto* token_key_node =
-      llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
-  ASSERT_THAT(token_key_node, NotNull());
-  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
-  auto* token_value_node =
-      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
-  ASSERT_THAT(token_value_node, NotNull());
-  auto token_it = token_value_node->begin();
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "0"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "Semi"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "2"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "2"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "2"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", ";"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("has_trailing_space", "true"));
-  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
-
-  ++mapping_it;
-  token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
-  ASSERT_THAT(token_node, NotNull());
-  token_key_node = llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
-  ASSERT_THAT(token_key_node, NotNull());
-  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
-  token_value_node =
-      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
-  ASSERT_THAT(token_value_node, NotNull());
-  token_it = token_value_node->begin();
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "Semi"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "5"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", ";"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("has_trailing_space", "true"));
-  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
-
-  ++mapping_it;
-  token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
-  ASSERT_THAT(token_node, NotNull());
-  token_key_node = llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
-  ASSERT_THAT(token_key_node, NotNull());
-  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
-  token_value_node =
-      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
-  ASSERT_THAT(token_value_node, NotNull());
-  token_it = token_value_node->begin();
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "2"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "Semi"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "5"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "3"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", ";"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("has_trailing_space", "true"));
-  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
-
-  ++mapping_it;
-  token_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*mapping_it);
-  ASSERT_THAT(token_node, NotNull());
-  token_key_node = llvm::dyn_cast<llvm::yaml::ScalarNode>(token_node->getKey());
-  ASSERT_THAT(token_key_node, NotNull());
-  EXPECT_THAT(token_key_node->getRawValue(), StrEq("token"));
-  token_value_node =
-      llvm::dyn_cast<llvm::yaml::MappingNode>(token_node->getValue());
-  ASSERT_THAT(token_value_node, NotNull());
-  token_it = token_value_node->begin();
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("index", "3"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("kind", "EndOfFile"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("line", "15"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("column", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("indent", "1"));
-  ++token_it;
-  EXPECT_THAT(&*token_it, IsKeyValueScalars("spelling", ""));
-  EXPECT_THAT(++token_it, Eq(token_value_node->end()));
-
-  ASSERT_THAT(++mapping_it, Eq(root_node->end()));
-  ASSERT_THAT(++yaml_it, Eq(yaml_stream.end()));
+  EXPECT_THAT(Yaml::Value::FromText(print_output),
+              ElementsAre(Yaml::MappingValue{
+                  {"token", Yaml::MappingValue{{"index", "0"},
+                                               {"kind", "Semi"},
+                                               {"line", "2"},
+                                               {"column", "2"},
+                                               {"indent", "2"},
+                                               {"spelling", ";"},
+                                               {"has_trailing_space", "true"}}},
+                  {"token", Yaml::MappingValue{{"index", "1"},
+                                               {"kind", "Semi"},
+                                               {"line", "5"},
+                                               {"column", "1"},
+                                               {"indent", "1"},
+                                               {"spelling", ";"},
+                                               {"has_trailing_space", "true"}}},
+                  {"token", Yaml::MappingValue{{"index", "2"},
+                                               {"kind", "Semi"},
+                                               {"line", "5"},
+                                               {"column", "3"},
+                                               {"indent", "1"},
+                                               {"spelling", ";"},
+                                               {"has_trailing_space", "true"}}},
+                  {"token", Yaml::MappingValue{{"index", "3"},
+                                               {"kind", "EndOfFile"},
+                                               {"line", "15"},
+                                               {"column", "1"},
+                                               {"indent", "1"},
+                                               {"spelling", ""}}}}));
 }
 
 }  // namespace

+ 0 - 39
toolchain/lexer/tokenized_buffer_test_helpers.h

@@ -151,45 +151,6 @@ MATCHER_P(HasTokens, raw_all_expected, "") {
   return matches;
 }
 
-// NOLINTNEXTLINE: Expands from GoogleTest.
-MATCHER_P2(IsKeyValueScalars, key, value, "") {
-  auto* kv_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(arg);
-  if (!kv_node) {
-    *result_listener << "this is a `" << arg->getType()
-                     << "` node, not a `KeyValueNode`.";
-    return false;
-  }
-
-  llvm::SmallString<128> storage;
-
-  auto* key_node = llvm::dyn_cast<llvm::yaml::ScalarNode>(kv_node->getKey());
-  if (!key_node) {
-    *result_listener << "the key is a `" << arg->getType()
-                     << "` node, not a `ScalarNode`.";
-    return false;
-  }
-  if (key != key_node->getValue(storage)) {
-    *result_listener << "the key is `" << key_node->getValue(storage).str()
-                     << "`, expected `" << key << "`.";
-    return false;
-  }
-
-  auto* value_node =
-      llvm::dyn_cast<llvm::yaml::ScalarNode>(kv_node->getValue());
-  if (!value_node) {
-    *result_listener << "the value is a `" << arg->getType()
-                     << "` node, not a `ScalarNode`.";
-    return false;
-  }
-  if (value != value_node->getValue(storage)) {
-    *result_listener << "the value is `" << value_node->getValue(storage).str()
-                     << "`, expected `" << value << "`.";
-    return false;
-  }
-
-  return true;
-}
-
 }  // namespace Testing
 }  // namespace Carbon
 

+ 1 - 1
toolchain/parser/BUILD

@@ -64,9 +64,9 @@ cc_test(
         ":parse_node_kind",
         ":parse_test_helpers",
         ":parse_tree",
+        "//toolchain/common:yaml_test_helpers",
         "//toolchain/diagnostics:diagnostic_emitter",
         "//toolchain/lexer:tokenized_buffer",
-        "//toolchain/lexer:tokenized_buffer_test_helpers",
         "@llvm-project//llvm:Support",
         "@llvm-project//llvm:gmock",
         "@llvm-project//llvm:gtest",

+ 31 - 126
toolchain/parser/parse_tree_test.cpp

@@ -10,10 +10,9 @@
 #include "gtest/gtest.h"
 #include "llvm/ADT/Sequence.h"
 #include "llvm/Support/SourceMgr.h"
-#include "llvm/Support/YAMLParser.h"
+#include "toolchain/common/yaml_test_helpers.h"
 #include "toolchain/diagnostics/diagnostic_emitter.h"
 #include "toolchain/lexer/tokenized_buffer.h"
-#include "toolchain/lexer/tokenized_buffer_test_helpers.h"
 #include "toolchain/parser/parse_node_kind.h"
 #include "toolchain/parser/parse_test_helpers.h"
 
@@ -21,13 +20,14 @@ namespace Carbon {
 namespace {
 
 using Carbon::Testing::ExpectedNode;
-using Carbon::Testing::IsKeyValueScalars;
 using Carbon::Testing::MatchParseTreeNodes;
 using namespace Carbon::Testing::NodeMatchers;
+using ::testing::ElementsAre;
 using ::testing::Eq;
 using ::testing::Ne;
 using ::testing::NotNull;
 using ::testing::StrEq;
+namespace Yaml = Carbon::Testing::Yaml;
 
 struct ParseTreeTest : ::testing::Test {
   std::forward_list<SourceBuffer> source_storage;
@@ -884,129 +884,34 @@ TEST_F(ParseTreeTest, PrintingAsYAML) {
   tree.Print(print_stream);
   print_stream.flush();
 
-  // Parse the output into a YAML stream. This will print errors to stderr.
-  llvm::SourceMgr source_manager;
-  llvm::yaml::Stream yaml_stream(print_output, source_manager);
-  auto di = yaml_stream.begin();
-  auto* root_node = llvm::dyn_cast<llvm::yaml::SequenceNode>(di->getRoot());
-  ASSERT_THAT(root_node, NotNull());
-
-  // The root node is just an array of top-level parse nodes.
-  auto ni = root_node->begin();
-  auto ne = root_node->end();
-  auto* node = llvm::dyn_cast<llvm::yaml::MappingNode>(&*ni);
-  ASSERT_THAT(node, NotNull());
-  auto nkvi = node->begin();
-  auto nkve = node->end();
-  EXPECT_THAT(&*nkvi, IsKeyValueScalars("node_index", "4"));
-  ++nkvi;
-  EXPECT_THAT(&*nkvi, IsKeyValueScalars("kind", "FunctionDeclaration"));
-  ++nkvi;
-  EXPECT_THAT(&*nkvi, IsKeyValueScalars("text", "fn"));
-  ++nkvi;
-  EXPECT_THAT(&*nkvi, IsKeyValueScalars("subtree_size", "5"));
-  ++nkvi;
-  auto* children_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*nkvi);
-  ASSERT_THAT(children_node, NotNull());
-  auto* children_key_node =
-      llvm::dyn_cast<llvm::yaml::ScalarNode>(children_node->getKey());
-  ASSERT_THAT(children_key_node, NotNull());
-  EXPECT_THAT(children_key_node->getRawValue(), StrEq("children"));
-  auto* children_value_node =
-      llvm::dyn_cast<llvm::yaml::SequenceNode>(children_node->getValue());
-  ASSERT_THAT(children_value_node, NotNull());
-
-  auto ci = children_value_node->begin();
-  auto ce = children_value_node->end();
-  ASSERT_THAT(ci, Ne(ce));
-  node = llvm::dyn_cast<llvm::yaml::MappingNode>(&*ci);
-  ASSERT_THAT(node, NotNull());
-  auto ckvi = node->begin();
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("node_index", "0"));
-  ++ckvi;
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("kind", "DeclaredName"));
-  ++ckvi;
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("text", "F"));
-  ++ckvi;
-  EXPECT_THAT(ckvi, Eq(node->end()));
-
-  ++ci;
-  ASSERT_THAT(ci, Ne(ce));
-  node = llvm::dyn_cast<llvm::yaml::MappingNode>(&*ci);
-  ASSERT_THAT(node, NotNull());
-  ckvi = node->begin();
-  auto ckve = node->end();
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("node_index", "2"));
-  ++ckvi;
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("kind", "ParameterList"));
-  ++ckvi;
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("text", "("));
-  ++ckvi;
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("subtree_size", "2"));
-  ++ckvi;
-  children_node = llvm::dyn_cast<llvm::yaml::KeyValueNode>(&*ckvi);
-  ASSERT_THAT(children_node, NotNull());
-  children_key_node =
-      llvm::dyn_cast<llvm::yaml::ScalarNode>(children_node->getKey());
-  ASSERT_THAT(children_key_node, NotNull());
-  EXPECT_THAT(children_key_node->getRawValue(), StrEq("children"));
-  children_value_node =
-      llvm::dyn_cast<llvm::yaml::SequenceNode>(children_node->getValue());
-  ASSERT_THAT(children_value_node, NotNull());
-
-  auto c2_i = children_value_node->begin();
-  auto c2_e = children_value_node->end();
-  ASSERT_THAT(c2_i, Ne(c2_e));
-  node = llvm::dyn_cast<llvm::yaml::MappingNode>(&*c2_i);
-  ASSERT_THAT(node, NotNull());
-  auto c2_kvi = node->begin();
-  EXPECT_THAT(&*c2_kvi, IsKeyValueScalars("node_index", "1"));
-  ++c2_kvi;
-  EXPECT_THAT(&*c2_kvi, IsKeyValueScalars("kind", "ParameterListEnd"));
-  ++c2_kvi;
-  EXPECT_THAT(&*c2_kvi, IsKeyValueScalars("text", ")"));
-  ++c2_kvi;
-  EXPECT_THAT(c2_kvi, Eq(node->end()));
-  ++c2_i;
-  EXPECT_THAT(c2_i, Eq(c2_e));
-  ++ckvi;
-  EXPECT_THAT(ckvi, Eq(ckve));
-
-  ++ci;
-  ASSERT_THAT(ci, Ne(ce));
-  node = llvm::dyn_cast<llvm::yaml::MappingNode>(&*ci);
-  ASSERT_THAT(node, NotNull());
-  ckvi = node->begin();
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("node_index", "3"));
-  ++ckvi;
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("kind", "DeclarationEnd"));
-  ++ckvi;
-  EXPECT_THAT(&*ckvi, IsKeyValueScalars("text", ";"));
-  ++ckvi;
-  EXPECT_THAT(ckvi, Eq(node->end()));
-  ++ci;
-  EXPECT_THAT(ci, Eq(ce));
-
-  ++nkvi;
-  EXPECT_THAT(nkvi, Eq(nkve));
-
-  ++ni;
-  ASSERT_THAT(ni, Ne(ne));
-  node = llvm::dyn_cast<llvm::yaml::MappingNode>(&*ni);
-  ASSERT_THAT(node, NotNull());
-  nkvi = node->begin();
-  EXPECT_THAT(&*nkvi, IsKeyValueScalars("node_index", "5"));
-  ++nkvi;
-  EXPECT_THAT(&*nkvi, IsKeyValueScalars("kind", "FileEnd"));
-  ++nkvi;
-  EXPECT_THAT(&*nkvi, IsKeyValueScalars("text", ""));
-  ++nkvi;
-  EXPECT_THAT(nkvi, Eq(node->end()));
-
-  ++ni;
-  EXPECT_THAT(ni, Eq(ne));
-  ++di;
-  EXPECT_THAT(di, Eq(yaml_stream.end()));
+  EXPECT_THAT(
+      Yaml::Value::FromText(print_output),
+      ElementsAre(Yaml::SequenceValue{
+          Yaml::MappingValue{
+              {"node_index", "4"},
+              {"kind", "FunctionDeclaration"},
+              {"text", "fn"},
+              {"subtree_size", "5"},
+              {"children",
+               Yaml::SequenceValue{
+                   Yaml::MappingValue{{"node_index", "0"},
+                                      {"kind", "DeclaredName"},
+                                      {"text", "F"}},
+                   Yaml::MappingValue{{"node_index", "2"},
+                                      {"kind", "ParameterList"},
+                                      {"text", "("},
+                                      {"subtree_size", "2"},
+                                      {"children",  //
+                                       Yaml::SequenceValue{Yaml::MappingValue{
+                                           {"node_index", "1"},
+                                           {"kind", "ParameterListEnd"},
+                                           {"text", ")"}}}}},
+                   Yaml::MappingValue{{"node_index", "3"},
+                                      {"kind", "DeclarationEnd"},
+                                      {"text", ";"}}}}},
+          Yaml::MappingValue{{"node_index", "5"},  //
+                             {"kind", "FileEnd"},
+                             {"text", ""}}}));
 }
 
 }  // namespace