Make command searcher not use wildcard search for execution (#9202)

This commit is contained in:
Travis Plunk 2019-04-02 11:54:11 -07:00 committed by Aditya Patwardhan
parent b7c7aa176e
commit 5e4b4d1351
4 changed files with 237 additions and 58 deletions

View file

@ -907,7 +907,10 @@ function Start-PSPester {
[switch]$IncludeCommonTests,
[string]$ExperimentalFeatureName,
[Parameter(HelpMessage='Title to publish the results as.')]
[string]$Title = 'PowerShell Core Tests'
[string]$Title = 'PowerShell Core Tests',
[Parameter(ParameterSetName='Wait', Mandatory=$true,
HelpMessage='Wait for the debugger to attach to PowerShell before Pester starts. Debug builds only!')]
[switch]$Wait
)
if (-not (Get-Module -ListAvailable -Name $Pester -ErrorAction SilentlyContinue | Where-Object { $_.Version -ge "4.2" } ))
@ -1101,6 +1104,13 @@ function Start-PSPester {
$PSFlags = @("-settings", $configFile, "-noprofile")
}
# -Wait is only available on Debug builds
# It is used to allow the debugger to attach before PowerShell
# runs pester in this case
if($Wait.IsPresent){
$PSFlags += '-wait'
}
# To ensure proper testing, the module path must not be inherited by the spawned process
try {
$originalModulePath = $env:PSModulePath

View file

@ -735,7 +735,7 @@ namespace System.Management.Automation
internal static CommandInfo LookupCommandInfo(string commandName, CommandOrigin commandOrigin, ExecutionContext context)
{
return LookupCommandInfo(commandName, CommandTypes.All, SearchResolutionOptions.None, commandOrigin, context);
return LookupCommandInfo(commandName, CommandTypes.All, SearchResolutionOptions.ResolveLiteralThenPathPatterns, commandOrigin, context);
}
internal static CommandInfo LookupCommandInfo(

View file

@ -456,52 +456,33 @@ namespace System.Management.Automation
"Trying to resolve the path as an PSPath");
// Find the match if it is.
// Try literal path resolution if it is set to run first
if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns))
{
var path = GetNextLiteralPathThatExists(_commandName, out _);
if (path != null)
{
return GetInfoFromPath(path);
}
}
Collection<string> resolvedPaths = new Collection<string>();
if (WildcardPattern.ContainsWildcardCharacters(_commandName))
{
resolvedPaths = GetNextFromPathUsingWildcards(_commandName, out _);
}
try
// Try literal path resolution if wildcards are enable first and wildcard search failed
if (!_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns) &&
resolvedPaths.Count == 0)
{
Provider.CmdletProvider providerInstance;
ProviderInfo provider;
resolvedPaths =
_context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(_commandName, false, out provider, out providerInstance);
}
catch (ItemNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path could not be found: {0}",
_commandName);
}
catch (DriveNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A drive could not be found for the path: {0}",
_commandName);
}
catch (ProviderNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A provider could not be found for the path: {0}",
_commandName);
}
catch (InvalidOperationException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path specified a home directory, but the provider home directory was not set. {0}",
_commandName);
}
catch (ProviderInvocationException providerException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' encountered an error: {1}",
_commandName,
providerException.Message);
}
catch (PSNotSupportedException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' does not implement ContainerCmdletProvider",
_commandName);
string path = GetNextLiteralPathThatExists(_commandName, out _);
if (path != null)
{
return GetInfoFromPath(path);
}
}
if (resolvedPaths.Count > 1)
@ -528,6 +509,64 @@ namespace System.Management.Automation
return result;
}
/// <summary>
/// Gets the next path using WildCards.
/// </summary>
/// <param name="command">
/// The command to search for.
/// </param>
/// <param name="provider">The provider that the command was found in.</param>
/// <returns>
/// A collection of full paths to the commands which were found.
/// </returns>
private Collection<string> GetNextFromPathUsingWildcards(string command, out ProviderInfo provider)
{
try
{
return _context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(path: command, allowNonexistingPaths: false, provider: out provider, providerInstance: out _);
}
catch (ItemNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path could not be found: {0}",
command);
}
catch (DriveNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A drive could not be found for the path: {0}",
command);
}
catch (ProviderNotFoundException)
{
CommandDiscovery.discoveryTracer.TraceError(
"A provider could not be found for the path: {0}",
command);
}
catch (InvalidOperationException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The path specified a home directory, but the provider home directory was not set. {0}",
command);
}
catch (ProviderInvocationException providerException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' encountered an error: {1}",
command,
providerException.Message);
}
catch (PSNotSupportedException)
{
CommandDiscovery.discoveryTracer.TraceError(
"The provider associated with the path '{0}' does not implement ContainerCmdletProvider",
command);
}
provider = null;
return null;
}
private static bool checkPath(string path, string commandName)
{
return path.StartsWith(commandName, StringComparison.OrdinalIgnoreCase);
@ -1092,15 +1131,21 @@ namespace System.Management.Automation
{
ProviderInfo provider = null;
string resolvedPath = null;
if (WildcardPattern.ContainsWildcardCharacters(path))
// Try literal path resolution if it is set to run first
if (_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns))
{
// Cannot return early as this code path only expects
// The file system provider and the final check for that
// must verify this before we return.
resolvedPath = GetNextLiteralPathThatExists(path, out provider);
}
if (WildcardPattern.ContainsWildcardCharacters(path) &&
((resolvedPath == null) || (provider == null)))
{
// Let PowerShell resolve relative path with wildcards.
Provider.CmdletProvider providerInstance;
Collection<string> resolvedPaths = _context.LocationGlobber.GetGlobbedProviderPathsFromMonadPath(
path,
false,
out provider,
out providerInstance);
Collection<string> resolvedPaths = GetNextFromPathUsingWildcards(path, out provider);
if (resolvedPaths.Count == 0)
{
@ -1124,14 +1169,15 @@ namespace System.Management.Automation
}
}
// Revert to previous path resolver if wildcards produces no results.
if ((resolvedPath == null) || (provider == null))
// Try literal path resolution if wildcards are enabled first and wildcard search failed
if (!_commandResolutionOptions.HasFlag(SearchResolutionOptions.ResolveLiteralThenPathPatterns) &&
((resolvedPath == null) || (provider == null)))
{
resolvedPath = _context.LocationGlobber.GetProviderPath(path, out provider);
resolvedPath = GetNextLiteralPathThatExists(path, out provider);
}
// Verify the path was resolved to a file system path
if (provider.NameEquals(_context.ProviderNames.FileSystem))
if (provider != null && provider.NameEquals(_context.ProviderNames.FileSystem))
{
result = resolvedPath;
@ -1176,6 +1222,32 @@ namespace System.Management.Automation
return result;
}
/// <summary>
/// Gets the next literal path.
/// Filtering to ones that exist for the filesystem.
/// </summary>
/// <param name="command">
/// The command to search for.
/// </param>
/// <param name="provider">The provider that the command was found in.</param>
/// <returns>
/// Full path to the command.
/// </returns>
private string GetNextLiteralPathThatExists(string command, out ProviderInfo provider)
{
string resolvedPath = _context.LocationGlobber.GetProviderPath(command, out provider);
if (provider.NameEquals(_context.ProviderNames.FileSystem)
&& !File.Exists(resolvedPath)
&& !Directory.Exists(resolvedPath))
{
provider = null;
return null;
}
return resolvedPath;
}
/// <summary>
/// Creates a collection of patterns used to find the command.
/// </summary>
@ -1609,5 +1681,10 @@ namespace System.Management.Automation
/// Enable searching for cmdlets/functions by abbreviation expansion.
/// </summary>
UseAbbreviationExpansion = 0x20,
/// <summary>
/// Enable resolving wildcard in paths.
/// </summary>
ResolveLiteralThenPathPatterns = 0x40
}
}

View file

@ -85,18 +85,107 @@ Describe "Command Discovery tests" -Tags "CI" {
(& 'location').Path | Should -Be (get-location).Path
}
Context "Get-Command should use globbing for scripts" {
Context "Use literal path first when executing scripts" {
BeforeAll {
$firstFileName = '[test1].ps1'
$secondFileName = '1.ps1'
$thirdFileName = '2.ps1'
$firstResult = "executing $firstFileName in root"
$secondResult = "executing $secondFileName in root"
$thirdResult = "executing $thirdFileName in root"
setup -f $firstFileName -content "'$firstResult'"
setup -f $secondFileName -content "'$secondResult'"
setup -f $thirdFileName -content "'$thirdResult'"
$subFolder = 'subFolder'
$firstFileInSubFolder = Join-Path $subFolder -ChildPath $firstFileName
$secondFileInSubFolder = Join-Path $subFolder -ChildPath $secondFileName
$thirdFileInSubFolder = Join-Path $subFolder -ChildPath $thirdFileName
setup -f $firstFileInSubFolder -content "'$firstResult'"
setup -f $secondFileInSubFolder -content "'$secondResult'"
setup -f $thirdFileInSubFolder -content "'$thirdResult'"
$secondFileSearchInSubfolder = (Join-Path -Path $subFolder -ChildPath '[t1].ps1')
$executionWithWildcardCases = @(
#Region relative paths with './'
@{command = '.\[test1].ps1' ; expectedResult = $firstResult; name = '.\[test1].ps1'}
@{command = '.\[t1].ps1' ; expectedResult = $secondResult; name = '.\[t1].ps1'}
#endregion
#Region relative Subfolder paths without './'
@{command = $secondFileInSubFolder ; expectedResult = $secondResult; name = $secondFileInSubFolder}
# Wildcard search is not being performed in this scenario before this change.
# I noted the issue in the pending message
@{command = $firstFileInSubFolder ; expectedResult = $firstResult; name = $firstFileInSubFolder; Pending="See note about wildcard in https://github.com/PowerShell/PowerShell/issues/9256"}
@{command = $secondFileSearchInSubfolder ; expectedResult = $secondResult; name = $secondFileSearchInSubfolder; Pending="See note about wildcard in https://github.com/PowerShell/PowerShell/issues/9256"}
#endregion
#Region relative Subfolder paths with '.\'
@{command = '.\' + $secondFileInSubFolder ; expectedResult = $secondResult; name = $secondFileInSubFolder}
@{command = '.\subFolder\[test1].ps1' ; expectedResult = $firstResult; name = '.\subFolder\[test1].ps1'}
@{command = '.\subFolder\[t1].ps1' ; expectedResult = $secondResult; name = '.\' + $secondFileSearchInSubfolder}
@{command = '.\' + $firstFileInSubFolder ; expectedResult = $firstResult; name = '.\' + $firstFileInSubFolder}
@{command = '.\' + $secondFileSearchInSubfolder ; expectedResult = $secondResult; name = '.\' + $secondFileSearchInSubfolder}
#endregion
#region rooted paths
@{command = (Join-Path ${TestDrive} -ChildPath '[test1].ps1') ; expectedResult = $firstResult; name = '.\[test1].ps1 by fully qualified path'}
@{command = (Join-Path ${TestDrive} -ChildPath '[t1].ps1') ; expectedResult = $secondResult; name = '.\1.ps1 by fully qualified path with wildcard'}
#endregion
)
$shouldNotExecuteCases = @(
@{command = 'subFolder\[test1].ps1' ; testName = 'Relative path that where module qualified syntax overlaps'; ExpectedErrorId = 'CouldNotAutoLoadModule'}
@{command = '.\[12].ps1' ; testName = 'relative path with bracket wildcard matctching multiple files'}
@{command = (Join-Path ${TestDrive} -ChildPath '[12].ps1') ; testName = 'fully qualified path with bracket wildcard matching multiple files'}
)
Push-Location ${TestDrive}\
}
AfterAll {
Pop-Location
}
It "Invoking <name> should return '<expectedResult>'" -TestCases $executionWithWildcardCases {
param($command, $expectedResult, [String]$Pending)
if($Pending)
{
Set-TestInconclusive -Message $Pending
}
& $command | Should -BeExactly $expectedResult
}
It "'<testName>' should not execute" -TestCases $shouldNotExecuteCases {
param(
[string]
$command,
[string]
$ExpectedErrorId = 'CommandNotFoundException'
)
{ & $command } | Should -Throw -ErrorId $ExpectedErrorId
}
}
Context "Get-Command should use globbing first for scripts" {
BeforeAll {
$firstResult = '[first script]'
$secondResult = 'alt script'
$thirdResult = 'bad script'
setup -f '[test1].ps1' -content "'$firstResult'"
setup -f '1.ps1' -content "'$secondResult'"
setup -f '2.ps1' -content "'$thirdResult'"
$gcmWithWildcardCases = @(
@{command = '.\?[tb]est1?.ps1'; expectedCommand = '[test1].ps1'; expectedCommandCount =1; name = '''.\?[tb]est1?.ps1'''}
@{command = (Join-Path ${TestDrive} -ChildPath '?[tb]est1?.ps1'); expectedCommand = '[test1].ps1'; expectedCommandCount =1 ; name = '''.\?[tb]est1?.ps1'' by fully qualified path'}
@{command = '.\[test1].ps1'; expectedCommand = '1.ps1'; expectedCommandCount =1; name = '''.\[test1].ps1'''}
@{command = (Join-Path ${TestDrive} -ChildPath '[test1].ps1'); expectedCommand = '1.ps1'; expectedCommandCount =1 ; name = '''.\[test1].ps1'' by fully qualified path'}
@{command = '.\[12].ps1'; expectedCommand = '1.ps1'; expectedCommandCount =0; name = 'relative path with bracket wildcard matctching multiple files'}
@{command = (Join-Path ${TestDrive} -ChildPath '[12].ps1'); expectedCommand = '1.ps1'; expectedCommandCount =0 ; name = 'fully qualified path with bracket wildcard matctching multiple files'}
)
Push-Location ${TestDrive}\
@ -108,9 +197,12 @@ Describe "Command Discovery tests" -Tags "CI" {
It "Get-Command <name> should return <expectedCommandCount> command named '<expectedCommand>'" -TestCases $gcmWithWildcardCases {
param($command, $expectedCommand, $expectedCommandCount)
$commands = Get-Command -Name $command
$commands = @(Get-Command -Name $command)
$commands.Count | Should -Be $expectedCommandCount
$commands.Name | Should -BeExactly $expectedCommand
if($expectedCommandCount -gt 0)
{
$commands.Name | Should -BeExactly $expectedCommand
}
}
}
}