From e393c769af8375648cf5b608820b1b2d2148ecfd Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Tue, 21 Jan 2025 14:04:13 -0800 Subject: [PATCH] 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. --- explorer/file_test.cpp | 10 +- testing/file_test/README.md | 56 +++--- testing/file_test/autoupdate.h | 6 +- testing/file_test/file_test_base.cpp | 63 ++++--- testing/file_test/file_test_base.h | 19 ++- testing/file_test/file_test_base_test.cpp | 160 ++++++++++-------- .../testdata/capture_console_output.carbon | 4 +- testing/file_test/testdata/stdin.carbon | 30 ++++ toolchain/testing/file_test.cpp | 13 +- 9 files changed, 225 insertions(+), 136 deletions(-) create mode 100644 testing/file_test/testdata/stdin.carbon diff --git a/explorer/file_test.cpp b/explorer/file_test.cpp index 94d408dffe7b2..e9cae1aa58b4a 100644 --- a/explorer/file_test.cpp +++ b/explorer/file_test.cpp @@ -29,7 +29,8 @@ class ExplorerFileTest : public FileTestBase { auto Run(const llvm::SmallVector& test_args, llvm::IntrusiveRefCntPtr& 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 override { // Add the prelude. llvm::ErrorOr> prelude = @@ -52,9 +53,10 @@ class ExplorerFileTest : public FileTestBase { 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}}; } diff --git a/testing/file_test/README.md b/testing/file_test/README.md index fe9219c7fae47..ba44db73605d0 100644 --- a/testing/file_test/README.md +++ b/testing/file_test/README.md @@ -50,9 +50,11 @@ class MyFileTest : public FileTestBase { // Called as part of individual test executions. auto Run(const llvm::SmallVector& test_args, const llvm::SmallVector& 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 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. @@ -108,21 +110,22 @@ Supported comment markers are: // 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 - `// --- AUTOUPDATE-SPLIT`, all CHECKs will be added there; no line + `// --- AUTOUPDATE-SPLIT`, all `CHECK`s will be added there; no line associations occur. - ``` @@ -149,8 +152,8 @@ Supported comment markers are: Replaces some implementation-specific identifier with a value. (Mappings 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: @@ -173,28 +176,33 @@ Supported comment markers are: 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. - SET-CAPTURE-CONSOLE-OUTPUT can be specified at most once. + `SET-CAPTURE-CONSOLE-OUTPUT` can be specified at most once. - ``` // 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 - 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. - ``` // --- ``` 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: diff --git a/testing/file_test/autoupdate.h b/testing/file_test/autoupdate.h index 6b20581f58e0a..ea9d9917c9fd7 100644 --- a/testing/file_test/autoupdate.h +++ b/testing/file_test/autoupdate.h @@ -39,7 +39,7 @@ class FileTestAutoupdater { const llvm::SmallVector& filenames, int autoupdate_line_number, bool autoupdate_split, const llvm::SmallVector& non_check_lines, - llvm::StringRef stdout, llvm::StringRef stderr, + llvm::StringRef actual_stdout, llvm::StringRef actual_stderr, const std::optional& default_file_re, const llvm::SmallVector& line_number_replacements, std::function do_extra_check_replacements) @@ -56,8 +56,8 @@ class FileTestAutoupdater { do_extra_check_replacements_(std::move(do_extra_check_replacements)), // BuildCheckLines should only be called after other member // 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( stdout_.lines, [&](const CheckLine& line) { return line.line_number() != -1; })), diff --git a/testing/file_test/file_test_base.cpp b/testing/file_test/file_test_base.cpp index ce80f1d25c73c..fd5b7a23e09fc 100644 --- a/testing/file_test/file_test_base.cpp +++ b/testing/file_test/file_test_base.cpp @@ -58,6 +58,8 @@ using ::testing::Matcher; using ::testing::MatchesRegex; using ::testing::StrEq; +static constexpr llvm::StringLiteral StdinFilename = "STDIN"; + // Reads a file to string. static auto ReadFile(std::string_view path) -> ErrorOr { std::ifstream proto_file{std::string(path)}; @@ -186,15 +188,15 @@ auto FileTestBase::TestBody() -> void { } SCOPED_TRACE(update_message); if (context.check_subset) { - EXPECT_THAT(SplitOutput(context.stdout), + EXPECT_THAT(SplitOutput(context.actual_stdout), IsSupersetOf(context.expected_stdout)); - EXPECT_THAT(SplitOutput(context.stderr), + EXPECT_THAT(SplitOutput(context.actual_stderr), IsSupersetOf(context.expected_stderr)); } else { - EXPECT_THAT(SplitOutput(context.stdout), + EXPECT_THAT(SplitOutput(context.actual_stdout), ElementsAreArray(context.expected_stdout)); - EXPECT_THAT(SplitOutput(context.stderr), + EXPECT_THAT(SplitOutput(context.actual_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::Dump, test_name_), 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), [&](std::string& line) { DoExtraCheckReplacements(line); }) .Run(dry_run); @@ -266,7 +269,8 @@ auto FileTestBase::DumpOutput() -> ErrorOr { return ErrorBuilder() << "Error updating " << test_name_ << ": " << 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" << banner; return Success(); @@ -297,18 +301,32 @@ auto FileTestBase::ProcessTestFileAndRun(TestContext& context) CARBON_RETURN_IF_ERROR( 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. llvm::IntrusiveRefCntPtr fs = new llvm::vfs::InMemoryFileSystem; 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; } } - // Convert the arguments to StringRef and const char* to match the // expectations of PrettyStackTraceProgram and Run. llvm::SmallVector test_args_ref; @@ -340,20 +358,21 @@ auto FileTestBase::ProcessTestFileAndRun(TestContext& context) if (context.capture_console_output) { // No need to flush stderr. 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 // constraints, we split calls to Run as a ternary based on whether we want to // 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( 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(); } @@ -380,10 +399,11 @@ auto FileTestBase::DoArgReplacements( it = test_args.erase(it); for (const auto& file : test_files) { 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. --it; @@ -546,7 +566,6 @@ static auto AddTestFile(llvm::StringRef filename, std::string* content, llvm::SmallVector* test_files) -> ErrorOr { CARBON_RETURN_IF_ERROR(ReplaceContentKeywords(filename, content)); - test_files->push_back( {.filename = filename.str(), .content = std::move(*content)}); content->clear(); diff --git a/testing/file_test/file_test_base.h b/testing/file_test/file_test_base.h index 2419bd70c4f44..115a703f77937 100644 --- a/testing/file_test/file_test_base.h +++ b/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) : 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. // @@ -78,8 +82,9 @@ class FileTestBase : public testing::Test { // RunResult otherwise. virtual auto Run(const llvm::SmallVector& test_args, llvm::IntrusiveRefCntPtr& fs, - llvm::raw_pwrite_stream& stdout, - llvm::raw_pwrite_stream& stderr) -> ErrorOr = 0; + FILE* input_stream, llvm::raw_pwrite_stream& output_stream, + llvm::raw_pwrite_stream& error_stream) + -> ErrorOr = 0; // 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 @@ -174,8 +179,8 @@ class FileTestBase : public testing::Test { llvm::SmallVector> expected_stderr; // 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}; }; diff --git a/testing/file_test/file_test_base_test.cpp b/testing/file_test/file_test_base_test.cpp index 2660ddb6c534a..aeedb6880f656 100644 --- a/testing/file_test/file_test_base_test.cpp +++ b/testing/file_test/file_test_base_test.cpp @@ -23,7 +23,8 @@ class FileTestBaseTest : public FileTestBase { auto Run(const llvm::SmallVector& test_args, llvm::IntrusiveRefCntPtr& 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 override; auto GetArgReplacements() -> llvm::StringMap override { @@ -55,13 +56,13 @@ class FileTestBaseTest : public FileTestBase { // Prints arguments so that they can be validated in tests. static auto PrintArgs(llvm::ArrayRef args, - llvm::raw_pwrite_stream& stdout) -> void { + llvm::raw_pwrite_stream& output_stream) -> void { llvm::ListSeparator sep; - stdout << args.size() << " args: "; + output_stream << args.size() << " 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. @@ -85,113 +86,131 @@ static auto GetFilesFromArgs(llvm::ArrayRef args, struct TestParams { // These are the arguments to `Run()`. 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. llvm::ArrayRef 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) -> ErrorOr { - 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}}; } -// 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) -> ErrorOr { llvm::errs() << "llvm::errs\n"; - params.stderr << "params.stderr\n"; + params.error_stream << "params.error_stream\n"; llvm::outs() << "llvm::outs\n"; - params.stdout << "params.stdout\n"; + params.output_stream << "params.output_stream\n"; 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) -> ErrorOr { 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}}; } -// Does printing and returns expected results for fail_example.carbon. +// Prints and returns expected results for fail_example.carbon. static auto TestFailExample(TestParams& params) -> ErrorOr { - params.stderr << "Oops\n"; + params.error_stream << "Oops\n"; return {{.success = false}}; } -// Does printing and returns expected results for +// Prints and returns expected results for // file_only_re_multi_file.carbon. static auto TestFileOnlyREMultiFile(TestParams& params) -> ErrorOr { 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}}; } -// 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) -> ErrorOr { - 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}}; } -// 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) -> ErrorOr { - 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}}; } -// 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 { + 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) -> ErrorOr { - 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}}; } -// Does printing and returns expected results for: +// Prints and returns expected results for: // - fail_multi_success_overall_fail.carbon // - multi_success.carbon // - multi_success_and_fail.carbon @@ -220,8 +239,8 @@ static auto EchoFileContent(TestParams& params) for (int line_number = 1; !buffer.empty(); ++line_number) { auto [line, remainder] = buffer.split('\n'); 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; } @@ -232,9 +251,9 @@ static auto EchoFileContent(TestParams& params) auto FileTestBaseTest::Run( const llvm::SmallVector& test_args, llvm::IntrusiveRefCntPtr& fs, - llvm::raw_pwrite_stream& stdout, llvm::raw_pwrite_stream& stderr) - -> ErrorOr { - PrintArgs(test_args, stdout); + FILE* input_stream, llvm::raw_pwrite_stream& output_stream, + llvm::raw_pwrite_stream& error_stream) -> ErrorOr { + PrintArgs(test_args, output_stream); auto filename = std::filesystem::path(test_name().str()).filename(); if (filename == "args.carbon") { @@ -254,6 +273,8 @@ auto FileTestBaseTest::Run( .Case("file_only_re_one_file.carbon", &TestFileOnlyREOneFile) .Case("file_only_re_multi_file.carbon", &TestFileOnlyREMultiFile) .Case("no_line_number.carbon", &TestNoLineNumber) + .Case("stdin.carbon", &TestStdin) + .Case("stdin_and_autoupdate_split.carbon", &TestStdin) .Case("unattached_multi_file.carbon", &TestUnattachedMultiFile) .Case("fail_multi_success_overall_fail.carbon", [&](TestParams&) { @@ -273,7 +294,10 @@ auto FileTestBaseTest::Run( .Default(&EchoFileContent); // 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)); return test_fn(params); } diff --git a/testing/file_test/testdata/capture_console_output.carbon b/testing/file_test/testdata/capture_console_output.carbon index 4b9687a0edabb..8f9d61229533c 100644 --- a/testing/file_test/testdata/capture_console_output.carbon +++ b/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: 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 -// CHECK:STDERR: params.stderr +// CHECK:STDERR: params.error_stream // CHECK:STDERR: llvm::errs // CHECK:STDOUT: 2 args: `default_args`, `capture_console_output.carbon` -// CHECK:STDOUT: params.stdout +// CHECK:STDOUT: params.output_stream // CHECK:STDOUT: llvm::outs diff --git a/testing/file_test/testdata/stdin.carbon b/testing/file_test/testdata/stdin.carbon new file mode 100644 index 0000000000000..a2a8cc0ef1869 --- /dev/null +++ b/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` diff --git a/toolchain/testing/file_test.cpp b/toolchain/testing/file_test.cpp index 882f535cac0ea..f9fd833ec230e 100644 --- a/toolchain/testing/file_test.cpp +++ b/toolchain/testing/file_test.cpp @@ -35,7 +35,8 @@ class ToolchainFileTest : public FileTestBase { auto Run(const llvm::SmallVector& test_args, llvm::IntrusiveRefCntPtr& 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 override { CARBON_ASSIGN_OR_RETURN(auto prelude, installation_.ReadPreludeManifest()); if (!is_no_prelude()) { @@ -46,16 +47,16 @@ class ToolchainFileTest : public FileTestBase { Driver driver({.fs = fs, .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); // If any diagnostics have been produced, add a trailing newline to make the // last diagnostic match intermediate diagnostics (that have a newline // separator between them). This reduces churn when adding new diagnostics // to test cases. - if (stderr.tell() > 0) { - stderr << '\n'; + if (error_stream.tell() > 0) { + error_stream << '\n'; } RunResult result{