1+ param ([Parameter (Mandatory = $true )][string ] $pulseFilePath )
2+
3+ $ErrorActionPreference = ' Stop'
4+ Set-PSDebug - Strict
5+
6+ Add-Type - AssemblyName System.IO.Compression.FileSystem
7+
8+ function Get-ArchiveTextEntries ([string ] $archivePath , [string []] $entryNames ) {
9+ $entries = @ ()
10+ $archive = [System.IO.Compression.ZipFile ]::Open($archivePath , 0 )
11+ try {
12+ $entryNames | % {
13+ $entry = $archive.getentry ($_ )
14+ if ($entry -eq $null ) { throw " Unable to find entry '$_ ' in $archivePath " }
15+ $entryStream = new-object System.IO.StreamReader($entry.open ())
16+ try {
17+ $entries += $entryStream.readtoend ()
18+ } finally {
19+ $entryStream.close ()
20+ }
21+ }
22+ } finally {
23+ $archive.dispose ()
24+ }
25+ $entries
26+ }
27+
28+ function Test-ExportFileVersion ([string ] $pulseFilePath , [string ] $version ) {
29+ (Get-ArchiveTextEntries $pulseFilePath ' .manifest' | select-string " version=$version " ) -ne $null
30+ }
31+
32+ function Write-CodeCoverage ($groupingNodes , $encounters ) {
33+ $groupingNodes | sort-object - property ' label' | % {
34+ Write-Host (" {0}{1} - {2} of {3} methods ({4})" -f `
35+ (new-object string(" `t " , $_.level )),`
36+ $_.label , `
37+ $_.encounters ,`
38+ $_.nodes ,`
39+ $_.coverage.tostring (' P' ))
40+
41+ if ($encounters -ne $null ) {
42+ $groupingNode = $_
43+ $groupingNode.methods.keys | ? { $encounters.ContainsKey ($_ ) } | sort-object ' label' | % {
44+ Write-Host (" {0}-> [{2}] {1}" -f `
45+ (new-object string(" `t " , ($groupingNode.level + 1 ))),`
46+ $groupingNode.methods [$_ ].label,`
47+ $groupingNode.methods [$_ ].parent.label)
48+ }
49+ }
50+ Write-CodeCoverage ($_.packages.keys | % { $nodes [$_ ] }) $encounters
51+ }
52+ }
53+
54+ Write-Verbose ' Checking export file version compatibility with this script...'
55+ $supportedVersions = ' 2' , ' 2.1'
56+ if (-not (($supportedVersions | % { Test-ExportFileVersion $pulseFilePath $_ }) -contains $true )) {
57+ throw " Unable to continue after finding unsupported manifest file version in $pulseFilePath (supported versions: $ ( [string ]::Join(' ,' , $supportedVersions )) )."
58+ }
59+
60+ Write-Verbose ' Reading export file data...'
61+ $coverageData = Get-ArchiveTextEntries $pulseFilePath ' nodes.json' , ' encounters.json'
62+
63+ Write-Verbose ' Indexing nodes...'
64+ $nodes = @ {}
65+ (ConvertFrom-Json $coverageData [0 ]) | % { $nodes [$_.id ] = $_ }
66+
67+ Write-Verbose ' Indexing encounters...'
68+ $encounters = @ {}
69+ (ConvertFrom-Json $coverageData [1 ]).all | % { $encounters [$_.nodeid ] = $_ }
70+
71+ Write-Verbose ' Processing packages/groups...'
72+ $groupingNodes = $nodes.keys | ? { $nodes [$_ ].kind -eq ' package' -or $nodes [$_ ].kind -eq ' group' } | % { $node = $nodes [$_ ]
73+
74+ $groupNode = $node
75+ $groupNodes = @ ($groupNode )
76+
77+ while ($groupNode.parentId -ne $null ) {
78+ $parentNode = $nodes [$groupNode.parentId ]
79+
80+ Add-Member -in $parentNode ' packages' @ {} - erroract SilentlyContinue
81+
82+ $parentNode.packages [$groupNode.id ] = $groupNode
83+ $groupNode = $parentNode
84+ $groupNodes += $groupNode
85+ }
86+
87+ $level = 0 ; [array ]::reverse($groupNodes )
88+ $groupNodes | % {
89+ if ((Get-Member -in $_ ' level' ) -eq $null ) { Add-Member -in $_ ' level' 0 }
90+ $_.level = $level ++
91+ }
92+
93+ $parentNode = $null
94+ if ($node.parentId -ne $null ) {
95+ $parentNode = $nodes [$node.parentId ]
96+ }
97+
98+ Add-Member -in $node ' nodes' 0
99+ Add-Member -in $node ' encounters' 0
100+ Add-Member -in $node ' coverage' 0
101+ Add-Member -in $node ' parent' $parentNode
102+ Add-Member -in $node ' packages' @ {} - erroract SilentlyContinue
103+ Add-Member -in $node ' methods' @ {} - pass
104+ }
105+
106+ Write-Verbose ' Processing method ancestors...'
107+ $methodNodes = $nodes.keys | ? { $nodes [$_ ].kind -eq ' method' } | % { $methodNode = $nodes [$_ ]
108+ $parent = $nodes [$methodNode.parentId ]
109+ while ($parent.kind -ne ' package' -and $parent.kind -ne ' group' ) {
110+ $parent = $nodes [$parent.parentId ]
111+ }
112+ $parent.methods [$methodNode.id ] = $methodNode
113+
114+ Add-Member -in $methodNode ' parent' $nodes [$methodNode.parentId ]
115+ $methodNode
116+ }
117+
118+ Write-Verbose ' Processing leaf group nodes...'
119+ $leafGroupingNodes = $groupingNodes | ? { $_.methods.Count -gt 0 }
120+ $leafGroupingNodes | % {
121+ $_.encounters = ($_.methods.keys | ? { $encounters.ContainsKey ($_ ) }).length
122+ $_.nodes = $_.methods.Count
123+ }
124+
125+ Write-Verbose ' Counting nodes and encounters across packages...'
126+ $maxLevel = ($groupingNodes | % { $_.level } | measure - max).Maximum
127+ $maxLevel .. 0 | % {
128+ $level = $_
129+ $groupingNodes | ? { $_.level -eq $level } | % {
130+ $groupingNode = $_
131+ $groupingNode.packages.keys | % {
132+ $groupingNode.encounters += $nodes [$_ ].encounters
133+ $groupingNode.nodes += $nodes [$_ ].nodes
134+ }
135+ $groupingNode.coverage = 0
136+ if ($groupingNode.nodes -gt 0 ) {
137+ $groupingNode.coverage = $groupingNode.encounters / $groupingNode.nodes
138+ }
139+ }
140+ }
141+
142+ Write-Verbose ' Finding root package/group nodes...'
143+ $topLevelGroupingNodes = $groupingNodes | ? { $_.level -eq 0 }
144+
145+ Write-Verbose ' Determining total method code coverage...'
146+ $encountersCount = 0 ; $nodesCount = 0 ; $codeCoverage = 0
147+ $topLevelGroupingNodes | % { $encountersCount = $_.encounters ; $nodesCount = $_.nodes }
148+ $codeCoverage = $ (if ($nodesCount -eq 0 ) { 0 } else { $encountersCount / $nodesCount })
149+
150+ Write-Verbose ' Writing total method code coverage by package...'
151+ Write-Host " `n Total Method Code Coverage: " $codeCoverage.ToString (' P' )
152+
153+ Write-Verbose ' Writing method code coverage by package...'
154+ Write-Host " `n`n Method Code Coverage by Package:`n "
155+ Write-CodeCoverage $topLevelGroupingNodes
156+
157+ Write-Verbose ' Writing method code coverage by package and method...'
158+ Write-Host " `n`n Method Code Coverage by Package and Method:`n "
159+ Write-CodeCoverage $topLevelGroupingNodes $encounters
0 commit comments