ultra-pro-pkg 0.0.1
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 +7 -0
- data/simplecov-1.0.0/LICENSE +20 -0
- data/simplecov-1.0.0/README.md +1524 -0
- data/simplecov-1.0.0/doc/alternate-formatters.md +66 -0
- data/simplecov-1.0.0/doc/commercial-services.md +25 -0
- data/simplecov-1.0.0/doc/editor-integration.md +18 -0
- data/simplecov-1.0.0/exe/simplecov +11 -0
- data/simplecov-1.0.0/lib/minitest/simplecov_plugin.rb +171 -0
- data/simplecov-1.0.0/lib/simplecov/autostart.rb +11 -0
- data/simplecov-1.0.0/lib/simplecov/cli/clean.rb +47 -0
- data/simplecov-1.0.0/lib/simplecov/cli/coverage.rb +91 -0
- data/simplecov-1.0.0/lib/simplecov/cli/diff.rb +151 -0
- data/simplecov-1.0.0/lib/simplecov/cli/dotfile.rb +100 -0
- data/simplecov-1.0.0/lib/simplecov/cli/merge.rb +116 -0
- data/simplecov-1.0.0/lib/simplecov/cli/open.rb +50 -0
- data/simplecov-1.0.0/lib/simplecov/cli/report.rb +84 -0
- data/simplecov-1.0.0/lib/simplecov/cli/run.rb +36 -0
- data/simplecov-1.0.0/lib/simplecov/cli/serve.rb +139 -0
- data/simplecov-1.0.0/lib/simplecov/cli/uncovered.rb +107 -0
- data/simplecov-1.0.0/lib/simplecov/cli.rb +150 -0
- data/simplecov-1.0.0/lib/simplecov/color.rb +74 -0
- data/simplecov-1.0.0/lib/simplecov/combine/branches_combiner.rb +54 -0
- data/simplecov-1.0.0/lib/simplecov/combine/files_combiner.rb +30 -0
- data/simplecov-1.0.0/lib/simplecov/combine/lines_combiner.rb +45 -0
- data/simplecov-1.0.0/lib/simplecov/combine/methods_combiner.rb +26 -0
- data/simplecov-1.0.0/lib/simplecov/combine/results_combiner.rb +61 -0
- data/simplecov-1.0.0/lib/simplecov/combine.rb +30 -0
- data/simplecov-1.0.0/lib/simplecov/command_guesser.rb +78 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/coverage.rb +171 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/coverage_criteria.rb +156 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/filters.rb +197 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/formatting.rb +119 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/ignored_entries.rb +63 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/merging.rb +164 -0
- data/simplecov-1.0.0/lib/simplecov/configuration/thresholds.rb +174 -0
- data/simplecov-1.0.0/lib/simplecov/configuration.rb +184 -0
- data/simplecov-1.0.0/lib/simplecov/coverage_statistics.rb +59 -0
- data/simplecov-1.0.0/lib/simplecov/coverage_violations.rb +148 -0
- data/simplecov-1.0.0/lib/simplecov/defaults.rb +60 -0
- data/simplecov-1.0.0/lib/simplecov/deprecation.rb +45 -0
- data/simplecov-1.0.0/lib/simplecov/directive.rb +162 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/exit_code_handling.rb +35 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +45 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +44 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +70 -0
- data/simplecov-1.0.0/lib/simplecov/exit_codes.rb +18 -0
- data/simplecov-1.0.0/lib/simplecov/exit_handling.rb +158 -0
- data/simplecov-1.0.0/lib/simplecov/file_list.rb +164 -0
- data/simplecov-1.0.0/lib/simplecov/filter.rb +139 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/base.rb +101 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter.rb +79 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/errors_formatter.rb +84 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +128 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter.rb +87 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/multi_formatter.rb +31 -0
- data/simplecov-1.0.0/lib/simplecov/formatter/simple_formatter.rb +23 -0
- data/simplecov-1.0.0/lib/simplecov/formatter.rb +14 -0
- data/simplecov-1.0.0/lib/simplecov/last_run.rb +35 -0
- data/simplecov-1.0.0/lib/simplecov/lines_classifier.rb +60 -0
- data/simplecov-1.0.0/lib/simplecov/load_global_config.rb +13 -0
- data/simplecov-1.0.0/lib/simplecov/no_defaults.rb +4 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_adapters/base.rb +61 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_adapters/generic.rb +44 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_adapters.rb +83 -0
- data/simplecov-1.0.0/lib/simplecov/parallel_coordination.rb +129 -0
- data/simplecov-1.0.0/lib/simplecov/process.rb +37 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/bundler_filter.rb +5 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/hidden_filter.rb +5 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/rails.rb +32 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/root_filter.rb +11 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/strict.rb +32 -0
- data/simplecov-1.0.0/lib/simplecov/profiles/test_frameworks.rb +5 -0
- data/simplecov-1.0.0/lib/simplecov/profiles.rb +64 -0
- data/simplecov-1.0.0/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
- data/simplecov-1.0.0/lib/simplecov/result/source_file_builder.rb +51 -0
- data/simplecov-1.0.0/lib/simplecov/result.rb +183 -0
- data/simplecov-1.0.0/lib/simplecov/result_adapter.rb +92 -0
- data/simplecov-1.0.0/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
- data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_file.rb +38 -0
- data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_store.rb +50 -0
- data/simplecov-1.0.0/lib/simplecov/result_merger.rb +162 -0
- data/simplecov-1.0.0/lib/simplecov/result_processing.rb +179 -0
- data/simplecov-1.0.0/lib/simplecov/simulate_coverage.rb +75 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/branch.rb +82 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/branch_builder.rb +114 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/builder_context.rb +28 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/line.rb +77 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/line_builder.rb +43 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/method.rb +52 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/method_builder.rb +58 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/skip_chunks.rb +75 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/source_loader.rb +63 -0
- data/simplecov-1.0.0/lib/simplecov/source_file/statistics.rb +57 -0
- data/simplecov-1.0.0/lib/simplecov/source_file.rb +189 -0
- data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/method_collector.rb +55 -0
- data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/visitor.rb +171 -0
- data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor.rb +110 -0
- data/simplecov-1.0.0/lib/simplecov/useless_results_remover.rb +27 -0
- data/simplecov-1.0.0/lib/simplecov/version.rb +5 -0
- data/simplecov-1.0.0/lib/simplecov-html.rb +4 -0
- data/simplecov-1.0.0/lib/simplecov.rb +255 -0
- data/simplecov-1.0.0/lib/simplecov_json_formatter.rb +4 -0
- data/simplecov-1.0.0/schemas/coverage-v1.0.schema.json +306 -0
- data/simplecov-1.0.0/schemas/coverage.schema.json +306 -0
- data/ultra-pro-pkg.gemspec +12 -0
- metadata +156 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
#
|
|
6
|
+
# Representing single branch that has been detected in coverage report.
|
|
7
|
+
# Give us support methods that handle needed calculations.
|
|
8
|
+
class Branch
|
|
9
|
+
attr_reader :start_line, :end_line, :coverage, :type
|
|
10
|
+
|
|
11
|
+
def initialize(start_line:, end_line:, coverage:, inline:, type:)
|
|
12
|
+
@start_line = start_line
|
|
13
|
+
@end_line = end_line
|
|
14
|
+
@coverage = coverage
|
|
15
|
+
@inline = inline
|
|
16
|
+
@type = type
|
|
17
|
+
@skipped = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def inline?
|
|
21
|
+
@inline
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# Return true if there is relevant count defined > 0
|
|
26
|
+
#
|
|
27
|
+
# @return [Boolean]
|
|
28
|
+
#
|
|
29
|
+
def covered?
|
|
30
|
+
!skipped? && coverage.positive?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Check if branch missed or not
|
|
35
|
+
#
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
#
|
|
38
|
+
def missed?
|
|
39
|
+
!skipped? && coverage.zero?
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The line on which we want to report the coverage
|
|
43
|
+
#
|
|
44
|
+
# Usually we choose the line above the start of the branch (so that it shows up
|
|
45
|
+
# at if/else) because that
|
|
46
|
+
# * highlights the condition
|
|
47
|
+
# * makes it distinguishable if the first line of the branch is an inline branch
|
|
48
|
+
# (see the nested_branches fixture)
|
|
49
|
+
#
|
|
50
|
+
def report_line
|
|
51
|
+
if inline?
|
|
52
|
+
start_line
|
|
53
|
+
else
|
|
54
|
+
start_line - 1
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Flags the branch as skipped
|
|
59
|
+
def skipped!
|
|
60
|
+
@skipped = true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns true if the branch was marked skipped by virtue of nocov comments.
|
|
64
|
+
def skipped?
|
|
65
|
+
@skipped
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def overlaps_with?(line_range)
|
|
69
|
+
start_line <= line_range.end && end_line >= line_range.begin
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
#
|
|
73
|
+
# Return array with coverage count and badge
|
|
74
|
+
#
|
|
75
|
+
# @return [Array]
|
|
76
|
+
#
|
|
77
|
+
def report
|
|
78
|
+
[type, coverage]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
# Builds the `SourceFile::Branch` objects for a source file from
|
|
6
|
+
# the raw branch data Ruby's Coverage library reports. Applies the
|
|
7
|
+
# `ignore_branches :eval_generated` / `:implicit_else` filters and
|
|
8
|
+
# marks branches inside `# simplecov:disable` / `# :nocov:` chunks
|
|
9
|
+
# as skipped.
|
|
10
|
+
class BranchBuilder
|
|
11
|
+
def initialize(source_file)
|
|
12
|
+
@source_file = source_file
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
coverage_branch_data = @source_file.coverage_data["branches"] || {}
|
|
17
|
+
branches = coverage_branch_data.flat_map do |condition, coverage_branches|
|
|
18
|
+
next [] if eval_generated_condition_to_ignore?(condition)
|
|
19
|
+
|
|
20
|
+
build_branches_from(condition, coverage_branches)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
process_skipped(branches)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
# Detect a Coverage-reported branch condition that originates from
|
|
29
|
+
# `eval`/`module_eval`/`class_eval`/`instance_eval` rather than from
|
|
30
|
+
# the file's literal source. Coverage attributes such branches to the
|
|
31
|
+
# caller's `__FILE__`/`__LINE__`, so a Rails `delegate :foo, to: :bar`
|
|
32
|
+
# call surfaces inside the source file as if there were branches at
|
|
33
|
+
# the `delegate` line. Prism never sees those branches in the static
|
|
34
|
+
# source, so a condition whose start_line isn't in the real-source
|
|
35
|
+
# branch set must be eval-generated. Only consulted when the user has
|
|
36
|
+
# opted in via `SimpleCov.ignore_branches :eval_generated`. See #1046.
|
|
37
|
+
def eval_generated_condition_to_ignore?(condition)
|
|
38
|
+
return false unless SimpleCov.ignored_branch?(:eval_generated)
|
|
39
|
+
|
|
40
|
+
positions = @source_file.real_source_positions
|
|
41
|
+
# simplecov:disable branch — nil branch fires only when Prism is unavailable
|
|
42
|
+
return false unless positions
|
|
43
|
+
|
|
44
|
+
# simplecov:enable branch
|
|
45
|
+
|
|
46
|
+
_type, _id, start_line, * = RubyDataParser.call(condition)
|
|
47
|
+
!positions[:branches].include?(start_line)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def build_branches_from(condition, branches)
|
|
51
|
+
# the format handed in from the coverage data is like this:
|
|
52
|
+
#
|
|
53
|
+
# [:then, 4, 6, 6, 6, 10]
|
|
54
|
+
#
|
|
55
|
+
# which is [type, id, start_line, start_col, end_line, end_col]
|
|
56
|
+
_condition_type, _condition_id, *condition_range = RubyDataParser.call(condition)
|
|
57
|
+
|
|
58
|
+
branches.filter_map do |branch_data, hit_count|
|
|
59
|
+
build_branch(RubyDataParser.call(branch_data), hit_count, condition_range)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def build_branch(branch_data, hit_count, condition_range)
|
|
64
|
+
type, _id, start_line, start_col, end_line, end_col = branch_data
|
|
65
|
+
return nil if implicit_else_to_ignore?(type, [start_line, start_col, end_line, end_col], condition_range)
|
|
66
|
+
|
|
67
|
+
SourceFile::Branch.new(
|
|
68
|
+
start_line: start_line,
|
|
69
|
+
end_line: end_line,
|
|
70
|
+
coverage: hit_count,
|
|
71
|
+
inline: start_line == condition_range.first,
|
|
72
|
+
type: type
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Detect synthetic `:else` branches that Ruby's Coverage library reports
|
|
77
|
+
# for constructs with no literal `else` keyword in source (`case/in` /
|
|
78
|
+
# `case/when` without else, `||=`, `&&=`, `if`/`unless` without else,
|
|
79
|
+
# and the postfix `return if cond` shape). The signal is structural:
|
|
80
|
+
# a synthetic else reuses its parent condition's *full source range*
|
|
81
|
+
# (start_line, start_col, end_line, end_col all identical), while an
|
|
82
|
+
# explicit `else` arm carries a narrower range — its own keyword/body
|
|
83
|
+
# position rather than the whole conditional. Comparing the full range
|
|
84
|
+
# (not just `start_line`) is what distinguishes a ternary's explicit
|
|
85
|
+
# else on the same line as the condition — `arg == 42 ? :yes : :no`,
|
|
86
|
+
# where the else's columns differ from the parent's — from a postfix
|
|
87
|
+
# `return if cond` where the synthetic else inherits the full range.
|
|
88
|
+
# Only consulted when the user has opted in via
|
|
89
|
+
# `SimpleCov.ignore_branches :implicit_else`. See #1033.
|
|
90
|
+
def implicit_else_to_ignore?(type, branch_range, condition_range)
|
|
91
|
+
return false unless type == :else
|
|
92
|
+
return false unless SimpleCov.ignored_branch?(:implicit_else)
|
|
93
|
+
|
|
94
|
+
branch_range == condition_range
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def process_skipped(branches)
|
|
98
|
+
chunks = @source_file.skip_chunks_for(:branch)
|
|
99
|
+
return branches if chunks.empty?
|
|
100
|
+
|
|
101
|
+
# A non-inline branch's source range starts on its arm body (e.g. the
|
|
102
|
+
# `:yes` line of `if cond / :yes / else / :no / end`), but `report_line`
|
|
103
|
+
# is the condition line above it — that's where the user sees the
|
|
104
|
+
# branch in the report and where they would naturally place an inline
|
|
105
|
+
# `# simplecov:disable branch` directive. Honour both.
|
|
106
|
+
branches.each do |branch|
|
|
107
|
+
branch.skipped! if chunks.any? { |chunk| branch.overlaps_with?(chunk) || chunk.include?(branch.report_line) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
branches
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
# Helpers shared by the per-criterion builders (LineBuilder /
|
|
6
|
+
# BranchBuilder / MethodBuilder). Mixed into SourceFile so each
|
|
7
|
+
# builder can ask the file for its skip-chunk ranges and Prism-derived
|
|
8
|
+
# real source positions without duplicating the memoization.
|
|
9
|
+
module BuilderContext
|
|
10
|
+
# Skip-chunk lookup for the named criterion (`:line`, `:branch`,
|
|
11
|
+
# `:method`).
|
|
12
|
+
def skip_chunks_for(criterion)
|
|
13
|
+
(@skip_chunks ||= SkipChunks.new(filename, src)).for(criterion)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Memoized set of real source positions (branch start lines, method
|
|
17
|
+
# name+line pairs) extracted via Prism. Returns nil when Prism is
|
|
18
|
+
# unavailable or parsing fails, signaling callers to keep every
|
|
19
|
+
# Coverage entry (no false drops). The `defined?` guard preserves a
|
|
20
|
+
# nil memoization across calls.
|
|
21
|
+
def real_source_positions
|
|
22
|
+
return @real_source_positions if defined?(@real_source_positions)
|
|
23
|
+
|
|
24
|
+
@real_source_positions = StaticCoverageExtractor.real_source_positions(src.join)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
# Representation of a single line in a source file including
|
|
6
|
+
# this specific line's source code, line_number and code coverage,
|
|
7
|
+
# with the coverage being either nil (coverage not applicable, e.g. comment
|
|
8
|
+
# line), 0 (line not covered) or >1 (the amount of times the line was
|
|
9
|
+
# executed)
|
|
10
|
+
class Line
|
|
11
|
+
# The source code for this line. Aliased as :source
|
|
12
|
+
attr_reader :src
|
|
13
|
+
# The line number in the source file. Aliased as :line, :number
|
|
14
|
+
attr_reader :line_number
|
|
15
|
+
# The coverage data for this line: either nil (never), 0 (missed) or >=1 (times covered)
|
|
16
|
+
attr_reader :coverage
|
|
17
|
+
# Whether this line was skipped
|
|
18
|
+
attr_reader :skipped
|
|
19
|
+
|
|
20
|
+
# Lets grab some fancy aliases, shall we?
|
|
21
|
+
alias source src
|
|
22
|
+
alias line line_number
|
|
23
|
+
alias number line_number
|
|
24
|
+
|
|
25
|
+
def initialize(src, line_number, coverage)
|
|
26
|
+
raise ArgumentError, "Only String accepted for source" unless src.is_a?(String)
|
|
27
|
+
raise ArgumentError, "Only Integer accepted for line_number" unless line_number.is_a?(Integer)
|
|
28
|
+
unless coverage.is_a?(Integer) || coverage.nil?
|
|
29
|
+
raise ArgumentError, "Only Integer and nil accepted for coverage"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@src = src
|
|
33
|
+
@line_number = line_number
|
|
34
|
+
@coverage = coverage
|
|
35
|
+
@skipped = false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns true if this is a line that should have been covered, but was not
|
|
39
|
+
def missed?
|
|
40
|
+
!never? && !skipped? && coverage.zero?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns true if this is a line that has been covered
|
|
44
|
+
def covered?
|
|
45
|
+
!never? && !skipped? && coverage.positive?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns true if this line is not relevant for coverage
|
|
49
|
+
def never?
|
|
50
|
+
!skipped? && coverage.nil?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Flags this line as skipped
|
|
54
|
+
def skipped!
|
|
55
|
+
@skipped = true
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns true if this line was skipped, false otherwise. Lines are skipped if they are wrapped with
|
|
59
|
+
# # :nocov: comment lines.
|
|
60
|
+
def skipped?
|
|
61
|
+
skipped
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The status of this line - either covered, missed, skipped or never. Useful i.e. for direct use
|
|
65
|
+
# as a css class in report generation
|
|
66
|
+
def status
|
|
67
|
+
return "skipped" if skipped?
|
|
68
|
+
return "never" if never?
|
|
69
|
+
return "missed" if missed?
|
|
70
|
+
|
|
71
|
+
# simplecov:disable — defensive: covered? is the only state left after the three above
|
|
72
|
+
"covered" if covered?
|
|
73
|
+
# simplecov:enable
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
# Builds the `SourceFile::Line` objects for a source file from the
|
|
6
|
+
# raw line-coverage array. Each line carries its source text, its
|
|
7
|
+
# 1-based line number, and the Coverage hit count (or nil for
|
|
8
|
+
# never-counted lines). Applies `# simplecov:disable` /
|
|
9
|
+
# `# :nocov:` block ranges via `skipped!`.
|
|
10
|
+
class LineBuilder
|
|
11
|
+
def initialize(source_file)
|
|
12
|
+
@source_file = source_file
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def call
|
|
16
|
+
lines = build_lines
|
|
17
|
+
mark_skipped(lines, @source_file.skip_chunks_for(:line))
|
|
18
|
+
lines
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# When `:line` coverage is disabled, the Ruby Coverage module
|
|
24
|
+
# doesn't emit "lines" data, so look up `nil` (never-counted) for
|
|
25
|
+
# every position. The source rows are still useful — e.g. for the
|
|
26
|
+
# HTML report's source view — even without per-line hits.
|
|
27
|
+
def build_lines
|
|
28
|
+
line_coverage = @source_file.coverage_data["lines"] || []
|
|
29
|
+
@source_file.src.map.with_index(1) do |src, i|
|
|
30
|
+
SourceFile::Line.new(src, i, line_coverage[i - 1])
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The array the lines are kept in is 0-based whereas the line
|
|
35
|
+
# numbers in the chunks are 1-based (more understandable elsewhere),
|
|
36
|
+
# so each range needs to be shifted down by one to slice into the
|
|
37
|
+
# `lines` array.
|
|
38
|
+
def mark_skipped(lines, chunks)
|
|
39
|
+
chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
# Represents a single method detected in coverage data.
|
|
6
|
+
# Provides helpers similar to Branch for coverage status.
|
|
7
|
+
class Method
|
|
8
|
+
attr_reader :source_file, :coverage, :class_name, :method_name,
|
|
9
|
+
:start_line, :start_col, :end_line, :end_col
|
|
10
|
+
|
|
11
|
+
def initialize(source_file, info, coverage)
|
|
12
|
+
@source_file = source_file
|
|
13
|
+
@class_name, @method_name, @start_line, @start_col, @end_line, @end_col = info
|
|
14
|
+
@coverage = coverage
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def covered?
|
|
18
|
+
!skipped? && coverage.positive?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def skipped?
|
|
22
|
+
return @skipped if defined?(@skipped)
|
|
23
|
+
|
|
24
|
+
@skipped = lines.all?(&:skipped?)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Flag the method as skipped directly, without going through its lines.
|
|
28
|
+
def skipped!
|
|
29
|
+
@skipped = true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def missed?
|
|
33
|
+
!skipped? && coverage.zero?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def lines
|
|
37
|
+
@lines ||= start_line && end_line ? source_file.lines[(start_line - 1)..(end_line - 1)] : []
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Whether this method's source range intersects the given inclusive line range.
|
|
41
|
+
def overlaps_with?(line_range)
|
|
42
|
+
return false unless start_line && end_line
|
|
43
|
+
|
|
44
|
+
start_line <= line_range.end && end_line >= line_range.begin
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_s
|
|
48
|
+
"#{class_name}##{method_name}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
# Builds the `SourceFile::Method` objects for a source file from
|
|
6
|
+
# the raw method-coverage data. Applies the
|
|
7
|
+
# `ignore_methods :eval_generated` filter and marks methods inside
|
|
8
|
+
# `# simplecov:disable` / `# :nocov:` chunks as skipped.
|
|
9
|
+
class MethodBuilder
|
|
10
|
+
def initialize(source_file)
|
|
11
|
+
@source_file = source_file
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call
|
|
15
|
+
methods = @source_file.coverage_data.fetch("methods", {}).filter_map do |info, hit_count|
|
|
16
|
+
info = RubyDataParser.call(info)
|
|
17
|
+
next if eval_generated_to_ignore?(info)
|
|
18
|
+
|
|
19
|
+
SourceFile::Method.new(@source_file, info, hit_count)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
process_skipped(methods)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# See `BranchBuilder#eval_generated_condition_to_ignore?` for the
|
|
28
|
+
# rationale. Coverage reports an eval'd `def` at the eval caller's
|
|
29
|
+
# line and name, so a method whose `(name, start_line)` is absent
|
|
30
|
+
# from the real-source `def` set is eval-generated. Only consulted
|
|
31
|
+
# when the user has opted in via
|
|
32
|
+
# `SimpleCov.ignore_methods :eval_generated`. See #1046.
|
|
33
|
+
def eval_generated_to_ignore?(info)
|
|
34
|
+
return false unless SimpleCov.ignored_method?(:eval_generated)
|
|
35
|
+
|
|
36
|
+
positions = @source_file.real_source_positions
|
|
37
|
+
# simplecov:disable branch — nil branch fires only when Prism is unavailable
|
|
38
|
+
return false unless positions
|
|
39
|
+
|
|
40
|
+
# simplecov:enable branch
|
|
41
|
+
|
|
42
|
+
_class_name, name, start_line, * = info
|
|
43
|
+
!positions[:methods].include?([name, start_line])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def process_skipped(methods)
|
|
47
|
+
chunks = @source_file.skip_chunks_for(:method)
|
|
48
|
+
return methods if chunks.empty?
|
|
49
|
+
|
|
50
|
+
methods.each do |method|
|
|
51
|
+
method.skipped! if chunks.any? { |chunk| method.overlaps_with?(chunk) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
methods
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ripper"
|
|
4
|
+
|
|
5
|
+
module SimpleCov
|
|
6
|
+
class SourceFile
|
|
7
|
+
# `Coverage.result` reports condition and method keys as Ruby
|
|
8
|
+
# arrays. When the resultset is round-tripped through JSON those
|
|
9
|
+
# array keys become their stringified inspect form, so this parser
|
|
10
|
+
# walks the literal back into a real Array without using `eval` (see
|
|
11
|
+
# #801). The grammar covers symbols, strings, integers, unary minus,
|
|
12
|
+
# and constant paths — every shape Coverage ever emits.
|
|
13
|
+
module RubyDataParser
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
# Tests use the real data structures (except for integration tests)
|
|
17
|
+
# so no need to put them through here.
|
|
18
|
+
def call(structure)
|
|
19
|
+
return structure if structure.is_a?(Array)
|
|
20
|
+
|
|
21
|
+
parse_array_string(structure.to_s)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Parse a string like '[:if, 0, 3, 4, 3, 21]' or
|
|
25
|
+
# '["ClassName", :method1, 2, 2, 5, 5]' back into a Ruby array.
|
|
26
|
+
def parse_array_string(str)
|
|
27
|
+
# Try plain Ripper first; only pre-quote `#<...>` inspect segments
|
|
28
|
+
# if the input isn't already valid Ruby (otherwise we corrupt
|
|
29
|
+
# `"#<Class:Foo>"` strings that *are* valid Ruby literals — exactly
|
|
30
|
+
# the shape simplecov-on-simplecov method-coverage keys take).
|
|
31
|
+
sexp = Ripper.sexp(str) || Ripper.sexp(quote_inspected_class_segments(str))
|
|
32
|
+
# simplecov:disable — defensive: Ripper.sexp returning nil from both passes requires malformed input
|
|
33
|
+
array_node = sexp&.dig(1, 0)
|
|
34
|
+
# simplecov:enable
|
|
35
|
+
raise ArgumentError, "expected array literal: #{str.inspect}" unless array_node && array_node[0] == :array
|
|
36
|
+
|
|
37
|
+
Array(array_node[1]).map { |element| parse_element(element) }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parse_element(node)
|
|
41
|
+
case node[0]
|
|
42
|
+
when :@int, :unary then parse_integer_node(node)
|
|
43
|
+
when :symbol_literal, :dyna_symbol then parse_symbol_node(node)
|
|
44
|
+
when :string_literal then unescape_ruby(string_literal_text(node[1]))
|
|
45
|
+
when :var_ref then node.dig(1, 1) # `Foo`
|
|
46
|
+
when :const_path_ref then "#{parse_element(node[1])}::#{node[2][1]}" # `Foo::Bar`
|
|
47
|
+
else
|
|
48
|
+
# simplecov:disable — defensive fallback for unexpected Ripper node shapes
|
|
49
|
+
raise ArgumentError, "unexpected element: #{node.inspect}"
|
|
50
|
+
# simplecov:enable
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def parse_integer_node(node)
|
|
55
|
+
node[0] == :@int ? node[1].to_i : -node[2][1].to_i
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def parse_symbol_node(node)
|
|
59
|
+
if node[0] == :symbol_literal
|
|
60
|
+
node.dig(1, 1, 1).to_sym
|
|
61
|
+
else
|
|
62
|
+
unescape_ruby(string_literal_text(node[1])).to_sym
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Concatenate the text fragments of a `:string_content` node. Ripper
|
|
67
|
+
# may emit zero, one, or many `:@tstring_content` children depending
|
|
68
|
+
# on the literal.
|
|
69
|
+
def string_literal_text(string_content)
|
|
70
|
+
Array(string_content[1..]).map { |child| child[1] }.join
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Undo the same backslash-prefix escapes the previous hand-rolled
|
|
74
|
+
# parser undid: `\X` → `X` for any X.
|
|
75
|
+
def unescape_ruby(raw)
|
|
76
|
+
raw.gsub(/\\(.)/) { ::Regexp.last_match(1) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Method coverage keys can contain inspect-format class references
|
|
80
|
+
# like `#<Class:Foo>` or `#<Class:0x...>`, which aren't valid Ruby
|
|
81
|
+
# syntax. Wrap them in quotes so Ripper can parse the surrounding
|
|
82
|
+
# array literal; downstream we treat them as opaque strings.
|
|
83
|
+
def quote_inspected_class_segments(str)
|
|
84
|
+
str.gsub(/#<[^>]*>/) { |segment| %("#{segment.gsub('"', '\\"')}") }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class SourceFile
|
|
5
|
+
# Computes the set of line ranges that should be excluded from a
|
|
6
|
+
# SourceFile's coverage for each criterion. Two sources contribute:
|
|
7
|
+
#
|
|
8
|
+
# * The deprecated `# :nocov:` block toggle (lines wrapped between
|
|
9
|
+
# even-numbered pairs of nocov markers are excluded from line and
|
|
10
|
+
# branch coverage).
|
|
11
|
+
# * `# simplecov:disable` / `# simplecov:enable` block directives,
|
|
12
|
+
# which can be scoped per-criterion (`# simplecov:disable branch`,
|
|
13
|
+
# etc.) — see `SimpleCov::Directive`.
|
|
14
|
+
class SkipChunks
|
|
15
|
+
@nocov_warned = Set.new
|
|
16
|
+
class << self
|
|
17
|
+
attr_reader :nocov_warned
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(filename, src)
|
|
21
|
+
@filename = filename
|
|
22
|
+
@src = src
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# `:method` ignores nocov chunks (Ruby's Coverage doesn't tie
|
|
26
|
+
# method entries to line ranges); `:line` / `:branch` honor both
|
|
27
|
+
# the nocov chunks and the per-criterion directive ranges.
|
|
28
|
+
def for(criterion)
|
|
29
|
+
if criterion == :method
|
|
30
|
+
directive_chunks.fetch(:method)
|
|
31
|
+
else
|
|
32
|
+
nocov_chunks + directive_chunks.fetch(criterion)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# no_cov_chunks is zero indexed to work directly with the array
|
|
37
|
+
# holding the lines.
|
|
38
|
+
def nocov_chunks
|
|
39
|
+
@nocov_chunks ||= build_nocov_chunks
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def directive_chunks
|
|
43
|
+
@directive_chunks ||= Directive.disabled_ranges(@src)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def build_nocov_chunks
|
|
49
|
+
no_cov_lines = @src.map.with_index(1).select { |line_src, _index| LinesClassifier.no_cov_line?(line_src) }
|
|
50
|
+
|
|
51
|
+
warn_nocov_deprecation(no_cov_lines.first.last) if no_cov_lines.any?
|
|
52
|
+
|
|
53
|
+
# If we have an uneven number of nocovs we assume they go to the
|
|
54
|
+
# end of the file, the source doesn't really matter. Can't deal
|
|
55
|
+
# with this within the each_slice due to differing behavior in
|
|
56
|
+
# JRuby: jruby/jruby#6048
|
|
57
|
+
no_cov_lines << ["", @src.size] if no_cov_lines.size.odd?
|
|
58
|
+
|
|
59
|
+
no_cov_lines.each_slice(2).map do |(_line_src_start, index_start), (_line_src_end, index_end)|
|
|
60
|
+
index_start..index_end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Emit a one-time-per-file deprecation warning pointing the user
|
|
65
|
+
# at the `# simplecov:disable` / `# simplecov:enable` replacement.
|
|
66
|
+
def warn_nocov_deprecation(first_line_number)
|
|
67
|
+
return unless self.class.nocov_warned.add?(@filename)
|
|
68
|
+
|
|
69
|
+
token = SimpleCov.current_nocov_token
|
|
70
|
+
warn "#{@filename}:#{first_line_number}: [DEPRECATION] `# :#{token}:` is deprecated and will be removed " \
|
|
71
|
+
"in a future release. Replace with `# simplecov:disable` / `# simplecov:enable` block comments."
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
|
6
|
+
# shebang and `coding:` magic comment when present. Always
|
|
7
|
+
# transcodes to UTF-8 with invalid/undef bytes replaced — both for
|
|
8
|
+
# JRuby compatibility and to keep encoding shenanigans in one place
|
|
9
|
+
# (see #866).
|
|
10
|
+
module SourceLoader
|
|
11
|
+
SHEBANG_REGEX = /\A#!/
|
|
12
|
+
RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def call(filename)
|
|
17
|
+
lines = []
|
|
18
|
+
# The default encoding is UTF-8
|
|
19
|
+
File.open(filename, "rb:UTF-8") do |file|
|
|
20
|
+
current_line = file.gets
|
|
21
|
+
|
|
22
|
+
if shebang?(current_line)
|
|
23
|
+
lines << current_line
|
|
24
|
+
current_line = file.gets
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
read_lines(file, lines, current_line)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def shebang?(line)
|
|
32
|
+
SHEBANG_REGEX.match?(line)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def read_lines(file, lines, current_line)
|
|
36
|
+
return lines unless current_line
|
|
37
|
+
|
|
38
|
+
set_encoding_based_on_magic_comment(file, current_line)
|
|
39
|
+
lines.concat([current_line], ensure_remove_undefs(file.readlines))
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Encoding magic comment must be placed at first line except for
|
|
43
|
+
# shebang.
|
|
44
|
+
def set_encoding_based_on_magic_comment(file, line)
|
|
45
|
+
if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
|
|
46
|
+
file.set_encoding(match[1], "UTF-8")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# invalid/undef replace are technically not really necessary but
|
|
51
|
+
# nice to have and work around a JRuby incompatibility. Setting
|
|
52
|
+
# these options on `file.set_encoding` doesn't seem to work
|
|
53
|
+
# properly, so it has to be done here.
|
|
54
|
+
def ensure_remove_undefs(file_lines)
|
|
55
|
+
file_lines.each do |line|
|
|
56
|
+
# simplecov:disable — defensive: only fires for non-UTF-8 source files
|
|
57
|
+
line.encode!("UTF-8", invalid: :replace, undef: :replace) unless line.encoding == Encoding::UTF_8
|
|
58
|
+
# simplecov:enable
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|