autoupdate.cpp 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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/StringExtras.h"
  11. #include "llvm/Support/FormatVariadic.h"
  12. #include "re2/re2.h"
  13. namespace Carbon::Testing {
  14. // Put helper classes in an anonymous namespace.
  15. namespace {
  16. class CheckLine : public FileTestLineBase {
  17. public:
  18. // RE2 is passed by a pointer because it doesn't support std::optional.
  19. explicit CheckLine(int line_number, const RE2* line_number_re,
  20. std::string line)
  21. : FileTestLineBase(line_number),
  22. line_number_re_(line_number_re),
  23. line_(std::move(line)) {}
  24. auto Print(llvm::raw_ostream& out) const -> void override {
  25. out << indent_ << line_;
  26. }
  27. // When the location of the CHECK in output is known, we can set the indent
  28. // and its line.
  29. auto SetOutputLine(llvm::StringRef indent, int output_line_number) -> void {
  30. indent_ = indent;
  31. output_line_number_ = output_line_number;
  32. }
  33. // When the location of all lines in a file are known, we can set the line
  34. // offset based on the target line.
  35. auto SetRemappedLine(const std::string& sub_for_formatv,
  36. int target_line_number) -> void {
  37. // Should only be called when we have a regex.
  38. CARBON_CHECK(line_number_re_);
  39. int offset = target_line_number - output_line_number_;
  40. const char* offset_prefix = offset < 0 ? "" : "+";
  41. std::string replacement =
  42. llvm::formatv(sub_for_formatv.data(),
  43. llvm::formatv("[[@LINE{0}{1}]]", offset_prefix, offset));
  44. RE2::Replace(&line_, *line_number_re_, replacement);
  45. }
  46. private:
  47. const RE2* line_number_re_;
  48. std::string line_;
  49. llvm::StringRef indent_;
  50. int output_line_number_ = -1;
  51. };
  52. } // namespace
  53. // Adds output lines for autoupdate.
  54. static auto AddCheckLines(
  55. llvm::StringRef output, const char* label,
  56. const llvm::SmallVector<llvm::StringRef>& filenames,
  57. bool line_number_re_has_file, const RE2& line_number_re,
  58. std::function<void(std::string&)> do_extra_check_replacements,
  59. llvm::SmallVector<llvm::SmallVector<CheckLine>>& check_lines) -> void {
  60. if (output.empty()) {
  61. return;
  62. }
  63. // Prepare to look for filenames in lines.
  64. llvm::StringRef current_filename = filenames[0];
  65. const auto* remaining_filenames = filenames.begin() + 1;
  66. // %t substitution means we may see TEST_TMPDIR in output.
  67. char* tmpdir_env = getenv("TEST_TMPDIR");
  68. CARBON_CHECK(tmpdir_env != nullptr);
  69. llvm::StringRef tmpdir = tmpdir_env;
  70. llvm::SmallVector<llvm::StringRef> lines(llvm::split(output, '\n'));
  71. // It's typical that output ends with a newline, but we don't want to add a
  72. // blank CHECK for it.
  73. if (lines.back().empty()) {
  74. lines.pop_back();
  75. }
  76. int append_to = 0;
  77. for (const auto& line : lines) {
  78. std::string check_line = llvm::formatv("// CHECK:{0}:{1}{2}", label,
  79. line.empty() ? "" : " ", line);
  80. // Ignore TEST_TMPDIR in output.
  81. if (auto pos = check_line.find(tmpdir); pos != std::string::npos) {
  82. check_line.replace(pos, tmpdir.size(), "{{.+}}");
  83. }
  84. do_extra_check_replacements(check_line);
  85. // Look for line information in the output. line_number is only set if the
  86. // match is correct.
  87. int line_number = -1;
  88. int match_line_number;
  89. if (line_number_re_has_file) {
  90. absl::string_view match_filename;
  91. if (RE2::PartialMatch(check_line, line_number_re, &match_filename,
  92. &match_line_number)) {
  93. llvm::StringRef match_filename_ref = match_filename;
  94. if (match_filename_ref != current_filename) {
  95. // If the filename doesn't match, it may be still usable if it refers
  96. // to a later file.
  97. const auto* pos = std::find(remaining_filenames, filenames.end(),
  98. match_filename_ref);
  99. if (pos != filenames.end()) {
  100. remaining_filenames = pos + 1;
  101. append_to = pos - filenames.begin();
  102. line_number = match_line_number;
  103. }
  104. } else {
  105. // The line applies to the current file.
  106. line_number = match_line_number;
  107. }
  108. }
  109. } else {
  110. // There's no file association, so we only look at the line.
  111. if (RE2::PartialMatch(check_line, line_number_re, &match_line_number)) {
  112. line_number = match_line_number;
  113. }
  114. }
  115. check_lines[append_to].push_back(
  116. CheckLine(line_number, line_number == -1 ? nullptr : &line_number_re,
  117. check_line));
  118. }
  119. }
  120. auto AutoupdateFileTest(
  121. const std::filesystem::path& file_test_path, llvm::StringRef input_content,
  122. const llvm::SmallVector<llvm::StringRef>& filenames,
  123. int autoupdate_line_number,
  124. llvm::SmallVector<llvm::SmallVector<FileTestLine>>& non_check_lines,
  125. llvm::StringRef stdout, llvm::StringRef stderr,
  126. FileTestLineNumberReplacement line_number_replacement,
  127. std::function<void(std::string&)> do_extra_check_replacements) -> bool {
  128. // Prepare CHECK lines.
  129. llvm::SmallVector<llvm::SmallVector<CheckLine>> check_lines;
  130. check_lines.resize(filenames.size());
  131. RE2 line_number_re(line_number_replacement.pattern);
  132. CARBON_CHECK(line_number_re.ok()) << "Invalid line replacement RE2: `"
  133. << line_number_replacement.pattern << "`";
  134. AddCheckLines(stdout, "STDOUT", filenames, line_number_replacement.has_file,
  135. line_number_re, do_extra_check_replacements, check_lines);
  136. AddCheckLines(stderr, "STDERR", filenames, line_number_replacement.has_file,
  137. line_number_re, do_extra_check_replacements, check_lines);
  138. // All CHECK lines are suppressed until we reach AUTOUPDATE.
  139. bool reached_autoupdate = false;
  140. // Stitch together content.
  141. llvm::SmallVector<const FileTestLineBase*> new_lines;
  142. for (auto [filename, non_check_file, check_file] :
  143. llvm::zip(filenames, non_check_lines, check_lines)) {
  144. llvm::DenseMap<int, int> output_line_remap;
  145. int output_line_number = 0;
  146. auto* check_line = check_file.begin();
  147. // Looping through the original file, print check lines preceding each
  148. // original line.
  149. for (const auto& non_check_line : non_check_file) {
  150. // If there are any non-check lines with an invalid line_number, it's
  151. // something like a split directive which shouldn't increment
  152. // output_line_number.
  153. if (non_check_line.line_number() < 1) {
  154. new_lines.push_back(&non_check_line);
  155. continue;
  156. }
  157. if (reached_autoupdate) {
  158. for (; check_line != check_file.end() &&
  159. check_line->line_number() <= non_check_line.line_number();
  160. ++check_line) {
  161. new_lines.push_back(check_line);
  162. check_line->SetOutputLine(non_check_line.indent(),
  163. ++output_line_number);
  164. }
  165. } else if (autoupdate_line_number == non_check_line.line_number()) {
  166. // This is the AUTOUPDATE line, so we'll print it, then start printing
  167. // CHECK lines.
  168. reached_autoupdate = true;
  169. }
  170. new_lines.push_back(&non_check_line);
  171. CARBON_CHECK(
  172. output_line_remap
  173. .insert({non_check_line.line_number(), ++output_line_number})
  174. .second);
  175. }
  176. // This should always be true after the first file is processed.
  177. CARBON_CHECK(reached_autoupdate);
  178. // Print remaining check lines which -- for whatever reason -- come after
  179. // all original lines.
  180. for (; check_line != check_file.end(); ++check_line) {
  181. new_lines.push_back(check_line);
  182. check_line->SetOutputLine("", ++output_line_number);
  183. }
  184. // Update all remapped lines in CHECK output.
  185. for (auto& offset_check_line : check_file) {
  186. if (offset_check_line.line_number() >= 1) {
  187. auto new_line = output_line_remap.find(offset_check_line.line_number());
  188. CARBON_CHECK(new_line != output_line_remap.end());
  189. offset_check_line.SetRemappedLine(
  190. line_number_replacement.sub_for_formatv, new_line->second);
  191. }
  192. }
  193. }
  194. // Generate the autoupdated file.
  195. std::string new_content;
  196. llvm::raw_string_ostream new_content_stream(new_content);
  197. for (const auto& line : new_lines) {
  198. line->Print(new_content_stream);
  199. new_content_stream << '\n';
  200. }
  201. // Update the file on disk if needed.
  202. if (new_content == input_content) {
  203. return false;
  204. }
  205. std::ofstream out(file_test_path);
  206. out << new_content;
  207. return true;
  208. }
  209. } // namespace Carbon::Testing