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
|
@@ -1,158 +1,111 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Result-building façade: turns the raw `Coverage.result` hash into a
|
|
4
|
-
# `SimpleCov::Result`, applies filters and groups, drives merging
|
|
5
|
-
# across test suites via `SimpleCov::ResultMerger`, and exposes the
|
|
6
|
-
# `collate` entry point for stitching disparate resultsets together.
|
|
7
3
|
module SimpleCov
|
|
8
4
|
class << self
|
|
5
|
+
# By default `collate` ignores the merge_timeout so all results in all
|
|
6
|
+
# files specified will be merged. Pass `ignore_timeout: false` to honor it.
|
|
9
7
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# resultsets dominates it. The report is identical either way, not merely
|
|
19
|
-
# equivalent — the workers visit the resultsets in the order the
|
|
20
|
-
# single-process merge visits them — and one process never forks at all.
|
|
21
|
-
# The count is deliberately not clamped to the machine's core count nor
|
|
22
|
-
# gated on some minimum number of resultsets: how many processes a collate
|
|
23
|
-
# job can afford is the caller's call, not SimpleCov's. Anything below 1 is
|
|
24
|
-
# taken as 1, so a count computed from arithmetic that can reach zero needs
|
|
25
|
-
# no guarding. Defaults to `SIMPLECOV_CONCURRENCY`, or 1 when that is
|
|
26
|
-
# unset. See `SimpleCov::ParallelResultMerger`.
|
|
27
|
-
#
|
|
28
|
-
def collate(result_filenames, profile = nil, processes: ENV.fetch("SIMPLECOV_CONCURRENCY", 1).to_i,
|
|
29
|
-
ignore_timeout: true, &)
|
|
8
|
+
# `processes:` above 1 fans the merge out across that many forked workers.
|
|
9
|
+
# The report is identical either way, not merely equivalent. The count is
|
|
10
|
+
# deliberately not clamped to the machine's core count nor gated on a
|
|
11
|
+
# minimum number of resultsets: what a collate job can afford is the
|
|
12
|
+
# caller's call. Anything below 1 is taken as 1, so an unset
|
|
13
|
+
# `SIMPLECOV_CONCURRENCY` needs no default of its own.
|
|
14
|
+
def collate(result_filenames, profile = nil, processes: ENV["SIMPLECOV_CONCURRENCY"].to_i,
|
|
15
|
+
ignore_timeout: true, &)
|
|
30
16
|
raise ArgumentError, "There are no reports to be merged" if result_filenames.empty?
|
|
31
17
|
|
|
32
18
|
initial_setup(profile, &)
|
|
33
19
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
ignore_timeout: ignore_timeout)
|
|
20
|
+
current_run.result = ParallelResultMerger.merge_and_store(*result_filenames, processes: [1, processes].max,
|
|
21
|
+
ignore_timeout: ignore_timeout)
|
|
37
22
|
|
|
38
|
-
|
|
23
|
+
current_run.collating_result = true
|
|
39
24
|
run_exit_tasks!
|
|
40
25
|
ensure
|
|
41
|
-
|
|
26
|
+
current_run.collating_result = false
|
|
42
27
|
end
|
|
43
28
|
|
|
44
|
-
#
|
|
45
|
-
# Returns the result for the current coverage run. With merging enabled,
|
|
46
|
-
# every process stores its own slice, but only the finalization owner reads
|
|
47
|
-
# and caches the merged result.
|
|
48
|
-
#
|
|
49
29
|
def result
|
|
50
|
-
return
|
|
30
|
+
return current_run.result if result?
|
|
51
31
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
# first (if there is one), then merge the results and return those
|
|
58
|
-
if use_merging
|
|
59
|
-
SimpleCov::ResultMerger.store_result(@result) if result?
|
|
60
|
-
return @result unless merge_finalization_owner?
|
|
32
|
+
merge = merging
|
|
33
|
+
collect_own_coverage(standalone: !merge)
|
|
34
|
+
merge_own_slice if merge
|
|
35
|
+
current_run.result
|
|
36
|
+
end
|
|
61
37
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
def merge_own_slice
|
|
39
|
+
ResultMerger.store_result(result) if result?
|
|
40
|
+
return unless merge_finalization_owner?
|
|
65
41
|
|
|
66
|
-
|
|
42
|
+
wait_for_other_processes
|
|
43
|
+
current_run.result = ResultMerger.merged_result
|
|
67
44
|
end
|
|
68
45
|
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# files. When a merge does follow, both jobs belong to the merged result.
|
|
46
|
+
# `standalone` is true when no merge step follows, which makes this the
|
|
47
|
+
# final result: the one that reports dropped source files and injects
|
|
48
|
+
# unloaded files. When a merge does follow, both jobs belong to the merge.
|
|
73
49
|
def collect_own_coverage(standalone:)
|
|
74
50
|
return unless defined?(Coverage) && Coverage.running?
|
|
75
51
|
|
|
76
52
|
process_coverage_result(report: standalone, inject_unloaded: standalone)
|
|
77
53
|
end
|
|
78
54
|
|
|
79
|
-
# Returns nil if the result has not been computed, otherwise the result.
|
|
80
|
-
def result?
|
|
81
|
-
defined?(@result) && @result
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
# @api private — true while `SimpleCov.collate` is running its finalizer.
|
|
85
|
-
def collating_result?
|
|
86
|
-
defined?(@collating_result) && @collating_result
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Applies the configured filters to the given array of SimpleCov::SourceFile items
|
|
90
55
|
def filtered(files)
|
|
91
|
-
result = files.to_a
|
|
56
|
+
result = files.to_a
|
|
92
57
|
filters.each do |filter|
|
|
93
58
|
result = result.reject { |source_file| filter.matches?(source_file) }
|
|
94
59
|
end
|
|
95
|
-
|
|
60
|
+
FileList.new result
|
|
96
61
|
end
|
|
97
62
|
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
# different group config (e.g., the snapshot a Result captured at
|
|
101
|
-
# construction). Files matched by no group fall into the implicit
|
|
102
|
-
# "Ungrouped" bucket.
|
|
103
|
-
def grouped(files, groups: SimpleCov.groups)
|
|
63
|
+
# Files matched by no group fall into the implicit "Ungrouped" bucket.
|
|
64
|
+
def grouped(files, groups: default_groups)
|
|
104
65
|
return {} if GroupNames.validate!(groups.keys).empty?
|
|
105
66
|
|
|
106
67
|
grouped = groups.transform_values do |filter|
|
|
107
|
-
|
|
68
|
+
FileList.new(files.select { |source_file| filter.matches?(source_file) })
|
|
108
69
|
end
|
|
109
70
|
|
|
110
|
-
in_group
|
|
71
|
+
in_group = grouped_file_set(grouped)
|
|
111
72
|
ungrouped = files.reject { |source_file| in_group.include?(source_file) }
|
|
112
|
-
grouped[GroupNames::UNGROUPED] =
|
|
73
|
+
grouped[GroupNames::UNGROUPED] = FileList.new(ungrouped) if ungrouped.any?
|
|
113
74
|
|
|
114
75
|
grouped
|
|
115
76
|
end
|
|
116
77
|
|
|
117
|
-
# Applies the profile of given name on SimpleCov configuration
|
|
118
78
|
def load_profile(name)
|
|
119
79
|
profiles.load(name)
|
|
120
80
|
end
|
|
121
81
|
|
|
122
|
-
# Clear out the previously cached .result. Primarily useful in testing.
|
|
123
82
|
def clear_result
|
|
124
|
-
|
|
83
|
+
current_run.result = nil
|
|
125
84
|
end
|
|
126
85
|
|
|
127
|
-
# @api private
|
|
128
|
-
# rounded down (see #679) so the next run can compute drift.
|
|
86
|
+
# @api private -- floored (#679) so the next run can compute drift.
|
|
129
87
|
def write_last_run(result)
|
|
130
|
-
|
|
88
|
+
LastRun.write(
|
|
131
89
|
result: result.coverage_statistics.transform_values { |stats| round_coverage(stats.percent) }
|
|
132
90
|
)
|
|
133
91
|
end
|
|
134
92
|
|
|
135
|
-
# @api private
|
|
93
|
+
# @api private -- floored, to be extra strict.
|
|
136
94
|
def round_coverage(coverage)
|
|
137
95
|
coverage.floor(2)
|
|
138
96
|
end
|
|
139
97
|
|
|
140
|
-
#
|
|
141
|
-
# already carry, and return it with the set of paths added.
|
|
142
|
-
#
|
|
143
|
-
# @api private — the seam `SimpleCov::ResultMerger` injects through. Public
|
|
98
|
+
# @api private -- the seam `SimpleCov::ResultMerger` injects through. Public
|
|
144
99
|
# only because the merge runs in another object, on behalf of processes
|
|
145
100
|
# whose configuration it may not share, so it supplies the paths itself.
|
|
146
101
|
def inject_unloaded_files(result, candidate_paths, synthesize: nil, lines: nil)
|
|
147
102
|
return [result, Set.new] if candidate_paths.empty?
|
|
148
103
|
|
|
149
104
|
# Synthesizing branch and method tuples means parsing every tracked file
|
|
150
|
-
# that wasn't loaded,
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
# for line data, which a branch-only or method-only run neither reports
|
|
155
|
-
# nor receives from `Coverage` for the files it loaded. See #1250.
|
|
105
|
+
# that wasn't loaded, about half the cost of simulating one, and nothing
|
|
106
|
+
# reads those tuples when neither criterion is enabled. The same goes for
|
|
107
|
+
# line data, which a branch-only or method-only run neither reports nor
|
|
108
|
+
# receives from `Coverage` for the files it loaded (#1250).
|
|
156
109
|
UnloadedFileInjector.call(
|
|
157
110
|
result, candidate_paths,
|
|
158
111
|
synthesize: synthesize.nil? ? branch_coverage? || method_coverage? : synthesize,
|
|
@@ -160,7 +113,7 @@ module SimpleCov
|
|
|
160
113
|
)
|
|
161
114
|
end
|
|
162
115
|
|
|
163
|
-
|
|
116
|
+
private
|
|
164
117
|
|
|
165
118
|
def initial_setup(profile, &block)
|
|
166
119
|
load_profile(profile) if profile
|
|
@@ -172,46 +125,48 @@ module SimpleCov
|
|
|
172
125
|
end
|
|
173
126
|
|
|
174
127
|
# Every path this process was told to track, whether or not it loaded them.
|
|
175
|
-
# Recorded on the result
|
|
176
|
-
#
|
|
177
|
-
#
|
|
178
|
-
# never ran `SimpleCov.start` and so has none of its own. See #1250.
|
|
128
|
+
# Recorded on the result so a merge in another process can inject the ones
|
|
129
|
+
# nobody loaded without needing this process's `cover` / `track_files`
|
|
130
|
+
# configuration. A standalone `collate` never ran `SimpleCov.start` (#1250).
|
|
179
131
|
def tracked_file_paths
|
|
180
132
|
UnloadedFileInjector.discover(
|
|
181
133
|
unloaded_file_discovery_globs, root: root, reject: filters.select(&:path_only?)
|
|
182
134
|
)
|
|
183
135
|
end
|
|
184
136
|
|
|
185
|
-
#
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
# but the restriction lives in `Result#apply_cover_filters!`).
|
|
137
|
+
# The legacy `track_files` glob (additive only) plus every string glob
|
|
138
|
+
# declared via `cover` (also restrictive, but the restriction lives in
|
|
139
|
+
# `Result#apply_cover_filters!`).
|
|
189
140
|
def unloaded_file_discovery_globs
|
|
190
141
|
[tracked_files, *cover_globs].compact
|
|
191
142
|
end
|
|
192
143
|
|
|
193
|
-
#
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
# so
|
|
197
|
-
#
|
|
198
|
-
# `inject_unloaded:` is likewise false when a merge step follows. Only the
|
|
199
|
-
# union of every process's loaded files says what was really never loaded,
|
|
200
|
-
# so injecting here means each worker simulates nearly the whole project
|
|
201
|
-
# and all but one of those passes is merged away. See #1250.
|
|
144
|
+
# `report:` and `inject_unloaded:` are false when a merge step follows.
|
|
145
|
+
# Only the union of every process's loaded files says what was really never
|
|
146
|
+
# loaded, and the merged result is the one that reports dropped source
|
|
147
|
+
# files, so a per-process slice would warn once per worker and simulate
|
|
148
|
+
# nearly the whole project only to have it merged away (#1250).
|
|
202
149
|
def process_coverage_result(report:, inject_unloaded: true)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
#
|
|
206
|
-
#
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
150
|
+
# Templates nobody rendered are compiled into the running Coverage so they
|
|
151
|
+
# arrive at 0% below rather than being absent. Needs a live Coverage and
|
|
152
|
+
# ActionView, which rules out the merge point that does the same job for
|
|
153
|
+
# unloaded `.rb` files.
|
|
154
|
+
ViewCoverage.compile_unrendered
|
|
155
|
+
raw = UselessResultsRemover.call(Coverage.result)
|
|
156
|
+
adapted = ResultAdapter.call(raw)
|
|
157
|
+
# `Result#to_hash` serializes coverage after filtering, so a file this
|
|
158
|
+
# process loaded and then filtered out would otherwise be recorded as
|
|
159
|
+
# tracked while absent from the stored coverage, and the merge would
|
|
160
|
+
# simulate it back in as never-loaded (#1250).
|
|
210
161
|
tracked = tracked_file_paths - adapted.keys
|
|
211
162
|
result, not_loaded = inject_unloaded ? inject_unloaded_files(adapted, tracked) : [adapted, Set.new]
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
163
|
+
current_run.result = build_result(raw, result, not_loaded: not_loaded, tracked: tracked, report: report)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def build_result(raw, coverage, not_loaded:, tracked:, report:)
|
|
167
|
+
Result.new(
|
|
168
|
+
coverage, not_loaded_files: not_loaded, tracked_files: tracked, run_id: run_id,
|
|
169
|
+
worker_id: worker_id, contexts: test_tracker&.recorded_map(closing: raw), report: report
|
|
215
170
|
)
|
|
216
171
|
end
|
|
217
172
|
end
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
require "securerandom"
|
|
4
4
|
|
|
5
5
|
module SimpleCov
|
|
6
|
-
# Identifies one test invocation and each top-level parallel worker within
|
|
7
|
-
#
|
|
6
|
+
# Identifies one test invocation and each top-level parallel worker within it.
|
|
7
|
+
# Forked subprocesses inherit both values from their parent worker.
|
|
8
8
|
module RunIdentity
|
|
9
|
-
|
|
9
|
+
extend self
|
|
10
10
|
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# parallel_tests' per-invocation pid file, or freshly random),
|
|
14
|
-
#
|
|
15
|
-
#
|
|
11
|
+
# Answers the run id together with its provenance: true when the id alone
|
|
12
|
+
# proves same-run membership (explicitly configured, derived from
|
|
13
|
+
# parallel_tests' per-invocation pid file, or freshly random), false when it
|
|
14
|
+
# was inferred from the parent pid, which consecutive runs launched by the
|
|
15
|
+
# same long-lived parent process share.
|
|
16
16
|
def generate
|
|
17
17
|
explicit = ENV.fetch("SIMPLECOV_RUN_ID", nil)
|
|
18
18
|
return [explicit, true] if explicit && !explicit.empty?
|
|
@@ -20,7 +20,7 @@ module SimpleCov
|
|
|
20
20
|
pid_file = ENV.fetch("PARALLEL_PID_FILE", nil)
|
|
21
21
|
return ["parallel-tests:#{File.basename(pid_file)}", true] if pid_file && !pid_file.empty?
|
|
22
22
|
|
|
23
|
-
return ["parallel-parent:#{Process.ppid}", false] if
|
|
23
|
+
return ["parallel-parent:#{Process.ppid}", false] if ParallelAdapters.current
|
|
24
24
|
|
|
25
25
|
[SecureRandom.uuid, true]
|
|
26
26
|
end
|
|
@@ -35,10 +35,9 @@ module SimpleCov
|
|
|
35
35
|
Process.pid.to_s
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# one is still trusted.
|
|
38
|
+
# Decided where the id is generated, never re-inferred from the id's shape,
|
|
39
|
+
# so an explicit SIMPLECOV_RUN_ID that happens to look like an inferred one
|
|
40
|
+
# is still trusted.
|
|
42
41
|
def authoritative?
|
|
43
42
|
materialize_current
|
|
44
43
|
@authoritative
|
|
@@ -50,7 +49,7 @@ module SimpleCov
|
|
|
50
49
|
end
|
|
51
50
|
|
|
52
51
|
def materialize_current
|
|
53
|
-
@current, @authoritative = generate unless
|
|
52
|
+
@current, @authoritative = generate unless instance_variable_defined?(:@current)
|
|
54
53
|
end
|
|
55
54
|
|
|
56
55
|
def current_worker_id
|
|
@@ -58,19 +57,18 @@ module SimpleCov
|
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
def prepare
|
|
61
|
-
|
|
60
|
+
ParallelAdapters.current
|
|
62
61
|
SimpleCov.run_id
|
|
63
62
|
SimpleCov.worker_id
|
|
64
63
|
end
|
|
65
64
|
|
|
66
|
-
# Mixed into SimpleCov after ParallelAdapters has loaded.
|
|
67
65
|
module Accessors
|
|
68
66
|
def run_id
|
|
69
|
-
|
|
67
|
+
RunIdentity.current
|
|
70
68
|
end
|
|
71
69
|
|
|
72
70
|
def worker_id
|
|
73
|
-
|
|
71
|
+
RunIdentity.current_worker_id
|
|
74
72
|
end
|
|
75
73
|
end
|
|
76
74
|
end
|
|
@@ -1,50 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "coverage"
|
|
3
4
|
require_relative "static_coverage_extractor"
|
|
4
5
|
|
|
5
6
|
module SimpleCov
|
|
6
|
-
#
|
|
7
|
-
# Responsible for producing file coverage metrics.
|
|
8
|
-
#
|
|
9
7
|
module SimulateCoverage
|
|
10
|
-
|
|
8
|
+
extend self
|
|
11
9
|
|
|
12
10
|
#
|
|
13
11
|
# Simulate a file coverage report for a file that was tracked but never
|
|
14
|
-
# required
|
|
15
|
-
# branches, methods).
|
|
12
|
+
# required, in the same hash shape as `Coverage.result`.
|
|
16
13
|
#
|
|
17
|
-
# The line classification comes from `Coverage.line_stub
|
|
14
|
+
# The line classification comes from `Coverage.line_stub`, the same
|
|
18
15
|
# classification the runtime would have produced if the file had been
|
|
19
|
-
# required
|
|
16
|
+
# required, overlaid with SimpleCov's `# :nocov:` toggles and
|
|
20
17
|
# `# simplecov:disable line` directive ranges, which `Coverage` doesn't
|
|
21
18
|
# know about. This keeps "relevant lines" identical whether a file was
|
|
22
|
-
# loaded or just tracked
|
|
23
|
-
# in https://github.com/simplecov-ruby/simplecov/issues/654.
|
|
19
|
+
# loaded or just tracked (#654).
|
|
24
20
|
#
|
|
25
|
-
# Branches and methods are enumerated by static analysis
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# https://github.com/simplecov-ruby/simplecov/issues/1059. When Prism
|
|
32
|
-
# isn't loadable (Ruby < 3.3 without the prism gem) or the file
|
|
33
|
-
# can't be parsed, fall back to the old empty hashes — old behavior,
|
|
34
|
-
# old tradeoff.
|
|
21
|
+
# Branches and methods are enumerated by static analysis. Earlier behavior
|
|
22
|
+
# left both as empty hashes, which made unloaded files invisible to the
|
|
23
|
+
# branch/method denominators while their lines did count, so a glob that
|
|
24
|
+
# picked up files without specs silently inflated branch% relative to
|
|
25
|
+
# line% (#1059). When Prism isn't loadable or the file can't be parsed,
|
|
26
|
+
# fall back to the old empty hashes.
|
|
35
27
|
#
|
|
36
|
-
# Pass `synthesize: false` to skip the static analysis
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
28
|
+
# Pass `synthesize: false` to skip the static analysis. Callers use it when
|
|
29
|
+
# neither branch nor method coverage is enabled, since nothing will read
|
|
30
|
+
# the tuples and the Prism parse is about half the cost of simulating a
|
|
31
|
+
# file (#1250).
|
|
40
32
|
#
|
|
41
33
|
# Pass `lines: false` to omit the `"lines"` key entirely, mirroring what
|
|
42
34
|
# `Coverage.result` reports for a file loaded under a branch-only or
|
|
43
|
-
# method-only run. Emitting zeroed lines there would make a simulated
|
|
44
|
-
#
|
|
45
|
-
# the two are merged.
|
|
46
|
-
#
|
|
47
|
-
# @return [Hash]
|
|
35
|
+
# method-only run. Emitting zeroed lines there would make a simulated file
|
|
36
|
+
# indistinguishable from one a sibling process actually loaded.
|
|
48
37
|
#
|
|
49
38
|
def call(absolute_path, synthesize: true, lines: true)
|
|
50
39
|
source_lines = read_lines(absolute_path)
|
|
@@ -52,47 +41,43 @@ module SimpleCov
|
|
|
52
41
|
return simulated unless lines
|
|
53
42
|
|
|
54
43
|
classified = coverage_stub(absolute_path, source_lines) ||
|
|
55
|
-
|
|
44
|
+
LinesClassifier.new.classify(source_lines)
|
|
56
45
|
{"lines" => classified}.merge(simulated)
|
|
57
46
|
end
|
|
58
47
|
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
# or the file doesn't parse).
|
|
48
|
+
# Empty hashes when the static analysis is skipped (nothing enabled reads
|
|
49
|
+
# them) or unavailable (no Prism, or the file doesn't parse).
|
|
62
50
|
def synthesized_tuples(source_lines, synthesize)
|
|
63
51
|
empty = {"branches" => {}, "methods" => {}} #: Hash[String, Hash[untyped, untyped]]
|
|
64
52
|
synthesized = (StaticCoverageExtractor.call(source_lines.join) if synthesize) || empty
|
|
65
53
|
|
|
66
|
-
{"branches" => synthesized
|
|
54
|
+
{"branches" => synthesized.fetch("branches"), "methods" => synthesized.fetch("methods")}
|
|
67
55
|
end
|
|
68
56
|
|
|
69
|
-
# SystemCallError, not just ENOENT: a `track_files` glob can sweep
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
# rather than crash the merge or report step.
|
|
57
|
+
# SystemCallError, not just ENOENT: a `track_files` glob can sweep up an
|
|
58
|
+
# unreadable file or a directory named like a Ruby file, and simulation must
|
|
59
|
+
# degrade to "empty file" rather than crash the merge or report step.
|
|
73
60
|
def read_lines(path)
|
|
74
61
|
File.readlines(path)
|
|
75
62
|
rescue SystemCallError
|
|
76
63
|
[]
|
|
77
64
|
end
|
|
78
65
|
|
|
79
|
-
#
|
|
66
|
+
# Combines `Coverage.line_stub` (which gets multi-line statements right)
|
|
80
67
|
# with `LinesClassifier` (which knows about `# :nocov:` toggles and
|
|
81
|
-
# `# simplecov:disable line` ranges).
|
|
82
|
-
#
|
|
83
|
-
# or parse the file, or when the runtime doesn't expose `line_stub`
|
|
84
|
-
# (JRuby and TruffleRuby).
|
|
68
|
+
# `# simplecov:disable line` ranges). Answers nil when `Coverage` can't
|
|
69
|
+
# read or parse the file, or the runtime doesn't expose `line_stub`.
|
|
85
70
|
def coverage_stub(path, source_lines)
|
|
86
71
|
return nil unless Coverage.respond_to?(:line_stub)
|
|
87
72
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
73
|
+
# Paired off rather than indexed: the two lists describe the same file
|
|
74
|
+
# line for line. A line the classifier has no verdict for keeps the nil
|
|
75
|
+
# it would have been set to.
|
|
76
|
+
relevance = LinesClassifier.new.classify(source_lines)
|
|
77
|
+
Coverage.line_stub(path).zip(relevance).map { |hits, relevant| hits if relevant }
|
|
92
78
|
rescue SystemCallError, SyntaxError
|
|
93
|
-
# SystemCallError for the same reason as read_lines
|
|
94
|
-
#
|
|
95
|
-
# too.
|
|
79
|
+
# SystemCallError for the same reason as read_lines: line_stub reads the
|
|
80
|
+
# file itself, so EACCES/EISDIR surface here too.
|
|
96
81
|
nil
|
|
97
82
|
end
|
|
98
83
|
end
|
|
@@ -2,51 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
class SourceFile
|
|
5
|
-
#
|
|
6
|
-
# Representing single branch that has been detected in coverage report.
|
|
7
|
-
# Give us support methods that handle needed calculations.
|
|
8
5
|
class Branch
|
|
9
6
|
attr_reader :start_line, :end_line, :coverage, :type
|
|
10
7
|
|
|
11
8
|
def initialize(start_line:, end_line:, coverage:, inline:, type:)
|
|
12
9
|
@start_line = start_line
|
|
13
|
-
@end_line
|
|
14
|
-
@coverage
|
|
15
|
-
@inline
|
|
16
|
-
@type
|
|
17
|
-
@skipped
|
|
10
|
+
@end_line = end_line
|
|
11
|
+
@coverage = coverage
|
|
12
|
+
@inline = inline
|
|
13
|
+
@type = type
|
|
14
|
+
@skipped = false
|
|
18
15
|
end
|
|
19
16
|
|
|
20
17
|
def inline?
|
|
21
18
|
@inline
|
|
22
19
|
end
|
|
23
20
|
|
|
24
|
-
#
|
|
25
|
-
# Return true if there is relevant count defined > 0
|
|
26
|
-
#
|
|
27
|
-
# @return [Boolean]
|
|
28
|
-
#
|
|
29
21
|
def covered?
|
|
30
22
|
!skipped? && coverage.positive?
|
|
31
23
|
end
|
|
32
24
|
|
|
33
|
-
#
|
|
34
|
-
# Check if branch missed or not
|
|
35
|
-
#
|
|
36
|
-
# @return [Boolean]
|
|
37
|
-
#
|
|
38
25
|
def missed?
|
|
39
26
|
!skipped? && coverage.zero?
|
|
40
27
|
end
|
|
41
28
|
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# at if/else) because that
|
|
46
|
-
# * highlights the condition
|
|
47
|
-
# * makes it distinguishable if the first line of the branch is an inline branch
|
|
48
|
-
# (see the nested_branches fixture)
|
|
49
|
-
#
|
|
29
|
+
# Usually the line above the start of the branch, so that it shows up at the
|
|
30
|
+
# if/else. That highlights the condition, and makes it distinguishable when
|
|
31
|
+
# the first line of the branch is itself an inline branch.
|
|
50
32
|
def report_line
|
|
51
33
|
if inline?
|
|
52
34
|
start_line
|
|
@@ -55,12 +37,10 @@ module SimpleCov
|
|
|
55
37
|
end
|
|
56
38
|
end
|
|
57
39
|
|
|
58
|
-
# Flags the branch as skipped
|
|
59
40
|
def skipped!
|
|
60
41
|
@skipped = true
|
|
61
42
|
end
|
|
62
43
|
|
|
63
|
-
# Returns true if the branch was marked skipped by virtue of nocov comments.
|
|
64
44
|
def skipped?
|
|
65
45
|
@skipped
|
|
66
46
|
end
|
|
@@ -69,11 +49,6 @@ module SimpleCov
|
|
|
69
49
|
start_line <= line_range.end && end_line >= line_range.begin
|
|
70
50
|
end
|
|
71
51
|
|
|
72
|
-
#
|
|
73
|
-
# Return array with coverage count and badge
|
|
74
|
-
#
|
|
75
|
-
# @return [Array]
|
|
76
|
-
#
|
|
77
52
|
def report
|
|
78
53
|
[type, coverage]
|
|
79
54
|
end
|