Skip to content

Commit

Permalink
Add support for testing with stdin (#4819)
Browse files Browse the repository at this point in the history
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.
jonmeow authored Jan 21, 2025
1 parent 667a010 commit e393c76
Showing 9 changed files with 225 additions and 136 deletions.
10 changes: 6 additions & 4 deletions explorer/file_test.cpp
Original file line number Diff line number Diff line change
@@ -29,7 +29,8 @@ class ExplorerFileTest : public FileTestBase {

auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
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 {
// Add the prelude.
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> 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}};
}
56 changes: 32 additions & 24 deletions testing/file_test/README.md
Original file line number Diff line number Diff line change
@@ -50,9 +50,11 @@ class MyFileTest : public FileTestBase {
// Called as part of individual test executions.
auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
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 {
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: <arguments>
@@ -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.
- ```
// --- <filename>
```
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>
6 changes: 3 additions & 3 deletions testing/file_test/autoupdate.h
Original file line number Diff line number Diff line change
@@ -39,7 +39,7 @@ class FileTestAutoupdater {
const llvm::SmallVector<llvm::StringRef>& filenames,
int autoupdate_line_number, bool autoupdate_split,
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 llvm::SmallVector<LineNumberReplacement>& line_number_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)),
// 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; })),
63 changes: 41 additions & 22 deletions testing/file_test/file_test_base.cpp
Original file line number Diff line number Diff line change
@@ -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::string> {
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<Success> {
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<llvm::vfs::InMemoryFileSystem> 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<llvm::StringRef> 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<FileTestBase::TestFile>* test_files)
-> ErrorOr<Success> {
CARBON_RETURN_IF_ERROR(ReplaceContentKeywords(filename, content));

test_files->push_back(
{.filename = filename.str(), .content = std::move(*content)});
content->clear();
19 changes: 12 additions & 7 deletions testing/file_test/file_test_base.h
Original file line number Diff line number Diff line change
@@ -68,18 +68,23 @@ 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.
//
// The return value should be an error if there was an abnormal error, and
// RunResult otherwise.
virtual auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
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
// 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;

// 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};
};
160 changes: 92 additions & 68 deletions testing/file_test/file_test_base_test.cpp
Original file line number Diff line number Diff line change
@@ -23,7 +23,8 @@ class FileTestBaseTest : public FileTestBase {

auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
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;

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.
static auto PrintArgs(llvm::ArrayRef<llvm::StringRef> 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<llvm::StringRef> 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<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)
-> 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}};
}

// 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<FileTestBaseTest::RunResult> {
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<FileTestBaseTest::RunResult> {
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<FileTestBaseTest::RunResult> {
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<FileTestBaseTest::RunResult> {
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<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}};
}

// 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<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}};
}

// 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)
-> 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}};
}

// 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<llvm::StringRef>& test_args,
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();
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);
}
4 changes: 2 additions & 2 deletions testing/file_test/testdata/capture_console_output.carbon
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions testing/file_test/testdata/stdin.carbon
Original file line number Diff line number Diff line change
@@ -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`
13 changes: 7 additions & 6 deletions toolchain/testing/file_test.cpp
Original file line number Diff line number Diff line change
@@ -35,7 +35,8 @@ class ToolchainFileTest : public FileTestBase {

auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
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 {
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{

0 comments on commit e393c76

Please sign in to comment.