blob: 441344b281411f2d2ff263e1f50db9dbbf7cbe5e [file] [log] [blame]
//===- LibraryResolverTest.cpp - Unit tests for LibraryResolver -===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "llvm/ExecutionEngine/Orc/TargetProcess/LibraryResolver.h"
#include "llvm/ExecutionEngine/Orc/JITTargetMachineBuilder.h"
#include "llvm/ExecutionEngine/Orc/TargetProcess/LibraryScanner.h"
#include "llvm/ObjectYAML/MachOYAML.h"
#include "llvm/ObjectYAML/yaml2obj.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/YAMLParser.h"
#include "llvm/Support/YAMLTraits.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Testing/Support/SupportHelpers.h"
#include "gtest/gtest.h"
#include <algorithm>
#include <string>
#include <vector>
using namespace llvm;
using namespace llvm::orc;
#if defined(__APPLE__) || defined(__linux__)
// TODO: Add COFF (Windows) support for these tests.
// this facility also works correctly on Windows (COFF),
// so we should eventually enable and run these tests for that platform as well.
namespace {
#if defined(__APPLE__)
constexpr const char *ext = ".dylib";
#elif defined(_WIN32)
constexpr const char *ext = ".dll";
#else
constexpr const char *ext = ".so";
#endif
bool EnvReady = false;
Triple getTargetTriple() {
auto JTMB = JITTargetMachineBuilder::detectHost();
if (!JTMB) {
consumeError(JTMB.takeError());
return Triple();
}
return JTMB->getTargetTriple();
}
static bool CheckHostSupport() {
auto Triple = getTargetTriple();
// TODO: Extend support to COFF (Windows) once test setup and YAML conversion
// are verified.
if (!Triple.isOSBinFormatMachO() &&
!(Triple.isOSBinFormatELF() && Triple.getArch() == Triple::x86_64))
return false;
return true;
}
std::string getYamlFilePlatformExt() {
auto Triple = getTargetTriple();
if (Triple.isOSBinFormatMachO())
return "_macho";
else if (Triple.isOSBinFormatELF())
return "_linux";
return "";
}
unsigned getYamlDocNum() {
// auto Triple = getTargetTriple();
// if (Triple.isOSBinFormatELF())
// return 1;
return 1;
}
class LibraryTestEnvironment : public ::testing::Environment {
std::vector<std::string> CreatedDylibsDir;
std::vector<std::string> CreatedDylibs;
SmallVector<char, 128> DirPath;
public:
void SetUp() override {
if (!CheckHostSupport()) {
EnvReady = false;
return;
}
StringRef ThisFile = __FILE__;
SmallVector<char, 128> InputDirPath(ThisFile.begin(), ThisFile.end());
sys::path::remove_filename(InputDirPath);
sys::path::append(InputDirPath, "Inputs");
if (!sys::fs::exists(InputDirPath))
return;
SmallString<512> ExecPath(sys::fs::getMainExecutable(nullptr, nullptr));
sys::path::remove_filename(ExecPath);
SmallString<128> UniqueDir(ExecPath);
std::error_code EC = sys::fs::createUniqueDirectory(UniqueDir, DirPath);
if (EC)
return;
// given yamlPath + DylibPath, validate + convert
auto processYamlToDylib = [&](const SmallVector<char, 128> &YamlPath,
const SmallVector<char, 128> &DylibPath,
unsigned DocNum) -> bool {
if (!sys::fs::exists(YamlPath)) {
errs() << "YAML file missing: "
<< StringRef(YamlPath.data(), YamlPath.size()) << "\n";
EnvReady = false;
return false;
}
auto BufOrErr = MemoryBuffer::getFile(YamlPath);
if (!BufOrErr) {
errs() << "Failed to read "
<< StringRef(YamlPath.data(), YamlPath.size()) << ": "
<< BufOrErr.getError().message() << "\n";
EnvReady = false;
return false;
}
yaml::Input yin(BufOrErr->get()->getBuffer());
std::error_code EC;
raw_fd_ostream outFile(StringRef(DylibPath.data(), DylibPath.size()), EC,
sys::fs::OF_None);
if (EC) {
errs() << "Failed to open "
<< StringRef(DylibPath.data(), DylibPath.size())
<< " for writing: " << EC.message() << "\n";
EnvReady = false;
return false;
}
if (!yaml::convertYAML(
yin, outFile,
[](const Twine &M) {
// Handle or ignore errors here
errs() << "Yaml Error :" << M << "\n";
},
DocNum)) {
errs() << "Failed to convert "
<< StringRef(YamlPath.data(), YamlPath.size()) << " to "
<< StringRef(DylibPath.data(), DylibPath.size()) << "\n";
EnvReady = false;
return false;
}
CreatedDylibsDir.push_back(std::string(sys::path::parent_path(
StringRef(DylibPath.data(), DylibPath.size()))));
CreatedDylibs.push_back(std::string(DylibPath.begin(), DylibPath.end()));
return true;
};
std::vector<const char *> LibDirs = {"Z", "A", "B", "C"};
unsigned DocNum = getYamlDocNum();
std::string YamlPltExt = getYamlFilePlatformExt();
for (const auto &LibdirName : LibDirs) {
// YAML path
SmallVector<char, 128> YamlPath(InputDirPath.begin(), InputDirPath.end());
SmallVector<char, 128> YamlFileName;
YamlFileName.append(LibdirName, LibdirName + strlen(LibdirName));
YamlFileName.append(YamlPltExt.begin(), YamlPltExt.end());
sys::path::append(YamlPath, LibdirName, YamlFileName);
sys::path::replace_extension(YamlPath, ".yaml");
// dylib path
SmallVector<char, 128> DylibPath(DirPath.begin(), DirPath.end());
SmallVector<char, 128> DylibFileName;
StringRef prefix("lib");
DylibFileName.append(prefix.begin(), prefix.end());
DylibFileName.append(LibdirName, LibdirName + strlen(LibdirName));
sys::path::append(DylibPath, LibdirName);
if (!sys::fs::exists(DylibPath)) {
auto EC = sys::fs::create_directory(DylibPath);
if (EC)
return;
}
sys::path::append(DylibPath, DylibFileName);
sys::path::replace_extension(DylibPath, ext);
if (!processYamlToDylib(YamlPath, DylibPath, DocNum))
return;
}
EnvReady = true;
}
void TearDown() override { sys::fs::remove_directories(DirPath); }
std::string getBaseDir() const {
return std::string(DirPath.begin(), DirPath.end());
}
std::vector<std::string> getDylibPaths() const { return CreatedDylibs; }
};
static LibraryTestEnvironment *GlobalEnv =
static_cast<LibraryTestEnvironment *>(
::testing::AddGlobalTestEnvironment(new LibraryTestEnvironment()));
inline std::string libPath(const std::string &BaseDir,
const std::string &name) {
#if defined(__APPLE__)
return BaseDir + "/" + name + ".dylib";
#elif defined(_WIN32)
return BaseDir + "/" + name + ".dll";
#else
return BaseDir + "/" + name + ".so";
#endif
}
inline std::string withext(const std::string &lib) {
SmallString<128> P(lib);
sys::path::replace_extension(P, ext);
return P.str().str();
}
inline std::string platformSymbolName(const std::string &name) {
#if defined(__APPLE__)
return "_" + name; // macOS prepends underscore
#else
return name;
#endif
}
struct TestLibrary {
std::string path;
std::vector<std::string> Syms;
};
class LibraryResolverIT : public ::testing::Test {
protected:
std::string BaseDir;
std::unordered_map<std::string, TestLibrary> libs;
void addLib(const std::string &name) {
SmallString<512> path;
std::error_code EC =
sys::fs::real_path(libPath(BaseDir, name + "/lib" + name), path);
if (EC || path.empty() || !sys::fs::exists(path))
GTEST_SKIP();
libs[name] = {path.str().str(), {platformSymbolName("say" + name)}};
}
void SetUp() override {
if (!EnvReady || GlobalEnv == nullptr)
GTEST_SKIP() << "Skipping test: environment setup failed.";
{
SmallString<512> path;
std::error_code EC = sys::fs::real_path(GlobalEnv->getBaseDir(), path);
if (path.empty() || EC)
GTEST_SKIP() << "Base directory resolution failed: " << EC.message();
BaseDir = path.str().str();
}
for (const auto &P : GlobalEnv->getDylibPaths()) {
if (!sys::fs::exists(P))
GTEST_SKIP() << "Missing dylib path: " << P;
}
const std::vector<std::string> libNames = {"A", "B", "C", "Z"};
for (const auto &name : libNames)
addLib(name);
if (!EnvReady)
GTEST_SKIP() << "Skipping test: environment setup failed.";
}
const std::vector<std::string> &sym(const std::string &key) {
return libs[key].Syms;
}
const std::string &lib(const std::string &key) { return libs[key].path; }
const std::string libdir(const std::string &key) {
SmallString<512> P(libs[key].path);
sys::path::remove_filename(P);
return P.str().str();
}
const std::string libname(const std::string &key) {
return sys::path::filename(libs[key].path).str();
}
};
// Helper: allow either "sayA" or "_sayA" depending on how your
// SymbolEnumerator reports.
static bool matchesEitherUnderscore(const std::string &got,
const std::string &bare) {
return got == bare || got == ("_" + bare);
}
// Helper: normalize path ending check (we only care that it resolved to the
// right dylib)
static bool endsWith(const std::string &s, const std::string &suffix) {
if (s.size() < suffix.size())
return false;
return std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());
}
TEST_F(LibraryResolverIT, EnumerateSymbols_ExportsOnly_DefaultFlags) {
const std::string libC = lib("C");
SymbolEnumeratorOptions Opts = SymbolEnumeratorOptions::defaultOptions();
std::vector<std::string> seen;
auto onEach = [&](llvm::StringRef sym) -> EnumerateResult {
seen.emplace_back(sym.str());
return EnumerateResult::Continue;
};
ASSERT_TRUE(SymbolEnumerator::enumerateSymbols(libC, onEach, Opts));
// sayC is exported, others are undefined → only sayC expected
EXPECT_TRUE(any_of(seen, [&](const std::string &s) {
return matchesEitherUnderscore(s, "sayC");
}));
EXPECT_FALSE(any_of(seen, [&](const std::string &s) {
return matchesEitherUnderscore(s, "sayA");
}));
EXPECT_FALSE(any_of(seen, [&](const std::string &s) {
return matchesEitherUnderscore(s, "sayB");
}));
}
TEST_F(LibraryResolverIT, EnumerateSymbols_IncludesUndefineds) {
const std::string libC = lib("C");
SymbolEnumeratorOptions Opts;
Opts.FilterFlags =
SymbolEnumeratorOptions::IgnoreWeak |
SymbolEnumeratorOptions::IgnoreIndirect; // no IgnoreUndefined
std::vector<std::string> seen;
auto onEach = [&](llvm::StringRef sym) -> EnumerateResult {
seen.emplace_back(sym.str());
return EnumerateResult::Continue;
};
ASSERT_TRUE(SymbolEnumerator::enumerateSymbols(libC, onEach, Opts));
// Now we should see both sayC (export) and the undefined refs sayA, sayB,
// sayZ
EXPECT_TRUE(any_of(seen, [&](const std::string &s) {
return matchesEitherUnderscore(s, "sayC");
}));
EXPECT_TRUE(any_of(seen, [&](const std::string &s) {
return matchesEitherUnderscore(s, "sayA");
}));
EXPECT_TRUE(any_of(seen, [&](const std::string &s) {
return matchesEitherUnderscore(s, "sayB");
}));
}
// Full resolution via LibraryResolutionDriver/LibraryResolver ---
TEST_F(LibraryResolverIT, DriverResolvesSymbolsToCorrectLibraries) {
// Create the resolver from real base paths (our fixtures dir)
auto Stup = LibraryResolver::Setup::create({BaseDir});
// Full system behavior: no mocks
auto Driver = LibraryResolutionDriver::create(Stup);
ASSERT_NE(Driver, nullptr);
// Tell the Driver about the scan path kinds (User/System) as your
// production code expects.
Driver->addScanPath(libdir("A"), PathType::User);
Driver->addScanPath(libdir("B"), PathType::User);
Driver->addScanPath(libdir("Z"), PathType::User);
// Symbols to resolve (bare names; class handles underscore differences
// internally)
std::vector<std::string> Syms = {platformSymbolName("sayA"),
platformSymbolName("sayB"),
platformSymbolName("sayZ")};
bool CallbackRan = false;
Driver->resolveSymbols(Syms, [&](SymbolQuery &Q) {
CallbackRan = true;
// sayA should resolve to A.dylib
{
auto lib = Q.getResolvedLib(platformSymbolName("sayA"));
ASSERT_TRUE(lib.has_value()) << "sayA should be resolved";
EXPECT_TRUE(endsWith(lib->str(), libname("A")))
<< "sayA resolved to: " << lib->str();
}
// sayB should resolve to B.dylib
{
auto lib = Q.getResolvedLib(platformSymbolName("sayB"));
ASSERT_TRUE(lib.has_value()) << "sayB should be resolved";
EXPECT_TRUE(endsWith(lib->str(), libname("B")))
<< "sayB resolved to: " << lib->str();
}
// sayZ should resolve to B.dylib
{
auto lib = Q.getResolvedLib(platformSymbolName("sayZ"));
ASSERT_TRUE(lib.has_value()) << "sayZ should be resolved";
EXPECT_TRUE(endsWith(lib->str(), libname("Z")))
<< "sayZ resolved to: " << lib->str();
}
EXPECT_TRUE(Q.allResolved());
});
EXPECT_TRUE(CallbackRan);
}
// stress SymbolQuery with the real resolve flow
// And resolve libC dependency libA, libB, libZ ---
TEST_F(LibraryResolverIT, ResolveManySymbols) {
auto Stup = LibraryResolver::Setup::create({BaseDir});
auto Driver = LibraryResolutionDriver::create(Stup);
ASSERT_NE(Driver, nullptr);
Driver->addScanPath(libdir("C"), PathType::User);
// Many duplicates to provoke concurrent updates inside SymbolQuery
std::vector<std::string> Syms = {
platformSymbolName("sayA"), platformSymbolName("sayB"),
platformSymbolName("sayA"), platformSymbolName("sayB"),
platformSymbolName("sayZ"), platformSymbolName("sayZ"),
platformSymbolName("sayZ"), platformSymbolName("sayZ"),
platformSymbolName("sayA"), platformSymbolName("sayB"),
platformSymbolName("sayA"), platformSymbolName("sayB")};
bool CallbackRan = false;
Driver->resolveSymbols(Syms, [&](SymbolQuery &Q) {
CallbackRan = true;
EXPECT_TRUE(Q.isResolved(platformSymbolName("sayA")));
EXPECT_TRUE(Q.isResolved(platformSymbolName("sayB")));
EXPECT_TRUE(Q.isResolved(platformSymbolName("sayZ")));
auto A = Q.getResolvedLib(platformSymbolName("sayA"));
auto B = Q.getResolvedLib(platformSymbolName("sayB"));
auto Z = Q.getResolvedLib(platformSymbolName("sayZ"));
ASSERT_TRUE(A.has_value());
ASSERT_TRUE(B.has_value());
ASSERT_TRUE(Z.has_value());
EXPECT_TRUE(endsWith(A->str(), libname("A")));
EXPECT_TRUE(endsWith(B->str(), libname("B")));
EXPECT_TRUE(endsWith(Z->str(), libname("Z")));
EXPECT_TRUE(Q.allResolved());
});
EXPECT_TRUE(CallbackRan);
}
TEST_F(LibraryResolverIT, ScanAndResolveDependencyGraph) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
LibraryScanHelper ScanH({}, LibPathCache, PResolver);
ScanH.addBasePath(libdir("C"), PathType::User);
LibraryManager LibMgr;
LibraryScanner Scanner(ScanH, LibMgr);
Scanner.scanNext(PathType::User, 0);
size_t numLibs = 0;
LibMgr.forEachLibrary([&](const LibraryInfo &L) {
numLibs++;
return true;
});
EXPECT_GT(numLibs, 0u) << "Expected at least one library scanned";
// Validate that each scanned library path is resolvable
std::error_code EC;
LibMgr.forEachLibrary([&](const LibraryInfo &L) {
auto R = PResolver->resolve(L.getFullPath(), EC);
EXPECT_TRUE(R.has_value());
EXPECT_FALSE(EC);
return true;
});
}
TEST_F(LibraryResolverIT, ScanEmptyPath) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
LibraryScanHelper ScanH({}, LibPathCache, PResolver);
ScanH.addBasePath("/tmp/empty", PathType::User);
LibraryManager LibMgr;
LibraryScanner Scanner(ScanH, LibMgr);
Scanner.scanNext(PathType::User, 0);
size_t count = 0;
LibMgr.forEachLibrary([&](const LibraryInfo &) {
count++;
return true;
});
EXPECT_EQ(count, 0u);
}
TEST_F(LibraryResolverIT, PathResolverResolvesKnownPaths) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
std::error_code EC;
auto Missing = PResolver->resolve("temp/foo/bar", EC);
EXPECT_FALSE(Missing.has_value()) << "Unexpectedly resolved a bogus path";
EXPECT_TRUE(EC) << "Expected error resolving path";
auto DirPath = PResolver->resolve(BaseDir, EC);
ASSERT_TRUE(DirPath.has_value());
EXPECT_FALSE(EC) << "Expected no error resolving path";
EXPECT_EQ(*DirPath, BaseDir);
auto DylibPath = PResolver->resolve(lib("C"), EC);
ASSERT_TRUE(DylibPath.has_value());
EXPECT_FALSE(EC) << "Expected no error resolving path";
EXPECT_EQ(*DylibPath, lib("C"));
}
TEST_F(LibraryResolverIT, PathResolverNormalizesDotAndDotDot) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
std::error_code EC;
// e.g. BaseDir + "/./C/../C/C.dylib" → BaseDir + "/C.dylib"
std::string Messy = BaseDir + "/C/./../C/./libC" + ext;
auto Resolved = PResolver->resolve(Messy, EC);
ASSERT_TRUE(Resolved.has_value());
EXPECT_FALSE(EC);
EXPECT_EQ(*Resolved, lib("C")) << "Expected realpath to collapse . and ..";
}
#if !defined(_WIN32)
TEST_F(LibraryResolverIT, PathResolverFollowsSymlinks) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
std::error_code EC;
// Create a symlink temp -> BaseDir (only if filesystem allows it)
std::string linkName = BaseDir + withext("/link_to_C");
std::string target = lib("C");
if (::symlink(target.c_str(), linkName.c_str()) != 0)
GTEST_SKIP() << "Failed to create symlink: " << strerror(errno);
auto resolved = PResolver->resolve(linkName, EC);
ASSERT_TRUE(resolved.has_value());
EXPECT_FALSE(EC);
EXPECT_EQ(*resolved, target);
(void)::unlink(linkName.c_str()); // cleanup
}
TEST_F(LibraryResolverIT, PathResolverCachesResults) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
SmallString<128> TmpDylib;
std::error_code EC;
EC = sys::fs::createUniqueFile(withext("A-copy"), TmpDylib);
if (EC)
GTEST_SKIP() << "Failed to create temp dylib" << EC.message();
EC = sys::fs::copy_file(lib("A"), TmpDylib);
if (EC)
GTEST_SKIP() << "Failed to copy libA: " << EC.message();
EC.clear();
// First resolve -> should populate LibPathCache
auto first = PResolver->resolve(TmpDylib, EC);
ASSERT_TRUE(first.has_value());
// Forcefully remove the file from disk
(void)::unlink(TmpDylib.c_str());
// Second resolve -> should still succeed from LibPathCache
auto second = PResolver->resolve(TmpDylib, EC);
EXPECT_TRUE(second.has_value());
EXPECT_EQ(*second, *first);
}
#endif
TEST_F(LibraryResolverIT, LoaderPathSubstitutionAndResolve) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
DylibSubstitutor substitutor;
substitutor.configure(libdir("C"));
#if defined(__APPLE__)
// Substitute @loader_path with BaseDir
std::string substituted =
substitutor.substitute(withext("@loader_path/libC"));
#elif defined(__linux__)
// Substitute $origin with BaseDir
std::string substituted = substitutor.substitute(withext("$ORIGIN/libC"));
#endif
ASSERT_FALSE(substituted.empty());
EXPECT_EQ(substituted, lib("C"));
// Now try resolving the substituted path
std::error_code EC;
auto resolved = PResolver->resolve(substituted, EC);
ASSERT_TRUE(resolved.has_value()) << "Expected to resolve substituted dylib";
EXPECT_EQ(*resolved, lib("C"));
EXPECT_FALSE(EC) << "Expected no error resolving substituted dylib";
}
TEST_F(LibraryResolverIT, ResolveFromUsrOrSystemPaths) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
DylibPathValidator validator(*PResolver);
std::vector<std::string> Paths = {"/foo/bar/", "temp/foo", libdir("C"),
libdir("A"), libdir("B"), libdir("Z")};
SmallVector<StringRef> P(Paths.begin(), Paths.end());
DylibResolver Resolver(validator);
Resolver.configure("", {{P, SearchPathType::UsrOrSys}});
// Check "C"
auto ValOptC = Resolver.resolve("libC", true);
EXPECT_TRUE(ValOptC.has_value());
EXPECT_EQ(*ValOptC, lib("C"));
auto ValOptCdylib = Resolver.resolve(withext("libC"));
EXPECT_TRUE(ValOptCdylib.has_value());
EXPECT_EQ(*ValOptCdylib, lib("C"));
// Check "A"
auto ValOptA = Resolver.resolve("libA", true);
EXPECT_TRUE(ValOptA.has_value());
EXPECT_EQ(*ValOptA, lib("A"));
auto ValOptAdylib = Resolver.resolve(withext("libA"));
EXPECT_TRUE(ValOptAdylib.has_value());
EXPECT_EQ(*ValOptAdylib, lib("A"));
// Check "B"
auto ValOptB = Resolver.resolve("libB", true);
EXPECT_TRUE(ValOptB.has_value());
EXPECT_EQ(*ValOptB, lib("B"));
auto ValOptBdylib = Resolver.resolve(withext("libB"));
EXPECT_TRUE(ValOptBdylib.has_value());
EXPECT_EQ(*ValOptBdylib, lib("B"));
// Check "Z"
auto ValOptZ = Resolver.resolve("libZ", true);
EXPECT_TRUE(ValOptZ.has_value());
EXPECT_EQ(*ValOptZ, lib("Z"));
auto ValOptZdylib = Resolver.resolve(withext("libZ"));
EXPECT_TRUE(ValOptZdylib.has_value());
EXPECT_EQ(*ValOptZdylib, lib("Z"));
}
#if defined(__APPLE__)
TEST_F(LibraryResolverIT, ResolveViaLoaderPathAndRPathSubstitution) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
DylibPathValidator validator(*PResolver);
std::vector<std::string> Paths = {"@loader_path/../A", "@loader_path/../B",
"@loader_path/../C", "@loader_path/../Z"};
SmallVector<StringRef> P(Paths.begin(), Paths.end());
DylibResolver Resolver(validator);
// Use only RPath config
Resolver.configure(lib("C"), {{P, SearchPathType::RPath}});
// --- Check A ---
auto ValOptA = Resolver.resolve("@rpath/libA", true);
EXPECT_TRUE(ValOptA.has_value());
EXPECT_EQ(*ValOptA, lib("A"));
auto ValOptAdylib = Resolver.resolve(withext("@rpath/libA"));
EXPECT_TRUE(ValOptAdylib.has_value());
EXPECT_EQ(*ValOptAdylib, lib("A"));
// --- Check B ---
auto ValOptB = Resolver.resolve("@rpath/libB", true);
EXPECT_TRUE(ValOptB.has_value());
EXPECT_EQ(*ValOptB, lib("B"));
auto ValOptBdylib = Resolver.resolve(withext("@rpath/libB"));
EXPECT_TRUE(ValOptBdylib.has_value());
EXPECT_EQ(*ValOptBdylib, lib("B"));
// --- Check Z ---
auto ValOptZ = Resolver.resolve("@rpath/libZ", true);
EXPECT_TRUE(ValOptZ.has_value());
EXPECT_EQ(*ValOptZ, lib("Z"));
auto ValOptZdylib = Resolver.resolve(withext("@rpath/libZ"));
EXPECT_TRUE(ValOptZdylib.has_value());
EXPECT_EQ(*ValOptZdylib, lib("Z"));
}
#endif
#if defined(__linux__)
TEST_F(LibraryResolverIT, ResolveViaOriginAndRPathSubstitution) {
auto LibPathCache = std::make_shared<LibraryPathCache>();
auto PResolver = std::make_shared<PathResolver>(LibPathCache);
DylibPathValidator validator(*PResolver);
// On Linux, $ORIGIN works like @loader_path
std::vector<std::string> Paths = {"$ORIGIN/../A", "$ORIGIN/../B",
"$ORIGIN/../C", "$ORIGIN/../Z"};
SmallVector<StringRef> P(Paths.begin(), Paths.end());
DylibResolver Resolver(validator);
// Use only RPath config
Resolver.configure(lib("C"), {{P, SearchPathType::RunPath}});
// --- Check A ---
auto ValOptA = Resolver.resolve("libA", true);
EXPECT_TRUE(ValOptA.has_value());
EXPECT_EQ(*ValOptA, lib("A"));
auto valOptASO = Resolver.resolve(withext("libA"));
EXPECT_TRUE(valOptASO.has_value());
EXPECT_EQ(*valOptASO, lib("A"));
// --- Check B ---
auto ValOptB = Resolver.resolve("libB", true);
EXPECT_TRUE(ValOptB.has_value());
EXPECT_EQ(*ValOptB, lib("B"));
auto valOptBSO = Resolver.resolve(withext("libB"));
EXPECT_TRUE(valOptBSO.has_value());
EXPECT_EQ(*valOptBSO, lib("B"));
// --- Check Z ---
auto ValOptZ = Resolver.resolve("libZ", true);
EXPECT_TRUE(ValOptZ.has_value());
EXPECT_EQ(*ValOptZ, lib("Z"));
auto valOptZSO = Resolver.resolve(withext("libZ"));
EXPECT_TRUE(valOptZSO.has_value());
EXPECT_EQ(*valOptZSO, lib("Z"));
}
#endif
} // namespace
#endif // defined(__APPLE__)