From edb292face367c444939e3a7acb59cabf5744ee9 Mon Sep 17 00:00:00 2001 From: Yanbing Date: Thu, 15 Sep 2016 11:27:44 -0700 Subject: [PATCH] Add tests for the Language (#2118) DeserializedMethods, DeserializedTypeConversion, DynamicParameters, Generics, Indexer, LindEndings, MyInvocation, OrderedAttributeForHashTable. --- .../Scripting/DeserializedMethods.Tests.ps1 | 19 ++ .../DeserializedTypeConversion.Tests.ps1 | 272 ++++++++++++++++++ .../Scripting/Dynamicparameters.Tests.ps1 | 84 ++++++ .../Language/Scripting/Generics.Tests.ps1 | 98 +++++++ .../Language/Scripting/Indexer.Tests.ps1 | 25 ++ .../Language/Scripting/LineEndings.Tests.ps1 | 141 +++++++++ .../Language/Scripting/MyInvocation.Tests.ps1 | 45 +++ .../OrderedAttributeForHashTables.Tests.ps1 | 70 +++++ 8 files changed, 754 insertions(+) create mode 100644 test/powershell/Language/Scripting/DeserializedMethods.Tests.ps1 create mode 100644 test/powershell/Language/Scripting/DeserializedTypeConversion.Tests.ps1 create mode 100644 test/powershell/Language/Scripting/Dynamicparameters.Tests.ps1 create mode 100644 test/powershell/Language/Scripting/Generics.Tests.ps1 create mode 100644 test/powershell/Language/Scripting/Indexer.Tests.ps1 create mode 100644 test/powershell/Language/Scripting/LineEndings.Tests.ps1 create mode 100644 test/powershell/Language/Scripting/MyInvocation.Tests.ps1 create mode 100644 test/powershell/Language/Scripting/OrderedAttributeForHashTables.Tests.ps1 diff --git a/test/powershell/Language/Scripting/DeserializedMethods.Tests.ps1 b/test/powershell/Language/Scripting/DeserializedMethods.Tests.ps1 new file mode 100644 index 0000000000..ab593bb7d1 --- /dev/null +++ b/test/powershell/Language/Scripting/DeserializedMethods.Tests.ps1 @@ -0,0 +1,19 @@ +Describe "DeserializedMethods" -Tags "CI" { + It "Deserialized objects shouldn't ever have any methods (unless they are primitive known types)" { + $a = [collections.arraylist]::new() + $null = $a.Add(1) + $null = $a.Add(2) + $null = $a.Add(3) + + # using linkedlist that implements IEnumerable, + # but doesn't implement IList or IList + $x = [collections.generic.linkedlist[int]]::new() + $null = $x.Add(123) + $null = $x.Add(456) + + $s = [System.Management.Automation.PSSerializer]::Serialize($x) + $d = [System.Management.Automation.PSSerializer]::Deserialize($s) + + $d | Get-Member -MemberType *Method* Add | Should Be $null + } +} \ No newline at end of file diff --git a/test/powershell/Language/Scripting/DeserializedTypeConversion.Tests.ps1 b/test/powershell/Language/Scripting/DeserializedTypeConversion.Tests.ps1 new file mode 100644 index 0000000000..a7f1092227 --- /dev/null +++ b/test/powershell/Language/Scripting/DeserializedTypeConversion.Tests.ps1 @@ -0,0 +1,272 @@ +Describe "Tests conversion of deserialized types to original type using object properties." -Tags "CI" { + BeforeAll { + # Create new types and test functions. + $type1,$type2,$type3,$type4 = Add-Type -PassThru -TypeDefinition @' + public class test1 + { + public string name; + public int port; + public string scriptText; + } + + public class test2 + { + private string name; + private int port; + private string scriptText; + + public string Name + { + get { return name; } + set { name = value; } + } + + public int Port + { + get { return port; } + set { port = value; } + } + + public string ScriptText + { + get { return scriptText; } + set { scriptText = value; } + } + } + + public class test3 + { + private string name = "default"; + private int port = 80; + private string scriptText = "1..6"; + + public string Name + { + get { return name; } + set { name = value; } + } + + public int Port + { + get { return port; } + set { port = value; } + } + + public string ScriptText + { + get { return scriptText; } + } + } + + public class test4 + { + private string name = "default"; + private int port = 80; + private string scriptText = "1..6"; + + public string Name + { + get { return name; } + set { name = value; } + } + + public int Port + { + get { return port; } + set { port = value; } + } + + internal void Compute() + { + scriptText = scriptText + " Computed"; + } + } +'@ + } + + Context 'Type conversion and parameter binding of deserialized Type case 1: type definition contains public fields' { + BeforeAll { + $t1 = new-object test1 -Property @{name="TestName1";port=80;scriptText="1..5"} + $s = [System.Management.Automation.PSSerializer]::Serialize($t1) + $dst1 = [System.Management.Automation.PSSerializer]::Deserialize($s) + } + + It 'Type casts should succeed.' { + { $tc1 = [test1]$dst1 }| Should not Throw + } + + It 'Parameter bindings should succeed.' { + + function test-1 + { + param( + [parameter(position=0, mandatory=1)] + [test1] $test + ) + + $test | Format-List | Out-String + } + { test-1 $dst1 } | Should Not Throw + } + } + + Context 'Type conversion and parameter binding of deserialized Type case 2: type definition contains public properties' { + BeforeAll { + $t2 = new-object test2 -Property @{Name="TestName2";Port=80;ScriptText="1..5"} + $s = [System.Management.Automation.PSSerializer]::Serialize($t2) + $dst2 = [System.Management.Automation.PSSerializer]::Deserialize($s) + } + It 'Type casts should succeed.' { + { $tc2 = [test2]$dst2 } | Should Not Throw + } + + It 'Parameter bindings should succeed.' { + function test-2 + { + param( + [parameter(position=0, mandatory=1)] + [test2] $test + ) + + $test | Format-List | Out-String + } + { test-2 $dst2 } | Should Not Throw + } + } + + Context 'Type conversion and parameter binding of deserialized Type case 1: type definition contains 2 public properties and 1 read only property' { + BeforeAll { + $t3 = new-object test3 -Property @{Name="TestName3";Port=80} + $s = [System.Management.Automation.PSSerializer]::Serialize($t3) + $dst3 = [System.Management.Automation.PSSerializer]::Deserialize($s) + } + + It 'Type casts should fail.' { + + try + { + $tc3 = [test3]$dst3 + Throw "Exception expected, execution should not have reached here" + } + catch { + $_.FullyQualifiedErrorId | Should Be 'InvalidCastConstructorException' + } + } + + It 'Parameter bindings should fail.' { + + function test-3 + { + param( + [parameter(position=0, mandatory=1)] + [test3] $test + ) + + $test | Format-List | Out-String + } + + try + { + test-3 $dst3 + Throw "Exception expected, execution should not have reached here" + } + catch { + $_.FullyQualifiedErrorId | Should Be 'ParameterArgumentTransformationError,test-3' + } + } + } + + Context 'Type conversion and parameter binding of deserialized Type case 1: type definition contains 2 public properties' { + BeforeAll { + $t4 = new-object test4 -Property @{Name="TestName4";Port=80} + $s = [System.Management.Automation.PSSerializer]::Serialize($t4) + $dst4 = [System.Management.Automation.PSSerializer]::Deserialize($s) + } + + It 'Type casts should succeed.' { + { $tc4 = [test4]$dst4 } | Should Not Throw + } + + It 'Parameter bindings should succeed.' { + function test-4 + { + param( + [parameter(position=0, mandatory=1)] + [test4] $test + ) + + $test | Format-List | Out-String + } + { test-4 $dst4 } | Should Not Throw + } + } + + Context 'Type conversion and parameter binding of deserialized Powershell class with default constructor' { + BeforeAll { + class PSClass1 { + [string] $name = "PSClassName1" + [int] $port = 80 + [string] $scriptText = "1..6" + } + + $t5 = [PSClass1]::new() + $s = [System.Management.Automation.PSSerializer]::Serialize($t5) + $dst5 = [System.Management.Automation.PSSerializer]::Deserialize($s) + } + + It 'Type casts should succeed.' { + + { $tc5 = [PSClass1]$dst5 } | Should Not Throw + } + + It 'Parameter bindings should succeed.' { + function test-PSClass1 + { + param( + [parameter(position=0, mandatory=1)] + [PSClass1] $test + ) + + $test | Format-List | Out-String + } + { test-PSClass1 $dst5 } | Should Not Throw + } + } + + Context 'Type conversion and parameter binding of deserialized Powershell class with a defualt constructor and a constructor' { + BeforeAll { + class PSClass2 { + [string] $name = "default" + [int] $port = 80 + [string] $scriptText = "1..6" + PSClass2() {} + PSClass2([string] $name1, [int] $port1, [string] $scriptText1) + { + $this.name = $name1 + $this.port = $port1 + $this.scriptText = $scriptText1 + } + } + $t6 = [PSClass2]::new("PSClassName2", 80, "1..5") + $s = [System.Management.Automation.PSSerializer]::Serialize($t6) + $dst6 = [System.Management.Automation.PSSerializer]::Deserialize($s) + } + + It 'Type casts should succeed.' { + { $tc6 = [PSClass2]$dst6 } | Should Not Throw + } + + It 'Parameter bindings should succeed.' { + function test-PSClass2 + { + param( + [parameter(position=0, mandatory=1)] + [PSClass2] $test + ) + + $test | Format-List | Out-String + } + { test-PSClass2 $dst6 } | Should Not Throw + } + } +} diff --git a/test/powershell/Language/Scripting/Dynamicparameters.Tests.ps1 b/test/powershell/Language/Scripting/Dynamicparameters.Tests.ps1 new file mode 100644 index 0000000000..2a53f06691 --- /dev/null +++ b/test/powershell/Language/Scripting/Dynamicparameters.Tests.ps1 @@ -0,0 +1,84 @@ +Describe "Dynamic parameter support in script cmdlets." -Tags "CI" { + BeforeAll { + Class MyTestParameter { + [parameter(ParameterSetName = 'pset1', position=0, mandatory=1)] + [string] $name + } + + function foo-bar + { + [CmdletBinding()] + param($path) + + dynamicparam { + if ($PSBoundParameters["path"] -contains "abc") { + $attributes = [System.Management.Automation.ParameterAttribute]::New() + $attributes.ParameterSetName = 'pset1' + $attributes.Mandatory = $false + + $attributeCollection = [System.Collections.ObjectModel.Collection``1[System.Attribute]]::new() + $attributeCollection.Add($attributes) + + $dynParam1 = [System.Management.Automation.RuntimeDefinedParameter]::new("dp1", [Int32], $attributeCollection) + + $paramDictionary = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() + $paramDictionary.Add("dp1", $dynParam1) + + return $paramDictionary + } + elseif($PSBoundParameters["path"] -contains "class") { + $paramDictionary = [MyTestParameter]::new() + return $paramDictionary + } + + $paramDictionary = $null + return $null + } + + begin { + if(($paramDictionary -ne $null) -and ($paramDictionary -is [MyTestParameter]) ) { + $paramDictionary.name + } + elseif ($paramDictionary -ne $null) { + if ($paramDictionary.dp1.Value -ne $null) { + $paramDictionary.dp1.Value + } + else { + "dynamic parameters not passed" + } + } + else { + "no dynamic parameters" + } + } + + process {} + end {} + } + } + + It "The dynamic parameter is enabled and bound" { + foo-bar -path abc -dp1 42 | Should Be 42 + } + + It "When the dynamic parameter is not available, and raises an error when specified" { + try { + foo-bar -path def -dp1 42 + Throw "Exception expected, execution should not have reached here" + } catch { + $_.FullyQualifiedErrorId | Should Be "NamedParameterNotFound,foo-bar" + } + } + + It "No dynamic parameter shouldn't cause an errr " { + foo-bar -path def | Should Be 'no dynamic parameters' + } + + It "Not specifying dynamic parameter shouldn't cause an error" { + foo-bar -path abc | Should Be 'dynamic parameters not passed' + } + + It "Parameter is defined in Class" { + foo-bar -path class -name "myName" | Should Be 'myName' + } +} diff --git a/test/powershell/Language/Scripting/Generics.Tests.ps1 b/test/powershell/Language/Scripting/Generics.Tests.ps1 new file mode 100644 index 0000000000..fdbdbc1fe2 --- /dev/null +++ b/test/powershell/Language/Scripting/Generics.Tests.ps1 @@ -0,0 +1,98 @@ +using namespace system.collections.generic +using namespace System.Management.Automation +Describe "Generics support" -Tags "CI" { + # list and stack are in different assemblies, and dictionary + # takes more than one type parameter. + + It 'Type list[Int] works properly' { + $x = [list[int]]::New() + $x.Add(42) + $x.Add(40) + $x.count | Should Be 2 + } + + It 'Type stack[Int] works properly' { + $x = [stack[int]]::New() + $x.Push(42) + $x.Push(40) + $x.count | Should Be 2 + } + + It 'Type dictionary[string, Int] works properly' { + $x = [dictionary[string, int]]::New() + $x.foo = 42 + $x.foo | Should Be 42 + } + + It 'Type list[[Int]] works properly' { + $x = [list[[int]]]::New() + $x.Add(42) + $x.Add(40) + $x.count | Should Be 2 + } + + It 'Type stack[[Int]] works properly' { + $x = [stack[[int]]]::New() + $x.Push(42) + $x.Push(40) + $x.count | Should Be 2 + } + + It 'Type dictionary[[string], [Int]] works properly' { + $x = [dictionary[[string], [int]]]::New() + $x.foo = 42 + $x.foo | Should Be 42 + } + + It 'Type dictionary[dictionary[list[int],string], stack[double]] works properly' { + $x = [dictionary[dictionary[list[int],string], stack[double]]]::new() + $x.gettype().fullname | should match "double" + + $y = new-object "dictionary[dictionary[list[int],string], stack[double]]" + $y.gettype().fullname | should match "double" + } + + It 'non-generic EventHandler works properly' { + # EventHandler has a generic and a non-generic. This code caused an exception trying to + # use the non-generic. + $x = [System.EventHandler[PSInvocationStateChangedEventArgs]] + + # The error message for a generic that doesn't meet the constraints should mention which + # argument failed. + $ex = $null + try { + [nullable[object]] + Throw "Exception expected, execution should not have reached here" + } catch { + $_.FullyQualifiedErrorId | Should be 'TypeNotFoundWithMessage' + $_ | should match "\[T\]" + } + } + + It 'Array type works properly' -skip:$IsCoreCLR{ + $x = [system.array]::ConvertAll.OverloadDefinitions + $x | Should match "static\s+TOutput\[\]\s+ConvertAll\[TInput,\s+TOutput\]\(" + } + + It 'Class type works properly' { + class TestClass { + [string] $name = "default" + [int] $port = 80 + [string] $scriptText = "1..6" + + TestClass([string] $name1, [int] $port1, [string] $scriptText1) + { + $this.name = $name1 + $this.port = $port1 + $this.scriptText = $scriptText1 + } + } + + $x = [TestClass]::New("default1", 90, "1...5") + $x.scriptText = "1...4" + $x.name | Should Be 'default1' + $x.port | Should Be 90 + $x.scriptText | Should Be "1...4" + } +} + diff --git a/test/powershell/Language/Scripting/Indexer.Tests.ps1 b/test/powershell/Language/Scripting/Indexer.Tests.ps1 new file mode 100644 index 0000000000..24d1d82b4a --- /dev/null +++ b/test/powershell/Language/Scripting/Indexer.Tests.ps1 @@ -0,0 +1,25 @@ +Describe 'Tests for indexers' -Tags "CI" { + It 'Indexer in dictionary' { + + $hashtable = @{ "Hello"="There" } + $hashtable["Hello"] | Should Be "There" + } + + It 'Accessing a Indexed property of a dictionary that does not exist should return $NULL' { + $hashtable = @{ "Hello"="There" } + $hashtable["Hello There"] | Should Be $null + } + + It 'Wmi object implements an indexer' -Skip:$IsCoreCLR { + + $service = Get-WmiObject -List -Amended Win32_Service + + $service.Properties["DisplayName"].Name | Should Be 'DisplayName' + } + + It 'Accessing a Indexed property of a wmi object that does not exist should return $NULL' -skip:$IsCoreCLR { + + $service = Get-WmiObject -List -Amended Win32_Service + $service.Properties["Hello There"] | Should Be $null + } +} diff --git a/test/powershell/Language/Scripting/LineEndings.Tests.ps1 b/test/powershell/Language/Scripting/LineEndings.Tests.ps1 new file mode 100644 index 0000000000..40ce798586 --- /dev/null +++ b/test/powershell/Language/Scripting/LineEndings.Tests.ps1 @@ -0,0 +1,141 @@ +Describe 'Line endings' -Tags "CI" { + BeforeAll { + $lf = "`n" + $cr = "`r" + $crlf="`r`n" + $dq = "`"" + $sq = "'" + $hereDB = "@`"" + $hereDE = "`"@" + $hereSB = "@'" + $hereSE = "'@" + } + + $testData = @( + @{ + Name = 'CR in single quotes here-string'; + NewLine = $cr + Begin = $hereSB+$cr; + End = $cr + $hereSE + }, + @{ + Name = 'LF in single quotes here-string'; + NewLine = $lf + Begin = $hereSB+$lf; + End = $lf + $hereSE + }, + @{ + Name = 'CRLF in single quotes here-string'; + NewLine = $cr+$lf + Begin = $hereSB+$cr+$lf; + End = $cr+$lf + $hereSE + }, + @{ + Name = 'CR in double quotes here string'; + Begin = $hereDB + $cr; + NewLine = $cr + End = $cr + $hereDE + }, + @{ + Name = 'LF in double quotes here string'; + Begin = $hereDB + $lf; + NewLine = $lf + End = $lf + $hereDE + }, + @{ + Name = 'CRLF in double quotes here string'; + Begin = $hereDB + $cr + $lf; + NewLine = $cr + $lf + End = $cr + $lf + $hereDE + }, + @{ + Name = 'CR in double quotes here string'; + Begin = $hereDB + $cr; + NewLine = $cr + End = $cr + $hereDE + }, + @{ + Name = 'Lf in double quotes here string'; + Begin = $hereDB + $lf; + NewLine = $lf + End = $lf + $hereDE + }, + @{ + Name = 'CRLF in double quotes here string'; + Begin = $hereDB + $cr + $lf; + NewLine = $cr + $lf + End = $cr + $lf + $hereDE + }, + @{ + Name = 'CR in single quotes string'; + Begin = $sq; + NewLine = $cr + End = $sq + }, + @{ + Name = 'LF in single quotes string'; + Begin = $sq; + NewLine = $lf + End = $sq + }, + @{ + Name = 'CRLF in single quotes string'; + Begin = $sq; + NewLine = $cr + $lf + End = $sq + }, + @{ + Name = 'CR in double quotes string'; + Begin = $dq; + NewLine = $cr + End = $dq + }, + @{ + Name = 'LF in double quotes string'; + Begin = $dq; + NewLine = $lf + End = $dq + }, + @{ + Name = 'CRLF in double quotes string'; + Begin = $dq; + NewLine = $cr + $lf + End = $dq + } + ) + + It ' in expression' -TestCases:$testData { + param([string]$Name, $Begin, $End, $NewLine) + # build the content string + $expected = "This$($newline)is$($newline)a$($newline)multi$($newline)line$($newline)string" + + # wrap the content in the specified begin and end quoting characters. + $content = "$($Begin)$($expected)$($End)" + $actual = iex $content + + # $actual should be the content string ($expected) without the begin and end quoting characters. + $actual | Should Be $expected + } + + It ' in ps file' -TestCases:$testData { + param([string]$Name, $Begin, $End) + + $fileName = $Name.Replace(' ', '') + '.ps1' + + # build the content string + $expected = "This$($newline)is$($newline)a$($newline)multi$($newline)line$($newline)string" + + # wrap the content in the specified begin and end quoting characters. + $content = "$($Begin)$($expected)$($End)" + # BUG: Set-Content is failing on linux if the file does not exit. + $null = New-item -path TESTDRIVE:$fileName -force + $content | Set-content -NoNewline -Encoding ascii -Path TESTDRIVE:\$fileName + $actual = &( "TESTDRIVE:\$fileName") + + # $actual should be the content string ($expected) without the begin and end quoting characters. + $actual | Should Be $expected + } +} + +$test = @" +"@ diff --git a/test/powershell/Language/Scripting/MyInvocation.Tests.ps1 b/test/powershell/Language/Scripting/MyInvocation.Tests.ps1 new file mode 100644 index 0000000000..81dfc27f9e --- /dev/null +++ b/test/powershell/Language/Scripting/MyInvocation.Tests.ps1 @@ -0,0 +1,45 @@ +Describe 'Testing of MyInvocation' -Tags "CI" { + It 'MyInvocation works in Function' { + + function myfunc + { + $MyInvocation.Line.IndexOf("myfunc") | Should BeGreaterThan -1 + } + + { . myfunc } | Should Not Throw + {& myfunc } | Should Not Throw + } + + It 'MyInvocation works in Filter' { + + filter myfilter + { + $MyInvocation.Line.IndexOf("myfilter") | Should BeGreaterThan -1 + } + + {. myfilter } | Should Not Throw + { & myfilter } | Should Not Throw + } + + Context 'MyInvocation works in Script block' { + + It 'MyInvocation works in dot sourced Script block' { + $a = . {$MyInvocation.Line} + $a.IndexOf('$a = . {$MyInvocation.Line}') | Should BeGreaterThan -1 + } + It 'MyInvocation works in & Script block2' { + $a = & {$MyInvocation.Line} + $a.IndexOf('$a = & {$MyInvocation.Line}') | Should BeGreaterThan -1 + } + + It 'MyInvocation works when run Script file' { + $a = & {$MyInvocation.ScriptName} + $a.ToLower().IndexOf("myinvocation.tests.ps1") | Should BeGreaterThan -1 + } + + It 'MyInvocation works when dot source Script file' { + $a = . {$MyInvocation.ScriptName} + $a.ToLower().IndexOf("myinvocation.tests.ps1") | Should BeGreaterThan -1 + } + } +} diff --git a/test/powershell/Language/Scripting/OrderedAttributeForHashTables.Tests.ps1 b/test/powershell/Language/Scripting/OrderedAttributeForHashTables.Tests.ps1 new file mode 100644 index 0000000000..49c92363c8 --- /dev/null +++ b/test/powershell/Language/Scripting/OrderedAttributeForHashTables.Tests.ps1 @@ -0,0 +1,70 @@ +Describe 'Test for cmdlet to support Ordered Attribute on hash literal nodes' -Tags "CI" { + BeforeAll { + If (-not $IsCoreCLR) { + Get-WmiObject -Query "select * from win32_environment where name='TestWmiInstance'" | Remove-WmiObject + } + } + AfterAll { + If (-not $IsCoreCLR) { + Get-WmiObject -Query "select * from win32_environment where name='TestWmiInstance'" | Remove-WmiObject + } + } + + It 'New-Object - Property Parameter Must take IDictionary' { + $a = new-object psobject -property ([ordered]@{one=1;two=2}) + $a | Should Not Be $null + $a.one | Should Be 1 + } + + + Context 'Select-Xml cmdlet - Namespace parameter must take IDictionary' { + $script:a = $null + + $helpXml = @' + + + + + + + + Stop-Transcript + + + + + + +'@ + + { $script:a = select-xml -content $helpXml -xpath "//command:name" -namespace ( + [ordered]@{command="http://schemas.microsoft.com/maml/dev/command/2004/10"; + maml="http://schemas.microsoft.com/maml/2004/10"; + dev="http://schemas.microsoft.com/maml/dev/2004/10"}) } | Should Not Throw + + It '$a should not be $null' { $script:a | Should Not Be $null } + } + + + It 'Set-WmiInstance cmdlet - Argument parameter must take IDictionary' -skip:$IsCoreCLR { + + $script:a = $null + + { $script:a = set-wmiinstance -class win32_environment -argument ([ordered]@{Name="TestWmiInstance"; + VariableValue="testvalu234e"; + UserName=""}) } | Should Not Throw + $script:a | Should Not Be $null + $script:a.Name | Should Be "TestWmiInstance" + } + + Context 'Select-Object cmdlet - Property parameter (Calculated properties) must take IDictionary' { + + $script:a = $null + + {$script:a = dir | select-object -property Name, ( + [ordered]@{Name="IsDirectory"; + Expression ={$_.PSIsContainer}})} | Should Not Throw + + It '$a should not be $null' { $script:a | Should Not Be $null } + } +}