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
|
@@ -2,36 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
class SourceFile
|
|
5
|
-
# Reads a source file into an array of lines, honoring the source's
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# (see #866).
|
|
5
|
+
# Reads a source file into an array of lines, honoring the source's shebang
|
|
6
|
+
# and `coding:` magic comment when present. Always transcodes to UTF-8 with
|
|
7
|
+
# invalid and undefined bytes replaced, both for JRuby compatibility and to
|
|
8
|
+
# keep encoding shenanigans in one place (#866).
|
|
10
9
|
module SourceLoader
|
|
11
10
|
SHEBANG_REGEX = /\A#!/
|
|
12
11
|
RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
extend self
|
|
15
14
|
|
|
16
15
|
def call(filename)
|
|
17
16
|
lines = [] #: Array[String]
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
current_line = scrub_invalid(file.gets)
|
|
21
|
-
|
|
22
|
-
if current_line && shebang?(current_line)
|
|
23
|
-
lines << current_line
|
|
24
|
-
current_line = scrub_invalid(file.gets)
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
read_lines(file, lines, current_line)
|
|
17
|
+
open_file(filename, "rb:UTF-8") do |file|
|
|
18
|
+
read_lines(file, lines, scrub_invalid(file.gets))
|
|
28
19
|
end
|
|
29
20
|
end
|
|
30
21
|
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
# any
|
|
34
|
-
#
|
|
22
|
+
# mutant:disable — Ruby 4.0 removed Kernel#open's leading-pipe
|
|
23
|
+
# command mode, so no test can tell `File.open` from `open` here
|
|
24
|
+
# any more. The explicit receiver is kept: on older rubies it is
|
|
25
|
+
# what refuses to run a filename as a command.
|
|
26
|
+
def open_file(name, mode, &)
|
|
27
|
+
File.open(name, mode, &)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# A line read as UTF-8 can still carry invalid bytes, from a Latin-1 source
|
|
31
|
+
# file without a magic comment, say. They are replaced before any regex sees
|
|
32
|
+
# the line: the shebang and magic-comment checks would otherwise raise
|
|
33
|
+
# ArgumentError and take the report down.
|
|
35
34
|
def scrub_invalid(line)
|
|
36
35
|
return line if line.nil? || line.valid_encoding?
|
|
37
36
|
|
|
@@ -45,11 +44,17 @@ module SimpleCov
|
|
|
45
44
|
def read_lines(file, lines, current_line)
|
|
46
45
|
return lines unless current_line
|
|
47
46
|
|
|
47
|
+
if shebang?(current_line)
|
|
48
|
+
lines << current_line
|
|
49
|
+
current_line = scrub_invalid(file.gets)
|
|
50
|
+
return lines unless current_line
|
|
51
|
+
end
|
|
52
|
+
|
|
48
53
|
set_encoding_based_on_magic_comment(file, current_line)
|
|
49
54
|
lines.concat([current_line], ensure_remove_undefs(file.readlines))
|
|
50
55
|
end
|
|
51
56
|
|
|
52
|
-
#
|
|
57
|
+
# An encoding magic comment must be placed on the first line, except after a
|
|
53
58
|
# shebang.
|
|
54
59
|
def set_encoding_based_on_magic_comment(file, line)
|
|
55
60
|
if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
|
|
@@ -57,21 +62,26 @@ module SimpleCov
|
|
|
57
62
|
end
|
|
58
63
|
end
|
|
59
64
|
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# invalid/undef options on `file.set_encoding` doesn't work
|
|
63
|
-
# properly, and this also works around a JRuby incompatibility)
|
|
64
|
-
# and scrub UTF-8-tagged lines that carry invalid bytes, which
|
|
65
|
-
# would otherwise raise from every regex the classifier runs.
|
|
65
|
+
# Setting invalid/undef options on `file.set_encoding` doesn't work
|
|
66
|
+
# properly, and transcoding here also works around a JRuby incompatibility.
|
|
66
67
|
def ensure_remove_undefs(file_lines)
|
|
67
|
-
file_lines.each
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
file_lines.each { |line| make_utf8(line) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# mutant:disable — on CRuby the two arms answer alike, because
|
|
72
|
+
# converting to the encoding a string already carries scrubs it
|
|
73
|
+
# when `invalid: :replace` is given. The arms are kept apart for
|
|
74
|
+
# the engines where that conversion is the documented no-op it
|
|
75
|
+
# reads as, which is the same reason the transcode is here rather
|
|
76
|
+
# than on `file.set_encoding`.
|
|
77
|
+
# Encodings are singletons, so identity is the whole of the question. A line
|
|
78
|
+
# already tagged UTF-8 has nothing to transcode, only invalid bytes to
|
|
79
|
+
# replace, and scrubbing a line that has none of those leaves it alone.
|
|
80
|
+
def make_utf8(line)
|
|
81
|
+
if line.encoding.equal?(Encoding::UTF_8)
|
|
82
|
+
line.scrub!
|
|
83
|
+
else
|
|
84
|
+
line.encode!("UTF-8", invalid: :replace, undef: :replace)
|
|
75
85
|
end
|
|
76
86
|
end
|
|
77
87
|
end
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
class SourceFile
|
|
5
|
-
# Builds the `CoverageStatistics` triple
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# downstream consumers don't have to special-case enable-state.
|
|
5
|
+
# Builds the `CoverageStatistics` triple for a SourceFile regardless of which
|
|
6
|
+
# criteria were enabled during the run: disabled or empty criteria collapse to
|
|
7
|
+
# 0/0/0 so downstream consumers don't have to special-case enable-state.
|
|
9
8
|
class Statistics
|
|
10
9
|
def initialize(source_file)
|
|
11
10
|
@source_file = source_file
|
|
@@ -19,7 +18,7 @@ module SimpleCov
|
|
|
19
18
|
}
|
|
20
19
|
end
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
private
|
|
23
22
|
|
|
24
23
|
def line_statistics
|
|
25
24
|
{
|
|
@@ -31,13 +30,15 @@ module SimpleCov
|
|
|
31
30
|
}
|
|
32
31
|
end
|
|
33
32
|
|
|
34
|
-
# Files added via track_files but never loaded have no branch
|
|
35
|
-
#
|
|
33
|
+
# Files added via track_files but never loaded have no branch or method data,
|
|
34
|
+
# and report 0% instead of the empty-set default of 100% (#902). A file with
|
|
35
|
+
# missed entries and none covered already computes to 0%, so only a file that
|
|
36
|
+
# really has covered entries keeps its computed percentage.
|
|
36
37
|
def branch_statistics
|
|
37
38
|
sf = @source_file
|
|
38
39
|
covered = sf.covered_branches
|
|
39
40
|
missed = sf.missed_branches
|
|
40
|
-
percent = 0.0 if sf.not_loaded? && covered.empty?
|
|
41
|
+
percent = 0.0 if sf.not_loaded? && covered.empty?
|
|
41
42
|
|
|
42
43
|
{branch: coverage_statistics(covered, missed, percent: percent)}
|
|
43
44
|
end
|
|
@@ -46,7 +47,7 @@ module SimpleCov
|
|
|
46
47
|
sf = @source_file
|
|
47
48
|
covered = sf.covered_methods
|
|
48
49
|
missed = sf.missed_methods
|
|
49
|
-
percent = 0.0 if sf.not_loaded? && covered.empty?
|
|
50
|
+
percent = 0.0 if sf.not_loaded? && covered.empty?
|
|
50
51
|
|
|
51
52
|
{method: coverage_statistics(covered, missed, percent: percent)}
|
|
52
53
|
end
|
|
@@ -12,17 +12,10 @@ require_relative "source_file/method_builder"
|
|
|
12
12
|
require_relative "source_file/statistics"
|
|
13
13
|
|
|
14
14
|
module SimpleCov
|
|
15
|
-
#
|
|
16
|
-
# Representation of a source file including it's coverage data, source code,
|
|
17
|
-
# source lines and featuring helpers to interpret that data.
|
|
18
|
-
#
|
|
19
15
|
class SourceFile
|
|
20
16
|
include BuilderContext
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
attr_reader :filename
|
|
24
|
-
# The array of coverage data received from the Coverage.result
|
|
25
|
-
attr_reader :coverage_data
|
|
18
|
+
attr_reader :filename, :coverage_data
|
|
26
19
|
|
|
27
20
|
def initialize(filename, coverage_data, loaded: true)
|
|
28
21
|
@filename = filename
|
|
@@ -30,72 +23,57 @@ module SimpleCov
|
|
|
30
23
|
@loaded = loaded
|
|
31
24
|
end
|
|
32
25
|
|
|
33
|
-
# The path to this source file relative to the projects directory
|
|
34
26
|
def project_filename
|
|
35
27
|
@filename.delete_prefix(SimpleCov.root).sub(%r{\A[/\\]}, "")
|
|
36
28
|
end
|
|
37
29
|
|
|
38
|
-
#
|
|
39
|
-
# Intentionally read lazily to suppress reading unused source code.
|
|
30
|
+
# Read lazily to suppress reading unused source code.
|
|
40
31
|
def src
|
|
41
32
|
@src ||= SourceLoader.call(filename)
|
|
42
33
|
end
|
|
43
|
-
|
|
34
|
+
alias_method :source, :src
|
|
44
35
|
|
|
45
|
-
# Returns a hash keyed by every supported coverage criterion. Each
|
|
46
|
-
# value is a CoverageStatistics, even for criteria that weren't
|
|
47
|
-
# enabled during the run — those collapse to 0/0/0. Consumers
|
|
48
|
-
# (FileList, formatters) decide which keys to surface based on
|
|
49
|
-
# `SimpleCov.coverage_criterion_enabled?`.
|
|
50
36
|
# The per-criterion coverage statistics for this file. With no argument
|
|
51
|
-
#
|
|
52
|
-
#
|
|
37
|
+
# answers the `{line:, branch:, method:}` Hash, and with a criterion symbol
|
|
38
|
+
# that one CoverageStatistics. Every criterion is present even when it
|
|
39
|
+
# wasn't enabled during the run, collapsed to 0/0/0; `FileList` is the
|
|
40
|
+
# layer that filters to the enabled set.
|
|
53
41
|
def coverage_statistics(criterion = nil)
|
|
54
42
|
stats = (@coverage_statistics ||= Statistics.new(self).call)
|
|
55
43
|
criterion ? stats[criterion] : stats
|
|
56
44
|
end
|
|
57
45
|
|
|
58
|
-
# Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,
|
|
59
|
-
# and thus including coverage data. Aliased as :source_lines
|
|
60
46
|
def lines
|
|
61
47
|
@lines ||= LineBuilder.new(self).call
|
|
62
48
|
end
|
|
63
|
-
|
|
49
|
+
alias_method :source_lines, :lines
|
|
64
50
|
|
|
65
|
-
# Returns all covered lines as SimpleCov::SourceFile::Line
|
|
66
51
|
def covered_lines
|
|
67
52
|
@covered_lines ||= lines.select(&:covered?)
|
|
68
53
|
end
|
|
69
54
|
|
|
70
|
-
# Returns all lines that should have been, but were not covered
|
|
71
|
-
# as instances of SimpleCov::SourceFile::Line
|
|
72
55
|
def missed_lines
|
|
73
56
|
@missed_lines ||= lines.select(&:missed?)
|
|
74
57
|
end
|
|
75
58
|
|
|
76
|
-
# Returns all lines that are not relevant for coverage as
|
|
77
|
-
# SimpleCov::SourceFile::Line instances
|
|
78
59
|
def never_lines
|
|
79
60
|
@never_lines ||= lines.select(&:never?)
|
|
80
61
|
end
|
|
81
62
|
|
|
82
|
-
# Returns all lines that were skipped as SimpleCov::SourceFile::Line instances
|
|
83
63
|
def skipped_lines
|
|
84
64
|
@skipped_lines ||= lines.select(&:skipped?)
|
|
85
65
|
end
|
|
86
66
|
|
|
87
|
-
# Returns the number of relevant lines (covered + missed)
|
|
88
67
|
def lines_of_code
|
|
89
68
|
coverage_statistics[:line]&.total || 0
|
|
90
69
|
end
|
|
91
70
|
|
|
92
|
-
#
|
|
71
|
+
# Answers nil for a number this file has no line for.
|
|
93
72
|
def line(number)
|
|
94
|
-
lines
|
|
73
|
+
lines.at(number - 1)
|
|
95
74
|
end
|
|
96
75
|
|
|
97
|
-
#
|
|
98
|
-
# default). Returns nil if the criterion was not measured.
|
|
76
|
+
# Nil if the criterion was not measured.
|
|
99
77
|
def covered_percent(criterion = :line)
|
|
100
78
|
coverage_statistics(criterion)&.percent
|
|
101
79
|
end
|
|
@@ -104,15 +82,16 @@ module SimpleCov
|
|
|
104
82
|
coverage_statistics(criterion)&.strength
|
|
105
83
|
end
|
|
106
84
|
|
|
85
|
+
# Every line the file has is irrelevant, a file with no lines at all
|
|
86
|
+
# included.
|
|
107
87
|
def no_lines?
|
|
108
|
-
lines.
|
|
88
|
+
lines.all?(&:never?)
|
|
109
89
|
end
|
|
110
90
|
|
|
111
91
|
def relevant_lines
|
|
112
92
|
lines.size - never_lines.size - skipped_lines.size
|
|
113
93
|
end
|
|
114
94
|
|
|
115
|
-
# Return all the branches inside current source file
|
|
116
95
|
def branches
|
|
117
96
|
@branches ||= BranchBuilder.new(self).call
|
|
118
97
|
end
|
|
@@ -121,33 +100,28 @@ module SimpleCov
|
|
|
121
100
|
total_branches.empty?
|
|
122
101
|
end
|
|
123
102
|
|
|
124
|
-
# DEPRECATED: use `covered_percent(:branch)`.
|
|
125
103
|
def branches_coverage_percent
|
|
126
|
-
|
|
127
|
-
|
|
104
|
+
Deprecation.warn("`SimpleCov::SourceFile#branches_coverage_percent` is deprecated. " \
|
|
105
|
+
"Use `covered_percent(:branch)`.")
|
|
128
106
|
covered_percent(:branch)
|
|
129
107
|
end
|
|
130
108
|
|
|
131
|
-
# Return the relevant branches to source file
|
|
132
109
|
def total_branches
|
|
133
110
|
@total_branches ||= covered_branches + missed_branches
|
|
134
111
|
end
|
|
135
112
|
|
|
136
|
-
# Return hash with key of line number and branch coverage count as value
|
|
137
113
|
def branches_report
|
|
138
114
|
@branches_report ||=
|
|
139
115
|
branches.reject(&:skipped?).group_by(&:report_line).transform_values { |bs| bs.map(&:report) }
|
|
140
116
|
end
|
|
141
117
|
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
# report.
|
|
118
|
+
# A tree schema, because some conditions like `case` may have an additional
|
|
119
|
+
# `else` that isn't declared in code but is given by default by the
|
|
120
|
+
# coverage report.
|
|
146
121
|
def covered_branches
|
|
147
122
|
@covered_branches ||= branches.select(&:covered?)
|
|
148
123
|
end
|
|
149
124
|
|
|
150
|
-
# Select the missed branches with coverage equal to zero
|
|
151
125
|
def missed_branches
|
|
152
126
|
@missed_branches ||= branches.select(&:missed?)
|
|
153
127
|
end
|
|
@@ -156,12 +130,10 @@ module SimpleCov
|
|
|
156
130
|
branches_report.fetch(line_number, [])
|
|
157
131
|
end
|
|
158
132
|
|
|
159
|
-
# Check if any branches missing on given line number
|
|
160
133
|
def line_with_missed_branch?(line_number)
|
|
161
134
|
branches_for_line(line_number).any? { |_type, count| count.zero? }
|
|
162
135
|
end
|
|
163
136
|
|
|
164
|
-
# Return all methods detected in this source file
|
|
165
137
|
def methods
|
|
166
138
|
@methods ||= MethodBuilder.new(self).call
|
|
167
139
|
end
|
|
@@ -174,14 +146,12 @@ module SimpleCov
|
|
|
174
146
|
@missed_methods ||= methods.select(&:missed?)
|
|
175
147
|
end
|
|
176
148
|
|
|
177
|
-
# DEPRECATED: use `covered_percent(:method)`.
|
|
178
149
|
def methods_coverage_percent
|
|
179
|
-
|
|
180
|
-
|
|
150
|
+
Deprecation.warn("`SimpleCov::SourceFile#methods_coverage_percent` is deprecated. " \
|
|
151
|
+
"Use `covered_percent(:method)`.")
|
|
181
152
|
covered_percent(:method)
|
|
182
153
|
end
|
|
183
154
|
|
|
184
|
-
# Whether this file was added via track_files but never loaded/required.
|
|
185
155
|
def not_loaded?
|
|
186
156
|
!@loaded
|
|
187
157
|
end
|
|
@@ -2,39 +2,32 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module StaticCoverageExtractor
|
|
5
|
-
# Detects the `if` / `unless` / ternary conditions CRuby folds away.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# failure mode as #1226 / #1233.
|
|
5
|
+
# Detects the `if` / `unless` / ternary conditions CRuby folds away. When a
|
|
6
|
+
# condition is a statically-known-truthy/falsy literal the compiler
|
|
7
|
+
# eliminates the dead arm and Coverage emits no branch, so the extractor
|
|
8
|
+
# must not synthesize one either: the arm would be a phantom no loaded run
|
|
9
|
+
# can hit, the same unmergeable-tuple failure as #1226 / #1233.
|
|
11
10
|
module ConditionFolding
|
|
12
|
-
# CRuby 3.4 rebuilt the fold on the Prism compiler, and the
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
# on 3.2 the dead arm's branch table entries survive the fold —
|
|
18
|
-
# parse.y instrumented branches before eliminating dead code — while
|
|
19
|
-
# its `def`s still never register.
|
|
11
|
+
# CRuby 3.4 rebuilt the fold on the Prism compiler, and the parse.y fold it
|
|
12
|
+
# replaced differed in three observable ways: `__FILE__` folded on
|
|
13
|
+
# 3.2/3.3; parentheses were transparent for every literal on 3.2; and on
|
|
14
|
+
# 3.2 the dead arm's branch table entries survive the fold while its
|
|
15
|
+
# `def`s still never register.
|
|
20
16
|
FOLDS_SOURCE_FILE = Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.4")
|
|
21
17
|
PARENS_ALWAYS_TRANSPARENT = Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.3")
|
|
22
18
|
DEAD_ARM_BRANCHES_SURVIVE = PARENS_ALWAYS_TRANSPARENT
|
|
23
|
-
# Container literals in discarded position are eliminated from 3.3
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
# (`[1]` goes, `[x]` stays). See `static_container_literal?`.
|
|
19
|
+
# Container literals in discarded position are eliminated from 3.3 on, but
|
|
20
|
+
# 3.3's compile.c elides a container whose contents are merely
|
|
21
|
+
# effect-free (`[x]`), while the Prism compiler demands fully static
|
|
22
|
+
# literals (`[1]` goes, `[x]` stays).
|
|
28
23
|
CONTAINER_CONTENTS_NEED_STATIC_LITERALS = !FOLDS_SOURCE_FILE
|
|
29
24
|
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# the compiler only folds what it can prove at compile time, and a
|
|
37
|
-
# method named `lambda` proves nothing.
|
|
25
|
+
# The literals that fold. `while` / `until` do NOT fold (`while true` is a
|
|
26
|
+
# real branch), so only the if-like visitors consult this. Regexp and
|
|
27
|
+
# Range literals are excluded on purpose: as conditions they mean
|
|
28
|
+
# `=~ $_` / flip-flop, which Coverage does branch on. `[]`, `{}`, and
|
|
29
|
+
# interpolated strings do not fold either, and `->` folds while a
|
|
30
|
+
# `lambda` call does not: a method named `lambda` proves nothing.
|
|
38
31
|
# simplecov:disable branch — which arm runs is fixed by the running Ruby's version
|
|
39
32
|
STATIC_CONDITION_TYPES = [
|
|
40
33
|
::Prism::IntegerNode, ::Prism::FloatNode, ::Prism::RationalNode,
|
|
@@ -45,27 +38,19 @@ module SimpleCov
|
|
|
45
38
|
].freeze
|
|
46
39
|
# simplecov:enable branch
|
|
47
40
|
|
|
48
|
-
# The literals whose fold eliminates the *then* side: `if false` /
|
|
49
|
-
# `if nil` keep only the else arm. Every other folded literal is
|
|
50
|
-
# truthy and keeps only the then arm.
|
|
51
41
|
FALSY_CONDITION_TYPES = [::Prism::FalseNode, ::Prism::NilNode].freeze
|
|
52
42
|
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
# while every other literal folds parenthesized or not. `__FILE__`
|
|
58
|
-
# is opaque like other strings on the Rubies that fold it at all.
|
|
59
|
-
# Consulted only when PARENS_ALWAYS_TRANSPARENT is false.
|
|
43
|
+
# CRuby folds `if nil`, `if "x"`, and `if -> {}` but keeps a real branch
|
|
44
|
+
# for `if (nil)`, `if ("x")`, and `if (-> {})`, while every other literal
|
|
45
|
+
# folds parenthesized or not. Consulted only when parentheses are not
|
|
46
|
+
# always transparent.
|
|
60
47
|
PAREN_OPAQUE_TYPES = [
|
|
61
48
|
::Prism::NilNode, ::Prism::StringNode, ::Prism::LambdaNode, ::Prism::SourceFileNode
|
|
62
49
|
].freeze
|
|
63
50
|
|
|
64
|
-
#
|
|
65
|
-
# multi-statement paren condition (`if (1; 2)`) folds by its last
|
|
51
|
+
# A multi-statement paren condition (`if (1; 2)`) folds by its last
|
|
66
52
|
# expression only when every leading statement is eliminated when
|
|
67
|
-
# discarded, and these
|
|
68
|
-
# always are. Pinned against real Coverage on 3.2 through 4.0.
|
|
53
|
+
# discarded, and these always are, bare or composing an Array/Hash/Range.
|
|
69
54
|
STATIC_LITERAL_LEAF_TYPES = [
|
|
70
55
|
::Prism::IntegerNode, ::Prism::FloatNode, ::Prism::RationalNode,
|
|
71
56
|
::Prism::ImaginaryNode, ::Prism::StringNode, ::Prism::SymbolNode,
|
|
@@ -73,11 +58,9 @@ module SimpleCov
|
|
|
73
58
|
::Prism::SourceLineNode, ::Prism::SourceFileNode, ::Prism::SourceEncodingNode, ::Prism::RegularExpressionNode
|
|
74
59
|
].freeze
|
|
75
60
|
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
# Anything that can raise or run hooks (constants, globals, calls,
|
|
80
|
-
# writes) is never eliminated and keeps the branch real.
|
|
61
|
+
# `self` is eliminated by every supported compiler; local/ivar/defined?
|
|
62
|
+
# elimination arrived with the Prism-era compilers. Anything that can
|
|
63
|
+
# raise or run hooks is never eliminated and keeps the branch real.
|
|
81
64
|
PRISM_ERA_ELIMINABLE_READS = [
|
|
82
65
|
::Prism::LocalVariableReadNode, ::Prism::InstanceVariableReadNode, ::Prism::DefinedNode
|
|
83
66
|
].freeze
|
|
@@ -89,28 +72,21 @@ module SimpleCov
|
|
|
89
72
|
].freeze
|
|
90
73
|
# simplecov:enable branch
|
|
91
74
|
|
|
92
|
-
|
|
75
|
+
private
|
|
93
76
|
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
# code, even inside dead `def` bodies and lambdas) while its
|
|
99
|
-
# methods never register, so the dead arm is visited with method
|
|
100
|
-
# collection suppressed.
|
|
77
|
+
# On 3.2 the dead arm's branch table entries survive the fold (parse.y
|
|
78
|
+
# instrumented branches before eliminating dead code) while its methods
|
|
79
|
+
# never register, so the dead arm is visited with method collection
|
|
80
|
+
# suppressed.
|
|
101
81
|
def visit_folded_arms(verdict, truthy_arm, falsy_arm)
|
|
102
|
-
live, dead = verdict
|
|
82
|
+
live, dead = verdict.equal?(:truthy) ? [truthy_arm, falsy_arm] : [falsy_arm, truthy_arm]
|
|
103
83
|
visit(live)
|
|
104
|
-
# simplecov:disable branch — the taken arm is fixed by the running Ruby's version
|
|
105
84
|
visit_dead_arm(dead) if DEAD_ARM_BRANCHES_SURVIVE
|
|
106
|
-
# simplecov:enable branch
|
|
107
85
|
end
|
|
108
86
|
|
|
109
|
-
# simplecov:disable — 3.2-only; unreachable on the modern dogfood Ruby
|
|
110
87
|
def visit_dead_arm(arm)
|
|
111
|
-
# Save/restore rather than set/clear: a fold nested inside this
|
|
112
|
-
#
|
|
113
|
-
# clear suppression for the rest of the outer dead arm.
|
|
88
|
+
# Save/restore rather than set/clear: a fold nested inside this dead arm
|
|
89
|
+
# re-enters here and must not clear suppression for the rest of it.
|
|
114
90
|
previous = @suppress_methods
|
|
115
91
|
begin
|
|
116
92
|
@suppress_methods = true
|
|
@@ -119,54 +95,42 @@ module SimpleCov
|
|
|
119
95
|
@suppress_methods = previous
|
|
120
96
|
end
|
|
121
97
|
end
|
|
122
|
-
# simplecov:enable
|
|
123
98
|
|
|
124
|
-
#
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
# so everything inside it (nested branches, methods) must go
|
|
128
|
-
# unvisited too, not just the folded condition's own tuple.
|
|
99
|
+
# `:truthy` or `:falsy` when the compiler folds, nil when it doesn't. The
|
|
100
|
+
# compiler eliminates the losing arm's entire subtree, so everything
|
|
101
|
+
# inside it must go unvisited too, not just the folded condition's tuple.
|
|
129
102
|
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
# deliberately not folded: `!` never folds, and `||` / `&&`
|
|
134
|
-
# constant-propagation diverges across Ruby versions, so matching
|
|
135
|
-
# it would trade a rare, version-specific gain for real risk.
|
|
103
|
+
# Compound forms (`!true`, `true || x`) are deliberately not folded: `!`
|
|
104
|
+
# never folds, and `||` / `&&` constant-propagation diverges across Ruby
|
|
105
|
+
# versions, so matching it would trade a rare gain for real risk.
|
|
136
106
|
def folded_condition(node)
|
|
137
107
|
unwrapped = unwrap_parentheses(node)
|
|
138
108
|
return nil unless foldable?(node, unwrapped)
|
|
139
109
|
|
|
140
|
-
FALSY_CONDITION_TYPES.any? { |type| unwrapped.
|
|
110
|
+
(FALSY_CONDITION_TYPES.any? { |type| unwrapped.instance_of?(type) }) ? :falsy : :truthy
|
|
141
111
|
end
|
|
142
112
|
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
# were seen through).
|
|
113
|
+
# `unwrapped` differing from `node` is what says parentheses were seen
|
|
114
|
+
# through.
|
|
146
115
|
def foldable?(node, unwrapped)
|
|
147
|
-
return false unless STATIC_CONDITION_TYPES.any? { |type| unwrapped.
|
|
116
|
+
return false unless STATIC_CONDITION_TYPES.any? { |type| unwrapped.instance_of?(type) }
|
|
148
117
|
|
|
149
|
-
# simplecov:disable — which of these lines runs is fixed by the
|
|
150
|
-
# running Ruby's version: on 3.2 the early return always fires and
|
|
151
|
-
# the opacity check below it is dead code, so it must be excluded
|
|
152
|
-
# from line coverage too, not only branch coverage.
|
|
153
118
|
return true if PARENS_ALWAYS_TRANSPARENT
|
|
154
119
|
|
|
155
|
-
unwrapped.equal?(node) || PAREN_OPAQUE_TYPES.none? { |type| unwrapped.
|
|
156
|
-
# simplecov:enable
|
|
120
|
+
unwrapped.equal?(node) || PAREN_OPAQUE_TYPES.none? { |type| unwrapped.instance_of?(type) }
|
|
157
121
|
end
|
|
158
122
|
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
# multi-statement bodies synthesized a phantom then/else pair no
|
|
163
|
-
# real run can ever hit.
|
|
123
|
+
# A multi-statement body (`if (1; 2)`) folds by its LAST expression, but
|
|
124
|
+
# only when the compiler eliminates every leading statement. Stopping at
|
|
125
|
+
# multi-statement bodies synthesized a phantom then/else pair.
|
|
164
126
|
def unwrap_parentheses(node)
|
|
165
127
|
# @type var current: untyped
|
|
166
128
|
current = node
|
|
167
|
-
while current.
|
|
129
|
+
while current.instance_of?(Prism::ParenthesesNode)
|
|
130
|
+
# Empty parentheses carry no statements node at all, so a
|
|
131
|
+
# body that is one always holds at least one statement.
|
|
168
132
|
body = current.body
|
|
169
|
-
break unless body.
|
|
133
|
+
break unless body.instance_of?(Prism::StatementsNode)
|
|
170
134
|
|
|
171
135
|
statements = body.body
|
|
172
136
|
break unless statements.take(statements.size - 1).all? { |leading| eliminable_when_discarded?(leading) }
|
|
@@ -176,40 +140,27 @@ module SimpleCov
|
|
|
176
140
|
current
|
|
177
141
|
end
|
|
178
142
|
|
|
179
|
-
# Whether the compiler compiles `node` to nothing when its value is
|
|
180
|
-
# discarded.
|
|
181
143
|
def eliminable_when_discarded?(node)
|
|
182
144
|
return true if static_container_literal?(node)
|
|
183
|
-
return true if ELIMINABLE_READ_TYPES.any? { |type| node.
|
|
145
|
+
return true if ELIMINABLE_READ_TYPES.any? { |type| node.instance_of?(type) }
|
|
184
146
|
|
|
185
|
-
node.
|
|
186
|
-
node.body.
|
|
147
|
+
node.instance_of?(Prism::ParenthesesNode) &&
|
|
148
|
+
node.body.instance_of?(Prism::StatementsNode) &&
|
|
187
149
|
node.body.body.all? { |statement| eliminable_when_discarded?(statement) }
|
|
188
150
|
end
|
|
189
151
|
|
|
190
|
-
# A scalar literal leaf, or an Array/Hash/Range whose contents pass
|
|
191
|
-
# the running compiler's contents rule (see
|
|
192
|
-
# CONTAINER_CONTENTS_NEED_STATIC_LITERALS). Container elimination
|
|
193
|
-
# only exists from 3.3 on.
|
|
194
152
|
def static_container_literal?(node)
|
|
195
|
-
return true if STATIC_LITERAL_LEAF_TYPES.any? { |type| node.
|
|
196
|
-
# simplecov:disable — which of these lines runs is fixed by the
|
|
197
|
-
# running Ruby's version: parse.y (3.2) never eliminates
|
|
198
|
-
# container literals, so there the early return always fires and
|
|
199
|
-
# the dispatch below is dead code.
|
|
153
|
+
return true if STATIC_LITERAL_LEAF_TYPES.any? { |type| node.instance_of?(type) }
|
|
200
154
|
return false if PARENS_ALWAYS_TRANSPARENT
|
|
201
155
|
|
|
202
156
|
static_container?(node)
|
|
203
|
-
# simplecov:enable
|
|
204
157
|
end
|
|
205
158
|
|
|
206
|
-
# The container dispatch, version-free: the predicate specs
|
|
207
|
-
# exercise it directly on every supported Ruby, 3.2 included.
|
|
208
159
|
def static_container?(node)
|
|
209
160
|
case node
|
|
210
|
-
when
|
|
211
|
-
when
|
|
212
|
-
when
|
|
161
|
+
when Prism::ArrayNode then static_array_literal?(node)
|
|
162
|
+
when Prism::HashNode then static_hash_literal?(node)
|
|
163
|
+
when Prism::RangeNode then static_range_literal?(node)
|
|
213
164
|
else false
|
|
214
165
|
end
|
|
215
166
|
end
|
|
@@ -220,7 +171,7 @@ module SimpleCov
|
|
|
220
171
|
|
|
221
172
|
def static_hash_literal?(node)
|
|
222
173
|
node.elements.all? do |element|
|
|
223
|
-
element.
|
|
174
|
+
element.instance_of?(Prism::AssocNode) &&
|
|
224
175
|
container_contents_eliminable?(element.key) && container_contents_eliminable?(element.value)
|
|
225
176
|
end
|
|
226
177
|
end
|
|
@@ -230,9 +181,7 @@ module SimpleCov
|
|
|
230
181
|
end
|
|
231
182
|
|
|
232
183
|
def container_contents_eliminable?(node)
|
|
233
|
-
# simplecov:disable branch — which arm runs is fixed by the running Ruby's version
|
|
234
184
|
CONTAINER_CONTENTS_NEED_STATIC_LITERALS ? static_container_literal?(node) : eliminable_when_discarded?(node)
|
|
235
|
-
# simplecov:enable branch
|
|
236
185
|
end
|
|
237
186
|
end
|
|
238
187
|
end
|