busybox_info_test.cpp 11 KB

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