409ab7443f
* Fix GetType() bad pattern and related issues in tests $var.GetType() can raise an exception in tests so we should check $var before make the call. A large part of the tests does not make this check. I start with searching ".GetType()" but discovered many related issues in tests (reduntant and unneeded tests, "throw" bad pattens, bugs, formattings (sorry!) and so on) - I had to fix them too. * Fix after code review * Second wave of migration GetType() -> BeOfType Removed 'GetType().Name' patterns.
525 lines
16 KiB
PowerShell
525 lines
16 KiB
PowerShell
#
|
|
# Copyright (c) Microsoft Corporation, 2015
|
|
#
|
|
|
|
Import-Module $PSScriptRoot\..\LanguageTestSupport.psm1
|
|
|
|
Describe 'Classes inheritance syntax' -Tags "CI" {
|
|
|
|
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"
|
|
throw "No Exception!"
|
|
} catch {
|
|
$_.Exception | Should BeOfType 'System.Management.Automation.SetValueInvocationException'
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'Classes inheritance syntax errors' -Tags "CI" {
|
|
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 "CI" {
|
|
|
|
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 "CI" {
|
|
|
|
#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 "CI" {
|
|
|
|
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)
|
|
throw "No Exception!"
|
|
} catch {
|
|
$_.Exception | Should BeOfType "System.Management.Automation.MethodException"
|
|
}
|
|
}
|
|
|
|
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 conversion [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' -Tags "CI" {
|
|
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
|
|
}
|
|
}
|