test_file.cpp 26 KB

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