simplecov 1.0.2 → 1.1.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 +65 -1425
- data/lib/simplecov/atomic_file.rb +70 -0
- data/lib/simplecov/cli/clean.rb +43 -3
- data/lib/simplecov/cli/command_helpers.rb +55 -0
- data/lib/simplecov/cli/coverage.rb +21 -28
- data/lib/simplecov/cli/coverage_file.rb +65 -0
- data/lib/simplecov/cli/diff.rb +43 -48
- data/lib/simplecov/cli/dotfile.rb +12 -6
- data/lib/simplecov/cli/merge.rb +12 -10
- data/lib/simplecov/cli/open.rb +3 -5
- data/lib/simplecov/cli/report.rb +31 -23
- data/lib/simplecov/cli/serve/report_preparer.rb +29 -0
- data/lib/simplecov/cli/serve/static_file_handler.rb +122 -0
- data/lib/simplecov/cli/serve.rb +30 -94
- data/lib/simplecov/cli/uncovered.rb +20 -25
- data/lib/simplecov/cli.rb +15 -2
- data/lib/simplecov/combine/branches_combiner.rb +43 -19
- data/lib/simplecov/combine/coverage_accumulator.rb +268 -0
- data/lib/simplecov/combine/identity_interner.rb +30 -0
- data/lib/simplecov/combine/interned_counts.rb +30 -0
- data/lib/simplecov/combine/lines_combiner.rb +48 -20
- data/lib/simplecov/combine/methods_combiner.rb +46 -21
- data/lib/simplecov/combine/results_combiner.rb +12 -37
- data/lib/simplecov/combine.rb +5 -23
- data/lib/simplecov/command_guesser.rb +66 -9
- data/lib/simplecov/configuration/coverage.rb +12 -15
- data/lib/simplecov/configuration/coverage_criteria.rb +34 -38
- data/lib/simplecov/configuration/eval_coverage.rb +41 -0
- data/lib/simplecov/configuration/filters.rb +11 -44
- data/lib/simplecov/configuration/formatting.rb +18 -9
- data/lib/simplecov/configuration/groups.rb +42 -0
- data/lib/simplecov/configuration/merging.rb +8 -7
- data/lib/simplecov/configuration/thresholds.rb +14 -13
- data/lib/simplecov/configuration.rb +13 -43
- data/lib/simplecov/coverage_json.rb +24 -0
- data/lib/simplecov/coverage_statistics.rb +1 -1
- data/lib/simplecov/coverage_violations.rb +15 -5
- data/lib/simplecov/defaults.rb +7 -3
- data/lib/simplecov/directive.rb +1 -1
- data/lib/simplecov/exit_codes/check.rb +33 -0
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +11 -20
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +3 -16
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +14 -23
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +14 -25
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +4 -17
- data/lib/simplecov/exit_codes.rb +1 -0
- data/lib/simplecov/exit_handling.rb +11 -41
- data/lib/simplecov/file_list.rb +5 -10
- data/lib/simplecov/filter.rb +43 -9
- data/lib/simplecov/formatter/base.rb +11 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +97 -0
- data/lib/simplecov/formatter/html_formatter/public/index.html +35 -5
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +96 -0
- data/lib/simplecov/formatter/html_formatter.rb +67 -47
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +54 -56
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +78 -88
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +70 -76
- data/lib/simplecov/formatter/json_formatter.rb +5 -48
- data/lib/simplecov/formatter/multi_formatter.rb +1 -1
- data/lib/simplecov/formatter/simple_formatter.rb +8 -5
- data/lib/simplecov/formatter.rb +12 -0
- data/lib/simplecov/group_names.rb +32 -0
- data/lib/simplecov/last_run.rb +16 -9
- data/lib/simplecov/lines_classifier.rb +29 -8
- data/lib/simplecov/load_global_config.rb +5 -2
- data/lib/simplecov/parallel_adapters/base.rb +17 -0
- data/lib/simplecov/parallel_adapters/generic.rb +2 -2
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +2 -2
- data/lib/simplecov/parallel_coordination.rb +6 -1
- data/lib/simplecov/parallel_result_merger.rb +230 -0
- data/lib/simplecov/report_deferral.rb +49 -0
- data/lib/simplecov/report_stamp.rb +28 -0
- data/lib/simplecov/result.rb +40 -10
- data/lib/simplecov/result_adapter.rb +48 -16
- data/lib/simplecov/result_merger/resultset_file.rb +43 -7
- data/lib/simplecov/result_merger/resultset_run_identity.rb +67 -0
- data/lib/simplecov/result_merger/resultset_store.rb +15 -12
- data/lib/simplecov/result_merger/unloaded_files.rb +103 -0
- data/lib/simplecov/result_merger.rb +69 -42
- data/lib/simplecov/result_processing.rb +82 -43
- data/lib/simplecov/run_identity.rb +77 -0
- data/lib/simplecov/simulate_coverage.rb +35 -11
- data/lib/simplecov/source_file/method.rb +7 -1
- data/lib/simplecov/source_file/ruby_data_parser.rb +25 -3
- data/lib/simplecov/source_file/skip_chunks.rb +7 -10
- data/lib/simplecov/source_file/source_loader.rb +23 -7
- data/lib/simplecov/source_file/statistics.rb +24 -16
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +203 -13
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +19 -30
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +7 -0
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +55 -0
- data/lib/simplecov/static_coverage_extractor/value_position.rb +6 -14
- data/lib/simplecov/static_coverage_extractor/visitor.rb +23 -36
- data/lib/simplecov/unloaded_file_injector.rb +75 -0
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov.rb +11 -5
- data/schemas/coverage-v1.0.schema.json +2 -2
- data/schemas/coverage.schema.json +2 -2
- data/sig/simplecov.rbs +206 -67
- metadata +28 -14
- data/doc/alternate-formatters.md +0 -66
- data/doc/commercial-services.md +0 -25
- data/doc/editor-integration.md +0 -18
- data/lib/simplecov/combine/files_combiner.rb +0 -70
- data/lib/simplecov/formatter/html_formatter/public/application.css +0 -1
- data/lib/simplecov/formatter/html_formatter/public/application.js +0 -18
- data/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "prism_compat"
|
|
3
4
|
require_relative "condition_folding"
|
|
4
5
|
require_relative "location_conventions"
|
|
5
6
|
require_relative "method_collector"
|
|
@@ -7,42 +8,13 @@ require_relative "value_position"
|
|
|
7
8
|
|
|
8
9
|
module SimpleCov
|
|
9
10
|
module StaticCoverageExtractor
|
|
10
|
-
# `Prism::IfNode#subsequent` was renamed from `consequent` in Prism
|
|
11
|
-
# 1.3 (Dec 2024). Ruby 3.3's stdlib still ships an older Prism that
|
|
12
|
-
# only exposes `consequent`; 3.4+ and any project that's done
|
|
13
|
-
# `gem install prism` exposes `subsequent`. Resolve the method name
|
|
14
|
-
# ONCE here so the per-node hot path stays branch-free. The
|
|
15
|
-
# not-taken arm on whichever Prism version we're on can't be
|
|
16
|
-
# exercised by our own dogfood (we only run on one Prism at a time).
|
|
17
|
-
# simplecov:disable
|
|
18
|
-
IF_NODE_SUBSEQUENT_METHOD =
|
|
19
|
-
if ::Prism::IfNode.method_defined?(:subsequent)
|
|
20
|
-
:subsequent
|
|
21
|
-
else
|
|
22
|
-
:consequent
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
# The same Prism 1.3 rename hit the `else` accessor on `UnlessNode`,
|
|
26
|
-
# `CaseNode`, and `CaseMatchNode` (all three: `consequent` ->
|
|
27
|
-
# `else_clause`). Ruby 3.3's stdlib Prism (0.19) only exposes
|
|
28
|
-
# `consequent`, so reaching for `else_clause` there raised
|
|
29
|
-
# NoMethodError inside the extractor — `call` swallowed it and the
|
|
30
|
-
# whole file silently fell back to no simulated data for any
|
|
31
|
-
# `unless`/`else` or empty-arm `case`. Resolve the name once, like
|
|
32
|
-
# IF_NODE_SUBSEQUENT_METHOD. All three nodes renamed together, so one
|
|
33
|
-
# constant (probed off CaseNode) covers them.
|
|
34
|
-
ELSE_CLAUSE_METHOD =
|
|
35
|
-
if ::Prism::CaseNode.method_defined?(:else_clause)
|
|
36
|
-
:else_clause
|
|
37
|
-
else
|
|
38
|
-
:consequent
|
|
39
|
-
end
|
|
40
|
-
# simplecov:enable
|
|
41
|
-
|
|
42
11
|
# Prism visitor that accumulates branch and method tuples in the
|
|
43
12
|
# shape Ruby's `Coverage` reports. Tuple ids are sequential across
|
|
44
|
-
# the file
|
|
45
|
-
#
|
|
13
|
+
# the file like `Coverage`'s, but the numbering order can differ
|
|
14
|
+
# (e.g. `case`/`when` and chained `&.` are visited in a different
|
|
15
|
+
# order than Coverage numbers them). That's fine: the combiners
|
|
16
|
+
# intern on source span and the report output drops ids, so nothing
|
|
17
|
+
# downstream compares them. Only defined when Prism is loadable;
|
|
46
18
|
# `StaticCoverageExtractor.available?` is the runtime gate.
|
|
47
19
|
class Visitor < ::Prism::Visitor
|
|
48
20
|
# Method tuples and the class/module nesting that names them are
|
|
@@ -64,6 +36,7 @@ module SimpleCov
|
|
|
64
36
|
@next_id = 0
|
|
65
37
|
@class_stack = []
|
|
66
38
|
@value_positions = nil
|
|
39
|
+
@suppress_methods = false
|
|
67
40
|
end
|
|
68
41
|
|
|
69
42
|
# Entry point for a parsed file. On legacy Rubies the location of an
|
|
@@ -84,13 +57,27 @@ module SimpleCov
|
|
|
84
57
|
# `else`, another IfNode for `elsif`). When the subsequent is
|
|
85
58
|
# missing, Coverage synthesizes a `:else` arm attributed to the
|
|
86
59
|
# whole condition's range — we do the same.
|
|
60
|
+
#
|
|
61
|
+
# A folded condition emits no tuple, and on modern Rubies only its
|
|
62
|
+
# live arm is descended into: the compiler eliminates the dead
|
|
63
|
+
# arm's entire subtree, so a branch or method nested there would be
|
|
64
|
+
# a phantom no loaded run can produce. A falsy `if`'s elsif chain
|
|
65
|
+
# survives as a plain `if`, which is what visiting the subsequent
|
|
66
|
+
# IfNode emits. On 3.2 the dead arm is visited too, branches only
|
|
67
|
+
# (see visit_folded_arms).
|
|
87
68
|
def visit_if_node(node)
|
|
88
|
-
|
|
69
|
+
verdict = folded_condition(node.predicate)
|
|
70
|
+
return visit_folded_arms(verdict, node.statements, PrismCompat.subsequent(node)) if verdict
|
|
71
|
+
|
|
72
|
+
emit_if_like(node, :if)
|
|
89
73
|
super
|
|
90
74
|
end
|
|
91
75
|
|
|
92
76
|
def visit_unless_node(node)
|
|
93
|
-
|
|
77
|
+
verdict = folded_condition(node.predicate)
|
|
78
|
+
return visit_folded_arms(verdict, PrismCompat.else_clause(node), node.statements) if verdict
|
|
79
|
+
|
|
80
|
+
emit_if_like(node, :unless)
|
|
94
81
|
super
|
|
95
82
|
end
|
|
96
83
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "simulate_coverage"
|
|
4
|
+
|
|
5
|
+
module SimpleCov
|
|
6
|
+
# Fills in coverage for files that were tracked (via `cover` / `track_files`)
|
|
7
|
+
# but never loaded, so they count toward the denominators instead of being
|
|
8
|
+
# absent from the report entirely.
|
|
9
|
+
#
|
|
10
|
+
# This is the mechanism; the merge-time policy of *when* to inject and
|
|
11
|
+
# which criteria the simulated files carry lives in
|
|
12
|
+
# `ResultMerger::UnloadedFiles` — easy names to confuse.
|
|
13
|
+
#
|
|
14
|
+
# Everything arrives as arguments rather than being read from the SimpleCov
|
|
15
|
+
# singleton. The merge step performs this injection on behalf of the processes
|
|
16
|
+
# that contributed to it, and it does not necessarily share their
|
|
17
|
+
# configuration: a standalone `collate` never ran `SimpleCov.start`, so it has
|
|
18
|
+
# no `cover` glob of its own and takes the tracked paths from the resultsets
|
|
19
|
+
# instead. See #1250.
|
|
20
|
+
module UnloadedFileInjector
|
|
21
|
+
module_function
|
|
22
|
+
|
|
23
|
+
# Expand `globs` into absolute paths, relative to `root` rather than
|
|
24
|
+
# `Dir.pwd` — test runners that chdir (or CI scripts that invoke the suite
|
|
25
|
+
# from a subdirectory) would otherwise silently miss files and produce a
|
|
26
|
+
# different set per environment. See #1106.
|
|
27
|
+
# `reject:` are the producing process's path-decidable filters. Paths its
|
|
28
|
+
# own report would exclude must not be recorded as tracked, or a merge that
|
|
29
|
+
# does not share the configuration would simulate them back in. Before
|
|
30
|
+
# injection moved to the merge the producer filtered them out of its own
|
|
31
|
+
# result and they never reached a resultset. A block filter is handed the
|
|
32
|
+
# source file and may consult coverage that does not exist yet, so those
|
|
33
|
+
# still fall to the merging process's filter chain. See #1250.
|
|
34
|
+
def discover(globs, root:, reject: [])
|
|
35
|
+
paths = globs.compact
|
|
36
|
+
.flat_map { |glob| Dir.glob(glob, base: root) }
|
|
37
|
+
.uniq
|
|
38
|
+
.map { |path| File.expand_path(path, root) }
|
|
39
|
+
return paths if reject.empty?
|
|
40
|
+
|
|
41
|
+
paths.reject { |path| rejected?(path, reject) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# `SourceFile` reads source and builds lines lazily, so a path-only filter
|
|
45
|
+
# answers without anything here touching disk.
|
|
46
|
+
def rejected?(path, filters)
|
|
47
|
+
candidate = SourceFile.new(path, NO_COVERAGE_YET, loaded: false)
|
|
48
|
+
filters.any? { |filter| filter.matches?(candidate) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
no_coverage_yet = {"lines" => []} #: Hash[String, Array[untyped]]
|
|
52
|
+
NO_COVERAGE_YET = no_coverage_yet.freeze
|
|
53
|
+
private_constant :NO_COVERAGE_YET
|
|
54
|
+
|
|
55
|
+
# Add simulated coverage for every path `coverage` does not already carry.
|
|
56
|
+
# Paths that are present are left alone, whoever put them there, which is
|
|
57
|
+
# what lets this run over resultsets a previous SimpleCov already injected
|
|
58
|
+
# into without double-counting or overwriting real data.
|
|
59
|
+
#
|
|
60
|
+
# Returns the augmented coverage and the set of paths that were added.
|
|
61
|
+
def call(coverage, paths, synthesize:, lines:)
|
|
62
|
+
injected = Set.new
|
|
63
|
+
augmented = coverage.dup
|
|
64
|
+
|
|
65
|
+
paths.each do |path|
|
|
66
|
+
next if augmented.key?(path)
|
|
67
|
+
|
|
68
|
+
augmented[path] = SimulateCoverage.call(path, synthesize: synthesize, lines: lines)
|
|
69
|
+
injected << path
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
[augmented, injected]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/simplecov/version.rb
CHANGED
data/lib/simplecov.rb
CHANGED
|
@@ -158,10 +158,8 @@ module SimpleCov
|
|
|
158
158
|
::Process.respond_to?(:_fork)
|
|
159
159
|
# simplecov:enable
|
|
160
160
|
|
|
161
|
-
#
|
|
162
|
-
|
|
163
|
-
# `current` is memoized; subsequent calls are cheap.
|
|
164
|
-
SimpleCov::ParallelAdapters.current
|
|
161
|
+
# Select the parallel adapter and generate identities before any forks.
|
|
162
|
+
SimpleCov::RunIdentity.prepare
|
|
165
163
|
|
|
166
164
|
@result = nil
|
|
167
165
|
self.pid = Process.pid
|
|
@@ -230,6 +228,7 @@ end
|
|
|
230
228
|
require "forwardable"
|
|
231
229
|
require_relative "simplecov/color"
|
|
232
230
|
require_relative "simplecov/deprecation"
|
|
231
|
+
require_relative "simplecov/group_names"
|
|
233
232
|
require_relative "simplecov/configuration"
|
|
234
233
|
SimpleCov.extend SimpleCov::Configuration
|
|
235
234
|
require_relative "simplecov/coverage_statistics"
|
|
@@ -245,22 +244,29 @@ require_relative "simplecov/result"
|
|
|
245
244
|
require_relative "simplecov/filter"
|
|
246
245
|
require_relative "simplecov/formatter"
|
|
247
246
|
require_relative "simplecov/last_run"
|
|
247
|
+
require_relative "simplecov/report_stamp"
|
|
248
248
|
require_relative "simplecov/lines_classifier"
|
|
249
249
|
require_relative "simplecov/result_merger"
|
|
250
|
+
require_relative "simplecov/parallel_result_merger"
|
|
250
251
|
require_relative "simplecov/parallel_adapters"
|
|
252
|
+
require_relative "simplecov/run_identity"
|
|
253
|
+
SimpleCov.extend SimpleCov::RunIdentity::Accessors
|
|
251
254
|
require_relative "simplecov/command_guesser"
|
|
252
255
|
require_relative "simplecov/version"
|
|
253
256
|
require_relative "simplecov/result_adapter"
|
|
254
257
|
require_relative "simplecov/combine"
|
|
258
|
+
require_relative "simplecov/combine/identity_interner"
|
|
255
259
|
require_relative "simplecov/combine/branches_combiner"
|
|
256
260
|
require_relative "simplecov/combine/methods_combiner"
|
|
257
|
-
require_relative "simplecov/combine/files_combiner"
|
|
258
261
|
require_relative "simplecov/combine/lines_combiner"
|
|
262
|
+
require_relative "simplecov/combine/coverage_accumulator"
|
|
259
263
|
require_relative "simplecov/combine/results_combiner"
|
|
260
264
|
require_relative "simplecov/useless_results_remover"
|
|
261
265
|
require_relative "simplecov/simulate_coverage"
|
|
266
|
+
require_relative "simplecov/unloaded_file_injector"
|
|
262
267
|
require_relative "simplecov/result_processing"
|
|
263
268
|
require_relative "simplecov/exit_handling"
|
|
269
|
+
require_relative "simplecov/report_deferral"
|
|
264
270
|
require_relative "simplecov/parallel_coordination"
|
|
265
271
|
|
|
266
272
|
# Load default config
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
},
|
|
255
255
|
"total": {"type": "integer", "minimum": 0, "description": "Executable lines: `covered + missed`. Does not include `omitted`."},
|
|
256
256
|
"percent": {"type": "number"},
|
|
257
|
-
"strength": {"type": "number", "description": "Average
|
|
257
|
+
"strength": {"type": "number", "description": "Average executions per coverable line: the sum of hit counts divided by `covered + missed`."}
|
|
258
258
|
}
|
|
259
259
|
},
|
|
260
260
|
"coverage_statistic": {
|
|
@@ -266,7 +266,7 @@
|
|
|
266
266
|
"missed": {"type": "integer", "minimum": 0},
|
|
267
267
|
"total": {"type": "integer", "minimum": 0, "description": "Total branches or methods: `covered + missed`."},
|
|
268
268
|
"percent": {"type": "number"},
|
|
269
|
-
"strength": {"type": "number", "description": "Average
|
|
269
|
+
"strength": {"type": "number", "description": "Average executions per coverable branch or method: the sum of hit counts divided by `covered + missed`."}
|
|
270
270
|
}
|
|
271
271
|
},
|
|
272
272
|
"line_coverage": {
|
|
@@ -254,7 +254,7 @@
|
|
|
254
254
|
},
|
|
255
255
|
"total": {"type": "integer", "minimum": 0, "description": "Executable lines: `covered + missed`. Does not include `omitted`."},
|
|
256
256
|
"percent": {"type": "number"},
|
|
257
|
-
"strength": {"type": "number", "description": "Average
|
|
257
|
+
"strength": {"type": "number", "description": "Average executions per coverable line: the sum of hit counts divided by `covered + missed`."}
|
|
258
258
|
}
|
|
259
259
|
},
|
|
260
260
|
"coverage_statistic": {
|
|
@@ -266,7 +266,7 @@
|
|
|
266
266
|
"missed": {"type": "integer", "minimum": 0},
|
|
267
267
|
"total": {"type": "integer", "minimum": 0, "description": "Total branches or methods: `covered + missed`."},
|
|
268
268
|
"percent": {"type": "number"},
|
|
269
|
-
"strength": {"type": "number", "description": "Average
|
|
269
|
+
"strength": {"type": "number", "description": "Average executions per coverable branch or method: the sum of hit counts divided by `covered + missed`."}
|
|
270
270
|
}
|
|
271
271
|
},
|
|
272
272
|
"line_coverage": {
|