Skip to content

Commit

Permalink
(chocolateyGH-14) Find path to executable
Browse files Browse the repository at this point in the history
Provide a way to determine if a command / executable is on the file
system. It starts by getting the path extensions `PATHEXT`
environment variable and using those extensions combined with the
executable name (unless an extension is provided as part of the name)
and searching for the file in all path locations, starting at current
directory and moving out from there in the order things would be
searched for by the system.

If nothing is found, we just return the name that was passed into the method.
  • Loading branch information
ferventcoder committed Jun 4, 2015
1 parent 31e3f9c commit 0f6bc80
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace chocolatey.tests.integration.infrastructure.filesystem
using NUnit.Framework;
using Should;
using chocolatey.infrastructure.filesystem;
using chocolatey.infrastructure.platforms;

public class DotNetFileSystemSpecs
{
Expand Down Expand Up @@ -48,6 +49,53 @@ public override void Context()
}
}

[Category("Integration")]
public class when_finding_paths_to_executables_with_dotNetFileSystem : DotNetFileSystemSpecsBase
{
public override void Because()
{
}

[Fact]
public void GetExecutablePath_should_find_existing_executable()
{
FileSystem.get_executable_path("cmd").ShouldEqual(
Platform.get_platform() == PlatformType.Windows ?
"C:\\Windows\\system32\\cmd.exe"
: "cmd"
);
}

[Fact]
public void GetExecutablePath_should_find_existing_executable_with_extension()
{
FileSystem.get_executable_path("cmd.exe").ShouldEqual(
Platform.get_platform() == PlatformType.Windows ?
"C:\\Windows\\system32\\cmd.exe"
: "cmd"
);
}

[Fact]
public void GetExecutablePath_should_return_same_value_when_executable_is_not_found()
{
FileSystem.get_executable_path("daslakjsfdasdfwea").ShouldEqual("daslakjsfdasdfwea");
}

[Fact]
public void GetExecutablePath_should_return_empty_string_when_value_is_null()
{
FileSystem.get_executable_path(null).ShouldEqual(string.Empty);
}

[Fact]
public void GetExecutablePath_should_return_empty_string_when_value_is_empty_string()
{
FileSystem.get_executable_path(string.Empty).ShouldEqual(string.Empty);
}

}

[Category("Integration")]
public class when_doing_file_system_operations_with_dotNetFileSystem : DotNetFileSystemSpecsBase
{
Expand Down
135 changes: 124 additions & 11 deletions src/chocolatey.tests/infrastructure/filesystem/DotNetFileSystemSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ namespace chocolatey.tests.infrastructure.filesystem
{
using System;
using System.IO;
using Moq;
using Should;
using chocolatey.infrastructure.adapters;
using chocolatey.infrastructure.app;
using chocolatey.infrastructure.filesystem;
using chocolatey.infrastructure.platforms;

Expand Down Expand Up @@ -73,46 +76,156 @@ public void GetExtension_should_return_the_extension_of_the_filename_even_with_a
public void GetDirectoryName_should_return_the_directory_of_the_path_to_the_file()
{
FileSystem.get_directory_name("C:\\temp\\test.txt").ShouldEqual(
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp"
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp"
: "C:/temp");
}

[Fact]
public void Combine_should_combine_the_file_paths_of_all_the_included_items_together()
{
FileSystem.combine_paths("C:\\temp", "yo", "filename.txt").ShouldEqual(
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp\\yo\\filename.txt"
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp\\yo\\filename.txt"
: "C:/temp/yo/filename.txt");
}

[Fact]
public void Combine_should_combine_when_paths_have_backslashes_in_subpaths()
{
FileSystem.combine_paths("C:\\temp", "yo\\timmy", "filename.txt").ShouldEqual(
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp\\yo\\timmy\\filename.txt"
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp\\yo\\timmy\\filename.txt"
: "C:/temp/yo/timmy/filename.txt");
}

[Fact]
public void Combine_should_combine_when_paths_start_with_backslashes_in_subpaths()
{
FileSystem.combine_paths("C:\\temp", "\\yo", "filename.txt").ShouldEqual(
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp\\yo\\filename.txt"
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp\\yo\\filename.txt"
: "C:/temp/yo/filename.txt");
}

[Fact]
public void Combine_should_combine_when_paths_start_with_forwardslashes_in_subpaths()
{
FileSystem.combine_paths("C:\\temp", "/yo", "filename.txt").ShouldEqual(
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp\\yo\\filename.txt"
Platform.get_platform() == PlatformType.Windows ?
"C:\\temp\\yo\\filename.txt"
: "C:/temp/yo/filename.txt");
}
}

public class when_finding_paths_to_executables_with_dotNetFileSystem : DotNetFileSystemSpecsBase
{
public Mock<IEnvironment> _environment = new Mock<IEnvironment>();

public override void Context()
{
base.Context();
_environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.PathExtensions)).Returns(".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL");
_environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.Path)).Returns(
@"C:\ProgramData\Chocolatey\bin{0}C:\Program Files\Microsoft\Web Platform Installer\{0}C:\Users\yes\AppData\Roaming\Boxstarter{0}C:\tools\ChocolateyPackageUpdater{0}C:\Windows\system32{0}C:\Windows{0}C:\Windows\System32\Wbem{0}C:\Windows\System32\WindowsPowerShell\v1.0\{0}"
.format_with(Path.PathSeparator)
);
FileSystem.initialize_with(new Lazy<IEnvironment>(() => _environment.Object));
}

public override void Because()
{
}

private void reset()
{
_environment.ResetCalls();
}

[Fact]
public void GetExecutablePath_should_find_existing_executable()
{
FileSystem.get_executable_path("cmd").ShouldEqual(
Platform.get_platform() == PlatformType.Windows ?
"C:\\Windows\\system32\\cmd.exe"
: "cmd"
);
}

[Fact]
public void GetExecutablePath_should_find_existing_executable_with_extension()
{
FileSystem.get_executable_path("cmd.exe").ShouldEqual(
Platform.get_platform() == PlatformType.Windows ?
"C:\\Windows\\system32\\cmd.exe"
: "cmd.exe"
);
}

[Fact]
public void GetExecutablePath_should_return_same_value_when_executable_is_not_found()
{
FileSystem.get_executable_path("daslakjsfdasdfwea").ShouldEqual("daslakjsfdasdfwea");
}

[Fact]
public void GetExecutablePath_should_return_empty_string_when_value_is_null()
{
FileSystem.get_executable_path(null).ShouldEqual(string.Empty);
}

[Fact]
public void GetExecutablePath_should_return_empty_string_when_value_is_empty_string()
{
FileSystem.get_executable_path(string.Empty).ShouldEqual(string.Empty);
}
}

public class when_finding_paths_to_executables_with_dotNetFileSystem_with_empty_path_extensions : DotNetFileSystemSpecsBase
{
public Mock<IEnvironment> _environment = new Mock<IEnvironment>();

public override void Context()
{
base.Context();
_environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.PathExtensions)).Returns(string.Empty);
_environment.Setup(x => x.GetEnvironmentVariable(ApplicationParameters.Environment.Path)).Returns(
"/usr/local/bin{0}/usr/bin/{0}/bin{0}/usr/sbin{0}/sbin"
.format_with(Path.PathSeparator)
);
FileSystem.initialize_with(new Lazy<IEnvironment>(() => _environment.Object));
}

public override void Because()
{
}

[Fact]
public void GetExecutablePath_should_find_existing_executable()
{
FileSystem.get_executable_path("ls").ShouldEqual(
Platform.get_platform() != PlatformType.Windows ?
"/bin/ls"
: "ls");
}

[Fact]
public void GetExecutablePath_should_return_same_value_when_executable_is_not_found()
{
FileSystem.get_executable_path("daslakjsfdasdfwea").ShouldEqual("daslakjsfdasdfwea");
}

[Fact]
public void GetExecutablePath_should_return_empty_string_when_value_is_null()
{
FileSystem.get_executable_path(null).ShouldEqual(string.Empty);
}

[Fact]
public void GetExecutablePath_should_return_empty_string_when_value_is_empty_string()
{
FileSystem.get_executable_path(string.Empty).ShouldEqual(string.Empty);
}
}
}
}
7 changes: 7 additions & 0 deletions src/chocolatey/infrastructure.app/ApplicationParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ public static class ApplicationParameters
public static readonly string RegistryValueInstallLocation = "InstallLocation";
public static readonly string AllPackages = "all";

public static class Environment
{
public static readonly string Path = "Path";
public static readonly string PathExtensions = "PATHEXT";
public static readonly string PathExtensionsSeparator = ";";
}

/// <summary>
/// Default is 45 minutes
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/chocolatey/infrastructure/adapters/Environment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,10 @@ public string NewLine
{
get { return System.Environment.NewLine; }
}

public string GetEnvironmentVariable(string variable)
{
return System.Environment.GetEnvironmentVariable(variable);
}
}
}
7 changes: 7 additions & 0 deletions src/chocolatey/infrastructure/adapters/IEnvironment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ public interface IEnvironment
/// </returns>
/// <filterpriority>1</filterpriority>
string NewLine { get; }

/// <summary>
/// Gets the environment variable.
/// </summary>
/// <param name="variable">The variable.</param>
/// <returns></returns>
string GetEnvironmentVariable(string variable);
}

// ReSharper restore InconsistentNaming
Expand Down
73 changes: 68 additions & 5 deletions src/chocolatey/infrastructure/filesystem/DotNetFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,19 @@ namespace chocolatey.infrastructure.filesystem
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using adapters;
using app;
using platforms;
using tolerance;
using Assembly = adapters.Assembly;
using Environment = adapters.Environment;

/// <summary>
/// Implementation of IFileSystem for Dot Net
Expand All @@ -33,14 +38,26 @@ namespace chocolatey.infrastructure.filesystem
public sealed class DotNetFileSystem : IFileSystem
{
private readonly int TIMES_TO_TRY_OPERATION = 3;
private static Lazy<IEnvironment> environment_initializer = new Lazy<IEnvironment>(() => new Environment());

private void allow_retries(Action action)
{
FaultTolerance.retry(
TIMES_TO_TRY_OPERATION,
action,
waitDurationMilliseconds: 200,
increaseRetryByMilliseconds: 100);
TIMES_TO_TRY_OPERATION,
action,
waitDurationMilliseconds: 200,
increaseRetryByMilliseconds: 100);
}

[EditorBrowsable(EditorBrowsableState.Never)]
public void initialize_with(Lazy<IEnvironment> environment)
{
environment_initializer = environment;
}

private static IEnvironment Environment
{
get { return environment_initializer.Value; }
}

#region Path
Expand Down Expand Up @@ -81,6 +98,52 @@ public char get_path_directory_separator_char()
return Path.DirectorySeparatorChar;
}

public char get_path_separator()
{
return Path.PathSeparator;
}

public string get_executable_path(string executableName)
{
if (string.IsNullOrWhiteSpace(executableName)) return string.Empty;

var isWindows = Platform.get_platform() == PlatformType.Windows;
IList<string> extensions = new List<string>();

if (get_file_name_without_extension(executableName).is_equal_to(executableName) && isWindows)
{
var pathExtensions = Environment.GetEnvironmentVariable(ApplicationParameters.Environment.PathExtensions).to_string().Split(new[] {ApplicationParameters.Environment.PathExtensionsSeparator}, StringSplitOptions.RemoveEmptyEntries);
foreach (var extension in pathExtensions.or_empty_list_if_null())
{
extensions.Add(extension.StartsWith(".") ? extension : ".{0}".format_with(extension));
}
}

// Always add empty, for when the executable name is enough.
extensions.Add(string.Empty);

// Gets the path to an executable based on looking in current
// working directory, next to the running process, then among the
// derivatives of Path and Pathext variables, applied in order.
var searchPaths = new List<string>();
searchPaths.Add(get_current_directory());
searchPaths.Add(get_directory_name(get_current_assembly_path()));
searchPaths.AddRange(Environment.GetEnvironmentVariable(ApplicationParameters.Environment.Path).to_string().Split(new[] { get_path_separator() }, StringSplitOptions.RemoveEmptyEntries));

foreach (var path in searchPaths.or_empty_list_if_null())
{
foreach (var extension in extensions.or_empty_list_if_null())
{
var possiblePath = combine_paths(path, "{0}{1}".format_with(executableName, extension.to_lower()));
if (file_exists(possiblePath)) return possiblePath;
}
}

// If not found, return the same as passed in - it may work,
// but possibly not.
return executableName;
}

public string get_current_assembly_path()
{
return Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", string.Empty);
Expand Down Expand Up @@ -130,7 +193,7 @@ public FileInfo get_file_info_for(string filePath)
return new FileInfo(filePath);
}

public DateTime get_file_modified_date(string filePath)
public System.DateTime get_file_modified_date(string filePath)
{
return new FileInfo(filePath).LastWriteTime;
}
Expand Down
Loading

0 comments on commit 0f6bc80

Please sign in to comment.