simplecov 1.1.0 → 1.2.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/README.md +165 -57
- data/exe/simplecov +0 -3
- data/lib/minitest/simplecov_plugin.rb +12 -9
- data/lib/simplecov/atomic_file.rb +16 -19
- data/lib/simplecov/autostart.rb +5 -7
- data/lib/simplecov/baseline/parser.rb +58 -0
- data/lib/simplecov/baseline.rb +170 -0
- data/lib/simplecov/cli/affected/changed_files.rb +57 -0
- data/lib/simplecov/cli/affected/selection.rb +117 -0
- data/lib/simplecov/cli/affected.rb +148 -0
- data/lib/simplecov/cli/badge/svg.rb +70 -0
- data/lib/simplecov/cli/badge.rb +62 -0
- data/lib/simplecov/cli/clean.rb +23 -24
- data/lib/simplecov/cli/command_helpers.rb +55 -19
- data/lib/simplecov/cli/completions/scripts.rb +114 -0
- data/lib/simplecov/cli/completions.rb +83 -0
- data/lib/simplecov/cli/coverage.rb +25 -28
- data/lib/simplecov/cli/coverage_file.rb +58 -14
- data/lib/simplecov/cli/dead_code/output.rb +100 -0
- data/lib/simplecov/cli/dead_code.rb +139 -0
- data/lib/simplecov/cli/diff/output.rb +45 -0
- data/lib/simplecov/cli/diff.rb +35 -67
- data/lib/simplecov/cli/dotfile.rb +83 -38
- data/lib/simplecov/cli/git.rb +41 -0
- data/lib/simplecov/cli/history/output.rb +135 -0
- data/lib/simplecov/cli/history.rb +67 -0
- data/lib/simplecov/cli/merge.rb +34 -39
- data/lib/simplecov/cli/open.rb +12 -15
- data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
- data/lib/simplecov/cli/patch/output.rb +135 -0
- data/lib/simplecov/cli/patch.rb +169 -0
- data/lib/simplecov/cli/ratchet/output.rb +44 -0
- data/lib/simplecov/cli/ratchet.rb +83 -0
- data/lib/simplecov/cli/report.rb +18 -24
- data/lib/simplecov/cli/run.rb +8 -9
- data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
- data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
- data/lib/simplecov/cli/serve.rb +27 -24
- data/lib/simplecov/cli/show/annotator.rb +87 -0
- data/lib/simplecov/cli/show/sweep.rb +36 -0
- data/lib/simplecov/cli/show.rb +133 -0
- data/lib/simplecov/cli/status/facts.rb +87 -0
- data/lib/simplecov/cli/status.rb +93 -0
- data/lib/simplecov/cli/tests/redundancy.rb +78 -0
- data/lib/simplecov/cli/tests.rb +148 -0
- data/lib/simplecov/cli/uncovered/misses.rb +37 -0
- data/lib/simplecov/cli/uncovered.rb +70 -32
- data/lib/simplecov/cli/usage.rb +152 -0
- data/lib/simplecov/cli/watch/live_report.rb +73 -0
- data/lib/simplecov/cli/watch/narrator.rb +55 -0
- data/lib/simplecov/cli/watch/poller.rb +45 -0
- data/lib/simplecov/cli/watch/session.rb +134 -0
- data/lib/simplecov/cli/watch/test_plan.rb +43 -0
- data/lib/simplecov/cli/watch.rb +85 -0
- data/lib/simplecov/cli.rb +43 -92
- data/lib/simplecov/color.rb +21 -35
- data/lib/simplecov/combine/branches_combiner.rb +7 -31
- data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
- data/lib/simplecov/combine/identity_interner.rb +10 -13
- data/lib/simplecov/combine/interned_counts.rb +7 -9
- data/lib/simplecov/combine/lines_combiner.rb +20 -44
- data/lib/simplecov/combine/methods_combiner.rb +9 -35
- data/lib/simplecov/combine/results_combiner.rb +4 -21
- data/lib/simplecov/combine.rb +0 -6
- data/lib/simplecov/command_guesser.rb +20 -34
- data/lib/simplecov/configuration/baseline.rb +31 -0
- data/lib/simplecov/configuration/coverage.rb +91 -79
- data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
- data/lib/simplecov/configuration/deprecations.rb +21 -0
- data/lib/simplecov/configuration/eval_coverage.rb +6 -10
- data/lib/simplecov/configuration/filters.rb +40 -73
- data/lib/simplecov/configuration/formatting.rb +74 -83
- data/lib/simplecov/configuration/groups.rb +19 -13
- data/lib/simplecov/configuration/history.rb +44 -0
- data/lib/simplecov/configuration/ignored_entries.rb +31 -22
- data/lib/simplecov/configuration/merging.rb +94 -62
- data/lib/simplecov/configuration/missed_caps.rb +51 -0
- data/lib/simplecov/configuration/production.rb +23 -0
- data/lib/simplecov/configuration/test_tracking.rb +48 -0
- data/lib/simplecov/configuration/thresholds.rb +33 -62
- data/lib/simplecov/configuration/view_coverage.rb +38 -0
- data/lib/simplecov/configuration.rb +70 -76
- data/lib/simplecov/configuration_error.rb +5 -0
- data/lib/simplecov/context_map/union.rb +81 -0
- data/lib/simplecov/context_map.rb +187 -0
- data/lib/simplecov/coverage_json.rb +2 -5
- data/lib/simplecov/coverage_statistics.rb +14 -24
- data/lib/simplecov/coverage_violations.rb +157 -64
- data/lib/simplecov/current_run.rb +61 -0
- data/lib/simplecov/defaults.rb +4 -23
- data/lib/simplecov/deprecation.rb +45 -27
- data/lib/simplecov/directive.rb +46 -62
- data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
- data/lib/simplecov/exit_codes/check.rb +17 -11
- data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
- data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
- data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +7 -12
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
- data/lib/simplecov/exit_codes.rb +17 -7
- data/lib/simplecov/exit_handling.rb +94 -59
- data/lib/simplecov/file_list.rb +20 -51
- data/lib/simplecov/filter.rb +43 -66
- data/lib/simplecov/formatter/base.rb +38 -51
- data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
- data/lib/simplecov/formatter/html_formatter/public/index.html +17 -10
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
- data/lib/simplecov/formatter/html_formatter.rb +24 -41
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
- data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
- data/lib/simplecov/formatter/json_formatter.rb +7 -13
- data/lib/simplecov/formatter/multi_formatter.rb +5 -11
- data/lib/simplecov/formatter/simple_formatter.rb +2 -8
- data/lib/simplecov/formatter.rb +14 -10
- data/lib/simplecov/group_names.rb +13 -12
- data/lib/simplecov/history.rb +115 -0
- data/lib/simplecov/last_run.rb +6 -9
- data/lib/simplecov/lines_classifier.rb +31 -16
- data/lib/simplecov/load_global_config.rb +7 -7
- data/lib/simplecov/parallel_adapters/base.rb +13 -41
- data/lib/simplecov/parallel_adapters/generic.rb +7 -18
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
- data/lib/simplecov/parallel_adapters.rb +24 -53
- data/lib/simplecov/parallel_coordination.rb +39 -49
- data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
- data/lib/simplecov/parallel_result_merger.rb +67 -126
- data/lib/simplecov/process.rb +14 -32
- data/lib/simplecov/production/error.rb +11 -0
- data/lib/simplecov/production/file_sink.rb +130 -0
- data/lib/simplecov/production.rb +249 -0
- data/lib/simplecov/profiles/rails.rb +1 -12
- data/lib/simplecov/profiles/root_filter.rb +0 -4
- data/lib/simplecov/profiles/strict.rb +9 -19
- data/lib/simplecov/profiles.rb +11 -33
- data/lib/simplecov/report_deferral.rb +11 -15
- data/lib/simplecov/report_stamp.rb +6 -9
- data/lib/simplecov/result/filter_config.rb +19 -0
- data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
- data/lib/simplecov/result/serialization.rb +43 -0
- data/lib/simplecov/result/source_file_builder.rb +7 -12
- data/lib/simplecov/result.rb +68 -117
- data/lib/simplecov/result_adapter.rb +58 -68
- data/lib/simplecov/result_merger/contexts.rb +22 -0
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
- data/lib/simplecov/result_merger/resultset_file.rb +20 -27
- data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
- data/lib/simplecov/result_merger/resultset_store.rb +23 -14
- data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
- data/lib/simplecov/result_merger.rb +59 -58
- data/lib/simplecov/result_processing.rb +74 -119
- data/lib/simplecov/run_identity.rb +16 -18
- data/lib/simplecov/simulate_coverage.rb +35 -50
- data/lib/simplecov/source_file/branch.rb +8 -33
- data/lib/simplecov/source_file/branch_builder.rb +28 -47
- data/lib/simplecov/source_file/builder_context.rb +6 -13
- data/lib/simplecov/source_file/line.rb +13 -33
- data/lib/simplecov/source_file/line_builder.rb +9 -15
- data/lib/simplecov/source_file/method.rb +3 -7
- data/lib/simplecov/source_file/method_builder.rb +6 -15
- data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
- data/lib/simplecov/source_file/skip_chunks.rb +19 -28
- data/lib/simplecov/source_file/source_loader.rb +45 -35
- data/lib/simplecov/source_file/statistics.rb +10 -9
- data/lib/simplecov/source_file.rb +21 -51
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +63 -114
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +11 -24
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
- data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
- data/lib/simplecov/static_coverage_extractor/visitor.rb +47 -64
- data/lib/simplecov/static_coverage_extractor.rb +46 -62
- data/lib/simplecov/test_tracker/accessors.rb +34 -0
- data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
- data/lib/simplecov/test_tracker/delta.rb +69 -0
- data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
- data/lib/simplecov/test_tracker.rb +162 -0
- data/lib/simplecov/unloaded_file_injector.rb +18 -31
- data/lib/simplecov/useless_results_remover.rb +7 -6
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
- data/lib/simplecov/view_coverage.rb +55 -0
- data/lib/simplecov-html.rb +1 -0
- data/lib/simplecov.rb +59 -114
- data/lib/simplecov_json_formatter.rb +1 -0
- data/man/simplecov.1 +349 -0
- data/sig/simplecov.rbs +523 -595
- metadata +72 -7
- data/schemas/coverage-v1.0.schema.json +0 -306
- data/schemas/coverage.schema.json +0 -306
data/lib/simplecov/directive.rb
CHANGED
|
@@ -3,42 +3,24 @@
|
|
|
3
3
|
require "ripper"
|
|
4
4
|
|
|
5
5
|
module SimpleCov
|
|
6
|
-
# Parses `# simplecov:disable` / `# simplecov:enable` directive comments
|
|
6
|
+
# Parses `# simplecov:disable` / `# simplecov:enable` directive comments, in
|
|
7
|
+
# both the block form (the directive is the entire comment on its own line,
|
|
8
|
+
# opening a region that runs until the matching enable) and the inline form
|
|
9
|
+
# (the directive trails real code, affecting that line alone).
|
|
7
10
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
# ...
|
|
15
|
-
# # simplecov:enable line
|
|
16
|
-
#
|
|
17
|
-
# Inline form (the directive trails real code on the same line) only affects
|
|
18
|
-
# that single line and does not need to be re-enabled:
|
|
19
|
-
#
|
|
20
|
-
# raise "absurd" # simplecov:disable
|
|
21
|
-
#
|
|
22
|
-
# Categories are `:line`, `:branch`, and `:method`. They may be combined
|
|
23
|
-
# with commas. Omitting categories targets all three.
|
|
24
|
-
#
|
|
25
|
-
# Any text after the directive (and the optional category list) is treated
|
|
26
|
-
# as a free-form reason and discarded:
|
|
27
|
-
#
|
|
28
|
-
# # simplecov:disable line not worth testing this glue
|
|
29
|
-
#
|
|
30
|
-
# As a consequence, an unrecognised category name silently falls into the
|
|
31
|
-
# reason bucket. `# simplecov:disable cyclomatic` is parsed as the bare
|
|
32
|
-
# form (disable everything) with reason "cyclomatic" — a deliberate
|
|
33
|
-
# over-disable so the typo is visible in the report rather than silently
|
|
34
|
-
# disabling nothing.
|
|
11
|
+
# Categories are `:line`, `:branch`, and `:method`, combinable with commas.
|
|
12
|
+
# Omitting categories targets all three. Any text after the directive is a
|
|
13
|
+
# free-form reason and is discarded, so an unrecognised category name
|
|
14
|
+
# silently falls into the reason bucket and disables everything: a
|
|
15
|
+
# deliberate over-disable, so the typo is visible in the report rather than
|
|
16
|
+
# silently disabling nothing.
|
|
35
17
|
#
|
|
36
18
|
# Comment extraction goes through `Ripper.lex` so directive markers inside
|
|
37
19
|
# string literals or heredocs are correctly ignored.
|
|
38
20
|
class Directive
|
|
39
21
|
CATEGORIES = %i[line branch method].freeze
|
|
40
22
|
|
|
41
|
-
CATEGORY_PATTERN = "(?:#{CATEGORIES.join(
|
|
23
|
+
CATEGORY_PATTERN = "(?:#{CATEGORIES.join("|")})".freeze
|
|
42
24
|
CATEGORIES_PATTERN = "(?:#{CATEGORY_PATTERN}(?:\\s*,\\s*#{CATEGORY_PATTERN})*)".freeze
|
|
43
25
|
PATTERN = /
|
|
44
26
|
\#\s*simplecov\s*:\s*
|
|
@@ -50,11 +32,9 @@ module SimpleCov
|
|
|
50
32
|
|
|
51
33
|
attr_reader :line_number, :mode, :categories
|
|
52
34
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
|
|
56
|
-
def self.disabled_ranges(src_lines)
|
|
57
|
-
lines = src_lines.to_a
|
|
35
|
+
# The disabled line ranges per category. An unclosed `disable` block
|
|
36
|
+
# extends to the end of the file.
|
|
37
|
+
def self.disabled_ranges(lines)
|
|
58
38
|
ranges = CATEGORIES.to_h do |category|
|
|
59
39
|
empty = [] # : Array[Range[Integer]]
|
|
60
40
|
[category, empty]
|
|
@@ -62,14 +42,14 @@ module SimpleCov
|
|
|
62
42
|
open_starts = {} # : Hash[Symbol, Integer]
|
|
63
43
|
|
|
64
44
|
directives_in(lines).each { |directive| directive.apply(ranges, open_starts) }
|
|
65
|
-
open_starts.each { |category, start| ranges
|
|
45
|
+
open_starts.each { |category, start| ranges.fetch(category) << (start..lines.size) }
|
|
66
46
|
|
|
67
47
|
ranges
|
|
68
48
|
end
|
|
69
49
|
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
50
|
+
# Every directive in the file, in source order. Comments inside string
|
|
51
|
+
# literals or heredocs are skipped because Ripper.lex doesn't tag them as
|
|
52
|
+
# :on_comment tokens.
|
|
73
53
|
def self.directives_in(lines)
|
|
74
54
|
return [] unless source_might_contain_directive?(lines)
|
|
75
55
|
|
|
@@ -78,13 +58,13 @@ module SimpleCov
|
|
|
78
58
|
end
|
|
79
59
|
end
|
|
80
60
|
|
|
81
|
-
# Cheap pre-check so
|
|
82
|
-
#
|
|
61
|
+
# Cheap pre-check, so files that obviously can't contain a directive are
|
|
62
|
+
# never tokenized.
|
|
83
63
|
def self.source_might_contain_directive?(lines)
|
|
84
64
|
lines.any? do |line|
|
|
85
65
|
line.include?("simplecov")
|
|
86
66
|
rescue ArgumentError, EncodingError
|
|
87
|
-
false
|
|
67
|
+
false
|
|
88
68
|
end
|
|
89
69
|
end
|
|
90
70
|
|
|
@@ -94,72 +74,76 @@ module SimpleCov
|
|
|
94
74
|
|
|
95
75
|
new(
|
|
96
76
|
line_number: line_number,
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
|
|
77
|
+
# The pattern admits exactly two modes, so asking whether this one is
|
|
78
|
+
# `disable` names the other by elimination. Comparing the captured text
|
|
79
|
+
# also keeps the nilable capture out of `to_sym`.
|
|
80
|
+
mode: match[:mode].eql?("disable") ? :disable : :enable,
|
|
100
81
|
categories: parse_categories(match[:categories]),
|
|
101
82
|
inline: inline?(lines, line_number, column + match.begin(0))
|
|
102
83
|
)
|
|
103
84
|
rescue ArgumentError, EncodingError
|
|
104
|
-
#
|
|
85
|
+
# The comment text contains an invalid byte sequence, say.
|
|
105
86
|
nil
|
|
106
87
|
end
|
|
107
88
|
|
|
89
|
+
# The bare form targets every category, and answers the frozen constant
|
|
90
|
+
# itself: a directive's categories are only ever iterated.
|
|
108
91
|
def self.parse_categories(text)
|
|
109
|
-
return CATEGORIES
|
|
92
|
+
return CATEGORIES if text.nil?
|
|
110
93
|
|
|
111
94
|
text.split(/\s*,\s*/).map(&:to_sym)
|
|
112
95
|
end
|
|
113
96
|
|
|
114
|
-
# Whether the directive sits after non-whitespace content on its line.
|
|
115
97
|
# `column` is the byte column of the directive's `#` in the source line,
|
|
116
|
-
# adjusted for any prefix that may precede it within the comment token
|
|
117
|
-
# (e.g., `# prefix # simplecov:disable line`).
|
|
98
|
+
# adjusted for any prefix that may precede it within the comment token.
|
|
118
99
|
def self.inline?(lines, line_number, column)
|
|
119
|
-
line = lines
|
|
120
|
-
|
|
100
|
+
line = lines.at(line_number - 1).to_s
|
|
101
|
+
# Indentation is all a non-inline directive may have before it, so
|
|
102
|
+
# stripping the prefix from the left decides the question. Doing it from
|
|
103
|
+
# the right instead would raise on a line whose trailing bytes are
|
|
104
|
+
# invalid, which is a property of the code after the directive.
|
|
105
|
+
!line.byteslice(0, column).to_s.lstrip.empty?
|
|
121
106
|
rescue ArgumentError, EncodingError
|
|
122
|
-
false
|
|
107
|
+
false
|
|
123
108
|
end
|
|
124
109
|
|
|
125
110
|
def self.comments_in(lines)
|
|
126
111
|
source = lines.map { |line| line.end_with?("\n") ? line : "#{line}\n" }.join
|
|
127
112
|
Ripper.lex(source).filter_map do |(line_number, column), type, text|
|
|
128
|
-
[line_number, column, text] if type
|
|
113
|
+
[line_number, column, text] if type.equal?(:on_comment)
|
|
129
114
|
end
|
|
130
115
|
rescue ArgumentError, EncodingError
|
|
131
|
-
[]
|
|
116
|
+
[]
|
|
132
117
|
end
|
|
133
118
|
|
|
134
119
|
private_class_method :directives_in, :source_might_contain_directive?,
|
|
135
|
-
|
|
120
|
+
:parse_comment, :parse_categories, :inline?, :comments_in
|
|
136
121
|
|
|
137
122
|
def initialize(line_number:, mode:, categories:, inline:)
|
|
138
123
|
@line_number = line_number
|
|
139
|
-
@mode
|
|
140
|
-
@categories
|
|
141
|
-
@inline
|
|
124
|
+
@mode = mode
|
|
125
|
+
@categories = categories
|
|
126
|
+
@inline = inline
|
|
142
127
|
end
|
|
143
128
|
|
|
144
129
|
def disabled?
|
|
145
|
-
mode
|
|
130
|
+
mode.equal?(:disable)
|
|
146
131
|
end
|
|
147
132
|
|
|
148
133
|
def inline?
|
|
149
134
|
@inline
|
|
150
135
|
end
|
|
151
136
|
|
|
152
|
-
# Apply this directive's effect to the in-flight per-category state.
|
|
153
137
|
# Inline directives mark just their line; block disables open a region;
|
|
154
138
|
# block enables close one. Re-opening an already-open block is a no-op.
|
|
155
139
|
def apply(ranges, open_starts)
|
|
156
140
|
categories.each do |category|
|
|
157
141
|
if inline?
|
|
158
|
-
ranges
|
|
142
|
+
ranges.fetch(category) << (line_number..line_number) if disabled?
|
|
159
143
|
elsif disabled?
|
|
160
144
|
open_starts[category] ||= line_number
|
|
161
145
|
elsif (start = open_starts.delete(category))
|
|
162
|
-
ranges
|
|
146
|
+
ranges.fetch(category) << (start..line_number)
|
|
163
147
|
end
|
|
164
148
|
end
|
|
165
149
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ExitCodes
|
|
5
|
+
# Fails when a file listed in the checked-in baseline drops below its own
|
|
6
|
+
# floor on both axes: a lower percent than the floor records and more misses
|
|
7
|
+
# than it allows. The second condition is the dampener that keeps an edit
|
|
8
|
+
# with no coverage change from failing the run; a percent-only floor is
|
|
9
|
+
# decided by the percent alone.
|
|
10
|
+
class BaselineCheck < Check
|
|
11
|
+
def initialize(result, baseline)
|
|
12
|
+
super(result, nil)
|
|
13
|
+
@baseline = baseline
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def exit_code
|
|
17
|
+
MINIMUM_COVERAGE
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def compute_violations
|
|
23
|
+
CoverageViolations.baseline(result, @baseline)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def violation_lines(violation)
|
|
27
|
+
message = format(
|
|
28
|
+
"%<criterion>s coverage (%<actual>s) dropped below its baseline floor (%<expected>s%%) in %<filename>s",
|
|
29
|
+
criterion: violation.fetch(:criterion).capitalize,
|
|
30
|
+
actual: Color.colorize_percent(violation.fetch(:actual)),
|
|
31
|
+
expected: violation.fetch(:expected),
|
|
32
|
+
filename: violation.fetch(:project_filename)
|
|
33
|
+
)
|
|
34
|
+
["#{message}#{missed_clause(violation)}."]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def missed_clause(violation)
|
|
38
|
+
allowed = violation.fetch(:allowed_missed)
|
|
39
|
+
return unless allowed
|
|
40
|
+
|
|
41
|
+
units = UNITS.fetch(SimpleCov.coverage_statistics_key(violation.fetch(:criterion)))
|
|
42
|
+
" (#{violation.fetch(:actual_missed)} uncovered #{units}, #{allowed} allowed)"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
# Shared skeleton for the threshold checks
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# `compute_violations` (memoized here — `failing?` and `report` both
|
|
9
|
-
# consult it), and `report_violation`.
|
|
5
|
+
# Shared skeleton for the threshold checks. Subclasses supply `exit_code`,
|
|
6
|
+
# `compute_violations` (memoized here, since `failing?` and `report_lines`
|
|
7
|
+
# both consult it), and `violation_lines`.
|
|
10
8
|
class Check
|
|
11
9
|
def initialize(result, thresholds)
|
|
12
10
|
@result = result
|
|
@@ -17,17 +15,25 @@ module SimpleCov
|
|
|
17
15
|
violations.any?
|
|
18
16
|
end
|
|
19
17
|
|
|
18
|
+
def violations
|
|
19
|
+
@violations ||= compute_violations
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Concatenated rather than flat-mapped: a check answering a bare string
|
|
23
|
+
# instead of its list of lines is an error this raises, not a spelling the
|
|
24
|
+
# flattening would forgive.
|
|
25
|
+
def report_lines
|
|
26
|
+
lines = [] #: Array[String]
|
|
27
|
+
violations.each_with_object(lines) { |violation, into| into.concat(violation_lines(violation)) }
|
|
28
|
+
end
|
|
29
|
+
|
|
20
30
|
def report
|
|
21
|
-
|
|
31
|
+
report_lines.each { |line| ExitCodes.print_error(line) }
|
|
22
32
|
end
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
private
|
|
25
35
|
|
|
26
36
|
attr_reader :result, :thresholds
|
|
27
|
-
|
|
28
|
-
def violations
|
|
29
|
-
@violations ||= compute_violations
|
|
30
|
-
end
|
|
31
37
|
end
|
|
32
38
|
end
|
|
33
39
|
end
|
|
@@ -2,10 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
# Runs every coverage check against the result and returns the exit
|
|
6
|
-
# code from the first failing one (or SUCCESS if all pass).
|
|
7
5
|
module ExitCodeHandling
|
|
8
|
-
|
|
6
|
+
extend self
|
|
9
7
|
|
|
10
8
|
def call(result, coverage_limits:)
|
|
11
9
|
checks = coverage_checks(result, coverage_limits)
|
|
@@ -15,21 +13,36 @@ module SimpleCov
|
|
|
15
13
|
failing_check.report if SimpleCov.print_errors
|
|
16
14
|
failing_check.exit_code
|
|
17
15
|
else
|
|
18
|
-
|
|
16
|
+
SUCCESS
|
|
19
17
|
end
|
|
20
18
|
end
|
|
21
19
|
|
|
22
20
|
def coverage_checks(result, coverage_limits)
|
|
23
21
|
[
|
|
24
22
|
MinimumOverallCoverageCheck.new(result, coverage_limits.minimum_coverage),
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
),
|
|
23
|
+
minimum_by_file_check(result, coverage_limits),
|
|
24
|
+
BaselineCheck.new(result, coverage_limits.baseline),
|
|
28
25
|
MinimumCoverageByGroupCheck.new(result, coverage_limits.minimum_coverage_by_group),
|
|
29
26
|
MaximumOverallCoverageCheck.new(result, coverage_limits.maximum_coverage),
|
|
30
|
-
MaximumCoverageDropCheck.new(result, coverage_limits.maximum_coverage_drop)
|
|
27
|
+
MaximumCoverageDropCheck.new(result, coverage_limits.maximum_coverage_drop),
|
|
28
|
+
MaximumMissedCheck.new(result, coverage_limits.maximum_missed),
|
|
29
|
+
maximum_missed_per_file_check(result, coverage_limits)
|
|
31
30
|
]
|
|
32
31
|
end
|
|
32
|
+
|
|
33
|
+
def minimum_by_file_check(result, coverage_limits)
|
|
34
|
+
MinimumCoverageByFileCheck.new(
|
|
35
|
+
result, coverage_limits.minimum_coverage_by_file, coverage_limits.minimum_coverage_by_file_overrides,
|
|
36
|
+
baseline: coverage_limits.baseline
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def maximum_missed_per_file_check(result, coverage_limits)
|
|
41
|
+
MaximumMissedPerFileCheck.new(
|
|
42
|
+
result, coverage_limits.maximum_missed_per_file, coverage_limits.maximum_missed_per_file_overrides,
|
|
43
|
+
baseline: coverage_limits.baseline
|
|
44
|
+
)
|
|
45
|
+
end
|
|
33
46
|
end
|
|
34
47
|
end
|
|
35
48
|
end
|
|
@@ -2,24 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
# Fails when any coverage criterion has dropped by more than the
|
|
6
|
-
# configured maximum since the last recorded run.
|
|
7
5
|
class MaximumCoverageDropCheck < Check
|
|
8
6
|
def exit_code
|
|
9
|
-
|
|
7
|
+
MAXIMUM_COVERAGE_DROP
|
|
10
8
|
end
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
private
|
|
13
11
|
|
|
14
12
|
def compute_violations
|
|
15
|
-
|
|
13
|
+
CoverageViolations.maximum_drop(result, thresholds)
|
|
16
14
|
end
|
|
17
15
|
|
|
18
|
-
# The
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
ExitCodes.print_error SimpleCov::Color.colorize(message_for(violation), :red)
|
|
16
|
+
# The drop percent is a delta, not a coverage level, so it has no natural
|
|
17
|
+
# green/yellow/red mapping and the whole line goes red.
|
|
18
|
+
def violation_lines(violation)
|
|
19
|
+
[Color.colorize(message_for(violation), :red)]
|
|
23
20
|
end
|
|
24
21
|
|
|
25
22
|
def message_for(violation)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ExitCodes
|
|
5
|
+
class MaximumMissedCheck < Check
|
|
6
|
+
def exit_code
|
|
7
|
+
MINIMUM_COVERAGE
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def compute_violations
|
|
13
|
+
CoverageViolations.maximum_missed(result, thresholds)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def violation_lines(violation)
|
|
17
|
+
[format(
|
|
18
|
+
"Missed %<units>s (%<actual>d) exceed the configured maximum_missed (%<maximum>d).",
|
|
19
|
+
units: UNITS.fetch(SimpleCov.coverage_statistics_key(violation.fetch(:criterion))),
|
|
20
|
+
actual: violation.fetch(:actual),
|
|
21
|
+
maximum: violation.fetch(:maximum)
|
|
22
|
+
)]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ExitCodes
|
|
5
|
+
class MaximumMissedPerFileCheck < Check
|
|
6
|
+
def initialize(result, maximum_missed_per_file, overrides = {}, baseline: nil)
|
|
7
|
+
super(result, maximum_missed_per_file)
|
|
8
|
+
@overrides = overrides
|
|
9
|
+
@baseline = baseline
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def exit_code
|
|
13
|
+
MINIMUM_COVERAGE
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def compute_violations
|
|
19
|
+
CoverageViolations.maximum_missed_by_file(result, thresholds, @overrides, baseline: @baseline)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def violation_lines(violation)
|
|
23
|
+
[format(
|
|
24
|
+
"Missed %<units>s (%<actual>d) exceed the configured maximum_missed_per_file (%<maximum>d) " \
|
|
25
|
+
"in %<filename>s.",
|
|
26
|
+
units: UNITS.fetch(SimpleCov.coverage_statistics_key(violation.fetch(:criterion))),
|
|
27
|
+
actual: violation.fetch(:actual),
|
|
28
|
+
maximum: violation.fetch(:maximum),
|
|
29
|
+
filename: violation.fetch(:project_filename)
|
|
30
|
+
)]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -2,30 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
# Fails when the overall (project-wide) coverage for any criterion is
|
|
6
|
-
# above the configured maximum. Pair with
|
|
7
|
-
# `SimpleCov::ExitCodes::MinimumOverallCoverageCheck` (or use
|
|
8
|
-
# `SimpleCov.expected_coverage`) to pin coverage to an exact value
|
|
9
|
-
# and surface unexpected increases instead of silently absorbing them.
|
|
10
5
|
class MaximumOverallCoverageCheck < Check
|
|
11
6
|
def exit_code
|
|
12
|
-
|
|
7
|
+
MAXIMUM_COVERAGE
|
|
13
8
|
end
|
|
14
9
|
|
|
15
|
-
|
|
10
|
+
private
|
|
16
11
|
|
|
17
12
|
def compute_violations
|
|
18
|
-
|
|
13
|
+
CoverageViolations.maximum_overall(result, thresholds)
|
|
19
14
|
end
|
|
20
15
|
|
|
21
|
-
def
|
|
22
|
-
|
|
16
|
+
def violation_lines(violation)
|
|
17
|
+
[format(
|
|
23
18
|
"%<criterion>s coverage (%<actual>s) is above the expected maximum coverage (%<expected>.2f%%). " \
|
|
24
19
|
"Time to bump the threshold!",
|
|
25
20
|
criterion: violation.fetch(:criterion).capitalize,
|
|
26
|
-
actual:
|
|
21
|
+
actual: Color.colorize_percent(violation.fetch(:actual)),
|
|
27
22
|
expected: violation.fetch(:expected)
|
|
28
|
-
)
|
|
23
|
+
)]
|
|
29
24
|
end
|
|
30
25
|
end
|
|
31
26
|
end
|
|
@@ -2,33 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
# Fails when any individual file falls below the configured minimum
|
|
6
|
-
# coverage for any criterion.
|
|
7
5
|
class MinimumCoverageByFileCheck < Check
|
|
8
|
-
|
|
6
|
+
# `baseline:` exempts the file-and-criterion pairs it covers: those answer to
|
|
7
|
+
# their own floor via `BaselineCheck` instead.
|
|
8
|
+
def initialize(result, minimum_coverage_by_file, overrides = {}, baseline: nil)
|
|
9
9
|
super(result, minimum_coverage_by_file)
|
|
10
10
|
@overrides = overrides
|
|
11
|
+
@baseline = baseline
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def exit_code
|
|
14
|
-
|
|
15
|
+
MINIMUM_COVERAGE
|
|
15
16
|
end
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
private
|
|
18
19
|
|
|
19
20
|
def compute_violations
|
|
20
|
-
|
|
21
|
+
CoverageViolations.minimum_by_file(result, thresholds, @overrides, baseline: @baseline)
|
|
21
22
|
end
|
|
22
23
|
|
|
23
|
-
def
|
|
24
|
-
|
|
24
|
+
def violation_lines(violation)
|
|
25
|
+
[format(
|
|
25
26
|
"%<criterion>s coverage by file (%<actual>s) is below the expected minimum coverage " \
|
|
26
27
|
"(%<expected>.2f%%) in %<filename>s.",
|
|
27
28
|
criterion: violation.fetch(:criterion).capitalize,
|
|
28
|
-
actual:
|
|
29
|
+
actual: Color.colorize_percent(violation.fetch(:actual)),
|
|
29
30
|
expected: violation.fetch(:expected),
|
|
30
31
|
filename: violation.fetch(:project_filename)
|
|
31
|
-
)
|
|
32
|
+
)]
|
|
32
33
|
end
|
|
33
34
|
end
|
|
34
35
|
end
|
|
@@ -2,28 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
# Fails when any configured group falls below its minimum coverage
|
|
6
|
-
# threshold for any criterion.
|
|
7
5
|
class MinimumCoverageByGroupCheck < Check
|
|
8
6
|
def exit_code
|
|
9
|
-
|
|
7
|
+
MINIMUM_COVERAGE
|
|
10
8
|
end
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
private
|
|
13
11
|
|
|
14
12
|
def compute_violations
|
|
15
|
-
|
|
13
|
+
CoverageViolations.minimum_by_group(result, thresholds)
|
|
16
14
|
end
|
|
17
15
|
|
|
18
|
-
def
|
|
19
|
-
|
|
16
|
+
def violation_lines(violation)
|
|
17
|
+
[format(
|
|
20
18
|
"%<criterion>s coverage by group (%<actual>s) is below the expected minimum coverage " \
|
|
21
19
|
"(%<expected>.2f%%) in %<group_name>s.",
|
|
22
20
|
criterion: violation.fetch(:criterion).capitalize,
|
|
23
|
-
actual:
|
|
21
|
+
actual: Color.colorize_percent(violation.fetch(:actual)),
|
|
24
22
|
expected: violation.fetch(:expected),
|
|
25
23
|
group_name: violation.fetch(:group_name)
|
|
26
|
-
)
|
|
24
|
+
)]
|
|
27
25
|
end
|
|
28
26
|
end
|
|
29
27
|
end
|
|
@@ -2,53 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
# Fails when the overall (project-wide) coverage for any criterion is
|
|
6
|
-
# below the configured minimum.
|
|
7
5
|
class MinimumOverallCoverageCheck < Check
|
|
8
6
|
def exit_code
|
|
9
|
-
|
|
7
|
+
MINIMUM_COVERAGE
|
|
10
8
|
end
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
private
|
|
13
11
|
|
|
14
12
|
WORST_FILES_LIMIT = 5
|
|
15
13
|
private_constant :WORST_FILES_LIMIT
|
|
16
14
|
|
|
17
15
|
def compute_violations
|
|
18
|
-
|
|
16
|
+
CoverageViolations.minimum_overall(result, thresholds)
|
|
19
17
|
end
|
|
20
18
|
|
|
21
|
-
def
|
|
19
|
+
def violation_lines(violation)
|
|
22
20
|
criterion = violation.fetch(:criterion)
|
|
23
|
-
|
|
24
|
-
ExitCodes.print_error format(
|
|
21
|
+
headline = format(
|
|
25
22
|
"%<criterion>s coverage (%<actual>s) is below the expected minimum coverage (%<expected>.2f%%).",
|
|
26
23
|
criterion: criterion.capitalize,
|
|
27
|
-
actual:
|
|
24
|
+
actual: Color.colorize_percent(violation.fetch(:actual)),
|
|
28
25
|
expected: violation.fetch(:expected)
|
|
29
26
|
)
|
|
30
|
-
|
|
27
|
+
[headline, *worst_files_lines(criterion)]
|
|
31
28
|
end
|
|
32
29
|
|
|
33
|
-
def
|
|
30
|
+
def worst_files_lines(criterion)
|
|
34
31
|
worst = worst_files_for(criterion)
|
|
35
|
-
return if worst.empty?
|
|
32
|
+
return [] if worst.empty?
|
|
36
33
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
ExitCodes.print_error format(
|
|
34
|
+
[" Lowest-coverage files (#{criterion}):"] + worst.map do |path, percent|
|
|
35
|
+
format(
|
|
40
36
|
" %<percent>s %<path>s",
|
|
41
|
-
percent:
|
|
37
|
+
percent: Color.colorize_percent(percent, format("%6.2f%%", percent)),
|
|
42
38
|
path: path
|
|
43
39
|
)
|
|
44
40
|
end
|
|
45
41
|
end
|
|
46
42
|
|
|
43
|
+
# The list is a hint at where to add tests, so a fully covered file has no
|
|
44
|
+
# business on it: without the filter, files at 100% padded the list out to
|
|
45
|
+
# its five entries (#1286).
|
|
47
46
|
def worst_files_for(criterion)
|
|
48
47
|
stats_key = SimpleCov.coverage_statistics_key(criterion)
|
|
49
48
|
with_stats = result.files.filter_map do |source_file|
|
|
50
49
|
stats = source_file.coverage_statistics[stats_key]
|
|
51
|
-
[source_file.project_filename, stats.percent] if stats
|
|
50
|
+
[source_file.project_filename, stats.percent] if stats && stats.percent < 100
|
|
52
51
|
end
|
|
53
52
|
with_stats.sort_by { |_path, percent| percent }.first(WORST_FILES_LIMIT)
|
|
54
53
|
end
|