simplecov-rspec 0.4.4 → 1.0.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 +4 -4
- data/.release-please-manifest.json +1 -1
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +13 -0
- data/README.md +93 -36
- data/lib/simplecov-rspec/list_uncovered_option.rb +79 -0
- data/lib/simplecov-rspec/uncovered_report.rb +186 -0
- data/lib/simplecov-rspec/version.rb +1 -1
- data/lib/simplecov-rspec.rb +207 -184
- data/release-please-config.json +2 -2
- metadata +33 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66e26ea8073a8373f072d8b58d5a11825fc26798d76b9ecf13b225279edfa06f
|
|
4
|
+
data.tar.gz: 59759b57e676fa16bbebd8d423dcf9852a3d4550c3b3864211c937003191c755
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 253e3bbfe90ac2bc476f7e81eb9926f2457fb0b9a1cf4a066d116cdad7c2db00f15a33fa9a4622265580b309eead7b30ccadb0ef6ddc37936a9b52ecb9d0f369
|
|
7
|
+
data.tar.gz: 0b0c8be4624b3f85d3ede8bbb5f64b4a3e71d8287f27fd7a46e102b01318558c1c91b5b59fa2ff1c5505ff65513295da0a6495415b9a056c15fa736f06b63b41
|
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ Changes for each release are listed in this file.
|
|
|
4
4
|
|
|
5
5
|
This project adheres to [Semantic Versioning](https://semver.org/) for its releases.
|
|
6
6
|
|
|
7
|
+
## [1.0.0](https://github.com/main-branch/simplecov-rspec/compare/v0.4.4...v1.0.0) (2026-08-05)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### ⚠ BREAKING CHANGES
|
|
11
|
+
|
|
12
|
+
* `coverage_threshold:` and `list_uncovered_lines:` are removed in favor of `minimum_coverage:` and `list_uncovered:`; the `LIST_UNCOVERED_LINES` env var is removed in favor of `LIST_UNCOVERED`; and `COVERAGE_THRESHOLD` now sets only the line-coverage threshold instead of an overall coverage percentage. Update calls to `SimpleCov::RSpec.start` and any CI environment variables accordingly.
|
|
13
|
+
* drops support for Ruby 3.1 and JRuby 9.4; the minimum is now Ruby 3.2 (or JRuby 10, which reports RUBY_VERSION 3.4).
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* Rely on SimpleCov's own minimum_coverage instead of reimplementing it ([d2fed78](https://github.com/main-branch/simplecov-rspec/commit/d2fed78ff5e5cb2bbf18bc89cb3b301455de0bce))
|
|
18
|
+
* Require Ruby >= 3.2, matching SimpleCov 1.0's own floor ([9452587](https://github.com/main-branch/simplecov-rspec/commit/9452587137e1b698019d4214981f82ed796b9c1d))
|
|
19
|
+
|
|
7
20
|
## [0.4.4](https://github.com/main-branch/simplecov-rspec/compare/v0.4.3...v0.4.4) (2026-04-24)
|
|
8
21
|
|
|
9
22
|
|
data/README.md
CHANGED
|
@@ -8,10 +8,14 @@
|
|
|
8
8
|
Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org)
|
|
9
9
|
[](https://main-branch.slack.com/archives/C07MCM9J72B)
|
|
10
10
|
|
|
11
|
-
`simplecov-rspec` is a Ruby gem that integrates SimpleCov with RSpec
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
`simplecov-rspec` is a Ruby gem that integrates SimpleCov with RSpec. SimpleCov
|
|
12
|
+
(`>= 1.0`) already enforces `minimum_coverage` for line, branch, and method coverage
|
|
13
|
+
and fails the build when a threshold is missed. This gem layers three things on top
|
|
14
|
+
that SimpleCov doesn't do on its own:
|
|
15
|
+
|
|
16
|
+
1. Suppresses coverage failures when RSpec is run in dry-run mode (e.g. from an IDE).
|
|
17
|
+
2. Lists (or summarizes) the individual uncovered lines, branches, and methods.
|
|
18
|
+
3. Lets all of the above be overridden from the environment, for CI.
|
|
15
19
|
|
|
16
20
|
When `simplecov-rspec` is used, RSpec will report an error if the percent of test
|
|
17
21
|
coverage falls below a defined threshold:
|
|
@@ -19,10 +23,10 @@ coverage falls below a defined threshold:
|
|
|
19
23
|
```text
|
|
20
24
|
Coverage report generated for RSpec to /Projects/example_project/coverage. 284 / 286 LOC (99.3%) covered.
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
Line coverage (99.3%) is below the expected minimum coverage (100.00%).
|
|
23
27
|
```
|
|
24
28
|
|
|
25
|
-
If configured to list the
|
|
29
|
+
If configured to list the items that were not covered by tests, RSpec will additionally output:
|
|
26
30
|
|
|
27
31
|
```text
|
|
28
32
|
2 lines are not covered by tests:
|
|
@@ -30,16 +34,18 @@ If configured to list the lines that were not covered by tests, RSpec will addit
|
|
|
30
34
|
./lib/example_project.rb:75
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
- [Installation](#installation)
|
|
38
|
+
- [Getting started](#getting-started)
|
|
39
|
+
- [Basic setup](#basic-setup)
|
|
40
|
+
- [Listing uncovered items](#listing-uncovered-items)
|
|
41
|
+
- [Configuration block](#configuration-block)
|
|
42
|
+
- [Configuration from environment variables](#configuration-from-environment-variables)
|
|
43
|
+
- [Development](#development)
|
|
44
|
+
- [Contributing](#contributing)
|
|
45
|
+
- [Commit message guidelines](#commit-message-guidelines)
|
|
46
|
+
- [Pull request guidelines](#pull-request-guidelines)
|
|
47
|
+
- [License](#license)
|
|
48
|
+
- [Code of conduct](#code-of-conduct)
|
|
43
49
|
|
|
44
50
|
## Installation
|
|
45
51
|
|
|
@@ -48,13 +54,13 @@ To install the gem, add to the following line to your application's gemspec OR G
|
|
|
48
54
|
gemspec:
|
|
49
55
|
|
|
50
56
|
```ruby
|
|
51
|
-
spec.add_development_dependency "simplecov-rspec", '~> 0
|
|
57
|
+
spec.add_development_dependency "simplecov-rspec", '~> 1.0'
|
|
52
58
|
```
|
|
53
59
|
|
|
54
60
|
Gemfile:
|
|
55
61
|
|
|
56
62
|
```ruby
|
|
57
|
-
gem "simplecov-rspec", "~> 0
|
|
63
|
+
gem "simplecov-rspec", "~> 1.0", groups: [:development, :test]
|
|
58
64
|
```
|
|
59
65
|
|
|
60
66
|
and then run `bundle install`
|
|
@@ -102,54 +108,105 @@ This is equivalent to starting with the following options:
|
|
|
102
108
|
|
|
103
109
|
```ruby
|
|
104
110
|
SimpleCov::RSpec.start(
|
|
105
|
-
|
|
111
|
+
minimum_coverage: { line: 100 },
|
|
106
112
|
fail_on_low_coverage: true,
|
|
107
|
-
|
|
113
|
+
list_uncovered: false,
|
|
114
|
+
list_uncovered_detail: true
|
|
108
115
|
)
|
|
109
116
|
```
|
|
110
117
|
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
`minimum_coverage` is the minimum percent of lines (and, optionally, branches and
|
|
119
|
+
methods) covered by tests, enforced by SimpleCov itself.
|
|
113
120
|
|
|
114
|
-
To
|
|
121
|
+
To require less than 100% line coverage:
|
|
115
122
|
|
|
116
123
|
```ruby
|
|
117
|
-
SimpleCov::RSpec.start(
|
|
124
|
+
SimpleCov::RSpec.start(minimum_coverage: 90)
|
|
118
125
|
```
|
|
119
126
|
|
|
127
|
+
To also require branch (and/or method) coverage, pass a Hash. Any criterion named
|
|
128
|
+
here is automatically enabled via `SimpleCov.enable_coverage`:
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
SimpleCov::RSpec.start(minimum_coverage: { line: 100, branch: 90 })
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Listing uncovered items
|
|
135
|
+
|
|
136
|
+
To list the individual lines, branches, and/or methods that are not covered, set
|
|
137
|
+
`list_uncovered`. It accepts `:all`, a single criterion, or an Array of criteria —
|
|
138
|
+
independent of what `minimum_coverage` enforces:
|
|
139
|
+
|
|
140
|
+
```ruby
|
|
141
|
+
SimpleCov::RSpec.start(minimum_coverage: { line: 100, branch: 90 }, list_uncovered: :all)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
1 line is not covered by tests:
|
|
146
|
+
./lib/example_project.rb:74
|
|
147
|
+
|
|
148
|
+
1 branch is not covered by tests:
|
|
149
|
+
./lib/example_project.rb:82 (else branch)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
For a quieter CI log, set `list_uncovered_detail: false` to print only the count per
|
|
153
|
+
criterion, along with a hint on how to see the details:
|
|
154
|
+
|
|
155
|
+
```ruby
|
|
156
|
+
SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_detail: false)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
```text
|
|
160
|
+
2 lines are not covered by tests.
|
|
161
|
+
1 branch is not covered by tests.
|
|
162
|
+
|
|
163
|
+
Run with LIST_UNCOVERED_DETAIL=true to see the uncovered lines and branches.
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Configuration block
|
|
167
|
+
|
|
120
168
|
A configuration block can be given to the `start` method to further configure
|
|
121
169
|
SimpleCov:
|
|
122
170
|
|
|
123
171
|
```ruby
|
|
124
172
|
# Initialize SimpleCov with a specific formatter
|
|
125
|
-
SimpleCov::RSpec.start { formatter
|
|
173
|
+
SimpleCov::RSpec.start { formatter SimpleCov::Formatter::LcovFormatter }
|
|
126
174
|
```
|
|
127
175
|
|
|
128
|
-
This block is passed on to `SimpleCov
|
|
129
|
-
SimpleCov](https://github.com/simplecov-ruby/simplecov?tab=readme-ov-file#
|
|
176
|
+
This block is passed on to `SimpleCov.start`. See [Configuring
|
|
177
|
+
SimpleCov](https://github.com/simplecov-ruby/simplecov?tab=readme-ov-file#configuration)
|
|
130
178
|
for details.
|
|
131
179
|
|
|
132
180
|
### Configuration from environment variables
|
|
133
181
|
|
|
134
182
|
Environment variables can be used to configure `simplecov-rspec`. These environment
|
|
135
|
-
variables take
|
|
136
|
-
|
|
137
|
-
* **`COVERAGE_THRESHOLD`**: Sets the minimum coverage threshold (0-100). Overrides
|
|
138
|
-
`
|
|
183
|
+
variables take precedence over the values passed to `SimpleCov::RSpec.start`.
|
|
184
|
+
|
|
185
|
+
* **`COVERAGE_THRESHOLD`**: Sets the minimum line coverage threshold (0-100). Overrides
|
|
186
|
+
`minimum_coverage[:line]`.
|
|
187
|
+
* **`COVERAGE_THRESHOLD_BRANCH`**: Sets the minimum branch coverage threshold (0-100), and
|
|
188
|
+
enables branch coverage. Overrides `minimum_coverage[:branch]`.
|
|
189
|
+
* **`COVERAGE_THRESHOLD_METHOD`**: Sets the minimum method coverage threshold (0-100), and
|
|
190
|
+
enables method coverage. Overrides `minimum_coverage[:method]`.
|
|
139
191
|
* **`FAIL_ON_LOW_COVERAGE`**: Controls whether tests fail if coverage is below the threshold.
|
|
140
192
|
Set to 'true', 'yes', 'on', or '1' (case insensitive) to enable.
|
|
141
|
-
* **`
|
|
142
|
-
'yes', 'on', or '1'
|
|
193
|
+
* **`LIST_UNCOVERED`**: Controls which criteria to list uncovered items for. Set to 'all',
|
|
194
|
+
'true', 'yes', 'on', or '1' to report every criterion; 'false', 'no', 'off', or '0' to
|
|
195
|
+
report none; or a comma-separated list, e.g. `line,branch`.
|
|
196
|
+
* **`LIST_UNCOVERED_DETAIL`**: Controls whether uncovered items are listed individually, or
|
|
197
|
+
just summarized as a count per criterion. Set to 'true', 'yes', 'on', or '1' (case
|
|
198
|
+
insensitive) to show individual items.
|
|
143
199
|
|
|
144
200
|
For example, here is a bash script to run tests in an infinite loop while writing
|
|
145
201
|
test output to `fail.txt`:
|
|
146
202
|
|
|
147
203
|
```bash
|
|
148
|
-
while true; do
|
|
204
|
+
while true; do FAIL_ON_LOW_COVERAGE=false rspec >> fail.txt; done
|
|
149
205
|
```
|
|
150
206
|
|
|
151
|
-
In a CI system, you might want to set `
|
|
152
|
-
|
|
207
|
+
In a CI system, you might want to set `LIST_UNCOVERED=all` in order to list uncovered
|
|
208
|
+
lines, branches, and methods on a platform other than the one you use for local
|
|
209
|
+
development.
|
|
153
210
|
|
|
154
211
|
## Development
|
|
155
212
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class RSpec
|
|
5
|
+
# Resolves the `list_uncovered:` option (plus its `LIST_UNCOVERED` ENV override)
|
|
6
|
+
# into a normalized Array<Symbol>, in a fixed line/branch/method order.
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
#
|
|
10
|
+
module ListUncoveredOption
|
|
11
|
+
# The coverage criteria this gem knows how to report on
|
|
12
|
+
ALL_CRITERIA = %i[line branch method].freeze
|
|
13
|
+
|
|
14
|
+
# LIST_UNCOVERED environment variable values that mean "every criterion"
|
|
15
|
+
ALL_ENV_VALUES = %w[yes on true 1 all].freeze
|
|
16
|
+
|
|
17
|
+
# LIST_UNCOVERED environment variable values that mean "no criteria"
|
|
18
|
+
NONE_ENV_VALUES = %w[false no off 0].freeze
|
|
19
|
+
|
|
20
|
+
module_function
|
|
21
|
+
|
|
22
|
+
# Resolve the effective criteria, applying the ENV override if present
|
|
23
|
+
# @param value [false, :all, Symbol, Array<Symbol>] the `list_uncovered:` argument
|
|
24
|
+
# @param env [Hash] the environment variables
|
|
25
|
+
# @param env_var [String] the ENV var name that overrides `value`
|
|
26
|
+
# @return [Array<Symbol>]
|
|
27
|
+
# @raise [ArgumentError] if value is not one of the accepted forms, or names an unknown criterion
|
|
28
|
+
# @example
|
|
29
|
+
# ListUncoveredOption.resolve(:all, env: {}, env_var: 'LIST_UNCOVERED') # => [:line, :branch, :method]
|
|
30
|
+
def resolve(value, env:, env_var:)
|
|
31
|
+
value = from_env(env.fetch(env_var)) if env.key?(env_var)
|
|
32
|
+
ALL_CRITERIA & normalize(value)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Parse a raw LIST_UNCOVERED environment variable value
|
|
36
|
+
# @param raw [String] the raw LIST_UNCOVERED environment variable value
|
|
37
|
+
# @return [false, :all, Array<Symbol>]
|
|
38
|
+
# @example
|
|
39
|
+
# ListUncoveredOption.from_env('line,branch') # => [:line, :branch]
|
|
40
|
+
def from_env(raw)
|
|
41
|
+
raw = raw.strip.downcase
|
|
42
|
+
return false if raw.empty? || NONE_ENV_VALUES.include?(raw)
|
|
43
|
+
return :all if ALL_ENV_VALUES.include?(raw)
|
|
44
|
+
|
|
45
|
+
raw.split(',').map { |criterion| criterion.strip.to_sym }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Normalize a `list_uncovered:`-style value into an Array of Symbols
|
|
49
|
+
# @param value [false, true, :all, Symbol, Array<Symbol>]
|
|
50
|
+
# @return [Array<Symbol>]
|
|
51
|
+
# @example
|
|
52
|
+
# ListUncoveredOption.normalize(:branch) # => [:branch]
|
|
53
|
+
def normalize(value)
|
|
54
|
+
case value
|
|
55
|
+
when nil, false then []
|
|
56
|
+
when :all then ALL_CRITERIA
|
|
57
|
+
when Symbol then validate([value])
|
|
58
|
+
when Array then validate(value)
|
|
59
|
+
else
|
|
60
|
+
raise ArgumentError,
|
|
61
|
+
"list_uncovered must be false, :all, a Symbol, or an Array of Symbols; got #{value.inspect}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Raise unless every given criterion is one this gem knows how to report on
|
|
66
|
+
# @param criteria [Array<Symbol>]
|
|
67
|
+
# @return [Array<Symbol>]
|
|
68
|
+
# @raise [ArgumentError] if criteria contains anything outside ALL_CRITERIA
|
|
69
|
+
# @example
|
|
70
|
+
# ListUncoveredOption.validate([:line]) # => [:line]
|
|
71
|
+
def validate(criteria)
|
|
72
|
+
invalid = criteria - ALL_CRITERIA
|
|
73
|
+
return criteria if invalid.empty?
|
|
74
|
+
|
|
75
|
+
raise ArgumentError, "Unknown coverage criterion #{invalid.inspect}; must be one of #{ALL_CRITERIA.inspect}"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class RSpec
|
|
5
|
+
# Formats the "uncovered lines/branches/methods" report printed after a run: either
|
|
6
|
+
# a full listing of every uncovered item, or a per-criterion count with a hint on
|
|
7
|
+
# how to see the details.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
10
|
+
#
|
|
11
|
+
class UncoveredReport
|
|
12
|
+
# Maps a coverage criterion to its [singular, plural] noun, for report formatting
|
|
13
|
+
# @api private
|
|
14
|
+
CRITERION_LABELS = {
|
|
15
|
+
line: %w[line lines],
|
|
16
|
+
branch: %w[branch branches],
|
|
17
|
+
method: %w[method methods]
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
# Build a report for the given result, criteria, and detail level
|
|
21
|
+
# @param result [SimpleCov::Result] the SimpleCov result to report on
|
|
22
|
+
# @param criteria [Array<Symbol>] which criteria (:line, :branch, :method) to report
|
|
23
|
+
# @param detail [Boolean] list individual items, or just a count per criterion
|
|
24
|
+
# @param detail_env_var [String] the ENV var name to suggest for switching to detail
|
|
25
|
+
# @example
|
|
26
|
+
# UncoveredReport.new(result: SimpleCov.result, criteria: [:line], detail: true, detail_env_var: 'X')
|
|
27
|
+
def initialize(result:, criteria:, detail:, detail_env_var:)
|
|
28
|
+
@result = result
|
|
29
|
+
@criteria = criteria
|
|
30
|
+
@detail = detail
|
|
31
|
+
@detail_env_var = detail_env_var
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The formatted report text, or an empty string if there is nothing to report
|
|
35
|
+
# @return [String]
|
|
36
|
+
def to_s = detail ? detailed_text : summary_text
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
# The SimpleCov result being reported on
|
|
41
|
+
# @return [SimpleCov::Result]
|
|
42
|
+
# @api private
|
|
43
|
+
attr_reader :result
|
|
44
|
+
|
|
45
|
+
# Which criteria (:line, :branch, :method) to report on
|
|
46
|
+
# @return [Array<Symbol>]
|
|
47
|
+
# @api private
|
|
48
|
+
attr_reader :criteria
|
|
49
|
+
|
|
50
|
+
# Whether to list individual items, or just a count per criterion
|
|
51
|
+
# @return [Boolean]
|
|
52
|
+
# @api private
|
|
53
|
+
attr_reader :detail
|
|
54
|
+
|
|
55
|
+
# The ENV var name to suggest for switching to detail
|
|
56
|
+
# @return [String]
|
|
57
|
+
# @api private
|
|
58
|
+
attr_reader :detail_env_var
|
|
59
|
+
|
|
60
|
+
# The full listing, one blank-line-separated section per criterion
|
|
61
|
+
# @return [String]
|
|
62
|
+
def detailed_text
|
|
63
|
+
criteria.filter_map { |criterion| section(criterion) }.join("\n\n")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# A criterion's report section, or nil when nothing is uncovered
|
|
67
|
+
# @param criterion [Symbol]
|
|
68
|
+
# @return [String, nil]
|
|
69
|
+
def section(criterion)
|
|
70
|
+
items = uncovered_items(criterion)
|
|
71
|
+
return nil if items.empty?
|
|
72
|
+
|
|
73
|
+
["#{header(criterion, items.count)}:", *items.map { |item| " #{item}" }].join("\n")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The per-criterion counts, plus a hint on how to see the details
|
|
77
|
+
# @return [String]
|
|
78
|
+
def summary_text
|
|
79
|
+
summarized = criteria.filter_map do |criterion|
|
|
80
|
+
count = uncovered_count(criterion)
|
|
81
|
+
[criterion, count] if count.positive?
|
|
82
|
+
end
|
|
83
|
+
return '' if summarized.empty?
|
|
84
|
+
|
|
85
|
+
counts = summarized.map { |criterion, count| "#{header(criterion, count)}." }.join("\n")
|
|
86
|
+
"#{counts}\n\nRun with #{detail_env_var}=true to see the uncovered #{noun_list(summarized.map(&:first))}."
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# The "N lines are not covered by tests" header for a criterion
|
|
90
|
+
# @param criterion [Symbol]
|
|
91
|
+
# @param count [Integer]
|
|
92
|
+
# @return [String]
|
|
93
|
+
def header(criterion, count)
|
|
94
|
+
singular, plural = CRITERION_LABELS.fetch(criterion)
|
|
95
|
+
"#{count} #{pluralize(count, "#{singular} is", "#{plural} are")} not covered by tests"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# The formatted, uncovered items for a single criterion, across all files
|
|
99
|
+
# @param criterion [Symbol]
|
|
100
|
+
# @return [Array<String>]
|
|
101
|
+
def uncovered_items(criterion)
|
|
102
|
+
result.files.flat_map { |file| items_for(file, criterion) }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# The count of uncovered items for a single criterion across all files
|
|
106
|
+
#
|
|
107
|
+
# @param criterion [Symbol]
|
|
108
|
+
# @return [Integer]
|
|
109
|
+
def uncovered_count(criterion)
|
|
110
|
+
result.files.sum { |file| count_for(file, criterion) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# The formatted, uncovered items of one criterion within a single file
|
|
114
|
+
# @param file [SimpleCov::SourceFile]
|
|
115
|
+
# @param criterion [Symbol]
|
|
116
|
+
# @return [Array<String>]
|
|
117
|
+
def items_for(file, criterion) = send(:"#{criterion}_items", file)
|
|
118
|
+
|
|
119
|
+
# The count of uncovered items of one criterion within a single file
|
|
120
|
+
# @param file [SimpleCov::SourceFile]
|
|
121
|
+
# @param criterion [Symbol]
|
|
122
|
+
# @return [Integer]
|
|
123
|
+
def count_for(file, criterion) = send(:"#{criterion}_count", file)
|
|
124
|
+
|
|
125
|
+
# The formatted, uncovered lines within a single file
|
|
126
|
+
# @param file [SimpleCov::SourceFile]
|
|
127
|
+
# @return [Array<String>]
|
|
128
|
+
def line_items(file) = file.missed_lines.map { |line| "#{project_path(file)}:#{line.number}" }
|
|
129
|
+
|
|
130
|
+
# The formatted, uncovered branches within a single file
|
|
131
|
+
# @param file [SimpleCov::SourceFile]
|
|
132
|
+
# @return [Array<String>]
|
|
133
|
+
def branch_items(file) = file.missed_branches.map { |branch| branch_text(file, branch) }
|
|
134
|
+
|
|
135
|
+
# The formatted, uncovered methods within a single file
|
|
136
|
+
# @param file [SimpleCov::SourceFile]
|
|
137
|
+
# @return [Array<String>]
|
|
138
|
+
def method_items(file)
|
|
139
|
+
file.missed_methods.map { |method| "#{project_path(file)}:#{method.start_line} #{method}" }
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# The count of uncovered lines within a single file
|
|
143
|
+
# @param file [SimpleCov::SourceFile]
|
|
144
|
+
# @return [Integer]
|
|
145
|
+
def line_count(file) = file.missed_lines.count
|
|
146
|
+
|
|
147
|
+
# The count of uncovered branches within a single file
|
|
148
|
+
# @param file [SimpleCov::SourceFile]
|
|
149
|
+
# @return [Integer]
|
|
150
|
+
def branch_count(file) = file.missed_branches.count
|
|
151
|
+
|
|
152
|
+
# The count of uncovered methods within a single file
|
|
153
|
+
# @param file [SimpleCov::SourceFile]
|
|
154
|
+
# @return [Integer]
|
|
155
|
+
def method_count(file) = file.missed_methods.count
|
|
156
|
+
|
|
157
|
+
# A single formatted, uncovered branch
|
|
158
|
+
# @param file [SimpleCov::SourceFile]
|
|
159
|
+
# @param branch [SimpleCov::SourceFile::Branch]
|
|
160
|
+
# @return [String]
|
|
161
|
+
def branch_text(file, branch) = "#{project_path(file)}:#{branch.report_line} (#{branch.type} branch)"
|
|
162
|
+
|
|
163
|
+
# The path to a source file, relative to the project root
|
|
164
|
+
# @param file [SimpleCov::SourceFile]
|
|
165
|
+
# @return [String]
|
|
166
|
+
def project_path(file) = File.join('.', file.project_filename)
|
|
167
|
+
|
|
168
|
+
# Join plural nouns into a friendly list, e.g. "lines and branches"
|
|
169
|
+
# @param criteria_list [Array<Symbol>]
|
|
170
|
+
# @return [String]
|
|
171
|
+
def noun_list(criteria_list)
|
|
172
|
+
nouns = criteria_list.map { |criterion| CRITERION_LABELS.fetch(criterion).last }
|
|
173
|
+
return nouns.first if nouns.size == 1
|
|
174
|
+
|
|
175
|
+
"#{nouns[0..-2].join(', ')} and #{nouns.last}"
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Return the singular or plural form of a word based on the count
|
|
179
|
+
# @param count [Integer] the count
|
|
180
|
+
# @param singular [String] the singular form of the phrase
|
|
181
|
+
# @param plural [String] the plural form of the phrase
|
|
182
|
+
# @return [String]
|
|
183
|
+
def pluralize(count, singular, plural) = count == 1 ? singular : plural
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
data/lib/simplecov-rspec.rb
CHANGED
|
@@ -1,55 +1,59 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'simplecov'
|
|
4
|
+
require_relative 'simplecov-rspec/list_uncovered_option'
|
|
5
|
+
require_relative 'simplecov-rspec/uncovered_report'
|
|
4
6
|
|
|
5
7
|
# SimpleCov namespace
|
|
6
8
|
module SimpleCov
|
|
7
|
-
# Configure SimpleCov to
|
|
9
|
+
# Configure SimpleCov to enforce coverage thresholds for RSpec, on top of what
|
|
10
|
+
# SimpleCov itself already provides.
|
|
8
11
|
#
|
|
9
|
-
#
|
|
12
|
+
# SimpleCov (>= 1.0) can already enforce `minimum_coverage` for line, branch, and
|
|
13
|
+
# method coverage, and will exit with a non-zero status when a threshold is missed.
|
|
14
|
+
# This gem layers three things SimpleCov doesn't do on its own:
|
|
10
15
|
#
|
|
11
|
-
# 1.
|
|
12
|
-
#
|
|
13
|
-
#
|
|
16
|
+
# 1. Suppresses coverage failures when RSpec is run in dry-run mode (e.g. from an IDE).
|
|
17
|
+
# 2. Lists (or summarizes) the individual uncovered lines, branches, and methods.
|
|
18
|
+
# 3. Lets all of the above be overridden from the environment, for CI.
|
|
14
19
|
#
|
|
15
|
-
# Simply add the line `SimpleCov::RSpec.start` in place of `SimpleCov
|
|
20
|
+
# Simply add the line `SimpleCov::RSpec.start` in place of `SimpleCov.start` in
|
|
16
21
|
# the project's `spec_helper.rb`. This line must appear before the project is
|
|
17
22
|
# required.
|
|
18
23
|
#
|
|
19
|
-
# @example Initialize SimpleCov with defaults
|
|
24
|
+
# @example Initialize SimpleCov with defaults (100% line coverage required)
|
|
20
25
|
# SimpleCov::RSpec.start
|
|
21
26
|
#
|
|
22
|
-
# @example
|
|
23
|
-
# SimpleCov::RSpec.start(
|
|
27
|
+
# @example Require 100% line coverage and 90% branch coverage
|
|
28
|
+
# SimpleCov::RSpec.start(minimum_coverage: { line: 100, branch: 90 })
|
|
24
29
|
#
|
|
25
|
-
# @example
|
|
26
|
-
# SimpleCov::RSpec.start {
|
|
30
|
+
# @example List every uncovered line, branch, and method when coverage is incomplete
|
|
31
|
+
# SimpleCov::RSpec.start(minimum_coverage: { line: 100, branch: 90 }, list_uncovered: :all)
|
|
27
32
|
#
|
|
28
|
-
# @example
|
|
29
|
-
#
|
|
33
|
+
# @example Report only counts, with a hint on how to see the details
|
|
34
|
+
# SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_detail: false)
|
|
30
35
|
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# @return [Hash]
|
|
34
|
-
# @api private
|
|
35
|
-
# @private
|
|
36
|
-
#
|
|
37
|
-
# @!attribute [r] simplecov_module
|
|
38
|
-
# The SimpleCov module (default: ::SimpleCov)
|
|
39
|
-
# @return [Module]
|
|
40
|
-
# @api private
|
|
41
|
-
# @private
|
|
42
|
-
#
|
|
43
|
-
# @!attribute [r] start_config_block
|
|
44
|
-
# A configuration block to pass to `SimpleCov.start`
|
|
45
|
-
# @return [Proc]
|
|
46
|
-
# @api private
|
|
47
|
-
# @private
|
|
36
|
+
# @example Pass a configuration block to SimpleCov.start
|
|
37
|
+
# SimpleCov::RSpec.start { formatter SimpleCov::Formatter::LcovFormatter }
|
|
48
38
|
#
|
|
49
39
|
# @api public
|
|
50
40
|
#
|
|
51
41
|
class RSpec
|
|
52
|
-
#
|
|
42
|
+
# @!attribute [r] env
|
|
43
|
+
# The environment variables consulted for overrides
|
|
44
|
+
# @return [Hash]
|
|
45
|
+
# @api private
|
|
46
|
+
#
|
|
47
|
+
# @!attribute [r] simplecov_module
|
|
48
|
+
# The SimpleCov module being configured
|
|
49
|
+
# @return [Module]
|
|
50
|
+
# @api private
|
|
51
|
+
#
|
|
52
|
+
# @!attribute [r] start_config_block
|
|
53
|
+
# A configuration block passed through to `SimpleCov.start`
|
|
54
|
+
# @return [Proc, nil]
|
|
55
|
+
# @api private
|
|
56
|
+
#
|
|
53
57
|
|
|
54
58
|
# Configure and start SimpleCov for RSpec
|
|
55
59
|
#
|
|
@@ -60,128 +64,153 @@ module SimpleCov
|
|
|
60
64
|
#
|
|
61
65
|
# @api public
|
|
62
66
|
#
|
|
63
|
-
# @overload start(
|
|
67
|
+
# @overload start(minimum_coverage: { line: 100 }, fail_on_low_coverage: true, list_uncovered: false, list_uncovered_detail: true, rspec_dry_run: ::RSpec.configuration.dry_run?, env: ENV, &start_config_block) # rubocop:disable Layout/LineLength
|
|
64
68
|
#
|
|
65
|
-
# @param
|
|
69
|
+
# @param minimum_coverage [Integer, Hash] the minimum coverage threshold (default: `{ line: 100 }`)
|
|
66
70
|
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
71
|
+
# An Integer sets the line coverage threshold. A Hash sets a threshold per
|
|
72
|
+
# criterion, e.g. `{ line: 100, branch: 90, method: 100 }`. Passed straight
|
|
73
|
+
# through to `SimpleCov.minimum_coverage`; any criterion given here is
|
|
74
|
+
# automatically enabled via `SimpleCov.enable_coverage`.
|
|
69
75
|
#
|
|
70
|
-
# @param fail_on_low_coverage [Boolean] whether to fail if
|
|
76
|
+
# @param fail_on_low_coverage [Boolean] whether to fail if coverage is below the threshold (default: true)
|
|
71
77
|
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# to 'true', 'yes', 'on', or '1' will cause this setting to be `false`
|
|
75
|
-
# (the logic is inverted). Any other value will cause this seetting to
|
|
76
|
-
# be `true`.
|
|
78
|
+
# When `false` (or when RSpec is in dry-run mode), `SimpleCov.minimum_coverage`
|
|
79
|
+
# is never set, so SimpleCov will not fail the build regardless of coverage.
|
|
77
80
|
#
|
|
78
|
-
#
|
|
81
|
+
# Read from the `FAIL_ON_LOW_COVERAGE` environment variable if set: `true`,
|
|
82
|
+
# `yes`, `on`, or `1` (case-insensitive) enables it; anything else disables it.
|
|
79
83
|
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
84
|
+
# @param list_uncovered [false, :all, Symbol, Array<Symbol>] which coverage criteria to
|
|
85
|
+
# list uncovered items for (default: false)
|
|
82
86
|
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# other value will cause this setting to be `false`.
|
|
87
|
+
# `false` reports nothing. `:all` reports line, branch, and method. A Symbol or
|
|
88
|
+
# Array of Symbols (`:line`, `:branch`, `:method`) reports just those criteria,
|
|
89
|
+
# independent of what `minimum_coverage` enforces.
|
|
87
90
|
#
|
|
88
|
-
#
|
|
91
|
+
# Read from the `LIST_UNCOVERED` environment variable if set: `all`, `true`,
|
|
92
|
+
# `yes`, `on`, or `1` means every criterion; `false`, `no`, `off`, or `0` means
|
|
93
|
+
# none; otherwise a comma-separated list of criteria, e.g. `line,branch`.
|
|
89
94
|
#
|
|
90
|
-
# @param
|
|
95
|
+
# @param list_uncovered_detail [Boolean] list individual items, or just a count (default: true)
|
|
91
96
|
#
|
|
92
|
-
#
|
|
97
|
+
# When `false`, only the count of uncovered items per criterion is printed,
|
|
98
|
+
# followed by a hint on how to see the details.
|
|
93
99
|
#
|
|
94
|
-
#
|
|
100
|
+
# Read from the `LIST_UNCOVERED_DETAIL` environment variable if set: `true`,
|
|
101
|
+
# `yes`, `on`, or `1` (case-insensitive) shows details; anything else summarizes.
|
|
95
102
|
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
103
|
+
# @param start_config_block [Proc] a configuration block to pass to `SimpleCov.start` (default: nil)
|
|
104
|
+
#
|
|
105
|
+
# @param rspec_dry_run [Boolean] whether the rspec run is a dry run
|
|
106
|
+
#
|
|
107
|
+
# Typically not set by the user. If RSpec is being run in dry run mode, test
|
|
108
|
+
# coverage under the threshold will not fail the build. This allows test
|
|
109
|
+
# coverage to be run in a dry run by an IDE so it can report failed tests and
|
|
110
|
+
# coverage without reporting that the entire RSpec run has failed.
|
|
99
111
|
#
|
|
100
112
|
# @param simplecov_module [Module] the SimpleCov module (default: ::SimpleCov)
|
|
101
113
|
#
|
|
102
114
|
# Typically not set by the user. Used for this gem's unit testing.
|
|
103
115
|
#
|
|
104
|
-
# Allows the SimpleCov module to be mocked.
|
|
105
|
-
#
|
|
106
116
|
# @param env [Hash] the environment variables (default: ENV)
|
|
107
117
|
#
|
|
108
118
|
# Typically not set by the user. Used for this gem's unit testing.
|
|
109
119
|
#
|
|
110
120
|
# @example Initialize SimpleCov with a test coverage threshold other than 100%
|
|
111
|
-
# SimpleCov::RSpec.start(
|
|
121
|
+
# SimpleCov::RSpec.start(minimum_coverage: 90)
|
|
112
122
|
#
|
|
113
123
|
# @example Initialize SimpleCov to not fail the test run if the coverage is below the threshold
|
|
114
124
|
# SimpleCov::RSpec.start(fail_on_low_coverage: false)
|
|
115
125
|
#
|
|
116
126
|
# # OR use an environment variable to override the default
|
|
117
|
-
#
|
|
118
|
-
# SimpleCov::RSpec.start
|
|
119
|
-
#
|
|
120
|
-
# # OR use an environment variable to override the default from the rspec command line
|
|
121
|
-
# FAIL_ON_LOW_COVERAGE=TRUE rspec
|
|
127
|
+
# FAIL_ON_LOW_COVERAGE=true rspec
|
|
122
128
|
#
|
|
123
|
-
# @example Initialize SimpleCov to list the lines not covered by tests
|
|
124
|
-
# SimpleCov::RSpec.start(
|
|
129
|
+
# @example Initialize SimpleCov to list the lines, branches, and methods not covered by tests
|
|
130
|
+
# SimpleCov::RSpec.start(list_uncovered: :all)
|
|
125
131
|
#
|
|
126
132
|
# # OR use an environment variable to override the default
|
|
127
|
-
#
|
|
128
|
-
# SimpleCov::RSpec.start
|
|
129
|
-
#
|
|
130
|
-
# # OR use an environment variable to override the default from the rspec command line
|
|
131
|
-
# LIST_UNCOVERED_LINES=TRUE rspec
|
|
133
|
+
# LIST_UNCOVERED=all rspec
|
|
132
134
|
#
|
|
133
135
|
def self.start(...) = new(...).send(:start)
|
|
134
136
|
|
|
135
|
-
#
|
|
136
|
-
|
|
137
|
-
# Environment variable to override coverage_threshold
|
|
137
|
+
# Environment variable to override minimum_coverage[:line]
|
|
138
138
|
# @api private
|
|
139
139
|
# @private
|
|
140
140
|
COVERAGE_THRESHOLD = 'COVERAGE_THRESHOLD'
|
|
141
141
|
|
|
142
|
+
# Environment variable to override minimum_coverage[:branch]
|
|
143
|
+
# @api private
|
|
144
|
+
# @private
|
|
145
|
+
COVERAGE_THRESHOLD_BRANCH = 'COVERAGE_THRESHOLD_BRANCH'
|
|
146
|
+
|
|
147
|
+
# Environment variable to override minimum_coverage[:method]
|
|
148
|
+
# @api private
|
|
149
|
+
# @private
|
|
150
|
+
COVERAGE_THRESHOLD_METHOD = 'COVERAGE_THRESHOLD_METHOD'
|
|
151
|
+
|
|
142
152
|
# Environment variable to override fail_on_low_coverage
|
|
143
153
|
# @api private
|
|
144
154
|
# @private
|
|
145
155
|
FAIL_ON_LOW_COVERAGE = 'FAIL_ON_LOW_COVERAGE'
|
|
146
156
|
|
|
147
|
-
# Environment variable to override
|
|
157
|
+
# Environment variable to override list_uncovered
|
|
158
|
+
# @api private
|
|
159
|
+
# @private
|
|
160
|
+
LIST_UNCOVERED = 'LIST_UNCOVERED'
|
|
161
|
+
|
|
162
|
+
# Environment variable to override list_uncovered_detail
|
|
148
163
|
# @api private
|
|
149
164
|
# @private
|
|
150
|
-
|
|
165
|
+
LIST_UNCOVERED_DETAIL = 'LIST_UNCOVERED_DETAIL'
|
|
151
166
|
|
|
152
|
-
#
|
|
167
|
+
# Maps a coverage criterion to the environment variable that overrides its threshold
|
|
153
168
|
# @api private
|
|
154
169
|
# @private
|
|
155
|
-
|
|
170
|
+
CRITERION_ENV_VARS = {
|
|
171
|
+
line: COVERAGE_THRESHOLD,
|
|
172
|
+
branch: COVERAGE_THRESHOLD_BRANCH,
|
|
173
|
+
method: COVERAGE_THRESHOLD_METHOD
|
|
174
|
+
}.freeze
|
|
175
|
+
|
|
176
|
+
# Default value for minimum_coverage
|
|
177
|
+
# @api private
|
|
178
|
+
# @private
|
|
179
|
+
DEFAULT_MINIMUM_COVERAGE = { line: 100 }.freeze
|
|
156
180
|
|
|
157
181
|
# Default value for fail_on_low_coverage
|
|
158
182
|
# @api private
|
|
159
183
|
# @private
|
|
160
|
-
|
|
184
|
+
DEFAULT_FAIL_ON_LOW_COVERAGE = true
|
|
185
|
+
|
|
186
|
+
# Default value for list_uncovered_detail
|
|
187
|
+
# @api private
|
|
188
|
+
# @private
|
|
189
|
+
DEFAULT_LIST_UNCOVERED_DETAIL = true
|
|
161
190
|
|
|
162
|
-
#
|
|
191
|
+
# Environment variable values that mean "true"
|
|
163
192
|
# @api private
|
|
164
193
|
# @private
|
|
165
|
-
|
|
194
|
+
TRUTHY_ENV_VALUES = %w[yes on true 1].freeze
|
|
166
195
|
|
|
167
196
|
attr_reader :env, :simplecov_module, :start_config_block
|
|
168
197
|
|
|
169
|
-
# The coverage threshold
|
|
198
|
+
# The minimum coverage threshold, per criterion
|
|
170
199
|
#
|
|
171
|
-
# Searches the ENV, the value given in the `.start` method, and the default value
|
|
172
|
-
#
|
|
200
|
+
# Searches the ENV, the value given in the `.start` method, and the default value,
|
|
201
|
+
# merging them (ENV takes precedence per-criterion).
|
|
173
202
|
#
|
|
174
|
-
# @return [Integer]
|
|
203
|
+
# @return [Hash{Symbol => Integer}]
|
|
175
204
|
#
|
|
176
205
|
# @api private
|
|
177
206
|
# @private
|
|
178
207
|
#
|
|
179
|
-
def
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
208
|
+
def minimum_coverage
|
|
209
|
+
base = @minimum_coverage
|
|
210
|
+
base = { line: base } if base.is_a?(Integer)
|
|
211
|
+
validate_minimum_coverage!(base)
|
|
183
212
|
|
|
184
|
-
|
|
213
|
+
(base || DEFAULT_MINIMUM_COVERAGE).merge(env_minimum_coverage_overrides)
|
|
185
214
|
end
|
|
186
215
|
|
|
187
216
|
# Whether to fail if the coverage is below the threshold
|
|
@@ -196,55 +225,48 @@ module SimpleCov
|
|
|
196
225
|
#
|
|
197
226
|
def fail_on_low_coverage?
|
|
198
227
|
return false if rspec_dry_run?
|
|
199
|
-
|
|
200
228
|
return env_true?(FAIL_ON_LOW_COVERAGE) if env.key?(FAIL_ON_LOW_COVERAGE)
|
|
201
|
-
|
|
202
229
|
return @fail_on_low_coverage unless @fail_on_low_coverage.nil?
|
|
203
230
|
|
|
204
|
-
|
|
231
|
+
DEFAULT_FAIL_ON_LOW_COVERAGE
|
|
205
232
|
end
|
|
206
233
|
|
|
207
|
-
#
|
|
234
|
+
# The coverage criteria to list uncovered items for
|
|
208
235
|
#
|
|
209
|
-
# Searches the ENV
|
|
210
|
-
#
|
|
236
|
+
# Searches the ENV and the value given in the `.start` method, and normalizes
|
|
237
|
+
# the result to an Array of Symbols in a fixed (line, branch, method) order.
|
|
211
238
|
#
|
|
212
|
-
# @return [
|
|
239
|
+
# @return [Array<Symbol>]
|
|
213
240
|
#
|
|
214
241
|
# @api private
|
|
215
242
|
# @private
|
|
216
243
|
#
|
|
217
|
-
def
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return @list_uncovered_lines unless @list_uncovered_lines.nil?
|
|
221
|
-
|
|
222
|
-
DEFAULT_LIST_UNCOVERED_LINES
|
|
244
|
+
def list_uncovered_criteria
|
|
245
|
+
@list_uncovered_criteria ||= ListUncoveredOption.resolve(@list_uncovered, env: env, env_var: LIST_UNCOVERED)
|
|
223
246
|
end
|
|
224
247
|
|
|
225
|
-
# Whether
|
|
248
|
+
# Whether to list individual uncovered items, or just a count per criterion
|
|
226
249
|
#
|
|
227
250
|
# @return [Boolean]
|
|
228
251
|
#
|
|
229
252
|
# @api private
|
|
230
253
|
# @private
|
|
231
254
|
#
|
|
232
|
-
def
|
|
255
|
+
def list_uncovered_detail?
|
|
256
|
+
return env_true?(LIST_UNCOVERED_DETAIL) if env.key?(LIST_UNCOVERED_DETAIL)
|
|
257
|
+
return @list_uncovered_detail unless @list_uncovered_detail.nil?
|
|
233
258
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
#
|
|
238
|
-
# @return [String]
|
|
239
|
-
# @api private
|
|
259
|
+
DEFAULT_LIST_UNCOVERED_DETAIL
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Whether the rspec run is a dry run
|
|
240
263
|
#
|
|
241
|
-
#
|
|
242
|
-
# The line number of the uncovered line
|
|
243
|
-
# @return [Integer]
|
|
244
|
-
# @api private
|
|
264
|
+
# @return [Boolean]
|
|
245
265
|
#
|
|
246
266
|
# @api private
|
|
247
|
-
|
|
267
|
+
# @private
|
|
268
|
+
#
|
|
269
|
+
def rspec_dry_run? = @rspec_dry_run
|
|
248
270
|
|
|
249
271
|
private
|
|
250
272
|
|
|
@@ -255,17 +277,19 @@ module SimpleCov
|
|
|
255
277
|
# @api private
|
|
256
278
|
# @private
|
|
257
279
|
def initialize(
|
|
258
|
-
|
|
280
|
+
minimum_coverage: nil,
|
|
259
281
|
fail_on_low_coverage: nil,
|
|
260
|
-
|
|
282
|
+
list_uncovered: nil,
|
|
283
|
+
list_uncovered_detail: nil,
|
|
261
284
|
rspec_dry_run: ::RSpec.configuration.dry_run?,
|
|
262
285
|
env: ENV,
|
|
263
286
|
simplecov_module: ::SimpleCov,
|
|
264
287
|
&start_config_block
|
|
265
288
|
)
|
|
266
|
-
@
|
|
289
|
+
@minimum_coverage = minimum_coverage
|
|
267
290
|
@fail_on_low_coverage = fail_on_low_coverage
|
|
268
|
-
@
|
|
291
|
+
@list_uncovered = list_uncovered
|
|
292
|
+
@list_uncovered_detail = list_uncovered_detail
|
|
269
293
|
@start_config_block = start_config_block
|
|
270
294
|
@rspec_dry_run = rspec_dry_run
|
|
271
295
|
@env = env
|
|
@@ -274,15 +298,29 @@ module SimpleCov
|
|
|
274
298
|
|
|
275
299
|
# rubocop:enable Metrics/ParameterLists
|
|
276
300
|
|
|
277
|
-
#
|
|
301
|
+
# Configure SimpleCov and start it
|
|
278
302
|
# @return [Void]
|
|
279
303
|
# @api private
|
|
280
304
|
# @private
|
|
281
305
|
def start
|
|
282
306
|
simplecov_module.at_exit(&at_exit_hook)
|
|
307
|
+
simplecov_module.enable_coverage(*criteria_to_enable) if criteria_to_enable.any?
|
|
308
|
+
simplecov_module.minimum_coverage(minimum_coverage) if enforce_minimum_coverage?
|
|
283
309
|
simplecov_module.start(&start_config_block)
|
|
284
310
|
end
|
|
285
311
|
|
|
312
|
+
# Whether SimpleCov should be configured to enforce minimum_coverage itself
|
|
313
|
+
# @return [Boolean]
|
|
314
|
+
# @api private
|
|
315
|
+
# @private
|
|
316
|
+
def enforce_minimum_coverage? = !rspec_dry_run? && fail_on_low_coverage?
|
|
317
|
+
|
|
318
|
+
# The coverage criteria that need to be enabled via SimpleCov.enable_coverage
|
|
319
|
+
# @return [Array<Symbol>]
|
|
320
|
+
# @api private
|
|
321
|
+
# @private
|
|
322
|
+
def criteria_to_enable = (minimum_coverage.keys + list_uncovered_criteria).uniq
|
|
323
|
+
|
|
286
324
|
# Called by SimpleCov.at_exit
|
|
287
325
|
# @return [Proc]
|
|
288
326
|
# @api private
|
|
@@ -290,102 +328,87 @@ module SimpleCov
|
|
|
290
328
|
def at_exit_hook
|
|
291
329
|
lambda do
|
|
292
330
|
simplecov_module.result.format!
|
|
293
|
-
|
|
294
|
-
exit 1 if coverage_below_threshold? && fail_on_low_coverage?
|
|
331
|
+
output_uncovered_report
|
|
295
332
|
end
|
|
296
333
|
end
|
|
297
334
|
|
|
298
|
-
# Output the
|
|
335
|
+
# Output the uncovered items report (detailed or summary), if requested
|
|
299
336
|
# @return [Void]
|
|
300
337
|
# @api private
|
|
301
338
|
# @private
|
|
302
|
-
def
|
|
303
|
-
|
|
304
|
-
uncovered_lines_report if show_uncovered_lines_report?
|
|
305
|
-
$stderr.puts if show_low_coverage_report? || show_uncovered_lines_report?
|
|
306
|
-
end
|
|
339
|
+
def output_uncovered_report
|
|
340
|
+
return if list_uncovered_criteria.empty?
|
|
307
341
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
342
|
+
report = UncoveredReport.new(
|
|
343
|
+
result: simplecov_module.result,
|
|
344
|
+
criteria: list_uncovered_criteria,
|
|
345
|
+
detail: list_uncovered_detail?,
|
|
346
|
+
detail_env_var: LIST_UNCOVERED_DETAIL
|
|
347
|
+
).to_s
|
|
348
|
+
return if report.empty?
|
|
313
349
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
# @private
|
|
318
|
-
def coverage_below_threshold? = simplecov_module.result.covered_percent < coverage_threshold
|
|
350
|
+
$stderr.puts
|
|
351
|
+
$stderr.puts report
|
|
352
|
+
end
|
|
319
353
|
|
|
320
|
-
#
|
|
354
|
+
# Raise unless `base` is a valid `minimum_coverage` value
|
|
355
|
+
#
|
|
356
|
+
# Valid values include `nil`, or a Hash keyed by `:line`, `:branch`, and/or
|
|
357
|
+
# `:method`, with Numeric values
|
|
358
|
+
#
|
|
359
|
+
# @param base [nil, Hash] the normalized `minimum_coverage` value
|
|
321
360
|
# @return [Void]
|
|
361
|
+
# @raise [ArgumentError] if `base` is not nil or a Hash, has an unknown key, or has a
|
|
362
|
+
# non-Numeric value
|
|
322
363
|
# @api private
|
|
323
364
|
# @private
|
|
324
|
-
def
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
end
|
|
365
|
+
def validate_minimum_coverage!(base)
|
|
366
|
+
unless base.nil? || base.is_a?(Hash)
|
|
367
|
+
raise ArgumentError, "minimum_coverage must be nil, an Integer, or a Hash; got #{base.inspect}"
|
|
368
|
+
end
|
|
329
369
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
# @api private
|
|
333
|
-
def uncovered_lines_found? = simplecov_module.result.files.any? { |source_file| source_file.missed_lines.any? }
|
|
370
|
+
base&.each_pair { |criterion, threshold| validate_minimum_coverage_entry!(criterion, threshold) }
|
|
371
|
+
end
|
|
334
372
|
|
|
335
|
-
#
|
|
336
|
-
# @
|
|
373
|
+
# Raise unless `criterion` is a known criterion with a Numeric `threshold`
|
|
374
|
+
# @param criterion [Object] the candidate `minimum_coverage` key
|
|
375
|
+
# @param threshold [Object] the candidate `minimum_coverage` value
|
|
376
|
+
# @return [Void]
|
|
377
|
+
# @raise [ArgumentError] if `criterion` is unknown, or `threshold` is not Numeric
|
|
337
378
|
# @api private
|
|
338
379
|
# @private
|
|
339
|
-
def
|
|
380
|
+
def validate_minimum_coverage_entry!(criterion, threshold)
|
|
381
|
+
unless CRITERION_ENV_VARS.key?(criterion)
|
|
382
|
+
raise ArgumentError,
|
|
383
|
+
"minimum_coverage keys must be #{CRITERION_ENV_VARS.keys.inspect}; got #{criterion.inspect}"
|
|
384
|
+
end
|
|
340
385
|
|
|
341
|
-
|
|
342
|
-
# @return [Array<UncoveredLine>]
|
|
343
|
-
# @api private
|
|
344
|
-
def uncovered_lines
|
|
345
|
-
@uncovered_lines ||=
|
|
346
|
-
simplecov_module.result.files.flat_map do |source_file|
|
|
347
|
-
source_file.missed_lines.map do |line|
|
|
348
|
-
project_filename = File.join('.', source_file.project_filename)
|
|
349
|
-
UncoveredLine.new(project_filename, line.number)
|
|
350
|
-
end
|
|
351
|
-
end
|
|
352
|
-
end
|
|
386
|
+
return if threshold.is_a?(Numeric)
|
|
353
387
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
# @param plural [String] the plural form of the phrase
|
|
358
|
-
# @return [String]
|
|
359
|
-
# @api private
|
|
360
|
-
def pluralize(count, singular, plural) = count == 1 ? singular : plural
|
|
388
|
+
raise ArgumentError,
|
|
389
|
+
"minimum_coverage[#{criterion.inspect}] must be a Numeric; got #{threshold.inspect}"
|
|
390
|
+
end
|
|
361
391
|
|
|
362
|
-
#
|
|
363
|
-
# @return [
|
|
392
|
+
# The per-criterion COVERAGE_THRESHOLD* ENV overrides, as a minimum_coverage Hash
|
|
393
|
+
# @return [Hash{Symbol => Integer}]
|
|
364
394
|
# @api private
|
|
365
395
|
# @private
|
|
366
|
-
def
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
things = pluralize(uncovered_lines.count, 'line is', 'lines are')
|
|
370
|
-
$stderr.puts "#{count} #{things} not covered by tests:\n"
|
|
371
|
-
uncovered_lines.each do |uncovered_line|
|
|
372
|
-
$stderr.puts " #{uncovered_line.project_filename}:#{uncovered_line.line_number}"
|
|
396
|
+
def env_minimum_coverage_overrides
|
|
397
|
+
CRITERION_ENV_VARS.each_with_object({}) do |(criterion, var), overrides|
|
|
398
|
+
overrides[criterion] = env.fetch(var).to_i if env.key?(var)
|
|
373
399
|
end
|
|
374
400
|
end
|
|
375
401
|
|
|
376
402
|
# Return `true` if the environment variable is set to a truthy value
|
|
377
403
|
#
|
|
378
404
|
# @example
|
|
379
|
-
# env_true?('
|
|
405
|
+
# env_true?('LIST_UNCOVERED_DETAIL')
|
|
380
406
|
#
|
|
381
407
|
# @param name [String] the name of the environment variable
|
|
382
408
|
# @return [Boolean]
|
|
383
409
|
# @api private
|
|
384
410
|
# @private
|
|
385
411
|
#
|
|
386
|
-
def env_true?(name)
|
|
387
|
-
value = env.fetch(name, '').downcase
|
|
388
|
-
%w[yes on true 1].include?(value)
|
|
389
|
-
end
|
|
412
|
+
def env_true?(name) = TRUTHY_ENV_VALUES.include?(env.fetch(name, '').downcase)
|
|
390
413
|
end
|
|
391
414
|
end
|
data/release-please-config.json
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"package-name": "simplecov-rspec",
|
|
7
7
|
"changelog-path": "CHANGELOG.md",
|
|
8
8
|
"version-file": "lib/simplecov-rspec/version.rb",
|
|
9
|
-
"bump-minor-pre-major":
|
|
10
|
-
"bump-patch-for-minor-pre-major":
|
|
9
|
+
"bump-minor-pre-major": false,
|
|
10
|
+
"bump-patch-for-minor-pre-major": false,
|
|
11
11
|
"draft": false,
|
|
12
12
|
"prerelease": false,
|
|
13
13
|
"include-component-in-tag": false,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simplecov-rspec
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Couball
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0
|
|
18
|
+
version: '1.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0
|
|
25
|
+
version: '1.0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: bundler-audit
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -85,14 +85,14 @@ dependencies:
|
|
|
85
85
|
requirements:
|
|
86
86
|
- - "~>"
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '13.
|
|
88
|
+
version: '13.4'
|
|
89
89
|
type: :development
|
|
90
90
|
prerelease: false
|
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
92
|
requirements:
|
|
93
93
|
- - "~>"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: '13.
|
|
95
|
+
version: '13.4'
|
|
96
96
|
- !ruby/object:Gem::Dependency
|
|
97
97
|
name: rspec
|
|
98
98
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -113,14 +113,14 @@ dependencies:
|
|
|
113
113
|
requirements:
|
|
114
114
|
- - "~>"
|
|
115
115
|
- !ruby/object:Gem::Version
|
|
116
|
-
version: '1.
|
|
116
|
+
version: '1.89'
|
|
117
117
|
type: :development
|
|
118
118
|
prerelease: false
|
|
119
119
|
version_requirements: !ruby/object:Gem::Requirement
|
|
120
120
|
requirements:
|
|
121
121
|
- - "~>"
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
|
-
version: '1.
|
|
123
|
+
version: '1.89'
|
|
124
124
|
- !ruby/object:Gem::Dependency
|
|
125
125
|
name: simplecov-lcov
|
|
126
126
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -149,6 +149,20 @@ dependencies:
|
|
|
149
149
|
- - "~>"
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
151
|
version: '4.4'
|
|
152
|
+
- !ruby/object:Gem::Dependency
|
|
153
|
+
name: irb
|
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - "~>"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '1.13'
|
|
159
|
+
type: :development
|
|
160
|
+
prerelease: false
|
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - "~>"
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '1.13'
|
|
152
166
|
- !ruby/object:Gem::Dependency
|
|
153
167
|
name: redcarpet
|
|
154
168
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -198,9 +212,9 @@ dependencies:
|
|
|
198
212
|
- !ruby/object:Gem::Version
|
|
199
213
|
version: '0.9'
|
|
200
214
|
description: |
|
|
201
|
-
Configures
|
|
202
|
-
|
|
203
|
-
lines
|
|
215
|
+
Configures SimpleCov's line, branch, and method coverage thresholds for RSpec,
|
|
216
|
+
suppresses failures during RSpec dry runs, and (optionally) lists or summarizes
|
|
217
|
+
the lines, branches, and methods not covered by tests.
|
|
204
218
|
email:
|
|
205
219
|
- jcouball@yahoo.com
|
|
206
220
|
executables: []
|
|
@@ -220,6 +234,8 @@ files:
|
|
|
220
234
|
- README.md
|
|
221
235
|
- Rakefile
|
|
222
236
|
- lib/simplecov-rspec.rb
|
|
237
|
+
- lib/simplecov-rspec/list_uncovered_option.rb
|
|
238
|
+
- lib/simplecov-rspec/uncovered_report.rb
|
|
223
239
|
- lib/simplecov-rspec/version.rb
|
|
224
240
|
- package.json
|
|
225
241
|
- release-please-config.json
|
|
@@ -230,8 +246,8 @@ metadata:
|
|
|
230
246
|
allowed_push_host: https://rubygems.org
|
|
231
247
|
homepage_uri: https://github.com/main-branch/simplecov-rspec
|
|
232
248
|
source_code_uri: https://github.com/main-branch/simplecov-rspec
|
|
233
|
-
documentation_uri: https://rubydoc.info/gems/simplecov-rspec/0.
|
|
234
|
-
changelog_uri: https://rubydoc.info/gems/simplecov-rspec/0.
|
|
249
|
+
documentation_uri: https://rubydoc.info/gems/simplecov-rspec/1.0.0
|
|
250
|
+
changelog_uri: https://rubydoc.info/gems/simplecov-rspec/1.0.0/file/CHANGELOG.md
|
|
235
251
|
rubygems_mfa_required: 'true'
|
|
236
252
|
rdoc_options: []
|
|
237
253
|
require_paths:
|
|
@@ -240,7 +256,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
240
256
|
requirements:
|
|
241
257
|
- - ">="
|
|
242
258
|
- !ruby/object:Gem::Version
|
|
243
|
-
version: 3.
|
|
259
|
+
version: 3.2.0
|
|
244
260
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
245
261
|
requirements:
|
|
246
262
|
- - ">="
|
|
@@ -248,9 +264,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
248
264
|
version: '0'
|
|
249
265
|
requirements:
|
|
250
266
|
- 'Platform: Mac, Linux, or Windows'
|
|
251
|
-
- 'Ruby: MRI 3.
|
|
252
|
-
rubygems_version: 4.0.
|
|
267
|
+
- 'Ruby: MRI 3.2 or later, TruffleRuby 24 or later, or JRuby 10 or later'
|
|
268
|
+
rubygems_version: 4.0.16
|
|
253
269
|
specification_version: 4
|
|
254
|
-
summary: Configure SimpleCov
|
|
255
|
-
|
|
270
|
+
summary: Configure SimpleCov coverage thresholds and uncovered item reporting for
|
|
271
|
+
RSpec
|
|
256
272
|
test_files: []
|