Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #1028 - Code Coverage for proxy functions/steppablePipelines (updated to v5.0.3) #1656

Merged
merged 1 commit into from Oct 6, 2020

Conversation

leojackson
Copy link
Contributor

1. General summary of the pull request

Note: This PR contains the same bug fix as #1494, but is based on the v5.0 branch instead of master.


Pester generates all breakpoints using a combination of line number and column number. PowerShell does not trigger breakpoints on the proxy function constructs described in #1028 when initiated using both line and column number, but it does when using line number only. This appears to be a bug within PowerShell itself, as the behavior can be replicated outside of Pester like so:

# Create a new proxy function for Write-Output in a file in the current directory
$ProxyMetadata = New-Object System.Management.Automation.CommandMetaData (Get-Command Write-Output)
@"
Function Write-FancyTestOutput {
$([System.Management.Automation.ProxyCommand]::Create($ProxyMetadata))
}
"@ | Out-File $PWD\Write-FancyTestOutput.ps1

# Get AST for the new function
$ast = [System.Management.Automation.Language.Parser]::ParseFile( `
    "$PWD\Write-FancyTestOutput.ps1", `
    [ref]$null, `
    [ref]$null `
)

# Initialize line number var
$LineNumber = 0

# Use FindAll to loop through all commands, look for the
# '& $wrappedCmd @PSBoundParameters' command, and add a
# breakpoint at line & column for that command -- this is
# what Pester currently does
$ast.FindAll( `
    {$args[0] -is [System.Management.Automation.Language.CommandBaseAst]}, `
    $true `
) | Where-Object {
    $_.Extent.Text -like '*& $wrappedCmd @PSBoundParameters*'
} | ForEach-Object {
    If($LineNumber -le 0) {
        $LineNumber = $_.Extent.StartLineNumber
    }
    Set-PSBreakpoint -Script "$PWD\Write-FancyTestOutput.ps1" `
                     -Action { } `
                     -Line $_.Extent.StartLineNumber `
                     -Column $_.Extent.StartColumnNumber
}

. $PWD\Write-FancyTestOutput.ps1
Write-FancyTestOutput -InputObject "Hello"   # will not trigger any breakpoints

# Add another breakpoint with no column specified for the same line
If($LineNumber -gt 0) {
    Set-PSBreakpoint -Script "$PWD\Write-FancyTestOutput.ps1" `
                     -Action { } `
                     -Line $LineNumber
}

Write-FancyTestOutput -InputObject "Hello again"   # Triggers the last breakpoint
Get-PSBreakpoint | fl

...which results in this output:

Id       : 0
Script   : C:\Temp\Write-FancyTestOutput.ps1
Line     : 22
Column   : 22
Enabled  : True
HitCount : 0
Action   :

Id       : 1
Script   : C:\Temp\Write-FancyTestOutput.ps1
Line     : 22
Column   : 23
Enabled  : True
HitCount : 0
Action   :

Id       : 2
Script   : C:\Temp\Write-FancyTestOutput.ps1
Line     : 22
Column   : 0
Enabled  : True
HitCount : 1
Action   :

Rather than trying to rewrite Pester to create breakpoints without column specifications, this PR includes proxy functions using steppable pipelines in the IsIgnoredCommand private function. I feel this is appropriate because in proxy command situations, the steppable pipeline constituents are already covered by breakpoints.

It does this be validating two criteria:

  • The extent text of the command matches '^{?& \$wrappedCmd @PSBoundParameters ?}?$', which matches the specific constructs created when auto-generating a proxy command using [System.Management.Automation.ProxyCommand]::Create().
  • The topmost parent of the command is like '*$steppablePipeline.Begin($PSCmdlet)*$steppablePipeline.Process($_)*$steppablePipeline.End()*', which matches when all three of the steppable pipeline steps have been defined in the function or file where this proxy command is defined.

Since there is no out-of-the-box mechanism to recursively iterate through nested AST objects to get to the topmost parent, this PR also includes a new private function, Get-AstTopParent, that does exactly that.

Fix #1028

@nohwnd nohwnd added this to the 5.0.x milestone Sep 8, 2020
@nohwnd nohwnd modified the milestones: 5.0.x, 5.1 Sep 20, 2020
@nohwnd nohwnd merged commit 4820503 into pester:v5.0 Oct 6, 2020
@nohwnd
Copy link
Member

nohwnd commented Oct 6, 2020

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Code Coverage for proxy functions/steppablePipelines
2 participants