diff --git a/toolchain/common/BUILD b/toolchain/common/BUILD new file mode 100644 index 000000000000..f664d580c9a5 --- /dev/null +++ b/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", + ], +) diff --git a/toolchain/common/yaml_test_helpers.cpp b/toolchain/common/yaml_test_helpers.cpp new file mode 100644 index 000000000000..58380c0f1a28 --- /dev/null +++ b/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(*node).getValue(storage).str()}; + } + + case llvm::yaml::Node::NK_BlockScalar: + return Value{ + llvm::cast(*node).getValue().str()}; + + case llvm::yaml::Node::NK_Mapping: { + MappingValue v; + for (llvm::yaml::KeyValueNode& kv : + llvm::cast(*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(*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 diff --git a/toolchain/common/yaml_test_helpers.h b/toolchain/common/yaml_test_helpers.h new file mode 100644 index 000000000000..058168fd26fc --- /dev/null +++ b/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 +#include +#include +#include + +#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>; +using SequenceValue = std::vector; +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 { + 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 +auto DescribeMatcher(::testing::Matcher matcher) -> std::string { + std::ostringstream out; + matcher.DescribeTo(&out); + return out.str(); +} + +// Match a Value that is a MappingValue. +// Same as testing::VariantWith(contents). +// NOLINTNEXTLINE: Expands from GoogleTest. +MATCHER_P(Mapping, contents, + "is mapping that " + DescribeMatcher(contents)) { + ::testing::Matcher contents_matcher = contents; + + if (auto* map = std::get_if(&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(contents). +// NOLINTNEXTLINE: Expands from GoogleTest. +MATCHER_P(Sequence, contents, + "is mapping that " + DescribeMatcher(contents)) { + ::testing::Matcher contents_matcher = contents; + + if (auto* map = std::get_if(&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(contents). +// NOLINTNEXTLINE: Expands from GoogleTest. +MATCHER_P(Scalar, value, + "has scalar value " + ::testing::PrintToString(value)) { + ::testing::Matcher value_matcher = value; + + if (auto* map = std::get_if(&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_ diff --git a/toolchain/driver/BUILD b/toolchain/driver/BUILD index d6e1ebc3ddd7..749d35c112d5 100644 --- a/toolchain/driver/BUILD +++ b/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", diff --git a/toolchain/driver/driver_test.cpp b/toolchain/driver/driver_test.cpp index 2ddce35ac198..30a1f5a9a172 100644 --- a/toolchain/driver/driver_test.cpp +++ b/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(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(root_node)->begin(); - auto* token_node = llvm::dyn_cast(&*mapping_it); - ASSERT_THAT(token_node, NotNull()); - auto* token_key_node = - llvm::dyn_cast(token_node->getKey()); - ASSERT_THAT(token_key_node, NotNull()); - EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); - auto* token_value_node = - llvm::dyn_cast(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(&*mapping_it); - ASSERT_THAT(token_node, NotNull()); - token_key_node = llvm::dyn_cast(token_node->getKey()); - ASSERT_THAT(token_key_node, NotNull()); - EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); - token_value_node = - llvm::dyn_cast(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(&*mapping_it); - ASSERT_THAT(token_node, NotNull()); - token_key_node = llvm::dyn_cast(token_node->getKey()); - ASSERT_THAT(token_key_node, NotNull()); - EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); - token_value_node = - llvm::dyn_cast(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})); diff --git a/toolchain/lexer/BUILD b/toolchain/lexer/BUILD index 3e15507bd545..7669f1dcafe7 100644 --- a/toolchain/lexer/BUILD +++ b/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", diff --git a/toolchain/lexer/tokenized_buffer_test.cpp b/toolchain/lexer/tokenized_buffer_test.cpp index c5b162875cd5..07297beda612 100644 --- a/toolchain/lexer/tokenized_buffer_test.cpp +++ b/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 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(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(root_node)->begin(); - auto* token_node = llvm::dyn_cast(&*mapping_it); - ASSERT_THAT(token_node, NotNull()); - auto* token_key_node = - llvm::dyn_cast(token_node->getKey()); - ASSERT_THAT(token_key_node, NotNull()); - EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); - auto* token_value_node = - llvm::dyn_cast(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(&*mapping_it); - ASSERT_THAT(token_node, NotNull()); - token_key_node = llvm::dyn_cast(token_node->getKey()); - ASSERT_THAT(token_key_node, NotNull()); - EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); - token_value_node = - llvm::dyn_cast(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(&*mapping_it); - ASSERT_THAT(token_node, NotNull()); - token_key_node = llvm::dyn_cast(token_node->getKey()); - ASSERT_THAT(token_key_node, NotNull()); - EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); - token_value_node = - llvm::dyn_cast(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(&*mapping_it); - ASSERT_THAT(token_node, NotNull()); - token_key_node = llvm::dyn_cast(token_node->getKey()); - ASSERT_THAT(token_key_node, NotNull()); - EXPECT_THAT(token_key_node->getRawValue(), StrEq("token")); - token_value_node = - llvm::dyn_cast(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 diff --git a/toolchain/lexer/tokenized_buffer_test_helpers.h b/toolchain/lexer/tokenized_buffer_test_helpers.h index 0fd8983893f1..86973972d789 100644 --- a/toolchain/lexer/tokenized_buffer_test_helpers.h +++ b/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(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(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(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 diff --git a/toolchain/parser/BUILD b/toolchain/parser/BUILD index 78eb64e2ca86..d62682a27496 100644 --- a/toolchain/parser/BUILD +++ b/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", diff --git a/toolchain/parser/parse_tree_test.cpp b/toolchain/parser/parse_tree_test.cpp index 081c271deda4..528cd5ba5977 100644 --- a/toolchain/parser/parse_tree_test.cpp +++ b/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 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(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(&*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(&*nkvi); - ASSERT_THAT(children_node, NotNull()); - auto* children_key_node = - llvm::dyn_cast(children_node->getKey()); - ASSERT_THAT(children_key_node, NotNull()); - EXPECT_THAT(children_key_node->getRawValue(), StrEq("children")); - auto* children_value_node = - llvm::dyn_cast(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(&*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(&*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(&*ckvi); - ASSERT_THAT(children_node, NotNull()); - children_key_node = - llvm::dyn_cast(children_node->getKey()); - ASSERT_THAT(children_key_node, NotNull()); - EXPECT_THAT(children_key_node->getRawValue(), StrEq("children")); - children_value_node = - llvm::dyn_cast(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(&*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(&*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(&*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