autoupdate.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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/autoupdate.h"
  5. #include <fstream>
  6. #include "absl/strings/string_view.h"
  7. #include "common/check.h"
  8. #include "common/ostream.h"
  9. #include "llvm/ADT/DenseMap.h"
  10. #include "llvm/ADT/STLFunctionalExtras.h"
  11. #include "llvm/ADT/StringExtras.h"
  12. #include "llvm/Support/FormatVariadic.h"
  13. namespace Carbon::Testing {
  14. // Put helper classes in an anonymous namespace.
  15. namespace {
  16. // Converts a matched line number to an int, trimming whitespace.
  17. static auto ParseLineNumber(absl::string_view matched_line_number) -> int {
  18. llvm::StringRef trimmed = matched_line_number;
  19. trimmed = trimmed.trim();
  20. // NOLINTNEXTLINE(google-runtime-int): API requirement.
  21. long long val;
  22. CARBON_CHECK(!llvm::getAsSignedInteger(trimmed, 10, val))
  23. << matched_line_number;
  24. return val;
  25. }
  26. // The file and line number that a CHECK line refers to, and the
  27. // replacement from which they were determined, if any.
  28. struct FileAndLineNumber {
  29. explicit FileAndLineNumber(int file_number) : file_number(file_number) {}
  30. explicit FileAndLineNumber(const FileTestLineNumberReplacement* replacement,
  31. int file_number, absl::string_view line_number)
  32. : replacement(replacement),
  33. file_number(file_number),
  34. line_number(ParseLineNumber(line_number)) {}
  35. const FileTestLineNumberReplacement* replacement = nullptr;
  36. int file_number;
  37. int line_number = -1;
  38. };
  39. class CheckLine : public FileTestLineBase {
  40. public:
  41. // RE2 is passed by a pointer because it doesn't support std::optional.
  42. explicit CheckLine(FileAndLineNumber file_and_line_number, std::string line)
  43. : FileTestLineBase(file_and_line_number.line_number),
  44. file_number_(file_and_line_number.file_number),
  45. replacement_(file_and_line_number.replacement),
  46. line_(std::move(line)) {}
  47. auto Print(llvm::raw_ostream& out) const -> void override {
  48. out << indent_ << line_;
  49. }
  50. // When the location of the CHECK in output is known, we can set the indent
  51. // and its line.
  52. auto SetOutputLine(llvm::StringRef indent, int output_file_number,
  53. int output_line_number) -> void {
  54. indent_ = indent;
  55. output_file_number_ = output_file_number;
  56. output_line_number_ = output_line_number;
  57. }
  58. // When the location of all lines in a file are known, we can set the line
  59. // offset based on the target line.
  60. auto RemapLineNumbers(
  61. const llvm::DenseMap<std::pair<int, int>, int>& output_line_remap,
  62. const llvm::SmallVector<int>& new_last_line_numbers) -> void {
  63. // Only need to do remappings when there's a line number replacement.
  64. if (!replacement_) {
  65. return;
  66. }
  67. bool found_one = false;
  68. // Use a cursor for the line so that we can't keep matching the same
  69. // content, which may occur when we keep a literal line number.
  70. int line_offset = 0;
  71. while (true) {
  72. // Rebuild the cursor each time because we're editing the line, which
  73. // could cause a reallocation.
  74. absl::string_view line_cursor = line_;
  75. line_cursor.remove_prefix(line_offset);
  76. // Look for a line number to replace. There may be multiple, so we
  77. // repeatedly check.
  78. absl::string_view matched_line_number;
  79. if (replacement_->has_file) {
  80. RE2::PartialMatch(line_cursor, *replacement_->re, nullptr,
  81. &matched_line_number);
  82. } else {
  83. RE2::PartialMatch(line_cursor, *replacement_->re, &matched_line_number);
  84. }
  85. if (matched_line_number.empty()) {
  86. CARBON_CHECK(found_one) << line_;
  87. return;
  88. }
  89. found_one = true;
  90. // Update the cursor offset from the match.
  91. line_offset = matched_line_number.begin() - line_.c_str();
  92. // Calculate the new line number (possibly with new CHECK lines added, or
  93. // some removed).
  94. int old_line_number = ParseLineNumber(matched_line_number);
  95. int new_line_number = -1;
  96. if (auto remapped =
  97. output_line_remap.find({file_number_, old_line_number});
  98. remapped != output_line_remap.end()) {
  99. // Map old non-check lines to their new line numbers.
  100. new_line_number = remapped->second;
  101. } else {
  102. // We assume unmapped references point to the end-of-file.
  103. new_line_number = new_last_line_numbers[file_number_];
  104. }
  105. std::string replacement;
  106. if (output_file_number_ == file_number_) {
  107. int offset = new_line_number - output_line_number_;
  108. // Update the line offset in the CHECK line.
  109. const char* offset_prefix = offset < 0 ? "" : "+";
  110. replacement = llvm::formatv(
  111. replacement_->line_formatv.c_str(),
  112. llvm::formatv("[[@LINE{0}{1}]]", offset_prefix, offset));
  113. } else {
  114. // If the CHECK was written to a different file from the file that it
  115. // refers to, leave behind an absolute line reference rather than a
  116. // cross-file offset.
  117. replacement =
  118. llvm::formatv(replacement_->line_formatv.c_str(), new_line_number);
  119. }
  120. line_.replace(matched_line_number.data() - line_.data(),
  121. matched_line_number.size(), replacement);
  122. }
  123. }
  124. auto file_number() const -> int { return file_number_; }
  125. auto is_blank() const -> bool override { return false; }
  126. private:
  127. int file_number_;
  128. const FileTestLineNumberReplacement* replacement_;
  129. std::string line_;
  130. llvm::StringRef indent_;
  131. int output_file_number_ = -1;
  132. int output_line_number_ = -1;
  133. };
  134. } // namespace
  135. // Looks for the patterns in the line. Returns the first match, or defaulted
  136. // information if not found.
  137. static auto GetFileAndLineNumber(
  138. const llvm::SmallVector<FileTestLineNumberReplacement>& replacements,
  139. llvm::DenseMap<llvm::StringRef, int> file_to_number_map,
  140. int default_file_number, const std::string& check_line)
  141. -> FileAndLineNumber {
  142. for (const auto& replacement : replacements) {
  143. if (replacement.has_file) {
  144. absl::string_view filename;
  145. absl::string_view line_number;
  146. if (RE2::PartialMatch(check_line, *replacement.re, &filename,
  147. &line_number)) {
  148. if (auto it = file_to_number_map.find(filename);
  149. it != file_to_number_map.end()) {
  150. return FileAndLineNumber(&replacement, it->second, line_number);
  151. } else {
  152. return FileAndLineNumber(default_file_number);
  153. }
  154. }
  155. } else {
  156. // There's no file association, so we only look at the line, and assume
  157. // it refers to the default file.
  158. absl::string_view line_number;
  159. if (RE2::PartialMatch(check_line, *replacement.re, &line_number)) {
  160. return FileAndLineNumber(&replacement, default_file_number,
  161. line_number);
  162. }
  163. }
  164. }
  165. return FileAndLineNumber(default_file_number);
  166. }
  167. // Builds CheckLine lists for autoupdate.
  168. static auto BuildCheckLines(
  169. llvm::StringRef output, const char* label,
  170. const llvm::SmallVector<llvm::StringRef>& filenames,
  171. const std::optional<RE2>& default_file_re,
  172. const llvm::SmallVector<FileTestLineNumberReplacement>& replacements,
  173. std::function<void(std::string&)> do_extra_check_replacements)
  174. -> llvm::SmallVector<CheckLine> {
  175. llvm::SmallVector<CheckLine> check_lines;
  176. if (output.empty()) {
  177. return check_lines;
  178. }
  179. // Prepare to look for filenames in lines.
  180. llvm::DenseMap<llvm::StringRef, int> file_to_number_map;
  181. for (auto [number, name] : llvm::enumerate(filenames)) {
  182. file_to_number_map.insert({name, number});
  183. }
  184. // %t substitution means we may see TEST_TMPDIR in output.
  185. char* tmpdir_env = getenv("TEST_TMPDIR");
  186. CARBON_CHECK(tmpdir_env != nullptr);
  187. llvm::StringRef tmpdir = tmpdir_env;
  188. llvm::SmallVector<llvm::StringRef> lines(llvm::split(output, '\n'));
  189. // It's typical that output ends with a newline, but we don't want to add a
  190. // blank CHECK for it.
  191. if (lines.back().empty()) {
  192. lines.pop_back();
  193. }
  194. // `{{` and `[[` are escaped as a regex matcher.
  195. RE2 double_brace_re(R"(\{\{)");
  196. RE2 double_square_bracket_re(R"(\[\[)");
  197. // End-of-line whitespace is replaced with a regex matcher to make it visible.
  198. RE2 end_of_line_whitespace_re(R"((\s+)$)");
  199. // The default file number for when no specific file is found.
  200. int default_file_number = 0;
  201. for (const auto& line : lines) {
  202. std::string check_line = llvm::formatv("// CHECK:{0}:{1}{2}", label,
  203. line.empty() ? "" : " ", line);
  204. RE2::Replace(&check_line, double_brace_re, R"({{\\{\\{}})");
  205. RE2::Replace(&check_line, double_square_bracket_re, R"({{\\[\\[}})");
  206. RE2::Replace(&check_line, end_of_line_whitespace_re, R"({{\1}})");
  207. // Ignore TEST_TMPDIR in output.
  208. if (auto pos = check_line.find(tmpdir); pos != std::string::npos) {
  209. check_line.replace(pos, tmpdir.size(), "{{.+}}");
  210. }
  211. do_extra_check_replacements(check_line);
  212. if (default_file_re) {
  213. absl::string_view filename;
  214. if (RE2::PartialMatch(line, *default_file_re, &filename)) {
  215. auto it = file_to_number_map.find(filename);
  216. CARBON_CHECK(it != file_to_number_map.end())
  217. << "default_file_re had unexpected match in '" << line << "' (`"
  218. << default_file_re->pattern() << "`)";
  219. default_file_number = it->second;
  220. }
  221. }
  222. auto file_and_line = GetFileAndLineNumber(replacements, file_to_number_map,
  223. default_file_number, check_line);
  224. check_lines.push_back(CheckLine(file_and_line, check_line));
  225. }
  226. return check_lines;
  227. }
  228. auto AutoupdateFileTest(
  229. const std::filesystem::path& file_test_path, llvm::StringRef input_content,
  230. const llvm::SmallVector<llvm::StringRef>& filenames,
  231. int autoupdate_line_number,
  232. const llvm::SmallVector<llvm::SmallVector<FileTestLine>>& non_check_lines,
  233. llvm::StringRef stdout, llvm::StringRef stderr,
  234. const std::optional<RE2>& default_file_re,
  235. const llvm::SmallVector<FileTestLineNumberReplacement>&
  236. line_number_replacements,
  237. std::function<void(std::string&)> do_extra_check_replacements) -> bool {
  238. for (const auto& replacement : line_number_replacements) {
  239. CARBON_CHECK(replacement.has_file || default_file_re)
  240. << "For replacement with pattern `" << replacement.re->pattern()
  241. << "` to have has_file=false, override GetDefaultFileRE.";
  242. CARBON_CHECK(replacement.re->ok())
  243. << "Invalid line replacement RE2: " << replacement.re->error();
  244. }
  245. // Prepare CHECK lines.
  246. llvm::SmallVector<CheckLine> stdout_check_lines =
  247. BuildCheckLines(stdout, "STDOUT", filenames, default_file_re,
  248. line_number_replacements, do_extra_check_replacements);
  249. llvm::SmallVector<CheckLine> stderr_check_lines =
  250. BuildCheckLines(stderr, "STDERR", filenames, default_file_re,
  251. line_number_replacements, do_extra_check_replacements);
  252. auto* stdout_check_line = stdout_check_lines.begin();
  253. auto* stderr_check_line = stderr_check_lines.begin();
  254. bool any_attached_stdout_lines = std::any_of(
  255. stdout_check_lines.begin(), stdout_check_lines.end(),
  256. [&](const CheckLine& line) { return line.line_number() != -1; });
  257. // All CHECK lines are suppressed until we reach AUTOUPDATE.
  258. bool reached_autoupdate = false;
  259. const FileTestLine blank_line(-1, "");
  260. // Maps {file_number, original line number} to a new line number.
  261. llvm::DenseMap<std::pair<int, int>, int> output_line_remap;
  262. // Tracks the new last line numbers for each file.
  263. llvm::SmallVector<int> new_last_line_numbers;
  264. new_last_line_numbers.reserve(filenames.size());
  265. // Stitch together content.
  266. llvm::SmallVector<const FileTestLineBase*> new_lines;
  267. for (auto [file_number_as_size_t, filename, non_check_file] :
  268. llvm::enumerate(filenames, non_check_lines)) {
  269. auto file_number = static_cast<int>(file_number_as_size_t);
  270. int output_line_number = 0;
  271. // Track the offset in new_lines to later determine the line count.
  272. int file_offset_in_new_lines = new_lines.size();
  273. // Add all check lines from the given vector until we reach a check line
  274. // attached to a line later than `to_line_number`.
  275. auto add_check_lines = [&](const llvm::SmallVector<CheckLine>& lines,
  276. CheckLine*& line, int to_line_number,
  277. llvm::StringRef indent) {
  278. for (; line != lines.end() && (line->file_number() < file_number ||
  279. (line->file_number() == file_number &&
  280. line->line_number() <= to_line_number));
  281. ++line) {
  282. new_lines.push_back(line);
  283. line->SetOutputLine(indent, file_number, ++output_line_number);
  284. }
  285. };
  286. // Looping through the original file, print check lines preceding each
  287. // original line.
  288. for (const auto& non_check_line : non_check_file) {
  289. // If there are any non-check lines with an invalid line_number, it's
  290. // something like a split directive which shouldn't increment
  291. // output_line_number.
  292. if (non_check_line.line_number() < 1) {
  293. // These are ignored when calculating line counts.
  294. CARBON_CHECK(file_offset_in_new_lines ==
  295. static_cast<int>(new_lines.size()));
  296. ++file_offset_in_new_lines;
  297. new_lines.push_back(&non_check_line);
  298. continue;
  299. }
  300. // STDERR check lines are placed before the line they refer to, or as
  301. // early as possible if they don't refer to a line. Include all STDERR
  302. // lines until we find one that wants to go later in the file.
  303. if (reached_autoupdate) {
  304. add_check_lines(stderr_check_lines, stderr_check_line,
  305. non_check_line.line_number(), non_check_line.indent());
  306. } else if (autoupdate_line_number == non_check_line.line_number()) {
  307. // This is the AUTOUPDATE line, so we'll print it, then start printing
  308. // CHECK lines.
  309. reached_autoupdate = true;
  310. }
  311. new_lines.push_back(&non_check_line);
  312. CARBON_CHECK(output_line_remap
  313. .insert({{file_number, non_check_line.line_number()},
  314. ++output_line_number})
  315. .second);
  316. // If we just added the AUTOUPDATE line, include any early STDERR lines
  317. // now, so that the initial batch of CHECK lines have STDERR before
  318. // STDOUT. This also ensures we don't insert a blank line before the
  319. // STDERR checks if there are no more lines after AUTOUPDATE.
  320. if (autoupdate_line_number == non_check_line.line_number()) {
  321. add_check_lines(stderr_check_lines, stderr_check_line,
  322. non_check_line.line_number(), non_check_line.indent());
  323. }
  324. // STDOUT check lines are placed after the line they refer to, or at the
  325. // end of the file if none of them refers to a line.
  326. if (reached_autoupdate && any_attached_stdout_lines) {
  327. add_check_lines(stdout_check_lines, stdout_check_line,
  328. non_check_line.line_number(), non_check_line.indent());
  329. }
  330. }
  331. // This should always be true after the first file is processed.
  332. CARBON_CHECK(reached_autoupdate);
  333. // At the end of each file, print any remaining lines which are associated
  334. // with the file.
  335. if ((stderr_check_line != stderr_check_lines.end() &&
  336. stderr_check_line->file_number() == file_number) ||
  337. (stdout_check_line != stdout_check_lines.end() &&
  338. stdout_check_line->file_number() == file_number)) {
  339. // Ensure there's a blank line before any trailing CHECKs.
  340. if (!new_lines.empty() && !new_lines.back()->is_blank()) {
  341. new_lines.push_back(&blank_line);
  342. ++output_line_number;
  343. }
  344. add_check_lines(stderr_check_lines, stderr_check_line, INT_MAX, "");
  345. add_check_lines(stdout_check_lines, stdout_check_line, INT_MAX, "");
  346. }
  347. new_last_line_numbers.push_back(new_lines.size() -
  348. file_offset_in_new_lines);
  349. }
  350. for (auto& check_line : stdout_check_lines) {
  351. check_line.RemapLineNumbers(output_line_remap, new_last_line_numbers);
  352. }
  353. for (auto& check_line : stderr_check_lines) {
  354. check_line.RemapLineNumbers(output_line_remap, new_last_line_numbers);
  355. }
  356. // Generate the autoupdated file.
  357. std::string new_content;
  358. llvm::raw_string_ostream new_content_stream(new_content);
  359. for (const auto& line : new_lines) {
  360. line->Print(new_content_stream);
  361. new_content_stream << '\n';
  362. }
  363. // Update the file on disk if needed.
  364. if (new_content == input_content) {
  365. return false;
  366. }
  367. std::ofstream out(file_test_path);
  368. out << new_content;
  369. return true;
  370. }
  371. } // namespace Carbon::Testing