From c03fdef0525bd2eee5863d4e2cc846b176b44b3b Mon Sep 17 00:00:00 2001 From: Daan Jonkers Date: Thu, 19 Oct 2017 15:46:10 +0200 Subject: [PATCH 1/3] Added detailed code coverage. Per sourcefile a line counter and each line covered or missed numbers. --- Functions/Coverage.Tests.ps1 | 25 +++++++++ Functions/Coverage.ps1 | 85 ++++++++++++++++++++++++++++- Pester.psm1 | 9 +++- report.xsd | 102 +++++++++++++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 3 deletions(-) create mode 100644 report.xsd diff --git a/Functions/Coverage.Tests.ps1 b/Functions/Coverage.Tests.ps1 index a0a0d029b..6389bb915 100644 --- a/Functions/Coverage.Tests.ps1 +++ b/Functions/Coverage.Tests.ps1 @@ -83,6 +83,31 @@ InModuleScope Pester { Exit-CoverageAnalysis -PesterState $testState } + Context 'Entire file detailed coverage' { + $testState = New-PesterState -Path $root + + # Path deliberately duplicated to make sure the code doesn't produce multiple breakpoints for the same commands + Enter-CoverageAnalysis -CodeCoverage "$root\TestScript.ps1", "$root\TestScript.ps1" -PesterState $testState + + It 'Has the proper number of breakpoints defined' { + $testState.CommandCoverage.Count | Should Be 7 + } + + $null = & "$root\TestScript.ps1" + $coverageReport = Get-CoverageReport -PesterState $testState + + It 'JaCoCo report must be correct'{ + [String]$jaCoCoReportXml = Get-JaCoCoReportXml -PesterState $testState -CoverageReport $coverageReport -DetailedCodeCoverage + $jaCoCoReportXml = $jaCoCoReportXml -replace 'Pester \([^\)]*','Pester (date' + $jaCoCoReportXml = $jaCoCoReportXml -replace 'start="[0-9]*"','start=""' + $jaCoCoReportXml = $jaCoCoReportXml -replace 'dump="[0-9]*"','dump=""' + $jaCoCoReportXml = $jaCoCoReportXml -replace '\n','' + $jaCoCoReportXml = $jaCoCoReportXml.Replace($root,'') + $jaCoCoReportXml | should be '' + } + Exit-CoverageAnalysis -PesterState $testState + } + Context 'Single function with missed commands' { $testState = New-PesterState -Path $root diff --git a/Functions/Coverage.ps1 b/Functions/Coverage.ps1 index ff3e8ada3..4b30d7b44 100644 --- a/Functions/Coverage.ps1 +++ b/Functions/Coverage.ps1 @@ -488,6 +488,7 @@ function Get-CoverageReport $missedCommands = @(Get-CoverageMissedCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) $hitCommands = @(Get-CoverageHitCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) + $allCommands = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command, Breakpoint) $analyzedFiles = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] -ExpandProperty File -Unique) $fileCount = $analyzedFiles.Count @@ -500,6 +501,7 @@ function Get-CoverageReport NumberOfCommandsMissed = $missedCommands.Count MissedCommands = $missedCommands HitCommands = $hitCommands + AllCommands = $allCommands AnalyzedFiles = $analyzedFiles } } @@ -580,7 +582,9 @@ function Get-JaCoCoReportXml { [parameter(Mandatory=$true)] $PesterState, [parameter(Mandatory=$true)] - [object] $CoverageReport + [object] $CoverageReport, + + [Switch]$DetailedCodeCoverage ) if ($null -eq $CoverageReport -or ($pester.Show -eq [Pester.OutputTypes]::None) -or $CoverageReport.NumberOfCommandsAnalyzed -eq 0) @@ -610,6 +614,11 @@ function Get-JaCoCoReportXml { $jaCoCoReport += "`n" $jaCoCoReport += "`n" $jaCoCoReport += "`n" + + if ($DetailedCodeCoverage) + { + $jaCoCoReport += "`n" + } $jaCoCoReport += "`n" $jaCoCoReport += "`n" $jaCoCoReport += "`n" @@ -620,6 +629,80 @@ function Get-JaCoCoReportXml { $jaCoCoReportXml.report.name = "Pester ($now)" $jaCoCoReportXml.report.sessioninfo.start=$startTime.ToString() $jaCoCoReportXml.report.sessioninfo.dump=$endTime.ToString() + + if ($DetailedCodeCoverage) + { + + $fileName = ""; + $lineNr = -1; + $report = $jaCoCoReportXml.ChildNodes[1] + $package = $report.ChildNodes[1] + $mi = 0 + $ci = 0 + $missed = 0 + $covered = 0 + $line = $null + foreach($row in $CoverageReport.AllCommands) + { + if ($sourceName -ne $row.File) + { + $xmlFile = $jaCoCoReportXml.CreateElement("sourcefile") + $xmlName = $jaCoCoReportXml.CreateAttribute("name") + $xmlName.value = $row.File + $null = $xmlFile.Attributes.Append($xmlName) + $null = $package.AppendChild($xmlFile) + $sourceName = $row.File + $lineNr = -1; + $counter = $jaCoCoReportXml.CreateElement("counter") + + $missed = 0 + $covered = 0 + + $typeCounter = $counter.Attributes.Append($jaCoCoReportXml.CreateAttribute("type")) + $typeCounter.Value = "LINE" + + $miCounter = $counter.Attributes.Append($jaCoCoReportXml.CreateAttribute("missed")) + $miCounter.value = $missed + + $ciCounter = $counter.Attributes.Append($jaCoCoReportXml.CreateAttribute("covered")) + $ciCounter.value = $covered + + $null = $xmlFile.AppendChild($counter) + } + + if ($lineNr -ne $row.Line) + { + $line = $jaCoCoReportXml.CreateElement("line") + $nr = $jaCoCoReportXml.CreateAttribute("nr") + $nr.value = $row.Line + $mi = 0 + $ci = 0 + $null = $line.Attributes.Append($nr) + $ciLine = $line.Attributes.Append($jaCoCoReportXml.CreateAttribute("ci")) + $ciLine.value = 0 + $miLine = $line.Attributes.Append($jaCoCoReportXml.CreateAttribute("mi")) + $miLine.value = 0 + $null = $xmlFile.InsertBefore($line, $counter) + $lineNr = $row.Line + } + + if ($row.Breakpoint.HitCount -eq 0) + { + $mi += 1 + $miLine.value = $mi + $missed += 1 + $miCounter.value = $missed + } + else + { + $ci += 1 + $ciLine.value = $ci + $covered += 1 + $ciCounter.value = $covered + } + } + } + $jaCoCoReportXml.report.counter[0].missed = $CoverageReport.MissedCommands.Count.ToString() $jaCoCoReportXml.report.counter[0].covered = $CoverageReport.HitCommands.Count.ToString() $jaCoCoReportXml.report.counter[1].missed = $missedLines.ToString() diff --git a/Pester.psm1 b/Pester.psm1 index 6226e9807..ccf895464 100644 --- a/Pester.psm1 +++ b/Pester.psm1 @@ -761,6 +761,8 @@ New-PesterOption [ValidateSet('JaCoCo')] [String]$CodeCoverageOutputFileFormat = "JaCoCo", + [Switch]$DetailedCodeCoverage = $false, + [Switch]$Strict, [Parameter(Mandatory = $true, ParameterSetName = 'NewOutputSet')] @@ -856,10 +858,13 @@ New-PesterOption $pester | Write-PesterReport $coverageReport = Get-CoverageReport -PesterState $pester - Write-CoverageReport -CoverageReport $coverageReport + if ($DetailedCodeCoverage -eq $false) + { + Write-CoverageReport -CoverageReport $coverageReport + } if ((& $script:SafeCommands['Get-Variable'] -Name CodeCoverageOutputFile -ValueOnly -ErrorAction $script:IgnoreErrorPreference) ` -and (& $script:SafeCommands['Get-Variable'] -Name CodeCoverageOutputFileFormat -ValueOnly -ErrorAction $script:IgnoreErrorPreference) -eq 'JaCoCo') { - $jaCoCoReport = Get-JaCoCoReportXml -PesterState $pester -CoverageReport $coverageReport + $jaCoCoReport = Get-JaCoCoReportXml -PesterState $pester -CoverageReport $coverageReport -DetailedCodeCoverage:$DetailedCodeCoverage $jaCoCoReport | & $SafeCommands['Out-File'] $CodeCoverageOutputFile -Encoding utf8 } Exit-CoverageAnalysis -PesterState $pester diff --git a/report.xsd b/report.xsd new file mode 100644 index 000000000..b47e1586b --- /dev/null +++ b/report.xsd @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 03f15c4d4819fa366c34eba9ce705da941db31f8 Mon Sep 17 00:00:00 2001 From: Daan Jonkers Date: Fri, 20 Oct 2017 00:59:29 +0200 Subject: [PATCH 2/3] Added CodeCoverage parameter to Describe. If the -DetailedCodeCoverage switch is on then the CodeCoverage parameters of the Describe parts will be used for code coverage. --- Functions/Coverage.ps1 | 60 +++++++++++++++++++-------------------- Functions/Describe.ps1 | 23 ++++++++++++++- Functions/PesterState.ps1 | 5 ++++ Pester.Tests.ps1 | 4 +-- Pester.psm1 | 42 +++++++++++++++++++++++---- 5 files changed, 95 insertions(+), 39 deletions(-) diff --git a/Functions/Coverage.ps1 b/Functions/Coverage.ps1 index 4b30d7b44..c4dd0a437 100644 --- a/Functions/Coverage.ps1 +++ b/Functions/Coverage.ps1 @@ -488,7 +488,7 @@ function Get-CoverageReport $missedCommands = @(Get-CoverageMissedCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) $hitCommands = @(Get-CoverageHitCommands -CommandCoverage $PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command) - $allCommands = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command, Breakpoint) + $allCommands = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] File, Line, Function, Command, Breakpoint) $analyzedFiles = @($PesterState.CommandCoverage | & $SafeCommands['Select-Object'] -ExpandProperty File -Unique) $fileCount = $analyzedFiles.Count @@ -501,7 +501,7 @@ function Get-CoverageReport NumberOfCommandsMissed = $missedCommands.Count MissedCommands = $missedCommands HitCommands = $hitCommands - AllCommands = $allCommands + AllCommands = $allCommands AnalyzedFiles = $analyzedFiles } } @@ -641,38 +641,38 @@ function Get-JaCoCoReportXml { $ci = 0 $missed = 0 $covered = 0 - $line = $null + $line = $null foreach($row in $CoverageReport.AllCommands) { if ($sourceName -ne $row.File) { - $xmlFile = $jaCoCoReportXml.CreateElement("sourcefile") + $xmlFile = $jaCoCoReportXml.CreateElement("sourcefile") $xmlName = $jaCoCoReportXml.CreateAttribute("name") $xmlName.value = $row.File - $null = $xmlFile.Attributes.Append($xmlName) + $null = $xmlFile.Attributes.Append($xmlName) $null = $package.AppendChild($xmlFile) $sourceName = $row.File $lineNr = -1; - $counter = $jaCoCoReportXml.CreateElement("counter") + $counter = $jaCoCoReportXml.CreateElement("counter") - $missed = 0 - $covered = 0 + $missed = 0 + $covered = 0 - $typeCounter = $counter.Attributes.Append($jaCoCoReportXml.CreateAttribute("type")) - $typeCounter.Value = "LINE" + $typeCounter = $counter.Attributes.Append($jaCoCoReportXml.CreateAttribute("type")) + $typeCounter.Value = "LINE" $miCounter = $counter.Attributes.Append($jaCoCoReportXml.CreateAttribute("missed")) - $miCounter.value = $missed + $miCounter.value = $missed $ciCounter = $counter.Attributes.Append($jaCoCoReportXml.CreateAttribute("covered")) $ciCounter.value = $covered - $null = $xmlFile.AppendChild($counter) + $null = $xmlFile.AppendChild($counter) } - if ($lineNr -ne $row.Line) - { - $line = $jaCoCoReportXml.CreateElement("line") + if ($lineNr -ne $row.Line) + { + $line = $jaCoCoReportXml.CreateElement("line") $nr = $jaCoCoReportXml.CreateAttribute("nr") $nr.value = $row.Line $mi = 0 @@ -681,25 +681,25 @@ function Get-JaCoCoReportXml { $ciLine = $line.Attributes.Append($jaCoCoReportXml.CreateAttribute("ci")) $ciLine.value = 0 $miLine = $line.Attributes.Append($jaCoCoReportXml.CreateAttribute("mi")) - $miLine.value = 0 - $null = $xmlFile.InsertBefore($line, $counter) + $miLine.value = 0 + $null = $xmlFile.InsertBefore($line, $counter) $lineNr = $row.Line - } - - if ($row.Breakpoint.HitCount -eq 0) - { - $mi += 1 + } + + if ($row.Breakpoint.HitCount -eq 0) + { + $mi += 1 $miLine.value = $mi - $missed += 1 + $missed += 1 $miCounter.value = $missed - } - else - { - $ci += 1 + } + else + { + $ci += 1 $ciLine.value = $ci - $covered += 1 - $ciCounter.value = $covered - } + $covered += 1 + $ciCounter.value = $covered + } } } diff --git a/Functions/Describe.ps1 b/Functions/Describe.ps1 index 629aca79f..23e70b00e 100644 --- a/Functions/Describe.ps1 +++ b/Functions/Describe.ps1 @@ -25,6 +25,13 @@ Optional parameter containing an array of strings. When calling Invoke-Pester, it is possible to specify a -Tag parameter which will only execute Describe blocks containing the same Tag. +.PARAMETER CodeCoverage +Adds a code coverage report to the Pester tests. Takes strings or hash table values. + +A code coverage report lists the lines of code that did and did not run during +a Pester test. This report does not tell whether code was tested; only whether +the code ran during the test. + .EXAMPLE function Add-Numbers($a, $b) { return $a + $b @@ -69,6 +76,8 @@ about_TestDrive [Alias('Tags')] [string[]] $Tag=@(), + [object[]] $CodeCoverage = @(), + [Parameter(Position = 1)] [ValidateNotNull()] [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)") @@ -81,7 +90,17 @@ about_TestDrive $script:mockTable = @{} } - DescribeImpl @PSBoundParameters -CommandUsed 'Describe' -Pester $Pester -DescribeOutputBlock ${function:Write-Describe} -TestOutputBlock ${function:Write-PesterResult} + if ($Pester.FindCodeCoverage) + { + foreach($cc in $CodeCoverage) + { + $Pester.CodeCoverage += $cc + } + } + else + { + DescribeImpl @PSBoundParameters -CommandUsed 'Describe' -Pester $Pester -DescribeOutputBlock ${function:Write-Describe} -TestOutputBlock ${function:Write-PesterResult} + } } function DescribeImpl { @@ -92,6 +111,8 @@ function DescribeImpl { [Alias('Tags')] $Tag=@(), + [object[]] $CodeCoverage = @(), + [Parameter(Position = 1)] [ValidateNotNull()] [ScriptBlock] $Fixture = $(Throw "No test script block is provided. (Have you put the open curly brace on the next line?)"), diff --git a/Functions/PesterState.ps1 b/Functions/PesterState.ps1 index 85ab4ea51..5945dd57b 100644 --- a/Functions/PesterState.ps1 +++ b/Functions/PesterState.ps1 @@ -52,6 +52,9 @@ function New-PesterState $script:Show = $Show $script:InTest = $false + $script:FindCodeCoverage = $false + $script:CodeCoverage = @() + $script:TestResult = @() $script:TotalCount = 0 @@ -319,6 +322,8 @@ function New-PesterState "TestResult", "SessionState", "CommandCoverage", + "FindCodeCoverage", + "CodeCoverage", "Strict", "Show", "Time", diff --git a/Pester.Tests.ps1 b/Pester.Tests.ps1 index 6455f5134..4c99a239d 100644 --- a/Pester.Tests.ps1 +++ b/Pester.Tests.ps1 @@ -4,8 +4,8 @@ $manifestPath = (Join-Path $here 'Pester.psd1') $changeLogPath = (Join-Path $here 'CHANGELOG.md') # DO NOT CHANGE THIS TAG NAME; IT AFFECTS THE CI BUILD. - -Describe -Tags 'VersionChecks' "Pester manifest and changelog" { +# +Describe -Tags 'VersionChecks' -CodeCoverage '.\Pester.psm1' "Pester manifest and changelog" { $script:manifest = $null $script:tagVersion = $null $script:tagVersionShort = $null diff --git a/Pester.psm1 b/Pester.psm1 index ccf895464..b8ed0e365 100644 --- a/Pester.psm1 +++ b/Pester.psm1 @@ -586,6 +586,9 @@ Default vaule is: JaCoCo. Currently supported formats are: - JaCoCo - this XML file format is compatible with the VSTS/TFS +.PARAMETER DetailedCodeCoverage +Add the sourcefile names and lines covered and missed to the codecoverage file. + .PARAMETER Strict Makes Pending and Skipped tests to Failed tests. Useful for continuous integration where you need to make sure all tests passed. @@ -761,7 +764,7 @@ New-PesterOption [ValidateSet('JaCoCo')] [String]$CodeCoverageOutputFileFormat = "JaCoCo", - [Switch]$DetailedCodeCoverage = $false, + [Switch]$DetailedCodeCoverage = $false, [Switch]$Strict, @@ -807,7 +810,6 @@ New-PesterOption try { - Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester Write-PesterStart $pester $Script $invokeTestScript = { @@ -826,6 +828,34 @@ New-PesterOption $testScripts = @(ResolveTestScripts $Script) + + if ($DetailedCodeCoverage) + { + $pester.FindCodeCoverage = $true + $pester.CodeCoverage = $CodeCoverage + + # find describe codecoverage here + foreach ($testScript in $testScripts) + { + try + { + do + { + & $invokeTestScript -Path $testScript.Path -Arguments $testScript.Arguments -Parameters $testScript.Parameters + } until ($true) + } + catch + { } + } + + + $pester.FindCodeCoverage = $false + $CodeCoverage = $pester.CodeCoverage + } + + + Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester + foreach ($testScript in $testScripts) { try @@ -858,10 +888,10 @@ New-PesterOption $pester | Write-PesterReport $coverageReport = Get-CoverageReport -PesterState $pester - if ($DetailedCodeCoverage -eq $false) - { - Write-CoverageReport -CoverageReport $coverageReport - } + if ($DetailedCodeCoverage -eq $false) + { + Write-CoverageReport -CoverageReport $coverageReport + } if ((& $script:SafeCommands['Get-Variable'] -Name CodeCoverageOutputFile -ValueOnly -ErrorAction $script:IgnoreErrorPreference) ` -and (& $script:SafeCommands['Get-Variable'] -Name CodeCoverageOutputFileFormat -ValueOnly -ErrorAction $script:IgnoreErrorPreference) -eq 'JaCoCo') { $jaCoCoReport = Get-JaCoCoReportXml -PesterState $pester -CoverageReport $coverageReport -DetailedCodeCoverage:$DetailedCodeCoverage From 5e352b81c23722d07929e169483956a9f3e385c7 Mon Sep 17 00:00:00 2001 From: Wojciech Sciesinski Date: Tue, 24 Jul 2018 02:20:23 +0200 Subject: [PATCH 3/3] A compatibility with PSCore 6.x correction and merging upstream error correction --- Functions/Coverage.Tests.ps1 | 10 ++++++---- Pester.Tests.ps1 | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Functions/Coverage.Tests.ps1 b/Functions/Coverage.Tests.ps1 index f53b46935..f0bcdfe6b 100644 --- a/Functions/Coverage.Tests.ps1 +++ b/Functions/Coverage.Tests.ps1 @@ -88,7 +88,8 @@ InModuleScope Pester { $jaCoCoReportXml = $jaCoCoReportXml -replace 'start="[0-9]*"','start=""' $jaCoCoReportXml = $jaCoCoReportXml -replace 'dump="[0-9]*"','dump=""' $jaCoCoReportXml = $jaCoCoReportXml -replace "$([System.Environment]::NewLine)",'' - $jaCoCoReportXml | should -be '' + [String]$ReferenceReport = [String]'' + $jaCoCoReportXml | should -Be $ReferenceReport } Exit-CoverageAnalysis -PesterState $testState } @@ -100,7 +101,7 @@ InModuleScope Pester { Enter-CoverageAnalysis -CodeCoverage "$root\TestScript.ps1", "$root\TestScript.ps1" -PesterState $testState It 'Has the proper number of breakpoints defined' { - $testState.CommandCoverage.Count | Should Be 7 + $testState.CommandCoverage.Count | Should -Be 7 } $null = & "$root\TestScript.ps1" @@ -111,9 +112,10 @@ InModuleScope Pester { $jaCoCoReportXml = $jaCoCoReportXml -replace 'Pester \([^\)]*','Pester (date' $jaCoCoReportXml = $jaCoCoReportXml -replace 'start="[0-9]*"','start=""' $jaCoCoReportXml = $jaCoCoReportXml -replace 'dump="[0-9]*"','dump=""' - $jaCoCoReportXml = $jaCoCoReportXml -replace '\n','' + $jaCoCoReportXml = $jaCoCoReportXml -replace "$([System.Environment]::NewLine)",'' $jaCoCoReportXml = $jaCoCoReportXml.Replace($root,'') - $jaCoCoReportXml | should be '' + [String]$ReferenceReport = "{0}{1}{2}" -f [String]'' + $jaCoCoReportXml | should -Be $ReferenceReport } Exit-CoverageAnalysis -PesterState $testState } diff --git a/Pester.Tests.ps1 b/Pester.Tests.ps1 index 4eba289cb..53d138a9e 100644 --- a/Pester.Tests.ps1 +++ b/Pester.Tests.ps1 @@ -7,7 +7,7 @@ $changeLogPath = (Join-Path $here 'CHANGELOG.md') # DO NOT CHANGE THIS TAG NAME; IT AFFECTS THE CI BUILD. # -Describe -Tags 'VersionChecks' -CodeCoverage '.\Pester.psm1' "Pester manifest and changelog" { +Describe -Tags 'VersionChecks' "Pester manifest and changelog" { $script:manifest = $null $script:tagVersion = $null $script:tagVersionShort = $null