file_test_base.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  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/file_test_base.h"
  5. #include <filesystem>
  6. #include <fstream>
  7. #include <string>
  8. #include <utility>
  9. #include "absl/flags/flag.h"
  10. #include "absl/flags/parse.h"
  11. #include "common/check.h"
  12. #include "llvm/ADT/StringExtras.h"
  13. #include "llvm/ADT/Twine.h"
  14. #include "llvm/Support/FormatVariadic.h"
  15. #include "llvm/Support/InitLLVM.h"
  16. #include "llvm/Support/MemoryBuffer.h"
  17. ABSL_FLAG(std::vector<std::string>, file_tests, {},
  18. "A comma-separated list of repo-relative names of test files. "
  19. "Overrides test_targets_file.");
  20. ABSL_FLAG(std::string, test_targets_file, "",
  21. "A path to a file containing repo-relative names of test files.");
  22. ABSL_FLAG(bool, autoupdate, false,
  23. "Instead of verifying files match test output, autoupdate files "
  24. "based on test output.");
  25. namespace Carbon::Testing {
  26. using ::testing::Eq;
  27. using ::testing::Matcher;
  28. using ::testing::MatchesRegex;
  29. using ::testing::StrEq;
  30. // Reads a file to string.
  31. static auto ReadFile(std::string_view path) -> std::string {
  32. std::ifstream proto_file(path);
  33. std::stringstream buffer;
  34. buffer << proto_file.rdbuf();
  35. proto_file.close();
  36. return buffer.str();
  37. }
  38. // Splits outputs to string_view because gtest handles string_view by default.
  39. static auto SplitOutput(llvm::StringRef output)
  40. -> llvm::SmallVector<std::string_view> {
  41. if (output.empty()) {
  42. return {};
  43. }
  44. llvm::SmallVector<llvm::StringRef> lines;
  45. llvm::StringRef(output).split(lines, "\n");
  46. return llvm::SmallVector<std::string_view>(lines.begin(), lines.end());
  47. }
  48. // Runs a test and compares output. This keeps output split by line so that
  49. // issues are a little easier to identify by the different line.
  50. auto FileTestBase::TestBody() -> void {
  51. const char* target = getenv("TEST_TARGET");
  52. CARBON_CHECK(target);
  53. // This advice overrides the --file_tests flag provided by the file_test rule.
  54. llvm::errs() << "\nTo test this file alone, run:\n bazel test " << target
  55. << " --test_arg=--file_tests=" << test_name_ << "\n\n";
  56. TestContext context;
  57. auto run_result = ProcessTestFileAndRun(context);
  58. ASSERT_TRUE(run_result.ok()) << run_result.error();
  59. ValidateRun();
  60. auto test_filename = std::filesystem::path(test_name_.str()).filename();
  61. EXPECT_THAT(!llvm::StringRef(test_filename).starts_with("fail_"),
  62. Eq(context.exit_with_success))
  63. << "Tests should be prefixed with `fail_` if and only if running them "
  64. "is expected to fail.";
  65. // Check results.
  66. if (context.check_subset) {
  67. EXPECT_THAT(SplitOutput(context.stdout),
  68. IsSupersetOf(context.expected_stdout));
  69. EXPECT_THAT(SplitOutput(context.stderr),
  70. IsSupersetOf(context.expected_stderr));
  71. } else {
  72. EXPECT_THAT(SplitOutput(context.stdout),
  73. ElementsAreArray(context.expected_stdout));
  74. EXPECT_THAT(SplitOutput(context.stderr),
  75. ElementsAreArray(context.expected_stderr));
  76. }
  77. }
  78. auto FileTestBase::Autoupdate() -> ErrorOr<bool> {
  79. TestContext context;
  80. auto run_result = ProcessTestFileAndRun(context);
  81. if (!run_result.ok()) {
  82. return ErrorBuilder() << "Error updating " << test_name_ << ": "
  83. << run_result.error();
  84. }
  85. if (!context.autoupdate_line_number) {
  86. return false;
  87. }
  88. llvm::SmallVector<llvm::StringRef> filenames;
  89. filenames.reserve(context.non_check_lines.size());
  90. if (context.non_check_lines.size() > 1) {
  91. // There are splits, so we provide an empty name for the first file.
  92. filenames.push_back({});
  93. }
  94. for (const auto& file : context.test_files) {
  95. filenames.push_back(file.filename);
  96. }
  97. llvm::ArrayRef filenames_for_line_number = filenames;
  98. if (filenames.size() > 1) {
  99. filenames_for_line_number = filenames_for_line_number.drop_front();
  100. }
  101. return AutoupdateFileTest(
  102. std::filesystem::absolute(test_name_.str()), context.input_content,
  103. filenames, *context.autoupdate_line_number, context.non_check_lines,
  104. context.stdout, context.stderr,
  105. GetLineNumberReplacement(filenames_for_line_number),
  106. [&](std::string& line) { DoExtraCheckReplacements(line); });
  107. }
  108. auto FileTestBase::GetLineNumberReplacement(
  109. llvm::ArrayRef<llvm::StringRef> filenames) -> LineNumberReplacement {
  110. return {
  111. .has_file = true,
  112. .pattern = llvm::formatv(R"(({0}):(\d+))", llvm::join(filenames, "|")),
  113. .line_formatv = R"({0})"};
  114. }
  115. auto FileTestBase::ProcessTestFileAndRun(TestContext& context)
  116. -> ErrorOr<Success> {
  117. // Store the file so that test_files can use references to content.
  118. context.input_content = ReadFile(test_name_);
  119. // Load expected output.
  120. CARBON_RETURN_IF_ERROR(ProcessTestFile(context));
  121. // Process arguments.
  122. if (context.test_args.empty()) {
  123. context.test_args = GetDefaultArgs();
  124. }
  125. CARBON_RETURN_IF_ERROR(
  126. DoArgReplacements(context.test_args, context.test_files));
  127. // Pass arguments as StringRef.
  128. llvm::SmallVector<llvm::StringRef> test_args_ref;
  129. test_args_ref.reserve(context.test_args.size());
  130. for (const auto& arg : context.test_args) {
  131. test_args_ref.push_back(arg);
  132. }
  133. // Create the files in-memory.
  134. llvm::vfs::InMemoryFileSystem fs;
  135. for (const auto& test_file : context.test_files) {
  136. if (!fs.addFile(test_file.filename, /*ModificationTime=*/0,
  137. llvm::MemoryBuffer::getMemBuffer(
  138. test_file.content, test_file.filename,
  139. /*RequiresNullTerminator=*/false))) {
  140. return ErrorBuilder() << "File is repeated: " << test_file.filename;
  141. }
  142. }
  143. // Capture trace streaming, but only when in debug mode.
  144. llvm::raw_svector_ostream stdout(context.stdout);
  145. llvm::raw_svector_ostream stderr(context.stderr);
  146. CARBON_ASSIGN_OR_RETURN(context.exit_with_success,
  147. Run(test_args_ref, fs, stdout, stderr));
  148. return Success();
  149. }
  150. auto FileTestBase::DoArgReplacements(
  151. llvm::SmallVector<std::string>& test_args,
  152. const llvm::SmallVector<TestFile>& test_files) -> ErrorOr<Success> {
  153. for (auto* it = test_args.begin(); it != test_args.end(); ++it) {
  154. auto percent = it->find("%");
  155. if (percent == std::string::npos) {
  156. continue;
  157. }
  158. if (percent + 1 >= it->size()) {
  159. return ErrorBuilder() << "% is not allowed on its own: " << *it;
  160. }
  161. char c = (*it)[percent + 1];
  162. switch (c) {
  163. case 's': {
  164. if (*it != "%s") {
  165. return ErrorBuilder() << "%s must be the full argument: " << *it;
  166. }
  167. it = test_args.erase(it);
  168. for (const auto& file : test_files) {
  169. it = test_args.insert(it, file.filename);
  170. ++it;
  171. }
  172. // Back up once because the for loop will advance.
  173. --it;
  174. break;
  175. }
  176. case 't': {
  177. char* tmpdir = getenv("TEST_TMPDIR");
  178. CARBON_CHECK(tmpdir != nullptr);
  179. it->replace(percent, 2, llvm::formatv("{0}/temp_file", tmpdir));
  180. break;
  181. }
  182. default:
  183. return ErrorBuilder() << "%" << c << " is not supported: " << *it;
  184. }
  185. }
  186. return Success();
  187. }
  188. auto FileTestBase::ProcessTestFile(TestContext& context) -> ErrorOr<Success> {
  189. // Original file content, and a cursor for walking through it.
  190. llvm::StringRef file_content = context.input_content;
  191. llvm::StringRef cursor = file_content;
  192. // Whether content has been found, only updated before a file split is found
  193. // (which may be never).
  194. bool found_content_pre_split = false;
  195. // Whether either AUTOUDPATE or NOAUTOUPDATE was found.
  196. bool found_autoupdate = false;
  197. // The index in the current test file. Will be reset on splits.
  198. int line_index = 0;
  199. // The current file name, considering splits. Not set for the default file.
  200. llvm::StringRef current_file_name;
  201. // The current file's start.
  202. const char* current_file_start = nullptr;
  203. context.non_check_lines.resize(1);
  204. while (!cursor.empty()) {
  205. auto [line, next_cursor] = cursor.split("\n");
  206. cursor = next_cursor;
  207. auto line_trimmed = line.ltrim();
  208. static constexpr llvm::StringLiteral SplitPrefix = "// ---";
  209. if (line_trimmed.consume_front(SplitPrefix)) {
  210. if (!found_autoupdate) {
  211. // If there's a split, all output is appended at the end of each file
  212. // before AUTOUPDATE. We may want to change that, but it's not necessary
  213. // to handle right now.
  214. return ErrorBuilder()
  215. << "AUTOUPDATE/NOAUTOUPDATE setting must be in the first file.";
  216. }
  217. context.non_check_lines.push_back({FileTestLine(0, line)});
  218. // On a file split, add the previous file, then start a new one.
  219. if (current_file_start) {
  220. context.test_files.push_back(TestFile(
  221. current_file_name.str(),
  222. llvm::StringRef(current_file_start, line_trimmed.begin() -
  223. current_file_start -
  224. SplitPrefix.size())));
  225. } else if (found_content_pre_split) {
  226. // For the first split, we make sure there was no content prior.
  227. return ErrorBuilder()
  228. << "When using split files, there must be no content before the "
  229. "first split file.";
  230. }
  231. current_file_name = line_trimmed.trim();
  232. current_file_start = cursor.begin();
  233. line_index = 0;
  234. continue;
  235. } else if (!current_file_start && !line_trimmed.starts_with("//") &&
  236. !line_trimmed.empty()) {
  237. found_content_pre_split = true;
  238. }
  239. ++line_index;
  240. // Process expectations when found.
  241. if (line_trimmed.consume_front("// CHECK")) {
  242. // Don't build expectations when doing an autoupdate. We don't want to
  243. // break the autoupdate on an invalid CHECK line.
  244. if (!absl::GetFlag(FLAGS_autoupdate)) {
  245. llvm::SmallVector<Matcher<std::string>>* expected = nullptr;
  246. if (line_trimmed.consume_front(":STDOUT:")) {
  247. expected = &context.expected_stdout;
  248. } else if (line_trimmed.consume_front(":STDERR:")) {
  249. expected = &context.expected_stderr;
  250. } else {
  251. return ErrorBuilder() << "Unexpected CHECK in input: " << line.str();
  252. }
  253. CARBON_ASSIGN_OR_RETURN(Matcher<std::string> check_matcher,
  254. TransformExpectation(line_index, line_trimmed));
  255. expected->push_back(check_matcher);
  256. }
  257. } else {
  258. context.non_check_lines.back().push_back(FileTestLine(line_index, line));
  259. if (line_trimmed.consume_front("// ARGS: ")) {
  260. if (context.test_args.empty()) {
  261. // Split the line into arguments.
  262. std::pair<llvm::StringRef, llvm::StringRef> cursor =
  263. llvm::getToken(line_trimmed);
  264. while (!cursor.first.empty()) {
  265. context.test_args.push_back(std::string(cursor.first));
  266. cursor = llvm::getToken(cursor.second);
  267. }
  268. } else {
  269. return ErrorBuilder()
  270. << "ARGS was specified multiple times: " << line.str();
  271. }
  272. } else if (line_trimmed == "// AUTOUPDATE" ||
  273. line_trimmed == "// NOAUTOUPDATE") {
  274. if (found_autoupdate) {
  275. return ErrorBuilder()
  276. << "Multiple AUTOUPDATE/NOAUTOUPDATE settings found";
  277. }
  278. found_autoupdate = true;
  279. if (line_trimmed == "// AUTOUPDATE") {
  280. context.autoupdate_line_number = line_index;
  281. }
  282. } else if (line_trimmed == "// SET-CHECK-SUBSET") {
  283. if (!context.check_subset) {
  284. context.check_subset = true;
  285. } else {
  286. return ErrorBuilder()
  287. << "SET-CHECK-SUBSET was specified multiple times";
  288. }
  289. }
  290. }
  291. }
  292. if (!found_autoupdate) {
  293. return ErrorBuilder() << "Missing AUTOUPDATE/NOAUTOUPDATE setting";
  294. }
  295. if (current_file_start) {
  296. context.test_files.push_back(
  297. TestFile(current_file_name.str(),
  298. llvm::StringRef(current_file_start,
  299. file_content.end() - current_file_start)));
  300. } else {
  301. // If no file splitting happened, use the main file as the test file.
  302. // There will always be a `/` unless tests are in the repo root.
  303. context.test_files.push_back(TestFile(
  304. test_name_.drop_front(test_name_.rfind("/") + 1).str(), file_content));
  305. }
  306. // Assume there is always a suffix `\n` in output.
  307. if (!context.expected_stdout.empty()) {
  308. context.expected_stdout.push_back(StrEq(""));
  309. }
  310. if (!context.expected_stderr.empty()) {
  311. context.expected_stderr.push_back(StrEq(""));
  312. }
  313. return Success();
  314. }
  315. auto FileTestBase::TransformExpectation(int line_index, llvm::StringRef in)
  316. -> ErrorOr<Matcher<std::string>> {
  317. if (in.empty()) {
  318. return Matcher<std::string>{StrEq("")};
  319. }
  320. if (in[0] != ' ') {
  321. return ErrorBuilder() << "Malformated CHECK line: " << in;
  322. }
  323. std::string str = in.substr(1).str();
  324. for (int pos = 0; pos < static_cast<int>(str.size());) {
  325. switch (str[pos]) {
  326. case '(':
  327. case ')':
  328. case ']':
  329. case '}':
  330. case '.':
  331. case '^':
  332. case '$':
  333. case '*':
  334. case '+':
  335. case '?':
  336. case '|':
  337. case '\\': {
  338. // Escape regex characters.
  339. str.insert(pos, "\\");
  340. pos += 2;
  341. break;
  342. }
  343. case '[': {
  344. llvm::StringRef line_keyword_cursor = llvm::StringRef(str).substr(pos);
  345. if (line_keyword_cursor.consume_front("[[")) {
  346. static constexpr llvm::StringLiteral LineKeyword = "@LINE";
  347. if (line_keyword_cursor.consume_front(LineKeyword)) {
  348. // Allow + or - here; consumeInteger handles -.
  349. line_keyword_cursor.consume_front("+");
  350. int offset;
  351. // consumeInteger returns true for errors, not false.
  352. if (line_keyword_cursor.consumeInteger(10, offset) ||
  353. !line_keyword_cursor.consume_front("]]")) {
  354. return ErrorBuilder()
  355. << "Unexpected @LINE offset at `"
  356. << line_keyword_cursor.substr(0, 5) << "` in: " << in;
  357. }
  358. std::string int_str = llvm::Twine(line_index + offset).str();
  359. int remove_len = (line_keyword_cursor.data() - str.data()) - pos;
  360. str.replace(pos, remove_len, int_str);
  361. pos += int_str.size();
  362. } else {
  363. return ErrorBuilder()
  364. << "Unexpected [[, should be {{\\[\\[}} at `"
  365. << line_keyword_cursor.substr(0, 5) << "` in: " << in;
  366. }
  367. } else {
  368. // Escape the `[`.
  369. str.insert(pos, "\\");
  370. pos += 2;
  371. }
  372. break;
  373. }
  374. case '{': {
  375. if (pos + 1 == static_cast<int>(str.size()) || str[pos + 1] != '{') {
  376. // Single `{`, escape it.
  377. str.insert(pos, "\\");
  378. pos += 2;
  379. } else {
  380. // Replace the `{{...}}` regex syntax with standard `(...)` syntax.
  381. str.replace(pos, 2, "(");
  382. for (++pos; pos < static_cast<int>(str.size() - 1); ++pos) {
  383. if (str[pos] == '}' && str[pos + 1] == '}') {
  384. str.replace(pos, 2, ")");
  385. ++pos;
  386. break;
  387. }
  388. }
  389. }
  390. break;
  391. }
  392. default: {
  393. ++pos;
  394. }
  395. }
  396. }
  397. return Matcher<std::string>{MatchesRegex(str)};
  398. }
  399. // Returns the tests to run.
  400. static auto GetTests() -> llvm::SmallVector<std::string> {
  401. // Prefer a user-specified list if present.
  402. auto specific_tests = absl::GetFlag(FLAGS_file_tests);
  403. if (!specific_tests.empty()) {
  404. return llvm::SmallVector<std::string>(specific_tests.begin(),
  405. specific_tests.end());
  406. }
  407. // Extracts tests from the target file.
  408. CARBON_CHECK(!absl::GetFlag(FLAGS_test_targets_file).empty())
  409. << "Missing --test_targets_file.";
  410. auto content = ReadFile(absl::GetFlag(FLAGS_test_targets_file));
  411. llvm::SmallVector<std::string> all_tests;
  412. for (llvm::StringRef file_ref : llvm::split(content, "\n")) {
  413. if (file_ref.empty()) {
  414. continue;
  415. }
  416. all_tests.push_back(file_ref.str());
  417. }
  418. return all_tests;
  419. }
  420. // Implements main() within the Carbon::Testing namespace for convenience.
  421. static auto Main(int argc, char** argv) -> int {
  422. absl::ParseCommandLine(argc, argv);
  423. testing::InitGoogleTest(&argc, argv);
  424. llvm::setBugReportMsg(
  425. "Please report issues to "
  426. "https://github.com/carbon-language/carbon-lang/issues and include the "
  427. "crash backtrace.\n");
  428. llvm::InitLLVM init_llvm(argc, argv);
  429. if (argc > 1) {
  430. llvm::errs() << "Unexpected arguments starting at: " << argv[1] << "\n";
  431. return EXIT_FAILURE;
  432. }
  433. llvm::SmallVector<std::string> tests = GetTests();
  434. auto test_factory = GetFileTestFactory();
  435. if (absl::GetFlag(FLAGS_autoupdate)) {
  436. for (const auto& test_name : tests) {
  437. std::unique_ptr<FileTestBase> test(test_factory.factory_fn(test_name));
  438. auto result = test->Autoupdate();
  439. llvm::errs() << (result.ok() ? (*result ? "!" : ".")
  440. : result.error().message());
  441. }
  442. llvm::errs() << "\nDone!\n";
  443. return EXIT_SUCCESS;
  444. } else {
  445. for (llvm::StringRef test_name : tests) {
  446. testing::RegisterTest(test_factory.name, test_name.data(), nullptr,
  447. test_name.data(), __FILE__, __LINE__,
  448. [&test_factory, test_name = test_name]() {
  449. return test_factory.factory_fn(test_name);
  450. });
  451. }
  452. return RUN_ALL_TESTS();
  453. }
  454. }
  455. } // namespace Carbon::Testing
  456. auto main(int argc, char** argv) -> int { Carbon::Testing::Main(argc, argv); }