mirror of
https://github.com/irongut/CodeCoverageSummary.git
synced 2026-05-14 22:20:13 +02:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5088d5eb31 | |||
| ee53bdd6c5 | |||
| 6a542ff4b0 | |||
| 46bc9869d5 | |||
| 97f4a52b81 | |||
| 95ea154a7b | |||
| 2e00d30f70 | |||
| c4d4b9a087 | |||
| ea5d3b417b | |||
| 3d33fafb37 | |||
| 3a41f0a5ea | |||
| dd5c8d3b75 | |||
| 4986e930de | |||
| 37dca42320 | |||
| 5ec10c1882 | |||
| 56c9f3d623 | |||
| f36c88a82b | |||
| ffe316d009 | |||
| 1ea9b55e4d | |||
| fcb924f622 | |||
| ed588922f2 | |||
| 6e12bd152f | |||
| 4ae964bab0 | |||
| 60646036b5 | |||
| 1c2edd9230 | |||
| 9caa66feee | |||
| 6c68cb69dd | |||
| 914b6fe5f9 | |||
| 46007a7270 | |||
| 71ae720dab | |||
| dd7d40d268 | |||
| 065eaf7bb5 | |||
| a5c4e90cbe | |||
| f89139ddac | |||
| ac37b8e9b7 | |||
| 7a76884d03 | |||
| 2389c9883a | |||
| 02bb824606 | |||
| b97bc1147a | |||
| d8b1bc5ef2 | |||
| 7c070aea38 | |||
| 6ed1f6c50d | |||
| ecc89a90a8 | |||
| 4a0dc323d3 | |||
| 711b5c996a | |||
| 9bbe00ce6b | |||
| f9c0e2020c | |||
| 1b1147a0ce | |||
| 3766e5780e | |||
| 473d18e3ad | |||
| 61d10948c8 | |||
| 7cd8fe49e9 | |||
| 174c97ac3d | |||
| 9b11daeca8 | |||
| 6db46e287e | |||
| c5d12d4a89 | |||
| cae1cbcdad | |||
| ed258b1479 |
@@ -0,0 +1,3 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [irongut]
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# Configuration for PR Labeller Action
|
||||||
|
# See: https://github.com/actions/labeler/blob/master/README.md
|
||||||
|
|
||||||
|
# Examples
|
||||||
|
# Add 'label1' to PR if anything changes within 'example' folder or any subfolders
|
||||||
|
# label1:
|
||||||
|
# - example/**/*
|
||||||
|
|
||||||
|
# Add 'label2' to PR if any file changes within 'example2' folder
|
||||||
|
# label2: example2/*
|
||||||
|
|
||||||
|
Action:
|
||||||
|
- action.yml
|
||||||
|
|
||||||
|
Docker:
|
||||||
|
- Dockerfile
|
||||||
|
- .dockerignore
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- src/CodeCoverageSummary/CommandLineOptions.cs
|
||||||
|
|
||||||
|
Parsing:
|
||||||
|
- src/CodeCoverageSummary/CodeSummary.cs
|
||||||
|
- src/CodeCoverageSummary/Program.cs
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
- src/CodeCoverageSummary/CodeSummary.cs
|
||||||
|
- src/CodeCoverageSummary/Program.cs
|
||||||
|
|
||||||
|
DevOps:
|
||||||
|
- .github/**/*
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
name: Assign to Project
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened, labeled]
|
||||||
|
pull_request:
|
||||||
|
types: [opened, labeled]
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
assign-to-project:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Assign to Project
|
||||||
|
steps:
|
||||||
|
- name: Assign Issues to Bugs
|
||||||
|
uses: srggrs/assign-one-project-github-action@1.3.1
|
||||||
|
if: contains(github.event.issue.labels.*.name, 'bug')
|
||||||
|
with:
|
||||||
|
project: 'https://github.com/irongut/CodeCoverageSummary/projects/1'
|
||||||
|
column_name: 'Needs triage'
|
||||||
|
|
||||||
|
- name: Assign Issues to Enhancements
|
||||||
|
uses: srggrs/assign-one-project-github-action@1.3.1
|
||||||
|
if: contains(github.event.issue.labels.*.name, 'enhancement')
|
||||||
|
with:
|
||||||
|
project: 'https://github.com/irongut/CodeCoverageSummary/projects/2'
|
||||||
|
column_name: 'To do'
|
||||||
|
|
||||||
|
- name: Assign PRs to Bugs
|
||||||
|
uses: srggrs/assign-one-project-github-action@1.3.1
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'bug')
|
||||||
|
with:
|
||||||
|
project: 'https://github.com/irongut/CodeCoverageSummary/projects/1'
|
||||||
|
column_name: 'In Progress'
|
||||||
|
|
||||||
|
- name: Assign PRs to Enhancements
|
||||||
|
uses: srggrs/assign-one-project-github-action@1.3.1
|
||||||
|
if: contains(github.event.pull_request.labels.*.name, 'enhancement')
|
||||||
|
with:
|
||||||
|
project: 'https://github.com/irongut/CodeCoverageSummary/projects/2'
|
||||||
|
column_name: 'In Progress'
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
# Assign PR to Author
|
# Assign PR to Author
|
||||||
# https://github.com/samspills/assign-pr-to-author
|
# https://github.com/samspills/assign-pr-to-author
|
||||||
|
|
||||||
# Disabled due to #14 HttpError: Resource not accessible by integration:
|
name: Auto Assign PR
|
||||||
# https://github.com/samspills/assign-pr-to-author/issues/14
|
|
||||||
|
|
||||||
#name: Auto Assign PR
|
on:
|
||||||
#on: [pull_request]
|
pull_request:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
#jobs:
|
jobs:
|
||||||
# assignAuthor:
|
assignAuthor:
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# steps:
|
steps:
|
||||||
# - name: Auto Assign PR
|
- name: Auto Assign PR
|
||||||
# uses: samspills/assign-pr-to-author@v1.0
|
uses: samspills/assign-pr-to-author@v1.0.1
|
||||||
# if: github.event_name == 'pull_request' && github.event.action == 'opened'
|
with:
|
||||||
# with:
|
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||||
# repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
- name: Setup .Net
|
- name: Setup .Net
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 5.0.x
|
dotnet-version: 6.0.x
|
||||||
|
|
||||||
- name: Restore Dependencies
|
- name: Restore Dependencies
|
||||||
run: dotnet restore src/CodeCoverageSummary.sln
|
run: dotnet restore src/CodeCoverageSummary.sln
|
||||||
@@ -26,4 +26,4 @@ jobs:
|
|||||||
run: dotnet build src/CodeCoverageSummary.sln --configuration Release --no-restore
|
run: dotnet build src/CodeCoverageSummary.sln --configuration Release --no-restore
|
||||||
|
|
||||||
- name: Test with sample file
|
- name: Test with sample file
|
||||||
run: dotnet src/CodeCoverageSummary/bin/Release/net5.0/CodeCoverageSummary.dll src/coverage.cobertura.xml --badge true
|
run: dotnet src/CodeCoverageSummary/bin/Release/net6.0/CodeCoverageSummary.dll --files src/coverage.cobertura.xml --badge true
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# Applies labels to pull requests based on paths & files modified in the pull request.
|
||||||
|
#
|
||||||
|
# .github/labeler.yml contains the list of labels & files / folders to match, see:
|
||||||
|
# https://github.com/actions/labeler/blob/master/README.md
|
||||||
|
|
||||||
|
name: PR Labeler
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/labeler@v3
|
||||||
|
with:
|
||||||
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Setup .Net
|
- name: Setup .Net
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 5.0.x
|
dotnet-version: 6.0.x
|
||||||
|
|
||||||
- name: Restore Dependencies
|
- name: Restore Dependencies
|
||||||
run: dotnet restore src/CodeCoverageSummary.sln
|
run: dotnet restore src/CodeCoverageSummary.sln
|
||||||
@@ -30,29 +30,7 @@ jobs:
|
|||||||
run: dotnet build src/CodeCoverageSummary.sln --configuration Release --no-restore
|
run: dotnet build src/CodeCoverageSummary.sln --configuration Release --no-restore
|
||||||
|
|
||||||
- name: Test with sample file
|
- name: Test with sample file
|
||||||
run: dotnet src/CodeCoverageSummary/bin/Release/net5.0/CodeCoverageSummary.dll src/coverage.cobertura.xml --badge true
|
run: dotnet src/CodeCoverageSummary/bin/Release/net6.0/CodeCoverageSummary.dll --files src/coverage.cobertura.xml --badge true
|
||||||
|
|
||||||
- name: Get Previous Tag
|
|
||||||
id: get_previous_tag
|
|
||||||
run: |
|
|
||||||
PREV_TAG=$(git describe --abbrev=0 --tags "${{ github.ref }}^")
|
|
||||||
echo "::set-output name=baseRef::${PREV_TAG}"
|
|
||||||
|
|
||||||
- name: Generate Changelog
|
|
||||||
id: generate_changelog
|
|
||||||
uses: nblagoev/pull-release-notes-action@v1.0.2
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
base-ref: ${{ steps.get_previous_tag.outputs.baseRef }}
|
|
||||||
head-ref: ${{ github.ref }}
|
|
||||||
|
|
||||||
- name: Add Changelog to Release
|
|
||||||
uses: irongut/EditRelease@v1.0.0
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
id: ${{ github.event.release.id }}
|
|
||||||
body: ${{ steps.generate_changelog.outputs.result }}
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy to GHCR
|
name: Deploy to GHCR
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: Test Linux Runner
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: CI Build
|
||||||
|
steps:
|
||||||
|
- name: Test Action
|
||||||
|
uses: irongut/CodeCoverageSummary@master
|
||||||
|
with:
|
||||||
|
filename: /app/sample.coverage.xml,/app/sample.coverage.xml
|
||||||
|
badge: true
|
||||||
|
fail_below_min: true
|
||||||
|
format: markdown
|
||||||
|
hide_branch_rate: false
|
||||||
|
hide_complexity: true
|
||||||
|
indicators: true
|
||||||
|
output: both
|
||||||
|
thresholds: '60 80'
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
name: Test MacOS Runner
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
branches: [master]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: macos-latest
|
||||||
|
name: CI Build
|
||||||
|
steps:
|
||||||
|
- name: Test Action
|
||||||
|
uses: irongut/CodeCoverageSummary@master
|
||||||
|
with:
|
||||||
|
filename: /app/sample.coverage.xml,/app/sample.coverage.xml
|
||||||
|
badge: true
|
||||||
|
fail_below_min: true
|
||||||
|
format: markdown
|
||||||
|
hide_branch_rate: false
|
||||||
|
hide_complexity: true
|
||||||
|
indicators: true
|
||||||
|
output: both
|
||||||
|
thresholds: '60 80'
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
name: Test Windows Runner
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
branches: [master]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: windows-latest
|
||||||
|
name: CI Build
|
||||||
|
steps:
|
||||||
|
- name: Test Action
|
||||||
|
uses: irongut/CodeCoverageSummary@master
|
||||||
|
with:
|
||||||
|
filename: /app/sample.coverage.xml,/app/sample.coverage.xml
|
||||||
|
badge: true
|
||||||
|
fail_below_min: true
|
||||||
|
format: markdown
|
||||||
|
hide_branch_rate: false
|
||||||
|
hide_complexity: true
|
||||||
|
indicators: true
|
||||||
|
output: both
|
||||||
|
thresholds: '60 80'
|
||||||
+6
-7
@@ -1,11 +1,10 @@
|
|||||||
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
|
||||||
|
COPY ["src/coverage.cobertura.xml", "/publish/sample.coverage.xml"]
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY ["src/CodeCoverageSummary/CodeCoverageSummary.csproj", "CodeCoverageSummary/"]
|
COPY ["src/CodeCoverageSummary/CodeCoverageSummary.csproj", "CodeCoverageSummary/"]
|
||||||
RUN dotnet restore CodeCoverageSummary/CodeCoverageSummary.csproj
|
RUN dotnet restore CodeCoverageSummary/CodeCoverageSummary.csproj
|
||||||
COPY ["src/CodeCoverageSummary", "CodeCoverageSummary/"]
|
COPY ["src/CodeCoverageSummary", "CodeCoverageSummary/"]
|
||||||
COPY ["src/coverage.cobertura.xml", "sample.coverage.xml"]
|
RUN dotnet publish CodeCoverageSummary/CodeCoverageSummary.csproj --configuration Release --no-restore --output /publish
|
||||||
RUN dotnet build CodeCoverageSummary/CodeCoverageSummary.csproj --configuration Release --no-restore --output /app/build
|
|
||||||
RUN dotnet publish CodeCoverageSummary/CodeCoverageSummary.csproj --configuration Release --no-restore --output /app/publish
|
|
||||||
|
|
||||||
# Label the container
|
# Label the container
|
||||||
LABEL maintainer="Irongut <murray.dave@outlook.com>"
|
LABEL maintainer="Irongut <murray.dave@outlook.com>"
|
||||||
@@ -18,8 +17,8 @@ LABEL com.github.actions.description="A GitHub Action that reads Cobertura forma
|
|||||||
LABEL com.github.actions.icon="book-open"
|
LABEL com.github.actions.icon="book-open"
|
||||||
LABEL com.github.actions.color="purple"
|
LABEL com.github.actions.color="purple"
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/runtime:5.0 AS final
|
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS final
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=build /app/publish .
|
COPY --from=build /publish .
|
||||||
COPY --from=build /src/sample.coverage.xml .
|
ENV DOTNET_EnableDiagnostics=0
|
||||||
ENTRYPOINT ["dotnet", "/app/CodeCoverageSummary.dll"]
|
ENTRYPOINT ["dotnet", "/app/CodeCoverageSummary.dll"]
|
||||||
@@ -1,29 +1,81 @@
|
|||||||
# Code Coverage Summary
|
# Code Coverage Summary
|
||||||
|
|
||||||
A GitHub Action that reads Cobertura format code coverage files from your test suite and outputs a text or markdown summary. This summary can then be posted as a Pull Request comment, included in Release Notes, etc by another action to give you an immediate insight into the health of your code without using a third-party site.
|
<div align="center">
|
||||||
|
|
||||||
|
[](https://github.com/irongut/CodeCoverageSummary/actions/workflows/ci-build.yml)
|
||||||
|
|
||||||
|
[](https://github.com/irongut/CodeCoverageSummary)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
A GitHub Action that reads Cobertura format code coverage files from your test suite and outputs a text or markdown summary. This summary can be posted as a Pull Request comment or included in Release Notes by other actions to give you an immediate insight into the health of your code without using a third-party site.
|
||||||
|
|
||||||
|
Code Coverage Summary is designed for use with [Coverlet](https://github.com/coverlet-coverage/coverlet) and [gcovr](https://github.com/gcovr/gcovr) but it should work with any test framework that outputs coverage in Cobertura format. If it doesn't work with your tooling please [open an issue][new-issue] to discuss the problem.
|
||||||
|
|
||||||
|
As a Docker based action Code Coverage Summary requires a Linux runner, see [Types of Action](https://docs.github.com/en/actions/creating-actions/about-custom-actions#types-of-actions). If you need to build with a Windows or MacOS runner a workaround would be to upload the coverage file as an artifact and use a separate job with a Linux runner to generate the summary.
|
||||||
|
|
||||||
Code Coverage Summary was designed for use with [Coverlet](https://github.com/coverlet-coverage/coverlet) and .Net but it should work with any test framework that outputs coverage in Cobertura format.
|
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
|
|
||||||
#### `filename`
|
### `filename`
|
||||||
**Required**
|
**Required**
|
||||||
|
|
||||||
Code coverage file to analyse.
|
A comma separated list of code coverage files to analyse. If there are any spaces in a path or filename this value must be in quotes.
|
||||||
|
|
||||||
Note: Coverlet creates the coverage file in a random named directory (guid) so you need to copy it to a predictable path before running this Action, see the [.Net 5 Workflow Example](#net-5-workflow-example) below.
|
Note: Coverlet creates the coverage file in a random named directory (guid) so you need to copy it to a predictable path before running this Action, see the [.Net Workflow Example](#net-workflow-example) below.
|
||||||
|
|
||||||
#### `badge`
|
|
||||||
|
### `badge`
|
||||||
|
|
||||||
Include a badge reporting the Line Rate coverage in the output using [shields.io](https://shields.io/) - `true` or `false` (default).
|
Include a badge reporting the Line Rate coverage in the output using [shields.io](https://shields.io/) - `true` or `false` (default).
|
||||||
|
|
||||||
If the overall Line Rate is less than 50% the badge will be red, if it is 50% - 74% it will be yellow and if it is 75% or over it will be green.
|
Line Rate | Badge
|
||||||
|
--------- | -----
|
||||||
|
less than lower threshold (50%) | 
|
||||||
|
between thresholds (50% - 74%) | 
|
||||||
|
equal or greater than upper threshold (75%) | 
|
||||||
|
|
||||||
#### `format`
|
See [`thresholds`](#thresholds) to change these values.
|
||||||
|
|
||||||
|
|
||||||
|
### `fail_below_min`
|
||||||
|
|
||||||
|
Fail the workflow if the overall Line Rate is below lower threshold - `true` or `false` (default). The default lower threshold is 50%, see [`thresholds`](#thresholds).
|
||||||
|
|
||||||
|
|
||||||
|
### `format`
|
||||||
|
|
||||||
Output Format - `markdown` or `text` (default).
|
Output Format - `markdown` or `text` (default).
|
||||||
|
|
||||||
#### `output`
|
|
||||||
|
### `hide_branch_rate`
|
||||||
|
|
||||||
|
Hide Branch Rate values in the output - `true` or `false` (default).
|
||||||
|
|
||||||
|
|
||||||
|
### `hide_complexity`
|
||||||
|
|
||||||
|
Hide Complexity values in the output - `true` or `false` (default).
|
||||||
|
|
||||||
|
|
||||||
|
### `indicators`
|
||||||
|
|
||||||
|
Include health indicators in the output - `true` (default) or `false`.
|
||||||
|
|
||||||
|
Line Rate | Indicator
|
||||||
|
--------- | ---------
|
||||||
|
less than lower threshold (50%) | ❌
|
||||||
|
between thresholds (50% - 74%) | ➖
|
||||||
|
equal or greater than upper threshold (75%) | ✔
|
||||||
|
|
||||||
|
See [`thresholds`](#thresholds) to change these values.
|
||||||
|
|
||||||
|
|
||||||
|
### `output`
|
||||||
|
|
||||||
Output Type - `console` (default), `file` or `both`.
|
Output Type - `console` (default), `file` or `both`.
|
||||||
|
|
||||||
@@ -33,34 +85,52 @@ Output Type - `console` (default), `file` or `both`.
|
|||||||
|
|
||||||
`both` will output the coverage summary to the Action log and a file as above.
|
`both` will output the coverage summary to the Action log and a file as above.
|
||||||
|
|
||||||
|
|
||||||
|
### `thresholds`
|
||||||
|
|
||||||
|
Lower and upper threshold percentages for badge and health indicators, lower threshold can also be used to fail the action. Separate the values with a space and enclose them in quotes; default `'50 75'`.
|
||||||
|
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
#### Text Example
|
### Text Example
|
||||||
```
|
```
|
||||||
https://img.shields.io/badge/Code%20Coverage-77%25-success?style=flat
|
https://img.shields.io/badge/Code%20Coverage-83%25-success?style=flat
|
||||||
Line Rate = 77%, Lines Covered = 1107 / 1433
|
|
||||||
Branch Rate = 60%, Branches Covered = 321 / 532
|
Company.Example: Line Rate = 83%, Branch Rate = 69%, Complexity = 671, ✔
|
||||||
Complexity = 917
|
Company.Example.Library: Line Rate = 27%, Branch Rate = 100%, Complexity = 11, ❌
|
||||||
Company.Example: Line Rate = 78%, Branch Rate = 60%, Complexity = 906
|
Summary: Line Rate = 83% (1212 / 1460), Branch Rate = 69% (262 / 378), Complexity = 682, ✔
|
||||||
Company.Example.Library: Line Rate = 27%, Branch Rate = 100%, Complexity = 11
|
Minimum allowed line rate is 50%
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Markdown Example
|
|
||||||

|
### Markdown Example
|
||||||
|
|
||||||
|
> 
|
||||||
|
>
|
||||||
|
> Package | Line Rate | Branch Rate | Complexity | Health
|
||||||
|
> -------- | --------- | ----------- | ---------- | ------
|
||||||
|
> Company.Example | 83% | 69% | 671 | ✔
|
||||||
|
> Company.Example.Library | 27% | 100% | 11 | ❌
|
||||||
|
> **Summary** | **83%** (1212 / 1460) | **69%** (262 / 378) | 682 | ✔
|
||||||
|
>
|
||||||
|
> _Minimum allowed line rate is `50%`_
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: Code Coverage Summary Report
|
name: Code Coverage Summary Report
|
||||||
uses: irongut/CodeCoverageSummary@v1.0.3
|
uses: irongut/CodeCoverageSummary@v1.2.0
|
||||||
with:
|
with:
|
||||||
filename: coverage/coverage.cobertura.xml
|
filename: coverage.cobertura.xml
|
||||||
```
|
```
|
||||||
|
|
||||||
### .Net 5 Workflow Example
|
|
||||||
|
### .Net Workflow Example
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: .Net 5 CI Build
|
name: .Net 6 CI Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@@ -79,7 +149,7 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v1
|
||||||
with:
|
with:
|
||||||
dotnet-version: 5.0.x
|
dotnet-version: 6.0.x
|
||||||
|
|
||||||
- name: Restore Dependencies
|
- name: Restore Dependencies
|
||||||
run: dotnet restore src/Example.sln
|
run: dotnet restore src/Example.sln
|
||||||
@@ -91,15 +161,20 @@ jobs:
|
|||||||
run: dotnet test src/Example.sln --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage
|
run: dotnet test src/Example.sln --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage
|
||||||
|
|
||||||
- name: Copy Coverage To Predictable Location
|
- name: Copy Coverage To Predictable Location
|
||||||
run: cp coverage/**/coverage.cobertura.xml coverage/coverage.cobertura.xml
|
run: cp coverage/**/coverage.cobertura.xml coverage.cobertura.xml
|
||||||
|
|
||||||
- name: Code Coverage Summary Report
|
- name: Code Coverage Summary Report
|
||||||
uses: irongut/CodeCoverageSummary@v1.0.3
|
uses: irongut/CodeCoverageSummary@v1.2.0
|
||||||
with:
|
with:
|
||||||
filename: coverage/coverage.cobertura.xml
|
filename: coverage.cobertura.xml
|
||||||
badge: true
|
badge: true
|
||||||
format: 'markdown'
|
fail_below_min: true
|
||||||
output: 'both'
|
format: markdown
|
||||||
|
hide_branch_rate: false
|
||||||
|
hide_complexity: true
|
||||||
|
indicators: true
|
||||||
|
output: both
|
||||||
|
thresholds: '60 80'
|
||||||
|
|
||||||
- name: Add Coverage PR Comment
|
- name: Add Coverage PR Comment
|
||||||
uses: marocchino/sticky-pull-request-comment@v2
|
uses: marocchino/sticky-pull-request-comment@v2
|
||||||
@@ -109,6 +184,7 @@ jobs:
|
|||||||
path: code-coverage-results.md
|
path: code-coverage-results.md
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
### Report Bugs
|
### Report Bugs
|
||||||
@@ -117,10 +193,12 @@ Please make sure the bug is not already reported by searching existing [issues].
|
|||||||
|
|
||||||
If you're unable to find an existing issue addressing the problem please [open a new one][new-issue]. Be sure to include a title and clear description, as much relevant information as possible, a workflow sample and any logs demonstrating the problem.
|
If you're unable to find an existing issue addressing the problem please [open a new one][new-issue]. Be sure to include a title and clear description, as much relevant information as possible, a workflow sample and any logs demonstrating the problem.
|
||||||
|
|
||||||
|
|
||||||
### Suggest an Enhancement
|
### Suggest an Enhancement
|
||||||
|
|
||||||
Please [open a new issue][new-issue].
|
Please [open a new issue][new-issue].
|
||||||
|
|
||||||
|
|
||||||
### Submit a Pull Request
|
### Submit a Pull Request
|
||||||
|
|
||||||
Discuss your idea first, so that your changes have a good chance of being merged in.
|
Discuss your idea first, so that your changes have a good chance of being merged in.
|
||||||
@@ -129,6 +207,7 @@ Submit your pull request against the `master` branch.
|
|||||||
|
|
||||||
Pull requests that include documentation and relevant updates to README.md are merged faster, because you won't have to wait for somebody else to complete your contribution.
|
Pull requests that include documentation and relevant updates to README.md are merged faster, because you won't have to wait for somebody else to complete your contribution.
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Code Coverage Summary is available under the MIT license, see the [LICENSE](LICENSE) file for more info.
|
Code Coverage Summary is available under the MIT license, see the [LICENSE](LICENSE) file for more info.
|
||||||
|
|||||||
+34
-3
@@ -6,28 +6,59 @@ branding:
|
|||||||
color: purple
|
color: purple
|
||||||
inputs:
|
inputs:
|
||||||
filename:
|
filename:
|
||||||
description: 'Code coverage file to analyse.'
|
description: 'A comma separated list of code coverage files to analyse.'
|
||||||
required: true
|
required: true
|
||||||
badge:
|
badge:
|
||||||
description: 'Include a badge in the output - true / false (default).'
|
description: 'Include a Line Rate coverage badge in the output using shields.io - true / false (default).'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
fail_below_min:
|
||||||
|
description: 'Fail if overall Line Rate below lower threshold - true / false (default).'
|
||||||
required: false
|
required: false
|
||||||
default: 'false'
|
default: 'false'
|
||||||
format:
|
format:
|
||||||
description: 'Output Format - markdown or text (default).'
|
description: 'Output Format - markdown or text (default).'
|
||||||
required: false
|
required: false
|
||||||
default: 'text'
|
default: 'text'
|
||||||
|
hide_branch_rate:
|
||||||
|
description: 'Hide Branch Rate values in the output - true / false (default).'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
hide_complexity:
|
||||||
|
description: 'Hide Complexity values in the output - true / false (default).'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
indicators:
|
||||||
|
description: 'Include health indicators in the output - true (default) / false.'
|
||||||
|
required: false
|
||||||
|
default: 'true'
|
||||||
output:
|
output:
|
||||||
description: 'Output Type - console (default), file or both.'
|
description: 'Output Type - console (default), file or both.'
|
||||||
required: false
|
required: false
|
||||||
default: 'console'
|
default: 'console'
|
||||||
|
thresholds:
|
||||||
|
description: 'Threshold percentages for badge and health indicators, lower threshold can also be used to fail the action.'
|
||||||
|
required: false
|
||||||
|
default: '50 75'
|
||||||
runs:
|
runs:
|
||||||
using: 'docker'
|
using: 'docker'
|
||||||
image: 'docker://ghcr.io/irongut/codecoveragesummary:v1.0.2'
|
image: 'docker://ghcr.io/irongut/codecoveragesummary:v1.2.0'
|
||||||
args:
|
args:
|
||||||
|
- '--files'
|
||||||
- ${{ inputs.filename }}
|
- ${{ inputs.filename }}
|
||||||
- '--badge'
|
- '--badge'
|
||||||
- ${{ inputs.badge }}
|
- ${{ inputs.badge }}
|
||||||
|
- '--fail'
|
||||||
|
- ${{ inputs.fail_below_min }}
|
||||||
- '--format'
|
- '--format'
|
||||||
- ${{ inputs.format }}
|
- ${{ inputs.format }}
|
||||||
|
- '--hidebranch'
|
||||||
|
- ${{ inputs.hide_branch_rate }}
|
||||||
|
- '--hidecomplexity'
|
||||||
|
- ${{ inputs.hide_complexity }}
|
||||||
|
- '--indicators'
|
||||||
|
- ${{ inputs.indicators }}
|
||||||
- '--output'
|
- '--output'
|
||||||
- ${{ inputs.output }}
|
- ${{ inputs.output }}
|
||||||
|
- '--thresholds'
|
||||||
|
- ${{ inputs.thresholds }}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net5.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
<Company>Taranis Software</Company>
|
<Company>Taranis Software</Company>
|
||||||
<Authors>Irongut</Authors>
|
<Authors>Irongut</Authors>
|
||||||
@@ -14,12 +14,12 @@
|
|||||||
<RepositoryUrl>https://github.com/irongut/CodeCoverageSummary</RepositoryUrl>
|
<RepositoryUrl>https://github.com/irongut/CodeCoverageSummary</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<PackageTags>coverage test-coverage cobertura action code-coverage coverlet github-actions</PackageTags>
|
<PackageTags>coverage test-coverage cobertura action code-coverage coverlet github-actions</PackageTags>
|
||||||
<Version>1.0.3</Version>
|
<Version>1.2.0</Version>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
<PackageReference Include="CommandLineParser" Version="2.8.0" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.11.1" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.14.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace CodeCoverageSummary
|
|||||||
|
|
||||||
public double BranchRate { get; set; }
|
public double BranchRate { get; set; }
|
||||||
|
|
||||||
public int Complexity { get; set; }
|
public double Complexity { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CodeSummary
|
public class CodeSummary
|
||||||
@@ -27,13 +27,10 @@ namespace CodeCoverageSummary
|
|||||||
|
|
||||||
public int BranchesValid { get; set; }
|
public int BranchesValid { get; set; }
|
||||||
|
|
||||||
public int Complexity { get; set; }
|
public double Complexity { get; set; }
|
||||||
|
|
||||||
public List<CodeCoverage> Packages { get; set; }
|
public List<CodeCoverage> Packages { get; set; }
|
||||||
|
|
||||||
public CodeSummary()
|
public CodeSummary() => Packages = new();
|
||||||
{
|
|
||||||
Packages = new List<CodeCoverage>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,46 @@
|
|||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace CodeCoverageSummary
|
namespace CodeCoverageSummary
|
||||||
{
|
{
|
||||||
public class CommandLineOptions
|
public class CommandLineOptions
|
||||||
{
|
{
|
||||||
[Value(index: 0, Required = true, HelpText = "Code coverage file to analyse.")]
|
[Option(longName: "files", Separator = ',', Required = true, HelpText = "A comma separated list of code coverage files to analyse.")]
|
||||||
public string Filename { get; set; }
|
public IEnumerable<string> Files { get; set; }
|
||||||
|
|
||||||
[Option(longName: "badge", Required = false, HelpText = "Include a badge reporting the Line Rate coverage in the output using shields.io - true or false.", Default = false)]
|
[Option(longName: "badge", Required = false, HelpText = "Include a Line Rate coverage badge in the output using shields.io - true or false.", Default = "false")]
|
||||||
public bool Badge { get; set; }
|
public string BadgeString { get; set; }
|
||||||
|
|
||||||
|
public bool Badge => BadgeString.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
[Option(longName: "fail", Required = false, HelpText = "Fail if overall Line Rate below lower threshold - true or false.", Default = "false")]
|
||||||
|
public string FailString { get; set; }
|
||||||
|
|
||||||
|
public bool FailBelowThreshold => FailString.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
[Option(longName: "format", Required = false, HelpText = "Output Format - markdown or text.", Default = "text")]
|
[Option(longName: "format", Required = false, HelpText = "Output Format - markdown or text.", Default = "text")]
|
||||||
public string Format { get; set; }
|
public string Format { get; set; }
|
||||||
|
|
||||||
|
[Option(longName: "hidebranch", Required = false, HelpText = "Hide Branch Rate values in the output - true or false.", Default = "false")]
|
||||||
|
public string HideBranchString { get; set; }
|
||||||
|
|
||||||
|
public bool HideBranchRate => HideBranchString.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
[Option(longName: "hidecomplexity", Required = false, HelpText = "Hide Complexity values in the output - true or false.", Default = "false")]
|
||||||
|
public string HideComplexityString { get; set; }
|
||||||
|
|
||||||
|
public bool HideComplexity => HideComplexityString.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
[Option(longName: "indicators", Required = false, HelpText = "Include health indicators in the output - true or false.", Default = "true")]
|
||||||
|
public string IndicatorsString { get; set; }
|
||||||
|
|
||||||
|
public bool Indicators => IndicatorsString.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
[Option(longName: "output", Required = false, HelpText = "Output Type - console, file or both.", Default = "console")]
|
[Option(longName: "output", Required = false, HelpText = "Output Type - console, file or both.", Default = "console")]
|
||||||
public string Output { get; set; }
|
public string Output { get; set; }
|
||||||
|
|
||||||
|
[Option(longName: "thresholds", Required = false, HelpText = "Threshold percentages for badge and health indicators, lower threshold can also be used to fail the action.", Default = "50 75")]
|
||||||
|
public string Thresholds { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,3 +6,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
[assembly: SuppressMessage("Performance", "RCS1197:Optimize StringBuilder.Append/AppendLine call.", Scope = "type", Target = "~T:CodeCoverageSummary.Program")]
|
[assembly: SuppressMessage("Performance", "RCS1197:Optimize StringBuilder.Append/AppendLine call.", Scope = "type", Target = "~T:CodeCoverageSummary.Program")]
|
||||||
|
[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Scope = "member", Target = "~M:CodeCoverageSummary.Program.SetThresholds(System.String)")]
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ namespace CodeCoverageSummary
|
|||||||
{
|
{
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
|
private static double lowerThreshold = 0.5;
|
||||||
|
private static double upperThreshold = 0.75;
|
||||||
|
|
||||||
private static int Main(string[] args)
|
private static int Main(string[] args)
|
||||||
{
|
{
|
||||||
return Parser.Default.ParseArguments<CommandLineOptions>(args)
|
return Parser.Default.ParseArguments<CommandLineOptions>(args)
|
||||||
@@ -16,22 +19,37 @@ namespace CodeCoverageSummary
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!File.Exists(o.Filename))
|
// check files exist
|
||||||
|
foreach (var file in o.Files)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error: Code coverage file not found.");
|
if (!File.Exists(file))
|
||||||
return -2; // error
|
{
|
||||||
|
Console.WriteLine($"Error: Code coverage file not found - {file}.");
|
||||||
|
return -2; // error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse code coverage file
|
// parse code coverage file
|
||||||
Console.WriteLine($"Code Coverage File: {o.Filename}");
|
CodeSummary summary = new();
|
||||||
CodeSummary summary = ParseTestResults(o.Filename);
|
foreach (var file in o.Files)
|
||||||
if (summary == null)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine("Error: Parsing code coverage file.");
|
Console.WriteLine($"Code Coverage File: {file}");
|
||||||
|
summary = ParseTestResults(file, summary);
|
||||||
|
}
|
||||||
|
summary.LineRate /= o.Files.Count();
|
||||||
|
summary.BranchRate /= o.Files.Count();
|
||||||
|
|
||||||
|
if (summary.Packages.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Error: Parsing code coverage file, no packages found.");
|
||||||
return -2; // error
|
return -2; // error
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// set health badge thresholds
|
||||||
|
if (!string.IsNullOrWhiteSpace(o.Thresholds))
|
||||||
|
SetThresholds(o.Thresholds);
|
||||||
|
|
||||||
// generate badge
|
// generate badge
|
||||||
string badgeUrl = o.Badge ? GenerateBadge(summary) : null;
|
string badgeUrl = o.Badge ? GenerateBadge(summary) : null;
|
||||||
|
|
||||||
@@ -41,12 +59,16 @@ namespace CodeCoverageSummary
|
|||||||
if (o.Format.Equals("text", StringComparison.OrdinalIgnoreCase))
|
if (o.Format.Equals("text", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
fileExt = "txt";
|
fileExt = "txt";
|
||||||
output = GenerateTextOutput(summary, badgeUrl);
|
output = GenerateTextOutput(summary, badgeUrl, o.Indicators, o.HideBranchRate, o.HideComplexity);
|
||||||
|
if (o.FailBelowThreshold)
|
||||||
|
output += $"Minimum allowed line rate is {lowerThreshold * 100:N0}%{Environment.NewLine}";
|
||||||
}
|
}
|
||||||
else if (o.Format.Equals("md", StringComparison.OrdinalIgnoreCase) || o.Format.Equals("markdown", StringComparison.OrdinalIgnoreCase))
|
else if (o.Format.Equals("md", StringComparison.OrdinalIgnoreCase) || o.Format.Equals("markdown", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
fileExt = "md";
|
fileExt = "md";
|
||||||
output = GenerateMarkdownOutput(summary, badgeUrl);
|
output = GenerateMarkdownOutput(summary, badgeUrl, o.Indicators, o.HideBranchRate, o.HideComplexity);
|
||||||
|
if (o.FailBelowThreshold)
|
||||||
|
output += $"{Environment.NewLine}_Minimum allowed line rate is `{lowerThreshold * 100:N0}%`_{Environment.NewLine}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -74,6 +96,12 @@ namespace CodeCoverageSummary
|
|||||||
return -2; // error
|
return -2; // error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (o.FailBelowThreshold && summary.LineRate < lowerThreshold)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"FAIL: Overall line rate below minimum threshold of {lowerThreshold * 100:N0}%.");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
return 0; // success
|
return 0; // success
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,9 +114,8 @@ namespace CodeCoverageSummary
|
|||||||
_ => -1); // invalid arguments
|
_ => -1); // invalid arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CodeSummary ParseTestResults(string filename)
|
private static CodeSummary ParseTestResults(string filename, CodeSummary summary)
|
||||||
{
|
{
|
||||||
CodeSummary summary = new();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string rss = File.ReadAllText(filename);
|
string rss = File.ReadAllText(filename);
|
||||||
@@ -101,50 +128,50 @@ namespace CodeCoverageSummary
|
|||||||
var lineR = from item in coverage.Attributes()
|
var lineR = from item in coverage.Attributes()
|
||||||
where item.Name == "line-rate"
|
where item.Name == "line-rate"
|
||||||
select item;
|
select item;
|
||||||
summary.LineRate = double.Parse(lineR.First().Value);
|
summary.LineRate += double.Parse(lineR.First().Value);
|
||||||
|
|
||||||
var linesCovered = from item in coverage.Attributes()
|
var linesCovered = from item in coverage.Attributes()
|
||||||
where item.Name == "lines-covered"
|
where item.Name == "lines-covered"
|
||||||
select item;
|
select item;
|
||||||
summary.LinesCovered = int.Parse(linesCovered.First().Value);
|
summary.LinesCovered += int.Parse(linesCovered.First().Value);
|
||||||
|
|
||||||
var linesValid = from item in coverage.Attributes()
|
var linesValid = from item in coverage.Attributes()
|
||||||
where item.Name == "lines-valid"
|
where item.Name == "lines-valid"
|
||||||
select item;
|
select item;
|
||||||
summary.LinesValid = int.Parse(linesValid.First().Value);
|
summary.LinesValid += int.Parse(linesValid.First().Value);
|
||||||
|
|
||||||
var branchR = from item in coverage.Attributes()
|
var branchR = from item in coverage.Attributes()
|
||||||
where item.Name == "branch-rate"
|
where item.Name == "branch-rate"
|
||||||
select item;
|
select item;
|
||||||
summary.BranchRate = double.Parse(branchR.First().Value);
|
summary.BranchRate += double.Parse(branchR.First().Value);
|
||||||
|
|
||||||
var branchesCovered = from item in coverage.Attributes()
|
var branchesCovered = from item in coverage.Attributes()
|
||||||
where item.Name == "branches-covered"
|
where item.Name == "branches-covered"
|
||||||
select item;
|
select item;
|
||||||
summary.BranchesCovered = int.Parse(branchesCovered.First().Value);
|
summary.BranchesCovered += int.Parse(branchesCovered.First().Value);
|
||||||
|
|
||||||
var branchesValid = from item in coverage.Attributes()
|
var branchesValid = from item in coverage.Attributes()
|
||||||
where item.Name == "branches-valid"
|
where item.Name == "branches-valid"
|
||||||
select item;
|
select item;
|
||||||
summary.BranchesValid = int.Parse(branchesValid.First().Value);
|
summary.BranchesValid += int.Parse(branchesValid.First().Value);
|
||||||
|
|
||||||
summary.Complexity = 0;
|
|
||||||
|
|
||||||
// test coverage for individual packages
|
// test coverage for individual packages
|
||||||
var packages = from item in coverage.Descendants("package")
|
var packages = from item in coverage.Descendants("package")
|
||||||
select item;
|
select item;
|
||||||
|
|
||||||
|
int i = 1;
|
||||||
foreach (var item in packages)
|
foreach (var item in packages)
|
||||||
{
|
{
|
||||||
CodeCoverage packageCoverage = new()
|
CodeCoverage packageCoverage = new()
|
||||||
{
|
{
|
||||||
Name = item.Attribute("name").Value,
|
Name = string.IsNullOrWhiteSpace(item.Attribute("name").Value) ? $"Package {i}" : item.Attribute("name").Value,
|
||||||
LineRate = double.Parse(item.Attribute("line-rate").Value),
|
LineRate = double.Parse(item.Attribute("line-rate")?.Value ?? "0"),
|
||||||
BranchRate = double.Parse(item.Attribute("branch-rate").Value),
|
BranchRate = double.Parse(item.Attribute("branch-rate")?.Value ?? "0"),
|
||||||
Complexity = int.Parse(item.Attribute("complexity")?.Value ?? "0")
|
Complexity = double.Parse(item.Attribute("complexity")?.Value ?? "0")
|
||||||
};
|
};
|
||||||
summary.Packages.Add(packageCoverage);
|
summary.Packages.Add(packageCoverage);
|
||||||
summary.Complexity += packageCoverage.Complexity;
|
summary.Complexity += packageCoverage.Complexity;
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
@@ -156,14 +183,49 @@ namespace CodeCoverageSummary
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SetThresholds(string thresholds)
|
||||||
|
{
|
||||||
|
int lowerPercentage;
|
||||||
|
int upperPercentage = (int)(upperThreshold * 100);
|
||||||
|
int s = thresholds.IndexOf(" ");
|
||||||
|
if (s == 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Threshold parameter set incorrectly.");
|
||||||
|
}
|
||||||
|
else if (s < 0)
|
||||||
|
{
|
||||||
|
if (!int.TryParse(thresholds, out lowerPercentage))
|
||||||
|
throw new ArgumentException("Threshold parameter set incorrectly.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!int.TryParse(thresholds.Substring(0, s), out lowerPercentage))
|
||||||
|
throw new ArgumentException("Threshold parameter set incorrectly.");
|
||||||
|
|
||||||
|
if (!int.TryParse(thresholds.Substring(s + 1), out upperPercentage))
|
||||||
|
throw new ArgumentException("Threshold parameter set incorrectly.");
|
||||||
|
}
|
||||||
|
lowerThreshold = lowerPercentage / 100.0;
|
||||||
|
upperThreshold = upperPercentage / 100.0;
|
||||||
|
|
||||||
|
if (lowerThreshold > 1.0)
|
||||||
|
lowerThreshold = 1.0;
|
||||||
|
|
||||||
|
if (lowerThreshold > upperThreshold)
|
||||||
|
upperThreshold = lowerThreshold + 0.1;
|
||||||
|
|
||||||
|
if (upperThreshold > 1.0)
|
||||||
|
upperThreshold = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
private static string GenerateBadge(CodeSummary summary)
|
private static string GenerateBadge(CodeSummary summary)
|
||||||
{
|
{
|
||||||
string colour;
|
string colour;
|
||||||
if (summary.LineRate < 0.5)
|
if (summary.LineRate < lowerThreshold)
|
||||||
{
|
{
|
||||||
colour = "critical";
|
colour = "critical";
|
||||||
}
|
}
|
||||||
else if (summary.LineRate < 0.75)
|
else if (summary.LineRate < upperThreshold)
|
||||||
{
|
{
|
||||||
colour = "yellow";
|
colour = "yellow";
|
||||||
}
|
}
|
||||||
@@ -174,47 +236,79 @@ namespace CodeCoverageSummary
|
|||||||
return $"https://img.shields.io/badge/Code%20Coverage-{summary.LineRate * 100:N0}%25-{colour}?style=flat";
|
return $"https://img.shields.io/badge/Code%20Coverage-{summary.LineRate * 100:N0}%25-{colour}?style=flat";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateTextOutput(CodeSummary summary, string badgeUrl)
|
private static string GenerateHealthIndicator(double rate)
|
||||||
|
{
|
||||||
|
if (rate < lowerThreshold)
|
||||||
|
{
|
||||||
|
return "❌";
|
||||||
|
}
|
||||||
|
else if (rate < upperThreshold)
|
||||||
|
{
|
||||||
|
return "➖";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "✔";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateTextOutput(CodeSummary summary, string badgeUrl, bool indicators, bool hideBranchRate, bool hideComplexity)
|
||||||
{
|
{
|
||||||
StringBuilder textOutput = new();
|
StringBuilder textOutput = new();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(badgeUrl))
|
if (!string.IsNullOrWhiteSpace(badgeUrl))
|
||||||
{
|
{
|
||||||
textOutput.AppendLine(badgeUrl);
|
textOutput.AppendLine(badgeUrl)
|
||||||
|
.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
textOutput.AppendLine($"Line Rate = {summary.LineRate * 100:N0}%, Lines Covered = {summary.LinesCovered} / {summary.LinesValid}")
|
|
||||||
.AppendLine($"Branch Rate = {summary.BranchRate * 100:N0}%, Branches Covered = {summary.BranchesCovered} / {summary.BranchesValid}")
|
|
||||||
.AppendLine($"Complexity = {summary.Complexity}");
|
|
||||||
|
|
||||||
foreach (CodeCoverage package in summary.Packages)
|
foreach (CodeCoverage package in summary.Packages)
|
||||||
{
|
{
|
||||||
textOutput.AppendLine($"{package.Name}: Line Rate = {package.LineRate * 100:N0}%, Branch Rate = {package.BranchRate * 100:N0}%, Complexity = {package.Complexity}");
|
textOutput.Append($"{package.Name}: Line Rate = {package.LineRate * 100:N0}%")
|
||||||
|
.Append(hideBranchRate ? string.Empty : $", Branch Rate = {package.BranchRate * 100:N0}%")
|
||||||
|
.Append(hideComplexity ? string.Empty : (package.Complexity % 1 == 0) ? $", Complexity = {package.Complexity}" : $", Complexity = {package.Complexity:N4}")
|
||||||
|
.AppendLine(indicators ? $", {GenerateHealthIndicator(package.LineRate)}" : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textOutput.Append($"Summary: Line Rate = {summary.LineRate * 100:N0}% ({summary.LinesCovered} / {summary.LinesValid})")
|
||||||
|
.Append(hideBranchRate ? string.Empty : $", Branch Rate = {summary.BranchRate * 100:N0}% ({summary.BranchesCovered} / {summary.BranchesValid})")
|
||||||
|
.Append(hideComplexity ? string.Empty : (summary.Complexity % 1 == 0) ? $", Complexity = {summary.Complexity}" : $", Complexity = {summary.Complexity:N4}")
|
||||||
|
.AppendLine(indicators ? $", {GenerateHealthIndicator(summary.LineRate)}" : string.Empty);
|
||||||
|
|
||||||
return textOutput.ToString();
|
return textOutput.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateMarkdownOutput(CodeSummary summary, string badgeUrl)
|
private static string GenerateMarkdownOutput(CodeSummary summary, string badgeUrl, bool indicators, bool hideBranchRate, bool hideComplexity)
|
||||||
{
|
{
|
||||||
StringBuilder markdownOutput = new();
|
StringBuilder markdownOutput = new();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(badgeUrl))
|
if (!string.IsNullOrWhiteSpace(badgeUrl))
|
||||||
{
|
{
|
||||||
markdownOutput.AppendLine($"");
|
markdownOutput.AppendLine($"")
|
||||||
markdownOutput.AppendLine("");
|
.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
markdownOutput.AppendLine("Package | Line Rate | Branch Rate | Complexity")
|
markdownOutput.Append("Package | Line Rate")
|
||||||
.AppendLine("-------- | --------- | ----------- | ----------");
|
.Append(hideBranchRate ? string.Empty : " | Branch Rate")
|
||||||
|
.Append(hideComplexity ? string.Empty : " | Complexity")
|
||||||
|
.AppendLine(indicators ? " | Health" : string.Empty)
|
||||||
|
.Append("-------- | ---------")
|
||||||
|
.Append(hideBranchRate ? string.Empty : " | -----------")
|
||||||
|
.Append(hideComplexity ? string.Empty : " | ----------")
|
||||||
|
.AppendLine(indicators ? " | ------" : string.Empty);
|
||||||
|
|
||||||
foreach (CodeCoverage package in summary.Packages)
|
foreach (CodeCoverage package in summary.Packages)
|
||||||
{
|
{
|
||||||
markdownOutput.AppendLine($"{package.Name} | {package.LineRate * 100:N0}% | {package.BranchRate * 100:N0}% | {package.Complexity}");
|
markdownOutput.Append($"{package.Name} | {package.LineRate * 100:N0}%")
|
||||||
|
.Append(hideBranchRate ? string.Empty : $" | {package.BranchRate * 100:N0}%")
|
||||||
|
.Append(hideComplexity ? string.Empty : (package.Complexity % 1 == 0) ? $" | {package.Complexity}" : $" | {package.Complexity:N4}" )
|
||||||
|
.AppendLine(indicators ? $" | {GenerateHealthIndicator(package.LineRate)}" : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
markdownOutput.Append($"**Summary** | **{summary.LineRate * 100:N0}%** ({summary.LinesCovered} / {summary.LinesValid}) | ")
|
markdownOutput.Append($"**Summary** | **{summary.LineRate * 100:N0}%** ({summary.LinesCovered} / {summary.LinesValid})")
|
||||||
.AppendLine($"**{summary.BranchRate * 100:N0}%** ({summary.BranchesCovered} / {summary.BranchesValid}) | {summary.Complexity}");
|
.Append(hideBranchRate ? string.Empty : $" | **{summary.BranchRate * 100:N0}%** ({summary.BranchesCovered} / {summary.BranchesValid})")
|
||||||
|
.Append(hideComplexity ? string.Empty : (summary.Complexity % 1 == 0) ? $" | **{summary.Complexity}**" : $" | **{summary.Complexity:N4}**")
|
||||||
|
.AppendLine(indicators ? $" | {GenerateHealthIndicator(summary.LineRate)}" : string.Empty);
|
||||||
|
|
||||||
return markdownOutput.ToString();
|
return markdownOutput.ToString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"CodeCoverageSummary": {
|
"CodeCoverageSummary": {
|
||||||
"commandName": "Project"
|
"commandName": "Project",
|
||||||
|
"commandLineArgs": "--files ../../../../coverage.cobertura.xml,../../../../coverage.cobertura.xml --format=text --badge true --thresholds=\"85 90\" --fail true"
|
||||||
},
|
},
|
||||||
"Docker": {
|
"Docker": {
|
||||||
"commandName": "Docker",
|
"commandName": "Docker",
|
||||||
"commandLineArgs": "/src/coverage.cobertura.xml --format=md --badge=true"
|
"commandLineArgs": "--files /app/sample.coverage.xml --format=text --badge=true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<!-- Portions (C) International Organization for Standardization 1986:
|
||||||
|
Permission to copy in any form is granted for use with
|
||||||
|
conforming SGML systems and applications as defined in
|
||||||
|
ISO 8879, provided this notice is included in all copies.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!ELEMENT coverage (sources?,packages)>
|
||||||
|
<!ATTLIST coverage line-rate CDATA #REQUIRED>
|
||||||
|
<!ATTLIST coverage branch-rate CDATA #REQUIRED>
|
||||||
|
<!ATTLIST coverage lines-covered CDATA #REQUIRED>
|
||||||
|
<!ATTLIST coverage lines-valid CDATA #REQUIRED>
|
||||||
|
<!ATTLIST coverage branches-covered CDATA #REQUIRED>
|
||||||
|
<!ATTLIST coverage branches-valid CDATA #REQUIRED>
|
||||||
|
<!ATTLIST coverage complexity CDATA #REQUIRED>
|
||||||
|
<!ATTLIST coverage version CDATA #REQUIRED>
|
||||||
|
<!ATTLIST coverage timestamp CDATA #REQUIRED>
|
||||||
|
|
||||||
|
<!ELEMENT sources (source*)>
|
||||||
|
|
||||||
|
<!ELEMENT source (#PCDATA)>
|
||||||
|
|
||||||
|
<!ELEMENT packages (package*)>
|
||||||
|
|
||||||
|
<!ELEMENT package (classes)>
|
||||||
|
<!ATTLIST package name CDATA #REQUIRED>
|
||||||
|
<!ATTLIST package line-rate CDATA #REQUIRED>
|
||||||
|
<!ATTLIST package branch-rate CDATA #REQUIRED>
|
||||||
|
<!ATTLIST package complexity CDATA #REQUIRED>
|
||||||
|
|
||||||
|
<!ELEMENT classes (class*)>
|
||||||
|
|
||||||
|
<!ELEMENT class (methods,lines)>
|
||||||
|
<!ATTLIST class name CDATA #REQUIRED>
|
||||||
|
<!ATTLIST class filename CDATA #REQUIRED>
|
||||||
|
<!ATTLIST class line-rate CDATA #REQUIRED>
|
||||||
|
<!ATTLIST class branch-rate CDATA #REQUIRED>
|
||||||
|
<!ATTLIST class complexity CDATA #REQUIRED>
|
||||||
|
|
||||||
|
<!ELEMENT methods (method*)>
|
||||||
|
|
||||||
|
<!ELEMENT method (lines)>
|
||||||
|
<!ATTLIST method name CDATA #REQUIRED>
|
||||||
|
<!ATTLIST method signature CDATA #REQUIRED>
|
||||||
|
<!ATTLIST method line-rate CDATA #REQUIRED>
|
||||||
|
<!ATTLIST method branch-rate CDATA #REQUIRED>
|
||||||
|
|
||||||
|
<!ELEMENT lines (line*)>
|
||||||
|
|
||||||
|
<!ELEMENT line (conditions*)>
|
||||||
|
<!ATTLIST line number CDATA #REQUIRED>
|
||||||
|
<!ATTLIST line hits CDATA #REQUIRED>
|
||||||
|
<!ATTLIST line branch CDATA "false">
|
||||||
|
<!ATTLIST line condition-coverage CDATA "100%">
|
||||||
|
|
||||||
|
<!ELEMENT conditions (condition*)>
|
||||||
|
|
||||||
|
<!ELEMENT condition EMPTY>
|
||||||
|
<!ATTLIST condition number CDATA #REQUIRED>
|
||||||
|
<!ATTLIST condition type CDATA #REQUIRED>
|
||||||
|
<!ATTLIST condition coverage CDATA #REQUIRED>
|
||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user