CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
771 lines
24 KiB
C++
771 lines
24 KiB
C++
//===- 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());
|
|
}
|
|
|
|
static llvm::SmallVector<llvm::StringRef, 16>
|
|
toRefs(const std::vector<std::string> &Syms) {
|
|
llvm::SmallVector<llvm::StringRef, 16> R;
|
|
for (auto &S : Syms)
|
|
R.push_back(S);
|
|
return R;
|
|
}
|
|
|
|
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(toRefs(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(toRefs(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, *LibPathCache);
|
|
|
|
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, *LibPathCache);
|
|
|
|
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, *LibPathCache);
|
|
|
|
// 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__)
|