simplecov 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +165 -57
- data/exe/simplecov +0 -3
- data/lib/minitest/simplecov_plugin.rb +12 -9
- data/lib/simplecov/atomic_file.rb +16 -19
- data/lib/simplecov/autostart.rb +5 -7
- data/lib/simplecov/baseline/parser.rb +58 -0
- data/lib/simplecov/baseline.rb +170 -0
- data/lib/simplecov/cli/affected/changed_files.rb +57 -0
- data/lib/simplecov/cli/affected/selection.rb +117 -0
- data/lib/simplecov/cli/affected.rb +148 -0
- data/lib/simplecov/cli/badge/svg.rb +70 -0
- data/lib/simplecov/cli/badge.rb +62 -0
- data/lib/simplecov/cli/clean.rb +23 -24
- data/lib/simplecov/cli/command_helpers.rb +55 -19
- data/lib/simplecov/cli/completions/scripts.rb +114 -0
- data/lib/simplecov/cli/completions.rb +83 -0
- data/lib/simplecov/cli/coverage.rb +25 -28
- data/lib/simplecov/cli/coverage_file.rb +58 -14
- data/lib/simplecov/cli/dead_code/output.rb +100 -0
- data/lib/simplecov/cli/dead_code.rb +139 -0
- data/lib/simplecov/cli/diff/output.rb +45 -0
- data/lib/simplecov/cli/diff.rb +35 -67
- data/lib/simplecov/cli/dotfile.rb +83 -38
- data/lib/simplecov/cli/git.rb +41 -0
- data/lib/simplecov/cli/history/output.rb +135 -0
- data/lib/simplecov/cli/history.rb +67 -0
- data/lib/simplecov/cli/merge.rb +34 -39
- data/lib/simplecov/cli/open.rb +12 -15
- data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
- data/lib/simplecov/cli/patch/output.rb +135 -0
- data/lib/simplecov/cli/patch.rb +169 -0
- data/lib/simplecov/cli/ratchet/output.rb +44 -0
- data/lib/simplecov/cli/ratchet.rb +83 -0
- data/lib/simplecov/cli/report.rb +18 -24
- data/lib/simplecov/cli/run.rb +8 -9
- data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
- data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
- data/lib/simplecov/cli/serve.rb +27 -24
- data/lib/simplecov/cli/show/annotator.rb +87 -0
- data/lib/simplecov/cli/show/sweep.rb +36 -0
- data/lib/simplecov/cli/show.rb +133 -0
- data/lib/simplecov/cli/status/facts.rb +87 -0
- data/lib/simplecov/cli/status.rb +93 -0
- data/lib/simplecov/cli/tests/redundancy.rb +78 -0
- data/lib/simplecov/cli/tests.rb +148 -0
- data/lib/simplecov/cli/uncovered/misses.rb +37 -0
- data/lib/simplecov/cli/uncovered.rb +70 -32
- data/lib/simplecov/cli/usage.rb +152 -0
- data/lib/simplecov/cli/watch/live_report.rb +73 -0
- data/lib/simplecov/cli/watch/narrator.rb +55 -0
- data/lib/simplecov/cli/watch/poller.rb +45 -0
- data/lib/simplecov/cli/watch/session.rb +134 -0
- data/lib/simplecov/cli/watch/test_plan.rb +43 -0
- data/lib/simplecov/cli/watch.rb +85 -0
- data/lib/simplecov/cli.rb +43 -92
- data/lib/simplecov/color.rb +21 -35
- data/lib/simplecov/combine/branches_combiner.rb +7 -31
- data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
- data/lib/simplecov/combine/identity_interner.rb +10 -13
- data/lib/simplecov/combine/interned_counts.rb +7 -9
- data/lib/simplecov/combine/lines_combiner.rb +20 -44
- data/lib/simplecov/combine/methods_combiner.rb +9 -35
- data/lib/simplecov/combine/results_combiner.rb +4 -21
- data/lib/simplecov/combine.rb +0 -6
- data/lib/simplecov/command_guesser.rb +20 -34
- data/lib/simplecov/configuration/baseline.rb +31 -0
- data/lib/simplecov/configuration/coverage.rb +91 -79
- data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
- data/lib/simplecov/configuration/deprecations.rb +21 -0
- data/lib/simplecov/configuration/eval_coverage.rb +6 -10
- data/lib/simplecov/configuration/filters.rb +40 -73
- data/lib/simplecov/configuration/formatting.rb +74 -83
- data/lib/simplecov/configuration/groups.rb +19 -13
- data/lib/simplecov/configuration/history.rb +44 -0
- data/lib/simplecov/configuration/ignored_entries.rb +31 -22
- data/lib/simplecov/configuration/merging.rb +94 -62
- data/lib/simplecov/configuration/missed_caps.rb +51 -0
- data/lib/simplecov/configuration/production.rb +23 -0
- data/lib/simplecov/configuration/test_tracking.rb +48 -0
- data/lib/simplecov/configuration/thresholds.rb +33 -62
- data/lib/simplecov/configuration/view_coverage.rb +38 -0
- data/lib/simplecov/configuration.rb +70 -76
- data/lib/simplecov/configuration_error.rb +5 -0
- data/lib/simplecov/context_map/union.rb +81 -0
- data/lib/simplecov/context_map.rb +187 -0
- data/lib/simplecov/coverage_json.rb +2 -5
- data/lib/simplecov/coverage_statistics.rb +14 -24
- data/lib/simplecov/coverage_violations.rb +157 -64
- data/lib/simplecov/current_run.rb +61 -0
- data/lib/simplecov/defaults.rb +4 -23
- data/lib/simplecov/deprecation.rb +45 -27
- data/lib/simplecov/directive.rb +46 -62
- data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
- data/lib/simplecov/exit_codes/check.rb +17 -11
- data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
- data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
- data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +7 -12
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
- data/lib/simplecov/exit_codes.rb +17 -7
- data/lib/simplecov/exit_handling.rb +94 -59
- data/lib/simplecov/file_list.rb +20 -51
- data/lib/simplecov/filter.rb +43 -66
- data/lib/simplecov/formatter/base.rb +38 -51
- data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
- data/lib/simplecov/formatter/html_formatter/public/index.html +17 -10
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
- data/lib/simplecov/formatter/html_formatter.rb +24 -41
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
- data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
- data/lib/simplecov/formatter/json_formatter.rb +7 -13
- data/lib/simplecov/formatter/multi_formatter.rb +5 -11
- data/lib/simplecov/formatter/simple_formatter.rb +2 -8
- data/lib/simplecov/formatter.rb +14 -10
- data/lib/simplecov/group_names.rb +13 -12
- data/lib/simplecov/history.rb +115 -0
- data/lib/simplecov/last_run.rb +6 -9
- data/lib/simplecov/lines_classifier.rb +31 -16
- data/lib/simplecov/load_global_config.rb +7 -7
- data/lib/simplecov/parallel_adapters/base.rb +13 -41
- data/lib/simplecov/parallel_adapters/generic.rb +7 -18
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
- data/lib/simplecov/parallel_adapters.rb +24 -53
- data/lib/simplecov/parallel_coordination.rb +39 -49
- data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
- data/lib/simplecov/parallel_result_merger.rb +67 -126
- data/lib/simplecov/process.rb +14 -32
- data/lib/simplecov/production/error.rb +11 -0
- data/lib/simplecov/production/file_sink.rb +130 -0
- data/lib/simplecov/production.rb +249 -0
- data/lib/simplecov/profiles/rails.rb +1 -12
- data/lib/simplecov/profiles/root_filter.rb +0 -4
- data/lib/simplecov/profiles/strict.rb +9 -19
- data/lib/simplecov/profiles.rb +11 -33
- data/lib/simplecov/report_deferral.rb +11 -15
- data/lib/simplecov/report_stamp.rb +6 -9
- data/lib/simplecov/result/filter_config.rb +19 -0
- data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
- data/lib/simplecov/result/serialization.rb +43 -0
- data/lib/simplecov/result/source_file_builder.rb +7 -12
- data/lib/simplecov/result.rb +68 -117
- data/lib/simplecov/result_adapter.rb +58 -68
- data/lib/simplecov/result_merger/contexts.rb +22 -0
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
- data/lib/simplecov/result_merger/resultset_file.rb +20 -27
- data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
- data/lib/simplecov/result_merger/resultset_store.rb +23 -14
- data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
- data/lib/simplecov/result_merger.rb +59 -58
- data/lib/simplecov/result_processing.rb +74 -119
- data/lib/simplecov/run_identity.rb +16 -18
- data/lib/simplecov/simulate_coverage.rb +35 -50
- data/lib/simplecov/source_file/branch.rb +8 -33
- data/lib/simplecov/source_file/branch_builder.rb +28 -47
- data/lib/simplecov/source_file/builder_context.rb +6 -13
- data/lib/simplecov/source_file/line.rb +13 -33
- data/lib/simplecov/source_file/line_builder.rb +9 -15
- data/lib/simplecov/source_file/method.rb +3 -7
- data/lib/simplecov/source_file/method_builder.rb +6 -15
- data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
- data/lib/simplecov/source_file/skip_chunks.rb +19 -28
- data/lib/simplecov/source_file/source_loader.rb +45 -35
- data/lib/simplecov/source_file/statistics.rb +10 -9
- data/lib/simplecov/source_file.rb +21 -51
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +63 -114
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +11 -24
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
- data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
- data/lib/simplecov/static_coverage_extractor/visitor.rb +47 -64
- data/lib/simplecov/static_coverage_extractor.rb +46 -62
- data/lib/simplecov/test_tracker/accessors.rb +34 -0
- data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
- data/lib/simplecov/test_tracker/delta.rb +69 -0
- data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
- data/lib/simplecov/test_tracker.rb +162 -0
- data/lib/simplecov/unloaded_file_injector.rb +18 -31
- data/lib/simplecov/useless_results_remover.rb +7 -6
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
- data/lib/simplecov/view_coverage.rb +55 -0
- data/lib/simplecov-html.rb +1 -0
- data/lib/simplecov.rb +59 -114
- data/lib/simplecov_json_formatter.rb +1 -0
- data/man/simplecov.1 +349 -0
- data/sig/simplecov.rbs +523 -595
- metadata +72 -7
- data/schemas/coverage-v1.0.schema.json +0 -306
- data/schemas/coverage.schema.json +0 -306
data/lib/simplecov/exit_codes.rb
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
|
-
# Exit statuses SimpleCov sets when coverage checks fail, and the output
|
|
5
|
-
# helper the enforcement machinery reports through.
|
|
6
4
|
module ExitCodes
|
|
7
5
|
SUCCESS = 0
|
|
8
6
|
EXCEPTION = 1
|
|
@@ -10,21 +8,33 @@ module SimpleCov
|
|
|
10
8
|
MAXIMUM_COVERAGE_DROP = 3
|
|
11
9
|
MAXIMUM_COVERAGE = 4
|
|
12
10
|
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
#
|
|
11
|
+
# The plural unit each criterion's misses count in, for the checks that report
|
|
12
|
+
# counts rather than percents.
|
|
13
|
+
UNITS = {line: "lines", branch: "branches", method: "methods"}.freeze
|
|
14
|
+
|
|
15
|
+
# Threshold-violation reports and exit-status notices are the output of the
|
|
16
|
+
# enforcement feature, not Ruby warnings: routing them through `Kernel#warn`
|
|
17
|
+
# made `-W0` swallow the explanation for a failing exit code, let
|
|
18
|
+
# `Warning.warn` hooks intercept them mid-at_exit, and fed colorized text to
|
|
18
19
|
# warning logs. `print_errors false` remains the intended opt-out.
|
|
20
|
+
#
|
|
21
|
+
# A parallel runner can close a worker's stderr before its at_exit hooks
|
|
22
|
+
# run, hence the rescue.
|
|
19
23
|
def self.print_error(message)
|
|
20
24
|
$stderr.puts message # rubocop:disable Style/StderrPuts
|
|
25
|
+
rescue IOError
|
|
26
|
+
# The violation still has to set the exit status even when its
|
|
27
|
+
# explanation cannot be printed.
|
|
21
28
|
end
|
|
22
29
|
end
|
|
23
30
|
end
|
|
24
31
|
|
|
25
32
|
require_relative "exit_codes/check"
|
|
33
|
+
require_relative "exit_codes/baseline_check"
|
|
26
34
|
require_relative "exit_codes/exit_code_handling"
|
|
27
35
|
require_relative "exit_codes/maximum_coverage_drop_check"
|
|
36
|
+
require_relative "exit_codes/maximum_missed_check"
|
|
37
|
+
require_relative "exit_codes/maximum_missed_per_file_check"
|
|
28
38
|
require_relative "exit_codes/maximum_overall_coverage_check"
|
|
29
39
|
require_relative "exit_codes/minimum_coverage_by_file_check"
|
|
30
40
|
require_relative "exit_codes/minimum_coverage_by_group_check"
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
require "English"
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# report, and
|
|
5
|
+
#
|
|
6
|
+
# `at_exit` orchestration: post-suite report generation, threshold checks,
|
|
7
|
+
# deferral when a sibling subprocess already wrote a fresher report, and
|
|
8
|
+
# exit-status propagation.
|
|
9
|
+
#
|
|
8
10
|
module SimpleCov
|
|
9
11
|
class << self
|
|
10
12
|
# @api private
|
|
@@ -14,38 +16,59 @@ module SimpleCov
|
|
|
14
16
|
:minimum_coverage_by_file_overrides,
|
|
15
17
|
:minimum_coverage_by_group,
|
|
16
18
|
:maximum_coverage,
|
|
17
|
-
:maximum_coverage_drop
|
|
19
|
+
:maximum_coverage_drop,
|
|
20
|
+
:maximum_missed,
|
|
21
|
+
:maximum_missed_per_file,
|
|
22
|
+
:maximum_missed_per_file_overrides,
|
|
23
|
+
:baseline
|
|
18
24
|
)
|
|
19
25
|
|
|
20
26
|
def at_exit_behavior
|
|
21
|
-
#
|
|
22
|
-
return
|
|
27
|
+
# A different process than the one that called start; don't interfere.
|
|
28
|
+
return unless started_in_this_process?
|
|
23
29
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
30
|
+
# Coverage is no longer running (someone stopped it manually, or a test
|
|
31
|
+
# consumed the result), so don't run exit tasks.
|
|
26
32
|
return unless Coverage.running?
|
|
27
33
|
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
34
|
+
# Captured BEFORE the deferral probe: its freshness check rescues
|
|
35
|
+
# filesystem errors, and completing any rescue inside an at_exit handler
|
|
36
|
+
# resets $ERROR_INFO to nil.
|
|
31
37
|
error_exit_status = exit_status_from_exception
|
|
32
38
|
|
|
33
39
|
# Stand down when we'd only clobber a fresher report (#581).
|
|
34
40
|
return if defer_to_existing_report?
|
|
35
41
|
|
|
36
|
-
|
|
42
|
+
run_exit_tasks!(error_exit_status)
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
# @api private
|
|
40
|
-
#
|
|
41
|
-
|
|
45
|
+
# @api private -- everything the end of a measured run does, answered as the
|
|
46
|
+
# exit status the process should end with rather than performed as an exit.
|
|
47
|
+
# The one side effect left to the caller is ending the process, which is
|
|
48
|
+
# what makes this whole path answerable from inside a test.
|
|
49
|
+
def run_exit_tasks(error_exit_status = exit_status_from_exception)
|
|
42
50
|
at_exit.call
|
|
43
51
|
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
if previous_error?(error_exit_status)
|
|
53
|
+
report_previous_error
|
|
54
|
+
error_exit_status
|
|
55
|
+
elsif ready_to_process_results?
|
|
56
|
+
report_processing_failure(process_result(result))
|
|
57
|
+
else
|
|
58
|
+
ExitCodes::SUCCESS
|
|
59
|
+
end
|
|
46
60
|
end
|
|
47
61
|
|
|
48
|
-
# @api private
|
|
62
|
+
# @api private -- the exiting adapter over `run_exit_tasks`, called from the
|
|
63
|
+
# at_exit block (which pre-captures the status, see there) and by `collate`.
|
|
64
|
+
def run_exit_tasks!(error_exit_status = exit_status_from_exception)
|
|
65
|
+
status = run_exit_tasks(error_exit_status)
|
|
66
|
+
|
|
67
|
+
# Force exit with stored status (#5).
|
|
68
|
+
Kernel.exit(status) unless successful?(status)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @api private
|
|
49
72
|
def exit_status_from_exception
|
|
50
73
|
@exit_exception = $ERROR_INFO
|
|
51
74
|
return nil unless @exit_exception
|
|
@@ -53,76 +76,88 @@ module SimpleCov
|
|
|
53
76
|
if @exit_exception.is_a?(SystemExit)
|
|
54
77
|
@exit_exception.status
|
|
55
78
|
else
|
|
56
|
-
|
|
79
|
+
ExitCodes::EXCEPTION
|
|
57
80
|
end
|
|
58
81
|
end
|
|
59
82
|
|
|
60
|
-
# @api private
|
|
61
|
-
# accepts it. test_unit sets status 0 on success, so SUCCESS must
|
|
62
|
-
#
|
|
83
|
+
# @api private -- strict boolean so rspec-mocks 4's predicate matcher
|
|
84
|
+
# accepts it. test_unit sets status 0 on success, so SUCCESS must also be
|
|
85
|
+
# treated as "not a previous error".
|
|
63
86
|
def previous_error?(error_exit_status)
|
|
64
|
-
|
|
87
|
+
return false unless error_exit_status
|
|
88
|
+
|
|
89
|
+
!successful?(error_exit_status)
|
|
65
90
|
end
|
|
66
91
|
|
|
67
|
-
# @api private
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
# @api private -- the notice alone; the status it accompanies is the
|
|
93
|
+
# caller's to answer with.
|
|
94
|
+
def report_previous_error
|
|
95
|
+
return unless print_errors
|
|
96
|
+
|
|
97
|
+
ExitCodes.print_error Color.colorize(
|
|
98
|
+
"Stopped processing SimpleCov as a previous error not related to SimpleCov has been detected",
|
|
99
|
+
:yellow
|
|
100
|
+
)
|
|
76
101
|
end
|
|
77
102
|
|
|
78
|
-
# @api private
|
|
79
|
-
#
|
|
80
|
-
# `wait_for_other_processes` confirmed every sibling reported.
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
# would surface a spurious "below minimum" violation about the
|
|
84
|
-
# missing slice rather than a real shortfall.
|
|
103
|
+
# @api private. The process that owns final merge processing is the only one
|
|
104
|
+
# that reports against thresholds, and only when its
|
|
105
|
+
# `wait_for_other_processes` confirmed every sibling reported. When the wait
|
|
106
|
+
# times out the merged total is partial, and comparing it against the
|
|
107
|
+
# thresholds would surface a spurious violation about the missing slice.
|
|
85
108
|
def ready_to_process_results?
|
|
86
109
|
merge_finalization_owner? && result? &&
|
|
87
110
|
(collating_result? || parallel_results_complete?)
|
|
88
111
|
end
|
|
89
112
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if print_errors
|
|
97
|
-
ExitCodes.print_error SimpleCov::Color.colorize(
|
|
113
|
+
# @api private -- answers the status it was handed, explaining it first when
|
|
114
|
+
# it is a coverage failure worth explaining.
|
|
115
|
+
def report_processing_failure(exit_status)
|
|
116
|
+
if exit_status.positive? && print_errors
|
|
117
|
+
ExitCodes.print_error Color.colorize(
|
|
98
118
|
"SimpleCov failed with exit #{exit_status} due to a coverage related error", :red
|
|
99
119
|
)
|
|
100
120
|
end
|
|
101
|
-
|
|
121
|
+
exit_status
|
|
102
122
|
end
|
|
103
123
|
|
|
104
|
-
# @api private
|
|
124
|
+
# @api private. The history entry is recorded on the same successful-run
|
|
125
|
+
# condition `.last_run.json` uses, so a run that failed its thresholds
|
|
126
|
+
# becomes neither the drop baseline nor a data point in the trend.
|
|
105
127
|
def process_result(result)
|
|
106
128
|
result_exit_status = result_exit_status(result)
|
|
107
|
-
|
|
129
|
+
if successful?(result_exit_status)
|
|
130
|
+
write_last_run(result)
|
|
131
|
+
History.record(result)
|
|
132
|
+
end
|
|
108
133
|
result_exit_status
|
|
109
134
|
end
|
|
110
135
|
|
|
136
|
+
# mutant:disable -- a pid is an Integer, and Integers answer ==, eql? and
|
|
137
|
+
# equal? alike for the same value.
|
|
138
|
+
def started_in_this_process?
|
|
139
|
+
SimpleCov.pid == Process.pid
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Asked as a predicate rather than compared against `ExitCodes::SUCCESS`,
|
|
143
|
+
# because two equal Integers are equal through every spelling of the
|
|
144
|
+
# comparison.
|
|
145
|
+
def successful?(status)
|
|
146
|
+
status.zero?
|
|
147
|
+
end
|
|
148
|
+
|
|
111
149
|
def result_exit_status(result)
|
|
112
150
|
ExitCodes::ExitCodeHandling.call(result, coverage_limits: build_coverage_limits)
|
|
113
151
|
end
|
|
114
152
|
|
|
115
|
-
|
|
153
|
+
private
|
|
116
154
|
|
|
155
|
+
# Every CoverageLimits member is named after the configuration reader that
|
|
156
|
+
# supplies it, so a new limit only has to be added to the Data definition
|
|
157
|
+
# and the checks.
|
|
117
158
|
def build_coverage_limits
|
|
118
|
-
CoverageLimits.
|
|
119
|
-
|
|
120
|
-
minimum_coverage_by_file: minimum_coverage_by_file,
|
|
121
|
-
minimum_coverage_by_file_overrides: minimum_coverage_by_file_overrides,
|
|
122
|
-
minimum_coverage_by_group: minimum_coverage_by_group,
|
|
123
|
-
maximum_coverage: maximum_coverage,
|
|
124
|
-
maximum_coverage_drop: maximum_coverage_drop
|
|
125
|
-
)
|
|
159
|
+
limits = CoverageLimits.members.to_h { |name| [name, public_send(name)] } #: Hash[Symbol, untyped]
|
|
160
|
+
CoverageLimits.new(**limits)
|
|
126
161
|
end
|
|
127
162
|
end
|
|
128
163
|
end
|
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)
|