simplecov 0.22.0 → 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/LICENSE +1 -1
- data/README.md +1058 -508
- data/doc/alternate-formatters.md +0 -5
- data/doc/commercial-services.md +5 -5
- data/exe/simplecov +11 -0
- data/lib/minitest/simplecov_plugin.rb +13 -5
- data/lib/simplecov/autostart.rb +11 -0
- data/lib/simplecov/cli/clean.rb +47 -0
- data/lib/simplecov/cli/coverage.rb +91 -0
- data/lib/simplecov/cli/diff.rb +151 -0
- data/lib/simplecov/cli/dotfile.rb +100 -0
- data/lib/simplecov/cli/merge.rb +116 -0
- data/lib/simplecov/cli/open.rb +50 -0
- data/lib/simplecov/cli/report.rb +84 -0
- data/lib/simplecov/cli/run.rb +36 -0
- data/lib/simplecov/cli/serve.rb +139 -0
- data/lib/simplecov/cli/uncovered.rb +107 -0
- data/lib/simplecov/cli.rb +150 -0
- data/lib/simplecov/color.rb +74 -0
- data/lib/simplecov/combine/branches_combiner.rb +27 -5
- data/lib/simplecov/combine/files_combiner.rb +7 -1
- data/lib/simplecov/combine/lines_combiner.rb +19 -17
- data/lib/simplecov/combine/methods_combiner.rb +26 -0
- data/lib/simplecov/combine/results_combiner.rb +5 -4
- data/lib/simplecov/command_guesser.rb +46 -32
- data/lib/simplecov/configuration/coverage.rb +171 -0
- data/lib/simplecov/configuration/coverage_criteria.rb +156 -0
- data/lib/simplecov/configuration/filters.rb +197 -0
- data/lib/simplecov/configuration/formatting.rb +119 -0
- data/lib/simplecov/configuration/ignored_entries.rb +63 -0
- data/lib/simplecov/configuration/merging.rb +164 -0
- data/lib/simplecov/configuration/thresholds.rb +174 -0
- data/lib/simplecov/configuration.rb +91 -407
- data/lib/simplecov/coverage_statistics.rb +12 -9
- data/lib/simplecov/coverage_violations.rb +148 -0
- data/lib/simplecov/defaults.rb +27 -20
- data/lib/simplecov/deprecation.rb +45 -0
- data/lib/simplecov/directive.rb +162 -0
- data/lib/simplecov/exit_codes/exit_code_handling.rb +8 -2
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +19 -57
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +17 -27
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +38 -21
- data/lib/simplecov/exit_codes.rb +3 -0
- data/lib/simplecov/exit_handling.rb +158 -0
- data/lib/simplecov/file_list.rb +61 -17
- data/lib/simplecov/filter.rb +69 -24
- data/lib/simplecov/formatter/base.rb +101 -0
- data/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
- data/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
- data/lib/simplecov/formatter/html_formatter.rb +79 -0
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +84 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +128 -0
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
- data/lib/simplecov/formatter/json_formatter.rb +87 -0
- data/lib/simplecov/formatter/multi_formatter.rb +4 -5
- data/lib/simplecov/formatter/simple_formatter.rb +9 -11
- data/lib/simplecov/formatter.rb +4 -0
- data/lib/simplecov/last_run.rb +10 -3
- data/lib/simplecov/lines_classifier.rb +25 -13
- data/lib/simplecov/load_global_config.rb +9 -4
- data/lib/simplecov/parallel_adapters/base.rb +61 -0
- data/lib/simplecov/parallel_adapters/generic.rb +44 -0
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
- data/lib/simplecov/parallel_adapters.rb +83 -0
- data/lib/simplecov/parallel_coordination.rb +129 -0
- data/lib/simplecov/process.rb +31 -13
- data/lib/simplecov/profiles/bundler_filter.rb +1 -1
- data/lib/simplecov/profiles/hidden_filter.rb +1 -1
- data/lib/simplecov/profiles/rails.rb +24 -10
- data/lib/simplecov/profiles/root_filter.rb +6 -5
- data/lib/simplecov/profiles/strict.rb +32 -0
- data/lib/simplecov/profiles/test_frameworks.rb +1 -4
- data/lib/simplecov/profiles.rb +32 -3
- data/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
- data/lib/simplecov/result/source_file_builder.rb +51 -0
- data/lib/simplecov/result.rb +108 -19
- data/lib/simplecov/result_adapter.rb +70 -8
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
- data/lib/simplecov/result_merger/resultset_file.rb +38 -0
- data/lib/simplecov/result_merger/resultset_store.rb +50 -0
- data/lib/simplecov/result_merger.rb +59 -91
- data/lib/simplecov/result_processing.rb +179 -0
- data/lib/simplecov/simulate_coverage.rb +54 -8
- data/lib/simplecov/source_file/branch.rb +1 -3
- data/lib/simplecov/source_file/branch_builder.rb +114 -0
- data/lib/simplecov/source_file/builder_context.rb +28 -0
- data/lib/simplecov/source_file/line.rb +7 -2
- data/lib/simplecov/source_file/line_builder.rb +43 -0
- data/lib/simplecov/source_file/method.rb +52 -0
- data/lib/simplecov/source_file/method_builder.rb +58 -0
- data/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
- data/lib/simplecov/source_file/skip_chunks.rb +75 -0
- data/lib/simplecov/source_file/source_loader.rb +63 -0
- data/lib/simplecov/source_file/statistics.rb +57 -0
- data/lib/simplecov/source_file.rb +66 -232
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +55 -0
- data/lib/simplecov/static_coverage_extractor/visitor.rb +171 -0
- data/lib/simplecov/static_coverage_extractor.rb +110 -0
- data/lib/simplecov/useless_results_remover.rb +16 -7
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov-html.rb +4 -0
- data/lib/simplecov.rb +156 -371
- data/lib/simplecov_json_formatter.rb +4 -0
- data/schemas/coverage-v1.0.schema.json +306 -0
- data/schemas/coverage.schema.json +306 -0
- metadata +90 -57
- data/CHANGELOG.md +0 -191
- data/lib/simplecov/default_formatter.rb +0 -20
|
@@ -9,34 +9,36 @@ module SimpleCov
|
|
|
9
9
|
module LinesCombiner
|
|
10
10
|
module_function
|
|
11
11
|
|
|
12
|
+
# Build a fresh array sized to the longer input. The previous
|
|
13
|
+
# implementation mutated whichever input was longer in place,
|
|
14
|
+
# which could surprise callers holding a reference to that array
|
|
15
|
+
# (e.g. the parsed `coverage` key of a resultset hash being
|
|
16
|
+
# passed into a second merge).
|
|
12
17
|
def combine(coverage_a, coverage_b)
|
|
13
|
-
coverage_a
|
|
14
|
-
|
|
15
|
-
.map do |coverage_a_val, coverage_b_val|
|
|
16
|
-
merge_line_coverage(coverage_a_val, coverage_b_val)
|
|
17
|
-
end
|
|
18
|
+
size = [coverage_a.size, coverage_b.size].max
|
|
19
|
+
Array.new(size) { |i| merge_line_coverage(coverage_a[i], coverage_b[i]) }
|
|
18
20
|
end
|
|
19
21
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
22
|
+
# Two runs of the same source file should agree on which lines
|
|
23
|
+
# are coverage-relevant (`nil` for comments / whitespace, `0`+
|
|
24
|
+
# for executable). When they don't, treat "relevant on either
|
|
25
|
+
# side" as relevant rather than masking a real `0` as `nil`,
|
|
26
|
+
# which would silently drop an uncovered line from the
|
|
27
|
+
# denominator and inflate the percentage.
|
|
24
28
|
#
|
|
25
29
|
# Logic:
|
|
26
30
|
#
|
|
27
|
-
# => nil + 0 = nil
|
|
28
31
|
# => nil + nil = nil
|
|
29
|
-
# =>
|
|
32
|
+
# => nil + int = int (preserves a relevant-but-uncovered 0)
|
|
33
|
+
# => int + int = int (sum)
|
|
30
34
|
#
|
|
35
|
+
# @param [Integer || nil] first_val
|
|
36
|
+
# @param [Integer || nil] second_val
|
|
31
37
|
# @return [Integer || nil]
|
|
32
38
|
def merge_line_coverage(first_val, second_val)
|
|
33
|
-
|
|
39
|
+
return nil if first_val.nil? && second_val.nil?
|
|
34
40
|
|
|
35
|
-
|
|
36
|
-
nil
|
|
37
|
-
else
|
|
38
|
-
sum
|
|
39
|
-
end
|
|
41
|
+
first_val.to_i + second_val.to_i
|
|
40
42
|
end
|
|
41
43
|
end
|
|
42
44
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module Combine
|
|
5
|
+
#
|
|
6
|
+
# Combine different method coverage results on a single file.
|
|
7
|
+
#
|
|
8
|
+
# Should be called through `SimpleCov.combine`.
|
|
9
|
+
module MethodsCombiner
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Return merged methods or the existing methods if other is missing.
|
|
14
|
+
#
|
|
15
|
+
# Method coverage is a flat hash mapping method identifiers to hit counts.
|
|
16
|
+
# Combining sums the hit counts for matching methods and preserves methods
|
|
17
|
+
# that only appear in one result.
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash]
|
|
20
|
+
#
|
|
21
|
+
def combine(coverage_a, coverage_b)
|
|
22
|
+
coverage_a.merge(coverage_b) { |_key, a_count, b_count| a_count + b_count }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -13,9 +13,10 @@ module SimpleCov
|
|
|
13
13
|
#
|
|
14
14
|
# Combine process explanation
|
|
15
15
|
# => ResultCombiner: define all present files between results and start combine on file level.
|
|
16
|
-
# ==> FileCombiner: collect result of next combine levels lines and
|
|
16
|
+
# ==> FileCombiner: collect result of next combine levels lines, branches, and methods.
|
|
17
17
|
# ===> LinesCombiner: combine lines results.
|
|
18
18
|
# ===> BranchesCombiner: combine branches results.
|
|
19
|
+
# ===> MethodsCombiner: combine methods results.
|
|
19
20
|
#
|
|
20
21
|
# @return [Hash]
|
|
21
22
|
#
|
|
@@ -36,11 +37,11 @@ module SimpleCov
|
|
|
36
37
|
def combine_result_sets(combined_results, result)
|
|
37
38
|
results_files = combined_results.keys | result.keys
|
|
38
39
|
|
|
39
|
-
results_files.
|
|
40
|
-
|
|
40
|
+
results_files.to_h do |file_name|
|
|
41
|
+
[file_name, combine_file_coverage(
|
|
41
42
|
combined_results[file_name],
|
|
42
43
|
result[file_name]
|
|
43
|
-
)
|
|
44
|
+
)]
|
|
44
45
|
end
|
|
45
46
|
end
|
|
46
47
|
|
|
@@ -14,50 +14,64 @@ module SimpleCov
|
|
|
14
14
|
attr_accessor :original_run_command
|
|
15
15
|
|
|
16
16
|
def guess
|
|
17
|
-
|
|
17
|
+
[from_command_line_options || from_defined_constants, parallel_data].compact.join(" ")
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
private
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
# When parallel_tests (or a compatible runner) is driving the suite,
|
|
23
|
+
# tag the command name with this worker's position in the pool.
|
|
24
|
+
def parallel_data
|
|
25
|
+
groups, number = ENV.values_at("PARALLEL_TEST_GROUPS", "TEST_ENV_NUMBER")
|
|
26
|
+
return unless groups && number
|
|
25
27
|
|
|
26
|
-
|
|
28
|
+
# parallel_tests sets the first worker's TEST_ENV_NUMBER to "" rather
|
|
29
|
+
# than "1"; restore the position so the rendered label reads cleanly.
|
|
27
30
|
number = "1" if number.empty?
|
|
28
|
-
"(#{number}/#{
|
|
31
|
+
"(#{number}/#{groups})"
|
|
29
32
|
end
|
|
30
33
|
|
|
34
|
+
COMMAND_LINE_FRAMEWORKS = {
|
|
35
|
+
%r{test/functional/} => "Functional Tests",
|
|
36
|
+
%r{test/\{.*functional.*\}/} => "Functional Tests",
|
|
37
|
+
%r{test/integration/} => "Integration Tests",
|
|
38
|
+
%r{test/} => "Unit Tests",
|
|
39
|
+
/spec/ => "RSpec",
|
|
40
|
+
/cucumber/ => "Cucumber Features",
|
|
41
|
+
/features/ => "Cucumber Features"
|
|
42
|
+
}.freeze
|
|
43
|
+
private_constant :COMMAND_LINE_FRAMEWORKS
|
|
44
|
+
|
|
31
45
|
def from_command_line_options
|
|
32
|
-
|
|
33
|
-
when /test\/functional\//, /test\/\{.*functional.*\}\//
|
|
34
|
-
"Functional Tests"
|
|
35
|
-
when /test\/integration\//
|
|
36
|
-
"Integration Tests"
|
|
37
|
-
when /test\//
|
|
38
|
-
"Unit Tests"
|
|
39
|
-
when /spec/
|
|
40
|
-
"RSpec"
|
|
41
|
-
when /cucumber/, /features/
|
|
42
|
-
"Cucumber Features"
|
|
43
|
-
end
|
|
46
|
+
COMMAND_LINE_FRAMEWORKS.find { |pattern, _| pattern.match?(original_run_command.to_s) }&.last
|
|
44
47
|
end
|
|
45
48
|
|
|
49
|
+
# Inner array literals after the first are flagged uncovered by Ruby's
|
|
50
|
+
# Coverage module even though the constant evaluates as a whole — known
|
|
51
|
+
# quirk with multi-line array literals.
|
|
52
|
+
DEFINED_CONSTANT_FRAMEWORKS = [
|
|
53
|
+
["RSpec", -> { defined?(::RSpec) }],
|
|
54
|
+
["Unit Tests", -> { defined?(Test::Unit) }], # simplecov:disable
|
|
55
|
+
["Minitest", -> { defined?(::Minitest) }], # simplecov:disable
|
|
56
|
+
["MiniTest", -> { defined?(MiniTest) }] # simplecov:disable
|
|
57
|
+
].freeze
|
|
58
|
+
private_constant :DEFINED_CONSTANT_FRAMEWORKS
|
|
59
|
+
|
|
60
|
+
# If the command regexps fail, let's try checking defined constants.
|
|
46
61
|
def from_defined_constants
|
|
47
|
-
#
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end
|
|
62
|
+
# simplecov:disable branch — first iter returns when ::RSpec is defined; later branches unreachable
|
|
63
|
+
DEFINED_CONSTANT_FRAMEWORKS.each { |name, defined_check| return name if defined_check.call }
|
|
64
|
+
# simplecov:enable branch
|
|
65
|
+
|
|
66
|
+
# TODO: Provide link to docs/wiki article
|
|
67
|
+
# simplecov:disable — only fires when no framework is detected, which
|
|
68
|
+
# is impossible while our own specs are running under rspec
|
|
69
|
+
warn(
|
|
70
|
+
"SimpleCov failed to recognize the test framework and/or suite used. " \
|
|
71
|
+
"Please specify manually using SimpleCov.command_name 'Unit Tests'."
|
|
72
|
+
)
|
|
73
|
+
"Unknown Test Framework"
|
|
74
|
+
# simplecov:enable
|
|
61
75
|
end
|
|
62
76
|
end
|
|
63
77
|
end
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
# The `coverage` configuration method configures each coverage criterion
|
|
5
|
+
# (`:line`, `:branch`, `:method`, `:eval`) uniformly in one place: naming a
|
|
6
|
+
# criterion enables it,
|
|
7
|
+
# and every threshold is declared with the same syntax regardless of which
|
|
8
|
+
# criterion it applies to. Because the criterion is fixed by the enclosing
|
|
9
|
+
# `coverage` call, threshold values are always plain percentages — there is
|
|
10
|
+
# no per-criterion Hash competing with the value for a slot.
|
|
11
|
+
#
|
|
12
|
+
# SimpleCov.start do
|
|
13
|
+
# coverage :line do
|
|
14
|
+
# minimum 90
|
|
15
|
+
# minimum_per_file 80
|
|
16
|
+
# minimum_per_file 100, only: "app/mailers/request_mailer.rb"
|
|
17
|
+
# maximum_drop 5
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# coverage :branch, minimum: 80
|
|
21
|
+
# coverage :method, minimum: 100
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# Line coverage is enabled by default, so `coverage :line` is only needed to
|
|
25
|
+
# set line thresholds or options. Thresholds feed the same internal stores
|
|
26
|
+
# as the flat `minimum_coverage` family, so enforcement is unchanged.
|
|
27
|
+
module Configuration
|
|
28
|
+
# One-liner keyword options `coverage` accepts, each forwarding to the
|
|
29
|
+
# `CoverageCriterion` verb of the same name. `minimum_per_group` is omitted
|
|
30
|
+
# because it needs an `only:` target, so it's block-only.
|
|
31
|
+
COVERAGE_THRESHOLD_OPTIONS = %i[minimum maximum exact maximum_drop minimum_per_file].freeze
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Configure (and, unless `enabled: false`, enable) a coverage criterion.
|
|
35
|
+
#
|
|
36
|
+
# Threshold options mirror the block verbs for one-liner use:
|
|
37
|
+
# coverage :branch, minimum: 80, maximum_drop: 5
|
|
38
|
+
#
|
|
39
|
+
# `primary: true` makes this the report's leading criterion (and the one a
|
|
40
|
+
# bare `minimum_coverage 90` targets). `oneshot: true` (valid only for
|
|
41
|
+
# `:line`) selects the faster oneshot-lines mode. `:eval` is enable-only.
|
|
42
|
+
#
|
|
43
|
+
def coverage(criterion, primary: false, enabled: true, oneshot: false, **thresholds, &block)
|
|
44
|
+
criterion = enable_coverage_criterion(criterion, enabled: enabled, oneshot: oneshot)
|
|
45
|
+
primary_coverage(criterion) if primary
|
|
46
|
+
|
|
47
|
+
configurator = CoverageCriterion.new(self, criterion)
|
|
48
|
+
apply_threshold_options(configurator, thresholds)
|
|
49
|
+
configurator.instance_eval(&block) if block
|
|
50
|
+
|
|
51
|
+
criterion
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Forward the one-liner threshold keywords (`coverage :branch, minimum: 80`)
|
|
57
|
+
# to the matching `CoverageCriterion` verbs, rejecting anything that isn't a
|
|
58
|
+
# recognized threshold option.
|
|
59
|
+
def apply_threshold_options(configurator, options)
|
|
60
|
+
options.each do |verb, value|
|
|
61
|
+
unless COVERAGE_THRESHOLD_OPTIONS.include?(verb)
|
|
62
|
+
raise SimpleCov::ConfigurationError,
|
|
63
|
+
"Unknown `coverage` option #{verb.inspect}. " \
|
|
64
|
+
"Supported options are #{COVERAGE_THRESHOLD_OPTIONS.inspect}."
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
configurator.public_send(verb, value)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Enable the criterion (or its oneshot / eval variant) and return the
|
|
72
|
+
# criterion symbol that thresholds should be stored under.
|
|
73
|
+
def enable_coverage_criterion(criterion, enabled:, oneshot:)
|
|
74
|
+
return enable_oneshot_line(criterion) if oneshot
|
|
75
|
+
return enable_eval_coverage_criterion if criterion == :eval
|
|
76
|
+
|
|
77
|
+
enabled ? enable_coverage(criterion) : disable_coverage(criterion)
|
|
78
|
+
criterion
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def enable_oneshot_line(criterion)
|
|
82
|
+
unless criterion == :line
|
|
83
|
+
raise SimpleCov::ConfigurationError, "`oneshot: true` is only valid for `coverage :line`"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
enable_coverage(ONESHOT_LINE_COVERAGE_CRITERION)
|
|
87
|
+
ONESHOT_LINE_COVERAGE_CRITERION
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def enable_eval_coverage_criterion
|
|
91
|
+
enable_coverage(:eval)
|
|
92
|
+
:eval
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @api private — threshold-store writers used by CoverageCriterion. They
|
|
96
|
+
# write the same `@minimum_coverage` / `@maximum_coverage` / ... hashes the
|
|
97
|
+
# flat threshold methods populate, so the exit-code checks are unchanged.
|
|
98
|
+
def store_overall_threshold(setting, criterion, percent)
|
|
99
|
+
raise_on_invalid_coverage({criterion => percent}, setting.to_s)
|
|
100
|
+
public_send(setting)[criterion] = percent
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def store_minimum_per_file(criterion, percent, target)
|
|
104
|
+
raise_on_invalid_coverage({criterion => percent}, "minimum_coverage_by_file")
|
|
105
|
+
return minimum_coverage_by_file[criterion] = percent if target.nil?
|
|
106
|
+
|
|
107
|
+
unless target.is_a?(String) || target.is_a?(Regexp)
|
|
108
|
+
raise SimpleCov::ConfigurationError, "`only:` must be a String path or Regexp, got #{target.inspect}"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
(minimum_coverage_by_file_overrides[target] ||= {})[criterion] = percent
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def store_minimum_per_group(criterion, percent, group_name)
|
|
115
|
+
raise_on_invalid_coverage({criterion => percent}, "minimum_coverage_by_group")
|
|
116
|
+
(minimum_coverage_by_group[group_name] ||= {})[criterion] = percent
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#
|
|
120
|
+
# Receiver for a `coverage <criterion> do ... end` block. Each verb writes a
|
|
121
|
+
# threshold for the single criterion the block configures, so the value is
|
|
122
|
+
# always a plain percentage (`minimum_per_file 100` is unambiguous) and the
|
|
123
|
+
# syntax is identical across line, branch, and method coverage.
|
|
124
|
+
#
|
|
125
|
+
class CoverageCriterion
|
|
126
|
+
def initialize(config, criterion)
|
|
127
|
+
@config = config
|
|
128
|
+
@criterion = criterion
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Overall (suite-wide) minimum for this criterion.
|
|
132
|
+
def minimum(percent)
|
|
133
|
+
@config.send(:store_overall_threshold, :minimum_coverage, @criterion, percent)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Overall maximum: fails the build if coverage rises above it. Paired with
|
|
137
|
+
# `minimum` (or via `exact`) this pins coverage so an unexpected jump fails.
|
|
138
|
+
def maximum(percent)
|
|
139
|
+
@config.send(:store_overall_threshold, :maximum_coverage, @criterion, percent)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Pin coverage to an exact figure (sets both `minimum` and `maximum`).
|
|
143
|
+
def exact(percent)
|
|
144
|
+
minimum(percent)
|
|
145
|
+
maximum(percent)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Maximum allowed drop between runs (`maximum_drop 0` refuses any drop).
|
|
149
|
+
def maximum_drop(percent)
|
|
150
|
+
@config.send(:store_overall_threshold, :maximum_coverage_drop, @criterion, percent)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Per-file minimum. With no `only:`, sets the default applied to every
|
|
154
|
+
# file; with `only:` (a String path or Regexp), overrides that default
|
|
155
|
+
# for the matching files.
|
|
156
|
+
def minimum_per_file(percent, only: nil)
|
|
157
|
+
@config.send(:store_minimum_per_file, @criterion, percent, only)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Per-group minimum for the named group (defined via `group`).
|
|
161
|
+
def minimum_per_group(percent, only:)
|
|
162
|
+
@config.send(:store_minimum_per_group, @criterion, percent, only)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Make this criterion the report's primary (leading) criterion.
|
|
166
|
+
def primary
|
|
167
|
+
@config.primary_coverage(@criterion)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
# Selection and validation of the coverage criteria Ruby's `Coverage`
|
|
5
|
+
# library should track. Supports `:line` (the historical default),
|
|
6
|
+
# `:branch`, `:method`, and `:oneshot_line`, plus the standalone
|
|
7
|
+
# `:eval` toggle for instrumenting `eval`'d code.
|
|
8
|
+
module Configuration
|
|
9
|
+
SUPPORTED_COVERAGE_CRITERIA = %i[line branch method oneshot_line].freeze
|
|
10
|
+
DEFAULT_COVERAGE_CRITERION = :line
|
|
11
|
+
ONESHOT_LINE_COVERAGE_CRITERION = :oneshot_line
|
|
12
|
+
|
|
13
|
+
# Enable one or more coverage criteria. `:eval` is accepted as a
|
|
14
|
+
# shorthand for the standalone eval-coverage toggle.
|
|
15
|
+
def enable_coverage(*criteria)
|
|
16
|
+
criteria.each do |criterion|
|
|
17
|
+
if criterion == :eval
|
|
18
|
+
enable_eval_coverage
|
|
19
|
+
else
|
|
20
|
+
raise_if_criterion_unsupported(criterion)
|
|
21
|
+
# :oneshot_lines can not be combined with :lines
|
|
22
|
+
coverage_criteria.delete(DEFAULT_COVERAGE_CRITERION) if criterion == ONESHOT_LINE_COVERAGE_CRITERION
|
|
23
|
+
coverage_criteria << criterion
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Remove `criterion` from the set of enabled coverage criteria.
|
|
29
|
+
# Disabling every criterion raises at `start_tracking` (not here),
|
|
30
|
+
# so config files that toggle criteria in arbitrary order don't
|
|
31
|
+
# have to worry about transient empty states.
|
|
32
|
+
def disable_coverage(criterion)
|
|
33
|
+
raise_if_criterion_unsupported(criterion)
|
|
34
|
+
coverage_criteria.delete(criterion)
|
|
35
|
+
@primary_coverage = nil if @primary_coverage == criterion
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def primary_coverage(criterion = nil)
|
|
39
|
+
if criterion.nil?
|
|
40
|
+
@primary_coverage ||= default_primary_coverage
|
|
41
|
+
else
|
|
42
|
+
raise_if_criterion_disabled(criterion)
|
|
43
|
+
@primary_coverage = criterion
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def coverage_criteria
|
|
48
|
+
@coverage_criteria ||= Set[DEFAULT_COVERAGE_CRITERION]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def coverage_criterion_enabled?(criterion)
|
|
52
|
+
coverage_criteria.member?(criterion)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Reset the criteria back to the lazy default (`Set[:line]`).
|
|
56
|
+
def clear_coverage_criteria
|
|
57
|
+
@coverage_criteria = nil
|
|
58
|
+
@primary_coverage = nil
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @api private — called from `SimpleCov.start_tracking` to fail
|
|
62
|
+
# fast when the user has disabled every coverage criterion.
|
|
63
|
+
def validate_coverage_criteria!
|
|
64
|
+
return unless coverage_criteria.empty?
|
|
65
|
+
|
|
66
|
+
raise SimpleCov::ConfigurationError,
|
|
67
|
+
"At least one coverage criterion must be enabled. " \
|
|
68
|
+
"Re-enable one with `enable_coverage :line`, `:branch`, or `:method`."
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def branch_coverage?
|
|
72
|
+
branch_coverage_supported? && coverage_criterion_enabled?(:branch)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def branch_coverage_supported?
|
|
76
|
+
coverage_criterion_supported?(:branches)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def method_coverage?
|
|
80
|
+
method_coverage_supported? && coverage_criterion_enabled?(:method)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def method_coverage_supported?
|
|
84
|
+
coverage_criterion_supported?(:methods)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def coverage_for_eval_supported?
|
|
88
|
+
coverage_criterion_supported?(:eval)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Ask the Coverage runtime itself whether a criterion is supported
|
|
92
|
+
# (Ruby >= 3.2). Older Rubies don't expose `Coverage.supported?`, so
|
|
93
|
+
# fall back to the historical engine check that line/branch/method
|
|
94
|
+
# were unavailable on JRuby. `:eval` was added later, so on older
|
|
95
|
+
# Rubies its fallback is "always unsupported" rather than the
|
|
96
|
+
# JRuby-only one above. The fallback arm is unreachable from the
|
|
97
|
+
# dogfood report, which runs on a newer Ruby.
|
|
98
|
+
# simplecov:disable
|
|
99
|
+
def coverage_criterion_supported?(criterion)
|
|
100
|
+
require "coverage"
|
|
101
|
+
return Coverage.supported?(criterion) if Coverage.respond_to?(:supported?)
|
|
102
|
+
|
|
103
|
+
criterion != :eval && RUBY_ENGINE != "jruby"
|
|
104
|
+
end
|
|
105
|
+
# simplecov:enable
|
|
106
|
+
|
|
107
|
+
def coverage_for_eval_enabled?
|
|
108
|
+
@coverage_for_eval_enabled ||= false
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# DEPRECATED: prefer `enable_coverage :eval`.
|
|
112
|
+
def enable_coverage_for_eval
|
|
113
|
+
SimpleCov::Deprecation.warn("`SimpleCov.enable_coverage_for_eval` is deprecated. " \
|
|
114
|
+
"Replace with `SimpleCov.enable_coverage :eval`.")
|
|
115
|
+
enable_eval_coverage
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
# Shared implementation backing both `enable_coverage :eval` and
|
|
121
|
+
# the deprecated `enable_coverage_for_eval`.
|
|
122
|
+
def enable_eval_coverage
|
|
123
|
+
if coverage_for_eval_supported?
|
|
124
|
+
@coverage_for_eval_enabled = true
|
|
125
|
+
else
|
|
126
|
+
warn "Coverage for eval is not available; Use Ruby 3.2.0 or later"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# If `:line` is enabled, it's the default primary; otherwise fall
|
|
131
|
+
# back to whichever criterion the user actually enabled (in
|
|
132
|
+
# insertion order). Returning `:line` even when disabled would
|
|
133
|
+
# propagate broken state into `minimum_coverage 90`.
|
|
134
|
+
def default_primary_coverage
|
|
135
|
+
return DEFAULT_COVERAGE_CRITERION if coverage_criterion_enabled?(DEFAULT_COVERAGE_CRITERION)
|
|
136
|
+
|
|
137
|
+
coverage_criteria.first
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def raise_if_criterion_disabled(criterion)
|
|
141
|
+
raise_if_criterion_unsupported(criterion)
|
|
142
|
+
return if coverage_criterion_enabled?(criterion)
|
|
143
|
+
|
|
144
|
+
raise SimpleCov::ConfigurationError,
|
|
145
|
+
"Coverage criterion #{criterion}, is disabled! " \
|
|
146
|
+
"Please enable it first through enable_coverage #{criterion} (if supported)"
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def raise_if_criterion_unsupported(criterion)
|
|
150
|
+
return if SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
|
|
151
|
+
|
|
152
|
+
raise SimpleCov::ConfigurationError,
|
|
153
|
+
"Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|