Skip to content

Invoke-Pester overwrites the caller's $p and $d variables #3060

Open

Description

@GioAbq

Checklist

  • Issue has a meaningful title
  • I have searched the existing issues
  • I have tested using the latest version of Pester (6.2.0, and main at 98e56d6)

What is the issue?

Invoke-Pester overwrites the caller's variables $p and $d. After the call, $p holds the path to Pester's own Pester.ScriptScope.ps1 and $d holds a hashtable with a ScriptBlock key. If the caller did not have them, they are created.

This happens in a script, in a function and at the prompt, on PowerShell 7.6 and Windows PowerShell 5.1. It goes back to Pester 5.2.0.

It can do real damage. A script that keeps a file path in $p and writes to it after running tests writes into Pester's installation instead:

$p = 'C:\temp\config.ps1'
$orig = [IO.File]::ReadAllBytes($p)
# ... change the file, run the tests ...
Invoke-Pester -Path .\config.Tests.ps1
[IO.File]::WriteAllBytes($p, $orig)   # overwrites Pester.ScriptScope.ps1, not config.ps1

This happened to us. Every later Invoke-Pester run then failed all tests with no useful message, or hung waiting for input, until we restored the file from a clean copy.

Expected Behavior

Invoke-Pester does not change or create variables in the caller's scope.

Steps To Reproduce

$p = 'my value'
$d = 'my other value'
$null = Invoke-Pester -Container (New-PesterContainer -ScriptBlock { Describe 'd' { It 'i' { } } }) -Output None -PassThru
$p   # ...\Pester\6.2.0/Pester.ScriptScope.ps1
$d   # @{ ScriptBlock = ... }

Describe your environment

  • Pester 6.2.0 (also reproduced on main at 98e56d6)
  • PowerShell 7.6.7 and Windows PowerShell 5.1.26100
  • Windows 11 Pro 10.0.26300

Possible Solution?

Invoke-InNewScriptScope in src/Pester.Runtime.ps1 dot-sources its wrapper:

$wrapper = {
    param ($private:p, $private:d)
    & $private:p @d
}
# the wrapper is bound to the caller's session state
. $wrapper $Path $Data

Dot-sourcing runs the wrapper in the caller's current scope, so its parameters $p and $d are bound there. private: only hides them from child scopes; it does not stop them from replacing the caller's variables.

Calling the wrapper with & instead of . fixes it. The wrapper then gets its own scope, and & $private:p still runs Pester.ScriptScope.ps1 as a script file, so the new script scope is still pushed. Invoke-File already calls its wrapper with & the same way. I will open a PR with this change and tests.

🤖

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions