Skip to content

Commit

Permalink
Added detailed code coverage. (#920)
Browse files Browse the repository at this point in the history
* Added detailed code coverage. Per sourcefile a line counter and each line covered or missed numbers.

* 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.

* A compatibility with PSCore 6.x correction and merging upstream error correction
  • Loading branch information
jonkeda authored and nohwnd committed Dec 12, 2018
1 parent d71dbc7 commit ac0ccb1
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 8 deletions.
29 changes: 28 additions & 1 deletion Functions/Coverage.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,34 @@ 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 '<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE report PUBLIC "-//JACOCO//DTD Report 1.0//EN" "report.dtd"><report name="Pester (date)"><sessioninfo id="this" start="" dump="" /><counter type="INSTRUCTION" missed="1" covered="7" /><counter type="LINE" missed="1" covered="7" /><counter type="METHOD" missed="1" covered="4" /><counter type="CLASS" missed="0" covered="2" /></report>'
[String]$ReferenceReport = [String]'<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE report PUBLIC "-//JACOCO//DTD Report 1.0//EN" "report.dtd"><report name="Pester (date)"><sessioninfo id="this" start="" dump="" /><counter type="INSTRUCTION" missed="1" covered="7" /><counter type="LINE" missed="1" covered="7" /><counter type="METHOD" missed="1" covered="4" /><counter type="CLASS" missed="0" covered="2" /></report>'
$jaCoCoReportXml | should -Be $ReferenceReport
}
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 "$([System.Environment]::NewLine)",''
$jaCoCoReportXml = $jaCoCoReportXml.Replace($root,'')
[String]$ReferenceReport = "{0}{1}{2}" -f [String]'<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE report PUBLIC "-//JACOCO//DTD Report 1.0//EN" "report.dtd"><report name="Pester (date)"><sessioninfo id="this" start="" dump="" /><package name="Powershell"><sourcefile name="', $([System.IO.Path]::DirectorySeparatorChar).ToString(), [String]'TestScript.ps1"><line nr="5" ci="1" mi="0" /><line nr="6" ci="1" mi="0" /><line nr="9" ci="1" mi="0" /><line nr="11" ci="1" mi="0" /><line nr="12" ci="1" mi="0" /><line nr="18" ci="0" mi="1" /><line nr="21" ci="1" mi="0" /><counter type="LINE" missed="1" covered="6" /></sourcefile></package><counter type="INSTRUCTION" missed="1" covered="6" /><counter type="LINE" missed="1" covered="6" /><counter type="METHOD" missed="1" covered="3" /><counter type="CLASS" missed="0" covered="1" /></report>'
$jaCoCoReportXml | should -Be $ReferenceReport
}
Exit-CoverageAnalysis -PesterState $testState
}
Expand Down
86 changes: 84 additions & 2 deletions Functions/Coverage.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -500,6 +501,7 @@ function Get-CoverageReport
NumberOfCommandsMissed = $missedCommands.Count
MissedCommands = $missedCommands
HitCommands = $hitCommands
AllCommands = $allCommands
AnalyzedFiles = $analyzedFiles
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -611,7 +615,11 @@ function Get-JaCoCoReportXml {
$jaCoCoReport = "<?xml version=""1.0"" encoding=""UTF-8"" standalone=""no""?>$([System.Environment]::NewLine)"
$jaCoCoReport += "<report name="""">$([System.Environment]::NewLine)"
$jaCoCoReport += "<sessioninfo id=""this"" start="""" dump="""" />$([System.Environment]::NewLine)"
$jaCoCoReport += "<counter type=""INSTRUCTION"" missed="""" covered=""""/>$([System.Environment]::NewLine)"
if ($DetailedCodeCoverage)
{
$jaCoCoReport += "<package name=""Powershell""/>$([System.Environment]::NewLine)"
}
$jaCoCoReport += "<counter type=""INSTRUCTION"" missed="""" covered=""""/>"
$jaCoCoReport += "<counter type=""LINE"" missed="""" covered=""""/>$([System.Environment]::NewLine)"
$jaCoCoReport += "<counter type=""METHOD"" missed="""" covered=""""/>$([System.Environment]::NewLine)"
$jaCoCoReport += "<counter type=""CLASS"" missed="""" covered=""""/>$([System.Environment]::NewLine)"
Expand All @@ -621,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()
Expand Down
23 changes: 22 additions & 1 deletion Functions/Describe.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?)")
Expand All @@ -82,7 +91,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 {
Expand All @@ -93,6 +112,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?)"),
Expand Down
5 changes: 5 additions & 0 deletions Functions/PesterState.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ function New-PesterState
$script:Show = $Show
$script:InTest = $false

$script:FindCodeCoverage = $false
$script:CodeCoverage = @()

$script:TestResult = @()

$script:TotalCount = 0
Expand Down Expand Up @@ -333,6 +336,8 @@ function New-PesterState
"TestResult",
"SessionState",
"CommandCoverage",
"FindCodeCoverage",
"CodeCoverage",
"Strict",
"Show",
"Time",
Expand Down
2 changes: 1 addition & 1 deletion Pester.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ $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" {
$script:manifest = $null
$script:tagVersion = $null
Expand Down
41 changes: 38 additions & 3 deletions Pester.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,9 @@ Default value 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.
Expand Down Expand Up @@ -787,6 +790,8 @@ New-PesterOption
[ValidateSet('JaCoCo')]
[String]$CodeCoverageOutputFileFormat = "JaCoCo",

[Switch]$DetailedCodeCoverage = $false,

[Switch]$Strict,

[Parameter(Mandatory = $true, ParameterSetName = 'NewOutputSet')]
Expand Down Expand Up @@ -832,7 +837,6 @@ New-PesterOption

try
{
Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester
Write-PesterStart $pester $Script

$invokeTestScript = {
Expand All @@ -851,6 +855,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
Expand Down Expand Up @@ -883,10 +915,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
Expand Down
Loading

0 comments on commit ac0ccb1

Please sign in to comment.