simplecov-rspec 0.4.4 → 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 +7 -1
- data/CHANGELOG.md +21 -0
- data/README.md +259 -38
- 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/list_uncovered_option.rb +79 -0
- data/lib/simplecov-rspec/uncovered_report.rb +349 -0
- data/lib/simplecov-rspec/version.rb +1 -1
- data/lib/simplecov-rspec.rb +288 -179
- data/release-please-config.json +2 -2
- metadata +36 -17
data/lib/simplecov-rspec.rb
CHANGED
|
@@ -1,55 +1,65 @@
|
|
|
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'
|
|
6
|
+
require_relative 'simplecov-rspec/list_uncovered_option'
|
|
7
|
+
require_relative 'simplecov-rspec/uncovered_report'
|
|
4
8
|
|
|
5
9
|
# SimpleCov namespace
|
|
6
10
|
module SimpleCov
|
|
7
|
-
# Configure SimpleCov to
|
|
11
|
+
# Configure SimpleCov to enforce coverage thresholds for RSpec, on top of what
|
|
12
|
+
# SimpleCov itself already provides.
|
|
8
13
|
#
|
|
9
|
-
#
|
|
14
|
+
# SimpleCov (>= 1.0) can already enforce `minimum_coverage` for line, branch, and
|
|
15
|
+
# method coverage, and will exit with a non-zero status when a threshold is missed.
|
|
16
|
+
# This gem layers four things SimpleCov doesn't do on its own:
|
|
10
17
|
#
|
|
11
|
-
# 1.
|
|
12
|
-
#
|
|
13
|
-
#
|
|
18
|
+
# 1. Suppresses coverage failures when RSpec is run in dry-run mode (e.g. from an IDE).
|
|
19
|
+
# 2. Lists (or summarizes) the individual uncovered lines, branches, and methods.
|
|
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.
|
|
14
22
|
#
|
|
15
|
-
# Simply add the line `SimpleCov::RSpec.start` in place of `SimpleCov
|
|
23
|
+
# Simply add the line `SimpleCov::RSpec.start` in place of `SimpleCov.start` in
|
|
16
24
|
# the project's `spec_helper.rb`. This line must appear before the project is
|
|
17
25
|
# required.
|
|
18
26
|
#
|
|
19
|
-
# @example Initialize SimpleCov with defaults
|
|
27
|
+
# @example Initialize SimpleCov with defaults (100% line coverage required)
|
|
20
28
|
# SimpleCov::RSpec.start
|
|
21
29
|
#
|
|
22
|
-
# @example
|
|
23
|
-
# SimpleCov::RSpec.start(
|
|
30
|
+
# @example Require 100% line coverage and 90% branch coverage
|
|
31
|
+
# SimpleCov::RSpec.start(minimum_coverage: { line: 100, branch: 90 })
|
|
24
32
|
#
|
|
25
|
-
# @example
|
|
26
|
-
# SimpleCov::RSpec.start {
|
|
33
|
+
# @example List every uncovered line, branch, and method when coverage is incomplete
|
|
34
|
+
# SimpleCov::RSpec.start(minimum_coverage: { line: 100, branch: 90 }, list_uncovered: :all)
|
|
27
35
|
#
|
|
28
|
-
# @example
|
|
29
|
-
#
|
|
36
|
+
# @example Report only counts, with a hint on how to see the details
|
|
37
|
+
# SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_detail: false)
|
|
30
38
|
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# @return [Hash]
|
|
34
|
-
# @api private
|
|
35
|
-
# @private
|
|
39
|
+
# @example Scope the listing to the code the run described
|
|
40
|
+
# SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_files: :described)
|
|
36
41
|
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
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
|
|
42
|
+
# @example Pass a configuration block to SimpleCov.start
|
|
43
|
+
# SimpleCov::RSpec.start { formatter SimpleCov::Formatter::LcovFormatter }
|
|
48
44
|
#
|
|
49
45
|
# @api public
|
|
50
46
|
#
|
|
51
47
|
class RSpec
|
|
52
|
-
#
|
|
48
|
+
# @!attribute [r] env
|
|
49
|
+
# The environment variables consulted for overrides
|
|
50
|
+
# @return [Hash]
|
|
51
|
+
# @api private
|
|
52
|
+
#
|
|
53
|
+
# @!attribute [r] simplecov_module
|
|
54
|
+
# The SimpleCov module being configured
|
|
55
|
+
# @return [Module]
|
|
56
|
+
# @api private
|
|
57
|
+
#
|
|
58
|
+
# @!attribute [r] start_config_block
|
|
59
|
+
# A configuration block passed through to `SimpleCov.start`
|
|
60
|
+
# @return [Proc, nil]
|
|
61
|
+
# @api private
|
|
62
|
+
#
|
|
53
63
|
|
|
54
64
|
# Configure and start SimpleCov for RSpec
|
|
55
65
|
#
|
|
@@ -60,128 +70,216 @@ module SimpleCov
|
|
|
60
70
|
#
|
|
61
71
|
# @api public
|
|
62
72
|
#
|
|
63
|
-
# @overload start(
|
|
73
|
+
# @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
74
|
#
|
|
65
|
-
# @param
|
|
75
|
+
# @param minimum_coverage [Integer, Hash] the minimum coverage threshold (default: `{ line: 100 }`)
|
|
66
76
|
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
77
|
+
# An Integer sets the line coverage threshold. A Hash sets a threshold per
|
|
78
|
+
# criterion, e.g. `{ line: 100, branch: 90, method: 100 }`. Passed straight
|
|
79
|
+
# through to `SimpleCov.minimum_coverage`; any criterion given here is
|
|
80
|
+
# automatically enabled via `SimpleCov.enable_coverage`.
|
|
69
81
|
#
|
|
70
|
-
# @param fail_on_low_coverage [Boolean] whether to fail if
|
|
82
|
+
# @param fail_on_low_coverage [Boolean] whether to fail if coverage is below the threshold (default: true)
|
|
71
83
|
#
|
|
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`.
|
|
84
|
+
# When `false` (or when RSpec is in dry-run mode), `SimpleCov.minimum_coverage`
|
|
85
|
+
# is never set, so SimpleCov will not fail the build regardless of coverage.
|
|
77
86
|
#
|
|
78
|
-
#
|
|
87
|
+
# Read from the `FAIL_ON_LOW_COVERAGE` environment variable if set: `true`,
|
|
88
|
+
# `yes`, `on`, or `1` (case-insensitive) enables it; anything else disables it.
|
|
79
89
|
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
90
|
+
# @param list_uncovered [false, :all, Symbol, Array<Symbol>] which coverage criteria to
|
|
91
|
+
# list uncovered items for (default: false)
|
|
82
92
|
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# other value will cause this setting to be `false`.
|
|
93
|
+
# `false` reports nothing. `:all` reports line, branch, and method. A Symbol or
|
|
94
|
+
# Array of Symbols (`:line`, `:branch`, `:method`) reports just those criteria,
|
|
95
|
+
# independent of what `minimum_coverage` enforces.
|
|
87
96
|
#
|
|
88
|
-
#
|
|
97
|
+
# Read from the `LIST_UNCOVERED` environment variable if set: `all`, `true`,
|
|
98
|
+
# `yes`, `on`, or `1` means every criterion; `false`, `no`, `off`, or `0` means
|
|
99
|
+
# none; otherwise a comma-separated list of criteria, e.g. `line,branch`.
|
|
89
100
|
#
|
|
90
|
-
# @param
|
|
101
|
+
# @param list_uncovered_detail [Boolean] list individual items, or just a count (default: true)
|
|
91
102
|
#
|
|
92
|
-
#
|
|
103
|
+
# When `false`, only the count of uncovered items per criterion is printed,
|
|
104
|
+
# followed by a hint on how to see the details.
|
|
105
|
+
#
|
|
106
|
+
# Read from the `LIST_UNCOVERED_DETAIL` environment variable if set: `true`,
|
|
107
|
+
# `yes`, `on`, or `1` (case-insensitive) shows details; anything else summarizes.
|
|
108
|
+
#
|
|
109
|
+
# @param list_uncovered_files [nil, Symbol, String, Array<String>, #call] which files to
|
|
110
|
+
# list uncovered items for (default: nil)
|
|
93
111
|
#
|
|
94
|
-
#
|
|
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.
|
|
95
118
|
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
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
|
+
#
|
|
130
|
+
# @param start_config_block [Proc] a configuration block to pass to `SimpleCov.start` (default: nil)
|
|
131
|
+
#
|
|
132
|
+
# @param rspec_dry_run [Boolean] whether the rspec run is a dry run
|
|
133
|
+
#
|
|
134
|
+
# Typically not set by the user. If RSpec is being run in dry run mode, test
|
|
135
|
+
# coverage under the threshold will not fail the build. This allows test
|
|
136
|
+
# coverage to be run in a dry run by an IDE so it can report failed tests and
|
|
137
|
+
# coverage without reporting that the entire RSpec run has failed.
|
|
99
138
|
#
|
|
100
139
|
# @param simplecov_module [Module] the SimpleCov module (default: ::SimpleCov)
|
|
101
140
|
#
|
|
102
141
|
# Typically not set by the user. Used for this gem's unit testing.
|
|
103
142
|
#
|
|
104
|
-
# Allows the SimpleCov module to be mocked.
|
|
105
|
-
#
|
|
106
143
|
# @param env [Hash] the environment variables (default: ENV)
|
|
107
144
|
#
|
|
108
145
|
# Typically not set by the user. Used for this gem's unit testing.
|
|
109
146
|
#
|
|
110
147
|
# @example Initialize SimpleCov with a test coverage threshold other than 100%
|
|
111
|
-
# SimpleCov::RSpec.start(
|
|
148
|
+
# SimpleCov::RSpec.start(minimum_coverage: 90)
|
|
112
149
|
#
|
|
113
150
|
# @example Initialize SimpleCov to not fail the test run if the coverage is below the threshold
|
|
114
151
|
# SimpleCov::RSpec.start(fail_on_low_coverage: false)
|
|
115
152
|
#
|
|
116
153
|
# # OR use an environment variable to override the default
|
|
117
|
-
#
|
|
118
|
-
#
|
|
154
|
+
# FAIL_ON_LOW_COVERAGE=true rspec
|
|
155
|
+
#
|
|
156
|
+
# @example Initialize SimpleCov to list the lines, branches, and methods not covered by tests
|
|
157
|
+
# SimpleCov::RSpec.start(list_uncovered: :all)
|
|
119
158
|
#
|
|
120
|
-
# # OR use an environment variable to override the default
|
|
121
|
-
#
|
|
159
|
+
# # OR use an environment variable to override the default
|
|
160
|
+
# LIST_UNCOVERED=all rspec
|
|
122
161
|
#
|
|
123
|
-
# @example
|
|
124
|
-
# SimpleCov::RSpec.start(
|
|
162
|
+
# @example List uncovered items for one file only
|
|
163
|
+
# SimpleCov::RSpec.start(list_uncovered: :all, list_uncovered_files: 'lib/example_project/parser.rb')
|
|
125
164
|
#
|
|
126
165
|
# # OR use an environment variable to override the default
|
|
127
|
-
#
|
|
128
|
-
# SimpleCov::RSpec.start
|
|
166
|
+
# LIST_UNCOVERED_FILES=lib/example_project/parser.rb rspec
|
|
129
167
|
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
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
|
|
132
173
|
#
|
|
133
174
|
def self.start(...) = new(...).send(:start)
|
|
134
175
|
|
|
135
|
-
#
|
|
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
|
|
136
200
|
|
|
137
|
-
# Environment variable to override
|
|
201
|
+
# Environment variable to override minimum_coverage[:line]
|
|
138
202
|
# @api private
|
|
139
203
|
# @private
|
|
140
204
|
COVERAGE_THRESHOLD = 'COVERAGE_THRESHOLD'
|
|
141
205
|
|
|
206
|
+
# Environment variable to override minimum_coverage[:branch]
|
|
207
|
+
# @api private
|
|
208
|
+
# @private
|
|
209
|
+
COVERAGE_THRESHOLD_BRANCH = 'COVERAGE_THRESHOLD_BRANCH'
|
|
210
|
+
|
|
211
|
+
# Environment variable to override minimum_coverage[:method]
|
|
212
|
+
# @api private
|
|
213
|
+
# @private
|
|
214
|
+
COVERAGE_THRESHOLD_METHOD = 'COVERAGE_THRESHOLD_METHOD'
|
|
215
|
+
|
|
142
216
|
# Environment variable to override fail_on_low_coverage
|
|
143
217
|
# @api private
|
|
144
218
|
# @private
|
|
145
219
|
FAIL_ON_LOW_COVERAGE = 'FAIL_ON_LOW_COVERAGE'
|
|
146
220
|
|
|
147
|
-
# Environment variable to override
|
|
221
|
+
# Environment variable to override list_uncovered
|
|
222
|
+
# @api private
|
|
223
|
+
# @private
|
|
224
|
+
LIST_UNCOVERED = 'LIST_UNCOVERED'
|
|
225
|
+
|
|
226
|
+
# Environment variable to override list_uncovered_detail
|
|
227
|
+
# @api private
|
|
228
|
+
# @private
|
|
229
|
+
LIST_UNCOVERED_DETAIL = 'LIST_UNCOVERED_DETAIL'
|
|
230
|
+
|
|
231
|
+
# Environment variable to override list_uncovered_files
|
|
148
232
|
# @api private
|
|
149
233
|
# @private
|
|
150
|
-
|
|
234
|
+
LIST_UNCOVERED_FILES = 'LIST_UNCOVERED_FILES'
|
|
151
235
|
|
|
152
|
-
#
|
|
236
|
+
# Maps a coverage criterion to the environment variable that overrides its threshold
|
|
153
237
|
# @api private
|
|
154
238
|
# @private
|
|
155
|
-
|
|
239
|
+
CRITERION_ENV_VARS = {
|
|
240
|
+
line: COVERAGE_THRESHOLD,
|
|
241
|
+
branch: COVERAGE_THRESHOLD_BRANCH,
|
|
242
|
+
method: COVERAGE_THRESHOLD_METHOD
|
|
243
|
+
}.freeze
|
|
244
|
+
|
|
245
|
+
# Default value for minimum_coverage
|
|
246
|
+
# @api private
|
|
247
|
+
# @private
|
|
248
|
+
DEFAULT_MINIMUM_COVERAGE = { line: 100 }.freeze
|
|
156
249
|
|
|
157
250
|
# Default value for fail_on_low_coverage
|
|
158
251
|
# @api private
|
|
159
252
|
# @private
|
|
160
|
-
|
|
253
|
+
DEFAULT_FAIL_ON_LOW_COVERAGE = true
|
|
254
|
+
|
|
255
|
+
# Default value for list_uncovered_detail
|
|
256
|
+
# @api private
|
|
257
|
+
# @private
|
|
258
|
+
DEFAULT_LIST_UNCOVERED_DETAIL = true
|
|
161
259
|
|
|
162
|
-
#
|
|
260
|
+
# Environment variable values that mean "true"
|
|
163
261
|
# @api private
|
|
164
262
|
# @private
|
|
165
|
-
|
|
263
|
+
TRUTHY_ENV_VALUES = %w[yes on true 1].freeze
|
|
166
264
|
|
|
167
265
|
attr_reader :env, :simplecov_module, :start_config_block
|
|
168
266
|
|
|
169
|
-
# The coverage threshold
|
|
267
|
+
# The minimum coverage threshold, per criterion
|
|
170
268
|
#
|
|
171
|
-
# Searches the ENV, the value given in the `.start` method, and the default value
|
|
172
|
-
#
|
|
269
|
+
# Searches the ENV, the value given in the `.start` method, and the default value,
|
|
270
|
+
# merging them (ENV takes precedence per-criterion).
|
|
173
271
|
#
|
|
174
|
-
# @return [Integer]
|
|
272
|
+
# @return [Hash{Symbol => Integer}]
|
|
175
273
|
#
|
|
176
274
|
# @api private
|
|
177
275
|
# @private
|
|
178
276
|
#
|
|
179
|
-
def
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
277
|
+
def minimum_coverage
|
|
278
|
+
base = @minimum_coverage
|
|
279
|
+
base = { line: base } if base.is_a?(Integer)
|
|
280
|
+
validate_minimum_coverage!(base)
|
|
183
281
|
|
|
184
|
-
|
|
282
|
+
(base || DEFAULT_MINIMUM_COVERAGE).merge(env_minimum_coverage_overrides)
|
|
185
283
|
end
|
|
186
284
|
|
|
187
285
|
# Whether to fail if the coverage is below the threshold
|
|
@@ -196,55 +294,65 @@ module SimpleCov
|
|
|
196
294
|
#
|
|
197
295
|
def fail_on_low_coverage?
|
|
198
296
|
return false if rspec_dry_run?
|
|
199
|
-
|
|
200
297
|
return env_true?(FAIL_ON_LOW_COVERAGE) if env.key?(FAIL_ON_LOW_COVERAGE)
|
|
201
|
-
|
|
202
298
|
return @fail_on_low_coverage unless @fail_on_low_coverage.nil?
|
|
203
299
|
|
|
204
|
-
|
|
300
|
+
DEFAULT_FAIL_ON_LOW_COVERAGE
|
|
205
301
|
end
|
|
206
302
|
|
|
207
|
-
#
|
|
303
|
+
# The coverage criteria to list uncovered items for
|
|
208
304
|
#
|
|
209
|
-
# Searches the ENV
|
|
210
|
-
#
|
|
305
|
+
# Searches the ENV and the value given in the `.start` method, and normalizes
|
|
306
|
+
# the result to an Array of Symbols in a fixed (line, branch, method) order.
|
|
211
307
|
#
|
|
212
|
-
# @return [
|
|
308
|
+
# @return [Array<Symbol>]
|
|
213
309
|
#
|
|
214
310
|
# @api private
|
|
215
311
|
# @private
|
|
216
312
|
#
|
|
217
|
-
def
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
return @list_uncovered_lines unless @list_uncovered_lines.nil?
|
|
313
|
+
def list_uncovered_criteria
|
|
314
|
+
@list_uncovered_criteria ||= ListUncoveredOption.resolve(@list_uncovered, env: env, env_var: LIST_UNCOVERED)
|
|
315
|
+
end
|
|
221
316
|
|
|
222
|
-
|
|
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
|
+
)
|
|
223
332
|
end
|
|
224
333
|
|
|
225
|
-
# Whether
|
|
334
|
+
# Whether to list individual uncovered items, or just a count per criterion
|
|
226
335
|
#
|
|
227
336
|
# @return [Boolean]
|
|
228
337
|
#
|
|
229
338
|
# @api private
|
|
230
339
|
# @private
|
|
231
340
|
#
|
|
232
|
-
def
|
|
341
|
+
def list_uncovered_detail?
|
|
342
|
+
return env_true?(LIST_UNCOVERED_DETAIL) if env.key?(LIST_UNCOVERED_DETAIL)
|
|
343
|
+
return @list_uncovered_detail unless @list_uncovered_detail.nil?
|
|
233
344
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
#
|
|
238
|
-
# @return [String]
|
|
239
|
-
# @api private
|
|
345
|
+
DEFAULT_LIST_UNCOVERED_DETAIL
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# Whether the rspec run is a dry run
|
|
240
349
|
#
|
|
241
|
-
#
|
|
242
|
-
# The line number of the uncovered line
|
|
243
|
-
# @return [Integer]
|
|
244
|
-
# @api private
|
|
350
|
+
# @return [Boolean]
|
|
245
351
|
#
|
|
246
352
|
# @api private
|
|
247
|
-
|
|
353
|
+
# @private
|
|
354
|
+
#
|
|
355
|
+
def rspec_dry_run? = @rspec_dry_run
|
|
248
356
|
|
|
249
357
|
private
|
|
250
358
|
|
|
@@ -255,17 +363,21 @@ module SimpleCov
|
|
|
255
363
|
# @api private
|
|
256
364
|
# @private
|
|
257
365
|
def initialize(
|
|
258
|
-
|
|
366
|
+
minimum_coverage: nil,
|
|
259
367
|
fail_on_low_coverage: nil,
|
|
260
|
-
|
|
368
|
+
list_uncovered: nil,
|
|
369
|
+
list_uncovered_detail: nil,
|
|
370
|
+
list_uncovered_files: nil,
|
|
261
371
|
rspec_dry_run: ::RSpec.configuration.dry_run?,
|
|
262
372
|
env: ENV,
|
|
263
373
|
simplecov_module: ::SimpleCov,
|
|
264
374
|
&start_config_block
|
|
265
375
|
)
|
|
266
|
-
@
|
|
376
|
+
@minimum_coverage = minimum_coverage
|
|
267
377
|
@fail_on_low_coverage = fail_on_low_coverage
|
|
268
|
-
@
|
|
378
|
+
@list_uncovered = list_uncovered
|
|
379
|
+
@list_uncovered_detail = list_uncovered_detail
|
|
380
|
+
@list_uncovered_files = list_uncovered_files
|
|
269
381
|
@start_config_block = start_config_block
|
|
270
382
|
@rspec_dry_run = rspec_dry_run
|
|
271
383
|
@env = env
|
|
@@ -274,15 +386,29 @@ module SimpleCov
|
|
|
274
386
|
|
|
275
387
|
# rubocop:enable Metrics/ParameterLists
|
|
276
388
|
|
|
277
|
-
#
|
|
389
|
+
# Configure SimpleCov and start it
|
|
278
390
|
# @return [Void]
|
|
279
391
|
# @api private
|
|
280
392
|
# @private
|
|
281
393
|
def start
|
|
282
394
|
simplecov_module.at_exit(&at_exit_hook)
|
|
395
|
+
simplecov_module.enable_coverage(*criteria_to_enable) if criteria_to_enable.any?
|
|
396
|
+
simplecov_module.minimum_coverage(minimum_coverage) if enforce_minimum_coverage?
|
|
283
397
|
simplecov_module.start(&start_config_block)
|
|
284
398
|
end
|
|
285
399
|
|
|
400
|
+
# Whether SimpleCov should be configured to enforce minimum_coverage itself
|
|
401
|
+
# @return [Boolean]
|
|
402
|
+
# @api private
|
|
403
|
+
# @private
|
|
404
|
+
def enforce_minimum_coverage? = !rspec_dry_run? && fail_on_low_coverage?
|
|
405
|
+
|
|
406
|
+
# The coverage criteria that need to be enabled via SimpleCov.enable_coverage
|
|
407
|
+
# @return [Array<Symbol>]
|
|
408
|
+
# @api private
|
|
409
|
+
# @private
|
|
410
|
+
def criteria_to_enable = (minimum_coverage.keys + list_uncovered_criteria).uniq
|
|
411
|
+
|
|
286
412
|
# Called by SimpleCov.at_exit
|
|
287
413
|
# @return [Proc]
|
|
288
414
|
# @api private
|
|
@@ -290,102 +416,85 @@ module SimpleCov
|
|
|
290
416
|
def at_exit_hook
|
|
291
417
|
lambda do
|
|
292
418
|
simplecov_module.result.format!
|
|
293
|
-
|
|
294
|
-
exit 1 if coverage_below_threshold? && fail_on_low_coverage?
|
|
419
|
+
output_uncovered_report
|
|
295
420
|
end
|
|
296
421
|
end
|
|
297
422
|
|
|
298
|
-
# Output the
|
|
423
|
+
# Output the uncovered items report (detailed or summary), if requested
|
|
299
424
|
# @return [Void]
|
|
300
425
|
# @api private
|
|
301
426
|
# @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
|
|
427
|
+
def output_uncovered_report
|
|
428
|
+
return if list_uncovered_criteria.empty?
|
|
307
429
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
430
|
+
report = UncoveredReport.new(
|
|
431
|
+
result: simplecov_module.result, criteria: list_uncovered_criteria, detail: list_uncovered_detail?,
|
|
432
|
+
detail_env_var: LIST_UNCOVERED_DETAIL, files: list_uncovered_files
|
|
433
|
+
).to_s
|
|
434
|
+
return if report.empty?
|
|
313
435
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
# @private
|
|
318
|
-
def coverage_below_threshold? = simplecov_module.result.covered_percent < coverage_threshold
|
|
436
|
+
$stderr.puts
|
|
437
|
+
$stderr.puts report
|
|
438
|
+
end
|
|
319
439
|
|
|
320
|
-
#
|
|
440
|
+
# Raise unless `base` is a valid `minimum_coverage` value
|
|
441
|
+
#
|
|
442
|
+
# Valid values include `nil`, or a Hash keyed by `:line`, `:branch`, and/or
|
|
443
|
+
# `:method`, with Numeric values
|
|
444
|
+
#
|
|
445
|
+
# @param base [nil, Hash] the normalized `minimum_coverage` value
|
|
321
446
|
# @return [Void]
|
|
447
|
+
# @raise [ArgumentError] if `base` is not nil or a Hash, has an unknown key, or has a
|
|
448
|
+
# non-Numeric value
|
|
322
449
|
# @api private
|
|
323
450
|
# @private
|
|
324
|
-
def
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
end
|
|
451
|
+
def validate_minimum_coverage!(base)
|
|
452
|
+
unless base.nil? || base.is_a?(Hash)
|
|
453
|
+
raise ArgumentError, "minimum_coverage must be nil, an Integer, or a Hash; got #{base.inspect}"
|
|
454
|
+
end
|
|
329
455
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
# @api private
|
|
333
|
-
def uncovered_lines_found? = simplecov_module.result.files.any? { |source_file| source_file.missed_lines.any? }
|
|
456
|
+
base&.each_pair { |criterion, threshold| validate_minimum_coverage_entry!(criterion, threshold) }
|
|
457
|
+
end
|
|
334
458
|
|
|
335
|
-
#
|
|
336
|
-
# @
|
|
459
|
+
# Raise unless `criterion` is a known criterion with a Numeric `threshold`
|
|
460
|
+
# @param criterion [Object] the candidate `minimum_coverage` key
|
|
461
|
+
# @param threshold [Object] the candidate `minimum_coverage` value
|
|
462
|
+
# @return [Void]
|
|
463
|
+
# @raise [ArgumentError] if `criterion` is unknown, or `threshold` is not Numeric
|
|
337
464
|
# @api private
|
|
338
465
|
# @private
|
|
339
|
-
def
|
|
466
|
+
def validate_minimum_coverage_entry!(criterion, threshold)
|
|
467
|
+
unless CRITERION_ENV_VARS.key?(criterion)
|
|
468
|
+
raise ArgumentError,
|
|
469
|
+
"minimum_coverage keys must be #{CRITERION_ENV_VARS.keys.inspect}; got #{criterion.inspect}"
|
|
470
|
+
end
|
|
340
471
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
|
472
|
+
return if threshold.is_a?(Numeric)
|
|
473
|
+
|
|
474
|
+
raise ArgumentError,
|
|
475
|
+
"minimum_coverage[#{criterion.inspect}] must be a Numeric; got #{threshold.inspect}"
|
|
352
476
|
end
|
|
353
477
|
|
|
354
|
-
#
|
|
355
|
-
# @
|
|
356
|
-
# @param singular [String] the singular form of the phrase
|
|
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
|
|
361
|
-
|
|
362
|
-
# Output the uncovered lines
|
|
363
|
-
# @return [Void]
|
|
478
|
+
# The per-criterion COVERAGE_THRESHOLD* ENV overrides, as a minimum_coverage Hash
|
|
479
|
+
# @return [Hash{Symbol => Integer}]
|
|
364
480
|
# @api private
|
|
365
481
|
# @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}"
|
|
482
|
+
def env_minimum_coverage_overrides
|
|
483
|
+
CRITERION_ENV_VARS.each_with_object({}) do |(criterion, var), overrides|
|
|
484
|
+
overrides[criterion] = env.fetch(var).to_i if env.key?(var)
|
|
373
485
|
end
|
|
374
486
|
end
|
|
375
487
|
|
|
376
488
|
# Return `true` if the environment variable is set to a truthy value
|
|
377
489
|
#
|
|
378
490
|
# @example
|
|
379
|
-
# env_true?('
|
|
491
|
+
# env_true?('LIST_UNCOVERED_DETAIL')
|
|
380
492
|
#
|
|
381
493
|
# @param name [String] the name of the environment variable
|
|
382
494
|
# @return [Boolean]
|
|
383
495
|
# @api private
|
|
384
496
|
# @private
|
|
385
497
|
#
|
|
386
|
-
def env_true?(name)
|
|
387
|
-
value = env.fetch(name, '').downcase
|
|
388
|
-
%w[yes on true 1].include?(value)
|
|
389
|
-
end
|
|
498
|
+
def env_true?(name) = TRUTHY_ENV_VALUES.include?(env.fetch(name, '').downcase)
|
|
390
499
|
end
|
|
391
500
|
end
|