simplecov 0.22.0 → 1.0.0.rc1
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 +81 -1
- data/LICENSE +1 -1
- data/README.md +1009 -511
- 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 +3 -2
- 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 +195 -0
- data/lib/simplecov/configuration/formatting.rb +119 -0
- data/lib/simplecov/configuration/ignored_entries.rb +63 -0
- data/lib/simplecov/configuration/merging.rb +74 -0
- data/lib/simplecov/configuration/thresholds.rb +174 -0
- data/lib/simplecov/configuration.rb +79 -405
- 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/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 +127 -0
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
- data/lib/simplecov/formatter/json_formatter.rb +77 -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 +26 -13
- data/lib/simplecov/load_global_config.rb +9 -4
- data/lib/simplecov/parallel_adapters/base.rb +51 -0
- data/lib/simplecov/parallel_adapters/generic.rb +42 -0
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +77 -0
- data/lib/simplecov/parallel_adapters.rb +83 -0
- data/lib/simplecov/parallel_coordination.rb +95 -0
- data/lib/simplecov/process.rb +20 -14
- 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 +97 -19
- data/lib/simplecov/result_adapter.rb +68 -6
- 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 +46 -90
- data/lib/simplecov/result_processing.rb +162 -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 +77 -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/visitor.rb +193 -0
- data/lib/simplecov/static_coverage_extractor.rb +111 -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 +131 -377
- data/lib/simplecov_json_formatter.rb +4 -0
- data/schemas/coverage-v1.0.schema.json +300 -0
- data/schemas/coverage.schema.json +300 -0
- metadata +88 -56
- data/lib/simplecov/default_formatter.rb +0 -20
|
@@ -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
|
+
warn "#{Kernel.caller.first}: [DEPRECATION] `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
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
# Inclusion / exclusion / grouping methods: `cover`, `skip`, `group`,
|
|
5
|
+
# plus the deprecated `track_files` / `add_filter` / `add_group`
|
|
6
|
+
# aliases. Mutates the same `filters`, `groups`, and `cover_filters`
|
|
7
|
+
# collections the main Configuration module exposes.
|
|
8
|
+
module Configuration
|
|
9
|
+
attr_writer :filters, :groups
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# Restrict the universe of files in the coverage report to those matching
|
|
13
|
+
# one or more globs, regexps, or block predicates. Multiple calls union;
|
|
14
|
+
# when any `cover` matcher is configured the report drops every file that
|
|
15
|
+
# doesn't match at least one of them.
|
|
16
|
+
#
|
|
17
|
+
# Strings are interpreted as shell globs (e.g. "lib/**/*.rb"), not
|
|
18
|
+
# substring matches — this is a deliberate departure from the legacy
|
|
19
|
+
# `add_filter` semantics and matches the way `track_files` already
|
|
20
|
+
# interprets its argument.
|
|
21
|
+
#
|
|
22
|
+
# When the matcher is a string-glob, `cover` also expands the glob on
|
|
23
|
+
# disk so files that exist but were never required during the run still
|
|
24
|
+
# appear in the report (at 0% coverage). This is the "include unloaded
|
|
25
|
+
# files" half of the legacy `track_files` behavior, rolled into the
|
|
26
|
+
# same call.
|
|
27
|
+
#
|
|
28
|
+
# SimpleCov.start do
|
|
29
|
+
# cover "lib/**/*.rb", "app/**/*.rb"
|
|
30
|
+
# cover(/_helper\.rb\z/)
|
|
31
|
+
# cover { |sf| sf.lines.count > 5 }
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
def cover(*args, &block)
|
|
35
|
+
args.each { |arg| cover_filters << build_cover_filter(arg) }
|
|
36
|
+
cover_filters << SimpleCov::BlockFilter.new(block) if block
|
|
37
|
+
cover_filters
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the list of configured inclusion filters added via `cover`.
|
|
41
|
+
def cover_filters
|
|
42
|
+
@cover_filters ||= []
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns the list of string globs passed to `cover` — used by the
|
|
46
|
+
# disk-discovery pass in `SimpleCov.add_not_loaded_files` so files
|
|
47
|
+
# matching a `cover` glob appear in the report even when they were
|
|
48
|
+
# never required during the suite.
|
|
49
|
+
#
|
|
50
|
+
# Walks into `ArrayFilter` entries (built when a caller passes an
|
|
51
|
+
# array to `cover`) so a glob nested inside `cover(["lib/**/*.rb",
|
|
52
|
+
# /helper\.rb\z/])` still drives unloaded-file discovery.
|
|
53
|
+
def cover_globs
|
|
54
|
+
collect_cover_globs(cover_filters)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# DEPRECATED: prefer `cover`, which both includes unloaded files (the
|
|
58
|
+
# historical `track_files` behavior) and restricts the report to the
|
|
59
|
+
# matching set.
|
|
60
|
+
def track_files(glob)
|
|
61
|
+
warn "#{Kernel.caller.first}: [DEPRECATION] `SimpleCov.track_files` is deprecated. " \
|
|
62
|
+
"#{track_files_replacement_hint(glob)}"
|
|
63
|
+
@tracked_files = glob
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# `track_files(nil)` is the documented way to clear a previously-set
|
|
67
|
+
# glob, but `cover(nil)` raises `ConfigurationError`, so don't point
|
|
68
|
+
# users at it. The `cover` API has no direct equivalent for "reset
|
|
69
|
+
# the inclusion list" — point users at the `@cover_filters` reset.
|
|
70
|
+
def track_files_replacement_hint(glob)
|
|
71
|
+
if glob.nil?
|
|
72
|
+
"Replace with `SimpleCov.cover_filters.clear` — clearing the inclusion list."
|
|
73
|
+
else
|
|
74
|
+
"Replace with `SimpleCov.cover #{glob.inspect}` — `cover` includes unloaded files on disk " \
|
|
75
|
+
"(the historical `track_files` behavior) and also restricts the report to the matching set. " \
|
|
76
|
+
"If you want to keep additional files outside #{glob.inspect} in the report, pass every " \
|
|
77
|
+
"directory you care about, e.g. `cover #{glob.inspect}, \"app/**/*.rb\"`."
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns the glob used to include files that were not explicitly required.
|
|
82
|
+
def tracked_files
|
|
83
|
+
@tracked_files if defined?(@tracked_files)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns the list of configured exclusion filters added via `skip`
|
|
87
|
+
# (or the deprecated `add_filter`).
|
|
88
|
+
def filters
|
|
89
|
+
@filters ||= []
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
#
|
|
93
|
+
# Drop matching files from the coverage report. The inverse of `cover`.
|
|
94
|
+
#
|
|
95
|
+
# See README for the full grammar; `skip` accepts a String (path-segment
|
|
96
|
+
# substring), Regexp, block predicate, or Array of any of those.
|
|
97
|
+
#
|
|
98
|
+
def skip(filter_argument = nil, &)
|
|
99
|
+
filters << parse_filter(filter_argument, &)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# DEPRECATED: alias for `skip`. Same matcher grammar, identical behavior.
|
|
103
|
+
def add_filter(filter_argument = nil, &block)
|
|
104
|
+
example = block ? "`SimpleCov.skip { ... }`" : "`SimpleCov.skip #{filter_argument.inspect}`"
|
|
105
|
+
warn "#{Kernel.caller.first}: [DEPRECATION] `SimpleCov.add_filter` is deprecated. " \
|
|
106
|
+
"Replace with `SimpleCov.skip` (same arguments, same behavior). Example: #{example}."
|
|
107
|
+
skip(filter_argument, &block)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Remove any filters whose `filter_argument` equals the given value.
|
|
111
|
+
# Returns true when at least one filter was removed, false otherwise.
|
|
112
|
+
def remove_filter(filter_argument) # rubocop:disable Naming/PredicateMethod
|
|
113
|
+
before = filters.size
|
|
114
|
+
filters.reject! { |filter| filter.respond_to?(:filter_argument) && filter.filter_argument == filter_argument }
|
|
115
|
+
filters.size != before
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Remove every filter from the chain, including the defaults installed
|
|
119
|
+
# by `SimpleCov.start`.
|
|
120
|
+
def clear_filters
|
|
121
|
+
@filters = []
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Returns the configured groups. Add groups using SimpleCov.group.
|
|
125
|
+
def groups
|
|
126
|
+
@groups ||= {}
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Define a display group for files. Same matcher grammar as `skip`,
|
|
130
|
+
# but instead of dropping the matching files it bins them under
|
|
131
|
+
# `group_name` for the formatter. Files matched by no group fall
|
|
132
|
+
# into the implicit "Ungrouped" bucket.
|
|
133
|
+
def group(group_name, filter_argument = nil, &)
|
|
134
|
+
groups[group_name] = parse_filter(filter_argument, &)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# DEPRECATED: alias for `group`. Same arguments, same behavior.
|
|
138
|
+
def add_group(group_name, filter_argument = nil, &block)
|
|
139
|
+
example = if block
|
|
140
|
+
"`SimpleCov.group #{group_name.inspect} { ... }`"
|
|
141
|
+
else
|
|
142
|
+
"`SimpleCov.group #{group_name.inspect}, #{filter_argument.inspect}`"
|
|
143
|
+
end
|
|
144
|
+
warn "#{Kernel.caller.first}: [DEPRECATION] `SimpleCov.add_group` is deprecated. " \
|
|
145
|
+
"Replace with `SimpleCov.group` (same arguments, same behavior). Example: #{example}."
|
|
146
|
+
group(group_name, filter_argument, &block)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Drop every filter previously installed (defaults plus anything
|
|
150
|
+
# earlier in this block) so subsequent `skip` calls start from a
|
|
151
|
+
# clean slate. Order matters — call this before your own `skip`
|
|
152
|
+
# invocations.
|
|
153
|
+
def no_default_skips
|
|
154
|
+
clear_filters
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
# The actual filter processor. Not meant for direct use.
|
|
160
|
+
def parse_filter(filter_argument = nil, &filter_proc)
|
|
161
|
+
filter = filter_argument || filter_proc
|
|
162
|
+
|
|
163
|
+
raise ArgumentError, "Please specify either a filter or a block to filter with" unless filter
|
|
164
|
+
|
|
165
|
+
SimpleCov::Filter.build_filter(filter)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Build a filter for a `cover` argument. Strings are treated as
|
|
169
|
+
# globs (not substrings — that's `skip`/`add_filter`'s semantics).
|
|
170
|
+
def build_cover_filter(arg)
|
|
171
|
+
case arg
|
|
172
|
+
when String then SimpleCov::GlobFilter.new(arg)
|
|
173
|
+
when Regexp then SimpleCov::RegexFilter.new(arg)
|
|
174
|
+
when Proc then SimpleCov::BlockFilter.new(arg)
|
|
175
|
+
when SimpleCov::Filter then arg
|
|
176
|
+
when Array then SimpleCov::ArrayFilter.new(arg.map { |a| build_cover_filter(a) })
|
|
177
|
+
else raise SimpleCov::ConfigurationError, "Unsupported `cover` argument #{arg.inspect}; " \
|
|
178
|
+
"expected a String glob, Regexp, Proc, " \
|
|
179
|
+
"SimpleCov::Filter, or Array of those."
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Walk a list of cover filters and return the string globs they hold,
|
|
184
|
+
# descending into `ArrayFilter` wrappers built by `cover(["a", "b"])`.
|
|
185
|
+
def collect_cover_globs(filter_list)
|
|
186
|
+
filter_list.flat_map do |filter|
|
|
187
|
+
case filter
|
|
188
|
+
when SimpleCov::GlobFilter then filter.filter_argument
|
|
189
|
+
when SimpleCov::ArrayFilter then collect_cover_globs(filter.filter_argument)
|
|
190
|
+
else []
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|