Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7095,6 +7095,32 @@ internal static object InvokeAdaptedMember(object obj, string methodName, object
return methodInfo.Invoke(args);
}

// The object doesn't have 'Where' and 'ForEach' methods.
// As a last resort, we invoke 'Where' and 'ForEach' operators on singletons like
// ([pscustomobject]@{ foo = 'bar' }).Foreach({$_})
// ([pscustomobject]@{ foo = 'bar' }).Where({1})
if (string.Equals(methodName, "Where", StringComparison.OrdinalIgnoreCase))
{
var enumerator = (new object[] {obj}).GetEnumerator();
switch (args.Length)
{
case 1:
return EnumerableOps.Where(enumerator, args[0] as ScriptBlock, WhereOperatorSelectionMode.Default, 0);
case 2:
return EnumerableOps.Where(enumerator, args[0] as ScriptBlock,
LanguagePrimitives.ConvertTo<WhereOperatorSelectionMode>(args[1]), 0);
case 3:
return EnumerableOps.Where(enumerator, args[0] as ScriptBlock,
LanguagePrimitives.ConvertTo<WhereOperatorSelectionMode>(args[1]), LanguagePrimitives.ConvertTo<int>(args[2]));
}
}

if (string.Equals(methodName, "Foreach", StringComparison.OrdinalIgnoreCase))
{
var enumerator = (new object[] {obj}).GetEnumerator();
return EnumerableOps.ForEach(enumerator, args[0], Utils.EmptyArray<object>());
}

throw InterpreterError.NewInterpreterException(methodName, typeof(RuntimeException), null,
"MethodNotFound", ParserStrings.MethodNotFound, ParserOps.GetTypeFullName(obj), methodName);
}
Expand Down
303 changes: 195 additions & 108 deletions test/powershell/engine/ETS/Adapter.Tests.ps1
Original file line number Diff line number Diff line change
@@ -1,143 +1,230 @@
Describe "Adapter Tests" -tags "CI" {
BeforeAll {
$pso = [System.Diagnostics.Process]::GetCurrentProcess()
$processName = $pso.Name

if(-not ('TestCodeMethodClass' -as "type"))
{
class TestCodeMethodClass {
static [int] TestCodeMethod([PSObject] $target, [int] $i)
{
return 1;
Context "Property Adapter Tests" {
BeforeAll {
$pso = [System.Diagnostics.Process]::GetCurrentProcess()
$processName = $pso.Name

if(-not ('TestCodeMethodClass' -as "type"))
{
class TestCodeMethodClass {
static [int] TestCodeMethod([PSObject] $target, [int] $i)
{
return 1;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better to return $i instead of 1?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know. It is used only in one test and the test works well.

}
}
}

$psmemberset = New-Object System.Management.Automation.PSMemberSet 'setname1'
$psmemberset | Add-Member -MemberType NoteProperty -Name NoteName -Value 1
$testmethod = [TestCodeMethodClass].GetMethod("TestCodeMethod")
$psmemberset | Add-Member -MemberType CodeMethod -Name TestCodeMethod -Value $testmethod

$document = new-object System.Xml.XmlDocument
$document.LoadXml("<book ISBN='12345'><title>Pride And Prejudice</title><price>19.95</price></book>")
$doc = $document.DocumentElement
}

$psmemberset = new-object System.Management.Automation.PSMemberSet 'setname1'
$psmemberset | Add-Member -MemberType NoteProperty -Name NoteName -Value 1
$testmethod = [TestCodeMethodClass].GetMethod("TestCodeMethod")
$psmemberset | Add-Member -MemberType CodeMethod -Name TestCodeMethod -Value $testmethod
It "Can get a Dotnet parameterized property" {
$col = $pso.psobject.Properties.Match("*")
$prop = $col.psobject.Members["Item"]
$prop | Should Not BeNullOrEmpty
$prop.IsGettable | Should Be $true
$prop.IsSettable | Should Be $false
$prop.TypeNameOfValue | Should Be "System.Management.Automation.PSPropertyInfo"
$prop.Invoke("ProcessName").Value | Should Be $processName
}

$document = new-object System.Xml.XmlDocument
$document.LoadXml("<book ISBN='12345'><title>Pride And Prejudice</title><price>19.95</price></book>")
$doc = $document.DocumentElement
}
It "can get a Dotnet parameterized property" {
$col = $pso.psobject.Properties.Match("*")
$prop = $col.psobject.Members["Item"]
$prop | Should Not BeNullOrEmpty
$prop.IsGettable | Should be $true
$prop.IsSettable | Should be $false
$prop.TypeNameOfValue | Should be "System.Management.Automation.PSPropertyInfo"
$prop.Invoke("ProcessName").Value | Should be $processName
}
It "Can get a property" {
$pso.psobject.Properties["ProcessName"] | Should Not BeNullOrEmpty
}

It "can get a property" {
$pso.psobject.Properties["ProcessName"] | should Not BeNullOrEmpty
}
It "Can access all properties" {
$props = $pso.psobject.Properties.Match("*")
$props | Should Not BeNullOrEmpty
$props["ProcessName"].Value | Should Be $processName
}

It "Can access all properties" {
$props = $pso.psobject.Properties.Match("*")
$props | should Not BeNullOrEmpty
$props["ProcessName"].Value |Should be $processName
}
It "Can invoke a method" {
$method = $pso.psobject.Methods["ToString"]
$method.Invoke() | Should Be ($pso.ToString())
}

It "Can invoke a method" {
$method = $pso.psobject.Methods["ToString"]
$method.Invoke() | should be ($pso.ToString())
}
It "Access a Method via MemberSet adapter" {
$prop = $psmemberset.psobject.Members["TestCodeMethod"]
$prop.Invoke(2) | Should Be 1
}

It "Access a Method via MemberSet adapter" {
$prop = $psmemberset.psobject.Members["TestCodeMethod"]
$prop.Invoke(2) | Should be 1
}
It "Access misc properties via MemberSet adapter" {
$prop = $psmemberset.psobject.Properties["NoteName"]
$prop | Should Not BeNullOrEmpty
$prop.IsGettable | Should Be $true
$prop.IsSettable | Should Be $true
$prop.TypeNameOfValue | Should Be "System.Int32"
}

It "Access misc properties via MemberSet adapter" {
$prop = $psmemberset.psobject.Properties["NoteName"]
$prop | Should Not BeNullOrEmpty
$prop.IsGettable | Should be $true
$prop.IsSettable | Should be $true
$prop.TypeNameOfValue | Should be "System.Int32"
}
It "Access all the properties via XmlAdapter" {
$col = $doc.psobject.Properties.Match("*")
$col.Count | Should Not Be 0
$prop = $col["price"]
$prop | Should Not BeNullOrEmpty
}

It "Access all the properties via XmlAdapter" {
$col = $doc.psobject.Properties.Match("*")
$col.Count | Should Not Be 0
$prop = $col["price"]
$prop | Should Not BeNullOrEmpty
}
It "Access all the properties via XmlAdapter" {
$prop = $doc.psobject.Properties["price"]
$prop.Value | Should Be "19.95"
$prop.IsGettable | Should Not BeNullOrEmpty
$prop.IsSettable | Should Not BeNullOrEmpty
$prop.TypeNameOfValue | Should Be "System.String"
}

It "Access all the properties via XmlAdapter" {
$prop = $doc.psobject.Properties["price"]
$prop.Value | Should Be "19.95"
$prop.IsGettable | Should Not BeNullOrEmpty
$prop.IsSettable | Should Not BeNullOrEmpty
$prop.TypeNameOfValue | Should be "System.String"
}
It "Call to string on a XmlNode object" {
$val = $doc.ToString()
$val | Should Be "book"
}

It "Call to string on a XmlNode object" {
$val = $doc.ToString()
$val | Should Be "book"
}
It "Calls CodeMethod with void result" {

It "Calls CodeMethod with void result" {
class TestCodeMethodInvokationWithVoidReturn {
[int] $CallCounter

class TestCodeMethodInvokationWithVoidReturn {
[int] $CallCounter
static [int] IntMethodCM([PSObject] $self) {
return $self.CallCounter
}

static [int] IntMethodCM([PSObject] $self) {
return $self.CallCounter
}
static [void] VoidMethodCM([PSObject] $self) {
$self.CallCounter++
}

static [void] VoidMethodCM([PSObject] $self) {
$self.CallCounter++
static [Reflection.MethodInfo] GetMethodInfo([string] $name) {
return [TestCodeMethodInvokationWithVoidReturn].GetMethod($name)
}
}

static [Reflection.MethodInfo] GetMethodInfo([string] $name) {
return [TestCodeMethodInvokationWithVoidReturn].GetMethod($name)
Update-TypeData -Force -TypeName TestCodeMethodInvokationWithVoidReturn -MemberType CodeMethod -MemberName IntMethod -Value ([TestCodeMethodInvokationWithVoidReturn]::GetMethodInfo('IntMethodCM'))
Update-TypeData -Force -TypeName TestCodeMethodInvokationWithVoidReturn -MemberType CodeMethod -MemberName VoidMethod -Value ([TestCodeMethodInvokationWithVoidReturn]::GetMethodInfo('VoidMethodCM'))
try {
$o = [TestCodeMethodInvokationWithVoidReturn]::new()
$o.CallCounter | Should Be 0
$o.VoidMethod()
$o.CallCounter | Should Be 1

$o.IntMethod() | Should Be 1
}
finally {
Remove-TypeData TestCodeMethodInvokationWithVoidReturn
}
}

Update-TypeData -Force -TypeName TestCodeMethodInvokationWithVoidReturn -MemberType CodeMethod -MemberName IntMethod -Value ([TestCodeMethodInvokationWithVoidReturn]::GetMethodInfo('IntMethodCM'))
Update-TypeData -Force -TypeName TestCodeMethodInvokationWithVoidReturn -MemberType CodeMethod -MemberName VoidMethod -Value ([TestCodeMethodInvokationWithVoidReturn]::GetMethodInfo('VoidMethodCM'))
try {
$o = [TestCodeMethodInvokationWithVoidReturn]::new()
$o.CallCounter | Should Be 0
$o.VoidMethod()
$o.CallCounter | Should be 1
It "Count and length property works for singletons" {
# Return magic Count and Length property if it absent.
$x = 5
$x.Count | Should Be 1
$x.Length | Should Be 1

$o.IntMethod() | Should be 1
}
finally {
Remove-TypeData TestCodeMethodInvokationWithVoidReturn
$null.Count | Should Be 0
$null.Length | Should Be 0

(10).Count | Should Be 1
(10).Length | Should Be 1

("a").Count | Should Be 1
# The Length property exists in String type, so here we check that we don't break strings.
("a").Length | Should Be 1
("aa").Length | Should Be 2

([psobject] @{ foo = 'bar' }).Count | Should Be 1
([psobject] @{ foo = 'bar' }).Length | Should Be 1

([pscustomobject] @{ foo = 'bar' }).Count | Should Be 1
([pscustomobject] @{ foo = 'bar' }).Length | Should Be 1

# Return real Count and Length property if it present.
([pscustomobject] @{ foo = 'bar'; count = 5 }).Count | Should Be 5
([pscustomobject] @{ foo = 'bar'; length = 5 }).Length | Should Be 5
}
}

It "Count and length property works for singletons" {
# Return magic Count and Length property if it absent.
$x = 5
$x.Count | Should Be 1
$x.Length | Should Be 1
Context "Null Magic Method Adapter Tests" {
It "ForEach and Where works for Null" {
$res = $null.ForEach({1})
$res.Count | Should Be 0
$res.GetType().Name | Should BeExactly "Collection``1"

$null.Count | Should Be 0
$null.Length | Should Be 0

(10).Count | Should Be 1
(10).Length | Should Be 1
$null.Where({$true})
$res.Count | Should Be 0
$res.GetType().Name | Should BeExactly "Collection``1"
}
}

("a").Count | Should Be 1
# The Length property exists for strings, so we skip the test in the context.
# ("a").Length | Should Be 1
Context "ForEach Magic Method Adapter Tests" {
It "Common ForEach magic method tests" -Pending:$true {
}

([psobject] @{ foo = 'bar' }).Count | Should Be 1
([psobject] @{ foo = 'bar' }).Length | Should Be 1
It "ForEach magic method works for singletions" {
$x = 5
$x.ForEach({$_}) | Should Be 5
(5).ForEach({$_}) | Should Be 5
("a").ForEach({$_}) | Should BeExactly "a"

([pscustomobject]@{ foo = 'bar' }).ForEach({1}) | Should Be 1

$x = ([pscustomobject]@{ foo = 'bar' }).ForEach({$_})
$x.Count | Should Be 1
$x[0].foo | Should BeExactly "bar"

$x = ([pscustomobject]@{ foo = 'bar' }).Foreach({$_ | Add-Member -NotePropertyName "foo2" -NotePropertyValue "bar2" -PassThru})
$x.Count | Should Be 1
$x[0].foo | Should BeExactly "bar"
$x[0].foo2 | Should BeExactly "bar2"

# We call ForEach method defined in an object if it is present (not magic ForEach method).
$x = [pscustomobject]@{ foo = 'bar' }
$x | Add-Member -MemberType ScriptMethod -Name ForEach -Value {
param ( [int]$param1 )
$param1*2
} -PassThru -Force
$x.ForEach(5) | Should Be 10
}
}

([pscustomobject] @{ foo = 'bar' }).Count | Should Be 1
([pscustomobject] @{ foo = 'bar' }).Length | Should Be 1
Context "Where Magic Method Adapter Tests" {
It "Common Where magic method tests" -Pending:$true {
}

# Return real Count and Length property if it present.
([pscustomobject] @{ foo = 'bar'; count = 5 }).Count | Should Be 5
([pscustomobject] @{ foo = 'bar'; length = 5 }).Length | Should Be 5
It "Where magic method works for singletions" {
$x = 5
$x.Where({$true}) | Should Be 5
(5).Where({$true}) | Should Be 5
("a").Where({$true}) | Should Be "a"

$x = ([pscustomobject] @{ foo = 'bar' }).Where({$true})
$x.Count | Should Be 1
$x[0].foo | Should BeExactly "bar"

$x = ([pscustomobject] @{ foo = 'bar' }).Where({$true}, 0)
$x.Count | Should Be 1
$x[0].foo | Should BeExactly "bar"

$x = ([pscustomobject] @{ foo = 'bar' }).Where({$true}, "Default")
$x.Count | Should Be 1
$x[0].foo | Should BeExactly "bar"

$x = ([pscustomobject] @{ foo = 'bar' }).Where({$true}, "Default", 0)
$x.Count | Should Be 1
$x[0].foo | Should BeExactly "bar"

$x = ([pscustomobject] @{ foo = 'bar' }).Where({$true}, "Default", "0")
$x.Count | Should Be 1
$x[0].foo | Should BeExactly "bar"

# We call Where method defined in an object if it is present (not magic Where method).
$x = [pscustomobject]@{ foo = 'bar' }
$x | Add-Member -MemberType ScriptMethod -Name Where -Value {
param ( [int]$param1 )
$param1*2
} -PassThru -Force
$x.Where(5) | Should Be 10
}
}
}

Expand Down