diff --git a/Functions/Coverage.Tests.ps1 b/Functions/Coverage.Tests.ps1
index d1fd8b12f..f0bcdfe6b 100644
--- a/Functions/Coverage.Tests.ps1
+++ b/Functions/Coverage.Tests.ps1
@@ -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 ''
+ [String]$ReferenceReport = [String]''
+ $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]''
+ $jaCoCoReportXml | should -Be $ReferenceReport
}
Exit-CoverageAnalysis -PesterState $testState
}
diff --git a/Functions/Coverage.ps1 b/Functions/Coverage.ps1
index 07509d401..73123ab75 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)
@@ -611,7 +615,11 @@ function Get-JaCoCoReportXml {
$jaCoCoReport = "$([System.Environment]::NewLine)"
$jaCoCoReport += "$([System.Environment]::NewLine)"
$jaCoCoReport += "$([System.Environment]::NewLine)"
- $jaCoCoReport += "$([System.Environment]::NewLine)"
+ if ($DetailedCodeCoverage)
+ {
+ $jaCoCoReport += "$([System.Environment]::NewLine)"
+ }
+ $jaCoCoReport += ""
$jaCoCoReport += "$([System.Environment]::NewLine)"
$jaCoCoReport += "$([System.Environment]::NewLine)"
$jaCoCoReport += "$([System.Environment]::NewLine)"
@@ -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()
diff --git a/Functions/Describe.ps1 b/Functions/Describe.ps1
index 2e8e166ad..559064c5f 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?)")
@@ -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 {
@@ -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?)"),
diff --git a/Functions/PesterState.ps1 b/Functions/PesterState.ps1
index e3e413edd..ed7fec501 100644
--- a/Functions/PesterState.ps1
+++ b/Functions/PesterState.ps1
@@ -54,6 +54,9 @@ function New-PesterState
$script:Show = $Show
$script:InTest = $false
+ $script:FindCodeCoverage = $false
+ $script:CodeCoverage = @()
+
$script:TestResult = @()
$script:TotalCount = 0
@@ -333,6 +336,8 @@ function New-PesterState
"TestResult",
"SessionState",
"CommandCoverage",
+ "FindCodeCoverage",
+ "CodeCoverage",
"Strict",
"Show",
"Time",
diff --git a/Pester.Tests.ps1 b/Pester.Tests.ps1
index 5de23b0b5..53d138a9e 100644
--- a/Pester.Tests.ps1
+++ b/Pester.Tests.ps1
@@ -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
diff --git a/Pester.psm1 b/Pester.psm1
index 8e094b9ae..101e5935a 100644
--- a/Pester.psm1
+++ b/Pester.psm1
@@ -604,6 +604,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.
@@ -779,6 +782,8 @@ New-PesterOption
[ValidateSet('JaCoCo')]
[String]$CodeCoverageOutputFileFormat = "JaCoCo",
+ [Switch]$DetailedCodeCoverage = $false,
+
[Switch]$Strict,
[Parameter(Mandatory = $true, ParameterSetName = 'NewOutputSet')]
@@ -824,7 +829,6 @@ New-PesterOption
try
{
- Enter-CoverageAnalysis -CodeCoverage $CodeCoverage -PesterState $pester
Write-PesterStart $pester $Script
$invokeTestScript = {
@@ -843,6 +847,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
@@ -875,10 +907,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