Преглед изворни кода

Add support for testing with stdin (#4819)

In order to write language-server tests, we need some way to pass stdin
input. This adds support for a split "// --- STDIN" which will be
provided as a temp file for testing.

Note this does more stdin -> input_stream style renaming, this is just
bugging me more since I know shadowing works but it can be subtle to
read, particularly since I'm now making direct use of stdin in a handful
of spots.
Jon Ross-Perkins пре 1 година
родитељ
комит
e393c769af

+ 6 - 4
explorer/file_test.cpp

@@ -29,7 +29,8 @@ class ExplorerFileTest : public FileTestBase {
 
 
   auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
   auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
            llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
            llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
-           llvm::raw_pwrite_stream& stdout, llvm::raw_pwrite_stream& stderr)
+           FILE* /*input_stream*/, llvm::raw_pwrite_stream& output_stream,
+           llvm::raw_pwrite_stream& error_stream)
       -> ErrorOr<RunResult> override {
       -> ErrorOr<RunResult> override {
     // Add the prelude.
     // Add the prelude.
     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> prelude =
     llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> prelude =
@@ -52,9 +53,10 @@ class ExplorerFileTest : public FileTestBase {
       args.push_back(arg.data());
       args.push_back(arg.data());
     }
     }
 
 
-    int exit_code = ExplorerMain(
-        args.size(), args.data(), /*install_path=*/"", PreludePath, stdout,
-        stderr, check_trace_output() ? stdout : trace_stream_, *fs);
+    int exit_code =
+        ExplorerMain(args.size(), args.data(), /*install_path=*/"", PreludePath,
+                     output_stream, error_stream,
+                     check_trace_output() ? output_stream : trace_stream_, *fs);
 
 
     return {{.success = exit_code == EXIT_SUCCESS}};
     return {{.success = exit_code == EXIT_SUCCESS}};
   }
   }

+ 32 - 24
testing/file_test/README.md

@@ -50,9 +50,11 @@ class MyFileTest : public FileTestBase {
   // Called as part of individual test executions.
   // Called as part of individual test executions.
   auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
   auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
            const llvm::SmallVector<TestFile>& test_files,
            const llvm::SmallVector<TestFile>& test_files,
-           llvm::raw_pwrite_stream& stdout, llvm::raw_pwrite_stream& stderr)
+           FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
+           llvm::raw_pwrite_stream& error_stream)
       -> ErrorOr<RunResult> override {
       -> ErrorOr<RunResult> override {
-    return MyFunctionality(test_args, stdout, stderr);
+    return MyFunctionality(test_args, input_stream, output_stream,
+                           error_stream);
   }
   }
 
 
   // Provides arguments which are used in tests that don't provide ARGS.
   // Provides arguments which are used in tests that don't provide ARGS.
@@ -108,21 +110,22 @@ Supported comment markers are:
     // NOAUTOUPDATE
     // NOAUTOUPDATE
     ```
     ```
 
 
-    Controls whether the checks in the file will be autoupdated if --autoupdate
-    is passed. Exactly one of these markers must be present. If the file uses
-    splits, the marker must currently be before any splits.
+    Controls whether the checks in the file will be autoupdated if
+    `--autoupdate` is passed. Exactly one of these markers must be present. If
+    the file uses splits, the marker must currently be before any splits.
 
 
-    When autoupdating, CHECKs will be inserted starting below AUTOUPDATE. When a
-    CHECK has line information, autoupdate will try to insert the CHECK
-    immediately next to the line it's associated with, with stderr CHECKs
-    preceding the line and stdout CHECKs following the line. When that happens,
-    any subsequent CHECK lines without line information, or that refer to lines
-    appearing earlier, will immediately follow. As an exception, if no STDOUT
-    check line refers to any line in the test, all STDOUT check lines are placed
-    at the end of the file instead of immediately after AUTOUPDATE.
+    When autoupdating, `CHECK`s will be inserted starting below `AUTOUPDATE`.
+    When a `CHECK` has line information, autoupdate will try to insert the
+    `CHECK` immediately next to the line it's associated with, with stderr
+    `CHECK`s preceding the line and stdout `CHECK`s following the line. When
+    that happens, any subsequent `CHECK` lines without line information, or that
+    refer to lines appearing earlier, will immediately follow. As an exception,
+    if no `STDOUT` check line refers to any line in the test, all `STDOUT` check
+    lines are placed at the end of the file instead of immediately after
+    `AUTOUPDATE`.
 
 
     When using split files, if the last split file is named
     When using split files, if the last split file is named
-    `// --- AUTOUPDATE-SPLIT`, all CHECKs will be added there; no line
+    `// --- AUTOUPDATE-SPLIT`, all `CHECK`s will be added there; no line
     associations occur.
     associations occur.
 
 
 -   ```
 -   ```
@@ -149,8 +152,8 @@ Supported comment markers are:
         Replaces some implementation-specific identifier with a value. (Mappings
         Replaces some implementation-specific identifier with a value. (Mappings
         provided by way of an optional `MyFileTest::GetArgReplacements`)
         provided by way of an optional `MyFileTest::GetArgReplacements`)
 
 
-    ARGS can be specified at most once. If not provided, the FileTestBase child
-    is responsible for providing default arguments.
+    `ARGS` can be specified at most once. If not provided, the `FileTestBase`
+    child is responsible for providing default arguments.
 
 
 -   ```
 -   ```
     // EXTRA-ARGS: <arguments>
     // EXTRA-ARGS: <arguments>
@@ -173,28 +176,33 @@ Supported comment markers are:
     This should be avoided because we are partly ensuring that streams are an
     This should be avoided because we are partly ensuring that streams are an
     API, but is helpful when wrapping Clang, where stderr is used directly.
     API, but is helpful when wrapping Clang, where stderr is used directly.
 
 
-    SET-CAPTURE-CONSOLE-OUTPUT can be specified at most once.
+    `SET-CAPTURE-CONSOLE-OUTPUT` can be specified at most once.
 
 
 -   ```
 -   ```
     // SET-CHECK-SUBSET
     // SET-CHECK-SUBSET
     ```
     ```
 
 
-    By default, all lines of output must have a CHECK match. Adding this as a
+    By default, all lines of output must have a `CHECK` match. Adding this as a
     option sets it so that non-matching lines are ignored. All provided
     option sets it so that non-matching lines are ignored. All provided
-    CHECK:STDOUT: and CHECK:STDERR: lines must still have a match in output.
+    `CHECK:STDOUT:` and `CHECK:STDERR:` lines must still have a match in output.
 
 
-    SET-CHECK-SUBSET can be specified at most once.
+    `SET-CHECK-SUBSET`can be specified at most once.
 
 
 -   ```
 -   ```
     // --- <filename>
     // --- <filename>
     ```
     ```
 
 
     By default, all file content is provided to the test as a single file in
     By default, all file content is provided to the test as a single file in
-    test_files. Using this marker allows the file to be split into multiple
-    files which will all be passed to test_files.
+    `test_files`. Using this marker allows the file to be split into multiple
+    files which will all be passed to `test_files`.
 
 
-    Files are not created on disk; it's expected the child will create an
-    InMemoryFilesystem if needed.
+    Files are not created on disk; instead, content is passed in through the
+    `fs` passed to `Run`.
+
+    If the filename is `STDIN`, it will be provided as `input_stream` instead of
+    in `test_files`. Currently, autoupdate can place `CHECK` lines in the
+    `STDIN` split; use `AUTOUPDATE-SPLIT` to avoid that (see `AUTOUPDATE` for
+    information).
 
 
 -   ```
 -   ```
     // CHECK:STDOUT: <output line>
     // CHECK:STDOUT: <output line>

+ 3 - 3
testing/file_test/autoupdate.h

@@ -39,7 +39,7 @@ class FileTestAutoupdater {
       const llvm::SmallVector<llvm::StringRef>& filenames,
       const llvm::SmallVector<llvm::StringRef>& filenames,
       int autoupdate_line_number, bool autoupdate_split,
       int autoupdate_line_number, bool autoupdate_split,
       const llvm::SmallVector<FileTestLine>& non_check_lines,
       const llvm::SmallVector<FileTestLine>& non_check_lines,
-      llvm::StringRef stdout, llvm::StringRef stderr,
+      llvm::StringRef actual_stdout, llvm::StringRef actual_stderr,
       const std::optional<RE2>& default_file_re,
       const std::optional<RE2>& default_file_re,
       const llvm::SmallVector<LineNumberReplacement>& line_number_replacements,
       const llvm::SmallVector<LineNumberReplacement>& line_number_replacements,
       std::function<void(std::string&)> do_extra_check_replacements)
       std::function<void(std::string&)> do_extra_check_replacements)
@@ -56,8 +56,8 @@ class FileTestAutoupdater {
         do_extra_check_replacements_(std::move(do_extra_check_replacements)),
         do_extra_check_replacements_(std::move(do_extra_check_replacements)),
         // BuildCheckLines should only be called after other member
         // BuildCheckLines should only be called after other member
         // initialization.
         // initialization.
-        stdout_(BuildCheckLines(stdout, "STDOUT")),
-        stderr_(BuildCheckLines(stderr, "STDERR")),
+        stdout_(BuildCheckLines(actual_stdout, "STDOUT")),
+        stderr_(BuildCheckLines(actual_stderr, "STDERR")),
         any_attached_stdout_lines_(llvm::any_of(
         any_attached_stdout_lines_(llvm::any_of(
             stdout_.lines,
             stdout_.lines,
             [&](const CheckLine& line) { return line.line_number() != -1; })),
             [&](const CheckLine& line) { return line.line_number() != -1; })),

+ 41 - 22
testing/file_test/file_test_base.cpp

@@ -58,6 +58,8 @@ using ::testing::Matcher;
 using ::testing::MatchesRegex;
 using ::testing::MatchesRegex;
 using ::testing::StrEq;
 using ::testing::StrEq;
 
 
+static constexpr llvm::StringLiteral StdinFilename = "STDIN";
+
 // Reads a file to string.
 // Reads a file to string.
 static auto ReadFile(std::string_view path) -> ErrorOr<std::string> {
 static auto ReadFile(std::string_view path) -> ErrorOr<std::string> {
   std::ifstream proto_file{std::string(path)};
   std::ifstream proto_file{std::string(path)};
@@ -186,15 +188,15 @@ auto FileTestBase::TestBody() -> void {
   }
   }
   SCOPED_TRACE(update_message);
   SCOPED_TRACE(update_message);
   if (context.check_subset) {
   if (context.check_subset) {
-    EXPECT_THAT(SplitOutput(context.stdout),
+    EXPECT_THAT(SplitOutput(context.actual_stdout),
                 IsSupersetOf(context.expected_stdout));
                 IsSupersetOf(context.expected_stdout));
-    EXPECT_THAT(SplitOutput(context.stderr),
+    EXPECT_THAT(SplitOutput(context.actual_stderr),
                 IsSupersetOf(context.expected_stderr));
                 IsSupersetOf(context.expected_stderr));
 
 
   } else {
   } else {
-    EXPECT_THAT(SplitOutput(context.stdout),
+    EXPECT_THAT(SplitOutput(context.actual_stdout),
                 ElementsAreArray(context.expected_stdout));
                 ElementsAreArray(context.expected_stdout));
-    EXPECT_THAT(SplitOutput(context.stderr),
+    EXPECT_THAT(SplitOutput(context.actual_stderr),
                 ElementsAreArray(context.expected_stderr));
                 ElementsAreArray(context.expected_stderr));
   }
   }
 
 
@@ -232,8 +234,9 @@ auto FileTestBase::RunAutoupdater(const TestContext& context, bool dry_run)
              GetBazelCommand(BazelMode::Test, test_name_),
              GetBazelCommand(BazelMode::Test, test_name_),
              GetBazelCommand(BazelMode::Dump, test_name_),
              GetBazelCommand(BazelMode::Dump, test_name_),
              context.input_content, filenames, *context.autoupdate_line_number,
              context.input_content, filenames, *context.autoupdate_line_number,
-             context.autoupdate_split, context.non_check_lines, context.stdout,
-             context.stderr, GetDefaultFileRE(expected_filenames),
+             context.autoupdate_split, context.non_check_lines,
+             context.actual_stdout, context.actual_stderr,
+             GetDefaultFileRE(expected_filenames),
              GetLineNumberReplacements(expected_filenames),
              GetLineNumberReplacements(expected_filenames),
              [&](std::string& line) { DoExtraCheckReplacements(line); })
              [&](std::string& line) { DoExtraCheckReplacements(line); })
       .Run(dry_run);
       .Run(dry_run);
@@ -266,7 +269,8 @@ auto FileTestBase::DumpOutput() -> ErrorOr<Success> {
     return ErrorBuilder() << "Error updating " << test_name_ << ": "
     return ErrorBuilder() << "Error updating " << test_name_ << ": "
                           << run_result.error();
                           << run_result.error();
   }
   }
-  llvm::errs() << banner << context.stdout << banner << "= Exit with success: "
+  llvm::errs() << banner << context.actual_stdout << banner
+               << "= Exit with success: "
                << (context.run_result.success ? "true" : "false") << "\n"
                << (context.run_result.success ? "true" : "false") << "\n"
                << banner;
                << banner;
   return Success();
   return Success();
@@ -297,18 +301,32 @@ auto FileTestBase::ProcessTestFileAndRun(TestContext& context)
   CARBON_RETURN_IF_ERROR(
   CARBON_RETURN_IF_ERROR(
       DoArgReplacements(context.test_args, context.test_files));
       DoArgReplacements(context.test_args, context.test_files));
 
 
+  // stdin needs to exist on-disk for compatibility. We'll use a pointer for it.
+  FILE* input_stream = nullptr;
+  auto erase_input_on_exit = llvm::make_scope_exit([&input_stream]() {
+    if (input_stream) {
+      // fclose should delete the tmpfile.
+      fclose(input_stream);
+      input_stream = nullptr;
+    }
+  });
+
   // Create the files in-memory.
   // Create the files in-memory.
   llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> fs =
   llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> fs =
       new llvm::vfs::InMemoryFileSystem;
       new llvm::vfs::InMemoryFileSystem;
   for (const auto& test_file : context.test_files) {
   for (const auto& test_file : context.test_files) {
-    if (!fs->addFile(test_file.filename, /*ModificationTime=*/0,
-                     llvm::MemoryBuffer::getMemBuffer(
-                         test_file.content, test_file.filename,
-                         /*RequiresNullTerminator=*/false))) {
+    if (test_file.filename == StdinFilename) {
+      input_stream = tmpfile();
+      fwrite(test_file.content.c_str(), sizeof(char), test_file.content.size(),
+             input_stream);
+      rewind(input_stream);
+    } else if (!fs->addFile(test_file.filename, /*ModificationTime=*/0,
+                            llvm::MemoryBuffer::getMemBuffer(
+                                test_file.content, test_file.filename,
+                                /*RequiresNullTerminator=*/false))) {
       return ErrorBuilder() << "File is repeated: " << test_file.filename;
       return ErrorBuilder() << "File is repeated: " << test_file.filename;
     }
     }
   }
   }
-
   // Convert the arguments to StringRef and const char* to match the
   // Convert the arguments to StringRef and const char* to match the
   // expectations of PrettyStackTraceProgram and Run.
   // expectations of PrettyStackTraceProgram and Run.
   llvm::SmallVector<llvm::StringRef> test_args_ref;
   llvm::SmallVector<llvm::StringRef> test_args_ref;
@@ -340,20 +358,21 @@ auto FileTestBase::ProcessTestFileAndRun(TestContext& context)
     if (context.capture_console_output) {
     if (context.capture_console_output) {
       // No need to flush stderr.
       // No need to flush stderr.
       llvm::outs().flush();
       llvm::outs().flush();
-      context.stdout += GetCapturedStdout();
-      context.stderr += GetCapturedStderr();
+      context.actual_stdout += GetCapturedStdout();
+      context.actual_stderr += GetCapturedStderr();
     }
     }
   });
   });
 
 
   // Prepare string streams to capture output. In order to address casting
   // Prepare string streams to capture output. In order to address casting
   // constraints, we split calls to Run as a ternary based on whether we want to
   // constraints, we split calls to Run as a ternary based on whether we want to
   // capture output.
   // capture output.
-  llvm::raw_svector_ostream stdout(context.stdout);
-  llvm::raw_svector_ostream stderr(context.stderr);
+  llvm::raw_svector_ostream output_stream(context.actual_stdout);
+  llvm::raw_svector_ostream error_stream(context.actual_stderr);
   CARBON_ASSIGN_OR_RETURN(
   CARBON_ASSIGN_OR_RETURN(
       context.run_result,
       context.run_result,
-      context.dump_output ? Run(test_args_ref, fs, llvm::outs(), llvm::errs())
-                          : Run(test_args_ref, fs, stdout, stderr));
+      context.dump_output
+          ? Run(test_args_ref, fs, input_stream, llvm::outs(), llvm::errs())
+          : Run(test_args_ref, fs, input_stream, output_stream, error_stream));
 
 
   return Success();
   return Success();
 }
 }
@@ -380,10 +399,11 @@ auto FileTestBase::DoArgReplacements(
         it = test_args.erase(it);
         it = test_args.erase(it);
         for (const auto& file : test_files) {
         for (const auto& file : test_files) {
           const std::string& filename = file.filename;
           const std::string& filename = file.filename;
-          if (!filename.ends_with(".h")) {
-            it = test_args.insert(it, filename);
-            ++it;
+          if (filename == StdinFilename || filename.ends_with(".h")) {
+            continue;
           }
           }
+          it = test_args.insert(it, filename);
+          ++it;
         }
         }
         // Back up once because the for loop will advance.
         // Back up once because the for loop will advance.
         --it;
         --it;
@@ -546,7 +566,6 @@ static auto AddTestFile(llvm::StringRef filename, std::string* content,
                         llvm::SmallVector<FileTestBase::TestFile>* test_files)
                         llvm::SmallVector<FileTestBase::TestFile>* test_files)
     -> ErrorOr<Success> {
     -> ErrorOr<Success> {
   CARBON_RETURN_IF_ERROR(ReplaceContentKeywords(filename, content));
   CARBON_RETURN_IF_ERROR(ReplaceContentKeywords(filename, content));
-
   test_files->push_back(
   test_files->push_back(
       {.filename = filename.str(), .content = std::move(*content)});
       {.filename = filename.str(), .content = std::move(*content)});
   content->clear();
   content->clear();

+ 12 - 7
testing/file_test/file_test_base.h

@@ -68,9 +68,13 @@ class FileTestBase : public testing::Test {
   explicit FileTestBase(std::mutex* output_mutex, llvm::StringRef test_name)
   explicit FileTestBase(std::mutex* output_mutex, llvm::StringRef test_name)
       : output_mutex_(output_mutex), test_name_(test_name) {}
       : output_mutex_(output_mutex), test_name_(test_name) {}
 
 
-  // Implemented by children to run the test. For example, TestBody validates
-  // stdout and stderr. Children should use fs for file content, and may add
-  // more files.
+  // Implemented by children to run the test. The framework will validate the
+  // content written to `output_stream` and `error_stream`. Children should use
+  // `fs` for file content, and may add more files.
+  //
+  // If there is a split test file named "STDIN", then its contents will be
+  // provided at `input_stream` instead of `fs`. Otherwise, `input_stream` will
+  // be null.
   //
   //
   // Any test expectations should be called from ValidateRun, not Run.
   // Any test expectations should be called from ValidateRun, not Run.
   //
   //
@@ -78,8 +82,9 @@ class FileTestBase : public testing::Test {
   // RunResult otherwise.
   // RunResult otherwise.
   virtual auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
   virtual auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
                    llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
                    llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
-                   llvm::raw_pwrite_stream& stdout,
-                   llvm::raw_pwrite_stream& stderr) -> ErrorOr<RunResult> = 0;
+                   FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
+                   llvm::raw_pwrite_stream& error_stream)
+      -> ErrorOr<RunResult> = 0;
 
 
   // Implemented by children to do post-Run test expectations. Only called when
   // Implemented by children to do post-Run test expectations. Only called when
   // testing. Does not need to be provided if only CHECK test expectations are
   // testing. Does not need to be provided if only CHECK test expectations are
@@ -174,8 +179,8 @@ class FileTestBase : public testing::Test {
     llvm::SmallVector<testing::Matcher<std::string>> expected_stderr;
     llvm::SmallVector<testing::Matcher<std::string>> expected_stderr;
 
 
     // stdout and stderr from Run. 16 is arbitrary but a required value.
     // stdout and stderr from Run. 16 is arbitrary but a required value.
-    llvm::SmallString<16> stdout;
-    llvm::SmallString<16> stderr;
+    llvm::SmallString<16> actual_stdout;
+    llvm::SmallString<16> actual_stderr;
 
 
     RunResult run_result = {.success = false};
     RunResult run_result = {.success = false};
   };
   };

+ 92 - 68
testing/file_test/file_test_base_test.cpp

@@ -23,7 +23,8 @@ class FileTestBaseTest : public FileTestBase {
 
 
   auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
   auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
            llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
            llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
-           llvm::raw_pwrite_stream& stdout, llvm::raw_pwrite_stream& stderr)
+           FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
+           llvm::raw_pwrite_stream& error_stream)
       -> ErrorOr<RunResult> override;
       -> ErrorOr<RunResult> override;
 
 
   auto GetArgReplacements() -> llvm::StringMap<std::string> override {
   auto GetArgReplacements() -> llvm::StringMap<std::string> override {
@@ -55,13 +56,13 @@ class FileTestBaseTest : public FileTestBase {
 
 
 // Prints arguments so that they can be validated in tests.
 // Prints arguments so that they can be validated in tests.
 static auto PrintArgs(llvm::ArrayRef<llvm::StringRef> args,
 static auto PrintArgs(llvm::ArrayRef<llvm::StringRef> args,
-                      llvm::raw_pwrite_stream& stdout) -> void {
+                      llvm::raw_pwrite_stream& output_stream) -> void {
   llvm::ListSeparator sep;
   llvm::ListSeparator sep;
-  stdout << args.size() << " args: ";
+  output_stream << args.size() << " args: ";
   for (auto arg : args) {
   for (auto arg : args) {
-    stdout << sep << "`" << arg << "`";
+    output_stream << sep << "`" << arg << "`";
   }
   }
-  stdout << "\n";
+  output_stream << "\n";
 }
 }
 
 
 // Verifies arguments are well-structured, and returns the files in them.
 // Verifies arguments are well-structured, and returns the files in them.
@@ -85,113 +86,131 @@ static auto GetFilesFromArgs(llvm::ArrayRef<llvm::StringRef> args,
 struct TestParams {
 struct TestParams {
   // These are the arguments to `Run()`.
   // These are the arguments to `Run()`.
   llvm::vfs::InMemoryFileSystem& fs;
   llvm::vfs::InMemoryFileSystem& fs;
-  llvm::raw_pwrite_stream& stdout;
-  llvm::raw_pwrite_stream& stderr;
+  FILE* input_stream;
+  llvm::raw_pwrite_stream& output_stream;
+  llvm::raw_pwrite_stream& error_stream;
 
 
   // This is assigned after construction.
   // This is assigned after construction.
   llvm::ArrayRef<llvm::StringRef> files;
   llvm::ArrayRef<llvm::StringRef> files;
 };
 };
 
 
-// Does printing and returns expected results for alternating_files.carbon.
+// Prints and returns expected results for alternating_files.carbon.
 static auto TestAlternatingFiles(TestParams& params)
 static auto TestAlternatingFiles(TestParams& params)
     -> ErrorOr<FileTestBaseTest::RunResult> {
     -> ErrorOr<FileTestBaseTest::RunResult> {
-  params.stdout << "unattached message 1\n"
-                << "a.carbon:2: message 2\n"
-                << "b.carbon:5: message 3\n"
-                << "a.carbon:2: message 4\n"
-                << "b.carbon:5: message 5\n"
-                << "unattached message 6\n";
-  params.stderr << "unattached message 1\n"
-                << "a.carbon:2: message 2\n"
-                << "b.carbon:5: message 3\n"
-                << "a.carbon:2: message 4\n"
-                << "b.carbon:5: message 5\n"
-                << "unattached message 6\n";
+  params.output_stream << "unattached message 1\n"
+                       << "a.carbon:2: message 2\n"
+                       << "b.carbon:5: message 3\n"
+                       << "a.carbon:2: message 4\n"
+                       << "b.carbon:5: message 5\n"
+                       << "unattached message 6\n";
+  params.error_stream << "unattached message 1\n"
+                      << "a.carbon:2: message 2\n"
+                      << "b.carbon:5: message 3\n"
+                      << "a.carbon:2: message 4\n"
+                      << "b.carbon:5: message 5\n"
+                      << "unattached message 6\n";
   return {{.success = true}};
   return {{.success = true}};
 }
 }
 
 
-// Does printing and returns expected results for capture_console_output.carbon.
+// Prints and returns expected results for capture_console_output.carbon.
 static auto TestCaptureConsoleOutput(TestParams& params)
 static auto TestCaptureConsoleOutput(TestParams& params)
     -> ErrorOr<FileTestBaseTest::RunResult> {
     -> ErrorOr<FileTestBaseTest::RunResult> {
   llvm::errs() << "llvm::errs\n";
   llvm::errs() << "llvm::errs\n";
-  params.stderr << "params.stderr\n";
+  params.error_stream << "params.error_stream\n";
   llvm::outs() << "llvm::outs\n";
   llvm::outs() << "llvm::outs\n";
-  params.stdout << "params.stdout\n";
+  params.output_stream << "params.output_stream\n";
   return {{.success = true}};
   return {{.success = true}};
 }
 }
 
 
-// Does printing and returns expected results for example.carbon.
+// Prints and returns expected results for example.carbon.
 static auto TestExample(TestParams& params)
 static auto TestExample(TestParams& params)
     -> ErrorOr<FileTestBaseTest::RunResult> {
     -> ErrorOr<FileTestBaseTest::RunResult> {
   int delta_line = 10;
   int delta_line = 10;
-  params.stdout << "something\n"
-                << "\n"
-                << "example.carbon:" << delta_line + 1 << ": Line delta\n"
-                << "example.carbon:" << delta_line << ": Negative line delta\n"
-                << "+*[]{}\n"
-                << "Foo baz\n";
+  params.output_stream << "something\n"
+                       << "\n"
+                       << "example.carbon:" << delta_line + 1
+                       << ": Line delta\n"
+                       << "example.carbon:" << delta_line
+                       << ": Negative line delta\n"
+                       << "+*[]{}\n"
+                       << "Foo baz\n";
   return {{.success = true}};
   return {{.success = true}};
 }
 }
 
 
-// Does printing and returns expected results for fail_example.carbon.
+// Prints and returns expected results for fail_example.carbon.
 static auto TestFailExample(TestParams& params)
 static auto TestFailExample(TestParams& params)
     -> ErrorOr<FileTestBaseTest::RunResult> {
     -> ErrorOr<FileTestBaseTest::RunResult> {
-  params.stderr << "Oops\n";
+  params.error_stream << "Oops\n";
   return {{.success = false}};
   return {{.success = false}};
 }
 }
 
 
-// Does printing and returns expected results for
+// Prints and returns expected results for
 // file_only_re_multi_file.carbon.
 // file_only_re_multi_file.carbon.
 static auto TestFileOnlyREMultiFile(TestParams& params)
 static auto TestFileOnlyREMultiFile(TestParams& params)
     -> ErrorOr<FileTestBaseTest::RunResult> {
     -> ErrorOr<FileTestBaseTest::RunResult> {
   int msg_count = 0;
   int msg_count = 0;
-  params.stdout << "unattached message " << ++msg_count << "\n"
-                << "file: a.carbon\n"
-                << "unattached message " << ++msg_count << "\n"
-                << "line: 3: attached message " << ++msg_count << "\n"
-                << "unattached message " << ++msg_count << "\n"
-                << "line: 8: late message " << ++msg_count << "\n"
-                << "unattached message " << ++msg_count << "\n"
-                << "file: b.carbon\n"
-                << "line: 2: attached message " << ++msg_count << "\n"
-                << "unattached message " << ++msg_count << "\n"
-                << "line: 7: late message " << ++msg_count << "\n"
-                << "unattached message " << ++msg_count << "\n";
+  params.output_stream << "unattached message " << ++msg_count << "\n"
+                       << "file: a.carbon\n"
+                       << "unattached message " << ++msg_count << "\n"
+                       << "line: 3: attached message " << ++msg_count << "\n"
+                       << "unattached message " << ++msg_count << "\n"
+                       << "line: 8: late message " << ++msg_count << "\n"
+                       << "unattached message " << ++msg_count << "\n"
+                       << "file: b.carbon\n"
+                       << "line: 2: attached message " << ++msg_count << "\n"
+                       << "unattached message " << ++msg_count << "\n"
+                       << "line: 7: late message " << ++msg_count << "\n"
+                       << "unattached message " << ++msg_count << "\n";
   return {{.success = true}};
   return {{.success = true}};
 }
 }
 
 
-// Does printing and returns expected results for file_only_re_one_file.carbon.
+// Prints and returns expected results for file_only_re_one_file.carbon.
 static auto TestFileOnlyREOneFile(TestParams& params)
 static auto TestFileOnlyREOneFile(TestParams& params)
     -> ErrorOr<FileTestBaseTest::RunResult> {
     -> ErrorOr<FileTestBaseTest::RunResult> {
-  params.stdout << "unattached message 1\n"
-                << "file: file_only_re_one_file.carbon\n"
-                << "line: 1\n"
-                << "unattached message 2\n";
+  params.output_stream << "unattached message 1\n"
+                       << "file: file_only_re_one_file.carbon\n"
+                       << "line: 1\n"
+                       << "unattached message 2\n";
   return {{.success = true}};
   return {{.success = true}};
 }
 }
 
 
-// Does printing and returns expected results for no_line_number.carbon.
+// Prints and returns expected results for no_line_number.carbon.
 static auto TestNoLineNumber(TestParams& params)
 static auto TestNoLineNumber(TestParams& params)
     -> ErrorOr<FileTestBaseTest::RunResult> {
     -> ErrorOr<FileTestBaseTest::RunResult> {
-  params.stdout << "a.carbon: msg1\n"
-                   "msg2\n"
-                   "b.carbon: msg3\n"
-                   "msg4\n"
-                   "a.carbon: msg5\n";
+  params.output_stream << "a.carbon: msg1\n"
+                          "msg2\n"
+                          "b.carbon: msg3\n"
+                          "msg4\n"
+                          "a.carbon: msg5\n";
   return {{.success = true}};
   return {{.success = true}};
 }
 }
 
 
-// Does printing and returns expected results for unattached_multi_file.carbon.
+// Prints and returns expected results for stdin.carbon.
+static auto TestStdin(TestParams& params)
+    -> ErrorOr<FileTestBaseTest::RunResult> {
+  CARBON_CHECK(params.input_stream);
+  constexpr int ReadSize = 256;
+  char buf[ReadSize];
+  while (feof(params.input_stream) == 0) {
+    auto read = fread(&buf, sizeof(char), ReadSize, params.input_stream);
+    if (read > 0) {
+      params.error_stream.write(buf, read);
+    }
+  }
+  return {{.success = true}};
+}
+
+// Prints and returns expected results for unattached_multi_file.carbon.
 static auto TestUnattachedMultiFile(TestParams& params)
 static auto TestUnattachedMultiFile(TestParams& params)
     -> ErrorOr<FileTestBaseTest::RunResult> {
     -> ErrorOr<FileTestBaseTest::RunResult> {
-  params.stdout << "unattached message 1\n"
-                << "unattached message 2\n";
-  params.stderr << "unattached message 3\n"
-                << "unattached message 4\n";
+  params.output_stream << "unattached message 1\n"
+                       << "unattached message 2\n";
+  params.error_stream << "unattached message 3\n"
+                      << "unattached message 4\n";
   return {{.success = true}};
   return {{.success = true}};
 }
 }
 
 
-// Does printing and returns expected results for:
+// Prints and returns expected results for:
 // - fail_multi_success_overall_fail.carbon
 // - fail_multi_success_overall_fail.carbon
 // - multi_success.carbon
 // - multi_success.carbon
 // - multi_success_and_fail.carbon
 // - multi_success_and_fail.carbon
@@ -220,8 +239,8 @@ static auto EchoFileContent(TestParams& params)
     for (int line_number = 1; !buffer.empty(); ++line_number) {
     for (int line_number = 1; !buffer.empty(); ++line_number) {
       auto [line, remainder] = buffer.split('\n');
       auto [line, remainder] = buffer.split('\n');
       if (!line.empty() && !line.starts_with("//")) {
       if (!line.empty() && !line.starts_with("//")) {
-        params.stdout << test_file << ":" << line_number << ": " << line
-                      << "\n";
+        params.output_stream << test_file << ":" << line_number << ": " << line
+                             << "\n";
       }
       }
       buffer = remainder;
       buffer = remainder;
     }
     }
@@ -232,9 +251,9 @@ static auto EchoFileContent(TestParams& params)
 auto FileTestBaseTest::Run(
 auto FileTestBaseTest::Run(
     const llvm::SmallVector<llvm::StringRef>& test_args,
     const llvm::SmallVector<llvm::StringRef>& test_args,
     llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
     llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
-    llvm::raw_pwrite_stream& stdout, llvm::raw_pwrite_stream& stderr)
-    -> ErrorOr<RunResult> {
-  PrintArgs(test_args, stdout);
+    FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
+    llvm::raw_pwrite_stream& error_stream) -> ErrorOr<RunResult> {
+  PrintArgs(test_args, output_stream);
 
 
   auto filename = std::filesystem::path(test_name().str()).filename();
   auto filename = std::filesystem::path(test_name().str()).filename();
   if (filename == "args.carbon") {
   if (filename == "args.carbon") {
@@ -254,6 +273,8 @@ auto FileTestBaseTest::Run(
           .Case("file_only_re_one_file.carbon", &TestFileOnlyREOneFile)
           .Case("file_only_re_one_file.carbon", &TestFileOnlyREOneFile)
           .Case("file_only_re_multi_file.carbon", &TestFileOnlyREMultiFile)
           .Case("file_only_re_multi_file.carbon", &TestFileOnlyREMultiFile)
           .Case("no_line_number.carbon", &TestNoLineNumber)
           .Case("no_line_number.carbon", &TestNoLineNumber)
+          .Case("stdin.carbon", &TestStdin)
+          .Case("stdin_and_autoupdate_split.carbon", &TestStdin)
           .Case("unattached_multi_file.carbon", &TestUnattachedMultiFile)
           .Case("unattached_multi_file.carbon", &TestUnattachedMultiFile)
           .Case("fail_multi_success_overall_fail.carbon",
           .Case("fail_multi_success_overall_fail.carbon",
                 [&](TestParams&) {
                 [&](TestParams&) {
@@ -273,7 +294,10 @@ auto FileTestBaseTest::Run(
           .Default(&EchoFileContent);
           .Default(&EchoFileContent);
 
 
   // Call the appropriate test function for the file.
   // Call the appropriate test function for the file.
-  TestParams params = {.fs = *fs, .stdout = stdout, .stderr = stderr};
+  TestParams params = {.fs = *fs,
+                       .input_stream = input_stream,
+                       .output_stream = output_stream,
+                       .error_stream = error_stream};
   CARBON_ASSIGN_OR_RETURN(params.files, GetFilesFromArgs(test_args, *fs));
   CARBON_ASSIGN_OR_RETURN(params.files, GetFilesFromArgs(test_args, *fs));
   return test_fn(params);
   return test_fn(params);
 }
 }

+ 2 - 2
testing/file_test/testdata/capture_console_output.carbon

@@ -8,9 +8,9 @@
 // TIP:   bazel test //testing/file_test:file_test_base_test --test_arg=--file_tests=testing/file_test/testdata/capture_console_output.carbon
 // TIP:   bazel test //testing/file_test:file_test_base_test --test_arg=--file_tests=testing/file_test/testdata/capture_console_output.carbon
 // TIP: To dump output, run:
 // TIP: To dump output, run:
 // TIP:   bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/capture_console_output.carbon
 // TIP:   bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/capture_console_output.carbon
-// CHECK:STDERR: params.stderr
+// CHECK:STDERR: params.error_stream
 // CHECK:STDERR: llvm::errs
 // CHECK:STDERR: llvm::errs
 
 
 // CHECK:STDOUT: 2 args: `default_args`, `capture_console_output.carbon`
 // CHECK:STDOUT: 2 args: `default_args`, `capture_console_output.carbon`
-// CHECK:STDOUT: params.stdout
+// CHECK:STDOUT: params.output_stream
 // CHECK:STDOUT: llvm::outs
 // CHECK:STDOUT: llvm::outs

+ 30 - 0
testing/file_test/testdata/stdin.carbon

@@ -0,0 +1,30 @@
+// 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
+
+// AUTOUPDATE
+// TIP: To test this file alone, run:
+// TIP:   bazel test //testing/file_test:file_test_base_test --test_arg=--file_tests=testing/file_test/testdata/stdin.carbon
+// TIP: To dump output, run:
+// TIP:   bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/stdin.carbon
+
+// --- a.carbon
+
+// --- STDIN
+
+S
+t
+d
+i
+n
+
+// --- AUTOUPDATE-SPLIT
+
+// CHECK:STDERR:
+// CHECK:STDERR: S
+// CHECK:STDERR: t
+// CHECK:STDERR: d
+// CHECK:STDERR: i
+// CHECK:STDERR: n
+// CHECK:STDERR:
+// CHECK:STDOUT: 2 args: `default_args`, `a.carbon`

+ 7 - 6
toolchain/testing/file_test.cpp

@@ -35,7 +35,8 @@ class ToolchainFileTest : public FileTestBase {
 
 
   auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
   auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
            llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
            llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
-           llvm::raw_pwrite_stream& stdout, llvm::raw_pwrite_stream& stderr)
+           FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
+           llvm::raw_pwrite_stream& error_stream)
       -> ErrorOr<RunResult> override {
       -> ErrorOr<RunResult> override {
     CARBON_ASSIGN_OR_RETURN(auto prelude, installation_.ReadPreludeManifest());
     CARBON_ASSIGN_OR_RETURN(auto prelude, installation_.ReadPreludeManifest());
     if (!is_no_prelude()) {
     if (!is_no_prelude()) {
@@ -46,16 +47,16 @@ class ToolchainFileTest : public FileTestBase {
 
 
     Driver driver({.fs = fs,
     Driver driver({.fs = fs,
                    .installation = &installation_,
                    .installation = &installation_,
-                   .input_stream = nullptr,
-                   .output_stream = &stdout,
-                   .error_stream = &stderr});
+                   .input_stream = input_stream,
+                   .output_stream = &output_stream,
+                   .error_stream = &error_stream});
     auto driver_result = driver.RunCommand(test_args);
     auto driver_result = driver.RunCommand(test_args);
     // If any diagnostics have been produced, add a trailing newline to make the
     // If any diagnostics have been produced, add a trailing newline to make the
     // last diagnostic match intermediate diagnostics (that have a newline
     // last diagnostic match intermediate diagnostics (that have a newline
     // separator between them). This reduces churn when adding new diagnostics
     // separator between them). This reduces churn when adding new diagnostics
     // to test cases.
     // to test cases.
-    if (stderr.tell() > 0) {
-      stderr << '\n';
+    if (error_stream.tell() > 0) {
+      error_stream << '\n';
     }
     }
 
 
     RunResult result{
     RunResult result{