getcov 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/pages.yml +54 -0
- data/.github/workflows/pr_coverage.yml +94 -0
- data/.github/workflows/release.yml +84 -0
- data/.github/workflows/ruby.yml +24 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +158 -0
- data/Rakefile +10 -0
- data/exe/getcov +10 -0
- data/lib/getcov/auto.rb +9 -0
- data/lib/getcov/configuration.rb +98 -0
- data/lib/getcov/formatter/badge.rb +38 -0
- data/lib/getcov/formatter/cobertura.rb +69 -0
- data/lib/getcov/formatter/html.rb +170 -0
- data/lib/getcov/formatter/lcov.rb +33 -0
- data/lib/getcov/formatter/pr_comment.rb +43 -0
- data/lib/getcov/formatter/summary_json.rb +33 -0
- data/lib/getcov/result.rb +114 -0
- data/lib/getcov/version.rb +4 -0
- data/lib/getcov.rb +165 -0
- data/test/auto_integration_test.rb +13 -0
- data/test/branch_summary_test.rb +17 -0
- data/test/configuration_test.rb +32 -0
- data/test/formatters_export_test.rb +33 -0
- data/test/html_formatter_detail_test.rb +32 -0
- data/test/html_formatter_test.rb +23 -0
- data/test/ignore_only_test.rb +14 -0
- data/test/merge_test.rb +20 -0
- data/test/per_file_minimum_test.rb +28 -0
- data/test/pr_comment_and_json_test.rb +25 -0
- data/test/result_test.rb +31 -0
- data/test/test_helper.rb +20 -0
- data/test/thresholds_test.rb +14 -0
- metadata +79 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6d05ca227c84cbbabe0b4988f9e05c32421c9264de47645135a594abea611be3
|
|
4
|
+
data.tar.gz: e4c472974841b668a70bc39bd73c7496917b8cdf059d8d862fa1fc4623265def
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4fd45e4fd86b3b9deb0c49fa2d8e6a5cbfebefeaa8db230f286ee5f015b3bdcdf012e9781a80fef7b5e69f853e156b658b6ee96e586b6972c0a15dae582e4676
|
|
7
|
+
data.tar.gz: fb6dcd4d57cf7bd706b4ad085ad0652efa27d4731cf31758ee167bf8e4fe3eaadc27a27e5ca3b771df8fbf22016cbf2367436729d861787d4e18dd4e77219481
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Publish Coverage to Pages
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
push:
|
|
6
|
+
branches: [ main, master ]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
pages: write
|
|
11
|
+
id-token: write
|
|
12
|
+
|
|
13
|
+
concurrency:
|
|
14
|
+
group: "pages"
|
|
15
|
+
cancel-in-progress: false
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: ruby/setup-ruby@v1
|
|
23
|
+
with:
|
|
24
|
+
ruby-version: '3.2'
|
|
25
|
+
bundler-cache: true
|
|
26
|
+
- run: bundle install --jobs 4 --retry 3
|
|
27
|
+
- name: Configure getcov (HTML only)
|
|
28
|
+
run: |
|
|
29
|
+
cat > getcov.rb <<'RUBY'
|
|
30
|
+
Getcov.configure do |c|
|
|
31
|
+
c.output_dir = 'coverage'
|
|
32
|
+
c.enable_html!
|
|
33
|
+
end
|
|
34
|
+
RUBY
|
|
35
|
+
- name: Run tests with coverage
|
|
36
|
+
env:
|
|
37
|
+
RUBYOPT: -rgetcov/auto
|
|
38
|
+
run: bundle exec rake test
|
|
39
|
+
- name: Setup Pages
|
|
40
|
+
uses: actions/configure-pages@v5
|
|
41
|
+
- name: Upload artifact
|
|
42
|
+
uses: actions/upload-pages-artifact@v3
|
|
43
|
+
with:
|
|
44
|
+
path: coverage
|
|
45
|
+
deploy:
|
|
46
|
+
environment:
|
|
47
|
+
name: github-pages
|
|
48
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
needs: build
|
|
51
|
+
steps:
|
|
52
|
+
- name: Deploy to GitHub Pages
|
|
53
|
+
id: deployment
|
|
54
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
name: PR Coverage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [ main, master ]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
coverage-diff:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
with:
|
|
13
|
+
fetch-depth: 0
|
|
14
|
+
|
|
15
|
+
- name: Set up Ruby
|
|
16
|
+
uses: ruby/setup-ruby@v1
|
|
17
|
+
with:
|
|
18
|
+
ruby-version: '3.2'
|
|
19
|
+
bundler-cache: true
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: bundle install --jobs 4 --retry 3
|
|
23
|
+
|
|
24
|
+
- name: Configure getcov (HTML + JSON + PR comment)
|
|
25
|
+
run: |
|
|
26
|
+
cat > getcov.rb <<'RUBY'
|
|
27
|
+
require 'getcov/formatter/summary_json'
|
|
28
|
+
require 'getcov/formatter/pr_comment'
|
|
29
|
+
Getcov.configure do |c|
|
|
30
|
+
c.output_dir = 'coverage'
|
|
31
|
+
c.enable_html!
|
|
32
|
+
c.add_formatter(Getcov::Formatter::SummaryJSON)
|
|
33
|
+
c.add_formatter(Getcov::Formatter::PRComment)
|
|
34
|
+
end
|
|
35
|
+
RUBY
|
|
36
|
+
|
|
37
|
+
- name: Run tests on HEAD with coverage
|
|
38
|
+
env:
|
|
39
|
+
RUBYOPT: -rgetcov/auto
|
|
40
|
+
run: bundle exec rake test
|
|
41
|
+
|
|
42
|
+
- name: Move HEAD coverage aside
|
|
43
|
+
run: |
|
|
44
|
+
mkdir -p .cov/head
|
|
45
|
+
cp coverage/summary.json .cov/head/summary.json
|
|
46
|
+
cp -r coverage .cov/head/html || true
|
|
47
|
+
|
|
48
|
+
- name: Compute BASE ref
|
|
49
|
+
id: base
|
|
50
|
+
run: |
|
|
51
|
+
echo "ref=${{ github.base_ref }}" >> "$GITHUB_OUTPUT"
|
|
52
|
+
|
|
53
|
+
- name: Checkout BASE
|
|
54
|
+
run: |
|
|
55
|
+
git checkout ${{ steps.base.outputs.ref }}
|
|
56
|
+
|
|
57
|
+
- name: Install deps for BASE
|
|
58
|
+
run: bundle install --jobs 4 --retry 3
|
|
59
|
+
|
|
60
|
+
- name: Configure getcov (JSON only for baseline)
|
|
61
|
+
run: |
|
|
62
|
+
cat > getcov.rb <<'RUBY'
|
|
63
|
+
require 'getcov/formatter/summary_json'
|
|
64
|
+
Getcov.configure do |c|
|
|
65
|
+
c.output_dir = 'coverage'
|
|
66
|
+
c.add_formatter(Getcov::Formatter::SummaryJSON)
|
|
67
|
+
end
|
|
68
|
+
RUBY
|
|
69
|
+
|
|
70
|
+
- name: Run tests on BASE with coverage
|
|
71
|
+
env:
|
|
72
|
+
RUBYOPT: -rgetcov/auto
|
|
73
|
+
run: bundle exec rake test
|
|
74
|
+
|
|
75
|
+
- name: Move BASE coverage aside
|
|
76
|
+
run: |
|
|
77
|
+
mkdir -p .cov/base
|
|
78
|
+
cp coverage/summary.json .cov/base/summary.json || echo '{}' > .cov/base/summary.json
|
|
79
|
+
|
|
80
|
+
- name: Coverage diff
|
|
81
|
+
id: diff
|
|
82
|
+
run: |
|
|
83
|
+
ruby .github/scripts/coverage_diff.rb --base ./.cov/base/summary.json --head ./.cov/head/summary.json --fail-drop ${FAIL_DROP:-1.0} > coverage_diff.md
|
|
84
|
+
env:
|
|
85
|
+
FAIL_DROP: "1.0" # fail if total coverage drops by >= 1.0%
|
|
86
|
+
|
|
87
|
+
- name: Append to step summary
|
|
88
|
+
run: cat coverage_diff.md >> "$GITHUB_STEP_SUMMARY"
|
|
89
|
+
|
|
90
|
+
- name: Upload coverage HTML (HEAD) as artifact
|
|
91
|
+
uses: actions/upload-artifact@v4
|
|
92
|
+
with:
|
|
93
|
+
name: coverage-html
|
|
94
|
+
path: .cov/head/html
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test-and-build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Set up Ruby
|
|
15
|
+
uses: ruby/setup-ruby@v1
|
|
16
|
+
with:
|
|
17
|
+
ruby-version: '3.2'
|
|
18
|
+
bundler-cache: true
|
|
19
|
+
|
|
20
|
+
- name: Install dependencies
|
|
21
|
+
run: bundle install --jobs 4 --retry 3
|
|
22
|
+
|
|
23
|
+
- name: Configure getcov (HTML, Cobertura, LCOV, Badge)
|
|
24
|
+
run: |
|
|
25
|
+
cat > getcov.rb <<'RUBY'
|
|
26
|
+
Getcov.configure do |c|
|
|
27
|
+
c.output_dir = 'coverage'
|
|
28
|
+
c.enable_html!
|
|
29
|
+
c.enable_cobertura!
|
|
30
|
+
c.enable_lcov!
|
|
31
|
+
c.enable_badge!
|
|
32
|
+
end
|
|
33
|
+
RUBY
|
|
34
|
+
|
|
35
|
+
- name: Run tests with coverage
|
|
36
|
+
env:
|
|
37
|
+
RUBYOPT: -rgetcov/auto
|
|
38
|
+
run: bundle exec rake test
|
|
39
|
+
|
|
40
|
+
- name: Archive coverage artifacts
|
|
41
|
+
run: |
|
|
42
|
+
mkdir -p pkg
|
|
43
|
+
(cd coverage && zip -r ../pkg/coverage.zip .) || true
|
|
44
|
+
|
|
45
|
+
- name: Build gem
|
|
46
|
+
run: |
|
|
47
|
+
gem build getcov.gemspec
|
|
48
|
+
mkdir -p pkg
|
|
49
|
+
mv getcov-*.gem pkg/
|
|
50
|
+
|
|
51
|
+
- name: Upload artifacts
|
|
52
|
+
uses: actions/upload-artifact@v4
|
|
53
|
+
with:
|
|
54
|
+
name: build-artifacts
|
|
55
|
+
path: |
|
|
56
|
+
pkg/*.gem
|
|
57
|
+
pkg/coverage.zip
|
|
58
|
+
|
|
59
|
+
publish:
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
needs: test-and-build
|
|
62
|
+
permissions:
|
|
63
|
+
contents: write
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/download-artifact@v4
|
|
66
|
+
with:
|
|
67
|
+
name: build-artifacts
|
|
68
|
+
path: pkg
|
|
69
|
+
|
|
70
|
+
- name: Publish to RubyGems
|
|
71
|
+
env:
|
|
72
|
+
GEM_HOST_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}
|
|
73
|
+
run: |
|
|
74
|
+
ls -l pkg
|
|
75
|
+
gem push pkg/getcov-*.gem
|
|
76
|
+
|
|
77
|
+
- name: Create GitHub Release
|
|
78
|
+
uses: softprops/action-gh-release@v2
|
|
79
|
+
with:
|
|
80
|
+
files: |
|
|
81
|
+
pkg/getcov-*.gem
|
|
82
|
+
pkg/coverage.zip
|
|
83
|
+
env:
|
|
84
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Ruby CI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [ main, master ]
|
|
5
|
+
pull_request:
|
|
6
|
+
branches: [ main, master ]
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
ruby-version: [ '3.1', '3.2', '3.3' ]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- name: Set up Ruby
|
|
17
|
+
uses: ruby/setup-ruby@v1
|
|
18
|
+
with:
|
|
19
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
20
|
+
bundler-cache: true
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: bundle install --jobs 4 --retry 3
|
|
23
|
+
- name: Run tests
|
|
24
|
+
run: bundle exec rake test
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Mridul
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Getcov
|
|
2
|
+
|
|
3
|
+
Getcov is a tiny, dependency‑free Ruby code‑coverage tool inspired by SimpleCov.
|
|
4
|
+
It uses Ruby’s built‑in `Coverage` module to measure which lines are executed during
|
|
5
|
+
your test run and prints concise reports (console + HTML), with exporters and CI helpers.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- CLI wrapper: `getcov <command>` auto-starts coverage
|
|
10
|
+
- Auto loader: `-r getcov/auto`
|
|
11
|
+
- Filters & only/ignore globs
|
|
12
|
+
- Groups + per-group minimum thresholds
|
|
13
|
+
- Track files not loaded (so 0% appears)
|
|
14
|
+
- Minimum coverage gate (exit code 2)
|
|
15
|
+
- Console summary
|
|
16
|
+
- HTML report (index + per-file with **per-line highlighting**), repo links
|
|
17
|
+
- Cobertura XML (`coverage/coverage.xml`) and LCOV (`coverage/lcov.info`)
|
|
18
|
+
- SVG coverage badge (`coverage/coverage.svg`)
|
|
19
|
+
- Branch coverage summary (if Ruby supports branches)
|
|
20
|
+
- Parallel test merging helpers
|
|
21
|
+
- GitHub Actions CI (Ruby 3.1/3.2/3.3 matrix)
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gem build getcov.gemspec
|
|
27
|
+
gem install getcov-0.2.0.gem
|
|
28
|
+
|
|
29
|
+
# recommended: wrap your test command
|
|
30
|
+
getcov bundle exec rspec
|
|
31
|
+
# or
|
|
32
|
+
getcov bundle exec rake test
|
|
33
|
+
|
|
34
|
+
# alternative: just require auto starter
|
|
35
|
+
ruby -r getcov/auto -S rspec
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
Create `getcov.rb` in your project root, or call `Getcov.configure` programmatically.
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
Getcov.configure do |c|
|
|
44
|
+
c.output_dir = 'coverage'
|
|
45
|
+
c.minimum_coverage = 90.0
|
|
46
|
+
|
|
47
|
+
# Groups + thresholds
|
|
48
|
+
c.add_group 'Models', %r{^app/models/}
|
|
49
|
+
c.add_group 'Controllers', %r{^app/controllers/}
|
|
50
|
+
c.add_group_minimum 'Models', 95
|
|
51
|
+
c.add_group_minimum 'Controllers', 90
|
|
52
|
+
|
|
53
|
+
# Selection helpers
|
|
54
|
+
c.only_files 'app/**/*.rb'
|
|
55
|
+
c.ignore_files 'app/admin/**/*', 'vendor/**/*'
|
|
56
|
+
c.add_filter %r{(^|/)spec/}
|
|
57
|
+
|
|
58
|
+
# Track files even if not loaded
|
|
59
|
+
c.track_files 'app/**/*.rb'
|
|
60
|
+
|
|
61
|
+
# Repo links in HTML
|
|
62
|
+
c.repo_url = 'https://github.com/your/repo'
|
|
63
|
+
c.repo_branch = 'main'
|
|
64
|
+
|
|
65
|
+
# Formatters
|
|
66
|
+
c.enable_html!
|
|
67
|
+
c.enable_cobertura!
|
|
68
|
+
c.enable_lcov!
|
|
69
|
+
c.enable_badge!
|
|
70
|
+
|
|
71
|
+
# Parallel merge directory (see below)
|
|
72
|
+
# c.parallel_merge_dir = 'tmp/getcov'
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Parallel runs (simple workflow)
|
|
77
|
+
|
|
78
|
+
Workers:
|
|
79
|
+
```bash
|
|
80
|
+
export GETCOV_ROLE=worker
|
|
81
|
+
# run tests with -r getcov/auto; finalize writes partial JSONs to c.parallel_merge_dir
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Master (after all workers finish):
|
|
85
|
+
```bash
|
|
86
|
+
export GETCOV_ROLE=master
|
|
87
|
+
# run a no-op to trigger finalize which merges partials + writes reports
|
|
88
|
+
ruby -r getcov/auto -e ""
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Alternatively, call `Getcov.merge_raw_results([...])` to combine raw result hashes yourself,
|
|
92
|
+
then build a `Getcov::Result` and run formatters.
|
|
93
|
+
|
|
94
|
+
## Badges
|
|
95
|
+
|
|
96
|
+
After a run, publish `coverage/coverage.svg` wherever you like (README image, docs, etc).
|
|
97
|
+
|
|
98
|
+
## CI
|
|
99
|
+
|
|
100
|
+
GitHub Actions workflow is included in `.github/workflows/ruby.yml` (Ruby 3.1/3.2/3.3).
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT © 2025 Mridul Shukla
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
## Extras
|
|
108
|
+
|
|
109
|
+
### Per-file thresholds
|
|
110
|
+
```ruby
|
|
111
|
+
Getcov.configure do |c|
|
|
112
|
+
c.per_file_minimum = 85.0
|
|
113
|
+
end
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### PR comment / CI summary
|
|
117
|
+
Enable the PR comment formatter to generate `coverage/pr_comment.md` and also write to
|
|
118
|
+
`$GITHUB_STEP_SUMMARY` when running in GitHub Actions:
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
require 'getcov/formatter/pr_comment'
|
|
122
|
+
Getcov.configure { |c| c.add_formatter(Getcov::Formatter::PRComment) }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### JSON summary
|
|
126
|
+
```ruby
|
|
127
|
+
require 'getcov/formatter/summary_json'
|
|
128
|
+
Getcov.configure { |c| c.add_formatter(Getcov::Formatter::SummaryJSON) }
|
|
129
|
+
# => writes coverage/summary.json
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
## Release (RubyGems + GitHub)
|
|
134
|
+
- Set repo secret `RUBYGEMS_API_KEY` (from rubygems.org → API Keys).
|
|
135
|
+
- Bump version in `lib/getcov/version.rb`, commit, then:
|
|
136
|
+
```bash
|
|
137
|
+
git tag vX.Y.Z
|
|
138
|
+
git push origin vX.Y.Z
|
|
139
|
+
```
|
|
140
|
+
- CI will test, generate coverage, publish the gem, and create a Release with artifacts.
|
|
141
|
+
See `RELEASE.md` for details.
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
## PR coverage diffs
|
|
145
|
+
A workflow `.github/workflows/pr_coverage.yml` runs tests on both **HEAD** and **BASE**, then
|
|
146
|
+
generates a Markdown diff (`coverage_diff.md`) and appends it to the GitHub Actions step summary.
|
|
147
|
+
It fails the PR if total coverage drops by ≥ `FAIL_DROP` (default **1.0%**).
|
|
148
|
+
|
|
149
|
+
To tweak the threshold, set an env var on the `Coverage diff` step:
|
|
150
|
+
```yaml
|
|
151
|
+
env:
|
|
152
|
+
FAIL_DROP: "0.5"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## GitHub Pages docs
|
|
156
|
+
Workflow `.github/workflows/pages.yml` runs HTML coverage on pushes to default branch and publishes
|
|
157
|
+
the `coverage/` folder to GitHub Pages.
|
|
158
|
+
Enable Pages in repo settings to serve `index.html`.
|
data/Rakefile
ADDED
data/exe/getcov
ADDED
data/lib/getcov/auto.rb
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Getcov
|
|
3
|
+
class Configuration
|
|
4
|
+
attr_accessor :root, :minimum_coverage, :per_file_minimum, :output_dir,
|
|
5
|
+
:repo_url, :repo_branch, :parallel_merge_dir
|
|
6
|
+
attr_reader :filters, :groups, :tracked_globs, :formatters,
|
|
7
|
+
:group_minimums, :ignore_globs, :only_globs
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@root = Dir.pwd
|
|
11
|
+
@filters = []
|
|
12
|
+
@groups = {}
|
|
13
|
+
@tracked_globs = []
|
|
14
|
+
@formatters = []
|
|
15
|
+
@minimum_coverage = nil
|
|
16
|
+
@per_file_minimum = nil
|
|
17
|
+
@group_minimums = {}
|
|
18
|
+
@output_dir = File.expand_path('coverage', @root)
|
|
19
|
+
@repo_url = nil
|
|
20
|
+
@repo_branch = 'main'
|
|
21
|
+
@ignore_globs = []
|
|
22
|
+
@only_globs = []
|
|
23
|
+
@parallel_merge_dir = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Selection & filtering
|
|
27
|
+
def add_filter(callable_or_pattern = nil, &block)
|
|
28
|
+
predicate =
|
|
29
|
+
if callable_or_pattern.respond_to?(:call)
|
|
30
|
+
callable_or_pattern
|
|
31
|
+
elsif callable_or_pattern
|
|
32
|
+
pattern = callable_or_pattern
|
|
33
|
+
->(path) { path.match?(pattern) }
|
|
34
|
+
elsif block
|
|
35
|
+
block
|
|
36
|
+
else
|
|
37
|
+
raise ArgumentError, 'add_filter requires a callable, pattern, or block'
|
|
38
|
+
end
|
|
39
|
+
@filters << predicate
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ignore_files(*globs) # convenience
|
|
43
|
+
@ignore_globs.concat(globs.flatten)
|
|
44
|
+
end
|
|
45
|
+
alias add_ignore_glob ignore_files
|
|
46
|
+
|
|
47
|
+
def only_files(*globs) # convenience
|
|
48
|
+
@only_globs.concat(globs.flatten)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def filtered?(path)
|
|
52
|
+
unless @only_globs.empty?
|
|
53
|
+
return true unless @only_globs.any? { |g| File.fnmatch?(g, path, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
|
|
54
|
+
end
|
|
55
|
+
if @ignore_globs.any? { |g| File.fnmatch?(g, path, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
|
|
56
|
+
return true
|
|
57
|
+
end
|
|
58
|
+
@filters.any? { |f| f.call(path) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def add_group(name, *patterns)
|
|
62
|
+
@groups[name] ||= []
|
|
63
|
+
@groups[name].concat(patterns.flatten)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def track_files(*globs)
|
|
67
|
+
@tracked_globs.concat(globs.flatten)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def add_formatter(formatter)
|
|
71
|
+
@formatters << formatter
|
|
72
|
+
end
|
|
73
|
+
def enable_html!; require 'getcov/formatter/html'; add_formatter(Getcov::Formatter::HTML); end
|
|
74
|
+
def enable_cobertura!; require 'getcov/formatter/cobertura'; add_formatter(Getcov::Formatter::Cobertura); end
|
|
75
|
+
def enable_lcov!; require 'getcov/formatter/lcov'; add_formatter(Getcov::Formatter::LCOV); end
|
|
76
|
+
def enable_badge!; require 'getcov/formatter/badge'; add_formatter(Getcov::Formatter::Badge); end
|
|
77
|
+
|
|
78
|
+
def add_group_minimum(name, percent)
|
|
79
|
+
@group_minimums[name] = percent.to_f
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def group_minimum(name)
|
|
83
|
+
@group_minimums[name]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def group_for(path)
|
|
87
|
+
@groups.each do |name, patterns|
|
|
88
|
+
return name if patterns.any? { |p| path.match?(p.is_a?(String) ? Regexp.new(Regexp.escape(p)) : p) }
|
|
89
|
+
end
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def tracked_paths
|
|
94
|
+
return [] if @tracked_globs.empty?
|
|
95
|
+
@tracked_globs.flat_map { |g| Dir.glob(File.join(@root, g)) }.uniq
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
|
|
4
|
+
module Getcov
|
|
5
|
+
module Formatter
|
|
6
|
+
class Badge
|
|
7
|
+
def initialize(config) @config = config end
|
|
8
|
+
|
|
9
|
+
def write(result)
|
|
10
|
+
pct = result.total_coverage.round(0)
|
|
11
|
+
color = case pct
|
|
12
|
+
when 90..100 then '#4c1'
|
|
13
|
+
when 75..89 then '#97CA00'
|
|
14
|
+
when 60..74 then '#dfb317'
|
|
15
|
+
else '#e05d44'
|
|
16
|
+
end
|
|
17
|
+
svg = <<~SVG
|
|
18
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="110" height="20" role="img" aria-label="coverage: #{pct}%">
|
|
19
|
+
<linearGradient id="s" x2="0" y2="100%">
|
|
20
|
+
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
|
21
|
+
<stop offset="1" stop-opacity=".1"/>
|
|
22
|
+
</linearGradient>
|
|
23
|
+
<rect rx="3" width="110" height="20" fill="#555"/>
|
|
24
|
+
<rect rx="3" x="60" width="50" height="20" fill="#{color}"/>
|
|
25
|
+
<path fill="#{color}" d="M60 0h4v20h-4z"/>
|
|
26
|
+
<rect rx="3" width="110" height="20" fill="url(#s)"/>
|
|
27
|
+
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
|
|
28
|
+
<text x="30" y="14">coverage</text>
|
|
29
|
+
<text x="85" y="14">#{pct}%</text>
|
|
30
|
+
</g>
|
|
31
|
+
</svg>
|
|
32
|
+
SVG
|
|
33
|
+
FileUtils.mkdir_p(@config.output_dir)
|
|
34
|
+
File.write(File.join(@config.output_dir, 'coverage.svg'), svg)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require 'time'
|
|
4
|
+
|
|
5
|
+
module Getcov
|
|
6
|
+
module Formatter
|
|
7
|
+
class Cobertura
|
|
8
|
+
def initialize(config) @config = config end
|
|
9
|
+
|
|
10
|
+
def write(result)
|
|
11
|
+
out_dir = @config.output_dir
|
|
12
|
+
FileUtils.mkdir_p(out_dir)
|
|
13
|
+
File.write(File.join(out_dir, 'coverage.xml'), render(result))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render(result)
|
|
17
|
+
timestamp = (Time.now.to_f * 1000).to_i
|
|
18
|
+
<<~XML
|
|
19
|
+
<?xml version="1.0"?>
|
|
20
|
+
<coverage lines-valid="#{result.files.sum(&:relevant)}" lines-covered="#{result.files.sum(&:covered)}" line-rate="#{(result.total_coverage/100.0).round(4)}" timestamp="#{timestamp}" version="getcov">
|
|
21
|
+
<packages>
|
|
22
|
+
#{packages_xml(result)} </packages>
|
|
23
|
+
</coverage>
|
|
24
|
+
XML
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def packages_xml(result)
|
|
30
|
+
groups = result.files.group_by { |fr| fr.group || 'Ungrouped' }
|
|
31
|
+
groups.map do |gname, files|
|
|
32
|
+
<<~PKG
|
|
33
|
+
<package name="#{xml_escape(gname)}">
|
|
34
|
+
<classes>
|
|
35
|
+
#{files.map { |fr| class_xml(fr) }.join}
|
|
36
|
+
</classes>
|
|
37
|
+
</package>
|
|
38
|
+
PKG
|
|
39
|
+
end.join
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def class_xml(fr)
|
|
43
|
+
rate = fr.relevant.zero? ? 1.0 : (fr.covered.to_f / fr.relevant)
|
|
44
|
+
<<~CLS
|
|
45
|
+
<class name="#{xml_escape(fr.path)}" filename="#{xml_escape(fr.path)}" line-rate="#{rate.round(4)}">
|
|
46
|
+
<lines>
|
|
47
|
+
#{lines_xml(fr)} </lines>
|
|
48
|
+
</class>
|
|
49
|
+
CLS
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def lines_xml(fr)
|
|
53
|
+
return "" unless fr.hits
|
|
54
|
+
out = String.new
|
|
55
|
+
fr.hits.each_with_index do |hit, idx|
|
|
56
|
+
next if hit.nil?
|
|
57
|
+
ln = idx + 1
|
|
58
|
+
out << %Q{ <line number="#{ln}" hits="#{hit.to_i}"/>
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
out
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def xml_escape(s)
|
|
65
|
+
s.to_s.gsub('&','&').gsub('<','<').gsub('>','>').gsub('"','"').gsub("'", ''')
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|