Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test-Path: Return $false when given an empty or $null -Path/-LiteralPath value #8080

Merged
merged 15 commits into from Nov 21, 2018
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Management.Automation;
using Dbg = System.Management.Automation;

@@ -41,6 +42,9 @@ public class TestPathCommand : CoreCommandWithCredentialsBase
/// </summary>
[Parameter(Position = 0, ParameterSetName = "Path",
Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
[AllowNull]
[AllowEmptyCollection]
This conversation was marked as resolved by iSazonov

This comment has been minimized.

Copy link
@iSazonov

iSazonov Oct 25, 2018

Collaborator

If we remove the two attributes do we get a terminating or non-terminating error?

This comment has been minimized.

Copy link
@vexx32

vexx32 Oct 25, 2018

Author Contributor

Hmmmm. It's a parameter binding error. Not sure if that registers as terminating or not...

This comment has been minimized.

Copy link
@vexx32

vexx32 Oct 30, 2018

Author Contributor

@iSazonov it is a terminating error, sadly.

[AllowEmptyString]
public string[] Path
{
get { return _paths; }
@@ -53,6 +57,9 @@ public string[] Path
[Parameter(ParameterSetName = "LiteralPath",
Mandatory = true, ValueFromPipeline = false, ValueFromPipelineByPropertyName = true)]
[Alias("PSPath", "LP")]
[AllowNull]
[AllowEmptyCollection]
[AllowEmptyString]
public string[] LiteralPath
{
get { return _paths; }
@@ -124,7 +131,7 @@ internal override object GetDynamicParameters(CmdletProviderContext context)

if (this.PathType == TestPathType.Any && !IsValid)
{
if (Path != null && Path.Length > 0)
if (Path != null && Path.Length > 0 && Path[0] != null)
{
result = InvokeProvider.Item.ItemExistsDynamicParameters(Path[0], context);
}
@@ -133,6 +140,7 @@ internal override object GetDynamicParameters(CmdletProviderContext context)
result = InvokeProvider.Item.ItemExistsDynamicParameters(".", context);
}
}

return result;
} // GetDynamicParameters

@@ -154,12 +162,39 @@ internal override object GetDynamicParameters(CmdletProviderContext context)
/// </summary>
protected override void ProcessRecord()
{
if (_paths == null || _paths.Length == 0)
{
WriteError(new ErrorRecord(
new ArgumentNullException("The path was null or an empty collection."),
This conversation was marked as resolved by vexx32

This comment has been minimized.

Copy link
@SteveL-MSFT

SteveL-MSFT Nov 14, 2018

Member

English text needs to be from a .resx file

This comment has been minimized.

Copy link
@vexx32

vexx32 Nov 16, 2018

Author Contributor

Done!

"NullPathNotPermitted",
ErrorCategory.InvalidArgument,
Path));

return;
}

CmdletProviderContext currentContext = CmdletProviderContext;

foreach (string path in _paths)
{
bool result = false;

if (path == null)
{
WriteError(new ErrorRecord(
new ArgumentNullException("The path was null or an empty collection."),
This conversation was marked as resolved by vexx32

This comment has been minimized.

Copy link
@SteveL-MSFT

SteveL-MSFT Nov 14, 2018

Member

English text needs to be from a .resx file

This comment has been minimized.

Copy link
@vexx32

vexx32 Nov 16, 2018

Author Contributor

And done!

"NullPath",
ErrorCategory.InvalidArgument,
Path));
continue;
}

if (string.IsNullOrWhiteSpace(path))
{
WriteObject(result);
continue;
}

try
{
if (IsValid)
@@ -184,6 +219,7 @@ protected override void ProcessRecord()
}
}
}

// Any of the known exceptions means the path does not exist.
catch (PSNotSupportedException)
{
@@ -17,7 +17,7 @@ for ($null[0];
'@
$for_pipeline_initializer_script = @'
$test = 1
for (Test-Path $null;
for (Test-Path $null, $null;
$test -gt 1;) { }
'@
$for_expression_condition_script = @'
@@ -27,7 +27,7 @@ for (;$null[0];)
'@
$for_pipeline_condition_script = @'
$test = 1
for (;Test-Path $null;)
for (;Test-Path $null, $null;)
{ }
'@
$do_while_expression_condition_script = @'
@@ -38,7 +38,7 @@ while ($null[0])
$do_while_pipeline_condition_script = @'
$test = 1
do {}
while (Test-Path $null)
while (Test-Path $null, $null)
'@
$do_until_expression_condition_script = @'
$test = 1
@@ -48,7 +48,7 @@ until ($null[0])
$do_until_pipeline_condition_script = @'
$test = 1
do {}
until (Test-Path $null)
until (Test-Path $null, $null)
'@

$testCases = @(
@@ -2,123 +2,136 @@
# Licensed under the MIT License.
Describe "Test-Path" -Tags "CI" {
BeforeAll {
$testdirectory = $TestDrive
$testfilename = New-Item -path $testdirectory -Name testfile.txt -ItemType file -Value 1 -force
$testdirectory = $TestDrive
$testfilename = New-Item -path $testdirectory -Name testfile.txt -ItemType file -Value 1 -force

# populate with additional files
New-Item -Path $testdirectory -Name datestfile -value 1 -ItemType file | Out-Null
New-Item -Path $testdirectory -Name gatestfile -value 1 -ItemType file | Out-Null
New-Item -Path $testdirectory -Name usr -value 1 -ItemType directory | Out-Null
# populate with additional files
New-Item -Path $testdirectory -Name datestfile -value 1 -ItemType file | Out-Null
New-Item -Path $testdirectory -Name gatestfile -value 1 -ItemType file | Out-Null
New-Item -Path $testdirectory -Name usr -value 1 -ItemType directory | Out-Null

$nonExistentDir = Join-Path -Path (Join-Path -Path $testdirectory -ChildPath usr) -ChildPath bin
$nonExistentPath = Join-Path -Path (Join-Path -Path (Join-Path -Path $testdirectory -ChildPath usr) -ChildPath bin) -ChildPath error
$nonExistentDir = Join-Path -Path (Join-Path -Path $testdirectory -ChildPath usr) -ChildPath bin
$nonExistentPath = Join-Path -Path (Join-Path -Path (Join-Path -Path $testdirectory -ChildPath usr) -ChildPath bin) -ChildPath error
}

It "Should be called on an existing path without error" {
{ Test-Path $testdirectory } | Should -Not -Throw
{ Test-Path -Path $testdirectory } | Should -Not -Throw
{ Test-Path -LiteralPath $testdirectory } | Should -Not -Throw
{ Test-Path $testdirectory } | Should -Not -Throw
{ Test-Path -Path $testdirectory } | Should -Not -Throw
{ Test-Path -LiteralPath $testdirectory } | Should -Not -Throw
}

It "Should allow piping objects to it" {
{ $testdirectory | Test-Path } | Should -Not -Throw
{ $testdirectory | Test-Path } | Should -Not -Throw

$testdirectory | Test-Path | Should -BeTrue
$nonExistentDir | Test-Path | Should -BeFalse
$testdirectory | Test-Path | Should -BeTrue
$nonExistentDir | Test-Path | Should -BeFalse
}

It "Should be called on a nonexistent path without error" {
{ Test-Path -Path $nonExistentPath } | Should -Not -Throw
{ Test-Path -Path $nonExistentPath } | Should -Not -Throw
}

It "Should return false for a nonexistent path" {
Test-Path -Path $nonExistentPath | Should -BeFalse
Test-Path -Path $nonExistentPath | Should -BeFalse
}

It "Should return true for an existing path" {
{ Test-Path -Path $testdirectory } | Should -BeTrue
{ Test-Path -Path $testdirectory } | Should -BeTrue
}

It 'Should return false for an empty string' {
Test-Path -Path '' | Should -BeFalse
}

It 'Should return false for a whitespace string' {
Test-Path -Path ' ' | Should -BeFalse
}

It 'Should write a non-terminating error when given a null path' {
{ Test-Path -Path $null -ErrorAction Stop } | Should -Throw -ErrorId 'NullPathNotPermitted'

This comment has been minimized.

Copy link
@iSazonov

iSazonov Oct 25, 2018

Collaborator

I believe we should tests here Test-Path $null, $null;.

This comment has been minimized.

Copy link
@vexx32

vexx32 Oct 30, 2018

Author Contributor

Done. Also had to modify some ErrorPosition tests as they were causing infinite loops when the cmldet simply wrote a non-terminating error.

{ Test-Path -Path $null -ErrorAction SilentlyContinue } | Should -Not -Throw

This comment has been minimized.

Copy link
@iSazonov

iSazonov Oct 25, 2018

Collaborator

Seems we should remove this. I am not sure that it tests "non-terminating"

This comment has been minimized.

Copy link
@vexx32

vexx32 Oct 25, 2018

Author Contributor

That's what I thought, too. Surprisingly, though, -ErrorAction SilentlyContinue will still trigger a | should -throw if and only if the error is terminating to start with (either throw or ThrowTerminatingError())

@indented-automation had to explain that one to me. :)

This comment has been minimized.

Copy link
@iSazonov

iSazonov Oct 25, 2018

Collaborator

@adityapatwardhan @JamesWTruher @SteveL-MSFT Can we use the pattern to test non-terminating errors?

This comment has been minimized.

Copy link
@SteveL-MSFT

SteveL-MSFT Oct 25, 2018

Member

Since this change is explicitly to be a non-terminating error, this pattern seems fine. But should probably be followed up with -ErrorAction Stop to verify the non-terminating error is correct.

This comment has been minimized.

Copy link
@vexx32

vexx32 Oct 25, 2018

Author Contributor

I'm not sure I follow? I don't see a need to double-check it with -ErrorAction Stop

This comment has been minimized.

Copy link
@iSazonov

iSazonov Oct 26, 2018

Collaborator

I believe we need to add a comment to describe the pattern.

}

It "Should be able to accept a regular expression" {
{ Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u*") } | Should -Not -Throw
{ Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u[a-z]r") } | Should -Not -Throw
{ Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u*") } | Should -Not -Throw
{ Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u[a-z]r") } | Should -Not -Throw
}

It "Should be able to return the correct result when a regular expression is used" {
Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u*") | Should -BeTrue
Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u[a-z]*") | Should -BeTrue
Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u*") | Should -BeTrue
Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u[a-z]*") | Should -BeTrue

Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "aoeu*") | Should -BeFalse
Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u[A-Z]") | Should -BeFalse
Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "aoeu*") | Should -BeFalse
Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "u[A-Z]") | Should -BeFalse
}

It "Should return false when the Leaf pathtype is used on a directory" {
Test-Path -Path $testdirectory -PathType Leaf | Should -BeFalse
Test-Path -Path $testdirectory -PathType Leaf | Should -BeFalse
}

It "Should return true when the Leaf pathtype is used on an existing endpoint" {
Test-Path -Path $testfilename -PathType Leaf | Should -BeTrue
Test-Path -Path $testfilename -PathType Leaf | Should -BeTrue
}

It "Should return false when the Leaf pathtype is used on a nonexistent file" {
Test-Path -Path "aoeu" -PathType Leaf | Should -BeFalse
Test-Path -Path "aoeu" -PathType Leaf | Should -BeFalse
}

It "Should return true when the Leaf pathtype is used on a file using the Type alias instead of PathType" {
Test-Path -Path $testfilename -Type Leaf | Should -BeTrue
Test-Path -Path $testfilename -Type Leaf | Should -BeTrue
}

It "Should be able to search multiple regular expressions using the include switch" {
{ Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "*") -Include t* } | Should -BeTrue
{ Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "*") -Include t* } | Should -BeTrue
}

It "Should be able to exclude a regular expression using the exclude switch" {
{ Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "*") -Exclude v* } | Should -BeTrue
{ Test-Path -Path (Join-Path -Path $testdirectory -ChildPath "*") -Exclude v* } | Should -BeTrue
}

It "Should be able to exclude multiple regular expressions using the exclude switch" {
# tests whether there's any files in the usr directory that don't start with 'd' or 'g'
{ Test-Path -Path $testfilename -Exclude d*, g* } | Should -BeTrue
# tests whether there's any files in the usr directory that don't start with 'd' or 'g'
{ Test-Path -Path $testfilename -Exclude d*, g* } | Should -BeTrue
}

It "Should return true if the syntax of the path is correct when using the IsValid switch" {
{ Test-Path -Path $nonExistentPath -IsValid } | Should -BeTrue
{ Test-Path -Path $nonExistentPath -IsValid } | Should -BeTrue
}

It "Should return false if the syntax of the path is incorrect when using the IsValid switch" {
$badPath = " :;!@#$%^&*(){}?+|_-"
Test-Path -Path $badPath -IsValid | Should -BeFalse
$badPath = " :;!@#$%^&*(){}?+|_-"
Test-Path -Path $badPath -IsValid | Should -BeFalse
}

It "Should return true on paths containing spaces when the path is surrounded in quotes" {
Test-Path -Path "/totally a valid/path" -IsValid | Should -BeTrue
Test-Path -Path "/totally a valid/path" -IsValid | Should -BeTrue
}

It "Should throw on paths containing spaces when the path is not surrounded in quotes" {
{ Test-Path -Path /a path/without quotes/around/it -IsValid } | Should -Throw -ErrorId "PositionalParameterNotFound,Microsoft.PowerShell.Commands.TestPathCommand"
{ Test-Path -Path /a path/without quotes/around/it -IsValid } | Should -Throw -ErrorId "PositionalParameterNotFound,Microsoft.PowerShell.Commands.TestPathCommand"
}

It "Should return true if a directory leads or trails with a space when surrounded by quotes" {
Test-Path -Path "/a path / with/funkyspaces" -IsValid | Should -BeTrue
Test-Path -Path "/a path / with/funkyspaces" -IsValid | Should -BeTrue
}

It "Should return true on a valid path when the LiteralPath switch is used" {
Test-Path -LiteralPath $testfilename | Should -BeTrue
Test-Path -LiteralPath $testfilename | Should -BeTrue
}

It "Should return false if regular expressions are used with the LiteralPath switch" {
Test-Path -LiteralPath (Join-Path -Path $testdirectory -ChildPath "u*") | Should -BeFalse
Test-Path -LiteralPath (Join-Path -Path $testdirectory -ChildPath "u[a-z]r") | Should -BeFalse
Test-Path -LiteralPath (Join-Path -Path $testdirectory -ChildPath "u*") | Should -BeFalse
Test-Path -LiteralPath (Join-Path -Path $testdirectory -ChildPath "u[a-z]r") | Should -BeFalse
}

It "Should return false if regular expressions are used with the LiteralPath alias PSPath switch" {
Test-Path -PSPath (Join-Path -Path $testdirectory -ChildPath "u*") | Should -BeFalse
Test-Path -PSPath (Join-Path -Path $testdirectory -ChildPath "u[a-z]r") | Should -BeFalse
Test-Path -PSPath (Join-Path -Path $testdirectory -ChildPath "u*") | Should -BeFalse
Test-Path -PSPath (Join-Path -Path $testdirectory -ChildPath "u[a-z]r") | Should -BeFalse
}

It "Should return true if used on components other than filesystem objects" {
Test-Path Alias:\gci | Should -BeTrue
Test-Path Env:\PATH | Should -BeTrue
Test-Path Alias:\gci | Should -BeTrue
Test-Path Env:\PATH | Should -BeTrue
}

}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.