Skip to content

Instantly share code, notes, and snippets.

@JPRuskin
Created August 1, 2019 21:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JPRuskin/92b6993ca627c5efb690982c0492bdb0 to your computer and use it in GitHub Desktop.
Save JPRuskin/92b6993ca627c5efb690982c0492bdb0 to your computer and use it in GitHub Desktop.
class fileregion {
hidden [string]$OriginalFilePath
[string]$RelativeFilePath
[int]$StartLine
[int]$EndLine
}
function Get-FileRegions {
param(
[string[]]$Path
)
begin {
$Results = @()
}
process {
foreach ($Path in $Path) {
$i = 1
switch -File $Path -Regex {
"^# BEGIN (?<Path>.+)$" {
$Current = [fileregion]@{
OriginalFilePath = $Path
RelativeFilePath = $Matches.Path
StartLine = ($i++)
}
}
"^# END (?<Path>.+)$" {
$Current.EndLine = ($i++)
$Results += $Current
}
default {$i++}
}
}
}
end {
$Results
}
}
function Add-XmlElement {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[System.Xml.XmlNode]$Parent,
[ValidateNotNullOrEmpty()]
[string]$Name = $(
$MyInvocation.InvocationName -replace '^Add-(.+)Element$','$1'
),
[System.Collections.DictionaryEntry]$Attributes
)
if ($Name -eq 'Xml') {
throw "Need to specify Element type"
}
$Element = $Parent.AppendChild($Parent.OwnerDocument.CreateElement($Name))
if ($Attributes) {
foreach ($Key in $Attributes.Keys) {
$attribute = $Element.Attributes.Append($Parent.OwnerDocument.CreateAttribute($key))
$attribute.Value = $Attributes.$Key
}
}
return $Element
}
function Add-JaCoCoCounter {
[Alias('Add-InstructionCounter', 'Add-LineCounter', 'Add-MethodCounter', 'Add-ClassCounter')]
[CmdletBinding()]
param(
[Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Piped')]
$InputObject,
[Parameter(Mandatory)]
[System.Xml.XmlNode]$Parent,
[ValidateNotNullOrEmpty()]
[ValidateSet('Instruction', 'Line', 'Method', 'Class')]
[string]$Type = $(
$MyInvocation.InvocationName -replace '^Add-(.+)Counter$','$1'
),
[Parameter(ParameterSetName = 'Specified')]
[int]$Missing,
[Parameter(ParameterSetName = 'Specified')]
[int]$Covered
)
process {
}
}
<#
TODO:
- Fix it so it removes the original nodes (unsure?), or allows you to create a new "CodeCoverage-Split.xml" file ✓
- Rewrite Add-JoCoCoCounter again to be more forgiving / allow for pipelining / defaulting to 0 if no value is found
-
#>
function Update-JaCoCoCoverageFile {
<#
.Synopsis
A function to modify code coverage to point at source-files in a Module-Builder (or similar) compiled module
.Example
Update-JaCoCoCoverageFile -Path .\CodeCoverage.xml -CompiledPath $env:ArtifactStagingDirectory
Updates the coverage file in place, resolving paths to the original source based on the regions in the compiled psm1
#>
[CmdletBinding()]
param(
# Path of the JaCoCo Code Coverage data
[string]$Path = "C:\Users\james.ruskin\VSO\Platform\QMODHelper\CodeCoverage.xml",
# Path to the compiled artifacts
[string[]]$CompiledPath = "C:\Users\james.ruskin\VSO\Platform\QMODHelper\output\",
# The path to save the updated Code Coverage data to. Defaults to the original file path.
[string]$OutputFile = $Path,
# Leaves original Code Coverage data in the file
[switch]$NoCleanup
)
begin {
# We're stealing Pester's XML tools that are used to generate this report in an interesting way
$CoverageTools = Join-Path (Get-Module Pester -ListAvailable -Verbose:$false)[0].ModuleBase "Functions/Coverage.ps1"
. $CoverageTools # Specifically Add-XmlElement and Add-JoCoCoCounter
$ConvertedCoverage = Get-FileRegions -Path (Get-ChildItem -Path $CompiledPath -Recurse -Filter *.psm1).FullName
function FindRegion ($Line) {
$Converter.Where({$_.StartLine -le $Line -and $_.EndLine -ge $Line}, "First")
}
}
process {
foreach ($Path in $Path) {
[xml]$CoverageFile = Get-Content -Path $Path
$reportElement = $CoverageFile.report[1..$($coverageFile.report.Count)] # DOCTYPE string injection takes 0?
foreach ($Package in $reportElement.package) {
Write-Verbose "Converting '$($Package.name)'"
$Converter = $ConvertedCoverage | ? OriginalFilePath -Match "$(Split-Path $Package.sourceFile.Name -Leaf)$"
$packageElement = Add-XmlElement $reportElement "package" @{
name = "$($Package.Name)"
}
# Adding Classes (per source file)
foreach ($FileGroup in ($Package.class.method | Group-Object {(FindRegion $_.Line).RelativeFilePath}).Where{$_.Name}) {
Write-Verbose " Adding items from '$($FileGroup.Name)'"
$classElement = Add-XmlElement $packageElement 'class' -Attributes ([ordered] @{
name = "$($FileGroup.Name -replace '^\.\\(.+)\.ps1$','$1' -replace '\\','.')"
sourcefilename = $FileGroup.Name
})
foreach ($method in $FileGroup.Group) {
Write-Verbose " Adding '$($method.Name)'"
$methodElement = Add-XmlElement $classElement "method" -Attributes ([ordered] @{
name = $method.Name
desc = " ($(Split-Path (Split-Path $FileGroup.Name -Parent) -Leaf))"
line = $_.Line - (FindRegion $FileGroup.Line).StartLine
})
# Adding Method (Function) Counters
foreach ($counter in $method.counter) {
Add-JaCoCoCounter $counter.Type @{"$($counter.Type)" = @{missed = 0+$counter.Missed; covered = 0+$counter.Covered}} $methodElement
}
}
foreach ($counter in $FileGroup.group.counter.type | Sort -Unique) {
Add-JaCoCoCounter $Counter @{
"$Counter" = @{
missed = [int](0+($FileGroup.group.counter.Where{$_.Type -eq $Counter}.missed | Measure-Object -Sum).sum)
covered = [int](0+($FileGroup.group.counter.Where{$_.Type -eq $Counter}.covered | Measure-Object -Sum).sum)
}
} $classElement
}
Add-JaCoCoCounter Class @{
Class = @{
missed = 0+($FileGroup.Group.Where{$_.counter.missed -ne 0}.Count)
covered = 0+($FileGroup.Group.Where{$_.counter.covered -eq 0}.Count)
}
} $classElement
}
Write-Verbose "Adding Line Coverage"
foreach ($File in $Converter) {
Write-Verbose " Adding '$($File.RelativeFilePath)'"
$FileLines = $Package.sourcefile.line | Where {[int]($_.nr) -ge $File.StartLine -and [int]($_.nr) -le $File.EndLine}
$sourceFileElement = Add-XmlElement $packageElement 'sourcefile' -Attributes ([ordered] @{
name = $File.RelativeFilePath
})
foreach ($Line in $FileLines) {
$null = Add-XmlElement $sourceFileElement 'line' -Attributes ([ordered]@{
nr = $Line.nr - $File.StartLine
mi = $Line.mi
ci = $Line.ci
})
}
$FileCoverage = $packageElement.class.Where{$_.sourceFileName -eq $File.RelativeFilePath}.counter
@("Instruction","Line","Method","Class").ForEach{
Add-JaCoCoCounter $_ @{$_ = @{
missed=[int](0 + ($FileCoverage | Where Type -eq $_).Missed)
covered=[int](0 + ($FileCoverage | Where Type -eq $_).Covered)
}} $sourceFileElement
}
}
@("Instruction","Line","Method","Class").ForEach{
Add-JaCoCoCounter $_ @{$_ = @{
missed=0
covered=0
}} $packageElement
}
}
@("Instruction","Line","Method","Class").ForEach{
Add-JaCoCoCounter $_ @{$_ = @{
missed=0
covered=0
}} $reportElement
}
}
}
end {
Write-Verbose "Saving XML File to '$($OutputFile)'"
$CoverageFile.Save($OutputFile)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment