Merge branch 'master' into psedition_rename

This commit is contained in:
Manikyam Bavandla 2016-07-21 12:16:38 -07:00
commit 66f0b83e89
43 changed files with 5153 additions and 58 deletions

View file

@ -37,6 +37,22 @@ Building PowerShell
|-----------------------|-------|-------------------|------|
| Build from **Source** | [Instructions][build-linux] | [Instructions][build-wc] | [Instructions][build-osx] |
Downloading the Source Code
----------------------
The PowerShell repository has a number of other repositories embedded as submodules.
To make things easy, we can just clone recursively.
```sh
git clone --recursive https://github.com/PowerShell/PowerShell.git
```
If you already cloned but forgot to use `--recursive`, you can update submodules manually:
```sh
git submodule init
git submodule update
```
See [working with the PowerShell repository][powershell-repo-101] for more information.
Developing and Contributing
--------------------------
@ -46,7 +62,7 @@ If you encounter issues in your development, please consult the [known issues][k
and [FAQ][faq] documents to see if the issue you are running into is
captured and if a workaround exists.
If you encounter issues with PowerShell itself, first search for it in our [issues][github-issues].
If you encounter issues with PowerShell itself, first search for it in our [issues][github-issues].
If you do not see your issue captured, please file a [new issue][new-issue]. For more details see [Contributing to issues][contribution-issues].
PowerShell Community
@ -63,7 +79,7 @@ Legal and Licensing
Code of Conduct
---------------
This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code].
This project has adopted the [Microsoft Open Source Code of Conduct][conduct-code].
For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact
[opencode@microsoft.com][conduct-email] with any additional questions or comments.
@ -85,3 +101,4 @@ For more information see the [Code of Conduct FAQ][conduct-FAQ] or contact
[new-issue]:https://github.com/PowerShell/PowerShell/issues/new
[pls-omi-provider]: https://github.com/PowerShell/psl-omi-provider
[releases]: https://github.com/PowerShell/PowerShell/releases
[powershell-repo-101]: docs/git/powershell-repository-101.md

View file

@ -34,14 +34,15 @@ test_script:
$env:CoreOutput = Split-Path -Parent (Get-PSOutput -Options (New-PSOptions -Publish))
Write-Host -Foreground Green 'Run CoreCLR tests'
$testResultsFile = "$pwd\TestsResults.xml"
& ("$env:CoreOutput\powershell.exe") -noprofile -noninteractive -c "Invoke-Pester test/powershell -OutputFormat NUnitXml -OutputFile $testResultsFile"
& ("$env:CoreOutput\powershell.exe") -noprofile -noninteractive -c "Invoke-Pester test/powershell -ExcludeTag 'Slow' -OutputFormat NUnitXml -OutputFile $testResultsFile"
(New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFile))
#
# FullCLR
$env:FullOutput = Split-Path -Parent (Get-PSOutput -Options (New-PSOptions -FullCLR))
Write-Host -Foreground Green 'Run FullCLR tests'
$testResultsFileFullCLR = "$pwd\TestsResults.FullCLR.xml"
Start-DevPowerShell -NoNewWindow -ArgumentList '-noprofile', '-noninteractive' -Command "Invoke-Pester test/fullCLR -OutputFormat NUnitXml -OutputFile $testResultsFileFullCLR"
Start-DevPowerShell -NoNewWindow -ArgumentList '-noprofile', '-noninteractive' -Command "Invoke-Pester test/fullCLR -ExcludeTag 'Slow' -OutputFormat NUnitXml -OutputFile $testResultsFileFullCLR"
(New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path $testResultsFileFullCLR))
#
# Fail the build, if tests failed
@ -57,12 +58,6 @@ test_script:
{
throw "$($x.'test-results'.failures) tests in test/fullCLR failed"
}
#
# Portable module test
Write-Host -Foreground Green 'Test use of cross-platform binary module'
Import-Module ./docs/cmdlet-example/bin/Debug/netstandard1.3/SendGreeting.dll
Send-Greeting -Name World
on_finish:
- ps: |

3
demos/README.md Normal file
View file

@ -0,0 +1,3 @@
This folder contains PowerShell demo.
Create a new folder for each demo.

View file

@ -1,9 +0,0 @@
# Use the crontab module available at ./modules/CronTab
# Get the existing cron jobs
# Sort them by data
# Create a new one using a DateTime object
# Show in bash that the new cron job exists

View file

@ -0,0 +1,61 @@
@{
# Script module or binary module file associated with this manifest.
RootModule = 'CronTab.psm1'
# Version number of this module.
ModuleVersion = '0.1.0.0'
# Supported PSEditions
CompatiblePSEditions = @('Linux')
# ID used to uniquely identify this module
GUID = '508bb97f-de2e-482e-aae2-01caec0be8c7'
# Author of this module
Author = 'Microsoft'
# Company or vendor of this module
CompanyName = 'Unknown'
# Copyright statement for this module
Copyright = '(c) 2016 Microsoft. All rights reserved.'
# Description of the functionality provided by this module
Description = 'Sample module for managing CronTab'
# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = 'CronTab.ps1xml'
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'New-CronJob','Remove-CronJob','Get-CronJob','Get-CronTabUser'
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = @{
PSData = @{
# Tags applied to this module. These help with module discovery in online galleries.
# Tags = @()
# A URL to the license for this module.
# LicenseUri = ''
# A URL to the main website for this project.
# ProjectUri = ''
# A URL to an icon representing this module.
# IconUri = ''
# ReleaseNotes of this module
# ReleaseNotes = ''
} # End of PSData hashtable
} # End of PrivateData hashtable
# HelpInfo URI of this module
# HelpInfoURI = ''
}

View file

@ -1,22 +1,31 @@
using namespace System.Collections.Generic
using namespace System.Management.Automation
$crontabcmd = "/usr/bin/crontab"
#TODO fix after https://github.com/PowerShell/PowerShell/issues/932 is fixed
#class CronJob {
# [string] $Minute
# [string] $Hour
# [string] $DayOfMonth
# [string] $Month
# [string] $DayOfWeek
# [string] $Command
#}
class CronJob {
[string] $Minute
[string] $Hour
[string] $DayOfMonth
[string] $Month
[string] $DayOfWeek
[string] $Command
[string] ToString()
{
return "{0} {1} {2} {3} {4} {5}" -f
$this.Minute, $this.Hour, $this.DayOfMonth, $this.Month, $this.DayOfWeek, $this.Command
}
}
# Internal helper functions
function Get-CronTab ([String] $user) {
$crontab = Invoke-CronTab -user $user -arguments "-l" -noThrow
if ($crontab -is [System.Management.Automation.ErrorRecord]) {
if ($crontab -is [ErrorRecord]) {
if ($crontab.Exception.Message.StartsWith("no crontab for ")) {
$crontab = $null
$crontab = @()
}
else {
throw $crontab.Exception
@ -27,8 +36,7 @@ function Get-CronTab ([String] $user) {
function ConvertTo-CronJob ([String] $crontab) {
$split = $crontab.split(" ", 6)
$cronjob = New-Object -TypeName PSObject -Property @{ #TODO: change to CronJob type
PSTypeName="CronJob";
$cronjob = [CronJob]@{
Minute = $split[0];
Hour = $split[1];
DayOfMonth= $split[2];
@ -39,14 +47,13 @@ function ConvertTo-CronJob ([String] $crontab) {
$cronjob
}
function Invoke-CronTab ([String] $user, [String] $arguments, [Switch] $noThrow) {
function Invoke-CronTab ([String] $user, [String[]] $arguments, [Switch] $noThrow) {
If ($user -ne [String]::Empty) {
$arguments = "-u $UserName $arguments"
$arguments = Write-Output "-u" $UserName $arguments
}
$cmd = "$crontabcmd $arguments 2>&1"
Write-Verbose $cmd
$output = Invoke-Expression $cmd
Write-Verbose "Running: $crontabcmd $arguments"
$output = & $crontabcmd @arguments 2>&1
if ($LastExitCode -ne 0 -and -not $noThrow) {
$e = New-Object System.InvalidOperationException -ArgumentList $output.Exception.Message
throw $e
@ -78,24 +85,37 @@ function Remove-CronJob {
Optional parameter to specify a specific user's cron table
.PARAMETER Job
Cron job object returned from Get-CronJob
.PARAMETER Force
Don't prompt when removing the cron job
#>
[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]
param (
[Alias("u")][Parameter(Mandatory=$false)][String] $UserName,
[Alias("j")][Parameter(Mandatory=$true,ValueFromPipeline=$true)][PSTypeName("CronJob")] $Job #TODO use CronJob type
[ArgumentCompleter( { $wordToComplete = $args[2]; Get-CronTabUser | Where-Object { $_ -like "$wordToComplete*" } | Sort-Object } )]
[Alias("u")]
[Parameter(Mandatory=$false)]
[String]
$UserName,
[Alias("j")]
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
[CronJob]
$Job,
[Switch]
$Force
)
process {
[string[]] $crontab = Get-CronTab -user $UserName
[string[]] $newcrontab = $null
$newcrontab = [List[string]]::new()
$found = $false
$JobAsString = $Job.ToString()
foreach ($line in $crontab) {
$cronjob = ConvertTo-CronJob -crontab $line
if ((Compare-object $cronjob.psobject.properties $Job.psobject.properties) -eq $null) {
if ($JobAsString -ceq $line) {
$found = $true
} else {
$newcrontab += $line
$newcrontab.Add($line)
}
}
@ -103,7 +123,7 @@ function Remove-CronJob {
$e = New-Object System.Exception -ArgumentList "Job not found"
throw $e
}
if ($pscmdlet.ShouldProcess($Job.Command,"Remove")) {
if ($Force -or $pscmdlet.ShouldProcess($Job.Command,"Remove")) {
Import-CronTab -user $UserName -crontab $newcrontab
}
}
@ -140,7 +160,12 @@ function New-CronJob {
#>
[CmdletBinding()]
param (
[Alias("u")][Parameter(Mandatory=$false)][String] $UserName,
[ArgumentCompleter( { $wordToComplete = $args[2]; Get-CronTabUser | Where-Object { $_ -like "$wordToComplete*" } | Sort-Object } )]
[Alias("u")]
[Parameter(Mandatory=$false)]
[String]
$UserName,
[Alias("mi")][Parameter(Position=1)][String[]] $Minute = "*",
[Alias("h")][Parameter(Position=2)][String[]] $Hour = "*",
[Alias("dm")][Parameter(Position=3)][String[]] $DayOfMonth = "*",
@ -173,7 +198,7 @@ function Get-CronJob {
Optional parameter to specify a specific user's cron table
#>
[CmdletBinding()]
[OutputType([PSObject])]
[OutputType([CronJob])]
param (
[Alias("u")][Parameter(Mandatory=$false)][String] $UserName
)
@ -187,3 +212,33 @@ function Get-CronJob {
}
}
}
function Get-CronTabUser {
<#
.SYNOPSIS
Returns the users allowed to use crontab
#>
[CmdletBinding()]
[OutputType([String])]
param()
$allow = '/etc/cron.allow'
if (Test-Path $allow)
{
Get-Content $allow
}
else
{
$users = Get-Content /etc/passwd | ForEach-Object { ($_ -split ':')[0] }
$deny = '/etc/cron.deny'
if (Test-Path $deny)
{
$denyUsers = Get-Content $deny
$users | Where-Object { $denyUsers -notcontains $_ }
}
else
{
$users
}
}
}

15
demos/crontab/README.md Normal file
View file

@ -0,0 +1,15 @@
## CronTab demo
This demo shows examining, creating, and removing cron jobs via crontab.
Output of Get-CronJob is a strongly typed object with properties like DayOfWeek or Command.
Remove-CronJob prompts before removing the job unless you specifiy -Force.
Tab completion of -UserName is supported, e.g.
Get-CronJob -u <TAB>
NYI: no way to run crontab with sudo if necessary
NYI: ignoring shell variables or comments
NYI: New-CronJob -Description "..." (save in comments"
NYI: @reboot,@daily,@hourly,etc

27
demos/crontab/crontab.ps1 Normal file
View file

@ -0,0 +1,27 @@
Import-Module $PSScriptRoot/CronTab/CronTab.psd1
Write-Host -Foreground Yellow "Remove all jobs to start, no prompting"
Get-CronJob | Remove-CronJob -Force
Write-Host -Foreground Yellow "Get the existing cron jobs"
Get-CronJob | Out-Host
Write-Host -Foreground Yellow "New cron job to clean out tmp every day at 1am"
New-CronJob -Command 'rm -rf /tmp/*' -Hour 1 | Out-Host
Write-Host -Foreground Yellow "Add some more jobs"
New-CronJob -Command 'python -c ~/scripts/backup_users' -Hour 2 -DayOfWeek 1-5 | Out-Host
New-CronJob -Command 'powershell -c "cd ~/src/PowerShell; ipmo ./build.psm1; Start-PSBuild"' -Hour 2 -DayOfWeek * | Out-Host
Write-Host -Foreground Yellow "Show in bash that the new cron job exists"
crontab -l
Write-Host -Foreground Yellow "Get jobs that run every day"
Get-CronJob | Where-Object { $_.DayOfWeek -eq '*' -or $_.DayOfWeek -eq '1-7' } | Out-Host
Write-Host -Foreground Yellow "Remove one cron job, with prompting to confirm"
Get-CronJob | Where-Object { $_.Command -match '^powershell.*' } | Remove-CronJob | Out-Host
Write-Host -Foreground Yellow "And the other job remains"
Get-CronJob | Out-Host

Binary file not shown.

View file

@ -1,4 +1,4 @@
Describe "Send-Greeting cmdlet" -Tag 'Slow' {
Describe "Send-Greeting cmdlet" -Tag 'Slow','CI' {
It "Should be able build the cmdlet" {
Remove-Item -Recurse -Force bin -ErrorAction SilentlyContinue
dotnet restore --verbosity Error | Should BeNullOrEmpty

View 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()
}

View 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 }
}

View file

@ -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
}
}
}

View file

@ -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'
}
}

View file

@ -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
}
}
}

View file

@ -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"'
}
}
}

View file

@ -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;"
}
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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
}
}
}
}

View 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
}

View 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
}
}
}
}
}
}
}

View 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

View 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"
}
}
}

View 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
}
}

View 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
}
}
}
}

View 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"
}
}

View 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
}

View file

@ -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
}
}

View 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"
}
}

View 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
}
}

View file

@ -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]
}
}
}
}
}

View 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
}
}

View 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
}
}

View 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
}

View file

@ -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() }
}
}

View 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"
}

View file

@ -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

View file

@ -281,7 +281,7 @@ Describe "Event Test" -Tags @('BVT', 'DRT') {
}
}
Describe "Find-Package" -Tags @('BVT', 'DRT'){
Describe "Find-Package" -Tags @('CI','SLOW'){
it "EXPECTED: Find a package with a location created via new-psdrive" -Skip:(-not $IsWindows) {
$Error.Clear()
New-PSDrive -Name xx -PSProvider FileSystem -Root $TestDrive -warningaction:silentlycontinue -ea silentlycontinue > $null; find-package -name "fooobarrr" -provider nuget -source xx:\ -warningaction:silentlycontinue -ea silentlycontinue

View file

@ -1,9 +1,93 @@
try {
$enlistmentRoot = git rev-parse --show-toplevel
# Looking at pester internal to get tag filter and ExcludeTagFilter
# This seems like the most stable way to do this
# other options like testing for tags seems more likely to break
InModuleScope Pester {
Describe 'Getting Tag Filters' -Tag CI {
$global:__PesterTags = $pester.TagFilter
$global:__PesterExcludeTags = $pester.ExcludeTagFilter
}
}
Describe 'SDK Send Greeting Sample Tests' -Tag CI {
try {
$enlistmentRoot = git rev-parse --show-toplevel
$docLocation = Join-Path -Path $enlistmentRoot -ChildPath '\docs\cmdlet-example'
$testResultPath = Join-Path $TestDrive 'sendgreetingresults.xml'
$sampleCopy = Join-Path $TestDrive 'sendgreeting'
$fullSampleCopyPath = Join-Path $sampleCopy 'cmdlet-example'
$powershell = (Get-Process -id $PID).MainModule.FileName
if(!(Test-Path $sampleCopy))
{
New-Item -ItemType Directory -Path $sampleCopy
}
Copy-Item -Recurse -Path $docLocation -Destination $sampleCopy -Force
Get-ChildItem -Recurse $sampleCopy | %{ Write-Verbose "sc: $($_.FullName)"}
$pesterCommand = "Invoke-Pester $sampleCopy -PassThru"
if($global:__PesterTags)
{
$pesterCommand += " -Tag $(@($global:__PesterTags) -join ',')"
}
if($global:__PesterExcludeTags)
{
$pesterCommand += " -ExcludeTag $(@($global:__PesterExcludeTags) -join ',')"
}
$importPesterCommand = 'Import-module Pester'
if($isCore)
{
$importPesterCommand = "Import-Module $(Join-Path -path $PSHOME -child '/Modules/Pester')"
}
$command = @"
Push-Location -Path $fullSampleCopyPath
$importPesterCommand
$pesterCommand | Export-Clixml -Path $testResultPath
"@
Write-Verbose -Message "command: '$command'"
$bytes = [System.Text.Encoding]::Unicode.GetBytes($command)
$encodedCommand = [Convert]::ToBase64String($bytes)
&$powershell -encodedCommand $encodedCommand
it "Should have test results file" {
$testResultPath | should exist
$script:results = Import-Clixml $testResultPath
}
it "Should have test results" {
$script:results | should not be BeNullOrEmpty
$script:results.TotalCount | should not BeNullOrEmpty
$script:results.TestResult.Count | should not BeNullOrEmpty
}
it "Should have no failures" {
$script:results.FailedCount | should be 0
}
foreach($testResult in $script:results.TestResult){
Context "Test $($testResult.Name)" {
it "should have no failure message" {
$testResult.FailureMessage | should BeNullOrEmpty
}
it "should have no stack trace" {
$testResult.StackTrace | should BeNullOrEmpty
}
it "should have no error record" {
$testResult.ErrorRecord | should BeNullOrEmpty
}
it "should have not failed" {
Write-Verbose "Result: $($testResult.Result)"
$testResult.Result | should not be Failed
}
}
}
} finally {
Pop-Location
}
$docLocation = [io.path]::Combine($enlistmentRoot, "docs","cmdlet-example")
Push-Location $docLocation
./SendGreeting.Tests.ps1
} finally {
Pop-Location
}