simplecov 1.1.1 → 1.3.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 +148 -5
- 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/annotations.rb +166 -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 +151 -0
- data/lib/simplecov/cli/patch.rb +175 -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 +33 -0
- data/lib/simplecov/cli/uncovered.rb +58 -34
- data/lib/simplecov/cli/usage.rb +153 -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 +19 -37
- 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 +7 -11
- 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 +37 -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 +160 -65
- 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/erb.rb +44 -0
- data/lib/simplecov/directive/haml.rb +33 -0
- data/lib/simplecov/directive/indented_template.rb +37 -0
- data/lib/simplecov/directive/slim.rb +35 -0
- data/lib/simplecov/directive/template.rb +29 -0
- data/lib/simplecov/directive.rb +47 -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 +16 -10
- 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 +37 -55
- 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 +15 -9
- 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 -19
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
- data/lib/simplecov/parallel_adapters.rb +24 -55
- 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 +4 -15
- data/lib/simplecov/profiles/root_filter.rb +0 -4
- data/lib/simplecov/profiles/strict.rb +11 -21
- 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 +74 -117
- data/lib/simplecov/result_adapter.rb +56 -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 +92 -122
- data/lib/simplecov/run_identity.rb +16 -18
- data/lib/simplecov/simulate_coverage.rb +34 -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 +13 -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 +29 -29
- data/lib/simplecov/source_file/source_loader.rb +45 -35
- data/lib/simplecov/source_file/statistics.rb +20 -9
- data/lib/simplecov/source_file.rb +21 -51
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +55 -139
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +9 -26
- 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 +45 -65
- data/lib/simplecov/static_coverage_extractor.rb +43 -75
- 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 +57 -113
- data/lib/simplecov_json_formatter.rb +1 -0
- data/man/simplecov.1 +354 -0
- data/sig/simplecov.rbs +528 -595
- metadata +79 -6
data/lib/simplecov/file_list.rb
CHANGED
|
@@ -1,31 +1,20 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
|
-
# An array of SimpleCov SourceFile instances with additional collection helper
|
|
5
|
-
# methods for calculating coverage across them etc.
|
|
6
4
|
class FileList
|
|
7
5
|
include Enumerable
|
|
8
6
|
extend Forwardable
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# also delegating methods implemented in Enumerable as they have
|
|
14
|
-
# custom Array implementations which are presumably better/more
|
|
15
|
-
# resource efficient
|
|
16
|
-
:size, :map, :count,
|
|
17
|
-
# surprisingly not in Enumerable
|
|
18
|
-
:empty?, :length,
|
|
19
|
-
# still act like we're kinda an array
|
|
20
|
-
:to_a, :to_ary
|
|
8
|
+
# Enumerable's own implementations are skipped where Array has a better
|
|
9
|
+
# one, and `to_a` / `to_ary` keep this acting like the array it wraps.
|
|
10
|
+
def_delegators :@files, :each, :size, :map, :count, :empty?, :length, :to_a, :to_ary
|
|
21
11
|
|
|
22
12
|
def initialize(files)
|
|
23
13
|
@files = files
|
|
24
14
|
end
|
|
25
15
|
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
# (`:line` / `:branch` / `:method`) to get that one CoverageStatistics.
|
|
16
|
+
# With no argument answers the `{line:, branch:, method:}` Hash, and with a
|
|
17
|
+
# criterion symbol that one CoverageStatistics.
|
|
29
18
|
def coverage_statistics(criterion = nil)
|
|
30
19
|
stats = (@coverage_statistics ||= compute_coverage_statistics)
|
|
31
20
|
criterion ? stats[criterion] : stats
|
|
@@ -35,34 +24,27 @@ module SimpleCov
|
|
|
35
24
|
@coverage_statistics_by_file ||= compute_coverage_statistics_by_file
|
|
36
25
|
end
|
|
37
26
|
|
|
38
|
-
# Returns the count of lines that have coverage
|
|
39
27
|
def covered_lines
|
|
40
28
|
coverage_statistics[:line]&.covered
|
|
41
29
|
end
|
|
42
30
|
|
|
43
|
-
# Returns the count of lines that have been missed
|
|
44
31
|
def missed_lines
|
|
45
32
|
coverage_statistics[:line]&.missed
|
|
46
33
|
end
|
|
47
34
|
|
|
48
|
-
# Returns the count of lines that are not relevant for coverage
|
|
49
35
|
def never_lines
|
|
50
36
|
sum { |f| f.never_lines.size }
|
|
51
37
|
end
|
|
52
38
|
|
|
53
|
-
# Returns the count of skipped lines
|
|
54
39
|
def skipped_lines
|
|
55
40
|
sum { |f| f.skipped_lines.size }
|
|
56
41
|
end
|
|
57
42
|
|
|
58
|
-
# Computes the coverage based upon lines covered and lines missed for each file
|
|
59
|
-
# Returns an array with all coverage percentages
|
|
60
43
|
def covered_percentages
|
|
61
44
|
map(&:covered_percent)
|
|
62
45
|
end
|
|
63
46
|
|
|
64
|
-
#
|
|
65
|
-
# for an empty list (e.g. a fully filtered result).
|
|
47
|
+
# Nil for an empty list, such as a fully filtered result.
|
|
66
48
|
def least_covered_file
|
|
67
49
|
# `covered_percent` is nil only for an unmeasured criterion, and :line
|
|
68
50
|
# is always measured, so the `|| 0.0` arm never fires at runtime; it
|
|
@@ -70,36 +52,28 @@ module SimpleCov
|
|
|
70
52
|
min_by { |file| file.covered_percent || 0.0 }&.filename
|
|
71
53
|
end
|
|
72
54
|
|
|
73
|
-
# Returns the overall amount of relevant lines of code across all files in this list
|
|
74
55
|
def lines_of_code
|
|
75
56
|
coverage_statistics[:line]&.total
|
|
76
57
|
end
|
|
77
58
|
|
|
78
|
-
#
|
|
79
|
-
# by default). Returns nil if the criterion was not measured.
|
|
80
|
-
# @return [Float, nil]
|
|
59
|
+
# Nil if the criterion was not measured.
|
|
81
60
|
def covered_percent(criterion = :line)
|
|
82
61
|
coverage_statistics(criterion)&.percent
|
|
83
62
|
end
|
|
84
63
|
|
|
85
|
-
# The strength (average hits per relevant unit) for the given criterion
|
|
86
|
-
# (line by default).
|
|
87
|
-
# @return [Float, nil]
|
|
64
|
+
# The strength (average hits per relevant unit) for the given criterion.
|
|
88
65
|
def covered_strength(criterion = :line)
|
|
89
66
|
coverage_statistics(criterion)&.strength
|
|
90
67
|
end
|
|
91
68
|
|
|
92
|
-
# Return total count of branches in all files
|
|
93
69
|
def total_branches
|
|
94
70
|
coverage_statistics[:branch]&.total
|
|
95
71
|
end
|
|
96
72
|
|
|
97
|
-
# Return total count of covered branches
|
|
98
73
|
def covered_branches
|
|
99
74
|
coverage_statistics[:branch]&.covered
|
|
100
75
|
end
|
|
101
76
|
|
|
102
|
-
# Return total count of covered branches
|
|
103
77
|
def missed_branches
|
|
104
78
|
coverage_statistics[:branch]&.missed
|
|
105
79
|
end
|
|
@@ -108,17 +82,14 @@ module SimpleCov
|
|
|
108
82
|
coverage_statistics[:branch]&.percent
|
|
109
83
|
end
|
|
110
84
|
|
|
111
|
-
# Return total count of methods in all files
|
|
112
85
|
def total_methods
|
|
113
86
|
coverage_statistics[:method]&.total
|
|
114
87
|
end
|
|
115
88
|
|
|
116
|
-
# Return total count of covered methods
|
|
117
89
|
def covered_methods
|
|
118
90
|
coverage_statistics[:method]&.covered
|
|
119
91
|
end
|
|
120
92
|
|
|
121
|
-
# Return total count of missed methods
|
|
122
93
|
def missed_methods
|
|
123
94
|
coverage_statistics[:method]&.missed
|
|
124
95
|
end
|
|
@@ -127,23 +98,21 @@ module SimpleCov
|
|
|
127
98
|
coverage_statistics[:method]&.percent
|
|
128
99
|
end
|
|
129
100
|
|
|
130
|
-
|
|
101
|
+
private
|
|
131
102
|
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
# enabled set so disabled criteria don't surface in totals, JSON,
|
|
138
|
-
# or the HTML report.
|
|
103
|
+
# Seeded with one entry per criterion the user enabled, so an empty FileList
|
|
104
|
+
# still yields the right shape. `SourceFile#coverage_statistics` always
|
|
105
|
+
# reports all three criteria; FileList is the layer that filters to the
|
|
106
|
+
# enabled set so disabled criteria don't surface in totals, JSON, or the
|
|
107
|
+
# HTML report.
|
|
139
108
|
def compute_coverage_statistics_by_file
|
|
140
109
|
seed = enabled_criteria_for_reporting.to_h do |criterion|
|
|
141
110
|
bucket = [] #: Array[CoverageStatistics]
|
|
142
111
|
[criterion, bucket]
|
|
143
112
|
end
|
|
144
|
-
|
|
113
|
+
each_with_object(seed) do |file, together|
|
|
145
114
|
file.coverage_statistics.each do |criterion, stats|
|
|
146
|
-
together
|
|
115
|
+
together.fetch(criterion) << stats if together.key?(criterion)
|
|
147
116
|
end
|
|
148
117
|
end
|
|
149
118
|
end
|
|
@@ -152,12 +121,12 @@ module SimpleCov
|
|
|
152
121
|
coverage_statistics_by_file.transform_values { |stats| CoverageStatistics.from(stats) }
|
|
153
122
|
end
|
|
154
123
|
|
|
155
|
-
# `:line` (or its `:oneshot_line` synonym) is reported when either
|
|
156
|
-
#
|
|
157
|
-
#
|
|
124
|
+
# `:line` (or its `:oneshot_line` synonym) is reported when either criterion
|
|
125
|
+
# is enabled; the JRuby-gated branch/method criteria are reported when they
|
|
126
|
+
# pass their own engine-support check.
|
|
158
127
|
def enabled_criteria_for_reporting
|
|
159
128
|
criteria = [] #: Array[SimpleCov::criterion]
|
|
160
|
-
criteria << :line
|
|
129
|
+
criteria << :line if SimpleCov.line_coverage?
|
|
161
130
|
criteria << :branch if SimpleCov.branch_coverage?
|
|
162
131
|
criteria << :method if SimpleCov.method_coverage?
|
|
163
132
|
criteria
|
data/lib/simplecov/filter.rb
CHANGED
|
@@ -2,15 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
#
|
|
5
|
-
# Base filter class. Inherit from this to create custom filters,
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# # A sample class that rejects all source files.
|
|
9
|
-
# class StupidFilter < SimpleCov::Filter
|
|
10
|
-
# def matches?(source_file)
|
|
11
|
-
# false
|
|
12
|
-
# end
|
|
13
|
-
# end
|
|
5
|
+
# Base filter class. Inherit from this to create custom filters, and
|
|
6
|
+
# override `matches?(source_file)`.
|
|
14
7
|
#
|
|
15
8
|
class Filter
|
|
16
9
|
attr_reader :filter_argument
|
|
@@ -25,50 +18,46 @@ module SimpleCov
|
|
|
25
18
|
|
|
26
19
|
# Whether this filter's verdict depends only on the file's path, so it can
|
|
27
20
|
# be decided before that file has any coverage. Used when recording which
|
|
28
|
-
# tracked files a process did not load
|
|
29
|
-
#
|
|
21
|
+
# tracked files a process did not load. Defaults to false so a custom
|
|
22
|
+
# filter is never guessed at (#1250).
|
|
30
23
|
def path_only?
|
|
31
24
|
false
|
|
32
25
|
end
|
|
33
26
|
|
|
34
|
-
# `string_filter` selects the semantics of bare String arguments
|
|
35
|
-
# StringFilter's segment-substring match for `
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
def self.build_filter(filter_argument, string_filter:
|
|
27
|
+
# `string_filter` selects the semantics of bare String arguments:
|
|
28
|
+
# StringFilter's segment-substring match for `skip`, GlobFilter for
|
|
29
|
+
# `cover`. It threads through Array elements so a list gets the same
|
|
30
|
+
# treatment as its members.
|
|
31
|
+
def self.build_filter(filter_argument, string_filter: StringFilter)
|
|
39
32
|
case filter_argument
|
|
40
|
-
when
|
|
41
|
-
when String
|
|
33
|
+
when Filter then filter_argument
|
|
34
|
+
when String then string_filter.new(filter_argument)
|
|
42
35
|
when Array
|
|
43
|
-
|
|
36
|
+
ArrayFilter.new(filter_argument.map { |arg| build_filter(arg, string_filter: string_filter) })
|
|
44
37
|
else class_for_argument(filter_argument).new(filter_argument)
|
|
45
38
|
end
|
|
46
39
|
end
|
|
47
40
|
|
|
48
41
|
def self.class_for_argument(filter_argument)
|
|
49
42
|
filter_classes_by_argument_type.find { |type, _| filter_argument.is_a?(type) }&.last ||
|
|
50
|
-
raise(
|
|
43
|
+
raise(ConfigurationError, "You have provided an unrecognized filter type")
|
|
51
44
|
end
|
|
52
45
|
|
|
53
46
|
def self.filter_classes_by_argument_type
|
|
54
47
|
@filter_classes_by_argument_type ||= {
|
|
55
|
-
String =>
|
|
56
|
-
Regexp =>
|
|
57
|
-
Array =>
|
|
58
|
-
Proc =>
|
|
48
|
+
String => StringFilter,
|
|
49
|
+
Regexp => RegexFilter,
|
|
50
|
+
Array => ArrayFilter,
|
|
51
|
+
Proc => BlockFilter
|
|
59
52
|
}.freeze
|
|
60
53
|
end
|
|
61
54
|
private_class_method :filter_classes_by_argument_type
|
|
62
55
|
end
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
#
|
|
68
|
-
# string configured when initializing this Filter with StringFilter.new('somestring').
|
|
69
|
-
# Matching is path-segment-aware: the argument must appear immediately after a "/"
|
|
70
|
-
# and be followed by "/" or end-of-string, so "lib" matches "/lib/foo.rb" but not
|
|
71
|
-
# "/app/models/library.rb".
|
|
57
|
+
class StringFilter < Filter
|
|
58
|
+
# Matching is path-segment-aware: the argument must appear immediately
|
|
59
|
+
# after a "/" and be followed by "/" or end-of-string, so "lib" matches
|
|
60
|
+
# "/lib/foo.rb" but not "/app/models/library.rb".
|
|
72
61
|
def matches?(source_file)
|
|
73
62
|
source_file.project_filename.match?(segment_pattern)
|
|
74
63
|
end
|
|
@@ -77,7 +66,7 @@ module SimpleCov
|
|
|
77
66
|
true
|
|
78
67
|
end
|
|
79
68
|
|
|
80
|
-
|
|
69
|
+
private
|
|
81
70
|
|
|
82
71
|
def segment_pattern
|
|
83
72
|
@segment_pattern ||= compute_segment_pattern
|
|
@@ -85,34 +74,32 @@ module SimpleCov
|
|
|
85
74
|
|
|
86
75
|
def compute_segment_pattern
|
|
87
76
|
normalized = filter_argument.delete_prefix("/")
|
|
88
|
-
escaped
|
|
89
|
-
boundary
|
|
77
|
+
escaped = Regexp.escape(normalized)
|
|
78
|
+
boundary = '(?:\A|/)'
|
|
90
79
|
|
|
91
80
|
if normalized.end_with?("/")
|
|
92
81
|
# Trailing slash signals directory-only matching.
|
|
93
82
|
/#{boundary}#{escaped}/
|
|
94
83
|
elsif normalized.include?(".") && !normalized.include?("/")
|
|
95
|
-
# Bare filename pattern (
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
|
|
84
|
+
# Bare filename pattern ("test.rb" matches "faked_test.rb"): allow a
|
|
85
|
+
# substring match within a single path segment. Multi-segment arguments
|
|
86
|
+
# must not get this relaxation, or "app/models/user.rb" would also
|
|
87
|
+
# match "webapp/models/user.rb". No boundary here: `[^/]*` already
|
|
88
|
+
# reaches back to the segment start, so prefixing one matches the same
|
|
89
|
+
# paths.
|
|
90
|
+
%r{[^/]*#{escaped}(?=[/.]|\z)}
|
|
100
91
|
else
|
|
101
|
-
#
|
|
102
|
-
#
|
|
92
|
+
# Require a segment-boundary match so "lib" matches "lib/" but not
|
|
93
|
+
# "library/".
|
|
103
94
|
%r{#{boundary}#{escaped}(?=[/.]|\z)}
|
|
104
95
|
end
|
|
105
96
|
end
|
|
106
97
|
end
|
|
107
98
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
#
|
|
112
|
-
# regex configured when initializing this Filter with RegexFilter.new(/someregex/).
|
|
113
|
-
# Uses `Regexp#match?` so the predicate returns a real boolean — `=~`
|
|
114
|
-
# would return the match position (an Integer or nil), which trips
|
|
115
|
-
# rspec-mocks 4's stricter predicate-matcher type check.
|
|
99
|
+
class RegexFilter < Filter
|
|
100
|
+
# `Regexp#match?` rather than `=~` so the predicate returns a real boolean:
|
|
101
|
+
# `=~` would return the match position, which trips rspec-mocks 4's
|
|
102
|
+
# stricter predicate-matcher type check.
|
|
116
103
|
def matches?(source_file)
|
|
117
104
|
filter_argument.match?(source_file.project_filename)
|
|
118
105
|
end
|
|
@@ -122,21 +109,16 @@ module SimpleCov
|
|
|
122
109
|
end
|
|
123
110
|
end
|
|
124
111
|
|
|
125
|
-
|
|
126
|
-
# source file.
|
|
127
|
-
class BlockFilter < SimpleCov::Filter
|
|
128
|
-
# Returns true if the block given when initializing this filter with BlockFilter.new {|src_file| ... }
|
|
129
|
-
# returns true for the given source file.
|
|
112
|
+
class BlockFilter < Filter
|
|
130
113
|
def matches?(source_file)
|
|
131
114
|
filter_argument.call(source_file)
|
|
132
115
|
end
|
|
133
116
|
end
|
|
134
117
|
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
|
|
139
|
-
class GlobFilter < SimpleCov::Filter
|
|
118
|
+
# Matches when the source file's project path matches the configured shell
|
|
119
|
+
# glob. Used by `cover`, and by `skip` when callers want glob semantics
|
|
120
|
+
# instead of `StringFilter`'s substring match.
|
|
121
|
+
class GlobFilter < Filter
|
|
140
122
|
def matches?(source_file)
|
|
141
123
|
File.fnmatch?(filter_argument, source_file.project_filename, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
|
142
124
|
end
|
|
@@ -146,9 +128,7 @@ module SimpleCov
|
|
|
146
128
|
end
|
|
147
129
|
end
|
|
148
130
|
|
|
149
|
-
|
|
150
|
-
# array's elements) match the source file.
|
|
151
|
-
class ArrayFilter < SimpleCov::Filter
|
|
131
|
+
class ArrayFilter < Filter
|
|
152
132
|
def initialize(filter_argument)
|
|
153
133
|
filter_objects = filter_argument.map do |arg|
|
|
154
134
|
Filter.build_filter(arg)
|
|
@@ -157,13 +137,10 @@ module SimpleCov
|
|
|
157
137
|
super(filter_objects)
|
|
158
138
|
end
|
|
159
139
|
|
|
160
|
-
# Path-decidable only when every component filter is.
|
|
161
140
|
def path_only?
|
|
162
141
|
filter_argument.all?(&:path_only?)
|
|
163
142
|
end
|
|
164
143
|
|
|
165
|
-
# Returns true if any of the filters in the array match the given source file.
|
|
166
|
-
# Configure this Filter like StringFilter.new(['some/path', /^some_regex/, Proc.new {|src_file| ... }])
|
|
167
144
|
def matches?(source_files_list)
|
|
168
145
|
filter_argument.any? do |arg|
|
|
169
146
|
arg.matches?(source_files_list)
|
|
@@ -6,42 +6,35 @@ module SimpleCov
|
|
|
6
6
|
module Formatter
|
|
7
7
|
# @api private
|
|
8
8
|
#
|
|
9
|
-
# Shared scaffolding for formatters that write a coverage report to
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# override `message_prefix` (e.g. JSON prepends "JSON ").
|
|
9
|
+
# Shared scaffolding for formatters that write a coverage report to an
|
|
10
|
+
# output directory and emit a "Coverage report generated for X to Y"
|
|
11
|
+
# summary on stderr. Subclasses override `format`, and may override
|
|
12
|
+
# `message_prefix`.
|
|
14
13
|
class Base
|
|
15
|
-
# `output_dir` defaults to `SimpleCov.coverage_path
|
|
16
|
-
#
|
|
17
|
-
# somewhere else (handy for tests that don't want to clobber the
|
|
18
|
-
# project's `coverage/` directory).
|
|
14
|
+
# `output_dir` defaults to `SimpleCov.coverage_path`. Pass it explicitly
|
|
15
|
+
# to write somewhere else.
|
|
19
16
|
def initialize(silent: false, output_dir: nil)
|
|
20
17
|
@silent = silent
|
|
21
18
|
@output_dir = output_dir
|
|
22
19
|
end
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
private
|
|
25
22
|
|
|
26
23
|
# The one home of the "status lines go to stderr, not through warn"
|
|
27
|
-
# decision (
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# trackers, raise-on-warning test setups) nor vanishes under `-W0`.
|
|
32
|
-
# Subclasses call this at the end of their `format`.
|
|
24
|
+
# decision (#1225). stderr rather than stdout because this is a status
|
|
25
|
+
# message, not the program's output, so it stays out of pipelines like
|
|
26
|
+
# `rspec -f json`. And `$stderr.puts` rather than `warn` so the line
|
|
27
|
+
# neither reaches `Warning.warn` hooks nor vanishes under `-W0`.
|
|
33
28
|
def emit_status(result)
|
|
34
29
|
$stderr.puts output_message(result) unless @silent # rubocop:disable Style/StderrPuts
|
|
35
30
|
rescue IOError
|
|
36
|
-
# A parallel runner can close a worker's stderr before its at_exit
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# threshold checks and the .last_run.json write.
|
|
31
|
+
# A parallel runner can close a worker's stderr before its at_exit hooks
|
|
32
|
+
# run (rspec-conductor does). Losing the status line must not abort the
|
|
33
|
+
# exit tasks that follow report generation.
|
|
40
34
|
end
|
|
41
35
|
|
|
42
|
-
# Subclasses override to prepend a marker (e.g. "JSON ") to the
|
|
43
|
-
#
|
|
44
|
-
# historically been the unmarked default.
|
|
36
|
+
# Subclasses override to prepend a marker (e.g. "JSON ") to the summary
|
|
37
|
+
# line. Empty for the HTML formatter, historically the unmarked default.
|
|
45
38
|
def message_prefix
|
|
46
39
|
""
|
|
47
40
|
end
|
|
@@ -50,15 +43,11 @@ module SimpleCov
|
|
|
50
43
|
@output_dir || SimpleCov.coverage_path
|
|
51
44
|
end
|
|
52
45
|
|
|
53
|
-
# The path shown in the "Coverage report generated for X to Y"
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
# hyperlinks paths) can act on — e.g. `coverage/index.html`
|
|
59
|
-
# instead of the bare directory `coverage`. Paths outside cwd
|
|
60
|
-
# stay absolute; a `../../../tmp/cov` display would be more
|
|
61
|
-
# confusing than the absolute form. See issue #197.
|
|
46
|
+
# The path shown in the "Coverage report generated for X to Y" status line.
|
|
47
|
+
# Rendered relative to cwd when `output_path` lives inside cwd, with the
|
|
48
|
+
# formatter's `entry_point_filename` appended so the line points at a
|
|
49
|
+
# concrete file a terminal can hyperlink. Paths outside cwd stay
|
|
50
|
+
# absolute; a `../../../tmp/cov` display would be more confusing (#197).
|
|
62
51
|
def displayable_output_path
|
|
63
52
|
directory = relative_or_absolute_output_path
|
|
64
53
|
entry_point = entry_point_filename
|
|
@@ -70,47 +59,40 @@ module SimpleCov
|
|
|
70
59
|
relative = Pathname.new(absolute).relative_path_from(Pathname.pwd).to_s
|
|
71
60
|
relative.start_with?("..") ? absolute : relative
|
|
72
61
|
rescue ArgumentError
|
|
73
|
-
# Pathname#relative_path_from raises across mixed absolute/
|
|
74
|
-
#
|
|
75
|
-
# absolute form on any unresolvable case.
|
|
62
|
+
# Pathname#relative_path_from raises across mixed absolute/relative
|
|
63
|
+
# inputs, and across Windows drives.
|
|
76
64
|
output_path
|
|
77
65
|
end
|
|
78
66
|
|
|
79
|
-
# Subclasses override to name the report's entry-point file
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# that has no single canonical entry point.
|
|
67
|
+
# Subclasses override to name the report's entry-point file, which gets
|
|
68
|
+
# appended to the directory in the status line. The empty body answers
|
|
69
|
+
# nil, which leaves the bare directory in place for any third-party
|
|
70
|
+
# formatter that has no single canonical entry point.
|
|
84
71
|
def entry_point_filename
|
|
85
|
-
nil
|
|
86
72
|
end
|
|
87
73
|
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# to Y") is always first; per-criterion lines follow in the
|
|
91
|
-
# order of `result.coverage_statistics` (which is the same
|
|
92
|
-
# insertion order as `SourceFile#coverage_statistics`, which in
|
|
93
|
-
# turn reflects what the user enabled).
|
|
74
|
+
# One summary line per criterion the run actually measured, in the order
|
|
75
|
+
# of `result.coverage_statistics`, which reflects what the user enabled.
|
|
94
76
|
def output_message(result)
|
|
95
77
|
header = "#{message_prefix}Coverage report generated for #{result.command_name} to #{displayable_output_path}"
|
|
96
|
-
body
|
|
78
|
+
body = result.coverage_statistics.filter_map { |criterion, stat| stats_line(criterion, stat) }
|
|
97
79
|
[header, *body].join("\n")
|
|
98
80
|
end
|
|
99
81
|
|
|
100
|
-
# Returns nil for branch/method criteria that have nothing to
|
|
101
|
-
#
|
|
102
|
-
# Showing "Branch coverage: 0 / 0 (100.00%)" is noise; the older
|
|
103
|
-
# output specifically suppressed it.
|
|
82
|
+
# Returns nil for branch/method criteria that have nothing to measure.
|
|
83
|
+
# Showing "Branch coverage: 0 / 0 (100.00%)" is noise.
|
|
104
84
|
def stats_line(criterion, stat)
|
|
105
|
-
return if criterion
|
|
85
|
+
return if !criterion.equal?(:line) && !stat.total.positive?
|
|
106
86
|
|
|
107
87
|
percent = SimpleCov.round_coverage(stat.percent)
|
|
88
|
+
# `Symbol#capitalize` answers a Symbol, which `%<label>s` renders as its
|
|
89
|
+
# capitalized name, so the label needs no `to_s` step of its own.
|
|
108
90
|
Kernel.format(
|
|
109
91
|
"%<label>s coverage: %<covered>d / %<total>d (%<percent>s)",
|
|
110
|
-
label: criterion.
|
|
92
|
+
label: criterion.capitalize,
|
|
111
93
|
covered: stat.covered,
|
|
112
94
|
total: stat.total,
|
|
113
|
-
percent:
|
|
95
|
+
percent: Color.colorize_percent(percent)
|
|
114
96
|
)
|
|
115
97
|
end
|
|
116
98
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../atomic_file"
|
|
5
|
+
|
|
6
|
+
module SimpleCov
|
|
7
|
+
module Formatter
|
|
8
|
+
# Auto-ratchets the per-file coverage baseline as a formatter, for teams
|
|
9
|
+
# that want floors to tighten on every run instead of by deliberate
|
|
10
|
+
# `simplecov ratchet` invocations. The semantics are exactly the CLI's, and
|
|
11
|
+
# the file is rewritten only when a floor actually moved.
|
|
12
|
+
#
|
|
13
|
+
# The exit checks still judge the run against the floors as they were when
|
|
14
|
+
# it started (the baseline is read once per path), and ratcheting never
|
|
15
|
+
# loosens a floor, so tightening before the checks cannot flip a failing
|
|
16
|
+
# file to passing.
|
|
17
|
+
class BaselineFormatter < Base
|
|
18
|
+
def format(result)
|
|
19
|
+
emit_status(ratchet(result))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def ratchet(result)
|
|
25
|
+
current = current_floors(result)
|
|
26
|
+
existing = SimpleCov.baseline
|
|
27
|
+
return generate(current) unless existing
|
|
28
|
+
|
|
29
|
+
outcome = existing.ratchet(current)
|
|
30
|
+
changed = outcome.tightened.any? || outcome.pruned.any?
|
|
31
|
+
write(outcome.baseline) if changed
|
|
32
|
+
note = summary(outcome, changed)
|
|
33
|
+
regressed = outcome.regressed
|
|
34
|
+
regressed.empty? ? note : "#{note}#{below_floors(regressed)}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def generate(current)
|
|
38
|
+
baseline = Baseline.generate(current)
|
|
39
|
+
write(baseline)
|
|
40
|
+
files = baseline.entries.size
|
|
41
|
+
"Coverage baseline generated to #{displayable_output_path} (#{files} #{files.eql?(1) ? "file" : "files"})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def summary(outcome, changed)
|
|
45
|
+
return "Coverage baseline unchanged at #{displayable_output_path}" unless changed
|
|
46
|
+
|
|
47
|
+
"Coverage baseline ratcheted to #{displayable_output_path} " \
|
|
48
|
+
"(#{outcome.tightened.size} tightened, #{outcome.pruned.size} pruned)"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def below_floors(regressed)
|
|
52
|
+
noun = regressed.size.eql?(1) ? "1 file below its floor" : "#{regressed.size} files below their floors"
|
|
53
|
+
", #{noun}: #{regressed.join(", ")}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# `SourceFile#coverage_statistics` answers for disabled criteria too, as
|
|
57
|
+
# empty statistics, so the criteria are gated on the configuration rather
|
|
58
|
+
# than on the stats' presence.
|
|
59
|
+
def current_floors(result)
|
|
60
|
+
criteria = measured_criteria
|
|
61
|
+
floors = {} #: Baseline::current_floors
|
|
62
|
+
result.files.each do |file|
|
|
63
|
+
floors[file.project_filename] = criteria.to_h { |criterion| [criterion, floor_of(file, criterion)] }
|
|
64
|
+
end
|
|
65
|
+
floors
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def measured_criteria
|
|
69
|
+
criteria = [] #: Array[Symbol]
|
|
70
|
+
criteria << :line if SimpleCov.line_coverage?
|
|
71
|
+
criteria << :branch if SimpleCov.branch_coverage?
|
|
72
|
+
criteria << :method if SimpleCov.method_coverage?
|
|
73
|
+
criteria
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def floor_of(file, criterion)
|
|
77
|
+
stats = file.coverage_statistics(criterion)
|
|
78
|
+
{percent: SimpleCov.round_coverage(stats.percent), missed: stats.missed}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def write(baseline)
|
|
82
|
+
AtomicFile.write(output_path, baseline.to_yaml)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The baseline lives where the exit check reads it, not under the coverage
|
|
86
|
+
# directory: it is checked-in policy, not a report artifact.
|
|
87
|
+
def output_path
|
|
88
|
+
File.expand_path(SimpleCov.baseline_file, SimpleCov.root)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# `format` hands the ratchet's summary to `emit_status`, which passes it
|
|
92
|
+
# through to here in the result's place.
|
|
93
|
+
def output_message(message)
|
|
94
|
+
message
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|