busybox_info_test.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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/install/busybox_info.h"
  5. #include <gmock/gmock.h>
  6. #include <gtest/gtest.h>
  7. #include <cstdlib>
  8. #include <fstream>
  9. #include <optional>
  10. #include <system_error>
  11. #include "common/check.h"
  12. #include "llvm/ADT/ScopeExit.h"
  13. #include "llvm/Support/FileSystem.h"
  14. namespace Carbon {
  15. namespace {
  16. using ::testing::Eq;
  17. class BusyboxInfoTest : public ::testing::Test {
  18. public:
  19. explicit BusyboxInfoTest() {
  20. // Set up a temp directory for the test case.
  21. const char* tmpdir = std::getenv("TEST_TMPDIR");
  22. CARBON_CHECK(tmpdir);
  23. dir_ = MakeDir(std::filesystem::absolute(
  24. std::filesystem::path(tmpdir) /
  25. ::testing::UnitTest::GetInstance()->current_test_info()->name()));
  26. // Most tests need the running binary for `MakeBusyboxFile`.
  27. static int static_for_main_addr;
  28. running_binary_ = llvm::sys::fs::getMainExecutable("busybox_info_test",
  29. &static_for_main_addr);
  30. }
  31. // Delete the test case's temp directory.
  32. ~BusyboxInfoTest() override {
  33. std::error_code ec;
  34. std::filesystem::remove_all(dir_, ec);
  35. CARBON_CHECK(!ec, "error removing {0}: {1}", dir_, ec.message());
  36. }
  37. // Creates a stub file. Returns the input file for easier use.
  38. auto MakeFile(std::filesystem::path file) -> std::filesystem::path {
  39. std::ofstream out(file.c_str());
  40. out << "stub";
  41. CARBON_CHECK(out, "error creating {0}", file);
  42. return file;
  43. }
  44. // Creates a symlink to the target. Returns the input file for easier use.
  45. auto MakeSymlink(std::filesystem::path file, auto target)
  46. -> std::filesystem::path {
  47. std::error_code ec;
  48. std::filesystem::create_symlink(target, file, ec);
  49. CARBON_CHECK(!ec, "error creating {0}: {1}", file, ec.message());
  50. return file;
  51. }
  52. // Creates a directory, recursively if needed. Returns the input file for
  53. // easier use.
  54. auto MakeDir(std::filesystem::path dir) -> std::filesystem::path {
  55. std::error_code ec;
  56. std::filesystem::create_directories(dir, ec);
  57. CARBON_CHECK(!ec, "error creating {0}: {1}", dir, ec.message());
  58. return dir;
  59. }
  60. // Creates a synthetic install tree to test a batch of interactions.
  61. // Optionally accepts a symlink target for the busybox in the install tree.
  62. // Returns the input prefix for easy use.
  63. auto MakeInstallTree(std::filesystem::path prefix,
  64. std::optional<std::filesystem::path> busybox_target = {})
  65. -> std::filesystem::path {
  66. MakeDir(prefix / "lib/carbon");
  67. if (busybox_target) {
  68. MakeSymlink(prefix / "lib/carbon/carbon-busybox", *busybox_target);
  69. } else {
  70. MakeBusyboxFile(prefix / "lib/carbon/carbon-busybox");
  71. }
  72. MakeDir(prefix / "lib/carbon/llvm/bin");
  73. MakeSymlink(prefix / "lib/carbon/llvm/bin/clang++", "clang");
  74. MakeSymlink(prefix / "lib/carbon/llvm/bin/clang", "../../carbon-busybox");
  75. MakeDir(prefix / "bin");
  76. MakeSymlink(prefix / "bin/carbon", "../lib/carbon/carbon-busybox");
  77. return prefix;
  78. }
  79. // Makes a fake busybox file. This is a symlink to the running binary because
  80. // we validate to make sure the running binary is one and the same.
  81. auto MakeBusyboxFile(std::filesystem::path file) -> std::filesystem::path {
  82. return MakeSymlink(file, running_binary_);
  83. }
  84. // The path to the running binary, `busybox_info_test`. This is provided
  85. // because `GetExecutablePath` can fall back to it.
  86. std::string running_binary_;
  87. // The test's temp directory, deleted on destruction.
  88. std::filesystem::path dir_;
  89. };
  90. TEST_F(BusyboxInfoTest, Direct) {
  91. auto busybox = MakeBusyboxFile(dir_ / "carbon-busybox");
  92. auto info = GetBusyboxInfo(busybox.c_str());
  93. ASSERT_TRUE(info.ok()) << info.error();
  94. EXPECT_THAT(info->bin_path, Eq(busybox));
  95. EXPECT_THAT(info->mode, Eq(std::nullopt));
  96. }
  97. TEST_F(BusyboxInfoTest, SymlinkInCurrentDirectory) {
  98. MakeBusyboxFile(dir_ / "carbon-busybox");
  99. auto target = MakeSymlink(dir_ / "carbon", "carbon-busybox");
  100. auto info = GetBusyboxInfo(target.c_str());
  101. ASSERT_TRUE(info.ok()) << info.error();
  102. EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox"));
  103. EXPECT_THAT(info->mode, Eq(std::nullopt));
  104. }
  105. TEST_F(BusyboxInfoTest, SymlinkInCurrentDirectoryWithDot) {
  106. MakeBusyboxFile(dir_ / "carbon-busybox");
  107. auto target = MakeSymlink(dir_ / "carbon", "./carbon-busybox");
  108. auto info = GetBusyboxInfo(target.c_str());
  109. ASSERT_TRUE(info.ok()) << info.error();
  110. EXPECT_THAT(info->bin_path, Eq(dir_ / "./carbon-busybox"));
  111. EXPECT_THAT(info->mode, Eq(std::nullopt));
  112. }
  113. TEST_F(BusyboxInfoTest, ExtraSymlink) {
  114. MakeBusyboxFile(dir_ / "carbon-busybox");
  115. MakeSymlink(dir_ / "c", "carbon-busybox");
  116. auto target = MakeSymlink(dir_ / "carbon", "c");
  117. auto info = GetBusyboxInfo(target.c_str());
  118. ASSERT_TRUE(info.ok()) << info.error();
  119. EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox"));
  120. EXPECT_THAT(info->mode, Eq(std::nullopt));
  121. }
  122. TEST_F(BusyboxInfoTest, OriginalSymlinkNameFormsMode) {
  123. MakeBusyboxFile(dir_ / "carbon-busybox");
  124. MakeSymlink(dir_ / "carbon", "carbon-busybox");
  125. auto clang_target = MakeSymlink(dir_ / "clang", "carbon");
  126. auto clang_plusplus_target = MakeSymlink(dir_ / "clang++", "clang");
  127. auto info = GetBusyboxInfo(clang_target.c_str());
  128. ASSERT_TRUE(info.ok()) << info.error();
  129. EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox"));
  130. EXPECT_THAT(info->mode, Eq("clang"));
  131. info = GetBusyboxInfo(clang_plusplus_target.c_str());
  132. ASSERT_TRUE(info.ok()) << info.error();
  133. EXPECT_THAT(info->bin_path, Eq(dir_ / "carbon-busybox"));
  134. EXPECT_THAT(info->mode, Eq("clang++"));
  135. }
  136. TEST_F(BusyboxInfoTest, BusyboxIsSymlinkToNowhere) {
  137. auto target = MakeSymlink(dir_ / "carbon-busybox", "nonexistent");
  138. auto info = GetBusyboxInfo(target.c_str());
  139. ASSERT_FALSE(info.ok());
  140. EXPECT_THAT(info.error().message(),
  141. Eq(llvm::formatv("expected carbon-busybox symlink at `{0}`",
  142. running_binary_)
  143. .str()));
  144. }
  145. TEST_F(BusyboxInfoTest, BusyboxIsWrongFile) {
  146. // This has the correct name, but it doesn't map back to the running binary
  147. // and so is ignored.
  148. auto target = MakeFile(dir_ / "carbon-busybox");
  149. auto info = GetBusyboxInfo(target.c_str());
  150. ASSERT_FALSE(info.ok());
  151. EXPECT_THAT(info.error().message(),
  152. Eq(llvm::formatv("expected carbon-busybox symlink at `{0}`",
  153. running_binary_)
  154. .str()));
  155. }
  156. TEST_F(BusyboxInfoTest, RelativeSymlink) {
  157. MakeDir(dir_ / "dir1");
  158. MakeBusyboxFile(dir_ / "dir1/carbon-busybox");
  159. MakeDir(dir_ / "dir2");
  160. auto target = MakeSymlink(dir_ / "dir2/carbon", "../dir1/carbon-busybox");
  161. auto info = GetBusyboxInfo(target.c_str());
  162. ASSERT_TRUE(info.ok()) << info.error();
  163. EXPECT_THAT(info->bin_path, Eq(dir_ / "dir2/../dir1/carbon-busybox"));
  164. EXPECT_THAT(info->mode, Eq(std::nullopt));
  165. }
  166. TEST_F(BusyboxInfoTest, AbsoluteSymlink) {
  167. MakeDir(dir_ / "dir1");
  168. auto busybox = MakeBusyboxFile(dir_ / "dir1/carbon-busybox");
  169. ASSERT_TRUE(busybox.is_absolute());
  170. MakeDir(dir_ / "dir2");
  171. auto target = MakeSymlink(dir_ / "dir2/carbon", busybox);
  172. auto info = GetBusyboxInfo(target.c_str());
  173. ASSERT_TRUE(info.ok()) << info.error();
  174. EXPECT_THAT(info->bin_path, Eq(busybox));
  175. EXPECT_THAT(info->mode, Eq(std::nullopt));
  176. }
  177. TEST_F(BusyboxInfoTest, NotBusyboxFile) {
  178. auto target = MakeFile(dir_ / "file");
  179. auto info = GetBusyboxInfo(target.c_str());
  180. EXPECT_FALSE(info.ok());
  181. }
  182. TEST_F(BusyboxInfoTest, NotBusyboxSymlink) {
  183. MakeFile(dir_ / "file");
  184. auto target = MakeSymlink(dir_ / "carbon", "file");
  185. auto info = GetBusyboxInfo(target.c_str());
  186. EXPECT_FALSE(info.ok());
  187. }
  188. TEST_F(BusyboxInfoTest, LayerSymlinksInstallTree) {
  189. auto actual_busybox = MakeBusyboxFile(dir_ / "actual-busybox");
  190. // Create a facsimile of the install prefix with even the busybox as a
  191. // symlink. Also include potential relative sibling symlinks like `clang++` to
  192. // `clang`.
  193. auto prefix = MakeInstallTree(dir_ / "test_prefix", actual_busybox);
  194. auto info = GetBusyboxInfo((prefix / "bin/carbon").c_str());
  195. ASSERT_TRUE(info.ok()) << info.error();
  196. EXPECT_THAT(info->bin_path, Eq(prefix / "bin/../lib/carbon/carbon-busybox"));
  197. EXPECT_THAT(info->mode, Eq(std::nullopt));
  198. info = GetBusyboxInfo((prefix / "lib/carbon/llvm/bin/clang").c_str());
  199. ASSERT_TRUE(info.ok()) << info.error();
  200. EXPECT_THAT(info->bin_path,
  201. Eq(prefix / "lib/carbon/llvm/bin/../../carbon-busybox"));
  202. EXPECT_THAT(info->mode, Eq("clang"));
  203. info = GetBusyboxInfo((prefix / "lib/carbon/llvm/bin/clang++").c_str());
  204. ASSERT_TRUE(info.ok()) << info.error();
  205. EXPECT_THAT(info->bin_path,
  206. Eq(prefix / "lib/carbon/llvm/bin/../../carbon-busybox"));
  207. EXPECT_THAT(info->mode, Eq("clang++"));
  208. }
  209. TEST_F(BusyboxInfoTest, StopSearchAtFirstSymlinkWithRelativeBusybox) {
  210. // Some install of Carbon under `opt`.
  211. auto opt_prefix = MakeInstallTree(dir_ / "opt");
  212. // A second install, but with its symlinks pointing into the `opt` tree rather
  213. // than at its busybox.
  214. MakeDir(dir_ / "lib/carbon");
  215. MakeBusyboxFile(dir_ / "lib/carbon/carbon-busybox");
  216. MakeDir(dir_ / "bin");
  217. auto target = MakeSymlink(dir_ / "bin/carbon", "../opt/bin/carbon");
  218. MakeDir(dir_ / "lib/carbon/llvm/bin");
  219. auto clang_target = MakeSymlink(dir_ / "lib/carbon/llvm/bin/clang",
  220. opt_prefix / "lib/carbon/llvm/bin/clang");
  221. // Starting from the second install uses the relative busybox rather than
  222. // traversing the symlink further.
  223. auto info = GetBusyboxInfo(target.c_str());
  224. ASSERT_TRUE(info.ok()) << info.error();
  225. EXPECT_THAT(info->bin_path, Eq(dir_ / "bin/../lib/carbon/carbon-busybox"));
  226. info = GetBusyboxInfo(clang_target.c_str());
  227. ASSERT_TRUE(info.ok()) << info.error();
  228. EXPECT_THAT(info->bin_path,
  229. Eq(dir_ / "lib/carbon/llvm/bin/../../carbon-busybox"));
  230. }
  231. TEST_F(BusyboxInfoTest, RejectSymlinkInUnrelatedInstall) {
  232. // Add two installs of Carbon nested inside each other in a realistic
  233. // scenario: `/usr` and `/usr/local`.
  234. MakeInstallTree(dir_ / "usr");
  235. MakeInstallTree(dir_ / "usr/local");
  236. // Now add a stray symlink directly in `.../usr/local` to the local install.
  237. //
  238. // This has the interesting property that both of these "work" and find the
  239. // same busybox but probably wanted to find different ones:
  240. // - `.../usr/local/../lib/carbon/carbon-busybox`
  241. // - `.../usr/bin/../lib/carbon/carbon-busybox`
  242. auto stray_target = MakeSymlink(dir_ / "usr/local/carbon", "bin/carbon");
  243. // Check that the busybox doesn't use the relative busybox in this case, and
  244. // walks the symlink to find the correct installation.
  245. auto info = GetBusyboxInfo(stray_target.c_str());
  246. ASSERT_TRUE(info.ok()) << info.error();
  247. EXPECT_THAT(info->bin_path,
  248. Eq(dir_ / "usr/local/bin/../lib/carbon/carbon-busybox"));
  249. // Ensure this works even with intervening `.` directory components.
  250. stray_target = MakeSymlink(dir_ / "usr/local/carbon2", "bin/././carbon");
  251. // Check that the busybox doesn't use the relative busybox in this case, and
  252. // walks the symlink to find the correct installation.
  253. info = GetBusyboxInfo(stray_target.c_str());
  254. ASSERT_TRUE(info.ok()) << info.error();
  255. EXPECT_THAT(info->bin_path,
  256. Eq(dir_ / "usr/local/bin/../lib/carbon/carbon-busybox"));
  257. }
  258. TEST_F(BusyboxInfoTest, EnvBinaryPathOverride) {
  259. // The test should not have this environment variable set.
  260. ASSERT_THAT(getenv(Argv0OverrideEnv), Eq(nullptr));
  261. // Set the environment to our actual busybox.
  262. auto busybox = MakeBusyboxFile(dir_ / "carbon-busybox");
  263. setenv(Argv0OverrideEnv, busybox.c_str(), /*overwrite=*/1);
  264. auto info = GetBusyboxInfo("/some/nonexistent/path");
  265. if (getenv(Argv0OverrideEnv)) {
  266. unsetenv(Argv0OverrideEnv);
  267. ADD_FAILURE() << "GetBusyboxInfo should unset Argv0OverrideEnv";
  268. }
  269. ASSERT_TRUE(info.ok()) << info.error();
  270. EXPECT_THAT(info->bin_path, Eq(busybox));
  271. EXPECT_THAT(info->mode, Eq(std::nullopt));
  272. }
  273. } // namespace
  274. } // namespace Carbon