autoupdate.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  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 <string>
  7. #include <utility>
  8. #include "absl/strings/str_replace.h"
  9. #include "absl/strings/string_view.h"
  10. #include "common/check.h"
  11. #include "common/ostream.h"
  12. #include "common/raw_string_ostream.h"
  13. #include "llvm/ADT/DenseMap.h"
  14. #include "llvm/ADT/StringExtras.h"
  15. #include "llvm/Support/FormatVariadic.h"
  16. #include "testing/base/file_helpers.h"
  17. namespace Carbon::Testing {
  18. // Converts a matched line number to an int, trimming whitespace. Returns 0 if
  19. // there is no line number, to assist early placement.
  20. static auto ParseLineNumber(absl::string_view matched_line_number) -> int {
  21. llvm::StringRef trimmed = matched_line_number;
  22. trimmed = trimmed.trim();
  23. if (trimmed.empty()) {
  24. return 0;
  25. }
  26. // NOLINTNEXTLINE(google-runtime-int): API requirement.
  27. long long val;
  28. CARBON_CHECK(!llvm::getAsSignedInteger(trimmed, 10, val), "{0}",
  29. matched_line_number);
  30. return val;
  31. }
  32. FileTestAutoupdater::FileAndLineNumber::FileAndLineNumber(
  33. const LineNumberReplacement* replacement, int file_number,
  34. absl::string_view line_number)
  35. : replacement(replacement),
  36. file_number(file_number),
  37. line_number(ParseLineNumber(line_number)) {}
  38. auto FileTestAutoupdater::CheckLine::RemapLineNumbers(
  39. const llvm::DenseMap<llvm::StringRef, int>& file_to_number_map,
  40. const llvm::DenseMap<std::pair<int, int>, int>& output_line_remap,
  41. const llvm::SmallVector<int>& new_last_line_numbers) -> void {
  42. // Only need to do remappings when there's a line number replacement.
  43. if (!replacement_) {
  44. return;
  45. }
  46. // Use a cursor for the line so that we can't keep matching the same
  47. // content, which may occur when we keep a literal line number.
  48. int line_offset = 0;
  49. while (true) {
  50. // Rebuild the cursor each time because we're editing the line, which
  51. // could cause a reallocation.
  52. absl::string_view line_cursor = line_;
  53. line_cursor.remove_prefix(line_offset);
  54. // Look for a line number to replace. There may be multiple, so we
  55. // repeatedly check.
  56. absl::string_view matched_filename;
  57. absl::string_view matched_line_number;
  58. if (replacement_->has_file) {
  59. RE2::PartialMatch(line_cursor, *replacement_->re, &matched_filename,
  60. &matched_line_number);
  61. } else {
  62. RE2::PartialMatch(line_cursor, *replacement_->re, &matched_line_number);
  63. }
  64. if (matched_line_number.empty()) {
  65. return;
  66. }
  67. // Map the matched filename to its file number.
  68. auto matched_file_number = file_number();
  69. if (replacement_->has_file) {
  70. auto it = file_to_number_map.find(matched_filename);
  71. if (it != file_to_number_map.end()) {
  72. matched_file_number = it->second;
  73. }
  74. }
  75. // Calculate the new line number (possibly with new CHECK lines added, or
  76. // some removed).
  77. int old_line_number = ParseLineNumber(matched_line_number);
  78. int new_line_number = -1;
  79. if (auto remapped =
  80. output_line_remap.find({matched_file_number, old_line_number});
  81. remapped != output_line_remap.end()) {
  82. // Map old non-check lines to their new line numbers.
  83. new_line_number = remapped->second;
  84. } else {
  85. // We assume unmapped references point to the end-of-file.
  86. new_line_number = new_last_line_numbers[matched_file_number];
  87. }
  88. std::string replacement;
  89. if (matched_file_number == output_file_number_) {
  90. int offset = new_line_number - output_line_number_;
  91. // Update the line offset in the CHECK line.
  92. const char* offset_prefix = offset < 0 ? "" : "+";
  93. replacement = llvm::formatv(
  94. replacement_->line_formatv.c_str(),
  95. llvm::formatv("[[@LINE{0}{1}]]", offset_prefix, offset));
  96. } else {
  97. // If the CHECK was written to a different file from the file that it
  98. // refers to, leave behind an absolute line reference rather than a
  99. // cross-file offset.
  100. replacement =
  101. llvm::formatv(replacement_->line_formatv.c_str(), new_line_number);
  102. }
  103. auto line_number_offset = matched_line_number.data() - line_.data();
  104. line_.replace(line_number_offset, matched_line_number.size(), replacement);
  105. // Resume matching from the end of the replacement line number.
  106. line_offset = line_number_offset + replacement.size();
  107. }
  108. }
  109. auto FileTestAutoupdater::GetFileAndLineNumber(
  110. const llvm::DenseMap<llvm::StringRef, int>& file_to_number_map,
  111. int default_file_number, const std::string& check_line)
  112. -> FileAndLineNumber {
  113. for (const auto& replacement : line_number_replacements_) {
  114. if (replacement.has_file) {
  115. absl::string_view filename;
  116. absl::string_view line_number;
  117. if (RE2::PartialMatch(check_line, *replacement.re, &filename,
  118. &line_number)) {
  119. if (auto it = file_to_number_map.find(filename);
  120. it != file_to_number_map.end()) {
  121. return FileAndLineNumber(&replacement, it->second, line_number);
  122. } else {
  123. return FileAndLineNumber(default_file_number);
  124. }
  125. }
  126. } else {
  127. // There's no file association, so we only look at the line, and assume
  128. // it refers to the default file.
  129. absl::string_view line_number;
  130. if (RE2::PartialMatch(check_line, *replacement.re, &line_number)) {
  131. return FileAndLineNumber(&replacement, default_file_number,
  132. line_number);
  133. }
  134. }
  135. }
  136. return FileAndLineNumber(default_file_number);
  137. }
  138. auto FileTestAutoupdater::BuildCheckLines(llvm::StringRef output,
  139. bool is_stderr) -> CheckLines {
  140. if (output.empty()) {
  141. return CheckLines({});
  142. }
  143. // %t substitution means we may see the temporary directory's path in output.
  144. std::filesystem::path tmpdir_path = GetTempDirectory();
  145. llvm::StringRef tmpdir = tmpdir_path.native();
  146. llvm::SmallVector<llvm::StringRef> lines(llvm::split(output, '\n'));
  147. // It's typical that output ends with a newline, but we don't want to add a
  148. // blank CHECK for it.
  149. if (lines.back().empty()) {
  150. lines.pop_back();
  151. }
  152. auto label =
  153. is_stderr ? llvm::StringLiteral("STDERR") : llvm::StringLiteral("STDOUT");
  154. // The default file number for when no specific file is found.
  155. int default_file_number = 0;
  156. CheckLineArray check_lines;
  157. for (const auto& line : lines) {
  158. // This code is relatively hot in our testing, and because when testing it
  159. // isn't run with an optimizer we benefit from making it use simple
  160. // constructs. For this reason, we avoid `llvm::formatv` and similar tools.
  161. std::string check_line;
  162. check_line.reserve(line.size() + label.size() + strlen("// CHECK:: "));
  163. check_line.append("// CHECK:");
  164. check_line.append(label);
  165. check_line.append(":");
  166. if (!line.empty()) {
  167. check_line.append(" ");
  168. check_line.append(line);
  169. }
  170. // \r and \t are invisible characters worth marking.
  171. // {{ and [[ are autoupdate syntax which we need to escape.
  172. check_line = absl::StrReplaceAll(check_line, {{"\r", R"({{\r}})"},
  173. {"\t", R"({{\t}})"},
  174. {"{{", R"({{\{\{}})"},
  175. {"[[", R"({{\[\[}})"}});
  176. // Add an empty regex to call out end-of-line whitespace.
  177. if (check_line.ends_with(' ')) {
  178. check_line.append("{{}}");
  179. }
  180. // Ignore mentions of the temporary directory in output.
  181. if (auto pos = check_line.find(tmpdir); pos != std::string::npos) {
  182. check_line.replace(pos, tmpdir.size(), "{{.+}}");
  183. }
  184. do_extra_check_replacements_(check_line);
  185. if (check_line.empty()) {
  186. continue;
  187. }
  188. if (default_file_re_) {
  189. absl::string_view filename;
  190. if (RE2::PartialMatch(line, *default_file_re_, &filename)) {
  191. auto it = file_to_number_map_.find(filename);
  192. CARBON_CHECK(it != file_to_number_map_.end(),
  193. "default_file_re had unexpected match in '{0}' (`{1}`)",
  194. line, default_file_re_->pattern());
  195. default_file_number = it->second;
  196. }
  197. }
  198. auto file_and_line = GetFileAndLineNumber(file_to_number_map_,
  199. default_file_number, check_line);
  200. check_lines.push_back(CheckLine(file_and_line, check_line));
  201. }
  202. finalize_check_lines_(check_lines, is_stderr);
  203. return CheckLines(check_lines);
  204. }
  205. auto FileTestAutoupdater::AddRemappedNonCheckLine() -> void {
  206. new_lines_.push_back(non_check_line_);
  207. CARBON_CHECK(output_line_remap_
  208. .insert({{non_check_line_->file_number(),
  209. non_check_line_->line_number()},
  210. ++output_line_number_})
  211. .second);
  212. }
  213. auto FileTestAutoupdater::AddTips() -> void {
  214. CARBON_CHECK(tips_.empty(), "Should only add tips once");
  215. tips_.reserve(4);
  216. // This puts commands on a single line so that they can be easily copied.
  217. tips_.emplace_back("// TIP: To test this file alone, run:");
  218. tips_.emplace_back("// TIP: " + test_command_);
  219. tips_.emplace_back("// TIP: To dump output, run:");
  220. tips_.emplace_back("// TIP: " + dump_command_);
  221. for (const auto& tip : tips_) {
  222. new_lines_.push_back(&tip);
  223. ++output_line_number_;
  224. }
  225. }
  226. auto FileTestAutoupdater::ShouldAddCheckLine(const CheckLines& check_lines,
  227. bool to_file_end) const -> bool {
  228. return !autoupdate_split_file_ &&
  229. check_lines.cursor != check_lines.lines.end() &&
  230. (check_lines.cursor->file_number() < output_file_number_ ||
  231. (check_lines.cursor->file_number() == output_file_number_ &&
  232. (to_file_end || check_lines.cursor->line_number() <=
  233. non_check_line_->line_number())));
  234. }
  235. auto FileTestAutoupdater::AddCheckLines(CheckLines& check_lines,
  236. bool to_file_end) -> void {
  237. for (; ShouldAddCheckLine(check_lines, to_file_end); ++check_lines.cursor) {
  238. new_lines_.push_back(check_lines.cursor);
  239. check_lines.cursor->SetOutputLine(
  240. to_file_end ? "" : non_check_line_->indent(), output_file_number_,
  241. ++output_line_number_);
  242. }
  243. }
  244. auto FileTestAutoupdater::FinishFile(bool is_last_file) -> void {
  245. bool include_stdout = any_attached_stdout_lines_ || is_last_file;
  246. // At the end of each file, print any remaining lines which are associated
  247. // with the file.
  248. if (ShouldAddCheckLine(stderr_, /*to_file_end=*/true) ||
  249. (include_stdout && ShouldAddCheckLine(stdout_, /*to_file_end=*/true))) {
  250. // Ensure there's a blank line before any trailing CHECKs.
  251. if (!new_lines_.empty() && !new_lines_.back()->is_blank()) {
  252. new_lines_.push_back(&blank_line_);
  253. ++output_line_number_;
  254. }
  255. AddCheckLines(stderr_, /*to_file_end=*/true);
  256. if (include_stdout) {
  257. AddCheckLines(stdout_, /*to_file_end=*/true);
  258. }
  259. }
  260. new_last_line_numbers_.push_back(output_line_number_);
  261. }
  262. auto FileTestAutoupdater::StartSplitFile() -> void {
  263. // Advance the file.
  264. ++output_file_number_;
  265. output_line_number_ = 0;
  266. CARBON_CHECK(output_file_number_ == non_check_line_->file_number(),
  267. "Non-sequential file: {0}", non_check_line_->file_number());
  268. // Each following file has precisely one split line.
  269. CARBON_CHECK(non_check_line_->line_number() < 1,
  270. "Expected a split line, got {0}", *non_check_line_);
  271. // The split line is ignored when calculating line counts.
  272. new_lines_.push_back(non_check_line_);
  273. // Add any file-specific but line-unattached STDOUT messages here. STDERR is
  274. // handled through the main loop because it's before the next line.
  275. if (any_attached_stdout_lines_) {
  276. AddCheckLines(stdout_, /*to_file_end=*/false);
  277. }
  278. ++non_check_line_;
  279. }
  280. auto FileTestAutoupdater::Run(bool dry_run) -> bool {
  281. // Print everything until the autoupdate line.
  282. while (non_check_line_->line_number() != autoupdate_line_number_) {
  283. CARBON_CHECK(non_check_line_ != non_check_lines_.end() &&
  284. non_check_line_->file_number() == 0,
  285. "Missed autoupdate?");
  286. AddRemappedNonCheckLine();
  287. ++non_check_line_;
  288. }
  289. // Add the AUTOUPDATE line along with any early STDERR lines, so that the
  290. // initial batch of CHECK lines have STDERR before STDOUT. This also ensures
  291. // we don't insert a blank line before the STDERR checks if there are no more
  292. // lines after AUTOUPDATE.
  293. AddRemappedNonCheckLine();
  294. AddTips();
  295. if (!autoupdate_split_file_) {
  296. AddCheckLines(stderr_, /*to_file_end=*/false);
  297. if (any_attached_stdout_lines_) {
  298. AddCheckLines(stdout_, /*to_file_end=*/false);
  299. }
  300. }
  301. ++non_check_line_;
  302. // Loop through remaining content.
  303. while (non_check_line_ != non_check_lines_.end()) {
  304. if (output_file_number_ < non_check_line_->file_number()) {
  305. FinishFile(/*is_last_file=*/false);
  306. StartSplitFile();
  307. if (output_file_number_ == autoupdate_split_file_) {
  308. break;
  309. }
  310. continue;
  311. }
  312. // STDERR check lines are placed before the line they refer to, or as
  313. // early as possible if they don't refer to a line. Include all STDERR
  314. // lines until we find one that wants to go later in the file.
  315. AddCheckLines(stderr_, /*to_file_end=*/false);
  316. AddRemappedNonCheckLine();
  317. // STDOUT check lines are placed after the line they refer to, or at the
  318. // end of the file if none of them refers to a line.
  319. if (any_attached_stdout_lines_) {
  320. AddCheckLines(stdout_, /*to_file_end=*/false);
  321. }
  322. ++non_check_line_;
  323. }
  324. // Clear out the autoupdate split, which would otherwise prevent check lines
  325. // being written to the autoupdate file. When autoupdate_split_file_ was set,
  326. // this will result in all check lines (and only check lines) being added to
  327. // the split by FinishFile. We don't use autoupdate_split_file_ past this
  328. // point.
  329. autoupdate_split_file_ = std::nullopt;
  330. FinishFile(/*is_last_file=*/true);
  331. for (auto& check_line : stdout_.lines) {
  332. check_line.RemapLineNumbers(file_to_number_map_, output_line_remap_,
  333. new_last_line_numbers_);
  334. }
  335. for (auto& check_line : stderr_.lines) {
  336. check_line.RemapLineNumbers(file_to_number_map_, output_line_remap_,
  337. new_last_line_numbers_);
  338. }
  339. // Generate the autoupdated file.
  340. RawStringOstream new_content_stream;
  341. for (const auto& line : new_lines_) {
  342. new_content_stream << *line << '\n';
  343. }
  344. std::string new_content = new_content_stream.TakeStr();
  345. // Update the file on disk if needed.
  346. if (new_content == input_content_) {
  347. return false;
  348. }
  349. if (!dry_run) {
  350. std::ofstream out(file_test_path_);
  351. out << new_content;
  352. }
  353. return true;
  354. }
  355. } // namespace Carbon::Testing