318 lines
12 KiB
PowerShell
318 lines
12 KiB
PowerShell
# Copyright (c) Microsoft Corporation.
|
|
# Licensed under the MIT License.
|
|
|
|
Describe "Handle ByRef-like types gracefully" -Tags "CI" {
|
|
|
|
BeforeAll {
|
|
$code = @'
|
|
using System;
|
|
using System.Management.Automation;
|
|
namespace DotNetInterop
|
|
{
|
|
public class Test
|
|
{
|
|
public Span<int> this[int i]
|
|
{
|
|
get { return default(Span<int>); }
|
|
set { DoNothing(value); }
|
|
}
|
|
|
|
public static Span<int> Space
|
|
{
|
|
get { return default(Span<int>); }
|
|
set { DoNothing(value); }
|
|
}
|
|
|
|
public Span<int> Room
|
|
{
|
|
get { return default(Span<int>); }
|
|
set { DoNothing(value); }
|
|
}
|
|
|
|
private static void DoNothing(Span<int> param)
|
|
{
|
|
}
|
|
|
|
public string PrintMySpan(string str, ReadOnlySpan<char> mySpan = default)
|
|
{
|
|
if (mySpan.Length == 0)
|
|
{
|
|
return str;
|
|
}
|
|
else
|
|
{
|
|
return str + mySpan.Length;
|
|
}
|
|
}
|
|
|
|
public Span<int> GetSpan(int[] array)
|
|
{
|
|
return array.AsSpan();
|
|
}
|
|
}
|
|
|
|
public class Test2
|
|
{
|
|
public Test2(ReadOnlySpan<char> span)
|
|
{
|
|
name = $"Number of chars: {span.Length}";
|
|
}
|
|
|
|
public Test2() {}
|
|
|
|
private string name = "Hello World";
|
|
public string this[ReadOnlySpan<char> span]
|
|
{
|
|
get { return name; }
|
|
set { name = value; }
|
|
}
|
|
|
|
public string Name => name;
|
|
}
|
|
|
|
public class CodeMethods
|
|
{
|
|
public static ReadOnlySpan<char> GetProperty(PSObject instance)
|
|
{
|
|
return default(ReadOnlySpan<char>);
|
|
}
|
|
|
|
public static void SetProperty(PSObject instance, ReadOnlySpan<char> span)
|
|
{
|
|
}
|
|
|
|
public static string RunMethod(PSObject instance, string str, ReadOnlySpan<char> span)
|
|
{
|
|
return str + span.Length;
|
|
}
|
|
}
|
|
|
|
public ref struct MyByRefLikeType
|
|
{
|
|
public MyByRefLikeType(int i) { }
|
|
public static int Index;
|
|
}
|
|
|
|
public class ExampleProblemClass
|
|
{
|
|
public void ProblemMethod(ref MyByRefLikeType value)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
'@
|
|
if (-not ("DotNetInterop.Test" -as [type]))
|
|
{
|
|
Add-Type -TypeDefinition $code -IgnoreWarnings
|
|
}
|
|
|
|
$testObj = [DotNetInterop.Test]::new()
|
|
$test2Obj = [DotNetInterop.Test2]::new()
|
|
}
|
|
|
|
It "New-Object should fail gracefully when used for a ByRef-like type" {
|
|
{ New-Object -TypeName 'System.Span[string]' } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType,Microsoft.PowerShell.Commands.NewObjectCommand"
|
|
{ New-Object -TypeName 'DotNetInterop.MyByRefLikeType' } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType,Microsoft.PowerShell.Commands.NewObjectCommand"
|
|
}
|
|
|
|
It "The 'new' method call should fail gracefully when used on a ByRef-like type" {
|
|
{ [System.Span[string]]::new() } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType"
|
|
{ [DotNetInterop.MyByRefLikeType]::new() } | Should -Throw -ErrorId "CannotInstantiateBoxedByRefLikeType"
|
|
}
|
|
|
|
It "Calling constructor of a ByRef-like type via dotnet adapter should fail gracefully - <Number>" -TestCases @(
|
|
@{ Number = 1; Script = { [System.Span[string]]::new.Invoke("abc") } }
|
|
@{ Number = 2; Script = { [DotNetInterop.MyByRefLikeType]::new.Invoke(2) } }
|
|
) {
|
|
param($Script)
|
|
$expectedError = $null
|
|
try {
|
|
& $Script
|
|
} catch {
|
|
$expectedError = $_
|
|
}
|
|
|
|
$expectedError | Should -Not -BeNullOrEmpty
|
|
$expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "CannotInstantiateBoxedByRefLikeType"
|
|
}
|
|
|
|
It "Cast to a ByRef-like type should fail gracefully" {
|
|
{ [System.Span[int]] ([int[]]1,2,3) } | Should -Throw -ErrorId "InvalidCastToByRefLikeType"
|
|
{ [DotNetInterop.MyByRefLikeType] "text" } | Should -Throw -ErrorId "InvalidCastToByRefLikeType"
|
|
}
|
|
|
|
It "LanguagePrimitives.ConvertTo should fail gracefully for a ByRef-like type '<Name>'" -TestCases @(
|
|
@{ Name = "Span"; Type = [System.Span[int]] }
|
|
@{ Name = "MyByRefLikeType"; Type = [DotNetInterop.MyByRefLikeType] }
|
|
) {
|
|
param($Type)
|
|
$expectedError = $null
|
|
try {
|
|
[System.Management.Automation.LanguagePrimitives]::ConvertTo(([int[]]1,2,3), $Type)
|
|
} catch {
|
|
$expectedError = $_
|
|
}
|
|
|
|
$expectedError | Should -Not -BeNullOrEmpty
|
|
$expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "InvalidCastToByRefLikeType"
|
|
}
|
|
|
|
It "Getting value of a ByRef-like type instance property should not throw and should return null, even in strict mode - <Mechanism>" -TestCases @(
|
|
@{ Mechanism = "Compiler/Binder"; Script = { [System.Text.Encoding]::ASCII.Preamble } }
|
|
@{ Mechanism = "Dotnet-Adapter"; Script = { [System.Text.Encoding]::ASCII.PSObject.Properties["Preamble"].Value } }
|
|
) {
|
|
param($Script)
|
|
|
|
try {
|
|
Set-StrictMode -Version 3.0
|
|
& $Script | Should -Be $null
|
|
} finally {
|
|
Set-StrictMode -Off
|
|
}
|
|
}
|
|
|
|
It "Setting value of a ByRef-like type instance property should fail gracefully - <Mechanism>" -TestCases @(
|
|
@{ Mechanism = "Compiler/Binder"; Script = { $testObj.Room = [int[]](1,2,3) } }
|
|
@{ Mechanism = "Dotnet-Adapter"; Script = { $testObj.PSObject.Properties["Room"].Value = [int[]](1,2,3) } }
|
|
) {
|
|
param($Script)
|
|
$Script | Should -Throw -ErrorId "CannotAccessByRefLikePropertyOrField"
|
|
}
|
|
|
|
It "<Action> value of a ByRef-like type static property should fail gracefully" -TestCases @(
|
|
@{ Action = "Getting"; Script = { [DotNetInterop.Test]::Space } }
|
|
@{ Action = "Setting"; Script = { [DotNetInterop.Test]::Space = "blah" } }
|
|
) {
|
|
param($Script)
|
|
$Script | Should -Throw -ErrorId "CannotAccessByRefLikePropertyOrField"
|
|
}
|
|
|
|
It "Invoke a method with optional ByRef-like parameter could work" {
|
|
$testObj.PrintMySpan("Hello") | Should -BeExactly "Hello"
|
|
}
|
|
|
|
It "Invoke a method with ByRef-like parameter should fail gracefully - <Mechanism>" -TestCases @(
|
|
@{ Mechanism = "Compiler/Binder"; Script = { $testObj.PrintMySpan("Hello", 1) } }
|
|
@{ Mechanism = "Dotnet-Adapter"; Script = { $testObj.psobject.Methods["PrintMySpan"].Invoke("Hello", 1) } }
|
|
) {
|
|
param($Script)
|
|
$Script | Should -Throw -ErrorId "MethodArgumentConversionInvalidCastArgument"
|
|
}
|
|
|
|
It "Invoke a method with ByRef-like return type should fail gracefully - Compiler/Binder" {
|
|
{ $testObj.GetSpan([int[]]@(1,2,3)) } | Should -Throw -ErrorId "CannotCallMethodWithByRefLikeReturnType"
|
|
}
|
|
|
|
It "Invoke a method with ByRef-like return type should fail gracefully - Dotnet-Adapter" {
|
|
$expectedError = $null
|
|
try {
|
|
$testObj.psobject.Methods["GetSpan"].Invoke([int[]]@(1,2,3))
|
|
} catch {
|
|
$expectedError = $_
|
|
}
|
|
$expectedError | Should -Not -BeNullOrEmpty
|
|
$expectedError.Exception.InnerException.ErrorRecord.FullyQualifiedErrorId | Should -BeExactly "CannotCallMethodWithByRefLikeReturnType"
|
|
}
|
|
|
|
It "Access static property of a ByRef-like type" {
|
|
[DotNetInterop.MyByRefLikeType]::Index = 10
|
|
[DotNetInterop.MyByRefLikeType]::Index | Should -Be 10
|
|
}
|
|
|
|
It "Get access of an indexer that returns ByRef-like type should return null in no-strict mode" {
|
|
$testObj[1] | Should -Be $null
|
|
}
|
|
|
|
It "Get access of an indexer that returns ByRef-like type should fail gracefully in strict mode" {
|
|
try {
|
|
Set-StrictMode -Version 3.0
|
|
{ $testObj[1] } | Should -Throw -ErrorId "CannotIndexWithByRefLikeReturnType"
|
|
} finally {
|
|
Set-StrictMode -Off
|
|
}
|
|
}
|
|
|
|
It "Set access of an indexer that accepts ByRef-like type value should fail gracefully" {
|
|
{ $testObj[1] = 1 } | Should -Throw -ErrorId "CannotIndexWithByRefLikeReturnType"
|
|
}
|
|
|
|
It "Create instance of type with method that use a ByRef-like type as a ByRef parameter" {
|
|
$obj = [DotNetInterop.ExampleProblemClass]::new()
|
|
$obj | Should -BeOfType DotNetInterop.ExampleProblemClass
|
|
}
|
|
|
|
Context "Passing value that is implicitly/explicitly castable to ByRef-like parameter in method invocation" {
|
|
|
|
BeforeAll {
|
|
$ps = [powershell]::Create()
|
|
|
|
# Define the CodeMethod 'RunTest'
|
|
$ps.AddCommand("Update-TypeData").
|
|
AddParameter("TypeName", "DotNetInterop.Test2").
|
|
AddParameter("MemberType", "CodeMethod").
|
|
AddParameter("MemberName", "RunTest").
|
|
AddParameter("Value", [DotNetInterop.CodeMethods].GetMethod('RunMethod')).Invoke()
|
|
$ps.Commands.Clear()
|
|
|
|
# Define the CodeProperty 'TestName'
|
|
$ps.AddCommand("Update-TypeData").
|
|
AddParameter("TypeName", "DotNetInterop.Test2").
|
|
AddParameter("MemberType", "CodeProperty").
|
|
AddParameter("MemberName", "TestName").
|
|
AddParameter("Value", [DotNetInterop.CodeMethods].GetMethod('GetProperty')).
|
|
AddParameter("SecondValue", [DotNetInterop.CodeMethods].GetMethod('SetProperty')).Invoke()
|
|
$ps.Commands.Clear()
|
|
|
|
$ps.AddScript('$test = [DotNetInterop.Test2]::new()').Invoke()
|
|
$ps.Commands.Clear()
|
|
}
|
|
|
|
AfterAll {
|
|
$ps.Dispose()
|
|
}
|
|
|
|
It "Support method calls with ByRef-like parameter as long as the argument can be casted to the ByRef-like type" {
|
|
$testObj.PrintMySpan("abc", "def") | Should -BeExactly "abc3"
|
|
$testObj.PrintMySpan("abc", "Hello".ToCharArray()) | Should -BeExactly "abc5"
|
|
{ $testObj.PrintMySpan("abc", 12) } | Should -Throw -ErrorId "MethodArgumentConversionInvalidCastArgument"
|
|
|
|
$path = [System.IO.Path]::GetTempPath()
|
|
[System.IO.Path]::IsPathRooted($path.ToCharArray()) | Should -BeTrue
|
|
}
|
|
|
|
It "Support constructor calls with ByRef-like parameter as long as the argument can be casted to the ByRef-like type" {
|
|
$result = [DotNetInterop.Test2]::new("abc")
|
|
$result.Name | Should -BeExactly "Number of chars: 3"
|
|
|
|
{ [DotNetInterop.Test2]::new(12) } | Should -Throw -ErrorId "MethodCountCouldNotFindBest"
|
|
}
|
|
|
|
It "Support indexing operation with ByRef-like index as long as the argument can be casted to the ByRef-like type" {
|
|
$test2Obj["abc"] | Should -BeExactly "Hello World"
|
|
$test2Obj["abc"] = "pwsh"
|
|
$test2Obj["abc"] | Should -BeExactly "pwsh"
|
|
}
|
|
|
|
It "Support CodeMethod with ByRef-like parameter as long as the argument can be casted to the ByRef-like type" {
|
|
$result = $ps.AddScript('$test.RunTest("Hello", "World".ToCharArray())').Invoke()
|
|
$ps.Commands.Clear()
|
|
$result.Count | Should -Be 1
|
|
$result[0] | Should -Be 'Hello5'
|
|
}
|
|
|
|
It "Return null for getter access of a CodeProperty that returns a ByRef-like type, even in strict mode" {
|
|
$result = $ps.AddScript(
|
|
'try { Set-StrictMode -Version 3.0; $test.TestName } finally { Set-StrictMode -Off }').Invoke()
|
|
$ps.Commands.Clear()
|
|
$result.Count | Should -Be 1
|
|
$result[0] | Should -Be $null
|
|
}
|
|
|
|
It "Fail gracefully for setter access of a CodeProperty that returns a ByRef-like type" {
|
|
$result = $ps.AddScript('$test.TestName = "Hello"').Invoke()
|
|
$ps.Commands.Clear()
|
|
$result.Count | Should -Be 0
|
|
$ps.Streams.Error[0].FullyQualifiedErrorId | Should -Be "CannotAccessByRefLikePropertyOrField"
|
|
}
|
|
}
|
|
}
|