From 51b4abec206ae5f88f5a76db22c828565a8c8a1a Mon Sep 17 00:00:00 2001 From: Jon Ross-Perkins Date: Thu, 5 Jun 2025 09:23:15 -0700 Subject: [PATCH] Allow args, includes, and splits in file_test includes (#5610) This builds a mechanism for the toolchain to construct more complex min_prelude files, which in turn should allow the toolchain to stop special-casing the min_prelude directory. For example, instead of: ``` args.insert(args.end(), {"--custom-core", "--exclude-dump-file-prefix=include_files/"}); ``` This should allow (in a `min_prelude` file): ``` // EXTRA-ARGS: --custom-core --exclude-dump-file-prefix=include_files/ ``` Then when that min_prelude is included, it'd be used. But also, this should allow sharing between min_prelude files with use of `INCLUDE-FILE`, which as we make progressively more complex min_preludes might become useful. --- testing/file_test/BUILD | 6 +- testing/file_test/README.md | 18 +- testing/file_test/file_test_base_test.cpp | 34 +- testing/file_test/run_test.cpp | 2 +- testing/file_test/test_file.cpp | 346 +++++++++++------- ...nclude_file.carbon => include_args.carbon} | 11 +- .../include_args_and_extra_args.carbon | 14 + .../file_test/testdata/include_empty.carbon | 13 + .../testdata/include_extra_args.carbon | 14 + .../testdata/include_files/args.carbon | 5 + .../testdata/include_files/empty.carbon | 5 + .../testdata/include_files/extra_args.carbon | 5 + .../testdata/include_files/no_split.carbon | 5 + .../testdata/include_files/recursive.carbon | 13 + .../testdata/include_files/split.carbon | 11 + .../testdata/include_no_split.carbon | 14 + .../testdata/include_recursive.carbon | 17 + .../file_test/testdata/include_split.carbon | 15 + 18 files changed, 396 insertions(+), 152 deletions(-) rename testing/file_test/testdata/{include_file.carbon => include_args.carbon} (57%) create mode 100644 testing/file_test/testdata/include_args_and_extra_args.carbon create mode 100644 testing/file_test/testdata/include_empty.carbon create mode 100644 testing/file_test/testdata/include_extra_args.carbon create mode 100644 testing/file_test/testdata/include_files/args.carbon create mode 100644 testing/file_test/testdata/include_files/empty.carbon create mode 100644 testing/file_test/testdata/include_files/extra_args.carbon create mode 100644 testing/file_test/testdata/include_files/no_split.carbon create mode 100644 testing/file_test/testdata/include_files/recursive.carbon create mode 100644 testing/file_test/testdata/include_files/split.carbon create mode 100644 testing/file_test/testdata/include_no_split.carbon create mode 100644 testing/file_test/testdata/include_recursive.carbon create mode 100644 testing/file_test/testdata/include_split.carbon diff --git a/testing/file_test/BUILD b/testing/file_test/BUILD index dc775e2b2215..caa4f704ee87 100644 --- a/testing/file_test/BUILD +++ b/testing/file_test/BUILD @@ -62,7 +62,11 @@ file_test( name = "file_test_base_test", size = "small", srcs = ["file_test_base_test.cpp"], - tests = glob(["testdata/**"]), + data = glob(["testdata/include_files/**"]), + tests = glob( + ["testdata/**"], + exclude = ["testdata/include_files/**"], + ), deps = [ ":file_test_base", "//common:ostream", diff --git a/testing/file_test/README.md b/testing/file_test/README.md index 01823626b921..0fdeba4a3ee9 100644 --- a/testing/file_test/README.md +++ b/testing/file_test/README.md @@ -202,18 +202,26 @@ Supported comment markers are: // EXTRA-ARGS: ``` - Same as `ARGS`, including substitution behavior, but appends to the default - argument list instead of replacing it. + Same as `ARGS`, including substitution behavior, but appends to the argument + list instead of replacing it. - `EXTRA-ARGS` can be specified at most once, and a test cannot specify both - `ARGS` and `EXTRA-ARGS`. + `EXTRA-ARGS` can be repeated, and provided with `ARGS`. - ``` // INCLUDE-FILE: ``` Includes the specified file in the test's virtual file system and adds the - path to the file's splits. + included file's content to the current file's splits. Included files can + use: + + - `// ARGS` + - `// EXTRA-ARGS` + - `// INCLUDE-FILE` + - `// --- ` + + Included files will use `include_files/` for their name when a + split isn't provided. This can be used to provide a minimal `Core` package (and `prelude` library) in toolchain tests. See the diff --git a/testing/file_test/file_test_base_test.cpp b/testing/file_test/file_test_base_test.cpp index 2420c7fd1026..4af016bdcdab 100644 --- a/testing/file_test/file_test_base_test.cpp +++ b/testing/file_test/file_test_base_test.cpp @@ -126,6 +126,19 @@ static auto TestCaptureConsoleOutput(TestParams& params) return {{.success = true}}; } +// Prints and returns expected results for escaping.carbon. +static auto TestEscaping(TestParams& params) + -> ErrorOr { + 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 { @@ -189,19 +202,6 @@ static auto TestNoLineNumber(TestParams& params) return {{.success = true}}; } -// Prints and returns expected results for escaping.carbon. -static auto TestEscaping(TestParams& params) - -> ErrorOr { - 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 unattached_multi_file.carbon. static auto TestUnattachedMultiFile(TestParams& params) -> ErrorOr { @@ -269,9 +269,11 @@ auto FileTestBaseTest::Run( PrintArgs(test_args, output_stream); auto filename = std::filesystem::path(test_name().str()).filename(); - if (filename == "args.carbon" || filename == "include_file.carbon") { - // 'args.carbon' and 'include_file.carbon' have custom arguments, so don't - // do regular argument validation for them. + 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}}; } diff --git a/testing/file_test/run_test.cpp b/testing/file_test/run_test.cpp index 6015784ca711..457b847e29ed 100644 --- a/testing/file_test/run_test.cpp +++ b/testing/file_test/run_test.cpp @@ -102,8 +102,8 @@ auto RunTestFile(const FileTestBase& test_base, bool dump_output, // Process arguments. if (test_file.test_args.empty()) { test_file.test_args = test_base.GetDefaultArgs(); - test_file.test_args.append(test_file.extra_args); } + test_file.test_args.append(test_file.extra_args); CARBON_RETURN_IF_ERROR(DoArgReplacements( test_file.test_args, test_base.GetArgReplacements(), all_splits)); diff --git a/testing/file_test/test_file.cpp b/testing/file_test/test_file.cpp index 6775f77dda35..cba68362dcd8 100644 --- a/testing/file_test/test_file.cpp +++ b/testing/file_test/test_file.cpp @@ -16,6 +16,7 @@ #include "llvm/ADT/StringExtras.h" #include "llvm/Support/JSON.h" #include "testing/base/file_helpers.h" +#include "testing/file_test/line.h" namespace Carbon::Testing { @@ -28,7 +29,7 @@ using ::testing::StrEq; static auto TryConsumeConflictMarker(bool running_autoupdate, llvm::StringRef line, llvm::StringRef line_trimmed, - bool* inside_conflict_marker) + bool& inside_conflict_marker) -> ErrorOr { bool is_start = line.starts_with("<<<<<<<"); bool is_middle = line.starts_with("=======") || line.starts_with("|||||||"); @@ -41,7 +42,7 @@ static auto TryConsumeConflictMarker(bool running_autoupdate, // Autoupdate tracks conflict markers for context, and will discard // conflicting lines when it can autoupdate them. - if (*inside_conflict_marker) { + if (inside_conflict_marker) { if (is_start) { return ErrorBuilder() << "Unexpected conflict marker inside conflict:\n" << line; @@ -50,7 +51,7 @@ static auto TryConsumeConflictMarker(bool running_autoupdate, return true; } if (is_end) { - *inside_conflict_marker = false; + inside_conflict_marker = false; return true; } @@ -66,7 +67,7 @@ static auto TryConsumeConflictMarker(bool running_autoupdate, << line; } else { if (is_start) { - *inside_conflict_marker = true; + inside_conflict_marker = true; return true; } if (is_middle || is_end) { @@ -114,10 +115,10 @@ static auto ExtractFilePathFromUri(llvm::StringRef uri) // When `FROM_FILE_SPLIT` is used in path `textDocument.text`, populate the // value from the split matching the `uri`. Only used for // `textDocument/didOpen`. -static auto AutoFillDidOpenParams(llvm::json::Object* params, +static auto AutoFillDidOpenParams(llvm::json::Object& params, llvm::ArrayRef splits) -> ErrorOr { - auto* text_document = params->getObject("textDocument"); + auto* text_document = params.getObject("textDocument"); if (text_document == nullptr) { return Success(); } @@ -144,12 +145,12 @@ static auto AutoFillDidOpenParams(llvm::json::Object* params, } // Reformats `[[@LSP:` and similar keyword as an LSP call with headers. -static auto ReplaceLspKeywordAt(std::string* content, size_t keyword_pos, +static auto ReplaceLspKeywordAt(std::string& content, size_t keyword_pos, int& lsp_call_id, llvm::ArrayRef splits) -> ErrorOr { llvm::StringRef content_at_keyword = - llvm::StringRef(*content).substr(keyword_pos); + llvm::StringRef(content).substr(keyword_pos); auto [keyword, body_start] = content_at_keyword.split(":"); if (body_start.empty()) { @@ -198,7 +199,7 @@ static auto ReplaceLspKeywordAt(std::string* content, size_t keyword_pos, if (extra_content_label == "params" && method_or_id == "textDocument/didOpen") { CARBON_RETURN_IF_ERROR( - AutoFillDidOpenParams(parsed_extra_content.getAsObject(), splits)); + AutoFillDidOpenParams(*parsed_extra_content.getAsObject(), splits)); } } @@ -231,17 +232,17 @@ static auto ReplaceLspKeywordAt(std::string* content, size_t keyword_pos, .str(); int keyword_len = (body_start.data() + body_end + LspEnd.size()) - keyword.data(); - content->replace(keyword_pos, keyword_len, json_with_header); + content.replace(keyword_pos, keyword_len, json_with_header); return keyword_pos + json_with_header.size(); } // Replaces the keyword at the given position. Returns the position to start a // find for the next keyword. -static auto ReplaceContentKeywordAt(std::string* content, size_t keyword_pos, +static auto ReplaceContentKeywordAt(std::string& content, size_t keyword_pos, llvm::StringRef test_name, int& lsp_call_id, llvm::ArrayRef splits) -> ErrorOr { - auto keyword = llvm::StringRef(*content).substr(keyword_pos); + auto keyword = llvm::StringRef(content).substr(keyword_pos); // Line replacements aren't handled here. static constexpr llvm::StringLiteral Line = "[[@LINE"; @@ -253,7 +254,7 @@ static auto ReplaceContentKeywordAt(std::string* content, size_t keyword_pos, // Replaced with the actual test name. static constexpr llvm::StringLiteral TestName = "[[@TEST_NAME]]"; if (keyword.starts_with(TestName)) { - content->replace(keyword_pos, TestName.size(), test_name); + content.replace(keyword_pos, TestName.size(), test_name); return keyword_pos + test_name.size(); } @@ -270,12 +271,12 @@ static auto ReplaceContentKeywordAt(std::string* content, size_t keyword_pos, // TEST_NAME is the only content keyword at present, but we do validate that // other names are reserved. static auto ReplaceContentKeywords(llvm::StringRef filename, - std::string* content, + std::string& content, llvm::ArrayRef splits) -> ErrorOr { static constexpr llvm::StringLiteral Prefix = "[[@"; - auto keyword_pos = content->find(Prefix); + auto keyword_pos = content.find(Prefix); // Return early if not finding anything. if (keyword_pos == std::string::npos) { return Success(); @@ -303,44 +304,45 @@ static auto ReplaceContentKeywords(llvm::StringRef filename, auto keyword_end, ReplaceContentKeywordAt(content, keyword_pos, test_name, lsp_call_id, splits)); - keyword_pos = content->find(Prefix, keyword_end); + keyword_pos = content.find(Prefix, keyword_end); } return Success(); } // Adds a file. Used for both split and unsplit test files. -static auto AddSplit(llvm::StringRef filename, std::string* content, - llvm::SmallVector* file_splits) +static auto AddSplit(llvm::StringRef filename, std::string& content, + llvm::SmallVector& file_splits) -> ErrorOr { CARBON_RETURN_IF_ERROR( - ReplaceContentKeywords(filename, content, *file_splits)); - file_splits->push_back( - {.filename = filename.str(), .content = std::move(*content)}); - content->clear(); + ReplaceContentKeywords(filename, content, file_splits)); + file_splits.push_back( + {.filename = filename.str(), .content = std::move(content)}); + content.clear(); return Success(); } // Process file split ("---") lines when found. Returns true if the line is -// consumed. +// consumed. `non_check_lines` is only provided for the main file, and will be +// null for includes. static auto TryConsumeSplit(llvm::StringRef line, llvm::StringRef line_trimmed, - bool found_autoupdate, int* line_index, - SplitState* split, - llvm::SmallVector* file_splits, + bool missing_autoupdate, int& line_index, + SplitState& split, + llvm::SmallVector& file_splits, llvm::SmallVector* non_check_lines) -> ErrorOr { if (!line_trimmed.consume_front("// ---")) { - if (!split->has_splits() && !line_trimmed.starts_with("//") && + if (!split.has_splits() && !line_trimmed.starts_with("//") && !line_trimmed.empty()) { - split->found_code_pre_split = true; + split.found_code_pre_split = true; } // Add the line to the current file's content (which may not be a split // file). - split->add_content(line); + split.add_content(line); return false; } - if (!found_autoupdate) { + if (missing_autoupdate) { // If there's a split, all output is appended at the end of each file // before AUTOUPDATE. We may want to change that, but it's not // necessary to handle right now. @@ -350,12 +352,12 @@ static auto TryConsumeSplit(llvm::StringRef line, llvm::StringRef line_trimmed, } // On a file split, add the previous file, then start a new one. - if (split->has_splits()) { + if (split.has_splits()) { CARBON_RETURN_IF_ERROR( - AddSplit(split->filename, &split->content, file_splits)); + AddSplit(split.filename, split.content, file_splits)); } else { - split->content.clear(); - if (split->found_code_pre_split) { + split.content.clear(); + if (split.found_code_pre_split) { // For the first split, we make sure there was no content prior. return Error( "When using split files, there must be no content before the first " @@ -363,16 +365,18 @@ static auto TryConsumeSplit(llvm::StringRef line, llvm::StringRef line_trimmed, } } - ++split->file_index; - split->filename = line_trimmed.trim(); - if (split->filename.empty()) { + ++split.file_index; + split.filename = line_trimmed.trim(); + if (split.filename.empty()) { return Error("Missing filename for split."); } // The split line is added to non_check_lines for retention in autoupdate, but // is not added to the test file content. - *line_index = 0; - non_check_lines->push_back( - FileTestLine(split->file_index, *line_index, line)); + line_index = 0; + if (non_check_lines) { + non_check_lines->push_back( + FileTestLine(split.file_index, line_index, line)); + } return true; } @@ -497,20 +501,26 @@ static auto TransformExpectation(int line_index, llvm::StringRef in) } // Once all content is processed, do any remaining split processing. -static auto FinishSplit(llvm::StringRef test_name, SplitState* split, - llvm::SmallVector* file_splits) +static auto FinishSplit(llvm::StringRef filename, bool is_include_file, + SplitState& split, + llvm::SmallVector& file_splits) -> ErrorOr { - if (split->has_splits()) { - return AddSplit(split->filename, &split->content, file_splits); + if (split.has_splits()) { + return AddSplit(split.filename, split.content, file_splits); } else { // If no file splitting happened, use the main file as the test file. // There will always be a `/` unless tests are in the repo root. - return AddSplit(test_name.drop_front(test_name.rfind("/") + 1), - &split->content, file_splits); + std::string split_name = std::filesystem::path(filename.str()).filename(); + if (is_include_file) { + split_name.insert(0, "include_files/"); + } + return AddSplit(split_name, split.content, file_splits); } } // Process CHECK lines when found. Returns true if the line is consumed. +// `expected_stdout` and `expected_stderr` are null in included files, where +// it's an error to use `CHECK`. static auto TryConsumeCheck( bool running_autoupdate, int line_index, llvm::StringRef line, llvm::StringRef line_trimmed, @@ -521,6 +531,11 @@ static auto TryConsumeCheck( return false; } + if (!expected_stdout) { + return ErrorBuilder() << "Included files can't add CHECKs: " + << line_trimmed; + } + // Don't build expectations when doing an autoupdate. We don't want to // break the autoupdate on an invalid CHECK line. if (!running_autoupdate) { @@ -542,50 +557,58 @@ static auto TryConsumeCheck( // Processes ARGS and EXTRA-ARGS lines when found. Returns true if the line is // consumed. static auto TryConsumeArgs(llvm::StringRef line, llvm::StringRef line_trimmed, - llvm::SmallVector* args, - llvm::SmallVector* extra_args) + llvm::SmallVector& args) -> ErrorOr { - llvm::SmallVector* arg_list = nullptr; - if (line_trimmed.consume_front("// ARGS: ")) { - arg_list = args; - } else if (line_trimmed.consume_front("// EXTRA-ARGS: ")) { - arg_list = extra_args; - } else { + if (!line_trimmed.consume_front("// ARGS: ")) { return false; } - if (!args->empty() || !extra_args->empty()) { - return ErrorBuilder() << "ARGS / EXTRA-ARGS specified multiple times: " - << line.str(); + if (!args.empty()) { + return ErrorBuilder() << "ARGS specified multiple times: " << line.str(); } // Split the line into arguments. std::pair cursor = llvm::getToken(line_trimmed); while (!cursor.first.empty()) { - arg_list->push_back(std::string(cursor.first)); + args.push_back(std::string(cursor.first)); + cursor = llvm::getToken(cursor.second); + } + + return true; +} +static auto TryConsumeExtraArgs(llvm::StringRef line_trimmed, + llvm::SmallVector& extra_args) + -> ErrorOr { + if (!line_trimmed.consume_front("// EXTRA-ARGS: ")) { + return false; + } + + // Split the line into arguments. + std::pair cursor = + llvm::getToken(line_trimmed); + while (!cursor.first.empty()) { + extra_args.push_back(std::string(cursor.first)); cursor = llvm::getToken(cursor.second); } return true; } -static auto TryConsumeIncludeFile( - llvm::StringRef line_trimmed, - llvm::SmallVector* include_files) -> ErrorOr { +static auto TryConsumeIncludeFile(llvm::StringRef line_trimmed, + llvm::SmallVector& include_files) + -> ErrorOr { if (!line_trimmed.consume_front("// INCLUDE-FILE: ")) { return false; } - std::filesystem::path path = std::string(line_trimmed); - CARBON_ASSIGN_OR_RETURN(std::string content, ReadFile(path)); - include_files->push_back( - {.filename = std::filesystem::path("include_files") / path.filename(), - .content = content}); + include_files.push_back(line_trimmed.str()); return true; } // Processes AUTOUPDATE lines when found. Returns true if the line is consumed. +// `found_autoupdate` and `autoupdate_line_number` are only provided for the +// main file; it's an error to have autoupdate in included files. static auto TryConsumeAutoupdate(int line_index, llvm::StringRef line_trimmed, bool* found_autoupdate, std::optional* autoupdate_line_number) @@ -595,6 +618,10 @@ static auto TryConsumeAutoupdate(int line_index, llvm::StringRef line_trimmed, if (line_trimmed != Autoupdate && line_trimmed != NoAutoupdate) { return false; } + if (!found_autoupdate) { + return ErrorBuilder() << "Included files can't control autoupdate: " + << line_trimmed; + } if (*found_autoupdate) { return Error("Multiple AUTOUPDATE/NOAUTOUPDATE settings found"); } @@ -606,12 +633,16 @@ static auto TryConsumeAutoupdate(int line_index, llvm::StringRef line_trimmed, } // Processes SET-* lines when found. Returns true if the line is consumed. +// If `flag` is null, we're in an included file where the flag can't be set. static auto TryConsumeSetFlag(llvm::StringRef line_trimmed, llvm::StringLiteral flag_name, bool* flag) -> ErrorOr { if (!line_trimmed.consume_front("// ") || line_trimmed != flag_name) { return false; } + if (!flag) { + return ErrorBuilder() << "Included files can't set flag: " << line_trimmed; + } if (*flag) { return ErrorBuilder() << flag_name << " was specified multiple times"; } @@ -619,49 +650,62 @@ static auto TryConsumeSetFlag(llvm::StringRef line_trimmed, return true; } -auto ProcessTestFile(llvm::StringRef test_name, bool running_autoupdate) - -> ErrorOr { - TestFile test_file; - - // Store the file so that file_splits can use references to content. - CARBON_ASSIGN_OR_RETURN(test_file.input_content, ReadFile(test_name.str())); - - // Original file content, and a cursor for walking through it. - llvm::StringRef file_content = test_file.input_content; - llvm::StringRef cursor = file_content; - - // Whether either AUTOUDPATE or NOAUTOUPDATE was found. - bool found_autoupdate = false; - +// Process content for either the main file (with `test_file` and +// `found_autoupdate` provided) or an included file (with those arguments null). +// +// - `found_autoupdate` is set to true when either `AUTOUPDATE` or +// `NOAUTOUPDATE` are found. +// - `args` is set from `ARGS`. +// - `extra_args` accumulates `EXTRA-ARGS`. +// - `splits` accumulates split form for the test (`// --- `, or the +// full file named as `filename` when there are no splits in the file). +// - `include_files` accumulates `INCLUDE-FILE`. +static auto ProcessFileContent(llvm::StringRef filename, + llvm::StringRef content_cursor, + bool running_autoupdate, TestFile* test_file, + bool* found_autoupdate, + llvm::SmallVector& args, + llvm::SmallVector& extra_args, + llvm::SmallVector& splits, + llvm::SmallVector& include_files) + -> ErrorOr { // The index in the current test file. Will be reset on splits. int line_index = 0; - SplitState split; - // When autoupdating, we track whether we're inside conflict markers. // Otherwise conflict markers are errors. bool inside_conflict_marker = false; - while (!cursor.empty()) { - auto [line, next_cursor] = cursor.split("\n"); - cursor = next_cursor; + SplitState split_state; + + while (!content_cursor.empty()) { + auto [line, next_cursor] = content_cursor.split("\n"); + content_cursor = next_cursor; auto line_trimmed = line.ltrim(); bool is_consumed = false; + CARBON_ASSIGN_OR_RETURN( is_consumed, TryConsumeConflictMarker(running_autoupdate, line, line_trimmed, - &inside_conflict_marker)); + inside_conflict_marker)); if (is_consumed) { continue; } // At this point, remaining lines are part of the test input. + + // We need to consume a split, but the main file has a little more handling. + bool missing_autoupdate = false; + llvm::SmallVector* non_check_lines = nullptr; + if (test_file) { + missing_autoupdate = !*found_autoupdate; + non_check_lines = &test_file->non_check_lines; + } CARBON_ASSIGN_OR_RETURN( is_consumed, - TryConsumeSplit(line, line_trimmed, found_autoupdate, &line_index, - &split, &test_file.file_splits, - &test_file.non_check_lines)); + TryConsumeSplit(line, line_trimmed, missing_autoupdate, line_index, + split_state, splits, non_check_lines)); if (is_consumed) { continue; } @@ -674,63 +718,99 @@ auto ProcessTestFile(llvm::StringRef test_name, bool running_autoupdate) } CARBON_ASSIGN_OR_RETURN( - is_consumed, TryConsumeCheck(running_autoupdate, line_index, line, - line_trimmed, &test_file.expected_stdout, - &test_file.expected_stderr)); + is_consumed, + TryConsumeCheck(running_autoupdate, line_index, line, line_trimmed, + test_file ? &test_file->expected_stdout : nullptr, + test_file ? &test_file->expected_stderr : nullptr)); if (is_consumed) { continue; } - // At this point, lines are retained as non-CHECK lines. - test_file.non_check_lines.push_back( - FileTestLine(split.file_index, line_index, line)); + if (test_file) { + // At this point, lines are retained as non-CHECK lines. + test_file->non_check_lines.push_back( + FileTestLine(split_state.file_index, line_index, line)); + } - CARBON_ASSIGN_OR_RETURN( - is_consumed, TryConsumeArgs(line, line_trimmed, &test_file.test_args, - &test_file.extra_args)); - if (is_consumed) { - continue; - } - CARBON_ASSIGN_OR_RETURN( - is_consumed, - TryConsumeIncludeFile(line_trimmed, &test_file.include_file_splits)); - if (is_consumed) { - continue; - } - CARBON_ASSIGN_OR_RETURN( - is_consumed, - TryConsumeAutoupdate(line_index, line_trimmed, &found_autoupdate, - &test_file.autoupdate_line_number)); - if (is_consumed) { - continue; - } - CARBON_ASSIGN_OR_RETURN( - is_consumed, - TryConsumeSetFlag(line_trimmed, "SET-CAPTURE-CONSOLE-OUTPUT", - &test_file.capture_console_output)); + CARBON_ASSIGN_OR_RETURN(is_consumed, + TryConsumeArgs(line, line_trimmed, args)); if (is_consumed) { continue; } CARBON_ASSIGN_OR_RETURN(is_consumed, - TryConsumeSetFlag(line_trimmed, "SET-CHECK-SUBSET", - &test_file.check_subset)); + TryConsumeExtraArgs(line_trimmed, extra_args)); + if (is_consumed) { + continue; + } + CARBON_ASSIGN_OR_RETURN(is_consumed, + TryConsumeIncludeFile(line_trimmed, include_files)); + if (is_consumed) { + continue; + } + + CARBON_ASSIGN_OR_RETURN( + is_consumed, + TryConsumeAutoupdate( + line_index, line_trimmed, found_autoupdate, + test_file ? &test_file->autoupdate_line_number : nullptr)); + if (is_consumed) { + continue; + } + CARBON_ASSIGN_OR_RETURN( + is_consumed, + TryConsumeSetFlag( + line_trimmed, "SET-CAPTURE-CONSOLE-OUTPUT", + test_file ? &test_file->capture_console_output : nullptr)); + if (is_consumed) { + continue; + } + CARBON_ASSIGN_OR_RETURN( + is_consumed, + TryConsumeSetFlag(line_trimmed, "SET-CHECK-SUBSET", + test_file ? &test_file->check_subset : nullptr)); if (is_consumed) { continue; } } + CARBON_RETURN_IF_ERROR(FinishSplit(filename, /*is_include_file=*/!test_file, + split_state, splits)); + + if (test_file) { + test_file->has_splits = split_state.has_splits(); + } + return Success(); +} + +auto ProcessTestFile(llvm::StringRef test_name, bool running_autoupdate) + -> ErrorOr { + TestFile test_file; + + // Store the original content, to avoid a read when autoupdating. + CARBON_ASSIGN_OR_RETURN(test_file.input_content, ReadFile(test_name.str())); + + // Whether either AUTOUDPATE or NOAUTOUPDATE was found. + bool found_autoupdate = false; + + // INCLUDE-FILE uses, accumulated across both the main file and any includes + // (recursively). + llvm::SmallVector include_files; + + // Process the main file. + CARBON_RETURN_IF_ERROR(ProcessFileContent( + test_name, test_file.input_content, running_autoupdate, &test_file, + &found_autoupdate, test_file.test_args, test_file.extra_args, + test_file.file_splits, include_files)); + if (!found_autoupdate) { return ErrorBuilder() << "Missing AUTOUPDATE/NOAUTOUPDATE setting: " << test_name; } - test_file.has_splits = split.has_splits(); - CARBON_RETURN_IF_ERROR( - FinishSplit(test_name, &split, &test_file.file_splits)); + constexpr llvm::StringLiteral AutoupdateSplit = "AUTOUPDATE-SPLIT"; // Validate AUTOUPDATE-SPLIT use, and remove it from test files if present. if (test_file.has_splits) { - constexpr llvm::StringLiteral AutoupdateSplit = "AUTOUPDATE-SPLIT"; for (const auto& test_file : llvm::ArrayRef(test_file.file_splits).drop_back()) { if (test_file.filename == AutoupdateSplit) { @@ -754,6 +834,24 @@ auto ProcessTestFile(llvm::StringRef test_name, bool running_autoupdate) test_file.expected_stderr.push_back(StrEq("")); } + // Process includes. This can add entries to `include_files`. + for (size_t i = 0; i < include_files.size(); ++i) { + const auto& filename = include_files[i]; + CARBON_ASSIGN_OR_RETURN(std::string content, ReadFile(filename)); + // Note autoupdate never touches included files. + CARBON_RETURN_IF_ERROR(ProcessFileContent( + filename, content, /*running_autoupdate=*/false, + /*test_file=*/nullptr, + /*found_autoupdate=*/nullptr, test_file.test_args, test_file.extra_args, + test_file.include_file_splits, include_files)); + } + + for (const auto& split : test_file.include_file_splits) { + if (split.filename == AutoupdateSplit) { + return Error("AUTOUPDATE-SPLIT is disallowed in included files"); + } + } + return std::move(test_file); } diff --git a/testing/file_test/testdata/include_file.carbon b/testing/file_test/testdata/include_args.carbon similarity index 57% rename from testing/file_test/testdata/include_file.carbon rename to testing/file_test/testdata/include_args.carbon index 67d97f916cd7..e1799e91853f 100644 --- a/testing/file_test/testdata/include_file.carbon +++ b/testing/file_test/testdata/include_args.carbon @@ -1,12 +1,13 @@ // 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-FILE: testing/file_test/testdata/no_line_number.carbon +// +// INCLUDE-FILE: testing/file_test/testdata/include_files/args.carbon +// // 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/include_file.carbon +// TIP: bazel test //testing/file_test:file_test_base_test --test_arg=--file_tests=testing/file_test/testdata/include_args.carbon // TIP: To dump output, run: -// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/include_file.carbon +// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/include_args.carbon -// CHECK:STDOUT: 3 args: `default_args`, `include_file.carbon`, `include_files/no_line_number.carbon` +// CHECK:STDOUT: 3 args: `foo`, `bar`, `baz` diff --git a/testing/file_test/testdata/include_args_and_extra_args.carbon b/testing/file_test/testdata/include_args_and_extra_args.carbon new file mode 100644 index 000000000000..7bec5d8c89aa --- /dev/null +++ b/testing/file_test/testdata/include_args_and_extra_args.carbon @@ -0,0 +1,14 @@ +// 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-FILE: testing/file_test/testdata/include_files/extra_args.carbon +// ARGS: foo +// +// 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/include_args_and_extra_args.carbon +// TIP: To dump output, run: +// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/include_args_and_extra_args.carbon + +// CHECK:STDOUT: 3 args: `foo`, `bar`, `baz` diff --git a/testing/file_test/testdata/include_empty.carbon b/testing/file_test/testdata/include_empty.carbon new file mode 100644 index 000000000000..1ca4e747d6c6 --- /dev/null +++ b/testing/file_test/testdata/include_empty.carbon @@ -0,0 +1,13 @@ +// 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-FILE: testing/file_test/testdata/include_files/empty.carbon +// +// 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/include_empty.carbon +// TIP: To dump output, run: +// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/include_empty.carbon + +// CHECK:STDOUT: 3 args: `default_args`, `include_empty.carbon`, `empty.carbon` diff --git a/testing/file_test/testdata/include_extra_args.carbon b/testing/file_test/testdata/include_extra_args.carbon new file mode 100644 index 000000000000..ce6bff541d42 --- /dev/null +++ b/testing/file_test/testdata/include_extra_args.carbon @@ -0,0 +1,14 @@ +// 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 +// +// EXTRA-ARGS: foo +// INCLUDE-FILE: testing/file_test/testdata/include_files/extra_args.carbon +// +// 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/include_extra_args.carbon +// TIP: To dump output, run: +// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/include_extra_args.carbon + +// CHECK:STDOUT: 6 args: `default_args`, `include_extra_args.carbon`, `include_files/extra_args.carbon`, `foo`, `bar`, `baz` diff --git a/testing/file_test/testdata/include_files/args.carbon b/testing/file_test/testdata/include_files/args.carbon new file mode 100644 index 000000000000..8d42f4781ef4 --- /dev/null +++ b/testing/file_test/testdata/include_files/args.carbon @@ -0,0 +1,5 @@ +// 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 +// +// ARGS: foo bar baz diff --git a/testing/file_test/testdata/include_files/empty.carbon b/testing/file_test/testdata/include_files/empty.carbon new file mode 100644 index 000000000000..2a57bed1f271 --- /dev/null +++ b/testing/file_test/testdata/include_files/empty.carbon @@ -0,0 +1,5 @@ +// 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 + +// --- empty.carbon diff --git a/testing/file_test/testdata/include_files/extra_args.carbon b/testing/file_test/testdata/include_files/extra_args.carbon new file mode 100644 index 000000000000..a8679c4fcecc --- /dev/null +++ b/testing/file_test/testdata/include_files/extra_args.carbon @@ -0,0 +1,5 @@ +// 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 +// +// EXTRA-ARGS: bar baz diff --git a/testing/file_test/testdata/include_files/no_split.carbon b/testing/file_test/testdata/include_files/no_split.carbon new file mode 100644 index 000000000000..b31125ed5a7b --- /dev/null +++ b/testing/file_test/testdata/include_files/no_split.carbon @@ -0,0 +1,5 @@ +// 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 + +no split diff --git a/testing/file_test/testdata/include_files/recursive.carbon b/testing/file_test/testdata/include_files/recursive.carbon new file mode 100644 index 000000000000..7dd167e8716e --- /dev/null +++ b/testing/file_test/testdata/include_files/recursive.carbon @@ -0,0 +1,13 @@ +// 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-FILE: testing/file_test/testdata/include_files/split.carbon + +// --- c.carbon + +c + +// --- d.carbon + +d diff --git a/testing/file_test/testdata/include_files/split.carbon b/testing/file_test/testdata/include_files/split.carbon new file mode 100644 index 000000000000..1763365595d3 --- /dev/null +++ b/testing/file_test/testdata/include_files/split.carbon @@ -0,0 +1,11 @@ +// 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 + +// --- a.carbon + +a + +// --- b.carbon + +b diff --git a/testing/file_test/testdata/include_no_split.carbon b/testing/file_test/testdata/include_no_split.carbon new file mode 100644 index 000000000000..f5f89a4c9686 --- /dev/null +++ b/testing/file_test/testdata/include_no_split.carbon @@ -0,0 +1,14 @@ +// 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-FILE: testing/file_test/testdata/include_files/no_split.carbon +// +// 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/include_no_split.carbon +// TIP: To dump output, run: +// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/include_no_split.carbon + +// CHECK:STDOUT: 3 args: `default_args`, `include_no_split.carbon`, `include_files/no_split.carbon` +// CHECK:STDOUT: include_files/no_split.carbon:5: no split diff --git a/testing/file_test/testdata/include_recursive.carbon b/testing/file_test/testdata/include_recursive.carbon new file mode 100644 index 000000000000..629aa12a334d --- /dev/null +++ b/testing/file_test/testdata/include_recursive.carbon @@ -0,0 +1,17 @@ +// 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-FILE: testing/file_test/testdata/include_files/recursive.carbon +// +// 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/include_recursive.carbon +// TIP: To dump output, run: +// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/include_recursive.carbon + +// CHECK:STDOUT: 6 args: `default_args`, `include_recursive.carbon`, `c.carbon`, `d.carbon`, `a.carbon`, `b.carbon` +// CHECK:STDOUT: c.carbon:2: c +// CHECK:STDOUT: d.carbon:2: d +// CHECK:STDOUT: a.carbon:2: a +// CHECK:STDOUT: b.carbon:2: b diff --git a/testing/file_test/testdata/include_split.carbon b/testing/file_test/testdata/include_split.carbon new file mode 100644 index 000000000000..dc5ec6234e94 --- /dev/null +++ b/testing/file_test/testdata/include_split.carbon @@ -0,0 +1,15 @@ +// 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-FILE: testing/file_test/testdata/include_files/split.carbon +// +// 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/include_split.carbon +// TIP: To dump output, run: +// TIP: bazel run //testing/file_test:file_test_base_test -- --dump_output --file_tests=testing/file_test/testdata/include_split.carbon + +// CHECK:STDOUT: 4 args: `default_args`, `include_split.carbon`, `a.carbon`, `b.carbon` +// CHECK:STDOUT: a.carbon:2: a +// CHECK:STDOUT: b.carbon:2: b