|
| 1 | +#include <gtest/gtest.h> |
| 2 | +#include <cerrno> |
| 3 | +#include <fstream> |
| 4 | + |
| 5 | +import std; |
| 6 | +import mcpp.build.runner_lookup; |
| 7 | + |
| 8 | +using namespace mcpp::build::runner_lookup; |
| 9 | + |
| 10 | +// The lookup order is the whole point (#544 §4.4): a declared payload's bin/ |
| 11 | +// beats PATH, because a bare name on PATH reaches an xvm shim that answers for |
| 12 | +// the current subos rather than for the package. The directories are real and |
| 13 | +// the files are executable, so what is asserted is the rule, not a mock of it. |
| 14 | +#if !defined(_WIN32) |
| 15 | + |
| 16 | +namespace { |
| 17 | +std::filesystem::path fresh_root(std::string_view name) { |
| 18 | + auto root = std::filesystem::temp_directory_path() / name; |
| 19 | + std::filesystem::remove_all(root); |
| 20 | + std::filesystem::create_directories(root); |
| 21 | + return root; |
| 22 | +} |
| 23 | +std::filesystem::path make_exe(const std::filesystem::path& dir, std::string_view name) { |
| 24 | + std::filesystem::create_directories(dir); |
| 25 | + auto p = dir / name; |
| 26 | + { std::ofstream o(p); o << "#!/bin/sh\nexit 0\n"; } |
| 27 | + std::filesystem::permissions(p, std::filesystem::perms::owner_all); |
| 28 | + return p; |
| 29 | +} |
| 30 | +} // namespace |
| 31 | + |
| 32 | +TEST(RunnerLookup, PayloadBinBeatsPath) { |
| 33 | + auto root = fresh_root("mcpp-runner-lookup-1"); |
| 34 | + auto inPayload = make_exe(root / "payload" / "bin", "qemu-x"); |
| 35 | + make_exe(root / "path", "qemu-x"); |
| 36 | + std::vector<std::filesystem::path> bins{root / "payload" / "bin"}; |
| 37 | + auto l = locate("qemu-x", bins, (root / "path").string()); |
| 38 | + ASSERT_TRUE(l.program.has_value()); |
| 39 | + EXPECT_EQ(*l.program, inPayload); |
| 40 | +} |
| 41 | + |
| 42 | +TEST(RunnerLookup, PathIsSearchedAfterPayloads) { |
| 43 | + auto root = fresh_root("mcpp-runner-lookup-2"); |
| 44 | + auto onPath = make_exe(root / "path", "qemu-y"); |
| 45 | + std::vector<std::filesystem::path> bins{root / "payload" / "bin"}; // absent dir |
| 46 | + auto l = locate("qemu-y", bins, (root / "path").string()); |
| 47 | + ASSERT_TRUE(l.program.has_value()); |
| 48 | + EXPECT_EQ(*l.program, onPath); |
| 49 | + ASSERT_EQ(l.searched.size(), 2u); |
| 50 | + EXPECT_EQ(l.searched[0], root / "payload" / "bin"); |
| 51 | + EXPECT_EQ(l.searched[1], root / "path"); |
| 52 | +} |
| 53 | + |
| 54 | +TEST(RunnerLookup, NonExecutableFileIsSkipped) { |
| 55 | + auto root = fresh_root("mcpp-runner-lookup-3"); |
| 56 | + std::filesystem::create_directories(root / "p1"); |
| 57 | + { std::ofstream o(root / "p1" / "tool"); o << "data"; } // no exec bit |
| 58 | + auto real = make_exe(root / "p2", "tool"); |
| 59 | + auto l = locate("tool", {}, (root / "p1").string() + ":" + (root / "p2").string()); |
| 60 | + ASSERT_TRUE(l.program.has_value()); |
| 61 | + EXPECT_EQ(*l.program, real); |
| 62 | +} |
| 63 | + |
| 64 | +TEST(RunnerLookup, NotFoundListsEveryDirectorySearched) { |
| 65 | + auto root = fresh_root("mcpp-runner-lookup-4"); |
| 66 | + std::vector<std::filesystem::path> bins{root / "a" / "bin"}; |
| 67 | + auto l = locate("nope", bins, (root / "p1").string() + ":" + (root / "p2").string()); |
| 68 | + EXPECT_FALSE(l.program.has_value()); |
| 69 | + ASSERT_EQ(l.searched.size(), 3u); |
| 70 | + auto msg = not_found_message("aarch64-linux-musl", "nope", l.searched); |
| 71 | + EXPECT_NE(msg.find("runner 'nope'"), std::string::npos) << msg; |
| 72 | + EXPECT_NE(msg.find("aarch64-linux-musl"), std::string::npos) << msg; |
| 73 | + EXPECT_NE(msg.find((root / "a" / "bin").string()), std::string::npos) << msg; |
| 74 | + EXPECT_NE(msg.find((root / "p2").string()), std::string::npos) << msg; |
| 75 | + EXPECT_NE(msg.find("[xlings] deps"), std::string::npos) << msg; |
| 76 | + EXPECT_NE(msg.find("--no-runner"), std::string::npos) << msg; |
| 77 | +} |
| 78 | + |
| 79 | +TEST(RunnerLookup, AbsoluteArgv0IsTakenAsIs) { |
| 80 | + auto root = fresh_root("mcpp-runner-lookup-5"); |
| 81 | + auto abs = make_exe(root, "runner.sh"); |
| 82 | + auto l = locate(abs.string(), {}, ""); |
| 83 | + ASSERT_TRUE(l.program.has_value()); |
| 84 | + EXPECT_EQ(*l.program, abs); |
| 85 | + // ...and an absolute path that is not there is not searched for elsewhere. |
| 86 | + auto missing = locate((root / "absent.sh").string(), {}, root.string()); |
| 87 | + EXPECT_FALSE(missing.program.has_value()); |
| 88 | +} |
| 89 | + |
| 90 | +TEST(RunnerLookup, EmptyPathEntriesAreIgnored) { |
| 91 | + auto root = fresh_root("mcpp-runner-lookup-6"); |
| 92 | + auto onPath = make_exe(root / "p", "tool"); |
| 93 | + auto l = locate("tool", {}, ":" + (root / "p").string() + "::"); |
| 94 | + ASSERT_TRUE(l.program.has_value()); |
| 95 | + EXPECT_EQ(*l.program, onPath); |
| 96 | + EXPECT_EQ(l.searched.size(), 1u); |
| 97 | +} |
| 98 | + |
| 99 | +TEST(RunnerLookup, ClassifiesENOEXECAsUnloadable) { |
| 100 | + EXPECT_EQ(classify(ENOEXEC), SpawnClass::Unloadable); |
| 101 | + EXPECT_EQ(classify(EACCES), SpawnClass::Other); |
| 102 | + EXPECT_EQ(classify(ENOENT), SpawnClass::Other); |
| 103 | + EXPECT_EQ(classify(0), SpawnClass::Other); |
| 104 | +} |
| 105 | + |
| 106 | +TEST(RunnerLookup, UnrunnableMessageNamesKernelAnswerTripleAndKey) { |
| 107 | + auto msg = unrunnable_message("aarch64-linux-musl", "/x/bin/app", ENOEXEC); |
| 108 | + EXPECT_NE(msg.find("Exec format error"), std::string::npos) << msg; |
| 109 | + EXPECT_NE(msg.find("/x/bin/app"), std::string::npos) << msg; |
| 110 | + EXPECT_NE(msg.find("built for 'aarch64-linux-musl'"), std::string::npos) << msg; |
| 111 | + EXPECT_NE(msg.find("[target.aarch64-linux-musl]"), std::string::npos) << msg; |
| 112 | + EXPECT_NE(msg.find("runner = [\"qemu-aarch64-static\"]"), std::string::npos) << msg; |
| 113 | + EXPECT_NE(msg.find("--no-runner"), std::string::npos) << msg; |
| 114 | +} |
| 115 | + |
| 116 | +TEST(RunnerLookup, SpawnFailedMessageIsVerbatim) { |
| 117 | + auto msg = spawn_failed_message("/x/bin/qemu", EACCES); |
| 118 | + EXPECT_NE(msg.find("'/x/bin/qemu' could not be started"), std::string::npos) << msg; |
| 119 | + EXPECT_NE(msg.find("Permission denied"), std::string::npos) << msg; |
| 120 | + EXPECT_NE(msg.find("(error 13)"), std::string::npos) << msg; |
| 121 | + EXPECT_EQ(msg.find("runner = ["), std::string::npos) << msg; // no runner advice |
| 122 | +} |
| 123 | + |
| 124 | +#else |
| 125 | + |
| 126 | +TEST(RunnerLookup, WindowsCoveredByIntegration) { SUCCEED(); } |
| 127 | + |
| 128 | +#endif |
0 commit comments