Add File Coverage to coverage data (#4556)
It is now possible to see the coverage based on the file and then format the data so a report generator is not needed. You can map the files from one location to another, so if you have a copy of the repo, but in a different location, you can still format the output in a way so it may be studied
This commit is contained in:
parent
befc5f8ae1
commit
f6208f3c42
|
@ -8,7 +8,7 @@ Copyright = '(c) Microsoft Corporation. All rights reserved.'
|
||||||
Description = 'Module to install OpenCover and run Powershell tests to collect code coverage'
|
Description = 'Module to install OpenCover and run Powershell tests to collect code coverage'
|
||||||
DotNetFrameworkVersion = 4.5
|
DotNetFrameworkVersion = 4.5
|
||||||
FormatsToProcess = @('OpenCover.Format.ps1xml')
|
FormatsToProcess = @('OpenCover.Format.ps1xml')
|
||||||
FunctionsToExport = @('Get-CodeCoverage','Compare-CodeCoverage', 'Install-OpenCover', 'Invoke-OpenCover')
|
FunctionsToExport = @('Get-CodeCoverage','Compare-CodeCoverage', 'Install-OpenCover', 'Invoke-OpenCover','Format-FileCoverage')
|
||||||
CmdletsToExport = @()
|
CmdletsToExport = @()
|
||||||
VariablesToExport = @()
|
VariablesToExport = @()
|
||||||
AliasesToExport = @()
|
AliasesToExport = @()
|
||||||
|
|
|
@ -40,6 +40,171 @@ function Get-ClassCoverageData([xml.xmlelement]$element)
|
||||||
return $classes
|
return $classes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# region FileCoverage
|
||||||
|
|
||||||
|
class FileCoverage
|
||||||
|
{
|
||||||
|
[string]$Path
|
||||||
|
[Collections.Generic.HashSet[int]]$Hit
|
||||||
|
[Collections.Generic.HashSet[int]]$Miss
|
||||||
|
[int]$SequencePointCount = 0
|
||||||
|
[Double]$Coverage
|
||||||
|
FileCoverage([string]$p) {
|
||||||
|
$this.Path = $p
|
||||||
|
$this.Hit = [Collections.Generic.HashSet[int]]::new()
|
||||||
|
$this.Miss = [Collections.Generic.HashSet[int]]::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<#
|
||||||
|
.Synopsis
|
||||||
|
Format the coverage data for a file
|
||||||
|
.Description
|
||||||
|
Show the lines which were hit or not in a specific file. Line numbers are included in the output.
|
||||||
|
If a line was hit during a test run a '+' will follow the line number, if a line was missed, a '-'
|
||||||
|
will follow the line number. If a line is not hittable, it will not show '+' or '-'.
|
||||||
|
|
||||||
|
You can map file locations with the -oldBase and -newBase parameters (see example below), so you can
|
||||||
|
view coverage on a system with a different file layout. It is obvious to note that if files are different
|
||||||
|
between the systems, the results will be misleading
|
||||||
|
.EXAMPLE
|
||||||
|
PS> $coverage = Get-CodeCoverage -CoverageXmlFile .\opencover.xml
|
||||||
|
PS> Format-FileCoverage -FileCoverageData $coverage.FileCoverage -filter "CredSSP.cs"
|
||||||
|
|
||||||
|
...
|
||||||
|
0790 try
|
||||||
|
0791 + {
|
||||||
|
0792 // ServiceController.Start will return before the service is actually started
|
||||||
|
0793 // This API will wait forever
|
||||||
|
0794 + serviceController.WaitForStatus(
|
||||||
|
0795 + targetStatus,
|
||||||
|
0796 + new TimeSpan(20000000) // 2 seconds
|
||||||
|
0797 + );
|
||||||
|
0798 + return true; // service reached target status
|
||||||
|
0799 }
|
||||||
|
0800 - catch (System.ServiceProcess.TimeoutException) // still waiting
|
||||||
|
0801 - {
|
||||||
|
0802 - if (serviceController.Status != pendingStatus
|
||||||
|
...
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
Map the file location from C:\projects\powershell-f975h to /users/james/src
|
||||||
|
PS> $coverage = Get-CodeCoverage -CoverageXmlFile .\opencover.xml
|
||||||
|
PS> $formatArgs = @{
|
||||||
|
FileCoverageData = $coverage.FileCoverage
|
||||||
|
filter = "Service.cs"
|
||||||
|
oldBase = "C:\\projects\\powershell-f975h"
|
||||||
|
newBase = "/users/james/src"
|
||||||
|
}
|
||||||
|
PS> Format-FileCoverage @formatArgs
|
||||||
|
|
||||||
|
...
|
||||||
|
0790 try
|
||||||
|
0791 + {
|
||||||
|
0792 // ServiceController.Start will return before the service is actually started
|
||||||
|
0793 // This API will wait forever
|
||||||
|
0794 + serviceController.WaitForStatus(
|
||||||
|
0795 + targetStatus,
|
||||||
|
0796 + new TimeSpan(20000000) // 2 seconds
|
||||||
|
0797 + );
|
||||||
|
0798 + return true; // service reached target status
|
||||||
|
0799 }
|
||||||
|
0800 - catch (System.ServiceProcess.TimeoutException) // still waiting
|
||||||
|
0801 - {
|
||||||
|
0802 - if (serviceController.Status != pendingStatus
|
||||||
|
...
|
||||||
|
#>
|
||||||
|
function Format-FileCoverage
|
||||||
|
{
|
||||||
|
param (
|
||||||
|
[Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]$FileCoverageData,
|
||||||
|
[Parameter()][string]$oldBase = "",
|
||||||
|
[Parameter()][string]$newBase = "",
|
||||||
|
[Parameter()][string]$filter = ".*"
|
||||||
|
)
|
||||||
|
|
||||||
|
$files = @($fileCoverageData.Keys) | Where-Object { $_ -match $filter }
|
||||||
|
|
||||||
|
foreach ( $file in $files ) {
|
||||||
|
$coverageData = $FileCoverageData[$file]
|
||||||
|
$filepath = $file -replace "$oldBase","${newBase}"
|
||||||
|
if ( test-path $filepath ) {
|
||||||
|
$content = get-content $filepath
|
||||||
|
for($i = 0; $i -lt $content.length; $i++ ) {
|
||||||
|
if ( $coverageData.Hit -contains ($i+1)) {
|
||||||
|
$sign = "+"
|
||||||
|
}
|
||||||
|
elseif ( $coverageData.Miss -contains ($i+1)) {
|
||||||
|
$sign = "-"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$sign = " "
|
||||||
|
}
|
||||||
|
$outputline = "{0:0000} {1} {2}" -f ($i+1),$sign,$content[$i]
|
||||||
|
if ( $sign -eq "+" ) { write-host -fore green $outputline }
|
||||||
|
elseif ( $sign -eq "-" ) { write-host -fore red $outputline }
|
||||||
|
else { write-host -fore white $outputline }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Error "Cannot find $filepath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-FileCoverageData([xml]$CoverageData)
|
||||||
|
{
|
||||||
|
$result = [Collections.Generic.Dictionary[string,FileCoverage]]::new()
|
||||||
|
$count = 0
|
||||||
|
Write-Progress "collecting files"
|
||||||
|
$filehash = $CoverageData.SelectNodes(".//File") | %{ $h = @{} } { $h[$_.uid] = $_.fullpath } { $h }
|
||||||
|
Write-Progress "collecting sequence points"
|
||||||
|
$nodes = $CoverageData.SelectNodes(".//SequencePoint")
|
||||||
|
$ncount = $nodes.count
|
||||||
|
Write-Progress "scanning sequence points"
|
||||||
|
foreach($point in $nodes) {
|
||||||
|
$fileid = $point.fileid
|
||||||
|
$filepath = $filehash[$fileid]
|
||||||
|
$s = [int]$point.sl
|
||||||
|
$e = [int]$point.el
|
||||||
|
$filedata = $null
|
||||||
|
if ( ! $result.TryGetValue($filepath, [ref]$filedata) ) {
|
||||||
|
$filedata = [FileCoverage]::new($filepath)
|
||||||
|
$null = $result.Add($filepath, $filedata)
|
||||||
|
}
|
||||||
|
|
||||||
|
for($i = $s; $i -le $e; $i++) {
|
||||||
|
if ( $point.vc -eq "0" ) {
|
||||||
|
$null = $filedata.Miss.Add($i)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$null = $filedata.Hit.Add($i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ( (++$count % 50000) -eq 0 ) { Write-Progress "$count of $ncount" }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Almost done, we're looking at two runs, and one run might have missed a line that
|
||||||
|
# was hit in another run, so go throw each one of the collections and remove any
|
||||||
|
# hit from the miss collection
|
||||||
|
Write-Progress "Cleanup up collections"
|
||||||
|
foreach ( $key in $result.keys ) {
|
||||||
|
$collection = $null
|
||||||
|
if ( $result.TryGetValue($key, [ref]$collection ) ) {
|
||||||
|
foreach ( $hit in $collection.hit ) {
|
||||||
|
$null = $collection.miss.remove($hit)
|
||||||
|
}
|
||||||
|
$collection.SequencePointCount = $collection.Hit.Count + $Collection.Miss.Count
|
||||||
|
$collection.Coverage = $collection.Hit.Count/$collection.SequencePointCount*100
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Error "Could not find '$key'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# now return $result
|
||||||
|
$result
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
function Get-CodeCoverageChange($r1, $r2, [string[]]$ClassName)
|
function Get-CodeCoverageChange($r1, $r2, [string[]]$ClassName)
|
||||||
{
|
{
|
||||||
$h = @{}
|
$h = @{}
|
||||||
|
@ -134,6 +299,7 @@ function Get-CoverageData($xmlPath)
|
||||||
CoverageLogFile = $xmlPath
|
CoverageLogFile = $xmlPath
|
||||||
CoverageSummary = (Get-CoverageSummary -element $CoverageXml.CoverageSession.Summary)
|
CoverageSummary = (Get-CoverageSummary -element $CoverageXml.CoverageSession.Summary)
|
||||||
Assembly = $assemblies
|
Assembly = $assemblies
|
||||||
|
FileCoverage = Get-FileCoverageData $CoverageXml
|
||||||
}
|
}
|
||||||
$CoverageData.PSTypeNames.Insert(0,"OpenCover.CoverageData")
|
$CoverageData.PSTypeNames.Insert(0,"OpenCover.CoverageData")
|
||||||
Add-Member -InputObject $CoverageData -MemberType ScriptMethod -Name GetClassCoverage -Value { param ( $name ) $this.assembly.classcoverage | Where-Object {$_.classname -match $name } }
|
Add-Member -InputObject $CoverageData -MemberType ScriptMethod -Name GetClassCoverage -Value { param ( $name ) $this.assembly.classcoverage | Where-Object {$_.classname -match $name } }
|
||||||
|
|
Loading…
Reference in a new issue