Code Monkey home page Code Monkey logo

Comments (10)

fflaten avatar fflaten commented on May 24, 2024 1

@csandfeld Interesting find, but it's not related to this issue. I'm curious why they don't provide the same Ast though, so might ask on the PowerShell discord or github-repo 🤔

The root cause is that Pester assumes that all provided container scriptblocks are bound to the same session state used when calling Invoke-Pester ("caller state"). It injects the provided parameters (Data) in that session state only, so if the scriptblocks are executed in a different session state they won't see those variables.

Using [scriptblock]::Create or {}.Ast.GetScriptblock() creates an unbound scriptblock, meaning it doesn't have a session state. As a result it is mistakenly executed in Pester's internal module state, while the variables are set in the default "terminal/script/global" state.

If possible Pester should detect unbound container scriptblocks and set the caller state to avoid being executed in Pester's internal state.

Bonus issue:
The same issue would occur by either generating the scriptblocks OR calling Invoke-Pester from a module as both cases would cause mismatching session states. So a follow up improvement might be to always set the variables in the container's session state.

from pester.

csandfeld avatar csandfeld commented on May 24, 2024

Interesting find @GitHubEddie. I had a look at the AST for each "type" of scriptblock, and I guess the problem is caused by missing data in the AST Parent property - check this out.

There IS data in the Parent if scriptblock is created using curly braces ...

{ 'Script block using curly braces' }.Ast

# Output
Attributes         : {}
UsingStatements    : {}
ParamBlock         :
BeginBlock         :
ProcessBlock       :
EndBlock           : 'Script block using curly braces'
DynamicParamBlock  :
ScriptRequirements :
Extent             : { 'Script block using curly braces' }
Parent             : { 'Script block using curly braces' }

... while there is NO data in the Parent property if scriptblock is created using the [scriptblock] accelerator ...

[scriptblock]::Create('Script block using accelerator').Ast

# Output
Attributes         : {}
UsingStatements    : {}
ParamBlock         :
BeginBlock         :
ProcessBlock       :
EndBlock           : Script block using accelerator
DynamicParamBlock  :
ScriptRequirements :
Extent             : Script block using accelerator
Parent             :

... and there is also NO data in the Parent property if scriptblock is created using the full [System.Management.Automation.ScriptBlock] class

[System.Management.Automation.ScriptBlock]::Create('Script block using full class name').Ast

# Output
Attributes         : {}
UsingStatements    : {}
ParamBlock         :
BeginBlock         :
ProcessBlock       :
EndBlock           : Script block using full class name
DynamicParamBlock  :
ScriptRequirements :
Extent             : Script block using full class name
Parent             :

Verified this on Windows PowerShell 5.1.22621.2506 and PowerShell 7.4.0

from pester.

csandfeld avatar csandfeld commented on May 24, 2024

... or by the difference in the Extent property for that matter

from pester.

fflaten avatar fflaten commented on May 24, 2024

Thanks for the detailed report.

@GitHubEddie: May I ask why you'd want to use [scriptblock]::Create(string script) in the first place when you already have a scriptblock? It will convert your scriptblock to a string, then create a scriptblock from it - which is really redundant. Instead you get weird issues like this and broken debugging. Are you getting the real code as text from external data/system?

@nohwnd The unbound scriptblock is executed in Pester's session state. Anything we can fix without breaking other scenarios?

from pester.

GitHubEddie avatar GitHubEddie commented on May 24, 2024

@fflaten, thanks for the explanation, it has been enlightening.

May I ask why you'd want to use [scriptblock]::Create(string script) in the first place when you already have a scriptblock?

The example given was for brevity, this is what I am actually doing.

I have a series of data driven tests that relate to individual systems, Active Directory for example. I keep these tests along with their pester configuration settings in PowerShell data files (.psd1).
The data files end up looking something like this:

# PesterConfiguration.Demo.Tests.psd1
@{
    TestResult = @{
        Enabled = $true
        OutputFormat = 'JUnitXml'
    }
 
    Output = @{
        Verbosity = 'Detailed'
        CIFormat = 'Auto'
    }
 
    Run = @{
        Exit = $false
        SkipRemainingOnFailure = 'None'
        PassThru = $true
		
        Container = @(
            @{
 
               Scriptblock = {
					param($PARAM1)
				 
					BeforeDiscovery {
						write-host ('BeforeDiscovery PARAM1: {0}' -f $PARAM1)
					}
				 
					BeforeAll {
						write-host ('BeforeAll PARAM1: {0}' -f $PARAM1)
					}
				 
					Describe '<PARAM1> attempt' {
						It '<PARAM1> attempt' {
							$PARAM1  | Should -Be 'param1 test'
						}
					}
                }
 
                Data = @{
                    PARAM1 = 'param1 test'
                }
            }
 
        )
    }
 
 
}

I safely import the data file and add it to new-pestercontainer and new-pesterconfiguration objects. This is where the odd scriptblock behaviour begins.

If you run this for example, where the imported scriptblock is injected directly into the pester container:

$import = Import-PowerShellDataFile -Path 'C:\PesterConfiguration.Demo.Tests.psd1'
$importScriptblock = $import.Run.Container[0].Scriptblock
$importData = $import.Run.Container[0].Data
$container = New-PesterContainer -ScriptBlock $importScriptblock -Data $importData
$config = New-PesterConfiguration -Hashtable $import
$config.run.Container = $container
$Result = invoke-pester -configuration $config

The test does not appear in the result:

Pester v5.5.0

Starting discovery in 1 files.
Discovery found 0 tests in 42ms.
Running tests.
Tests completed in 39ms
Tests Passed: 0, Failed: 0, Skipped: 0 NotRun: 0

If we look at the script block in a working container we get:

PS C:\> $config.Run.Container[0].Value[0].item

    param($PARAM1)
 
    BeforeDiscovery {
        write-host ('BeforeDiscovery PARAM1: {0}' -f $PARAM1)
    }
 
    BeforeAll {
        write-host ('BeforeAll PARAM1: {0}' -f $PARAM1)
    }
 
    Describe '<PARAM1> attempt' {
        It '<PARAM1> attempt' {
            $PARAM1  | Should -Be 'param1 test'
        }
    }

However, the script block in the container where the test is not in the result we see the issue is 'extra' curly braces:

PS C:\> $config.Run.Container[0].Value[0].item
{
					param($PARAM1)
				 
					BeforeDiscovery {
						write-host ('BeforeDiscovery PARAM1: {0}' -f $PARAM1)
					}
				 
					BeforeAll {
						write-host ('BeforeAll PARAM1: {0}' -f $PARAM1)
					}
				 
					Describe '<PARAM1> attempt' {
						It '<PARAM1> attempt' {
							$PARAM1  | Should -Be 'param1 test'
						}
					}
                }

These 'extra' curly braces come from the Import-PowerShellDataFile function.
To accommodate this, I convert the script block to a string, remove the 'extra' curly braces, then convert it back to a script block.
This is why I have been using the [scriptblock]::Create static method.

When the script block is in the right sessionstate without the extra curly braces the test runs as expected:

Pester v5.5.0

Starting discovery in 1 files.
BeforeDiscovery PARAM1: param1 test
Discovery found 1 tests in 45ms.
Running tests.
BeforeAll PARAM1: param1 test
Describing param1 test attempt
  [+] param1 test attempt 8ms (2ms|6ms)
Tests completed in 124ms
Tests Passed: 1, Failed: 0, Skipped: 0 NotRun: 0

from pester.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.