Skip to content

Commit ad49647

Browse files
authored
Refactor ToolchainFileTest functions out-of-line (#4839)
I'm going to be adding more, and it's large already. Also adding API comments.
1 parent 58fba07 commit ad49647

File tree

1 file changed

+168
-133
lines changed

1 file changed

+168
-133
lines changed

toolchain/testing/file_test.cpp

Lines changed: 168 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -24,168 +24,203 @@ namespace {
2424
class ToolchainFileTest : public FileTestBase {
2525
public:
2626
explicit ToolchainFileTest(llvm::StringRef exe_path, std::mutex* output_mutex,
27-
llvm::StringRef test_name)
28-
: FileTestBase(output_mutex, test_name),
29-
component_(GetComponent(test_name)),
30-
installation_(InstallPaths::MakeForBazelRunfiles(exe_path)) {}
27+
llvm::StringRef test_name);
3128

32-
auto GetArgReplacements() -> llvm::StringMap<std::string> override {
33-
return {{"core_package_dir", installation_.core_package()}};
34-
}
29+
// Adds a replacement for `core_package_dir`.
30+
auto GetArgReplacements() -> llvm::StringMap<std::string> override;
3531

32+
// Loads files into the VFS and runs the driver.
3633
auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
3734
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
3835
FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
3936
llvm::raw_pwrite_stream& error_stream)
40-
-> ErrorOr<RunResult> override {
41-
CARBON_ASSIGN_OR_RETURN(auto prelude, installation_.ReadPreludeManifest());
42-
if (!is_no_prelude()) {
43-
for (const auto& file : prelude) {
44-
CARBON_RETURN_IF_ERROR(AddFile(*fs, file));
45-
}
46-
}
47-
48-
Driver driver({.fs = fs,
49-
.installation = &installation_,
50-
.input_stream = input_stream,
51-
.output_stream = &output_stream,
52-
.error_stream = &error_stream});
53-
auto driver_result = driver.RunCommand(test_args);
54-
// If any diagnostics have been produced, add a trailing newline to make the
55-
// last diagnostic match intermediate diagnostics (that have a newline
56-
// separator between them). This reduces churn when adding new diagnostics
57-
// to test cases.
58-
if (error_stream.tell() > 0) {
59-
error_stream << '\n';
60-
}
61-
62-
RunResult result{
63-
.success = driver_result.success,
64-
.per_file_success = std::move(driver_result.per_file_success)};
65-
// Drop entries that don't look like a file, and entries corresponding to
66-
// the prelude. Note this can empty out the list.
67-
llvm::erase_if(result.per_file_success,
68-
[&](std::pair<llvm::StringRef, bool> entry) {
69-
return entry.first == "." || entry.first == "-" ||
70-
entry.first.starts_with("not_file") ||
71-
llvm::is_contained(prelude, entry.first);
72-
});
73-
return result;
74-
}
37+
-> ErrorOr<RunResult> override;
7538

76-
auto GetDefaultArgs() -> llvm::SmallVector<std::string> override {
77-
if (component_ == "format") {
78-
return {"format", "%s"};
79-
}
80-
81-
llvm::SmallVector<std::string> args = {
82-
"compile", "--include-diagnostic-kind", "--phase=" + component_.str()};
83-
84-
if (component_ == "lex") {
85-
args.insert(args.end(), {"--dump-tokens", "--omit-file-boundary-tokens"});
86-
} else if (component_ == "parse") {
87-
args.push_back("--dump-parse-tree");
88-
} else if (component_ == "check") {
89-
args.push_back("--dump-sem-ir");
90-
} else if (component_ == "lower") {
91-
args.push_back("--dump-llvm-ir");
92-
} else {
93-
CARBON_FATAL("Unexpected test component {0}: {1}", component_,
94-
test_name());
95-
}
96-
97-
// For `lex` and `parse`, we don't need to import the prelude; exclude it to
98-
// focus errors. In other phases we only do this for explicit "no_prelude"
99-
// tests.
100-
if (component_ == "lex" || component_ == "parse" || is_no_prelude()) {
101-
args.push_back("--no-prelude-import");
102-
}
103-
104-
args.insert(
105-
args.end(),
106-
{"--exclude-dump-file-prefix=" + installation_.core_package(), "%s"});
107-
return args;
108-
}
39+
// Sets different default flags based on the component being tested.
40+
auto GetDefaultArgs() -> llvm::SmallVector<std::string> override;
10941

42+
// Generally uses the parent implementation, with special handling for lex.
11043
auto GetDefaultFileRE(llvm::ArrayRef<llvm::StringRef> filenames)
111-
-> std::optional<RE2> override {
112-
if (component_ == "lex") {
113-
return std::make_optional<RE2>(
114-
llvm::formatv(R"(^- filename: ({0})$)", llvm::join(filenames, "|")));
115-
}
116-
return FileTestBase::GetDefaultFileRE(filenames);
117-
}
44+
-> std::optional<RE2> override;
11845

46+
// Generally uses the parent implementation, with special handling for lex.
11947
auto GetLineNumberReplacements(llvm::ArrayRef<llvm::StringRef> filenames)
120-
-> llvm::SmallVector<LineNumberReplacement> override {
121-
auto replacements = FileTestBase::GetLineNumberReplacements(filenames);
122-
if (component_ == "lex") {
123-
replacements.push_back({.has_file = false,
124-
.re = std::make_shared<RE2>(R"(line: (\s*\d+))"),
125-
// The `{{{{` becomes `{{`.
126-
.line_formatv = "{{{{ *}}{0}"});
127-
}
128-
return replacements;
129-
}
48+
-> llvm::SmallVector<LineNumberReplacement> override;
13049

131-
auto DoExtraCheckReplacements(std::string& check_line) -> void override {
132-
if (component_ == "driver") {
133-
// TODO: Disable token output, it's not interesting for these tests.
134-
if (llvm::StringRef(check_line).starts_with("// CHECK:STDOUT: {")) {
135-
check_line = "// CHECK:STDOUT: {{.*}}";
136-
}
137-
} else if (component_ == "lex") {
138-
// Both FileStart and FileEnd regularly have locations on CHECK
139-
// comment lines that don't work correctly. The line happens to be correct
140-
// for the FileEnd, but we need to avoid checking the column.
141-
// The column happens to be right for FileStart, but the line is wrong.
142-
static RE2 file_token_re(
143-
R"((FileEnd.*column: |FileStart.*line: )( *\d+))");
144-
RE2::Replace(&check_line, file_token_re, R"(\1{{ *\\d+}})");
145-
} else {
146-
FileTestBase::DoExtraCheckReplacements(check_line);
147-
}
148-
}
50+
// Generally uses the parent implementation, with special handling for lex and
51+
// driver.
52+
auto DoExtraCheckReplacements(std::string& check_line) -> void override;
14953

15054
private:
15155
// Adds a file to the fs.
15256
auto AddFile(llvm::vfs::InMemoryFileSystem& fs, llvm::StringRef path)
153-
-> ErrorOr<Success> {
154-
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> file =
155-
llvm::MemoryBuffer::getFile(path);
156-
if (file.getError()) {
157-
return ErrorBuilder()
158-
<< "Getting `" << path << "`: " << file.getError().message();
159-
}
160-
if (!fs.addFile(path, /*ModificationTime=*/0, std::move(*file))) {
161-
return ErrorBuilder() << "Duplicate file: `" << path << "`";
162-
}
163-
return Success();
164-
}
165-
166-
// Returns the toolchain subdirectory being tested.
167-
static auto GetComponent(llvm::StringRef test_name) -> llvm::StringRef {
168-
// This handles cases where the toolchain directory may be copied into a
169-
// repository that doesn't put it at the root.
170-
auto pos = test_name.find("toolchain/");
171-
CARBON_CHECK(pos != llvm::StringRef::npos, "{0}", test_name);
172-
test_name = test_name.drop_front(pos + strlen("toolchain/"));
173-
test_name = test_name.take_front(test_name.find("/"));
174-
return test_name;
175-
}
57+
-> ErrorOr<Success>;
17658

59+
// Controls whether `Run()` includes the prelude.
17760
auto is_no_prelude() const -> bool {
17861
return test_name().find("/no_prelude/") != llvm::StringRef::npos;
17962
}
18063

64+
// The toolchain component subdirectory, such as `lex` or `language_server`.
18165
const llvm::StringRef component_;
66+
// The toolchain install information.
18267
const InstallPaths installation_;
18368
};
18469

18570
} // namespace
18671

18772
CARBON_FILE_TEST_FACTORY(ToolchainFileTest)
18873

74+
// Returns the toolchain subdirectory being tested.
75+
static auto GetComponent(llvm::StringRef test_name) -> llvm::StringRef {
76+
// This handles cases where the toolchain directory may be copied into a
77+
// repository that doesn't put it at the root.
78+
auto pos = test_name.find("toolchain/");
79+
CARBON_CHECK(pos != llvm::StringRef::npos, "{0}", test_name);
80+
test_name = test_name.drop_front(pos + strlen("toolchain/"));
81+
test_name = test_name.take_front(test_name.find("/"));
82+
return test_name;
83+
}
84+
85+
ToolchainFileTest::ToolchainFileTest(llvm::StringRef exe_path,
86+
std::mutex* output_mutex,
87+
llvm::StringRef test_name)
88+
: FileTestBase(output_mutex, test_name),
89+
component_(GetComponent(test_name)),
90+
installation_(InstallPaths::MakeForBazelRunfiles(exe_path)) {}
91+
92+
auto ToolchainFileTest::GetArgReplacements() -> llvm::StringMap<std::string> {
93+
return {{"core_package_dir", installation_.core_package()}};
94+
}
95+
96+
auto ToolchainFileTest::Run(
97+
const llvm::SmallVector<llvm::StringRef>& test_args,
98+
llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
99+
FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
100+
llvm::raw_pwrite_stream& error_stream) -> ErrorOr<RunResult> {
101+
CARBON_ASSIGN_OR_RETURN(auto prelude, installation_.ReadPreludeManifest());
102+
if (!is_no_prelude()) {
103+
for (const auto& file : prelude) {
104+
CARBON_RETURN_IF_ERROR(AddFile(*fs, file));
105+
}
106+
}
107+
108+
Driver driver({.fs = fs,
109+
.installation = &installation_,
110+
.input_stream = input_stream,
111+
.output_stream = &output_stream,
112+
.error_stream = &error_stream});
113+
auto driver_result = driver.RunCommand(test_args);
114+
// If any diagnostics have been produced, add a trailing newline to make the
115+
// last diagnostic match intermediate diagnostics (that have a newline
116+
// separator between them). This reduces churn when adding new diagnostics
117+
// to test cases.
118+
if (error_stream.tell() > 0) {
119+
error_stream << '\n';
120+
}
121+
122+
RunResult result{
123+
.success = driver_result.success,
124+
.per_file_success = std::move(driver_result.per_file_success)};
125+
// Drop entries that don't look like a file, and entries corresponding to
126+
// the prelude. Note this can empty out the list.
127+
llvm::erase_if(result.per_file_success,
128+
[&](std::pair<llvm::StringRef, bool> entry) {
129+
return entry.first == "." || entry.first == "-" ||
130+
entry.first.starts_with("not_file") ||
131+
llvm::is_contained(prelude, entry.first);
132+
});
133+
return result;
134+
}
135+
136+
auto ToolchainFileTest::GetDefaultArgs() -> llvm::SmallVector<std::string> {
137+
if (component_ == "format") {
138+
return {"format", "%s"};
139+
}
140+
141+
llvm::SmallVector<std::string> args = {"compile", "--include-diagnostic-kind",
142+
"--phase=" + component_.str()};
143+
144+
if (component_ == "lex") {
145+
args.insert(args.end(), {"--dump-tokens", "--omit-file-boundary-tokens"});
146+
} else if (component_ == "parse") {
147+
args.push_back("--dump-parse-tree");
148+
} else if (component_ == "check") {
149+
args.push_back("--dump-sem-ir");
150+
} else if (component_ == "lower") {
151+
args.push_back("--dump-llvm-ir");
152+
} else {
153+
CARBON_FATAL("Unexpected test component {0}: {1}", component_, test_name());
154+
}
155+
156+
// For `lex` and `parse`, we don't need to import the prelude; exclude it to
157+
// focus errors. In other phases we only do this for explicit "no_prelude"
158+
// tests.
159+
if (component_ == "lex" || component_ == "parse" || is_no_prelude()) {
160+
args.push_back("--no-prelude-import");
161+
}
162+
163+
args.insert(
164+
args.end(),
165+
{"--exclude-dump-file-prefix=" + installation_.core_package(), "%s"});
166+
return args;
167+
}
168+
169+
auto ToolchainFileTest::GetDefaultFileRE(
170+
llvm::ArrayRef<llvm::StringRef> filenames) -> std::optional<RE2> {
171+
if (component_ == "lex") {
172+
return std::make_optional<RE2>(
173+
llvm::formatv(R"(^- filename: ({0})$)", llvm::join(filenames, "|")));
174+
}
175+
return FileTestBase::GetDefaultFileRE(filenames);
176+
}
177+
178+
auto ToolchainFileTest::GetLineNumberReplacements(
179+
llvm::ArrayRef<llvm::StringRef> filenames)
180+
-> llvm::SmallVector<LineNumberReplacement> {
181+
auto replacements = FileTestBase::GetLineNumberReplacements(filenames);
182+
if (component_ == "lex") {
183+
replacements.push_back({.has_file = false,
184+
.re = std::make_shared<RE2>(R"(line: (\s*\d+))"),
185+
// The `{{{{` becomes `{{`.
186+
.line_formatv = "{{{{ *}}{0}"});
187+
}
188+
return replacements;
189+
}
190+
191+
auto ToolchainFileTest::DoExtraCheckReplacements(std::string& check_line)
192+
-> void {
193+
if (component_ == "driver") {
194+
// TODO: Disable token output, it's not interesting for these tests.
195+
if (llvm::StringRef(check_line).starts_with("// CHECK:STDOUT: {")) {
196+
check_line = "// CHECK:STDOUT: {{.*}}";
197+
}
198+
} else if (component_ == "lex") {
199+
// Both FileStart and FileEnd regularly have locations on CHECK
200+
// comment lines that don't work correctly. The line happens to be correct
201+
// for the FileEnd, but we need to avoid checking the column.
202+
// The column happens to be right for FileStart, but the line is wrong.
203+
static RE2 file_token_re(R"((FileEnd.*column: |FileStart.*line: )( *\d+))");
204+
RE2::Replace(&check_line, file_token_re, R"(\1{{ *\\d+}})");
205+
} else {
206+
FileTestBase::DoExtraCheckReplacements(check_line);
207+
}
208+
}
209+
210+
auto ToolchainFileTest::AddFile(llvm::vfs::InMemoryFileSystem& fs,
211+
llvm::StringRef path) -> ErrorOr<Success> {
212+
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> file =
213+
llvm::MemoryBuffer::getFile(path);
214+
if (file.getError()) {
215+
return ErrorBuilder() << "Getting `" << path
216+
<< "`: " << file.getError().message();
217+
}
218+
if (!fs.addFile(path, /*ModificationTime=*/0, std::move(*file))) {
219+
return ErrorBuilder() << "Duplicate file: `" << path << "`";
220+
}
221+
return Success();
222+
}
223+
189224
} // namespace Carbon::Testing
190225

191226
#endif // CARBON_TOOLCHAIN_DRIVER_DRIVER_FILE_TEST_BASE_H_

0 commit comments

Comments
 (0)