simplecov 0.22.0 → 1.3.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/LICENSE +1 -1
- data/README.md +173 -875
- data/exe/simplecov +8 -0
- data/lib/minitest/simplecov_plugin.rb +18 -7
- data/lib/simplecov/atomic_file.rb +67 -0
- data/lib/simplecov/autostart.rb +9 -0
- 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/annotations.rb +166 -0
- data/lib/simplecov/cli/badge/svg.rb +70 -0
- data/lib/simplecov/cli/badge.rb +62 -0
- data/lib/simplecov/cli/clean.rb +86 -0
- data/lib/simplecov/cli/command_helpers.rb +91 -0
- data/lib/simplecov/cli/completions/scripts.rb +114 -0
- data/lib/simplecov/cli/completions.rb +83 -0
- data/lib/simplecov/cli/coverage.rb +83 -0
- data/lib/simplecov/cli/coverage_file.rb +109 -0
- 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 +115 -0
- data/lib/simplecov/cli/dotfile.rb +156 -0
- 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 +115 -0
- data/lib/simplecov/cli/open.rb +45 -0
- data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
- data/lib/simplecov/cli/patch/output.rb +151 -0
- data/lib/simplecov/cli/patch.rb +175 -0
- data/lib/simplecov/cli/ratchet/output.rb +44 -0
- data/lib/simplecov/cli/ratchet.rb +83 -0
- data/lib/simplecov/cli/report.rb +91 -0
- data/lib/simplecov/cli/run.rb +35 -0
- data/lib/simplecov/cli/serve/report_preparer.rb +30 -0
- data/lib/simplecov/cli/serve/static_file_handler.rb +128 -0
- data/lib/simplecov/cli/serve.rb +88 -0
- 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 +33 -0
- data/lib/simplecov/cli/uncovered.rb +127 -0
- data/lib/simplecov/cli/usage.rb +153 -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 +114 -0
- data/lib/simplecov/color.rb +65 -0
- data/lib/simplecov/combine/branches_combiner.rb +44 -21
- data/lib/simplecov/combine/coverage_accumulator.rb +175 -0
- data/lib/simplecov/combine/identity_interner.rb +27 -0
- data/lib/simplecov/combine/interned_counts.rb +28 -0
- data/lib/simplecov/combine/lines_combiner.rb +35 -29
- data/lib/simplecov/combine/methods_combiner.rb +48 -0
- data/lib/simplecov/combine/results_combiner.rb +9 -49
- data/lib/simplecov/combine.rb +0 -24
- data/lib/simplecov/command_guesser.rb +99 -42
- data/lib/simplecov/configuration/baseline.rb +31 -0
- data/lib/simplecov/configuration/coverage.rb +184 -0
- data/lib/simplecov/configuration/coverage_criteria.rb +143 -0
- data/lib/simplecov/configuration/deprecations.rb +21 -0
- data/lib/simplecov/configuration/eval_coverage.rb +37 -0
- data/lib/simplecov/configuration/filters.rb +131 -0
- data/lib/simplecov/configuration/formatting.rb +129 -0
- data/lib/simplecov/configuration/groups.rb +48 -0
- data/lib/simplecov/configuration/history.rb +44 -0
- data/lib/simplecov/configuration/ignored_entries.rb +72 -0
- data/lib/simplecov/configuration/merging.rb +197 -0
- 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 +150 -0
- data/lib/simplecov/configuration/view_coverage.rb +37 -0
- data/lib/simplecov/configuration.rb +100 -443
- 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 +21 -0
- data/lib/simplecov/coverage_statistics.rb +24 -28
- data/lib/simplecov/coverage_violations.rb +254 -0
- data/lib/simplecov/current_run.rb +61 -0
- data/lib/simplecov/defaults.rb +20 -28
- data/lib/simplecov/deprecation.rb +63 -0
- data/lib/simplecov/directive/erb.rb +44 -0
- data/lib/simplecov/directive/haml.rb +33 -0
- data/lib/simplecov/directive/indented_template.rb +37 -0
- data/lib/simplecov/directive/slim.rb +35 -0
- data/lib/simplecov/directive/template.rb +29 -0
- data/lib/simplecov/directive.rb +152 -0
- data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
- data/lib/simplecov/exit_codes/check.rb +39 -0
- data/lib/simplecov/exit_codes/exit_code_handling.rb +24 -5
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +17 -67
- 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 +27 -0
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +20 -38
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +28 -0
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +37 -34
- data/lib/simplecov/exit_codes.rb +26 -0
- data/lib/simplecov/exit_handling.rb +163 -0
- data/lib/simplecov/file_list.rb +63 -48
- data/lib/simplecov/filter.rb +98 -42
- data/lib/simplecov/formatter/base.rb +100 -0
- data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +95 -0
- data/lib/simplecov/formatter/html_formatter/public/index.html +93 -0
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +153 -0
- data/lib/simplecov/formatter/html_formatter.rb +88 -0
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +122 -0
- data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +132 -0
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +97 -0
- data/lib/simplecov/formatter/json_formatter.rb +40 -0
- data/lib/simplecov/formatter/multi_formatter.rb +8 -10
- data/lib/simplecov/formatter/simple_formatter.rb +12 -15
- data/lib/simplecov/formatter.rb +21 -1
- data/lib/simplecov/group_names.rb +33 -0
- data/lib/simplecov/history.rb +115 -0
- data/lib/simplecov/last_run.rb +16 -5
- data/lib/simplecov/lines_classifier.rb +67 -19
- data/lib/simplecov/load_global_config.rb +12 -4
- data/lib/simplecov/parallel_adapters/base.rb +50 -0
- data/lib/simplecov/parallel_adapters/generic.rb +33 -0
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +72 -0
- data/lib/simplecov/parallel_adapters.rb +54 -0
- data/lib/simplecov/parallel_coordination.rb +124 -0
- data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
- data/lib/simplecov/parallel_result_merger.rb +171 -0
- data/lib/simplecov/process.rb +34 -12
- 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/bundler_filter.rb +1 -1
- data/lib/simplecov/profiles/hidden_filter.rb +1 -1
- data/lib/simplecov/profiles/rails.rb +13 -10
- data/lib/simplecov/profiles/root_filter.rb +2 -5
- data/lib/simplecov/profiles/strict.rb +22 -0
- data/lib/simplecov/profiles/test_frameworks.rb +1 -4
- data/lib/simplecov/profiles.rb +26 -19
- data/lib/simplecov/report_deferral.rb +45 -0
- data/lib/simplecov/report_stamp.rb +25 -0
- data/lib/simplecov/result/filter_config.rb +19 -0
- data/lib/simplecov/result/missing_source_files_reporter.rb +48 -0
- data/lib/simplecov/result/serialization.rb +43 -0
- data/lib/simplecov/result/source_file_builder.rb +46 -0
- data/lib/simplecov/result.rb +123 -47
- data/lib/simplecov/result_adapter.rb +130 -11
- data/lib/simplecov/result_merger/contexts.rb +22 -0
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +24 -0
- data/lib/simplecov/result_merger/resultset_file.rb +67 -0
- data/lib/simplecov/result_merger/resultset_run_identity.rb +65 -0
- data/lib/simplecov/result_merger/resultset_store.rb +62 -0
- data/lib/simplecov/result_merger/unloaded_files.rb +95 -0
- data/lib/simplecov/result_merger.rb +120 -124
- data/lib/simplecov/result_processing.rb +188 -0
- data/lib/simplecov/run_identity.rb +75 -0
- data/lib/simplecov/simulate_coverage.rb +72 -17
- data/lib/simplecov/source_file/branch.rb +8 -35
- data/lib/simplecov/source_file/branch_builder.rb +95 -0
- data/lib/simplecov/source_file/builder_context.rb +28 -0
- data/lib/simplecov/source_file/line.rb +17 -32
- data/lib/simplecov/source_file/line_builder.rb +37 -0
- data/lib/simplecov/source_file/method.rb +54 -0
- data/lib/simplecov/source_file/method_builder.rb +51 -0
- data/lib/simplecov/source_file/ruby_data_parser.rb +103 -0
- data/lib/simplecov/source_file/skip_chunks.rb +72 -0
- data/lib/simplecov/source_file/source_loader.rb +89 -0
- data/lib/simplecov/source_file/statistics.rb +76 -0
- data/lib/simplecov/source_file.rb +64 -260
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +155 -0
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +191 -0
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +45 -0
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +46 -0
- data/lib/simplecov/static_coverage_extractor/value_position.rb +61 -0
- data/lib/simplecov/static_coverage_extractor/visitor.rb +163 -0
- data/lib/simplecov/static_coverage_extractor.rb +78 -0
- 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 +62 -0
- data/lib/simplecov/useless_results_remover.rb +17 -7
- 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 +5 -0
- data/lib/simplecov.rb +143 -394
- data/lib/simplecov_json_formatter.rb +5 -0
- data/man/simplecov.1 +354 -0
- data/sig/simplecov.rbs +1710 -0
- metadata +183 -61
- data/CHANGELOG.md +0 -191
- data/doc/alternate-formatters.md +0 -71
- data/doc/commercial-services.md +0 -25
- data/doc/editor-integration.md +0 -18
- data/lib/simplecov/combine/files_combiner.rb +0 -24
- data/lib/simplecov/default_formatter.rb +0 -20
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
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).
|
|
9
|
+
module SourceLoader
|
|
10
|
+
SHEBANG_REGEX = /\A#!/
|
|
11
|
+
RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/
|
|
12
|
+
|
|
13
|
+
extend self
|
|
14
|
+
|
|
15
|
+
def call(filename)
|
|
16
|
+
lines = [] #: Array[String]
|
|
17
|
+
open_file(filename, "rb:UTF-8") do |file|
|
|
18
|
+
read_lines(file, lines, scrub_invalid(file.gets))
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
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.
|
|
34
|
+
def scrub_invalid(line)
|
|
35
|
+
return line if line.nil? || line.valid_encoding?
|
|
36
|
+
|
|
37
|
+
line.scrub
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def shebang?(line)
|
|
41
|
+
SHEBANG_REGEX.match?(line)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def read_lines(file, lines, current_line)
|
|
45
|
+
return lines unless current_line
|
|
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
|
+
|
|
53
|
+
set_encoding_based_on_magic_comment(file, current_line)
|
|
54
|
+
lines.concat([current_line], ensure_remove_undefs(file.readlines))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# An encoding magic comment must be placed on the first line, except after a
|
|
58
|
+
# shebang.
|
|
59
|
+
def set_encoding_based_on_magic_comment(file, line)
|
|
60
|
+
if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
|
|
61
|
+
file.set_encoding(match[1], "UTF-8")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Setting invalid/undef options on `file.set_encoding` doesn't work
|
|
66
|
+
# properly, and transcoding here also works around a JRuby incompatibility.
|
|
67
|
+
def ensure_remove_undefs(file_lines)
|
|
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)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
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.
|
|
8
|
+
class Statistics
|
|
9
|
+
def initialize(source_file)
|
|
10
|
+
@source_file = source_file
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
{
|
|
15
|
+
**line_statistics,
|
|
16
|
+
**branch_statistics,
|
|
17
|
+
**method_statistics
|
|
18
|
+
}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def line_statistics
|
|
24
|
+
{
|
|
25
|
+
line: coverage_statistics(
|
|
26
|
+
@source_file.covered_lines,
|
|
27
|
+
@source_file.missed_lines,
|
|
28
|
+
omitted: @source_file.never_lines.size
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def branch_statistics
|
|
34
|
+
sf = @source_file
|
|
35
|
+
covered = sf.covered_branches
|
|
36
|
+
missed = sf.missed_branches
|
|
37
|
+
percent = 0.0 if covered.empty? && unaccounted?(sf, "branches")
|
|
38
|
+
|
|
39
|
+
{branch: coverage_statistics(covered, missed, percent: percent)}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def method_statistics
|
|
43
|
+
sf = @source_file
|
|
44
|
+
covered = sf.covered_methods
|
|
45
|
+
missed = sf.missed_methods
|
|
46
|
+
percent = 0.0 if covered.empty? && unaccounted?(sf, "methods")
|
|
47
|
+
|
|
48
|
+
{method: coverage_statistics(covered, missed, percent: percent)}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# A file tracked but never loaded once carried no branch or method tuples
|
|
52
|
+
# at all, so an empty set meant "nobody knows", and answering the
|
|
53
|
+
# empty-set default of 100% overstated it (#902). Simulation now
|
|
54
|
+
# synthesizes those tuples statically, so a file carrying a table at all,
|
|
55
|
+
# empty or not, has been accounted for: it has no branches, or a
|
|
56
|
+
# directive skipped the ones it has, and it is as covered as a loaded
|
|
57
|
+
# file with none. Only a file carrying no table for the criterion is
|
|
58
|
+
# still unaccounted for. A file with missed entries and none covered
|
|
59
|
+
# already computes to 0% either way, so only one that really has covered
|
|
60
|
+
# entries keeps its computed percentage.
|
|
61
|
+
def unaccounted?(source_file, table)
|
|
62
|
+
source_file.not_loaded? && source_file.coverage_data[table].nil?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def coverage_statistics(covered, missed, omitted: 0, percent: nil)
|
|
66
|
+
CoverageStatistics.new(
|
|
67
|
+
total_strength: covered.sum { |item| item.coverage.to_i },
|
|
68
|
+
covered: covered.size,
|
|
69
|
+
missed: missed.size,
|
|
70
|
+
omitted: omitted,
|
|
71
|
+
percent: percent
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -1,102 +1,99 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "directive"
|
|
4
|
+
require_relative "static_coverage_extractor"
|
|
5
|
+
require_relative "source_file/ruby_data_parser"
|
|
6
|
+
require_relative "source_file/source_loader"
|
|
7
|
+
require_relative "source_file/skip_chunks"
|
|
8
|
+
require_relative "source_file/builder_context"
|
|
9
|
+
require_relative "source_file/line_builder"
|
|
10
|
+
require_relative "source_file/branch_builder"
|
|
11
|
+
require_relative "source_file/method_builder"
|
|
12
|
+
require_relative "source_file/statistics"
|
|
13
|
+
|
|
3
14
|
module SimpleCov
|
|
4
|
-
#
|
|
5
|
-
# Representation of a source file including it's coverage data, source code,
|
|
6
|
-
# source lines and featuring helpers to interpret that data.
|
|
7
|
-
#
|
|
8
15
|
class SourceFile
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
attr_reader :coverage_data
|
|
16
|
+
include BuilderContext
|
|
17
|
+
|
|
18
|
+
attr_reader :filename, :coverage_data
|
|
13
19
|
|
|
14
|
-
def initialize(filename, coverage_data)
|
|
20
|
+
def initialize(filename, coverage_data, loaded: true)
|
|
15
21
|
@filename = filename
|
|
16
22
|
@coverage_data = coverage_data
|
|
23
|
+
@loaded = loaded
|
|
17
24
|
end
|
|
18
25
|
|
|
19
|
-
# The path to this source file relative to the projects directory
|
|
20
26
|
def project_filename
|
|
21
|
-
@filename.delete_prefix(SimpleCov.root)
|
|
27
|
+
@filename.delete_prefix(SimpleCov.root).sub(%r{\A[/\\]}, "")
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
#
|
|
30
|
+
# Read lazily to suppress reading unused source code.
|
|
25
31
|
def src
|
|
26
|
-
|
|
27
|
-
# suppress reading unused source code.
|
|
28
|
-
@src ||= load_source
|
|
32
|
+
@src ||= SourceLoader.call(filename)
|
|
29
33
|
end
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
alias_method :source, :src
|
|
35
|
+
|
|
36
|
+
# The per-criterion coverage statistics for this file. With no argument
|
|
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.
|
|
41
|
+
def coverage_statistics(criterion = nil)
|
|
42
|
+
stats = (@coverage_statistics ||= Statistics.new(self).call)
|
|
43
|
+
criterion ? stats[criterion] : stats
|
|
38
44
|
end
|
|
39
45
|
|
|
40
|
-
# Returns all source lines for this file as instances of SimpleCov::SourceFile::Line,
|
|
41
|
-
# and thus including coverage data. Aliased as :source_lines
|
|
42
46
|
def lines
|
|
43
|
-
@lines ||=
|
|
47
|
+
@lines ||= LineBuilder.new(self).call
|
|
44
48
|
end
|
|
45
|
-
|
|
49
|
+
alias_method :source_lines, :lines
|
|
46
50
|
|
|
47
|
-
# Returns all covered lines as SimpleCov::SourceFile::Line
|
|
48
51
|
def covered_lines
|
|
49
52
|
@covered_lines ||= lines.select(&:covered?)
|
|
50
53
|
end
|
|
51
54
|
|
|
52
|
-
# Returns all lines that should have been, but were not covered
|
|
53
|
-
# as instances of SimpleCov::SourceFile::Line
|
|
54
55
|
def missed_lines
|
|
55
56
|
@missed_lines ||= lines.select(&:missed?)
|
|
56
57
|
end
|
|
57
58
|
|
|
58
|
-
# Returns all lines that are not relevant for coverage as
|
|
59
|
-
# SimpleCov::SourceFile::Line instances
|
|
60
59
|
def never_lines
|
|
61
60
|
@never_lines ||= lines.select(&:never?)
|
|
62
61
|
end
|
|
63
62
|
|
|
64
|
-
# Returns all lines that were skipped as SimpleCov::SourceFile::Line instances
|
|
65
63
|
def skipped_lines
|
|
66
64
|
@skipped_lines ||= lines.select(&:skipped?)
|
|
67
65
|
end
|
|
68
66
|
|
|
69
|
-
# Returns the number of relevant lines (covered + missed)
|
|
70
67
|
def lines_of_code
|
|
71
|
-
coverage_statistics[:line]&.total
|
|
68
|
+
coverage_statistics[:line]&.total || 0
|
|
72
69
|
end
|
|
73
70
|
|
|
74
|
-
#
|
|
71
|
+
# Answers nil for a number this file has no line for.
|
|
75
72
|
def line(number)
|
|
76
|
-
lines
|
|
73
|
+
lines.at(number - 1)
|
|
77
74
|
end
|
|
78
75
|
|
|
79
|
-
#
|
|
80
|
-
def covered_percent
|
|
81
|
-
coverage_statistics
|
|
76
|
+
# Nil if the criterion was not measured.
|
|
77
|
+
def covered_percent(criterion = :line)
|
|
78
|
+
coverage_statistics(criterion)&.percent
|
|
82
79
|
end
|
|
83
80
|
|
|
84
|
-
def covered_strength
|
|
85
|
-
coverage_statistics
|
|
81
|
+
def covered_strength(criterion = :line)
|
|
82
|
+
coverage_statistics(criterion)&.strength
|
|
86
83
|
end
|
|
87
84
|
|
|
85
|
+
# Every line the file has is irrelevant, a file with no lines at all
|
|
86
|
+
# included.
|
|
88
87
|
def no_lines?
|
|
89
|
-
lines.
|
|
88
|
+
lines.all?(&:never?)
|
|
90
89
|
end
|
|
91
90
|
|
|
92
91
|
def relevant_lines
|
|
93
92
|
lines.size - never_lines.size - skipped_lines.size
|
|
94
93
|
end
|
|
95
94
|
|
|
96
|
-
#
|
|
97
|
-
# Return all the branches inside current source file
|
|
98
95
|
def branches
|
|
99
|
-
@branches ||=
|
|
96
|
+
@branches ||= BranchBuilder.new(self).call
|
|
100
97
|
end
|
|
101
98
|
|
|
102
99
|
def no_branches?
|
|
@@ -104,37 +101,27 @@ module SimpleCov
|
|
|
104
101
|
end
|
|
105
102
|
|
|
106
103
|
def branches_coverage_percent
|
|
107
|
-
|
|
104
|
+
Deprecation.warn("`SimpleCov::SourceFile#branches_coverage_percent` is deprecated. " \
|
|
105
|
+
"Use `covered_percent(:branch)`.")
|
|
106
|
+
covered_percent(:branch)
|
|
108
107
|
end
|
|
109
108
|
|
|
110
|
-
#
|
|
111
|
-
# Return the relevant branches to source file
|
|
112
109
|
def total_branches
|
|
113
110
|
@total_branches ||= covered_branches + missed_branches
|
|
114
111
|
end
|
|
115
112
|
|
|
116
|
-
#
|
|
117
|
-
# Return hash with key of line number and branch coverage count as value
|
|
118
113
|
def branches_report
|
|
119
|
-
@branches_report ||=
|
|
114
|
+
@branches_report ||=
|
|
115
|
+
branches.reject(&:skipped?).group_by(&:report_line).transform_values { |bs| bs.map(&:report) }
|
|
120
116
|
end
|
|
121
117
|
|
|
122
|
-
#
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
# else that is not in declared inside the code but given by default by coverage report
|
|
126
|
-
#
|
|
127
|
-
# @return [Array]
|
|
128
|
-
#
|
|
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.
|
|
129
121
|
def covered_branches
|
|
130
122
|
@covered_branches ||= branches.select(&:covered?)
|
|
131
123
|
end
|
|
132
124
|
|
|
133
|
-
#
|
|
134
|
-
# Select the missed branches with coverage equal to zero
|
|
135
|
-
#
|
|
136
|
-
# @return [Array]
|
|
137
|
-
#
|
|
138
125
|
def missed_branches
|
|
139
126
|
@missed_branches ||= branches.select(&:missed?)
|
|
140
127
|
end
|
|
@@ -143,213 +130,30 @@ module SimpleCov
|
|
|
143
130
|
branches_report.fetch(line_number, [])
|
|
144
131
|
end
|
|
145
132
|
|
|
146
|
-
#
|
|
147
|
-
# Check if any branches missing on given line number
|
|
148
|
-
#
|
|
149
|
-
# @param [Integer] line_number
|
|
150
|
-
#
|
|
151
|
-
# @return [Boolean]
|
|
152
|
-
#
|
|
153
133
|
def line_with_missed_branch?(line_number)
|
|
154
|
-
branches_for_line(line_number).
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
private
|
|
158
|
-
|
|
159
|
-
# no_cov_chunks is zero indexed to work directly with the array holding the lines
|
|
160
|
-
def no_cov_chunks
|
|
161
|
-
@no_cov_chunks ||= build_no_cov_chunks
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def build_no_cov_chunks
|
|
165
|
-
no_cov_lines = src.map.with_index(1).select { |line_src, _index| LinesClassifier.no_cov_line?(line_src) }
|
|
166
|
-
|
|
167
|
-
# if we have an uneven number of nocovs we assume they go to the
|
|
168
|
-
# end of the file, the source doesn't really matter
|
|
169
|
-
# Can't deal with this within the each_slice due to differing
|
|
170
|
-
# behavior in JRuby: jruby/jruby#6048
|
|
171
|
-
no_cov_lines << ["", src.size] if no_cov_lines.size.odd?
|
|
172
|
-
|
|
173
|
-
no_cov_lines.each_slice(2).map do |(_line_src_start, index_start), (_line_src_end, index_end)|
|
|
174
|
-
index_start..index_end
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def load_source
|
|
179
|
-
lines = []
|
|
180
|
-
# The default encoding is UTF-8
|
|
181
|
-
File.open(filename, "rb:UTF-8") do |file|
|
|
182
|
-
current_line = file.gets
|
|
183
|
-
|
|
184
|
-
if shebang?(current_line)
|
|
185
|
-
lines << current_line
|
|
186
|
-
current_line = file.gets
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
read_lines(file, lines, current_line)
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
SHEBANG_REGEX = /\A#!/.freeze
|
|
194
|
-
def shebang?(line)
|
|
195
|
-
SHEBANG_REGEX.match?(line)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def read_lines(file, lines, current_line)
|
|
199
|
-
return lines unless current_line
|
|
200
|
-
|
|
201
|
-
set_encoding_based_on_magic_comment(file, current_line)
|
|
202
|
-
lines.concat([current_line], ensure_remove_undefs(file.readlines))
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/.freeze
|
|
206
|
-
def set_encoding_based_on_magic_comment(file, line)
|
|
207
|
-
# Check for encoding magic comment
|
|
208
|
-
# Encoding magic comment must be placed at first line except for shebang
|
|
209
|
-
if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
|
|
210
|
-
file.set_encoding(match[1], "UTF-8")
|
|
211
|
-
end
|
|
134
|
+
branches_for_line(line_number).any? { |_type, count| count.zero? }
|
|
212
135
|
end
|
|
213
136
|
|
|
214
|
-
def
|
|
215
|
-
|
|
216
|
-
# have and work around a JRuby incompatibility. Also moved here from
|
|
217
|
-
# simplecov-html to have encoding shenaningans in one place. See #866
|
|
218
|
-
# also setting these option on `file.set_encoding` doesn't seem to work
|
|
219
|
-
# properly so it has to be done here.
|
|
220
|
-
file_lines.each do |line|
|
|
221
|
-
if line.encoding == Encoding::UTF_8
|
|
222
|
-
line
|
|
223
|
-
else
|
|
224
|
-
line.encode!("UTF-8", invalid: :replace, undef: :replace)
|
|
225
|
-
end
|
|
226
|
-
end
|
|
137
|
+
def methods
|
|
138
|
+
@methods ||= MethodBuilder.new(self).call
|
|
227
139
|
end
|
|
228
140
|
|
|
229
|
-
def
|
|
230
|
-
|
|
231
|
-
lines = src.map.with_index(1) do |src, i|
|
|
232
|
-
SimpleCov::SourceFile::Line.new(src, i, coverage_data["lines"][i - 1])
|
|
233
|
-
end
|
|
234
|
-
process_skipped_lines(lines)
|
|
141
|
+
def covered_methods
|
|
142
|
+
@covered_methods ||= methods.select(&:covered?)
|
|
235
143
|
end
|
|
236
144
|
|
|
237
|
-
def
|
|
238
|
-
|
|
239
|
-
# chunks are 1-based and are expected to be like this in other parts (and it's also
|
|
240
|
-
# arguably more understandable)
|
|
241
|
-
no_cov_chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
|
|
242
|
-
|
|
243
|
-
lines
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
def lines_strength
|
|
247
|
-
lines.sum { |line| line.coverage.to_i }
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
# Warning to identify condition from Issue #56
|
|
251
|
-
def coverage_exceeding_source_warn
|
|
252
|
-
warn "Warning: coverage data provided by Coverage [#{coverage_data['lines'].size}] exceeds number of lines in #{filename} [#{src.size}]"
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
#
|
|
256
|
-
# Build full branches report
|
|
257
|
-
# Root branches represent the wrapper of all condition state that
|
|
258
|
-
# have inside the branches
|
|
259
|
-
#
|
|
260
|
-
# @return [Hash]
|
|
261
|
-
#
|
|
262
|
-
def build_branches_report
|
|
263
|
-
branches.reject(&:skipped?).each_with_object({}) do |branch, coverage_statistics|
|
|
264
|
-
coverage_statistics[branch.report_line] ||= []
|
|
265
|
-
coverage_statistics[branch.report_line] << branch.report
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
#
|
|
270
|
-
# Call recursive method that transform our static hash to array of objects
|
|
271
|
-
# @return [Array]
|
|
272
|
-
#
|
|
273
|
-
def build_branches
|
|
274
|
-
coverage_branch_data = coverage_data.fetch("branches", {})
|
|
275
|
-
branches = coverage_branch_data.flat_map do |condition, coverage_branches|
|
|
276
|
-
build_branches_from(condition, coverage_branches)
|
|
277
|
-
end
|
|
278
|
-
|
|
279
|
-
process_skipped_branches(branches)
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
def process_skipped_branches(branches)
|
|
283
|
-
return branches if no_cov_chunks.empty?
|
|
284
|
-
|
|
285
|
-
branches.each do |branch|
|
|
286
|
-
branch.skipped! if no_cov_chunks.any? { |no_cov_chunk| branch.overlaps_with?(no_cov_chunk) }
|
|
287
|
-
end
|
|
288
|
-
|
|
289
|
-
branches
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
# Since we are dumping to and loading from JSON, and we have arrays as keys those
|
|
293
|
-
# don't make their way back to us intact e.g. just as a string
|
|
294
|
-
#
|
|
295
|
-
# We should probably do something different here, but as it stands these are
|
|
296
|
-
# our data structures that we write so eval isn't _too_ bad.
|
|
297
|
-
#
|
|
298
|
-
# See #801
|
|
299
|
-
#
|
|
300
|
-
def restore_ruby_data_structure(structure)
|
|
301
|
-
# Tests use the real data structures (except for integration tests) so no need to
|
|
302
|
-
# put them through here.
|
|
303
|
-
return structure if structure.is_a?(Array)
|
|
304
|
-
|
|
305
|
-
# rubocop:disable Security/Eval
|
|
306
|
-
eval structure
|
|
307
|
-
# rubocop:enable Security/Eval
|
|
308
|
-
end
|
|
309
|
-
|
|
310
|
-
def build_branches_from(condition, branches)
|
|
311
|
-
# the format handed in from the coverage data is like this:
|
|
312
|
-
#
|
|
313
|
-
# [:then, 4, 6, 6, 6, 10]
|
|
314
|
-
#
|
|
315
|
-
# which is [type, id, start_line, start_col, end_line, end_col]
|
|
316
|
-
_condition_type, _condition_id, condition_start_line, * = restore_ruby_data_structure(condition)
|
|
317
|
-
|
|
318
|
-
branches.map do |branch_data, hit_count|
|
|
319
|
-
branch_data = restore_ruby_data_structure(branch_data)
|
|
320
|
-
build_branch(branch_data, hit_count, condition_start_line)
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
def build_branch(branch_data, hit_count, condition_start_line)
|
|
325
|
-
type, _id, start_line, _start_col, end_line, _end_col = branch_data
|
|
326
|
-
|
|
327
|
-
SourceFile::Branch.new(
|
|
328
|
-
start_line: start_line,
|
|
329
|
-
end_line: end_line,
|
|
330
|
-
coverage: hit_count,
|
|
331
|
-
inline: start_line == condition_start_line,
|
|
332
|
-
type: type
|
|
333
|
-
)
|
|
145
|
+
def missed_methods
|
|
146
|
+
@missed_methods ||= methods.select(&:missed?)
|
|
334
147
|
end
|
|
335
148
|
|
|
336
|
-
def
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
covered: covered_lines.size,
|
|
341
|
-
missed: missed_lines.size
|
|
342
|
-
)
|
|
343
|
-
}
|
|
149
|
+
def methods_coverage_percent
|
|
150
|
+
Deprecation.warn("`SimpleCov::SourceFile#methods_coverage_percent` is deprecated. " \
|
|
151
|
+
"Use `covered_percent(:method)`.")
|
|
152
|
+
covered_percent(:method)
|
|
344
153
|
end
|
|
345
154
|
|
|
346
|
-
def
|
|
347
|
-
|
|
348
|
-
branch: CoverageStatistics.new(
|
|
349
|
-
covered: covered_branches.size,
|
|
350
|
-
missed: missed_branches.size
|
|
351
|
-
)
|
|
352
|
-
}
|
|
155
|
+
def not_loaded?
|
|
156
|
+
!@loaded
|
|
353
157
|
end
|
|
354
158
|
end
|
|
355
159
|
end
|