busybox_info_test.cpp 14 KB

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