Skip to content

Commit

Permalink
Merge pull request #11 from natescherer/feature/code-coverage
Browse files Browse the repository at this point in the history
Code Coverage
  • Loading branch information
ebekker committed Apr 24, 2021
2 parents 1aca324 + 89f0729 commit bb711b3
Show file tree
Hide file tree
Showing 8 changed files with 774 additions and 7 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ This Action defines the following formal inputs.
| **`gist_badge_label`** | false | If specified, the Test Report Gist will also include an adjacent badge rendered with the status of the associated Test Report and and label content of this input. In addition to any static text you can provide _escape tokens_ of the form `%name%` where name can be the name of any field returned from a Pester Result, such as `ExecutedAt` or `Result`. If you want a literal percent, just specify an empty name as in `%%`.
| **`gist_badge_message`** | false | If Gist badge generation is enabled by providing a value for the `gist_badge_label` input, this input allows you to override the default message on the badge, which is equivalent to the the Pester Result `Status` such as `Failed` or `Passed`. As with the label input, you can specify escape tokens in addition to literal text. See the label input description for more details.
| **`gist_token`** | false | GitHub OAuth/PAT token to be used for accessing Gist to store test results report. The integrated GITHUB_TOKEN that is normally accessible during a Workflow does not include read/write permissions to associated Gists, therefore a separate token is needed. You can control which account is used to actually store the state by generating a token associated with the target account.
| **`coverage_paths`** | false | Comma-separated list of one or more directories to scan for code coverage, relative to the root of the project. Will include all .ps1 and .psm1 files under these directories recursively.
| **`coverage_report_name`** | false | The name of the code coverage report object that will be attached to the Workflow Run. Defaults to the name `COVERAGE_RESULTS_<datetime>` where `<datetime>` is in the form `yyyyMMdd_hhmmss`.
| **`coverage_report_title`** | false | The title of the code coverage report that will be embedded in the report itself, which defaults to the same as the `code_coverage_report_name` input.
| **`coverage_gist`** | false | If true, will attach the coverage results to the gist specified in `gist_name`.
| **`coverage_gist_badge_label`** | false | If specified, the Test Report Gist will also include an adjacent badge rendered with the percentage of the associated Coverage Report and label content of this input.
| **`tests_fail_step`** | false | If true, will cause the step to fail if one or more tests fails.


Expand All @@ -116,6 +121,7 @@ This Action defines the following formal outputs.
| **`total_count`** | Total number of tests discovered.
| **`passed_count`** | Total number of tests passed.
| **`failed_count`** | Total number of tests failed.
| **`coverage_results_path`** | Path to the code coverage results file in JaCoCo XML format.

### PowerShell GitHub Action

Expand Down
116 changes: 109 additions & 7 deletions action.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ $inputs = @{
gist_token = Get-ActionInput gist_token
gist_badge_label = Get-ActionInput gist_badge_label
gist_badge_message = Get-ActionInput gist_badge_message
coverage_paths = Get-ActionInput coverage_paths
coverage_report_name = Get-ActionInput coverage_report_name
coverage_report_title = Get-ActionInput coverage_report_title
coverage_gist = Get-ActionInput coverage_gist
coverage_gist_badge_label = Get-ActionInput coverage_gist_badge_label
tests_fail_step = Get-ActionInput tests_fail_step
}

Expand All @@ -65,6 +70,7 @@ else {
$exclude_paths = splitListInput $inputs.exclude_paths
$include_tags = splitListInput $inputs.include_tags
$exclude_tags = splitListInput $inputs.exclude_tags
$coverage_paths = splitListInput $inputs.coverage_paths
$output_level = splitListInput $inputs.output_level

Write-ActionInfo "Running Pester tests with following:"
Expand Down Expand Up @@ -111,6 +117,19 @@ else {
$pesterConfig.Output.Verbosity = $output_level
}

if ($coverage_paths) {
Write-ActionInfo " * coverage_paths:"
writeListInput $coverage_paths
$coverageFiles = @()
foreach ($path in $coverage_paths) {
$coverageFiles += Get-ChildItem $Path -Recurse -Include @("*.ps1","*.psm1") -Exclude "*.Tests.ps1"
}
$pesterConfig.CodeCoverage.Enabled = $true
$pesterConfig.CodeCoverage.Path = $coverageFiles
$coverage_results_path = Join-Path $test_results_dir coverage.xml
$pesterConfig.CodeCoverage.OutputPath = $coverage_results_path
}

if ($inputs.tests_fail_step) {
Write-ActionInfo " * tests_fail_step: true"
}
Expand Down Expand Up @@ -211,9 +230,33 @@ function Build-MarkdownReport {
}
}

function Build-CoverageReport {
Write-ActionInfo "Building human-readable code-coverage report"
$script:coverage_report_name = $inputs.coverage_report_name
$script:coverage_report_title = $inputs.coverage_report_title

if (-not $script:coverage_report_name) {
$script:coverage_report_name = "COVERAGE_RESULTS_$([datetime]::Now.ToString('yyyyMMdd_hhmmss'))"
}
if (-not $coverage_report_title) {
$script:coverage_report_title = $report_name
}

$script:coverage_report_path = Join-Path $test_results_dir coverage-results.md
& "$PSScriptRoot/jacoco-report/jacocoxml2md.ps1" -Verbose `
-xmlFile $script:coverage_results_path `
-mdFile $script:coverage_report_path -xslParams @{
reportTitle = $script:coverage_report_title
}

& "$PSScriptRoot/jacoco-report/embedmissedlines.ps1" -mdFile $script:coverage_report_path
}

function Publish-ToCheckRun {
param(
[string]$reportData
[string]$reportData,
[string]$reportName,
[string]$reportTitle
)

Write-ActionInfo "Publishing Report to GH Workflow"
Expand Down Expand Up @@ -247,12 +290,12 @@ function Publish-ToCheckRun {
Authorization = "token $ghToken"
}
$bdy = @{
name = $report_name
name = $reportName
head_sha = $ref
status = 'completed'
conclusion = 'neutral'
output = @{
title = $report_title
title = $reportTitle
summary = "This run completed at ``$([datetime]::Now)``"
text = $reportData
}
Expand All @@ -262,7 +305,8 @@ function Publish-ToCheckRun {

function Publish-ToGist {
param(
[string]$reportData
[string]$reportData,
[string]$coverageData
)

Write-ActionInfo "Publishing Report to GH Workflow"
Expand All @@ -273,7 +317,7 @@ function Publish-ToGist {

$gistsApiUrl = "https://api.github.com/gists"
$apiHeaders = @{
Accept = "application/vnd.github.v2+json"
Accept = "application/vnd.github.v3+json"
Authorization = "token $gist_token"
}

Expand Down Expand Up @@ -334,6 +378,49 @@ function Publish-ToGist {
$gistFiles."$($reportGistName)_badge.svg" = @{ content = $gistBadgeResult.Content }
}
}
if ($coverageData) {
$gistFiles."$([io.path]::GetFileNameWithoutExtension($reportGistName))_Coverage.md" = @{ content = $coverageData }
}
if ($inputs.coverage_gist_badge_label) {
$coverage_gist_badge_label = $inputs.coverage_gist_badge_label
$coverage_gist_badge_label = Resolve-EscapeTokens $coverage_gist_badge_label $pesterResult -UrlEncode

$coverageXmlData = Select-Xml -Path $coverage_results_path -XPath "/report/counter[@type='LINE']"
$coveredLines = $coverageXmlData.Node.covered
Write-Host "Covered Lines: $coveredLines"
$missedLines = $coverageXmlData.Node.missed
Write-Host "Missed Lines: $missedLines"
if ($missedLines -eq 0) {
$coveragePercentage = 100
} else {
$coveragePercentage = [math]::Round(100 - (($missedLines / $coveredLines) * 100))
}
$coveragePercentageString = "$coveragePercentage%"

if ($coveragePercentage -eq 100) {
$coverage_gist_badge_color = 'brightgreen'
} elseif ($coveragePercentage -ge 80) {
$coverage_gist_badge_color = 'green'
} elseif ($coveragePercentage -ge 60) {
$coverage_gist_badge_color = 'yellowgreen'
} elseif ($coveragePercentage -ge 40) {
$coverage_gist_badge_color = 'yellow'
} elseif ($coveragePercentage -ge 20) {
$coverage_gist_badge_color = 'orange'
} else {
$coverage_gist_badge_color = 'red'
}

$coverage_gist_badge_url = "https://img.shields.io/badge/$coverage_gist_badge_label-$coveragePercentageString-$coverage_gist_badge_color"
Write-ActionInfo "Computed Coverage Badge URL: $coverage_gist_badge_url"
$coverageGistBadgeResult = Invoke-WebRequest $coverage_gist_badge_url -ErrorVariable $coverageGistBadgeError
if ($coverageGistBadgeError) {
$gistFiles."$($reportGistName)_coverage_badge.txt" = @{ content = $coverageGistBadgeError.Message }
}
else {
$gistFiles."$($reportGistName)_coverage_badge.svg" = @{ content = $coverageGistBadgeResult.Content }
}
}

if (-not $reportGist) {
Write-ActionInfo "Creating initial Tests Report Gist"
Expand Down Expand Up @@ -363,11 +450,26 @@ if ($test_results_path) {

$reportData = [System.IO.File]::ReadAllText($test_report_path)

if ($coverage_results_path) {
Set-ActionOutput -Name coverage_results_path -Value $coverage_results_path

Build-CoverageReport

$coverageSummaryData = [System.IO.File]::ReadAllText($coverage_report_path)
}

if ($inputs.skip_check_run -ne $true) {
Publish-ToCheckRun -ReportData $reportData
Publish-ToCheckRun -ReportData $reportData -ReportName $report_name -ReportTitle $report_title
if ($coverage_results_path) {
Publish-ToCheckRun -ReportData $coverageSummaryData -ReportName $coverage_report_name -ReportTitle $coverage_report_title
}
}
if ($inputs.gist_name -and $inputs.gist_token) {
Publish-ToGist -ReportData $reportData
if ($inputs.coverage_gist) {
Publish-ToGist -ReportData $reportData -CoverageData $coverageSummaryData
} else {
Publish-ToGist -ReportData $reportData
}
}
}

Expand Down
39 changes: 39 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,41 @@ inputs:
You can control which account is used to actually store the state by
generating a token associated with the target account.
coverage_paths:
description: |
Comma-separated list of one or more directories to scan for code
coverage, relative to the root of the project. Will include all .ps1
and .psm1 files under these directories recursively.
required: false

coverage_report_name:
description: |
The name of the code coverage report object that will be attached
to the Workflow Run. Defaults to the name
`COVERAGE_RESULTS_<datetime>` where `<datetime>` is in the form
`yyyyMMdd_hhmmss`.
required: false

coverage_report_title:
description: |
The title of the code coverage report that will be embedded in the
report itself, which defaults to the same as the
`code_coverage_report_name` input.
required: false

coverage_gist:
description: |
If true, will attach the coverage results to the gist specified in
`gist_name`.
required: false

coverage_gist_badge_label:
description: |
If specified, the Test Report Gist will also include an adjacent
badge rendered with the percentage of the associated Coverage Report
and label content of this input.
required: false

tests_fail_step:
description: |
If true, will cause the step to fail if one or more tests fails.
Expand Down Expand Up @@ -181,6 +216,10 @@ outputs:
failed_count:
description: Total number of tests failed.

coverage_results_path:
description: |
Path to the code coverage results file in JaCoCo XML format.
branding:
color: purple
Expand Down
37 changes: 37 additions & 0 deletions jacoco-report/embedmissedlines.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$mdFile
)

$mdData = Get-Content -Path $mdFile

$outputData = @()
foreach ($line in $mdData) {
if ($line -like "- Line #*") {
$linePrefix = $line.Split("|")[0]
$lineNumber = $linePrefix.Split("#")[1]
$arrayLineNumber = $lineNumber - 1

$filePath = $line.Split("|")[1]
if ($IsWindows -or $PSVersionTable.PSVersion.Major -le 5) {
$filePath = $filePath.Replace("/","\")
}
$workspaceFiles = Get-ChildItem -Path "$env:GITHUB_WORKSPACE" -Recurse -File
$resolvedFilePath = $workspaceFiles | Where-Object {$_.FullName -like "*$filePath"}
$fileContents = Get-Content -Path $resolvedFilePath
$missedLine = $fileContents[$arrayLineNumber]

$outputData += $linePrefix
$outputData += "``````"
$outputData += $missedLine
$outputData += "``````"

}
else {
$outputData += $line
}
}

Set-Content -Value $outputData -Path $mdFile
98 changes: 98 additions & 0 deletions jacoco-report/example.jacoco.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@

# Coverage Report: Pester (04/16/2021 11:51:47)

* Pester (04/16/2021 11:51:47)

Outcome: 98.43% Coverage
| Lines Covered: 191
| Lines Missed: 3

## Details:


### src

<details>
<summary>
:x: ChangelogManagement.psm1
</summary>

#### Lines Missed:
- Line #4
```
. $PrivateFile.FullName
```
</details>


### src/public

<details>
<summary>
:heavy_check_mark: Add-ChangelogData.ps1
</summary>

#### All Lines Covered!
</details>



<details>
<summary>
:heavy_check_mark: ConvertFrom-Changelog.ps1
</summary>

#### All Lines Covered!
</details>



<details>
<summary>
:heavy_check_mark: Get-ChangelogData.ps1
</summary>

#### All Lines Covered!
</details>



<details>
<summary>
:heavy_check_mark: New-Changelog.ps1
</summary>

#### All Lines Covered!
</details>



<details>
<summary>
:x: Update-Changelog.ps1
</summary>

#### Lines Missed:
- Line #79
```
throw "You must be running in GitHub Actions to use GitHub LinkMode"
```
- Line #89
```
throw "You must be running in Azure Pipelines to use AzureDevOps LinkMode"
```
</details>


Loading

0 comments on commit bb711b3

Please sign in to comment.