mirror of
https://github.com/carbon-language/carbon-lang.git
synced 2026-09-24 21:50:14 +01:00
Rearrange the file_test infrastructure so that we can customize the mapping of file names to command line arguments. Map `module.modulemap` files to corresponding Clang driver flags. In passing, also clean up the interface for specifying custom argument replacements so that we don't build a string map for each file we process, and stop using `SmallVector::insert`. Assisted-by: Gemini via Antigravity
335 lines
13 KiB
C++
335 lines
13 KiB
C++
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
|
|
// Exceptions. See /LICENSE for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
|
|
#include "testing/file_test/file_test_base.h"
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
#include "common/ostream.h"
|
|
#include "llvm/ADT/StringExtras.h"
|
|
#include "llvm/ADT/StringSwitch.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
|
|
namespace Carbon::Testing {
|
|
namespace {
|
|
|
|
class FileTestBaseTest : public FileTestBase {
|
|
public:
|
|
FileTestBaseTest(llvm::StringRef /*exe_path*/, llvm::StringRef test_name)
|
|
: FileTestBase(test_name) {}
|
|
|
|
auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
|
|
FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
|
|
llvm::raw_pwrite_stream& error_stream) const
|
|
-> ErrorOr<RunResult> override;
|
|
|
|
auto GetDefaultArgs() const -> llvm::SmallVector<std::string> override {
|
|
return {"default_args", "%s"};
|
|
}
|
|
|
|
auto AddArgsForFilename(llvm::SmallVectorImpl<std::string>& args,
|
|
llvm::StringRef filename) const -> void override {
|
|
if (!filename.ends_with(".exclude")) {
|
|
args.emplace_back(filename);
|
|
}
|
|
}
|
|
|
|
auto GetArgReplacement(llvm::StringRef key) const
|
|
-> std::optional<std::string> override {
|
|
if (key == "replacement") {
|
|
return "replaced";
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto GetDefaultFileRE(llvm::ArrayRef<llvm::StringRef> filenames) const
|
|
-> std::optional<RE2> override {
|
|
return std::make_optional<RE2>(
|
|
llvm::formatv(R"(file: ({0}))", llvm::join(filenames, "|")));
|
|
}
|
|
|
|
auto GetLineNumberReplacements(llvm::ArrayRef<llvm::StringRef> filenames)
|
|
const -> llvm::SmallVector<LineNumberReplacement> override {
|
|
auto replacements = FileTestBase::GetLineNumberReplacements(filenames);
|
|
auto filename = std::filesystem::path(test_name().str()).filename();
|
|
if (llvm::StringRef(filename).starts_with("file_only_re_")) {
|
|
replacements.push_back({.has_file = false,
|
|
.re = std::make_shared<RE2>(R"(line: (\d+))"),
|
|
.line_formatv = "{0}"});
|
|
}
|
|
return replacements;
|
|
}
|
|
};
|
|
|
|
// Prints arguments so that they can be validated in tests.
|
|
static auto PrintArgs(llvm::ArrayRef<llvm::StringRef> args,
|
|
llvm::raw_pwrite_stream& output_stream) -> void {
|
|
llvm::ListSeparator sep;
|
|
output_stream << args.size() << " args: ";
|
|
for (auto arg : args) {
|
|
output_stream << sep << "`" << arg << "`";
|
|
}
|
|
output_stream << "\n";
|
|
}
|
|
|
|
// Verifies arguments are well-structured, and returns the files in them.
|
|
static auto GetFilesFromArgs(llvm::ArrayRef<llvm::StringRef> args,
|
|
llvm::vfs::InMemoryFileSystem& fs)
|
|
-> ErrorOr<llvm::ArrayRef<llvm::StringRef>> {
|
|
if (args.empty() || args.front() != "default_args") {
|
|
return ErrorBuilder() << "missing `default_args` argument";
|
|
}
|
|
args.consume_front();
|
|
|
|
for (auto arg : args) {
|
|
if (!fs.exists(arg)) {
|
|
return ErrorBuilder() << "Missing file: " << arg;
|
|
}
|
|
}
|
|
return args;
|
|
}
|
|
|
|
// Parameters used to by individual test handlers, for easy value forwarding.
|
|
struct TestParams {
|
|
// These are the arguments to `Run()`.
|
|
llvm::vfs::InMemoryFileSystem& fs;
|
|
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;
|
|
};
|
|
|
|
// Prints and returns expected results for alternating_files.carbon.
|
|
static auto TestAlternatingFiles(TestParams& params)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
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}};
|
|
}
|
|
|
|
// Prints and returns expected results for capture_console_output.carbon.
|
|
static auto TestCaptureConsoleOutput(TestParams& params)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
llvm::errs() << "llvm::errs\n";
|
|
params.error_stream << "params.error_stream\n";
|
|
llvm::outs() << "llvm::outs\n";
|
|
params.output_stream << "params.output_stream\n";
|
|
return {{.success = true}};
|
|
}
|
|
|
|
// Prints and returns expected results for escaping.carbon.
|
|
static auto TestEscaping(TestParams& params)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
params.error_stream << "carriage return\r\n"
|
|
"{one brace}\n"
|
|
"{{two braces}}\n"
|
|
"[one bracket]\n"
|
|
"[[two brackets]]\n"
|
|
"end of line whitespace \n"
|
|
"\ttabs\t\n";
|
|
return {{.success = true}};
|
|
}
|
|
|
|
// Prints and returns expected results for example.carbon.
|
|
static auto TestExample(TestParams& params)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
int delta_line = 10;
|
|
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}};
|
|
}
|
|
|
|
// Prints and returns expected results for fail_example.carbon.
|
|
static auto TestFailExample(TestParams& params)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
params.error_stream << "Oops\n";
|
|
return {{.success = false}};
|
|
}
|
|
|
|
// 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.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}};
|
|
}
|
|
|
|
// Prints and returns expected results for file_only_re_one_file.carbon.
|
|
static auto TestFileOnlyREOneFile(TestParams& params)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
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}};
|
|
}
|
|
|
|
// Prints and returns expected results for no_line_number.carbon.
|
|
static auto TestNoLineNumber(TestParams& params)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
params.output_stream << "a.carbon: msg1\n"
|
|
"msg2\n"
|
|
"b.carbon: msg3\n"
|
|
"msg4\n"
|
|
"a.carbon: msg5\n";
|
|
return {{.success = true}};
|
|
}
|
|
|
|
// Prints and returns expected results for unattached_multi_file.carbon.
|
|
static auto TestUnattachedMultiFile(TestParams& params)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
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}};
|
|
}
|
|
|
|
// Prints and returns expected results for:
|
|
// - fail_multi_success_overall_fail.carbon
|
|
// - multi_success.carbon
|
|
// - multi_success_and_fail.carbon
|
|
//
|
|
// Parameters indicate overall and per-file success.
|
|
static auto HandleMultiSuccessTests(bool overall, bool a, bool b)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
FileTestBaseTest::RunResult result = {.success = overall};
|
|
result.per_file_success.push_back({a ? "a.carbon" : "fail_a.carbon", a});
|
|
result.per_file_success.push_back({b ? "b.carbon" : "fail_b.carbon", b});
|
|
return result;
|
|
}
|
|
|
|
// Echoes back non-comment file content. Used for default file handling.
|
|
static auto EchoFileContent(TestParams& params)
|
|
-> ErrorOr<FileTestBaseTest::RunResult> {
|
|
// By default, echo non-comment content of files back.
|
|
for (auto test_file : params.files) {
|
|
// Describe file contents to stdout to validate splitting.
|
|
auto file = params.fs.getBufferForFile(test_file, /*FileSize=*/-1,
|
|
/*RequiresNullTerminator=*/false);
|
|
if (file.getError()) {
|
|
return Error(file.getError().message());
|
|
}
|
|
llvm::StringRef buffer = file.get()->getBuffer();
|
|
for (int line_number = 1; !buffer.empty(); ++line_number) {
|
|
auto [line, remainder] = buffer.split('\n');
|
|
if (!line.empty() && !line.starts_with("//")) {
|
|
params.output_stream << test_file << ":" << line_number << ": " << line
|
|
<< "\n";
|
|
}
|
|
buffer = remainder;
|
|
}
|
|
}
|
|
if (params.input_stream) {
|
|
params.error_stream << "--- STDIN:\n";
|
|
constexpr int ReadSize = 1024;
|
|
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}};
|
|
}
|
|
|
|
auto FileTestBaseTest::Run(
|
|
const llvm::SmallVector<llvm::StringRef>& test_args,
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
|
|
FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
|
|
llvm::raw_pwrite_stream& error_stream) const -> ErrorOr<RunResult> {
|
|
PrintArgs(test_args, output_stream);
|
|
|
|
auto filename = std::filesystem::path(test_name().str()).filename();
|
|
if (filename == "args.carbon" || filename == "include_args.carbon" ||
|
|
filename == "include_extra_args.carbon" ||
|
|
filename == "include_args_and_extra_args.carbon") {
|
|
// These files are testing argument behavior, which doesn't work with the
|
|
// default test logic.
|
|
return {{.success = true}};
|
|
}
|
|
|
|
// Choose the test function based on filename.
|
|
auto test_fn =
|
|
llvm::StringSwitch<std::function<auto(TestParams&)->ErrorOr<RunResult>>>(
|
|
filename.string())
|
|
.Case("alternating_files.carbon", &TestAlternatingFiles)
|
|
.Case("capture_console_output.carbon", &TestCaptureConsoleOutput)
|
|
.Case("escaping.carbon", &TestEscaping)
|
|
.Case("example.carbon", &TestExample)
|
|
.Case("fail_example.carbon", &TestFailExample)
|
|
.Case("file_only_re_one_file.carbon", &TestFileOnlyREOneFile)
|
|
.Case("file_only_re_multi_file.carbon", &TestFileOnlyREMultiFile)
|
|
.Case("no_line_number.carbon", &TestNoLineNumber)
|
|
.Case("unattached_multi_file.carbon", &TestUnattachedMultiFile)
|
|
.Case("fail_multi_success_overall_fail.carbon",
|
|
[&](TestParams&) {
|
|
return HandleMultiSuccessTests(/*overall=*/false, /*a=*/true,
|
|
/*b=*/true);
|
|
})
|
|
.Case("multi_success.carbon",
|
|
[&](TestParams&) {
|
|
return HandleMultiSuccessTests(/*overall=*/true, /*a=*/true,
|
|
/*b=*/true);
|
|
})
|
|
.Case("multi_success_and_fail.carbon",
|
|
[&](TestParams&) {
|
|
return HandleMultiSuccessTests(/*overall=*/false, /*a=*/true,
|
|
/*b=*/false);
|
|
})
|
|
.Default(&EchoFileContent);
|
|
|
|
// Call the appropriate test function for the file.
|
|
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);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CARBON_FILE_TEST_FACTORY(FileTestBaseTest)
|
|
|
|
} // namespace Carbon::Testing
|