using FluentAssertions; using Xunit; namespace StellaOps.Scanner.Analyzers.Native.Tests; public class ElfResolverTests { [Fact] public void Resolve_WithRpath_FindsLibraryInRpathDirectory() { // Arrange var fs = new VirtualFileSystem(["/opt/myapp/lib/libfoo.so.1"]); var rpaths = new[] { "/opt/myapp/lib" }; var runpaths = Array.Empty(); // Act var result = ElfResolver.Resolve("libfoo.so.1", rpaths, runpaths, null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/opt/myapp/lib/libfoo.so.1"); result.Steps.Should().ContainSingle() .Which.Should().Match(s => s.SearchPath == "/opt/myapp/lib" && s.SearchReason == "rpath" && s.Found == true); } [Fact] public void Resolve_WithRunpath_IgnoresRpath() { // Arrange - library exists in rpath but not runpath var fs = new VirtualFileSystem([ "/opt/rpath/lib/libfoo.so.1", "/opt/runpath/lib/libfoo.so.1" ]); var rpaths = new[] { "/opt/rpath/lib" }; var runpaths = new[] { "/opt/runpath/lib" }; // Act var result = ElfResolver.Resolve("libfoo.so.1", rpaths, runpaths, null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/opt/runpath/lib/libfoo.so.1"); result.Steps.Should().NotContain(s => s.SearchReason == "rpath"); result.Steps.Should().Contain(s => s.SearchReason == "runpath"); } [Fact] public void Resolve_WithLdLibraryPath_SearchesBeforeRunpath() { // Arrange var fs = new VirtualFileSystem([ "/custom/lib/libfoo.so.1", "/opt/runpath/lib/libfoo.so.1" ]); var rpaths = Array.Empty(); var runpaths = new[] { "/opt/runpath/lib" }; var ldLibraryPath = new[] { "/custom/lib" }; // Act var result = ElfResolver.Resolve("libfoo.so.1", rpaths, runpaths, ldLibraryPath, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/custom/lib/libfoo.so.1"); result.Steps.First().SearchReason.Should().Be("ld_library_path"); } [Fact] public void Resolve_WithOriginExpansion_ExpandsOriginVariable() { // Arrange var fs = new VirtualFileSystem(["/app/bin/../lib/libfoo.so.1"]); var rpaths = new[] { "$ORIGIN/../lib" }; var runpaths = Array.Empty(); // Act var result = ElfResolver.Resolve("libfoo.so.1", rpaths, runpaths, null, "/app/bin", fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/app/bin/../lib/libfoo.so.1"); } [Fact] public void Resolve_WithOriginBraceSyntax_ExpandsOriginVariable() { // Arrange var fs = new VirtualFileSystem(["/app/bin/../lib/libbar.so.2"]); var rpaths = new[] { "${ORIGIN}/../lib" }; var runpaths = Array.Empty(); // Act var result = ElfResolver.Resolve("libbar.so.2", rpaths, runpaths, null, "/app/bin", fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/app/bin/../lib/libbar.so.2"); } [Fact] public void Resolve_NotFound_ReturnsUnresolvedWithSteps() { // Arrange var fs = new VirtualFileSystem([]); var rpaths = new[] { "/opt/lib" }; var runpaths = Array.Empty(); // Act var result = ElfResolver.Resolve("libmissing.so.1", rpaths, runpaths, null, null, fs); // Assert result.Resolved.Should().BeFalse(); result.ResolvedPath.Should().BeNull(); result.Steps.Should().NotBeEmpty(); result.Steps.Should().Contain(s => s.SearchReason == "rpath" && !s.Found); result.Steps.Should().Contain(s => s.SearchReason == "default" && !s.Found); } [Fact] public void Resolve_WithDefaultPaths_SearchesSystemDirectories() { // Arrange var fs = new VirtualFileSystem(["/usr/lib/libc.so.6"]); // Act var result = ElfResolver.Resolve("libc.so.6", [], [], null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/usr/lib/libc.so.6"); result.Steps.Should().Contain(s => s.SearchReason == "default"); } [Fact] public void Resolve_SearchOrder_FollowsCorrectPriority() { // Arrange - library exists in all locations var fs = new VirtualFileSystem([ "/rpath/libfoo.so", "/ldpath/libfoo.so", "/usr/lib/libfoo.so" ]); var rpaths = new[] { "/rpath" }; var ldLibraryPath = new[] { "/ldpath" }; // Act - no runpath, so rpath should be checked first var result = ElfResolver.Resolve("libfoo.so", rpaths, [], ldLibraryPath, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/rpath/libfoo.so"); result.Steps.First().SearchReason.Should().Be("rpath"); } } public class PeResolverTests { [Fact] public void Resolve_InApplicationDirectory_FindsDll() { // Arrange var fs = new VirtualFileSystem(["C:/MyApp/mylib.dll"]); // Act var result = PeResolver.Resolve("mylib.dll", "C:/MyApp", null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("C:/MyApp/mylib.dll"); result.Steps.Should().ContainSingle() .Which.SearchReason.Should().Be("application_directory"); } [Fact] public void Resolve_InSystem32_FindsDll() { // Arrange var fs = new VirtualFileSystem(["C:/Windows/System32/kernel32.dll"]); // Act var result = PeResolver.Resolve("kernel32.dll", "C:/MyApp", null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("C:/Windows/System32/kernel32.dll"); result.Steps.Should().Contain(s => s.SearchReason == "system_directory"); } [Fact] public void Resolve_InSysWOW64_FindsDll() { // Arrange var fs = new VirtualFileSystem(["C:/Windows/SysWOW64/wow64.dll"]); // Act var result = PeResolver.Resolve("wow64.dll", null, null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("C:/Windows/SysWOW64/wow64.dll"); } [Fact] public void Resolve_InCurrentDirectory_FindsDll() { // Arrange var fs = new VirtualFileSystem(["C:/WorkDir/plugin.dll"]); // Act var result = PeResolver.Resolve("plugin.dll", "C:/MyApp", "C:/WorkDir", null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("C:/WorkDir/plugin.dll"); result.Steps.Should().Contain(s => s.SearchReason == "current_directory" && s.Found); } [Fact] public void Resolve_InPathEnvironment_FindsDll() { // Arrange var fs = new VirtualFileSystem(["D:/Tools/bin/tool.dll"]); var pathEnv = new[] { "D:/Tools/bin", "D:/Other" }; // Act var result = PeResolver.Resolve("tool.dll", "C:/MyApp", null, pathEnv, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("D:/Tools/bin/tool.dll"); result.Steps.Should().Contain(s => s.SearchReason == "path_environment" && s.Found); } [Fact] public void Resolve_SafeDllSearchOrder_ApplicationBeforeSystem() { // Arrange - DLL exists in both app dir and system32 var fs = new VirtualFileSystem([ "C:/MyApp/common.dll", "C:/Windows/System32/common.dll" ]); // Act var result = PeResolver.Resolve("common.dll", "C:/MyApp", null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("C:/MyApp/common.dll"); result.Steps.First().SearchReason.Should().Be("application_directory"); } [Fact] public void Resolve_NotFound_ReturnsAllSearchedPaths() { // Arrange var fs = new VirtualFileSystem([]); var pathEnv = new[] { "D:/Tools" }; // Act var result = PeResolver.Resolve("missing.dll", "C:/MyApp", "C:/Work", pathEnv, fs); // Assert result.Resolved.Should().BeFalse(); result.ResolvedPath.Should().BeNull(); result.Steps.Should().HaveCountGreaterThan(4); result.Steps.Should().Contain(s => s.SearchReason == "application_directory"); result.Steps.Should().Contain(s => s.SearchReason == "system_directory"); result.Steps.Should().Contain(s => s.SearchReason == "current_directory"); result.Steps.Should().Contain(s => s.SearchReason == "path_environment"); } [Fact] public void Resolve_WithNullApplicationDirectory_SkipsAppDirSearch() { // Arrange var fs = new VirtualFileSystem(["C:/Windows/System32/test.dll"]); // Act var result = PeResolver.Resolve("test.dll", null, null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.Steps.Should().NotContain(s => s.SearchReason == "application_directory"); } } public class MachOResolverTests { [Fact] public void Resolve_WithRpath_ExpandsAndFindsLibrary() { // Arrange var fs = new VirtualFileSystem(["/opt/myapp/Frameworks/libfoo.dylib"]); var rpaths = new[] { "/opt/myapp/Frameworks" }; // Act var result = MachOResolver.Resolve("@rpath/libfoo.dylib", rpaths, null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/opt/myapp/Frameworks/libfoo.dylib"); result.Steps.Should().ContainSingle() .Which.SearchReason.Should().Be("rpath"); } [Fact] public void Resolve_WithMultipleRpaths_SearchesInOrder() { // Arrange var fs = new VirtualFileSystem(["/second/path/libfoo.dylib"]); var rpaths = new[] { "/first/path", "/second/path" }; // Act var result = MachOResolver.Resolve("@rpath/libfoo.dylib", rpaths, null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/second/path/libfoo.dylib"); result.Steps.Should().HaveCount(2); result.Steps[0].Found.Should().BeFalse(); result.Steps[1].Found.Should().BeTrue(); } [Fact] public void Resolve_WithLoaderPath_ExpandsPlaceholder() { // Arrange var fs = new VirtualFileSystem(["/app/Contents/MacOS/../Frameworks/libbar.dylib"]); // Act var result = MachOResolver.Resolve( "@loader_path/../Frameworks/libbar.dylib", [], "/app/Contents/MacOS", null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/app/Contents/MacOS/../Frameworks/libbar.dylib"); result.Steps.Should().ContainSingle() .Which.SearchReason.Should().Be("loader_path"); } [Fact] public void Resolve_WithExecutablePath_ExpandsPlaceholder() { // Arrange var fs = new VirtualFileSystem(["/Applications/MyApp.app/Contents/MacOS/../Frameworks/lib.dylib"]); // Act var result = MachOResolver.Resolve( "@executable_path/../Frameworks/lib.dylib", [], null, "/Applications/MyApp.app/Contents/MacOS", fs); // Assert result.Resolved.Should().BeTrue(); result.Steps.Should().ContainSingle() .Which.SearchReason.Should().Be("executable_path"); } [Fact] public void Resolve_WithRpathContainingLoaderPath_ExpandsBoth() { // Arrange var fs = new VirtualFileSystem(["/app/bin/../lib/libfoo.dylib"]); var rpaths = new[] { "@loader_path/../lib" }; // Act var result = MachOResolver.Resolve("@rpath/libfoo.dylib", rpaths, "/app/bin", null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/app/bin/../lib/libfoo.dylib"); } [Fact] public void Resolve_AbsolutePath_ChecksDirectly() { // Arrange var fs = new VirtualFileSystem(["/usr/lib/libSystem.B.dylib"]); // Act var result = MachOResolver.Resolve("/usr/lib/libSystem.B.dylib", [], null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/usr/lib/libSystem.B.dylib"); result.Steps.Should().ContainSingle() .Which.SearchReason.Should().Be("absolute_path"); } [Fact] public void Resolve_RelativePath_SearchesDefaultPaths() { // Arrange var fs = new VirtualFileSystem(["/usr/local/lib/libcustom.dylib"]); // Act var result = MachOResolver.Resolve("libcustom.dylib", [], null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/usr/local/lib/libcustom.dylib"); result.Steps.Should().Contain(s => s.SearchReason == "default_library_path"); } [Fact] public void Resolve_RpathNotFound_FallsBackToDefaultPaths() { // Arrange - library not in rpath but in default path var fs = new VirtualFileSystem(["/usr/lib/libfoo.dylib"]); var rpaths = new[] { "/nonexistent/path" }; // Act var result = MachOResolver.Resolve("@rpath/libfoo.dylib", rpaths, null, null, fs); // Assert result.Resolved.Should().BeTrue(); result.ResolvedPath.Should().Be("/usr/lib/libfoo.dylib"); result.Steps.Should().Contain(s => s.SearchReason == "rpath" && !s.Found); result.Steps.Should().Contain(s => s.SearchReason == "default_library_path" && s.Found); } [Fact] public void Resolve_NotFound_ReturnsAllSearchedPaths() { // Arrange var fs = new VirtualFileSystem([]); var rpaths = new[] { "/opt/lib" }; // Act var result = MachOResolver.Resolve("@rpath/missing.dylib", rpaths, null, null, fs); // Assert result.Resolved.Should().BeFalse(); result.ResolvedPath.Should().BeNull(); result.Steps.Should().NotBeEmpty(); result.Steps.Should().OnlyContain(s => !s.Found); } [Fact] public void Resolve_LoaderPathNotFound_ReturnsFalse() { // Arrange var fs = new VirtualFileSystem([]); // Act var result = MachOResolver.Resolve("@loader_path/missing.dylib", [], "/app", null, fs); // Assert result.Resolved.Should().BeFalse(); result.Steps.Should().ContainSingle() .Which.SearchReason.Should().Be("loader_path"); } } public class VirtualFileSystemTests { [Fact] public void FileExists_WithExistingFile_ReturnsTrue() { // Arrange var fs = new VirtualFileSystem(["/usr/lib/libc.so.6"]); // Act & Assert fs.FileExists("/usr/lib/libc.so.6").Should().BeTrue(); } [Fact] public void FileExists_WithNonExistingFile_ReturnsFalse() { // Arrange var fs = new VirtualFileSystem(["/usr/lib/libc.so.6"]); // Act & Assert fs.FileExists("/usr/lib/missing.so").Should().BeFalse(); } [Fact] public void FileExists_IsCaseInsensitive() { // Arrange var fs = new VirtualFileSystem(["/USR/LIB/libc.so.6"]); // Act & Assert fs.FileExists("/usr/lib/LIBC.SO.6").Should().BeTrue(); } [Fact] public void DirectoryExists_WithExistingDirectory_ReturnsTrue() { // Arrange var fs = new VirtualFileSystem(["/usr/lib/x86_64-linux-gnu/libc.so.6"]); // Act & Assert fs.DirectoryExists("/usr/lib").Should().BeTrue(); fs.DirectoryExists("/usr/lib/x86_64-linux-gnu").Should().BeTrue(); } [Fact] public void NormalizePath_HandlesBackslashes() { // Arrange var fs = new VirtualFileSystem(["C:/Windows/System32/kernel32.dll"]); // Act & Assert fs.FileExists("C:\\Windows\\System32\\kernel32.dll").Should().BeTrue(); } [Fact] public void EnumerateFiles_ReturnsFilesInDirectory() { // Arrange var fs = new VirtualFileSystem([ "/usr/lib/liba.so", "/usr/lib/libb.so", "/usr/local/lib/libc.so" ]); // Act var files = fs.EnumerateFiles("/usr/lib", "*").ToList(); // Assert files.Should().HaveCount(2); files.Should().Contain("/usr/lib/liba.so"); files.Should().Contain("/usr/lib/libb.so"); } }