simplecov 0.18.5 → 0.21.1
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/CHANGELOG.md +75 -520
- data/README.md +112 -29
- data/doc/alternate-formatters.md +12 -2
- data/doc/commercial-services.md +5 -0
- data/lib/minitest/simplecov_plugin.rb +1 -1
- data/lib/simplecov.rb +126 -137
- data/lib/simplecov/configuration.rb +90 -15
- data/lib/simplecov/default_formatter.rb +20 -0
- data/lib/simplecov/defaults.rb +11 -8
- data/lib/simplecov/exit_codes.rb +5 -0
- data/lib/simplecov/exit_codes/exit_code_handling.rb +29 -0
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +73 -0
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +54 -0
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +53 -0
- data/lib/simplecov/file_list.rb +12 -6
- data/lib/simplecov/filter.rb +7 -5
- data/lib/simplecov/formatter.rb +2 -2
- data/lib/simplecov/formatter/multi_formatter.rb +5 -7
- data/lib/simplecov/lines_classifier.rb +3 -3
- data/lib/simplecov/no_defaults.rb +1 -1
- data/lib/simplecov/process.rb +19 -0
- data/lib/simplecov/result.rb +8 -35
- data/lib/simplecov/result_merger.rb +121 -57
- data/lib/simplecov/source_file/line.rb +1 -1
- data/lib/simplecov/useless_results_remover.rb +5 -3
- data/lib/simplecov/version.rb +1 -1
- metadata +31 -12
- data/CODE_OF_CONDUCT.md +0 -76
- data/CONTRIBUTING.md +0 -51
- data/ISSUE_TEMPLATE.md +0 -23
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "fileutils"
|
4
4
|
require "docile"
|
5
|
-
|
5
|
+
require_relative "formatter/multi_formatter"
|
6
6
|
|
7
7
|
module SimpleCov
|
8
8
|
#
|
@@ -10,7 +10,7 @@ module SimpleCov
|
|
10
10
|
# defined here are usable from SimpleCov directly. Please check out
|
11
11
|
# SimpleCov documentation for further info.
|
12
12
|
#
|
13
|
-
module Configuration
|
13
|
+
module Configuration
|
14
14
|
attr_writer :filters, :groups, :formatter, :print_error_status
|
15
15
|
|
16
16
|
#
|
@@ -22,6 +22,7 @@ module SimpleCov
|
|
22
22
|
def root(root = nil)
|
23
23
|
return @root if defined?(@root) && root.nil?
|
24
24
|
|
25
|
+
@coverage_path = nil # invalidate cache
|
25
26
|
@root = File.expand_path(root || Dir.getwd)
|
26
27
|
end
|
27
28
|
|
@@ -190,12 +191,59 @@ module SimpleCov
|
|
190
191
|
# end
|
191
192
|
#
|
192
193
|
def at_exit(&block)
|
193
|
-
return
|
194
|
+
return Proc.new unless running || block_given?
|
194
195
|
|
195
196
|
@at_exit = block if block_given?
|
196
197
|
@at_exit ||= proc { SimpleCov.result.format! }
|
197
198
|
end
|
198
199
|
|
200
|
+
# gets or sets the enabled_for_subprocess configuration
|
201
|
+
# when true, this will inject SimpleCov code into Process.fork
|
202
|
+
def enable_for_subprocesses(value = nil)
|
203
|
+
return @enable_for_subprocesses if defined?(@enable_for_subprocesses) && value.nil?
|
204
|
+
|
205
|
+
@enable_for_subprocesses = value || false
|
206
|
+
end
|
207
|
+
|
208
|
+
# gets the enabled_for_subprocess configuration
|
209
|
+
def enabled_for_subprocesses?
|
210
|
+
enable_for_subprocesses
|
211
|
+
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Gets or sets the behavior to start a new forked Process.
|
215
|
+
#
|
216
|
+
# By default, it will add " (Process #{pid})" to the command_name, and start SimpleCov in quiet mode
|
217
|
+
#
|
218
|
+
# Configure with:
|
219
|
+
#
|
220
|
+
# SimpleCov.at_fork do |pid|
|
221
|
+
# SimpleCov.start do
|
222
|
+
# # This needs a unique name so it won't be ovewritten
|
223
|
+
# SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
|
224
|
+
# # be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
|
225
|
+
# SimpleCov.print_error_status = false
|
226
|
+
# SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
|
227
|
+
# SimpleCov.minimum_coverage 0
|
228
|
+
# # start
|
229
|
+
# SimpleCov.start
|
230
|
+
# end
|
231
|
+
# end
|
232
|
+
#
|
233
|
+
def at_fork(&block)
|
234
|
+
@at_fork = block if block_given?
|
235
|
+
@at_fork ||= lambda { |pid|
|
236
|
+
# This needs a unique name so it won't be ovewritten
|
237
|
+
SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
|
238
|
+
# be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
|
239
|
+
SimpleCov.print_error_status = false
|
240
|
+
SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
|
241
|
+
SimpleCov.minimum_coverage 0
|
242
|
+
# start
|
243
|
+
SimpleCov.start
|
244
|
+
}
|
245
|
+
end
|
246
|
+
|
199
247
|
#
|
200
248
|
# Returns the project name - currently assuming the last dirname in
|
201
249
|
# the SimpleCov.root is this.
|
@@ -242,15 +290,20 @@ module SimpleCov
|
|
242
290
|
def minimum_coverage(coverage = nil)
|
243
291
|
return @minimum_coverage ||= {} unless coverage
|
244
292
|
|
245
|
-
coverage = {
|
246
|
-
|
247
|
-
coverage
|
248
|
-
minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
|
249
|
-
end
|
293
|
+
coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
|
294
|
+
|
295
|
+
raise_on_invalid_coverage(coverage, "minimum_coverage")
|
250
296
|
|
251
297
|
@minimum_coverage = coverage
|
252
298
|
end
|
253
299
|
|
300
|
+
def raise_on_invalid_coverage(coverage, coverage_setting)
|
301
|
+
coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) }
|
302
|
+
coverage.each_value do |percent|
|
303
|
+
minimum_possible_coverage_exceeded(coverage_setting) if percent && percent > 100
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
254
307
|
#
|
255
308
|
# Defines the maximum coverage drop at once allowed for the testsuite to pass.
|
256
309
|
# SimpleCov will return non-zero if the coverage decreases by more than this threshold.
|
@@ -258,7 +311,13 @@ module SimpleCov
|
|
258
311
|
# Default is 100% (disabled)
|
259
312
|
#
|
260
313
|
def maximum_coverage_drop(coverage_drop = nil)
|
261
|
-
@maximum_coverage_drop ||=
|
314
|
+
return @maximum_coverage_drop ||= {} unless coverage_drop
|
315
|
+
|
316
|
+
coverage_drop = {primary_coverage => coverage_drop} if coverage_drop.is_a?(Numeric)
|
317
|
+
|
318
|
+
raise_on_invalid_coverage(coverage_drop, "maximum_coverage_drop")
|
319
|
+
|
320
|
+
@maximum_coverage_drop = coverage_drop
|
262
321
|
end
|
263
322
|
|
264
323
|
#
|
@@ -269,16 +328,23 @@ module SimpleCov
|
|
269
328
|
# Default is 0% (disabled)
|
270
329
|
#
|
271
330
|
def minimum_coverage_by_file(coverage = nil)
|
272
|
-
|
273
|
-
|
331
|
+
return @minimum_coverage_by_file ||= {} unless coverage
|
332
|
+
|
333
|
+
coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
|
334
|
+
|
335
|
+
raise_on_invalid_coverage(coverage, "minimum_coverage_by_file")
|
336
|
+
|
337
|
+
@minimum_coverage_by_file = coverage
|
274
338
|
end
|
275
339
|
|
276
340
|
#
|
277
341
|
# Refuses any coverage drop. That is, coverage is only allowed to increase.
|
278
342
|
# SimpleCov will return non-zero if the coverage decreases.
|
279
343
|
#
|
280
|
-
def refuse_coverage_drop
|
281
|
-
|
344
|
+
def refuse_coverage_drop(*criteria)
|
345
|
+
criteria = coverage_criteria if criteria.empty?
|
346
|
+
|
347
|
+
maximum_coverage_drop(criteria.map { |c| [c, 0] }.to_h)
|
282
348
|
end
|
283
349
|
|
284
350
|
#
|
@@ -325,7 +391,7 @@ module SimpleCov
|
|
325
391
|
# @param [Symbol] criterion
|
326
392
|
#
|
327
393
|
def coverage_criterion(criterion = nil)
|
328
|
-
return @coverage_criterion ||=
|
394
|
+
return @coverage_criterion ||= primary_coverage unless criterion
|
329
395
|
|
330
396
|
raise_if_criterion_unsupported(criterion)
|
331
397
|
|
@@ -338,8 +404,17 @@ module SimpleCov
|
|
338
404
|
coverage_criteria << criterion
|
339
405
|
end
|
340
406
|
|
407
|
+
def primary_coverage(criterion = nil)
|
408
|
+
if criterion.nil?
|
409
|
+
@primary_coverage ||= DEFAULT_COVERAGE_CRITERION
|
410
|
+
else
|
411
|
+
raise_if_criterion_disabled(criterion)
|
412
|
+
@primary_coverage = criterion
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
341
416
|
def coverage_criteria
|
342
|
-
@coverage_criteria ||= Set[
|
417
|
+
@coverage_criteria ||= Set[primary_coverage]
|
343
418
|
end
|
344
419
|
|
345
420
|
def coverage_criterion_enabled?(criterion)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "simplecov-html"
|
4
|
+
module SimpleCov
|
5
|
+
module Formatter
|
6
|
+
class << self
|
7
|
+
def from_env(env)
|
8
|
+
formatters = [SimpleCov::Formatter::HTMLFormatter]
|
9
|
+
|
10
|
+
# When running under a CI that uses CodeClimate, JSON output is expected
|
11
|
+
if env.fetch("CC_TEST_REPORTER_ID", nil)
|
12
|
+
require "simplecov_json_formatter"
|
13
|
+
formatters.push(SimpleCov::Formatter::JSONFormatter)
|
14
|
+
end
|
15
|
+
|
16
|
+
formatters
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/simplecov/defaults.rb
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Load default formatter gem
|
4
|
-
require "simplecov-html"
|
5
4
|
require "pathname"
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
require_relative "default_formatter"
|
6
|
+
require_relative "profiles/root_filter"
|
7
|
+
require_relative "profiles/test_frameworks"
|
8
|
+
require_relative "profiles/bundler_filter"
|
9
|
+
require_relative "profiles/hidden_filter"
|
10
|
+
require_relative "profiles/rails"
|
11
11
|
|
12
12
|
# Default configuration
|
13
13
|
SimpleCov.configure do
|
14
|
-
formatter SimpleCov::Formatter::
|
14
|
+
formatter SimpleCov::Formatter::MultiFormatter.new(
|
15
|
+
SimpleCov::Formatter.from_env(ENV)
|
16
|
+
)
|
17
|
+
|
15
18
|
load_profile "bundler_filter"
|
16
19
|
load_profile "hidden_filter"
|
17
20
|
# Exclude files outside of SimpleCov.root
|
@@ -28,7 +31,7 @@ at_exit do
|
|
28
31
|
end
|
29
32
|
|
30
33
|
# Autoload config from ~/.simplecov if present
|
31
|
-
|
34
|
+
require_relative "load_global_config"
|
32
35
|
|
33
36
|
# Autoload config from .simplecov if present
|
34
37
|
# Recurse upwards until we find .simplecov or reach the root directory
|
data/lib/simplecov/exit_codes.rb
CHANGED
@@ -8,3 +8,8 @@ module SimpleCov
|
|
8
8
|
MAXIMUM_COVERAGE_DROP = 3
|
9
9
|
end
|
10
10
|
end
|
11
|
+
|
12
|
+
require_relative "exit_codes/exit_code_handling"
|
13
|
+
require_relative "exit_codes/maximum_coverage_drop_check"
|
14
|
+
require_relative "exit_codes/minimum_coverage_by_file_check"
|
15
|
+
require_relative "exit_codes/minimum_overall_coverage_check"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleCov
|
4
|
+
module ExitCodes
|
5
|
+
module ExitCodeHandling
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def call(result, coverage_limits:)
|
9
|
+
checks = coverage_checks(result, coverage_limits)
|
10
|
+
|
11
|
+
failing_check = checks.find(&:failing?)
|
12
|
+
if failing_check
|
13
|
+
failing_check.report
|
14
|
+
failing_check.exit_code
|
15
|
+
else
|
16
|
+
SimpleCov::ExitCodes::SUCCESS
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def coverage_checks(result, coverage_limits)
|
21
|
+
[
|
22
|
+
MinimumOverallCoverageCheck.new(result, coverage_limits.minimum_coverage),
|
23
|
+
MinimumCoverageByFileCheck.new(result, coverage_limits.minimum_coverage_by_file),
|
24
|
+
MaximumCoverageDropCheck.new(result, coverage_limits.maximum_coverage_drop)
|
25
|
+
]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleCov
|
4
|
+
module ExitCodes
|
5
|
+
class MaximumCoverageDropCheck
|
6
|
+
def initialize(result, maximum_coverage_drop)
|
7
|
+
@result = result
|
8
|
+
@maximum_coverage_drop = maximum_coverage_drop
|
9
|
+
end
|
10
|
+
|
11
|
+
def failing?
|
12
|
+
return false unless maximum_coverage_drop && last_run
|
13
|
+
|
14
|
+
coverage_drop_violations.any?
|
15
|
+
end
|
16
|
+
|
17
|
+
def report
|
18
|
+
coverage_drop_violations.each do |violation|
|
19
|
+
$stderr.printf(
|
20
|
+
"%<criterion>s coverage has dropped by %<drop_percent>.2f%% since the last time (maximum allowed: %<max_drop>.2f%%).\n",
|
21
|
+
criterion: violation[:criterion].capitalize,
|
22
|
+
drop_percent: SimpleCov.round_coverage(violation[:drop_percent]),
|
23
|
+
max_drop: violation[:max_drop]
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def exit_code
|
29
|
+
SimpleCov::ExitCodes::MAXIMUM_COVERAGE_DROP
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :result, :maximum_coverage_drop
|
35
|
+
|
36
|
+
def last_run
|
37
|
+
return @last_run if defined?(@last_run)
|
38
|
+
|
39
|
+
@last_run = SimpleCov::LastRun.read
|
40
|
+
end
|
41
|
+
|
42
|
+
def coverage_drop_violations
|
43
|
+
@coverage_drop_violations ||=
|
44
|
+
compute_coverage_drop_data.select do |achieved|
|
45
|
+
achieved.fetch(:max_drop) < achieved.fetch(:drop_percent)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def compute_coverage_drop_data
|
50
|
+
maximum_coverage_drop.map do |criterion, percent|
|
51
|
+
{
|
52
|
+
criterion: criterion,
|
53
|
+
max_drop: percent,
|
54
|
+
drop_percent: last_coverage(criterion) -
|
55
|
+
SimpleCov.round_coverage(
|
56
|
+
result.coverage_statistics.fetch(criterion).percent
|
57
|
+
)
|
58
|
+
}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def last_coverage(criterion)
|
63
|
+
last_coverage_percent = last_run[:result][criterion]
|
64
|
+
|
65
|
+
if !last_coverage_percent && criterion == "line"
|
66
|
+
last_run[:result][:covered_percent]
|
67
|
+
else
|
68
|
+
last_coverage_percent
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleCov
|
4
|
+
module ExitCodes
|
5
|
+
class MinimumCoverageByFileCheck
|
6
|
+
def initialize(result, minimum_coverage_by_file)
|
7
|
+
@result = result
|
8
|
+
@minimum_coverage_by_file = minimum_coverage_by_file
|
9
|
+
end
|
10
|
+
|
11
|
+
def failing?
|
12
|
+
minimum_violations.any?
|
13
|
+
end
|
14
|
+
|
15
|
+
def report
|
16
|
+
minimum_violations.each do |violation|
|
17
|
+
$stderr.printf(
|
18
|
+
"%<criterion>s coverage by file (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
|
19
|
+
covered: SimpleCov.round_coverage(violation.fetch(:actual)),
|
20
|
+
minimum_coverage: violation.fetch(:minimum_expected),
|
21
|
+
criterion: violation.fetch(:criterion).capitalize
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def exit_code
|
27
|
+
SimpleCov::ExitCodes::MINIMUM_COVERAGE
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :result, :minimum_coverage_by_file
|
33
|
+
|
34
|
+
def minimum_violations
|
35
|
+
@minimum_violations ||=
|
36
|
+
compute_minimum_coverage_data.select do |achieved|
|
37
|
+
achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def compute_minimum_coverage_data
|
42
|
+
minimum_coverage_by_file.flat_map do |criterion, expected_percent|
|
43
|
+
result.coverage_statistics_by_file.fetch(criterion).map do |actual_coverage|
|
44
|
+
{
|
45
|
+
criterion: criterion,
|
46
|
+
minimum_expected: expected_percent,
|
47
|
+
actual: SimpleCov.round_coverage(actual_coverage.percent)
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleCov
|
4
|
+
module ExitCodes
|
5
|
+
class MinimumOverallCoverageCheck
|
6
|
+
def initialize(result, minimum_coverage)
|
7
|
+
@result = result
|
8
|
+
@minimum_coverage = minimum_coverage
|
9
|
+
end
|
10
|
+
|
11
|
+
def failing?
|
12
|
+
minimum_violations.any?
|
13
|
+
end
|
14
|
+
|
15
|
+
def report
|
16
|
+
minimum_violations.each do |violation|
|
17
|
+
$stderr.printf(
|
18
|
+
"%<criterion>s coverage (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
|
19
|
+
covered: SimpleCov.round_coverage(violation.fetch(:actual)),
|
20
|
+
minimum_coverage: violation.fetch(:minimum_expected),
|
21
|
+
criterion: violation.fetch(:criterion).capitalize
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def exit_code
|
27
|
+
SimpleCov::ExitCodes::MINIMUM_COVERAGE
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :result, :minimum_coverage
|
33
|
+
|
34
|
+
def minimum_violations
|
35
|
+
@minimum_violations ||= calculate_minimum_violations
|
36
|
+
end
|
37
|
+
|
38
|
+
def calculate_minimum_violations
|
39
|
+
coverage_achieved = minimum_coverage.map do |criterion, percent|
|
40
|
+
{
|
41
|
+
criterion: criterion,
|
42
|
+
minimum_expected: percent,
|
43
|
+
actual: result.coverage_statistics.fetch(criterion).percent
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
coverage_achieved.select do |achieved|
|
48
|
+
achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|