simplecov 1.0.0.rc3 → 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 +13 -0
- data/README.md +51 -5
- data/lib/simplecov/combine/branches_combiner.rb +24 -3
- data/lib/simplecov/configuration/merging.rb +77 -0
- data/lib/simplecov/configuration.rb +7 -2
- data/lib/simplecov/exit_handling.rb +4 -3
- data/lib/simplecov/parallel_adapters/generic.rb +2 -0
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +15 -5
- data/lib/simplecov/result_processing.rb +11 -1
- 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 -7
- data/lib/simplecov/version.rb +1 -1
- metadata +5 -4
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,16 @@
|
|
|
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
|
+
|
|
1
14
|
1.0.0.rc3 (2026-06-18)
|
|
2
15
|
======================
|
|
3
16
|
|
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
|
|
|
@@ -748,6 +749,51 @@ whatever has arrived, skipping the minimum / maximum coverage checks against tha
|
|
|
748
749
|
much heavier test files and routinely finishes a minute or more after the others, raise it with
|
|
749
750
|
`SimpleCov.parallel_wait_timeout 180` so its coverage is included.
|
|
750
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
|
+
|
|
751
797
|
### Merging across execution environments
|
|
752
798
|
|
|
753
799
|
If your tests run in parallel across multiple build machines, download each run's `.resultset.json` and merge them into
|
|
@@ -866,14 +912,14 @@ SimpleCov coordinates with parallel test runners through a small pluggable adapt
|
|
|
866
912
|
|
|
867
913
|
- **`ParallelTestsAdapter`** — wraps the [grosser/parallel_tests](https://github.com/grosser/parallel_tests) gem and
|
|
868
914
|
uses its `ParallelTests.first_process?` / `ParallelTests.wait_for_other_processes_to_finish` APIs for precise worker
|
|
869
|
-
coordination.
|
|
915
|
+
coordination. Activates only when the native `parallel_tests` pid-file contract is present.
|
|
870
916
|
- **`GenericAdapter`** — catch-all for any runner that follows the `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` env-var
|
|
871
917
|
convention but doesn't ship a Ruby API (parallel_rspec, knapsack-style splitters, custom CI sharding scripts).
|
|
872
918
|
Activates when `TEST_ENV_NUMBER` is set and no more-specific adapter is.
|
|
873
919
|
|
|
874
920
|
Adapters are tried in registration order; the first whose `active?` returns `true` is chosen. With both built-ins, this
|
|
875
921
|
means parallel_tests users get the precise gem-based path and parallel_rspec (or any env-var-only runner) gets the
|
|
876
|
-
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).
|
|
877
923
|
|
|
878
924
|
#### Registering a custom adapter
|
|
879
925
|
|
|
@@ -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. " \
|
|
@@ -83,5 +115,50 @@ module SimpleCov
|
|
|
83
115
|
@parallel_wait_timeout = seconds if seconds.is_a?(Integer)
|
|
84
116
|
@parallel_wait_timeout ||= 60
|
|
85
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
|
|
86
163
|
end
|
|
87
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
|
|
@@ -105,15 +105,16 @@ module SimpleCov
|
|
|
105
105
|
Kernel.exit(exit_status)
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
# @api private — the
|
|
109
|
-
# 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
|
|
110
110
|
# `wait_for_other_processes` confirmed every sibling reported.
|
|
111
111
|
# When the wait times out, the merged total is partial and
|
|
112
112
|
# comparing it against `minimum_coverage` / `maximum_coverage`
|
|
113
113
|
# would surface a spurious "below minimum" violation about the
|
|
114
114
|
# missing slice rather than a real shortfall.
|
|
115
115
|
def ready_to_process_results?
|
|
116
|
-
final_result_process? && result? &&
|
|
116
|
+
merge_finalization_owner? && final_result_process? && result? &&
|
|
117
|
+
(collating_result? || parallel_results_complete?)
|
|
117
118
|
end
|
|
118
119
|
|
|
119
120
|
def process_results_and_report_error
|
|
@@ -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
|
|
@@ -21,7 +21,10 @@ module SimpleCov
|
|
|
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
|
#
|
|
@@ -41,8 +44,10 @@ module SimpleCov
|
|
|
41
44
|
# If we're using merging of results, store the current result
|
|
42
45
|
# first (if there is one), then merge the results and return those
|
|
43
46
|
if use_merging
|
|
44
|
-
wait_for_other_processes
|
|
45
47
|
SimpleCov::ResultMerger.store_result(@result) if result?
|
|
48
|
+
return @result unless finalize_merge?
|
|
49
|
+
|
|
50
|
+
wait_for_other_processes
|
|
46
51
|
@result = SimpleCov::ResultMerger.merged_result
|
|
47
52
|
end
|
|
48
53
|
|
|
@@ -54,6 +59,11 @@ module SimpleCov
|
|
|
54
59
|
defined?(@result) && @result
|
|
55
60
|
end
|
|
56
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
|
+
|
|
57
67
|
# Applies the configured filters to the given array of SimpleCov::SourceFile items
|
|
58
68
|
def filtered(files)
|
|
59
69
|
result = files.clone
|
|
@@ -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
|
|
@@ -79,13 +79,14 @@ module SimpleCov
|
|
|
79
79
|
# methods: Set[[name, start_line], ...] # e.g., [[:foo, 7], [:bar, 13]]
|
|
80
80
|
# }
|
|
81
81
|
#
|
|
82
|
-
# Branch matching is start_line-only
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
# a real branch and an
|
|
87
|
-
#
|
|
88
|
-
#
|
|
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.
|
|
89
90
|
#
|
|
90
91
|
# Returns nil when Prism is unavailable or parsing fails, signaling
|
|
91
92
|
# callers to keep every Coverage entry (no false drops).
|
data/lib/simplecov/version.rb
CHANGED
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:
|
|
@@ -168,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
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: []
|