simplecov 1.1.1 → 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 +148 -5
- data/exe/simplecov +0 -3
- data/lib/minitest/simplecov_plugin.rb +12 -9
- data/lib/simplecov/atomic_file.rb +16 -19
- data/lib/simplecov/autostart.rb +5 -7
- data/lib/simplecov/baseline/parser.rb +58 -0
- data/lib/simplecov/baseline.rb +170 -0
- data/lib/simplecov/cli/affected/changed_files.rb +57 -0
- data/lib/simplecov/cli/affected/selection.rb +117 -0
- data/lib/simplecov/cli/affected.rb +148 -0
- data/lib/simplecov/cli/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 +19 -37
- data/lib/simplecov/combine/branches_combiner.rb +7 -31
- data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
- data/lib/simplecov/combine/identity_interner.rb +10 -13
- data/lib/simplecov/combine/interned_counts.rb +7 -9
- data/lib/simplecov/combine/lines_combiner.rb +20 -44
- data/lib/simplecov/combine/methods_combiner.rb +9 -35
- data/lib/simplecov/combine/results_combiner.rb +4 -21
- data/lib/simplecov/combine.rb +0 -6
- data/lib/simplecov/command_guesser.rb +20 -34
- data/lib/simplecov/configuration/baseline.rb +31 -0
- data/lib/simplecov/configuration/coverage.rb +91 -79
- data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
- data/lib/simplecov/configuration/deprecations.rb +21 -0
- data/lib/simplecov/configuration/eval_coverage.rb +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 +16 -10
- data/lib/simplecov/exit_handling.rb +94 -59
- data/lib/simplecov/file_list.rb +20 -51
- data/lib/simplecov/filter.rb +43 -66
- data/lib/simplecov/formatter/base.rb +37 -55
- data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
- data/lib/simplecov/formatter/html_formatter/public/index.html +15 -9
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
- data/lib/simplecov/formatter/html_formatter.rb +24 -41
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
- data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
- data/lib/simplecov/formatter/json_formatter.rb +7 -13
- data/lib/simplecov/formatter/multi_formatter.rb +5 -11
- data/lib/simplecov/formatter/simple_formatter.rb +2 -8
- data/lib/simplecov/formatter.rb +14 -10
- data/lib/simplecov/group_names.rb +13 -12
- data/lib/simplecov/history.rb +115 -0
- data/lib/simplecov/last_run.rb +6 -9
- data/lib/simplecov/lines_classifier.rb +31 -16
- data/lib/simplecov/load_global_config.rb +7 -7
- data/lib/simplecov/parallel_adapters/base.rb +13 -41
- data/lib/simplecov/parallel_adapters/generic.rb +7 -19
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
- data/lib/simplecov/parallel_adapters.rb +24 -55
- data/lib/simplecov/parallel_coordination.rb +39 -49
- data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
- data/lib/simplecov/parallel_result_merger.rb +67 -126
- data/lib/simplecov/process.rb +14 -32
- data/lib/simplecov/production/error.rb +11 -0
- data/lib/simplecov/production/file_sink.rb +130 -0
- data/lib/simplecov/production.rb +249 -0
- data/lib/simplecov/profiles/rails.rb +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 +56 -68
- data/lib/simplecov/result_merger/contexts.rb +22 -0
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
- data/lib/simplecov/result_merger/resultset_file.rb +20 -27
- data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
- data/lib/simplecov/result_merger/resultset_store.rb +23 -14
- data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
- data/lib/simplecov/result_merger.rb +59 -58
- data/lib/simplecov/result_processing.rb +74 -119
- data/lib/simplecov/run_identity.rb +16 -18
- data/lib/simplecov/simulate_coverage.rb +34 -50
- data/lib/simplecov/source_file/branch.rb +8 -33
- data/lib/simplecov/source_file/branch_builder.rb +28 -47
- data/lib/simplecov/source_file/builder_context.rb +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 +57 -113
- data/lib/simplecov_json_formatter.rb +1 -0
- data/man/simplecov.1 +349 -0
- data/sig/simplecov.rbs +523 -595
- metadata +72 -5
|
@@ -3,27 +3,26 @@
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
class Result
|
|
5
5
|
# When a resultset references source files that don't exist on the local
|
|
6
|
-
# filesystem they're silently dropped
|
|
7
|
-
# (100.00%)` report that looks like success but isn't.
|
|
8
|
-
# warning summarizing the drop and, when every entry was lost,
|
|
9
|
-
# the typical cause
|
|
10
|
-
# different from where the resultsets were generated
|
|
6
|
+
# filesystem they're silently dropped, which produces an empty
|
|
7
|
+
# `0 / 0 (100.00%)` report that looks like success but isn't. This emits a
|
|
8
|
+
# single warning summarizing the drop and, when every entry was lost, points
|
|
9
|
+
# at the typical cause: `SimpleCov.collate` invoked from a machine or path
|
|
10
|
+
# different from where the resultsets were generated (#980).
|
|
11
11
|
class MissingSourceFilesReporter
|
|
12
|
-
def initialize(missing_paths,
|
|
12
|
+
def initialize(missing_paths, every_entry_dropped:)
|
|
13
13
|
@missing_paths = missing_paths
|
|
14
|
-
@input_size = input_size
|
|
15
14
|
@every_entry_dropped = every_entry_dropped
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def warn!
|
|
19
|
-
warn
|
|
18
|
+
warn Color.colorize(message, :yellow)
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
def message
|
|
23
22
|
@every_entry_dropped ? all_missing_warning : partial_missing_warning
|
|
24
23
|
end
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
private
|
|
27
26
|
|
|
28
27
|
def all_missing_warning
|
|
29
28
|
"SimpleCov dropped all #{@missing_paths.size} source file(s) from the result — " \
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class Result
|
|
5
|
+
module Serialization
|
|
6
|
+
def to_hash
|
|
7
|
+
data = {"coverage" => coverage, "timestamp" => created_at.to_f} #: Hash[String, untyped]
|
|
8
|
+
data["run_id"] = run_id if run_id
|
|
9
|
+
data["worker_id"] = worker_id if worker_id
|
|
10
|
+
data["tracked_files"] = tracked_files unless tracked_files.empty?
|
|
11
|
+
append_contexts(data)
|
|
12
|
+
{command_name => data}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
# Serialized whenever a map was recorded, even an empty one: for the merge,
|
|
18
|
+
# the key's presence is the signal that this result tracked tests, which is
|
|
19
|
+
# what lets it tell "tracked and covered nothing" from "never tracked". The
|
|
20
|
+
# map's files are restricted to this result's post-filter universe.
|
|
21
|
+
def append_contexts(data)
|
|
22
|
+
map = contexts
|
|
23
|
+
return unless map
|
|
24
|
+
|
|
25
|
+
data["contexts"] = map.to_h(only: context_filenames)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def context_filenames
|
|
29
|
+
Set.new(filenames)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# A live result's criterion keys are Symbols while entries parsed back from
|
|
33
|
+
# `.resultset.json` carry Strings, and the combiners read only String keys.
|
|
34
|
+
# Serializing with String keys is what lets a live result merged against a
|
|
35
|
+
# stored entry contribute its counts instead of being dropped.
|
|
36
|
+
def coverage
|
|
37
|
+
original_result.slice(*filenames).transform_values do |file_coverage|
|
|
38
|
+
file_coverage.transform_keys(&:to_s)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -2,10 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
class Result
|
|
5
|
-
# Constructs `SimpleCov::SourceFile` instances from a raw coverage
|
|
6
|
-
# hash, sorts them by filename, and surfaces filenames whose source
|
|
7
|
-
# is no longer present on disk so the caller can warn about the
|
|
8
|
-
# silent drop (see #980).
|
|
9
5
|
class SourceFileBuilder
|
|
10
6
|
attr_reader :missing_source_files
|
|
11
7
|
|
|
@@ -16,14 +12,14 @@ module SimpleCov
|
|
|
16
12
|
end
|
|
17
13
|
|
|
18
14
|
def call
|
|
19
|
-
|
|
15
|
+
FileList.new(
|
|
20
16
|
@original_result
|
|
21
17
|
.filter_map { |filename, coverage| build_source_file(filename, coverage) }
|
|
22
18
|
.sort_by(&:filename)
|
|
23
19
|
)
|
|
24
20
|
end
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
private
|
|
27
23
|
|
|
28
24
|
def build_source_file(filename, coverage)
|
|
29
25
|
unless File.file?(filename)
|
|
@@ -31,18 +27,17 @@ module SimpleCov
|
|
|
31
27
|
return
|
|
32
28
|
end
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
SourceFile.new(
|
|
35
31
|
filename,
|
|
36
32
|
stringify_outer_keys(coverage),
|
|
37
33
|
loaded: !@not_loaded_files.include?(filename)
|
|
38
34
|
)
|
|
39
35
|
end
|
|
40
36
|
|
|
41
|
-
# `Coverage.result` returns symbol keys
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# so only the outer hash needs normalizing.
|
|
37
|
+
# `Coverage.result` returns symbol keys; resultsets loaded from disk are
|
|
38
|
+
# already string-keyed. SourceFile reads with strings and handles both Array
|
|
39
|
+
# and stringified-Array branch/method keys itself, so only the outer hash
|
|
40
|
+
# needs normalizing.
|
|
46
41
|
def stringify_outer_keys(coverage)
|
|
47
42
|
coverage.transform_keys(&:to_s)
|
|
48
43
|
end
|
data/lib/simplecov/result.rb
CHANGED
|
@@ -2,210 +2,161 @@
|
|
|
2
2
|
|
|
3
3
|
require "digest/sha1"
|
|
4
4
|
require "forwardable"
|
|
5
|
+
require_relative "result/filter_config"
|
|
5
6
|
require_relative "result/missing_source_files_reporter"
|
|
7
|
+
require_relative "result/serialization"
|
|
6
8
|
require_relative "result/source_file_builder"
|
|
7
9
|
|
|
8
10
|
module SimpleCov
|
|
9
11
|
#
|
|
10
|
-
# A
|
|
11
|
-
# library generates
|
|
12
|
+
# A code coverage result, initialized from the Hash Ruby's built-in coverage
|
|
13
|
+
# library generates.
|
|
12
14
|
#
|
|
13
15
|
class Result
|
|
14
16
|
extend Forwardable
|
|
17
|
+
include Serialization
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# without needing that process's `cover` / `track_files` config. See #1250.
|
|
19
|
+
attr_reader :original_result, :files
|
|
20
|
+
# Every path the producing process was told to track, loaded or not.
|
|
21
|
+
# Carried into the resultset so a merge elsewhere can inject the ones
|
|
22
|
+
# nobody loaded without needing that process's config (#1250).
|
|
21
23
|
attr_reader :tracked_files
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
+
# Used only for parallel-result coordination. They do not change which
|
|
25
|
+
# fresh suites are merged.
|
|
24
26
|
attr_reader :run_id, :worker_id
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
|
|
27
|
+
alias_method :source_files, :files
|
|
28
|
+
# The `ContextMap` recorded under `track_tests`, or nil when this result
|
|
29
|
+
# carries none: tracking was off, or a merge dropped the map because not
|
|
30
|
+
# every merged result recorded one.
|
|
31
|
+
attr_reader :contexts
|
|
32
|
+
|
|
33
|
+
# The distinct run names behind this result, set by a merge so presentation
|
|
34
|
+
# can say "A and N other runs" instead of reading the joined
|
|
35
|
+
# `command_name` aloud in full. Splitting that string back apart would
|
|
36
|
+
# misread a run name that itself contains a comma.
|
|
37
|
+
attr_writer :created_at, :command_name, :command_names
|
|
33
38
|
|
|
34
39
|
def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
:covered_lines, :missed_lines,
|
|
41
|
+
:total_branches, :covered_branches, :missed_branches,
|
|
42
|
+
:total_methods, :covered_methods, :missed_methods,
|
|
43
|
+
:coverage_statistics, :coverage_statistics_by_file
|
|
39
44
|
def_delegator :files, :lines_of_code, :total_lines
|
|
40
45
|
|
|
41
|
-
# Bundles the filter and grouping configuration a Result applies to its
|
|
42
|
-
# source files after building them. Each field defaults to the SimpleCov
|
|
43
|
-
# singleton's configuration, so ordinary callers never construct one;
|
|
44
|
-
# tests pass a custom instance to opt out of (or extend) the project's
|
|
45
|
-
# filters or groups (e.g. `filters: []` to keep every file). Grouping the
|
|
46
|
-
# three together keeps Result#initialize's parameter list small.
|
|
47
|
-
class FilterConfig
|
|
48
|
-
attr_reader :filters, :cover_filters, :groups
|
|
49
|
-
|
|
50
|
-
def initialize(filters: SimpleCov.filters, cover_filters: SimpleCov.cover_filters, groups: SimpleCov.groups)
|
|
51
|
-
@filters = filters
|
|
52
|
-
@cover_filters = cover_filters
|
|
53
|
-
@groups = groups
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Initialize a new SimpleCov::Result from given Coverage.result (a Hash of filenames each containing an array of
|
|
58
|
-
# coverage data).
|
|
59
|
-
#
|
|
60
46
|
# `filter_config` defaults to the SimpleCov singleton's filter / group
|
|
61
|
-
# configuration
|
|
62
|
-
#
|
|
63
|
-
#
|
|
47
|
+
# configuration. Pass a custom FilterConfig to opt out, which is useful for
|
|
48
|
+
# tests that build synthetic Results.
|
|
49
|
+
#
|
|
50
|
+
# `tracked_files` accepts any collection that answers `to_a` (the merge
|
|
51
|
+
# passes a Set), and nil for a run that tracked nothing.
|
|
64
52
|
def initialize(original_result, command_name: nil, created_at: nil, not_loaded_files: Set.new,
|
|
65
|
-
|
|
53
|
+
tracked_files: nil, run_id: nil, worker_id: nil, contexts: nil, report: false,
|
|
54
|
+
filter_config: FilterConfig.new)
|
|
66
55
|
@original_result = original_result.freeze
|
|
67
56
|
@command_name = command_name
|
|
68
57
|
@created_at = created_at
|
|
69
|
-
|
|
58
|
+
initialize_resultset_metadata(tracked_files, run_id, worker_id, contexts)
|
|
70
59
|
@groups_config = filter_config.groups
|
|
71
60
|
builder = SourceFileBuilder.new(original_result, not_loaded_files: not_loaded_files)
|
|
72
61
|
@files = builder.call
|
|
73
|
-
warn_about_missing_source_files(builder.missing_source_files
|
|
62
|
+
warn_about_missing_source_files(builder.missing_source_files) if report
|
|
74
63
|
apply_cover_filters!(filter_config.cover_filters)
|
|
75
64
|
apply_filters!(filter_config.filters)
|
|
76
65
|
end
|
|
77
66
|
|
|
78
|
-
# Returns all filenames for source files contained in this result
|
|
79
67
|
def filenames
|
|
80
68
|
files.map(&:filename)
|
|
81
69
|
end
|
|
82
70
|
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# SimpleCov.root, so callers can pass either an absolute path or a
|
|
86
|
-
# project-relative one.
|
|
71
|
+
# The path is resolved against SimpleCov.root, so callers can pass either
|
|
72
|
+
# an absolute path or a project-relative one.
|
|
87
73
|
def source_file_for(path)
|
|
88
74
|
target = File.expand_path(path, SimpleCov.root)
|
|
89
|
-
files.find { |file| file.filename
|
|
75
|
+
files.find { |file| file.filename.eql?(target) }
|
|
90
76
|
end
|
|
91
77
|
|
|
92
|
-
#
|
|
93
|
-
# given file path, or nil if no matching source file is in this
|
|
94
|
-
# result. See SimpleCov::Result#source_file_for for path resolution.
|
|
78
|
+
# Path resolution as in `source_file_for`.
|
|
95
79
|
def coverage_for(path)
|
|
96
80
|
source_file_for(path)&.coverage_statistics
|
|
97
81
|
end
|
|
98
82
|
|
|
99
|
-
# Returns a Hash of groups for this result. Define groups using SimpleCov.group 'Models', 'app/models'
|
|
100
83
|
def groups
|
|
101
84
|
@groups ||= SimpleCov.grouped(files, groups: @groups_config)
|
|
102
85
|
end
|
|
103
86
|
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
# `.resultset.json` on disk. See #964.
|
|
87
|
+
# Returns nil if formatting has been opted out of (`SimpleCov.formatter
|
|
88
|
+
# false` / `SimpleCov.formatters []`), the cheap path for non-final
|
|
89
|
+
# processes in a parallel CI run, which only need their `.resultset.json`
|
|
90
|
+
# on disk (#964).
|
|
109
91
|
def format!
|
|
110
92
|
formatter = SimpleCov.formatter
|
|
111
93
|
return nil if formatter.nil?
|
|
112
94
|
|
|
113
95
|
formatted = Formatter.format(formatter, self)
|
|
114
96
|
# Recorded regardless of how the run ends, so a parent process's
|
|
115
|
-
# clobber-prevention backstop can tell a report was produced even
|
|
116
|
-
#
|
|
117
|
-
|
|
118
|
-
SimpleCov::ReportStamp.touch
|
|
97
|
+
# clobber-prevention backstop can tell a report was produced even when
|
|
98
|
+
# this run's checks or tests failed, unlike .last_run.json.
|
|
99
|
+
ReportStamp.touch
|
|
119
100
|
formatted
|
|
120
101
|
end
|
|
121
102
|
|
|
122
|
-
# Defines when this result has been created. Defaults to Time.now
|
|
123
103
|
def created_at
|
|
124
104
|
@created_at ||= Time.now
|
|
125
105
|
end
|
|
126
106
|
|
|
127
|
-
# The command name that launched this result.
|
|
128
|
-
# Delegated to SimpleCov.command_name if not set manually
|
|
129
107
|
def command_name
|
|
130
108
|
@command_name ||= SimpleCov.command_name
|
|
131
109
|
end
|
|
132
110
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
data = {"coverage" => coverage, "timestamp" => created_at.to_f} #: Hash[String, untyped]
|
|
136
|
-
data["run_id"] = run_id if run_id
|
|
137
|
-
data["worker_id"] = worker_id if worker_id
|
|
138
|
-
# Omitted when empty so a run that tracks nothing writes the shape it
|
|
139
|
-
# always has, and so the key only appears where it carries information.
|
|
140
|
-
data["tracked_files"] = tracked_files unless tracked_files.empty?
|
|
141
|
-
{command_name => data}
|
|
111
|
+
def command_names
|
|
112
|
+
@command_names ||= [command_name]
|
|
142
113
|
end
|
|
143
114
|
|
|
144
|
-
# Loads a SimpleCov::Result#to_hash dump
|
|
145
115
|
def self.from_hash(hash)
|
|
146
116
|
hash.map do |command_name, data|
|
|
147
|
-
new(data.fetch("coverage"), command_name: command_name, created_at: Time.at(data
|
|
148
|
-
|
|
149
|
-
|
|
117
|
+
new(data.fetch("coverage"), command_name: command_name, created_at: Time.at(data.fetch("timestamp")),
|
|
118
|
+
tracked_files: data["tracked_files"], run_id: data["run_id"],
|
|
119
|
+
worker_id: data["worker_id"], contexts: ContextMap.from_hash(data["contexts"]))
|
|
150
120
|
end
|
|
151
121
|
end
|
|
152
122
|
|
|
153
|
-
|
|
123
|
+
private
|
|
154
124
|
|
|
155
|
-
def
|
|
125
|
+
def initialize_resultset_metadata(tracked_files, run_id, worker_id, contexts)
|
|
156
126
|
@tracked_files = tracked_files.to_a
|
|
157
127
|
@run_id = run_id
|
|
158
128
|
@worker_id = worker_id
|
|
129
|
+
@contexts = contexts
|
|
159
130
|
end
|
|
160
131
|
|
|
161
|
-
def warn_about_missing_source_files(missing
|
|
132
|
+
def warn_about_missing_source_files(missing)
|
|
162
133
|
return if missing.empty?
|
|
163
134
|
|
|
164
|
-
# Emit only from the process that writes the final report
|
|
165
|
-
# result is rebuilt in every parallel worker
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
# and in many parallel runners the final-report process is itself a
|
|
171
|
-
# worker, so a print_errors gate would suppress the one warning we want.
|
|
172
|
-
# See issues #980 and #1171.
|
|
135
|
+
# Emit only from the process that writes the final report: the merged
|
|
136
|
+
# result is rebuilt in every parallel worker, so without this gate the
|
|
137
|
+
# warning prints once per worker. Intentionally not gated on
|
|
138
|
+
# print_errors, because the default at_fork sets print_errors false on
|
|
139
|
+
# workers and in many parallel runners the final-report process is itself
|
|
140
|
+
# a worker (#980, #1171).
|
|
173
141
|
return unless SimpleCov.final_result_process?
|
|
174
142
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
every_entry_dropped: @files.empty? && missing.size == input_size
|
|
179
|
-
).warn!
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
# A live result's criterion keys are Symbols (`:lines`, `:branches`),
|
|
183
|
-
# while entries parsed back from `.resultset.json` carry Strings, and
|
|
184
|
-
# the combiners read only String keys. Serialize with String keys so a
|
|
185
|
-
# live result merged against a stored entry contributes its counts
|
|
186
|
-
# instead of being silently dropped for every shared file.
|
|
187
|
-
def coverage
|
|
188
|
-
original_result.slice(*filenames).transform_values do |file_coverage|
|
|
189
|
-
file_coverage.transform_keys(&:to_s)
|
|
190
|
-
end
|
|
143
|
+
# Every built file survives (only the missing ones are dropped), so an
|
|
144
|
+
# empty file list is exactly the "nothing was found" case.
|
|
145
|
+
MissingSourceFilesReporter.new(missing, every_entry_dropped: @files.empty?).warn!
|
|
191
146
|
end
|
|
192
147
|
|
|
193
|
-
# Applies the given filter chain to `@files`, dropping each source
|
|
194
|
-
# file that any filter matches.
|
|
195
148
|
def apply_filters!(filters)
|
|
196
149
|
filters.each do |filter|
|
|
197
|
-
@files =
|
|
150
|
+
@files = FileList.new(@files.reject { |source_file| filter.matches?(source_file) })
|
|
198
151
|
end
|
|
199
152
|
end
|
|
200
153
|
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
# this is a no-op, preserving the historical "everything required, then
|
|
204
|
-
# filtered" universe.
|
|
154
|
+
# With no cover matchers configured this is a no-op, preserving the
|
|
155
|
+
# historical "everything required, then filtered" universe.
|
|
205
156
|
def apply_cover_filters!(cover_filters)
|
|
206
157
|
return if cover_filters.empty?
|
|
207
158
|
|
|
208
|
-
@files =
|
|
159
|
+
@files = FileList.new(
|
|
209
160
|
@files.select { |source_file| cover_filters.any? { |filter| filter.matches?(source_file) } }
|
|
210
161
|
)
|
|
211
162
|
end
|
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
require "coverage"
|
|
4
4
|
|
|
5
5
|
module SimpleCov
|
|
6
|
-
#
|
|
7
|
-
# Responsible for adapting the format of the coverage result whether it's default or with statistics
|
|
8
|
-
#
|
|
9
6
|
class ResultAdapter
|
|
10
7
|
attr_reader :result
|
|
11
8
|
|
|
@@ -25,14 +22,12 @@ module SimpleCov
|
|
|
25
22
|
end
|
|
26
23
|
end
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
private
|
|
29
26
|
|
|
30
27
|
# Pre-0.18 resultsets pointed each filename straight at a line-coverage
|
|
31
|
-
# array; everything since uses the `{lines:, branches:, methods:}`
|
|
32
|
-
# shape. Newer entries also need their methods and branches tables
|
|
33
|
-
# massaged before downstream code reports or merges them.
|
|
28
|
+
# array; everything since uses the `{lines:, branches:, methods:}` shape.
|
|
34
29
|
def adapt_one(file_name, cover_statistic)
|
|
35
|
-
return {"lines" => cover_statistic} if cover_statistic.
|
|
30
|
+
return {"lines" => cover_statistic} if cover_statistic.instance_of?(Array)
|
|
36
31
|
|
|
37
32
|
adapt_oneshot_lines_if_needed(file_name, cover_statistic)
|
|
38
33
|
normalize_method_keys(cover_statistic)
|
|
@@ -40,42 +35,33 @@ module SimpleCov
|
|
|
40
35
|
cover_statistic
|
|
41
36
|
end
|
|
42
37
|
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# "#<Class:0x00007ff19ab24790>" get inconsistent addresses across runs
|
|
46
|
-
#
|
|
47
|
-
# CRuby: 16; some JVM/TruffleRuby formats may differ), so match any
|
|
48
|
-
# length of hex digits and collapse to a single placeholder.
|
|
38
|
+
# Normalizes memory addresses in method coverage keys so results from
|
|
39
|
+
# different processes can be merged: anonymous class names like
|
|
40
|
+
# "#<Class:0x00007ff19ab24790>" get inconsistent addresses across runs, and
|
|
41
|
+
# address widths vary by runtime.
|
|
49
42
|
ADDRESS_PATTERN = /0x\h+/
|
|
50
43
|
private_constant :ADDRESS_PATTERN
|
|
51
44
|
|
|
52
45
|
ADDRESS_PLACEHOLDER = "0x0"
|
|
53
46
|
private_constant :ADDRESS_PLACEHOLDER
|
|
54
47
|
|
|
55
|
-
#
|
|
48
|
+
# Strips the `#<Class:Foo>` wrapper Ruby's Coverage adds to singleton-class
|
|
56
49
|
# method keys. `module_function` and class methods get recorded both as
|
|
57
|
-
# singleton
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# constants — anonymous-class addresses like `#<Class:0x0>` are left
|
|
61
|
-
# alone (handled by ADDRESS_PATTERN above).
|
|
50
|
+
# singleton and instance/module entries pointing at the same source
|
|
51
|
+
# location, and only one of the two is ever reachable at runtime. Only
|
|
52
|
+
# named constants: anonymous-class addresses are left to ADDRESS_PATTERN.
|
|
62
53
|
SINGLETON_WRAPPER_PATTERN = /\A#<Class:([A-Z_][\w:]*)>\z/
|
|
63
54
|
private_constant :SINGLETON_WRAPPER_PATTERN
|
|
64
55
|
|
|
65
|
-
# Ruby's method coverage records one entry per
|
|
66
|
-
# source location: a block handed to `define_method`
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
# hits — otherwise each receiver or name whose generated copy never
|
|
75
|
-
# ran shows as a phantom uncovered method on a line whose line
|
|
76
|
-
# coverage is 100%. Regular `def`s map one location to one name, so
|
|
77
|
-
# they are unaffected. The first entry's (normalized) key is kept for
|
|
78
|
-
# display. See issue #1234.
|
|
56
|
+
# Ruby's method coverage records one entry per defined method, not per
|
|
57
|
+
# source location: a block handed to `define_method` from a shared code
|
|
58
|
+
# path yields a separate `[receiver, name, location]` entry for every class
|
|
59
|
+
# it's defined on and every name it's defined under, all pointing at the
|
|
60
|
+
# same source. A file-based report can only express "was the method at this
|
|
61
|
+
# location ever executed", so entries are aggregated by location alone,
|
|
62
|
+
# summing hits. Otherwise each generated copy that never ran shows as a
|
|
63
|
+
# phantom uncovered method on a line whose line coverage is 100% (#1234).
|
|
64
|
+
# The first entry's normalized key is kept for display.
|
|
79
65
|
def normalize_method_keys(cover_statistic)
|
|
80
66
|
methods = cover_statistic[:methods]
|
|
81
67
|
return unless methods
|
|
@@ -90,72 +76,74 @@ module SimpleCov
|
|
|
90
76
|
end
|
|
91
77
|
|
|
92
78
|
def normalize_method_key(key)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
79
|
+
receiver, *rest = key
|
|
80
|
+
normalized_receiver = class_display_name(receiver)
|
|
81
|
+
.gsub(ADDRESS_PATTERN, ADDRESS_PLACEHOLDER)
|
|
82
|
+
.sub(SINGLETON_WRAPPER_PATTERN, '\1')
|
|
83
|
+
[normalized_receiver, *rest]
|
|
98
84
|
end
|
|
99
85
|
|
|
100
|
-
# Rendering a class name can execute user code: a singleton class's
|
|
101
|
-
#
|
|
102
|
-
#
|
|
86
|
+
# Rendering a class name can execute user code: a singleton class's `to_s`
|
|
87
|
+
# renders its attached object via `#inspect`, which a module can shadow
|
|
88
|
+
# with an incompatible signature (Liquid::Utils defines
|
|
103
89
|
# `inspect(value, max_depth = 2)` as a module_function, so rendering
|
|
104
|
-
# `#<Class:Liquid::Utils>` raises ArgumentError). A coverage report
|
|
105
|
-
#
|
|
106
|
-
# singleton wrapper from `Module#name` via bound methods (which cannot
|
|
107
|
-
# be shadowed), falling back to the address form, which
|
|
108
|
-
# ADDRESS_PATTERN then normalizes. See issue #1236.
|
|
90
|
+
# `#<Class:Liquid::Utils>` raises ArgumentError). A coverage report must
|
|
91
|
+
# never crash the host suite over that (#1236).
|
|
109
92
|
def class_display_name(klass)
|
|
110
93
|
klass.to_s
|
|
111
|
-
rescue
|
|
94
|
+
rescue
|
|
112
95
|
singleton_wrapper_name(klass) || Object.instance_method(:to_s).bind_call(klass)
|
|
113
96
|
end
|
|
114
97
|
|
|
98
|
+
# `singleton_class?` is a Module method, so a receiver that is neither a
|
|
99
|
+
# class nor a module has to be turned away first. Anything that then
|
|
100
|
+
# answers `singleton_class?` truthily is a Class, so `attached_object`
|
|
101
|
+
# applies, reached through a bound method so shadowing cannot divert it.
|
|
115
102
|
def singleton_wrapper_name(klass)
|
|
116
|
-
return nil unless klass.is_a?(
|
|
103
|
+
return nil unless klass.is_a?(Module) && klass.singleton_class?
|
|
117
104
|
|
|
118
|
-
attached =
|
|
119
|
-
# simplecov:disable branch — CRuby only reaches the rescue via a
|
|
120
|
-
# Module/Class attached object (instance singletons render from the
|
|
121
|
-
# class name chain without calling user inspect), so the non-Module
|
|
122
|
-
# arm is defensive.
|
|
105
|
+
attached = Class.instance_method(:attached_object).bind_call(klass)
|
|
123
106
|
name = Module.instance_method(:name).bind_call(attached) if attached.is_a?(Module)
|
|
124
|
-
# simplecov:enable branch
|
|
125
107
|
name && "#<Class:#{name}>"
|
|
126
108
|
end
|
|
127
109
|
|
|
128
110
|
# Ruby's eval coverage records a fresh set of branch entries for every
|
|
129
|
-
#
|
|
130
|
-
# classes
|
|
131
|
-
#
|
|
132
|
-
# the same file, each counting only the renders that flowed through that
|
|
111
|
+
# compile of an eval'd string: a template rendered through multiple view
|
|
112
|
+
# classes yields several `[:if, id, location]` conditions at identical
|
|
113
|
+
# coordinates, each counting only the renders that flowed through that
|
|
133
114
|
# compile. Reported as-is they inflate the branch denominator and turn a
|
|
134
|
-
# side covered under a different compile into a phantom miss (
|
|
135
|
-
#
|
|
136
|
-
#
|
|
137
|
-
#
|
|
138
|
-
# conditions at the same location, so this is a no-op outside eval.
|
|
115
|
+
# side covered under a different compile into a phantom miss (#1235).
|
|
116
|
+
# Absorbing into an empty table dedups, since BranchesCombiner keys arms on
|
|
117
|
+
# location identity. Regular source can never produce two conditions at the
|
|
118
|
+
# same location, so this is a no-op outside eval.
|
|
139
119
|
def aggregate_duplicated_branches(cover_statistic)
|
|
140
120
|
branches = cover_statistic[:branches]
|
|
141
121
|
return unless branches
|
|
142
122
|
|
|
143
|
-
|
|
123
|
+
combiner = Combine::BranchesCombiner
|
|
124
|
+
cover_statistic[:branches] = combiner.materialize(combiner.absorb({}, branches))
|
|
144
125
|
end
|
|
145
126
|
|
|
146
127
|
def adapt_oneshot_lines_if_needed(file_name, cover_statistic)
|
|
147
128
|
return unless cover_statistic.key?(:oneshot_lines)
|
|
148
129
|
|
|
149
130
|
oneshot_lines = cover_statistic.delete(:oneshot_lines)
|
|
150
|
-
line_stub
|
|
131
|
+
line_stub = build_line_stub(file_name)
|
|
151
132
|
oneshot_lines.each { |covered_line| line_stub[covered_line - 1] = 1 }
|
|
152
133
|
cover_statistic[:lines] = line_stub
|
|
153
134
|
end
|
|
154
135
|
|
|
155
|
-
|
|
136
|
+
# A file that has vanished or no longer parses has no stub to build from,
|
|
137
|
+
# and neither does a runtime without `Coverage.line_stub` (TruffleRuby), so
|
|
138
|
+
# start from nothing: the assignments in `adapt_oneshot_lines_if_needed`
|
|
139
|
+
# grow the array to the highest covered line, which is as far as the
|
|
140
|
+
# oneshot data reaches anyway.
|
|
141
|
+
def build_line_stub(file_name)
|
|
142
|
+
return [] unless Coverage.respond_to?(:line_stub)
|
|
143
|
+
|
|
156
144
|
Coverage.line_stub(file_name)
|
|
157
145
|
rescue Errno::ENOENT, SyntaxError
|
|
158
|
-
|
|
146
|
+
[]
|
|
159
147
|
end
|
|
160
148
|
end
|
|
161
149
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ResultMerger
|
|
5
|
+
module Contexts
|
|
6
|
+
extend self
|
|
7
|
+
|
|
8
|
+
# The same union-or-drop rule the cross-suite merge applies: the union when
|
|
9
|
+
# both sides recorded a map, no map at all when either did not, since a
|
|
10
|
+
# partial map would present one runner's tests as the pair's. `entry` starts
|
|
11
|
+
# as a copy of `incoming`, so the drop has to remove the key rather than
|
|
12
|
+
# merely decline to add it.
|
|
13
|
+
def carry(entry, existing, incoming)
|
|
14
|
+
ours = ContextMap.from_hash(existing["contexts"])
|
|
15
|
+
theirs = ContextMap.from_hash(incoming["contexts"])
|
|
16
|
+
return entry.except("contexts") unless ours && theirs
|
|
17
|
+
|
|
18
|
+
entry.merge("contexts" => ours.absorb(theirs).to_h)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|