diff --git a/src/functions/Coverage.ps1 b/src/functions/Coverage.ps1 index 739ed90fc..781b9c331 100644 --- a/src/functions/Coverage.ps1 +++ b/src/functions/Coverage.ps1 @@ -249,7 +249,8 @@ function Get-CoverageBreakpoints { [ScriptBlock]$Logger ) - $fileGroups = @($CoverageInfo | & $SafeCommands['Group-Object'] -Property Path) + # PowerShell 6.1+ sorts by default in Group-Object. We need to sort for consistent output in Windows PowerShell + $fileGroups = @($CoverageInfo | & $SafeCommands['Group-Object'] -Property Path | & $SafeCommands['Sort-Object'] -Property Name) foreach ($fileGroup in $fileGroups) { if ($null -ne $Logger) { $sw = [System.Diagnostics.Stopwatch]::StartNew() @@ -287,9 +288,16 @@ function Get-CommandsInFile { # In PowerShell 5.0, dynamic keywords for DSC configurations are represented by the DynamicKeywordStatementAst # class. They still trigger breakpoints, but are not a child class of CommandBaseAst anymore. + # ReturnStatementAst is excluded as it's not behaving consistent. + # "return" is not hit in 5.1 but fixed in a later version. Using "return 123" we get hit on 123 but not return. + # See https://github.com/pester/Pester/issues/1465#issuecomment-604323645 $predicate = { $args[0] -is [System.Management.Automation.Language.DynamicKeywordStatementAst] -or - $args[0] -is [System.Management.Automation.Language.CommandBaseAst] + $args[0] -is [System.Management.Automation.Language.CommandBaseAst] -or + $args[0] -is [System.Management.Automation.Language.BreakStatementAst] -or + $args[0] -is [System.Management.Automation.Language.ContinueStatementAst] -or + $args[0] -is [System.Management.Automation.Language.ExitStatementAst] -or + $args[0] -is [System.Management.Automation.Language.ThrowStatementAst] } } else { @@ -551,6 +559,7 @@ function Get-CoverageCommandText { $reportParentExtentTypes = @( [System.Management.Automation.Language.ReturnStatementAst] + [System.Management.Automation.Language.ExitStatementAst] [System.Management.Automation.Language.ThrowStatementAst] [System.Management.Automation.Language.AssignmentStatementAst] [System.Management.Automation.Language.IfStatementAst] @@ -818,9 +827,10 @@ function Get-JaCoCoReportXml { [long] $endTime = [System.DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() [long] $startTime = [math]::Floor($endTime - $TotalMilliseconds) + # PowerShell 6.1+ sorts by default in Group-Object. We need to sort for consistent output in Windows PowerShell $folderGroups = $CommandCoverage | & $SafeCommands["Group-Object"] -Property { & $SafeCommands["Split-Path"] $_.File -Parent - } + } | & $SafeCommands["Sort-Object"] -Property Name $packageList = [System.Collections.Generic.List[psobject]]@() diff --git a/src/functions/TestResults.NUnit25.ps1 b/src/functions/TestResults.NUnit25.ps1 index aa0eec17a..a0b8f74ff 100644 --- a/src/functions/TestResults.NUnit25.ps1 +++ b/src/functions/TestResults.NUnit25.ps1 @@ -110,7 +110,8 @@ function Write-NUnitTestSuiteElements { $suites = @( # Tests only have GroupId if parameterized. All other tests are put in group with '' value - $Node.Tests | & $SafeCommands['Group-Object'] -Property GroupId + # PowerShell 6.1+ sorts by default in Group-Object. We need to sort for consistent output in Windows PowerShell + $Node.Tests | & $SafeCommands['Group-Object'] -Property GroupId | & $SafeCommands["Sort-Object"] -Property Name ) foreach ($suite in $suites) { diff --git a/src/functions/TestResults.NUnit3.ps1 b/src/functions/TestResults.NUnit3.ps1 index 75db6f9cc..a4b08a70f 100644 --- a/src/functions/TestResults.NUnit3.ps1 +++ b/src/functions/TestResults.NUnit3.ps1 @@ -135,7 +135,8 @@ function Write-NUnit3TestSuiteElement { $blockGroups = @( # Blocks only have GroupId if parameterized (using -ForEach). All other blocks are put in group with '' value - $Node.Blocks | & $SafeCommands['Group-Object'] -Property GroupId + # PowerShell 6.1+ sorts by default in Group-Object. We need to sort for consistent output in Windows PowerShell + $Node.Blocks | & $SafeCommands['Group-Object'] -Property GroupId | & $SafeCommands["Sort-Object"] -Property Name ) foreach ($group in $blockGroups) { @@ -170,7 +171,8 @@ function Write-NUnit3TestSuiteElement { $testGroups = @( # Tests only have GroupId if parameterized. All other tests are put in group with '' value - $Node.Tests | & $SafeCommands['Group-Object'] -Property GroupId + # PowerShell 6.1+ sorts by default in Group-Object. We need to sort for consistent output in Windows PowerShell + $Node.Tests | & $SafeCommands['Group-Object'] -Property GroupId | & $SafeCommands["Sort-Object"] -Property Name ) foreach ($group in $testGroups) { diff --git a/tst/functions/Coverage.Tests.ps1 b/tst/functions/Coverage.Tests.ps1 index bcfa99ba0..b108745fb 100644 --- a/tst/functions/Coverage.Tests.ps1 +++ b/tst/functions/Coverage.Tests.ps1 @@ -17,6 +17,8 @@ InPesterModuleScope { $testScriptPath = Join-Path -Path $root -ChildPath TestScript.ps1 $testScript2Path = Join-Path -Path $root -ChildPath TestScript2.ps1 $testScript3Path = Join-Path -Path $rootSubFolder -ChildPath TestScript3.ps1 + $testScriptStatementsPath = Join-Path -Path $root -ChildPath TestScriptStatements.ps1 + $testScriptExitPath = Join-Path -Path $root -ChildPath TestScriptExit.ps1 $null = New-Item -Path $testScriptPath -ItemType File -ErrorAction SilentlyContinue @@ -42,7 +44,7 @@ InPesterModuleScope { function FunctionTwo { - 'I am function two. I never get called.' + 'I am function two. I never get called.' } FunctionOne @@ -133,6 +135,54 @@ InPesterModuleScope { -f ` 'other' +'@ + + $null = New-Item -Path $testScriptStatementsPath -ItemType File -ErrorAction SilentlyContinue + + Set-Content -Path $testScriptStatementsPath -Value @' + try { + try { + throw 'omg' + } + catch { + throw + } + } + catch { } + + switch (1,2,3) { + 1 { continue; } + 2 { break } + 3 { 'I was skipped because 2 called break in switch.' } + } + + :myBreakLabel foreach ($i in 1..100) { + foreach ($o in 1) { + break myBreakLabel + } + 'I was skipped by a labeled break.' + } + + :myLoopLabel foreach ($i in 1) { + foreach ($o in 1..100) { + continue myLoopLabel + } + 'I was skipped by a labeled contiune.' + } + + # These should not be included in code coverage + & { return } + & { return 123 } + + # will exit the script + exit +'@ + + $null = New-Item -Path $testScriptExitPath -ItemType File -ErrorAction SilentlyContinue + + Set-Content -Path $testScriptExitPath -Value @' + # will exit the script, so keep in own file + exit 123 '@ } @@ -143,14 +193,16 @@ InPesterModuleScope { BeforeAll { # TODO: renaming, breakpoints mean "code point of interests" in most cases here, not actual breakpoints # Path deliberately duplicated to make sure the code doesn't produce multiple breakpoints for the same commands - $breakpoints = Enter-CoverageAnalysis -CodeCoverage $testScriptPath, $testScriptPath, $testScript2Path, $testScript3Path -UseBreakpoints $UseBreakpoints + $breakpoints = Enter-CoverageAnalysis -CodeCoverage $testScriptPath, $testScriptPath, $testScript2Path, $testScript3Path, $testScriptStatementsPath, $testScriptExitPath -UseBreakpoints $UseBreakpoints - @($breakpoints).Count | Should -Be 19 -Because 'it has the proper number of breakpoints defined' + @($breakpoints).Count | Should -Be 40 -Because 'it has the proper number of breakpoints defined' $sb = { $null = & $testScriptPath $null = & $testScript2Path $null = & $testScript3Path + $null = & $testScriptStatementsPath + $null = & $testScriptExitPath } if ($UseBreakpoints) { @@ -167,29 +219,32 @@ InPesterModuleScope { } It 'Reports the proper number of executed commands' { - $coverageReport.NumberOfCommandsExecuted | Should -Be 16 + $coverageReport.NumberOfCommandsExecuted | Should -Be 34 } It 'Reports the proper number of analyzed commands' { - $coverageReport.NumberOfCommandsAnalyzed | Should -Be 19 + $coverageReport.NumberOfCommandsAnalyzed | Should -Be 40 } It 'Reports the proper number of analyzed files' { - $coverageReport.NumberOfFilesAnalyzed | Should -Be 3 + $coverageReport.NumberOfFilesAnalyzed | Should -Be 5 } It 'Reports the proper number of missed commands' { - $coverageReport.MissedCommands.Count | Should -Be 3 + $coverageReport.MissedCommands.Count | Should -Be 6 } It 'Reports the correct missed command' { $coverageReport.MissedCommands[0].Command | Should -Be "'I cannot get called.'" - $coverageReport.MissedCommands[1].Command | Should -Be "'I am function two. I never get called.'" + $coverageReport.MissedCommands[1].Command | Should -Be "'I am function two. I never get called.'" $coverageReport.MissedCommands[2].Command | Should -Be "'I am method two. I never get called.'" + $coverageReport.MissedCommands[3].Command | Should -Be "'I was skipped because 2 called break in switch.'" + $coverageReport.MissedCommands[4].Command | Should -Be "'I was skipped by a labeled break.'" + $coverageReport.MissedCommands[5].Command | Should -Be "'I was skipped by a labeled contiune.'" } It 'Reports the proper number of hit commands' { - $coverageReport.HitCommands.Count | Should -Be 16 + $coverageReport.HitCommands.Count | Should -Be 34 } It 'Reports the correct hit command' { @@ -289,6 +344,28 @@ InPesterModuleScope { + + + + + + + + + + + + + + + + + + + + + + @@ -317,10 +394,40 @@ InPesterModuleScope { - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + ') } @@ -429,6 +536,28 @@ InPesterModuleScope { + + + + + + + + + + + + + + + + + + + + + + @@ -457,10 +586,40 @@ InPesterModuleScope { - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -486,10 +645,10 @@ InPesterModuleScope { - - - - + + + + ') } @@ -503,13 +662,13 @@ InPesterModuleScope { (Clear-WhiteSpace $coberturaReportXml) | Should -Be (Clear-WhiteSpace ' - CommonRoot - + @@ -580,6 +739,36 @@ InPesterModuleScope { + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -663,7 +852,7 @@ InPesterModuleScope { } It 'Reports the correct missed command' { - $coverageReport.MissedCommands[0].Command | Should -Be "'I am function two. I never get called.'" + $coverageReport.MissedCommands[0].Command | Should -Be "'I am function two. I never get called.'" } It 'Reports the proper number of hit commands' { @@ -814,7 +1003,7 @@ InPesterModuleScope { It 'Reports the correct missed command' { $coverageReport.MissedCommands[0].Command | Should -Be "'I cannot get called.'" - $coverageReport.MissedCommands[1].Command | Should -Be "'I am function two. I never get called.'" + $coverageReport.MissedCommands[1].Command | Should -Be "'I am function two. I never get called.'" } It 'Reports the proper number of hit commands' {