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

Code coverage fails in some circumstances when UseBreakpoints is $false #2306

Open
3 tasks done
johlju opened this issue Feb 19, 2023 · 5 comments
Open
3 tasks done

Comments

@johlju
Copy link
Contributor

johlju commented Feb 19, 2023

Checklist

What is the issue?

I started seening this issue when I started doing class-based resource ~1 year ago, and since it has been troublesome to make a repro case so I just switched to using breakpoints for code coverage instead. But now I managed to repro it with simpler code and simpler tests.

This test works when CodeCoverage.UseBreakpoints = $true but fails when CodeCoverage.UseBreakpoints = $false.

There seems to be a problem somewhere inside Pester when using this new code coverage method.

Expected Behavior

The tests to pass regardless if UseBreakpoints is set to $true or $false.

Steps To Reproduce

File SqlServerDsc.psm1

class StartupParameters
{
    [System.UInt32[]]
    $TraceFlag

    static [StartupParameters] Parse([System.String] $InstanceStartupParameters)
    {
        $startupParameters = [StartupParameters]::new()

        $startupParameterValues = $InstanceStartupParameters -split ';'

        $startupParameters.TraceFlag = [System.UInt32[]] @(
            $startupParameterValues |
                Where-Object -FilterScript {
                    $_ -match '^-T\d+'
                } |
                ForEach-Object -Process {
                    [System.UInt32] $_.TrimStart('-T')
                }
        )

        return $startupParameters
    }
}

function Get-SqlDscTraceFlag
{
    [OutputType([System.UInt32[]])]
    [CmdletBinding(DefaultParameterSetName = 'ByServerName')]
    param
    (
        [Parameter(ParameterSetName = 'ByServiceObject', Mandatory = $true)]
        [Object]
        $ServiceObject
    )

    $traceFlags = [System.UInt32[]] @()

    if ($ServiceObject.StartupParameters)
    {
        $traceFlags = [StartupParameters]::Parse($ServiceObject.StartupParameters).TraceFlag
    }

    return , [System.UInt32[]] $traceFlags
}

File SqlServerDsc.Tests.ps1

BeforeAll {
    Import-Module -Name './SqlServerDsc.psm1' -Force
}

AfterAll {
    # Unload the module being tested so that it doesn't impact any other tests.
    Get-Module -Name 'SqlServerDsc' -All | Remove-Module -Force
}

Describe 'SqlServerDsc' {
    Context 'When one trace flag exist' {
        BeforeAll {
            $mockStartupParameters = '-T4199'

            $mockServiceObject = [PSCustomObject] @{
                StartupParameters = $mockStartupParameters
            }
        }

        Context 'When passing a service object' {
            It 'Should return the correct values' {
                $result = Get-SqlDscTraceFlag -ServiceObject $mockServiceObject

                $result | Should -HaveCount 1
                $result | Should -Contain 4199
            }
        }
    }
}

File debug.ps1

Switch UseBreakpoints to $true to verify that the tests passes using breakpoints for code coverage.

$pesterConfig = New-PesterConfiguration -Hashtable @{
    CodeCoverage = @{
        Enabled = $true
        Path = './SqlServerDsc.psm1'
        OutputPath = './Pester_coverage.xml'
        # Tests fails if this is $false
        UseBreakpoints = $false
    }
    Run = @{
        Path = '*.Tests.ps1'
    }
}

$Error.Clear()
$ErrorView = 'DetailedView'

Invoke-Pester -Configuration $pesterConfig

$Error

Describe your environment

Pester version     : 5.4.0 C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1                       
PowerShell version : 7.3.2
OS version         : Microsoft Windows NT 10.0.22623.0

Possible Solution?

Sorry, unknown.

@johlju
Copy link
Contributor Author

johlju commented Feb 19, 2023

Forgot to add the actual error.

Exception             : 
    Type       : System.ArgumentOutOfRangeException
    Message    : Index was out of range. Must be non-negative and less than or equal to the size of the collection. (Parameter 'index')
    ParamName  : index
    TargetSite : 
        Name          : Insert
        DeclaringType : System.Text.StringBuilder
        MemberType    : Method
        Module        : System.Private.CoreLib.dll
    Data       : System.Collections.ListDictionaryInternal
    Source     : System.Private.CoreLib
    HResult    : -2146233086
    StackTrace : 
   at System.Text.StringBuilder.Insert(Int32 index, String value)
   at System.Management.Automation.Language.PositionUtilities.BriefMessage(IScriptPosition position)
   at System.Management.Automation.ScriptDebugger.TraceLine(IScriptExtent extent)
   at System.Management.Automation.ScriptDebugger.OnSequencePointHit(FunctionContext functionContext)
   at System.Management.Automation.ScriptDebugger.EnterScriptFunction(FunctionContext functionContext)
   at System.Management.Automation.Interpreter.ActionCallInstruction`2.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.ScriptBlock.InvokeWithPipeImpl(ScriptBlockClauseToInvoke clauseToInvoke, Boolean createLocalScope, Dictionary`2 functionsToDefine, List`1 variablesToDefine, ErrorHandlingBehavior errorHandlingBehavior, Object dollarUnder, Object input, Object scriptThis, Pipe outputPipe, InvocationInfo invocationInfo, Object[] args)
   at System.Management.Automation.ScriptBlock.InvokeWithPipe(Boolean useLocalScope, ErrorHandlingBehavior errorHandlingBehavior, Object dollarUnder, Object input, Object scriptThis, Pipe outputPipe, InvocationInfo invocationInfo, Boolean propagateAllExceptionsToTop, List`1 variablesToDefine, Dictionary`2 functionsToDefine, Object[] args)
   at System.Management.Automation.ScriptBlock.InvokeAsMemberFunction(Object instance, Object[] args)
   at System.Management.Automation.Internal.ScriptBlockMemberMethodWrapper.InvokeHelper(Object instance, Object sessionStateInternal, Object[] args)
   at CallSite.Target(Closure, CallSite, Type)
   at System.Management.Automation.Interpreter.DynamicInstruction`2.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
CategoryInfo          : OperationStopped: (:) [], ArgumentOutOfRangeException
FullyQualifiedErrorId : System.ArgumentOutOfRangeException
InvocationInfo        : 
    ScriptLineNumber : 9
    OffsetInLine     : 9
    HistoryId        : -1
    ScriptName       : C:\source\DebugCodeCoverage\SqlServerDsc.psm1
    Line             : $startupParameters = [StartupParameters]::new()

    PositionMessage  : At C:\source\DebugCodeCoverage\SqlServerDsc.psm1:9 char:9
                       +         $startupParameters = [StartupParameters]::new()
                       +         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    PSScriptRoot     : C:\source\DebugCodeCoverage
    PSCommandPath    : C:\source\DebugCodeCoverage\SqlServerDsc.psm1
    CommandOrigin    : Internal
ScriptStackTrace      : at Parse, C:\source\DebugCodeCoverage\SqlServerDsc.psm1: line 9
                        at Get-SqlDscTraceFlag, C:\source\DebugCodeCoverage\SqlServerDsc.psm1: line 45
                        at <ScriptBlock>, C:\source\DebugCodeCoverage\SqlServerDsc.Tests.ps1: line 22
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1998
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1959
                        at Invoke-ScriptBlock, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 2120
                        at Invoke-TestItem, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1194
                        at Invoke-Block, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 830
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 888
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1998
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1959
                        at Invoke-ScriptBlock, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 2123
                        at Invoke-Block, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 935
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 888
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1998
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1959
                        at Invoke-ScriptBlock, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 2123
                        at Invoke-Block, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 935
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 888
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1998
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1959
                        at Invoke-ScriptBlock, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 2123
                        at Invoke-Block, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 935
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 888
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1998
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1959
                        at Invoke-ScriptBlock, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 2123
                        at Invoke-Block, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 935
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1672
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.ps1: line 3
                        at <ScriptBlock>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 3164
                        at Invoke-InNewScriptScope, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 3171
                        at Run-Test, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 1675
                        at Invoke-Test, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 2475
                        at Invoke-Pester<End>, C:\source\SqlServerDsc\output\RequiredModules\Pester\5.4.0\Pester.psm1: line 5272
                        at <ScriptBlock>, C:\source\DebugCodeCoverage\debug.ps1: line 17
                        at <ScriptBlock>, <No file>: line 1

@johlju johlju changed the title Codecoverage fails in some circumstances when UseBreakpoints is $false Code coverage fails in some circumstances when UseBreakpoints is $false Feb 19, 2023
@fflaten
Copy link
Collaborator

fflaten commented Feb 19, 2023

Thanks for the report. This is a weird one. Can reproduce, but have no idea what's going on. Will probably need to debug pwsh, so leaving it to our CC-wizard 🧙‍♂️

@fflaten
Copy link
Collaborator

fflaten commented Feb 23, 2023

This is also a PowerShell-bug. The invoked code isn't modified by us at all. Our injected code gets triggered right after this actually.

Repro:

class MyClass {
    # Any method has to exist, regardless of scriptblock, parameters or static/non-static
    MyMethod() { }
}

Set-PSDebug -Trace 1
[MyClass]::new()

Found this issue when I went to report it: PowerShell/PowerShell#16874

@fflaten
Copy link
Collaborator

fflaten commented Feb 24, 2023

@johlju: I found a workaround for now. Add a constructor to the class, ex. StartupParameters() { }

@johlju
Copy link
Contributor Author

johlju commented Feb 25, 2023

Thank you for putting the work in, and also finding a workaround for this! It seems to work perfectly. I have re-enabled Pester's new code coverage method in the pipeline for SqlServerDsc.

johlju added a commit to dsccommunity/SqlServerDsc that referenced this issue Feb 25, 2023
- SqlServerDsc
  - Add empty constructor to classes to be able to use Pester's new code
    coverage method. See more information can be found in [pester/Pester#2306]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants