run_test.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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/run_test.h"
  5. #include <gtest/gtest.h>
  6. #include <string>
  7. #include <utility>
  8. #include "common/pretty_stack_trace_function.h"
  9. #include "llvm/ADT/ScopeExit.h"
  10. #include "llvm/ADT/SmallVector.h"
  11. #include "llvm/ADT/StringMap.h"
  12. #include "llvm/ADT/StringRef.h"
  13. #include "llvm/Support/PrettyStackTrace.h"
  14. #include "testing/base/file_helpers.h"
  15. #include "testing/file_test/file_test_base.h"
  16. #include "testing/file_test/test_file.h"
  17. namespace Carbon::Testing {
  18. // While these are marked as "internal" APIs, they seem to work and be pretty
  19. // widely used for their exact documented behavior.
  20. using ::testing::internal::CaptureStderr;
  21. using ::testing::internal::CaptureStdout;
  22. using ::testing::internal::GetCapturedStderr;
  23. using ::testing::internal::GetCapturedStdout;
  24. static constexpr llvm::StringLiteral StdinFilename = "STDIN";
  25. // Does replacements in ARGS for %s and %t.
  26. static auto DoArgReplacements(llvm::SmallVector<std::string>& test_args,
  27. const llvm::StringMap<std::string>& replacements,
  28. llvm::ArrayRef<TestFile::Split*> split_files)
  29. -> ErrorOr<Success> {
  30. for (auto* it = test_args.begin(); it != test_args.end(); ++it) {
  31. auto percent = it->find("%");
  32. if (percent == std::string::npos) {
  33. continue;
  34. }
  35. if (percent + 1 >= it->size()) {
  36. return ErrorBuilder() << "% is not allowed on its own: " << *it;
  37. }
  38. char c = (*it)[percent + 1];
  39. switch (c) {
  40. case 's': {
  41. if (*it != "%s") {
  42. return ErrorBuilder() << "%s must be the full argument: " << *it;
  43. }
  44. it = test_args.erase(it);
  45. for (const auto& split : split_files) {
  46. const std::string& filename = split->filename;
  47. if (filename == StdinFilename || filename.ends_with(".h")) {
  48. continue;
  49. }
  50. it = test_args.insert(it, filename);
  51. ++it;
  52. }
  53. // Back up once because the for loop will advance.
  54. --it;
  55. break;
  56. }
  57. case 't': {
  58. std::filesystem::path tmpdir = GetTempDirectory();
  59. it->replace(percent, 2, llvm::formatv("{0}/temp_file", tmpdir));
  60. break;
  61. }
  62. case '{': {
  63. auto end_brace = it->find('}', percent);
  64. if (end_brace == std::string::npos) {
  65. return ErrorBuilder() << "%{ without closing }: " << *it;
  66. }
  67. llvm::StringRef substr(&*(it->begin() + percent + 2),
  68. end_brace - percent - 2);
  69. auto replacement = replacements.find(substr);
  70. if (replacement == replacements.end()) {
  71. return ErrorBuilder()
  72. << "unknown substitution: %{" << substr << "}: " << *it;
  73. }
  74. it->replace(percent, end_brace - percent + 1, replacement->second);
  75. break;
  76. }
  77. default:
  78. return ErrorBuilder() << "%" << c << " is not supported: " << *it;
  79. }
  80. }
  81. return Success();
  82. }
  83. // Collects captured output when enabled.
  84. static auto CollectOutputIfCapturing(TestFile& test_file) -> void {
  85. if (!test_file.capture_console_output) {
  86. return;
  87. }
  88. // No need to flush stderr.
  89. llvm::outs().flush();
  90. test_file.actual_stdout += GetCapturedStdout();
  91. test_file.actual_stderr += GetCapturedStderr();
  92. }
  93. auto RunTestFile(const FileTestBase& test_base, bool dump_output,
  94. TestFile& test_file) -> ErrorOr<Success> {
  95. llvm::SmallVector<TestFile::Split*> all_splits;
  96. for (auto& split : test_file.file_splits) {
  97. all_splits.push_back(&split);
  98. }
  99. for (auto& split : test_file.include_file_splits) {
  100. all_splits.push_back(&split);
  101. }
  102. // Process arguments.
  103. if (test_file.test_args.empty()) {
  104. test_file.test_args = test_base.GetDefaultArgs();
  105. }
  106. test_file.test_args.append(test_file.extra_args);
  107. CARBON_RETURN_IF_ERROR(DoArgReplacements(
  108. test_file.test_args, test_base.GetArgReplacements(), all_splits));
  109. // stdin needs to exist on-disk for compatibility. We'll use a pointer for it.
  110. FILE* input_stream = nullptr;
  111. auto erase_input_on_exit = llvm::scope_exit([&input_stream]() {
  112. if (input_stream) {
  113. // fclose should delete the tmpfile.
  114. fclose(input_stream);
  115. input_stream = nullptr;
  116. }
  117. });
  118. // Create the files in-memory.
  119. llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> fs =
  120. new llvm::vfs::InMemoryFileSystem;
  121. for (const auto& split : all_splits) {
  122. if (split->filename == StdinFilename) {
  123. input_stream = tmpfile();
  124. fwrite(split->content.c_str(), sizeof(char), split->content.size(),
  125. input_stream);
  126. CARBON_CHECK(!fseek(input_stream, 0, SEEK_SET));
  127. } else if (!fs->addFile(split->filename, /*ModificationTime=*/0,
  128. llvm::MemoryBuffer::getMemBuffer(
  129. split->content, split->filename,
  130. /*RequiresNullTerminator=*/false))) {
  131. return ErrorBuilder() << "File is repeated: " << split->filename;
  132. }
  133. }
  134. // Convert the arguments to StringRef and const char* to match the
  135. // expectations of PrettyStackTraceProgram and Run.
  136. llvm::SmallVector<llvm::StringRef> test_args_ref;
  137. llvm::SmallVector<const char*> test_argv_for_stack_trace;
  138. test_args_ref.reserve(test_file.test_args.size());
  139. test_argv_for_stack_trace.reserve(test_file.test_args.size() + 1);
  140. for (const auto& arg : test_file.test_args) {
  141. test_args_ref.push_back(arg);
  142. test_argv_for_stack_trace.push_back(arg.c_str());
  143. }
  144. // Add a trailing null so that this is a proper argv.
  145. test_argv_for_stack_trace.push_back(nullptr);
  146. // Add a stack trace entry for the test invocation.
  147. llvm::PrettyStackTraceProgram stack_trace_entry(
  148. test_argv_for_stack_trace.size() - 1, test_argv_for_stack_trace.data());
  149. // Conditionally capture console output. We use a scope exit to ensure the
  150. // captures terminate even on run failures.
  151. if (test_file.capture_console_output) {
  152. CaptureStderr();
  153. CaptureStdout();
  154. }
  155. // Prepare string streams to capture output. In order to address casting
  156. // constraints, we split calls to Run as a ternary based on whether we want to
  157. // capture output.
  158. llvm::raw_svector_ostream output_stream(test_file.actual_stdout);
  159. llvm::raw_svector_ostream error_stream(test_file.actual_stderr);
  160. // Dump any available captured output if `Run` crashes.
  161. PrettyStackTraceFunction stack_trace_streams([&](llvm::raw_ostream& out) {
  162. CollectOutputIfCapturing(test_file);
  163. auto dump_stream = [&](llvm::SmallString<16> stream) {
  164. if (stream.empty()) {
  165. out << " (none)\n";
  166. } else {
  167. out << "\n" << stream << "\n";
  168. }
  169. };
  170. out << "Test stdout:";
  171. dump_stream(test_file.actual_stdout);
  172. out << "\tTest stderr:";
  173. dump_stream(test_file.actual_stderr);
  174. });
  175. Timer timer;
  176. ErrorOr<FileTestBase::RunResult> run_result =
  177. dump_output ? test_base.Run(test_args_ref, fs, input_stream, llvm::outs(),
  178. llvm::errs())
  179. : test_base.Run(test_args_ref, fs, input_stream,
  180. output_stream, error_stream);
  181. test_file.run_elapsed_ms = timer.elapsed_ms();
  182. // Collect captured stdout/stderr, even when discarded on error.
  183. CollectOutputIfCapturing(test_file);
  184. if (!run_result.ok()) {
  185. return std::move(run_result).error();
  186. }
  187. test_file.run_result = std::move(*run_result);
  188. return Success();
  189. }
  190. } // namespace Carbon::Testing