test_file.cpp 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. // Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  2. // Exceptions. See /LICENSE for license information.
  3. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  4. #include "testing/file_test/test_file.h"
  5. #include <fstream>
  6. #include "common/check.h"
  7. #include "common/error.h"
  8. #include "common/raw_string_ostream.h"
  9. #include "llvm/ADT/StringExtras.h"
  10. #include "llvm/Support/JSON.h"
  11. #include "testing/base/file_helpers.h"
  12. namespace Carbon::Testing {
  13. using ::testing::Matcher;
  14. using ::testing::MatchesRegex;
  15. using ::testing::StrEq;
  16. // Processes conflict markers, including tracking of whether code is within a
  17. // conflict marker. Returns true if the line is consumed.
  18. static auto TryConsumeConflictMarker(bool running_autoupdate,
  19. llvm::StringRef line,
  20. llvm::StringRef line_trimmed,
  21. bool* inside_conflict_marker)
  22. -> ErrorOr<bool> {
  23. bool is_start = line.starts_with("<<<<<<<");
  24. bool is_middle = line.starts_with("=======") || line.starts_with("|||||||");
  25. bool is_end = line.starts_with(">>>>>>>");
  26. // When running the test, any conflict marker is an error.
  27. if (!running_autoupdate && (is_start || is_middle || is_end)) {
  28. return ErrorBuilder() << "Conflict marker found:\n" << line;
  29. }
  30. // Autoupdate tracks conflict markers for context, and will discard
  31. // conflicting lines when it can autoupdate them.
  32. if (*inside_conflict_marker) {
  33. if (is_start) {
  34. return ErrorBuilder() << "Unexpected conflict marker inside conflict:\n"
  35. << line;
  36. }
  37. if (is_middle) {
  38. return true;
  39. }
  40. if (is_end) {
  41. *inside_conflict_marker = false;
  42. return true;
  43. }
  44. // Look for CHECK and TIP lines, which can be discarded.
  45. if (line_trimmed.starts_with("// CHECK:STDOUT:") ||
  46. line_trimmed.starts_with("// CHECK:STDERR:") ||
  47. line_trimmed.starts_with("// TIP:")) {
  48. return true;
  49. }
  50. return ErrorBuilder()
  51. << "Autoupdate can't discard non-CHECK lines inside conflicts:\n"
  52. << line;
  53. } else {
  54. if (is_start) {
  55. *inside_conflict_marker = true;
  56. return true;
  57. }
  58. if (is_middle || is_end) {
  59. return ErrorBuilder() << "Unexpected conflict marker outside conflict:\n"
  60. << line;
  61. }
  62. return false;
  63. }
  64. }
  65. // State for file splitting logic: TryConsumeSplit and FinishSplit.
  66. struct SplitState {
  67. auto has_splits() const -> bool { return file_index > 0; }
  68. auto add_content(llvm::StringRef line) -> void {
  69. content.append(line.str());
  70. content.append("\n");
  71. }
  72. // Whether content has been found. Only updated before a file split is found
  73. // (which may be never).
  74. bool found_code_pre_split = false;
  75. // The current file name, considering splits. Empty for the default file.
  76. llvm::StringRef filename = "";
  77. // The accumulated content for the file being built. This may elide some of
  78. // the original content, such as conflict markers.
  79. std::string content;
  80. // The current file index.
  81. int file_index = 0;
  82. };
  83. // Given a `file:/<filename>` URI, returns the filename.
  84. static auto ExtractFilePathFromUri(llvm::StringRef uri)
  85. -> ErrorOr<llvm::StringRef> {
  86. static constexpr llvm::StringRef FilePrefix = "file:/";
  87. if (!uri.starts_with(FilePrefix)) {
  88. return ErrorBuilder("uri `") << uri << "` is not a file uri";
  89. }
  90. return uri.drop_front(FilePrefix.size());
  91. }
  92. // When `FROM_FILE_SPLIT` is used in path `textDocument.text`, populate the
  93. // value from the split matching the `uri`. Only used for
  94. // `textDocument/didOpen`.
  95. static auto AutoFillDidOpenParams(llvm::json::Object* params,
  96. llvm::ArrayRef<TestFile::Split> splits)
  97. -> ErrorOr<Success> {
  98. auto* text_document = params->getObject("textDocument");
  99. if (text_document == nullptr) {
  100. return Success();
  101. }
  102. auto attr_it = text_document->find("text");
  103. if (attr_it == text_document->end() || attr_it->second != "FROM_FILE_SPLIT") {
  104. return Success();
  105. }
  106. auto uri = text_document->getString("uri");
  107. if (!uri) {
  108. return Error("missing uri in params.textDocument");
  109. }
  110. CARBON_ASSIGN_OR_RETURN(auto file_path, ExtractFilePathFromUri(*uri));
  111. const auto* split_it =
  112. llvm::find_if(splits, [&](const TestFile::Split& split) {
  113. return split.filename == file_path;
  114. });
  115. if (split_it == splits.end()) {
  116. return ErrorBuilder() << "No split found for uri: " << *uri;
  117. }
  118. attr_it->second = split_it->content;
  119. return Success();
  120. }
  121. // Reformats `[[@LSP:` and similar keyword as an LSP call with headers.
  122. static auto ReplaceLspKeywordAt(std::string* content, size_t keyword_pos,
  123. int& lsp_call_id,
  124. llvm::ArrayRef<TestFile::Split> splits)
  125. -> ErrorOr<size_t> {
  126. llvm::StringRef content_at_keyword =
  127. llvm::StringRef(*content).substr(keyword_pos);
  128. auto [keyword, body_start] = content_at_keyword.split(":");
  129. if (body_start.empty()) {
  130. return ErrorBuilder() << "Missing `:` for `"
  131. << content_at_keyword.take_front(10) << "`";
  132. }
  133. // Whether the first param is a method or id.
  134. llvm::StringRef method_or_id_label = "method";
  135. // Whether to attach the `lsp_call_id`.
  136. bool use_call_id = false;
  137. // The JSON label for extra content.
  138. llvm::StringRef extra_content_label;
  139. if (keyword == "[[@LSP-CALL") {
  140. use_call_id = true;
  141. extra_content_label = "params";
  142. } else if (keyword == "[[@LSP-NOTIFY") {
  143. extra_content_label = "params";
  144. } else if (keyword == "[[@LSP-REPLY") {
  145. method_or_id_label = "id";
  146. extra_content_label = "result";
  147. } else if (keyword != "[[@LSP") {
  148. return ErrorBuilder() << "Unrecognized @LSP keyword at `"
  149. << keyword.take_front(10) << "`";
  150. }
  151. static constexpr llvm::StringLiteral LspEnd = "]]";
  152. auto body_end = body_start.find(LspEnd);
  153. if (body_end == std::string::npos) {
  154. return ErrorBuilder() << "Missing `" << LspEnd << "` after `" << keyword
  155. << "`";
  156. }
  157. llvm::StringRef body = body_start.take_front(body_end);
  158. auto [method_or_id, extra_content] = body.split(":");
  159. llvm::json::Value parsed_extra_content = nullptr;
  160. if (!extra_content.empty()) {
  161. std::string extra_content_as_object =
  162. llvm::formatv("{{{0}}", extra_content);
  163. auto parse_result = llvm::json::parse(extra_content_as_object);
  164. if (auto err = parse_result.takeError()) {
  165. return ErrorBuilder() << "Error parsing extra content: " << err;
  166. }
  167. parsed_extra_content = std::move(*parse_result);
  168. CARBON_CHECK(parsed_extra_content.kind() == llvm::json::Value::Object);
  169. if (extra_content_label == "params" &&
  170. method_or_id == "textDocument/didOpen") {
  171. CARBON_RETURN_IF_ERROR(
  172. AutoFillDidOpenParams(parsed_extra_content.getAsObject(), splits));
  173. }
  174. }
  175. // Form the JSON.
  176. RawStringOstream buffer;
  177. llvm::json::OStream json(buffer);
  178. json.object([&] {
  179. json.attribute("jsonrpc", "2.0");
  180. json.attribute(method_or_id_label, method_or_id);
  181. if (use_call_id) {
  182. json.attribute("id", ++lsp_call_id);
  183. }
  184. if (parsed_extra_content != nullptr) {
  185. if (!extra_content_label.empty()) {
  186. json.attribute(extra_content_label, parsed_extra_content);
  187. } else {
  188. for (const auto& [key, value] : *parsed_extra_content.getAsObject()) {
  189. json.attribute(key, value);
  190. }
  191. }
  192. }
  193. });
  194. // Add the Content-Length header. The `2` accounts for extra newlines.
  195. int content_length = buffer.size() + 2;
  196. auto json_with_header = llvm::formatv("Content-Length: {0}\n\n{1}\n",
  197. content_length, buffer.TakeStr())
  198. .str();
  199. int keyword_len =
  200. (body_start.data() + body_end + LspEnd.size()) - keyword.data();
  201. content->replace(keyword_pos, keyword_len, json_with_header);
  202. return keyword_pos + json_with_header.size();
  203. }
  204. // Replaces the keyword at the given position. Returns the position to start a
  205. // find for the next keyword.
  206. static auto ReplaceContentKeywordAt(std::string* content, size_t keyword_pos,
  207. llvm::StringRef test_name, int& lsp_call_id,
  208. llvm::ArrayRef<TestFile::Split> splits)
  209. -> ErrorOr<size_t> {
  210. auto keyword = llvm::StringRef(*content).substr(keyword_pos);
  211. // Line replacements aren't handled here.
  212. static constexpr llvm::StringLiteral Line = "[[@LINE";
  213. if (keyword.starts_with(Line)) {
  214. // Just move past the prefix to find the next one.
  215. return keyword_pos + Line.size();
  216. }
  217. // Replaced with the actual test name.
  218. static constexpr llvm::StringLiteral TestName = "[[@TEST_NAME]]";
  219. if (keyword.starts_with(TestName)) {
  220. content->replace(keyword_pos, TestName.size(), test_name);
  221. return keyword_pos + test_name.size();
  222. }
  223. if (keyword.starts_with("[[@LSP")) {
  224. return ReplaceLspKeywordAt(content, keyword_pos, lsp_call_id, splits);
  225. }
  226. return ErrorBuilder() << "Unexpected use of `[[@` at `"
  227. << keyword.substr(0, 5) << "`";
  228. }
  229. // Replaces the content keywords.
  230. //
  231. // TEST_NAME is the only content keyword at present, but we do validate that
  232. // other names are reserved.
  233. static auto ReplaceContentKeywords(llvm::StringRef filename,
  234. std::string* content,
  235. llvm::ArrayRef<TestFile::Split> splits)
  236. -> ErrorOr<Success> {
  237. static constexpr llvm::StringLiteral Prefix = "[[@";
  238. auto keyword_pos = content->find(Prefix);
  239. // Return early if not finding anything.
  240. if (keyword_pos == std::string::npos) {
  241. return Success();
  242. }
  243. // Construct the test name by getting the base name without the extension,
  244. // then removing any "fail_" or "todo_" prefixes.
  245. llvm::StringRef test_name = filename;
  246. if (auto last_slash = test_name.rfind("/");
  247. last_slash != llvm::StringRef::npos) {
  248. test_name = test_name.substr(last_slash + 1);
  249. }
  250. if (auto ext_dot = test_name.find("."); ext_dot != llvm::StringRef::npos) {
  251. test_name = test_name.substr(0, ext_dot);
  252. }
  253. // Note this also handles `fail_todo_` and `todo_fail_`.
  254. test_name.consume_front("todo_");
  255. test_name.consume_front("fail_");
  256. test_name.consume_front("todo_");
  257. // A counter for LSP calls.
  258. int lsp_call_id = 0;
  259. while (keyword_pos != std::string::npos) {
  260. CARBON_ASSIGN_OR_RETURN(
  261. auto keyword_end,
  262. ReplaceContentKeywordAt(content, keyword_pos, test_name, lsp_call_id,
  263. splits));
  264. keyword_pos = content->find(Prefix, keyword_end);
  265. }
  266. return Success();
  267. }
  268. // Adds a file. Used for both split and unsplit test files.
  269. static auto AddSplit(llvm::StringRef filename, std::string* content,
  270. llvm::SmallVector<TestFile::Split>* file_splits)
  271. -> ErrorOr<Success> {
  272. CARBON_RETURN_IF_ERROR(
  273. ReplaceContentKeywords(filename, content, *file_splits));
  274. file_splits->push_back(
  275. {.filename = filename.str(), .content = std::move(*content)});
  276. content->clear();
  277. return Success();
  278. }
  279. // Process file split ("---") lines when found. Returns true if the line is
  280. // consumed.
  281. static auto TryConsumeSplit(llvm::StringRef line, llvm::StringRef line_trimmed,
  282. bool found_autoupdate, int* line_index,
  283. SplitState* split,
  284. llvm::SmallVector<TestFile::Split>* file_splits,
  285. llvm::SmallVector<FileTestLine>* non_check_lines)
  286. -> ErrorOr<bool> {
  287. if (!line_trimmed.consume_front("// ---")) {
  288. if (!split->has_splits() && !line_trimmed.starts_with("//") &&
  289. !line_trimmed.empty()) {
  290. split->found_code_pre_split = true;
  291. }
  292. // Add the line to the current file's content (which may not be a split
  293. // file).
  294. split->add_content(line);
  295. return false;
  296. }
  297. if (!found_autoupdate) {
  298. // If there's a split, all output is appended at the end of each file
  299. // before AUTOUPDATE. We may want to change that, but it's not
  300. // necessary to handle right now.
  301. return Error(
  302. "AUTOUPDATE/NOAUTOUPDATE setting must be in "
  303. "the first file.");
  304. }
  305. // On a file split, add the previous file, then start a new one.
  306. if (split->has_splits()) {
  307. CARBON_RETURN_IF_ERROR(
  308. AddSplit(split->filename, &split->content, file_splits));
  309. } else {
  310. split->content.clear();
  311. if (split->found_code_pre_split) {
  312. // For the first split, we make sure there was no content prior.
  313. return Error(
  314. "When using split files, there must be no content before the first "
  315. "split file.");
  316. }
  317. }
  318. ++split->file_index;
  319. split->filename = line_trimmed.trim();
  320. if (split->filename.empty()) {
  321. return Error("Missing filename for split.");
  322. }
  323. // The split line is added to non_check_lines for retention in autoupdate, but
  324. // is not added to the test file content.
  325. *line_index = 0;
  326. non_check_lines->push_back(
  327. FileTestLine(split->file_index, *line_index, line));
  328. return true;
  329. }
  330. // Converts a `FileCheck`-style expectation string into a single complete regex
  331. // string by escaping all regex characters outside of the designated `{{...}}`
  332. // regex sequences, and switching those to a normal regex sub-pattern syntax.
  333. static auto ConvertExpectationStringToRegex(std::string& str) -> void {
  334. for (int pos = 0; pos < static_cast<int>(str.size());) {
  335. switch (str[pos]) {
  336. case '(':
  337. case ')':
  338. case '[':
  339. case ']':
  340. case '}':
  341. case '.':
  342. case '^':
  343. case '$':
  344. case '*':
  345. case '+':
  346. case '?':
  347. case '|':
  348. case '\\': {
  349. // Escape regex characters.
  350. str.insert(pos, "\\");
  351. pos += 2;
  352. break;
  353. }
  354. case '{': {
  355. if (pos + 1 == static_cast<int>(str.size()) || str[pos + 1] != '{') {
  356. // Single `{`, escape it.
  357. str.insert(pos, "\\");
  358. pos += 2;
  359. break;
  360. }
  361. // Replace the `{{...}}` regex syntax with standard `(...)` syntax.
  362. str.replace(pos, 2, "(");
  363. for (++pos; pos < static_cast<int>(str.size() - 1); ++pos) {
  364. if (str[pos] == '}' && str[pos + 1] == '}') {
  365. str.replace(pos, 2, ")");
  366. ++pos;
  367. break;
  368. }
  369. }
  370. break;
  371. }
  372. default: {
  373. ++pos;
  374. }
  375. }
  376. }
  377. }
  378. // Transforms an expectation on a given line from `FileCheck` syntax into a
  379. // standard regex matcher.
  380. static auto TransformExpectation(int line_index, llvm::StringRef in)
  381. -> ErrorOr<Matcher<std::string>> {
  382. if (in.empty()) {
  383. return Matcher<std::string>{StrEq("")};
  384. }
  385. if (!in.consume_front(" ")) {
  386. return ErrorBuilder() << "Malformated CHECK line: " << in;
  387. }
  388. // Check early if we have a regex component as we can avoid building an
  389. // expensive matcher when not using those.
  390. bool has_regex = in.find("{{") != llvm::StringRef::npos;
  391. // Now scan the string and expand any keywords. Note that this needs to be
  392. // `size_t` to correctly store `npos`.
  393. size_t keyword_pos = in.find("[[");
  394. // If there are neither keywords nor regex sequences, we can match the
  395. // incoming string directly.
  396. if (!has_regex && keyword_pos == llvm::StringRef::npos) {
  397. return Matcher<std::string>{StrEq(in)};
  398. }
  399. std::string str = in.str();
  400. // First expand the keywords.
  401. while (keyword_pos != std::string::npos) {
  402. llvm::StringRef line_keyword_cursor =
  403. llvm::StringRef(str).substr(keyword_pos);
  404. CARBON_CHECK(line_keyword_cursor.consume_front("[["));
  405. static constexpr llvm::StringLiteral LineKeyword = "@LINE";
  406. if (!line_keyword_cursor.consume_front(LineKeyword)) {
  407. return ErrorBuilder()
  408. << "Unexpected [[, should be {{\\[\\[}} at `"
  409. << line_keyword_cursor.substr(0, 5) << "` in: " << in;
  410. }
  411. // Allow + or - here; consumeInteger handles -.
  412. line_keyword_cursor.consume_front("+");
  413. int offset;
  414. // consumeInteger returns true for errors, not false.
  415. if (line_keyword_cursor.consumeInteger(10, offset) ||
  416. !line_keyword_cursor.consume_front("]]")) {
  417. return ErrorBuilder()
  418. << "Unexpected @LINE offset at `"
  419. << line_keyword_cursor.substr(0, 5) << "` in: " << in;
  420. }
  421. std::string int_str = llvm::Twine(line_index + offset).str();
  422. int remove_len = (line_keyword_cursor.data() - str.data()) - keyword_pos;
  423. str.replace(keyword_pos, remove_len, int_str);
  424. keyword_pos += int_str.size();
  425. // Find the next keyword start or the end of the string.
  426. keyword_pos = str.find("[[", keyword_pos);
  427. }
  428. // If there was no regex, we can directly match the adjusted string.
  429. if (!has_regex) {
  430. return Matcher<std::string>{StrEq(str)};
  431. }
  432. // Otherwise, we need to turn the entire string into a regex by escaping
  433. // things outside the regex region and transforming the regex region into a
  434. // normal syntax.
  435. ConvertExpectationStringToRegex(str);
  436. return Matcher<std::string>{MatchesRegex(str)};
  437. }
  438. // Once all content is processed, do any remaining split processing.
  439. static auto FinishSplit(llvm::StringRef test_name, SplitState* split,
  440. llvm::SmallVector<TestFile::Split>* file_splits)
  441. -> ErrorOr<Success> {
  442. if (split->has_splits()) {
  443. return AddSplit(split->filename, &split->content, file_splits);
  444. } else {
  445. // If no file splitting happened, use the main file as the test file.
  446. // There will always be a `/` unless tests are in the repo root.
  447. return AddSplit(test_name.drop_front(test_name.rfind("/") + 1),
  448. &split->content, file_splits);
  449. }
  450. }
  451. // Process CHECK lines when found. Returns true if the line is consumed.
  452. static auto TryConsumeCheck(
  453. bool running_autoupdate, int line_index, llvm::StringRef line,
  454. llvm::StringRef line_trimmed,
  455. llvm::SmallVector<testing::Matcher<std::string>>* expected_stdout,
  456. llvm::SmallVector<testing::Matcher<std::string>>* expected_stderr)
  457. -> ErrorOr<bool> {
  458. if (!line_trimmed.consume_front("// CHECK")) {
  459. return false;
  460. }
  461. // Don't build expectations when doing an autoupdate. We don't want to
  462. // break the autoupdate on an invalid CHECK line.
  463. if (!running_autoupdate) {
  464. llvm::SmallVector<Matcher<std::string>>* expected;
  465. if (line_trimmed.consume_front(":STDOUT:")) {
  466. expected = expected_stdout;
  467. } else if (line_trimmed.consume_front(":STDERR:")) {
  468. expected = expected_stderr;
  469. } else {
  470. return ErrorBuilder() << "Unexpected CHECK in input: " << line.str();
  471. }
  472. CARBON_ASSIGN_OR_RETURN(Matcher<std::string> check_matcher,
  473. TransformExpectation(line_index, line_trimmed));
  474. expected->push_back(check_matcher);
  475. }
  476. return true;
  477. }
  478. // Processes ARGS and EXTRA-ARGS lines when found. Returns true if the line is
  479. // consumed.
  480. static auto TryConsumeArgs(llvm::StringRef line, llvm::StringRef line_trimmed,
  481. llvm::SmallVector<std::string>* args,
  482. llvm::SmallVector<std::string>* extra_args)
  483. -> ErrorOr<bool> {
  484. llvm::SmallVector<std::string>* arg_list = nullptr;
  485. if (line_trimmed.consume_front("// ARGS: ")) {
  486. arg_list = args;
  487. } else if (line_trimmed.consume_front("// EXTRA-ARGS: ")) {
  488. arg_list = extra_args;
  489. } else {
  490. return false;
  491. }
  492. if (!args->empty() || !extra_args->empty()) {
  493. return ErrorBuilder() << "ARGS / EXTRA-ARGS specified multiple times: "
  494. << line.str();
  495. }
  496. // Split the line into arguments.
  497. std::pair<llvm::StringRef, llvm::StringRef> cursor =
  498. llvm::getToken(line_trimmed);
  499. while (!cursor.first.empty()) {
  500. arg_list->push_back(std::string(cursor.first));
  501. cursor = llvm::getToken(cursor.second);
  502. }
  503. return true;
  504. }
  505. static auto TryConsumeIncludeFile(
  506. llvm::StringRef line_trimmed,
  507. llvm::SmallVector<TestFile::Split>* include_files) -> ErrorOr<bool> {
  508. if (!line_trimmed.consume_front("// INCLUDE-FILE: ")) {
  509. return false;
  510. }
  511. std::filesystem::path path = std::string(line_trimmed);
  512. CARBON_ASSIGN_OR_RETURN(std::string content, ReadFile(path));
  513. include_files->push_back(
  514. {.filename = std::filesystem::path("include_files") / path.filename(),
  515. .content = content});
  516. return true;
  517. }
  518. // Processes AUTOUPDATE lines when found. Returns true if the line is consumed.
  519. static auto TryConsumeAutoupdate(int line_index, llvm::StringRef line_trimmed,
  520. bool* found_autoupdate,
  521. std::optional<int>* autoupdate_line_number)
  522. -> ErrorOr<bool> {
  523. static constexpr llvm::StringLiteral Autoupdate = "// AUTOUPDATE";
  524. static constexpr llvm::StringLiteral NoAutoupdate = "// NOAUTOUPDATE";
  525. if (line_trimmed != Autoupdate && line_trimmed != NoAutoupdate) {
  526. return false;
  527. }
  528. if (*found_autoupdate) {
  529. return Error("Multiple AUTOUPDATE/NOAUTOUPDATE settings found");
  530. }
  531. *found_autoupdate = true;
  532. if (line_trimmed == Autoupdate) {
  533. *autoupdate_line_number = line_index;
  534. }
  535. return true;
  536. }
  537. // Processes SET-* lines when found. Returns true if the line is consumed.
  538. static auto TryConsumeSetFlag(llvm::StringRef line_trimmed,
  539. llvm::StringLiteral flag_name, bool* flag)
  540. -> ErrorOr<bool> {
  541. if (!line_trimmed.consume_front("// ") || line_trimmed != flag_name) {
  542. return false;
  543. }
  544. if (*flag) {
  545. return ErrorBuilder() << flag_name << " was specified multiple times";
  546. }
  547. *flag = true;
  548. return true;
  549. }
  550. auto ProcessTestFile(llvm::StringRef test_name, bool running_autoupdate)
  551. -> ErrorOr<TestFile> {
  552. TestFile test_file;
  553. // Store the file so that file_splits can use references to content.
  554. CARBON_ASSIGN_OR_RETURN(test_file.input_content, ReadFile(test_name.str()));
  555. // Original file content, and a cursor for walking through it.
  556. llvm::StringRef file_content = test_file.input_content;
  557. llvm::StringRef cursor = file_content;
  558. // Whether either AUTOUDPATE or NOAUTOUPDATE was found.
  559. bool found_autoupdate = false;
  560. // The index in the current test file. Will be reset on splits.
  561. int line_index = 0;
  562. SplitState split;
  563. // When autoupdating, we track whether we're inside conflict markers.
  564. // Otherwise conflict markers are errors.
  565. bool inside_conflict_marker = false;
  566. while (!cursor.empty()) {
  567. auto [line, next_cursor] = cursor.split("\n");
  568. cursor = next_cursor;
  569. auto line_trimmed = line.ltrim();
  570. bool is_consumed = false;
  571. CARBON_ASSIGN_OR_RETURN(
  572. is_consumed,
  573. TryConsumeConflictMarker(running_autoupdate, line, line_trimmed,
  574. &inside_conflict_marker));
  575. if (is_consumed) {
  576. continue;
  577. }
  578. // At this point, remaining lines are part of the test input.
  579. CARBON_ASSIGN_OR_RETURN(
  580. is_consumed,
  581. TryConsumeSplit(line, line_trimmed, found_autoupdate, &line_index,
  582. &split, &test_file.file_splits,
  583. &test_file.non_check_lines));
  584. if (is_consumed) {
  585. continue;
  586. }
  587. ++line_index;
  588. // TIP lines have no impact on validation.
  589. if (line_trimmed.starts_with("// TIP:")) {
  590. continue;
  591. }
  592. CARBON_ASSIGN_OR_RETURN(
  593. is_consumed, TryConsumeCheck(running_autoupdate, line_index, line,
  594. line_trimmed, &test_file.expected_stdout,
  595. &test_file.expected_stderr));
  596. if (is_consumed) {
  597. continue;
  598. }
  599. // At this point, lines are retained as non-CHECK lines.
  600. test_file.non_check_lines.push_back(
  601. FileTestLine(split.file_index, line_index, line));
  602. CARBON_ASSIGN_OR_RETURN(
  603. is_consumed, TryConsumeArgs(line, line_trimmed, &test_file.test_args,
  604. &test_file.extra_args));
  605. if (is_consumed) {
  606. continue;
  607. }
  608. CARBON_ASSIGN_OR_RETURN(
  609. is_consumed,
  610. TryConsumeIncludeFile(line_trimmed, &test_file.include_file_splits));
  611. if (is_consumed) {
  612. continue;
  613. }
  614. CARBON_ASSIGN_OR_RETURN(
  615. is_consumed,
  616. TryConsumeAutoupdate(line_index, line_trimmed, &found_autoupdate,
  617. &test_file.autoupdate_line_number));
  618. if (is_consumed) {
  619. continue;
  620. }
  621. CARBON_ASSIGN_OR_RETURN(
  622. is_consumed,
  623. TryConsumeSetFlag(line_trimmed, "SET-CAPTURE-CONSOLE-OUTPUT",
  624. &test_file.capture_console_output));
  625. if (is_consumed) {
  626. continue;
  627. }
  628. CARBON_ASSIGN_OR_RETURN(is_consumed,
  629. TryConsumeSetFlag(line_trimmed, "SET-CHECK-SUBSET",
  630. &test_file.check_subset));
  631. if (is_consumed) {
  632. continue;
  633. }
  634. }
  635. if (!found_autoupdate) {
  636. return ErrorBuilder() << "Missing AUTOUPDATE/NOAUTOUPDATE setting: "
  637. << test_name;
  638. }
  639. test_file.has_splits = split.has_splits();
  640. CARBON_RETURN_IF_ERROR(
  641. FinishSplit(test_name, &split, &test_file.file_splits));
  642. // Validate AUTOUPDATE-SPLIT use, and remove it from test files if present.
  643. if (test_file.has_splits) {
  644. constexpr llvm::StringLiteral AutoupdateSplit = "AUTOUPDATE-SPLIT";
  645. for (const auto& test_file :
  646. llvm::ArrayRef(test_file.file_splits).drop_back()) {
  647. if (test_file.filename == AutoupdateSplit) {
  648. return Error("AUTOUPDATE-SPLIT must be the last split");
  649. }
  650. }
  651. if (test_file.file_splits.back().filename == AutoupdateSplit) {
  652. if (!test_file.autoupdate_line_number) {
  653. return Error("AUTOUPDATE-SPLIT requires AUTOUPDATE");
  654. }
  655. test_file.autoupdate_split = true;
  656. test_file.file_splits.pop_back();
  657. }
  658. }
  659. // Assume there is always a suffix `\n` in output.
  660. if (!test_file.expected_stdout.empty()) {
  661. test_file.expected_stdout.push_back(StrEq(""));
  662. }
  663. if (!test_file.expected_stderr.empty()) {
  664. test_file.expected_stderr.push_back(StrEq(""));
  665. }
  666. return std::move(test_file);
  667. }
  668. } // namespace Carbon::Testing