Explorar o código

Sort diagnostic output from the driver by default (#1143)

Jon Meow %!s(int64=4) %!d(string=hai) anos
pai
achega
699172633f

+ 23 - 0
toolchain/diagnostics/BUILD

@@ -19,6 +19,29 @@ cc_library(
     ],
     ],
 )
 )
 
 
+cc_library(
+    name = "sorting_diagnostic_consumer",
+    hdrs = ["sorting_diagnostic_consumer.h"],
+    deps = [
+        ":diagnostic_emitter",
+        "//common:check",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
+cc_test(
+    name = "sorting_diagnostic_consumer_test",
+    size = "small",
+    srcs = ["sorting_diagnostic_consumer_test.cpp"],
+    deps = [
+        ":diagnostic_emitter",
+        ":mocks",
+        ":sorting_diagnostic_consumer",
+        "@com_google_googletest//:gtest_main",
+        "@llvm-project//llvm:Support",
+    ],
+)
+
 cc_library(
 cc_library(
     name = "mocks",
     name = "mocks",
     testonly = 1,
     testonly = 1,

+ 3 - 0
toolchain/diagnostics/diagnostic_emitter.h

@@ -52,6 +52,9 @@ class DiagnosticConsumer {
 
 
   // Handle a diagnostic.
   // Handle a diagnostic.
   virtual auto HandleDiagnostic(const Diagnostic& diagnostic) -> void = 0;
   virtual auto HandleDiagnostic(const Diagnostic& diagnostic) -> void = 0;
+
+  // Flushes any buffered input.
+  virtual auto Flush() -> void {}
 };
 };
 
 
 // An interface that can translate some representation of a location into a
 // An interface that can translate some representation of a location into a

+ 46 - 0
toolchain/diagnostics/sorting_diagnostic_consumer.h

@@ -0,0 +1,46 @@
+// 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
+
+#ifndef TOOLCHAIN_DIAGNOSTICS_SORTING_DIAGNOSTIC_CONSUMER_H_
+#define TOOLCHAIN_DIAGNOSTICS_SORTING_DIAGNOSTIC_CONSUMER_H_
+
+#include "common/check.h"
+#include "llvm/ADT/STLExtras.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
+
+namespace Carbon {
+
+// Buffers incoming diagnostics for printing and sorting.
+class SortingDiagnosticConsumer : public DiagnosticConsumer {
+ public:
+  explicit SortingDiagnosticConsumer(DiagnosticConsumer& next_consumer)
+      : next_consumer_(&next_consumer) {}
+
+  ~SortingDiagnosticConsumer() override { Flush(); }
+
+  // Buffers the diagnostic.
+  auto HandleDiagnostic(const Diagnostic& diagnostic) -> void override {
+    diagnostics_.push_back(diagnostic);
+  }
+
+  // Sorts and flushes buffered diagnostics.
+  void Flush() override {
+    llvm::sort(diagnostics_, [](const Diagnostic& lhs, const Diagnostic& rhs) {
+      return std::tie(lhs.location.line_number, lhs.location.column_number) <
+             std::tie(rhs.location.line_number, rhs.location.column_number);
+    });
+    for (const auto& diagnostic : diagnostics_) {
+      next_consumer_->HandleDiagnostic(diagnostic);
+    }
+    diagnostics_.clear();
+  }
+
+ private:
+  llvm::SmallVector<Diagnostic, 0> diagnostics_;
+  DiagnosticConsumer* next_consumer_;
+};
+
+}  // namespace Carbon
+
+#endif  // TOOLCHAIN_DIAGNOSTICS_SORTING_DIAGNOSTIC_CONSUMER_H_

+ 86 - 0
toolchain/diagnostics/sorting_diagnostic_consumer_test.cpp

@@ -0,0 +1,86 @@
+// 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/diagnostics/sorting_diagnostic_consumer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
+#include "toolchain/diagnostics/mocks.h"
+
+namespace Carbon::Testing {
+namespace {
+
+using ::testing::InSequence;
+
+struct FakeDiagnostic : DiagnosticBase<FakeDiagnostic> {
+  static constexpr llvm::StringLiteral ShortName = "fake-diagnostic";
+  // TODO: consider ways to put the Message into `format` to allow dynamic
+  // selection of the message.
+  static constexpr llvm::StringLiteral Message = "{0}";
+
+  auto Format() -> std::string {
+    // Work around a bug in Clang's unused const variable warning by marking it
+    // used here with a no-op.
+    static_cast<void>(ShortName);
+
+    return llvm::formatv(Message.data(), message).str();
+  }
+
+  std::string message;
+};
+
+struct FakeDiagnosticLocationTranslator
+    : DiagnosticLocationTranslator<Diagnostic::Location> {
+  auto GetLocation(Diagnostic::Location loc) -> Diagnostic::Location override {
+    return loc;
+  }
+};
+
+// Produces a location for the given line and column.
+static auto MakeLoc(int line, int col) -> Diagnostic::Location {
+  return {.file_name = "test", .line_number = line, .column_number = col};
+}
+
+TEST(SortedDiagnosticEmitterTest, SortErrors) {
+  FakeDiagnosticLocationTranslator translator;
+  Testing::MockDiagnosticConsumer consumer;
+  SortingDiagnosticConsumer sorting_consumer(consumer);
+  DiagnosticEmitter<Diagnostic::Location> emitter(translator, sorting_consumer);
+
+  emitter.EmitError<FakeDiagnostic>(MakeLoc(2, 1), {.message = "M1"});
+  emitter.EmitError<FakeDiagnostic>(MakeLoc(1, 1), {.message = "M2"});
+  emitter.EmitError<FakeDiagnostic>(MakeLoc(1, 3), {.message = "M3"});
+  emitter.EmitError<FakeDiagnostic>(MakeLoc(3, 4), {.message = "M4"});
+  emitter.EmitError<FakeDiagnostic>(MakeLoc(3, 2), {.message = "M5"});
+
+  InSequence s;
+  EXPECT_CALL(consumer, HandleDiagnostic(
+                            AllOf(DiagnosticLevel(Diagnostic::Error),
+                                  DiagnosticAt(1, 1), DiagnosticMessage("M2"),
+                                  DiagnosticShortName("fake-diagnostic"))));
+  EXPECT_CALL(consumer, HandleDiagnostic(
+                            AllOf(DiagnosticLevel(Diagnostic::Error),
+                                  DiagnosticAt(1, 3), DiagnosticMessage("M3"),
+                                  DiagnosticShortName("fake-diagnostic"))));
+  EXPECT_CALL(consumer, HandleDiagnostic(
+                            AllOf(DiagnosticLevel(Diagnostic::Error),
+                                  DiagnosticAt(2, 1), DiagnosticMessage("M1"),
+                                  DiagnosticShortName("fake-diagnostic"))));
+  EXPECT_CALL(consumer, HandleDiagnostic(
+                            AllOf(DiagnosticLevel(Diagnostic::Error),
+                                  DiagnosticAt(3, 2), DiagnosticMessage("M5"),
+                                  DiagnosticShortName("fake-diagnostic"))));
+  EXPECT_CALL(consumer, HandleDiagnostic(
+                            AllOf(DiagnosticLevel(Diagnostic::Error),
+                                  DiagnosticAt(3, 4), DiagnosticMessage("M4"),
+                                  DiagnosticShortName("fake-diagnostic"))));
+  sorting_consumer.Flush();
+}
+
+}  // namespace
+}  // namespace Carbon::Testing

+ 2 - 0
toolchain/driver/BUILD

@@ -13,6 +13,7 @@ cc_library(
     textual_hdrs = ["flags.def"],
     textual_hdrs = ["flags.def"],
     deps = [
     deps = [
         "//toolchain/diagnostics:diagnostic_emitter",
         "//toolchain/diagnostics:diagnostic_emitter",
+        "//toolchain/diagnostics:sorting_diagnostic_consumer",
         "//toolchain/lexer:tokenized_buffer",
         "//toolchain/lexer:tokenized_buffer",
         "//toolchain/parser:parse_tree",
         "//toolchain/parser:parse_tree",
         "//toolchain/source:source_buffer",
         "//toolchain/source:source_buffer",
@@ -27,6 +28,7 @@ cc_test(
     deps = [
     deps = [
         ":driver",
         ":driver",
         "//toolchain/common:yaml_test_helpers",
         "//toolchain/common:yaml_test_helpers",
+        "//toolchain/diagnostics:diagnostic_emitter",
         "//toolchain/lexer:tokenized_buffer_test_helpers",
         "//toolchain/lexer:tokenized_buffer_test_helpers",
         "@com_google_googletest//:gtest_main",
         "@com_google_googletest//:gtest_main",
         "@llvm-project//llvm:Support",
         "@llvm-project//llvm:Support",

+ 24 - 11
toolchain/driver/driver.cpp

@@ -11,7 +11,7 @@
 #include "llvm/Support/Error.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/Format.h"
 #include "llvm/Support/Format.h"
-#include "toolchain/diagnostics/diagnostic_emitter.h"
+#include "toolchain/diagnostics/sorting_diagnostic_consumer.h"
 #include "toolchain/lexer/tokenized_buffer.h"
 #include "toolchain/lexer/tokenized_buffer.h"
 #include "toolchain/parser/parse_tree.h"
 #include "toolchain/parser/parse_tree.h"
 #include "toolchain/source/source_buffer.h"
 #include "toolchain/source/source_buffer.h"
@@ -44,6 +44,17 @@ auto Driver::RunFullCommand(llvm::ArrayRef<llvm::StringRef> args) -> bool {
   llvm::StringRef subcommand_text = args[0];
   llvm::StringRef subcommand_text = args[0];
   llvm::SmallVector<llvm::StringRef, 16> subcommand_args(
   llvm::SmallVector<llvm::StringRef, 16> subcommand_args(
       std::next(args.begin()), args.end());
       std::next(args.begin()), args.end());
+
+  DiagnosticConsumer* consumer = &ConsoleDiagnosticConsumer();
+  std::unique_ptr<SortingDiagnosticConsumer> sorting_consumer;
+  // TODO: Figure out command-line support (llvm::cl?), this is temporary.
+  if (!subcommand_args.empty() &&
+      subcommand_args[0] == "--print-errors=streamed") {
+    subcommand_args.erase(subcommand_args.begin());
+  } else {
+    sorting_consumer = std::make_unique<SortingDiagnosticConsumer>(*consumer);
+    consumer = sorting_consumer.get();
+  }
   switch (GetSubcommand(subcommand_text)) {
   switch (GetSubcommand(subcommand_text)) {
     case Subcommand::Unknown:
     case Subcommand::Unknown:
       error_stream_ << "ERROR: Unknown subcommand '" << subcommand_text
       error_stream_ << "ERROR: Unknown subcommand '" << subcommand_text
@@ -52,13 +63,14 @@ auto Driver::RunFullCommand(llvm::ArrayRef<llvm::StringRef> args) -> bool {
 
 
 #define CARBON_SUBCOMMAND(Name, ...) \
 #define CARBON_SUBCOMMAND(Name, ...) \
   case Subcommand::Name:             \
   case Subcommand::Name:             \
-    return Run##Name##Subcommand(subcommand_args);
+    return Run##Name##Subcommand(*consumer, subcommand_args);
 #include "toolchain/driver/flags.def"
 #include "toolchain/driver/flags.def"
   }
   }
   llvm_unreachable("All subcommands handled!");
   llvm_unreachable("All subcommands handled!");
 }
 }
 
 
-auto Driver::RunHelpSubcommand(llvm::ArrayRef<llvm::StringRef> args) -> bool {
+auto Driver::RunHelpSubcommand(DiagnosticConsumer& consumer,
+                               llvm::ArrayRef<llvm::StringRef> args) -> bool {
   // FIXME: We should support getting detailed help on a subcommand by looking
   // FIXME: We should support getting detailed help on a subcommand by looking
   // for it as a positional parameter here.
   // for it as a positional parameter here.
   if (!args.empty()) {
   if (!args.empty()) {
@@ -93,7 +105,8 @@ auto Driver::RunHelpSubcommand(llvm::ArrayRef<llvm::StringRef> args) -> bool {
   return true;
   return true;
 }
 }
 
 
-auto Driver::RunDumpTokensSubcommand(llvm::ArrayRef<llvm::StringRef> args)
+auto Driver::RunDumpTokensSubcommand(DiagnosticConsumer& consumer,
+                                     llvm::ArrayRef<llvm::StringRef> args)
     -> bool {
     -> bool {
   if (args.empty()) {
   if (args.empty()) {
     error_stream_ << "ERROR: No input file specified.\n";
     error_stream_ << "ERROR: No input file specified.\n";
@@ -117,13 +130,14 @@ auto Driver::RunDumpTokensSubcommand(llvm::ArrayRef<llvm::StringRef> args)
                           });
                           });
     return false;
     return false;
   }
   }
-  auto tokenized_source =
-      TokenizedBuffer::Lex(*source, ConsoleDiagnosticConsumer());
+  auto tokenized_source = TokenizedBuffer::Lex(*source, consumer);
+  consumer.Flush();
   tokenized_source.Print(output_stream_);
   tokenized_source.Print(output_stream_);
   return !tokenized_source.has_errors();
   return !tokenized_source.has_errors();
 }
 }
 
 
-auto Driver::RunDumpParseTreeSubcommand(llvm::ArrayRef<llvm::StringRef> args)
+auto Driver::RunDumpParseTreeSubcommand(DiagnosticConsumer& consumer,
+                                        llvm::ArrayRef<llvm::StringRef> args)
     -> bool {
     -> bool {
   if (args.empty()) {
   if (args.empty()) {
     error_stream_ << "ERROR: No input file specified.\n";
     error_stream_ << "ERROR: No input file specified.\n";
@@ -147,10 +161,9 @@ auto Driver::RunDumpParseTreeSubcommand(llvm::ArrayRef<llvm::StringRef> args)
                           });
                           });
     return false;
     return false;
   }
   }
-  auto tokenized_source =
-      TokenizedBuffer::Lex(*source, ConsoleDiagnosticConsumer());
-  auto parse_tree =
-      ParseTree::Parse(tokenized_source, ConsoleDiagnosticConsumer());
+  auto tokenized_source = TokenizedBuffer::Lex(*source, consumer);
+  auto parse_tree = ParseTree::Parse(tokenized_source, consumer);
+  consumer.Flush();
   parse_tree.Print(output_stream_);
   parse_tree.Print(output_stream_);
   return !tokenized_source.has_errors() && !parse_tree.has_errors();
   return !tokenized_source.has_errors() && !parse_tree.has_errors();
 }
 }

+ 7 - 3
toolchain/driver/driver.h

@@ -11,6 +11,7 @@
 #include "llvm/ADT/StringRef.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Support/raw_ostream.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
 
 
 namespace Carbon {
 namespace Carbon {
 
 
@@ -45,7 +46,8 @@ class Driver {
   //
   //
   // Returns true if appropriate help text was found and printed. If an invalid
   // Returns true if appropriate help text was found and printed. If an invalid
   // positional parameter (or flag) is provided, returns false.
   // positional parameter (or flag) is provided, returns false.
-  auto RunHelpSubcommand(llvm::ArrayRef<llvm::StringRef> args) -> bool;
+  auto RunHelpSubcommand(DiagnosticConsumer& consumer,
+                         llvm::ArrayRef<llvm::StringRef> args) -> bool;
 
 
   // Subcommand that dumps the token information for the provided source file.
   // Subcommand that dumps the token information for the provided source file.
   //
   //
@@ -55,7 +57,8 @@ class Driver {
   // Returns true if the operation succeeds. If the operation fails, this
   // Returns true if the operation succeeds. If the operation fails, this
   // returns false and any information about the failure is printed to the
   // returns false and any information about the failure is printed to the
   // registered error stream (stderr by default).
   // registered error stream (stderr by default).
-  auto RunDumpTokensSubcommand(llvm::ArrayRef<llvm::StringRef> args) -> bool;
+  auto RunDumpTokensSubcommand(DiagnosticConsumer& consumer,
+                               llvm::ArrayRef<llvm::StringRef> args) -> bool;
 
 
   // Subcommand that dumps the parse tree for the provided source file.
   // Subcommand that dumps the parse tree for the provided source file.
   //
   //
@@ -65,7 +68,8 @@ class Driver {
   // Returns true if the operation succeeds. If the operation fails, this
   // Returns true if the operation succeeds. If the operation fails, this
   // returns false and any information about the failure is printed to the
   // returns false and any information about the failure is printed to the
   // registered error stream (stderr by default).
   // registered error stream (stderr by default).
-  auto RunDumpParseTreeSubcommand(llvm::ArrayRef<llvm::StringRef> args) -> bool;
+  auto RunDumpParseTreeSubcommand(DiagnosticConsumer& consumer,
+                                  llvm::ArrayRef<llvm::StringRef> args) -> bool;
 
 
  private:
  private:
   auto ReportExtraArgs(llvm::StringRef subcommand_text,
   auto ReportExtraArgs(llvm::StringRef subcommand_text,

+ 15 - 9
toolchain/driver/driver_test.cpp

@@ -11,6 +11,7 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/SourceMgr.h"
 #include "llvm/Support/SourceMgr.h"
 #include "toolchain/common/yaml_test_helpers.h"
 #include "toolchain/common/yaml_test_helpers.h"
+#include "toolchain/diagnostics/diagnostic_emitter.h"
 
 
 namespace Carbon::Testing {
 namespace Carbon::Testing {
 namespace {
 namespace {
@@ -70,7 +71,7 @@ TEST(DriverTest, Help) {
   RawTestOstream test_error_stream;
   RawTestOstream test_error_stream;
   Driver driver = Driver(test_output_stream, test_error_stream);
   Driver driver = Driver(test_output_stream, test_error_stream);
 
 
-  EXPECT_TRUE(driver.RunHelpSubcommand({}));
+  EXPECT_TRUE(driver.RunHelpSubcommand(ConsoleDiagnosticConsumer(), {}));
   EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
   auto help_text = test_output_stream.TakeStr();
   auto help_text = test_output_stream.TakeStr();
 
 
@@ -90,15 +91,16 @@ TEST(DriverTest, HelpErrors) {
   RawTestOstream test_error_stream;
   RawTestOstream test_error_stream;
   Driver driver = Driver(test_output_stream, test_error_stream);
   Driver driver = Driver(test_output_stream, test_error_stream);
 
 
-  EXPECT_FALSE(driver.RunHelpSubcommand({"foo"}));
+  EXPECT_FALSE(driver.RunHelpSubcommand(ConsoleDiagnosticConsumer(), {"foo"}));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
 
 
-  EXPECT_FALSE(driver.RunHelpSubcommand({"help"}));
+  EXPECT_FALSE(driver.RunHelpSubcommand(ConsoleDiagnosticConsumer(), {"help"}));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
 
 
-  EXPECT_FALSE(driver.RunHelpSubcommand({"--xyz"}));
+  EXPECT_FALSE(
+      driver.RunHelpSubcommand(ConsoleDiagnosticConsumer(), {"--xyz"}));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
 }
 }
@@ -125,7 +127,8 @@ TEST(DriverTest, DumpTokens) {
   Driver driver = Driver(test_output_stream, test_error_stream);
   Driver driver = Driver(test_output_stream, test_error_stream);
 
 
   auto test_file_path = CreateTestFile("Hello World");
   auto test_file_path = CreateTestFile("Hello World");
-  EXPECT_TRUE(driver.RunDumpTokensSubcommand({test_file_path}));
+  EXPECT_TRUE(driver.RunDumpTokensSubcommand(ConsoleDiagnosticConsumer(),
+                                             {test_file_path}));
   EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
   auto tokenized_text = test_output_stream.TakeStr();
   auto tokenized_text = test_output_stream.TakeStr();
 
 
@@ -165,15 +168,17 @@ TEST(DriverTest, DumpTokenErrors) {
   RawTestOstream test_error_stream;
   RawTestOstream test_error_stream;
   Driver driver = Driver(test_output_stream, test_error_stream);
   Driver driver = Driver(test_output_stream, test_error_stream);
 
 
-  EXPECT_FALSE(driver.RunDumpTokensSubcommand({}));
+  EXPECT_FALSE(driver.RunDumpTokensSubcommand(ConsoleDiagnosticConsumer(), {}));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
 
 
-  EXPECT_FALSE(driver.RunDumpTokensSubcommand({"--xyz"}));
+  EXPECT_FALSE(
+      driver.RunDumpTokensSubcommand(ConsoleDiagnosticConsumer(), {"--xyz"}));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
 
 
-  EXPECT_FALSE(driver.RunDumpTokensSubcommand({"/not/a/real/file/name"}));
+  EXPECT_FALSE(driver.RunDumpTokensSubcommand(ConsoleDiagnosticConsumer(),
+                                              {"/not/a/real/file/name"}));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_output_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
   EXPECT_THAT(test_error_stream.TakeStr(), HasSubstr("ERROR"));
 }
 }
@@ -184,7 +189,8 @@ TEST(DriverTest, DumpParseTree) {
   Driver driver = Driver(test_output_stream, test_error_stream);
   Driver driver = Driver(test_output_stream, test_error_stream);
 
 
   auto test_file_path = CreateTestFile("var v: Int = 42;");
   auto test_file_path = CreateTestFile("var v: Int = 42;");
-  EXPECT_TRUE(driver.RunDumpParseTreeSubcommand({test_file_path}));
+  EXPECT_TRUE(driver.RunDumpParseTreeSubcommand(ConsoleDiagnosticConsumer(),
+                                                {test_file_path}));
   EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
   EXPECT_THAT(test_error_stream.TakeStr(), StrEq(""));
   auto tokenized_text = test_output_stream.TakeStr();
   auto tokenized_text = test_output_stream.TakeStr();
 
 

+ 13 - 17
toolchain/driver/testdata/carbon_test.carbon

@@ -2,26 +2,22 @@
 // Exceptions. See /LICENSE for license information.
 // Exceptions. See /LICENSE for license information.
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 //
 //
-// FIXME: We need to figure out how to integrate this style of integration
-// testing with Bazel.
-//
 // RUN: %{carbon} dump-tokens %s 2>&1 | \
 // RUN: %{carbon} dump-tokens %s 2>&1 | \
-// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false \
-// RUN:   --check-prefix=TOKENS %s
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
 
 
 fn run(String program) {
 fn run(String program) {
   return True;
   return True;
 }
 }
 
 
-// TOKENS: token: { index:  0, kind:              'Fn', line: 12, column:   1, indent: 1, spelling: 'fn', has_trailing_space: true }
-// TOKENS: token: { index:  1, kind:      'Identifier', line: 12, column:   4, indent: 1, spelling: 'run', identifier: 0 }
-// TOKENS: token: { index:  2, kind:       'OpenParen', line: 12, column:   7, indent: 1, spelling: '(', closing_token: 5 }
-// TOKENS: token: { index:  3, kind:      'Identifier', line: 12, column:   8, indent: 1, spelling: 'String', identifier: 1, has_trailing_space: true }
-// TOKENS: token: { index:  4, kind:      'Identifier', line: 12, column:  15, indent: 1, spelling: 'program', identifier: 2 }
-// TOKENS: token: { index:  5, kind:      'CloseParen', line: 12, column:  22, indent: 1, spelling: ')', opening_token: 2, has_trailing_space: true }
-// TOKENS: token: { index:  6, kind:  'OpenCurlyBrace', line: 12, column:  24, indent: 1, spelling: '{', closing_token: 10, has_trailing_space: true }
-// TOKENS: token: { index:  7, kind:          'Return', line: 13, column:   3, indent: 3, spelling: 'return', has_trailing_space: true }
-// TOKENS: token: { index:  8, kind:      'Identifier', line: 13, column:  10, indent: 3, spelling: 'True', identifier: 3 }
-// TOKENS: token: { index:  9, kind:            'Semi', line: 13, column:  14, indent: 3, spelling: ';', has_trailing_space: true }
-// TOKENS: token: { index: 10, kind: 'CloseCurlyBrace', line: 14, column:   1, indent: 1, spelling: '}', opening_token: 6, has_trailing_space: true }
-// TOKENS: token: { index: 11, kind:       'EndOfFile', line: 27, column: 105, indent: 1, spelling: '' }
+// CHECK: token: { index:  0, kind:              'Fn', line: 8, column:   1, indent: 1, spelling: 'fn', has_trailing_space: true }
+// CHECK: token: { index:  1, kind:      'Identifier', line: 8, column:   4, indent: 1, spelling: 'run', identifier: 0 }
+// CHECK: token: { index:  2, kind:       'OpenParen', line: 8, column:   7, indent: 1, spelling: '(', closing_token: 5 }
+// CHECK: token: { index:  3, kind:      'Identifier', line: 8, column:   8, indent: 1, spelling: 'String', identifier: 1, has_trailing_space: true }
+// CHECK: token: { index:  4, kind:      'Identifier', line: 8, column:  15, indent: 1, spelling: 'program', identifier: 2 }
+// CHECK: token: { index:  5, kind:      'CloseParen', line: 8, column:  22, indent: 1, spelling: ')', opening_token: 2, has_trailing_space: true }
+// CHECK: token: { index:  6, kind:  'OpenCurlyBrace', line: 8, column:  24, indent: 1, spelling: '{', closing_token: 10, has_trailing_space: true }
+// CHECK: token: { index:  7, kind:          'Return', line: 9, column:   3, indent: 3, spelling: 'return', has_trailing_space: true }
+// CHECK: token: { index:  8, kind:      'Identifier', line: 9, column:  10, indent: 3, spelling: 'True', identifier: 3 }
+// CHECK: token: { index:  9, kind:            'Semi', line: 9, column:  14, indent: 3, spelling: ';', has_trailing_space: true }
+// CHECK: token: { index: 10, kind: 'CloseCurlyBrace', line: 10, column:   1, indent: 1, spelling: '}', opening_token: 6, has_trailing_space: true }
+// CHECK: token: { index: 11, kind:       'EndOfFile', line: 23, column: 104, indent: 1, spelling: '' }

+ 15 - 0
toolchain/driver/testdata/errors_sorted_test.carbon

@@ -0,0 +1,15 @@
+// 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
+//
+// RUN: %{not} %{carbon} dump-tokens %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+
+fn run(String program) {
+  return True;
+
+var x = 3a;
+
+// CHECK: {{.*}}/errors_sorted_test.carbon:8:24: Closing symbol does not match most recent opening symbol.
+// CHECK: {{.*}}/errors_sorted_test.carbon:11:10: Invalid digit 'a' in decimal numeric literal.
+// CHECK-COUNT-17: token: {{.*}}

+ 15 - 0
toolchain/driver/testdata/errors_streamed_test.carbon

@@ -0,0 +1,15 @@
+// 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
+//
+// RUN: %{not} %{carbon} dump-tokens --print-errors=streamed %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+
+fn run(String program) {
+  return True;
+
+var x = 3a;
+
+// CHECK: {{.*}}/errors_streamed_test.carbon:11:10: Invalid digit 'a' in decimal numeric literal.
+// CHECK: {{.*}}/errors_streamed_test.carbon:8:24: Closing symbol does not match most recent opening symbol.
+// CHECK-COUNT-17: token: {{.*}}