From 0be252ec0e05425bdd33f88dec481d65f67143c2 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Tue, 19 Jul 2016 12:19:56 -0700 Subject: [PATCH 01/25] Use class instead of PSObject for Get/Remove-CronJob --- demos/modules/CronTab/CronTab.psm1 | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/demos/modules/CronTab/CronTab.psm1 b/demos/modules/CronTab/CronTab.psm1 index f8bd2e7c4..0301636b2 100644 --- a/demos/modules/CronTab/CronTab.psm1 +++ b/demos/modules/CronTab/CronTab.psm1 @@ -1,14 +1,13 @@ $crontabcmd = "/usr/bin/crontab" -#TODO fix after https://github.com/PowerShell/PowerShell/issues/932 is fixed -#class CronJob { -# [string] $Minute -# [string] $Hour -# [string] $DayOfMonth -# [string] $Month -# [string] $DayOfWeek -# [string] $Command -#} +class CronJob { + [string] $Minute + [string] $Hour + [string] $DayOfMonth + [string] $Month + [string] $DayOfWeek + [string] $Command +} # Internal helper functions @@ -27,8 +26,7 @@ function Get-CronTab ([String] $user) { function ConvertTo-CronJob ([String] $crontab) { $split = $crontab.split(" ", 6) - $cronjob = New-Object -TypeName PSObject -Property @{ #TODO: change to CronJob type - PSTypeName="CronJob"; + $cronjob = [CronJob]@{ Minute = $split[0]; Hour = $split[1]; DayOfMonth= $split[2]; @@ -82,7 +80,7 @@ function Remove-CronJob { [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")] param ( [Alias("u")][Parameter(Mandatory=$false)][String] $UserName, - [Alias("j")][Parameter(Mandatory=$true,ValueFromPipeline=$true)][PSTypeName("CronJob")] $Job #TODO use CronJob type + [Alias("j")][Parameter(Mandatory=$true,ValueFromPipeline=$true)][CronJob] $Job ) process { @@ -173,7 +171,7 @@ function Get-CronJob { Optional parameter to specify a specific user's cron table #> [CmdletBinding()] - [OutputType([PSObject])] + [OutputType([CronJob])] param ( [Alias("u")][Parameter(Mandatory=$false)][String] $UserName ) From 7048f370bd2a5c0105856709e50de7bcbaee3f71 Mon Sep 17 00:00:00 2001 From: PowerShell Team Date: Tue, 19 Jul 2016 13:44:10 -0700 Subject: [PATCH 02/25] Add map.json for language test migration --- test/powershell/Language/map.json | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/powershell/Language/map.json diff --git a/test/powershell/Language/map.json b/test/powershell/Language/map.json new file mode 100644 index 000000000..4cd1ac0b6 --- /dev/null +++ b/test/powershell/Language/map.json @@ -0,0 +1,30 @@ +{ + "monad/tests/ci/powershell/tests/Scripting/LanguageTestSupport.psm1": "LanguageTestSupport.psm1", + "monad/tests/ci/powershell/tests/Scripting/CompletionTestSupport.psm1": "CompletionTestSupport.psm1", + "monad/tests/ci/powershell/tests/Scripting/Classes/MSFT_778492.psm1": "Classes/MSFT_778492.psm1", + "monad/tests/ci/powershell/tests/Scripting/Classes/ProtectedAccess.Tests.ps1": "Classes/ProtectedAccess.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.Attributes.Tests.ps1": "Classes/Scripting.Classes.Attributes.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.BasicParsing.Tests.ps1": "Classes/Scripting.Classes.BasicParsing.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.Break.Tests.ps1": "Classes/Scripting.Classes.Break.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.Exceptions.Tests.ps1": "Classes/Scripting.Classes.Exceptions.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/scripting.Classes.inheritance.tests.ps1": "Classes/scripting.Classes.inheritance.tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.MiscOps.Tests.ps1": "Classes/Scripting.Classes.MiscOps.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.Modules.Tests.ps1": "Classes/Scripting.Classes.Modules.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/scripting.Classes.NestedModules.tests.ps1": "Classes/scripting.Classes.NestedModules.tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/scripting.Classes.using.tests.ps1": "Classes/scripting.Classes.using.tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Classes/scripting.enums.tests.ps1": "Classes/scripting.enums.tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Debugging/Pester.DebuggerScriptTests.Tests.ps1": "Scripting/Debugging/DebuggerScriptTests.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/Debugging/Pester.DebuggingInHost.Tests.ps1": "Scripting/Debugging/DebuggingInHost.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/AutomaticVariables.Tests.ps1": "Parser/AutomaticVariables.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/BNotOperator.Tests.ps1": "Parser/BNotOperator.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/Conversions.Tests.ps1": "Parser/Conversions.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/ExtensibleCompletion.Tests.ps1": "Parser/ExtensibleCompletion.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/LanguageAndParser.TestFollowup.Tests.ps1": "Parser/LanguageAndParser.TestFollowup.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/MethodInvocation.Tests.ps1": "Parser/MethodInvocation.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/ParameterBinding.Tests.ps1": "Parser/ParameterBinding.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/Pester.Ast.Tests.ps1": "Parser/Ast.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/Pester.RedirectionOperator.Tests.ps1": "Parser/RedirectionOperator.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/TypeAccelerator.Tests.ps1": "Parser/TypeAccelerator.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/UsingAssembly.Tests.ps1": "Parser/UsingAssembly.Tests.ps1", + "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/UsingNamespace.Tests.ps1": "Parser/UsingNamespace.Tests.ps1" +} From 395657e33c7555406f9277e32a9158eb759c48f3 Mon Sep 17 00:00:00 2001 From: PowerShell Team Date: Tue, 19 Jul 2016 13:51:04 -0700 Subject: [PATCH 03/25] fixed up incorrect cases --- test/powershell/Language/map.json | 56 +++++++++++++++---------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/test/powershell/Language/map.json b/test/powershell/Language/map.json index 4cd1ac0b6..a9f2430ba 100644 --- a/test/powershell/Language/map.json +++ b/test/powershell/Language/map.json @@ -1,30 +1,30 @@ { - "monad/tests/ci/powershell/tests/Scripting/LanguageTestSupport.psm1": "LanguageTestSupport.psm1", - "monad/tests/ci/powershell/tests/Scripting/CompletionTestSupport.psm1": "CompletionTestSupport.psm1", - "monad/tests/ci/powershell/tests/Scripting/Classes/MSFT_778492.psm1": "Classes/MSFT_778492.psm1", - "monad/tests/ci/powershell/tests/Scripting/Classes/ProtectedAccess.Tests.ps1": "Classes/ProtectedAccess.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.Attributes.Tests.ps1": "Classes/Scripting.Classes.Attributes.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.BasicParsing.Tests.ps1": "Classes/Scripting.Classes.BasicParsing.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.Break.Tests.ps1": "Classes/Scripting.Classes.Break.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.Exceptions.Tests.ps1": "Classes/Scripting.Classes.Exceptions.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/scripting.Classes.inheritance.tests.ps1": "Classes/scripting.Classes.inheritance.tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.MiscOps.Tests.ps1": "Classes/Scripting.Classes.MiscOps.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/Scripting.Classes.Modules.Tests.ps1": "Classes/Scripting.Classes.Modules.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/scripting.Classes.NestedModules.tests.ps1": "Classes/scripting.Classes.NestedModules.tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/scripting.Classes.using.tests.ps1": "Classes/scripting.Classes.using.tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Classes/scripting.enums.tests.ps1": "Classes/scripting.enums.tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Debugging/Pester.DebuggerScriptTests.Tests.ps1": "Scripting/Debugging/DebuggerScriptTests.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/Debugging/Pester.DebuggingInHost.Tests.ps1": "Scripting/Debugging/DebuggingInHost.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/AutomaticVariables.Tests.ps1": "Parser/AutomaticVariables.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/BNotOperator.Tests.ps1": "Parser/BNotOperator.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/Conversions.Tests.ps1": "Parser/Conversions.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/ExtensibleCompletion.Tests.ps1": "Parser/ExtensibleCompletion.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/LanguageAndParser.TestFollowup.Tests.ps1": "Parser/LanguageAndParser.TestFollowup.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/MethodInvocation.Tests.ps1": "Parser/MethodInvocation.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/ParameterBinding.Tests.ps1": "Parser/ParameterBinding.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/Pester.Ast.Tests.ps1": "Parser/Ast.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/Pester.RedirectionOperator.Tests.ps1": "Parser/RedirectionOperator.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/TypeAccelerator.Tests.ps1": "Parser/TypeAccelerator.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/UsingAssembly.Tests.ps1": "Parser/UsingAssembly.Tests.ps1", - "monad/tests/ci/powershell/tests/Scripting/LanguageandParser/UsingNamespace.Tests.ps1": "Parser/UsingNamespace.Tests.ps1" + "monad/tests/ci/PowerShell/tests/Scripting/LanguageTestSupport.psm1": "LanguageTestSupport.psm1", + "monad/tests/ci/PowerShell/tests/Scripting/CompletionTestSupport.psm1": "CompletionTestSupport.psm1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/MSFT_778492.psm1": "Classes/MSFT_778492.psm1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/ProtectedAccess.Tests.ps1": "Classes/ProtectedAccess.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/Scripting.Classes.Attributes.Tests.ps1": "Classes/Scripting.Classes.Attributes.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/Scripting.Classes.BasicParsing.Tests.ps1": "Classes/Scripting.Classes.BasicParsing.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/Scripting.Classes.Break.Tests.ps1": "Classes/Scripting.Classes.Break.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/Scripting.Classes.Exceptions.Tests.ps1": "Classes/Scripting.Classes.Exceptions.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/scripting.classes.inheritance.tests.ps1": "Classes/scripting.Classes.inheritance.tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/Scripting.Classes.MiscOps.Tests.ps1": "Classes/Scripting.Classes.MiscOps.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/Scripting.Classes.Modules.Tests.ps1": "Classes/Scripting.Classes.Modules.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/scripting.classes.NestedModules.tests.ps1": "Classes/scripting.Classes.NestedModules.tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/scripting.classes.using.tests.ps1": "Classes/scripting.Classes.using.tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Classes/scripting.enums.tests.ps1": "Classes/scripting.enums.tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Debugging/Pester.DebuggerScriptTests.Tests.ps1": "Scripting/Debugging/DebuggerScriptTests.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/Debugging/Pester.DebuggingInHost.Tests.ps1": "Scripting/Debugging/DebuggingInHost.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/AutomaticVariables.Tests.ps1": "Parser/AutomaticVariables.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/BNotOperator.Tests.ps1": "Parser/BNotOperator.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/Conversions.Tests.ps1": "Parser/Conversions.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/ExtensibleCompletion.Tests.ps1": "Parser/ExtensibleCompletion.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/LanguageAndParser.TestFollowup.Tests.ps1": "Parser/LanguageAndParser.TestFollowup.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/MethodInvocation.Tests.ps1": "Parser/MethodInvocation.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/ParameterBinding.Tests.ps1": "Parser/ParameterBinding.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/Pester.Ast.Tests.ps1": "Parser/Ast.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/Pester.RedirectionOperator.Tests.ps1": "Parser/RedirectionOperator.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/TypeAccelerator.Tests.ps1": "Parser/TypeAccelerator.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/UsingAssembly.Tests.ps1": "Parser/UsingAssembly.Tests.ps1", + "monad/tests/ci/PowerShell/tests/Scripting/LanguageandParser/UsingNamespace.Tests.ps1": "Parser/UsingNamespace.Tests.ps1" } From cb08681a55e6b5c0896087558bcd0186cc997a04 Mon Sep 17 00:00:00 2001 From: PowerShell Team Date: Tue, 19 Jul 2016 13:52:03 -0700 Subject: [PATCH 04/25] Add new test files to Language directory --- .../Language/Classes/MSFT_778492.psm1 | 15 + .../Classes/ProtectedAccess.Tests.ps1 | 188 ++++ .../Scripting.Classes.Attributes.Tests.ps1 | 218 +++++ .../Scripting.Classes.BasicParsing.Tests.ps1 | 823 ++++++++++++++++++ .../Classes/Scripting.Classes.Break.Tests.ps1 | 145 +++ .../Scripting.Classes.Exceptions.Tests.ps1 | 349 ++++++++ .../Scripting.Classes.MiscOps.Tests.ps1 | 46 + .../Scripting.Classes.Modules.Tests.ps1 | 74 ++ .../scripting.Classes.NestedModules.tests.ps1 | 126 +++ .../scripting.Classes.inheritance.tests.ps1 | 529 +++++++++++ .../Classes/scripting.Classes.using.tests.ps1 | 500 +++++++++++ .../Classes/scripting.enums.tests.ps1 | 97 +++ .../Language/CompletionTestSupport.psm1 | 142 +++ .../Language/LanguageTestSupport.psm1 | 178 ++++ test/powershell/Language/Parser/Ast.Tests.ps1 | 38 + .../Parser/AutomaticVariables.Tests.ps1 | 19 + .../Language/Parser/BNotOperator.Tests.ps1 | 142 +++ .../Language/Parser/Conversions.Tests.ps1 | 23 + .../Parser/ExtensibleCompletion.Tests.ps1 | 323 +++++++ .../LanguageAndParser.TestFollowup.Tests.ps1 | 191 ++++ .../Parser/MethodInvocation.Tests.ps1 | 78 ++ .../Parser/ParameterBinding.Tests.ps1 | 79 ++ .../Parser/RedirectionOperator.Tests.ps1 | 69 ++ .../Language/Parser/TypeAccelerator.Tests.ps1 | 24 + .../Language/Parser/UsingAssembly.Tests.ps1 | 113 +++ .../Language/Parser/UsingNamespace.Tests.ps1 | 154 ++++ .../Debugging/DebuggerScriptTests.Tests.ps1 | Bin 0 -> 39238 bytes .../Debugging/DebuggingInHost.Tests.ps1 | 82 ++ 28 files changed, 4765 insertions(+) create mode 100644 test/powershell/Language/Classes/MSFT_778492.psm1 create mode 100644 test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 create mode 100644 test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 create mode 100644 test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 create mode 100644 test/powershell/Language/Classes/Scripting.Classes.Break.Tests.ps1 create mode 100644 test/powershell/Language/Classes/Scripting.Classes.Exceptions.Tests.ps1 create mode 100644 test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 create mode 100644 test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 create mode 100644 test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 create mode 100644 test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 create mode 100644 test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 create mode 100644 test/powershell/Language/Classes/scripting.enums.tests.ps1 create mode 100644 test/powershell/Language/CompletionTestSupport.psm1 create mode 100644 test/powershell/Language/LanguageTestSupport.psm1 create mode 100644 test/powershell/Language/Parser/Ast.Tests.ps1 create mode 100644 test/powershell/Language/Parser/AutomaticVariables.Tests.ps1 create mode 100644 test/powershell/Language/Parser/BNotOperator.Tests.ps1 create mode 100644 test/powershell/Language/Parser/Conversions.Tests.ps1 create mode 100644 test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 create mode 100644 test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 create mode 100644 test/powershell/Language/Parser/MethodInvocation.Tests.ps1 create mode 100644 test/powershell/Language/Parser/ParameterBinding.Tests.ps1 create mode 100644 test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 create mode 100644 test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 create mode 100644 test/powershell/Language/Parser/UsingAssembly.Tests.ps1 create mode 100644 test/powershell/Language/Parser/UsingNamespace.Tests.ps1 create mode 100644 test/powershell/Language/Scripting/Debugging/DebuggerScriptTests.Tests.ps1 create mode 100644 test/powershell/Language/Scripting/Debugging/DebuggingInHost.Tests.ps1 diff --git a/test/powershell/Language/Classes/MSFT_778492.psm1 b/test/powershell/Language/Classes/MSFT_778492.psm1 new file mode 100644 index 000000000..13d00162c --- /dev/null +++ b/test/powershell/Language/Classes/MSFT_778492.psm1 @@ -0,0 +1,15 @@ + +$foo = 'MSFT_778492 script scope' + +class MSFT_778492 +{ + [string] F() + { + return $script:foo + } +} + +function Get-MSFT_778492 +{ + [MSFT_778492]::new() +} diff --git a/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 b/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 new file mode 100644 index 000000000..2c79c9d99 --- /dev/null +++ b/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 @@ -0,0 +1,188 @@ + +Add-Type -WarningAction Ignore @' +public class Base +{ + private int data; + + protected Base() + { + data = 10; + } + + protected Base(int i) + { + data = i; + } + + protected int Field; + protected int Property { get; set; } + public int Property1 { get; protected set; } + public int Property2 { protected get; set; } + + protected int Method() + { + return 32 + data; + } + protected int OverloadedMethod1(int i) + { + return 32 + i + data; + } + protected int OverloadedMethod1(string i) + { + return 1 + data; + } + public int OverloadedMethod2(int i) + { + return 32 + i + data; + } + protected int OverloadedMethod2(string i) + { + return 1 + data; + } + protected int OverloadedMethod3(int i) + { + return 32 + i + data; + } + public int OverloadedMethod3(string i) + { + return 1 + data; + } +} +'@ + +$derived1,$derived2,$derived3 = Invoke-Expression @' +class Derived : Base +{ + Derived() : Base() {} + Derived([int] $i) : Base($i) {} + + [int] TestPropertyAccess() + { + $this.Property = 1111 + return $this.Property + } + + [int] TestPropertyAccess1() + { + $this.Property1 = 2111 + return $this.Property1 + } + + [int] TestPropertyAccess2() + { + $this.Property2 = 3111 + return $this.Property2 + } + + [int] TestDynamicPropertyAccess() + { + $p = 'Property' + $this.$p = 1112 + return $this.$p + } + + [int] TestFieldAccess() + { + $this.Field = 11 + return $this.Field + } + + [int] TestDynamicFieldAccess() + { + $f = 'Field' + $this.$f = 12 + return $this.$f + } + + [int] TestMethodAccess() + { + return $this.Method() + } + + [int] TestDynamicMethodAccess() + { + $m = 'Method' + return $this.$m() + } + + [int] TestOverloadedMethodAccess1a() + { + return $this.OverloadedMethod1(42) + } + [int] TestOverloadedMethodAccess1b() + { + return $this.OverloadedMethod1("abc") + } + [int] TestOverloadedMethodAccess2a() + { + return $this.OverloadedMethod2(42) + } + [int] TestOverloadedMethodAccess2b() + { + return $this.OverloadedMethod2("abc") + } + [int] TestOverloadedMethodAccess3a() + { + return $this.OverloadedMethod3(42) + } + [int] TestOverloadedMethodAccess3b() + { + return $this.OverloadedMethod3("abc") + } +} + +class Derived2 : Base {} + +[Derived]::new() +[Derived]::new(20) +[Derived2]::new() +'@ + +Describe "Protected Member Access - w/ default ctor" -Tags "DRT" { + It "Method Access" { $derived1.TestMethodAccess() | Should Be 42 } + It "Dynamic Method Access" { $derived1.TestDynamicMethodAccess() | Should Be 42 } + It "Field Access" { $derived1.TestFieldAccess() | Should Be 11 } + It "Dynamic Field Access" { $derived1.TestDynamicFieldAccess() | Should Be 12 } + It "Property Access - protected get/protected set" { $derived1.TestPropertyAccess() | Should Be 1111 } + It "Property Access - public get/protected set " { $derived1.TestPropertyAccess1() | Should Be 2111 } + It "Property Access - protected get/public set" { $derived1.TestPropertyAccess2() | Should Be 3111 } + It "Dynamic Property Access" { $derived1.TestDynamicPropertyAccess() | Should Be 1112 } + + It "Method Access - overloaded 1a" { $derived1.TestOverloadedMethodAccess1a() | Should Be 84 } + It "Method Access - overloaded 1b" { $derived1.TestOverloadedMethodAccess1b() | Should Be 11 } + It "Method Access - overloaded 2a" { $derived1.TestOverloadedMethodAccess2a() | Should Be 84 } + It "Method Access - overloaded 2b" { $derived1.TestOverloadedMethodAccess2b() | Should Be 11 } + It "Method Access - overloaded 3a" { $derived1.TestOverloadedMethodAccess3a() | Should Be 84 } + It "Method Access - overloaded 3b" { $derived1.TestOverloadedMethodAccess3b() | Should Be 11 } + It "Implicit ctor calls protected ctor" { $derived3.OverloadedMethod2(42) | Should Be 84 } +} + +Describe "Protected Member Access - w/ non-default ctor" -Tags "DRT" { + It "Method Access" { $derived2.TestMethodAccess() | Should Be 52 } + It "Dynamic Method Access" { $derived2.TestDynamicMethodAccess() | Should Be 52 } + It "Field Access" { $derived2.TestFieldAccess() | Should Be 11 } + It "Dynamic Field Access" { $derived2.TestDynamicFieldAccess() | Should Be 12 } + It "Property Access - protected get/protected set" { $derived1.TestPropertyAccess() | Should Be 1111 } + It "Property Access - public get/protected set " { $derived1.TestPropertyAccess1() | Should Be 2111 } + It "Property Access - protected get/public set" { $derived1.TestPropertyAccess2() | Should Be 3111 } + It "Dynamic Property Access" { $derived2.TestDynamicPropertyAccess() | Should Be 1112 } + + It "Method Access - overloaded 1a" { $derived2.TestOverloadedMethodAccess1a() | Should Be 94 } + It "Method Access - overloaded 1b" { $derived2.TestOverloadedMethodAccess1b() | Should Be 21 } + It "Method Access - overloaded 2a" { $derived2.TestOverloadedMethodAccess2a() | Should Be 94 } + It "Method Access - overloaded 2b" { $derived2.TestOverloadedMethodAccess2b() | Should Be 21 } + It "Method Access - overloaded 3a" { $derived2.TestOverloadedMethodAccess3a() | Should Be 94 } + It "Method Access - overloaded 3b" { $derived2.TestOverloadedMethodAccess3b() | Should Be 21 } +} + +Describe "Protected Member Access - members not visible outside class" -Tags "DRT" { + Set-StrictMode -v 3 + It "Invalid protected field Get Access" { { $derived1.Field } | Should Throw } + It "Invalid protected property Get Access" { { $derived1.Property } | Should Throw } + It "Invalid protected field Set Access" { { $derived1.Field = 1 } | Should Throw } + It "Invalid protected property Set Access" { { $derived1.Property = 1 } | Should Throw } + + It "Invalid protected constructor Access" { { [Base]::new() } | Should Throw } + It "Invalid protected method Access" { { $derived1.Method() } | Should Throw } +} + diff --git a/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 new file mode 100644 index 000000000..943b054f5 --- /dev/null +++ b/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 @@ -0,0 +1,218 @@ +Describe 'Attributes Test' -Tags "innerloop", "DRT" { + + BeforeAll { + $dummyAttributesSource = @' +using System.Management.Automation; +namespace Dummy +{ + public class DoubleStringTransformationAttribute : ArgumentTransformationAttribute + { + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + { + string arg = inputData as string; + if (arg != null) + { + return arg + arg; + } + return inputData; + } + } + + public class AppendStringTransformationAttribute : ArgumentTransformationAttribute + { + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + { + string arg = inputData as string; + if (arg != null) + { + return arg + "___"; + } + return inputData; + } + } + + public class DoubleInt : ArgumentTransformationAttribute + { + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + { + int? arg = inputData as int?; + if (arg != null) + { + return arg + arg; + } + return inputData; + } + } +} +'@ + Add-Type -TypeDefinition $dummyAttributesSource -ReferencedAssemblies "System.Management.Automation" + } + + + + Context 'Property.Instance.ValidateSet.String' { + class C1 { [ValidateSet("Present", "Absent")][string]$Ensure } + # This call should not throw exception + [C1]::new().Ensure = "Present" + + It 'Error when ValidateSet should be ExceptionWhenSetting' { + try + { + [C1]::new().Ensure = "foo" + throw "Exception expected" + } + catch + { + $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' + } + } + } + + Context 'Property.Static.ValidateSet.String' { + class C1 { static [ValidateSet("Present", "Absent")][string]$Ensure } + # This call should not throw exception + [C1]::Ensure = "Present" + It 'Error when ValidateSet should be ExceptionWhenSetting'{ + try { + [C1]::Ensure = "foo" + throw "Exception expected" + } + catch { + $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' + } + } + } + + Context 'Property.Instance.ValidateRange.Int' { + class C1 { [ValidateRange(1, 10)][int]$f } + # This call should not throw exception + [C1]::new().f = 10 + [C1]::new().f = 1 + It 'Error when ValidateSet should be ExceptionWhenSetting'{ + try { + [C1]::new().f = 20 + throw "Exception expected" + } + catch { + $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' + } + } + } + + Context 'Property.Static.ValidateRange.Int' { + class C1 { static [ValidateRange(1, 10)][int]$f } + # This call should not throw exception + [C1]::f = 5 + It 'Error when ValidateSet should be ExceptionWhenSetting'{ + try { + [C1]::f = 20 + throw "Exception expected" + } + catch { + $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' + } + } + } + + Context 'Property.Static.ValidateSet.ImplicitObject' { + class C1 { static [ValidateSet("abc", 5)]$o } + # This call should not throw exception + [C1]::o = "abc" + [C1]::o = 5 + It 'Error when ValidateSet should be ExceptionWhenSetting'{ + try { + [C1]::o = 1 + throw "Exception expected" + } + catch { + $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' + } + } + } + + # + # We use [scriptblock]::Create() here to allow SuiteSetup add Dummy.Transformation type to + # the scope. Otherwise, we will need to have all classes for attributes in parse time. + # + # Invoke() returns an array, we need first element of it. + # + + Context 'Property.Instance.Transformation.ImplicitObject' { + $c = [scriptblock]::Create('class C1 { [Dummy.DoubleStringTransformation()]$arg }; [C1]::new()').Invoke()[0] + + It 'Implicitly Transform to 100' { + $c.arg = 100 + $c.arg | should be 100 + } + It 'Implicitly Transform to foo' { + $c.arg = "foo" + $c.arg | should be "foofoo" + } + } + + Context 'Property.Instance.Transformation.String' { + $c = [scriptblock]::Create('class C1 { [Dummy.DoubleStringTransformation()][string]$arg }; [C1]::new()').Invoke()[0] + It 'set to foo' { + $c.arg = "foo" + $c.arg | should be "foofoo" + } + } + + Context Property.Instance.Transformation.Int { + $c = [scriptblock]::Create('class C1 { [Dummy.DoubleInt()][int]$arg }; [C1]::new()').Invoke()[0] + It 'arg should be 200' { + $c.arg = 100 + $c.arg | should be 200 + } + It 'Set to string should fail with ExceptionWhenSetting' { + try { + $c.arg = "abc" + throw "Exception expected" + } + catch { + $_.FullyQualifiedErrorId | Should be 'ExceptionWhenSetting' + } + } + } + + Context Property.Instance.Transformation.Nullable { + $c = [scriptblock]::Create('class C1 { [Nullable[int]][Dummy.DoubleStringTransformation()]$arg }; [C1]::new()').Invoke()[0] + It 'arg should be 100' { + $c.arg = 100 + $c.arg | should be 100 + } + } + + Context Property.Instance.Transformation.Order { + $c = [scriptblock]::Create('class C1 { [Dummy.DoubleStringTransformation()][Dummy.AppendStringTransformation()]$arg }; [C1]::new()').Invoke()[0] + It 'arg should be 100' { + $c.arg = 100 + $c.arg | should be 100 + } + + It 'arg should be foo___foo___g' { + $c.arg = "foo" + $c.arg | should be "foo___foo___" + } + } +} + +Describe 'Type resolution with attributes' { + # There is kind of a collision between names + # System.Diagnostics.Tracing.EventSource + # System.Diagnostics.Tracing.EventSourceAttribute + # We need to make sure that we resolve type name to the right class at each usage + Context 'Name collision' { + + It 'Resolve System.Diagnostics.Tracing.EventSource to Attribute and to Type in the different contexts' { + [System.Diagnostics.Tracing.EventSource(Name = "MyPSEventSource")] + class MyEventSource : System.Diagnostics.Tracing.EventSource + { + [void] OnEvent([string]$Message) {} + } + + [MyEventSource]::new() | Should Not Be $null + + } + } +} \ No newline at end of file diff --git a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 new file mode 100644 index 000000000..157a7024e --- /dev/null +++ b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 @@ -0,0 +1,823 @@ +# +# Copyright (c) Microsoft Corporation, 2014 +# +Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 -force + +Describe 'Positive Parse Properties Tests' -Tags "innerloop", "DRT" { + It 'PositiveParsePropertiesTest' { + # Just a bunch of random basic things here + # This test doesn't need to check anything, if there are + # any parse errors, the entire suite will fail because the + # script will fail to parse. + + # No members + class C1 {} + + # Simple field + class C2 { $x; } + + # Simple typed field + class C3 { [int] $x; } + + # Multiple fields, one line, last w/o semicolon + class C4 { $x; $y } + + # Multiple fields, multiple lines + class C5 + { + $x + $y + } + + # Static field + class C6 { static $x; } + + # Static field w/ type - order doesn't matter + class C7a { static [hashtable] $x; } + class C7b { [hashtable] static $x; } + + # Field using type defined in this scope + class C8a { [C1] $c1; } + class C8b { [c1] $c1; } + + # Field referring to self type + class C9 { [C9] $c9; } + + # Hidden fields + class C10a { hidden $x } + class C10b { hidden [int] $x } + class C10c { hidden static $x; static hidden $y } + class C10d { hidden static [int] $x; static hidden [int] $y } + } + + It 'Positive Parse Methods Tests' { + # Just a bunch of random basic things here + # This test doesn't need to check anything, if there are + # any parse errors, the entire suite will fail because the + # script will fail to parse. + + # No members + class C1 {} + + # Simple method + class C2 { f() {} } + + # Simple method with return type + class C3 { [int] f() { return 1 } } + + # Multiple methods, one line + class C4 { f() {} f1() {} } + + # Multiple methods w/ overloads + class C5 + { + f1() {} + f1($a) {} + } + + # Static method + class C6 { static f() {} } + + # Static method w/ return type + class C7 { static [hashtable] f1() { return @{} } } + + # Method using return type defined in this scope + class C8a { [C1] f1() { return [C1]::new() } } + class C8b { [c1] f1() { return [c1]::new() } } + + # Hidden methods + class C10a { hidden F() { } } + class C10b { hidden [void] F() { } } + class C10c { hidden static F() { } static hidden G() { } } + class C10d { hidden static [void] F() { } static hidden [void] G() { } } + + # return analysis + class C11a { [int]foo() { try {throw "foo"} catch {throw $_} } } + class C11b { [int]foo() { try {throw "foo"} finally {}; try {} catch {} } } + class C11c { [int]foo() { try {throw "foo"} catch [ArgumentException] {throw $_} catch {throw $_} } } + class C11d { [int]foo() { try {if (1 -eq 2) { throw "1"} else {throw "2"}} finally {} } } + class C11e { [int]foo() { try {throw "foo"} catch [ArgumentException] {throw $_} catch {return 1} } } + class C11f { [int]foo() { try {} finally {throw "bar"} } } + class C11g { [int]foo() { do { return 1 } while ($false) } } + class C11h { [int]foo() { try {throw "foo"} finally {} } } + + # local variables + class C12a { static f() { [bigint]$foo = 42 } } + class C12b { [void] f() { [bigint]$foo = 42 } } + class C12c { [void] f() { [System.Management.Automation.Host.Rectangle]$foo = [System.Management.Automation.Host.Rectangle]::new(0, 0, 0, 0) } } + } + + context "Positive ParseMethods return type Test" { + # Method with return type of self + class C9 { [C9] f() { return [C9]::new() } } + $c9 = [C9]::new().f() + It "Expected a C9 returned" { $c9.GetType().Name | should be C9 } + class C9a { [C9a[]] f() { return [C9a]::new() } } + $c9a = [C9a]::new().f() + It "Expected a C9a[] returned" { $c9a.GetType().Name | should be C9a[] } + class C9b { [System.Collections.Generic.List[C9b]] f() { return [C9b]::new() } } + $c9b = [C9b]::new().f() + It "Expected a System.Collections.Generic.List[C9b] returned" { $c9b -is [System.Collections.Generic.List[C9b]] | should be $true } + } + + + It 'Positive ParseProperty Attributes Test' { + class C1a { [ValidateNotNull()][int]$x; } + class C1b + { + [ValidateNotNull()] + [int] + $x + } + class C1c + { + [ValidateNotNull()] + [int]$x + } + class C1d + { + [ValidateNotNull()][int] + $x + } + } + + It 'PositiveParseMethodAttributesTest' { + class C1a { [Obsolete()][int] f() { return 0 } } + class C1b + { + [Obsolete()] + [int] + f() { return 1 } + } + class C1c + { + [Obsolete("Message")] + [int] f() { return 0 } + } + class C1d + { + [Obsolete()][int] + f(){ return -1 } + } + } + + It 'Clas sMethod Reference ConstantVars' { + class C1 + { + [bool] f1() { return $true } + [bool] f2() { return $false } + [object] f3() { return $null } + } + } + + It 'Positive Parsing Of DscResource' { + [DscResource()] + class C1 + { + [DscProperty(Key)][string]$Key + [bool] Test() { return $false } + [C1] Get() { return $this } + Set() {} + } + + [DscResource()] + class C2 + { + [DscProperty(Key)][int]$Key = 1 + [bool] Test() { return $false } + [C2] Get() { return $this } + Set() {} + } + + [DscResource()] + class C4 + { + [DscProperty(Key)][byte]$Key=1 + C4() { } + C4($a) { } + [bool] Test() { return $false } + [C4] Get() { return $this } + Set() {} + } + + [DscResource()] + class C5 + { + [DscProperty(Key)][int]$Key = 1 + C5() { } + static C5() { } + C5($a) { } + [bool] Test() { return $false } + [C5] Get() { return $this } + Set() {} + } + } + + It 'allows some useful implicit variables inside methods' { + class C { + [void] m() + { + $LASTEXITCODE + $lastexitcode + '111' -match '1' + $Matches + $mAtches + $Error[0] + $error + $pwd + foreach ($i in 1..10) {$foreach} + switch ($i) + { + '1' { + $switch + } + } + } + } + } + + It 'allowes [ordered] attribute inside methods' { + class A + { + $h + A() + { + $this.h = [ordered] @{} + } + } + [A]::new().h.GetType().Name | Should Be 'OrderedDictionary' + } +} +Describe 'Negative Parsing Tests' -Tags "innerloop", "DRT" { + ShouldBeParseError 'class' MissingNameAfterKeyword 5 + ShouldBeParseError 'class foo' MissingTypeBody 9 + ShouldBeParseError 'class foo {' MissingEndCurlyBrace 10 + ShouldBeParseError 'class foo { [int] }' IncompleteMemberDefinition 17 + ShouldBeParseError 'class foo { $private: }' InvalidVariableReference 12 + ShouldBeParseError 'class foo { [int]$global: }' InvalidVariableReference 17 + ShouldBeParseError 'class foo { [Attr()] }' IncompleteMemberDefinition 20 + ShouldBeParseError 'class foo {} class foo {}' MemberAlreadyDefined 13 + ShouldBeParseError 'class foo { $x; $x; }' MemberAlreadyDefined 16 -SkipAndCheckRuntimeError + ShouldBeParseError 'class foo { [int][string]$x; }' TooManyTypes 17 + ShouldBeParseError 'class foo { [int][string]$x; }' TooManyTypes 17 + ShouldBeParseError 'class foo { static static $x; }' DuplicateQualifier 19 + ShouldBeParseError 'class foo { [zz]$x; }' TypeNotFound 13 + ShouldBeParseError 'class foo { [zz]f() { return 0 } }' TypeNotFound 13 + ShouldBeParseError 'class foo { f([zz]$x) {} }' TypeNotFound 15 + + ShouldBeParseError 'class C {} class C {}' MemberAlreadyDefined 11 + ShouldBeParseError 'class C { f(){} f(){} }' MemberAlreadyDefined 16 -SkipAndCheckRuntimeError + ShouldBeParseError 'class C { F(){} F($o){} [int] F($o) {return 1} }' MemberAlreadyDefined 24 -SkipAndCheckRuntimeError + ShouldBeParseError 'class C { f(){} f($a){} f(){} }' MemberAlreadyDefined 24 -SkipAndCheckRuntimeError + ShouldBeParseError 'class C { f([int]$a){} f([int]$b){} }' MemberAlreadyDefined 23 -SkipAndCheckRuntimeError + ShouldBeParseError 'class C { $x; [int]$x; }' MemberAlreadyDefined 14 -SkipAndCheckRuntimeError + ShouldBeParseError 'class C { static C($x) {} }' StaticConstructorCantHaveParameters 19 -SkipAndCheckRuntimeError + ShouldBeParseError 'class C { static C([int]$x = 100) {} }' StaticConstructorCantHaveParameters 19 -SkipAndCheckRuntimeError + + ShouldBeParseError 'class C {f(){ return 1} }' VoidMethodHasReturn 14 + ShouldBeParseError 'class C {[int] f(){ return } }' NonVoidMethodMissingReturnValue 20 + ShouldBeParseError 'class C {[int] f(){} }' MethodHasCodePathNotReturn 15 + ShouldBeParseError 'class C {f(){ $x=1; if($x -lt 2){ return } elseif($x -gt 0 ) {return 1} else{return 2} return 3 } }' @("VoidMethodHasReturn", "VoidMethodHasReturn", "VoidMethodHasReturn") @(62,77,87) + + ShouldBeParseError 'class foo { [int] bar() { $y = $z; return $y} }' VariableNotLocal 31 + ShouldBeParseError 'class foo { bar() { foreach ($zz in $yy) { } } }' VariableNotLocal 36 + ShouldBeParseError 'class foo { bar() { foreach ($zz in $global:yy) { $abc = $zzzzz } } }' VariableNotLocal 57 + ShouldBeParseError 'class foo { bar() { try { $zz = 42 } finally { } $zz } }' VariableNotLocal 49 + ShouldBeParseError 'class foo { bar() { try { $zz = 42 } catch { } $zz } }' VariableNotLocal 47 + ShouldBeParseError 'class foo { bar() { switch (@()) { default { $aa = 42 } } $aa } }' VariableNotLocal 58 + ShouldBeParseError 'class C { $x; static bar() { $this.x = 1 } }' NonStaticMemberAccessInStaticMember 29 + ShouldBeParseError 'class C { $x; static $y = $this.x }' NonStaticMemberAccessInStaticMember 26 + + ShouldBeParseError 'class C { $x; static bar() { $this.x = 1 } }' NonStaticMemberAccessInStaticMember 29 + ShouldBeParseError 'class C { $x; static $y = $this.x }' NonStaticMemberAccessInStaticMember 26 + ShouldBeParseError 'class C { $x; static bar() { $This.x = 1 } }' NonStaticMemberAccessInStaticMember 29 + ShouldBeParseError 'class C { $x; static $y = $This.x }' NonStaticMemberAccessInStaticMember 26 + + ShouldBeParseError 'class C { [void]foo() { try { throw "foo"} finally { return } } }' ControlLeavingFinally 53 + ShouldBeParseError 'class C { [int]foo() { return; return 1 } }' NonVoidMethodMissingReturnValue 23 + ShouldBeParseError 'class C { [int]foo() { try { throw "foo"} catch { } } }' MethodHasCodePathNotReturn 15 + ShouldBeParseError 'class C { [int]foo() { try { throw "foo"} catch [ArgumentException] {} catch {throw $_} } }' MethodHasCodePathNotReturn 15 + ShouldBeParseError 'class C { [int]foo() { try { throw "foo"} catch [ArgumentException] {return 1} catch {} } }' MethodHasCodePathNotReturn 15 + ShouldBeParseError 'class C { [int]foo() { while ($false) { return 1 } } }' MethodHasCodePathNotReturn 15 + ShouldBeParseError 'class C { [int]foo() { try { mkdir foo } finally { rm -rec foo } } }' MethodHasCodePathNotReturn 15 + ShouldBeParseError 'class C { [int]foo() { try { mkdir foo; return 1 } catch { } } }' MethodHasCodePathNotReturn 15 + ShouldBeParseError 'class C { [bool] Test() { if ($false) { return $true; } } }' MethodHasCodePathNotReturn 17 + + ShouldBeParseError 'class C { [int]$i; [void] foo() {$i = 10} }' MissingThis 33 + ShouldBeParseError 'class C { static [int]$i; [void] foo() {$i = 10} }' MissingTypeInStaticPropertyAssignment 40 + + ShouldBeParseError 'class C : B' MissingTypeBody 11 +} + +Describe 'Negative methods Tests' -Tags "innerloop", "DRT" { + ShouldBeParseError 'class foo { f() { param($x) } }' ParamBlockNotAllowedInMethod 18 + ShouldBeParseError 'class foo { f() { dynamicparam {} } }' NamedBlockNotAllowedInMethod 18 + ShouldBeParseError 'class foo { f() { begin {} } }' NamedBlockNotAllowedInMethod 18 + ShouldBeParseError 'class foo { f() { process {} } }' NamedBlockNotAllowedInMethod 18 + ShouldBeParseError 'class foo { f() { end {} } }' NamedBlockNotAllowedInMethod 18 + ShouldBeParseError 'class foo { f([Parameter()]$a) {} }' AttributeNotAllowedOnDeclaration 14 + ShouldBeParseError 'class foo { [int] foo() { return 1 }}' ConstructorCantHaveReturnType 12 + ShouldBeParseError 'class foo { [void] bar($a, [string][int]$b, $c) {} }' MultipleTypeConstraintsOnMethodParam 35 +} + +Describe 'Negative Assignment Tests' -Tags "innerloop", "DRT" { + ShouldBeParseError 'class foo { [string ]$path; f() { $path="" } }' MissingThis 34 + ShouldBeParseError 'class foo { [string ]$path; f() { [string] $path="" } }' MissingThis 43 + ShouldBeParseError 'class foo { [string ]$path; f() { [int] [string] $path="" } }' MissingThis 49 +} + +Describe 'Negative Assignment Tests' -Tags "innerloop", "DRT" { + ShouldBeParseError '[DscResource()]class C { [bool] Test() { return $false } [C] Get() { return $this } Set() {} }' DscResourceMissingKeyProperty 0 + + # Test method + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [C] Get() { return $this } Set() {} }' DscResourceMissingTestMethod 0 + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [C] Get() { return $this } Set() {} Test() { } }' DscResourceMissingTestMethod 0 + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [C] Get() { return $this } Set() {} [int] Test() { return 1 } }' DscResourceMissingTestMethod 0 + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [C] Get() { return $this } Set() {} [bool] Test($a) { return $false } }' DscResourceMissingTestMethod 0 + + # Get method + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [bool] Test() { return $false } Set() {} }' DscResourceMissingGetMethod 0 + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [bool] Test() { return $false } Set() {} Get() { } }' DscResourceInvalidGetMethod 98 + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [bool] Test() { return $false } Set() {} [int] Get() { return 1 } }' DscResourceInvalidGetMethod 98 + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [bool] Test() { return $false } Set() {} [C] Get($a) { return $this } }' DscResourceMissingGetMethod 0 + + # Set method + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [bool] Test() { return $false } [C] Get() { return $this } }' DscResourceMissingSetMethod 0 + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [bool] Test() { return $false } [C] Get() { return $this } [int] Set() { return 1 } }' DscResourceMissingSetMethod 0 + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [bool] Test() { return $false } [C] Get() { return $this } Set($a) { } }' DscResourceMissingSetMethod 0 + + # Default ctor + ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [bool] Test() { return $false } [C] Get() { return $this } Set() {} C($a) { } }' DscResourceMissingDefaultConstructor 0 +} + +Describe 'Negative DscResources Tests' -Tags "innerloop", "DRT" { + # Usage errors + ShouldBeParseError '[Flags()]class C{}' AttributeNotAllowedOnDeclaration 0 + ShouldBeParseError 'class C { [Flags()]$field; }' AttributeNotAllowedOnDeclaration 10 + ShouldBeParseError 'class C { [Flags()]foo(){} }' AttributeNotAllowedOnDeclaration 10 + + # Errors related to construction of the attribute + ShouldBeParseError '[UnknownAttr()]class C{}' CustomAttributeTypeNotFound 1 + ShouldBeParseError '[System.Management.Automation.Cmdlet()]class C{}' MethodCountCouldNotFindBest 0 -SkipAndCheckRuntimeError + ShouldBeParseError '[System.Management.Automation.Cmdlet("zz")]class C{}' MethodCountCouldNotFindBest 0 -SkipAndCheckRuntimeError + ShouldBeParseError '[System.Management.Automation.Cmdlet("Get", "Thing", Prop=1)]class C{}' PropertyNotFoundForAttribute 53 + ShouldBeParseError '[System.Management.Automation.Cmdlet("Get", "Thing", ConfirmImpact="foo")]class C{}' CannotConvertValue 67 -SkipAndCheckRuntimeError + ShouldBeParseError '[System.Management.Automation.Cmdlet("Get", "Thing", NounName="foo")]class C{}' ReadOnlyProperty 53 + ShouldBeParseError '[System.Management.Automation.Cmdlet("Get", "Thing", ConfirmImpact=$zed)]class C{}' ParameterAttributeArgumentNeedsToBeConstant 67 + ShouldBeParseError 'class C{ [ValidateScript({})]$p; }' ParameterAttributeArgumentNeedsToBeConstant 25 +} + +Describe 'Negative ClassAttributes Tests' -Tags "innerloop", "DRT" { + + [System.Management.Automation.Cmdlet("Get", "Thing")]class C{} + $t = [C].GetCustomAttributes($false) + + It "Should have one attribute" {$t.Count | should be 1} + It "Should have instance of CmdletAttribute" {$t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } + + [System.Management.Automation.CmdletAttribute]$c = $t[0] + It "Verb should be Get" {$c.VerbName | should be 'Get'} + It "Noun should be Thing" {$c.NounName | should be 'Thing'} + + [System.Management.Automation.Cmdlet("Get", "Thing", ConfirmImpact = 'High', SupportsPaging = $true)]class C2{} + $t = [C2].GetCustomAttributes($false) + It "Should have one attribute" { $t.Count | should be 1 } + It "Should have instance of CmdletAttribute" { $t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } + [System.Management.Automation.CmdletAttribute]$c = $t[0] + It "Verb should be Get" {$c.VerbName | should be 'Get'} + It "Noun should be Thing" {$c.NounName | should be 'Thing'} + + It "ConfirmImpact should be high" { $c.ConfirmImpact | should be 'High' } + It "SupportsPaging should be `$true" { $c.SupportsPaging | should be $true } +} + + +Describe 'Property Attributes Test' -Tags "innerloop", "DRT" { + class C { [ValidateSet('a', 'b')]$p; } + + $t = [C].GetProperty('p').GetCustomAttributes($false) + It "Should have one attribute" { $t.Count | should be 1 } + [ValidateSet]$v = $t[0] + It "Should have 2 valid values" { $v.ValidValues.Count | should be 2 } + It "first value should be a" { $v.ValidValues[0] | should be 'a' } + It "second value should be b" { $v.ValidValues[1] -eq 'b' } + } + +Describe 'Method Attributes Test' -Tags "innerloop", "DRT" { + class C { [Obsolete("aaa")][int]f() { return 1 } } + + $t = [C].GetMethod('f').GetCustomAttributes($false) + It "Should have one attribute" {$t.Count | should be 1 } + It "Attribute type should be ObsoleteAttribute" { $t[0].GetType().FullName | should be System.ObsoleteAttribute } + } + +Describe 'Positive SelfClass Type As Parameter Test' -Tags "innerloop", "DRT" { + class Point + { + Point($x, $y) { $this.x = $x; $this.y = $y } + Point() {} + + [int] $x = 0 + [int] $y = 0 + Add([Point] $val) { $this.x += $val.x; $this.y += $val.y; } + + Print() { Write-Host "[`$x=$($this.x) `$y=$($this.y)]" } + Set($x, $y) { $this.x = $x; $this.y = $y } + } + It "[Point]::Add works" { + $point = [Point]::new(100,200) + $point2 = [Point]::new(1,2) + $point.Add($point2) + + $point.x | should be 101 + $point.y | should be 202 + } + + It "[Point]::Add works" { + $point = New-Object Point 100,200 + $point2 = New-Object Point 1,2 + $point.Add($point2) + + $point.x | should be 101 + $point.y | should be 202 + } + } + +Describe 'PositiveReturnSelfClassTypeFromMemberFunction Test' -Tags "innerloop", "DRT" { + class ReturnObjectFromMemberFunctionTest + { + [ReturnObjectFromMemberFunctionTest] CreateInstance() + { + return [ReturnObjectFromMemberFunctionTest]::new() + } + [string] SayHello() + { + return "Hello1" + } + } + $f = [ReturnObjectFromMemberFunctionTest]::new() + $z = $f.CreateInstance() # Line 13 + It "CreateInstance works" { $z.SayHello() | should be 'Hello1' } + } + +Describe 'TestMultipleArguments Test' -Tags "innerloop", "DRT" { + for ($i = 0; $i -lt 16; $i++) + { + $properties = $(for ($j = 0; $j -le $i; $j++) { + " [int]`$Prop$j" + }) -join "`n" + + $methodParameters = $(for ($j = 0; $j -le $i; $j++) { + "[int]`$arg$j" + }) -join ", " + + $ctorAssignments = $(for ($j = 0; $j -le $i; $j++) { + " `$this.Prop$j = `$arg$j" + }) -join "`n" + + $methodReturnValue = $(for ($j = 0; $j -le $i; $j++) { + "`$arg$j" + }) -join " + " + + $methodArguments = $(for ($j = 0; $j -le $i; $j++) { + $j + }) -join ", " + + $addUpProperties = $(for ($j = 0; $j -le $i; $j++) { + "`$inst.`Prop$j" + }) -join " + " + + $expectedTotal = (0..$i | Measure-Object -Sum).Sum + + $class = @" + class Foo + { +$properties + + Foo($methodParameters) + { +$ctorAssignments + } + + [int] DoSomething($methodParameters) + { + return $methodReturnValue + } + } + + `$inst = [Foo]::new($methodArguments) + `$sum = $addUpProperties + It "ExpectedTotal" { `$sum | should be $expectedTotal } + It "ExpectedTotal"{ `$inst.DoSomething($methodArguments) | should be $expectedTotal } +"@ + + Invoke-Expression $class + } + } +Describe 'Scopes Test' -Tags "innerloop", "DRT" { + class C1 + { + static C1() { + $global:foo = $script:foo + } + C1() { + $script:bar = $global:foo + } + static [int] f1() { + return $script:bar + $global:bar + } + [int] f2() { + return $script:bar + $global:bar + } + } + } + +Describe 'Check PS Class Assembly Test' -Tags "innerloop", "DRT" { + class C1 {} + $assem = [C1].Assembly + $attrs = @($assem.GetCustomAttributes($true)) + $expectedAttr = @($attrs | ? { $_ -is [System.Management.Automation.DynamicClassImplementationAssemblyAttribute] }) + It "Expected a DynamicClassImplementationAssembly attribute" { $expectedAttr.Length | should be 1} + } + +Describe 'ScriptScopeAccessFromClassMethod' -Tags "innerloop", "DRT" { + Import-Module "$PSScriptRoot\MSFT_778492.psm1" + try + { + $c = Get-MSFT_778492 + It "Method should have found variable in module scope" { $c.F() | should be 'MSFT_778492 script scope'} + } + finally + { + Remove-Module MSFT_778492 + } + } + +Describe 'Hidden Members Test ' -Tags "innerloop", "DRT" { + class C1 + { + [int]$visibleX + [int]$visibleY + hidden [int]$hiddenZ + } + + # Create an instance + $instance = [C1]@{ visibleX = 10; visibleY = 12; hiddenZ = 42 } + + It "Access hidden property should still work" { $instance.hiddenZ | should be 42 } + + + # Formatting should not include hidden members by default + $tableOutput = $instance | Format-Table -HideTableHeaders -AutoSize | Out-String + It "Table formatting should not have included hidden member hiddenZ - should contain 10" { $tableOutput.Contains(10) | should be $true} + It "Table formatting should not have included hidden member hiddenZ- should contain 12" { $tableOutput.Contains(12) | should be $true} + It "Table formatting should not have included hidden member hiddenZ - should not contain 42" { $tableOutput.Contains(42) | should be $false} + + # Get-Member should not include hidden members by default + $member = $instance | Get-Member hiddenZ + it "Get-Member should not find hidden member w/o -Force" { $member | should be $null } + + # Get-Member should include hidden members with -Force + $member = $instance | Get-Member hiddenZ -Force + It "Get-Member should find hidden member w/ -Force" { $member | should not be $null } + + # Tab completion should not return a hidden member + $line = 'class C2 { hidden [int]$hiddenZ } [C2]::new().h' + $completions = [System.Management.Automation.CommandCompletion]::CompleteInput($line, $line.Length, $null) + It "Tab completion should not return a hidden member" { $completions.CompletionMatches.Count | should be 0 } + } + +Describe 'BaseMethodCall Test ' -Tags "innerloop", "DRT" { + It "Derived class method call" {"abc".ToString() | should be "abc" } + # call [object] ToString() method as a base class method. + It "Base class method call" {([object]"abc").ToString() | should be "System.String" } + } + +Describe 'Scoped Types Test' -Tags "innerloop", "DRT" { + class C1 { [string] GetContext() { return "Test scope" } } + + filter f1 + { + class C1 { [string] GetContext() { return "f1 scope" } } + + return [C1]::new().GetContext() + } + + filter f2 + { + class C1 { [string] GetContext() { return "f2 scope" } } + + return (new-object C1).GetContext() + } + + It "New-Object at test scope" { (new-object C1).GetContext() | should be "Test scope" } + It "[C1]::new() at test scope" { [C1]::new().GetContext() | should be "Test scope" } + + It "[C1]::new() in nested scope" { (f1) | should be "f1 scope" } + It "'new-object C1' in nested scope" { (f2) | should be "f2 scope" } + + + It "[C1]::new() in nested scope (in pipeline)" { (1 | f1 | f2 | f1) | should be "f1 scope" } + It "'new-object C1' in nested scope (in pipeline)" { (1 | f2 | f1 | f2) | should be "f2 scope" } + } + +Describe 'ParameterOfClassTypeInModule Test' -Tags "innerloop", "DRT" { + try + { + $sb = [scriptblock]::Create(@' +enum EE {one = 1} +function test-it([EE]$ee){$ee} +'@) + $mod = New-Module $sb -Name MSFT_2081529 | Import-Module + $result = test-it -ee one + It "Parameter of class/enum type defined in module should work" { $result | should be 1 } + } + finally + { + Remove-Module -ea ignore MSFT_2081529 + } + } + +Describe 'Type building' -Tags "DRT" { + It 'should build the type only once for scriptblock' { + $a = $null + 1..10 | % { + class C {} + if ($a) { + $a -eq [C] | Should Be $true + } + $a = [C] + } + } + + It 'should create a new type every time scriptblock executed?' -Pending { + $sb = [scriptblock]::Create('class A {static [int] $a }; [A]::new()') + 1..2 | % { + $a = $sb.Invoke()[0] + ++$a::a | Should Be 1 + ++$a::a | Should Be 2 + } + } +} + +Describe 'RuntimeType created for TypeDefinitionAst' { + + It 'can make cast to the right RuntimeType in two different contexts' { + + $ssfe = [System.Management.Automation.Runspaces.SessionStateFunctionEntry]::new("foo", @' +class Base +{ + [int] foo() { return 100 } +} + +class Derived : Base +{ + [int] foo() { return 2 * ([Base]$this).foo() } +} + +[Derived]::new().foo() +'@) + + $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss.Commands.Add($ssfe) + + $ps = [powershell]::Create($iss) + $ps.AddCommand("foo").Invoke() | Should be 200 + $ps.Streams.Error | Should Be $null + + $ps1 = [powershell]::Create($iss) + $ps1.AddCommand("foo").Invoke() | Should be 200 + $ps1.Streams.Error | Should Be $null + + $ps.Commands.Clear() + $ps.Streams.Error.Clear() + $ps.AddScript(". foo").Invoke() | Should be 200 + $ps.Streams.Error | Should Be $null + } +} + +Describe 'TypeTable lookups' { + + Context 'Call methods from a different thread' { + $b = [powershell]::Create().AddScript( +@' +class A {} +class B +{ + [object] getA1() { return New-Object A } + [object] getA2() { return [A]::new() } +} + +[B]::new() + +'@).Invoke()[0] + + It 'can do type lookup by name' { + $b.getA1() | Should Be 'A' + } + + It 'can do type lookup by [type]' { + $b.getA2() | Should Be 'A' + } + } +} + +Describe 'Protected method access' { + + Add-Type @' +namespace Foo +{ + public class Bar + { + protected int x {get; set;} + } +} +'@ + + It 'doesn''t allow protected methods access outside of inheritance chain' { + $a = [scriptblock]::Create(@' +class A +{ + SetX([Foo.Bar]$bar, [int]$x) + { + $bar.x = $x + } + + [int] GetX([Foo.Bar]$bar) + { + Set-StrictMode -Version latest + return $bar.x + } +} +[A]::new() + +'@).Invoke() + $bar = [Foo.Bar]::new() + $throwCount = 0 + try { + $a.SetX($bar, 42) + } catch { + $_.FullyQualifiedErrorId | Should Be PropertyAssignmentException + $throwCount++ + } + try { + $a.GetX($bar) + } catch { + $_.FullyQualifiedErrorId | Should Be PropertyNotFoundStrict + $throwCount++ + } + $throwCount | Should Be 2 + } + + It 'can call protected methods sequentially from two different contexts' { + $ssfe = [System.Management.Automation.Runspaces.SessionStateFunctionEntry]::new("foo", @' +class A : Foo.Bar +{ + SetX([int]$x) + { + $this.x = $x + } + + [int] GetX() + { + return $this.x + } +} +return [A]::new() +'@) + + $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss.Commands.Add($ssfe) + + $ps = [powershell]::Create($iss) + $a = $ps.AddCommand("foo").Invoke()[0] + $ps.Streams.Error | Should Be $null + + $ps1 = [powershell]::Create($iss) + $a1 = $ps1.AddCommand("foo").Invoke()[0] + $ps1.Streams.Error | Should Be $null + + $a.SetX(101) + $a1.SetX(103) + + $a.GetX() | Should Be 101 + $a1.GetX() | Should Be 103 + } +} + +Describe 'variable analysis' { + It 'can specify type construct on the local variables' { + class A { [string] getFoo() { return 'foo'} } + + class B + { + static [A] getA () + { + [A] $var = [A]::new() + return $var + } + } + + [B]::getA().getFoo() | Should Be 'foo' + } +} diff --git a/test/powershell/Language/Classes/Scripting.Classes.Break.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Break.Tests.ps1 new file mode 100644 index 000000000..d9e1f8f74 --- /dev/null +++ b/test/powershell/Language/Classes/Scripting.Classes.Break.Tests.ps1 @@ -0,0 +1,145 @@ +Describe 'Break statements with classes' -Tags "DRT" { + + function Get-Errors([string]$sourceCode) { + $tokens = $null + $errors = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput($sourceCode, [ref] $tokens, [ref] $errors) + return $errors + } + + Context 'break is inside a class method' { + It 'reports parse error for break on non-existing label' { + $errors = Get-Errors @' +class A +{ + static [int] foo() + { + while (1) { break some_label } + return 1 + } +} +'@ + $errors.Count | Should be 1 + $errors[0].ErrorId | Should be 'LabelNotFound' + } + + It 'reports parse error for break outside of loop' { + $errors = Get-Errors @' +class A +{ + static [int] foo() + { + break some_label + return 1 + } +} +'@ + $errors.Count | Should be 1 + $errors[0].ErrorId | Should be 'LabelNotFound' + } + + It 'work fine, when break is legite' { + class C + { + static [int] foo() + { + foreach ($i in 101..102) { + break + } + return $i + } + } + [C]::foo() | Should be 101 + } + } + + Context 'continue inside a class method' { + It 'reports parse error for continue on non-existing label' { + $errors = Get-Errors @' +class A +{ + static [int] foo() + { + while (1) { continue some_label } + return 1 + } +} +'@ + $errors.Count | Should be 1 + $errors[0].ErrorId | Should be 'LabelNotFound' + } + } + + Context 'break is in called function' { + It 'doesn''t terminate caller method' -Skip { + + function ImBreak() { + break + } + + class C + { + static [int] getInt() + { + ImBreak + return 123 + } + } + + $canary = $false + try { + [C]::getInt() | Should Be 123 + $canary = $true + } finally { + $canary | Should be $true + } + } + + It 'doesn''t allow goto outside of function with break' -Skip { + + function ImBreak() { + break label1 + } + + class C + { + static [int] getInt() + { + $count = 123 + :label1 + foreach ($i in 0..3) { + foreach ($i in 0..3) { + ImBreak + $count++ + } + } + return $count + } + } + + $canary = $false + try { + [C]::getInt() | Should Be (123 + 4*4) + $canary = $true + } finally { + $canary | Should be $true + } + } + } + + Context 'no classes involved' { + + It 'doesn''t report parse error for non-existing label' { + $errors = Get-Errors @' +function foo() +{ + while (1) { break some_label } + while (1) { continue another_label } + return 1 +} +'@ + $errors.Count | Should be 0 + } + + } +} diff --git a/test/powershell/Language/Classes/Scripting.Classes.Exceptions.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Exceptions.Tests.ps1 new file mode 100644 index 000000000..539e90d18 --- /dev/null +++ b/test/powershell/Language/Classes/Scripting.Classes.Exceptions.Tests.ps1 @@ -0,0 +1,349 @@ +Describe 'Exceptions flow for classes' -Tags "DRT" { + + $canaryHashtable = @{} + + $iss = [initialsessionstate]::CreateDefault() + $iss.Variables.Add([System.Management.Automation.Runspaces.SessionStateVariableEntry]::new('canaryHashtable', $canaryHashtable, $null)) + $iss.Commands.Add([System.Management.Automation.Runspaces.SessionStateFunctionEntry]::new('Get-Canary', '$canaryHashtable')) + $ps = [powershell]::Create($iss) + + BeforeEach { + $canaryHashtable.Clear() + $ps.Commands.Clear() + } + + Context 'All calls are inside classes' { + + It 'does not execute statements after instance method with exception' { + + # Put try-catch outside to avoid try-catch logic altering analysis + try { + + $ps.AddScript( @' +class C +{ + [void] m1() + { + $canaryHashtable = Get-Canary + $canaryHashtable['canary'] = 42 + $this.ImThrow() + $canaryHashtable['canary'] = 100 + } + + [void] ImThrow() + { + throw 'I told you' + } +} +[C]::new().m1() +'@).Invoke() + + } catch {} + + $canaryHashtable['canary'] | Should Be 42 + } + + It 'does not execute statements after static method with exception' { + + # Put try-catch outside to avoid try-catch logic altering analysis + try { + + $ps.AddScript( @' +class C +{ + static [void] s1() + { + $canaryHashtable = Get-Canary + $canaryHashtable['canary'] = 43 + [C]::ImThrow() + $canaryHashtable['canary'] = 100 + } + + static [void] ImThrow() + { + 1 / 0 + } +} +[C]::s1() +'@).Invoke() + + } catch {} + + $canaryHashtable['canary'] | Should Be 43 + } + + It 'does not execute statements after instance method with exception and deep stack' { + + # Put try-catch outside to avoid try-catch logic altering analysis + try { + + $ps.AddScript( @' +class C +{ + [void] m1() + { + $canaryHashtable = Get-Canary + $canaryHashtable['canary'] = 1 + $this.m2() + $canaryHashtable['canary'] = -6101 + } + + [void] m2() + { + $canaryHashtable = Get-Canary + $canaryHashtable['canary'] += 10 + $this.m3() + $canaryHashtable['canary'] = -6102 + } + + [void] m3() + { + $canaryHashtable = Get-Canary + $canaryHashtable['canary'] += 100 + $this.m4() + $canaryHashtable['canary'] = -6103 + } + + [void] m4() + { + $canaryHashtable = Get-Canary + $canaryHashtable['canary'] += 1000 + $this.ImThrow() + $canaryHashtable['canary'] = -6104 + } + + [void] ImThrow() + { + $canaryHashtable = Get-Canary + $canaryHashtable['canary'] += 10000 + + 1 / 0 + } +} +[C]::new().m1() +'@).Invoke() + + } catch {} + + $canaryHashtable['canary'] | Should Be 11111 + } + } + + Context 'Class method call PS function' { + + $body = @' +class C +{ + [void] m1() + { + m2 + } + + static [void] s1() + { + s2 + } +} + + +function m2() +{ + $canary = Get-Canary + $canary['canaryM'] = 45 + ImThrow + $canary['canaryM'] = 100 +} + +function s2() +{ + $canary = Get-Canary + $canary['canaryS'] = 46 + CallImThrow + $canary['canaryS'] = 100 +} + +function CallImThrow() +{ + ImThrow +} + +function ImThrow() +{ + 1 / 0 +} + +'@ + + It 'does not execute statements after function with exception called from instance method' { + + # Put try-catch outside to avoid try-catch logic altering analysis + try { + + $ps.AddScript($body).Invoke() + $ps.AddScript('$c = [C]::new(); $c.m1()').Invoke() + + } catch {} + + $canaryHashtable['canaryM'] | Should Be 45 + } + + It 'does not execute statements after function with exception called from static method' { + + # Put try-catch outside to avoid try-catch logic altering analysis + try { + + $ps.AddScript($body).Invoke() + $ps.AddScript('[C]::s1()').Invoke() + + } catch {} + + $canaryHashtable['canaryS'] | Should Be 46 + } + + } + + Context "No class is involved" { + It "functions calls continue execution by default" { + + try { + + $ps.AddScript( @' + +$canaryHashtable = Get-Canary +function foo() { 1 / 0; $canaryHashtable['canary'] += 10 } +$canaryHashtable['canary'] = 1 +foo +$canaryHashtable['canary'] += 100 + +'@).Invoke() + + } catch {} + + $canaryHashtable['canary'] | Should Be 111 + } + } +} + +Describe "Exception error position" -Tags "DRT" { + class MSFT_3090412 + { + static f1() { [MSFT_3090412]::bar = 42 } + static f2() { throw "an error in f2" } + static f3() { "".Substring(0, 10) } + static f4() { dir nosuchfile -ea Stop } + } + + It "Setting a property that doesn't exist" { + try { + [MSFT_3090412]::f1() + throw "f1 should have thrown" + } catch { + $_.InvocationInfo.Line | Should Match ([regex]::Escape('[MSFT_3090412]::bar = 42')) + } + } + + It "Throwing an exception" { + try { + [MSFT_3090412]::f2() + throw "f2 should have thrown" + } catch { + $_.InvocationInfo.Line | Should Match ([regex]::Escape('throw "an error in f2"')) + } + } + + It "Calling a .Net method that throws" { + try { + [MSFT_3090412]::f3() + throw "f3 should have thrown" + } catch { + $_.InvocationInfo.Line | Should Match ([regex]::Escape('"".Substring(0, 10)')) + } + } + + It "Terminating error" { + try { + [MSFT_3090412]::f4() + throw "f4 should have thrown" + } catch { + $_.InvocationInfo.Line | Should Match ([regex]::Escape('dir nosuchfile -ea Stop')) + } + } +} + +Describe "Exception from initializer" -Tags "DRT" { + class MSFT_6397334a + { + [int]$a = "zz" + MSFT_6397334a() {} + } + + class MSFT_6397334b + { + [int]$a = "zz" + } + + class MSFT_6397334c + { + static [int]$a = "zz" + static MSFT_6397334a() {} + } + + class MSFT_6397334d + { + static [int]$a = "zz" + } + + It "instance member w/ ctor" { + try { + [MSFT_6397334a]::new() + throw "[MSFT_6397334a]::new() should have thrown" + } + catch + { + $e = $_ + $e.FullyQualifiedErrorId | Should Be InvalidCastFromStringToInteger + $e.InvocationInfo.Line | Should Match 'a = "zz"' + } + } + + It "instance member w/o ctor" { + try { + [MSFT_6397334b]::new() + throw "[MSFT_6397334b]::new() should have thrown" + } + catch + { + $e = $_ + $e.FullyQualifiedErrorId | Should Be InvalidCastFromStringToInteger + $e.InvocationInfo.Line | Should Match 'a = "zz"' + } + } + + It "static member w/ ctor" { + try { + $null = [MSFT_6397334c]::a + throw "[MSFT_6397334c]::a should have thrown" + } + catch + { + $_.Exception.GetType().FullName | Should Be System.TypeInitializationException + $e = $_.Exception.InnerException.InnerException.ErrorRecord + $e.FullyQualifiedErrorId | Should Be InvalidCastFromStringToInteger + $e.InvocationInfo.Line | Should Match 'a = "zz"' + } + } + + It "static member w/o ctor" { + try { + $null = [MSFT_6397334d]::a + throw "[MSFT_6397334d]::a should have thrown" + } + catch + { + $_.Exception.GetType().FullName | Should Be System.TypeInitializationException + $e = $_.Exception.InnerException.InnerException.ErrorRecord + $e.FullyQualifiedErrorId | Should Be InvalidCastFromStringToInteger + $e.InvocationInfo.Line | Should Match 'a = "zz"' + } + } +} diff --git a/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 new file mode 100644 index 000000000..45b4efdf4 --- /dev/null +++ b/test/powershell/Language/Classes/Scripting.Classes.MiscOps.Tests.ps1 @@ -0,0 +1,46 @@ +Describe 'Misc Test' -Tags "innerloop", "DRT" { + + Context 'Where' { + class C1 { + [int[]] $Wheels = @(1,2,3); + [string] Foo() { + return (1..10).Where({ $PSItem -in $this.Wheels; }) -join ';' + } + + [string] Bar() + { + return (1..10 | Where { $PSItem -in $this.Wheels; }) -join ';' + } + } + It 'Invoke Where' { + [C1]::new().Foo() | should be "1;2;3" + } + It 'Pipe to where' { + [C1]::new().Bar() | should be "1;2;3" + } + } + + Context 'ForEach' { + class C1 { + [int[]] $Wheels = @(1,2,3); + [string] Foo() { + $ret="" + Foreach($PSItem in $this.Wheels) { $ret +="$PSItem;"} + return $ret + } + + [string] Bar() + { + $ret = "" + $this.Wheels | foreach { $ret += "$_;" } + return $ret + } + } + It 'Invoke Foreach' { + [C1]::new().Foo() | should be "1;2;3;" + } + It 'Pipe to Foreach' { + [C1]::new().Bar() | should be "1;2;3;" + } + } +} \ No newline at end of file diff --git a/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 new file mode 100644 index 000000000..a5310275b --- /dev/null +++ b/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 @@ -0,0 +1,74 @@ +Describe 'PSModuleInfo.GetExportedTypeDefinitions()' { + It "doesn't throw for any module" { + $discard = Get-Module -ListAvailable | % { $_.GetExportedTypeDefinitions() } + $true | Should Be $true # we only verify that we didn't throw. This line contains a dummy Should to make pester happy. + } +} + +Describe 'use of a module from two runspaces' { + function New-TestModule { + param( + [string]$Name, + [string]$Content + ) + + mkdir -Force "TestDrive:\$Name" > $null + $manifestParams = @{ + Path = "TestDrive:\$Name\$Name.psd1" + } + + if ($Content) { + Set-Content -Path TestDrive:\$Name\$Name.psm1 -Value $Content + $manifestParams['RootModule'] = "$Name.psm1" + } + + New-ModuleManifest @manifestParams + + $resolvedTestDrivePath = Split-Path ((ls TestDrive:\)[0].FullName) + if (-not ($env:PSModulePath -like "*$resolvedTestDrivePath*")) { + $env:PSModulePath += ";$resolvedTestDrivePath" + } + } + + $originalPSModulePath = $env:PSModulePath + try { + + New-TestModule -Name 'Random' -Content @' +$script:random = Get-Random +class RandomWrapper +{ + [int] getRandom() + { + return $script:random + } +} +'@ + + It 'use different sessionStates for different modules' { + $ps = 1..2 | % { $p = [powershell]::Create().AddScript(@' +Import-Module Random +'@) + $p.Invoke() > $null + $p + } + $res = 1..2 | % { + 0..1 | % { + $ps[$_].Commands.Clear() + # The idea: instance created inside the context, in one runspace. + # Method is called on instance in the different runspace, but it should know about the origin. + $w = $ps[$_].AddScript('& (Get-Module Random) { [RandomWrapper]::new() }').Invoke()[0] + $w.getRandom() + } + } + + $res.Count | Should Be 4 + $res[0] | Should Not Be $res[1] + $res[0] | Should Be $res[2] + $res[1] | Should Be $res[3] + } + + } finally { + $env:PSModulePath = $originalPSModulePath + } + +} \ No newline at end of file diff --git a/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 new file mode 100644 index 000000000..8b0be4247 --- /dev/null +++ b/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 @@ -0,0 +1,126 @@ +Describe 'NestedModules' -Tags "DRT" { + + Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 + + function New-TestModule { + param( + [string]$Name, + [string]$Content, + [string[]]$NestedContents + ) + + mkdir -Force "TestDrive:\$Name" > $null + $manifestParams = @{ + Path = "TestDrive:\$Name\$Name.psd1" + } + + if ($Content) { + Set-Content -Path TestDrive:\$Name\$Name.psm1 -Value $Content + $manifestParams['RootModule'] = "$Name.psm1" + } + + if ($NestedContents) { + $manifestParams['NestedModules'] = 1..$NestedContents.Count | % { + $null = mkdir TestDrive:\$Name\Nested$_ + $null = Set-Content -Path TestDrive:\$Name\Nested$_\Nested$_.psm1 -Value $NestedContents[$_ - 1] + "Nested$_" + } + } + + New-ModuleManifest @manifestParams + + $resolvedTestDrivePath = Split-Path ((ls TestDrive:\)[0].FullName) + if (-not ($env:PSModulePath -like "*$resolvedTestDrivePath*")) { + $env:PSModulePath += ";$resolvedTestDrivePath" + } + } + + $originalPSModulePath = $env:PSModulePath + + try { + + + # Create modules in TestDrive:\ + New-TestModule -Name NoRoot -NestedContents @( + 'class A { [string] foo() { return "A1"} }', + 'class A { [string] foo() { return "A2"} }' + ) + + New-TestModule -Name WithRoot -NestedContents @( + 'class A { [string] foo() { return "A1"} }', + 'class A { [string] foo() { return "A2"} }' + ) -Content 'class A { [string] foo() { return "A0"} }' + + New-TestModule -Name ABC -NestedContents @( + 'class A { [string] foo() { return "A"} }', + 'class B { [string] foo() { return "B"} }' + ) -Content 'class C { [string] foo() { return "C"} }' + + It 'Get-Module is able to find types' { + $module = Get-Module NoRoot -ListAvailable + $module.GetExportedTypeDefinitions().Count | Should Be 1 + + $module = Get-Module WithRoot -ListAvailable + $module.GetExportedTypeDefinitions().Count | Should Be 1 + + $module = Get-Module ABC -ListAvailable + $module.GetExportedTypeDefinitions().Count | Should Be 3 + } + + It 'Import-Module pick the right type' { + $module = Import-Module ABC -PassThru + $module.GetExportedTypeDefinitions().Count | Should Be 3 + $module = Import-Module ABC -PassThru -Force + $module.GetExportedTypeDefinitions().Count | Should Be 3 + + $module = Import-Module NoRoot -PassThru + $module.GetExportedTypeDefinitions().Count | Should Be 1 + $module = Import-Module NoRoot -PassThru -Force + $module.GetExportedTypeDefinitions().Count | Should Be 1 + [scriptblock]::Create(@' +using module NoRoot +[A]::new().foo() +'@ +).Invoke() | Should Be A2 + + $module = Import-Module WithRoot -PassThru + $module.GetExportedTypeDefinitions().Count | Should Be 1 + $module = Import-Module WithRoot -PassThru -Force + $module.GetExportedTypeDefinitions().Count | Should Be 1 + [scriptblock]::Create(@' +using module WithRoot +[A]::new().foo() +'@ +).Invoke() | Should Be A0 + } + + Context 'execute type creation in the module context' { + + # let's define types to make it more fun + class A { [string] foo() { return "local"} } + class B { [string] foo() { return "local"} } + class C { [string] foo() { return "local"} } + + # We need to think about it: should it work or not. + # Currently, types are resolved in compile-time to the 'local' versions + # So at runtime we don't call the module versions. + It 'Can execute type creation in the module context with new()' -Skip { + & (Get-Module ABC) { [C]::new().foo() } | Should Be C + & (Get-Module NoRoot) { [A]::new().foo() } | Should Be A2 + & (Get-Module WithRoot) { [A]::new().foo() } | Should Be A0 + & (Get-Module ABC) { [A]::new().foo() } | Should Be A + } + + It 'Can execute type creation in the module context with New-Object' { + & (Get-Module ABC) { (New-Object C).foo() } | Should Be C + & (Get-Module NoRoot) { (New-Object A).foo() } | Should Be A2 + & (Get-Module WithRoot) { (New-Object A).foo() } | Should Be A0 + & (Get-Module ABC) { (New-Object A).foo() } | Should Be A + } + } + + } finally { + $env:PSModulePath = $originalPSModulePath + Get-Module @('ABC', 'NoRoot', 'WithRoot') | Remove-Module + } +} diff --git a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 new file mode 100644 index 000000000..b02d18f12 --- /dev/null +++ b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 @@ -0,0 +1,529 @@ +# +# Copyright (c) Microsoft Corporation, 2015 +# + +Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 + +Describe 'Classes inheritance syntax' -Tags "DRT" { + + It 'Base types' { + class C1 {} + class C2a : C1 {} + class C2b:C1 {} + + [C2a]::new().GetType().BaseType.Name | Should Be "C1" + [C2b].BaseType.Name | Should Be "C1" + } + + It 'inheritance from abstract base class with no abstract methods and protected ctor' { + class C3 : system.collections.collectionbase {} + + class C4 { C4([int]$a) {} } + class C5 : C4 { C5() : base(1) {} } + } + + It 'inheritance from base class with implicit ctor' { + class C6 {} + class C7 : C6 { C7() : base() {} } + } + + It 'inheritance syntax allows newlines in varius places' { + class C {} + class C2a:C,system.ICloneable{ [object] Clone() {return $null}} + class C2b + : + C + , + system.ICloneable + { + [object] Clone() {return $null} + C2b() + : # there are extra spaces here + base + ( + ) + { + } + } + + [C2a].GetInterface("System.ICloneable") | Should Not Be $null + [C2b].GetInterface("System.ICloneable") | Should Not Be $null + } + + It 'can subclass .NET type' { + class MyIntList : system.collections.generic.list[int] {} + [MyIntList]::new().GetType().BaseType.FullName.StartsWith('System.Collections.Generic.List') | Should Be $true + } + + It 'can implement .NET interface' { + class MyComparable : system.IComparable + { + [int] CompareTo([object] $obj) + { + return 0; + } + } + [MyComparable].GetInterface("System.IComparable") | Should Not Be $null + } + + It 'allows use of defined later type as a property type' { + class A { static [B]$b } + class B : A {} + [A]::b = [B]::new() + try { + [A]::b = "bla" + } catch { + $_.Exception.GetType().Name | Should Be SetValueInvocationException + return + } + # Fail, if come heres + '' | Should Be "Exception expected" + } +} + +Describe 'Classes inheritance syntax errors' -Tags "DRT" { + ShouldBeParseError "class A : NonExistingClass {}" TypeNotFound 10 + ShouldBeParseError "class A : {}" TypeNameExpected 9 + ShouldBeParseError "class A {}; class B : A, {}" TypeNameExpected 24 + ShouldBeParseError "class A{} ; class B : A[] {}" SubtypeArray 22 -SkipAndCheckRuntimeError + ShouldBeParseError "class A : System.Collections.Generic.List``1 {}" SubtypeUnclosedGeneric 10 -SkipAndCheckRuntimeError + + ShouldBeParseError "class A {}; class B : A, NonExistingInterface {}" TypeNotFound 25 + ShouldBeParseError "class A {} ; class B {}; class C : A, B {}" InterfaceNameExpected 38 -SkipAndCheckRuntimeError + ShouldBeParseError "class A{} ; class B : A, System.ICloneable[] {}" SubtypeArray 25 -SkipAndCheckRuntimeError + ShouldBeParseError "class A {}; class B : A, NonExistingInterface {}" TypeNotFound 25 + + # base should be accepted only on instance ctors + ShouldBeParseError 'class A { A($a){} } ; class B : A { foo() : base(1) {} }' MissingFunctionBody 41 + ShouldBeParseError 'class A { static A() {} }; class B { static B() : base() {} }' MissingFunctionBody 47 + + # Incomplete input + ShouldBeParseError 'class A { A($a){} } ; class B : A { B() : bas {} }' MissingBaseCtorCall 41 + ShouldBeParseError 'class A { A($a){} } ; class B : A { B() : base( {} }' @('MissingEndParenthesisInMethodCall', 'MissingFunctionBody') @(50, 39) + ShouldBeParseError 'class A { A($a){} } ; class B : A { B() : base {} }' @('MissingMethodParameterList', 'UnexpectedToken') @(46, 50) + + # Sealed base + ShouldBeParseError "class baz : string {}" SealedBaseClass 12 -SkipAndCheckRuntimeError + # Non-existing Interface + ShouldBeParseError "class bar {}; class baz : bar, Non.Existing.Interface {}" TypeNotFound 31 -SkipAndCheckRuntimeError + + # .NET abstract method not implemented + ShouldBeParseError "class MyType : Type {}" TypeCreationError 0 -SkipAndCheckRuntimeError + + # inheritance doesn't allow non linear order + ShouldBeParseError "class A : B {}; class B {}" TypeNotFound 10 -SkipAndCheckRuntimeError + + # inheritance doesn't allow circular order + ShouldBeParseError "class A : B {}; class B : A {}" TypeNotFound 10 -SkipAndCheckRuntimeError + ShouldBeParseError "class A : C {}; class B : A {}; class C : B {}" TypeNotFound 10 -SkipAndCheckRuntimeError +} + +Describe 'Classes methods with inheritance' -Tags "DRT" { + + Context 'Method calls' { + + It 'can call instance method on base class' { + class bar + { + [int]foo() {return 100500} + } + class baz : bar {} + [baz]::new().foo() | Should Be 100500 + } + + It 'can call static method on base class' { + class bar + { + static [int]foo() {return 100500} + } + class baz : bar {} + [baz]::foo() | Should Be 100500 + } + + It 'can access static and instance base class property' { + class A + { + static [int]$si + [int]$i + } + class B : A + { + [void]foo() + { + $this::si = 1001 + $this.i = 1003 + } + } + $b = [B]::new() + $b.foo() + [A]::si | Should Be 1001 + ($b.i) | Should Be 1003 + } + + It 'works with .NET types' { + class MyIntList : system.collections.generic.list[int] {} + $intList = [MyIntList]::new() + $intList.Add(100501) + $intList.Add(100502) + $intList.Count | Should Be 2 + $intList[0] | Should Be 100501 + $intList[1] | Should Be 100502 + } + + It 'overrides instance method' { + class bar + { + [int]foo() {return 100500} + } + class baz : bar + { + [int]foo() {return 200600} + } + [baz]::new().foo() | Should Be 200600 + } + + It 'allows base class method call and doesn''t fall into recursion' { + class bar + { + [int]foo() {return 1001} + } + class baz : bar + { + [int] $fooCallCounter + [int]foo() + { + if ($this.fooCallCounter++ -gt 0) + { + throw "Recursion happens" + } + return 3 * ([bar]$this).foo() + } + } + + $res = [baz]::new().foo() + $res | Should Be 3003 + } + + It 'case insensitive for base class method calls' { + class bar + { + [int]foo() {return 1001} + } + class baz : bar + { + [int] $fooCallCounter + [int]fOo() + { + if ($this.fooCallCounter++ -gt 0) + { + throw "Recursion happens" + } + return ([bAr]$this).fOo() + ([bAr]$this).FOO() + } + } + + $res = [baz]::new().foo() + $res | Should Be 2002 + } + + It 'allows any call from the inheritance hierarchy' { + class A + { + [string]GetName() {return "A"} + } + class B : A + { + [string]GetName() {return "B"} + } + class C : B + { + [string]GetName() {return "C"} + } + class D : C + { + [string]GetName() {return "D"} + } + $d = [D]::new() + + ([A]$d).GetName() | Should Be "A" + ([B]$d).GetName() | Should Be "B" + ([C]$d).GetName() | Should Be "C" + ([D]$d).GetName() | Should Be "D" + $d.GetName() | Should Be "D" + } + + It 'can call base method with params' { + class A + { + [string]ToStr([int]$a) {return "A" + $a} + } + class B : A + { + [string]ToStr([int]$a) {return "B" + $a} + } + $b = [B]::new() + ([A]$b).ToStr(101) | Should Be "A101" + $b.ToStr(100) | Should Be "B100" + } + + It 'can call base method with many params' { + class A + { + [string]ToStr([int]$a1, [int]$a2, [int]$a3, [int]$a4, [int]$a5, [int]$a6, [int]$a7, [int]$a8, [int]$a9, [int]$a10, [int]$a11, [int]$a12, [int]$a13, [int]$a14) + { + return "A" + } + + [void]Noop([int]$a1, [int]$a2, [int]$a3, [int]$a4, [int]$a5, [int]$a6, [int]$a7, [int]$a8, [int]$a9, [int]$a10, [int]$a11, [int]$a12, [int]$a13, [int]$a14) + { + } + } + class B : A + { + [string]ToStr([int]$a1, [int]$a2, [int]$a3, [int]$a4, [int]$a5, [int]$a6, [int]$a7, [int]$a8, [int]$a9, [int]$a10, [int]$a11, [int]$a12, [int]$a13, [int]$a14) + { + return "B" + } + + [void]Noop([int]$a1, [int]$a2, [int]$a3, [int]$a4, [int]$a5, [int]$a6, [int]$a7, [int]$a8, [int]$a9, [int]$a10, [int]$a11, [int]$a12, [int]$a13, [int]$a14) + { + } + } + $b = [B]::new() + + # we don't really care about methods results, we only checks that calls doesn't throw + + # 14 args is a limit + $b.ToStr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) | Should Be 'B' + ([A]$b).ToStr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) | Should Be 'A' + + # 14 args is a limit + $b.Noop(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + ([A]$b).Noop(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + } + + It 'overrides void method call' { + $script:voidOverrideVar = $null + class A + { + [void]SetStr([int]$a) {$script:voidOverrideVar = "A" + $a} + [void]SetStr() {$script:voidOverrideVar = "A"} + } + class B : A + { + [void]SetStr([int]$a) {$script:voidOverrideVar = "B" + $a} + [void]SetStr() {$script:voidOverrideVar = "B"} + } + $b = [B]::new() + ([A]$b).SetStr(101) + $script:voidOverrideVar | Should Be "A101" + $b.SetStr(100) + $script:voidOverrideVar | Should Be "B100" + ([A]$b).SetStr() + $script:voidOverrideVar | Should Be "A" + $b.SetStr() + $script:voidOverrideVar | Should Be "B" + } + + It 'hides final .NET method' { + class MyIntList : system.collections.generic.list[int] + { + # Add is final, can we hide it? + [void] Add([int]$arg) + { + ([system.collections.generic.list[int]]$this).Add($arg * 2) + } + } + + $intList = [MyIntList]::new() + $intList.Add(100201) + $intList.Count | Should Be 1 + $intList[0] | Should Be 200402 + } + } + + Context 'base static method call' { + class A + { + static [string]ToStr([int]$a) {return "A" + $a} + } + class B : A + { + static [string]ToStr([int]$a) {return "B" + $a} + } + + $b = [B]::new() + + # MSFT:1911652 + # MSFT:2973835 + It 'doesn''t affect static method call on type' -Skip { + ([A]$b)::ToStr(101) | Should Be "A101" + } + + It 'overrides static method call on instance' { + $b::ToStr(100) | Should Be "B100" + } + } +} + + +Describe 'Classes inheritance ctors syntax errors' -Tags "DRT" { + + #DotNet.Interface.NotImplemented + ShouldBeParseError "class MyComparable : system.IComparable {}" TypeCreationError 0 -SkipAndCheckRuntimeError + + #DotNet.Interface.WrongSignature + ShouldBeParseError 'class MyComparable : system.IComparable { [void] CompareTo([object]$obj) {} }' TypeCreationError 0 -SkipAndCheckRuntimeError + + #DotNet.NoDefaultCtor + ShouldBeParseError "class MyCollection : System.Collections.ObjectModel.ReadOnlyCollection[int] {}" BaseClassNoDefaultCtor 0 -SkipAndCheckRuntimeError + + #NoDefaultCtor + ShouldBeParseError 'class A { A([int]$a) {} }; class B : A {}' BaseClassNoDefaultCtor 27 -SkipAndCheckRuntimeError +} + +Describe 'Classes inheritance ctors' -Tags "DRT" { + + It 'can call base ctor' { + class A { + [int]$a + A([int]$a) + { + $this.a = $a + } + } + + class B : A + { + B([int]$a) : base($a * 2) {} + } + + $b = [B]::new(101) + $b.a | Should Be 202 + } + + # TODO: can we detect it in the parse time? + It 'cannot call base ctor with the wrong number of parameters' { + class A { + [int]$a + A([int]$a) + { + $this.a = $a + } + } + + class B : A + { + B([int]$a) : base($a * 2, 100) {} + } + + try { + [B]::new(101) + } catch { + $_.Exception.GetType().Name | Should Be MethodException + return + } + # Fail + '' | Should Be "Exception expected" + } + + It 'call default base ctor implicitly' { + class A { + [int]$a + A() + { + $this.a = 1007 + } + } + + class B : A + { + B() {} + } + + class C : A + { + } + + $b = [B]::new() + $c = [C]::new() + $b.a | Should Be 1007 + $c.a | Should Be 1007 + } + + It 'doesn''t allow base ctor as an explicit method call' { + $o = [object]::new() + try { + # we should not allow direct .ctor call. + $o.{.ctor}() + } catch { + $_.FullyQualifiedErrorId | Should Be MethodNotFound + return + } + # Fail + '' | Should Be "Exception expected" + } + + It 'allow use convertion [string -> int] in base ctor call' { + class A { + [int]$a + A([int]$a) + { + $this.a = $a + } + } + + class B : A + { + B() : base("103") {} + } + + $b = [B]::new() + $b.a | Should Be 103 + } + + It 'resolves ctor call based on argument type' { + class A { + [int]$i + [string]$s + A([int]$a) + { + $this.i = $a + } + A([string]$a) + { + $this.s = $a + } + } + + class B : A + { + B($a) : base($a) {} + } + + $b1 = [B]::new("foo") + $b2 = [B]::new(1001) + $b1.s | Should Be "foo" + $b2.i | Should Be 1001 + } +} + +Describe 'Type creation' { + It 'can call super-class methods sequentially' { + $sb = [scriptblock]::Create(@' +class Base +{ + [int] foo() { return 100 } +} + +class Derived : Base +{ + [int] foo() { return 2 * ([Base]$this).foo() } +} + +[Derived]::new().foo() +'@) + $sb.Invoke() | Should Be 200 + $sb.Invoke() | Should Be 200 + } +} \ No newline at end of file diff --git a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 new file mode 100644 index 000000000..cb697e103 --- /dev/null +++ b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 @@ -0,0 +1,500 @@ +Describe 'using module' -Tags "DRT" { + + Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 + + function New-TestModule { + param( + [string]$Name, + [string]$Content, + [switch]$Manifest, + [version]$Version = '1.0', # ignored, if $Manifest -eq $false + [string]$ModulePathPrefix = 'modules' # module is created under TestDrive:\$ModulePathPrefix\$Name + ) + + if ($manifest) { + mkdir -Force "TestDrive:\$ModulePathPrefix\$Name\$Version" > $null + Set-Content -Path TestDrive:\$ModulePathPrefix\$Name\$Version\$Name.psm1 -Value $Content + New-ModuleManifest -RootModule "$Name.psm1" -Path TestDrive:\$ModulePathPrefix\$Name\$Version\$Name.psd1 -ModuleVersion $Version + } else { + mkdir -Force "TestDrive:\$ModulePathPrefix\$Name" > $null + Set-Content -Path TestDrive:\$ModulePathPrefix\$Name\$Name.psm1 -Value $Content + } + + $resolvedTestDrivePath = Split-Path ((ls TestDrive:\$ModulePathPrefix)[0].FullName) + if (-not ($env:PSModulePath -like "*$resolvedTestDrivePath*")) { + $env:PSModulePath += ";$resolvedTestDrivePath" + } + } + + $originalPSModulePath = $env:PSModulePath + + try { + + # Create modules in TestDrive:\ + New-TestModule -Name Foo -Content 'class Foo { [string] GetModuleName() { return "Foo" } }' + New-TestModule -Manifest -Name FooWithManifest -Content 'class Foo { [string] GetModuleName() { return "FooWithManifest" } }' + + It 'Import-Module has ImplementedAssembly, when classes are present in the module' { + $module = Import-Module Foo -PassThru + try { + $module.ImplementingAssembly | Should Not Be $null + } finally { + $module | Remove-Module + } + } + + It "can use class from another module as a base class with using module" { + $barType = [scriptblock]::Create(@" +using module Foo +class Bar : Foo {} +[Bar] +"@).Invoke() + + $barType.BaseType.Name | Should Be 'Foo' + } + + It "can use class from another module in New-Object" { + $foo = [scriptblock]::Create(@" +using module FooWithManifest +using module Foo +New-Object FooWithManifest.Foo +New-Object Foo.Foo +"@).Invoke() + + $foo.Count | Should Be 2 + $foo[0].GetModuleName() | Should Be 'FooWithManifest' + $foo[1].GetModuleName() | Should Be 'Foo' + } + + It "can use class from another module by full name as base class and [type]" { + $fooObject = [scriptblock]::Create(@" +using module Foo +class Bar : Foo.Foo {} +[Foo.Foo]::new() +"@).Invoke() + $fooObject.GetModuleName() | Should Be 'Foo' + } + + It "can use modules with classes collision" { + # we use 3 classes with name Foo at the same time + # two of them come from 'using module' and one is defined in the scriptblock itself. + # we should be able to use first two of them by the module-quilified name and the third one it's name. + $fooModuleName = [scriptblock]::Create(@" +using module Foo +using module FooWithManifest + +class Foo { [string] GetModuleName() { return "This" } } + +class Bar1 : Foo.Foo {} +class Bar2 : FooWithManifest.Foo {} +class Bar : Foo {} + +[Bar1]::new().GetModuleName() # Foo +[Bar2]::new().GetModuleName() # FooWithManifest +[Bar]::new().GetModuleName() # This +(New-Object Foo).GetModuleName() # This +"@).Invoke() + + $fooModuleName.Count | Should Be 4 + $fooModuleName[0] | Should Be 'Foo' + $fooModuleName[1] | Should Be 'FooWithManifest' + $fooModuleName[2] | Should Be 'This' + $fooModuleName[3] | Should Be 'This' + } + + It "doesn't mess up two consequitive scripts" { + $sb1 = [scriptblock]::Create(@" +using module Foo +class Bar : Foo {} +[Bar]::new().GetModuleName() +"@) + + $sb2 = [scriptblock]::Create(@" +using module Foo + +class Foo { [string] GetModuleName() { return "This" } } +class Bar : Foo {} +[Bar]::new().GetModuleName() + +"@) + $sb1.Invoke() | Should Be 'Foo' + $sb2.Invoke() | Should Be 'This' + } + + It "can use modules with classes collision simple" { + $fooModuleName = [scriptblock]::Create(@" +using module Foo + +class Foo { [string] GetModuleName() { return "This" } } + +class Bar1 : Foo.Foo {} +class Bar : Foo {} + +[Foo.Foo]::new().GetModuleName() # Foo +[Bar1]::new().GetModuleName() # Foo +[Bar]::new().GetModuleName() # This +[Foo]::new().GetModuleName() # This +(New-Object Foo).GetModuleName() # This +"@).Invoke() + + $fooModuleName.Count | Should Be 5 + $fooModuleName[0] | Should Be 'Foo' + $fooModuleName[1] | Should Be 'Foo' + $fooModuleName[2] | Should Be 'This' + $fooModuleName[3] | Should Be 'This' + $fooModuleName[4] | Should Be 'This' + } + + It "can use class from another module as a base class with using module with manifest" { + $barType = [scriptblock]::Create(@" +using module FooWithManifest +class Bar : Foo {} +[Bar] +"@).Invoke() + + $barType.BaseType.Name | Should Be 'Foo' + } + + It "can instantiate class from another module" { + $foo = [scriptblock]::Create(@" +using module Foo +[Foo]::new() +"@).Invoke() + + $foo.GetModuleName() | Should Be 'Foo' + } + + It "cannot instantiate class from another module without using statement" { + $err = Get-RuntimeError @" +#using module Foo +[Foo]::new() +"@ + $err.FullyQualifiedErrorId | Should Be TypeNotFound + } + + It "can use class from another module in New-Object by short name" { + $foo = [scriptblock]::Create(@" +using module FooWithManifest +New-Object Foo +"@).Invoke() + $foo.GetModuleName() | Should Be 'FooWithManifest' + } + + It "can use class from this module in New-Object by short name" { + $foo = [scriptblock]::Create(@" +class Foo {} +New-Object Foo +"@).Invoke() + $foo | Should Not Be $null + } + + # Pending reason: + # it's not yet implemented. + It "accept module specification" { + $foo = [scriptblock]::Create(@" +using module @{ ModuleName = 'FooWithManifest'; ModuleVersion = '1.0' } +New-Object Foo +"@).Invoke() + $foo.GetModuleName() | Should Be 'FooWithManifest' + } + + Context 'parse time errors' { + + It "report an error about not found module" { + $err = Get-ParseResults "using module ThisModuleDoesntExist" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'ModuleNotFoundDuringParse' + } + + + It "report an error about misformatted module specification" { + $err = Get-ParseResults "using module @{ Foo = 'Foo' }" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'RequiresModuleInvalid' + } + + It "report an error about wildcard in the module name" { + $err = Get-ParseResults "using module fo*" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'WildCardModuleNameError' + } + + It "report an error about wildcard in the module path" { + $err = Get-ParseResults "using module C:\fo*" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'WildCardModuleNameError' + } + + It "report an error about wildcard in the module name inside ModuleSpecification hashtable" { + $err = Get-ParseResults "using module @{ModuleName = 'Fo*'; RequiredVersion = '1.0'}" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'WildCardModuleNameError' + } + + # MSFT:5246105 + It "report an error when tokenizer encounters comma" { + $err = Get-ParseResults "using module ,FooWithManifest" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'MissingUsingItemName' + } + + It "report an error when tokenizer encounters nothing" { + $err = Get-ParseResults "using module " + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'MissingUsingItemName' + } + + It "report an error on badly formatted RequiredVersion" { + $err = Get-ParseResults "using module @{ModuleName = 'FooWithManifest'; RequiredVersion = 1. }" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'RequiresModuleInvalid' + } + + # MSFT:6897275 + It "report an error on incomplete using input" { + $err = Get-ParseResults "using module @{ModuleName = 'FooWithManifest'; FooWithManifest = 1." # missing closing bracket + $err.Count | Should Be 2 + $err[0].ErrorId | Should Be 'IncompleteHashLiteral' + $err[1].ErrorId | Should Be 'RequiresModuleInvalid' + } + + } + + Context 'short name in case of name collision' { + It "cannot use as base class" { + $err = Get-RuntimeError @" +using module Foo +using module FooWithManifest +class Bar : Foo {} +"@ + $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference + } + + It "cannot use as [...]" { + $err = Get-RuntimeError @" +using module Foo +using module FooWithManifest +[Foo] +"@ + $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference + } + + It "cannot use in New-Object" { + $err = Get-RuntimeError @" +using module Foo +using module FooWithManifest +New-Object Foo +"@ + $err.FullyQualifiedErrorId | Should Be 'AmbiguousTypeReference,Microsoft.PowerShell.Commands.NewObjectCommand' + } + + It "cannot use [type] cast from string" { + $err = Get-RuntimeError @" +using module Foo +using module FooWithManifest +[type]"Foo" +"@ + $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference + } + } + + Context 'using use the latest version of module after Import-Module -Force' { + New-TestModule -Name Foo -Content 'class Foo { [string] GetModuleName() { return "Foo2" } }' + Import-Module Foo -Force + It "can use class from another module as a base class with using module" { + $moduleName = [scriptblock]::Create(@" +using module Foo +[Foo]::new().GetModuleName() +"@).Invoke() + + $moduleName | Should Be 'Foo2' + } + } + + Context 'Side by side' { + # Add side-by-side module + $newVersion = '3.4.5' + New-TestModule -Manifest -Name FooWithManifest -Content 'class Foo { [string] GetModuleName() { return "Foo230" } }' -Version '2.3.0' + New-TestModule -Manifest -Name FooWithManifest -Content 'class Foo { [string] GetModuleName() { return "Foo345" } }' -Version '3.4.5' -ModulePathPrefix 'Modules2' + + # 'using module' behavior must be alligned with Import-Module. + # Import-Module does the following: + # 1) find the first directory from $env:PSModulePath that contains the module + # 2) Import highest available version of the module + # In out case TestDrive:\Module is before TestDrive:\Modules2 and so 2.3.0 is the right version + It "uses the last module, if multiple versions are present" { + $foo = [scriptblock]::Create(@" +using module FooWithManifest +[Foo]::new() +"@).Invoke() + $foo.GetModuleName() | Should Be 'Foo230' + } + + It "uses right version, when RequiredModule=1.0 specified" { + $foo = [scriptblock]::Create(@" +using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '1.0'} +[Foo]::new() +"@).Invoke() + $foo.GetModuleName() | Should Be 'FooWithManifest' + } + + It "uses right version, when RequiredModule=2.3.0 specified" { + $foo = [scriptblock]::Create(@" +using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '2.3.0'} +[Foo]::new() +"@).Invoke() + $foo.GetModuleName() | Should Be 'Foo230' + } + + It "uses right version, when RequiredModule=3.4.5 specified" { + $foo = [scriptblock]::Create(@" +using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '3.4.5'} +[Foo]::new() +"@).Invoke() + $foo.GetModuleName() | Should Be 'Foo345' + } + } + + Context 'Use module with runtime error' { + + New-TestModule -Name ModuleWithRuntimeError -Content @' +class Foo { [string] GetModuleName() { return "ModuleWithRuntimeError" } } +throw 'error' +'@ + + It "handles runtime errors in imported module" { + $err = Get-RuntimeError @" +using module ModuleWithRuntimeError +[Foo]::new().GetModuleName() +"@ + + $err | Should Be 'error' + } + } + + Context 'shared InitialSessionState' { + + It 'can pick the right module' { + + $scriptToProcessPath = 'TestDrive:\toProcess.ps1' + Set-Content -Path $scriptToProcessPath -Value @' +using module Foo +function foo() +{ + [Foo]::new() +} +'@ + # resolve name to absolute path + $scriptToProcessPath = (ls $scriptToProcessPath).FullName + $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss.StartupScripts.Add($scriptToProcessPath) + + $ps = [powershell]::Create($iss) + $ps.AddCommand("foo").Invoke() | Should be Foo + $ps.Streams.Error | Should Be $null + + $ps1 = [powershell]::Create($iss) + $ps1.AddCommand("foo").Invoke() | Should be Foo + $ps1.Streams.Error | Should Be $null + + $ps.Commands.Clear() + $ps.Streams.Error.Clear() + $ps.AddScript(". foo").Invoke() | Should be Foo + $ps.Streams.Error | Should Be $null + } + } + + # this is a setup for Context "Module by path" + New-TestModule -Name FooForPaths -Content 'class Foo { [string] GetModuleName() { return "FooForPaths" } }' + + + } finally { + $env:PSModulePath = $originalPSModulePath + } + + # here we are back to normal $env:PSModulePath, but all modules are there + Context "Module by path" { + + It 'use non-modified PSModulePath' { + $env:PSModulePath | Should Be $originalPSModulePath + } + + mkdir -Force TestDrive:\FooRelativeConsumer + Set-Content -Path TestDrive:\FooRelativeConsumer\FooRelativeConsumer.ps1 -Value @' +using module ..\Modules\FooForPaths +class Bar : Foo {} +[Bar]::new() +'@ + + Set-Content -Path TestDrive:\FooRelativeConsumerErr.ps1 -Value @' +using module FooForPaths +class Bar : Foo {} +[Bar]::new() +'@ + + It "can be accessed by relative path" { + $barObject = & TestDrive:\FooRelativeConsumer\FooRelativeConsumer.ps1 + $barObject.GetModuleName() | Should Be 'FooForPaths' + } + + It "cannot be accessed by relative path without .\ from a script" { + $err = Get-RuntimeError '& TestDrive:\FooRelativeConsumerErr.ps1' + $err.FullyQualifiedErrorId | Should Be ModuleNotFoundDuringParse + } + + It "can be accessed by absolute path" { + $resolvedTestDrivePath = Split-Path ((ls TestDrive:\Modules)[0].FullName) + $s = @" +using module $resolvedTestDrivePath\FooForPaths +[Foo]::new() +"@ + $err = Get-ParseResults $s + $err.Count | Should Be 0 + $barObject = [scriptblock]::Create($s).Invoke() + $barObject.GetModuleName() | Should Be 'FooForPaths' + } + + It "can be accessed by absolute path with file extension" { + $resolvedTestDrivePath = Split-Path ((ls TestDrive:\Modules)[0].FullName) + $barObject = [scriptblock]::Create(@" +using module $resolvedTestDrivePath\FooForPaths\FooForPaths.psm1 +[Foo]::new() +"@).Invoke() + $barObject.GetModuleName() | Should Be 'FooForPaths' + } + + It "can be accessed by relative path without file" { + # we should not be able to access .\FooForPaths without cd + $err = Get-RuntimeError @" +using module .\FooForPaths +[Foo]::new() +"@ + $err.FullyQualifiedErrorId | Should Be ModuleNotFoundDuringParse + + Push-Location TestDrive:\Modules + try { + $barObject = [scriptblock]::Create(@" +using module .\FooForPaths +[Foo]::new() +"@).Invoke() + $barObject.GetModuleName() | Should Be 'FooForPaths' + } finally { + Pop-Location + } + } + + It "cannot be accessed by relative path without .\" { + Push-Location TestDrive:\Modules + try { + $err = Get-RuntimeError @" +using module FooForPaths +[Foo]::new() +"@ + $err.FullyQualifiedErrorId | Should Be ModuleNotFoundDuringParse + } finally { + Pop-Location + } + } + } +} + diff --git a/test/powershell/Language/Classes/scripting.enums.tests.ps1 b/test/powershell/Language/Classes/scripting.enums.tests.ps1 new file mode 100644 index 000000000..88258d620 --- /dev/null +++ b/test/powershell/Language/Classes/scripting.enums.tests.ps1 @@ -0,0 +1,97 @@ +# +# Copyright (c) Microsoft Corporation, 2015 +# + +Describe 'enums' -Tags "DRT" { + + Context 'basic enums' { + enum E1 + { + e0 + e1 + e2 + } + + It "has correct value 0" { [E1]::e0 | Should Be ([E1]0) } + It "has correct value 1" { [E1]::e1 | Should Be ([E1]1) } + It "has correct value 2" { [E1]::e2 | Should Be ([E1]2) } + It "cast from string" { [E1]::e1 | Should Be 'e1' } + It "cast to string" { 'e2' | Should Be ([E1]::e2) } + } + + Context 'Basic enum with initial value' { + enum E2 + { + e0 + e1 = 5 + e2 + } + + It "has correct value 0" { [E2]::e0 | Should Be ([E2]0) } + It "has correct value 5" { [E2]::e1 | Should Be ([E2]5) } + It "has correct value 6" { [E2]::e2 | Should Be ([E2]6) } + It "cast from string" { [E2]::e1 | Should Be 'e1' } + It "cast to string" { 'e2' | Should Be ([E2]::e2) } + } + + Context 'Basic enum with initial value expression' { + enum E3 + { + e0 + e1 = 5 + e2 = [int]::MaxValue + e3 = 1 # This shouldn't be an error even though previous member was max int + } + + It "has correct value 0" { [E3]::e0 | Should Be ([E3]0) } + It "has correct value 5" { [E3]::e1 | Should Be ([E3]5) } + It "has correct value [int]::MaxValue" { [E3]::e2 | Should Be ([E3]([int]::MaxValue)) } + It "has correct value 1" { [E3]::e3 | Should Be ([E3]1) } + It "cast from string" { [E3]::e2 | Should Be 'e2' } + It "cast to string" { 'e3' | Should Be ([E3]::e3) } + } + + Context 'Enum with complicated initial value' { + enum E4 + { + e0 = [E5]::e0 + 2 + } + + enum E5 + { + e0 = [E6]::e0 + 2 + } + + enum E6 + { + e0 = 38 + } + + It 'E4 has correct value' { [E4]::e0 | Should Be ([E4]42) } + It 'E5 has correct value' { [E5]::e0 | Should Be ([E5]40) } + It 'E6 has correct value' { [E6]::e0 | Should Be ([E6]38) } + } +} + +Describe 'Basic enum errors' -Tags "DRT" { + + Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 + + AfterAll { + Remove-Module LanguageTestSupport + } + + ShouldBeParseError 'enum' MissingNameAfterKeyword 4 + ShouldBeParseError 'enum foo' MissingTypeBody 8 + ShouldBeParseError 'enum foo {' MissingEndCurlyBrace 10 + ShouldBeParseError 'enum foo { x = }' ExpectedValueExpression 14 + ShouldBeParseError 'enum foo { x =' ExpectedValueExpression,MissingEndCurlyBrace 14,14 + ShouldBeParseError 'enum foo {} enum foo {}' MemberAlreadyDefined 12 + ShouldBeParseError 'enum foo { x; x }' MemberAlreadyDefined 14 -SkipAndCheckRuntimeError + ShouldBeParseError 'enum foo { X; x }' MemberAlreadyDefined 14 -SkipAndCheckRuntimeError + ShouldBeParseError 'enum foo1 { x = [foo2]::x } enum foo2 { x = [foo1]::x }' CycleInEnumInitializers,CycleInEnumInitializers 0,28 -SkipAndCheckRuntimeError + ShouldBeParseError 'enum foo { e = [int]::MaxValue; e2 }' EnumeratorValueTooLarge 33 -SkipAndCheckRuntimeError + ShouldBeParseError 'enum foo { e = [int]::MaxValue + 1 }' EnumeratorValueTooLarge 15 -SkipAndCheckRuntimeError + ShouldBeParseError 'enum foo { e = $foo }' EnumeratorValueMustBeConstant 15 -SkipAndCheckRuntimeError + ShouldBeParseError 'enum foo { e = "hello" }' CannotConvertValue 15 -SkipAndCheckRuntimeError +} diff --git a/test/powershell/Language/CompletionTestSupport.psm1 b/test/powershell/Language/CompletionTestSupport.psm1 new file mode 100644 index 000000000..aeaa12da5 --- /dev/null +++ b/test/powershell/Language/CompletionTestSupport.psm1 @@ -0,0 +1,142 @@ + +class CompletionResult +{ + [string]$CompletionText + [string]$ListItemText + [System.Management.Automation.CompletionResultType]$ResultType + [string]$ToolTip + [bool]$Found + + [bool] Equals($Other) + { + if ($Other -isnot [CompletionResult] -and + $Other -isnot [System.Management.Automation.CompletionResult]) + { + return $false + } + + # Comparison is intentionally fuzzy - CompletionText and ResultType must be specified + # but the other properties don't need to match if they aren't specified + + if ($this.CompletionText -cne $Other.CompletionText -or + $this.ResultType -ne $Other.ResultType) + { + return $false + } + + if ($this.ListItemText -cne $Other.ListItemText -and + ![string]::IsNullOrEmpty($this.ListItemText) -and ![string]::IsNullOrEmpty($Other.ListItemText)) + { + return $false + } + + if ($this.ToolTip -cne $Other.ToolTip -and + ![string]::IsNullOrEmpty($this.ToolTip) -and ![string]::IsNullOrEmpty($Other.ToolTip)) + { + return $false + } + + return $true + } +} + +class CompletionTestCase +{ + [string]$Description + [CompletionResult[]]$ExpectedResults + [string[]]$NotExpectedResults + [hashtable]$TestInput +} + +function Get-Completions +{ + [CmdletBinding()] + param([string]$InputScript, [int]$CursorColumn, $Options = $null) + + if (!$PSBoundParameters.ContainsKey('CursorColumn')) + { + $CursorColumn = $InputScript.IndexOf('<#CURSOR#>') + if ($CursorColumn -lt 0) + { + $CursorColumn = $InputScript.Length + } + else + { + $InputScript = $InputScript -replace '<#CURSOR#>','' + } + } + + $results = [System.Management.Automation.CommandCompletion]::CompleteInput( + <#inputScript#> $InputScript, + <#cursorColumn#> $CursorColumn, + <#options#> $Options) + + return $results +} + +function Get-CompletionTestCaseData +{ + param( + [Parameter(ValueFromPipeline)] + [hashtable[]]$Data) + + process + { + Write-Output ([CompletionTestCase[]]$Data) + } +} + +function Test-Completions +{ + param( + [Parameter(ValueFromPipeline)] + [CompletionTestCase[]]$TestCases, + [string] + $Description) + + process + { + foreach ($test in $TestCases) + { + Describe $test.Description { + $hash = $Test.TestInput + $results = Get-Completions @hash + + foreach ($expected in $test.ExpectedResults) + { + foreach ($result in $results.CompletionMatches) + { + + if ($expected.Equals($result)) + { + It "Checking for duplicates of: $($expected.CompletionText)" { + # We should only find 1 of each expected result + $expected.Found | Should Be $false + } + $expected.Found = $true + } + } + } + + foreach ($expected in $test.ExpectedResults) + { + It "Checking for presence of expected result: $($expected.CompletionText)" { + $expected.Found | Should Be $true + } + } + + foreach ($notExpected in $test.NotExpectedResults) + { + foreach ($result in $results.CompletionMatches) + { + It "Checking for results that should not be found: $notExpected" { + $result.CompletionText -cne $notExpected | Should Be $true + } + } + } + + } + } + } +} + diff --git a/test/powershell/Language/LanguageTestSupport.psm1 b/test/powershell/Language/LanguageTestSupport.psm1 new file mode 100644 index 000000000..2d7e46a1d --- /dev/null +++ b/test/powershell/Language/LanguageTestSupport.psm1 @@ -0,0 +1,178 @@ + +# +# Run the new parser, return either errors or the ast +# +function Get-ParseResults +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True,Mandatory=$True)] + [string]$src, + [switch]$Ast + ) + + $errors = $null + $result = [System.Management.Automation.Language.Parser]::ParseInput($src, [ref]$null, [ref]$errors) + if ($Ast) { $result } else { ,$errors } +} + +# +# Run script and return errors +# +function Get-RuntimeError +{ + [CmdletBinding()] + param( + [Parameter(ValueFromPipeline=$True,Mandatory=$True)] + [string]$src + ) + + $errors = $null + try + { + [scriptblock]::Create($src).Invoke() > $null + } + catch + { + return $_.Exception.InnerException.ErrorRecord + } +} + +function position_message +{ + param($position) + + if ($position.Line.Length -lt $position.ColumnNumber) + { + $position.Line + " <<<<" + } + else + { + $position.Line.Insert($position.ColumnNumber, " <<<<") + } +} + + +# +# Run the new parser, check the errors against the expected errors +# +function Test-Error +{ + [CmdletBinding()] + param([string]$src, [string[]]$expectedErrors, [int[]]$expectedOffsets) + + Assert ($expectedErrors.Count -eq $expectedOffsets.Count) "Test case error" + + $errors = Get-ParseResults -Src $src + + + if ($null -ne $errors) + { + Assert ($errors.Count -eq $expectedErrors.Count) "Expected $($expectedErrors.Count) errors, got $($errors.Count)" + for ($i = 0; $i -lt $errors.Count; ++$i) + { + $err = $errors[$i] + Assert ($expectedErrors[$i] -eq $err.ErrorId) ("Unexpected error: {0,-30}{1}" -f ("$($err.ErrorId):", + (position_message $err.Extent.StartScriptPosition))) + Assert ($expectedOffsets[$i] -eq $err.Extent.StartScriptPosition.Offset) ` + "Expected position: $($expectedOffsets[$i]), got $($err.Extent.StartScriptPosition.Offset)" + } + } + else + { + Assert $false "Expected errors but didn't receive any." + } +} + +# +# Pester friendly version of Test-Error +# +function ShouldBeParseError +{ + [CmdletBinding()] + param( + [string]$src, + [string[]]$expectedErrors, + [int[]]$expectedOffsets, + # This is a temporary solution after moving type creation from parse time to runtime + [switch]$SkipAndCheckRuntimeError + ) + + Context "Parse error expected: <<$src>>" { + # Test case error if this fails + $expectedErrors.Count | Should Be $expectedOffsets.Count + + if ($SkipAndCheckRuntimeError) + { + It "error should happen at parse time, not at runtime" -Skip {} + $errors = Get-RuntimeError -Src $src + # for runtime errors we will only get the first one + $expectedErrors = ,$expectedErrors[0] + $expectedOffsets = ,$expectedOffsets[0] + } + else + { + $errors = Get-ParseResults -Src $src + } + + It "Error count" { $errors.Count | Should Be $expectedErrors.Count } + for ($i = 0; $i -lt $errors.Count; ++$i) + { + $err = $errors[$i] + + if ($SkipAndCheckRuntimeError) + { + $errorId = $err.FullyQualifiedErrorId + } + else + { + $errorId = $err.ErrorId + } + It "Error Id" { $errorId | Should Be $expectedErrors[$i] } + It "Error position" -Pending:$SkipAndCheckRuntimeError { $err.Extent.StartScriptPosition.Offset | Should Be $expectedOffsets[$i] } + } + } +} + + +function Flatten-Ast +{ + [CmdletBinding()] + param([System.Management.Automation.Language.Ast] $ast) + + $ast + $ast | gm -type property | ? { ($prop = $_.Name) -ne 'Parent' } | % { + $ast.$prop | ? { $_ -is [System.Management.Automation.Language.Ast] } | % { Flatten-Ast $_ } + } +} + +function Test-ErrorStmt +{ + param([string]$src, [string]$errorStmtExtent) + + $ast = Get-ParseResults $src -Ast + $asts = @(Flatten-Ast $ast.EndBlock.Statements[0]) + + Assert ($asts[0] -is [System.Management.Automation.Language.ErrorStatementAst]) "Expected error statement" + Assert ($asts.Count -eq $args.Count + 1) "Incorrect number of nested asts" + Assert ($asts[0].Extent.Text -eq $errorStmtExtent) "Error statement expected <$errorStmtExtent>, got <$($asts[0].Extent.Text)>" + for ($i = 0; $i -lt $args.Count; ++$i) + { + Assert ($asts[$i + 1].Extent.Text -eq $args[$i]) "Nested ast incorrect: <$($asts[$i+1].Extent.Text)>, expected <$($args[$i])>" + } +} + +function Test-Ast +{ + param([string]$src) + + $ast = Get-ParseResults $src -Ast + $asts = @(Flatten-Ast $ast) + Assert ($asts.Count -eq $args.Count) "Incorrect number of nested asts, got $($asts.Count), expected $($args.Count)" + for ($i = 0; $i -lt $args.Count; ++$i) + { + Assert ($asts[$i].Extent.Text -eq $args[$i]) "Nested ast incorrect: <$($asts[$i].Extent.Text)>, expected <$($args[$i])>" + } +} + +Export-ModuleMember -Function Test-Error, Test-ErrorStmt, Test-Ast, ShouldBeParseError, Get-ParseResults, Get-RuntimeError diff --git a/test/powershell/Language/Parser/Ast.Tests.ps1 b/test/powershell/Language/Parser/Ast.Tests.ps1 new file mode 100644 index 000000000..f220ef362 --- /dev/null +++ b/test/powershell/Language/Parser/Ast.Tests.ps1 @@ -0,0 +1,38 @@ +using Namespace System.Management.Automation.Language +Describe "The SafeGetValue method on AST returns safe values" -Tags "DRT" { + It "A hashtable is returned from a HashtableAst" { + $HashtableAstType = [HashtableAst] + $HtAst = { + @{ one = 1 } + }.ast.Find({$args[0] -is $HashtableAstType}, $true) + $HtAst.SafeGetValue().GetType().Name | Should be Hashtable + } + It "An Array is returned from a LiteralArrayAst" { + $ArrayAstType = [ArrayLiteralAst] + $ArrayAst = { + @( 1,2,3,4) + }.ast.Find({$args[0] -is $ArrayAstType}, $true) + $ArrayAst.SafeGetValue().GetType().Name | Should be "Object[]" + } + It "The proper error is returned when a variable is referenced" { + $ast = { $a }.Ast.Find({$args[0] -is "VariableExpressionAst"},$true) + try { + $ast.SafeGetValue() | out-null + Throw "Execution Succeeded" + } + catch { + $_.FullyQualifiedErrorId | Should be "InvalidOperationException" + $_.ToString() | Should Match '\$a' + } + } + It "A ScriptBlock AST fails with the proper error" { + try { + { 1 }.Ast.SafeGetValue() + Throw "Execution Succeeded" + } + catch { + $_.FullyQualifiedErrorId | Should be "InvalidOperationException" + } + } + +} diff --git a/test/powershell/Language/Parser/AutomaticVariables.Tests.ps1 b/test/powershell/Language/Parser/AutomaticVariables.Tests.ps1 new file mode 100644 index 000000000..0912e2206 --- /dev/null +++ b/test/powershell/Language/Parser/AutomaticVariables.Tests.ps1 @@ -0,0 +1,19 @@ + +Describe 'Automatic variable $input' { + It '$input Type should be enumerator' { + function from_begin { [cmdletbinding()]param() begin { Write-Output -NoEnumerate $input } } + function from_process { [cmdletbinding()]param() process { Write-Output -NoEnumerate $input } } + function from_end { [cmdletbinding()]param() end { Write-Output -NoEnumerate $input } } + + (from_begin) -is [System.Collections.IEnumerator] | Should Be $true + (from_process) -is [System.Collections.IEnumerator] | Should Be $true + (from_end) -is [System.Collections.IEnumerator] | Should Be $true + } + + It 'Empty $input really is empty' { + & { @($input).Count } | Should Be 0 + & { [cmdletbinding()]param() begin { @($input).Count } } | Should Be 0 + & { [cmdletbinding()]param() process { @($input).Count } } | Should Be 0 + & { [cmdletbinding()]param() end { @($input).Count } } | Should Be 0 + } +} diff --git a/test/powershell/Language/Parser/BNotOperator.Tests.ps1 b/test/powershell/Language/Parser/BNotOperator.Tests.ps1 new file mode 100644 index 000000000..8e5c4be66 --- /dev/null +++ b/test/powershell/Language/Parser/BNotOperator.Tests.ps1 @@ -0,0 +1,142 @@ + + +$baseTypes = @{ + [SByte] = 'sbyte'; [Byte] = 'byte' + [Int16] = 'short'; [UInt16] = 'ushort' + [Int32] = 'int'; [UInt32] = 'uint' + [Int64] = 'long'; [UInt64] = 'ulong' +} + +$ns = [Guid]::NewGuid() -replace '-','' + +$typeDefinition = "namespace ns_$ns`n{" + +$enumTypeNames = foreach ($baseType in $baseTypes.Keys) +{ + $baseTypeName = $baseTypes[$baseType] + $typeDefinition += @" + public enum E_$baseTypeName : $baseTypeName + { + Min = $($baseType::MinValue), + MinPlus1 = $($baseType::MinValue + 1), + MaxMinus1 = $($baseType::MaxValue - 1), + Max = $($baseType::MaxValue) + } +"@ + + "ns_$ns.E_$baseTypeName" +} + +$typeDefinition += "`n}" + +Write-Verbose $typeDefinition +Add-Type $typeDefinition + +Describe "bnot on enums" -Tags "DRT" { + foreach ($enumType in [type[]]$enumTypeNames) + { + Context $enumType.Name { + It "max - 1" { + $res = -bnot $enumType::MaxMinus1 + $res | Should Be $enumType::MinPlus1 + $res.GetType() | Should Be $enumType + } + + It "min + 1" { + $res = -bnot $enumType::MinPlus1 + $res | Should Be $enumType::MaxMinus1 + $res.GetType() | Should Be $enumType + } + + It "Max" { + $res = -bnot $enumType::Max + $res | Should Be $enumType::Min + $res.GetType() | Should Be $enumType + } + + It "Min" { + $res = -bnot $enumType::Min + $res | Should Be $enumType::Max + $res.GetType() | Should Be $enumType + } + } + } +} + +Describe "bnot on integral types" -Tags "DRT" { + foreach ($baseType in $baseTypes.Keys) + { + Context $baseType.Name { + + $max = $baseType::MaxValue + $maxMinus1 = $max - 1 + $min = $baseType::MinValue + $minPlus1 = $min + 1 + + if ([System.Runtime.InteropServices.Marshal]::SizeOf([type]$baseType) -lt 4) + { + $expectedResultType = [int] + } + else + { + $expectedResultType = $baseType + } + + if ($baseType -eq [byte] -or $baseType -eq [uint16]) + { + # Because of type promotion rules, unsigned types smaller than int + # don't quite follow the pattern of "flip all the bits", so our + # tests are a little different. + It "max - 1" { + $res = -bnot $maxMinus1 + $res | Should Be (-bnot [int]$maxMinus1) + $res.GetType() | Should Be $expectedResultType + } + + It "min + 1" { + $res = -bnot $minPlus1 + $res | Should Be (-bnot [int]$minPlus1) + $res.GetType() | Should Be $expectedResultType + } + + It "max" { + $res = -bnot $max + $res | Should Be (-bnot [int]$max) + $res.GetType() | Should Be $expectedResultType + } + + It "min" { + $res = -bnot $min + $res | Should Be (-bnot [int]$min) + $res.GetType() | Should Be $expectedResultType + } + return + } + + It "max - 1" { + $res = -bnot $maxMinus1 + $res | Should Be $minPlus1 + $res.GetType() | Should Be $expectedResultType + } + + It "min + 1" { + $res = -bnot $minPlus1 + $res | Should Be $maxMinus1 + $res.GetType() | Should Be $expectedResultType + } + + It "max" { + $res = -bnot $max + $res | Should Be $min + $res.GetType() | Should Be $expectedResultType + } + + It "min" { + $res = -bnot $min + $res | Should Be $max + $res.GetType() | Should Be $expectedResultType + } + } + } +} + diff --git a/test/powershell/Language/Parser/Conversions.Tests.ps1 b/test/powershell/Language/Parser/Conversions.Tests.ps1 new file mode 100644 index 000000000..593ba4281 --- /dev/null +++ b/test/powershell/Language/Parser/Conversions.Tests.ps1 @@ -0,0 +1,23 @@ +Describe 'conversion syntax' -Tags "innerloop", "DRT" { + # these test suite covers ([]).() syntax. + # it mixes two purposes: casting and super-class method calls. + + It 'converts array of single enum to bool' { + # This test relies on the fact that [ConsoleColor]::Black is 0 and all other values are non-zero + [bool]@([ConsoleColor]::Black) | Should Be $false + [bool]@([ConsoleColor]::Yellow) | Should Be $true + } + + It 'calls virtual method non-virtually' { + ([object]"abc").ToString() | Should Be "System.String" + + # generate random string to avoid JIT optimization + $r = [guid]::NewGuid().Guid + ([object]($r + "a")).Equals(($r + "a")) | Should Be $false + } + + It 'calls method on a super-type, when conversion syntax used' { + # This test relies on the fact that there are overloads (at least 2) for ToString method. + ([System.Management.Automation.ActionPreference]"Stop").ToString() | Should Be "Stop" + } +} diff --git a/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 new file mode 100644 index 000000000..bffad6e4d --- /dev/null +++ b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 @@ -0,0 +1,323 @@ +<# + Much of this script belongs in a module, but we don't support importing classes yet. +#> +using namespace System.Management.Automation +using namespace System.Management.Automation.Language +using namespace System.Collections +using namespace System.Collections.Generic + +#region Testcase infrastructure + +class CompletionTestResult +{ + [string]$CompletionText + [string]$ListItemText + [CompletionResultType]$ResultType + [string]$ToolTip + [bool]$Found + + [bool] Equals($Other) + { + if ($Other -isnot [CompletionTestResult] -and + $Other -isnot [CompletionResult]) + { + return $false + } + + # Comparison is intentionally fuzzy - CompletionText and ResultType must be specified + # but the other properties don't need to match if they aren't specified + + if ($this.CompletionText -cne $Other.CompletionText -or + $this.ResultType -ne $Other.ResultType) + { + return $false + } + + if ($this.ListItemText -cne $Other.ListItemText -and + ![string]::IsNullOrEmpty($this.ListItemText) -and ![string]::IsNullOrEmpty($Other.ListItemText)) + { + return $false + } + + if ($this.ToolTip -cne $Other.ToolTip -and + ![string]::IsNullOrEmpty($this.ToolTip) -and ![string]::IsNullOrEmpty($Other.ToolTip)) + { + return $false + } + + return $true + } +} + +class CompletionTestCase +{ + [CompletionTestResult[]]$ExpectedResults + [string[]]$NotExpectedResults + [string]$TestInput +} + +function Get-Completions +{ + param([string]$inputScript, [int]$cursorColumn = $inputScript.Length) + + $results = [System.Management.Automation.CommandCompletion]::CompleteInput( + <#inputScript#> $inputScript, + <#cursorColumn#> $cursorColumn, + <#options#> $null) + + return $results +} + +function Get-CompletionTestCaseData +{ + param( + [Parameter(ValueFromPipeline)] + [hashtable[]]$Data) + + process + { + Write-Output ([CompletionTestCase[]]$Data) + } +} + +function Test-Completions +{ + param( + [Parameter(ValueFromPipeline)] + [CompletionTestCase[]]$TestCases) + + process + { + foreach ($test in $TestCases) + { + Context ("Command line: <" + $test.TestInput + ">") { + $results = Get-Completions $test.TestInput + foreach ($result in $results.CompletionMatches) + { + foreach ($expected in $test.ExpectedResults) + { + if ($expected.Equals($result)) + { + $expected.Found = $true + } + } + } + foreach ($expected in $test.ExpectedResults) + { + It ($expected.CompletionText) { + $expected.Found | Should Be $true + } + } + + foreach ($notExpected in $test.NotExpectedResults) + { + It "Not expected: $notExpected" { + foreach ($result in $results.CompletionMatches) + { + ($result.CompletionText -ceq $notExpected) | Should Be $False + } + } + } + } + } + } +} + +#endregion Testcase infrastructure + +function AlphaArgumentCompleter +{ + param( + [string] $CommandName, + [string] $parameterName, + [string] $wordToComplete, + [CommandAst] $commandAst, + [IDictionary] $fakeBoundParameters) + + $beta = $fakeBoundParameters['beta'] + $gamma = $fakeBoundParameters['Gamma'] + $result = "beta: $beta gamma: $gamma command: $commandName parameterName: $parameterName wordToComplete: $wordToComplete" + [CompletionResult]::new($result, $result, "ParameterValue", $result) +} + +class BetaArgumentCompleter : IArgumentCompleter +{ + [IEnumerable[CompletionResult]] CompleteArgument( + [string] $CommandName, + [string] $parameterName, + [string] $wordToComplete, + [CommandAst] $commandAst, + [IDictionary] $fakeBoundParameters) + { + $resultList = [List[CompletionResult]]::new() + + $alpha = $fakeBoundParameters['Alpha'] + $gamma = $fakeBoundParameters['Gamma'] + $result = "alpha: $alpha gamma: $gamma command: $commandName parameterName: $parameterName wordToComplete: $wordToComplete" + $resultList.Add([CompletionResult]::new($result, $result, "ParameterValue", $result)) + + return $resultList + } +} + +function TestFunction +{ + param( + [ArgumentCompleter({ AlphaArgumentCompleter @args })] + $Alpha, + [ArgumentCompleter([BetaArgumentCompleter])] + $Beta, + $Gamma + ) +} + +Describe "Script block based extensible completion" -Tags "Innerloop", "BVT" { + @{ + ExpectedResults = @( + @{CompletionText = "beta: 11 gamma: 22 command: TestFunction parameterName: Alpha wordToComplete: aa" + ResultType = "ParameterValue"}) + TestInput = 'TestFunction -Beta 11 -Gamma 22 -Alpha aa' + } | Get-CompletionTestCaseData | Test-Completions +} + +Describe "Test class based extensible completion" -Tags "Innerloop", "BVT" { + @{ + ExpectedResults = @( + @{CompletionText = "alpha: 42 gamma: 44 command: TestFunction parameterName: Beta wordToComplete: zz" + ResultType = "ParameterValue"}) + TestInput = 'TestFunction -Alpha 42 -Gamma 44 -Beta zz' + } | Get-CompletionTestCaseData | Test-Completions +} + +Describe "Test registration based exensible completion" -Tags "Innerloop", "BVT" { + Register-ArgumentCompleter -Command TestFunction -Parameter Gamma -ScriptBlock { + param( + [string] $CommandName, + [string] $parameterName, + [string] $wordToComplete, + [CommandAst] $commandAst, + [IDictionary] $fakeBoundParameters) + + $beta = $fakeBoundParameters['beta'] + $alpha = $fakeBoundParameters['alpha'] + $result = "beta: $beta alpha: $alpha command: $commandName parameterName: $parameterName wordToComplete: $wordToComplete" + [CompletionResult]::new($result, $result, "ParameterValue", $result) + } + + @{ + ExpectedResults = @( + @{CompletionText = "beta: bb alpha: aa command: TestFunction parameterName: Gamma wordToComplete: 42" + ResultType = "ParameterValue"}) + TestInput = 'TestFunction -Alpha aa -Beta bb -Gamma 42' + } | Get-CompletionTestCaseData | Test-Completions +} + +Describe "Test extensible completion of native commands" -Tags "Innerloop", "BVT" { + Register-ArgumentCompleter -Command netsh -Native -ScriptBlock { + [CompletionResult]::new('advfirewall', 'advfirewall', "ParameterValue", 'advfirewall') + [CompletionResult]::new('bridge', 'bridge', "ParameterValue", 'bridge') + } + + @{ + ExpectedResults = @( + @{CompletionText = "advfirewall"; ResultType = "ParameterValue"} + @{CompletionText = "bridge"; ResultType = "ParameterValue"} + ) + TestInput = 'netsh ' + } | Get-CompletionTestCaseData | Test-Completions +} + +Describe "Test extensible completion of using namespace" -Tags "Innerloop", "BVT" { + @{ + ExpectedResults = @( + @{CompletionText = "System"; ResultType = "Namespace"} + ) + TestInput = 'Using namespace sys' + }, + @{ + ExpectedResults = @( + @{CompletionText = "System.Xml"; ResultType = "Namespace"} + @{CompletionText = "System.Data"; ResultType = "Namespace"} + @{CompletionText = "System.Collections"; ResultType = "Namespace"} + @{CompletionText = "System.IO"; ResultType = "Namespace"} + ) + TestInput = 'Using namespace system.' + }, + @{ + ExpectedResults = @( + @{CompletionText = "System.Management.Automation"; ResultType = "Namespace"} + ) + TestInput = 'Using namespace System.Management.Automati' + }, + @{ + ExpectedResults = @( + @{CompletionText = "System.Management.Automation.Host"; ResultType = "Namespace"} + @{CompletionText = "System.Management.Automation.Internal"; ResultType = "Namespace"} + @{CompletionText = "System.Management.Automation.Language"; ResultType = "Namespace"} + @{CompletionText = "System.Management.Automation.PerformanceData"; ResultType = "Namespace"} + @{CompletionText = "System.Management.Automation.Provider"; ResultType = "Namespace"} + @{CompletionText = "System.Management.Automation.Remoting"; ResultType = "Namespace"} + @{CompletionText = "System.Management.Automation.Runspaces"; ResultType = "Namespace"} + @{CompletionText = "System.Management.Automation.Security"; ResultType = "Namespace"} + ) + TestInput = 'using namespace System.Management.Automation.' + } | Get-CompletionTestCaseData | Test-Completions +} + +Describe "Type extensible completion of type after using namespace" -Tags "Innerloop", "BVT" { + @{ + ExpectedResults = @( + @{CompletionText = "IO.TextReader"; ResultType = "Type"} + ) + TestInput = 'using namespace System; [TextR' + }, + @{ + ExpectedResults = @( + @{CompletionText = "TextReader"; ResultType = "Type"} + ) + TestInput = 'using namespace System.IO; [TextR' + }, + @{ + ExpectedResults = @( + @{CompletionText = "Alias"; ResultType = "Type"} + ) + TestInput = '[aliasatt' + }, + @{ + ExpectedResults = @( + @{CompletionText = "string"; ResultType = "Type"} + ) + TestInput = 'using namespace System; [strin' + } | Get-CompletionTestCaseData | Test-Completions +} + +Describe "Additional type name completion tests" -Tags "Innerloop", "BVT" { + @{ + ExpectedResults = @( + @{CompletionText = "System"; ResultType = "Namespace"} + @{CompletionText = "System.Security.AccessControl.SystemAcl"; ResultType = "Type"} + ) + TestInput = 'Get-Command -ParameterType System' + }, + @{ + ExpectedResults = @( + @{CompletionText = "System.Action"; ResultType = "Type"} + @{CompletionText = "System.Activator"; ResultType = "Type"} + ) + TestInput = 'Get-Command -ParameterType System.' + }, + @{ + ExpectedResults = @( + @{CompletionText = "System.Collections.Generic.LinkedList"; ResultType = "Type"; ListItemText = "LinkedList<>"; ToolTip = "Class System.Collections.Generic.LinkedList[T]"} + @{CompletionText = "System.Collections.Generic.LinkedListNode"; ResultType = "Type"; ListItemText = "LinkedListNode<>"; ToolTip = "Class System.Collections.Generic.LinkedListNode[T]"} + @{CompletionText = "System.Collections.Generic.List"; ResultType = "Type"; ListItemText = "List<>"; ToolTip = "Class System.Collections.Generic.List[T]"} + ) + TestInput = 'Get-Command -ParameterType System.Collections.Generic.Li' + }, + @{ + ExpectedResults = @( + @{CompletionText = "System.Collections.Generic.Dictionary"; ResultType = "Type"; ListItemText = "Dictionary<>"; ToolTip = "Class System.Collections.Generic.Dictionary[TKey, TValue]"} + ) + TestInput = 'Get-Command -ParameterType System.Collections.Generic.Dic' + } | Get-CompletionTestCaseData | Test-Completions +} diff --git a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 new file mode 100644 index 000000000..a8b2e4f5f --- /dev/null +++ b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 @@ -0,0 +1,191 @@ + +Describe "Clone array" -Tags DRT { + It "Cast in target expr" { + (([int[]](42)).clone()) | Should Be 42 + (([int[]](1..5)).clone()).Length | Should Be 5 + (([int[]](1..5)).clone()).GetType() | Should Be ([int[]]) + + } + It "Cast not in target expr" { + $e = [int[]](42) + $e.Clone() | Should Be 42 + $e = [int[]](1..5) + $e.Clone().Length | Should Be 5 + $e.Clone().GetType() | Should Be ([int[]]) + } +} + +Describe "Set fields through PSMemberInfo" -Tags DRT { + Add-Type @" + public struct AStruct { public string s; } +"@ + + It "via cast" { + ([AStruct]@{s = "abc" }).s | Should Be "abc" + } + It "via new-object" { + (new-object AStruct -prop @{s="abc"}).s | Should Be "abc" + } + It "via PSObject" { + $x = [AStruct]::new() + $x.psobject.properties['s'].Value = 'abc' + $x.s | Should Be "abc" + } +} + +Describe "MSFT:3309783" -Tags DRT { + + It "Run in another process" { + # For a reliable test, we must run this in a new process because an earlier binding in this process + # could mask the bug/fix. + powershell -noprofile -command "[psobject] | % FullName" | Should Be System.Management.Automation.PSObject + } + + It "Run in current process" { + # For good measure, do the same thing in this process + [psobject] | % FullName | Should Be System.Management.Automation.PSObject + } + + It "Pipe objects derived from PSObject" { + # Related - make sure we can still pipe objects derived from PSObject + class MyPsObj : PSObject + { + MyPsObj($obj) : base($obj) { } + [string] ToString() { + # Don't change access via .psobject, that was also a bug. + return "MyObj: " + $this.psobject.BaseObject + } + } + + [MyPsObj]::new("abc").psobject.ToString() | Should Be "MyObj: abc" + [MyPsObj]::new("def") | Out-String | % Trim | Should Be "MyObj: def" + } +} + +Describe "ScriptBlockAst.GetScriptBlock throws on error" -Tags DRT { + + $e = $null + + It "with parse error" { + $ast = [System.Management.Automation.Language.Parser]::ParseInput('function ', [ref]$null, [ref]$e) + { $ast.GetScriptBlock() } | Should Throw + } + + + It "with semantic errors" { + $ast = [System.Management.Automation.Language.Parser]::ParseInput('function foo{param()begin{}end{[ref][ref]1}dynamicparam{}}', [ref]$null, [ref]$e) + + { $ast.GetScriptBlock() } | Should Throw + { $ast.EndBlock.Statements[0].Body.GetScriptBlock() } | Should Throw + } +} + +Describe "Hashtable key property syntax" -Tags DRT { + $script = @' + # First create a hashtable wrapped in PSObject + $hash = New-Object hashtable + $key = [ConsoleColor]::Red + $null = $hash.$key + $hash = @{} + $hash.$key = 'Hello' + # works in PS 2,3,4. Fails in PS 5: + $hash.$key +'@ + + It "In current process" { + # Run in current process, but something that ran earlier could influence + # the result + Invoke-Expression $script | Should Be Hello + } + + It "In different process" { + # So also run in a fresh process + $bytes = [System.Text.Encoding]::Unicode.GetBytes($script) + powershell -noprofile -encodedCommand ([Convert]::ToBase64String($bytes)) | Should Be Hello + } +} + +Describe "Assign automatic variables" -Tags DRT { + + $autos = '_', 'args', 'this', 'input', 'pscmdlet', 'psboundparameters', 'myinvocation', 'psscriptroot', 'pscommandpath' + + foreach ($auto in $autos) + { + It "Assign auto w/ invalid type constraint - $auto" { + { & ([ScriptBlock]::Create("[datetime]`$$auto = 1")) } | Should Throw $auto + { . ([ScriptBlock]::Create("[datetime]`$$auto = 1")) } | Should Throw $auto + { & ([ScriptBlock]::Create("[runspace]`$$auto = 1")) } | Should Throw $auto + { . ([ScriptBlock]::Create("[runspace]`$$auto = 1")) } | Should Throw $auto + { & ([ScriptBlock]::Create("[notexist]`$$auto = 1")) } | Should Throw $auto + { . ([ScriptBlock]::Create("[notexist]`$$auto = 1")) } | Should Throw $auto + } + } + + foreach ($auto in $autos) + { + It "Assign auto w/o type constraint - $auto" { + & ([ScriptBlock]::Create("`$$auto = 1; `$$auto")) | Should Be 1 + . ([ScriptBlock]::Create("`$$auto = 1; `$$auto")) | Should Be 1 + } + } + + It "Assign auto w/ correct type constraint" { + & { [object]$_ = 1; $_ } | Should Be 1 + & { [object[]]$args = 1; $args } | Should Be 1 + & { [object]$this = 1; $this } | Should Be 1 + & { [object]$input = 1; $input } | Should Be 1 + # Can't test PSCmdlet or PSBoundParameters, they use an internal type + & { [System.Management.Automation.InvocationInfo]$myInvocation = $myInvocation; $myInvocation.Line } | Should Match Automation.InvocationInfo + & { [string]$PSScriptRoot = 'abc'; $PSScriptRoot } | Should Be abc + & { [string]$PSCommandPath = 'abc'; $PSCommandPath } | Should Be abc + } +} + +Describe "Attribute error position" -Tags DRT { + It "Ambiguous overloads" { + try + { + & { + param( + [ValidateNotNull(1,2,3,4)] + $param + ) + } + throw "Should have thrown" + } + catch + { + $_.InvocationInfo.Line | Should Match ValidateNotNull + $_.FullyQualifiedErrorId | Should Be MethodCountCouldNotFindBest + } + } +} + +Describe "Multiple alias attributes" -Tags DRT { + It "basic test" { + function foo { + param( + [alias('aa')] + [alias('bb')] + $cc + ) + $cc + } + + foo -aa 1 | Should Be 1 + foo -bb 2 | Should Be 2 + foo -cc 3 | Should Be 3 + } +} + +Describe "Members of System.Type" -Tags DRT { + It "Members in public classes derived from System.Type should be found" { + class MyType : System.Collections.IEnumerable + { + [System.Collections.IEnumerator] GetEnumerator() { return $null } + } + + [type] | Get-Member ImplementedInterfaces | Should Be 'System.Collections.Generic.IEnumerable[type] ImplementedInterfaces {get;}' + [MyType].ImplementedInterfaces | Should Be System.Collections.IEnumerable + } +} diff --git a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 new file mode 100644 index 000000000..d3c87a2a5 --- /dev/null +++ b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 @@ -0,0 +1,78 @@ + +Describe "Interface inheritance with remoting proxies" -Tags "P1", "RI" { + $src = @" +using System; +using System.ServiceModel; + +namespace MSFT_716893 +{ + [ServiceContract] + public interface IInterface1 + { + [OperationContract]string BaseOperation(int i); + } + + [ServiceContract] + public interface IInterface2 : IInterface1 + { + [OperationContract(Name="op1")]string Operation(string a); + [OperationContract(Name="op2")]string Operation(string a, string b); + } + + public class ServiceImplementation : IInterface2 + { + public string Operation(string a) { return "1 - " + a; } + public string Operation(string a, string b) { return "2 - " + a + " " + b; } + public string BaseOperation(int i) { return "3 - " + i; } + } + + public static class Service + { + static ServiceHost serviceHost; + + public static void Init() + { + Uri baseAddress = new Uri("http://localhost:8080/service"); + serviceHost = new ServiceHost(typeof(ServiceImplementation), baseAddress); + serviceHost.Open(); + } + + public static IInterface1 GetProxy() + { + ChannelFactory factory = new ChannelFactory( + serviceHost.Description.Endpoints[0].Binding, + serviceHost.Description.Endpoints[0].Address); + return factory.CreateChannel(); + } + + public static void Close() + { + serviceHost.Close(); + } + } +} +"@ + + Add-Type -TypeDefinition $src -ReferencedAssemblies System.ServiceModel.dll + + BeforeEach { + [MSFT_716893.Service]::Init() + $proxy = [MSFT_716893.Service]::GetProxy() + } + + AfterEach { + [MSFT_716893.Service]::Close() + } + + It "Direct invocation" { + $proxy.Operation("a") | Should Be "1 - a" + $proxy.Operation("a", "b") | Should Be "2 - a b" + $proxy.BaseOperation(42) | Should Be "3 - 42" + } + + It "Invocation via method constraints" { + ([MSFT_716893.IInterface2]$proxy).Operation("c") | Should Be "1 - c" + ([MSFT_716893.IInterface2]$proxy).Operation("d", "e") | Should Be "2 - d e" + ([MSFT_716893.IInterface1]$proxy).BaseOperation(22) | Should Be "3 - 22" + } +} diff --git a/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 b/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 new file mode 100644 index 000000000..0d08df763 --- /dev/null +++ b/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 @@ -0,0 +1,79 @@ + +Describe 'Argument transformation attribute on optional argument with explicit $null' -Tags "P1", "RI" { + $mod = Add-Type -PassThru -TypeDefinition @' + using System; + using System.Management.Automation; + using System.Reflection; + + namespace MSFT_1407291 + { + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] + public class AddressTransformationAttribute : ArgumentTransformationAttribute + { + public override object Transform(EngineIntrinsics engineIntrinsics, object inputData) + { + return (ulong) 42; + } + } + + [Cmdlet(VerbsLifecycle.Invoke, "CSharpCmdletTakesUInt64")] + [OutputType(typeof(System.String))] + public class Cmdlet1 : PSCmdlet + { + [Parameter(Mandatory = false)] + [AddressTransformation] + public ulong Address { get; set; } + + protected override void ProcessRecord() + { + WriteObject(Address); + } + } + + [Cmdlet(VerbsLifecycle.Invoke, "CSharpCmdletTakesObject")] + [OutputType(typeof(System.String))] + public class Cmdlet2 : PSCmdlet + { + [Parameter(Mandatory = false)] + [AddressTransformation] + public object Address { get; set; } + + protected override void ProcessRecord() + { + WriteObject(Address ?? "passed in null"); + } + } + } +'@ + + Import-Module $mod[0].Assembly + + function Invoke-ScriptFunctionTakesObject + { + param([MSFT_1407291.AddressTransformation()] + [Parameter(Mandatory = $false)] + [object]$Address = "passed in null") + + return $Address + } + + function Invoke-ScriptFunctionTakesUInt64 + { + param([MSFT_1407291.AddressTransformation()] + [Parameter(Mandatory = $false)] + [Uint64]$Address = 11) + + return $Address + } + + + Invoke-ScriptFunctionTakesObject | Should Be 42 + Invoke-ScriptFunctionTakesUInt64 | Should Be 42 + Invoke-CSharpCmdletTakesObject | Should Be "passed in null" + Invoke-CSharpCmdletTakesUInt64 | Should Be 0 + + Invoke-ScriptFunctionTakesObject -Address $null | Should Be 42 + Invoke-ScriptFunctionTakesUInt64 -Address $null | Should Be 42 + Invoke-CSharpCmdletTakesObject -Address $null | Should Be 42 + Invoke-CSharpCmdletTakesUInt64 -Address $null | Should Be 42 +} diff --git a/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 b/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 new file mode 100644 index 000000000..9e641acff --- /dev/null +++ b/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 @@ -0,0 +1,69 @@ +Describe "Redirection operator now supports encoding changes" { + BeforeAll { + $asciiString = "abc" + $asciiCR = "`r`n" + + # If out-file -encoding happens to have a default, be sure to + # save it away + $SavedValue = $null + $oldDefaultParameterValues = $psDefaultParameterValues + $psDefaultParameterValues = @{} + } + AfterAll { + # be sure to tidy up afterwards + $psDefaultParameterValues = $oldDefaultParameterValues + } + BeforeEach { + # start each test with a clean plate! + $psdefaultParameterValues.Remove("out-file:encoding") + } + AfterEach { + # end each test with a clean plate! + $psdefaultParameterValues.Remove("out-file:encoding") + } + + It "If encoding is unset, redirection should be Unicode" { + $asciiString > TESTDRIVE:\file.txt + $bytes = get-content -encoding byte TESTDRIVE:\file.txt + # create the expected + $BOM = [text.encoding]::unicode.GetPreamble() + $TXT = [text.encoding]::unicode.GetBytes($asciiString) + $CR = [text.encoding]::unicode.GetBytes($asciiCR) + $expectedBytes = .{ $BOM; $TXT; $CR } + $bytes.Count | should be $expectedBytes.count + for($i = 0; $i -lt $bytes.count; $i++) { + $bytes[$i] | Should be $expectedBytes[$i] + } + } + + # $availableEncodings = "unknown","string","unicode","bigendianunicode","utf8","utf7", "utf32","ascii","default","oem" + $availableEncodings = (get-command out-file).Parameters["Encoding"].Attributes.ValidValues + + foreach($encoding in $availableEncodings) { + # some of the encodings accepted by out-file aren't real, + # and out-file has its own translation, so we'll + # not do that logic here, but simply ignore those encodings + # as they eventually are translated to "real" encoding + $enc = [system.text.encoding]::$encoding + if ( $enc ) + { + $msg = "Overriding encoding for out-file is respected for $encoding" + $BOM = $enc.GetPreamble() + $TXT = $enc.GetBytes($asciiString) + $CR = $enc.GetBytes($asciiCR) + $expectedBytes = .{ $BOM; $TXT; $CR } + $psdefaultparameterValues["out-file:encoding"] = "$encoding" + $asciiString > TESTDRIVE:/file.txt + $observedBytes = Get-Content -encoding Byte TESTDRIVE:/file.txt + # THE TEST + It $msg { + $observedBytes.Count | Should be $expectedBytes.Count + for($i = 0;$i -lt $observedBytes.Count; $i++) { + $observedBytes[$i] | Should be $expectedBytes[$i] + } + } + + } + } + +} diff --git a/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 b/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 new file mode 100644 index 000000000..fe011a453 --- /dev/null +++ b/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 @@ -0,0 +1,24 @@ + +Describe "Type accelerators" -Tags "DRT" { + $TypeAcceleratorsType = [psobject].Assembly.GetType("System.Management.Automation.TypeAccelerators") + + $TypeAccelerators = $TypeAcceleratorsType::Get + $TypeAcceleratorsType::Add('msft_2174855', [int]) + $TypeAcceleratorsType::Add('msft_2174855_rm', [int]) + + It "Basic type accelerator usage" { + [msft_2174855] | Should Be ([int]) + } + + It "Can query type accelerators" { + $TypeAccelerators.Count -gt 82 | Should Be $true + $TypeAccelerators['xml'] | Should Be ([System.Xml.XmlDocument]) + $TypeAccelerators['AllowNull'] | Should Be ([System.Management.Automation.AllowNullAttribute]) + } + + It "Can remove type accelerator" { + $TypeAcceleratorsType::Get['msft_2174855_rm'] | Should Be ([int]) + $TypeAcceleratorsType::Remove('msft_2174855_rm') + $TypeAcceleratorsType::Get['msft_2174855_rm'] | Should Be $null + } +} diff --git a/test/powershell/Language/Parser/UsingAssembly.Tests.ps1 b/test/powershell/Language/Parser/UsingAssembly.Tests.ps1 new file mode 100644 index 000000000..deb420045 --- /dev/null +++ b/test/powershell/Language/Parser/UsingAssembly.Tests.ps1 @@ -0,0 +1,113 @@ + +Describe "Using assembly" -Tags "DRT" { + + try + { + pushd $PSScriptRoot + $guid = [Guid]::NewGuid() + + Add-Type -OutputAssembly $PSScriptRoot\UsingAssemblyTest$guid.dll -TypeDefinition @" +public class ABC {} +"@ + + It 'parse reports error on non-existing assembly by relative path' { + $err = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput("using assembly foo.dll", [ref]$null, [ref]$err) + + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be ErrorLoadingAssembly + } + + It 'parse reports error on assembly with non-existing fully qualified name' { + $err = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput("using assembly 'System.Management.Automation, Version=99.0.0.0'", [ref]$null, [ref]$err) + + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be ErrorLoadingAssembly + } + + It 'not allow UNC path' { + $err = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput("using assembly \\networkshare\foo.dll", [ref]$null, [ref]$err) + + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be CannotLoadAssemblyFromUncPath + } + + It 'not allow http path' { + $err = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput("using assembly http://microsoft.com/foo.dll", [ref]$null, [ref]$err) + + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be CannotLoadAssemblyWithUriSchema + } + + It "parse does not load the assembly" { + $assemblies = [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name + $assemblies -contains "UsingAssemblyTest$guid" | Should Be $false + + $err = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput("using assembly .\UsingAssemblyTest$guid.dll", [ref]$null, [ref]$err) + + $assemblies = [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name + $assemblies -contains "UsingAssemblyTest$guid" | Should Be $false + $err.Count | Should Be 0 + + $ast = [System.Management.Automation.Language.Parser]::ParseInput("using assembly '$PSScriptRoot\UsingAssemblyTest$guid.dll'", [ref]$null, [ref]$err) + + $assemblies = [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name + $assemblies -contains "UsingAssemblyTest$guid" | Should Be $false + $err.Count | Should Be 0 + + $ast = [System.Management.Automation.Language.Parser]::ParseInput("using assembly `"$PSScriptRoot\UsingAssemblyTest$guid.dll`"", [ref]$null, [ref]$err) + + $assemblies = [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name + $assemblies -contains "UsingAssemblyTest$guid" | Should Be $false + $err.Count | Should Be 0 + } + + It "reports runtime error about non-existing assembly with relative path" { + $failed = $true + try { + [scriptblock]::Create("using assembly .\NonExistingAssembly.dll") + $failed = $false + } catch { + $_.FullyQualifiedErrorId | Should be 'ParseException' + $_.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should be 'ErrorLoadingAssembly' + } + $failed | Should be $true + } +#> + It "Assembly loaded at runtime" { + $assemblies = powershell -noprofile -command @" + using assembly .\UsingAssemblyTest$guid.dll + [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name +"@ + $assemblies -contains "UsingAssemblyTest$guid" | Should Be $true + + $assemblies = powershell -noprofile -command @" + using assembly $PSScriptRoot\UsingAssemblyTest$guid.dll + [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name +"@ + $assemblies -contains "UsingAssemblyTest$guid" | Should Be $true + + + $assemblies = powershell -noprofile -command @" + using assembly System.Drawing + [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name +"@ + $assemblies -contains "System.Drawing" | Should Be $true + + $assemblies = powershell -noprofile -command @" + using assembly 'System.Drawing, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' + [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name +"@ + $assemblies -contains "System.Drawing" | Should Be $true + } + } + finally + { + Remove-Item .\UsingAssemblyTest$guid.dll + popd + } +} diff --git a/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 new file mode 100644 index 000000000..b2ad631cc --- /dev/null +++ b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 @@ -0,0 +1,154 @@ + +# There is an automatic 'using namespace system' which is +# tested by this test, so don't uncomment the following: +#using namespace System +using namespace System.Threading +using namespace System.Timers +# Test parsing more than one using statement on one line +using namespace System.Diagnostics; using namespace System.Runtime.CompilerServices +using namespace System.Collections.Generic + +Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 + +function ShouldBeErrorId +{ + param([Parameter(ValueFromPipeline, Mandatory)] + [ScriptBlock] + $sb, + + [Parameter(Mandatory, Position=0)] + [string] + $FullyQualifiedErrorId) + + try + { + & $sb + throw "Unexpected" + } + catch + { + $_.FullyQualifiedErrorId | Should Be $FullyQualifiedErrorId + } +} + +# Flags is System.FlagsAttribute +# This tests our implicit 'using namespace System' +# despite having other explicit using namespace statements. +[Flags()] +enum E1 +{ + E1 = 0x01 + E2 = 0x02 + E4 = 0x04 +} + +# Test attributes that won't be found w/o using, but w/o implicit Attribute suffix +[CompilerGenerated()] +class C1 +{ + [Thread][CompilerGenerated()]$Thread + [Int32][CompilerGenerated()]$Int + [ElapsedEventHandler][CompilerGenerated()]$EventHandler +} + +# Test attributes that won't be found w/o using, but w/ implicit Attribute suffix +[CompilerGeneratedAttribute()] +class C2 +{ + [Thread][CompilerGeneratedAttribute()]$Thread + [Int32][CompilerGeneratedAttribute()]$Int + [ElapsedEventHandler][CompilerGeneratedAttribute()]$EventHandler +} + +Describe "Using Namespace" -Tags "DRT" { + It "Type literals w/ using namespace" { + [Thread].FullName | Should Be System.Threading.Thread + [Int32].FullName | Should Be System.Int32 + [ElapsedEventHandler].FullName | Should Be System.Timers.ElapsedEventHandler + + [C1].GetProperty("Thread").PropertyType.FullName | Should Be System.Threading.Thread + [C1].GetProperty("Int").PropertyType.FullName | Should Be System.Int32 + [C1].GetProperty("EventHandler").PropertyType.FullName | Should Be System.Timers.ElapsedEventHandler + } + + It "Covert string to Type w/ using namespace" { + ("Thread" -as [Type]).FullName | Should Be System.Threading.Thread + ("Int32" -as [Type]).FullName | Should Be System.Int32 + ("ElapsedEventHandler" -as [Type]).FullName | Should Be System.Timers.ElapsedEventHandler + + New-Object Int32 | Should Be 0 + New-Object CompilerGeneratedAttribute | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + } + + It "Attributes w/ using namespace" { + function foo + { + [DebuggerStepThrough()] + param( + [CompilerGeneratedAttribute()] + $a, + + [CompilerGenerated()] + $b + ) + + "OK" + } + + foo | Should Be OK + $cmdInfo = gcm foo + $cmdInfo.ScriptBlock.Attributes[0] | Should Be System.Diagnostics.DebuggerStepThroughAttribute + $cmdInfo.Parameters['a'].Attributes[1] | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + $cmdInfo.Parameters['b'].Attributes[1] | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + + [C1].GetProperty("Thread").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + [C1].GetProperty("Int").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + [C1].GetProperty("EventHandler").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + + [C2].GetProperty("Thread").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + [C2].GetProperty("Int").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + [C2].GetProperty("EventHandler").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + + [C1].GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + [C2].GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + + [E1].GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.FlagsAttribute + } + + It "Ambiguous type reference" { + { [Timer] } | ShouldBeErrorId AmbiguousTypeReference + } + + It "Parameters" { + function foo([Thread]$t = $null) { 42 } + + foo | Should Be 42 + + $mod = New-Module -Name UsingNamespaceModule -ScriptBlock { + function Set-Thread([Thread]$t = $null) + { + 44 + } + } + Import-Module $mod + Set-Thread | Should Be 44 + Remove-Module $mod + } + + It "Generics" { + function foo([List[string]]$l) + { + $l | Should Be "a string" + } + + $l = [List[string]]::new() + $l.Add("a string") + foo $l + } + + ShouldBeParseError "1; using namespace System" UsingMustBeAtStartOfScript 3 + ShouldBeParseError "using namespace Foo = System" UsingStatementNotSupported 0 + # TODO: add diagnostic (low pri) + # ShouldBeParseError "using namespace System; using namespace System" UsingNamespaceAlreadySpecified 24 +} + diff --git a/test/powershell/Language/Scripting/Debugging/DebuggerScriptTests.Tests.ps1 b/test/powershell/Language/Scripting/Debugging/DebuggerScriptTests.Tests.ps1 new file mode 100644 index 0000000000000000000000000000000000000000..82a342a361fbf2006ecc8025a25276ebeb9cb922 GIT binary patch literal 39238 zcmeHQZF3aI5#Fy(RsO@3Py&?E1rYcJ+mrztL(13$F!|t6Eq=T_Z)c|8yQimT_P_r*QJtv%Sp5ONPgIlYTD4Q{R&T1kYO8u$ zJ*y6I?^N};I*scs+TGqcx_H=k9OG{2 zWecs?`$n}{J*r+}w5MqO0>3A?zlkvp@S|3|^5={24WZaVt4%z+QeDM=n;8Aa00;YE z%L#rq!w7_Ef-zr)K6k2Z{C6GgpX2+xz?CCyhP%7yX{UOH?+Dc%o_jKT&iZ?XZ(pL_ z2EKV6aQ+eFPpUQa@DPymsgu=BjQ1k6e~xEJ&y#^7CjtGP>K^`^R6hl5CisgV<3Rk6 ze|?H4jS81*&+57O#64iKi>EKoM)i&Q3MEf_yy#B@TLGP0n=FJLO-UA1OT#0Lhzd?}OY-F@oKFP+h|3t3Y*1 zJjYgxQ?NVT{eOd=wgdd^eOfrM}$^0?9i zg6qL5Jg;&{d`xLEw_&>|t(&l|KCNg$^KaC*QN*LlYv4*rBqvV-?q~SxIX*l1n>Oci zqt80r?A7aei2O(+DLZYpwf{Jz(`)dH{MZOMz6v<5fr8@vL6949_X_w%tIQTno9^5G zHS|ntO`nugUeYKEzL;@)IJNErZt~~}C_}qRtHSa47rFKb-_||Z1kcmzn3bX5;j^@K zv~!dq^O)8{9ozm?-VOad3-+!_%}dokT*+81z)Vdv|7%?EF`J^-oYs=^$&7;!VSf}= z&}-6<)1UJm`9P}u;zIUkd{gdUyn7E%+2>~9gda^r>T~p$VSM_1v$pS5u@|2n)9MD` zJqXd2wVqTzqy1g*GDZ|v12*RZHnGPXyZk@u8g+nL-p3En+Umv#HfhLltHUJSUOV&-bv>H<;>uDcik+HyFhX}19*9~lD-~tBsaK4XFW2Z=vJ}$a@3V15lH*hL!=DUv1IdDgDWZ%dw<%+D} zli+nyUr6nF6f}%J@-hDN+WGSV^~xBW^Y-mv^J&u=nKDKx!>YEjeC7+(j(WUV{R{Lv z8*&EiWU-H@%q0x&XVbkyV@{cg$w z(voqI@j|22uk4faS3auBYQJUXamPa0~ zPP_8F=5CP4uE<qjpa-{jp1Nl*QAJU`v`&kKss3`4Hc(L7!7Q;l<3K z;JGs089CQuqnwU0I%PCf%E8J1Q@fdc+Y9p*i;9}|O!J#%N7!Eh4$iR{1@0kB#yMED zIuSFYk0d7lkCHhg?v578p9hsAzLGo~ZCq{K?~13}B6G>;B4SWR>Mt@Ge+(XS4#XJi zF|OnLIt)zemcJxn*{C5s|XwWLc5&4M@7>K~wnZc3R2o-Te|$ykUE_ZY%#Tw z%6E3hwq4P&Kck{{m$S^q^XOG5*o6bb&ct$VWh?#k4YazP$4qdKRSjHIS;Lk0 z*w-%pVpW0eyo-6-)QcZwe0&knv3=-aj*5TLiymd|LQ3OY3oL8(S4bsgF4wW2AbBsa zhH@#`=WR&hFQ8O&%|4~%bXNZ|MH{wIL`euzDEdb5mIYc1>vmjoBt*^i;Ysy5*4NYi zWwYtcreOy3v-|y@fRU}9{eZuSA1xQxN%k<#8m^D3e_~YTbNHP%##4OHXX$IVka^{r zD4#nCsCf0-_|}&8pqyu7e(4M{jb{QrePWWf98+9Dv@x4GC&Av=liOO8Y?ZAa&zDo$ zN3F`)>cIkhIB^+^2mRUnc$^!E$9j;P2y^s7@m_mmRp$ru0Q2KQS49 z#x87gl(CDyvd}&wW=`!n;H+EnG>-9XP9DETc}kz92eul?xK_%Fz;*b_vmv`}7TK%# z{5z~4XNy)-LHnt?opgj8YCWOZpy!EB!3v~RqEpm&ehZtRwrTwu^IXg{=sT|H)VK>3)#}hvoZEbw{7gSTC8&sP-D8zJ)jS?*h-X^G{Qg&XpI=c!XVqTc zCcf`iXp*hz=6BP|K1L8v!I$fhgL-9e+A5cZC$mvG{&ZHup&m-LdG!@JU#2v}=i1q{ zAeAXBePfa_zFV9pOk=&cWKGv@4<1J)p8RS0j;O7iZ&+T@ul?0((%aLKOFwm0l+vBakiORKn|c}(7sNhOTc;kqm>`b~ zyllsJVWv~6t$GD3!nJtTnvsHO1-oBi`!41++G!MRsJ9+Xs`ahho3H*%azM);>yV-! z;A}MdnPyFrWP3I79jZU7x6@d@6sJAd9GE*G7*VofReos|d!XKg{XMThA?CVOo6M+? z*^2zGDnC=%kMb>u@^y<7!1+Ej-s(xV@E7^P$d;qUiWIINv6h1QyF9OF6}PIVQfp@P zHp7kw%O_}Oi}pyd^4FJ!6#5+2X8u1y{suK=jGW{Ck*lC6>GLDzaO7(aHCGbrk@hX% z%sLB3lHB3J?8~&e0?p>y{dS;;?LwNmHt~CmWMkDa&u2a*nXpQlml%gL9?He%z{+wJ zIrLFBy%;Cbz^8Ga)shsJX|GCh&ONxpCGtCMm&apGsg!hTwa4lv+N#4Dfs?N(q&c0{ zr_no7O81pfuq?F7b-+Eum-xm+LC))<7U%uu^5yGsb7mzD_|NNGPtQq_TyylaWH+^Vl-qQN zhj>Byv+{_Pk9C7h`ui)V8j&+gTBd#UvRv8Ji01KFKb1;`zHO%_b;Ne5nk8MWet<1+ zN{VLT+PBIolj-5kx}o$J#m(E1!l^wpkC;nT7tJFUWoyvnq%SkM1rQzcyM63}Vtd;! zt$uR*(bNK;UwNz87uQ~BA6dsqc{2-Qf2%fwt4h9yV-#ZX3$>chGnU~la`t5VoIRQH zp?Ny1+OS+HD>x(&3+{RxcP-1$n&`4W9GdnDM>*+Wj<_7U=&kTOcjGy3}xu3`Li zXQ=c{hv?G5gyU&b3N;%5hU}1)*FG(w2feRNVjy=22DIMY+VQqJOTFSCSYEWw!SJ-GCMq~IGVWzLjVNUOeJ1NZ3hD56xF*a_ioxyIW zVKD1u^FK;(KTIS~>j{+Bx9->srJv*q^7=K766sD`kblSpj{Q8y8fU;f*@372)GOME zTNi%K8A@LrjB=;l@+M0wVSPBaCRc;sqetd!_kMt$QX4ajO*s6MNm40mwiyn)WJfw< zUM$UM`Eeli;TQaLW7~w%F!MO$TS#)eX<5>lc>lgq`t_$)I!`Fa5W{Ur+OLPh7-)Td z(Ib;z^-pSnmS;UuJQZ^hBF^xJ+l>A&9}hc`Vo&LL8|_6MhxC7O)S``LV&~}ytwXLS zaW#cApXW0x`wD-@Dg@32l-IC5q8oV9@<_}S?1fnqD>Lh7uSkPdVZXNypJIzQhj}cE z=IiSn<;tQjiaph&XQs*&J_*Y zA!qAD=?(#ZT}}NzfOpSF_G1#w9=4!SHpf&CM**SnEskA|F2+gL!7OLOSVz$kv&fz| zl@>Oyb;(mt^n&`(lJTrb%h7o9F$ROO^bExyofUa=%K2v*bzVj}8W$;uvh}MZHr4!e z#>80%xzRs$2)ZWQrD<~*3w?QEC^+=OAedyegC*x zvcBx7$iu<6wWLdPu3>p?RwQs|RsY_`NkMLNDGT`>o?3aParZp#47Ez6yQkI4b>!oE zcpp86!*s!FIwF=)7mkMpuzsNa(|)bEeg2kR&I z49nJsI_$&o9H$k+^|)z8lO9JiYtGl^Ifr0<8uR>p&fkpnii+-Rt);y4I(?22&K*SR z^Vb4L-*4+M-Qz14v#9aqQy9;dM~_b}5_d(Wea2o6-i#i*@#a&D#9O-DK6ZR+@nd&M zn~l9_np>}3G>=aW$J_IA(skA5u&}SpPO%Vc)NB3+R z$ytxPzE7dntBRFxA!636UW*;Af>-mlk5;y8(JIqf_ha!fcb0kYAmHg&w5#Ql2lE|w zrBUN5G5hO}v%k-X(fnulQKe_e+g`vkeNN%|1kd^W^T%iG-e>&bClg9;boIyQJgv|8 z)XSk1@OU#tFFuV+65P+jx7k$n{2s=(59fIF@LsGUxo-OC`Oec}Mc0j>1B}lYwGal* ze!V?z7RN-0quq=5`e0$vk>$*+nqtv-4Lfzw-Zl_%7>k!>l`O89_oO4%OwP+0Nmj1= z<1?0?_3O%Bmf8%@um~GnmCNvLNLQZ8gKN=QCvS|d98{^z;^@^s8+96XV0bQy7Wug3 zeV8ZTfL)_b{(i__mvsS#D?> zncfx5=@qSsX1Z?UBnzwXtYg?5)86xzuHKh=jWcz=#nY3(X1tVYR=?=9ea4x>psZ8- z7|nUJKWmQBm8hueFs$awM#WrO8&=EBN2MdDIVF$d?c93%b5fBXnl+WE^Y&z;hT-&f z=Ank+IDZDp(~Zq=rh}cLxt4iho){G0OP5MiKO4D-o_#yjx92Wx@aPe?8NfE<4Yd!XDHhMG1=#Q=(;}ztiwI|kQ z#mrALU&i{cwzp9G`3Yt&^BiYs{EK*U4RfHr<9qseE74cq@_DWL*dDfvnX|U+A2UVG z`6hGp`D*FBnX{wKX)9X+B3uR!Nk7Dz|@`^yNbx1E0W$X2TMLX5wIn*YsOyEb|Cv*78d%?wU=KD@m>>A4rEYA0&4 z0)qEgnZ(szTdQVF!CLaJ_`>LCzlkY-yb8*@uyULSF;d}mA7gc|qbt(f!rptH=|(Es zy9E1IN!nW+ygJDt?~nd1pH1Z_pBr#iG`70a=rUh5XAYU-FTVfc1=f%4)RRkqkne;3 z5O2=7P^_%3mK<|E9(xW>CU$>7{1zDF;uzu-Hc^O^{wo~*Uxasrv>8uSD|JXa~s zHcvtqC`TK(`v~7Y4d+2nlc*zcuktjT*Tzv6`|j7@QX+?qEvq0(*H5_ibNUwbdo`H( z`T53S))1R;nqyMlxhEIRdJeRR2K|;su&6MUWX8vPZn8PNB z)4A$yIP-K-W0DhP>D0LQVAC0&rG7d6yX;l@j+n0cpt4@mf~2#ccIu3Wvd{jt5|pNA KWhf11`~5$OZeMi( literal 0 HcmV?d00001 diff --git a/test/powershell/Language/Scripting/Debugging/DebuggingInHost.Tests.ps1 b/test/powershell/Language/Scripting/Debugging/DebuggingInHost.Tests.ps1 new file mode 100644 index 000000000..81628df62 --- /dev/null +++ b/test/powershell/Language/Scripting/Debugging/DebuggingInHost.Tests.ps1 @@ -0,0 +1,82 @@ +## +## Copyright (c) Microsoft Corporation +## +## Debugging in Host tests +## + +Describe "Tests Debugger GetCallStack() on runspaces when attached to a WinRM host process" -Tags 'InnerLoop','P1' { + + It -skip "Disabled test because it is fragile and does not consistently succeed on test VMs" { } + return + + try + { + # Create PSSession + $wc = [System.Management.Automation.Runspaces.WSManConnectionInfo]::new() + $rs = [runspacefactory]::CreateRunspace($host, $wc) + $rs.Open() + + # Get WinRM host process id + [powershell] $ps = [powershell]::Create() + $ps.Runspace = $rs + $result = $ps.AddScript('$pid').Invoke() + It "Verifies the WinRM host process Id was found" { + $result | Should Not Be $null + ($result.Count -eq 1) | Should Be $true + } + [int]$winRMHostProcId = $result[0] + + # Run script to stop at breakpoint + $ps.Commands.Clear() + $ps.AddScript('"Hello"; Wait-Debugger; "Goodbye"').BeginInvoke() + + # Attach to process. + Enter-PSHostProcess -Id $winRMHostProcId + + # Get local remote runspace to attached process + $hostRS = Get-Runspace -Name PSAttachRunspace + It "Verifies that the attached-to host runspace was found" { + $hostRS | Should Not Be $null + ($hostRS.RunspaceStateInfo.State -eq 'Opened') | Should Be $true + } + + # Wait for host runspace to become available. + $count = 0 + while (($hostRS.RunspaceAvailability -ne 'Available') -and ($count++ -lt 60)) + { + sleep -Milliseconds 500 + } + It "Verifies that the attached-to host runspace is available" { + ($hostRS.RunspaceAvailability -eq 'Available') | Should Be $true + } + + # Get call stack from default runspace. + $script = @' + $rs = Get-Runspace -Id 1 + if ($rs -eq $null) { throw 'Runspace not found' } + return $rs.Debugger.GetCallStack() +'@ + [powershell]$psHost = [powershell]::Create() + $psHost.Runspace = $hostRS + $psHost.AddScript($script) + $stack = $psHost.Invoke() + + # Detach from process + Exit-PSHostProcess + + It "Verifies a call stack was returned from the attached-to host." { + $stack | Should Not Be $null + ($stack.Count -gt 0) | Should Be $true + } + } + finally + { + # Clean up + if ($host.IsRunspacePushed) { $host.PopRunspace() } + + if ($psHost -ne $null) { $psHost.Dispose() } + if ($hostRS -ne $null) { $hostRS.Dispose() } + if ($ps -ne $null) { $ps.Dispose() } + if ($rs -ne $null) { $rs.Dispose() } + } +} From 2674564542ba388c4efd395b8baaec6d0f5241d5 Mon Sep 17 00:00:00 2001 From: PowerShell Team Date: Tue, 19 Jul 2016 17:21:32 -0700 Subject: [PATCH 05/25] Language tests now run clean on Linux --- .../Classes/ProtectedAccess.Tests.ps1 | 6 +- .../Scripting.Classes.Attributes.Tests.ps1 | 8 +- .../Scripting.Classes.BasicParsing.Tests.ps1 | 89 ++++++++++-------- .../Scripting.Classes.Modules.Tests.ps1 | 16 ++-- .../scripting.Classes.NestedModules.tests.ps1 | 20 ++-- .../scripting.Classes.inheritance.tests.ps1 | 16 ++-- .../Classes/scripting.Classes.using.tests.ps1 | 48 +++++----- .../Parser/ExtensibleCompletion.Tests.ps1 | 26 ++--- .../LanguageAndParser.TestFollowup.Tests.ps1 | 18 ++-- .../Parser/MethodInvocation.Tests.ps1 | 5 +- .../Parser/ParameterBinding.Tests.ps1 | 37 ++++++-- .../Parser/RedirectionOperator.Tests.ps1 | 9 +- .../Language/Parser/TypeAccelerator.Tests.ps1 | 3 +- .../Language/Parser/UsingAssembly.Tests.ps1 | 6 +- .../Language/Parser/UsingNamespace.Tests.ps1 | 19 ++-- .../Debugging/DebuggerScriptTests.Tests.ps1 | Bin 39238 -> 39806 bytes 16 files changed, 184 insertions(+), 142 deletions(-) diff --git a/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 b/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 index 2c79c9d99..e9b7be2fb 100644 --- a/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 +++ b/test/powershell/Language/Classes/ProtectedAccess.Tests.ps1 @@ -138,7 +138,7 @@ class Derived2 : Base {} [Derived2]::new() '@ -Describe "Protected Member Access - w/ default ctor" -Tags "DRT" { +Describe "Protected Member Access - w/ default ctor" -Tags "CI" { It "Method Access" { $derived1.TestMethodAccess() | Should Be 42 } It "Dynamic Method Access" { $derived1.TestDynamicMethodAccess() | Should Be 42 } It "Field Access" { $derived1.TestFieldAccess() | Should Be 11 } @@ -157,7 +157,7 @@ Describe "Protected Member Access - w/ default ctor" -Tags "DRT" { It "Implicit ctor calls protected ctor" { $derived3.OverloadedMethod2(42) | Should Be 84 } } -Describe "Protected Member Access - w/ non-default ctor" -Tags "DRT" { +Describe "Protected Member Access - w/ non-default ctor" -Tags "CI" { It "Method Access" { $derived2.TestMethodAccess() | Should Be 52 } It "Dynamic Method Access" { $derived2.TestDynamicMethodAccess() | Should Be 52 } It "Field Access" { $derived2.TestFieldAccess() | Should Be 11 } @@ -175,7 +175,7 @@ Describe "Protected Member Access - w/ non-default ctor" -Tags "DRT" { It "Method Access - overloaded 3b" { $derived2.TestOverloadedMethodAccess3b() | Should Be 21 } } -Describe "Protected Member Access - members not visible outside class" -Tags "DRT" { +Describe "Protected Member Access - members not visible outside class" -Tags "CI" { Set-StrictMode -v 3 It "Invalid protected field Get Access" { { $derived1.Field } | Should Throw } It "Invalid protected property Get Access" { { $derived1.Property } | Should Throw } diff --git a/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 index 943b054f5..77dac3bc2 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.Attributes.Tests.ps1 @@ -1,4 +1,4 @@ -Describe 'Attributes Test' -Tags "innerloop", "DRT" { +Describe 'Attributes Test' -Tags "CI" { BeforeAll { $dummyAttributesSource = @' @@ -45,7 +45,7 @@ namespace Dummy } } '@ - Add-Type -TypeDefinition $dummyAttributesSource -ReferencedAssemblies "System.Management.Automation" + Add-Type -TypeDefinition $dummyAttributesSource -ReferencedAssemblies "System.Management.Automation","mscorlib" } @@ -197,7 +197,7 @@ namespace Dummy } } -Describe 'Type resolution with attributes' { +Describe 'Type resolution with attributes' -Tag "CI" { # There is kind of a collision between names # System.Diagnostics.Tracing.EventSource # System.Diagnostics.Tracing.EventSourceAttribute @@ -215,4 +215,4 @@ Describe 'Type resolution with attributes' { } } -} \ No newline at end of file +} diff --git a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 index 157a7024e..b11782c27 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 @@ -3,7 +3,7 @@ # Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 -force -Describe 'Positive Parse Properties Tests' -Tags "innerloop", "DRT" { +Describe 'Positive Parse Properties Tests' -Tags "CI" { It 'PositiveParsePropertiesTest' { # Just a bunch of random basic things here # This test doesn't need to check anything, if there are @@ -248,7 +248,7 @@ Describe 'Positive Parse Properties Tests' -Tags "innerloop", "DRT" { [A]::new().h.GetType().Name | Should Be 'OrderedDictionary' } } -Describe 'Negative Parsing Tests' -Tags "innerloop", "DRT" { +Describe 'Negative Parsing Tests' -Tags "CI" { ShouldBeParseError 'class' MissingNameAfterKeyword 5 ShouldBeParseError 'class foo' MissingTypeBody 9 ShouldBeParseError 'class foo {' MissingEndCurlyBrace 10 @@ -309,7 +309,7 @@ Describe 'Negative Parsing Tests' -Tags "innerloop", "DRT" { ShouldBeParseError 'class C : B' MissingTypeBody 11 } -Describe 'Negative methods Tests' -Tags "innerloop", "DRT" { +Describe 'Negative methods Tests' -Tags "CI" { ShouldBeParseError 'class foo { f() { param($x) } }' ParamBlockNotAllowedInMethod 18 ShouldBeParseError 'class foo { f() { dynamicparam {} } }' NamedBlockNotAllowedInMethod 18 ShouldBeParseError 'class foo { f() { begin {} } }' NamedBlockNotAllowedInMethod 18 @@ -320,13 +320,13 @@ Describe 'Negative methods Tests' -Tags "innerloop", "DRT" { ShouldBeParseError 'class foo { [void] bar($a, [string][int]$b, $c) {} }' MultipleTypeConstraintsOnMethodParam 35 } -Describe 'Negative Assignment Tests' -Tags "innerloop", "DRT" { +Describe 'Negative Assignment Tests' -Tags "CI" { ShouldBeParseError 'class foo { [string ]$path; f() { $path="" } }' MissingThis 34 ShouldBeParseError 'class foo { [string ]$path; f() { [string] $path="" } }' MissingThis 43 ShouldBeParseError 'class foo { [string ]$path; f() { [int] [string] $path="" } }' MissingThis 49 } -Describe 'Negative Assignment Tests' -Tags "innerloop", "DRT" { +Describe 'Negative Assignment Tests' -Tags "CI" { ShouldBeParseError '[DscResource()]class C { [bool] Test() { return $false } [C] Get() { return $this } Set() {} }' DscResourceMissingKeyProperty 0 # Test method @@ -350,7 +350,7 @@ Describe 'Negative Assignment Tests' -Tags "innerloop", "DRT" { ShouldBeParseError '[DscResource()]class C { [DscProperty(Key)][string]$Key; [bool] Test() { return $false } [C] Get() { return $this } Set() {} C($a) { } }' DscResourceMissingDefaultConstructor 0 } -Describe 'Negative DscResources Tests' -Tags "innerloop", "DRT" { +Describe 'Negative DscResources Tests' -Tags "CI" { # Usage errors ShouldBeParseError '[Flags()]class C{}' AttributeNotAllowedOnDeclaration 0 ShouldBeParseError 'class C { [Flags()]$field; }' AttributeNotAllowedOnDeclaration 10 @@ -367,32 +367,42 @@ Describe 'Negative DscResources Tests' -Tags "innerloop", "DRT" { ShouldBeParseError 'class C{ [ValidateScript({})]$p; }' ParameterAttributeArgumentNeedsToBeConstant 25 } -Describe 'Negative ClassAttributes Tests' -Tags "innerloop", "DRT" { +Describe 'Negative ClassAttributes Tests' -Tags "CI" { + [System.Management.Automation.Cmdlet("Get", "Thing")]class C{} + $t = [C].GetCustomAttributes($false) + + It "Should have one attribute" {$t.Count | should be 1} + It "Should have instance of CmdletAttribute" {$t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } - [System.Management.Automation.Cmdlet("Get", "Thing")]class C{} - $t = [C].GetCustomAttributes($false) + [System.Management.Automation.CmdletAttribute]$c = $t[0] + It "Verb should be Get" {$c.VerbName | should be 'Get'} + It "Noun should be Thing" {$c.NounName | should be 'Thing'} - It "Should have one attribute" {$t.Count | should be 1} - It "Should have instance of CmdletAttribute" {$t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } - - [System.Management.Automation.CmdletAttribute]$c = $t[0] - It "Verb should be Get" {$c.VerbName | should be 'Get'} - It "Noun should be Thing" {$c.NounName | should be 'Thing'} - - [System.Management.Automation.Cmdlet("Get", "Thing", ConfirmImpact = 'High', SupportsPaging = $true)]class C2{} - $t = [C2].GetCustomAttributes($false) - It "Should have one attribute" { $t.Count | should be 1 } - It "Should have instance of CmdletAttribute" { $t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } - [System.Management.Automation.CmdletAttribute]$c = $t[0] - It "Verb should be Get" {$c.VerbName | should be 'Get'} - It "Noun should be Thing" {$c.NounName | should be 'Thing'} - - It "ConfirmImpact should be high" { $c.ConfirmImpact | should be 'High' } - It "SupportsPaging should be `$true" { $c.SupportsPaging | should be $true } + [System.Management.Automation.Cmdlet("Get", "Thing", SupportsShouldProcess = $true, SupportsPaging = $true)]class C2{} + $t = [C2].GetCustomAttributes($false) + It "Should have one attribute" { $t.Count | should be 1 } + It "Should have instance of CmdletAttribute" { $t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } + [System.Management.Automation.CmdletAttribute]$c = $t[0] + It "Verb should be Get" {$c.VerbName | should be 'Get'} + It "Noun should be Thing" {$c.NounName | should be 'Thing'} + + It "SupportsShouldProcess should be $true" { $c.ConfirmImpact | should be $true } + It "SupportsPaging should be `$true" { $c.SupportsPaging | should be $true } + Context "Support ConfirmImpact as an attribute" { + It "ConfirmImpact should be high" -pending { + [System.Management.Automation.Cmdlet("Get", "Thing", ConfirmImpact = 'High', SupportsPaging = $true)]class C3{} + $t = [C3].GetCustomAttributes($false) + It "Should have one attribute" { $t.Count | should be 1 } + It "Should have instance of CmdletAttribute" { $t[0].GetType().FullName | should be System.Management.Automation.CmdletAttribute } + [System.Management.Automation.CmdletAttribute]$c = $t[0] + $c.ConfirmImpact | should be 'High' + + } + } } -Describe 'Property Attributes Test' -Tags "innerloop", "DRT" { +Describe 'Property Attributes Test' -Tags "CI" { class C { [ValidateSet('a', 'b')]$p; } $t = [C].GetProperty('p').GetCustomAttributes($false) @@ -403,7 +413,7 @@ Describe 'Property Attributes Test' -Tags "innerloop", "DRT" { It "second value should be b" { $v.ValidValues[1] -eq 'b' } } -Describe 'Method Attributes Test' -Tags "innerloop", "DRT" { +Describe 'Method Attributes Test' -Tags "CI" { class C { [Obsolete("aaa")][int]f() { return 1 } } $t = [C].GetMethod('f').GetCustomAttributes($false) @@ -411,7 +421,7 @@ Describe 'Method Attributes Test' -Tags "innerloop", "DRT" { It "Attribute type should be ObsoleteAttribute" { $t[0].GetType().FullName | should be System.ObsoleteAttribute } } -Describe 'Positive SelfClass Type As Parameter Test' -Tags "innerloop", "DRT" { +Describe 'Positive SelfClass Type As Parameter Test' -Tags "CI" { class Point { Point($x, $y) { $this.x = $x; $this.y = $y } @@ -443,7 +453,7 @@ Describe 'Positive SelfClass Type As Parameter Test' -Tags "innerloop", "DRT" { } } -Describe 'PositiveReturnSelfClassTypeFromMemberFunction Test' -Tags "innerloop", "DRT" { +Describe 'PositiveReturnSelfClassTypeFromMemberFunction Test' -Tags "CI" { class ReturnObjectFromMemberFunctionTest { [ReturnObjectFromMemberFunctionTest] CreateInstance() @@ -460,8 +470,9 @@ Describe 'PositiveReturnSelfClassTypeFromMemberFunction Test' -Tags "innerloop", It "CreateInstance works" { $z.SayHello() | should be 'Hello1' } } -Describe 'TestMultipleArguments Test' -Tags "innerloop", "DRT" { - for ($i = 0; $i -lt 16; $i++) +Describe 'TestMultipleArguments Test' -Tags "CI" { + if ( $IsCore ) { $maxCount = 14 } else { $maxCount = 16 } + for ($i = 0; $i -lt $maxCount; $i++) { $properties = $(for ($j = 0; $j -le $i; $j++) { " [int]`$Prop$j" @@ -514,7 +525,7 @@ $ctorAssignments Invoke-Expression $class } } -Describe 'Scopes Test' -Tags "innerloop", "DRT" { +Describe 'Scopes Test' -Tags "CI" { class C1 { static C1() { @@ -532,7 +543,7 @@ Describe 'Scopes Test' -Tags "innerloop", "DRT" { } } -Describe 'Check PS Class Assembly Test' -Tags "innerloop", "DRT" { +Describe 'Check PS Class Assembly Test' -Tags "CI" { class C1 {} $assem = [C1].Assembly $attrs = @($assem.GetCustomAttributes($true)) @@ -540,7 +551,7 @@ Describe 'Check PS Class Assembly Test' -Tags "innerloop", "DRT" { It "Expected a DynamicClassImplementationAssembly attribute" { $expectedAttr.Length | should be 1} } -Describe 'ScriptScopeAccessFromClassMethod' -Tags "innerloop", "DRT" { +Describe 'ScriptScopeAccessFromClassMethod' -Tags "CI" { Import-Module "$PSScriptRoot\MSFT_778492.psm1" try { @@ -553,7 +564,7 @@ Describe 'ScriptScopeAccessFromClassMethod' -Tags "innerloop", "DRT" { } } -Describe 'Hidden Members Test ' -Tags "innerloop", "DRT" { +Describe 'Hidden Members Test ' -Tags "CI" { class C1 { [int]$visibleX @@ -587,13 +598,13 @@ Describe 'Hidden Members Test ' -Tags "innerloop", "DRT" { It "Tab completion should not return a hidden member" { $completions.CompletionMatches.Count | should be 0 } } -Describe 'BaseMethodCall Test ' -Tags "innerloop", "DRT" { +Describe 'BaseMethodCall Test ' -Tags "CI" { It "Derived class method call" {"abc".ToString() | should be "abc" } # call [object] ToString() method as a base class method. It "Base class method call" {([object]"abc").ToString() | should be "System.String" } } -Describe 'Scoped Types Test' -Tags "innerloop", "DRT" { +Describe 'Scoped Types Test' -Tags "CI" { class C1 { [string] GetContext() { return "Test scope" } } filter f1 @@ -621,7 +632,7 @@ Describe 'Scoped Types Test' -Tags "innerloop", "DRT" { It "'new-object C1' in nested scope (in pipeline)" { (1 | f2 | f1 | f2) | should be "f2 scope" } } -Describe 'ParameterOfClassTypeInModule Test' -Tags "innerloop", "DRT" { +Describe 'ParameterOfClassTypeInModule Test' -Tags "CI" { try { $sb = [scriptblock]::Create(@' diff --git a/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 index a5310275b..16f5d0ae6 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.Modules.Tests.ps1 @@ -12,25 +12,25 @@ Describe 'use of a module from two runspaces' { [string]$Content ) - mkdir -Force "TestDrive:\$Name" > $null + Setup -Dir $Name $manifestParams = @{ Path = "TestDrive:\$Name\$Name.psd1" } if ($Content) { - Set-Content -Path TestDrive:\$Name\$Name.psm1 -Value $Content + Set-Content -Path "${TestDrive}\$Name\$Name.psm1" -Value $Content $manifestParams['RootModule'] = "$Name.psm1" } New-ModuleManifest @manifestParams - $resolvedTestDrivePath = Split-Path ((ls TestDrive:\)[0].FullName) - if (-not ($env:PSModulePath -like "*$resolvedTestDrivePath*")) { - $env:PSModulePath += ";$resolvedTestDrivePath" + $resolvedTestDrivePath = Split-Path ((get-childitem TestDrive:\)[0].FullName) + if (-not ($env:PSMODULEPATH -like "*$resolvedTestDrivePath*")) { + $env:PSMODULEPATH += ";$resolvedTestDrivePath" } } - $originalPSModulePath = $env:PSModulePath + $originalPSMODULEPATH = $env:PSMODULEPATH try { New-TestModule -Name 'Random' -Content @' @@ -68,7 +68,7 @@ Import-Module Random } } finally { - $env:PSModulePath = $originalPSModulePath + $env:PSMODULEPATH = $originalPSMODULEPATH } -} \ No newline at end of file +} diff --git a/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 index 8b0be4247..fec7fc7c5 100644 --- a/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.NestedModules.tests.ps1 @@ -9,33 +9,33 @@ Describe 'NestedModules' -Tags "DRT" { [string[]]$NestedContents ) - mkdir -Force "TestDrive:\$Name" > $null + new-item -type directory -Force "TestDrive:\$Name" > $null $manifestParams = @{ Path = "TestDrive:\$Name\$Name.psd1" } if ($Content) { - Set-Content -Path TestDrive:\$Name\$Name.psm1 -Value $Content + Set-Content -Path "${TestDrive}\$Name\$Name.psm1" -Value $Content $manifestParams['RootModule'] = "$Name.psm1" } if ($NestedContents) { $manifestParams['NestedModules'] = 1..$NestedContents.Count | % { - $null = mkdir TestDrive:\$Name\Nested$_ - $null = Set-Content -Path TestDrive:\$Name\Nested$_\Nested$_.psm1 -Value $NestedContents[$_ - 1] + $null = new-item -type directory TestDrive:\$Name\Nested$_ + $null = Set-Content -Path "${TestDrive}\$Name\Nested$_\Nested$_.psm1" -Value $NestedContents[$_ - 1] "Nested$_" } } New-ModuleManifest @manifestParams - $resolvedTestDrivePath = Split-Path ((ls TestDrive:\)[0].FullName) - if (-not ($env:PSModulePath -like "*$resolvedTestDrivePath*")) { - $env:PSModulePath += ";$resolvedTestDrivePath" + $resolvedTestDrivePath = Split-Path ((get-childitem TestDrive:\)[0].FullName) + if (-not ($env:PSMODULEPATH -like "*$resolvedTestDrivePath*")) { + $env:PSMODULEPATH += ";$resolvedTestDrivePath" } } - $originalPSModulePath = $env:PSModulePath + $originalPSMODULEPATH = $env:PSMODULEPATH try { @@ -104,7 +104,7 @@ using module WithRoot # We need to think about it: should it work or not. # Currently, types are resolved in compile-time to the 'local' versions # So at runtime we don't call the module versions. - It 'Can execute type creation in the module context with new()' -Skip { + It 'Can execute type creation in the module context with new()' -pending { & (Get-Module ABC) { [C]::new().foo() } | Should Be C & (Get-Module NoRoot) { [A]::new().foo() } | Should Be A2 & (Get-Module WithRoot) { [A]::new().foo() } | Should Be A0 @@ -120,7 +120,7 @@ using module WithRoot } } finally { - $env:PSModulePath = $originalPSModulePath + $env:PSMODULEPATH = $originalPSMODULEPATH Get-Module @('ABC', 'NoRoot', 'WithRoot') | Remove-Module } } diff --git a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 index b02d18f12..5941314a0 100644 --- a/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1 @@ -27,16 +27,16 @@ Describe 'Classes inheritance syntax' -Tags "DRT" { class C7 : C6 { C7() : base() {} } } - It 'inheritance syntax allows newlines in varius places' { + It 'inheritance syntax allows newlines in various places' { class C {} - class C2a:C,system.ICloneable{ [object] Clone() {return $null}} + class C2a:C,system.IDisposable{ [void] Dispose() { }} class C2b : C , - system.ICloneable + system.IDisposable { - [object] Clone() {return $null} + [void] Dispose() {} C2b() : # there are extra spaces here base @@ -46,8 +46,8 @@ Describe 'Classes inheritance syntax' -Tags "DRT" { } } - [C2a].GetInterface("System.ICloneable") | Should Not Be $null - [C2b].GetInterface("System.ICloneable") | Should Not Be $null + [C2a].GetInterface("System.IDisposable") | Should Not Be $null + [C2b].GetInterface("System.IDisposable") | Should Not Be $null } It 'can subclass .NET type' { @@ -90,7 +90,7 @@ Describe 'Classes inheritance syntax errors' -Tags "DRT" { ShouldBeParseError "class A {}; class B : A, NonExistingInterface {}" TypeNotFound 25 ShouldBeParseError "class A {} ; class B {}; class C : A, B {}" InterfaceNameExpected 38 -SkipAndCheckRuntimeError - ShouldBeParseError "class A{} ; class B : A, System.ICloneable[] {}" SubtypeArray 25 -SkipAndCheckRuntimeError + ShouldBeParseError "class A{} ; class B : A, System.IDisposable[] {}" SubtypeArray 25 -SkipAndCheckRuntimeError ShouldBeParseError "class A {}; class B : A, NonExistingInterface {}" TypeNotFound 25 # base should be accepted only on instance ctors @@ -526,4 +526,4 @@ class Derived : Base $sb.Invoke() | Should Be 200 $sb.Invoke() | Should Be 200 } -} \ No newline at end of file +} diff --git a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 index cb697e103..06b7dc20d 100644 --- a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 @@ -12,21 +12,21 @@ Describe 'using module' -Tags "DRT" { ) if ($manifest) { - mkdir -Force "TestDrive:\$ModulePathPrefix\$Name\$Version" > $null - Set-Content -Path TestDrive:\$ModulePathPrefix\$Name\$Version\$Name.psm1 -Value $Content - New-ModuleManifest -RootModule "$Name.psm1" -Path TestDrive:\$ModulePathPrefix\$Name\$Version\$Name.psd1 -ModuleVersion $Version + new-item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name\$Version" > $null + Set-Content -Path "${TestDrive}\$ModulePathPrefix\$Name\$Version\$Name.psm1" -Value $Content + New-ModuleManifest -RootModule "$Name.psm1" -Path "${TestDrive}\$ModulePathPrefix\$Name\$Version\$Name.psd1" -ModuleVersion $Version } else { - mkdir -Force "TestDrive:\$ModulePathPrefix\$Name" > $null - Set-Content -Path TestDrive:\$ModulePathPrefix\$Name\$Name.psm1 -Value $Content + new-item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name" > $null + Set-Content -Path "${TestDrive}\$ModulePathPrefix\$Name\$Name.psm1" -Value $Content } - $resolvedTestDrivePath = Split-Path ((ls TestDrive:\$ModulePathPrefix)[0].FullName) - if (-not ($env:PSModulePath -like "*$resolvedTestDrivePath*")) { - $env:PSModulePath += ";$resolvedTestDrivePath" + $resolvedTestDrivePath = Split-Path ((get-childitem "${TestDrive}\$ModulePathPrefix")[0].FullName) + if (-not ($env:PSMODULEPATH -like "*$resolvedTestDrivePath*")) { + $env:PSMODULEPATH += ";$resolvedTestDrivePath" } } - $originalPSModulePath = $env:PSModulePath + $originalPSMODULEPATH = $env:PSMODULEPATH try { @@ -319,7 +319,7 @@ using module Foo # 'using module' behavior must be alligned with Import-Module. # Import-Module does the following: - # 1) find the first directory from $env:PSModulePath that contains the module + # 1) find the first directory from $env:PSMODULEPATH that contains the module # 2) Import highest available version of the module # In out case TestDrive:\Module is before TestDrive:\Modules2 and so 2.3.0 is the right version It "uses the last module, if multiple versions are present" { @@ -376,7 +376,7 @@ using module ModuleWithRuntimeError It 'can pick the right module' { - $scriptToProcessPath = 'TestDrive:\toProcess.ps1' + $scriptToProcessPath = "${TestDrive}\toProcess.ps1" Set-Content -Path $scriptToProcessPath -Value @' using module Foo function foo() @@ -385,7 +385,7 @@ function foo() } '@ # resolve name to absolute path - $scriptToProcessPath = (ls $scriptToProcessPath).FullName + $scriptToProcessPath = (get-childitem $scriptToProcessPath).FullName $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() $iss.StartupScripts.Add($scriptToProcessPath) @@ -409,24 +409,24 @@ function foo() } finally { - $env:PSModulePath = $originalPSModulePath + $env:PSMODULEPATH = $originalPSMODULEPATH } - # here we are back to normal $env:PSModulePath, but all modules are there + # here we are back to normal $env:PSMODULEPATH, but all modules are there Context "Module by path" { - It 'use non-modified PSModulePath' { - $env:PSModulePath | Should Be $originalPSModulePath + It 'use non-modified PSMODULEPATH' { + $env:PSMODULEPATH | Should Be $originalPSMODULEPATH } - mkdir -Force TestDrive:\FooRelativeConsumer - Set-Content -Path TestDrive:\FooRelativeConsumer\FooRelativeConsumer.ps1 -Value @' -using module ..\Modules\FooForPaths + new-item -type directory -Force TestDrive:\FooRelativeConsumer + Set-Content -Path "${TestDrive}\FooRelativeConsumer\FooRelativeConsumer.ps1" -Value @' +using module ..\modules\FooForPaths class Bar : Foo {} [Bar]::new() '@ - Set-Content -Path TestDrive:\FooRelativeConsumerErr.ps1 -Value @' + Set-Content -Path "${TestDrive}\FooRelativeConsumerErr.ps1" -Value @' using module FooForPaths class Bar : Foo {} [Bar]::new() @@ -443,7 +443,7 @@ class Bar : Foo {} } It "can be accessed by absolute path" { - $resolvedTestDrivePath = Split-Path ((ls TestDrive:\Modules)[0].FullName) + $resolvedTestDrivePath = Split-Path ((get-childitem TestDrive:\modules)[0].FullName) $s = @" using module $resolvedTestDrivePath\FooForPaths [Foo]::new() @@ -455,7 +455,7 @@ using module $resolvedTestDrivePath\FooForPaths } It "can be accessed by absolute path with file extension" { - $resolvedTestDrivePath = Split-Path ((ls TestDrive:\Modules)[0].FullName) + $resolvedTestDrivePath = Split-Path ((get-childitem TestDrive:\modules)[0].FullName) $barObject = [scriptblock]::Create(@" using module $resolvedTestDrivePath\FooForPaths\FooForPaths.psm1 [Foo]::new() @@ -471,7 +471,7 @@ using module .\FooForPaths "@ $err.FullyQualifiedErrorId | Should Be ModuleNotFoundDuringParse - Push-Location TestDrive:\Modules + Push-Location TestDrive:\modules try { $barObject = [scriptblock]::Create(@" using module .\FooForPaths @@ -484,7 +484,7 @@ using module .\FooForPaths } It "cannot be accessed by relative path without .\" { - Push-Location TestDrive:\Modules + Push-Location TestDrive:\modules try { $err = Get-RuntimeError @" using module FooForPaths diff --git a/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 index bffad6e4d..77cdcfd2c 100644 --- a/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 +++ b/test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1 @@ -104,7 +104,9 @@ function Test-Completions } foreach ($expected in $test.ExpectedResults) { - It ($expected.CompletionText) { + $skip = $false + if ( $expected.CompletionText -match "System.Management.Automation.PerformanceData|System.Management.Automation.Security" ) { $skip = $true } + It ($expected.CompletionText) -skip:$skip { $expected.Found | Should Be $true } } @@ -171,7 +173,7 @@ function TestFunction ) } -Describe "Script block based extensible completion" -Tags "Innerloop", "BVT" { +Describe "Script block based extensible completion" -Tags "CI" { @{ ExpectedResults = @( @{CompletionText = "beta: 11 gamma: 22 command: TestFunction parameterName: Alpha wordToComplete: aa" @@ -180,7 +182,7 @@ Describe "Script block based extensible completion" -Tags "Innerloop", "BVT" { } | Get-CompletionTestCaseData | Test-Completions } -Describe "Test class based extensible completion" -Tags "Innerloop", "BVT" { +Describe "Test class based extensible completion" -Tags "CI" { @{ ExpectedResults = @( @{CompletionText = "alpha: 42 gamma: 44 command: TestFunction parameterName: Beta wordToComplete: zz" @@ -189,7 +191,7 @@ Describe "Test class based extensible completion" -Tags "Innerloop", "BVT" { } | Get-CompletionTestCaseData | Test-Completions } -Describe "Test registration based exensible completion" -Tags "Innerloop", "BVT" { +Describe "Test registration based exensible completion" -Tags "CI" { Register-ArgumentCompleter -Command TestFunction -Parameter Gamma -ScriptBlock { param( [string] $CommandName, @@ -212,7 +214,7 @@ Describe "Test registration based exensible completion" -Tags "Innerloop", "BVT" } | Get-CompletionTestCaseData | Test-Completions } -Describe "Test extensible completion of native commands" -Tags "Innerloop", "BVT" { +Describe "Test extensible completion of native commands" -Tags "CI" { Register-ArgumentCompleter -Command netsh -Native -ScriptBlock { [CompletionResult]::new('advfirewall', 'advfirewall', "ParameterValue", 'advfirewall') [CompletionResult]::new('bridge', 'bridge', "ParameterValue", 'bridge') @@ -227,7 +229,7 @@ Describe "Test extensible completion of native commands" -Tags "Innerloop", "BVT } | Get-CompletionTestCaseData | Test-Completions } -Describe "Test extensible completion of using namespace" -Tags "Innerloop", "BVT" { +Describe "Test extensible completion of using namespace" -Tags "CI" { @{ ExpectedResults = @( @{CompletionText = "System"; ResultType = "Namespace"} @@ -264,7 +266,7 @@ Describe "Test extensible completion of using namespace" -Tags "Innerloop", "BVT } | Get-CompletionTestCaseData | Test-Completions } -Describe "Type extensible completion of type after using namespace" -Tags "Innerloop", "BVT" { +Describe "Type extensible completion of type after using namespace" -Tags "CI" { @{ ExpectedResults = @( @{CompletionText = "IO.TextReader"; ResultType = "Type"} @@ -291,7 +293,7 @@ Describe "Type extensible completion of type after using namespace" -Tags "Inner } | Get-CompletionTestCaseData | Test-Completions } -Describe "Additional type name completion tests" -Tags "Innerloop", "BVT" { +Describe "Additional type name completion tests" -Tags "CI" { @{ ExpectedResults = @( @{CompletionText = "System"; ResultType = "Namespace"} @@ -308,15 +310,15 @@ Describe "Additional type name completion tests" -Tags "Innerloop", "BVT" { }, @{ ExpectedResults = @( - @{CompletionText = "System.Collections.Generic.LinkedList"; ResultType = "Type"; ListItemText = "LinkedList<>"; ToolTip = "Class System.Collections.Generic.LinkedList[T]"} - @{CompletionText = "System.Collections.Generic.LinkedListNode"; ResultType = "Type"; ListItemText = "LinkedListNode<>"; ToolTip = "Class System.Collections.Generic.LinkedListNode[T]"} - @{CompletionText = "System.Collections.Generic.List"; ResultType = "Type"; ListItemText = "List<>"; ToolTip = "Class System.Collections.Generic.List[T]"} + @{CompletionText = "System.Collections.Generic.LinkedList"; ResultType = "Type"; ListItemText = "LinkedList<>"; ToolTip = "System.Collections.Generic.LinkedList[T]"} + @{CompletionText = "System.Collections.Generic.LinkedListNode"; ResultType = "Type"; ListItemText = "LinkedListNode<>"; ToolTip = "System.Collections.Generic.LinkedListNode[T]"} + @{CompletionText = "System.Collections.Generic.List"; ResultType = "Type"; ListItemText = "List<>"; ToolTip = "System.Collections.Generic.List[T]"} ) TestInput = 'Get-Command -ParameterType System.Collections.Generic.Li' }, @{ ExpectedResults = @( - @{CompletionText = "System.Collections.Generic.Dictionary"; ResultType = "Type"; ListItemText = "Dictionary<>"; ToolTip = "Class System.Collections.Generic.Dictionary[TKey, TValue]"} + @{CompletionText = "System.Collections.Generic.Dictionary"; ResultType = "Type"; ListItemText = "Dictionary<>"; ToolTip = "System.Collections.Generic.Dictionary[T1, T2]"} ) TestInput = 'Get-Command -ParameterType System.Collections.Generic.Dic' } | Get-CompletionTestCaseData | Test-Completions diff --git a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 index a8b2e4f5f..a1118541d 100644 --- a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 +++ b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 @@ -1,5 +1,5 @@ -Describe "Clone array" -Tags DRT { +Describe "Clone array" -Tags "CI" { It "Cast in target expr" { (([int[]](42)).clone()) | Should Be 42 (([int[]](1..5)).clone()).Length | Should Be 5 @@ -15,7 +15,7 @@ Describe "Clone array" -Tags DRT { } } -Describe "Set fields through PSMemberInfo" -Tags DRT { +Describe "Set fields through PSMemberInfo" -Tags "CI" { Add-Type @" public struct AStruct { public string s; } "@ @@ -33,7 +33,7 @@ Describe "Set fields through PSMemberInfo" -Tags DRT { } } -Describe "MSFT:3309783" -Tags DRT { +Describe "MSFT:3309783" -Tags "CI" { It "Run in another process" { # For a reliable test, we must run this in a new process because an earlier binding in this process @@ -62,7 +62,7 @@ Describe "MSFT:3309783" -Tags DRT { } } -Describe "ScriptBlockAst.GetScriptBlock throws on error" -Tags DRT { +Describe "ScriptBlockAst.GetScriptBlock throws on error" -Tags "CI" { $e = $null @@ -80,7 +80,7 @@ Describe "ScriptBlockAst.GetScriptBlock throws on error" -Tags DRT { } } -Describe "Hashtable key property syntax" -Tags DRT { +Describe "Hashtable key property syntax" -Tags "CI" { $script = @' # First create a hashtable wrapped in PSObject $hash = New-Object hashtable @@ -105,7 +105,7 @@ Describe "Hashtable key property syntax" -Tags DRT { } } -Describe "Assign automatic variables" -Tags DRT { +Describe "Assign automatic variables" -Tags "CI" { $autos = '_', 'args', 'this', 'input', 'pscmdlet', 'psboundparameters', 'myinvocation', 'psscriptroot', 'pscommandpath' @@ -141,7 +141,7 @@ Describe "Assign automatic variables" -Tags DRT { } } -Describe "Attribute error position" -Tags DRT { +Describe "Attribute error position" -Tags "CI" { It "Ambiguous overloads" { try { @@ -161,7 +161,7 @@ Describe "Attribute error position" -Tags DRT { } } -Describe "Multiple alias attributes" -Tags DRT { +Describe "Multiple alias attributes" -Tags "CI" { It "basic test" { function foo { param( @@ -178,7 +178,7 @@ Describe "Multiple alias attributes" -Tags DRT { } } -Describe "Members of System.Type" -Tags DRT { +Describe "Members of System.Type" -Tags "CI" { It "Members in public classes derived from System.Type should be found" { class MyType : System.Collections.IEnumerable { diff --git a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 index d3c87a2a5..deaf64520 100644 --- a/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 +++ b/test/powershell/Language/Parser/MethodInvocation.Tests.ps1 @@ -1,5 +1,8 @@ +if ( $IsCore ) { + return +} -Describe "Interface inheritance with remoting proxies" -Tags "P1", "RI" { +Describe "Interface inheritance with remoting proxies" -Tags "CI" { $src = @" using System; using System.ServiceModel; diff --git a/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 b/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 index 0d08df763..b5ae9b7d5 100644 --- a/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 +++ b/test/powershell/Language/Parser/ParameterBinding.Tests.ps1 @@ -1,6 +1,6 @@ -Describe 'Argument transformation attribute on optional argument with explicit $null' -Tags "P1", "RI" { - $mod = Add-Type -PassThru -TypeDefinition @' +Describe 'Argument transformation attribute on optional argument with explicit $null' -Tags "CI" { + $tdefinition = @' using System; using System.Management.Automation; using System.Reflection; @@ -45,6 +45,7 @@ Describe 'Argument transformation attribute on optional argument with explicit $ } } '@ + $mod = Add-Type -PassThru -TypeDefinition $tdefinition -refer mscorlib,System.Management.Automation Import-Module $mod[0].Assembly @@ -67,13 +68,29 @@ Describe 'Argument transformation attribute on optional argument with explicit $ } - Invoke-ScriptFunctionTakesObject | Should Be 42 - Invoke-ScriptFunctionTakesUInt64 | Should Be 42 - Invoke-CSharpCmdletTakesObject | Should Be "passed in null" - Invoke-CSharpCmdletTakesUInt64 | Should Be 0 + It "Script function takes object" { + Invoke-ScriptFunctionTakesObject | Should Be 42 + } + It "Script function takes uint64" { + Invoke-ScriptFunctionTakesUInt64 | Should Be 42 + } + it "csharp cmdlet takes object" { + Invoke-CSharpCmdletTakesObject | Should Be "passed in null" + } + it "csharp cmdlet takes uint64" { + Invoke-CSharpCmdletTakesUInt64 | Should Be 0 + } - Invoke-ScriptFunctionTakesObject -Address $null | Should Be 42 - Invoke-ScriptFunctionTakesUInt64 -Address $null | Should Be 42 - Invoke-CSharpCmdletTakesObject -Address $null | Should Be 42 - Invoke-CSharpCmdletTakesUInt64 -Address $null | Should Be 42 + it "script function takes object when parameter is null" { + Invoke-ScriptFunctionTakesObject -Address $null | Should Be 42 + } + it "script function takes unit64 when parameter is null" { + Invoke-ScriptFunctionTakesUInt64 -Address $null | Should Be 42 + } + it "script csharp cmdlet takes object when parameter is null" { + Invoke-CSharpCmdletTakesObject -Address $null | Should Be 42 + } + it "script csharp cmdlet takes uint64 when parameter is null" { + Invoke-CSharpCmdletTakesUInt64 -Address $null | Should Be 42 + } } diff --git a/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 b/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 index 9e641acff..08aa7da26 100644 --- a/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 +++ b/test/powershell/Language/Parser/RedirectionOperator.Tests.ps1 @@ -1,7 +1,14 @@ Describe "Redirection operator now supports encoding changes" { BeforeAll { $asciiString = "abc" - $asciiCR = "`r`n" + + if ( $IsWindows ) { + $asciiCR = "`r`n" + } + else { + $asciiCR = [string][char]10 + } + # If out-file -encoding happens to have a default, be sure to # save it away diff --git a/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 b/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 index fe011a453..59f004838 100644 --- a/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 +++ b/test/powershell/Language/Parser/TypeAccelerator.Tests.ps1 @@ -11,7 +11,8 @@ Describe "Type accelerators" -Tags "DRT" { } It "Can query type accelerators" { - $TypeAccelerators.Count -gt 82 | Should Be $true + if ( $IsCore ) { $count = 80 } else { $count = 82 } + $TypeAccelerators.Count -gt $count | Should Be $true $TypeAccelerators['xml'] | Should Be ([System.Xml.XmlDocument]) $TypeAccelerators['AllowNull'] | Should Be ([System.Management.Automation.AllowNullAttribute]) } diff --git a/test/powershell/Language/Parser/UsingAssembly.Tests.ps1 b/test/powershell/Language/Parser/UsingAssembly.Tests.ps1 index deb420045..c45e143cd 100644 --- a/test/powershell/Language/Parser/UsingAssembly.Tests.ps1 +++ b/test/powershell/Language/Parser/UsingAssembly.Tests.ps1 @@ -1,5 +1,5 @@ -Describe "Using assembly" -Tags "DRT" { +Describe "Using assembly" -Tags "CI" { try { @@ -42,7 +42,7 @@ public class ABC {} $err[0].ErrorId | Should Be CannotLoadAssemblyWithUriSchema } - It "parse does not load the assembly" { + It "parse does not load the assembly" -pending { $assemblies = [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name $assemblies -contains "UsingAssemblyTest$guid" | Should Be $false @@ -78,7 +78,7 @@ public class ABC {} $failed | Should be $true } #> - It "Assembly loaded at runtime" { + It "Assembly loaded at runtime" -pending { $assemblies = powershell -noprofile -command @" using assembly .\UsingAssemblyTest$guid.dll [Appdomain]::CurrentDomain.GetAssemblies().GetName().Name diff --git a/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 index b2ad631cc..aa9c60fef 100644 --- a/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 +++ b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 @@ -4,6 +4,7 @@ #using namespace System using namespace System.Threading using namespace System.Timers +using namespace System.Diagnostics # Test parsing more than one using statement on one line using namespace System.Diagnostics; using namespace System.Runtime.CompilerServices using namespace System.Collections.Generic @@ -48,7 +49,7 @@ class C1 { [Thread][CompilerGenerated()]$Thread [Int32][CompilerGenerated()]$Int - [ElapsedEventHandler][CompilerGenerated()]$EventHandler + #[ElapsedEventHandler][CompilerGenerated()]$EventHandler } # Test attributes that won't be found w/o using, but w/ implicit Attribute suffix @@ -57,30 +58,30 @@ class C2 { [Thread][CompilerGeneratedAttribute()]$Thread [Int32][CompilerGeneratedAttribute()]$Int - [ElapsedEventHandler][CompilerGeneratedAttribute()]$EventHandler + #[ElapsedEventHandler][CompilerGeneratedAttribute()]$EventHandler } Describe "Using Namespace" -Tags "DRT" { It "Type literals w/ using namespace" { [Thread].FullName | Should Be System.Threading.Thread [Int32].FullName | Should Be System.Int32 - [ElapsedEventHandler].FullName | Should Be System.Timers.ElapsedEventHandler + #[ElapsedEventHandler].FullName | Should Be System.Timers.ElapsedEventHandler [C1].GetProperty("Thread").PropertyType.FullName | Should Be System.Threading.Thread [C1].GetProperty("Int").PropertyType.FullName | Should Be System.Int32 - [C1].GetProperty("EventHandler").PropertyType.FullName | Should Be System.Timers.ElapsedEventHandler + # [C1].GetProperty("EventHandler").PropertyType.FullName | Should Be System.Timers.ElapsedEventHandler } It "Covert string to Type w/ using namespace" { ("Thread" -as [Type]).FullName | Should Be System.Threading.Thread ("Int32" -as [Type]).FullName | Should Be System.Int32 - ("ElapsedEventHandler" -as [Type]).FullName | Should Be System.Timers.ElapsedEventHandler + # ("ElapsedEventHandler" -as [Type]).FullName | Should Be System.Timers.ElapsedEventHandler New-Object Int32 | Should Be 0 New-Object CompilerGeneratedAttribute | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute } - It "Attributes w/ using namespace" { + It "Attributes w/ using namespace" -pending { function foo { [DebuggerStepThrough()] @@ -103,11 +104,11 @@ Describe "Using Namespace" -Tags "DRT" { [C1].GetProperty("Thread").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute [C1].GetProperty("Int").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute - [C1].GetProperty("EventHandler").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + # [C1].GetProperty("EventHandler").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute [C2].GetProperty("Thread").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute [C2].GetProperty("Int").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute - [C2].GetProperty("EventHandler").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute + # [C2].GetProperty("EventHandler").GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute [C1].GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute [C2].GetCustomAttributesData()[0].AttributeType.FullName | Should Be System.Runtime.CompilerServices.CompilerGeneratedAttribute @@ -116,7 +117,7 @@ Describe "Using Namespace" -Tags "DRT" { } It "Ambiguous type reference" { - { [Timer] } | ShouldBeErrorId AmbiguousTypeReference + { [ThreadState] } | ShouldBeErrorId AmbiguousTypeReference } It "Parameters" { diff --git a/test/powershell/Language/Scripting/Debugging/DebuggerScriptTests.Tests.ps1 b/test/powershell/Language/Scripting/Debugging/DebuggerScriptTests.Tests.ps1 index 82a342a361fbf2006ecc8025a25276ebeb9cb922..50ca09cb1d7d0d37e0696c5926ad59d51181f4eb 100644 GIT binary patch delta 975 zcma)5L2DC16n@4gY)njc6$-ms&DbV2*>scAgM`pX($+w+VnRv}B2kb?Ok)$%SR~b0 z#e;aLCGVto7C{fe1yQ_sQt%)uR4?L1&;Eqqn@zFV#X}G8&CIvoH{Y8#v+rLzdaoSa zZ_a}{)ZjWaq0Y0h?R@LDK@a^=j}=0K@VAn+p%Hc7hFnuiR>as=KM2wylwg%C=b!;s zU=2#f^WZOMXqGBjA-DuLC^Hy^SvU)4XtoHapg_3*qbO-IvO7cA(TezUzaY&J|1v2X zG-jX*Rbm{&v`0A2APDnh#P{W28AK@7Dfp^M=WUQ5gIg416f4AI|2%mu3@-Jxn3n?h zS@z<^sRJlP6*WlJ*bYJL0?a@Tzi5YXD|!Occ>y;Z0)CQxTz7;)ia4t~-gTPmRJm29 zNgU;_{Z3G(Zp-FA#Sk;cc4*EVHy;T2G33RLf7o2#pIF1L-1qEFWbxghUH>hofaml{ zWH&`j3&WVcI)vi5f*k*&FPQpdY!aWAHGhVx;?Zq zTatA;(=uIT9g^0L_iuvSda)!X^5TGM8Xw1%Jt%*~lf0%KuW77=_o8|c?)!;VA9md? zOh0mAtC=*uPM!BzA0*Z?W0=b@D{9m-Z$ehceDg|K?A&vq_ck@Lynxx(lA)CEO8;R# tX^reMHP65=hJn_1n;_6SZj7u~_SxW6JC5U(XBIJGo#oeqkdbP;)xU7&|G@wN delta 382 zcmY+Aze@sf7{d|z9D_h4S_*P*4hQMHA&8fU4-fD2yu&;0O!Xe7+5<`%QjasZKKI@ZOmh>$ z$cRWY33QnQ#mn{#rVJ^P3JH@k$&dmmDLU?pf^#K}kw7EnV|6?}w*1^F$*Yqj`)crz z@MKwy|0@y?@j&N~qwAYTnR7v=47ydk-YnwGZ$+Pr!0PhBCRA`Ii0X9_PkJ87Kpmff zASyK)iBJSVcT?k!#%;*QJibF=v`)%sdm^aqiLlYm$#uL7R$S(7sC(&&SDb4-quxQ> zuovwZ$7nQ|**~9(Pb7&YBUGXss<5Y)IwI%6;~2L0w+KQ5~pqp_JRjPL0J# jGHXH0(KDJCeN_U6`Hsey!Uh^S3tAG7s>CW$X?^JzfFz8~ From b680b48160d6212417996f93ed3cdc5b93b59998 Mon Sep 17 00:00:00 2001 From: James Truher Date: Tue, 19 Jul 2016 18:13:47 -0700 Subject: [PATCH 06/25] tests now run clean on Windows and Linux --- .../Scripting.Classes.BasicParsing.Tests.ps1 | 6 +- .../Classes/scripting.Classes.using.tests.ps1 | 484 +++++++++--------- .../LanguageAndParser.TestFollowup.Tests.ps1 | 5 +- .../Debugging/DebuggerScriptTests.Tests.ps1 | Bin 39806 -> 39884 bytes 4 files changed, 251 insertions(+), 244 deletions(-) diff --git a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 index b11782c27..7a366e928 100644 --- a/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 +++ b/test/powershell/Language/Classes/Scripting.Classes.BasicParsing.Tests.ps1 @@ -673,7 +673,7 @@ Describe 'Type building' -Tags "DRT" { Describe 'RuntimeType created for TypeDefinitionAst' { - It 'can make cast to the right RuntimeType in two different contexts' { + It 'can make cast to the right RuntimeType in two different contexts' -pending { $ssfe = [System.Management.Automation.Runspaces.SessionStateFunctionEntry]::new("foo", @' class Base @@ -689,7 +689,7 @@ class Derived : Base [Derived]::new().foo() '@) - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault2() $iss.Commands.Add($ssfe) $ps = [powershell]::Create($iss) @@ -745,7 +745,7 @@ namespace Foo } '@ - It 'doesn''t allow protected methods access outside of inheritance chain' { + It 'doesn''t allow protected methods access outside of inheritance chain' -pending { $a = [scriptblock]::Create(@' class A { diff --git a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 index 06b7dc20d..d4c6fc42f 100644 --- a/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 +++ b/test/powershell/Language/Classes/scripting.Classes.using.tests.ps1 @@ -1,85 +1,89 @@ -Describe 'using module' -Tags "DRT" { - - Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 - - function New-TestModule { - param( - [string]$Name, - [string]$Content, - [switch]$Manifest, - [version]$Version = '1.0', # ignored, if $Manifest -eq $false - [string]$ModulePathPrefix = 'modules' # module is created under TestDrive:\$ModulePathPrefix\$Name - ) +Describe 'using module' -Tags "CI" { + BeforeAll { + $originalPSMODULEPATH = $env:PSMODULEPATH - if ($manifest) { - new-item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name\$Version" > $null - Set-Content -Path "${TestDrive}\$ModulePathPrefix\$Name\$Version\$Name.psm1" -Value $Content - New-ModuleManifest -RootModule "$Name.psm1" -Path "${TestDrive}\$ModulePathPrefix\$Name\$Version\$Name.psd1" -ModuleVersion $Version - } else { - new-item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name" > $null - Set-Content -Path "${TestDrive}\$ModulePathPrefix\$Name\$Name.psm1" -Value $Content + Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 + + function New-TestModule { + param( + [string]$Name, + [string]$Content, + [switch]$Manifest, + [version]$Version = '1.0', # ignored, if $Manifest -eq $false + [string]$ModulePathPrefix = 'modules' # module is created under TestDrive:\$ModulePathPrefix\$Name + ) + + if ($manifest) { + new-item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name\$Version" > $null + Set-Content -Path "${TestDrive}\$ModulePathPrefix\$Name\$Version\$Name.psm1" -Value $Content + New-ModuleManifest -RootModule "$Name.psm1" -Path "${TestDrive}\$ModulePathPrefix\$Name\$Version\$Name.psd1" -ModuleVersion $Version + } else { + new-item -type directory -Force "${TestDrive}\$ModulePathPrefix\$Name" > $null + Set-Content -Path "${TestDrive}\$ModulePathPrefix\$Name\$Name.psm1" -Value $Content + } + + $resolvedTestDrivePath = Split-Path ((get-childitem "${TestDrive}\$ModulePathPrefix")[0].FullName) + if (-not ($env:PSMODULEPATH -like "*$resolvedTestDrivePath*")) { + $env:PSMODULEPATH += ";$resolvedTestDrivePath" + } } - $resolvedTestDrivePath = Split-Path ((get-childitem "${TestDrive}\$ModulePathPrefix")[0].FullName) - if (-not ($env:PSMODULEPATH -like "*$resolvedTestDrivePath*")) { - $env:PSMODULEPATH += ";$resolvedTestDrivePath" - } } - $originalPSMODULEPATH = $env:PSMODULEPATH + AfterAll { + $env:PSMODULEPATH = $originalPSMODULEPATH + } - try { - + It 'Import-Module has ImplementedAssembly, when classes are present in the module' { # Create modules in TestDrive:\ New-TestModule -Name Foo -Content 'class Foo { [string] GetModuleName() { return "Foo" } }' New-TestModule -Manifest -Name FooWithManifest -Content 'class Foo { [string] GetModuleName() { return "FooWithManifest" } }' - It 'Import-Module has ImplementedAssembly, when classes are present in the module' { - $module = Import-Module Foo -PassThru - try { - $module.ImplementingAssembly | Should Not Be $null - } finally { - $module | Remove-Module - } + $module = Import-Module Foo -PassThru + try { + $module.ImplementingAssembly | Should Not Be $null + } finally { + $module | Remove-Module } + } - It "can use class from another module as a base class with using module" { - $barType = [scriptblock]::Create(@" + It "can use class from another module as a base class with using module" { + $barType = [scriptblock]::Create(@" using module Foo class Bar : Foo {} [Bar] "@).Invoke() - $barType.BaseType.Name | Should Be 'Foo' - } + $barType.BaseType.Name | Should Be 'Foo' + } - It "can use class from another module in New-Object" { - $foo = [scriptblock]::Create(@" + It "can use class from another module in New-Object" { + $foo = [scriptblock]::Create(@" using module FooWithManifest using module Foo New-Object FooWithManifest.Foo New-Object Foo.Foo "@).Invoke() - $foo.Count | Should Be 2 - $foo[0].GetModuleName() | Should Be 'FooWithManifest' - $foo[1].GetModuleName() | Should Be 'Foo' - } + $foo.Count | Should Be 2 + $foo[0].GetModuleName() | Should Be 'FooWithManifest' + $foo[1].GetModuleName() | Should Be 'Foo' + } - It "can use class from another module by full name as base class and [type]" { - $fooObject = [scriptblock]::Create(@" + It "can use class from another module by full name as base class and [type]" { + $fooObject = [scriptblock]::Create(@" using module Foo class Bar : Foo.Foo {} [Foo.Foo]::new() "@).Invoke() - $fooObject.GetModuleName() | Should Be 'Foo' - } + $fooObject.GetModuleName() | Should Be 'Foo' + } - It "can use modules with classes collision" { - # we use 3 classes with name Foo at the same time - # two of them come from 'using module' and one is defined in the scriptblock itself. - # we should be able to use first two of them by the module-quilified name and the third one it's name. - $fooModuleName = [scriptblock]::Create(@" + It "can use modules with classes collision" { + # we use 3 classes with name Foo at the same time + # two of them come from 'using module' and one is defined in the scriptblock itself. + # we should be able to use first two of them by the module-quilified name and the third one it's name. + $fooModuleName = [scriptblock]::Create(@" using module Foo using module FooWithManifest @@ -95,21 +99,21 @@ class Bar : Foo {} (New-Object Foo).GetModuleName() # This "@).Invoke() - $fooModuleName.Count | Should Be 4 - $fooModuleName[0] | Should Be 'Foo' - $fooModuleName[1] | Should Be 'FooWithManifest' - $fooModuleName[2] | Should Be 'This' - $fooModuleName[3] | Should Be 'This' - } + $fooModuleName.Count | Should Be 4 + $fooModuleName[0] | Should Be 'Foo' + $fooModuleName[1] | Should Be 'FooWithManifest' + $fooModuleName[2] | Should Be 'This' + $fooModuleName[3] | Should Be 'This' + } - It "doesn't mess up two consequitive scripts" { - $sb1 = [scriptblock]::Create(@" + It "doesn't mess up two consequitive scripts" { + $sb1 = [scriptblock]::Create(@" using module Foo class Bar : Foo {} [Bar]::new().GetModuleName() "@) - $sb2 = [scriptblock]::Create(@" + $sb2 = [scriptblock]::Create(@" using module Foo class Foo { [string] GetModuleName() { return "This" } } @@ -117,12 +121,12 @@ class Bar : Foo {} [Bar]::new().GetModuleName() "@) - $sb1.Invoke() | Should Be 'Foo' - $sb2.Invoke() | Should Be 'This' - } + $sb1.Invoke() | Should Be 'Foo' + $sb2.Invoke() | Should Be 'This' + } - It "can use modules with classes collision simple" { - $fooModuleName = [scriptblock]::Create(@" + It "can use modules with classes collision simple" { + $fooModuleName = [scriptblock]::Create(@" using module Foo class Foo { [string] GetModuleName() { return "This" } } @@ -137,300 +141,302 @@ class Bar : Foo {} (New-Object Foo).GetModuleName() # This "@).Invoke() - $fooModuleName.Count | Should Be 5 - $fooModuleName[0] | Should Be 'Foo' - $fooModuleName[1] | Should Be 'Foo' - $fooModuleName[2] | Should Be 'This' - $fooModuleName[3] | Should Be 'This' - $fooModuleName[4] | Should Be 'This' - } + $fooModuleName.Count | Should Be 5 + $fooModuleName[0] | Should Be 'Foo' + $fooModuleName[1] | Should Be 'Foo' + $fooModuleName[2] | Should Be 'This' + $fooModuleName[3] | Should Be 'This' + $fooModuleName[4] | Should Be 'This' + } - It "can use class from another module as a base class with using module with manifest" { - $barType = [scriptblock]::Create(@" + It "can use class from another module as a base class with using module with manifest" { + $barType = [scriptblock]::Create(@" using module FooWithManifest class Bar : Foo {} [Bar] "@).Invoke() - $barType.BaseType.Name | Should Be 'Foo' - } + $barType.BaseType.Name | Should Be 'Foo' + } - It "can instantiate class from another module" { - $foo = [scriptblock]::Create(@" + It "can instantiate class from another module" { + $foo = [scriptblock]::Create(@" using module Foo [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'Foo' - } + $foo.GetModuleName() | Should Be 'Foo' + } - It "cannot instantiate class from another module without using statement" { - $err = Get-RuntimeError @" + It "cannot instantiate class from another module without using statement" { + $err = Get-RuntimeError @" #using module Foo [Foo]::new() "@ - $err.FullyQualifiedErrorId | Should Be TypeNotFound - } + $err.FullyQualifiedErrorId | Should Be TypeNotFound + } - It "can use class from another module in New-Object by short name" { - $foo = [scriptblock]::Create(@" + It "can use class from another module in New-Object by short name" { + $foo = [scriptblock]::Create(@" using module FooWithManifest New-Object Foo "@).Invoke() - $foo.GetModuleName() | Should Be 'FooWithManifest' - } + $foo.GetModuleName() | Should Be 'FooWithManifest' + } - It "can use class from this module in New-Object by short name" { - $foo = [scriptblock]::Create(@" + It "can use class from this module in New-Object by short name" { + $foo = [scriptblock]::Create(@" class Foo {} New-Object Foo "@).Invoke() - $foo | Should Not Be $null - } + $foo | Should Not Be $null + } - # Pending reason: - # it's not yet implemented. - It "accept module specification" { - $foo = [scriptblock]::Create(@" + # Pending reason: + # it's not yet implemented. + It "accept module specification" { + $foo = [scriptblock]::Create(@" using module @{ ModuleName = 'FooWithManifest'; ModuleVersion = '1.0' } New-Object Foo "@).Invoke() - $foo.GetModuleName() | Should Be 'FooWithManifest' + $foo.GetModuleName() | Should Be 'FooWithManifest' + } + + Context 'parse time errors' { + + It "report an error about not found module" { + $err = Get-ParseResults "using module ThisModuleDoesntExist" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'ModuleNotFoundDuringParse' } - Context 'parse time errors' { - - It "report an error about not found module" { - $err = Get-ParseResults "using module ThisModuleDoesntExist" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'ModuleNotFoundDuringParse' - } - - - It "report an error about misformatted module specification" { - $err = Get-ParseResults "using module @{ Foo = 'Foo' }" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'RequiresModuleInvalid' - } - - It "report an error about wildcard in the module name" { - $err = Get-ParseResults "using module fo*" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'WildCardModuleNameError' - } - - It "report an error about wildcard in the module path" { - $err = Get-ParseResults "using module C:\fo*" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'WildCardModuleNameError' - } - - It "report an error about wildcard in the module name inside ModuleSpecification hashtable" { - $err = Get-ParseResults "using module @{ModuleName = 'Fo*'; RequiredVersion = '1.0'}" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'WildCardModuleNameError' - } - - # MSFT:5246105 - It "report an error when tokenizer encounters comma" { - $err = Get-ParseResults "using module ,FooWithManifest" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'MissingUsingItemName' - } - - It "report an error when tokenizer encounters nothing" { - $err = Get-ParseResults "using module " - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'MissingUsingItemName' - } - - It "report an error on badly formatted RequiredVersion" { - $err = Get-ParseResults "using module @{ModuleName = 'FooWithManifest'; RequiredVersion = 1. }" - $err.Count | Should Be 1 - $err[0].ErrorId | Should Be 'RequiresModuleInvalid' - } - - # MSFT:6897275 - It "report an error on incomplete using input" { - $err = Get-ParseResults "using module @{ModuleName = 'FooWithManifest'; FooWithManifest = 1." # missing closing bracket - $err.Count | Should Be 2 - $err[0].ErrorId | Should Be 'IncompleteHashLiteral' - $err[1].ErrorId | Should Be 'RequiresModuleInvalid' - } - + It "report an error about misformatted module specification" { + $err = Get-ParseResults "using module @{ Foo = 'Foo' }" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'RequiresModuleInvalid' } - Context 'short name in case of name collision' { - It "cannot use as base class" { - $err = Get-RuntimeError @" + It "report an error about wildcard in the module name" { + $err = Get-ParseResults "using module fo*" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'WildCardModuleNameError' + } + + It "report an error about wildcard in the module path" { + $err = Get-ParseResults "using module C:\fo*" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'WildCardModuleNameError' + } + + It "report an error about wildcard in the module name inside ModuleSpecification hashtable" { + $err = Get-ParseResults "using module @{ModuleName = 'Fo*'; RequiredVersion = '1.0'}" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'WildCardModuleNameError' + } + + # MSFT:5246105 + It "report an error when tokenizer encounters comma" { + $err = Get-ParseResults "using module ,FooWithManifest" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'MissingUsingItemName' + } + + It "report an error when tokenizer encounters nothing" { + $err = Get-ParseResults "using module " + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'MissingUsingItemName' + } + + It "report an error on badly formatted RequiredVersion" { + $err = Get-ParseResults "using module @{ModuleName = 'FooWithManifest'; RequiredVersion = 1. }" + $err.Count | Should Be 1 + $err[0].ErrorId | Should Be 'RequiresModuleInvalid' + } + + # MSFT:6897275 + It "report an error on incomplete using input" { + $err = Get-ParseResults "using module @{ModuleName = 'FooWithManifest'; FooWithManifest = 1." # missing closing bracket + $err.Count | Should Be 2 + $err[0].ErrorId | Should Be 'IncompleteHashLiteral' + $err[1].ErrorId | Should Be 'RequiresModuleInvalid' + } + + } + + Context 'short name in case of name collision' { + It "cannot use as base class" { + $err = Get-RuntimeError @" using module Foo using module FooWithManifest class Bar : Foo {} "@ - $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference - } + $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference + } - It "cannot use as [...]" { - $err = Get-RuntimeError @" + It "cannot use as [...]" { + $err = Get-RuntimeError @" using module Foo using module FooWithManifest [Foo] "@ - $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference - } + $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference + } - It "cannot use in New-Object" { - $err = Get-RuntimeError @" + It "cannot use in New-Object" { + $err = Get-RuntimeError @" using module Foo using module FooWithManifest New-Object Foo "@ - $err.FullyQualifiedErrorId | Should Be 'AmbiguousTypeReference,Microsoft.PowerShell.Commands.NewObjectCommand' - } + $err.FullyQualifiedErrorId | Should Be 'AmbiguousTypeReference,Microsoft.PowerShell.Commands.NewObjectCommand' + } - It "cannot use [type] cast from string" { - $err = Get-RuntimeError @" + It "cannot use [type] cast from string" { + $err = Get-RuntimeError @" using module Foo using module FooWithManifest [type]"Foo" "@ - $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference - } + $err.FullyQualifiedErrorId | Should Be AmbiguousTypeReference } + } - Context 'using use the latest version of module after Import-Module -Force' { + Context 'using use the latest version of module after Import-Module -Force' { + BeforeAll { New-TestModule -Name Foo -Content 'class Foo { [string] GetModuleName() { return "Foo2" } }' Import-Module Foo -Force - It "can use class from another module as a base class with using module" { - $moduleName = [scriptblock]::Create(@" + } + It "can use class from another module as a base class with using module" { + $moduleName = [scriptblock]::Create(@" using module Foo [Foo]::new().GetModuleName() "@).Invoke() - $moduleName | Should Be 'Foo2' - } + $moduleName | Should Be 'Foo2' } + } Context 'Side by side' { + BeforeAll { # Add side-by-side module $newVersion = '3.4.5' New-TestModule -Manifest -Name FooWithManifest -Content 'class Foo { [string] GetModuleName() { return "Foo230" } }' -Version '2.3.0' New-TestModule -Manifest -Name FooWithManifest -Content 'class Foo { [string] GetModuleName() { return "Foo345" } }' -Version '3.4.5' -ModulePathPrefix 'Modules2' + } - # 'using module' behavior must be alligned with Import-Module. - # Import-Module does the following: - # 1) find the first directory from $env:PSMODULEPATH that contains the module - # 2) Import highest available version of the module - # In out case TestDrive:\Module is before TestDrive:\Modules2 and so 2.3.0 is the right version - It "uses the last module, if multiple versions are present" { - $foo = [scriptblock]::Create(@" + # 'using module' behavior must be alligned with Import-Module. + # Import-Module does the following: + # 1) find the first directory from $env:PSMODULEPATH that contains the module + # 2) Import highest available version of the module + # In out case TestDrive:\Module is before TestDrive:\Modules2 and so 2.3.0 is the right version + It "uses the last module, if multiple versions are present" { + $foo = [scriptblock]::Create(@" using module FooWithManifest [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'Foo230' - } + $foo.GetModuleName() | Should Be 'Foo230' + } - It "uses right version, when RequiredModule=1.0 specified" { - $foo = [scriptblock]::Create(@" + It "uses right version, when RequiredModule=1.0 specified" { + $foo = [scriptblock]::Create(@" using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '1.0'} [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'FooWithManifest' - } + $foo.GetModuleName() | Should Be 'FooWithManifest' + } - It "uses right version, when RequiredModule=2.3.0 specified" { - $foo = [scriptblock]::Create(@" + It "uses right version, when RequiredModule=2.3.0 specified" { + $foo = [scriptblock]::Create(@" using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '2.3.0'} [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'Foo230' - } + $foo.GetModuleName() | Should Be 'Foo230' + } - It "uses right version, when RequiredModule=3.4.5 specified" { - $foo = [scriptblock]::Create(@" + It "uses right version, when RequiredModule=3.4.5 specified" { + $foo = [scriptblock]::Create(@" using module @{ModuleName = 'FooWithManifest'; RequiredVersion = '3.4.5'} [Foo]::new() "@).Invoke() - $foo.GetModuleName() | Should Be 'Foo345' - } + $foo.GetModuleName() | Should Be 'Foo345' } + } - Context 'Use module with runtime error' { - + Context 'Use module with runtime error' { + BeforeAll { New-TestModule -Name ModuleWithRuntimeError -Content @' class Foo { [string] GetModuleName() { return "ModuleWithRuntimeError" } } throw 'error' '@ + } - It "handles runtime errors in imported module" { - $err = Get-RuntimeError @" + It "handles runtime errors in imported module" { + $err = Get-RuntimeError @" using module ModuleWithRuntimeError [Foo]::new().GetModuleName() "@ $err | Should Be 'error' - } } + } - Context 'shared InitialSessionState' { + Context 'shared InitialSessionState' { - It 'can pick the right module' { + It 'can pick the right module' { - $scriptToProcessPath = "${TestDrive}\toProcess.ps1" - Set-Content -Path $scriptToProcessPath -Value @' + $scriptToProcessPath = "${TestDrive}\toProcess.ps1" + Set-Content -Path $scriptToProcessPath -Value @' using module Foo function foo() { [Foo]::new() } '@ - # resolve name to absolute path - $scriptToProcessPath = (get-childitem $scriptToProcessPath).FullName - $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() - $iss.StartupScripts.Add($scriptToProcessPath) + # resolve name to absolute path + $scriptToProcessPath = (get-childitem $scriptToProcessPath).FullName + $iss = [System.Management.Automation.Runspaces.initialsessionstate]::CreateDefault() + $iss.StartupScripts.Add($scriptToProcessPath) - $ps = [powershell]::Create($iss) - $ps.AddCommand("foo").Invoke() | Should be Foo - $ps.Streams.Error | Should Be $null + $ps = [powershell]::Create($iss) + $ps.AddCommand("foo").Invoke() | Should be Foo + $ps.Streams.Error | Should Be $null - $ps1 = [powershell]::Create($iss) - $ps1.AddCommand("foo").Invoke() | Should be Foo - $ps1.Streams.Error | Should Be $null + $ps1 = [powershell]::Create($iss) + $ps1.AddCommand("foo").Invoke() | Should be Foo + $ps1.Streams.Error | Should Be $null - $ps.Commands.Clear() - $ps.Streams.Error.Clear() - $ps.AddScript(". foo").Invoke() | Should be Foo - $ps.Streams.Error | Should Be $null - } + $ps.Commands.Clear() + $ps.Streams.Error.Clear() + $ps.AddScript(". foo").Invoke() | Should be Foo + $ps.Streams.Error | Should Be $null } - - # this is a setup for Context "Module by path" - New-TestModule -Name FooForPaths -Content 'class Foo { [string] GetModuleName() { return "FooForPaths" } }' - - - } finally { - $env:PSMODULEPATH = $originalPSMODULEPATH } + # here we are back to normal $env:PSMODULEPATH, but all modules are there Context "Module by path" { + BeforeAll { + # this is a setup for Context "Module by path" + New-TestModule -Name FooForPaths -Content 'class Foo { [string] GetModuleName() { return "FooForPaths" } }' + $env:PSMODULEPATH = $originalPSMODULEPATH - It 'use non-modified PSMODULEPATH' { - $env:PSMODULEPATH | Should Be $originalPSMODULEPATH - } - - new-item -type directory -Force TestDrive:\FooRelativeConsumer - Set-Content -Path "${TestDrive}\FooRelativeConsumer\FooRelativeConsumer.ps1" -Value @' + new-item -type directory -Force TestDrive:\FooRelativeConsumer + Set-Content -Path "${TestDrive}\FooRelativeConsumer\FooRelativeConsumer.ps1" -Value @' using module ..\modules\FooForPaths class Bar : Foo {} [Bar]::new() '@ - Set-Content -Path "${TestDrive}\FooRelativeConsumerErr.ps1" -Value @' + Set-Content -Path "${TestDrive}\FooRelativeConsumerErr.ps1" -Value @' using module FooForPaths class Bar : Foo {} [Bar]::new() '@ + } + + It 'use non-modified PSMODULEPATH' { + $env:PSMODULEPATH | Should Be $originalPSMODULEPATH + } It "can be accessed by relative path" { $barObject = & TestDrive:\FooRelativeConsumer\FooRelativeConsumer.ps1 diff --git a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 index a1118541d..9854493d8 100644 --- a/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 +++ b/test/powershell/Language/Parser/LanguageAndParser.TestFollowup.Tests.ps1 @@ -1,3 +1,4 @@ +$powershellexe = (get-process -id $PID).mainmodule.filename Describe "Clone array" -Tags "CI" { It "Cast in target expr" { @@ -38,7 +39,7 @@ Describe "MSFT:3309783" -Tags "CI" { It "Run in another process" { # For a reliable test, we must run this in a new process because an earlier binding in this process # could mask the bug/fix. - powershell -noprofile -command "[psobject] | % FullName" | Should Be System.Management.Automation.PSObject + & $powershellexe -noprofile -command "[psobject] | % FullName" | Should Be System.Management.Automation.PSObject } It "Run in current process" { @@ -101,7 +102,7 @@ Describe "Hashtable key property syntax" -Tags "CI" { It "In different process" { # So also run in a fresh process $bytes = [System.Text.Encoding]::Unicode.GetBytes($script) - powershell -noprofile -encodedCommand ([Convert]::ToBase64String($bytes)) | Should Be Hello + & $powershellexe -noprofile -encodedCommand ([Convert]::ToBase64String($bytes)) | Should Be Hello } } diff --git a/test/powershell/Language/Scripting/Debugging/DebuggerScriptTests.Tests.ps1 b/test/powershell/Language/Scripting/Debugging/DebuggerScriptTests.Tests.ps1 index 50ca09cb1d7d0d37e0696c5926ad59d51181f4eb..5795da4494555d3ce95e8437a461a904af4b45a2 100644 GIT binary patch delta 50 zcmV-20L}mYwgSwz0 IvtThEY765M{{R30 delta 14 WcmX@Jo$22;rVT{~n-3W9WCH*=0S2f5 From 84041fb37da079a779b59d7d9a95c85e7ecaece9 Mon Sep 17 00:00:00 2001 From: James Truher Date: Wed, 20 Jul 2016 12:54:59 -0700 Subject: [PATCH 07/25] fix Get-PSBreakpoint test to not require 0 as the breakpoint id Essentially, other tests may set breakpoints and the Id is ever increasing, so a test for Id 0 will only be true if this is the first time a breakpoint has been set in this session --- .../Get-PSBreakpoint.Tests.ps1 | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-PSBreakpoint.Tests.ps1 b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-PSBreakpoint.Tests.ps1 index 7c567e8cc..c08f10f40 100644 --- a/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-PSBreakpoint.Tests.ps1 +++ b/test/powershell/Modules/Microsoft.PowerShell.Utility/Get-PSBreakpoint.Tests.ps1 @@ -13,11 +13,14 @@ { Get-PSBreakpoint -Script $fullScriptPath } | Should Not Throw $Id = (Get-PSBreakpoint -Script $fullScriptPath).Id - $Id | Should be 0 - + # if breakpoints have been set by other tests, the number may or may not be 0 + # so we can't check against a specific number + # however, we can be sure that we're getting an int and that the int is + # greater or equal to 0 + ([int]$Id) -ge 0 | should be $true } - It "sshould be able to get PSBreakpoint with using Variable switch" { + It "should be able to get PSBreakpoint with using Variable switch" { Set-PSBreakpoint -Script $fullScriptPath -Variable "$scriptName" { Get-PSBreakpoint -Variable "$scriptName" -Script $fullScriptPath } | Should Not Throw From ab5bda18e07f56f95b3ab53a7bdcd99033a878bd Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Wed, 20 Jul 2016 13:49:44 -0700 Subject: [PATCH 08/25] Fix removing all lines from crontab --- demos/crontab.ps1 | 23 ++++++++++++++++++++--- demos/modules/CronTab/CronTab.psm1 | 9 ++++++--- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/demos/crontab.ps1 b/demos/crontab.ps1 index d90e2dbdb..85bd63139 100644 --- a/demos/crontab.ps1 +++ b/demos/crontab.ps1 @@ -1,9 +1,26 @@ -# Use the crontab module available at ./modules/CronTab + +if (!($env:PSMODULEPATH -split ';' | Where-Object { $_.StartsWith($PSScriptRoot) })) +{ + $env:PSMODULEPATH += ";$PSScriptRoot/modules" +} + +Import-Module CronTab # Get the existing cron jobs +Get-CronJob + +# New cron job to clean out tmp every day at 1am +New-CronJob -Command 'rm -rf /tmp/*' -Hour 1 + +# New cron job to start a build +New-CronJob -Command 'powershell -c "cd ~/src/PowerShell; ipmo ./build.psm1; Start-PSBuild"' -Hour 2 -DayOfWeek 1-5 # Sort them by data - -# Create a new one using a DateTime object +Get-CronJob | Sort-Object Command # Show in bash that the new cron job exists +crontab -l + +# Remove a cron job +Get-CronJob | Where-Object { $_.Command -match '^powershell.*' } | Remove-CronJob + diff --git a/demos/modules/CronTab/CronTab.psm1 b/demos/modules/CronTab/CronTab.psm1 index 0301636b2..61cf8ecf7 100644 --- a/demos/modules/CronTab/CronTab.psm1 +++ b/demos/modules/CronTab/CronTab.psm1 @@ -1,3 +1,6 @@ + +using namespace System.Collections.Generic + $crontabcmd = "/usr/bin/crontab" class CronJob { @@ -15,7 +18,7 @@ function Get-CronTab ([String] $user) { $crontab = Invoke-CronTab -user $user -arguments "-l" -noThrow if ($crontab -is [System.Management.Automation.ErrorRecord]) { if ($crontab.Exception.Message.StartsWith("no crontab for ")) { - $crontab = $null + $crontab = @() } else { throw $crontab.Exception @@ -85,7 +88,7 @@ function Remove-CronJob { process { [string[]] $crontab = Get-CronTab -user $UserName - [string[]] $newcrontab = $null + $newcrontab = [List[string]]::new() $found = $false foreach ($line in $crontab) { @@ -93,7 +96,7 @@ function Remove-CronJob { if ((Compare-object $cronjob.psobject.properties $Job.psobject.properties) -eq $null) { $found = $true } else { - $newcrontab += $line + $newcrontab.Add($line) } } From ffe0a250a90e522adfc872b88632ba8a9a074b28 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Tue, 19 Jul 2016 16:22:59 -0700 Subject: [PATCH 09/25] Changed Find-Package test to 'SLOW' --- test/powershell/Modules/PackageManagement/Nuget.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/powershell/Modules/PackageManagement/Nuget.Tests.ps1 b/test/powershell/Modules/PackageManagement/Nuget.Tests.ps1 index 7c3712a1d..54dd7df37 100644 --- a/test/powershell/Modules/PackageManagement/Nuget.Tests.ps1 +++ b/test/powershell/Modules/PackageManagement/Nuget.Tests.ps1 @@ -281,7 +281,7 @@ Describe "Event Test" -Tags @('BVT', 'DRT') { } } -Describe "Find-Package" -Tags @('BVT', 'DRT'){ +Describe "Find-Package" -Tags @('CI','SLOW'){ it "EXPECTED: Find a package with a location created via new-psdrive" -Skip:(-not $IsWindows) { $Error.Clear() New-PSDrive -Name xx -PSProvider FileSystem -Root $TestDrive -warningaction:silentlycontinue -ea silentlycontinue > $null; find-package -name "fooobarrr" -provider nuget -source xx:\ -warningaction:silentlycontinue -ea silentlycontinue From c69104a7505118741cb67314c8b282f7634ec8ed Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Tue, 19 Jul 2016 16:47:48 -0700 Subject: [PATCH 10/25] Made appveyor exclude 'slow' tests --- appveyor.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index c33a35031..7b9e368cb 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,14 +34,15 @@ test_script: $env:CoreOutput = Split-Path -Parent (Get-PSOutput -Options (New-PSOptions -Publish)) Write-Host -Foreground Green 'Run CoreCLR tests' $testResultsFile = "$pwd\TestsResults.xml" - & ("$env:CoreOutput\powershell.exe") -noprofile -noninteractive -c "Invoke-Pester test/powershell -OutputFormat NUnitXml -OutputFile $testResultsFile" + & ("$env:CoreOutput\powershell.exe") -noprofile -noninteractive -c "Invoke-Pester test/powershell -ExcludeTag 'Slow' -OutputFormat NUnitXml -OutputFile $testResultsFile" + (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile)) # # FullCLR $env:FullOutput = Split-Path -Parent (Get-PSOutput -Options (New-PSOptions -FullCLR)) Write-Host -Foreground Green 'Run FullCLR tests' $testResultsFileFullCLR = "$pwd\TestsResults.FullCLR.xml" - Start-DevPowerShell -NoNewWindow -ArgumentList '-noprofile', '-noninteractive' -Command "Invoke-Pester test/fullCLR -OutputFormat NUnitXml -OutputFile $testResultsFileFullCLR" + Start-DevPowerShell -NoNewWindow -ArgumentList '-noprofile', '-noninteractive' -Command "Invoke-Pester test/fullCLR -ExcludeTag 'Slow' -OutputFormat NUnitXml -OutputFile $testResultsFileFullCLR" (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFileFullCLR)) # # Fail the build, if tests failed From 7f97e4b9cfab6c100530be47423f3566d65da24a Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Jul 2016 14:46:54 -0700 Subject: [PATCH 11/25] Added CI Tag to sample --- docs/cmdlet-example/SendGreeting.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cmdlet-example/SendGreeting.Tests.ps1 b/docs/cmdlet-example/SendGreeting.Tests.ps1 index c4390fbe2..039be41eb 100644 --- a/docs/cmdlet-example/SendGreeting.Tests.ps1 +++ b/docs/cmdlet-example/SendGreeting.Tests.ps1 @@ -1,4 +1,4 @@ -Describe "Send-Greeting cmdlet" -Tag 'Slow' { +Describe "Send-Greeting cmdlet" -Tag 'Slow','CI' { It "Should be able build the cmdlet" { Remove-Item -Recurse -Force bin -ErrorAction SilentlyContinue dotnet restore --verbosity Error | Should BeNullOrEmpty From 441b105fd82be6eef314907c952b6bf9fb5edf92 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Jul 2016 14:47:58 -0700 Subject: [PATCH 12/25] Made the test run in new powershell Ran the test with the same filter Imported the result cleaned up the test run --- test/powershell/SDK/CmdletExample.Tests.ps1 | 78 +++++++++++++++++++-- 1 file changed, 71 insertions(+), 7 deletions(-) diff --git a/test/powershell/SDK/CmdletExample.Tests.ps1 b/test/powershell/SDK/CmdletExample.Tests.ps1 index 517d7a51a..25f64e715 100644 --- a/test/powershell/SDK/CmdletExample.Tests.ps1 +++ b/test/powershell/SDK/CmdletExample.Tests.ps1 @@ -1,9 +1,73 @@ -try { - $enlistmentRoot = git rev-parse --show-toplevel +# Looking at pester internal to get tag filter and ExcludeTagFilter +# This seems like the most stable way to do this +# other options like testing for tags seems more likely to break + +InModuleScope Pester { + Describe 'Getting Tag Filters' -Tag CI { + $global:__PesterTags = $pester.TagFilter + $global:__PesterExcludeTags = $pester.ExcludeTagFilter + } +} +Describe 'SDK Send Greeting Sample Tests' -Tag CI { + + try { + $enlistmentRoot = git rev-parse --show-toplevel + $docLocation = Join-Path -Path $enlistmentRoot -ChildPath '\docs\cmdlet-example' + $testResultPath = Join-Path $TestDrive 'sendgreetingresults.xml' + $sampleCopy = Join-Path $TestDrive 'sendgreeting' + $fullSampleCopyPath = Join-Path $sampleCopy 'cmdlet-example' + if(!(Test-Path $sampleCopy)) + { + New-Item -ItemType Directory -Path $sampleCopy + } + + Copy-Item -Recurse -Path $docLocation -Destination $sampleCopy -Force + dir -Recurse $sampleCopy | %{ Write-Verbose "sc: $($_.FullName)"} + +$pesterCommand = "Invoke-Pester $sampleCopy -PassThru" +if($global:__PesterTags) +{ + $pesterCommand += " -Tag $(@($global:__PesterTags) -join ',')" +} + +if($global:__PesterExcludeTags) +{ + $pesterCommand += " -ExcludeTag $(@($global:__PesterExcludeTags) -join ',')" +} + + $command = @" +Push-Location -Path $fullSampleCopyPath +Import-module $(Join-path $env:PSModulePath pester) +$pesterCommand | Export-Clixml -Path $testResultPath +"@ + + Write-Verbose -Message "command: '$command'" -Verbose + $bytes = [System.Text.Encoding]::Unicode.GetBytes($command) + $encodedCommand = [Convert]::ToBase64String($bytes) + &"$PSHOME/Powershell.exe" -encodedCommand $encodedCommand + + it "Should have test results file" { + $testResultPath | should exist + $script:results = Import-Clixml $testResultPath + } + #$host.EnterNestedPrompt(); + it "Should have test results" { + $script:results | should not be BeNullOrEmpty + $script:results.TotalCount | should not BeNullOrEmpty + $script:results.TestResult.Count | should not BeNullOrEmpty + } + + foreach($testResult in $script:results.TestResult){ + it "Test $($testResult.Name) should not fail" { + $testResult.FailureMessage + $testResult.StackTrace | should BeNullOrEmpty + $testResult.ErrorRecord | should BeNullOrEmpty + Write-Verbose "Result: $($testResult.Result)" + $testResult.Result | should not be Failed + } + } + + } finally { + Pop-Location + } - $docLocation = [io.path]::Combine($enlistmentRoot, "docs","cmdlet-example") - Push-Location $docLocation - ./SendGreeting.Tests.ps1 -} finally { - Pop-Location } From a51618aa4cb738fb0c5953428802270cde85254a Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Jul 2016 14:48:36 -0700 Subject: [PATCH 13/25] Removed test for appveyor.yml --- appveyor.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 7b9e368cb..a63c3cd45 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -58,12 +58,6 @@ test_script: { throw "$($x.'test-results'.failures) tests in test/fullCLR failed" } - # - # Portable module test - Write-Host -Foreground Green 'Test use of cross-platform binary module' - Import-Module ./docs/cmdlet-example/bin/Debug/netstandard1.3/SendGreeting.dll - Send-Greeting -Name World - on_finish: - ps: | From 6fea33379d642a67ec1f9f3ccc6f3905c666d773 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Jul 2016 15:10:39 -0700 Subject: [PATCH 14/25] made powershell invoke compatible with linux --- test/powershell/SDK/CmdletExample.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/powershell/SDK/CmdletExample.Tests.ps1 b/test/powershell/SDK/CmdletExample.Tests.ps1 index 25f64e715..287ea3950 100644 --- a/test/powershell/SDK/CmdletExample.Tests.ps1 +++ b/test/powershell/SDK/CmdletExample.Tests.ps1 @@ -44,7 +44,7 @@ $pesterCommand | Export-Clixml -Path $testResultPath Write-Verbose -Message "command: '$command'" -Verbose $bytes = [System.Text.Encoding]::Unicode.GetBytes($command) $encodedCommand = [Convert]::ToBase64String($bytes) - &"$PSHOME/Powershell.exe" -encodedCommand $encodedCommand + &"$PSHOME/powershell" -encodedCommand $encodedCommand it "Should have test results file" { $testResultPath | should exist From cd9ddb156ddc072752b15850279e530aedd22947 Mon Sep 17 00:00:00 2001 From: Travis Plunk Date: Wed, 20 Jul 2016 16:16:21 -0700 Subject: [PATCH 15/25] Used method from PR comments to find powershell replaced dir with get-childitem used appropriate path when importing pester verified failure count was 0 split test case verification into individual it's so we get all the info. --- test/powershell/SDK/CmdletExample.Tests.ps1 | 58 ++++++++++++++------- 1 file changed, 39 insertions(+), 19 deletions(-) diff --git a/test/powershell/SDK/CmdletExample.Tests.ps1 b/test/powershell/SDK/CmdletExample.Tests.ps1 index 287ea3950..4b41f314b 100644 --- a/test/powershell/SDK/CmdletExample.Tests.ps1 +++ b/test/powershell/SDK/CmdletExample.Tests.ps1 @@ -16,53 +16,73 @@ Describe 'SDK Send Greeting Sample Tests' -Tag CI { $testResultPath = Join-Path $TestDrive 'sendgreetingresults.xml' $sampleCopy = Join-Path $TestDrive 'sendgreeting' $fullSampleCopyPath = Join-Path $sampleCopy 'cmdlet-example' + $powershell = (Get-Process -id $PID).MainModule.FileName if(!(Test-Path $sampleCopy)) { New-Item -ItemType Directory -Path $sampleCopy } Copy-Item -Recurse -Path $docLocation -Destination $sampleCopy -Force - dir -Recurse $sampleCopy | %{ Write-Verbose "sc: $($_.FullName)"} + Get-ChildItem -Recurse $sampleCopy | %{ Write-Verbose "sc: $($_.FullName)"} -$pesterCommand = "Invoke-Pester $sampleCopy -PassThru" -if($global:__PesterTags) -{ - $pesterCommand += " -Tag $(@($global:__PesterTags) -join ',')" -} + $pesterCommand = "Invoke-Pester $sampleCopy -PassThru" + if($global:__PesterTags) + { + $pesterCommand += " -Tag $(@($global:__PesterTags) -join ',')" + } -if($global:__PesterExcludeTags) -{ - $pesterCommand += " -ExcludeTag $(@($global:__PesterExcludeTags) -join ',')" -} + if($global:__PesterExcludeTags) + { + $pesterCommand += " -ExcludeTag $(@($global:__PesterExcludeTags) -join ',')" + } + + $importPesterCommand = 'Import-module Pester' + if($isCore) + { + $importPesterCommand = "Import-Module $(Join-Path -path $PSHOME -child '/Modules/Pester')" + } $command = @" Push-Location -Path $fullSampleCopyPath -Import-module $(Join-path $env:PSModulePath pester) +$importPesterCommand $pesterCommand | Export-Clixml -Path $testResultPath "@ - Write-Verbose -Message "command: '$command'" -Verbose + Write-Verbose -Message "command: '$command'" $bytes = [System.Text.Encoding]::Unicode.GetBytes($command) $encodedCommand = [Convert]::ToBase64String($bytes) - &"$PSHOME/powershell" -encodedCommand $encodedCommand + &$powershell -encodedCommand $encodedCommand it "Should have test results file" { $testResultPath | should exist $script:results = Import-Clixml $testResultPath } - #$host.EnterNestedPrompt(); + it "Should have test results" { $script:results | should not be BeNullOrEmpty $script:results.TotalCount | should not BeNullOrEmpty $script:results.TestResult.Count | should not BeNullOrEmpty } + it "Should have no failures" { + $script:results.FailedCount | should be 0 + } + foreach($testResult in $script:results.TestResult){ - it "Test $($testResult.Name) should not fail" { - $testResult.FailureMessage + $testResult.StackTrace | should BeNullOrEmpty - $testResult.ErrorRecord | should BeNullOrEmpty - Write-Verbose "Result: $($testResult.Result)" - $testResult.Result | should not be Failed + Context "Test $($testResult.Name)" { + it "should have no failure message" { + $testResult.FailureMessage | should BeNullOrEmpty + } + it "should have no stack trace" { + $testResult.StackTrace | should BeNullOrEmpty + } + it "should have no error record" { + $testResult.ErrorRecord | should BeNullOrEmpty + } + it "should have not failed" { + Write-Verbose "Result: $($testResult.Result)" + $testResult.Result | should not be Failed + } } } From 7b2d101668a3fd01d6c6c0a44d3b40af8f7a4498 Mon Sep 17 00:00:00 2001 From: Jianyun Tao Date: Wed, 20 Jul 2016 17:09:41 -0700 Subject: [PATCH 16/25] added "recursive cloning" back to readme per request #1197 --- README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4b1e5f6d3..799431411 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,16 @@ Building PowerShell |-----------------------|-------|-------------------|------| | Build from **Source** | [Instructions][build-linux] | [Instructions][build-wc] | [Instructions][build-osx] | +Downloading the Source Code +---------------------- +The PowerShell repository has a number of other repositories embedded as submodules. +To make things easy, we can just clone recursively. + +```sh +git clone --recursive https://github.com/PowerShell/PowerShell.git +``` +See [working with the PowerShell repository][powershell-repo-101] for more information. + Developing and Contributing -------------------------- @@ -46,7 +56,7 @@ If you encounter issues in your development, please consult the [known issues][k and [FAQ][faq] documents to see if the issue you are running into is captured and if a workaround exists. -If you encounter issues with PowerShell itself, first search for it in our [issues][github-issues]. +If you encounter issues with PowerShell itself, first search for it in our [issues][github-issues]. If you do not see your issue captured, please file a [new issue][new-issue]. For more details see [Contributing to issues][contribution-issues]. PowerShell Community @@ -63,7 +73,7 @@ Legal and Licensing Code of Conduct --------------- -This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code]. +This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code]. For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [opencode@microsoft.com][conduct-email] with any additional questions or comments. @@ -85,3 +95,4 @@ For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact [new-issue]:https://github.com/PowerShell/PowerShell/issues/new [pls-omi-provider]: https://github.com/PowerShell/psl-omi-provider [releases]: https://github.com/PowerShell/PowerShell/releases +[powershell-repo-101]: docs/git/powershell-repository-101.md From 31344808bde326abe1c6f7ea9a6f9f120b743e83 Mon Sep 17 00:00:00 2001 From: Jianyun Tao Date: Wed, 20 Jul 2016 17:23:06 -0700 Subject: [PATCH 17/25] Updated the Downloading Source code section to address the feedback from lzybkr. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 799431411..1813aca07 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,12 @@ To make things easy, we can just clone recursively. ```sh git clone --recursive https://github.com/PowerShell/PowerShell.git ``` + +If you already cloned but forgot to use `--recursive`, you can update submodules manually: +```sh +git submodule init +git submodule update +``` See [working with the PowerShell repository][powershell-repo-101] for more information. Developing and Contributing From ae69d1c1db451b335add37be3d0434da571c06da Mon Sep 17 00:00:00 2001 From: hiteshraigandhi Date: Wed, 20 Jul 2016 17:30:51 -0700 Subject: [PATCH 18/25] Create README.md --- demos/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 demos/README.md diff --git a/demos/README.md b/demos/README.md new file mode 100644 index 000000000..5ae138c32 --- /dev/null +++ b/demos/README.md @@ -0,0 +1,3 @@ +This folder contains PowerShell demo. + +Create a new folder for each demo. From ec1369f1dcbee63c581c28fef13feeddf8517446 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 08:44:36 -0700 Subject: [PATCH 19/25] Add argument completion for -UserName --- demos/modules/CronTab/CronTab.psd1 | Bin 3520 -> 3556 bytes demos/modules/CronTab/CronTab.psm1 | 52 ++++++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/demos/modules/CronTab/CronTab.psd1 b/demos/modules/CronTab/CronTab.psd1 index 23d9d95f051b15feb3b1c0ca41ca273156cd41b4..5a73eb59cf2eb65bbaca9301c1427f3eb49f9234 100644 GIT binary patch delta 30 mcmX>g{X}}h7WT9hCIAHL diff --git a/demos/modules/CronTab/CronTab.psm1 b/demos/modules/CronTab/CronTab.psm1 index 61cf8ecf7..b49201ce9 100644 --- a/demos/modules/CronTab/CronTab.psm1 +++ b/demos/modules/CronTab/CronTab.psm1 @@ -1,5 +1,6 @@ using namespace System.Collections.Generic +using namespace System.Management.Automation $crontabcmd = "/usr/bin/crontab" @@ -16,7 +17,7 @@ class CronJob { function Get-CronTab ([String] $user) { $crontab = Invoke-CronTab -user $user -arguments "-l" -noThrow - if ($crontab -is [System.Management.Automation.ErrorRecord]) { + if ($crontab -is [ErrorRecord]) { if ($crontab.Exception.Message.StartsWith("no crontab for ")) { $crontab = @() } @@ -82,8 +83,16 @@ function Remove-CronJob { #> [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")] param ( - [Alias("u")][Parameter(Mandatory=$false)][String] $UserName, - [Alias("j")][Parameter(Mandatory=$true,ValueFromPipeline=$true)][CronJob] $Job + [ArgumentCompleter( { $wordToComplete = $args[2]; Get-CronTabUser | Where-Object { $_ -like "$wordToComplete*" } | Sort-Object } )] + [Alias("u")] + [Parameter(Mandatory=$false)] + [String] + $UserName, + + [Alias("j")] + [Parameter(Mandatory=$true,ValueFromPipeline=$true)] + [CronJob] + $Job ) process { @@ -141,7 +150,12 @@ function New-CronJob { #> [CmdletBinding()] param ( - [Alias("u")][Parameter(Mandatory=$false)][String] $UserName, + [ArgumentCompleter( { $wordToComplete = $args[2]; Get-CronTabUser | Where-Object { $_ -like "$wordToComplete*" } | Sort-Object } )] + [Alias("u")] + [Parameter(Mandatory=$false)] + [String] + $UserName, + [Alias("mi")][Parameter(Position=1)][String[]] $Minute = "*", [Alias("h")][Parameter(Position=2)][String[]] $Hour = "*", [Alias("dm")][Parameter(Position=3)][String[]] $DayOfMonth = "*", @@ -188,3 +202,33 @@ function Get-CronJob { } } } + +function Get-CronTabUser { +<# +.SYNOPSIS + Returns the users allowed to use crontab +#> + [CmdletBinding()] + [OutputType([String])] + param() + + $allow = '/etc/cron.allow' + if (Test-Path $allow) + { + Get-Content $allow + } + else + { + $users = Get-Content /etc/passwd | ForEach-Object { ($_ -split ':')[0] } + $deny = '/etc/cron.deny' + if (Test-Path $deny) + { + $denyUsers = Get-Content $deny + $users | Where-Object { $denyUsers -notcontains $_ } + } + else + { + $users + } + } +} From 3dafae5357926de040ee2f3e8d9647af6d0a1bc6 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 08:50:11 -0700 Subject: [PATCH 20/25] Move crontab demo to it's own directory Also change CronTab.psd1 encoding to ASCII --- .../CronTab/CronTab.ps1xml | 0 demos/crontab/CronTab/CronTab.psd1 | 61 ++++++++++++++++++ .../{modules => crontab}/CronTab/CronTab.psm1 | 0 demos/{ => crontab}/crontab.ps1 | 7 +- demos/modules/CronTab/CronTab.psd1 | Bin 3556 -> 0 bytes 5 files changed, 62 insertions(+), 6 deletions(-) rename demos/{modules => crontab}/CronTab/CronTab.ps1xml (100%) create mode 100755 demos/crontab/CronTab/CronTab.psd1 rename demos/{modules => crontab}/CronTab/CronTab.psm1 (100%) rename demos/{ => crontab}/crontab.ps1 (75%) delete mode 100644 demos/modules/CronTab/CronTab.psd1 diff --git a/demos/modules/CronTab/CronTab.ps1xml b/demos/crontab/CronTab/CronTab.ps1xml similarity index 100% rename from demos/modules/CronTab/CronTab.ps1xml rename to demos/crontab/CronTab/CronTab.ps1xml diff --git a/demos/crontab/CronTab/CronTab.psd1 b/demos/crontab/CronTab/CronTab.psd1 new file mode 100755 index 000000000..15c279cbb --- /dev/null +++ b/demos/crontab/CronTab/CronTab.psd1 @@ -0,0 +1,61 @@ +@{ + +# Script module or binary module file associated with this manifest. +RootModule = 'CronTab.psm1' + +# Version number of this module. +ModuleVersion = '0.1.0.0' + +# Supported PSEditions +CompatiblePSEditions = @('Linux') + +# ID used to uniquely identify this module +GUID = '508bb97f-de2e-482e-aae2-01caec0be8c7' + +# Author of this module +Author = 'Microsoft' + +# Company or vendor of this module +CompanyName = 'Unknown' + +# Copyright statement for this module +Copyright = '(c) 2016 Microsoft. All rights reserved.' + +# Description of the functionality provided by this module +Description = 'Sample module for managing CronTab' + +# Format files (.ps1xml) to be loaded when importing this module +FormatsToProcess = 'CronTab.ps1xml' + +# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. +FunctionsToExport = 'New-CronJob','Remove-CronJob','Get-CronJob','Get-CronTabUser' + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + # Tags = @() + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + # ProjectUri = '' + + # A URL to an icon representing this module. + # IconUri = '' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + } # End of PSData hashtable + +} # End of PrivateData hashtable + +# HelpInfo URI of this module +# HelpInfoURI = '' + +} + diff --git a/demos/modules/CronTab/CronTab.psm1 b/demos/crontab/CronTab/CronTab.psm1 similarity index 100% rename from demos/modules/CronTab/CronTab.psm1 rename to demos/crontab/CronTab/CronTab.psm1 diff --git a/demos/crontab.ps1 b/demos/crontab/crontab.ps1 similarity index 75% rename from demos/crontab.ps1 rename to demos/crontab/crontab.ps1 index 85bd63139..9e17acaed 100644 --- a/demos/crontab.ps1 +++ b/demos/crontab/crontab.ps1 @@ -1,10 +1,5 @@ -if (!($env:PSMODULEPATH -split ';' | Where-Object { $_.StartsWith($PSScriptRoot) })) -{ - $env:PSMODULEPATH += ";$PSScriptRoot/modules" -} - -Import-Module CronTab +Import-Module $PSScriptRoot/CronTab/CronTab.psd1 # Get the existing cron jobs Get-CronJob diff --git a/demos/modules/CronTab/CronTab.psd1 b/demos/modules/CronTab/CronTab.psd1 deleted file mode 100644 index 5a73eb59cf2eb65bbaca9301c1427f3eb49f9234..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3556 zcmbuCUr!TJ5XI-&#P6_)4`Ko;Mg<>CAb^TNVg!6|p@mhtTiYTU!&g_&?+%x{ciTmc zA$9-Uxie?Z%$&Ra`nh30?5^Djzk62LfgRh#N*miuPa~`C#4haI270QsQdnQBmGyN0 zea>R2HMi~ARDZR+m9m{_|B0+G^)$0H-SKZJ3u5%-ue70Trh5M{VELfEwVvMT--O_$ z?C;x_V%OSpsGWV;kF{@Pt6FJh@=^XKS}B8VA^Qti^}}0o8MaWQIF6thx$VnwJXY*g z{kT4EML$rqvHsvJX8&I7?kF2La8%QPcuQ6z*}_vPgfO_swlLyu*pg!H$&!q|>Ha~M z_pWWr>Oz@2=QDZd8IHf`I*@-VR|?5MZ;3e!Z<;kpY{2PjJ2Lm{nLks$>q6SM7q+I| zE5X+j`CGB4TJgJ{G8*W9Mb@k81fGs{PYu>}x7OlpQ|Q3peRf-gz?54%u6N3X{;icg z`)92PiRmh2?2*0-Po)h0E^h8)Us;W`-)nPUJXQ8tzgoRsEr>o=T;}Ukc`{*iyw3+- zs74{&j-Ko-MWW~Fy0LuM^6uS-CiMT%dtEU---E#aSP_Vv>s92@6{aW7 znVD1B0K&HzxaD+8l>1VQRpr@9u>*YfOD;(2NPXE!wCRq`3{(*uIQ(q7+ifyIDLrWI_Dl( zLr1;S@IH}e*Se_oV8sioTtPSAv5Oh7jfV0K;M1SJ zE||jRIg96ZM(8cO6Cdf!bWM6M6qkX~gTM)j={=Bhnov>q<`-3=XZ~{RT~^Who^~7x z+3EFc@Ew7IHqZ0L`Vo0`6)&z)kL53Mlh+lNrh#ud(ZA`Ob1Fd8O79C{fkSwvoAB>U zM9lG;WA{v#8eb^UE?%+U)bB`rw5My{gTzNoD#ZguRG+Tw`7Vf_ zxqI#NU+|%KbPrlHuzgJyT%oh++admcAe}f}_3ZZM$~#}vD@CH7J7R^C#-rwQIj Date: Thu, 21 Jul 2016 09:18:28 -0700 Subject: [PATCH 21/25] Fix Remove-CronTab to only remove the correct job(s) --- demos/crontab/CronTab/CronTab.psm1 | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/demos/crontab/CronTab/CronTab.psm1 b/demos/crontab/CronTab/CronTab.psm1 index b49201ce9..b694ddf52 100644 --- a/demos/crontab/CronTab/CronTab.psm1 +++ b/demos/crontab/CronTab/CronTab.psm1 @@ -11,6 +11,12 @@ class CronJob { [string] $Month [string] $DayOfWeek [string] $Command + + [string] ToString() + { + return "{0} {1} {2} {3} {4} {5}" -f + $this.Minute, $this.Hour, $this.DayOfMonth, $this.Month, $this.DayOfWeek, $this.Command + } } # Internal helper functions @@ -100,9 +106,9 @@ function Remove-CronJob { $newcrontab = [List[string]]::new() $found = $false + $JobAsString = $Job.ToString() foreach ($line in $crontab) { - $cronjob = ConvertTo-CronJob -crontab $line - if ((Compare-object $cronjob.psobject.properties $Job.psobject.properties) -eq $null) { + if ($JobAsString -ceq $line) { $found = $true } else { $newcrontab.Add($line) From d7d516a0662c578b15daec7e5d3ddc257cb41471 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 09:40:03 -0700 Subject: [PATCH 22/25] Don't use Invoke-Expression --- demos/crontab/CronTab/CronTab.psm1 | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/demos/crontab/CronTab/CronTab.psm1 b/demos/crontab/CronTab/CronTab.psm1 index b694ddf52..813de1834 100644 --- a/demos/crontab/CronTab/CronTab.psm1 +++ b/demos/crontab/CronTab/CronTab.psm1 @@ -47,14 +47,13 @@ function ConvertTo-CronJob ([String] $crontab) { $cronjob } -function Invoke-CronTab ([String] $user, [String] $arguments, [Switch] $noThrow) { +function Invoke-CronTab ([String] $user, [String[]] $arguments, [Switch] $noThrow) { If ($user -ne [String]::Empty) { - $arguments = "-u $UserName $arguments" + $arguments = Write-Output "-u" $UserName $arguments } - $cmd = "$crontabcmd $arguments 2>&1" - Write-Verbose $cmd - $output = Invoke-Expression $cmd + Write-Verbose "Running: $crontabcmd $arguments" + $output = & $crontabcmd @arguments 2>&1 if ($LastExitCode -ne 0 -and -not $noThrow) { $e = New-Object System.InvalidOperationException -ArgumentList $output.Exception.Message throw $e From 444769cb6b19a1963128815e38287260df9da77a Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 09:55:48 -0700 Subject: [PATCH 23/25] Add -Force to Remove-CronJob --- demos/crontab/CronTab/CronTab.psm1 | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/demos/crontab/CronTab/CronTab.psm1 b/demos/crontab/CronTab/CronTab.psm1 index 813de1834..5dd80e846 100644 --- a/demos/crontab/CronTab/CronTab.psm1 +++ b/demos/crontab/CronTab/CronTab.psm1 @@ -85,6 +85,8 @@ function Remove-CronJob { Optional parameter to specify a specific user's cron table .PARAMETER Job Cron job object returned from Get-CronJob +.PARAMETER Force + Don't prompt when removing the cron job #> [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")] param ( @@ -97,7 +99,10 @@ function Remove-CronJob { [Alias("j")] [Parameter(Mandatory=$true,ValueFromPipeline=$true)] [CronJob] - $Job + $Job, + + [Switch] + $Force ) process { @@ -118,7 +123,7 @@ function Remove-CronJob { $e = New-Object System.Exception -ArgumentList "Job not found" throw $e } - if ($pscmdlet.ShouldProcess($Job.Command,"Remove")) { + if ($Force -or $pscmdlet.ShouldProcess($Job.Command,"Remove")) { Import-CronTab -user $UserName -crontab $newcrontab } } From 3fb9315fced5c506e67a6099954a158112800654 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 09:56:14 -0700 Subject: [PATCH 24/25] cleanup demo script --- demos/crontab/crontab.ps1 | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/demos/crontab/crontab.ps1 b/demos/crontab/crontab.ps1 index 9e17acaed..91a396a66 100644 --- a/demos/crontab/crontab.ps1 +++ b/demos/crontab/crontab.ps1 @@ -1,21 +1,27 @@ Import-Module $PSScriptRoot/CronTab/CronTab.psd1 -# Get the existing cron jobs -Get-CronJob +Write-Host -Foreground Yellow "Remove all jobs to start, no prompting" +Get-CronJob | Remove-CronJob -Force -# New cron job to clean out tmp every day at 1am -New-CronJob -Command 'rm -rf /tmp/*' -Hour 1 +Write-Host -Foreground Yellow "Get the existing cron jobs" +Get-CronJob | Out-Host -# New cron job to start a build -New-CronJob -Command 'powershell -c "cd ~/src/PowerShell; ipmo ./build.psm1; Start-PSBuild"' -Hour 2 -DayOfWeek 1-5 +Write-Host -Foreground Yellow "New cron job to clean out tmp every day at 1am" +New-CronJob -Command 'rm -rf /tmp/*' -Hour 1 | Out-Host -# Sort them by data -Get-CronJob | Sort-Object Command +Write-Host -Foreground Yellow "Add some more jobs" +New-CronJob -Command 'python -c ~/scripts/backup_users' -Hour 2 -DayOfWeek 1-5 | Out-Host +New-CronJob -Command 'powershell -c "cd ~/src/PowerShell; ipmo ./build.psm1; Start-PSBuild"' -Hour 2 -DayOfWeek * | Out-Host -# Show in bash that the new cron job exists +Write-Host -Foreground Yellow "Show in bash that the new cron job exists" crontab -l -# Remove a cron job -Get-CronJob | Where-Object { $_.Command -match '^powershell.*' } | Remove-CronJob +Write-Host -Foreground Yellow "Get jobs that run every day" +Get-CronJob | Where-Object { $_.DayOfWeek -eq '*' -or $_.DayOfWeek -eq '1-7' } | Out-Host +Write-Host -Foreground Yellow "Remove one cron job, with prompting to confirm" +Get-CronJob | Where-Object { $_.Command -match '^powershell.*' } | Remove-CronJob | Out-Host + +Write-Host -Foreground Yellow "And the other job remains" +Get-CronJob | Out-Host From d300bf0ef7b6db191dae63b716e29278418b5c47 Mon Sep 17 00:00:00 2001 From: Jason Shirk Date: Thu, 21 Jul 2016 10:03:08 -0700 Subject: [PATCH 25/25] Add README --- demos/crontab/README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 demos/crontab/README.md diff --git a/demos/crontab/README.md b/demos/crontab/README.md new file mode 100644 index 000000000..02138b1e4 --- /dev/null +++ b/demos/crontab/README.md @@ -0,0 +1,15 @@ +## CronTab demo + +This demo shows examining, creating, and removing cron jobs via crontab. + +Output of Get-CronJob is a strongly typed object with properties like DayOfWeek or Command. +Remove-CronJob prompts before removing the job unless you specifiy -Force. + +Tab completion of -UserName is supported, e.g. + +Get-CronJob -u + +NYI: no way to run crontab with sudo if necessary +NYI: ignoring shell variables or comments +NYI: New-CronJob -Description "..." (save in comments" +NYI: @reboot,@daily,@hourly,etc