simplecov 1.0.3 → 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 -1439
- 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 +36 -12
- 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 +6 -3
- 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 +5 -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_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 +6 -2
- 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 +200 -66
- 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
|
@@ -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
|
data/lib/simplecov/filter.rb
CHANGED
|
@@ -23,10 +23,26 @@ module SimpleCov
|
|
|
23
23
|
raise NotImplementedError, "The base filter class is not intended for direct use"
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
# Whether this filter's verdict depends only on the file's path, so it can
|
|
27
|
+
# be decided before that file has any coverage. Used when recording which
|
|
28
|
+
# tracked files a process did not load, where no coverage exists yet.
|
|
29
|
+
# Defaults to false so a custom filter is never guessed at. See #1250.
|
|
30
|
+
def path_only?
|
|
31
|
+
false
|
|
32
|
+
end
|
|
28
33
|
|
|
29
|
-
|
|
34
|
+
# `string_filter` selects the semantics of bare String arguments —
|
|
35
|
+
# StringFilter's segment-substring match for `add_filter`/`skip`,
|
|
36
|
+
# GlobFilter for `cover` — and threads through Array elements so a
|
|
37
|
+
# list gets the same treatment as its members.
|
|
38
|
+
def self.build_filter(filter_argument, string_filter: SimpleCov::StringFilter)
|
|
39
|
+
case filter_argument
|
|
40
|
+
when SimpleCov::Filter then filter_argument
|
|
41
|
+
when String then string_filter.new(filter_argument)
|
|
42
|
+
when Array
|
|
43
|
+
SimpleCov::ArrayFilter.new(filter_argument.map { |arg| build_filter(arg, string_filter: string_filter) })
|
|
44
|
+
else class_for_argument(filter_argument).new(filter_argument)
|
|
45
|
+
end
|
|
30
46
|
end
|
|
31
47
|
|
|
32
48
|
def self.class_for_argument(filter_argument)
|
|
@@ -57,6 +73,10 @@ module SimpleCov
|
|
|
57
73
|
source_file.project_filename.match?(segment_pattern)
|
|
58
74
|
end
|
|
59
75
|
|
|
76
|
+
def path_only?
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
|
|
60
80
|
private
|
|
61
81
|
|
|
62
82
|
def segment_pattern
|
|
@@ -68,14 +88,15 @@ module SimpleCov
|
|
|
68
88
|
escaped = Regexp.escape(normalized)
|
|
69
89
|
boundary = '(?:\A|/)'
|
|
70
90
|
|
|
71
|
-
if normalized.
|
|
72
|
-
# Filename pattern (e.g. "test.rb" matches "faked_test.rb"): allow
|
|
73
|
-
# substring match within the last path segment, anchored to a
|
|
74
|
-
# segment boundary.
|
|
75
|
-
%r{#{boundary}[^/]*#{escaped}}
|
|
76
|
-
elsif normalized.end_with?("/")
|
|
91
|
+
if normalized.end_with?("/")
|
|
77
92
|
# Trailing slash signals directory-only matching.
|
|
78
93
|
/#{boundary}#{escaped}/
|
|
94
|
+
elsif normalized.include?(".") && !normalized.include?("/")
|
|
95
|
+
# Bare filename pattern (e.g. "test.rb" matches "faked_test.rb"):
|
|
96
|
+
# allow a substring match within a single path segment. Multi-segment
|
|
97
|
+
# arguments must not get this relaxation, or "app/models/user.rb"
|
|
98
|
+
# would also match "webapp/models/user.rb".
|
|
99
|
+
%r{#{boundary}[^/]*#{escaped}(?=[/.]|\z)}
|
|
79
100
|
else
|
|
80
101
|
# Directory or path: require a segment-boundary match so "lib"
|
|
81
102
|
# matches "lib/" but not "library/".
|
|
@@ -95,6 +116,10 @@ module SimpleCov
|
|
|
95
116
|
def matches?(source_file)
|
|
96
117
|
filter_argument.match?(source_file.project_filename)
|
|
97
118
|
end
|
|
119
|
+
|
|
120
|
+
def path_only?
|
|
121
|
+
true
|
|
122
|
+
end
|
|
98
123
|
end
|
|
99
124
|
|
|
100
125
|
# Filter that matches when the configured block returns truthy for the
|
|
@@ -115,6 +140,10 @@ module SimpleCov
|
|
|
115
140
|
def matches?(source_file)
|
|
116
141
|
File.fnmatch?(filter_argument, source_file.project_filename, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
|
117
142
|
end
|
|
143
|
+
|
|
144
|
+
def path_only?
|
|
145
|
+
true
|
|
146
|
+
end
|
|
118
147
|
end
|
|
119
148
|
|
|
120
149
|
# Filter that matches when any of its component filters (built from the
|
|
@@ -128,6 +157,11 @@ module SimpleCov
|
|
|
128
157
|
super(filter_objects)
|
|
129
158
|
end
|
|
130
159
|
|
|
160
|
+
# Path-decidable only when every component filter is.
|
|
161
|
+
def path_only?
|
|
162
|
+
filter_argument.all?(&:path_only?)
|
|
163
|
+
end
|
|
164
|
+
|
|
131
165
|
# Returns true if any of the filters in the array match the given source file.
|
|
132
166
|
# Configure this Filter like StringFilter.new(['some/path', /^some_regex/, Proc.new {|src_file| ... }])
|
|
133
167
|
def matches?(source_files_list)
|
|
@@ -23,6 +23,17 @@ module SimpleCov
|
|
|
23
23
|
|
|
24
24
|
private
|
|
25
25
|
|
|
26
|
+
# The one home of the "status lines go to stderr, not through warn"
|
|
27
|
+
# decision (see #1225). stderr rather than stdout because this is a
|
|
28
|
+
# status message, not the program's output, so it stays out of
|
|
29
|
+
# pipelines like `rspec -f json`. And `$stderr.puts` rather than
|
|
30
|
+
# `warn` so the line neither reaches `Warning.warn` hooks (warning
|
|
31
|
+
# trackers, raise-on-warning test setups) nor vanishes under `-W0`.
|
|
32
|
+
# Subclasses call this at the end of their `format`.
|
|
33
|
+
def emit_status(result)
|
|
34
|
+
$stderr.puts output_message(result) unless @silent # rubocop:disable Style/StderrPuts
|
|
35
|
+
end
|
|
36
|
+
|
|
26
37
|
# Subclasses override to prepend a marker (e.g. "JSON ") to the
|
|
27
38
|
# summary line. Default empty for the HTML formatter, which has
|
|
28
39
|
# historically been the unmarked default.
|