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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95cc757bf172ce50cd60e636048c88e30fade408c962d8fe8c9478465b6b2d67
4
- data.tar.gz: 9ee4b98d8f3f790289415b03c3e37c436b32d1e2a9d193fd435dfdb966d4b658
3
+ metadata.gz: e60ecd48ca2790532fba8cdf76aaf6f29f18dd08c27fdc4d349988d2d60d6cd6
4
+ data.tar.gz: 600d7ca6449e36825de427633b8b6890cd77f292ad661c4a86b325e3424081bd
5
5
  SHA512:
6
- metadata.gz: 719bcdfee0e3e79de52e7da4fdd955d85416b2c33c3b8bcd437d13187b9d883c6430ea55e0d52ca17c392cfe4e506f6a433278d5d285091858702bd669eb43bd
7
- data.tar.gz: 4e70c6fabe5ed02ea4ece1ca811434c96b760e871507cc607a05af64710a9f0c14ee87c16dbcf82d19cdaaf23f3896d78ea0500e051130dbc15b7c6046e4b6b8
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 #581.
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.1+ and JRuby 9.4+. On CRuby, every coverage criterion
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.merge(coverage_b) do |_condition, branches_inside_a, branches_inside_b|
26
- branches_inside_a.merge(branches_inside_b) do |_branch, a_count, b_count|
27
- a_count + b_count
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 calls SimpleCov.result.format!
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 { SimpleCov.result.format! }
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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module SimpleCov
6
4
  # Emits legacy-API deprecation warnings, deduplicated by the source
7
5
  # location that triggered them. A deprecated method called in a loop —
@@ -8,14 +8,13 @@ require "English"
8
8
  module SimpleCov
9
9
  class << self
10
10
  # @api private
11
- CoverageLimits = Struct.new(
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 first worker in a parallel run is the only
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? && parallel_results_complete?
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
- existing_ts = existing_timestamp(path) or return
52
- return unless existing_ts > start_time
51
+ existing = existing_meta(path) or return
52
+ return unless existing[:timestamp] > start_time
53
53
 
54
- warn "simplecov: #{path} was written at #{existing_ts.iso8601} after " \
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 existing_timestamp(path)
68
+ def existing_meta(path)
62
69
  return nil unless File.exist?(path)
63
70
 
64
- timestamp = JSON.parse(File.read(path), symbolize_names: true).dig(:meta, :timestamp)
65
- timestamp && Time.iso8601(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
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
3
  require_relative "directive"
5
4
 
6
5
  module SimpleCov
@@ -23,6 +23,8 @@ module SimpleCov
23
23
  class GenericAdapter < Base
24
24
  class << self
25
25
  def active?
26
+ return false if SimpleCov.parallel_tests == false
27
+
26
28
  ENV.key?("TEST_ENV_NUMBER")
27
29
  end
28
30
 
@@ -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 is the standard pair: the `ParallelTests`
11
- # constant has been loaded AND `TEST_ENV_NUMBER` is set. The gem itself
12
- # is autoloaded lazily on first `active?` check so users who don't have
13
- # it installed see no warnings (see #1018).
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) && ENV.key?("TEST_ENV_NUMBER"))
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
- return true unless adapter
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) + PARALLEL_RESULTS_WAIT_TIMEOUT
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
- "#{PARALLEL_RESULTS_WAIT_TIMEOUT}s. Coverage totals are partial; " \
89
- "minimum / maximum coverage checks are skipped for this run.",
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
@@ -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
- SimpleCov.at_fork.call(::Process.pid) if pid.zero? && active
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
@@ -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,
@@ -11,8 +11,8 @@ module SimpleCov
11
11
  @result = result
12
12
  end
13
13
 
14
- def self.call(*args)
15
- new(*args).adapt
14
+ def self.call(*)
15
+ new(*).adapt
16
16
  end
17
17
 
18
18
  def adapt
@@ -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
- SimpleCov::Result.new(coverage, command_name: command_name)
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, &block)
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, &block)
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
- # Collect our coverage result
35
- process_coverage_result if defined?(Coverage) && Coverage.running?
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 merging
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
- def process_coverage_result
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
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module SimpleCov
6
4
  class SourceFile
7
5
  # Computes the set of line ranges that should be excluded from a
@@ -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(:if, node.location)] = {
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 because Coverage's condition type
85
- # vocabulary (`:if`, `:unless`, `:case`, `:while`, `:until`) does not
86
- # always match Prism's emitted type (the existing visitor reports
87
- # `:if` for `unless` and ternary). Coincidental line-sharing between
88
- # a real branch and an eval-generated one will keep both, which is
89
- # an acceptable false-negative for an opt-in filter. Method matching
90
- # uses (name, start_line) since a method name is unique at any line.
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).
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- VERSION = "1.0.0.rc2"
4
+ VERSION = "1.0.0.rc4"
5
5
  end
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.1, JRuby >= 9.4, TruffleRuby >= 22) accepts the
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.rc2
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.rc2
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.rc2
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.1'
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.14
172
+ rubygems_version: 4.0.15
172
173
  specification_version: 4
173
174
  summary: Code coverage for Ruby
174
175
  test_files: []