-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Add File Coverage to coverage data #4556
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,171 @@ function Get-ClassCoverageData([xml.xmlelement]$element) | |
| 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) | ||
| { | ||
| $h = @{} | ||
|
|
@@ -134,6 +299,7 @@ function Get-CoverageData($xmlPath) | |
| CoverageLogFile = $xmlPath | ||
| CoverageSummary = (Get-CoverageSummary -element $CoverageXml.CoverageSession.Summary) | ||
| Assembly = $assemblies | ||
| FileCoverage = Get-FileCoverageData $CoverageXml | ||
| } | ||
| $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 } } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be have a default value for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
under most circumstances (for example, when running on the system where the data was collected), it's not needed at all, so I don't want to do any substitution. By setting it to default of "" and "" (for old and new) no substitution takes place and everything works as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is precedence here with Invoke-OpenCover, we try to find powershell root directory and build path.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe it's the wrong thing to do. This may be used completely outside of a repo unlike invoke-opencover. consider the scenario inspecting coverage on a remote machine where the paths are not the same as what is in the file. so I can say
-oldbase f:/github/blah -newwbase \\system\share\blahIf you're on the system where you collected the data, you don't to substitute anything, doing a replacement is unneeded. In this scenario (which is quite real, since I work on mac). The path in the log is wrong for me, and my repo may not be the same as where the coverage was gathered. I don't need a lot of complicated, fragile logic when I can just supply it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Closed.