simplecov-rspec 1.0.0 → 1.1.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 +6 -0
- data/CHANGELOG.md +8 -0
- data/README.md +173 -9
- data/lib/simplecov-rspec/described_source_files.rb +76 -0
- data/lib/simplecov-rspec/list_uncovered_files_option.rb +130 -0
- data/lib/simplecov-rspec/uncovered_report.rb +167 -4
- data/lib/simplecov-rspec/version.rb +1 -1
- data/lib/simplecov-rspec.rb +92 -6
- metadata +7 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fcdb582042f1b21ad1a1bd486c120fd590f143e73cb51b6b693e3d5755396546
|
|
4
|
+
data.tar.gz: 6a39d87792ea1505b4e0b9af8e4e81ec447541f8e1a0aea8413b49918cfa70d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 27efb22e915daa0cf8a00692a0f65ecdcf659b1badb7cef5210a86aed05a264cb1c4aee1d89aefe39e53515b3d8edf85481d071ef23e31c93f87f616fe4485ec
|
|
7
|
+
data.tar.gz: 2883aab5cd82ddecbcc46e081cb03048e64dd0297abc0d06da1998cc178b5697e2813bf5cbe4489c4b5d2889425afa39cfc2804ec1e0409bbd857e3142a16422
|
data/.rubocop.yml
CHANGED
|
@@ -6,6 +6,12 @@ AllCops:
|
|
|
6
6
|
# your project supports:
|
|
7
7
|
TargetRubyVersion: 3.2
|
|
8
8
|
|
|
9
|
+
Metrics/ClassLength:
|
|
10
|
+
# SimpleCov::RSpec is a configuration surface: every option it accepts costs a reader
|
|
11
|
+
# method, an ENV-name constant, and an ivar. The default 100 is a smell threshold for
|
|
12
|
+
# classes that do work, not for one that mostly declares options.
|
|
13
|
+
Max: 120
|
|
14
|
+
|
|
9
15
|
Style/StderrPuts:
|
|
10
16
|
Enabled: false
|
|
11
17
|
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ 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.1.0](https://github.com/main-branch/simplecov-rspec/compare/v1.0.0...v1.1.0) (2026-08-06)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
|
|
12
|
+
* Add :described to scope the listing to the code under test ([71fe0c7](https://github.com/main-branch/simplecov-rspec/commit/71fe0c715063582ff207a6ac15e9a2c6dbe26243))
|
|
13
|
+
* Add list_uncovered_files to scope the uncovered listing ([1981e65](https://github.com/main-branch/simplecov-rspec/commit/1981e6528a418340c4e211ff0b34ed745693151a))
|
|
14
|
+
|
|
7
15
|
## [1.0.0](https://github.com/main-branch/simplecov-rspec/compare/v0.4.4...v1.0.0) (2026-08-05)
|
|
8
16
|
|
|
9
17
|
|
data/README.md
CHANGED
|
@@ -10,34 +10,67 @@ Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?log
|
|
|
10
10
|
|
|
11
11
|
`simplecov-rspec` is a Ruby gem that integrates SimpleCov with RSpec. SimpleCov
|
|
12
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
|
|
13
|
+
and fails the build when a threshold is missed. This gem layers four things on top
|
|
14
14
|
that SimpleCov doesn't do on its own:
|
|
15
15
|
|
|
16
16
|
1. Suppresses coverage failures when RSpec is run in dry-run mode (e.g. from an IDE).
|
|
17
17
|
2. Lists (or summarizes) the individual uncovered lines, branches, and methods.
|
|
18
|
-
3.
|
|
18
|
+
3. Scopes that listing to the files you name, or to the code the run described.
|
|
19
|
+
4. Lets all of the above be overridden from the environment, for CI.
|
|
19
20
|
|
|
20
21
|
When `simplecov-rspec` is used, RSpec will report an error if the percent of test
|
|
21
22
|
coverage falls below a defined threshold:
|
|
22
23
|
|
|
23
24
|
```text
|
|
24
|
-
Coverage report generated for RSpec to
|
|
25
|
-
|
|
26
|
-
Line coverage (99.
|
|
25
|
+
Coverage report generated for RSpec to coverage/index.html
|
|
26
|
+
Line coverage: 284 / 286 (99.30%)
|
|
27
|
+
Line coverage (99.30%) is below the expected minimum coverage (100.00%).
|
|
28
|
+
Lowest-coverage files (line):
|
|
29
|
+
99.30% lib/example_project.rb
|
|
30
|
+
SimpleCov failed with exit 2 due to a coverage related error
|
|
27
31
|
```
|
|
28
32
|
|
|
29
|
-
If configured to list the items that were not
|
|
33
|
+
All of that comes from SimpleCov itself. If configured to list the items that were not
|
|
34
|
+
covered by tests, this gem adds its own listing between SimpleCov's summary and its
|
|
35
|
+
failure message:
|
|
30
36
|
|
|
31
37
|
```text
|
|
38
|
+
Coverage report generated for RSpec to coverage/index.html
|
|
39
|
+
Line coverage: 284 / 286 (99.30%)
|
|
40
|
+
|
|
32
41
|
2 lines are not covered by tests:
|
|
33
42
|
./lib/example_project.rb:74
|
|
34
43
|
./lib/example_project.rb:75
|
|
44
|
+
Line coverage (99.30%) is below the expected minimum coverage (100.00%).
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Scoping the listing to particular files changes its shape again. It is marked off with a
|
|
48
|
+
header saying how much of the result it covers, and each criterion reports what the
|
|
49
|
+
scoped files cover directly above what they miss — see [Scoping the listing to specific
|
|
50
|
+
files](#scoping-the-listing-to-specific-files):
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
Coverage report generated for RSpec to coverage/index.html
|
|
54
|
+
Line coverage: 284 / 286 (99.30%)
|
|
55
|
+
Branch coverage: 138 / 150 (92.00%)
|
|
56
|
+
|
|
57
|
+
-- Reporting uncovered lines and branches for 1 of 12 files --
|
|
58
|
+
|
|
59
|
+
Scoped line coverage: 73 / 74 (98.64%)
|
|
60
|
+
1 line is not covered by tests:
|
|
61
|
+
./lib/example_project/parser.rb:74
|
|
62
|
+
|
|
63
|
+
Scoped branch coverage: 11 / 12 (91.66%)
|
|
64
|
+
1 branch is not covered by tests:
|
|
65
|
+
./lib/example_project/parser.rb:82 (then branch)
|
|
35
66
|
```
|
|
36
67
|
|
|
37
68
|
- [Installation](#installation)
|
|
38
69
|
- [Getting started](#getting-started)
|
|
39
70
|
- [Basic setup](#basic-setup)
|
|
40
71
|
- [Listing uncovered items](#listing-uncovered-items)
|
|
72
|
+
- [Scoping the listing to specific files](#scoping-the-listing-to-specific-files)
|
|
73
|
+
- [Scoping to the code under test](#scoping-to-the-code-under-test)
|
|
41
74
|
- [Configuration block](#configuration-block)
|
|
42
75
|
- [Configuration from environment variables](#configuration-from-environment-variables)
|
|
43
76
|
- [Development](#development)
|
|
@@ -111,7 +144,8 @@ SimpleCov::RSpec.start(
|
|
|
111
144
|
minimum_coverage: { line: 100 },
|
|
112
145
|
fail_on_low_coverage: true,
|
|
113
146
|
list_uncovered: false,
|
|
114
|
-
list_uncovered_detail: true
|
|
147
|
+
list_uncovered_detail: true,
|
|
148
|
+
list_uncovered_files: nil
|
|
115
149
|
)
|
|
116
150
|
```
|
|
117
151
|
|
|
@@ -142,13 +176,20 @@ SimpleCov::RSpec.start(minimum_coverage: { line: 100, branch: 90 }, list_uncover
|
|
|
142
176
|
```
|
|
143
177
|
|
|
144
178
|
```text
|
|
145
|
-
|
|
179
|
+
2 lines are not covered by tests:
|
|
146
180
|
./lib/example_project.rb:74
|
|
181
|
+
./lib/example_project.rb:75
|
|
147
182
|
|
|
148
183
|
1 branch is not covered by tests:
|
|
149
184
|
./lib/example_project.rb:82 (else branch)
|
|
185
|
+
|
|
186
|
+
1 method is not covered by tests:
|
|
187
|
+
./lib/example_project.rb:96 ExampleProject#unused
|
|
150
188
|
```
|
|
151
189
|
|
|
190
|
+
A criterion with nothing uncovered is left out entirely, so `:all` prints fewer sections
|
|
191
|
+
than this when there is less to say.
|
|
192
|
+
|
|
152
193
|
For a quieter CI log, set `list_uncovered_detail: false` to print only the count per
|
|
153
194
|
criterion, along with a hint on how to see the details:
|
|
154
195
|
|
|
@@ -159,10 +200,125 @@ SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_detail: false)
|
|
|
159
200
|
```text
|
|
160
201
|
2 lines are not covered by tests.
|
|
161
202
|
1 branch is not covered by tests.
|
|
203
|
+
1 method is not covered by tests.
|
|
204
|
+
|
|
205
|
+
Run with LIST_UNCOVERED_DETAIL=true to see the uncovered lines, branches and methods.
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Scoping the listing to specific files
|
|
209
|
+
|
|
210
|
+
By default the uncovered listing covers every file SimpleCov tracked. On a focused run
|
|
211
|
+
— one spec file, or one directory — that listing is mostly noise: `spec_helper`
|
|
212
|
+
requires the whole project, so nearly all of it is legitimately unexercised.
|
|
213
|
+
|
|
214
|
+
`list_uncovered_files` (available since version 1.1) narrows the listing to the files
|
|
215
|
+
you care about, given as `Dir.glob` patterns resolved against `SimpleCov.root`. An
|
|
216
|
+
absolute path is used as given:
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_files: 'lib/example_project/parser.rb')
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
This option scopes the listing that `list_uncovered` asks for; it does not ask for one.
|
|
223
|
+
`list_uncovered` defaults to `false`, which lists nothing, so setting only
|
|
224
|
+
`list_uncovered_files` produces no output at all. Set both.
|
|
225
|
+
|
|
226
|
+
```text
|
|
227
|
+
-- Reporting uncovered lines, branches and methods for 1 of 218 files --
|
|
228
|
+
|
|
229
|
+
Scoped line coverage: 73 / 74 (98.64%)
|
|
230
|
+
1 line is not covered by tests:
|
|
231
|
+
./lib/example_project/parser.rb:74
|
|
232
|
+
|
|
233
|
+
Scoped branch coverage: 12 / 12 (100.00%)
|
|
234
|
+
Scoped method coverage: 8 / 8 (100.00%)
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Each criterion reports what the scoped files cover directly above what they miss, so a
|
|
238
|
+
count like "1 line is not covered" arrives with the denominator that makes it readable,
|
|
239
|
+
and a blank line always means "next criterion". These are deliberately labelled
|
|
240
|
+
differently from SimpleCov's own project-wide summary, and the report is marked off with
|
|
241
|
+
a header, because SimpleCov prints that summary a few lines earlier on the same stream
|
|
242
|
+
while counting different things.
|
|
243
|
+
|
|
244
|
+
This narrows **only the listing**. Coverage is still measured, enforced, and formatted
|
|
245
|
+
for the whole project, so the reported percentage and the HTML report mean the same
|
|
246
|
+
thing whether or not this option is set. There is one definition of "the coverage
|
|
247
|
+
number", and this option does not change it.
|
|
162
248
|
|
|
163
|
-
|
|
249
|
+
A scoped report always prints something, and always says how much of the result it
|
|
250
|
+
covered, so it can never be mistaken for a clean run of the whole suite:
|
|
251
|
+
|
|
252
|
+
```text
|
|
253
|
+
-- Reporting uncovered lines, branches and methods for 1 of 218 files --
|
|
254
|
+
|
|
255
|
+
Scoped line coverage: 74 / 74 (100.00%)
|
|
256
|
+
Scoped branch coverage: 12 / 12 (100.00%)
|
|
257
|
+
Scoped method coverage: 8 / 8 (100.00%)
|
|
258
|
+
|
|
259
|
+
No uncovered lines, branches and methods in this file.
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
When it comes up empty, it says which of the three reasons applies, since only one of
|
|
263
|
+
them means you mistyped a pattern:
|
|
264
|
+
|
|
265
|
+
```text
|
|
266
|
+
-- Reporting uncovered lines, branches and methods for 0 of 218 files --
|
|
267
|
+
|
|
268
|
+
No files matched, so no coverage was reported.
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
```text
|
|
272
|
+
-- Reporting uncovered lines, branches and methods for 0 of 218 files --
|
|
273
|
+
|
|
274
|
+
1 file matched, but it is not in the coverage result. It may not have been loaded by
|
|
275
|
+
this run, or may be excluded by a SimpleCov filter.
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
```text
|
|
279
|
+
-- Reporting uncovered lines, branches and methods for 0 of 218 files --
|
|
280
|
+
|
|
281
|
+
No files were requested, so no coverage was reported.
|
|
164
282
|
```
|
|
165
283
|
|
|
284
|
+
### Scoping to the code under test
|
|
285
|
+
|
|
286
|
+
Naming the files by hand is the awkward part of a focused run: the file you want is
|
|
287
|
+
whatever you happen to be testing right now. `:described` resolves to the source files
|
|
288
|
+
defining the classes the run described, so it follows you from run to run:
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_files: :described)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
```bash
|
|
295
|
+
# Report on lib/example_project/parser.rb, because that is what these specs describe
|
|
296
|
+
bundle exec rspec spec/example_project/parser_spec.rb
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
It walks nested groups too, so a `describe` of one class inside another contributes
|
|
300
|
+
both. A group describing something that is not a class contributes nothing, as does one
|
|
301
|
+
whose class is anonymous, defined in C, or no longer reachable by name — there is no
|
|
302
|
+
source file to report on in those cases.
|
|
303
|
+
|
|
304
|
+
The scope is the classes the run *described*, not the ones it exercised. A class that a
|
|
305
|
+
described class delegates to is not included, and a group written as `describe 'the
|
|
306
|
+
parser' do` names no class at all. Where that matters, build the scope yourself:
|
|
307
|
+
`SimpleCov::RSpec.described_source_files` is public, and you can add to what it returns.
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
SimpleCov::RSpec.start(
|
|
311
|
+
list_uncovered: :all,
|
|
312
|
+
list_uncovered_files: -> { SimpleCov::RSpec.described_source_files + ['lib/example_project/lexer.rb'] }
|
|
313
|
+
)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
The lambda is doing real work there, and leaving it off is a mistake worth naming.
|
|
317
|
+
`SimpleCov::RSpec.start` runs before any example is defined, so calling
|
|
318
|
+
`described_source_files` at that point returns an empty list and you get a report scoped
|
|
319
|
+
to nothing. Any scope derived from the run has to be passed as a callable and resolved
|
|
320
|
+
afterwards — which is exactly what `:described` does for you.
|
|
321
|
+
|
|
166
322
|
### Configuration block
|
|
167
323
|
|
|
168
324
|
A configuration block can be given to the `start` method to further configure
|
|
@@ -196,6 +352,14 @@ variables take precedence over the values passed to `SimpleCov::RSpec.start`.
|
|
|
196
352
|
* **`LIST_UNCOVERED_DETAIL`**: Controls whether uncovered items are listed individually, or
|
|
197
353
|
just summarized as a count per criterion. Set to 'true', 'yes', 'on', or '1' (case
|
|
198
354
|
insensitive) to show individual items.
|
|
355
|
+
* **`LIST_UNCOVERED_FILES`**: Controls which files uncovered items are listed for. Set to a
|
|
356
|
+
comma-separated list of `Dir.glob` patterns, relative to `SimpleCov.root`; to
|
|
357
|
+
'described', for the files defining the classes the run described; or to 'all'
|
|
358
|
+
(or 'false', 'no', 'off', '0', or empty) to list them for every file. Since the
|
|
359
|
+
separator is a comma, a brace pattern such as `lib/{a,b}.rb` cannot be used here —
|
|
360
|
+
give the alternatives separately, as `lib/a.rb,lib/b.rb`. Like `list_uncovered_files`,
|
|
361
|
+
this scopes the listing rather than asking for one: it has no effect unless
|
|
362
|
+
`LIST_UNCOVERED` (or `list_uncovered:`) names at least one criterion.
|
|
199
363
|
|
|
200
364
|
For example, here is a bash script to run tests in an infinite loop while writing
|
|
201
365
|
test output to `fail.txt`:
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class RSpec
|
|
5
|
+
# Finds the source files that define the classes an RSpec run described.
|
|
6
|
+
#
|
|
7
|
+
# This is the scope most people want from `list_uncovered_files:` on a focused run:
|
|
8
|
+
# "report on the code I am actually testing". Deriving it means reaching into
|
|
9
|
+
# `RSpec.world`, which is not part of RSpec's public API, so it lives here rather
|
|
10
|
+
# than in each project's `spec_helper.rb`, where it could not be fixed centrally.
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
#
|
|
14
|
+
module DescribedSourceFiles
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# The source files defining the classes the run described
|
|
18
|
+
#
|
|
19
|
+
# A group that describes something other than a Module contributes nothing, as
|
|
20
|
+
# does one whose described class is anonymous, defined in C, or no longer
|
|
21
|
+
# reachable by name. Such a group has no source file to report on, and saying so
|
|
22
|
+
# is not useful, so it is skipped silently.
|
|
23
|
+
#
|
|
24
|
+
# @param example_groups [Array<Class>] the run's top-level example groups
|
|
25
|
+
#
|
|
26
|
+
# @return [Array<String>] absolute paths, without duplicates
|
|
27
|
+
#
|
|
28
|
+
# @example
|
|
29
|
+
# DescribedSourceFiles.call # => ['/project/lib/parser.rb']
|
|
30
|
+
#
|
|
31
|
+
# @api private
|
|
32
|
+
#
|
|
33
|
+
def call(example_groups: ::RSpec.world.example_groups)
|
|
34
|
+
with_nested(example_groups).filter_map { |group| source_file(group) }.uniq
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The given example groups, plus every group nested within them
|
|
38
|
+
#
|
|
39
|
+
# A nested group can describe a different class than the group containing it, so
|
|
40
|
+
# the whole tree is walked rather than just the top level.
|
|
41
|
+
#
|
|
42
|
+
# @param groups [Array<Class>] the example groups to walk
|
|
43
|
+
#
|
|
44
|
+
# @return [Array<Class>]
|
|
45
|
+
#
|
|
46
|
+
# @example
|
|
47
|
+
# DescribedSourceFiles.with_nested(::RSpec.world.example_groups)
|
|
48
|
+
#
|
|
49
|
+
# @api private
|
|
50
|
+
#
|
|
51
|
+
def with_nested(groups)
|
|
52
|
+
groups.flat_map { |group| [group, *with_nested(group.children)] }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The source file defining one group's described class, if it has one
|
|
56
|
+
#
|
|
57
|
+
# @param group [Class] the example group
|
|
58
|
+
#
|
|
59
|
+
# @return [String, nil] an absolute path, or nil if there is nothing to report on
|
|
60
|
+
#
|
|
61
|
+
# @example
|
|
62
|
+
# DescribedSourceFiles.source_file(group) # => '/project/lib/parser.rb'
|
|
63
|
+
#
|
|
64
|
+
# @api private
|
|
65
|
+
#
|
|
66
|
+
def source_file(group)
|
|
67
|
+
described_class = group.described_class
|
|
68
|
+
return nil unless described_class.is_a?(Module)
|
|
69
|
+
|
|
70
|
+
# A class defined in C answers [], one whose constant is gone answers nil
|
|
71
|
+
name = described_class.name
|
|
72
|
+
name && Object.const_source_location(name)&.first
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class RSpec
|
|
5
|
+
# Resolves the `list_uncovered_files:` option (plus its `LIST_UNCOVERED_FILES` ENV
|
|
6
|
+
# override) into a normalized Array of absolute paths, or nil for "every file".
|
|
7
|
+
#
|
|
8
|
+
# The option may be given as a callable so that it can be evaluated after the test
|
|
9
|
+
# run rather than when `SimpleCov::RSpec.start` is called. `start` runs before any
|
|
10
|
+
# example is defined, so a caller that wants to scope the report to the code under
|
|
11
|
+
# test cannot know which files those are yet.
|
|
12
|
+
#
|
|
13
|
+
# @api private
|
|
14
|
+
#
|
|
15
|
+
module ListUncoveredFilesOption
|
|
16
|
+
# LIST_UNCOVERED_FILES environment variable values that mean "every file"
|
|
17
|
+
ALL_ENV_VALUES = %w[all false no off 0].freeze
|
|
18
|
+
|
|
19
|
+
# The value that means "the files defining the classes this run described"
|
|
20
|
+
#
|
|
21
|
+
# Sugar over passing a callable that does the same thing: the scope is only
|
|
22
|
+
# knowable after the run, so it resolves to one.
|
|
23
|
+
#
|
|
24
|
+
# @see SimpleCov::RSpec.described_source_files
|
|
25
|
+
DESCRIBED = :described
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# Resolve the effective file list, applying the ENV override if present
|
|
30
|
+
#
|
|
31
|
+
# @param value [nil, Symbol, String, Array<String>, #call] the `list_uncovered_files:` argument
|
|
32
|
+
# @param env [Hash] the environment variables
|
|
33
|
+
# @param env_var [String] the ENV var name that overrides `value`
|
|
34
|
+
# @param root [String] the directory that relative patterns are resolved against
|
|
35
|
+
#
|
|
36
|
+
# @return [Array<String>, nil] absolute paths, or nil to report on every file
|
|
37
|
+
#
|
|
38
|
+
# @raise [ArgumentError] if value is not one of the accepted forms
|
|
39
|
+
#
|
|
40
|
+
# @example
|
|
41
|
+
# ListUncoveredFilesOption.resolve('lib/a.rb', env: {}, env_var: 'X', root: '/p') # => ['/p/lib/a.rb']
|
|
42
|
+
#
|
|
43
|
+
def resolve(value, env:, env_var:, root:)
|
|
44
|
+
value = from_env(env.fetch(env_var)) if env.key?(env_var)
|
|
45
|
+
value = -> { ::SimpleCov::RSpec.described_source_files } if value == DESCRIBED
|
|
46
|
+
value = value.call if value.respond_to?(:call)
|
|
47
|
+
return nil if value.nil?
|
|
48
|
+
|
|
49
|
+
expand(normalize(value), root)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Parse a raw LIST_UNCOVERED_FILES environment variable value
|
|
53
|
+
#
|
|
54
|
+
# A value that lists no patterns at all — `''`, `' '`, or separators alone such as
|
|
55
|
+
# `', ,'` — means "every file". Dropping the empty entries can empty the list even
|
|
56
|
+
# though the value was not empty to start with, so the check is made after parsing
|
|
57
|
+
# as well as before it.
|
|
58
|
+
#
|
|
59
|
+
# @param raw [String] the raw LIST_UNCOVERED_FILES environment variable value
|
|
60
|
+
# @return [nil, Symbol, Array<String>] nil for "every file", {DESCRIBED}, or the listed patterns
|
|
61
|
+
# @example
|
|
62
|
+
# ListUncoveredFilesOption.from_env('lib/a.rb,lib/b.rb') # => ['lib/a.rb', 'lib/b.rb']
|
|
63
|
+
#
|
|
64
|
+
def from_env(raw)
|
|
65
|
+
stripped = raw.strip
|
|
66
|
+
return nil if stripped.empty? || ALL_ENV_VALUES.include?(stripped.downcase)
|
|
67
|
+
return DESCRIBED if stripped.casecmp?(DESCRIBED.to_s)
|
|
68
|
+
|
|
69
|
+
patterns = stripped.split(',').map(&:strip).reject(&:empty?)
|
|
70
|
+
patterns.empty? ? nil : patterns
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Normalize a `list_uncovered_files:`-style value into an Array of Strings
|
|
74
|
+
#
|
|
75
|
+
# @param value [String, Array<String>] the pattern or patterns
|
|
76
|
+
# @return [Array<String>]
|
|
77
|
+
# @raise [ArgumentError] if value is not a String or an Array of Strings
|
|
78
|
+
# @example
|
|
79
|
+
# ListUncoveredFilesOption.normalize('lib/a.rb') # => ['lib/a.rb']
|
|
80
|
+
#
|
|
81
|
+
def normalize(value)
|
|
82
|
+
case value
|
|
83
|
+
when String then [value]
|
|
84
|
+
when Array then validate(value)
|
|
85
|
+
else
|
|
86
|
+
raise ArgumentError,
|
|
87
|
+
'list_uncovered_files must be nil, :described, a String, an Array of Strings, or a callable ' \
|
|
88
|
+
"returning one of those; got #{value.inspect}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Raise unless every element of patterns is a String
|
|
93
|
+
#
|
|
94
|
+
# @param patterns [Array<String>]
|
|
95
|
+
# @return [Array<String>]
|
|
96
|
+
# @raise [ArgumentError] if patterns contains a non-String
|
|
97
|
+
# @example
|
|
98
|
+
# ListUncoveredFilesOption.validate(['lib/a.rb']) # => ['lib/a.rb']
|
|
99
|
+
#
|
|
100
|
+
def validate(patterns)
|
|
101
|
+
invalid = patterns.grep_v(String)
|
|
102
|
+
return patterns if invalid.empty?
|
|
103
|
+
|
|
104
|
+
raise ArgumentError, "list_uncovered_files entries must be Strings; got #{invalid.inspect}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Expand patterns to absolute paths, resolving relative patterns against root
|
|
108
|
+
#
|
|
109
|
+
# Each pattern is expanded with `Dir.glob`. A pattern that matches nothing on disk
|
|
110
|
+
# expands to itself, so the literal pattern is kept rather than dropped. No file in
|
|
111
|
+
# the coverage result can carry that path, so it adds nothing to the listing; it is
|
|
112
|
+
# kept because the report tests these paths against the file system to tell a
|
|
113
|
+
# pattern that matched nothing from one whose files SimpleCov never tracked.
|
|
114
|
+
#
|
|
115
|
+
# @param patterns [Array<String>] the patterns to expand
|
|
116
|
+
# @param root [String] the directory that relative patterns are resolved against
|
|
117
|
+
# @return [Array<String>] absolute paths, without duplicates
|
|
118
|
+
# @example
|
|
119
|
+
# ListUncoveredFilesOption.expand(['lib/*.rb'], '/p') # => ['/p/lib/a.rb', '/p/lib/b.rb']
|
|
120
|
+
#
|
|
121
|
+
def expand(patterns, root)
|
|
122
|
+
patterns.flat_map do |pattern|
|
|
123
|
+
absolute = File.absolute_path(pattern, root)
|
|
124
|
+
matches = Dir.glob(absolute)
|
|
125
|
+
matches.empty? ? [absolute] : matches
|
|
126
|
+
end.uniq
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -22,18 +22,26 @@ module SimpleCov
|
|
|
22
22
|
# @param criteria [Array<Symbol>] which criteria (:line, :branch, :method) to report
|
|
23
23
|
# @param detail [Boolean] list individual items, or just a count per criterion
|
|
24
24
|
# @param detail_env_var [String] the ENV var name to suggest for switching to detail
|
|
25
|
+
# @param files [Array<String>, nil] absolute paths to report on, or nil for every file
|
|
25
26
|
# @example
|
|
26
27
|
# UncoveredReport.new(result: SimpleCov.result, criteria: [:line], detail: true, detail_env_var: 'X')
|
|
27
|
-
def initialize(result:, criteria:, detail:, detail_env_var:)
|
|
28
|
+
def initialize(result:, criteria:, detail:, detail_env_var:, files: nil)
|
|
28
29
|
@result = result
|
|
29
30
|
@criteria = criteria
|
|
30
31
|
@detail = detail
|
|
31
32
|
@detail_env_var = detail_env_var
|
|
33
|
+
@files = files
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
# The formatted report text, or an empty string if there is nothing to report
|
|
37
|
+
#
|
|
38
|
+
# A scoped report (one where `files` is not nil) always produces text, even when
|
|
39
|
+
# every file it covers is fully covered. Silence there would be indistinguishable
|
|
40
|
+
# from a clean run of the whole suite, which is the opposite of what asking for a
|
|
41
|
+
# scope means.
|
|
42
|
+
#
|
|
35
43
|
# @return [String]
|
|
36
|
-
def to_s =
|
|
44
|
+
def to_s = files.nil? ? uncovered_text : scoped_text
|
|
37
45
|
|
|
38
46
|
private
|
|
39
47
|
|
|
@@ -57,6 +65,161 @@ module SimpleCov
|
|
|
57
65
|
# @api private
|
|
58
66
|
attr_reader :detail_env_var
|
|
59
67
|
|
|
68
|
+
# The absolute paths to report on, or nil to report on every file in the result
|
|
69
|
+
# @return [Array<String>, nil]
|
|
70
|
+
# @api private
|
|
71
|
+
attr_reader :files
|
|
72
|
+
|
|
73
|
+
# The result's files, narrowed to `files` when a scope was given
|
|
74
|
+
# @return [Array<SimpleCov::SourceFile>]
|
|
75
|
+
def reported_files
|
|
76
|
+
@reported_files ||= files.nil? ? result.files : result.files.select { |file| files.include?(file.filename) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# The uncovered items or counts, in the requested level of detail
|
|
80
|
+
# @return [String]
|
|
81
|
+
def uncovered_text = detail ? detailed_text : summary_text
|
|
82
|
+
|
|
83
|
+
# The report for a scope, which is always non-empty
|
|
84
|
+
#
|
|
85
|
+
# Names how much of the result the scope covered, then one section per criterion,
|
|
86
|
+
# then a closing statement.
|
|
87
|
+
#
|
|
88
|
+
# @return [String]
|
|
89
|
+
def scoped_text
|
|
90
|
+
return "#{scope_text}\n\n#{nothing_reported_text}" if reported_files.empty?
|
|
91
|
+
|
|
92
|
+
[scope_text, *criterion_blocks, closing_text].compact.join("\n\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Why a scope produced nothing to report on
|
|
96
|
+
#
|
|
97
|
+
# Three different mistakes end up here, and only one of them is "your pattern
|
|
98
|
+
# matched nothing". Saying that when the pattern matched a real file that
|
|
99
|
+
# SimpleCov never saw sends the reader looking for a typo that isn't there.
|
|
100
|
+
#
|
|
101
|
+
# @return [String]
|
|
102
|
+
def nothing_reported_text
|
|
103
|
+
return 'No files were requested, so no coverage was reported.' if files.empty?
|
|
104
|
+
return 'No files matched, so no coverage was reported.' if matched_files.empty?
|
|
105
|
+
|
|
106
|
+
untracked_text
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# The requested paths that name a file on disk
|
|
110
|
+
#
|
|
111
|
+
# A pattern that globbed successfully expands to paths that exist; one that
|
|
112
|
+
# matched nothing is kept as a literal path that does not.
|
|
113
|
+
#
|
|
114
|
+
# @return [Array<String>]
|
|
115
|
+
def matched_files = @matched_files ||= files.select { |path| File.file?(path) }
|
|
116
|
+
|
|
117
|
+
# The report for files that exist but are absent from the coverage result
|
|
118
|
+
# @return [String]
|
|
119
|
+
def untracked_text
|
|
120
|
+
count = matched_files.count
|
|
121
|
+
"#{count} #{pluralize(count, 'file matched, but it is not', 'files matched, but none are')} in the " \
|
|
122
|
+
"coverage result. #{pluralize(count, 'It', 'They')} may not have been loaded by this run, or may " \
|
|
123
|
+
'be excluded by a SimpleCov filter.'
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# The line stating how much of the result the scope covered
|
|
127
|
+
#
|
|
128
|
+
# Reported alongside the total so that a scoped report showing nothing uncovered
|
|
129
|
+
# cannot be misread as the whole suite being fully covered. Marked off as a section
|
|
130
|
+
# header because SimpleCov prints its own project-wide summary just above, on the
|
|
131
|
+
# same stream, counting different things.
|
|
132
|
+
#
|
|
133
|
+
# @return [String]
|
|
134
|
+
def scope_text
|
|
135
|
+
"-- Reporting uncovered #{noun_list(criteria)} for " \
|
|
136
|
+
"#{reported_files.count} of #{result.files.count} #{pluralize(result.files.count, 'file', 'files')} --"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# The body of a scoped report: what the files cover, then what they miss
|
|
140
|
+
#
|
|
141
|
+
# A criterion's coverage sits directly above its own listing, and criteria with
|
|
142
|
+
# nothing to list share a block, so that a blank line always means "next criterion"
|
|
143
|
+
# and never separates a heading from what it heads.
|
|
144
|
+
#
|
|
145
|
+
# @return [Array<String>]
|
|
146
|
+
def criterion_blocks
|
|
147
|
+
criteria.map { |criterion| [coverage_text(criterion), missing_text(criterion)] }
|
|
148
|
+
.chunk_while { |(_, missing), (_, next_missing)| missing.nil? && next_missing.nil? }
|
|
149
|
+
.map { |block| block.flatten.compact.join("\n") }
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# A criterion's coverage across the scoped files
|
|
153
|
+
#
|
|
154
|
+
# Deliberately not labelled the way SimpleCov labels its own project-wide summary.
|
|
155
|
+
# The two appear within a few lines of each other and count different things, so
|
|
156
|
+
# sharing a label would make the narrower number look like a restatement of the
|
|
157
|
+
# broader one.
|
|
158
|
+
#
|
|
159
|
+
# @param criterion [Symbol]
|
|
160
|
+
# @return [String]
|
|
161
|
+
def coverage_text(criterion)
|
|
162
|
+
covered = covered_count(criterion)
|
|
163
|
+
total = covered + uncovered_count(criterion)
|
|
164
|
+
"Scoped #{criterion} coverage: #{covered} / #{total} (#{percent_text(covered, total)})"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# A covered-of-total ratio, formatted the way SimpleCov formats its own
|
|
168
|
+
#
|
|
169
|
+
# Truncated rather than rounded, through SimpleCov's own helper, because its
|
|
170
|
+
# project-wide summary prints a few lines above this one over the same kind of
|
|
171
|
+
# ratio. Two percentages differing in the last digit would read as a bug in one of
|
|
172
|
+
# them. A criterion with nothing to cover is 100%, which is also what SimpleCov says.
|
|
173
|
+
#
|
|
174
|
+
# @param covered [Integer] the number of covered items
|
|
175
|
+
# @param total [Integer] the number of items that could be covered
|
|
176
|
+
#
|
|
177
|
+
# @return [String]
|
|
178
|
+
def percent_text(covered, total)
|
|
179
|
+
percent = total.zero? ? 100.0 : covered * 100.0 / total
|
|
180
|
+
"#{format('%.2f', ::SimpleCov.round_coverage(percent))}%"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# A criterion's uncovered items or count
|
|
184
|
+
#
|
|
185
|
+
# Nil when the scoped files leave nothing uncovered for it.
|
|
186
|
+
#
|
|
187
|
+
# @param criterion [Symbol]
|
|
188
|
+
# @return [String, nil]
|
|
189
|
+
def missing_text(criterion)
|
|
190
|
+
return section(criterion) if detail
|
|
191
|
+
|
|
192
|
+
count = uncovered_count(criterion)
|
|
193
|
+
count.positive? ? "#{header(criterion, count)}." : nil
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# The closing statement: that nothing is uncovered, or how to see what is
|
|
197
|
+
# @return [String, nil]
|
|
198
|
+
def closing_text
|
|
199
|
+
return nothing_uncovered_text if missing_criteria.empty?
|
|
200
|
+
return nil if detail
|
|
201
|
+
|
|
202
|
+
"Run with #{detail_env_var}=true to see the uncovered #{noun_list(missing_criteria)}."
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# The criteria the scoped files leave something uncovered for
|
|
206
|
+
# @return [Array<Symbol>]
|
|
207
|
+
def missing_criteria = criteria.reject { |criterion| uncovered_count(criterion).zero? }
|
|
208
|
+
|
|
209
|
+
# The statement that a scope turned up no uncovered items
|
|
210
|
+
# @return [String]
|
|
211
|
+
def nothing_uncovered_text
|
|
212
|
+
"No uncovered #{noun_list(criteria)} in #{pluralize(reported_files.count, 'this file', 'these files')}."
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The count of covered items for a single criterion across the reported files
|
|
216
|
+
# @param criterion [Symbol]
|
|
217
|
+
# @return [Integer]
|
|
218
|
+
def covered_count(criterion)
|
|
219
|
+
plural = CRITERION_LABELS.fetch(criterion).last
|
|
220
|
+
reported_files.sum { |file| file.public_send(:"covered_#{plural}").count }
|
|
221
|
+
end
|
|
222
|
+
|
|
60
223
|
# The full listing, one blank-line-separated section per criterion
|
|
61
224
|
# @return [String]
|
|
62
225
|
def detailed_text
|
|
@@ -99,7 +262,7 @@ module SimpleCov
|
|
|
99
262
|
# @param criterion [Symbol]
|
|
100
263
|
# @return [Array<String>]
|
|
101
264
|
def uncovered_items(criterion)
|
|
102
|
-
|
|
265
|
+
reported_files.flat_map { |file| items_for(file, criterion) }
|
|
103
266
|
end
|
|
104
267
|
|
|
105
268
|
# The count of uncovered items for a single criterion across all files
|
|
@@ -107,7 +270,7 @@ module SimpleCov
|
|
|
107
270
|
# @param criterion [Symbol]
|
|
108
271
|
# @return [Integer]
|
|
109
272
|
def uncovered_count(criterion)
|
|
110
|
-
|
|
273
|
+
reported_files.sum { |file| count_for(file, criterion) }
|
|
111
274
|
end
|
|
112
275
|
|
|
113
276
|
# The formatted, uncovered items of one criterion within a single file
|
data/lib/simplecov-rspec.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'simplecov'
|
|
4
|
+
require_relative 'simplecov-rspec/described_source_files'
|
|
5
|
+
require_relative 'simplecov-rspec/list_uncovered_files_option'
|
|
4
6
|
require_relative 'simplecov-rspec/list_uncovered_option'
|
|
5
7
|
require_relative 'simplecov-rspec/uncovered_report'
|
|
6
8
|
|
|
@@ -11,11 +13,12 @@ module SimpleCov
|
|
|
11
13
|
#
|
|
12
14
|
# SimpleCov (>= 1.0) can already enforce `minimum_coverage` for line, branch, and
|
|
13
15
|
# method coverage, and will exit with a non-zero status when a threshold is missed.
|
|
14
|
-
# This gem layers
|
|
16
|
+
# This gem layers four things SimpleCov doesn't do on its own:
|
|
15
17
|
#
|
|
16
18
|
# 1. Suppresses coverage failures when RSpec is run in dry-run mode (e.g. from an IDE).
|
|
17
19
|
# 2. Lists (or summarizes) the individual uncovered lines, branches, and methods.
|
|
18
|
-
# 3.
|
|
20
|
+
# 3. Scopes that listing to the files you name, or to the code the run described.
|
|
21
|
+
# 4. Lets all of the above be overridden from the environment, for CI.
|
|
19
22
|
#
|
|
20
23
|
# Simply add the line `SimpleCov::RSpec.start` in place of `SimpleCov.start` in
|
|
21
24
|
# the project's `spec_helper.rb`. This line must appear before the project is
|
|
@@ -33,6 +36,9 @@ module SimpleCov
|
|
|
33
36
|
# @example Report only counts, with a hint on how to see the details
|
|
34
37
|
# SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_detail: false)
|
|
35
38
|
#
|
|
39
|
+
# @example Scope the listing to the code the run described
|
|
40
|
+
# SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_files: :described)
|
|
41
|
+
#
|
|
36
42
|
# @example Pass a configuration block to SimpleCov.start
|
|
37
43
|
# SimpleCov::RSpec.start { formatter SimpleCov::Formatter::LcovFormatter }
|
|
38
44
|
#
|
|
@@ -100,6 +106,27 @@ module SimpleCov
|
|
|
100
106
|
# Read from the `LIST_UNCOVERED_DETAIL` environment variable if set: `true`,
|
|
101
107
|
# `yes`, `on`, or `1` (case-insensitive) shows details; anything else summarizes.
|
|
102
108
|
#
|
|
109
|
+
# @param list_uncovered_files [nil, Symbol, String, Array<String>, #call] which files to
|
|
110
|
+
# list uncovered items for (default: nil)
|
|
111
|
+
#
|
|
112
|
+
# `nil` reports on every file in the result. A String or Array of Strings names
|
|
113
|
+
# the files to report on, as `Dir.glob` patterns resolved against `SimpleCov.root`.
|
|
114
|
+
# `:described` reports on the files defining the classes the run described, which
|
|
115
|
+
# is the scope a focused run usually wants. A callable returning a String or an
|
|
116
|
+
# Array of Strings is resolved after the run rather than at `start`, which is what
|
|
117
|
+
# any scope derived from the run needs: `start` runs before any example is defined.
|
|
118
|
+
#
|
|
119
|
+
# This narrows only the uncovered listing. Coverage is still measured, enforced,
|
|
120
|
+
# and formatted for the whole project, so the percentage and the HTML report mean
|
|
121
|
+
# the same thing whether or not this is set.
|
|
122
|
+
#
|
|
123
|
+
# A scoped report names how many of the result's files it covered, and says so
|
|
124
|
+
# explicitly when they are fully covered or when nothing matched.
|
|
125
|
+
#
|
|
126
|
+
# Read from the `LIST_UNCOVERED_FILES` environment variable if set: a
|
|
127
|
+
# comma-separated list of patterns, `described`, or `all` (or `false`, `no`,
|
|
128
|
+
# `off`, `0`, or empty) for every file.
|
|
129
|
+
#
|
|
103
130
|
# @param start_config_block [Proc] a configuration block to pass to `SimpleCov.start` (default: nil)
|
|
104
131
|
#
|
|
105
132
|
# @param rspec_dry_run [Boolean] whether the rspec run is a dry run
|
|
@@ -132,8 +159,45 @@ module SimpleCov
|
|
|
132
159
|
# # OR use an environment variable to override the default
|
|
133
160
|
# LIST_UNCOVERED=all rspec
|
|
134
161
|
#
|
|
162
|
+
# @example List uncovered items for one file only
|
|
163
|
+
# SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_files: 'lib/example_project/parser.rb')
|
|
164
|
+
#
|
|
165
|
+
# # OR use an environment variable to override the default
|
|
166
|
+
# LIST_UNCOVERED_FILES=lib/example_project/parser.rb rspec
|
|
167
|
+
#
|
|
168
|
+
# @example Scope the listing to the classes the run actually described
|
|
169
|
+
# SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_files: :described)
|
|
170
|
+
#
|
|
171
|
+
# # OR use an environment variable to override the default
|
|
172
|
+
# LIST_UNCOVERED_FILES=described rspec
|
|
173
|
+
#
|
|
135
174
|
def self.start(...) = new(...).send(:start)
|
|
136
175
|
|
|
176
|
+
# The source files defining the classes this RSpec run described
|
|
177
|
+
#
|
|
178
|
+
# What `list_uncovered_files: :described` scopes to. Call it directly to build a
|
|
179
|
+
# scope of your own — to add files the run touches but does not describe, say:
|
|
180
|
+
#
|
|
181
|
+
# list_uncovered_files: -> { SimpleCov::RSpec.described_source_files + ['lib/support.rb'] }
|
|
182
|
+
#
|
|
183
|
+
# Only meaningful once the run has defined its examples, which is why it is passed
|
|
184
|
+
# as a callable rather than called at `start`.
|
|
185
|
+
#
|
|
186
|
+
# A described class contributes nothing when it is anonymous, defined in C, or no
|
|
187
|
+
# longer reachable by name, since there is no source file to report on.
|
|
188
|
+
#
|
|
189
|
+
# @example Scope the listing to the code under test, plus one more file
|
|
190
|
+
# SimpleCov::RSpec.start(
|
|
191
|
+
# list_uncovered: :all,
|
|
192
|
+
# list_uncovered_files: -> { SimpleCov::RSpec.described_source_files + ['lib/support.rb'] }
|
|
193
|
+
# )
|
|
194
|
+
#
|
|
195
|
+
# @return [Array<String>] absolute paths, without duplicates
|
|
196
|
+
#
|
|
197
|
+
# @api public
|
|
198
|
+
#
|
|
199
|
+
def self.described_source_files = DescribedSourceFiles.call
|
|
200
|
+
|
|
137
201
|
# Environment variable to override minimum_coverage[:line]
|
|
138
202
|
# @api private
|
|
139
203
|
# @private
|
|
@@ -164,6 +228,11 @@ module SimpleCov
|
|
|
164
228
|
# @private
|
|
165
229
|
LIST_UNCOVERED_DETAIL = 'LIST_UNCOVERED_DETAIL'
|
|
166
230
|
|
|
231
|
+
# Environment variable to override list_uncovered_files
|
|
232
|
+
# @api private
|
|
233
|
+
# @private
|
|
234
|
+
LIST_UNCOVERED_FILES = 'LIST_UNCOVERED_FILES'
|
|
235
|
+
|
|
167
236
|
# Maps a coverage criterion to the environment variable that overrides its threshold
|
|
168
237
|
# @api private
|
|
169
238
|
# @private
|
|
@@ -245,6 +314,23 @@ module SimpleCov
|
|
|
245
314
|
@list_uncovered_criteria ||= ListUncoveredOption.resolve(@list_uncovered, env: env, env_var: LIST_UNCOVERED)
|
|
246
315
|
end
|
|
247
316
|
|
|
317
|
+
# The files to list uncovered items for, or nil for every file in the result
|
|
318
|
+
#
|
|
319
|
+
# Resolved from the `at_exit` hook rather than at `start`: `start` runs before any
|
|
320
|
+
# example is defined, so a caller scoping the report to the code under test cannot
|
|
321
|
+
# know which files those are until the run is over.
|
|
322
|
+
#
|
|
323
|
+
# @return [Array<String>, nil]
|
|
324
|
+
#
|
|
325
|
+
# @api private
|
|
326
|
+
# @private
|
|
327
|
+
#
|
|
328
|
+
def list_uncovered_files
|
|
329
|
+
ListUncoveredFilesOption.resolve(
|
|
330
|
+
@list_uncovered_files, env: env, env_var: LIST_UNCOVERED_FILES, root: simplecov_module.root
|
|
331
|
+
)
|
|
332
|
+
end
|
|
333
|
+
|
|
248
334
|
# Whether to list individual uncovered items, or just a count per criterion
|
|
249
335
|
#
|
|
250
336
|
# @return [Boolean]
|
|
@@ -281,6 +367,7 @@ module SimpleCov
|
|
|
281
367
|
fail_on_low_coverage: nil,
|
|
282
368
|
list_uncovered: nil,
|
|
283
369
|
list_uncovered_detail: nil,
|
|
370
|
+
list_uncovered_files: nil,
|
|
284
371
|
rspec_dry_run: ::RSpec.configuration.dry_run?,
|
|
285
372
|
env: ENV,
|
|
286
373
|
simplecov_module: ::SimpleCov,
|
|
@@ -290,6 +377,7 @@ module SimpleCov
|
|
|
290
377
|
@fail_on_low_coverage = fail_on_low_coverage
|
|
291
378
|
@list_uncovered = list_uncovered
|
|
292
379
|
@list_uncovered_detail = list_uncovered_detail
|
|
380
|
+
@list_uncovered_files = list_uncovered_files
|
|
293
381
|
@start_config_block = start_config_block
|
|
294
382
|
@rspec_dry_run = rspec_dry_run
|
|
295
383
|
@env = env
|
|
@@ -340,10 +428,8 @@ module SimpleCov
|
|
|
340
428
|
return if list_uncovered_criteria.empty?
|
|
341
429
|
|
|
342
430
|
report = UncoveredReport.new(
|
|
343
|
-
result: simplecov_module.result,
|
|
344
|
-
|
|
345
|
-
detail: list_uncovered_detail?,
|
|
346
|
-
detail_env_var: LIST_UNCOVERED_DETAIL
|
|
431
|
+
result: simplecov_module.result, criteria: list_uncovered_criteria, detail: list_uncovered_detail?,
|
|
432
|
+
detail_env_var: LIST_UNCOVERED_DETAIL, files: list_uncovered_files
|
|
347
433
|
).to_s
|
|
348
434
|
return if report.empty?
|
|
349
435
|
|
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: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- James Couball
|
|
@@ -214,7 +214,8 @@ dependencies:
|
|
|
214
214
|
description: |
|
|
215
215
|
Configures SimpleCov's line, branch, and method coverage thresholds for RSpec,
|
|
216
216
|
suppresses failures during RSpec dry runs, and (optionally) lists or summarizes
|
|
217
|
-
the lines, branches, and methods not covered by tests
|
|
217
|
+
the lines, branches, and methods not covered by tests, either for the whole
|
|
218
|
+
project or scoped to the code the run described.
|
|
218
219
|
email:
|
|
219
220
|
- jcouball@yahoo.com
|
|
220
221
|
executables: []
|
|
@@ -234,6 +235,8 @@ files:
|
|
|
234
235
|
- README.md
|
|
235
236
|
- Rakefile
|
|
236
237
|
- lib/simplecov-rspec.rb
|
|
238
|
+
- lib/simplecov-rspec/described_source_files.rb
|
|
239
|
+
- lib/simplecov-rspec/list_uncovered_files_option.rb
|
|
237
240
|
- lib/simplecov-rspec/list_uncovered_option.rb
|
|
238
241
|
- lib/simplecov-rspec/uncovered_report.rb
|
|
239
242
|
- lib/simplecov-rspec/version.rb
|
|
@@ -246,8 +249,8 @@ metadata:
|
|
|
246
249
|
allowed_push_host: https://rubygems.org
|
|
247
250
|
homepage_uri: https://github.com/main-branch/simplecov-rspec
|
|
248
251
|
source_code_uri: https://github.com/main-branch/simplecov-rspec
|
|
249
|
-
documentation_uri: https://rubydoc.info/gems/simplecov-rspec/1.
|
|
250
|
-
changelog_uri: https://rubydoc.info/gems/simplecov-rspec/1.
|
|
252
|
+
documentation_uri: https://rubydoc.info/gems/simplecov-rspec/1.1.0
|
|
253
|
+
changelog_uri: https://rubydoc.info/gems/simplecov-rspec/1.1.0/file/CHANGELOG.md
|
|
251
254
|
rubygems_mfa_required: 'true'
|
|
252
255
|
rdoc_options: []
|
|
253
256
|
require_paths:
|