file_test_base_test.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  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 <gmock/gmock.h>
  6. #include <gtest/gtest.h>
  7. #include <functional>
  8. #include <memory>
  9. #include <optional>
  10. #include <string>
  11. #include "common/ostream.h"
  12. #include "llvm/ADT/StringExtras.h"
  13. #include "llvm/ADT/StringSwitch.h"
  14. #include "llvm/Support/FormatVariadic.h"
  15. namespace Carbon::Testing {
  16. namespace {
  17. class FileTestBaseTest : public FileTestBase {
  18. public:
  19. FileTestBaseTest(llvm::StringRef /*exe_path*/, llvm::StringRef test_name)
  20. : FileTestBase(test_name) {}
  21. auto Run(const llvm::SmallVector<llvm::StringRef>& test_args,
  22. llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
  23. FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
  24. llvm::raw_pwrite_stream& error_stream) const
  25. -> ErrorOr<RunResult> override;
  26. auto GetArgReplacements() const -> llvm::StringMap<std::string> override {
  27. return {{"replacement", "replaced"}};
  28. }
  29. auto GetDefaultArgs() const -> llvm::SmallVector<std::string> override {
  30. return {"default_args", "%s"};
  31. }
  32. auto GetDefaultFileRE(llvm::ArrayRef<llvm::StringRef> filenames) const
  33. -> std::optional<RE2> override {
  34. return std::make_optional<RE2>(
  35. llvm::formatv(R"(file: ({0}))", llvm::join(filenames, "|")));
  36. }
  37. auto GetLineNumberReplacements(llvm::ArrayRef<llvm::StringRef> filenames)
  38. const -> llvm::SmallVector<LineNumberReplacement> override {
  39. auto replacements = FileTestBase::GetLineNumberReplacements(filenames);
  40. auto filename = std::filesystem::path(test_name().str()).filename();
  41. if (llvm::StringRef(filename).starts_with("file_only_re_")) {
  42. replacements.push_back({.has_file = false,
  43. .re = std::make_shared<RE2>(R"(line: (\d+))"),
  44. .line_formatv = "{0}"});
  45. }
  46. return replacements;
  47. }
  48. };
  49. // Prints arguments so that they can be validated in tests.
  50. static auto PrintArgs(llvm::ArrayRef<llvm::StringRef> args,
  51. llvm::raw_pwrite_stream& output_stream) -> void {
  52. llvm::ListSeparator sep;
  53. output_stream << args.size() << " args: ";
  54. for (auto arg : args) {
  55. output_stream << sep << "`" << arg << "`";
  56. }
  57. output_stream << "\n";
  58. }
  59. // Verifies arguments are well-structured, and returns the files in them.
  60. static auto GetFilesFromArgs(llvm::ArrayRef<llvm::StringRef> args,
  61. llvm::vfs::InMemoryFileSystem& fs)
  62. -> ErrorOr<llvm::ArrayRef<llvm::StringRef>> {
  63. if (args.empty() || args.front() != "default_args") {
  64. return ErrorBuilder() << "missing `default_args` argument";
  65. }
  66. args.consume_front();
  67. for (auto arg : args) {
  68. if (!fs.exists(arg)) {
  69. return ErrorBuilder() << "Missing file: " << arg;
  70. }
  71. }
  72. return args;
  73. }
  74. // Parameters used to by individual test handlers, for easy value forwarding.
  75. struct TestParams {
  76. // These are the arguments to `Run()`.
  77. llvm::vfs::InMemoryFileSystem& fs;
  78. FILE* input_stream;
  79. llvm::raw_pwrite_stream& output_stream;
  80. llvm::raw_pwrite_stream& error_stream;
  81. // This is assigned after construction.
  82. llvm::ArrayRef<llvm::StringRef> files;
  83. };
  84. // Prints and returns expected results for alternating_files.carbon.
  85. static auto TestAlternatingFiles(TestParams& params)
  86. -> ErrorOr<FileTestBaseTest::RunResult> {
  87. params.output_stream << "unattached message 1\n"
  88. << "a.carbon:2: message 2\n"
  89. << "b.carbon:5: message 3\n"
  90. << "a.carbon:2: message 4\n"
  91. << "b.carbon:5: message 5\n"
  92. << "unattached message 6\n";
  93. params.error_stream << "unattached message 1\n"
  94. << "a.carbon:2: message 2\n"
  95. << "b.carbon:5: message 3\n"
  96. << "a.carbon:2: message 4\n"
  97. << "b.carbon:5: message 5\n"
  98. << "unattached message 6\n";
  99. return {{.success = true}};
  100. }
  101. // Prints and returns expected results for capture_console_output.carbon.
  102. static auto TestCaptureConsoleOutput(TestParams& params)
  103. -> ErrorOr<FileTestBaseTest::RunResult> {
  104. llvm::errs() << "llvm::errs\n";
  105. params.error_stream << "params.error_stream\n";
  106. llvm::outs() << "llvm::outs\n";
  107. params.output_stream << "params.output_stream\n";
  108. return {{.success = true}};
  109. }
  110. // Prints and returns expected results for escaping.carbon.
  111. static auto TestEscaping(TestParams& params)
  112. -> ErrorOr<FileTestBaseTest::RunResult> {
  113. params.error_stream << "carriage return\r\n"
  114. "{one brace}\n"
  115. "{{two braces}}\n"
  116. "[one bracket]\n"
  117. "[[two brackets]]\n"
  118. "end of line whitespace \n"
  119. "\ttabs\t\n";
  120. return {{.success = true}};
  121. }
  122. // Prints and returns expected results for example.carbon.
  123. static auto TestExample(TestParams& params)
  124. -> ErrorOr<FileTestBaseTest::RunResult> {
  125. int delta_line = 10;
  126. params.output_stream << "something\n"
  127. << "\n"
  128. << "example.carbon:" << delta_line + 1
  129. << ": Line delta\n"
  130. << "example.carbon:" << delta_line
  131. << ": Negative line delta\n"
  132. << "+*[]{}\n"
  133. << "Foo baz\n";
  134. return {{.success = true}};
  135. }
  136. // Prints and returns expected results for fail_example.carbon.
  137. static auto TestFailExample(TestParams& params)
  138. -> ErrorOr<FileTestBaseTest::RunResult> {
  139. params.error_stream << "Oops\n";
  140. return {{.success = false}};
  141. }
  142. // Prints and returns expected results for
  143. // file_only_re_multi_file.carbon.
  144. static auto TestFileOnlyREMultiFile(TestParams& params)
  145. -> ErrorOr<FileTestBaseTest::RunResult> {
  146. int msg_count = 0;
  147. params.output_stream << "unattached message " << ++msg_count << "\n"
  148. << "file: a.carbon\n"
  149. << "unattached message " << ++msg_count << "\n"
  150. << "line: 3: attached message " << ++msg_count << "\n"
  151. << "unattached message " << ++msg_count << "\n"
  152. << "line: 8: late message " << ++msg_count << "\n"
  153. << "unattached message " << ++msg_count << "\n"
  154. << "file: b.carbon\n"
  155. << "line: 2: attached message " << ++msg_count << "\n"
  156. << "unattached message " << ++msg_count << "\n"
  157. << "line: 7: late message " << ++msg_count << "\n"
  158. << "unattached message " << ++msg_count << "\n";
  159. return {{.success = true}};
  160. }
  161. // Prints and returns expected results for file_only_re_one_file.carbon.
  162. static auto TestFileOnlyREOneFile(TestParams& params)
  163. -> ErrorOr<FileTestBaseTest::RunResult> {
  164. params.output_stream << "unattached message 1\n"
  165. << "file: file_only_re_one_file.carbon\n"
  166. << "line: 1\n"
  167. << "unattached message 2\n";
  168. return {{.success = true}};
  169. }
  170. // Prints and returns expected results for no_line_number.carbon.
  171. static auto TestNoLineNumber(TestParams& params)
  172. -> ErrorOr<FileTestBaseTest::RunResult> {
  173. params.output_stream << "a.carbon: msg1\n"
  174. "msg2\n"
  175. "b.carbon: msg3\n"
  176. "msg4\n"
  177. "a.carbon: msg5\n";
  178. return {{.success = true}};
  179. }
  180. // Prints and returns expected results for unattached_multi_file.carbon.
  181. static auto TestUnattachedMultiFile(TestParams& params)
  182. -> ErrorOr<FileTestBaseTest::RunResult> {
  183. params.output_stream << "unattached message 1\n"
  184. << "unattached message 2\n";
  185. params.error_stream << "unattached message 3\n"
  186. << "unattached message 4\n";
  187. return {{.success = true}};
  188. }
  189. // Prints and returns expected results for:
  190. // - fail_multi_success_overall_fail.carbon
  191. // - multi_success.carbon
  192. // - multi_success_and_fail.carbon
  193. //
  194. // Parameters indicate overall and per-file success.
  195. static auto HandleMultiSuccessTests(bool overall, bool a, bool b)
  196. -> ErrorOr<FileTestBaseTest::RunResult> {
  197. FileTestBaseTest::RunResult result = {.success = overall};
  198. result.per_file_success.push_back({a ? "a.carbon" : "fail_a.carbon", a});
  199. result.per_file_success.push_back({b ? "b.carbon" : "fail_b.carbon", b});
  200. return result;
  201. }
  202. // Echoes back non-comment file content. Used for default file handling.
  203. static auto EchoFileContent(TestParams& params)
  204. -> ErrorOr<FileTestBaseTest::RunResult> {
  205. // By default, echo non-comment content of files back.
  206. for (auto test_file : params.files) {
  207. // Describe file contents to stdout to validate splitting.
  208. auto file = params.fs.getBufferForFile(test_file, /*FileSize=*/-1,
  209. /*RequiresNullTerminator=*/false);
  210. if (file.getError()) {
  211. return Error(file.getError().message());
  212. }
  213. llvm::StringRef buffer = file.get()->getBuffer();
  214. for (int line_number = 1; !buffer.empty(); ++line_number) {
  215. auto [line, remainder] = buffer.split('\n');
  216. if (!line.empty() && !line.starts_with("//")) {
  217. params.output_stream << test_file << ":" << line_number << ": " << line
  218. << "\n";
  219. }
  220. buffer = remainder;
  221. }
  222. }
  223. if (params.input_stream) {
  224. params.error_stream << "--- STDIN:\n";
  225. constexpr int ReadSize = 1024;
  226. char buf[ReadSize];
  227. while (feof(params.input_stream) == 0) {
  228. auto read = fread(&buf, sizeof(char), ReadSize, params.input_stream);
  229. if (read > 0) {
  230. params.error_stream.write(buf, read);
  231. }
  232. }
  233. }
  234. return {{.success = true}};
  235. }
  236. auto FileTestBaseTest::Run(
  237. const llvm::SmallVector<llvm::StringRef>& test_args,
  238. llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem>& fs,
  239. FILE* input_stream, llvm::raw_pwrite_stream& output_stream,
  240. llvm::raw_pwrite_stream& error_stream) const -> ErrorOr<RunResult> {
  241. PrintArgs(test_args, output_stream);
  242. auto filename = std::filesystem::path(test_name().str()).filename();
  243. if (filename == "args.carbon" || filename == "include_args.carbon" ||
  244. filename == "include_extra_args.carbon" ||
  245. filename == "include_args_and_extra_args.carbon") {
  246. // These files are testing argument behavior, which doesn't work with the
  247. // default test logic.
  248. return {{.success = true}};
  249. }
  250. // Choose the test function based on filename.
  251. auto test_fn =
  252. llvm::StringSwitch<std::function<auto(TestParams&)->ErrorOr<RunResult>>>(
  253. filename.string())
  254. .Case("alternating_files.carbon", &TestAlternatingFiles)
  255. .Case("capture_console_output.carbon", &TestCaptureConsoleOutput)
  256. .Case("escaping.carbon", &TestEscaping)
  257. .Case("example.carbon", &TestExample)
  258. .Case("fail_example.carbon", &TestFailExample)
  259. .Case("file_only_re_one_file.carbon", &TestFileOnlyREOneFile)
  260. .Case("file_only_re_multi_file.carbon", &TestFileOnlyREMultiFile)
  261. .Case("no_line_number.carbon", &TestNoLineNumber)
  262. .Case("unattached_multi_file.carbon", &TestUnattachedMultiFile)
  263. .Case("fail_multi_success_overall_fail.carbon",
  264. [&](TestParams&) {
  265. return HandleMultiSuccessTests(/*overall=*/false, /*a=*/true,
  266. /*b=*/true);
  267. })
  268. .Case("multi_success.carbon",
  269. [&](TestParams&) {
  270. return HandleMultiSuccessTests(/*overall=*/true, /*a=*/true,
  271. /*b=*/true);
  272. })
  273. .Case("multi_success_and_fail.carbon",
  274. [&](TestParams&) {
  275. return HandleMultiSuccessTests(/*overall=*/false, /*a=*/true,
  276. /*b=*/false);
  277. })
  278. .Default(&EchoFileContent);
  279. // Call the appropriate test function for the file.
  280. TestParams params = {.fs = *fs,
  281. .input_stream = input_stream,
  282. .output_stream = output_stream,
  283. .error_stream = error_stream};
  284. CARBON_ASSIGN_OR_RETURN(params.files, GetFilesFromArgs(test_args, *fs));
  285. return test_fn(params);
  286. }
  287. } // namespace
  288. CARBON_FILE_TEST_FACTORY(FileTestBaseTest)
  289. } // namespace Carbon::Testing