run_test.cpp 6.8 KB

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