Skip to content

Commit 33b59d7

Browse files
natemcmastergithub-actions[bot]claude
authored
fix: validate dotnet path exists before returning from TryFindDotNetExePath (#607)
* fix: validate dotnet path exists before returning from TryFindDotNetExePath TryFindDotNetExePath() was always returning a constructed path even when the file didn't exist (e.g., /usr/local/share/dotnet/dotnet on Alpine). This prevented FullPathOrDefault() from falling back to just "dotnet". Added File.Exists check and extracted FindDotNetInRoot as an internal method for testability. Added tests covering exists/not-exists scenarios. Fixes #600 Co-authored-by: Nate McMaster <natemcmaster@users.noreply.github.com> * docs: update release notes for 5.1.0 with dotnet path fix Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Nate McMaster <natemcmaster@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0709a62 commit 33b59d7

File tree

4 files changed

+71
-1
lines changed

4 files changed

+71
-1
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
### Features
66
* [@Chris-Wolfgang]: Add support for keyed dependency injection via `[FromKeyedServices]` attribute ([#560])
77

8+
### Fixes
9+
* [@claude]: Validate dotnet path exists before returning from `TryFindDotNetExePath` ([#600])
10+
811
[#560]: https://github.com/natemcmaster/CommandLineUtils/pull/560
12+
[#600]: https://github.com/natemcmaster/CommandLineUtils/issues/600
913

1014
## [v5.0.1](https://github.com/natemcmaster/CommandLineUtils/compare/v5.0.0...v5.0.1)
1115

src/CommandLineUtils/Utilities/DotNetExe.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,13 @@ public static string FullPathOrDefault()
6565
dotnetRoot = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\Program Files\\dotnet" : "/usr/local/share/dotnet";
6666
}
6767

68-
return Path.Combine(dotnetRoot, fileName);
68+
return FindDotNetInRoot(dotnetRoot, fileName);
69+
}
70+
71+
internal static string? FindDotNetInRoot(string dotnetRoot, string fileName)
72+
{
73+
var dotnetPath = Path.Combine(dotnetRoot, fileName);
74+
return File.Exists(dotnetPath) ? dotnetPath : null;
6975
}
7076
}
7177
}

src/CommandLineUtils/releasenotes.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ Changes since 5.0:
55

66
Features:
77
* @Chris-Wolfgang: Add support for keyed dependency injection via [FromKeyedServices] attribute (#560)
8+
9+
Fixes:
10+
* @claude: Validate dotnet path exists before returning from TryFindDotNetExePath (#600)
811
</PackageReleaseNotes>
912
<PackageReleaseNotes Condition="$(VersionPrefix.StartsWith('5.0.'))">
1013
Changes since 4.1:

test/CommandLineUtils.Tests/DotNetExeTests.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#if NET6_0_OR_GREATER
77
using System.IO;
8+
using System.Runtime.InteropServices;
89
using Xunit;
910

1011
namespace McMaster.Extensions.CommandLineUtils.Tests
@@ -20,6 +21,62 @@ public void FindsTheDotNetPath()
2021
Assert.True(Path.IsPathRooted(dotnetPath), "The path should be rooted");
2122
Assert.Equal("dotnet", Path.GetFileNameWithoutExtension(dotnetPath), ignoreCase: true);
2223
}
24+
25+
[Fact]
26+
public void FullPathOrDefaultReturnsPathOrDotnet()
27+
{
28+
var result = DotNetExe.FullPathOrDefault();
29+
Assert.NotNull(result);
30+
Assert.NotEmpty(result);
31+
// Should either be a rooted path that exists, or just "dotnet"
32+
if (Path.IsPathRooted(result))
33+
{
34+
Assert.True(File.Exists(result), "The file did not exist");
35+
}
36+
else
37+
{
38+
Assert.Equal("dotnet", result);
39+
}
40+
}
41+
42+
[Fact]
43+
public void FindDotNetInRoot_ReturnsNull_WhenDirectoryDoesNotExist()
44+
{
45+
var fileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "dotnet.exe" : "dotnet";
46+
var result = DotNetExe.FindDotNetInRoot("/nonexistent/path/that/does/not/exist", fileName);
47+
Assert.Null(result);
48+
}
49+
50+
[Fact]
51+
public void FindDotNetInRoot_ReturnsNull_WhenFileDoesNotExist()
52+
{
53+
// Use a directory that exists but won't contain dotnet
54+
var tempDir = Path.GetTempPath();
55+
var result = DotNetExe.FindDotNetInRoot(tempDir, "dotnet-nonexistent-file");
56+
Assert.Null(result);
57+
}
58+
59+
[Fact]
60+
public void FindDotNetInRoot_ReturnsPath_WhenFileExists()
61+
{
62+
// Create a temp file to simulate dotnet existing
63+
var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
64+
Directory.CreateDirectory(tempDir);
65+
try
66+
{
67+
var fakeDotnet = "dotnet-test";
68+
var fakePath = Path.Combine(tempDir, fakeDotnet);
69+
File.WriteAllText(fakePath, "");
70+
71+
var result = DotNetExe.FindDotNetInRoot(tempDir, fakeDotnet);
72+
Assert.NotNull(result);
73+
Assert.Equal(fakePath, result);
74+
}
75+
finally
76+
{
77+
Directory.Delete(tempDir, recursive: true);
78+
}
79+
}
2380
}
2481
}
2582
#elif NET472_OR_GREATER

0 commit comments

Comments
 (0)