simplecov 1.0.0.rc1 → 1.0.0.rc3
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 +21 -0
- data/README.md +7 -1
- data/lib/simplecov/configuration/coverage_criteria.rb +2 -2
- data/lib/simplecov/configuration/filters.rb +8 -6
- data/lib/simplecov/configuration/formatting.rb +4 -4
- data/lib/simplecov/configuration/merging.rb +17 -4
- data/lib/simplecov/configuration/thresholds.rb +4 -4
- data/lib/simplecov/configuration.rb +9 -4
- data/lib/simplecov/deprecation.rb +45 -0
- data/lib/simplecov/exit_handling.rb +2 -3
- data/lib/simplecov/formatter/json_formatter.rb +18 -8
- data/lib/simplecov/lines_classifier.rb +0 -1
- data/lib/simplecov/parallel_coordination.rb +14 -16
- data/lib/simplecov/process.rb +13 -1
- data/lib/simplecov/result.rb +13 -2
- data/lib/simplecov/result_adapter.rb +2 -2
- data/lib/simplecov/result_merger.rb +13 -1
- data/lib/simplecov/result_processing.rb +14 -7
- data/lib/simplecov/source_file/skip_chunks.rb +0 -2
- data/lib/simplecov/source_file.rb +4 -4
- data/lib/simplecov/static_coverage_extractor.rb +0 -2
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov.rb +33 -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: eda0f749acc9b248635b6b1128c601b28bd77304994f78ebbbec0fc9097a2969
|
|
4
|
+
data.tar.gz: 8d55b00892b8f2cd47f7d3960c52e5d177c4951a77cbcd4af42282ae7ec745fa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4cafbbdb4eb3ee18f0c6fad85b865e5522d7e5b86d8d154a42b716a61bfbf57a734d2a0381f95d0d59cd6b914d910f559054725ba92ed6c3f1388421299d824e
|
|
7
|
+
data.tar.gz: 79c267679cbf76017bbbc2541ab239a226145c2b798fc75731492f7600edafc9efe7c56e3e5a769392b4a0dff7b1a3a8d808af8a9d341b6ab26b6c19b1676a6a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,24 @@
|
|
|
1
|
+
1.0.0.rc3 (2026-06-18)
|
|
2
|
+
======================
|
|
3
|
+
|
|
4
|
+
## Breaking Changes
|
|
5
|
+
* 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.
|
|
6
|
+
|
|
7
|
+
## Enhancements
|
|
8
|
+
* 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.
|
|
9
|
+
|
|
10
|
+
## Bugfixes
|
|
11
|
+
* 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.
|
|
12
|
+
* 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.
|
|
13
|
+
|
|
14
|
+
1.0.0.rc2 (2026-06-10)
|
|
15
|
+
======================
|
|
16
|
+
|
|
17
|
+
## Bugfixes
|
|
18
|
+
* Forked subprocesses (for example Rails' `parallelize`) are now named by a stable per-run serial rather than the child's OS process id. Because the pid changed every run, a re-run's worker results were never named the same as the previous run's and so never overwrote them. They accumulated in `.resultset.json` until `merge_timeout` dropped them, and while several runs sat inside that window together SimpleCov merged them all. When the set of files had drifted between those runs (a deleted file, a changed filter) the stale results leaked into the report, inflating the denominator and changing the coverage percentage from one run to the next. The serial sequence is identical from one run to the next, so a re-run's workers now overwrite the previous run's entries and the resultset stays bounded. See #1171.
|
|
19
|
+
* The "Excluded N result(s) older than `merge_timeout`" warning is now emitted at most once, from the reporting process, instead of once per forked worker. Every worker merges the resultset too, and the default `at_fork` sets `print_errors false` for them, but this one warning did not honor the flag, so an eight-worker run printed eight copies. See #1171.
|
|
20
|
+
* Legacy-API deprecation warnings (`track_files`, `add_filter`, `add_group`, and the rest) are now deduplicated by call site and emitted at most once per source location. A deprecated method called in a loop, or a configuration block re-evaluated once per parallel worker or spec file, previously repeated the same notice until the surrounding output was unreadable. See #1204.
|
|
21
|
+
|
|
1
22
|
1.0.0.rc1 (2026-06-02)
|
|
2
23
|
======================
|
|
3
24
|
|
data/README.md
CHANGED
|
@@ -742,6 +742,12 @@ Cached coverage data eventually goes stale, so result sets older than `SimpleCov
|
|
|
742
742
|
merge. The default is 600 seconds (10 minutes); raise or lower it with `SimpleCov.merge_timeout 3600` (1 hour), or
|
|
743
743
|
`merge_timeout 3600` inside a configure/start block. Deactivate automatic merging entirely with `SimpleCov.merging false`.
|
|
744
744
|
|
|
745
|
+
In a parallel run, the process that writes the final report waits for the other workers to finish and write their
|
|
746
|
+
result sets before merging. It gives up after `SimpleCov.parallel_wait_timeout` seconds (default 60) and reports
|
|
747
|
+
whatever has arrived, skipping the minimum / maximum coverage checks against that partial total. If one worker runs
|
|
748
|
+
much heavier test files and routinely finishes a minute or more after the others, raise it with
|
|
749
|
+
`SimpleCov.parallel_wait_timeout 180` so its coverage is included.
|
|
750
|
+
|
|
745
751
|
### Merging across execution environments
|
|
746
752
|
|
|
747
753
|
If your tests run in parallel across multiple build machines, download each run's `.resultset.json` and merge them into
|
|
@@ -1262,7 +1268,7 @@ anything; `-q` / `--quiet` suppresses status lines.
|
|
|
1262
1268
|
|
|
1263
1269
|
### Ruby version compatibility
|
|
1264
1270
|
|
|
1265
|
-
SimpleCov is built in [Continuous Integration] on Ruby 3.
|
|
1271
|
+
SimpleCov is built in [Continuous Integration] on Ruby 3.2+ and JRuby 10+. On CRuby, every coverage criterion
|
|
1266
1272
|
described above is available on the supported versions, with one exception: [eval coverage](#eval-coverage) requires
|
|
1267
1273
|
CRuby 3.2+.
|
|
1268
1274
|
|
|
@@ -110,8 +110,8 @@ module SimpleCov
|
|
|
110
110
|
|
|
111
111
|
# DEPRECATED: prefer `enable_coverage :eval`.
|
|
112
112
|
def enable_coverage_for_eval
|
|
113
|
-
warn
|
|
114
|
-
|
|
113
|
+
SimpleCov::Deprecation.warn("`SimpleCov.enable_coverage_for_eval` is deprecated. " \
|
|
114
|
+
"Replace with `SimpleCov.enable_coverage :eval`.")
|
|
115
115
|
enable_eval_coverage
|
|
116
116
|
end
|
|
117
117
|
|
|
@@ -58,8 +58,8 @@ module SimpleCov
|
|
|
58
58
|
# historical `track_files` behavior) and restricts the report to the
|
|
59
59
|
# matching set.
|
|
60
60
|
def track_files(glob)
|
|
61
|
-
warn
|
|
62
|
-
|
|
61
|
+
SimpleCov::Deprecation.warn("`SimpleCov.track_files` is deprecated. " \
|
|
62
|
+
"#{track_files_replacement_hint(glob)}")
|
|
63
63
|
@tracked_files = glob
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -102,8 +102,8 @@ module SimpleCov
|
|
|
102
102
|
# DEPRECATED: alias for `skip`. Same matcher grammar, identical behavior.
|
|
103
103
|
def add_filter(filter_argument = nil, &block)
|
|
104
104
|
example = block ? "`SimpleCov.skip { ... }`" : "`SimpleCov.skip #{filter_argument.inspect}`"
|
|
105
|
-
warn
|
|
106
|
-
|
|
105
|
+
SimpleCov::Deprecation.warn("`SimpleCov.add_filter` is deprecated. " \
|
|
106
|
+
"Replace with `SimpleCov.skip` (same arguments, same behavior). Example: #{example}.")
|
|
107
107
|
skip(filter_argument, &block)
|
|
108
108
|
end
|
|
109
109
|
|
|
@@ -141,8 +141,10 @@ module SimpleCov
|
|
|
141
141
|
else
|
|
142
142
|
"`SimpleCov.group #{group_name.inspect}, #{filter_argument.inspect}`"
|
|
143
143
|
end
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
SimpleCov::Deprecation.warn(
|
|
145
|
+
"`SimpleCov.add_group` is deprecated. " \
|
|
146
|
+
"Replace with `SimpleCov.group` (same arguments, same behavior). Example: #{example}."
|
|
147
|
+
)
|
|
146
148
|
group(group_name, filter_argument, &block)
|
|
147
149
|
end
|
|
148
150
|
|
|
@@ -89,8 +89,8 @@ module SimpleCov
|
|
|
89
89
|
|
|
90
90
|
# DEPRECATED: alias for `print_errors`. Same value, same behavior.
|
|
91
91
|
def print_error_status
|
|
92
|
-
warn
|
|
93
|
-
|
|
92
|
+
SimpleCov::Deprecation.warn("`SimpleCov.print_error_status` is deprecated. " \
|
|
93
|
+
"Replace with `SimpleCov.print_errors` (same value).")
|
|
94
94
|
defined?(@print_error_status) ? @print_error_status : true
|
|
95
95
|
end
|
|
96
96
|
|
|
@@ -101,8 +101,8 @@ module SimpleCov
|
|
|
101
101
|
# be removed in a future release.
|
|
102
102
|
#
|
|
103
103
|
def nocov_token(nocov_token = nil)
|
|
104
|
-
warn
|
|
105
|
-
|
|
104
|
+
SimpleCov::Deprecation.warn("`SimpleCov.nocov_token` and `SimpleCov.skip_token` are deprecated. " \
|
|
105
|
+
"Replace with `# simplecov:disable` / `# simplecov:enable` block comments.")
|
|
106
106
|
current_nocov_token(nocov_token)
|
|
107
107
|
end
|
|
108
108
|
alias skip_token nocov_token
|
|
@@ -36,8 +36,8 @@ module SimpleCov
|
|
|
36
36
|
|
|
37
37
|
# DEPRECATED: alias for `merge_subprocesses`. Same value/behavior.
|
|
38
38
|
def enable_for_subprocesses(value = nil)
|
|
39
|
-
warn
|
|
40
|
-
|
|
39
|
+
SimpleCov::Deprecation.warn("`SimpleCov.enable_for_subprocesses` is deprecated. " \
|
|
40
|
+
"Replace with `SimpleCov.merge_subprocesses` (same value, same behavior).")
|
|
41
41
|
return @enable_for_subprocesses if defined?(@enable_for_subprocesses) && value.nil?
|
|
42
42
|
|
|
43
43
|
@enable_for_subprocesses = value || false
|
|
@@ -56,8 +56,8 @@ module SimpleCov
|
|
|
56
56
|
|
|
57
57
|
# DEPRECATED: alias for `merging`. Same value, same behavior.
|
|
58
58
|
def use_merging(use = nil)
|
|
59
|
-
warn
|
|
60
|
-
|
|
59
|
+
SimpleCov::Deprecation.warn("`SimpleCov.use_merging` is deprecated. " \
|
|
60
|
+
"Replace with `SimpleCov.merging` (same value, same behavior).")
|
|
61
61
|
@use_merging = use unless use.nil?
|
|
62
62
|
@use_merging = true unless defined?(@use_merging) && @use_merging == false
|
|
63
63
|
end
|
|
@@ -70,5 +70,18 @@ module SimpleCov
|
|
|
70
70
|
@merge_timeout = seconds if seconds.is_a?(Integer)
|
|
71
71
|
@merge_timeout ||= 600
|
|
72
72
|
end
|
|
73
|
+
|
|
74
|
+
#
|
|
75
|
+
# Defines how long (in seconds) the reporting process waits for the
|
|
76
|
+
# remaining parallel-test workers to write their resultsets before it
|
|
77
|
+
# proceeds with a partial merge. Default is 60 seconds. Raise it when a
|
|
78
|
+
# slow worker routinely finishes well after the others, so its coverage
|
|
79
|
+
# is still included and the minimum / maximum coverage checks aren't
|
|
80
|
+
# skipped against a partial total.
|
|
81
|
+
#
|
|
82
|
+
def parallel_wait_timeout(seconds = nil)
|
|
83
|
+
@parallel_wait_timeout = seconds if seconds.is_a?(Integer)
|
|
84
|
+
@parallel_wait_timeout ||= 60
|
|
85
|
+
end
|
|
73
86
|
end
|
|
74
87
|
end
|
|
@@ -76,8 +76,8 @@ module SimpleCov
|
|
|
76
76
|
coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
|
|
77
77
|
defaults, overrides = partition_per_file_thresholds(coverage)
|
|
78
78
|
|
|
79
|
-
warn
|
|
80
|
-
|
|
79
|
+
SimpleCov::Deprecation.warn("`SimpleCov.minimum_coverage_by_file` is deprecated. " \
|
|
80
|
+
"Replace it with:\n#{per_file_coverage_replacement(defaults, overrides)}")
|
|
81
81
|
|
|
82
82
|
raise_on_invalid_coverage(defaults, "minimum_coverage_by_file")
|
|
83
83
|
overrides.each_value { |criteria| raise_on_invalid_coverage(criteria, "minimum_coverage_by_file") }
|
|
@@ -98,8 +98,8 @@ module SimpleCov
|
|
|
98
98
|
def minimum_coverage_by_group(coverage = nil)
|
|
99
99
|
return @minimum_coverage_by_group ||= {} unless coverage
|
|
100
100
|
|
|
101
|
-
warn
|
|
102
|
-
|
|
101
|
+
SimpleCov::Deprecation.warn("`SimpleCov.minimum_coverage_by_group` is deprecated. " \
|
|
102
|
+
"Replace it with:\n#{per_group_coverage_replacement(coverage)}")
|
|
103
103
|
@minimum_coverage_by_group = coverage.dup.transform_values do |group_coverage|
|
|
104
104
|
group_coverage = {primary_coverage => group_coverage} if group_coverage.is_a?(Numeric)
|
|
105
105
|
raise_on_invalid_coverage(group_coverage, "minimum_coverage_by_group")
|
|
@@ -113,14 +113,19 @@ module SimpleCov
|
|
|
113
113
|
|
|
114
114
|
#
|
|
115
115
|
# Gets or sets the behavior to start a new forked Process.
|
|
116
|
-
# Defaults to adding " (subprocess: #{
|
|
116
|
+
# Defaults to adding " (subprocess: #{serial})" to command_name and
|
|
117
117
|
# starting SimpleCov in quiet mode.
|
|
118
118
|
#
|
|
119
119
|
def at_fork(&block)
|
|
120
120
|
@at_fork = block if block
|
|
121
|
-
@at_fork ||= lambda { |
|
|
122
|
-
#
|
|
123
|
-
|
|
121
|
+
@at_fork ||= lambda { |_pid|
|
|
122
|
+
# Needs a name that's unique per worker within a run yet identical
|
|
123
|
+
# across runs. Build it from SimpleCov's stable fork serial rather
|
|
124
|
+
# than the OS pid: with the pid, every run produced uniquely-named
|
|
125
|
+
# results that never overwrote the previous run's, so they piled up
|
|
126
|
+
# in .resultset.json until merge_timeout and the merged report's
|
|
127
|
+
# file set drifted from run to run. See issue #1171.
|
|
128
|
+
SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{SimpleCov.subprocess_serial})"
|
|
124
129
|
# be quiet, the parent process will use the regular formatter
|
|
125
130
|
SimpleCov.print_errors false
|
|
126
131
|
SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
# Emits legacy-API deprecation warnings, deduplicated by the source
|
|
5
|
+
# location that triggered them. A deprecated method called in a loop —
|
|
6
|
+
# or a config block re-evaluated once per parallel worker / spec file —
|
|
7
|
+
# otherwise repeats the same notice until stderr is unreadable. Keying
|
|
8
|
+
# on the caller location collapses those repeats to a single line while
|
|
9
|
+
# still warning separately about each distinct call site the user needs
|
|
10
|
+
# to fix. See issue #1204.
|
|
11
|
+
module Deprecation
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
# Warn about a deprecated API. `message` is the notice without the
|
|
15
|
+
# `[DEPRECATION]` tag or location prefix (both are added here).
|
|
16
|
+
#
|
|
17
|
+
# `location` defaults to the caller of the deprecated method that
|
|
18
|
+
# called us — every shipped call site is a one-level alias such as
|
|
19
|
+
# `track_files`, so the frame two up is the user code. Pass `location:`
|
|
20
|
+
# explicitly when the relevant site isn't that frame (e.g. a source
|
|
21
|
+
# file and line discovered while parsing).
|
|
22
|
+
# `Array(...)` coerces a missing backtrace (nil) to `[]` so `.first`
|
|
23
|
+
# yields nil rather than raising — and, unlike `&.`, adds no branch for
|
|
24
|
+
# the unreachable no-caller case to the project's 100% coverage target.
|
|
25
|
+
def warn(message, location: Array(Kernel.caller(2..2)).first)
|
|
26
|
+
# Key on location when we have one (collapses a deprecated call in a
|
|
27
|
+
# loop to a single warning); fall back to the message so a missing
|
|
28
|
+
# backtrace never silently swallows every notice.
|
|
29
|
+
return unless emitted.add?(location || message)
|
|
30
|
+
|
|
31
|
+
Kernel.warn "#{"#{location}: " if location}[DEPRECATION] #{message}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Already-emitted dedup keys for this process. Parallel workers are
|
|
35
|
+
# separate processes with their own set, so each warns at most once.
|
|
36
|
+
def emitted
|
|
37
|
+
@emitted ||= Set.new
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @api private — reset emitted state between tests.
|
|
41
|
+
def reset!
|
|
42
|
+
@emitted = Set.new
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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
|
@@ -15,8 +15,20 @@ module SimpleCov
|
|
|
15
15
|
# the child.
|
|
16
16
|
module ProcessForkHook
|
|
17
17
|
def _fork
|
|
18
|
+
active = defined?(SimpleCov) && Coverage.running?
|
|
19
|
+
# Assign the next serial in the PARENT, before the fork, so the child
|
|
20
|
+
# inherits its own stable ordinal via copy-on-write. The default
|
|
21
|
+
# at_fork uses it (instead of the child pid) to name the subprocess's
|
|
22
|
+
# result, keeping that name identical across runs. See issue #1171.
|
|
23
|
+
SimpleCov.next_subprocess_serial! if active
|
|
18
24
|
pid = super
|
|
19
|
-
|
|
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
|
|
20
32
|
pid
|
|
21
33
|
end
|
|
22
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,
|
|
@@ -68,6 +68,14 @@ module SimpleCov
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def warn_about_expired_results(expired_command_names)
|
|
71
|
+
# Subprocesses merge the resultset too (each forked worker calls
|
|
72
|
+
# `SimpleCov.result` to store its slice), and the default `at_fork`
|
|
73
|
+
# sets `print_errors false` for them. Without this guard the warning
|
|
74
|
+
# is emitted once per worker — N copies of the same message for an
|
|
75
|
+
# N-worker run. Gate on `print_errors` like every other SimpleCov
|
|
76
|
+
# warning so only the reporting process speaks up.
|
|
77
|
+
return unless SimpleCov.print_errors
|
|
78
|
+
|
|
71
79
|
warn "[SimpleCov]: Excluded #{expired_command_names.size} result(s) older than " \
|
|
72
80
|
"merge_timeout (#{SimpleCov.merge_timeout}s) from the merged report: " \
|
|
73
81
|
"#{expired_command_names.sort.join(', ')}. " \
|
|
@@ -78,7 +86,11 @@ module SimpleCov
|
|
|
78
86
|
return nil unless coverage
|
|
79
87
|
|
|
80
88
|
command_name = command_names.reject(&:empty?).sort.join(", ")
|
|
81
|
-
|
|
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)
|
|
82
94
|
end
|
|
83
95
|
|
|
84
96
|
def merge_coverage(*results)
|
|
@@ -13,10 +13,10 @@ 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)
|
|
@@ -31,12 +31,16 @@ module SimpleCov
|
|
|
31
31
|
def result
|
|
32
32
|
return @result if result?
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
use_merging = merging
|
|
35
|
+
|
|
36
|
+
# Collect our coverage result. When merging is off there is no merge
|
|
37
|
+
# step, so this per-process result is the final one and reports any
|
|
38
|
+
# dropped source files; otherwise the merged result does the reporting.
|
|
39
|
+
process_coverage_result(report: !use_merging) if defined?(Coverage) && Coverage.running?
|
|
36
40
|
|
|
37
41
|
# If we're using merging of results, store the current result
|
|
38
42
|
# first (if there is one), then merge the results and return those
|
|
39
|
-
if
|
|
43
|
+
if use_merging
|
|
40
44
|
wait_for_other_processes
|
|
41
45
|
SimpleCov::ResultMerger.store_result(@result) if result?
|
|
42
46
|
@result = SimpleCov::ResultMerger.merged_result
|
|
@@ -152,11 +156,14 @@ module SimpleCov
|
|
|
152
156
|
end
|
|
153
157
|
|
|
154
158
|
# Run all the steps that handle processing the raw coverage result.
|
|
155
|
-
|
|
159
|
+
# `report:` is true only when this slice is the final result (merging
|
|
160
|
+
# off); with merging on the merged result reports dropped source files,
|
|
161
|
+
# so the per-process slice stays quiet to avoid one warning per worker.
|
|
162
|
+
def process_coverage_result(report:)
|
|
156
163
|
@result = SimpleCov::UselessResultsRemover.call(Coverage.result)
|
|
157
164
|
@result = SimpleCov::ResultAdapter.call(@result)
|
|
158
165
|
result, not_loaded_files = add_not_loaded_files(@result)
|
|
159
|
-
@result = SimpleCov::Result.new(result, not_loaded_files: not_loaded_files)
|
|
166
|
+
@result = SimpleCov::Result.new(result, not_loaded_files: not_loaded_files, report: report)
|
|
160
167
|
end
|
|
161
168
|
end
|
|
162
169
|
end
|
|
@@ -123,8 +123,8 @@ module SimpleCov
|
|
|
123
123
|
|
|
124
124
|
# DEPRECATED: use `covered_percent(:branch)`.
|
|
125
125
|
def branches_coverage_percent
|
|
126
|
-
warn
|
|
127
|
-
|
|
126
|
+
SimpleCov::Deprecation.warn("`SimpleCov::SourceFile#branches_coverage_percent` is deprecated. " \
|
|
127
|
+
"Use `covered_percent(:branch)`.")
|
|
128
128
|
covered_percent(:branch)
|
|
129
129
|
end
|
|
130
130
|
|
|
@@ -176,8 +176,8 @@ module SimpleCov
|
|
|
176
176
|
|
|
177
177
|
# DEPRECATED: use `covered_percent(:method)`.
|
|
178
178
|
def methods_coverage_percent
|
|
179
|
-
warn
|
|
180
|
-
|
|
179
|
+
SimpleCov::Deprecation.warn("`SimpleCov::SourceFile#methods_coverage_percent` is deprecated. " \
|
|
180
|
+
"Use `covered_percent(:method)`.")
|
|
181
181
|
covered_percent(:method)
|
|
182
182
|
end
|
|
183
183
|
|
data/lib/simplecov/version.rb
CHANGED
data/lib/simplecov.rb
CHANGED
|
@@ -21,6 +21,37 @@ module SimpleCov
|
|
|
21
21
|
# so JSONFormatter can detect when an existing coverage.json was written
|
|
22
22
|
# by a sibling process running concurrently.
|
|
23
23
|
attr_accessor :process_start_time
|
|
24
|
+
|
|
25
|
+
# A monotonically increasing serial the parent assigns to each forked
|
|
26
|
+
# subprocess (see SimpleCov::ProcessForkHook). The default `at_fork`
|
|
27
|
+
# builds the worker's command_name from this rather than the OS pid:
|
|
28
|
+
# the serial sequence is the same from one run to the next, so a re-run
|
|
29
|
+
# overwrites the previous run's resultset entries instead of writing
|
|
30
|
+
# uniquely-named ones that pile up until merge_timeout. See issue #1171.
|
|
31
|
+
def subprocess_serial
|
|
32
|
+
@subprocess_serial ||= 0
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @api private — bump the serial in the parent before a fork so the
|
|
36
|
+
# child inherits its own ordinal via copy-on-write.
|
|
37
|
+
def next_subprocess_serial!
|
|
38
|
+
@subprocess_serial = subprocess_serial + 1
|
|
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
|
|
24
55
|
# Should we take care of at_exit behavior or something else? Used by the
|
|
25
56
|
# minitest plugin. See lib/minitest/simplecov_plugin.rb.
|
|
26
57
|
attr_accessor :external_at_exit
|
|
@@ -132,7 +163,7 @@ module SimpleCov
|
|
|
132
163
|
|
|
133
164
|
#
|
|
134
165
|
# Trigger Coverage.start with the configured criteria. Every supported
|
|
135
|
-
# runtime (CRuby >= 3.
|
|
166
|
+
# runtime (CRuby >= 3.2, JRuby >= 10, TruffleRuby >= 22) accepts the
|
|
136
167
|
# criteria-hash form, so no compatibility fallback is needed.
|
|
137
168
|
#
|
|
138
169
|
def start_coverage_measurement
|
|
@@ -183,9 +214,9 @@ module SimpleCov
|
|
|
183
214
|
end
|
|
184
215
|
|
|
185
216
|
# requires are down here for a load order reason I'm not sure what it is about
|
|
186
|
-
require "set"
|
|
187
217
|
require "forwardable"
|
|
188
218
|
require_relative "simplecov/color"
|
|
219
|
+
require_relative "simplecov/deprecation"
|
|
189
220
|
require_relative "simplecov/configuration"
|
|
190
221
|
SimpleCov.extend SimpleCov::Configuration
|
|
191
222
|
require_relative "simplecov/coverage_statistics"
|
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.rc3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Erik Berlin
|
|
@@ -71,6 +71,7 @@ files:
|
|
|
71
71
|
- lib/simplecov/coverage_statistics.rb
|
|
72
72
|
- lib/simplecov/coverage_violations.rb
|
|
73
73
|
- lib/simplecov/defaults.rb
|
|
74
|
+
- lib/simplecov/deprecation.rb
|
|
74
75
|
- lib/simplecov/directive.rb
|
|
75
76
|
- lib/simplecov/exit_codes.rb
|
|
76
77
|
- lib/simplecov/exit_codes/exit_code_handling.rb
|
|
@@ -149,9 +150,9 @@ licenses:
|
|
|
149
150
|
metadata:
|
|
150
151
|
bug_tracker_uri: https://github.com/simplecov-ruby/simplecov/issues
|
|
151
152
|
changelog_uri: https://github.com/simplecov-ruby/simplecov/blob/main/CHANGELOG.md
|
|
152
|
-
documentation_uri: https://www.rubydoc.info/gems/simplecov/1.0.0.
|
|
153
|
+
documentation_uri: https://www.rubydoc.info/gems/simplecov/1.0.0.rc3
|
|
153
154
|
mailing_list_uri: https://groups.google.com/forum/#!forum/simplecov
|
|
154
|
-
source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v1.0.0.
|
|
155
|
+
source_code_uri: https://github.com/simplecov-ruby/simplecov/tree/v1.0.0.rc3
|
|
155
156
|
rubygems_mfa_required: 'true'
|
|
156
157
|
rdoc_options: []
|
|
157
158
|
require_paths:
|
|
@@ -160,14 +161,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
160
161
|
requirements:
|
|
161
162
|
- - ">="
|
|
162
163
|
- !ruby/object:Gem::Version
|
|
163
|
-
version: '3.
|
|
164
|
+
version: '3.2'
|
|
164
165
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
165
166
|
requirements:
|
|
166
167
|
- - ">="
|
|
167
168
|
- !ruby/object:Gem::Version
|
|
168
169
|
version: '0'
|
|
169
170
|
requirements: []
|
|
170
|
-
rubygems_version: 4.0.
|
|
171
|
+
rubygems_version: 4.0.14
|
|
171
172
|
specification_version: 4
|
|
172
173
|
summary: Code coverage for Ruby
|
|
173
174
|
test_files: []
|