Merge pull request #1447 from PowerShell/jameswtruher/LanguageTestMigration
language pester test migration - woo - adding 800 more tests
This commit is contained in:
commit
a6072c4576
15
test/powershell/Language/Classes/MSFT_778492.psm1
Normal file
15
test/powershell/Language/Classes/MSFT_778492.psm1
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
$foo = 'MSFT_778492 script scope'
|
||||
|
||||
class MSFT_778492
|
||||
{
|
||||
[string] F()
|
||||
{
|
||||
return $script:foo
|
||||
}
|
||||
}
|
||||
|
||||
function Get-MSFT_778492
|
||||
{
|
||||
[MSFT_778492]::new()
|
||||
}
|
188
test/powershell/Language/Classes/ProtectedAccess.Tests.ps1
Normal file
188
test/powershell/Language/Classes/ProtectedAccess.Tests.ps1
Normal file
|
@ -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 "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 }
|
||||
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 "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 }
|
||||
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 "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 }
|
||||
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 }
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
Describe 'Attributes Test' -Tags "CI" {
|
||||
|
||||
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","mscorlib"
|
||||
}
|
||||
|
||||
|
||||
|
||||
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' -Tag "CI" {
|
||||
# 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
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,834 @@
|
|||
#
|
||||
# Copyright (c) Microsoft Corporation, 2014
|
||||
#
|
||||
Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1 -force
|
||||
|
||||
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
|
||||
# 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 "CI" {
|
||||
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 "CI" {
|
||||
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 "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 "CI" {
|
||||
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 "CI" {
|
||||
# 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 "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.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", 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 "CI" {
|
||||
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 "CI" {
|
||||
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 "CI" {
|
||||
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 "CI" {
|
||||
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 "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"
|
||||
}) -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 "CI" {
|
||||
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 "CI" {
|
||||
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 "CI" {
|
||||
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 "CI" {
|
||||
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 "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 "CI" {
|
||||
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 "CI" {
|
||||
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' -pending {
|
||||
|
||||
$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]::CreateDefault2()
|
||||
$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' -pending {
|
||||
$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'
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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"'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
||||
Setup -Dir $Name
|
||||
$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 ((get-childitem 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
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
Describe 'NestedModules' -Tags "DRT" {
|
||||
|
||||
Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1
|
||||
|
||||
function New-TestModule {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$Content,
|
||||
[string[]]$NestedContents
|
||||
)
|
||||
|
||||
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
|
||||
$manifestParams['RootModule'] = "$Name.psm1"
|
||||
}
|
||||
|
||||
if ($NestedContents) {
|
||||
$manifestParams['NestedModules'] = 1..$NestedContents.Count | % {
|
||||
$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 ((get-childitem 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()' -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
|
||||
& (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
|
||||
}
|
||||
}
|
|
@ -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 various places' {
|
||||
class C {}
|
||||
class C2a:C,system.IDisposable{ [void] Dispose() { }}
|
||||
class C2b
|
||||
:
|
||||
C
|
||||
,
|
||||
system.IDisposable
|
||||
{
|
||||
[void] Dispose() {}
|
||||
C2b()
|
||||
: # there are extra spaces here
|
||||
base
|
||||
(
|
||||
)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[C2a].GetInterface("System.IDisposable") | Should Not Be $null
|
||||
[C2b].GetInterface("System.IDisposable") | 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.IDisposable[] {}" 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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,506 @@
|
|||
Describe 'using module' -Tags "CI" {
|
||||
BeforeAll {
|
||||
$originalPSMODULEPATH = $env:PSMODULEPATH
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
AfterAll {
|
||||
$env:PSMODULEPATH = $originalPSMODULEPATH
|
||||
}
|
||||
|
||||
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" } }'
|
||||
|
||||
$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' {
|
||||
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(@"
|
||||
using module Foo
|
||||
[Foo]::new().GetModuleName()
|
||||
"@).Invoke()
|
||||
|
||||
$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 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' {
|
||||
BeforeAll {
|
||||
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 = (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
|
||||
|
||||
$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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
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 @'
|
||||
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
|
||||
$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 ((get-childitem 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 ((get-childitem 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
97
test/powershell/Language/Classes/scripting.enums.tests.ps1
Normal file
97
test/powershell/Language/Classes/scripting.enums.tests.ps1
Normal file
|
@ -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
|
||||
}
|
142
test/powershell/Language/CompletionTestSupport.psm1
Normal file
142
test/powershell/Language/CompletionTestSupport.psm1
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
178
test/powershell/Language/LanguageTestSupport.psm1
Normal file
178
test/powershell/Language/LanguageTestSupport.psm1
Normal file
|
@ -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
|
38
test/powershell/Language/Parser/Ast.Tests.ps1
Normal file
38
test/powershell/Language/Parser/Ast.Tests.ps1
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
19
test/powershell/Language/Parser/AutomaticVariables.Tests.ps1
Normal file
19
test/powershell/Language/Parser/AutomaticVariables.Tests.ps1
Normal file
|
@ -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
|
||||
}
|
||||
}
|
142
test/powershell/Language/Parser/BNotOperator.Tests.ps1
Normal file
142
test/powershell/Language/Parser/BNotOperator.Tests.ps1
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
test/powershell/Language/Parser/Conversions.Tests.ps1
Normal file
23
test/powershell/Language/Parser/Conversions.Tests.ps1
Normal file
|
@ -0,0 +1,23 @@
|
|||
Describe 'conversion syntax' -Tags "innerloop", "DRT" {
|
||||
# these test suite covers ([<type>]<expression>).<method>() 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"
|
||||
}
|
||||
}
|
325
test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1
Normal file
325
test/powershell/Language/Parser/ExtensibleCompletion.Tests.ps1
Normal file
|
@ -0,0 +1,325 @@
|
|||
<#
|
||||
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)
|
||||
{
|
||||
$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
|
||||
}
|
||||
}
|
||||
|
||||
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 "CI" {
|
||||
@{
|
||||
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 "CI" {
|
||||
@{
|
||||
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 "CI" {
|
||||
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 "CI" {
|
||||
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 "CI" {
|
||||
@{
|
||||
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 "CI" {
|
||||
@{
|
||||
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 "CI" {
|
||||
@{
|
||||
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 = "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 = "System.Collections.Generic.Dictionary[T1, T2]"}
|
||||
)
|
||||
TestInput = 'Get-Command -ParameterType System.Collections.Generic.Dic'
|
||||
} | Get-CompletionTestCaseData | Test-Completions
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
$powershellexe = (get-process -id $PID).mainmodule.filename
|
||||
|
||||
Describe "Clone array" -Tags "CI" {
|
||||
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 "CI" {
|
||||
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 "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.
|
||||
& $powershellexe -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 "CI" {
|
||||
|
||||
$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 "CI" {
|
||||
$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)
|
||||
& $powershellexe -noprofile -encodedCommand ([Convert]::ToBase64String($bytes)) | Should Be Hello
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Assign automatic variables" -Tags "CI" {
|
||||
|
||||
$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 "CI" {
|
||||
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 "CI" {
|
||||
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 "CI" {
|
||||
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
|
||||
}
|
||||
}
|
81
test/powershell/Language/Parser/MethodInvocation.Tests.ps1
Normal file
81
test/powershell/Language/Parser/MethodInvocation.Tests.ps1
Normal file
|
@ -0,0 +1,81 @@
|
|||
if ( $IsCore ) {
|
||||
return
|
||||
}
|
||||
|
||||
Describe "Interface inheritance with remoting proxies" -Tags "CI" {
|
||||
$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<IInterface2> factory = new ChannelFactory<IInterface2>(
|
||||
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"
|
||||
}
|
||||
}
|
96
test/powershell/Language/Parser/ParameterBinding.Tests.ps1
Normal file
96
test/powershell/Language/Parser/ParameterBinding.Tests.ps1
Normal file
|
@ -0,0 +1,96 @@
|
|||
|
||||
Describe 'Argument transformation attribute on optional argument with explicit $null' -Tags "CI" {
|
||||
$tdefinition = @'
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
'@
|
||||
$mod = Add-Type -PassThru -TypeDefinition $tdefinition -refer mscorlib,System.Management.Automation
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
Describe "Redirection operator now supports encoding changes" {
|
||||
BeforeAll {
|
||||
$asciiString = "abc"
|
||||
|
||||
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
|
||||
$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]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
25
test/powershell/Language/Parser/TypeAccelerator.Tests.ps1
Normal file
25
test/powershell/Language/Parser/TypeAccelerator.Tests.ps1
Normal file
|
@ -0,0 +1,25 @@
|
|||
|
||||
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" {
|
||||
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])
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
113
test/powershell/Language/Parser/UsingAssembly.Tests.ps1
Normal file
113
test/powershell/Language/Parser/UsingAssembly.Tests.ps1
Normal file
|
@ -0,0 +1,113 @@
|
|||
|
||||
Describe "Using assembly" -Tags "CI" {
|
||||
|
||||
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" -pending {
|
||||
$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" -pending {
|
||||
$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
|
||||
}
|
||||
}
|
155
test/powershell/Language/Parser/UsingNamespace.Tests.ps1
Normal file
155
test/powershell/Language/Parser/UsingNamespace.Tests.ps1
Normal file
|
@ -0,0 +1,155 @@
|
|||
|
||||
# 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
|
||||
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
|
||||
|
||||
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" -pending {
|
||||
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" {
|
||||
{ [ThreadState] } | 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
|
||||
}
|
||||
|
Binary file not shown.
|
@ -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() }
|
||||
}
|
||||
}
|
30
test/powershell/Language/map.json
Normal file
30
test/powershell/Language/map.json
Normal file
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue