Skip to content

Commit

Permalink
Close madelson#118: Support relative paths
Browse files Browse the repository at this point in the history
  • Loading branch information
Bartleby2718 committed May 28, 2024
1 parent a37d644 commit bf2c436
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 4 deletions.
40 changes: 40 additions & 0 deletions MedallionShell.Tests/GeneralTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,46 @@ public void TestProcessKeepsWritingAfterOutputIsClosed()
command.Result.Success.ShouldEqual(true);
}

[Platform("Win", Reason = "Tests Windows-specific paths")]
[TestCase(@".\dotnet.exe", @"C:\Program Files\dotnet")]
[TestCase(@".\dotnet.exe", @"C:\Program Files\dotnet\")]
[TestCase("dotnet.exe", @"C:\Program Files\dotnet")]
[TestCase("dotnet.exe", @"C:\Program Files\dotnet\")]
[TestCase(@"dotnet\dotnet.exe", @"C:\Program Files")]
[TestCase(@"Program Files\dotnet\dotnet.exe", @"C:\")]
public void TestRelativePathsOnWindows(string relativeExecutablePath, string workingDirectory) =>
TestRelativePaths(relativeExecutablePath, workingDirectory);

#if !NETFRAMEWORK
[Platform("Unix", Reason = "Tests Unix-specific paths")]
[TestCase("./dotnet", "/usr/bin")]
[TestCase("./dotnet", "/usr/bin/")]
[TestCase("dotnet", "/usr/bin")]
[TestCase("dotnet", "/usr/bin/")]
[TestCase("bin/dotnet", "/usr")]
[TestCase("usr/bin/dotnet", "/")]
public void TestRelativePathsOnUnix(string relativeExecutablePath, string workingDirectory) =>
TestRelativePaths(relativeExecutablePath, workingDirectory);
#endif

private static void TestRelativePaths(string relativeExecutablePath, string workingDirectory)
{
var expectedVersion = TestShell.Run(DotNetPath, ["--version"])
.StandardOutput
.ReadToEnd();

Assert.That(expectedVersion, Does.Match(@"\d.\d.\d"));

Assert.That(
TestShell.Run(
relativeExecutablePath,
["--version"],
o => o.WorkingDirectory(workingDirectory))
.StandardOutput
.ReadToEnd(),
Is.EqualTo(expectedVersion));
}

[Test]
[Obsolete("Tests obsolete code")]
public async Task TestCustomCommandLineSyntaxIsUsed()
Expand Down
11 changes: 8 additions & 3 deletions MedallionShell.sln
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.6
# Visual Studio Version 17
VisualStudioVersion = 17.9.34902.65
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MedallionShell", "MedallionShell\MedallionShell.csproj", "{15AF2EC0-F7B2-4206-B92A-DD1F3DC25F30}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MedallionShell.Tests", "MedallionShell.Tests\MedallionShell.Tests.csproj", "{1B474A08-2A88-4FEA-A290-0555556C8229}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SampleCommand", "SampleCommand\SampleCommand.csproj", "{1F78EB31-414F-4C1E-893B-C281F6B4FFC0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MedallionShell.ProcessSignaler", "MedallionShell.ProcessSignaler\MedallionShell.ProcessSignaler.csproj", "{63CEF80E-6E45-4CB2-9830-10ADDF44757A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MedallionShell.ProcessSignaler", "MedallionShell.ProcessSignaler\MedallionShell.ProcessSignaler.csproj", "{63CEF80E-6E45-4CB2-9830-10ADDF44757A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0F0605C3-2439-444D-9033-40A95CC95922}"
ProjectSection(SolutionItems) = preProject
appveyor.yml = appveyor.yml
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand Down
31 changes: 30 additions & 1 deletion MedallionShell/Shell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -38,10 +39,27 @@ public Command Run(string executable, IEnumerable<object>? arguments = null, Act

var finalOptions = this.GetOptions(options);

var fullyQualifiedExecutablePath =
#if NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1
Path.IsPathFullyQualified(executable)
#else
IsPathFullyQualified(executable)
#endif
? executable
// Normalize the path using Path.GetFullPath
: Path.GetFullPath(Path.Combine(
finalOptions.WorkingDirectoryPath
#if NETSTANDARD1_3
?? Directory.GetCurrentDirectory(),
#else
?? Environment.CurrentDirectory,
#endif
executable));

var processStartInfo = new ProcessStartInfo
{
CreateNoWindow = true,
FileName = executable,
FileName = fullyQualifiedExecutablePath,
RedirectStandardError = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
Expand Down Expand Up @@ -204,6 +222,7 @@ internal Options()
internal TimeSpan ProcessTimeout { get; private set; }
internal Encoding? ProcessStreamEncoding { get; private set; }
internal CancellationToken ProcessCancellationToken { get; private set; }
internal string? WorkingDirectoryPath { get; private set; }

#region ---- Builder methods ----
/// <summary>
Expand Down Expand Up @@ -267,6 +286,7 @@ public Options Command(Func<Command, Command> initializer)
/// </summary>.
public Options WorkingDirectory(string path)
{
this.WorkingDirectoryPath = path;
return this.StartInfo(psi => psi.WorkingDirectory = path);
}

Expand Down Expand Up @@ -378,5 +398,14 @@ private static bool IsIgnorableAttachingException(Exception exception)
return exception is ArgumentException // process has already exited or ID is invalid
|| exception is InvalidOperationException; // process exited after its creation but before taking its handle
}

#if !NETCOREAPP2_1_OR_GREATER && !NETSTANDARD2_1
// Path.IsPathFullyQualified is defined only on .NET Core 2.1+, so use the code from https://stackoverflow.com/a/49883481
private static bool IsPathFullyQualified(string path)
{
var root = Path.GetPathRoot(path);
return root.StartsWith(@"\\") || (root.EndsWith(@"\") && root != @"\");
}
#endif
}
}

0 comments on commit bf2c436

Please sign in to comment.