From 5871e1ac4b0e6613bfd61649058a3537dcad49ae Mon Sep 17 00:00:00 2001 From: "James Truher [MSFT]" Date: Mon, 6 Feb 2017 23:04:45 -0800 Subject: [PATCH] Provide class level coverage data via OpenCover module (#3088) --- test/tools/OpenCover/OpenCover.Format.ps1xml | 125 +++++++++++++++++++ test/tools/OpenCover/OpenCover.psd1 | 1 + test/tools/OpenCover/OpenCover.psm1 | 68 ++++++++-- 3 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 test/tools/OpenCover/OpenCover.Format.ps1xml diff --git a/test/tools/OpenCover/OpenCover.Format.ps1xml b/test/tools/OpenCover/OpenCover.Format.ps1xml new file mode 100644 index 000000000..a381e4d2d --- /dev/null +++ b/test/tools/OpenCover/OpenCover.Format.ps1xml @@ -0,0 +1,125 @@ + + + + CoverageDataTable + + OpenCover.CoverageData + + + + + + + + + + CoverageLogFile + CoverageSummary + + + + + + + ClassCoverageDeltaTable + + ClassCoverageDelta + + + + + Right + Right + Right + Right + + + + + ClassName + Sequence + SequenceDelta + Branch + BranchDelta + + + + + + + + AssemblyCoverageChangeTable + + OpenCover.AssemblyCoverageChange + + + + + Right + Right + Right + Right + + + + + AssemblyName + Sequence + {0:N}SequenceDelta + Branch + {0:N}BranchDelta + + + + + + + AssemblyCoverageDataTable + + OpenCover.AssemblyCoverageData + + + + + Right + Right + + + + + AssemblyName + Sequence + Branch + + + + + + + + CoverageChangeTable + + OpenCover.CoverageChange + + + + + + + + + + + + Sequence + SequenceDelta + Branch + BranchDelta + + + + + + + + diff --git a/test/tools/OpenCover/OpenCover.psd1 b/test/tools/OpenCover/OpenCover.psd1 index 435070e86..f66c71fbc 100644 --- a/test/tools/OpenCover/OpenCover.psd1 +++ b/test/tools/OpenCover/OpenCover.psd1 @@ -7,6 +7,7 @@ CompanyName = 'Microsoft Corporation' Copyright = '(c) Microsoft Corporation. All rights reserved.' Description = 'Module to install OpenCover and run Powershell tests to collect code coverage' DotNetFrameworkVersion = 4.5 +FormatsToProcess = @('OpenCover.Format.ps1xml') FunctionsToExport = @('Get-CodeCoverage','Compare-CodeCoverage', 'Install-OpenCover', 'Invoke-OpenCover') CmdletsToExport = @() VariablesToExport = @() diff --git a/test/tools/OpenCover/OpenCover.psm1 b/test/tools/OpenCover/OpenCover.psm1 index b996a3b26..2dae3b466 100644 --- a/test/tools/OpenCover/OpenCover.psm1 +++ b/test/tools/OpenCover/OpenCover.psm1 @@ -8,11 +8,13 @@ if ((Get-Command -Name 'git' -ErrorAction Ignore) -ne $Null) { function Get-AssemblyCoverageData([xml.xmlelement] $element) { $coverageSummary = (Get-CoverageSummary -element $element.Summary) + $classCoverage = Get-ClassCoverageData $element $AssemblyCoverageData = [PSCustomObject] @{ AssemblyName = $element.ModuleName CoverageSummary = $coverageSummary Branch = $coverageSummary.BranchCoverage Sequence = $coverageSummary.SequenceCoverage + ClassCoverage = $classCoverage } $AssemblyCoverageData | Add-Member -MemberType ScriptMethod -Name ToString -Value { "{0} ({1})" -f $this.AssemblyName,$this.CoverageSummary.BranchCoverage } -Force @@ -21,11 +23,45 @@ function Get-AssemblyCoverageData([xml.xmlelement] $element) return $AssemblyCoverageData } -function Get-CodeCoverageChange($r1, $r2) +function Get-ClassCoverageData([xml.xmlelement]$element) +{ + $classes = [system.collections.arraylist]::new() + foreach ( $class in $element.classes.class ) + { + # skip classes with names likeĀ <>f__AnonymousType6`4 + if ( $class.fullname -match "<>" ) { continue } + $name = $class.fullname + $branch = $class.summary.branchcoverage + $sequence = $class.summary.sequenceCoverage + $o = [pscustomobject]@{ ClassName = $name; Branch = $branch; Sequence = $sequence} + $o.psobject.TypeNames.Insert(0, "ClassCoverageData") + $null = $classes.Add($o) + } + return $classes +} + +function Get-CodeCoverageChange($r1, $r2, [string[]]$ClassName) { $h = @{} $Deltas = new-object "System.Collections.ArrayList" + if ( $ClassName ) { + foreach ( $Class in $ClassName ) { + $c1 = $r1.Assembly.ClassCoverage | ?{$_.ClassName -eq $Class } + $c2 = $r2.Assembly.ClassCoverage | ?{$_.ClassName -eq $Class } + $ClassCoverageChange = [pscustomobject]@{ + ClassName = $Class + Branch = $c2.Branch + BranchDelta = $c2.Branch - $c1.Branch + Sequence = $c2.Sequence + SequenceDelta = $c2.Sequence - $c1.sequence + } + $ClassCoverageChange.psobject.typenames.insert(0,"ClassCoverageDelta") + Write-Output $ClassCoverageChange + } + return + } + $r1.assembly | ForEach-Object { $h[$_.assemblyname] = @($_) } $r2.assembly | ForEach-Object { if($h.ContainsKey($_.assemblyname)) @@ -42,16 +78,16 @@ function Get-CodeCoverageChange($r1, $r2) { $runs = @($h[$kvPair.Name]) $assemblyCoverageChange = (Get-AssemblyCoverageChange -r1 $runs[0] -r2 $runs[1]) - $Deltas.Add($assemblyCoverageChange) | Out-Null + $null = $Deltas.Add($assemblyCoverageChange) } $CoverageChange = [PSCustomObject] @{ Run1 = $r1 Run2 = $r2 - Branch = $r2.Summary.BranchCoverage - Sequence = $r2.Summary.SequenceCoverage - BranchDelta = [double] ($r2.Summary.BranchCoverage - $r1.Summary.BranchCoverage) - SequenceDelta = [double] ($r2.Summary.SequenceCoverage - $r1.Summary.SequenceCoverage) + Branch = $r2.CoverageSummary.BranchCoverage + Sequence = $r2.CoverageSummary.SequenceCoverage + BranchDelta = [double] ($r2.CoverageSummary.BranchCoverage - $r1.CoverageSummary.BranchCoverage) + SequenceDelta = [double] ($r2.CoverageSummary.SequenceCoverage - $r1.CoverageSummary.SequenceCoverage) Deltas = $Deltas } $CoverageChange.PSTypeNames.Insert(0,"OpenCover.CoverageChange") @@ -80,7 +116,6 @@ function Get-AssemblyCoverageChange($r1, $r2) SequenceDelta = $r2.Sequence - $r1.Sequence } $AssemblyCoverageChange.PSTypeNames.Insert(0,"OpenCover.AssemblyCoverageChange") - return $AssemblyCoverageChange } @@ -96,11 +131,12 @@ function Get-CoverageData($xmlPath) } $CoverageData = [PSCustomObject] @{ + CoverageLogFile = $xmlPath CoverageSummary = (Get-CoverageSummary -element $CoverageXml.CoverageSession.Summary) Assembly = $assemblies } $CoverageData.PSTypeNames.Insert(0,"OpenCover.CoverageData") - + Add-Member -InputObject $CoverageData -MemberType ScriptMethod -Name GetClassCoverage -Value { param ( $name ) $this.assembly.classcoverage | ?{$_.classname -match $name } } $null = $CoverageXml ## Adding explicit garbage collection as the $CoverageXml object tends to be very large, in order of 1 GB. @@ -292,7 +328,9 @@ function Compare-CodeCoverage [Parameter(Mandatory=$true,Position=0,ParameterSetName="file")][string]$RunFile1, [Parameter(Mandatory=$true,Position=1,ParameterSetName="file")][string]$RunFile2, [Parameter(Mandatory=$true,Position=0,ParameterSetName="coverage")][Object]$Run1, - [Parameter(Mandatory=$true,Position=1,ParameterSetName="coverage")][Object]$Run2 + [Parameter(Mandatory=$true,Position=1,ParameterSetName="coverage")][Object]$Run2, + [Parameter()][String[]]$ClassName, + [Parameter()][switch]$Summary ) if ( $PSCmdlet.ParameterSetName -eq "file" ) @@ -304,7 +342,15 @@ function Compare-CodeCoverage $Run2 = (Get-CoverageData -xmlPath $xmlPath2) } - (Get-CodeCoverageChange -r1 $Run1 -r2 $Run2) + $change = Get-CodeCoverageChange -r1 $Run1 -r2 $Run2 -Class $ClassName + if ( $Summary -or $ClassName ) + { + $change + } + else + { + $change.Deltas + } } <# @@ -473,4 +519,4 @@ function Invoke-OpenCover Remove-Item "$env:temp\unelevated.ps1" -force -ErrorAction SilentlyContinue } } -} \ No newline at end of file +}