driver_test.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  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 "toolchain/driver/driver.h"
  5. #include <gmock/gmock.h>
  6. #include <gtest/gtest.h>
  7. #include <filesystem>
  8. #include <fstream>
  9. #include <optional>
  10. #include <string>
  11. #include <system_error>
  12. #include <utility>
  13. #include "common/error_test_helpers.h"
  14. #include "common/filesystem.h"
  15. #include "common/raw_string_ostream.h"
  16. #include "llvm/ADT/ScopeExit.h"
  17. #include "llvm/Object/Binary.h"
  18. #include "llvm/Support/Error.h"
  19. #include "llvm/Support/FormatVariadic.h"
  20. #include "llvm/Support/JSON.h"
  21. #include "testing/base/capture_std_streams.h"
  22. #include "testing/base/file_helpers.h"
  23. #include "testing/base/global_exe_path.h"
  24. #include "toolchain/testing/yaml_test_helpers.h"
  25. namespace Carbon {
  26. namespace {
  27. using ::testing::_;
  28. using Testing::CallWithCapturedOutput;
  29. using ::testing::ContainsRegex;
  30. using ::testing::HasSubstr;
  31. using Testing::IsSuccess;
  32. using ::testing::Ne;
  33. using ::testing::NotNull;
  34. using ::testing::StrEq;
  35. namespace Yaml = ::Carbon::Testing::Yaml;
  36. class DriverTest : public testing::Test {
  37. public:
  38. DriverTest()
  39. : installation_(
  40. InstallPaths::MakeForBazelRunfiles(Testing::GetExePath())),
  41. driver_(fs_, &installation_, /*input_stream=*/nullptr,
  42. &test_output_stream_, &test_error_stream_) {
  43. test_tmpdir_ = Testing::GetTempDirectory();
  44. }
  45. auto MakeTestFile(llvm::StringRef text,
  46. llvm::StringRef filename = "test_file.carbon")
  47. -> llvm::StringRef {
  48. fs_->addFile(filename, /*ModificationTime=*/0,
  49. llvm::MemoryBuffer::getMemBuffer(text));
  50. return filename;
  51. }
  52. // Makes a temp directory and changes the working directory to it. Returns an
  53. // LLVM `scope_exit` that will restore the working directory and remove the
  54. // temporary directory (and everything it contains) when destroyed.
  55. auto ScopedTempWorkingDir() {
  56. // Save our current working directory.
  57. std::error_code ec;
  58. auto original_dir = std::filesystem::current_path(ec);
  59. CARBON_CHECK(!ec, "{0}", ec.message());
  60. Driver original_driver = std::move(driver_);
  61. const auto* unit_test = ::testing::UnitTest::GetInstance();
  62. const auto* test_info = unit_test->current_test_info();
  63. std::filesystem::path test_dir = test_tmpdir_.append(
  64. llvm::formatv("{0}_{1}", test_info->test_suite_name(),
  65. test_info->name())
  66. .str());
  67. std::filesystem::create_directory(test_dir, ec);
  68. CARBON_CHECK(!ec, "Could not create test working dir '{0}': {1}", test_dir,
  69. ec.message());
  70. std::filesystem::current_path(test_dir, ec);
  71. CARBON_CHECK(!ec, "Could not change the current working dir to '{0}': {1}",
  72. test_dir, ec.message());
  73. // Build an overlay filesystem between the in-memory one and this new
  74. // directory.
  75. llvm::IntrusiveRefCntPtr<llvm::vfs::OverlayFileSystem> overlay_fs =
  76. new llvm::vfs::OverlayFileSystem(llvm::vfs::getRealFileSystem());
  77. overlay_fs->pushOverlay(fs_);
  78. // Rebuild the driver around this filesystem.
  79. driver_ = Driver(overlay_fs, &installation_, /*input_stream=*/nullptr,
  80. &test_output_stream_, &test_error_stream_);
  81. return llvm::scope_exit([this, original_dir, original_driver, test_dir] {
  82. std::error_code ec;
  83. std::filesystem::current_path(original_dir, ec);
  84. CARBON_CHECK(!ec,
  85. "Could not change the current working dir to '{0}': {1}",
  86. original_dir, ec.message());
  87. driver_ = original_driver;
  88. std::filesystem::remove_all(test_dir, ec);
  89. CARBON_CHECK(!ec, "Could not remove the test working dir '{0}': {1}",
  90. test_dir, ec.message());
  91. });
  92. }
  93. llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> fs_ =
  94. new llvm::vfs::InMemoryFileSystem;
  95. const InstallPaths installation_;
  96. RawStringOstream test_output_stream_;
  97. RawStringOstream test_error_stream_;
  98. // Some tests work directly with files in the test temporary directory.
  99. std::filesystem::path test_tmpdir_;
  100. Driver driver_;
  101. };
  102. TEST_F(DriverTest, BadCommandErrors) {
  103. EXPECT_FALSE(driver_.RunCommand({}).success);
  104. EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("error:"));
  105. EXPECT_FALSE(driver_.RunCommand({"foo"}).success);
  106. EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("error:"));
  107. EXPECT_FALSE(driver_.RunCommand({"foo --bar --baz"}).success);
  108. EXPECT_THAT(test_error_stream_.TakeStr(), HasSubstr("error:"));
  109. }
  110. TEST_F(DriverTest, CompileCommandErrors) {
  111. // No input file. This error message is important so check all of it.
  112. EXPECT_FALSE(driver_.RunCommand({"compile"}).success);
  113. EXPECT_THAT(
  114. test_error_stream_.TakeStr(),
  115. StrEq("error: not all required positional arguments were provided; first "
  116. "missing and required positional argument: `FILE`\n"));
  117. // Pass non-existing file
  118. EXPECT_FALSE(driver_
  119. .RunCommand({"compile", "--dump-mem-usage",
  120. "non-existing-file.carbon"})
  121. .success);
  122. EXPECT_THAT(
  123. test_error_stream_.TakeStr(),
  124. ContainsRegex("No such file or directory[\\n]*non-existing-file.carbon"));
  125. // Flush output stream, because it's ok that it's not empty here.
  126. test_output_stream_.TakeStr();
  127. // Invalid output filename. No reliably error message here.
  128. // TODO: Likely want a different filename on Windows.
  129. auto empty_file = MakeTestFile("");
  130. EXPECT_FALSE(driver_
  131. .RunCommand({"compile", "--no-prelude-import",
  132. "--output=/dev/empty", empty_file})
  133. .success);
  134. EXPECT_THAT(test_error_stream_.TakeStr(),
  135. ContainsRegex("error: .*/dev/empty.*"));
  136. }
  137. TEST_F(DriverTest, DumpTokens) {
  138. auto file = MakeTestFile("Hello World");
  139. EXPECT_TRUE(driver_
  140. .RunCommand({"compile", "--no-prelude-import", "--phase=lex",
  141. "--dump-tokens", file})
  142. .success);
  143. EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
  144. // Verify there is output without examining it.
  145. EXPECT_THAT(Yaml::Value::FromText(test_output_stream_.TakeStr()),
  146. Yaml::IsYaml(_));
  147. }
  148. TEST_F(DriverTest, DumpParseTree) {
  149. auto file = MakeTestFile("var v: () = ();");
  150. EXPECT_TRUE(driver_
  151. .RunCommand({"compile", "--no-prelude-import",
  152. "--phase=parse", "--dump-parse-tree", file})
  153. .success);
  154. EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
  155. // Verify there is output without examining it.
  156. EXPECT_THAT(Yaml::Value::FromText(test_output_stream_.TakeStr()),
  157. Yaml::IsYaml(_));
  158. }
  159. TEST_F(DriverTest, StdoutOutput) {
  160. // Use explicit filenames so we can look for those to validate output.
  161. MakeTestFile("fn Run() {}", "test.carbon");
  162. EXPECT_TRUE(driver_
  163. .RunCommand({"compile", "--no-prelude-import", "--output=-",
  164. "test.carbon"})
  165. .success);
  166. EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
  167. // The default is textual assembly.
  168. EXPECT_THAT(test_output_stream_.TakeStr(), ContainsRegex("main:"));
  169. EXPECT_TRUE(driver_
  170. .RunCommand({"compile", "--no-prelude-import", "--output=-",
  171. "--force-obj-output", "test.carbon"})
  172. .success);
  173. EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
  174. std::string output = test_output_stream_.TakeStr();
  175. auto result =
  176. llvm::object::createBinary(llvm::MemoryBufferRef(output, "test_output"));
  177. if (auto error = result.takeError()) {
  178. FAIL() << toString(std::move(error));
  179. }
  180. EXPECT_TRUE(result->get()->isObject());
  181. }
  182. TEST_F(DriverTest, FileOutput) {
  183. auto scope = ScopedTempWorkingDir();
  184. // Use explicit filenames as the default output filename is computed from
  185. // this, and we can use this to validate output.
  186. MakeTestFile("fn Run() {}", "test.carbon");
  187. // Object output (the default) uses `.o`.
  188. // TODO: This should actually reflect the platform defaults.
  189. EXPECT_TRUE(
  190. driver_.RunCommand({"compile", "--no-prelude-import", "test.carbon"})
  191. .success);
  192. EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
  193. // Ensure we wrote an object file of some form with the correct name.
  194. auto result = llvm::object::createBinary("test.o");
  195. if (auto error = result.takeError()) {
  196. FAIL() << toString(std::move(error));
  197. }
  198. EXPECT_TRUE(result->getBinary()->isObject());
  199. // Assembly output uses `.s`.
  200. // TODO: This should actually reflect the platform defaults.
  201. EXPECT_TRUE(driver_
  202. .RunCommand({"compile", "--no-prelude-import", "--asm-output",
  203. "test.carbon"})
  204. .success);
  205. EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
  206. // TODO: This may need to be tailored to other assembly formats.
  207. EXPECT_THAT(*Testing::ReadFile("test.s"), ContainsRegex("main:"));
  208. }
  209. TEST_F(DriverTest, Link) {
  210. auto scope = ScopedTempWorkingDir();
  211. // First compile a file to get a linkable object.
  212. MakeTestFile("fn Run() {}", "test.carbon");
  213. ASSERT_TRUE(
  214. driver_.RunCommand({"compile", "--no-prelude-import", "test.carbon"})
  215. .success)
  216. << test_error_stream_.TakeStr();
  217. // Now link this into a binary. Note that we suppress building runtimes on
  218. // demand here as no runtimes should be needed for the empty program. We also
  219. // pass some system library link flags through to the underlying Clang layer.
  220. EXPECT_TRUE(driver_
  221. .RunCommand({"--no-build-runtimes", "link", "--output=test",
  222. "test.o", "--", "-lc", "-lm"})
  223. .success);
  224. EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
  225. // Ensure we wrote an executable file of some form with the correct name.
  226. // TODO: We may need to update this if we implicitly synthesize a
  227. // platform-specific `.exe` suffix or something similar.
  228. auto result = llvm::object::createBinary("test");
  229. if (auto error = result.takeError()) {
  230. FAIL() << toString(std::move(error));
  231. }
  232. // Executables are also classified as object files.
  233. EXPECT_TRUE(result->getBinary()->isObject());
  234. }
  235. TEST_F(DriverTest, LinkWithFlagLikeFiles) {
  236. auto scope = ScopedTempWorkingDir();
  237. // First compile a file to get a linkable object.
  238. MakeTestFile("fn Run() {}", "test.carbon");
  239. ASSERT_TRUE(
  240. driver_.RunCommand({"compile", "--no-prelude-import", "test.carbon"})
  241. .success)
  242. << test_error_stream_.TakeStr();
  243. // Rename it to a flag-like name.
  244. Filesystem::Cwd().Rename("test.o", Filesystem::Cwd(), "--test.o").Check();
  245. // Link this into a binary and pass flags to the Clang link invocation even
  246. // though we use `--` before the object file input list to handle weirdly
  247. // named objects.
  248. //
  249. // TODO: This works correctly in the Carbon link subcommand, but Clang itself
  250. // fails to pass object files to LLD in a way that supports flag-shaped object
  251. // file names. The last flag being `-Wl,--` tries to work around this by
  252. // passing a `--` to the linker before the object files. However, LLD in turn
  253. // appears to have bugs parsing command lines in this shape. We should get
  254. // these fixed and then can remove the `-Wl,--` hack and the test should
  255. // actually pass.
  256. std::string out;
  257. std::string err;
  258. EXPECT_FALSE(CallWithCapturedOutput(out, err, [&] {
  259. return driver_.RunCommand({"--no-build-runtimes", "link",
  260. "--output=test", "--", "--test.o",
  261. "--", "-lc", "-lm", "-Wl,--"});
  262. }).success);
  263. EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
  264. EXPECT_THAT(out, StrEq(""));
  265. // This error seems to stem from incorrectly handling `--` in the LLD command
  266. // line. See above; this should go away when the underlying bugs are fixed.
  267. EXPECT_THAT(err,
  268. HasSubstr("error: completed parsing all 1 configured positional "
  269. "arguments, but found a subsequent `--`"));
  270. }
  271. TEST_F(DriverTest, ConfigJson) {
  272. EXPECT_TRUE(driver_.RunCommand({"config", "--json"}).success);
  273. EXPECT_THAT(test_error_stream_.TakeStr(), StrEq(""));
  274. // Make sure the output parses as JSON.
  275. std::string output = test_output_stream_.TakeStr();
  276. llvm::Expected<llvm::json::Value> json_value = llvm::json::parse(output);
  277. if (auto error = json_value.takeError()) {
  278. FAIL() << "Unable to parse to JSON: " << toString(std::move(error))
  279. << "\nOriginal text:\n"
  280. << output << "\n";
  281. }
  282. llvm::json::Object* json_obj = json_value->getAsObject();
  283. ASSERT_THAT(json_obj, NotNull());
  284. // Check relevant paths in the output point to existing directories.
  285. std::optional<llvm::StringRef> install_root =
  286. json_obj->getString("INSTALL_ROOT");
  287. ASSERT_THAT(install_root, Ne(std::nullopt));
  288. EXPECT_THAT(Filesystem::Cwd().OpenDir(install_root->str()), IsSuccess(_));
  289. std::optional<llvm::StringRef> clang_sysroot =
  290. json_obj->getString("CLANG_SYSROOT");
  291. ASSERT_THAT(clang_sysroot, Ne(std::nullopt));
  292. EXPECT_THAT(Filesystem::Cwd().OpenDir(clang_sysroot->str()), IsSuccess(_));
  293. }
  294. } // namespace
  295. } // namespace Carbon