simplecov 1.0.0.rc2 → 1.0.0.rc4
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/CHANGELOG.md +26 -0
- data/README.md +58 -6
- data/lib/simplecov/combine/branches_combiner.rb +24 -3
- data/lib/simplecov/configuration/merging.rb +90 -0
- data/lib/simplecov/configuration.rb +7 -2
- data/lib/simplecov/deprecation.rb +0 -2
- data/lib/simplecov/exit_handling.rb +6 -6
- data/lib/simplecov/formatter/json_formatter.rb +18 -8
- data/lib/simplecov/lines_classifier.rb +0 -1
- data/lib/simplecov/parallel_adapters/generic.rb +2 -0
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +15 -5
- data/lib/simplecov/parallel_coordination.rb +14 -16
- data/lib/simplecov/process.rb +7 -1
- data/lib/simplecov/result.rb +13 -2
- data/lib/simplecov/result_adapter.rb +2 -2
- data/lib/simplecov/result_merger.rb +5 -1
- data/lib/simplecov/result_processing.rb +25 -8
- data/lib/simplecov/source_file/skip_chunks.rb +0 -2
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +55 -0
- data/lib/simplecov/static_coverage_extractor/visitor.rb +23 -45
- data/lib/simplecov/static_coverage_extractor.rb +8 -9
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov.rb +16 -2
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e60ecd48ca2790532fba8cdf76aaf6f29f18dd08c27fdc4d349988d2d60d6cd6
|
|
4
|
+
data.tar.gz: 600d7ca6449e36825de427633b8b6890cd77f292ad661c4a86b325e3424081bd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 49db7a5cf640b63fd877832f67580a15ea384a79ce6f4778be807474170b69dde752c58f60729ce5ba2e2a9d3266a53704ff70cbf76b4f70c697d28f4553e200
|
|
7
|
+
data.tar.gz: e69d632cb5f3e0a4b8d7e65c9bbfae5bb6b779df1b37c28227374ca799a9a57910438a0bc6c32b1e34785e04db70e19ce53db33771ddf7d2bb5d4cf9c9dccb5f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
1.0.0.rc4 (2026-06-26)
|
|
2
|
+
======================
|
|
3
|
+
|
|
4
|
+
## Enhancements
|
|
5
|
+
* Added `SimpleCov.finalize_merge` to separate storing mergeable worker resultsets from owning final report finalization. Parallel workers that write to explicit custom coverage destinations can now store their shard `.resultset.json` files without waiting on sibling shards they cannot see; an explicit `SimpleCov.collate` cleanup step then formats the merged report, enforces thresholds, and writes `.last_run.json`. SimpleCov infers this external-finalization mode only for recognized multi-worker parallel runs with merging enabled and a custom coverage destination, and emits a configuration warning until users set `finalize_merge false` (or `true`) explicitly. See #1215.
|
|
6
|
+
|
|
7
|
+
## Bugfixes
|
|
8
|
+
* The `parallel_tests` adapter now only activates and uses the native wait API when the native pid-file synchronization contract is present. Processes that inherit `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` without `PARALLEL_PID_FILE`, or lose `PARALLEL_PID_FILE` before SimpleCov's `at_exit` hook runs, now use the generic resultset polling path instead of calling `ParallelTests.wait_for_other_processes_to_finish` and failing when `parallel_tests` fetches the missing pid-file path. See #1210.
|
|
9
|
+
* The default `at_exit` formatter now writes reports only from the final parallel-test worker while still storing each worker's resultset for the final merge, so JSON/XML/HTML formatters no longer clobber canonical coverage files from non-final workers. See #1210.
|
|
10
|
+
* `SimpleCov.parallel_tests false` now disables the generic `TEST_ENV_NUMBER` adapter as well as the `parallel_tests` gem adapter, so projects that use those environment variables for a different coverage collation flow can opt out consistently. See #1208.
|
|
11
|
+
* Parallel result coordination now stores the final worker's own resultset before waiting for sibling resultsets, preventing an off-by-one timeout where the final worker reported `N-1` of `N` workers and skipped threshold checks immediately before producing a complete merged report. See #1208.
|
|
12
|
+
* Static branch coverage now matches Ruby's runtime branch tuple identities for `unless` and safe-navigation calls, and resultset merges now combine serialized branch tuples by source location instead of by their local sequential ids. This prevents equivalent branches from being duplicated when static and runtime branch extraction assign different ids. See #1206.
|
|
13
|
+
|
|
14
|
+
1.0.0.rc3 (2026-06-18)
|
|
15
|
+
======================
|
|
16
|
+
|
|
17
|
+
## Breaking Changes
|
|
18
|
+
* Dropped support for Ruby 3.1 and JRuby 9.4. The minimum is now Ruby 3.2 (and JRuby 10, which reports `RUBY_VERSION` 3.4). Ruby 3.1 reached end of life in March 2025, and a recent `i18n` release calls `Fiber[]`, a Ruby 3.2 API, at load time, so suites that load Rails no longer run on 3.1. Raising `required_ruby_version` to `>= 3.2` also excludes JRuby 9.4, which reports `RUBY_VERSION` 3.1.x. See #1171.
|
|
19
|
+
|
|
20
|
+
## Enhancements
|
|
21
|
+
* Added `SimpleCov.parallel_wait_timeout` (default 60 seconds), which controls how long the process that writes the final report waits for the other parallel-test workers to finish writing their resultsets before it merges. Raise it when one worker runs much heavier test files and routinely finishes well after the others, so its coverage is included in the merge and the minimum and maximum coverage checks run against the full total instead of being skipped against a partial one. See #1171.
|
|
22
|
+
|
|
23
|
+
## Bugfixes
|
|
24
|
+
* The "SimpleCov dropped N source file(s)" warning is now emitted at most once, from the process that writes the final report, instead of once per parallel worker. It was previously raised every time a result was built, so an eight-worker run produced eight copies. Fork-based runners that do not set `TEST_ENV_NUMBER`, such as Minitest's `parallelize`, match no parallel-test adapter, so SimpleCov now marks forked children (it already hooks `Process._fork`) and treats them as non-reporters when no adapter is active, leaving the process that did the forking to merge every slice and report once. See #1171.
|
|
25
|
+
* Using `[SimpleCov::Formatter::HTMLFormatter, SimpleCov::Formatter::JSONFormatter]` together no longer prints a false "coverage.json was written after this process started" warning. As of 1.0 the HTML formatter writes `coverage.json` itself, so the JSON formatter found a file its own run had just written and mistook it for a concurrent worker about to lose data. The warning is now skipped when the existing file belongs to the same merged result. Because the HTML formatter already writes `coverage.json`, listing `JSONFormatter` alongside it is redundant and can be removed. See #1171.
|
|
26
|
+
|
|
1
27
|
1.0.0.rc2 (2026-06-10)
|
|
2
28
|
======================
|
|
3
29
|
|
data/README.md
CHANGED
|
@@ -160,7 +160,8 @@ This is recommended whenever you merge frameworks that rely on each other, like
|
|
|
160
160
|
> Calling `SimpleCov.start` directly from `.simplecov` is deprecated. Tracking still begins for backward
|
|
161
161
|
> compatibility, but a one-time deprecation warning fires; a future release will require the explicit `SimpleCov.start`
|
|
162
162
|
> from a test helper. Migrating prevents a long-standing bug where `.simplecov` auto-loaded in a Rakefile or Rails'
|
|
163
|
-
> `Bundler.require` would leave an empty parent-process report that overwrites the test subprocess's good one. See
|
|
163
|
+
> `Bundler.require` would leave an empty parent-process report that overwrites the test subprocess's good one. See
|
|
164
|
+
> [#581](https://github.com/simplecov-ruby/simplecov/issues/581).
|
|
164
165
|
|
|
165
166
|
### Changing the report location
|
|
166
167
|
|
|
@@ -217,8 +218,8 @@ Brand-new in the redesigned API (no legacy method to migrate from):
|
|
|
217
218
|
|-------------------------------------|--------------------------------------------------------------------------------------------------------------------------|
|
|
218
219
|
| `cover "lib/**/*.rb"` | Positive scope (allowlist). Multiple calls union; strings are globs. See above for the relationship with `track_files`. |
|
|
219
220
|
| `no_default_skips` | Clear every previously-installed filter — defaults and anything earlier in the block — so subsequent `skip`s start clean.|
|
|
220
|
-
| `formatter false` / `formatters []` | Opt out of formatting entirely. Workers in big parallel CI runs only need their `.resultset.json` for a final `SimpleCov.collate` step; skipping the formatter saves the per-job HTML / multi-formatter overhead. See #964. |
|
|
221
|
-
| `parallel_tests true` / `false` | Force on / off the auto-require of the `parallel_tests` gem. Default (unset) auto-detects from `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` and silently skips if the gem isn't installed. Set explicitly when you use those env vars for unrelated subprocess coordination. See #1018. |
|
|
221
|
+
| `formatter false` / `formatters []` | Opt out of formatting entirely. Workers in big parallel CI runs only need their `.resultset.json` for a final `SimpleCov.collate` step; skipping the formatter saves the per-job HTML / multi-formatter overhead. See [#964](https://github.com/simplecov-ruby/simplecov/issues/964). |
|
|
222
|
+
| `parallel_tests true` / `false` | Force on / off the auto-require of the `parallel_tests` gem. Default (unset) auto-detects from `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` and silently skips if the gem isn't installed. Set explicitly when you use those env vars for unrelated subprocess coordination. See [#1018](https://github.com/simplecov-ruby/simplecov/issues/1018). |
|
|
222
223
|
|
|
223
224
|
Example before/after:
|
|
224
225
|
|
|
@@ -742,6 +743,57 @@ Cached coverage data eventually goes stale, so result sets older than `SimpleCov
|
|
|
742
743
|
merge. The default is 600 seconds (10 minutes); raise or lower it with `SimpleCov.merge_timeout 3600` (1 hour), or
|
|
743
744
|
`merge_timeout 3600` inside a configure/start block. Deactivate automatic merging entirely with `SimpleCov.merging false`.
|
|
744
745
|
|
|
746
|
+
In a parallel run, the process that writes the final report waits for the other workers to finish and write their
|
|
747
|
+
result sets before merging. It gives up after `SimpleCov.parallel_wait_timeout` seconds (default 60) and reports
|
|
748
|
+
whatever has arrived, skipping the minimum / maximum coverage checks against that partial total. If one worker runs
|
|
749
|
+
much heavier test files and routinely finishes a minute or more after the others, raise it with
|
|
750
|
+
`SimpleCov.parallel_wait_timeout 180` so its coverage is included.
|
|
751
|
+
|
|
752
|
+
### Merge finalization ownership
|
|
753
|
+
|
|
754
|
+
`SimpleCov.merging true` stores each process' resultset so it can be merged with other suites or workers. By default,
|
|
755
|
+
SimpleCov also owns **finalizing** that merge: waiting for sibling workers, building the merged result, formatting the
|
|
756
|
+
report, enforcing minimum / maximum coverage, and writing `.last_run.json`.
|
|
757
|
+
|
|
758
|
+
Some parallel runners intentionally write each worker's resultset to a separate coverage directory and then run an
|
|
759
|
+
explicit cleanup step with `SimpleCov.collate`. In that setup, workers should still store their resultsets, but the
|
|
760
|
+
cleanup task owns finalization:
|
|
761
|
+
|
|
762
|
+
```ruby
|
|
763
|
+
# spec/spec_helper.rb
|
|
764
|
+
SimpleCov.start do
|
|
765
|
+
if ENV["TEST_ENV_NUMBER"]
|
|
766
|
+
merging true
|
|
767
|
+
coverage_dir "coverage/turbo_tests/#{ENV["TEST_ENV_NUMBER"]}"
|
|
768
|
+
command_name "rspec-#{ENV["TEST_ENV_NUMBER"]}"
|
|
769
|
+
finalize_merge false
|
|
770
|
+
end
|
|
771
|
+
end
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
```ruby
|
|
775
|
+
# Rakefile
|
|
776
|
+
task "coverage:collate" do
|
|
777
|
+
require "simplecov"
|
|
778
|
+
|
|
779
|
+
SimpleCov.collate Dir["coverage/turbo_tests/*/.resultset.json"] do
|
|
780
|
+
coverage(:line) { minimum 100 }
|
|
781
|
+
coverage(:branch) { minimum 100 }
|
|
782
|
+
end
|
|
783
|
+
end
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
When `finalize_merge false`, the worker writes its `.resultset.json` and exits without waiting for siblings, formatting,
|
|
787
|
+
checking thresholds, or writing `.last_run.json`. The `SimpleCov.collate` process is the finalizer and performs those
|
|
788
|
+
steps for the merged result.
|
|
789
|
+
|
|
790
|
+
For compatibility, SimpleCov infers `finalize_merge false` and prints a configuration warning only when all of these are
|
|
791
|
+
true: a recognized parallel adapter is active, more than one worker is expected, merging is enabled, the coverage
|
|
792
|
+
destination was explicitly changed from the default, and the process has parallel-worker environment variables. Set
|
|
793
|
+
`SimpleCov.finalize_merge false` to keep external collation ownership without the warning, or
|
|
794
|
+
`SimpleCov.finalize_merge true` if the selected worker should own the built-in wait / merge / report flow even with a
|
|
795
|
+
custom coverage destination.
|
|
796
|
+
|
|
745
797
|
### Merging across execution environments
|
|
746
798
|
|
|
747
799
|
If your tests run in parallel across multiple build machines, download each run's `.resultset.json` and merge them into
|
|
@@ -860,14 +912,14 @@ SimpleCov coordinates with parallel test runners through a small pluggable adapt
|
|
|
860
912
|
|
|
861
913
|
- **`ParallelTestsAdapter`** — wraps the [grosser/parallel_tests](https://github.com/grosser/parallel_tests) gem and
|
|
862
914
|
uses its `ParallelTests.first_process?` / `ParallelTests.wait_for_other_processes_to_finish` APIs for precise worker
|
|
863
|
-
coordination.
|
|
915
|
+
coordination. Activates only when the native `parallel_tests` pid-file contract is present.
|
|
864
916
|
- **`GenericAdapter`** — catch-all for any runner that follows the `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` env-var
|
|
865
917
|
convention but doesn't ship a Ruby API (parallel_rspec, knapsack-style splitters, custom CI sharding scripts).
|
|
866
918
|
Activates when `TEST_ENV_NUMBER` is set and no more-specific adapter is.
|
|
867
919
|
|
|
868
920
|
Adapters are tried in registration order; the first whose `active?` returns `true` is chosen. With both built-ins, this
|
|
869
921
|
means parallel_tests users get the precise gem-based path and parallel_rspec (or any env-var-only runner) gets the
|
|
870
|
-
polling-based fallback without any configuration change. See #1065.
|
|
922
|
+
polling-based fallback without any configuration change. See [#1065](https://github.com/simplecov-ruby/simplecov/issues/1065).
|
|
871
923
|
|
|
872
924
|
#### Registering a custom adapter
|
|
873
925
|
|
|
@@ -1262,7 +1314,7 @@ anything; `-q` / `--quiet` suppresses status lines.
|
|
|
1262
1314
|
|
|
1263
1315
|
### Ruby version compatibility
|
|
1264
1316
|
|
|
1265
|
-
SimpleCov is built in [Continuous Integration] on Ruby 3.
|
|
1317
|
+
SimpleCov is built in [Continuous Integration] on Ruby 3.2+ and JRuby 10+. On CRuby, every coverage criterion
|
|
1266
1318
|
described above is available on the supported versions, with one exception: [eval coverage](#eval-coverage) requires
|
|
1267
1319
|
CRuby 3.2+.
|
|
1268
1320
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../source_file/ruby_data_parser"
|
|
4
|
+
|
|
3
5
|
module SimpleCov
|
|
4
6
|
module Combine
|
|
5
7
|
#
|
|
@@ -22,11 +24,30 @@ module SimpleCov
|
|
|
22
24
|
# @return [Hash]
|
|
23
25
|
#
|
|
24
26
|
def combine(coverage_a, coverage_b)
|
|
25
|
-
coverage_a.
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
combined = [coverage_a, coverage_b].each_with_object({}) do |coverage, memo|
|
|
28
|
+
coverage.each do |condition, branches_inside|
|
|
29
|
+
condition_key = tuple_identity(condition)
|
|
30
|
+
condition_tuple, merged_branches = memo[condition_key] ||= [condition, {}]
|
|
31
|
+
merge_branches(merged_branches, branches_inside)
|
|
32
|
+
memo[condition_key] = [condition_tuple, merged_branches]
|
|
28
33
|
end
|
|
29
34
|
end
|
|
35
|
+
|
|
36
|
+
combined.values.to_h { |condition, branches| [condition, branches.values.to_h] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def merge_branches(target, source)
|
|
40
|
+
source.each do |branch, count|
|
|
41
|
+
branch_key = tuple_identity(branch)
|
|
42
|
+
branch_tuple, existing_count = target[branch_key]
|
|
43
|
+
target[branch_key] = [branch_tuple || branch, existing_count ? existing_count + count : count]
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def tuple_identity(tuple)
|
|
48
|
+
tuple = SourceFile::RubyDataParser.call(tuple)
|
|
49
|
+
type, _id, start_line, start_column, end_line, end_column = tuple
|
|
50
|
+
[type, start_line, start_column, end_line, end_column]
|
|
30
51
|
end
|
|
31
52
|
end
|
|
32
53
|
end
|
|
@@ -54,6 +54,38 @@ module SimpleCov
|
|
|
54
54
|
@use_merging
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
+
#
|
|
58
|
+
# Get or set whether this process owns final merge processing:
|
|
59
|
+
# waiting for sibling workers, building the merged result, formatting,
|
|
60
|
+
# enforcing thresholds, and writing `.last_run.json`.
|
|
61
|
+
#
|
|
62
|
+
# Defaults to true, except for recognized multi-worker parallel runs
|
|
63
|
+
# that explicitly write to a custom coverage destination while merging
|
|
64
|
+
# is enabled. Those runs are likely using an external `SimpleCov.collate`
|
|
65
|
+
# step to finalize the merge.
|
|
66
|
+
#
|
|
67
|
+
def finalize_merge(value = :__no_arg__)
|
|
68
|
+
unless value == :__no_arg__
|
|
69
|
+
@finalize_merge = value
|
|
70
|
+
@finalize_merge_explicit = true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
return @finalize_merge if defined?(@finalize_merge_explicit) && @finalize_merge_explicit
|
|
74
|
+
|
|
75
|
+
inferred = inferred_finalize_merge?
|
|
76
|
+
warn_about_inferred_finalize_merge unless inferred
|
|
77
|
+
inferred
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def finalize_merge?
|
|
81
|
+
finalize_merge
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @api private
|
|
85
|
+
def merge_finalization_owner?
|
|
86
|
+
collating_result? || finalize_merge?
|
|
87
|
+
end
|
|
88
|
+
|
|
57
89
|
# DEPRECATED: alias for `merging`. Same value, same behavior.
|
|
58
90
|
def use_merging(use = nil)
|
|
59
91
|
SimpleCov::Deprecation.warn("`SimpleCov.use_merging` is deprecated. " \
|
|
@@ -70,5 +102,63 @@ module SimpleCov
|
|
|
70
102
|
@merge_timeout = seconds if seconds.is_a?(Integer)
|
|
71
103
|
@merge_timeout ||= 600
|
|
72
104
|
end
|
|
105
|
+
|
|
106
|
+
#
|
|
107
|
+
# Defines how long (in seconds) the reporting process waits for the
|
|
108
|
+
# remaining parallel-test workers to write their resultsets before it
|
|
109
|
+
# proceeds with a partial merge. Default is 60 seconds. Raise it when a
|
|
110
|
+
# slow worker routinely finishes well after the others, so its coverage
|
|
111
|
+
# is still included and the minimum / maximum coverage checks aren't
|
|
112
|
+
# skipped against a partial total.
|
|
113
|
+
#
|
|
114
|
+
def parallel_wait_timeout(seconds = nil)
|
|
115
|
+
@parallel_wait_timeout = seconds if seconds.is_a?(Integer)
|
|
116
|
+
@parallel_wait_timeout ||= 60
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def inferred_finalize_merge?
|
|
122
|
+
return true unless merging
|
|
123
|
+
|
|
124
|
+
adapter = SimpleCov::ParallelAdapters.current
|
|
125
|
+
return true unless adapter
|
|
126
|
+
return true unless adapter.expected_worker_count > 1
|
|
127
|
+
return true unless parallel_worker_environment?
|
|
128
|
+
return true unless explicit_custom_coverage_destination?
|
|
129
|
+
|
|
130
|
+
false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parallel_worker_environment?
|
|
134
|
+
ENV.key?("TEST_ENV_NUMBER") || ENV.key?("PARALLEL_TEST_GROUPS")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def explicit_custom_coverage_destination?
|
|
138
|
+
return false unless explicit_coverage_destination?
|
|
139
|
+
|
|
140
|
+
coverage_path != File.expand_path("coverage", root)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def explicit_coverage_destination?
|
|
144
|
+
(defined?(@coverage_path_explicit) && @coverage_path_explicit) ||
|
|
145
|
+
(defined?(@coverage_dir_explicit) && @coverage_dir_explicit)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def warn_about_inferred_finalize_merge
|
|
149
|
+
return if defined?(@finalize_merge_inference_warned) && @finalize_merge_inference_warned
|
|
150
|
+
return unless print_errors
|
|
151
|
+
|
|
152
|
+
@finalize_merge_inference_warned = true
|
|
153
|
+
warn SimpleCov::Color.colorize(inferred_finalize_merge_warning, :yellow)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def inferred_finalize_merge_warning
|
|
157
|
+
"SimpleCov inferred `finalize_merge false` because this parallel worker is merging " \
|
|
158
|
+
"into a custom coverage destination. Set `SimpleCov.finalize_merge false` to keep " \
|
|
159
|
+
"external collation ownership, or `SimpleCov.finalize_merge true` if this worker " \
|
|
160
|
+
"should wait, merge, format, enforce thresholds, and write `.last_run.json`. " \
|
|
161
|
+
"See https://github.com/simplecov-ruby/simplecov#merge-finalization-ownership."
|
|
162
|
+
end
|
|
73
163
|
end
|
|
74
164
|
end
|
|
@@ -31,6 +31,7 @@ module SimpleCov
|
|
|
31
31
|
return @coverage_dir if defined?(@coverage_dir) && dir.nil?
|
|
32
32
|
|
|
33
33
|
@coverage_path = nil unless @coverage_path_explicit # invalidate cache
|
|
34
|
+
@coverage_dir_explicit = true unless dir.nil?
|
|
34
35
|
@coverage_dir = dir || "coverage"
|
|
35
36
|
end
|
|
36
37
|
|
|
@@ -94,14 +95,18 @@ module SimpleCov
|
|
|
94
95
|
|
|
95
96
|
#
|
|
96
97
|
# Gets or sets the behavior to process coverage results.
|
|
97
|
-
# By default, it
|
|
98
|
+
# By default, it stores/merges the current result and formats only
|
|
99
|
+
# from the final reporting process.
|
|
98
100
|
#
|
|
99
101
|
def at_exit(&block)
|
|
100
102
|
@at_exit = block if block
|
|
101
103
|
return @at_exit if @at_exit
|
|
102
104
|
return proc {} unless active_session?
|
|
103
105
|
|
|
104
|
-
@at_exit = proc
|
|
106
|
+
@at_exit = proc do
|
|
107
|
+
result = SimpleCov.result
|
|
108
|
+
result.format! if result && SimpleCov.merge_finalization_owner? && SimpleCov.final_result_process?
|
|
109
|
+
end
|
|
105
110
|
end
|
|
106
111
|
|
|
107
112
|
# Whether SimpleCov has anything to do at exit: the Coverage module
|
|
@@ -8,14 +8,13 @@ require "English"
|
|
|
8
8
|
module SimpleCov
|
|
9
9
|
class << self
|
|
10
10
|
# @api private
|
|
11
|
-
CoverageLimits =
|
|
11
|
+
CoverageLimits = Data.define(
|
|
12
12
|
:minimum_coverage,
|
|
13
13
|
:minimum_coverage_by_file,
|
|
14
14
|
:minimum_coverage_by_file_overrides,
|
|
15
15
|
:minimum_coverage_by_group,
|
|
16
16
|
:maximum_coverage,
|
|
17
|
-
:maximum_coverage_drop
|
|
18
|
-
keyword_init: true
|
|
17
|
+
:maximum_coverage_drop
|
|
19
18
|
)
|
|
20
19
|
|
|
21
20
|
def at_exit_behavior
|
|
@@ -106,15 +105,16 @@ module SimpleCov
|
|
|
106
105
|
Kernel.exit(exit_status)
|
|
107
106
|
end
|
|
108
107
|
|
|
109
|
-
# @api private — the
|
|
110
|
-
# one that reports against thresholds, and only when its
|
|
108
|
+
# @api private — the process that owns final merge processing is the
|
|
109
|
+
# only one that reports against thresholds, and only when its
|
|
111
110
|
# `wait_for_other_processes` confirmed every sibling reported.
|
|
112
111
|
# When the wait times out, the merged total is partial and
|
|
113
112
|
# comparing it against `minimum_coverage` / `maximum_coverage`
|
|
114
113
|
# would surface a spurious "below minimum" violation about the
|
|
115
114
|
# missing slice rather than a real shortfall.
|
|
116
115
|
def ready_to_process_results?
|
|
117
|
-
final_result_process? && result? &&
|
|
116
|
+
merge_finalization_owner? && final_result_process? && result? &&
|
|
117
|
+
(collating_result? || parallel_results_complete?)
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def process_results_and_report_error
|
|
@@ -25,7 +25,7 @@ module SimpleCov
|
|
|
25
25
|
def format(result)
|
|
26
26
|
FileUtils.mkdir_p(output_path)
|
|
27
27
|
path = File.join(output_path, FILENAME)
|
|
28
|
-
warn_if_concurrent_overwrite(path)
|
|
28
|
+
warn_if_concurrent_overwrite(path, result)
|
|
29
29
|
File.write(path, JSON.pretty_generate(self.class.build_hash(result)))
|
|
30
30
|
# stderr, not stdout: this is a status message, not the program's
|
|
31
31
|
# output. Keeps the line out of pipelines like `rspec -f json`.
|
|
@@ -46,23 +46,33 @@ module SimpleCov
|
|
|
46
46
|
# process's start time — a strong signal that a sibling test process
|
|
47
47
|
# (e.g., parallel_tests) wrote it while we were running, and that our
|
|
48
48
|
# write is about to clobber their data.
|
|
49
|
-
def warn_if_concurrent_overwrite(path)
|
|
49
|
+
def warn_if_concurrent_overwrite(path, result)
|
|
50
50
|
start_time = SimpleCov.process_start_time or return
|
|
51
|
-
|
|
52
|
-
return unless
|
|
51
|
+
existing = existing_meta(path) or return
|
|
52
|
+
return unless existing[:timestamp] > start_time
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
# The HTML formatter also writes coverage.json (it shares the file as
|
|
55
|
+
# a side artifact), so when both formatters are configured the file we
|
|
56
|
+
# find was just written by our own run, not a concurrent one. A
|
|
57
|
+
# matching command_name means the same merged result, so there's
|
|
58
|
+
# nothing to lose by overwriting. See issue #1171.
|
|
59
|
+
return if existing[:command_name] == result.command_name
|
|
60
|
+
|
|
61
|
+
warn "simplecov: #{path} was written at #{existing[:timestamp].iso8601} — after " \
|
|
55
62
|
"this process started at #{start_time.iso8601}. Overwriting " \
|
|
56
63
|
"likely loses coverage data from a concurrent test run. For " \
|
|
57
64
|
"parallel test setups, use SimpleCov::ResultMerger or run a single " \
|
|
58
65
|
"collation step after all workers finish."
|
|
59
66
|
end
|
|
60
67
|
|
|
61
|
-
def
|
|
68
|
+
def existing_meta(path)
|
|
62
69
|
return nil unless File.exist?(path)
|
|
63
70
|
|
|
64
|
-
|
|
65
|
-
timestamp
|
|
71
|
+
meta = JSON.parse(File.read(path), symbolize_names: true)
|
|
72
|
+
timestamp = meta.dig(:meta, :timestamp)
|
|
73
|
+
return nil unless timestamp
|
|
74
|
+
|
|
75
|
+
{timestamp: Time.iso8601(timestamp), command_name: meta.dig(:meta, :command_name)}
|
|
66
76
|
rescue JSON::ParserError, ArgumentError
|
|
67
77
|
nil
|
|
68
78
|
end
|
|
@@ -7,16 +7,20 @@ module SimpleCov
|
|
|
7
7
|
# Adapter for [grosser/parallel_tests](https://github.com/grosser/parallel_tests).
|
|
8
8
|
# This is the historical default — SimpleCov has special-cased
|
|
9
9
|
# parallel_tests since 0.18 — and remains the most precise option for
|
|
10
|
-
# projects on it. Detection
|
|
11
|
-
#
|
|
12
|
-
# is
|
|
13
|
-
#
|
|
10
|
+
# projects on it. Detection requires the full native coordination
|
|
11
|
+
# contract: the `ParallelTests` constant has been loaded,
|
|
12
|
+
# `TEST_ENV_NUMBER` is set, and `PARALLEL_PID_FILE` is set. The pid-file
|
|
13
|
+
# path is required because the native wait API reads it with `ENV.fetch`.
|
|
14
|
+
# When a runner only provides the env-var convention, GenericAdapter is
|
|
15
|
+
# the correct coordination path.
|
|
14
16
|
class ParallelTestsAdapter < Base
|
|
15
17
|
class << self
|
|
16
18
|
def active?
|
|
19
|
+
return false if SimpleCov.parallel_tests == false
|
|
20
|
+
|
|
17
21
|
ensure_loaded
|
|
18
22
|
# !! to coerce `defined?` (returns nil or "constant") to a proper bool.
|
|
19
|
-
!!(defined?(::ParallelTests) &&
|
|
23
|
+
!!(defined?(::ParallelTests) && native_parallel_tests_environment?)
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
# Pick the *first* started process to do the final-result work,
|
|
@@ -34,6 +38,8 @@ module SimpleCov
|
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
def wait_for_siblings
|
|
41
|
+
return unless native_parallel_tests_environment?
|
|
42
|
+
|
|
37
43
|
::ParallelTests.wait_for_other_processes_to_finish
|
|
38
44
|
end
|
|
39
45
|
|
|
@@ -71,6 +77,10 @@ module SimpleCov
|
|
|
71
77
|
def env_suggests_parallel_tests?
|
|
72
78
|
ENV.key?("TEST_ENV_NUMBER") && ENV.key?("PARALLEL_TEST_GROUPS")
|
|
73
79
|
end
|
|
80
|
+
|
|
81
|
+
def native_parallel_tests_environment?
|
|
82
|
+
ENV.key?("TEST_ENV_NUMBER") && ENV.key?("PARALLEL_PID_FILE")
|
|
83
|
+
end
|
|
74
84
|
end
|
|
75
85
|
end
|
|
76
86
|
end
|
|
@@ -8,21 +8,16 @@
|
|
|
8
8
|
# parent this dogfood report measures.
|
|
9
9
|
module SimpleCov
|
|
10
10
|
class << self
|
|
11
|
-
# @api private
|
|
12
|
-
# How long the first worker is willing to wait for all sibling
|
|
13
|
-
# workers' resultsets to appear in the cache before proceeding
|
|
14
|
-
# with whatever it has. Tuned generously enough that slow CI
|
|
15
|
-
# runners with one straggler don't trip the "incomplete results"
|
|
16
|
-
# path on a routine basis. See #1065 for the parallel_rspec /
|
|
17
|
-
# GenericAdapter case where there is no native wait primitive
|
|
18
|
-
# and this poll is the only synchronization available.
|
|
19
|
-
PARALLEL_RESULTS_WAIT_TIMEOUT = 60
|
|
20
|
-
private_constant :PARALLEL_RESULTS_WAIT_TIMEOUT
|
|
21
|
-
|
|
22
11
|
# @api private
|
|
23
12
|
def final_result_process?
|
|
24
13
|
adapter = SimpleCov::ParallelAdapters.current
|
|
25
|
-
|
|
14
|
+
# No recognized parallel-test adapter. A subprocess forked while
|
|
15
|
+
# coverage was running is never the final reporter — the process that
|
|
16
|
+
# spawned it merges every slice and produces the report. Without this,
|
|
17
|
+
# fork-based runners that don't set TEST_ENV_NUMBER (e.g. Minitest's
|
|
18
|
+
# `parallelize`) have every worker produce the final report and its
|
|
19
|
+
# warnings. See issue #1171.
|
|
20
|
+
return !forked_subprocess? unless adapter
|
|
26
21
|
|
|
27
22
|
adapter.first_worker?
|
|
28
23
|
end
|
|
@@ -56,11 +51,13 @@ module SimpleCov
|
|
|
56
51
|
|
|
57
52
|
# @api private — returns true when every expected worker reported
|
|
58
53
|
# before the deadline, false on timeout. Single-process runs
|
|
59
|
-
# (expected <= 1) short-circuit to true with no waiting.
|
|
54
|
+
# (expected <= 1) short-circuit to true with no waiting. The deadline
|
|
55
|
+
# is `SimpleCov.parallel_wait_timeout` seconds out; raise that setting
|
|
56
|
+
# when a slow worker routinely finishes well after the others.
|
|
60
57
|
def wait_for_parallel_results(expected)
|
|
61
58
|
return true unless expected > 1 # simplecov:disable branch — only false in real parallel runs
|
|
62
59
|
|
|
63
|
-
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) +
|
|
60
|
+
deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + parallel_wait_timeout
|
|
64
61
|
loop do
|
|
65
62
|
seen = SimpleCov::ResultMerger.read_resultset.size
|
|
66
63
|
return true if seen >= expected
|
|
@@ -85,8 +82,9 @@ module SimpleCov
|
|
|
85
82
|
|
|
86
83
|
warn SimpleCov::Color.colorize(
|
|
87
84
|
"Only #{seen} of #{expected} parallel-test workers reported within " \
|
|
88
|
-
"#{
|
|
89
|
-
"
|
|
85
|
+
"#{parallel_wait_timeout}s, so coverage totals are partial and minimum / " \
|
|
86
|
+
"maximum coverage checks are skipped for this run. Increase " \
|
|
87
|
+
"SimpleCov.parallel_wait_timeout if a worker routinely needs longer.",
|
|
90
88
|
:yellow
|
|
91
89
|
)
|
|
92
90
|
end
|
data/lib/simplecov/process.rb
CHANGED
|
@@ -22,7 +22,13 @@ module SimpleCov
|
|
|
22
22
|
# result, keeping that name identical across runs. See issue #1171.
|
|
23
23
|
SimpleCov.next_subprocess_serial! if active
|
|
24
24
|
pid = super
|
|
25
|
-
|
|
25
|
+
if pid.zero? && active
|
|
26
|
+
# Mark the child here, independent of whatever custom at_fork block
|
|
27
|
+
# the user installed, so `final_result_process?` can keep forked
|
|
28
|
+
# workers from each producing the final report. See issue #1171.
|
|
29
|
+
SimpleCov.mark_forked_subprocess!
|
|
30
|
+
SimpleCov.at_fork.call(::Process.pid)
|
|
31
|
+
end
|
|
26
32
|
pid
|
|
27
33
|
end
|
|
28
34
|
end
|
data/lib/simplecov/result.rb
CHANGED
|
@@ -55,14 +55,14 @@ module SimpleCov
|
|
|
55
55
|
# FilterConfig to opt out — useful for tests that build synthetic Results
|
|
56
56
|
# and don't want the project's filters or groups applied.
|
|
57
57
|
def initialize(original_result, command_name: nil, created_at: nil, not_loaded_files: Set.new,
|
|
58
|
-
filter_config: FilterConfig.new)
|
|
58
|
+
report: false, filter_config: FilterConfig.new)
|
|
59
59
|
@original_result = original_result.freeze
|
|
60
60
|
@command_name = command_name
|
|
61
61
|
@created_at = created_at
|
|
62
62
|
@groups_config = filter_config.groups
|
|
63
63
|
builder = SourceFileBuilder.new(original_result, not_loaded_files: not_loaded_files)
|
|
64
64
|
@files = builder.call
|
|
65
|
-
warn_about_missing_source_files(builder.missing_source_files, original_result.size)
|
|
65
|
+
warn_about_missing_source_files(builder.missing_source_files, original_result.size) if report
|
|
66
66
|
apply_cover_filters!(filter_config.cover_filters)
|
|
67
67
|
apply_filters!(filter_config.filters)
|
|
68
68
|
end
|
|
@@ -138,6 +138,17 @@ module SimpleCov
|
|
|
138
138
|
def warn_about_missing_source_files(missing, input_size)
|
|
139
139
|
return if missing.empty?
|
|
140
140
|
|
|
141
|
+
# Emit only from the process that writes the final report. The merged
|
|
142
|
+
# result is rebuilt in every parallel worker (each one stores its own
|
|
143
|
+
# slice), so without this gate the warning prints once per worker — this
|
|
144
|
+
# is the same signal SimpleCov uses to pick the process that runs the
|
|
145
|
+
# report and threshold checks. It's intentionally not gated on
|
|
146
|
+
# print_errors: the default at_fork sets print_errors false on workers,
|
|
147
|
+
# and in many parallel runners the final-report process is itself a
|
|
148
|
+
# worker, so a print_errors gate would suppress the one warning we want.
|
|
149
|
+
# See issues #980 and #1171.
|
|
150
|
+
return unless SimpleCov.final_result_process?
|
|
151
|
+
|
|
141
152
|
MissingSourceFilesReporter.new(
|
|
142
153
|
missing,
|
|
143
154
|
input_size: input_size,
|
|
@@ -86,7 +86,11 @@ module SimpleCov
|
|
|
86
86
|
return nil unless coverage
|
|
87
87
|
|
|
88
88
|
command_name = command_names.reject(&:empty?).sort.join(", ")
|
|
89
|
-
|
|
89
|
+
# The merged result is the authoritative one users actually see, so
|
|
90
|
+
# it's the one that warns about source files dropped because they no
|
|
91
|
+
# longer exist on disk (issue #980). The per-process slices built in
|
|
92
|
+
# `process_coverage_result` stay quiet to avoid one warning per worker.
|
|
93
|
+
SimpleCov::Result.new(coverage, command_name: command_name, report: true)
|
|
90
94
|
end
|
|
91
95
|
|
|
92
96
|
def merge_coverage(*results)
|
|
@@ -13,15 +13,18 @@ module SimpleCov
|
|
|
13
13
|
# so all results in all files specified will be merged. Pass
|
|
14
14
|
# `ignore_timeout: false` to honor it.
|
|
15
15
|
#
|
|
16
|
-
def collate(result_filenames, profile = nil, ignore_timeout: true, &
|
|
16
|
+
def collate(result_filenames, profile = nil, ignore_timeout: true, &)
|
|
17
17
|
raise ArgumentError, "There are no reports to be merged" if result_filenames.empty?
|
|
18
18
|
|
|
19
|
-
initial_setup(profile, &
|
|
19
|
+
initial_setup(profile, &)
|
|
20
20
|
|
|
21
21
|
# Use the ResultMerger to produce a single, merged result, ready to use.
|
|
22
22
|
@result = ResultMerger.merge_and_store(*result_filenames, ignore_timeout: ignore_timeout)
|
|
23
23
|
|
|
24
|
+
@collating_result = true
|
|
24
25
|
run_exit_tasks!
|
|
26
|
+
ensure
|
|
27
|
+
@collating_result = false
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
#
|
|
@@ -31,14 +34,20 @@ module SimpleCov
|
|
|
31
34
|
def result
|
|
32
35
|
return @result if result?
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
use_merging = merging
|
|
38
|
+
|
|
39
|
+
# Collect our coverage result. When merging is off there is no merge
|
|
40
|
+
# step, so this per-process result is the final one and reports any
|
|
41
|
+
# dropped source files; otherwise the merged result does the reporting.
|
|
42
|
+
process_coverage_result(report: !use_merging) if defined?(Coverage) && Coverage.running?
|
|
36
43
|
|
|
37
44
|
# If we're using merging of results, store the current result
|
|
38
45
|
# first (if there is one), then merge the results and return those
|
|
39
|
-
if
|
|
40
|
-
wait_for_other_processes
|
|
46
|
+
if use_merging
|
|
41
47
|
SimpleCov::ResultMerger.store_result(@result) if result?
|
|
48
|
+
return @result unless finalize_merge?
|
|
49
|
+
|
|
50
|
+
wait_for_other_processes
|
|
42
51
|
@result = SimpleCov::ResultMerger.merged_result
|
|
43
52
|
end
|
|
44
53
|
|
|
@@ -50,6 +59,11 @@ module SimpleCov
|
|
|
50
59
|
defined?(@result) && @result
|
|
51
60
|
end
|
|
52
61
|
|
|
62
|
+
# @api private — true while `SimpleCov.collate` is running its finalizer.
|
|
63
|
+
def collating_result?
|
|
64
|
+
defined?(@collating_result) && @collating_result
|
|
65
|
+
end
|
|
66
|
+
|
|
53
67
|
# Applies the configured filters to the given array of SimpleCov::SourceFile items
|
|
54
68
|
def filtered(files)
|
|
55
69
|
result = files.clone
|
|
@@ -152,11 +166,14 @@ module SimpleCov
|
|
|
152
166
|
end
|
|
153
167
|
|
|
154
168
|
# Run all the steps that handle processing the raw coverage result.
|
|
155
|
-
|
|
169
|
+
# `report:` is true only when this slice is the final result (merging
|
|
170
|
+
# off); with merging on the merged result reports dropped source files,
|
|
171
|
+
# so the per-process slice stays quiet to avoid one warning per worker.
|
|
172
|
+
def process_coverage_result(report:)
|
|
156
173
|
@result = SimpleCov::UselessResultsRemover.call(Coverage.result)
|
|
157
174
|
@result = SimpleCov::ResultAdapter.call(@result)
|
|
158
175
|
result, not_loaded_files = add_not_loaded_files(@result)
|
|
159
|
-
@result = SimpleCov::Result.new(result, not_loaded_files: not_loaded_files)
|
|
176
|
+
@result = SimpleCov::Result.new(result, not_loaded_files: not_loaded_files, report: report)
|
|
160
177
|
end
|
|
161
178
|
end
|
|
162
179
|
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module StaticCoverageExtractor
|
|
5
|
+
# Visitor mixin that collects method tuples and tracks the lexical
|
|
6
|
+
# class / module nesting that names them, in the shape Ruby's
|
|
7
|
+
# `Coverage` reports methods. Mixed into `Visitor`, it shares that
|
|
8
|
+
# visitor's `@methods` / `@class_stack` state and keeps the
|
|
9
|
+
# method-collection concern separate from branch extraction.
|
|
10
|
+
module MethodCollector
|
|
11
|
+
# Track class/module nesting so method tuples carry the lexical
|
|
12
|
+
# class name. Module + Class are both treated as namespaces here
|
|
13
|
+
# since `Coverage` reports both as the constant.
|
|
14
|
+
def visit_class_node(node)
|
|
15
|
+
with_class(constant_name(node.constant_path)) { super }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def visit_module_node(node)
|
|
19
|
+
with_class(constant_name(node.constant_path)) { super }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# `def name(...)` and `def self.name(...)` both produce DefNode.
|
|
23
|
+
# The class context is the surrounding lexical class/module (or
|
|
24
|
+
# `Object` at the top level, matching `Coverage`'s convention).
|
|
25
|
+
def visit_def_node(node)
|
|
26
|
+
loc = node.location
|
|
27
|
+
class_name = @class_stack.last || "Object"
|
|
28
|
+
key = [class_name, node.name, loc.start_line, loc.start_column, loc.end_line, loc.end_column]
|
|
29
|
+
@methods[key] = 0
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
# Render a constant path (e.g., `Foo::Bar`) as its source-form
|
|
36
|
+
# string. Defensive nil / to_s fallbacks: ClassNode and ModuleNode
|
|
37
|
+
# always carry a constant_path in practice.
|
|
38
|
+
# simplecov:disable
|
|
39
|
+
def constant_name(node)
|
|
40
|
+
return "<anonymous>" if node.nil?
|
|
41
|
+
return node.slice if node.respond_to?(:slice)
|
|
42
|
+
|
|
43
|
+
node.to_s
|
|
44
|
+
end
|
|
45
|
+
# simplecov:enable
|
|
46
|
+
|
|
47
|
+
def with_class(name)
|
|
48
|
+
@class_stack.push(name)
|
|
49
|
+
yield
|
|
50
|
+
ensure
|
|
51
|
+
@class_stack.pop
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "method_collector"
|
|
4
|
+
|
|
3
5
|
module SimpleCov
|
|
4
6
|
module StaticCoverageExtractor
|
|
5
7
|
# `Prism::IfNode#subsequent` was renamed from `consequent` in Prism
|
|
@@ -24,6 +26,10 @@ module SimpleCov
|
|
|
24
26
|
# conventional shape. Only defined when Prism is loadable;
|
|
25
27
|
# `StaticCoverageExtractor.available?` is the runtime gate.
|
|
26
28
|
class Visitor < ::Prism::Visitor
|
|
29
|
+
# Method tuples and the class/module nesting that names them are
|
|
30
|
+
# collected by this mixin; this class focuses on branch extraction.
|
|
31
|
+
include MethodCollector
|
|
32
|
+
|
|
27
33
|
attr_reader :branches, :methods
|
|
28
34
|
|
|
29
35
|
def initialize
|
|
@@ -41,12 +47,17 @@ module SimpleCov
|
|
|
41
47
|
# missing, Coverage synthesizes a `:else` arm attributed to the
|
|
42
48
|
# whole condition's range — we do the same.
|
|
43
49
|
def visit_if_node(node)
|
|
44
|
-
emit_if_like(node)
|
|
50
|
+
emit_if_like(node, :if)
|
|
45
51
|
super
|
|
46
52
|
end
|
|
47
53
|
|
|
48
54
|
def visit_unless_node(node)
|
|
49
|
-
emit_if_like(node)
|
|
55
|
+
emit_if_like(node, :unless)
|
|
56
|
+
super
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def visit_call_node(node)
|
|
60
|
+
emit_safe_navigation(node) if node.respond_to?(:safe_navigation?) && node.safe_navigation?
|
|
50
61
|
super
|
|
51
62
|
end
|
|
52
63
|
|
|
@@ -75,42 +86,28 @@ module SimpleCov
|
|
|
75
86
|
super
|
|
76
87
|
end
|
|
77
88
|
|
|
78
|
-
# Track class/module nesting so method tuples carry the lexical
|
|
79
|
-
# class name. Module + Class are both treated as namespaces here
|
|
80
|
-
# since `Coverage` reports both as the constant.
|
|
81
|
-
def visit_class_node(node)
|
|
82
|
-
with_class(constant_name(node.constant_path)) { super }
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def visit_module_node(node)
|
|
86
|
-
with_class(constant_name(node.constant_path)) { super }
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# `def name(...)` and `def self.name(...)` both produce DefNode.
|
|
90
|
-
# The class context is the surrounding lexical class/module (or
|
|
91
|
-
# `Object` at the top level, matching `Coverage`'s convention).
|
|
92
|
-
def visit_def_node(node)
|
|
93
|
-
loc = node.location
|
|
94
|
-
class_name = @class_stack.last || "Object"
|
|
95
|
-
key = [class_name, node.name, loc.start_line, loc.start_column, loc.end_line, loc.end_column]
|
|
96
|
-
@methods[key] = 0
|
|
97
|
-
super
|
|
98
|
-
end
|
|
99
|
-
|
|
100
89
|
private
|
|
101
90
|
|
|
102
91
|
# IfNode and UnlessNode share a shape (predicate + then body +
|
|
103
92
|
# optional else/elsif) but expose the trailing arm under different
|
|
104
93
|
# accessors. `if_like_else_location` hides that split.
|
|
105
|
-
def emit_if_like(node)
|
|
94
|
+
def emit_if_like(node, type)
|
|
106
95
|
then_loc = arm_location(node.statements, node.location)
|
|
107
96
|
else_loc = if_like_else_location(node)
|
|
108
|
-
@branches[build_tuple(
|
|
97
|
+
@branches[build_tuple(type, node.location)] = {
|
|
109
98
|
build_tuple(:then, then_loc) => 0,
|
|
110
99
|
build_tuple(:else, else_loc) => 0
|
|
111
100
|
}
|
|
112
101
|
end
|
|
113
102
|
|
|
103
|
+
def emit_safe_navigation(node)
|
|
104
|
+
loc = node.location
|
|
105
|
+
@branches[build_tuple(:"&.", loc)] = {
|
|
106
|
+
build_tuple(:then, loc) => 0,
|
|
107
|
+
build_tuple(:else, loc) => 0
|
|
108
|
+
}
|
|
109
|
+
end
|
|
110
|
+
|
|
114
111
|
# Resolve the source range Coverage attributes to a real-or-synthetic
|
|
115
112
|
# `:else` arm of an if-like construct. IfNode uses
|
|
116
113
|
# `subsequent` / `consequent` depending on Prism version (resolved
|
|
@@ -169,25 +166,6 @@ module SimpleCov
|
|
|
169
166
|
@next_id += 1
|
|
170
167
|
[type, id, location.start_line, location.start_column, location.end_line, location.end_column]
|
|
171
168
|
end
|
|
172
|
-
|
|
173
|
-
# Render a constant path (e.g., `Foo::Bar`) as its source-form
|
|
174
|
-
# string. Defensive nil / to_s fallbacks: ClassNode and ModuleNode
|
|
175
|
-
# always carry a constant_path in practice.
|
|
176
|
-
# simplecov:disable
|
|
177
|
-
def constant_name(node)
|
|
178
|
-
return "<anonymous>" if node.nil?
|
|
179
|
-
return node.slice if node.respond_to?(:slice)
|
|
180
|
-
|
|
181
|
-
node.to_s
|
|
182
|
-
end
|
|
183
|
-
# simplecov:enable
|
|
184
|
-
|
|
185
|
-
def with_class(name)
|
|
186
|
-
@class_stack.push(name)
|
|
187
|
-
yield
|
|
188
|
-
ensure
|
|
189
|
-
@class_stack.pop
|
|
190
|
-
end
|
|
191
169
|
end
|
|
192
170
|
end
|
|
193
171
|
end
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "set"
|
|
4
|
-
|
|
5
3
|
begin
|
|
6
4
|
require "prism"
|
|
7
5
|
rescue LoadError
|
|
@@ -81,13 +79,14 @@ module SimpleCov
|
|
|
81
79
|
# methods: Set[[name, start_line], ...] # e.g., [[:foo, 7], [:bar, 13]]
|
|
82
80
|
# }
|
|
83
81
|
#
|
|
84
|
-
# Branch matching is start_line-only
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# a real branch and an
|
|
89
|
-
#
|
|
90
|
-
#
|
|
82
|
+
# Branch matching is start_line-only rather than by the full tuple.
|
|
83
|
+
# Static extraction and Coverage can still disagree on a branch's exact
|
|
84
|
+
# column positions (and, for some constructs, its type), so matching on
|
|
85
|
+
# start_line alone is the conservative choice that tolerates those
|
|
86
|
+
# differences. Coincidental line-sharing between a real branch and an
|
|
87
|
+
# eval-generated one will keep both, which is an acceptable
|
|
88
|
+
# false-negative for an opt-in filter. Method matching uses
|
|
89
|
+
# (name, start_line) since a method name is unique at any line.
|
|
91
90
|
#
|
|
92
91
|
# Returns nil when Prism is unavailable or parsing fails, signaling
|
|
93
92
|
# callers to keep every Coverage entry (no false drops).
|
data/lib/simplecov/version.rb
CHANGED
data/lib/simplecov.rb
CHANGED
|
@@ -37,6 +37,21 @@ module SimpleCov
|
|
|
37
37
|
def next_subprocess_serial!
|
|
38
38
|
@subprocess_serial = subprocess_serial + 1
|
|
39
39
|
end
|
|
40
|
+
|
|
41
|
+
# @api private — true in a process that was forked while coverage was
|
|
42
|
+
# running (set by SimpleCov::ProcessForkHook in the child). Such a child
|
|
43
|
+
# stores its own slice but must not act as the final-result process: the
|
|
44
|
+
# process that forked it merges every slice and produces the report. Only
|
|
45
|
+
# consulted when no parallel-test adapter is active, since adapters answer
|
|
46
|
+
# `first_worker?` themselves. See issue #1171.
|
|
47
|
+
def forked_subprocess?
|
|
48
|
+
!!(defined?(@forked_subprocess) && @forked_subprocess)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @api private — marked in the child immediately after a fork.
|
|
52
|
+
def mark_forked_subprocess!
|
|
53
|
+
@forked_subprocess = true
|
|
54
|
+
end
|
|
40
55
|
# Should we take care of at_exit behavior or something else? Used by the
|
|
41
56
|
# minitest plugin. See lib/minitest/simplecov_plugin.rb.
|
|
42
57
|
attr_accessor :external_at_exit
|
|
@@ -148,7 +163,7 @@ module SimpleCov
|
|
|
148
163
|
|
|
149
164
|
#
|
|
150
165
|
# Trigger Coverage.start with the configured criteria. Every supported
|
|
151
|
-
# runtime (CRuby >= 3.
|
|
166
|
+
# runtime (CRuby >= 3.2, JRuby >= 10, TruffleRuby >= 22) accepts the
|
|
152
167
|
# criteria-hash form, so no compatibility fallback is needed.
|
|
153
168
|
#
|
|
154
169
|
def start_coverage_measurement
|
|
@@ -199,7 +214,6 @@ module SimpleCov
|
|
|
199
214
|
end
|
|
200
215
|
|
|
201
216
|
# requires are down here for a load order reason I'm not sure what it is about
|
|
202
|
-
require "set"
|
|
203
217
|
require "forwardable"
|
|
204
218
|
require_relative "simplecov/color"
|
|
205
219
|
require_relative "simplecov/deprecation"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: simplecov
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.0.
|
|
4
|
+
version: 1.0.0.rc4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Erik Berlin
|
|
@@ -138,6 +138,7 @@ files:
|
|
|
138
138
|
- lib/simplecov/source_file/source_loader.rb
|
|
139
139
|
- lib/simplecov/source_file/statistics.rb
|
|
140
140
|
- lib/simplecov/static_coverage_extractor.rb
|
|
141
|
+
- lib/simplecov/static_coverage_extractor/method_collector.rb
|
|
141
142
|
- lib/simplecov/static_coverage_extractor/visitor.rb
|
|
142
143
|
- lib/simplecov/useless_results_remover.rb
|
|
143
144
|
- lib/simplecov/version.rb
|
|
@@ -150,9 +151,9 @@ licenses:
|
|
|
150
151
|
metadata:
|
|
151
152
|
bug_tracker_uri: https://github.com/simplecov-ruby/simplecov/issues
|
|
152
153
|
changelog_uri: https://github.com/simplecov-ruby/simplecov/blob/main/CHANGELOG.md
|
|
153
|
-
documentation_uri: https://www.rubydoc.info/gems/simplecov/1.0.0.
|
|
154
|
+
documentation_uri: https://www.rubydoc.info/gems/simplecov/1.0.0.rc4
|
|
154
155
|
mailing_list_uri: https://groups.google.com/forum/#!forum/simplecov
|
|
155
|
-
source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v1.0.0.
|
|
156
|
+
source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v1.0.0.rc4
|
|
156
157
|
rubygems_mfa_required: 'true'
|
|
157
158
|
rdoc_options: []
|
|
158
159
|
require_paths:
|
|
@@ -161,14 +162,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
161
162
|
requirements:
|
|
162
163
|
- - ">="
|
|
163
164
|
- !ruby/object:Gem::Version
|
|
164
|
-
version: '3.
|
|
165
|
+
version: '3.2'
|
|
165
166
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
166
167
|
requirements:
|
|
167
168
|
- - ">="
|
|
168
169
|
- !ruby/object:Gem::Version
|
|
169
170
|
version: '0'
|
|
170
171
|
requirements: []
|
|
171
|
-
rubygems_version: 4.0.
|
|
172
|
+
rubygems_version: 4.0.15
|
|
172
173
|
specification_version: 4
|
|
173
174
|
summary: Code coverage for Ruby
|
|
174
175
|
test_files: []
|