Compare commits

...

108 Commits

Author SHA1 Message Date
irongut 51cc3a756d prepare v1.3.0 release 2022-07-29 16:50:11 +01:00
irongut df43d7d643 updated test workflow 2022-07-27 00:51:05 +01:00
irongut 8accb9dd63 prepare v1.3.0-beta release 2022-07-27 00:34:24 +01:00
irongut 21fb9f6797 merge PR #53 MATLAB compatibility
PR: MATLAB compatibility
2022-07-26 23:35:31 +01:00
irongut 05b101a9e5 handle NaN in branch + complexity values #46 2022-07-26 01:39:53 +01:00
irongut 405344bb11 add security policy 2022-07-25 02:26:32 +01:00
irongut 6575f73c8b merge PR #52 Sign Docker image on release
PR: Sign Docker image on release
2022-07-24 22:35:06 +01:00
irongut 9d67d209d5 remove workflow permissions from assign PR to author 2022-07-24 22:27:52 +01:00
irongut f055ebd021 sign Docker image on release #32 2022-07-24 22:16:13 +01:00
irongut 5783d61eba merge PR #50 Implement StepSecurity Secure Workflows
PR: Implement StepSecurity Secure Workflows
2022-07-24 21:59:44 +01:00
irongut 7ede3df512 add permissions to assign PR to author #49 2022-07-24 21:56:20 +01:00
irongut 4149d3cd42 remove workflow permissions from assign PR to author #49 2022-07-24 21:40:38 +01:00
irongut e585016ed7 remove workflow permissions from assign PR to author #49 2022-07-24 21:33:43 +01:00
irongut 875e6d6260 use StepSecurity Secure Workflows for project management #49 2022-07-24 21:22:31 +01:00
irongut 8e03759e2f use StepSecurity Secure Workflows for builds #49 2022-07-24 21:14:28 +01:00
irongut 7cdc061845 merge PR #44 from dependabot/Microsoft.VisualStudio.Azure.Containers.Tools.Targets-1.16.1
Bump Microsoft.VisualStudio.Azure.Containers.Tools.Targets from 1.14.0 to 1.16.1
2022-07-24 18:28:00 +01:00
dependabot[bot] 4a8d89664f Bump Microsoft.VisualStudio.Azure.Containers.Tools.Targets
Bumps Microsoft.VisualStudio.Azure.Containers.Tools.Targets from 1.14.0 to 1.16.1.

---
updated-dependencies:
- dependency-name: Microsoft.VisualStudio.Azure.Containers.Tools.Targets
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-24 17:24:09 +00:00
irongut 92459b634d merge PR #40 from dependabot/Microsoft.VisualStudio.Azure.Containers.Tools.Targets-1.15.1
Bump Microsoft.VisualStudio.Azure.Containers.Tools.Targets from 1.14.0 to 1.15.1
2022-07-24 18:23:49 +01:00
irongut 7f81eacb3c merge PR #42 from dependabot/CommandLineParser-2.9.1
Bump CommandLineParser from 2.8.0 to 2.9.1
2022-07-24 18:23:19 +01:00
dependabot[bot] c34bdfe66a Bump CommandLineParser from 2.8.0 to 2.9.1
Bumps [CommandLineParser](https://github.com/commandlineparser/commandline) from 2.8.0 to 2.9.1.
- [Release notes](https://github.com/commandlineparser/commandline/releases)
- [Changelog](https://github.com/commandlineparser/commandline/blob/master/CHANGELOG.md)
- [Commits](https://github.com/commandlineparser/commandline/compare/2.8.0...v2.9.1)

---
updated-dependencies:
- dependency-name: CommandLineParser
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-17 23:09:57 +00:00
dependabot[bot] 85a9fe6bb1 Bump Microsoft.VisualStudio.Azure.Containers.Tools.Targets
Bumps Microsoft.VisualStudio.Azure.Containers.Tools.Targets from 1.14.0 to 1.15.1.

---
updated-dependencies:
- dependency-name: Microsoft.VisualStudio.Azure.Containers.Tools.Targets
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-18 23:11:54 +00:00
irongut 5b38ba1306 merge PR #37 Improve Simplecov compatibility
PR: Improve Simplecov compatibility
2022-02-19 18:21:36 +00:00
irongut 7b1923cbc6 improved generated package names when names missing 2022-02-19 18:10:29 +00:00
irongut 1699cd4b65 improved error messages #33 2022-02-19 18:02:02 +00:00
irongut 4c876916c9 use AsSpan instead of SubString 2022-02-19 03:28:59 +00:00
irongut 1c5e7f1a60 improved error handling when parsing coverage files #33 2022-02-19 03:25:34 +00:00
irongut 9196cf3448 dont fail if branch metrics missing #33 2022-02-19 02:37:17 +00:00
irongut baf620bf17 merge PR #35 Glob Pattern Matching
PR: Glob Pattern Matching
2022-02-19 02:03:05 +00:00
irongut bc2adc7690 use glob pattern in workflows #31 2022-02-19 01:46:18 +00:00
irongut ce279ec14b error if no files match glob pattern #31 2022-02-19 01:40:00 +00:00
irongut 009a5455b8 use glob pattern matching #31 2022-02-19 01:35:28 +00:00
irongut 0d078db7cc added Microsoft.Extensions.FileSystemGlobbing #31 2022-02-19 01:08:32 +00:00
irongut 65684d3463 addded versioning to readme 2022-02-19 00:40:08 +00:00
irongut a6a88c76f7 added sImplecov note to readme 2022-02-18 01:18:48 +00:00
irongut 5088d5eb31 prepare v1.2.0 release 2021-11-25 21:53:22 +00:00
irongut ee53bdd6c5 updated workflows 2021-11-25 20:44:07 +00:00
irongut 6a542ff4b0 updated test workflows 2021-11-25 20:29:07 +00:00
irongut 46bc9869d5 updated test workflow 2021-11-24 23:33:15 +00:00
irongut 97f4a52b81 fixed release build workflow 2021-11-24 22:01:43 +00:00
irongut 95ea154a7b prepare v1.2.0-beta release 2021-11-24 21:47:59 +00:00
irongut 2e00d30f70 merge PR #30 Update to .Net 6 2021-11-24 20:27:20 +00:00
irongut c4d4b9a087 updated build workflows to .Net 6 #23 2021-11-24 00:34:44 +00:00
irongut ea5d3b417b updated build workflows to .Net 6 #23 2021-11-24 00:32:25 +00:00
irongut 3d33fafb37 updated Docker launch settings 2021-11-24 00:24:33 +00:00
irongut 3a41f0a5ea reduced number of Docker image layers 2021-11-23 21:54:11 +00:00
irongut dd5c8d3b75 updated Dockerfile to .Net 6 #23 2021-11-23 20:56:58 +00:00
irongut 4986e930de updated CLI to .Net 6 #23 2021-11-23 01:08:46 +00:00
irongut 37dca42320 added Assign to Project workflow 2021-11-22 02:19:37 +00:00
irongut 5ec10c1882 updated assign-pr-to-author action 2021-11-22 02:10:34 +00:00
irongut 56c9f3d623 merge PR #29 add PR Labeler action
PR: add PR Labeler action
2021-11-22 02:05:16 +00:00
irongut f36c88a82b configure pr labeler 2021-11-22 02:00:18 +00:00
irongut ffe316d009 added pr labeler action 2021-11-22 01:33:05 +00:00
irongut 1ea9b55e4d merge PR #27 from dependabot/Microsoft.VisualStudio.Azure.Containers.Tools.Targets-1.14.0
Bump Microsoft.VisualStudio.Azure.Containers.Tools.Targets from 1.11.1 to 1.14.0
2021-11-16 10:32:40 +00:00
dependabot[bot] fcb924f622 Bump Microsoft.VisualStudio.Azure.Containers.Tools.Targets
Bumps Microsoft.VisualStudio.Azure.Containers.Tools.Targets from 1.11.1 to 1.14.0.

---
updated-dependencies:
- dependency-name: Microsoft.VisualStudio.Azure.Containers.Tools.Targets
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-15 23:08:36 +00:00
irongut ed588922f2 merge PR #26 Support multiple cobertura files
PR: Support multiple cobertura files
2021-11-15 01:21:55 +00:00
irongut 6e12bd152f removed commented line 2021-11-15 00:33:15 +00:00
irongut 4ae964bab0 update ci build test command line #19 2021-11-15 00:24:46 +00:00
irongut 60646036b5 support multiple cobertura files in action definition #19 2021-11-15 00:18:30 +00:00
irongut 1c2edd9230 support multiple cobertura files in CLI #19 2021-11-14 23:51:30 +00:00
irongut 9caa66feee merge PR #25 Allow hiding Branch Rate + Complexity values in output
PR: Allow hiding Branch Rate + Complexity values in output
2021-11-14 00:41:09 +00:00
irongut 6c68cb69dd added hide branch rate + complexity to action definition #22 2021-11-13 23:53:03 +00:00
irongut 914b6fe5f9 fixed summary complexity should be bold in md output 2021-11-13 22:35:52 +00:00
irongut 46007a7270 hide branch rate + complexity for markdown output #22 2021-11-13 22:29:15 +00:00
irongut 71ae720dab hide branch rate + complexity for text output #22 2021-11-13 21:55:29 +00:00
irongut dd7d40d268 added hide branch + complixity CLI options #22 2021-11-13 21:29:36 +00:00
irongut 065eaf7bb5 prepare v1.1.0 release 2021-10-27 22:35:09 +01:00
irongut a5c4e90cbe updated readme 2021-10-27 00:41:20 +01:00
irongut f89139ddac updated test action 2021-10-26 23:53:11 +01:00
irongut ac37b8e9b7 prepare v1.1.0-beta release 2021-10-26 23:08:13 +01:00
irongut 7a76884d03 merge PR #18 Health indicators, change thresholds + fail workflow 2021-10-26 22:20:15 +01:00
irongut 2389c9883a added fail_below_min to action definition #16 2021-10-20 00:01:43 +01:00
irongut 02bb824606 added ability to fail a workflow #16 2021-10-19 23:49:51 +01:00
irongut b97bc1147a added indicators parameter to action definition #14 2021-10-19 00:29:37 +01:00
irongut d8b1bc5ef2 updated CLI help text 2021-10-19 00:28:24 +01:00
irongut 7c070aea38 fixed issue with bool parameters 2021-10-19 00:03:22 +01:00
irongut 6ed1f6c50d added health indicators #14 2021-10-18 23:26:42 +01:00
irongut ecc89a90a8 added Thresholds parameter to action definition #15 2021-10-18 01:30:03 +01:00
irongut 4a0dc323d3 added Thresholds parameter to CLI #15 2021-10-18 01:24:48 +01:00
irongut 711b5c996a added sponsorship 2021-10-13 13:01:08 +01:00
irongut 9bbe00ce6b added Cobertura DTD for info 2021-10-10 17:31:53 +01:00
irongut f9c0e2020c updated readme 2021-10-07 23:52:04 +01:00
irongut 1b1147a0ce updated workflows 2021-10-07 15:36:49 +01:00
irongut 3766e5780e add manual testing workflows 2021-10-07 14:52:19 +01:00
irongut 473d18e3ad prepare v1.0.5 2021-09-27 20:21:03 +01:00
irongut 61d10948c8 merge PR #10 Make compatible with gcovr 2021-09-27 19:44:43 +01:00
irongut 7cd8fe49e9 improved decimal complexity output #9 2021-09-27 19:41:59 +01:00
irongut 174c97ac3d handle empty package names #9 2021-09-26 01:49:23 +01:00
irongut 9b11daeca8 handle Complexity as an int or percentage #9 2021-09-26 00:58:44 +01:00
irongut 6db46e287e handle missing package attributes better 2021-09-26 00:01:55 +01:00
irongut c5d12d4a89 prepare v1.0.4 2021-09-25 21:48:17 +01:00
irongut cae1cbcdad merge PR #8 Bump docker image to v1.0.3 in action.yml from reallyreallyreal/master
Bump docker image to v1.0.3 in action.yml
2021-09-25 21:24:23 +01:00
Jonathan Derrough ed258b1479 Bump docker image to v1.0.3 in action.yml 2021-09-25 12:09:21 +07:00
irongut 0723132cf6 prepare v1.0.3 2021-09-24 20:41:43 +01:00
irongut 758c38f591 removed short params 2021-09-23 02:08:23 +01:00
irongut 24062a1b2a added changelog to release workflow 2021-09-23 01:48:41 +01:00
irongut dcef09e405 merge PR #7 Handle optional complexity attribute 2021-09-22 22:32:30 +01:00
Jonathan Derrough 9cce3f5374 Handle optional complexity attribute 2021-09-22 18:19:21 +07:00
irongut 20025e7e93 formatting + cleanup 2021-09-20 01:00:33 +01:00
irongut 0bcadffdb9 workflow fixes 2021-09-06 15:06:40 +01:00
irongut 71c6f23d2f use container from GHCR for Action #4 2021-09-06 01:11:36 +01:00
irongut 162def6e89 deploy step needs to checkout again #4 2021-09-06 00:58:20 +01:00
irongut e07628e8aa added deploy to GHCR workflow #4 2021-09-06 00:43:14 +01:00
irongut fc71f6831f merge PR #3 from dependabot/Microsoft.VisualStudio.Azure.Containers.Tools.Targets-1.11.1
Bump Microsoft.VisualStudio.Azure.Containers.Tools.Targets from 1.10.13 to 1.11.1
2021-09-05 23:15:14 +01:00
irongut c4d24d5996 merge PR #5 add empty line after badge from joshjohanning/master
adding empty line after badge so table renders properly
2021-09-05 23:09:40 +01:00
Joshua Johanning f5bcfce861 adding empty line after badge so table formats properly 2021-09-03 15:51:45 -05:00
dependabot[bot] a97b5158b3 Bump Microsoft.VisualStudio.Azure.Containers.Tools.Targets
Bumps Microsoft.VisualStudio.Azure.Containers.Tools.Targets from 1.10.13 to 1.11.1.

---
updated-dependencies:
- dependency-name: Microsoft.VisualStudio.Azure.Containers.Tools.Targets
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-10 23:11:27 +00:00
irongut e67bf0d2d1 updated documentation 2021-05-13 23:06:38 +01:00
irongut 1c05e17f24 updated readme 2021-05-10 23:30:39 +01:00
24 changed files with 2379 additions and 181 deletions
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [irongut]
+31
View File
@@ -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/**/*
+53
View File
@@ -0,0 +1,53 @@
name: Assign to Project
on:
issues:
types: [opened, labeled]
pull_request:
types: [opened, labeled]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
permissions:
contents: read
jobs:
assign-to-project:
permissions:
repository-projects: write # for srggrs/assign-one-project-github-action to assign issues and PRs to repo project
runs-on: ubuntu-latest
name: Assign to Project
steps:
- name: Harden Runner
uses: step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Assign Issues to Bugs
uses: srggrs/assign-one-project-github-action@4d59cc619499b55ca689fb13cfcc72324a8b8435
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@4d59cc619499b55ca689fb13cfcc72324a8b8435
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@4d59cc619499b55ca689fb13cfcc72324a8b8435
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@4d59cc619499b55ca689fb13cfcc72324a8b8435
if: contains(github.event.pull_request.labels.*.name, 'enhancement')
with:
project: 'https://github.com/irongut/CodeCoverageSummary/projects/2'
column_name: 'In Progress'
+11 -3
View File
@@ -2,14 +2,22 @@
# https://github.com/samspills/assign-pr-to-author # https://github.com/samspills/assign-pr-to-author
name: Auto Assign PR name: Auto Assign PR
on: [pull_request]
on:
pull_request:
types: [opened]
jobs: jobs:
assignAuthor: assignAuthor:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner
uses: step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Auto Assign PR - name: Auto Assign PR
uses: samspills/assign-pr-to-author@v1.0 uses: samspills/assign-pr-to-author@223a87a821f7e7447cfb5221bc53ceeb633341c2
if: github.event_name == 'pull_request' && github.event.action == 'opened'
with: with:
repo-token: '${{ secrets.GITHUB_TOKEN }}' repo-token: '${{ secrets.GITHUB_TOKEN }}'
+17 -12
View File
@@ -6,16 +6,27 @@ on:
pull_request: pull_request:
branches: [ master ] branches: [ master ]
permissions:
contents: read
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
name: CI Build name: CI Build
steps: steps:
- uses: actions/checkout@v2
- name: Setup .NET - name: Harden Runner
uses: actions/setup-dotnet@v1 uses: step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504
with: with:
dotnet-version: 5.0.x egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout
uses: actions/checkout@d171c3b028d844f2bf14e9fdec0c58114451e4bf
- name: Setup .Net
uses: actions/setup-dotnet@608ee757cfcce72c2e91e99aca128e0cae67de87
with:
dotnet-version: 6.0.x
- name: Restore Dependencies - name: Restore Dependencies
run: dotnet restore src/CodeCoverageSummary.sln run: dotnet restore src/CodeCoverageSummary.sln
@@ -23,11 +34,5 @@ jobs:
- name: Build CodeCoverageSummary - name: Build CodeCoverageSummary
run: dotnet build src/CodeCoverageSummary.sln --configuration Release --no-restore run: dotnet build src/CodeCoverageSummary.sln --configuration Release --no-restore
- name: Run CodeCoverageSummary 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 **/coverage.*.xml --badge true
- name: Test Action
uses: irongut/CodeCoverageSummary@master
with:
filename: '/app/sample.coverage.xml'
badge: 'true'
+13 -2
View File
@@ -4,14 +4,25 @@ on:
schedule: schedule:
- cron: "30 1 * * *" - cron: "30 1 * * *"
permissions:
contents: read
jobs: jobs:
stale: stale:
permissions:
issues: write # for actions/stale to close stale issues
pull-requests: write # for actions/stale to close stale PRs
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner
uses: step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Mark Stale - name: Mark Stale
uses: actions/stale@v3 uses: actions/stale@98ed4cb500039dbcccf4bd9bedada4d0187f2757
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
exempt-all-milestones: true exempt-all-milestones: true
+28
View File
@@ -0,0 +1,28 @@
# 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:
permissions:
contents: read
jobs:
label:
permissions:
contents: read # for actions/labeler to determine modified files
pull-requests: write # for actions/labeler to add labels to PRs
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- uses: actions/labeler@472c5d3aaacde439785e94966eb2e545627f4935
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
+99
View File
@@ -0,0 +1,99 @@
name: Build + Deploy to GHCR
on:
release:
types: [published]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
permissions:
contents: read
jobs:
build:
name: Test Build
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout
uses: actions/checkout@d171c3b028d844f2bf14e9fdec0c58114451e4bf
with:
fetch-depth: 0
- name: Setup .Net
uses: actions/setup-dotnet@608ee757cfcce72c2e91e99aca128e0cae67de87
with:
dotnet-version: 6.0.x
- name: Restore Dependencies
run: dotnet restore src/CodeCoverageSummary.sln
- name: Build CodeCoverageSummary
run: dotnet build src/CodeCoverageSummary.sln --configuration Release --no-restore
- name: Test with sample file
run: dotnet src/CodeCoverageSummary/bin/Release/net6.0/CodeCoverageSummary.dll --files **/coverage.*.xml --badge true
deploy:
name: Deploy to GHCR
needs: [build]
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write # Used for identity challenge with sigstore/fulcio
steps:
- name: Harden Runner
uses: step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504
with:
egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs
- name: Checkout
uses: actions/checkout@d171c3b028d844f2bf14e9fdec0c58114451e4bf
- name: Install Cosign
uses: sigstore/cosign-installer@c68f43abf1ae5df2528c9c250088fa14ed2d0ef5
with:
cosign-release: 'v1.9.0'
- name: Setup Docker Buildx
uses: docker/setup-buildx-action@dc7b9719a96d48369863986a06765841d7ea23f6
- name: Login to GitHub Container Registry
uses: docker/login-action@dd4fa0671be5250ee6f50aedf4cb05514abda2c7
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@b2391d37b4157fa4aa2e118d643f417910ff3242
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build + Push Docker image
id: build-and-push
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Sign the Docker image digest
# Uses the identity token to provision an ephemeral certificate against the community Fulcio instance
# https://github.com/sigstore/cosign
- name: Sign the Docker image
env:
COSIGN_EXPERIMENTAL: "true"
run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }}
+28
View File
@@ -0,0 +1,28 @@
name: Test Linux Runner
on:
workflow_dispatch:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
name: CI Build
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Test Action
uses: irongut/CodeCoverageSummary@master
with:
filename: /src/coverage.*.xml
badge: true
fail_below_min: true
format: markdown
hide_branch_rate: false
hide_complexity: true
indicators: true
output: both
thresholds: '60 80'
+25
View File
@@ -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'
+25
View File
@@ -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
View File
@@ -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"]
+120 -29
View File
@@ -1,29 +1,83 @@
# Code Coverage Summary # Code Coverage Summary
A GitHub Action that reads Cobertura format code coverage files and outputs a text or markdown summary. Other Actions can then post the summary as a pull request comment, include it in release notes, etc. <div align="center">
[![CI Build](https://github.com/irongut/CodeCoverageSummary/actions/workflows/ci-build.yml/badge.svg)](https://github.com/irongut/CodeCoverageSummary/actions/workflows/ci-build.yml)
&nbsp;
[![GitHub](https://img.shields.io/badge/GitHub-irongut/CodeCoverageSummary-informational?style=flat&logo=github)](https://github.com/irongut/CodeCoverageSummary)
&nbsp;
![.NET 6.0](https://img.shields.io/badge/Version-.NET%206.0-informational?style=flat&logo=dotnet)
&nbsp;
![Built With Docker](https://img.shields.io/badge/Built_With-Docker-informational?style=flat&logo=docker)
</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 any test framework that outputs coverage in Cobertura XML format including [Coverlet](https://github.com/coverlet-coverage/coverlet), [gcovr](https://github.com/gcovr/gcovr), [simplecov](https://github.com/simplecov-ruby/simplecov) and [MATLAB](https://uk.mathworks.com/help/matlab/ref/matlab.unittest.plugins.codecoverageplugin-class.html). See the [FAQ](https://github.com/irongut/CodeCoverageSummary/wiki/Frequently-Asked-Questions#which-testing-tools-does-ccs-work-with) for more details. If it doesn't work with your tooling please [open an issue][new-issue] to discuss the problem.
Code Coverage Summary is compatible with [StepSecurity Secure Workflows](https://github.com/step-security/secure-workflows) and uses a Docker image that is cryptographically signed using [Sigstore](https://www.sigstore.dev/). For instructions how to verify the Docker image please see the [Wiki](https://github.com/irongut/CodeCoverageSummary/wiki/Verify-the-Docker-Image).
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.
Written for use with [Coverlet](https://github.com/coverlet-coverage/coverlet) and .Net but it should work with any tests that output 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. Also supports using glob patterns to match multiple files. 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%) | ![Code Coverage](https://img.shields.io/badge/Code%20Coverage-45%25-critical?style=flat)
between thresholds (50% - 74%) | ![Code Coverage](https://img.shields.io/badge/Code%20Coverage-65%25-yellow?style=flat)
equal or greater than upper threshold (75%) | ![Code Coverage](https://img.shields.io/badge/Code%20Coverage-83%25-success?style=flat)
#### `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 metrics in the output - `true` or `false` (default).
### `hide_complexity`
Hide Complexity metrics 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 +87,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
![image](https://user-images.githubusercontent.com/27953302/117726304-4ac1c100-b1de-11eb-8d9a-6286ba1f5523.png) ### Markdown Example
> ![Code Coverage](https://img.shields.io/badge/Code%20Coverage-83%25-success?style=flat)
>
> 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 uses: irongut/CodeCoverageSummary@v1.3.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 +151,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 +163,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 uses: irongut/CodeCoverageSummary@v1.3.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,18 +186,31 @@ jobs:
path: code-coverage-results.md path: code-coverage-results.md
``` ```
## Version Numbers
Version numbers will be assigned according to the [Semantic Versioning](https://semver.org/) scheme.
This means, given a version number MAJOR.MINOR.PATCH, we will increment the:
1. MAJOR version when we make incompatible API changes
2. MINOR version when we add functionality in a backwards compatible manner
3. PATCH version when we make backwards compatible bug fixes
## Contributing ## Contributing
### Report Bugs ### Report Bugs
Please make sure the bug is not already reported by searching existing [issues]. 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 [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 +219,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.
+26
View File
@@ -0,0 +1,26 @@
# Security Policy
## Supported Versions
Version | Supported
------- | ------------------
1.2.0 | :white_check_mark:
1.2.0-beta | :x:
1.1.0 | :x:
1.1.0-beta| :x:
1.0.x | :x:
Only the latest version is supported. If you find a security vulnerability in an eariler version please check it exists in the latest version before reporting.
## Reporting a Vulnerability
Please make sure the vulnerability is not already reported by searching existing [issues].
If you're unable to find an existing issue addressing the vulnerability please [open a new issue][new-issue].
Be sure to include a title and clear description and as much relevant information as possible.
Please tag the issue with the `Security` label.
[issues]: https://github.com/irongut/CodeCoverageSummary/issues
[new-issue]: https://github.com/irongut/CodeCoverageSummary/issues/new
+34 -3
View File
@@ -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. Also supports using glob patterns to match multiple files.'
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: 'Dockerfile' image: 'docker://ghcr.io/irongut/codecoveragesummary:v1.3.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,23 +2,25 @@
<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>
<Description>A GitHub Action that reads Cobertura format code coverage files and outputs a summary.</Description> <Description>A GitHub Action that reads Cobertura format code coverage files and outputs a text or markdown summary.</Description>
<Copyright>Copyright © 2021 Taranis Software</Copyright> <Copyright>Copyright © 2021 - 2022 Taranis Software</Copyright>
<PackageId>Taranis.CodeCoverageSummary</PackageId> <PackageId>Taranis.CodeCoverageSummary</PackageId>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/irongut/CodeCoverageSummary</PackageProjectUrl> <PackageProjectUrl>https://github.com/irongut/CodeCoverageSummary</PackageProjectUrl>
<RepositoryUrl>https://github.com/irongut/CodeCoverageSummary</RepositoryUrl> <RepositoryUrl>https://github.com/irongut/CodeCoverageSummary</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<PackageTags>code-coverage cobertura coverlet</PackageTags> <PackageTags>coverage test-coverage cobertura action code-coverage coverlet github-actions</PackageTags>
<Version>1.3.0</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" /> <PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.13" /> <PackageReference Include="Microsoft.Extensions.FileSystemGlobbing" Version="6.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.16.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+3 -6
View File
@@ -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>();
}
} }
} }
+33 -6
View File
@@ -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. Also accepts glob patterns.")]
public string Filename { get; set; } public IEnumerable<string> Files { get; set; }
[Option(shortName: 'b', longName: "badge", Required = false, HelpText = "Include a badge in the output - true / 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; }
[Option(shortName: 'f', longName: "format", Required = false, HelpText = "Output Format - markdown or text.", Default = "text")] 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")]
public string Format { get; set; } public string Format { get; set; }
[Option(shortName: 'o', longName: "output", Required = false, HelpText = "Output Type - console, file or both.", Default = "console")] [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")]
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; }
} }
} }
@@ -0,0 +1,9 @@
// This file is used by Code Analysis to maintain SuppressMessage
// attributes that are applied to this project.
// Project-level suppressions either have no target or are given
// a specific target and scoped to a namespace, type, member, etc.
using System.Diagnostics.CodeAnalysis;
[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)")]
+249 -105
View File
@@ -1,5 +1,7 @@
using CommandLine; using CommandLine;
using Microsoft.Extensions.FileSystemGlobbing;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -9,88 +11,138 @@ namespace CodeCoverageSummary
{ {
internal static class Program internal static class Program
{ {
// test file: /Dev/Csharp/CodeCoverageSummary/coverage.cobertura.xml 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)
.MapResult(o => .MapResult(o =>
{ {
try try
{ {
if (!File.Exists(o.Filename)) // use glob patterns to match files
{ Matcher matcher = new();
Console.WriteLine("Error: Code coverage file not found."); matcher.AddIncludePatterns(o.Files.ToArray());
return -2; // error IEnumerable<string> matchingFiles = matcher.GetResultsInFullPath(".");
}
// parse code coverage file if (matchingFiles?.Any() == false)
Console.WriteLine($"Code Coverage File: {o.Filename}"); {
CodeSummary summary = ParseTestResults(o.Filename); Console.WriteLine("Error: No files found matching glob pattern.");
if (summary == null) return -2; // error
{ }
Console.WriteLine("Error: Parsing code coverage file.");
return -2; // error
}
else
{
// generate badge
string badgeUrl = o.Badge ? GenerateBadge(summary) : null;
// generate output // check files exist
string output; foreach (var file in matchingFiles)
string fileExt; {
if (o.Format.Equals("text", StringComparison.OrdinalIgnoreCase)) if (!File.Exists(file))
{ {
fileExt = "txt"; Console.WriteLine($"Error: Coverage file not found - {file}.");
output = GenerateTextOutput(summary, badgeUrl); return -2; // error
} }
else if (o.Format.Equals("md", StringComparison.OrdinalIgnoreCase) || o.Format.Equals("markdown", StringComparison.OrdinalIgnoreCase)) }
{
fileExt = "md";
output = GenerateMarkdownOutput(summary, badgeUrl);
}
else
{
Console.WriteLine("Error: Unknown output format.");
return -2; // error
}
// output // parse code coverage file
if (o.Output.Equals("console", StringComparison.OrdinalIgnoreCase)) CodeSummary summary = new();
{ foreach (var file in matchingFiles)
Console.WriteLine(output); {
} Console.WriteLine($"Coverage File: {file}");
else if (o.Output.Equals("file", StringComparison.OrdinalIgnoreCase)) summary = ParseTestResults(file, summary);
{ }
File.WriteAllText($"code-coverage-results.{fileExt}", output);
}
else if (o.Output.Equals("both", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine(output);
File.WriteAllText($"code-coverage-results.{fileExt}", output);
}
else
{
Console.WriteLine("Error: Unknown output type.");
return -2; // error
}
return 0; // success if (summary == null)
} return -2; // error
}
catch (Exception ex) summary.LineRate /= matchingFiles.Count();
{ summary.BranchRate /= matchingFiles.Count();
Console.WriteLine($"Error: {ex.GetType()} - {ex.Message}");
return -3; // unhandled error if (summary.Packages.Count == 0)
} {
}, Console.WriteLine("Parsing Error: No packages found in coverage files.");
errs => -1); // invalid arguments return -2; // error
}
else
{
// hide branch rate if metrics missing
bool hideBranchRate = o.HideBranchRate;
if (summary.BranchRate == 0 && summary.BranchesCovered == 0 && summary.BranchesValid == 0)
hideBranchRate = true;
// set health badge thresholds
if (!string.IsNullOrWhiteSpace(o.Thresholds))
SetThresholds(o.Thresholds);
// generate badge
string badgeUrl = o.Badge ? GenerateBadge(summary) : null;
// generate output
string output;
string fileExt;
if (o.Format.Equals("text", StringComparison.OrdinalIgnoreCase))
{
fileExt = "txt";
output = GenerateTextOutput(summary, badgeUrl, o.Indicators, 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))
{
fileExt = "md";
output = GenerateMarkdownOutput(summary, badgeUrl, o.Indicators, hideBranchRate, o.HideComplexity);
if (o.FailBelowThreshold)
output += $"{Environment.NewLine}_Minimum allowed line rate is `{lowerThreshold * 100:N0}%`_{Environment.NewLine}";
}
else
{
Console.WriteLine("Error: Unknown output format.");
return -2; // error
}
// output
if (o.Output.Equals("console", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine();
Console.WriteLine(output);
}
else if (o.Output.Equals("file", StringComparison.OrdinalIgnoreCase))
{
File.WriteAllText($"code-coverage-results.{fileExt}", output);
}
else if (o.Output.Equals("both", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine();
Console.WriteLine(output);
File.WriteAllText($"code-coverage-results.{fileExt}", output);
}
else
{
Console.WriteLine("Error: Unknown output type.");
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
}
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.GetType()} - {ex.Message}");
return -3; // unhandled error
}
},
_ => -1); // invalid arguments
} }
private static CodeSummary ParseTestResults(string filename) private static CodeSummary ParseTestResults(string filename, CodeSummary summary)
{ {
CodeSummary summary = new(); if (summary == null)
return null;
try try
{ {
string rss = File.ReadAllText(filename); string rss = File.ReadAllText(filename);
@@ -100,72 +152,131 @@ namespace CodeCoverageSummary
var coverage = from item in xdoc.Descendants("coverage") var coverage = from item in xdoc.Descendants("coverage")
select item; select item;
if (!coverage.Any())
throw new Exception("Coverage file invalid, data not found");
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);
if (!lineR.Any())
throw new Exception("Overall line rate not found");
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);
if (!linesCovered.Any())
throw new Exception("Overall lines covered not found");
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);
if (!linesValid.Any())
throw new Exception("Overall lines valid not found");
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);
var branchesCovered = from item in coverage.Attributes() if (branchR.Any())
where item.Name == "branches-covered" {
select item; summary.BranchRate += double.TryParse(branchR.First().Value, out double bRate) ? bRate : 0;
summary.BranchesCovered = int.Parse(branchesCovered.First().Value);
var branchesValid = from item in coverage.Attributes() var branchesCovered = from item in coverage.Attributes()
where item.Name == "branches-valid" where item.Name == "branches-covered"
select item; select item;
summary.BranchesValid = int.Parse(branchesValid.First().Value);
summary.Complexity = 0; summary.BranchesCovered += int.TryParse(branchesCovered?.First().Value ?? "0", out int bCovered) ? bCovered : 0;
var branchesValid = from item in coverage.Attributes()
where item.Name == "branches-valid"
select item;
summary.BranchesValid += int.TryParse(branchesValid?.First().Value ?? "0", out int bValid) ? bValid : 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;
if (!packages.Any())
throw new Exception("No package data found");
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) ? $"{Path.GetFileNameWithoutExtension(filename)} 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.TryParse(item.Attribute("branch-rate")?.Value ?? "0", out double bRate) ? bRate : 0,
Complexity = int.Parse(item.Attribute("complexity").Value) Complexity = double.TryParse(item.Attribute("complexity")?.Value ?? "0", out double complex) ? complex : 0
}; };
summary.Packages.Add(packageCoverage); summary.Packages.Add(packageCoverage);
summary.Complexity += packageCoverage.Complexity; summary.Complexity += packageCoverage.Complexity;
i++;
} }
return summary; return summary;
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Parse Error: {ex.Message}"); Console.WriteLine($"Parsing Error: {ex.Message} - {filename}");
return null; return null;
} }
} }
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.AsSpan(0, s), out lowerPercentage))
throw new ArgumentException("Threshold parameter set incorrectly.");
if (!int.TryParse(thresholds.AsSpan(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";
} }
@@ -176,46 +287,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($"![Code Coverage]({badgeUrl})"); markdownOutput.AppendLine($"![Code Coverage]({badgeUrl})")
.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.*.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"
} }
} }
} }
+60
View File
@@ -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
+441
View File
@@ -0,0 +1,441 @@
<?xml version='1.0'?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
<!-- Generated by simplecov-cobertura version 2.1.0 (https://github.com/dashingrocket/simplecov-cobertura) -->
<coverage line-rate="0.97" lines-covered="350" lines-valid="362" complexity="0" version="0" timestamp="1645060052">
<sources>
<source>/Users/pboling/src/my/oauth2</source>
</sources>
<packages>
<package name="oauth2" line-rate="0.97" complexity="0">
<classes>
<class name="oauth2" filename="lib/oauth2.rb" line-rate="1.0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="5" hits="1"/>
<line number="6" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="1"/>
<line number="9" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="1"/>
<line number="12" hits="1"/>
</lines>
</class>
<class name="access_token" filename="lib/oauth2/access_token.rb" line-rate="0.96" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="7" hits="1"/>
<line number="13" hits="1"/>
<line number="14" hits="7"/>
<line number="15" hits="7"/>
<line number="23" hits="1"/>
<line number="24" hits="1"/>
<line number="41" hits="1"/>
<line number="42" hits="151"/>
<line number="43" hits="151"/>
<line number="44" hits="151"/>
<line number="45" hits="151"/>
<line number="46" hits="453"/>
<line number="48" hits="151"/>
<line number="49" hits="151"/>
<line number="50" hits="151"/>
<line number="51" hits="151"/>
<line number="52" hits="151"/>
<line number="55" hits="151"/>
<line number="61" hits="1"/>
<line number="62" hits="6"/>
<line number="68" hits="1"/>
<line number="69" hits="11"/>
<line number="75" hits="1"/>
<line number="76" hits="3"/>
<line number="83" hits="1"/>
<line number="84" hits="2"/>
<line number="86" hits="2"/>
<line number="87" hits="2"/>
<line number="88" hits="2"/>
<line number="89" hits="2"/>
<line number="90" hits="2"/>
<line number="91" hits="2"/>
<line number="97" hits="1"/>
<line number="98" hits="1"/>
<line number="107" hits="1"/>
<line number="108" hits="20"/>
<line number="109" hits="20"/>
<line number="115" hits="1"/>
<line number="116" hits="2"/>
<line number="122" hits="1"/>
<line number="123" hits="18"/>
<line number="129" hits="1"/>
<line number="130" hits="2"/>
<line number="136" hits="1"/>
<line number="137" hits="0"/>
<line number="143" hits="1"/>
<line number="144" hits="2"/>
<line number="148" hits="1"/>
<line number="149" hits="8"/>
<line number="152" hits="1"/>
<line number="154" hits="1"/>
<line number="155" hits="20"/>
<line number="157" hits="8"/>
<line number="158" hits="8"/>
<line number="160" hits="8"/>
<line number="161" hits="8"/>
<line number="163" hits="4"/>
<line number="164" hits="4"/>
<line number="165" hits="4"/>
<line number="167" hits="0"/>
<line number="171" hits="0"/>
<line number="175" hits="1"/>
<line number="176" hits="13"/>
<line number="178" hits="12"/>
</lines>
</class>
<class name="authenticator" filename="lib/oauth2/authenticator.rb" line-rate="1.0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="5" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="106"/>
<line number="9" hits="106"/>
<line number="10" hits="106"/>
<line number="22" hits="1"/>
<line number="23" hits="106"/>
<line number="25" hits="14"/>
<line number="27" hits="89"/>
<line number="29" hits="1"/>
<line number="31" hits="1"/>
<line number="33" hits="1"/>
<line number="37" hits="1"/>
<line number="38" hits="15"/>
<line number="41" hits="1"/>
<line number="45" hits="1"/>
<line number="46" hits="89"/>
<line number="51" hits="1"/>
<line number="52" hits="1"/>
<line number="57" hits="1"/>
<line number="58" hits="14"/>
<line number="59" hits="14"/>
<line number="60" hits="14"/>
<line number="64" hits="1"/>
<line number="65" hits="14"/>
</lines>
</class>
<class name="client" filename="lib/oauth2/client.rb" line-rate="0.95" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="4" hits="1"/>
<line number="6" hits="1"/>
<line number="7" hits="1"/>
<line number="9" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="1"/>
<line number="31" hits="1"/>
<line number="32" hits="204"/>
<line number="33" hits="204"/>
<line number="34" hits="204"/>
<line number="35" hits="204"/>
<line number="36" hits="204"/>
<line number="39" hits="204"/>
<line number="49" hits="204"/>
<line number="55" hits="1"/>
<line number="56" hits="0"/>
<line number="57" hits="0"/>
<line number="61" hits="1"/>
<line number="62" hits="423"/>
<line number="63" hits="158"/>
<line number="64" hits="158"/>
<line number="65" hits="134"/>
<line number="66" hits="134"/>
<line number="69" hits="158"/>
<line number="76" hits="1"/>
<line number="77" hits="11"/>
<line number="78" hits="11"/>
<line number="84" hits="1"/>
<line number="85" hits="100"/>
<line number="100" hits="1"/>
<line number="101" hits="141"/>
<line number="103" hits="141"/>
<line number="105" hits="141"/>
<line number="106" hits="141"/>
<line number="107" hits="141"/>
<line number="109" hits="141"/>
<line number="111" hits="141"/>
<line number="113" hits="3"/>
<line number="114" hits="3"/>
<line number="115" hits="3"/>
<line number="117" hits="2"/>
<line number="118" hits="1"/>
<line number="119" hits="1"/>
<line number="121" hits="2"/>
<line number="124" hits="127"/>
<line number="126" hits="11"/>
<line number="127" hits="11"/>
<line number="129" hits="2"/>
<line number="130" hits="2"/>
<line number="132" hits="0"/>
<line number="133" hits="0"/>
<line number="143" hits="1"/>
<line number="144" hits="97"/>
<line number="145" hits="182"/>
<line number="146" hits="2"/>
<line number="148" hits="180"/>
<line number="151" hits="97"/>
<line number="153" hits="97"/>
<line number="154" hits="97"/>
<line number="155" hits="97"/>
<line number="156" hits="97"/>
<line number="157" hits="79"/>
<line number="158" hits="79"/>
<line number="160" hits="18"/>
<line number="161" hits="18"/>
<line number="163" hits="97"/>
<line number="164" hits="97"/>
<line number="167" hits="97"/>
<line number="169" hits="0"/>
<line number="172" hits="97"/>
<line number="173" hits="3"/>
<line number="174" hits="3"/>
<line number="176" hits="94"/>
<line number="182" hits="1"/>
<line number="183" hits="48"/>
<line number="189" hits="1"/>
<line number="190" hits="5"/>
<line number="196" hits="1"/>
<line number="197" hits="11"/>
<line number="203" hits="1"/>
<line number="204" hits="21"/>
<line number="207" hits="1"/>
<line number="208" hits="9"/>
<line number="227" hits="1"/>
<line number="228" hits="55"/>
<line number="229" hits="2"/>
<line number="231" hits="53"/>
<line number="235" hits="1"/>
<line number="236" hits="91"/>
<line number="237" hits="91"/>
<line number="240" hits="1"/>
<line number="242" hits="1"/>
<line number="243" hits="97"/>
<line number="244" hits="97"/>
<line number="246" hits="93"/>
<line number="250" hits="93"/>
<line number="251" hits="1"/>
<line number="253" hits="92"/>
</lines>
</class>
<class name="error" filename="lib/oauth2/error.rb" line-rate="1.0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="14"/>
<line number="9" hits="14"/>
<line number="11" hits="14"/>
<line number="12" hits="7"/>
<line number="13" hits="7"/>
<line number="14" hits="7"/>
<line number="17" hits="14"/>
<line number="23" hits="1"/>
<line number="24" hits="14"/>
<line number="26" hits="14"/>
<line number="28" hits="14"/>
<line number="29" hits="7"/>
<line number="30" hits="7"/>
<line number="32" hits="7"/>
<line number="35" hits="14"/>
<line number="37" hits="14"/>
</lines>
</class>
<class name="mac_token" filename="lib/oauth2/mac_token.rb" line-rate="0.95" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="6" hits="1"/>
<line number="7" hits="1"/>
<line number="14" hits="1"/>
<line number="15" hits="3"/>
<line number="18" hits="1"/>
<line number="30" hits="1"/>
<line number="31" hits="19"/>
<line number="32" hits="19"/>
<line number="34" hits="18"/>
<line number="43" hits="1"/>
<line number="44" hits="4"/>
<line number="46" hits="4"/>
<line number="47" hits="4"/>
<line number="49" hits="4"/>
<line number="53" hits="1"/>
<line number="54" hits="1"/>
<line number="61" hits="1"/>
<line number="62" hits="9"/>
<line number="63" hits="9"/>
<line number="65" hits="9"/>
<line number="67" hits="8"/>
<line number="69" hits="7"/>
<line number="71" hits="7"/>
<line number="80" hits="1"/>
<line number="82" hits="8"/>
<line number="91" hits="8"/>
<line number="97" hits="1"/>
<line number="98" hits="19"/>
<line number="101" hits="1"/>
<line number="103" hits="0"/>
<line number="107" hits="17"/>
<line number="109" hits="0"/>
<line number="112" hits="1"/>
<line number="116" hits="1"/>
<line number="120" hits="1"/>
<line number="124" hits="1"/>
<line number="125" hits="8"/>
</lines>
</class>
<class name="response" filename="lib/oauth2/response.rb" line-rate="1.0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="5" hits="1"/>
<line number="7" hits="1"/>
<line number="8" hits="1"/>
<line number="9" hits="1"/>
<line number="14" hits="62"/>
<line number="15" hits="44"/>
<line number="16" hits="3"/>
<line number="20" hits="1"/>
<line number="32" hits="1"/>
<line number="33" hits="3"/>
<line number="34" hits="3"/>
<line number="35" hits="3"/>
<line number="36" hits="6"/>
<line number="46" hits="1"/>
<line number="47" hits="147"/>
<line number="48" hits="147"/>
<line number="52" hits="1"/>
<line number="53" hits="6"/>
<line number="57" hits="1"/>
<line number="58" hits="150"/>
<line number="62" hits="1"/>
<line number="63" hits="154"/>
<line number="69" hits="1"/>
<line number="70" hits="134"/>
<line number="72" hits="131"/>
<line number="76" hits="1"/>
<line number="77" hits="244"/>
<line number="81" hits="1"/>
<line number="82" hits="244"/>
<line number="84" hits="244"/>
<line number="89" hits="1"/>
<line number="90" hits="1"/>
</lines>
</class>
<class name="assertion" filename="lib/oauth2/strategy/assertion.rb" line-rate="0.89" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="23" hits="1"/>
<line number="27" hits="1"/>
<line number="28" hits="1"/>
<line number="45" hits="1"/>
<line number="46" hits="8"/>
<line number="47" hits="8"/>
<line number="50" hits="1"/>
<line number="51" hits="8"/>
<line number="53" hits="8"/>
<line number="60" hits="1"/>
<line number="62" hits="8"/>
<line number="67" hits="8"/>
<line number="68" hits="8"/>
<line number="69" hits="0"/>
<line number="70" hits="0"/>
</lines>
</class>
<class name="auth_code" filename="lib/oauth2/strategy/auth_code.rb" line-rate="1.0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="6" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="3"/>
<line number="17" hits="1"/>
<line number="18" hits="3"/>
<line number="27" hits="1"/>
<line number="28" hits="44"/>
<line number="30" hits="44"/>
</lines>
</class>
<class name="base" filename="lib/oauth2/strategy/base.rb" line-rate="1.0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="3" hits="1"/>
<line number="4" hits="1"/>
<line number="5" hits="95"/>
</lines>
</class>
<class name="client_credentials" filename="lib/oauth2/strategy/client_credentials.rb" line-rate="1.0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="6" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="1"/>
<line number="18" hits="1"/>
<line number="19" hits="20"/>
<line number="20" hits="20"/>
</lines>
</class>
<class name="implicit" filename="lib/oauth2/strategy/implicit.rb" line-rate="1.0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="6" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="3"/>
<line number="17" hits="1"/>
<line number="18" hits="3"/>
<line number="24" hits="1"/>
<line number="25" hits="1"/>
</lines>
</class>
<class name="password" filename="lib/oauth2/strategy/password.rb" line-rate="1.0" complexity="0">
<methods/>
<lines>
<line number="1" hits="1"/>
<line number="2" hits="1"/>
<line number="6" hits="1"/>
<line number="10" hits="1"/>
<line number="11" hits="1"/>
<line number="19" hits="1"/>
<line number="20" hits="10"/>
<line number="23" hits="10"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>