diff options
Diffstat (limited to 'llvm/unittests/ExecutionEngine/Orc/LibraryResolverTest.cpp')
| -rw-r--r-- | llvm/unittests/ExecutionEngine/Orc/LibraryResolverTest.cpp | 764 | 
1 files changed, 764 insertions, 0 deletions
diff --git a/llvm/unittests/ExecutionEngine/Orc/LibraryResolverTest.cpp b/llvm/unittests/ExecutionEngine/Orc/LibraryResolverTest.cpp new file mode 100644 index 0000000..2a396da --- /dev/null +++ b/llvm/unittests/ExecutionEngine/Orc/LibraryResolverTest.cpp @@ -0,0 +1,764 @@ +//===- 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 <optional> +#include <string> +#include <vector> + +using namespace llvm; +using namespace llvm::orc; + +// Disabled due to test setup issue — YAML to shared library creation seems +// invalid on some build bots. (PR #165360) Not related to code logic. +#if 0 +// 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<128> UniqueDir; +    sys::path::append(UniqueDir, InputDirPath); +    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__)  | 
