simplecov 1.0.2 → 1.1.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 +65 -1425
- data/lib/simplecov/atomic_file.rb +70 -0
- data/lib/simplecov/cli/clean.rb +43 -3
- data/lib/simplecov/cli/command_helpers.rb +55 -0
- data/lib/simplecov/cli/coverage.rb +21 -28
- data/lib/simplecov/cli/coverage_file.rb +65 -0
- data/lib/simplecov/cli/diff.rb +43 -48
- data/lib/simplecov/cli/dotfile.rb +12 -6
- data/lib/simplecov/cli/merge.rb +12 -10
- data/lib/simplecov/cli/open.rb +3 -5
- data/lib/simplecov/cli/report.rb +31 -23
- data/lib/simplecov/cli/serve/report_preparer.rb +29 -0
- data/lib/simplecov/cli/serve/static_file_handler.rb +122 -0
- data/lib/simplecov/cli/serve.rb +30 -94
- data/lib/simplecov/cli/uncovered.rb +20 -25
- data/lib/simplecov/cli.rb +15 -2
- data/lib/simplecov/combine/branches_combiner.rb +43 -19
- data/lib/simplecov/combine/coverage_accumulator.rb +268 -0
- data/lib/simplecov/combine/identity_interner.rb +30 -0
- data/lib/simplecov/combine/interned_counts.rb +30 -0
- data/lib/simplecov/combine/lines_combiner.rb +48 -20
- data/lib/simplecov/combine/methods_combiner.rb +46 -21
- data/lib/simplecov/combine/results_combiner.rb +12 -37
- data/lib/simplecov/combine.rb +5 -23
- data/lib/simplecov/command_guesser.rb +66 -9
- data/lib/simplecov/configuration/coverage.rb +12 -15
- data/lib/simplecov/configuration/coverage_criteria.rb +34 -38
- data/lib/simplecov/configuration/eval_coverage.rb +41 -0
- data/lib/simplecov/configuration/filters.rb +11 -44
- data/lib/simplecov/configuration/formatting.rb +18 -9
- data/lib/simplecov/configuration/groups.rb +42 -0
- data/lib/simplecov/configuration/merging.rb +8 -7
- data/lib/simplecov/configuration/thresholds.rb +14 -13
- data/lib/simplecov/configuration.rb +13 -43
- data/lib/simplecov/coverage_json.rb +24 -0
- data/lib/simplecov/coverage_statistics.rb +1 -1
- data/lib/simplecov/coverage_violations.rb +15 -5
- data/lib/simplecov/defaults.rb +7 -3
- data/lib/simplecov/directive.rb +1 -1
- data/lib/simplecov/exit_codes/check.rb +33 -0
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +11 -20
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +3 -16
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +14 -23
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +14 -25
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +4 -17
- data/lib/simplecov/exit_codes.rb +1 -0
- data/lib/simplecov/exit_handling.rb +11 -41
- data/lib/simplecov/file_list.rb +5 -10
- data/lib/simplecov/filter.rb +43 -9
- data/lib/simplecov/formatter/base.rb +11 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +97 -0
- data/lib/simplecov/formatter/html_formatter/public/index.html +35 -5
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +96 -0
- data/lib/simplecov/formatter/html_formatter.rb +67 -47
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +54 -56
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +78 -88
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +70 -76
- data/lib/simplecov/formatter/json_formatter.rb +5 -48
- data/lib/simplecov/formatter/multi_formatter.rb +1 -1
- data/lib/simplecov/formatter/simple_formatter.rb +8 -5
- data/lib/simplecov/formatter.rb +12 -0
- data/lib/simplecov/group_names.rb +32 -0
- data/lib/simplecov/last_run.rb +16 -9
- data/lib/simplecov/lines_classifier.rb +29 -8
- data/lib/simplecov/load_global_config.rb +5 -2
- data/lib/simplecov/parallel_adapters/base.rb +17 -0
- data/lib/simplecov/parallel_adapters/generic.rb +2 -2
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +2 -2
- data/lib/simplecov/parallel_coordination.rb +6 -1
- data/lib/simplecov/parallel_result_merger.rb +230 -0
- data/lib/simplecov/report_deferral.rb +49 -0
- data/lib/simplecov/report_stamp.rb +28 -0
- data/lib/simplecov/result.rb +40 -10
- data/lib/simplecov/result_adapter.rb +48 -16
- data/lib/simplecov/result_merger/resultset_file.rb +43 -7
- data/lib/simplecov/result_merger/resultset_run_identity.rb +67 -0
- data/lib/simplecov/result_merger/resultset_store.rb +15 -12
- data/lib/simplecov/result_merger/unloaded_files.rb +103 -0
- data/lib/simplecov/result_merger.rb +69 -42
- data/lib/simplecov/result_processing.rb +82 -43
- data/lib/simplecov/run_identity.rb +77 -0
- data/lib/simplecov/simulate_coverage.rb +35 -11
- data/lib/simplecov/source_file/method.rb +7 -1
- data/lib/simplecov/source_file/ruby_data_parser.rb +25 -3
- data/lib/simplecov/source_file/skip_chunks.rb +7 -10
- data/lib/simplecov/source_file/source_loader.rb +23 -7
- data/lib/simplecov/source_file/statistics.rb +24 -16
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +203 -13
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +19 -30
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +7 -0
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +55 -0
- data/lib/simplecov/static_coverage_extractor/value_position.rb +6 -14
- data/lib/simplecov/static_coverage_extractor/visitor.rb +23 -36
- data/lib/simplecov/unloaded_file_injector.rb +75 -0
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov.rb +11 -5
- data/schemas/coverage-v1.0.schema.json +2 -2
- data/schemas/coverage.schema.json +2 -2
- data/sig/simplecov.rbs +206 -67
- metadata +28 -14
- data/doc/alternate-formatters.md +0 -66
- data/doc/commercial-services.md +0 -25
- data/doc/editor-integration.md +0 -18
- data/lib/simplecov/combine/files_combiner.rb +0 -70
- data/lib/simplecov/formatter/html_formatter/public/application.css +0 -1
- data/lib/simplecov/formatter/html_formatter/public/application.js +0 -18
- 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
|
@@ -77,27 +77,18 @@ module SimpleCov
|
|
|
77
77
|
|
|
78
78
|
#
|
|
79
79
|
# Allows you to configure simplecov in a block instead of
|
|
80
|
-
# prepending SimpleCov to each config method.
|
|
80
|
+
# prepending SimpleCov to each config method. Parameterized blocks retain
|
|
81
|
+
# their caller context and receive this configuration target explicitly.
|
|
81
82
|
#
|
|
82
83
|
def configure(&block)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return instance_exec(&(_ = block)) if equal?(block_context)
|
|
92
|
-
|
|
93
|
-
# Copy the caller's instance variables in so that references like @filter
|
|
94
|
-
# inside the block resolve to the caller's values, not ours.
|
|
95
|
-
saved = swap_ivars_from(block_context)
|
|
96
|
-
instance_exec(&(_ = block))
|
|
97
|
-
ensure
|
|
98
|
-
# @type var block_context: untyped
|
|
99
|
-
# @type var saved: Hash[Symbol, untyped]?
|
|
100
|
-
restore_ivars(block_context, saved) if saved
|
|
84
|
+
raise ArgumentError, "configuration block required" unless block
|
|
85
|
+
|
|
86
|
+
if block.parameters.empty?
|
|
87
|
+
instance_exec(&(_ = block))
|
|
88
|
+
else
|
|
89
|
+
yield self
|
|
90
|
+
end
|
|
91
|
+
self
|
|
101
92
|
end
|
|
102
93
|
|
|
103
94
|
#
|
|
@@ -113,7 +104,7 @@ module SimpleCov
|
|
|
113
104
|
|
|
114
105
|
@at_exit = proc do
|
|
115
106
|
result = SimpleCov.result
|
|
116
|
-
result.format! if result && SimpleCov.merge_finalization_owner?
|
|
107
|
+
result.format! if result && SimpleCov.merge_finalization_owner?
|
|
117
108
|
end
|
|
118
109
|
end
|
|
119
110
|
|
|
@@ -158,35 +149,14 @@ module SimpleCov
|
|
|
158
149
|
@project_name = new_name if new_name.is_a?(String)
|
|
159
150
|
@project_name ||= File.basename(root).capitalize.tr("_", " ")
|
|
160
151
|
end
|
|
161
|
-
|
|
162
|
-
private
|
|
163
|
-
|
|
164
|
-
# Copy instance variables from block_context into self, saving any
|
|
165
|
-
# of ours that would be clobbered. Returns the saved values for
|
|
166
|
-
# later restoration.
|
|
167
|
-
def swap_ivars_from(block_context)
|
|
168
|
-
saved = {} # : Hash[Symbol, untyped]
|
|
169
|
-
our_ivars = instance_variables
|
|
170
|
-
block_context.instance_variables.each do |ivar|
|
|
171
|
-
saved[ivar] = instance_variable_get(ivar) if our_ivars.include?(ivar)
|
|
172
|
-
instance_variable_set(ivar, block_context.instance_variable_get(ivar))
|
|
173
|
-
end
|
|
174
|
-
saved
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Copy instance variables back to block_context and restore our saved values.
|
|
178
|
-
def restore_ivars(block_context, saved)
|
|
179
|
-
block_context.instance_variables.each do |ivar|
|
|
180
|
-
block_context.instance_variable_set(ivar, instance_variable_get(ivar))
|
|
181
|
-
end
|
|
182
|
-
saved.each { |ivar, value| instance_variable_set(ivar, value) }
|
|
183
|
-
end
|
|
184
152
|
end
|
|
185
153
|
end
|
|
186
154
|
|
|
187
155
|
require_relative "configuration/coverage"
|
|
188
156
|
require_relative "configuration/coverage_criteria"
|
|
157
|
+
require_relative "configuration/eval_coverage"
|
|
189
158
|
require_relative "configuration/filters"
|
|
159
|
+
require_relative "configuration/groups"
|
|
190
160
|
require_relative "configuration/formatting"
|
|
191
161
|
require_relative "configuration/ignored_entries"
|
|
192
162
|
require_relative "configuration/merging"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module SimpleCov
|
|
6
|
+
# Shared parser for coverage.json consumers. It enforces the encoding and
|
|
7
|
+
# stable outermost shape while leaving command- or viewer-specific fields to
|
|
8
|
+
# their respective callers.
|
|
9
|
+
module CoverageJSON
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
def self.load(path)
|
|
13
|
+
source = File.binread(path).force_encoding(Encoding::UTF_8)
|
|
14
|
+
raise Error, "input is not valid UTF-8" unless source.valid_encoding?
|
|
15
|
+
|
|
16
|
+
document = JSON.parse(source)
|
|
17
|
+
return document if document.is_a?(Hash)
|
|
18
|
+
|
|
19
|
+
raise Error, "top-level value must be an object"
|
|
20
|
+
rescue JSON::ParserError => e
|
|
21
|
+
raise Error, e.message
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -10,7 +10,7 @@ module SimpleCov
|
|
|
10
10
|
# * missed - how many of the coverables are missed
|
|
11
11
|
# * omitted - how many lines cannot be covered (blank lines/comments); only meaningful for line coverage
|
|
12
12
|
# * percent - percentage as covered/missed
|
|
13
|
-
# * strength - average hits per
|
|
13
|
+
# * strength - average hits per coverable unit (will not exist for one shot lines format)
|
|
14
14
|
class CoverageStatistics
|
|
15
15
|
attr_reader :total, :covered, :missed, :omitted, :strength, :percent
|
|
16
16
|
|
|
@@ -122,11 +122,15 @@ module SimpleCov
|
|
|
122
122
|
end
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
+
# The misconfiguration notice is enforcement output, not a Ruby
|
|
126
|
+
# warning: like every other enforcement message it must survive
|
|
127
|
+
# `-W0` and `Warning.warn` hooks (see ExitCodes.print_error) and
|
|
128
|
+
# honor the `print_errors` opt-out.
|
|
125
129
|
def lookup_group(result, group_name)
|
|
126
130
|
group = result.groups[group_name]
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
131
|
+
if group.nil? && SimpleCov.print_errors
|
|
132
|
+
ExitCodes.print_error "minimum_coverage_by_group: no group named '#{group_name}' exists. " \
|
|
133
|
+
"Available groups: #{result.groups.keys.join(', ')}"
|
|
130
134
|
end
|
|
131
135
|
group
|
|
132
136
|
end
|
|
@@ -134,8 +138,14 @@ module SimpleCov
|
|
|
134
138
|
def compute_drop(criterion, result, last_run)
|
|
135
139
|
stats_key = SimpleCov.coverage_statistics_key(criterion)
|
|
136
140
|
last_coverage_percent = last_run.dig(:result, stats_key)
|
|
137
|
-
last_coverage_percent
|
|
138
|
-
|
|
141
|
+
if last_coverage_percent.nil? && stats_key == :line
|
|
142
|
+
last_coverage_percent = last_run.dig(:result, :covered_percent)
|
|
143
|
+
end
|
|
144
|
+
# A hand-edited .last_run.json can carry any value type, and
|
|
145
|
+
# LastRun.read only vouches for the top level being a Hash.
|
|
146
|
+
# Treat a non-numeric percent like a missing one instead of
|
|
147
|
+
# raising out of the at_exit hook.
|
|
148
|
+
return unless last_coverage_percent.is_a?(Numeric)
|
|
139
149
|
|
|
140
150
|
current = percent_for(result, criterion) or return
|
|
141
151
|
(last_coverage_percent - current).floor(10)
|
data/lib/simplecov/defaults.rb
CHANGED
|
@@ -24,8 +24,11 @@ SimpleCov.configure do
|
|
|
24
24
|
load_profile "test_frameworks"
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
# Gotta stash this a-s-a-p, see the CommandGuesser class and i.e. #110 for further info
|
|
27
|
+
# Gotta stash this a-s-a-p, see the CommandGuesser class and i.e. #110 for further info.
|
|
28
|
+
# The program name is kept separately as well: the join below is lossy for a
|
|
29
|
+
# path containing a space, and the guesser reads the executable from it.
|
|
28
30
|
SimpleCov::CommandGuesser.original_run_command = "#{$PROGRAM_NAME} #{ARGV.join(' ')}"
|
|
31
|
+
SimpleCov::CommandGuesser.original_program_name = $PROGRAM_NAME
|
|
29
32
|
|
|
30
33
|
# Autoload config from ~/.simplecov if present
|
|
31
34
|
require_relative "load_global_config"
|
|
@@ -38,8 +41,9 @@ loop do
|
|
|
38
41
|
filename = config_path.join(".simplecov")
|
|
39
42
|
if filename.exist?
|
|
40
43
|
# `.simplecov` is a configuration file; SimpleCov.start calls inside
|
|
41
|
-
# it
|
|
42
|
-
#
|
|
44
|
+
# it draw a deprecation warning, and tracking still begins for
|
|
45
|
+
# backward compatibility. See `SimpleCov.with_dot_simplecov_autoload`
|
|
46
|
+
# and issue #581.
|
|
43
47
|
SimpleCov.with_dot_simplecov_autoload do
|
|
44
48
|
load filename.to_s
|
|
45
49
|
rescue LoadError, StandardError => e
|
data/lib/simplecov/directive.rb
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ExitCodes
|
|
5
|
+
# Shared skeleton for the threshold checks: stash the result and the
|
|
6
|
+
# threshold configuration, fail when any violation exists, and
|
|
7
|
+
# report one message per violation. Subclasses supply `exit_code`,
|
|
8
|
+
# `compute_violations` (memoized here — `failing?` and `report` both
|
|
9
|
+
# consult it), and `report_violation`.
|
|
10
|
+
class Check
|
|
11
|
+
def initialize(result, thresholds)
|
|
12
|
+
@result = result
|
|
13
|
+
@thresholds = thresholds
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def failing?
|
|
17
|
+
violations.any?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def report
|
|
21
|
+
violations.each { |violation| report_violation(violation) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
attr_reader :result, :thresholds
|
|
27
|
+
|
|
28
|
+
def violations
|
|
29
|
+
@violations ||= compute_violations
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -4,29 +4,24 @@ module SimpleCov
|
|
|
4
4
|
module ExitCodes
|
|
5
5
|
# Fails when any coverage criterion has dropped by more than the
|
|
6
6
|
# configured maximum since the last recorded run.
|
|
7
|
-
class MaximumCoverageDropCheck
|
|
8
|
-
def initialize(result, maximum_coverage_drop)
|
|
9
|
-
@result = result
|
|
10
|
-
@maximum_coverage_drop = maximum_coverage_drop
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def failing?
|
|
14
|
-
violations.any?
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def report
|
|
18
|
-
violations.each { |violation| ExitCodes.print_error SimpleCov::Color.colorize(message_for(violation), :red) }
|
|
19
|
-
end
|
|
20
|
-
|
|
7
|
+
class MaximumCoverageDropCheck < Check
|
|
21
8
|
def exit_code
|
|
22
9
|
SimpleCov::ExitCodes::MAXIMUM_COVERAGE_DROP
|
|
23
10
|
end
|
|
24
11
|
|
|
25
12
|
private
|
|
26
13
|
|
|
14
|
+
def compute_violations
|
|
15
|
+
SimpleCov::CoverageViolations.maximum_drop(result, thresholds)
|
|
16
|
+
end
|
|
17
|
+
|
|
27
18
|
# The "drop percent" is a delta, not a coverage level — it has no
|
|
28
|
-
# natural green/yellow/red mapping
|
|
29
|
-
#
|
|
19
|
+
# natural green/yellow/red mapping, so the whole line goes red to
|
|
20
|
+
# keep the failure visible at a glance.
|
|
21
|
+
def report_violation(violation)
|
|
22
|
+
ExitCodes.print_error SimpleCov::Color.colorize(message_for(violation), :red)
|
|
23
|
+
end
|
|
24
|
+
|
|
30
25
|
def message_for(violation)
|
|
31
26
|
format(
|
|
32
27
|
"%<criterion>s coverage has dropped by %<drop_percent>.2f%% since the last time " \
|
|
@@ -36,10 +31,6 @@ module SimpleCov
|
|
|
36
31
|
max_drop: violation.fetch(:maximum)
|
|
37
32
|
)
|
|
38
33
|
end
|
|
39
|
-
|
|
40
|
-
def violations
|
|
41
|
-
@violations ||= SimpleCov::CoverageViolations.maximum_drop(@result, @maximum_coverage_drop)
|
|
42
|
-
end
|
|
43
34
|
end
|
|
44
35
|
end
|
|
45
36
|
end
|
|
@@ -7,28 +7,15 @@ module SimpleCov
|
|
|
7
7
|
# `SimpleCov::ExitCodes::MinimumOverallCoverageCheck` (or use
|
|
8
8
|
# `SimpleCov.expected_coverage`) to pin coverage to an exact value
|
|
9
9
|
# and surface unexpected increases instead of silently absorbing them.
|
|
10
|
-
class MaximumOverallCoverageCheck
|
|
11
|
-
def initialize(result, maximum_coverage)
|
|
12
|
-
@result = result
|
|
13
|
-
@maximum_coverage = maximum_coverage
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def failing?
|
|
17
|
-
violations.any?
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def report
|
|
21
|
-
violations.each { |violation| report_violation(violation) }
|
|
22
|
-
end
|
|
23
|
-
|
|
10
|
+
class MaximumOverallCoverageCheck < Check
|
|
24
11
|
def exit_code
|
|
25
12
|
SimpleCov::ExitCodes::MAXIMUM_COVERAGE
|
|
26
13
|
end
|
|
27
14
|
|
|
28
15
|
private
|
|
29
16
|
|
|
30
|
-
def
|
|
31
|
-
|
|
17
|
+
def compute_violations
|
|
18
|
+
SimpleCov::CoverageViolations.maximum_overall(result, thresholds)
|
|
32
19
|
end
|
|
33
20
|
|
|
34
21
|
def report_violation(violation)
|
|
@@ -4,39 +4,30 @@ module SimpleCov
|
|
|
4
4
|
module ExitCodes
|
|
5
5
|
# Fails when any individual file falls below the configured minimum
|
|
6
6
|
# coverage for any criterion.
|
|
7
|
-
class MinimumCoverageByFileCheck
|
|
7
|
+
class MinimumCoverageByFileCheck < Check
|
|
8
8
|
def initialize(result, minimum_coverage_by_file, overrides = {})
|
|
9
|
-
|
|
10
|
-
@minimum_coverage_by_file = minimum_coverage_by_file
|
|
9
|
+
super(result, minimum_coverage_by_file)
|
|
11
10
|
@overrides = overrides
|
|
12
11
|
end
|
|
13
12
|
|
|
14
|
-
def failing?
|
|
15
|
-
violations.any?
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def report
|
|
19
|
-
violations.each do |violation|
|
|
20
|
-
ExitCodes.print_error format(
|
|
21
|
-
"%<criterion>s coverage by file (%<actual>s) is below the expected minimum coverage " \
|
|
22
|
-
"(%<expected>.2f%%) in %<filename>s.",
|
|
23
|
-
criterion: violation.fetch(:criterion).capitalize,
|
|
24
|
-
actual: SimpleCov::Color.colorize_percent(violation.fetch(:actual)),
|
|
25
|
-
expected: violation.fetch(:expected),
|
|
26
|
-
filename: violation.fetch(:project_filename)
|
|
27
|
-
)
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
13
|
def exit_code
|
|
32
14
|
SimpleCov::ExitCodes::MINIMUM_COVERAGE
|
|
33
15
|
end
|
|
34
16
|
|
|
35
17
|
private
|
|
36
18
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
def compute_violations
|
|
20
|
+
SimpleCov::CoverageViolations.minimum_by_file(result, thresholds, @overrides)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def report_violation(violation)
|
|
24
|
+
ExitCodes.print_error format(
|
|
25
|
+
"%<criterion>s coverage by file (%<actual>s) is below the expected minimum coverage " \
|
|
26
|
+
"(%<expected>.2f%%) in %<filename>s.",
|
|
27
|
+
criterion: violation.fetch(:criterion).capitalize,
|
|
28
|
+
actual: SimpleCov::Color.colorize_percent(violation.fetch(:actual)),
|
|
29
|
+
expected: violation.fetch(:expected),
|
|
30
|
+
filename: violation.fetch(:project_filename)
|
|
40
31
|
)
|
|
41
32
|
end
|
|
42
33
|
end
|
|
@@ -4,37 +4,26 @@ module SimpleCov
|
|
|
4
4
|
module ExitCodes
|
|
5
5
|
# Fails when any configured group falls below its minimum coverage
|
|
6
6
|
# threshold for any criterion.
|
|
7
|
-
class MinimumCoverageByGroupCheck
|
|
8
|
-
def initialize(result, minimum_coverage_by_group)
|
|
9
|
-
@result = result
|
|
10
|
-
@minimum_coverage_by_group = minimum_coverage_by_group
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def failing?
|
|
14
|
-
violations.any?
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def report
|
|
18
|
-
violations.each do |violation|
|
|
19
|
-
ExitCodes.print_error format(
|
|
20
|
-
"%<criterion>s coverage by group (%<actual>s) is below the expected minimum coverage " \
|
|
21
|
-
"(%<expected>.2f%%) in %<group_name>s.",
|
|
22
|
-
criterion: violation.fetch(:criterion).capitalize,
|
|
23
|
-
actual: SimpleCov::Color.colorize_percent(violation.fetch(:actual)),
|
|
24
|
-
expected: violation.fetch(:expected),
|
|
25
|
-
group_name: violation.fetch(:group_name)
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
7
|
+
class MinimumCoverageByGroupCheck < Check
|
|
30
8
|
def exit_code
|
|
31
9
|
SimpleCov::ExitCodes::MINIMUM_COVERAGE
|
|
32
10
|
end
|
|
33
11
|
|
|
34
12
|
private
|
|
35
13
|
|
|
36
|
-
def
|
|
37
|
-
|
|
14
|
+
def compute_violations
|
|
15
|
+
SimpleCov::CoverageViolations.minimum_by_group(result, thresholds)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def report_violation(violation)
|
|
19
|
+
ExitCodes.print_error format(
|
|
20
|
+
"%<criterion>s coverage by group (%<actual>s) is below the expected minimum coverage " \
|
|
21
|
+
"(%<expected>.2f%%) in %<group_name>s.",
|
|
22
|
+
criterion: violation.fetch(:criterion).capitalize,
|
|
23
|
+
actual: SimpleCov::Color.colorize_percent(violation.fetch(:actual)),
|
|
24
|
+
expected: violation.fetch(:expected),
|
|
25
|
+
group_name: violation.fetch(:group_name)
|
|
26
|
+
)
|
|
38
27
|
end
|
|
39
28
|
end
|
|
40
29
|
end
|
|
@@ -4,20 +4,7 @@ module SimpleCov
|
|
|
4
4
|
module ExitCodes
|
|
5
5
|
# Fails when the overall (project-wide) coverage for any criterion is
|
|
6
6
|
# below the configured minimum.
|
|
7
|
-
class MinimumOverallCoverageCheck
|
|
8
|
-
def initialize(result, minimum_coverage)
|
|
9
|
-
@result = result
|
|
10
|
-
@minimum_coverage = minimum_coverage
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def failing?
|
|
14
|
-
violations.any?
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def report
|
|
18
|
-
violations.each { |violation| report_violation(violation) }
|
|
19
|
-
end
|
|
20
|
-
|
|
7
|
+
class MinimumOverallCoverageCheck < Check
|
|
21
8
|
def exit_code
|
|
22
9
|
SimpleCov::ExitCodes::MINIMUM_COVERAGE
|
|
23
10
|
end
|
|
@@ -27,8 +14,8 @@ module SimpleCov
|
|
|
27
14
|
WORST_FILES_LIMIT = 5
|
|
28
15
|
private_constant :WORST_FILES_LIMIT
|
|
29
16
|
|
|
30
|
-
def
|
|
31
|
-
|
|
17
|
+
def compute_violations
|
|
18
|
+
SimpleCov::CoverageViolations.minimum_overall(result, thresholds)
|
|
32
19
|
end
|
|
33
20
|
|
|
34
21
|
def report_violation(violation)
|
|
@@ -59,7 +46,7 @@ module SimpleCov
|
|
|
59
46
|
|
|
60
47
|
def worst_files_for(criterion)
|
|
61
48
|
stats_key = SimpleCov.coverage_statistics_key(criterion)
|
|
62
|
-
with_stats =
|
|
49
|
+
with_stats = result.files.filter_map do |source_file|
|
|
63
50
|
stats = source_file.coverage_statistics[stats_key]
|
|
64
51
|
[source_file.project_filename, stats.percent] if stats
|
|
65
52
|
end
|
data/lib/simplecov/exit_codes.rb
CHANGED
|
@@ -25,50 +25,20 @@ module SimpleCov
|
|
|
25
25
|
# or a test consumed the result) then don't run exit tasks.
|
|
26
26
|
return unless Coverage.running?
|
|
27
27
|
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
SimpleCov.run_exit_tasks!
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Returns true when our process has no coverage data to contribute
|
|
36
|
-
# (after the resultset merge) and a newer report already exists on
|
|
37
|
-
# disk. Typically fires when `SimpleCov.start` ran in a parent
|
|
38
|
-
# process — e.g. a Rakefile or Rails' `Bundler.require` — that
|
|
39
|
-
# shelled out to the test runner. See issue #581.
|
|
40
|
-
def defer_to_existing_report?
|
|
41
|
-
return false unless existing_report_newer_than_us?
|
|
42
|
-
|
|
43
|
-
res = result
|
|
44
|
-
empty = res.nil? || res.files.empty?
|
|
45
|
-
warn_about_deferred_report if empty
|
|
46
|
-
empty
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def existing_report_newer_than_us?
|
|
50
|
-
return false unless process_start_time
|
|
51
|
-
|
|
52
|
-
last_run_path = File.join(coverage_path, ".last_run.json")
|
|
53
|
-
File.exist?(last_run_path) && File.mtime(last_run_path) > process_start_time
|
|
54
|
-
end
|
|
28
|
+
# Capture the suite's exit status BEFORE the deferral probe: its
|
|
29
|
+
# freshness check rescues filesystem errors, and completing any
|
|
30
|
+
# rescue inside an at_exit handler resets $ERROR_INFO to nil.
|
|
31
|
+
error_exit_status = exit_status_from_exception
|
|
55
32
|
|
|
56
|
-
|
|
57
|
-
return
|
|
33
|
+
# Stand down when we'd only clobber a fresher report (#581).
|
|
34
|
+
return if defer_to_existing_report?
|
|
58
35
|
|
|
59
|
-
|
|
60
|
-
"Skipping SimpleCov report — this process tracked no application code and a newer " \
|
|
61
|
-
"report already exists at #{coverage_path}. This usually means SimpleCov.start ran in a " \
|
|
62
|
-
"parent process (e.g. a Rakefile or Rails' Bundler.require) that shelled out to the test " \
|
|
63
|
-
"runner. See https://github.com/simplecov-ruby/simplecov/issues/581.",
|
|
64
|
-
:yellow
|
|
65
|
-
)
|
|
36
|
+
SimpleCov.run_exit_tasks!(error_exit_status)
|
|
66
37
|
end
|
|
67
38
|
|
|
68
|
-
# @api private — called from the at_exit block
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
39
|
+
# @api private — called from the at_exit block (which pre-captures
|
|
40
|
+
# the status, see there) and by `collate` (the default argument).
|
|
41
|
+
def run_exit_tasks!(error_exit_status = exit_status_from_exception)
|
|
72
42
|
at_exit.call
|
|
73
43
|
|
|
74
44
|
exit_and_report_previous_error(error_exit_status) if previous_error?(error_exit_status)
|
|
@@ -113,7 +83,7 @@ module SimpleCov
|
|
|
113
83
|
# would surface a spurious "below minimum" violation about the
|
|
114
84
|
# missing slice rather than a real shortfall.
|
|
115
85
|
def ready_to_process_results?
|
|
116
|
-
merge_finalization_owner? &&
|
|
86
|
+
merge_finalization_owner? && result? &&
|
|
117
87
|
(collating_result? || parallel_results_complete?)
|
|
118
88
|
end
|
|
119
89
|
|
data/lib/simplecov/file_list.rb
CHANGED
|
@@ -47,15 +47,11 @@ module SimpleCov
|
|
|
47
47
|
|
|
48
48
|
# Returns the count of lines that are not relevant for coverage
|
|
49
49
|
def never_lines
|
|
50
|
-
return 0.0 if empty?
|
|
51
|
-
|
|
52
50
|
sum { |f| f.never_lines.size }
|
|
53
51
|
end
|
|
54
52
|
|
|
55
53
|
# Returns the count of skipped lines
|
|
56
54
|
def skipped_lines
|
|
57
|
-
return 0.0 if empty?
|
|
58
|
-
|
|
59
55
|
sum { |f| f.skipped_lines.size }
|
|
60
56
|
end
|
|
61
57
|
|
|
@@ -65,13 +61,13 @@ module SimpleCov
|
|
|
65
61
|
map(&:covered_percent)
|
|
66
62
|
end
|
|
67
63
|
|
|
68
|
-
# Finds the least covered file and returns that file's name
|
|
64
|
+
# Finds the least covered file and returns that file's name, or nil
|
|
65
|
+
# for an empty list (e.g. a fully filtered result).
|
|
69
66
|
def least_covered_file
|
|
70
67
|
# `covered_percent` is nil only for an unmeasured criterion, and :line
|
|
71
68
|
# is always measured, so the `|| 0.0` arm never fires at runtime; it
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
(_ = least_covered).filename
|
|
69
|
+
# exists to satisfy min_by's Comparable requirement.
|
|
70
|
+
min_by { |file| file.covered_percent || 0.0 }&.filename
|
|
75
71
|
end
|
|
76
72
|
|
|
77
73
|
# Returns the overall amount of relevant lines of code across all files in this list
|
|
@@ -161,8 +157,7 @@ module SimpleCov
|
|
|
161
157
|
# reported when they pass their own engine-support check.
|
|
162
158
|
def enabled_criteria_for_reporting
|
|
163
159
|
criteria = [] #: Array[SimpleCov::criterion]
|
|
164
|
-
criteria << :line if SimpleCov.
|
|
165
|
-
SimpleCov.coverage_criterion_enabled?(:oneshot_line)
|
|
160
|
+
criteria << :line if SimpleCov.line_coverage?
|
|
166
161
|
criteria << :branch if SimpleCov.branch_coverage?
|
|
167
162
|
criteria << :method if SimpleCov.method_coverage?
|
|
168
163
|
criteria
|