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

This commit is contained in:
Joel Sallow (/u/ta11ow)
2018-11-21 10:08:31 +05:00
committed by Ilya
parent 7193763467
commit 775552c0f5
4 changed files with 228 additions and 49 deletions
@@ -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 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
[Parameter(Position = 0, ParameterSetName = "Path",
Mandatory = true, ValueFromPipeline = true, ValueFromPipelineByPropertyName = true)]
[AllowNull]
[AllowEmptyCollection]
[AllowEmptyString]
public string[] Path
{
get { return _paths; }
@@ -53,6 +57,9 @@ namespace Microsoft.PowerShell.Commands
[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 @@ namespace Microsoft.PowerShell.Commands
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 @@ namespace Microsoft.PowerShell.Commands
result = InvokeProvider.Item.ItemExistsDynamicParameters(".", context);
}
}
return result;
} // GetDynamicParameters
@@ -154,12 +162,39 @@ namespace Microsoft.PowerShell.Commands
/// </summary>
protected override void ProcessRecord()
{
if (_paths == null || _paths.Length == 0)
{
WriteError(new ErrorRecord(
new ArgumentNullException(TestPathResources.PathIsNullOrEmptyCollection),
"NullPathNotPermitted",
ErrorCategory.InvalidArgument,
Path));
return;
}
CmdletProviderContext currentContext = CmdletProviderContext;
foreach (string path in _paths)
{
bool result = false;
if (path == null)
{
WriteError(new ErrorRecord(
new ArgumentNullException(TestPathResources.PathIsNullOrEmptyCollection),
"NullPathNotPermitted",
ErrorCategory.InvalidArgument,
Path));
continue;
}
if (string.IsNullOrWhiteSpace(path))
{
WriteObject(result);
continue;
}
try
{
if (IsValid)
@@ -184,6 +219,7 @@ namespace Microsoft.PowerShell.Commands
}
}
}
// Any of the known exceptions means the path does not exist.
catch (PSNotSupportedException)
{
@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="https://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="https://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="PathIsNullOrEmptyCollection" xml:space="preserve">
<value>The provided Path argument was null or an empty collection.</value>
</data>
</root>
@@ -17,7 +17,7 @@ for ($null[0];
'@
$for_pipeline_initializer_script = @'
$test = 1
for (Test-Path $null;
for (Test-Path $null -ErrorAction Stop;
$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 -ErrorAction Stop;)
{ }
'@
$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 -ErrorAction Stop)
'@
$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 -ErrorAction Stop)
'@
$testCases = @(
@@ -2,123 +2,143 @@
# 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' {
# This ensures the error is non-terminating; a terminating error would fail the first test
{ Test-Path -Path $null -ErrorAction SilentlyContinue } | Should -Not -Throw
{ Test-Path -Path $null -ErrorAction Stop } | Should -Throw -ErrorId 'NullPathNotPermitted,Microsoft.PowerShell.Commands.TestPathCommand'
}
It 'Should write a non-terminating error when given an array of null paths' {
# This ensures the error is non-terminating; a terminating error would fail the first test
{ Test-Path -Path $null, $null -ErrorAction SilentlyContinue } | Should -Not -Throw
{ Test-Path -Path $null, $null -ErrorAction Stop } | Should -Throw -ErrorId 'NullPathNotPermitted,Microsoft.PowerShell.Commands.TestPathCommand'
}
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
}
}