pester / Pester

Pester is the ubiquitous test and mock framework for PowerShell.
https://pester.dev/
Other
3.08k stars 470 forks source link

Pester throws if a test calls New-HashSet from ListFunctions package #2113

Open scottbilas opened 2 years ago

scottbilas commented 2 years ago

General summary of the issue

Pester fails with a quite deep call stack if I have a test that uses New-HashSet from the ListFunctions package (Gallery; GitHub).

Describe your environment

Pester version : 5.3.1 C:\Users\scott\Documents\PowerShell\Modules\Pester\5.3.1\Pester.psm1 PowerShell version : 7.1.3 OS version : Microsoft Windows NT 10.0.22000.0

(I also tried on the posh classic, same issue)

Pester version : 5.3.1 C:\Users\scott\Documents\PowerShell\Modules\Pester\5.3.1\Pester.psm1 PowerShell version : 5.1.22000.282 OS version : Microsoft Windows NT 10.0.22000.0

Steps to reproduce

  1. Install-Module ListFunctions (requires PSGallery as a PSRepository)
  2. Copy this script into test.ps1
    #Requires -Modules ListFunctions
    Describe 'Demo' {
    It 'Shows the issue' { New-HashSet }
    }
  3. Invoke-Pester test.ps1

Expected Behavior

Should not throw an exception. (If I run New-HashSet on its own, there's no problem.)

The code to New-HashSet is at New-HashSet.ps1. Nothing in there seems particularly weird to me, but it is apparently confusing Pester.

Current Behavior

I get a call stack like this:

 invoke-pester C:\temp\test.ps1

Starting discovery in 1 files.
Discovery found 1 tests in 1.14s.
Running tests.
System.Management.Automation.ParameterBindingArgumentTransformationException: Cannot process argument transformation on parameter 'Condition'. Cannot convert the "System.Collections.Generic.List`1[Pester.Block]" value of type "System.Collections.Generic.List`1[[Pester.Block, Pester, Version=5.3.1.0, Culture=neutral, PublicKeyToken=null]]" to type "System.Management.Automation.ScriptBlock".
 ---> System.Management.Automation.ArgumentTransformationMetadataException: Cannot convert the "System.Collections.Generic.List`1[Pester.Block]" value of type "System.Collections.Generic.List`1[[Pester.Block, Pester, Version=5.3.1.0, Culture=neutral, PublicKeyToken=null]]" to type "System.Management.Automation.ScriptBlock".
 ---> System.Management.Automation.PSInvalidCastException: Cannot convert the "System.Collections.Generic.List`1[Pester.Block]" value of type "System.Collections.Generic.List`1[[Pester.Block, Pester, Version=5.3.1.0, Culture=neutral, PublicKeyToken=null]]" to type "System.Management.Automation.ScriptBlock".
   at System.Management.Automation.LanguagePrimitives.ThrowInvalidCastException(Object valueToConvert, Type resultType)
   at System.Management.Automation.LanguagePrimitives.ConvertNoConversion(Object valueToConvert, Type resultType, Boolean recurse, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable)
   at System.Management.Automation.LanguagePrimitives.ConversionData`1.Invoke(Object valueToConvert, Type resultType, Boolean recurse, PSObject originalValueToConvert, IFormatProvider formatProvider, TypeTable backupTable)
   at System.Management.Automation.LanguagePrimitives.ConvertTo(Object valueToConvert, Type resultType, Boolean recursion, IFormatProvider formatProvider, TypeTable backupTypeTable)                                                                                                at System.Management.Automation.ArgumentTypeConverterAttribute.Transform(EngineIntrinsics engineIntrinsics, Object inputData, Boolean bindingParameters, Boolean bindingScriptCmdlet)                                                                                             --- End of inner exception stack trace ---
   at System.Management.Automation.ArgumentTypeConverterAttribute.Transform(EngineIntrinsics engineIntrinsics, Object inputData, Boolean bindingParameters, Boolean bindingScriptCmdlet)
   at System.Management.Automation.ParameterBinderBase.BindParameter(CommandParameterInternal parameter, CompiledCommandParameter parameterMetadata, ParameterBindingFlags flags)
   --- End of inner exception stack trace ---
   at System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exception)
   at System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.Interpreter.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.LightLambda.RunVoid1[T0](T0 arg0)
   at System.Management.Automation.PSScriptCmdlet.RunClause(Action`1 clause, Object dollarUnderbar, Object inputToProcess)
   at System.Management.Automation.PSScriptCmdlet.DoProcessRecord()
   at System.Management.Automation.CommandProcessor.ProcessRecord()
at none, C:\Users\scott\Documents\PowerShell\Modules\Pester\5.3.1\Pester.psm1: line 234
at PostProcess-ExecutedBlock<Process>, C:\Users\scott\Documents\PowerShell\Modules\Pester\5.3.1\Pester.psm1: line 2713
at Run-Test, C:\Users\scott\Documents\PowerShell\Modules\Pester\5.3.1\Pester.psm1: line 1671
at Invoke-Test, C:\Users\scott\Documents\PowerShell\Modules\Pester\5.3.1\Pester.psm1: line 2465
at Invoke-Pester<End>, C:\Users\scott\Documents\PowerShell\Modules\Pester\5.3.1\Pester.psm1: line 5225
at <ScriptBlock>, <No file>: line 1
nohwnd commented 2 years ago

I don't remember writing that function, but it sounds like function name conflict maybe.

fflaten commented 2 years ago

This is caused by an alias in the module that hijacks an internal Pester-function.

image

Try importing the module without aliases (or specify the one you need except 'any' with -Alias 'name','of','aliases')

BeforeAll {
    Import-Module ListFunctions -Function *
}

Describe 'Demo' {
    It 'Shows the issue' {
        New-HashSet
    }
}
nohwnd commented 2 years ago

Time to rename all non-public functions to Verb-PesterNoun and make our so much more non-standard again?

fflaten commented 2 years ago

Or register them in SafeCommands?

nohwnd commented 2 years ago

Yeah that is an option too.

fflaten commented 2 years ago

Yeah that is an option too.

Any preference?

We could probably limit it to some or all of these single word functions at first. Less risk of someone adding aliases for conflicting verb-noun.

function any ($InputObject) {  # Mandatory
function combineNonNull ($Array) {
function defined {  # Mandatory
function none ($InputObject) {  # Mandatory
function notDefined {  # Mandatory
function or {  # Mandatory
function sum ($InputObject, $PropertyName, $Zero) {  # Mandatory
function tryAddValue {
function tryGetProperty {
function tryGetValue {
function tryRemoveKey ($Hashtable, $Key) {
function trySetProperty {
nohwnd commented 2 years ago

Updating all of those functions with a call via safeCommands will make the complicated code look even more complex. How about a naming convention instead? We could just suffix the name with underscore or underscore and some other char like any_-? Intellisense keeps working and we have very small likelyhood of conflicts.

fflaten commented 2 years ago

I agree. Suffix or "pesterAny" etc sounds good. We real only need to fix the ones above to if you want. Don't think Fold-Block etc will be stolen by an alias, but.. you never know 😁

nohwnd commented 2 years ago

Ok let me suffix them with "_"