From 9281796251c3235f9ed9581751a0393720249018 Mon Sep 17 00:00:00 2001 From: Matt Nadareski Date: Thu, 2 Jul 2026 10:21:15 -0400 Subject: [PATCH] ResolvePath should always return an aboslute path --- .../IOExtensionsTests.cs | 67 +++++++++++++------ SabreTools.IO.Extensions/IOExtensions.cs | 14 ++-- 2 files changed, 53 insertions(+), 28 deletions(-) diff --git a/SabreTools.IO.Extensions.Test/IOExtensionsTests.cs b/SabreTools.IO.Extensions.Test/IOExtensionsTests.cs index d2c4314..0a60166 100644 --- a/SabreTools.IO.Extensions.Test/IOExtensionsTests.cs +++ b/SabreTools.IO.Extensions.Test/IOExtensionsTests.cs @@ -369,29 +369,60 @@ namespace SabreTools.IO.Extensions.Test // TODO: Add tests for home path without modifying the runner's home path [Fact] - public void ResolvePath_AbsolutePath_Exists_ReturnsAsIs() + public void ResolvePath_AbsolutePath_Exists_ReturnsFullPath() { - string path = typeof(IOExtensionsTests).Assembly.Location; + string filename = Guid.NewGuid().ToString(); + string path = Path.Combine(Environment.CurrentDirectory, filename); - string? actual = path.ResolvePath(); - Assert.Equal(path, actual); + File.WriteAllBytes(path, []); + try + { + string? actual = filename.ResolvePath(); + Assert.Equal(path, actual); + } + finally + { + File.Delete(path); + } } [Fact] public void ResolvePath_AbsolutePath_Missing_ReturnsNull() { - string path = Path.Combine(Path.GetTempPath(), "fake-path.bin"); + string filename = Guid.NewGuid().ToString(); + string path = Path.Combine(Environment.CurrentDirectory, filename); string? actual = path.ResolvePath(); Assert.Null(actual); } - // TODO: Add ResolvePath_RelativePath_Exists_ReturnsAsIs + [Fact] + public void ResolvePath_RelativePath_Exists_ReturnsFullPath() + { + string filename = Guid.NewGuid().ToString(); + string subDir = "RELATIVE"; + string path = Path.Combine(subDir, filename); + string expected = Path.Combine(Environment.CurrentDirectory, path); + + Directory.CreateDirectory(subDir); + File.WriteAllBytes(path, []); + try + { + string? actual = path.ResolvePath(); + Assert.Equal(expected, actual); + } + finally + { + Directory.Delete(subDir, recursive: true); + } + } [Fact] public void ResolvePath_RelativePath_Missing_ReturnsNull() { - string path = Path.Combine("INVALID", "fake-path.bin"); + string filename = Guid.NewGuid().ToString(); + string subDir = "INVALID"; + string path = Path.Combine(subDir, filename); string? actual = path.ResolvePath(); Assert.Null(actual); @@ -400,8 +431,7 @@ namespace SabreTools.IO.Extensions.Test [Fact] public void ResolvePath_BareName_RuntimeDirectory_ReturnsFullPath() { - // Create a file in the test assembly's runtime directory - string filename = "fake-path.bin"; + string filename = Guid.NewGuid().ToString(); string path = Path.Combine(AppContext.BaseDirectory, filename); File.WriteAllBytes(path, []); @@ -419,13 +449,12 @@ namespace SabreTools.IO.Extensions.Test [Fact] public void ResolvePath_BareName_Path_ReturnsFullPath() { - // Create a temporary directory for testing - string tempDir = Path.Combine(Path.GetTempPath(), "fake-path"); - Directory.CreateDirectory(tempDir); + string filename = Guid.NewGuid().ToString(); + string subDir = Path.GetFullPath("TEMP"); + string path = Path.Combine(subDir, filename); + string expected = Path.Combine(Environment.CurrentDirectory, path); - // Create a file in the temporary directory - string filename = "fake-path.bin"; - string path = Path.Combine(tempDir, filename); + Directory.CreateDirectory(subDir); File.WriteAllBytes(path, []); // Prepend the temp directory to PATH and check it exists @@ -433,21 +462,21 @@ namespace SabreTools.IO.Extensions.Test string? originalPath = Environment.GetEnvironmentVariable("PATH"); try { - Environment.SetEnvironmentVariable("PATH", tempDir + Path.PathSeparator + (originalPath ?? string.Empty)); + Environment.SetEnvironmentVariable("PATH", subDir + Path.PathSeparator + (originalPath ?? string.Empty)); string? actual = filename.ResolvePath(); - Assert.Equal(path, actual); + Assert.Equal(expected, actual); } finally { Environment.SetEnvironmentVariable("PATH", originalPath); - Directory.Delete(tempDir, recursive: true); + Directory.Delete(subDir, recursive: true); } } [Fact] public void ResolvePath_BareName_Missing_ReturnsNull() { - string filename = "fake-path.bin"; + string filename = Guid.NewGuid().ToString(); string? actual = filename.ResolvePath(); Assert.Null(actual); diff --git a/SabreTools.IO.Extensions/IOExtensions.cs b/SabreTools.IO.Extensions/IOExtensions.cs index f918346..e508f43 100644 --- a/SabreTools.IO.Extensions/IOExtensions.cs +++ b/SabreTools.IO.Extensions/IOExtensions.cs @@ -419,15 +419,11 @@ namespace SabreTools.IO.Extensions } /// - /// Resolve a file path that may be relative or contained within a PATH directory. + /// Resolve a file path that may be absolute, relative, + /// within the runtime directory, or contained within a PATH directory. /// /// Raw value from the user's options /// The absolute path of the located files, or null on failure - /// - /// A value containing a path separator is treated as an explicit location and - /// returned as-is when it exists. A bare name (no separator) is searched in the - /// runtime directory first, then in each PATH entry. - /// public static string? ResolvePath(this string? path) { // Invalid paths always return null @@ -442,12 +438,12 @@ namespace SabreTools.IO.Extensions path = path.Substring(2); path = Path.Combine(homeDirectory, path); - return File.Exists(path) ? path : null; + return File.Exists(path) ? Path.GetFullPath(path) : null; } // Explicit location (absolute or relative path) if (path.Contains("/") || path.Contains("\\")) - return File.Exists(path) ? path : null; + return File.Exists(path) ? Path.GetFullPath(path) : null; // Check the runtime directory if no directory path is provided string runtimeDir = PathTool.GetRuntimeDirectory(); @@ -940,7 +936,7 @@ namespace SabreTools.IO.Extensions private static EnumerationOptions FromSearchOption(SearchOption searchOption) { if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) - throw new System.ArgumentOutOfRangeException(nameof(searchOption)); + throw new ArgumentOutOfRangeException(nameof(searchOption)); return searchOption == SearchOption.AllDirectories ? new EnumerationOptions { RecurseSubdirectories = true, MatchType = MatchType.Win32, AttributesToSkip = 0, IgnoreInaccessible = false }