PowerShell/test/powershell/Language/Classes/scripting.Classes.inheritance.tests.ps1
Steve Lee 2639cd89ce Autocorrected CRLF to LF (#4943)
Also, fix `Parser.Tests.ps1` after correcting CRLF.
2017-09-29 16:28:15 -07:00

546 lines
16 KiB
PowerShell

#
# Copyright (c) Microsoft Corporation, 2015
#
try {
#
# CrossGen'ed assemblies cause a hang to happen intermittently when running this test suite in Linux and macOS.
# The issue has been reported to CoreCLR team. We need to work around it for now with the following approach:
# 1. For pull request and push commit, build without '-CrossGen' and run the parsing tests
# 2. For daily build, build with '-CrossGen' but don't run the parsing tests
# In this way, we will continue to exercise these parsing tests for each CI build, and skip them for daily
# build to avoid a hang.
# Note: this change should be reverted once the 'CrossGen' issue is fixed by CoreCLR. The issue is tracked by
# https://github.com/dotnet/coreclr/issues/9745
#
$isDailyBuild = $env:TRAVIS_EVENT_TYPE -eq 'cron' -or $env:TRAVIS_EVENT_TYPE -eq 'api'
$defaultParamValues = $PSdefaultParameterValues.Clone()
$IsSkipped = (!$IsWindows -and $isDailyBuild)
$PSDefaultParameterValues["it:skip"] = $IsSkipped
$PSDefaultParameterValues["ShouldBeParseError:SkipInTravisFullBuild"] = $IsSkipped
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
}
}
} finally {
$global:PSdefaultParameterValues = $defaultParamValues
}