simplecov 0.22.0 → 1.0.2
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/LICENSE +1 -1
- data/README.md +1058 -508
- data/doc/alternate-formatters.md +0 -5
- data/doc/commercial-services.md +5 -5
- data/exe/simplecov +11 -0
- data/lib/minitest/simplecov_plugin.rb +13 -5
- data/lib/simplecov/autostart.rb +11 -0
- data/lib/simplecov/cli/clean.rb +47 -0
- data/lib/simplecov/cli/coverage.rb +93 -0
- data/lib/simplecov/cli/diff.rb +152 -0
- data/lib/simplecov/cli/dotfile.rb +105 -0
- data/lib/simplecov/cli/merge.rb +118 -0
- data/lib/simplecov/cli/open.rb +50 -0
- data/lib/simplecov/cli/report.rb +89 -0
- data/lib/simplecov/cli/run.rb +36 -0
- data/lib/simplecov/cli/serve.rb +149 -0
- data/lib/simplecov/cli/uncovered.rb +108 -0
- data/lib/simplecov/cli.rb +150 -0
- data/lib/simplecov/color.rb +79 -0
- data/lib/simplecov/combine/branches_combiner.rb +28 -5
- data/lib/simplecov/combine/files_combiner.rb +47 -1
- data/lib/simplecov/combine/lines_combiner.rb +19 -17
- data/lib/simplecov/combine/methods_combiner.rb +49 -0
- data/lib/simplecov/combine/results_combiner.rb +7 -5
- data/lib/simplecov/command_guesser.rb +46 -32
- data/lib/simplecov/configuration/coverage.rb +175 -0
- data/lib/simplecov/configuration/coverage_criteria.rb +158 -0
- data/lib/simplecov/configuration/filters.rb +197 -0
- data/lib/simplecov/configuration/formatting.rb +129 -0
- data/lib/simplecov/configuration/ignored_entries.rb +63 -0
- data/lib/simplecov/configuration/merging.rb +164 -0
- data/lib/simplecov/configuration/thresholds.rb +178 -0
- data/lib/simplecov/configuration.rb +105 -412
- data/lib/simplecov/coverage_statistics.rb +15 -9
- data/lib/simplecov/coverage_violations.rb +149 -0
- data/lib/simplecov/defaults.rb +29 -22
- data/lib/simplecov/deprecation.rb +45 -0
- data/lib/simplecov/directive.rb +167 -0
- data/lib/simplecov/exit_codes/exit_code_handling.rb +8 -2
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +19 -57
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +17 -27
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +38 -21
- data/lib/simplecov/exit_codes.rb +15 -0
- data/lib/simplecov/exit_handling.rb +158 -0
- data/lib/simplecov/file_list.rb +70 -19
- data/lib/simplecov/filter.rb +69 -24
- data/lib/simplecov/formatter/base.rb +102 -0
- data/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
- data/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
- data/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
- data/lib/simplecov/formatter/html_formatter.rb +85 -0
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +90 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +128 -0
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
- data/lib/simplecov/formatter/json_formatter.rb +89 -0
- data/lib/simplecov/formatter/multi_formatter.rb +11 -7
- data/lib/simplecov/formatter/simple_formatter.rb +11 -11
- data/lib/simplecov/formatter.rb +4 -0
- data/lib/simplecov/last_run.rb +10 -3
- data/lib/simplecov/lines_classifier.rb +25 -13
- data/lib/simplecov/load_global_config.rb +9 -4
- data/lib/simplecov/parallel_adapters/base.rb +61 -0
- data/lib/simplecov/parallel_adapters/generic.rb +44 -0
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
- data/lib/simplecov/parallel_adapters.rb +83 -0
- data/lib/simplecov/parallel_coordination.rb +129 -0
- data/lib/simplecov/process.rb +52 -12
- data/lib/simplecov/profiles/bundler_filter.rb +1 -1
- data/lib/simplecov/profiles/hidden_filter.rb +1 -1
- data/lib/simplecov/profiles/rails.rb +24 -10
- data/lib/simplecov/profiles/root_filter.rb +6 -5
- data/lib/simplecov/profiles/strict.rb +32 -0
- data/lib/simplecov/profiles/test_frameworks.rb +1 -4
- data/lib/simplecov/profiles.rb +32 -3
- data/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
- data/lib/simplecov/result/source_file_builder.rb +51 -0
- data/lib/simplecov/result.rb +108 -19
- data/lib/simplecov/result_adapter.rb +105 -8
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
- data/lib/simplecov/result_merger/resultset_file.rb +38 -0
- data/lib/simplecov/result_merger/resultset_store.rb +50 -0
- data/lib/simplecov/result_merger.rb +59 -91
- data/lib/simplecov/result_processing.rb +179 -0
- data/lib/simplecov/simulate_coverage.rb +54 -8
- data/lib/simplecov/source_file/branch.rb +1 -3
- data/lib/simplecov/source_file/branch_builder.rb +114 -0
- data/lib/simplecov/source_file/builder_context.rb +28 -0
- data/lib/simplecov/source_file/line.rb +9 -4
- data/lib/simplecov/source_file/line_builder.rb +43 -0
- data/lib/simplecov/source_file/method.rb +52 -0
- data/lib/simplecov/source_file/method_builder.rb +60 -0
- data/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
- data/lib/simplecov/source_file/skip_chunks.rb +75 -0
- data/lib/simplecov/source_file/source_loader.rb +63 -0
- data/lib/simplecov/source_file/statistics.rb +57 -0
- data/lib/simplecov/source_file.rb +66 -232
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +49 -0
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +247 -0
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +55 -0
- data/lib/simplecov/static_coverage_extractor/value_position.rb +82 -0
- data/lib/simplecov/static_coverage_extractor/visitor.rb +196 -0
- data/lib/simplecov/static_coverage_extractor.rb +110 -0
- data/lib/simplecov/useless_results_remover.rb +16 -7
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov-html.rb +4 -0
- data/lib/simplecov.rb +178 -380
- data/lib/simplecov_json_formatter.rb +4 -0
- data/schemas/coverage-v1.0.schema.json +306 -0
- data/schemas/coverage.schema.json +306 -0
- data/sig/simplecov.rbs +1638 -0
- metadata +94 -57
- data/CHANGELOG.md +0 -191
- data/lib/simplecov/default_formatter.rb +0 -20
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module SimpleCov
|
|
6
|
+
module ParallelAdapters
|
|
7
|
+
# Adapter for [grosser/parallel_tests](https://github.com/grosser/parallel_tests).
|
|
8
|
+
# This is the historical default — SimpleCov has special-cased
|
|
9
|
+
# parallel_tests since 0.18 — and remains the most precise option for
|
|
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.
|
|
16
|
+
class ParallelTestsAdapter < Base
|
|
17
|
+
class << self
|
|
18
|
+
def active?
|
|
19
|
+
return false if SimpleCov.parallel_tests == false
|
|
20
|
+
|
|
21
|
+
ensure_loaded
|
|
22
|
+
# !! to coerce `defined?` (returns nil or "constant") to a proper bool.
|
|
23
|
+
!!(defined?(::ParallelTests) && native_parallel_tests_environment?)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Pick the *first* started process to do the final-result work,
|
|
27
|
+
# not the last. The parallel_tests README recommends
|
|
28
|
+
# `first_process?` for "do something once after every worker
|
|
29
|
+
# finishes" hooks, so user code that has its own
|
|
30
|
+
# `wait_for_other_processes_to_finish` in an `RSpec.after(:suite)`
|
|
31
|
+
# overwhelmingly waits in the first process — picking the same
|
|
32
|
+
# side avoids the cross-process deadlock #922 reported. Also
|
|
33
|
+
# handles `PARALLEL_TEST_GROUPS=1` naturally (the only worker's
|
|
34
|
+
# `TEST_ENV_NUMBER` is "" and `first_process?` tests for that
|
|
35
|
+
# empty string).
|
|
36
|
+
def first_worker?
|
|
37
|
+
::ParallelTests.first_process?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def wait_for_siblings
|
|
41
|
+
return unless native_parallel_tests_environment?
|
|
42
|
+
|
|
43
|
+
::ParallelTests.wait_for_other_processes_to_finish
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# The native wait blocks until every sibling process exits, but only
|
|
47
|
+
# when the pid-file contract is present (see `wait_for_siblings`).
|
|
48
|
+
def native_wait?
|
|
49
|
+
native_parallel_tests_environment?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def expected_worker_count
|
|
53
|
+
ENV["PARALLEL_TEST_GROUPS"]&.to_i || 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Auto-require `parallel_tests` when it's installed AND the env
|
|
57
|
+
# vars it sets are present, so callers can rely on
|
|
58
|
+
# `defined?(::ParallelTests)` downstream. parallel_tests is an
|
|
59
|
+
# optional dependency (see https://github.com/grosser/parallel_tests/issues/772),
|
|
60
|
+
# and `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` are commonly set
|
|
61
|
+
# for other reasons (custom subprocess coordination, CI sharding,
|
|
62
|
+
# the parallel_rspec gem which intentionally mirrors the env-var
|
|
63
|
+
# convention), so a missing gem is treated as "user isn't using
|
|
64
|
+
# parallel_tests" — silently skip and let GenericAdapter handle
|
|
65
|
+
# it. Users who want to override the auto-detect can set
|
|
66
|
+
# `SimpleCov.parallel_tests true` (force on) or `false` (force
|
|
67
|
+
# off). See #1018.
|
|
68
|
+
def ensure_loaded
|
|
69
|
+
return if defined?(::ParallelTests) # simplecov:disable — only true after a previous load
|
|
70
|
+
return if SimpleCov.parallel_tests == false # simplecov:disable — only fires when user opts out
|
|
71
|
+
# simplecov:disable — env-var-only path
|
|
72
|
+
return unless SimpleCov.parallel_tests || env_suggests_parallel_tests?
|
|
73
|
+
|
|
74
|
+
# simplecov:disable — only fires under a real parallel_tests setup
|
|
75
|
+
require "parallel_tests"
|
|
76
|
+
rescue LoadError
|
|
77
|
+
# Gem isn't installed; stay quiet — warning here regressed
|
|
78
|
+
# users who use those env vars for their own subprocess
|
|
79
|
+
# coordination.
|
|
80
|
+
# simplecov:enable
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def env_suggests_parallel_tests?
|
|
84
|
+
ENV.key?("TEST_ENV_NUMBER") && ENV.key?("PARALLEL_TEST_GROUPS")
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def native_parallel_tests_environment?
|
|
88
|
+
ENV.key?("TEST_ENV_NUMBER") && ENV.key?("PARALLEL_PID_FILE")
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "parallel_adapters/base"
|
|
4
|
+
require_relative "parallel_adapters/parallel_tests"
|
|
5
|
+
require_relative "parallel_adapters/generic"
|
|
6
|
+
|
|
7
|
+
module SimpleCov
|
|
8
|
+
# Registry + selection for parallel-test-runner adapters. An adapter
|
|
9
|
+
# answers a small fixed set of questions on SimpleCov's behalf:
|
|
10
|
+
#
|
|
11
|
+
# - `active?` — are WE the runner in charge for this process?
|
|
12
|
+
# - `first_worker?` — should this process do the final-result work?
|
|
13
|
+
# - `wait_for_siblings` — block until siblings finish (optional)
|
|
14
|
+
# - `expected_worker_count` — how many workers total
|
|
15
|
+
#
|
|
16
|
+
# `SimpleCov::ParallelAdapters::Base` provides safe no-op defaults; two
|
|
17
|
+
# adapters ship out of the box:
|
|
18
|
+
#
|
|
19
|
+
# - `ParallelTestsAdapter` — wraps the grosser/parallel_tests gem
|
|
20
|
+
# (precise sync + first-process detection via the gem's own API).
|
|
21
|
+
# - `GenericAdapter` — env-var-only detection for runners that follow
|
|
22
|
+
# the parallel_tests `TEST_ENV_NUMBER` convention but don't ship a
|
|
23
|
+
# Ruby API (parallel_rspec, custom CI sharding, knapsack-style
|
|
24
|
+
# splitters). See https://github.com/simplecov-ruby/simplecov/issues/1065.
|
|
25
|
+
#
|
|
26
|
+
# Users can plug in additional adapters:
|
|
27
|
+
#
|
|
28
|
+
# SimpleCov::ParallelAdapters.register MyRunnerAdapter
|
|
29
|
+
#
|
|
30
|
+
# An adapter just needs to be a class responding to the four methods
|
|
31
|
+
# above. Subclass `SimpleCov::ParallelAdapters::Base` to inherit the
|
|
32
|
+
# no-op defaults and override only what you need (the contract methods
|
|
33
|
+
# are defined as class methods, so plain inheritance is what carries
|
|
34
|
+
# them through; `extend Base` won't pick them up).
|
|
35
|
+
module ParallelAdapters
|
|
36
|
+
module_function
|
|
37
|
+
|
|
38
|
+
# Adapters in selection order. ParallelTestsAdapter first (most
|
|
39
|
+
# specific — uses the gem's own API when the gem is loaded); then
|
|
40
|
+
# GenericAdapter as the env-var fallback. User-registered adapters
|
|
41
|
+
# are prepended (#register puts new entries at the front) so
|
|
42
|
+
# downstream code can override the built-ins by registering a more
|
|
43
|
+
# specific match.
|
|
44
|
+
def adapters
|
|
45
|
+
@adapters ||= [ParallelTestsAdapter, GenericAdapter]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Register a custom adapter. Newly registered adapters are inserted
|
|
49
|
+
# at the front of the selection list so a custom adapter for a
|
|
50
|
+
# specific runner takes precedence over the built-in ParallelTests
|
|
51
|
+
# and Generic adapters.
|
|
52
|
+
#
|
|
53
|
+
# class MyRunnerAdapter < SimpleCov::ParallelAdapters::Base
|
|
54
|
+
# def self.active? = ENV["MY_RUNNER_PID"]
|
|
55
|
+
# def self.first_worker? = ENV["MY_RUNNER_PID"].to_i == 1
|
|
56
|
+
# def self.expected_worker_count = ENV["MY_RUNNER_WORKERS"].to_i
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# SimpleCov::ParallelAdapters.register MyRunnerAdapter
|
|
60
|
+
def register(adapter)
|
|
61
|
+
reset_current!
|
|
62
|
+
adapters.unshift(adapter) unless adapters.include?(adapter)
|
|
63
|
+
adapter
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The adapter SimpleCov should consult for this process — the first
|
|
67
|
+
# registered adapter whose `active?` returns true. Returns nil when
|
|
68
|
+
# no adapter is active (i.e., we're not running under any recognized
|
|
69
|
+
# parallel test runner), in which case the caller should treat the
|
|
70
|
+
# process as single-worker.
|
|
71
|
+
def current
|
|
72
|
+
return @current if defined?(@current)
|
|
73
|
+
|
|
74
|
+
@current = adapters.find(&:active?)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Clear the memoized `current` selection. Primarily for tests that
|
|
78
|
+
# mutate env vars between examples; production runs are single-shot.
|
|
79
|
+
def reset_current!
|
|
80
|
+
remove_instance_variable(:@current) if defined?(@current)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# simplecov:disable
|
|
4
|
+
# Methods below only fire under a parallel test runner; not reachable
|
|
5
|
+
# from a single-process rspec run. Cucumber's test_projects exercise
|
|
6
|
+
# the parallel_tests integration end-to-end in subprocesses, but
|
|
7
|
+
# those subprocesses don't merge their Coverage data back into the
|
|
8
|
+
# parent this dogfood report measures.
|
|
9
|
+
module SimpleCov
|
|
10
|
+
class << self
|
|
11
|
+
# @api private
|
|
12
|
+
def final_result_process?
|
|
13
|
+
adapter = SimpleCov::ParallelAdapters.current
|
|
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
|
|
21
|
+
|
|
22
|
+
adapter.first_worker?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @api private
|
|
26
|
+
def wait_for_other_processes
|
|
27
|
+
adapter = SimpleCov::ParallelAdapters.current
|
|
28
|
+
return unless adapter && final_result_process?
|
|
29
|
+
|
|
30
|
+
# Native synchronization first (adapters that wrap a runner with a
|
|
31
|
+
# real "wait" primitive — parallel_tests'
|
|
32
|
+
# `wait_for_other_processes_to_finish` — implement this; adapters
|
|
33
|
+
# without a native API no-op and rely on the polling fallback below).
|
|
34
|
+
adapter.wait_for_siblings
|
|
35
|
+
|
|
36
|
+
# The native wait can return before sibling at_exit handlers finish
|
|
37
|
+
# writing resultsets, and adapters without a native wait have
|
|
38
|
+
# nothing else. Either way, poll the resultset cache until all
|
|
39
|
+
# expected workers have reported or a timeout is reached. Capture
|
|
40
|
+
# the outcome so `ready_to_process_results?` can suppress min/max
|
|
41
|
+
# threshold checks against a partial total.
|
|
42
|
+
@parallel_results_complete =
|
|
43
|
+
wait_for_parallel_results(adapter.expected_worker_count, native_wait: adapter.native_wait?)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @api private — true when every sibling reported its resultset
|
|
47
|
+
# before the wait deadline. Defaults to true outside a parallel
|
|
48
|
+
# run (when `wait_for_other_processes` is a no-op).
|
|
49
|
+
def parallel_results_complete?
|
|
50
|
+
defined?(@parallel_results_complete) ? @parallel_results_complete : true
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @api private — seconds the resultset count must hold steady, after a
|
|
54
|
+
# native wait, before we accept fewer than `expected` workers as final.
|
|
55
|
+
PARALLEL_RESULTS_SETTLE = 0.5
|
|
56
|
+
private_constant :PARALLEL_RESULTS_SETTLE
|
|
57
|
+
|
|
58
|
+
# @api private — returns true when the reporting worker has every
|
|
59
|
+
# resultset it's going to get, false on timeout. Single-process runs
|
|
60
|
+
# (expected <= 1) short-circuit to true with no waiting.
|
|
61
|
+
#
|
|
62
|
+
# Normally we poll until `expected` (= PARALLEL_TEST_GROUPS) workers have
|
|
63
|
+
# reported or `SimpleCov.parallel_wait_timeout` elapses; raise that setting
|
|
64
|
+
# when a slow worker routinely finishes well after the others.
|
|
65
|
+
#
|
|
66
|
+
# When a native wait already confirmed every sibling PROCESS exited
|
|
67
|
+
# (`native_wait`), no further resultset will appear, so a count below
|
|
68
|
+
# `expected` just means some workers produced none — e.g. parallel_test
|
|
69
|
+
# groups that got no spec file on a machine with more cores than files.
|
|
70
|
+
# Once the count then holds steady for `PARALLEL_RESULTS_SETTLE` we accept
|
|
71
|
+
# it as final rather than blocking for the whole timeout. Without a native
|
|
72
|
+
# wait (GenericAdapter) we can't tell an idle worker from a slow one, so we
|
|
73
|
+
# keep waiting the full timeout.
|
|
74
|
+
def wait_for_parallel_results(expected, native_wait: false)
|
|
75
|
+
return true unless expected > 1 # simplecov:disable branch — only false in real parallel runs
|
|
76
|
+
|
|
77
|
+
deadline = monotonic_time + parallel_wait_timeout
|
|
78
|
+
tracker = {count: 0, since: monotonic_time}
|
|
79
|
+
loop do
|
|
80
|
+
seen = SimpleCov::ResultMerger.read_resultset.size
|
|
81
|
+
return true if seen >= expected
|
|
82
|
+
return true if native_wait && resultset_count_settled?(tracker, seen)
|
|
83
|
+
return false if parallel_wait_timed_out?(deadline, expected, seen)
|
|
84
|
+
|
|
85
|
+
sleep 0.1
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Track whether the resultset count has held steady (and positive) for
|
|
90
|
+
# `PARALLEL_RESULTS_SETTLE` seconds. `tracker` carries the last count and
|
|
91
|
+
# the time it last changed across poll iterations.
|
|
92
|
+
def resultset_count_settled?(tracker, count)
|
|
93
|
+
if count > tracker[:count]
|
|
94
|
+
tracker[:count] = count
|
|
95
|
+
tracker[:since] = monotonic_time
|
|
96
|
+
return false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
count.positive? && (monotonic_time - tracker[:since]) >= PARALLEL_RESULTS_SETTLE
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def monotonic_time
|
|
103
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @api private — true once the wait deadline has passed; warns on
|
|
107
|
+
# the first timeout so the user knows the merged total is partial.
|
|
108
|
+
def parallel_wait_timed_out?(deadline, expected, seen)
|
|
109
|
+
return false unless monotonic_time > deadline
|
|
110
|
+
|
|
111
|
+
warn_about_incomplete_parallel_results(expected, seen)
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# @api private
|
|
116
|
+
def warn_about_incomplete_parallel_results(expected, seen)
|
|
117
|
+
return unless print_errors
|
|
118
|
+
|
|
119
|
+
warn SimpleCov::Color.colorize(
|
|
120
|
+
"Only #{seen} of #{expected} parallel-test workers reported within " \
|
|
121
|
+
"#{parallel_wait_timeout}s, so coverage totals are partial and minimum / " \
|
|
122
|
+
"maximum coverage checks are skipped for this run. Increase " \
|
|
123
|
+
"SimpleCov.parallel_wait_timeout if a worker routinely needs longer.",
|
|
124
|
+
:yellow
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
# simplecov:enable
|
data/lib/simplecov/process.rb
CHANGED
|
@@ -1,19 +1,59 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Hooks `Process._fork` (Ruby 3.1+) so child processes inherit SimpleCov's
|
|
4
|
+
# coverage tracking when `SimpleCov.enable_for_subprocesses?` is set.
|
|
5
|
+
#
|
|
6
|
+
# `Process._fork` is the official extension point: `Kernel#fork`,
|
|
7
|
+
# `Process.fork`, `IO.popen("-")`, and similar all funnel through it.
|
|
8
|
+
# Hooking `_fork` (instead of redefining `Process.fork`) composes
|
|
9
|
+
# correctly with other libraries doing the same — they each prepend
|
|
10
|
+
# their own module and chain via `super`.
|
|
11
|
+
|
|
12
|
+
# Reopened here for the fork-related at_exit plumbing that only matters
|
|
13
|
+
# when subprocess support is loaded.
|
|
14
|
+
module SimpleCov
|
|
4
15
|
class << self
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
# Forked children inherit at_exit state that is wrong for them:
|
|
17
|
+
# @at_exit_hook_installed may describe a hook the parent already
|
|
18
|
+
# consumed before forking (Minitest autorun runs the suite inside the
|
|
19
|
+
# parent's at_exit, after SimpleCov's own hook has fired), and
|
|
20
|
+
# external_at_exit may point at a Minitest.after_run deferral that is
|
|
21
|
+
# pid-pinned to the parent. Reset both so the at_fork proc's
|
|
22
|
+
# SimpleCov.start installs a fresh hook that actually fires at child
|
|
23
|
+
# exit. See issue #1227.
|
|
24
|
+
def reset_inherited_at_exit_state!
|
|
25
|
+
@at_exit_hook_installed = false
|
|
26
|
+
self.external_at_exit = false
|
|
14
27
|
end
|
|
28
|
+
end
|
|
15
29
|
|
|
16
|
-
|
|
17
|
-
|
|
30
|
+
# Prepended onto Process's singleton class so every fork — direct or
|
|
31
|
+
# via Kernel#fork / IO.popen — re-runs SimpleCov's at_fork callback in
|
|
32
|
+
# the child.
|
|
33
|
+
module ProcessForkHook
|
|
34
|
+
def _fork
|
|
35
|
+
active = defined?(SimpleCov) && Coverage.running?
|
|
36
|
+
# Assign the next serial in the PARENT, before the fork, so the child
|
|
37
|
+
# inherits its own stable ordinal via copy-on-write. The default
|
|
38
|
+
# at_fork uses it (instead of the child pid) to name the subprocess's
|
|
39
|
+
# result, keeping that name identical across runs. See issue #1171.
|
|
40
|
+
SimpleCov.next_subprocess_serial! if active
|
|
41
|
+
pid = super
|
|
42
|
+
if pid.zero? && active
|
|
43
|
+
# Mark the child here, independent of whatever custom at_fork block
|
|
44
|
+
# the user installed, so `final_result_process?` can keep forked
|
|
45
|
+
# workers from each producing the final report. See issue #1171.
|
|
46
|
+
SimpleCov.mark_forked_subprocess!
|
|
47
|
+
# Without this, a child forked under Minitest autorun measures
|
|
48
|
+
# coverage and then silently discards it: every inherited exit
|
|
49
|
+
# route is dead in the child (see the method's comment). A custom
|
|
50
|
+
# at_fork runs afterwards and can still override. See issue #1227.
|
|
51
|
+
SimpleCov.reset_inherited_at_exit_state!
|
|
52
|
+
SimpleCov.at_fork.call(::Process.pid)
|
|
53
|
+
end
|
|
54
|
+
pid
|
|
55
|
+
end
|
|
18
56
|
end
|
|
19
57
|
end
|
|
58
|
+
|
|
59
|
+
Process.singleton_class.prepend(SimpleCov::ProcessForkHook)
|
|
@@ -3,16 +3,30 @@
|
|
|
3
3
|
SimpleCov.profiles.define "rails" do
|
|
4
4
|
load_profile "test_frameworks"
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
skip %r{\Aconfig/}
|
|
7
|
+
skip %r{\Adb/}
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
group "Controllers", "app/controllers"
|
|
10
|
+
group "Channels", "app/channels"
|
|
11
|
+
group "Models", "app/models"
|
|
12
|
+
group "Mailers", "app/mailers"
|
|
13
|
+
group "Helpers", "app/helpers"
|
|
14
|
+
group "Jobs", %w[app/jobs app/workers]
|
|
15
|
+
group "Libraries", "lib/"
|
|
16
16
|
|
|
17
|
-
track_files
|
|
17
|
+
# Preserve the legacy `track_files` semantics (additive disk-discovery
|
|
18
|
+
# without restricting the report's universe): write the ivar directly
|
|
19
|
+
# so loading the profile doesn't emit the public-API deprecation. Users
|
|
20
|
+
# migrating their own configs should prefer `cover "{app,lib}/**/*.rb"`,
|
|
21
|
+
# which both injects unloaded files and scopes the report to the match
|
|
22
|
+
# set — usually the intended behavior for a Rails project.
|
|
23
|
+
@tracked_files = "{app,lib}/**/*.rb"
|
|
24
|
+
|
|
25
|
+
# `parallelize(workers: ...)` forks worker processes that each run a
|
|
26
|
+
# slice of the suite. Without subprocess support, the workers' coverage
|
|
27
|
+
# is dropped on the floor and the parent records 0% for everything they
|
|
28
|
+
# touched. Hooking `Process._fork` makes each worker re-call
|
|
29
|
+
# `SimpleCov.start` with a unique command_name so the resultsets merge
|
|
30
|
+
# correctly.
|
|
31
|
+
merge_subprocesses true
|
|
18
32
|
end
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
SimpleCov.profiles.define "root_filter" do
|
|
4
|
-
# Exclude all files outside of simplecov root
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
# Exclude all files outside of simplecov root. Shares the regex with
|
|
5
|
+
# SimpleCov::UselessResultsRemover so the root-prefix logic lives in one
|
|
6
|
+
# place; this profile is the user-facing entry point that tools like
|
|
7
|
+
# `SimpleCov.filtered` apply.
|
|
8
|
+
skip do |src|
|
|
9
|
+
src.filename !~ SimpleCov::UselessResultsRemover.root_regex
|
|
9
10
|
end
|
|
10
11
|
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# A "no shortfalls allowed" profile: every coverage criterion is
|
|
4
|
+
# enabled and held to 100%. Per
|
|
5
|
+
# https://github.com/simplecov-ruby/simplecov/issues/1061, this lives as
|
|
6
|
+
# an opt-in profile rather than the default — most projects can't
|
|
7
|
+
# realistically start at 100% on day one — so a team that has paid down
|
|
8
|
+
# the debt can flip the strict switch without re-typing the threshold
|
|
9
|
+
# trio every time.
|
|
10
|
+
#
|
|
11
|
+
# JRuby gracefully degrades: `enable_coverage :branch` / `:method` are
|
|
12
|
+
# accepted (they just add to the configured criteria set), but the
|
|
13
|
+
# runtime can't actually measure them, so the corresponding stats never
|
|
14
|
+
# materialize. `CoverageViolations` skips threshold lookups for
|
|
15
|
+
# criteria not in the stats, so the `:branch` / `:method` clauses below
|
|
16
|
+
# silently no-op on JRuby and only `:line` is enforced.
|
|
17
|
+
#
|
|
18
|
+
# `:eval` widens the universe of code held to 100% to include strings
|
|
19
|
+
# passed through `Kernel#eval` (typically ERB templates, when the user
|
|
20
|
+
# sets `ERB#filename=`). Guarded on `coverage_for_eval_supported?` so
|
|
21
|
+
# the profile stays quiet on Ruby < 3.2, where `enable_coverage :eval`
|
|
22
|
+
# would otherwise warn about the missing runtime support every time
|
|
23
|
+
# the profile loads.
|
|
24
|
+
SimpleCov.profiles.define "strict" do
|
|
25
|
+
enable_coverage :branch
|
|
26
|
+
enable_coverage :method
|
|
27
|
+
# simplecov:disable branch — dogfood runs on Ruby >= 3.2 only, so
|
|
28
|
+
# the else arm (eval coverage not supported) is unreachable from CI.
|
|
29
|
+
enable_coverage :eval if coverage_for_eval_supported?
|
|
30
|
+
# simplecov:enable
|
|
31
|
+
minimum_coverage line: 100, branch: 100, method: 100
|
|
32
|
+
end
|
data/lib/simplecov/profiles.rb
CHANGED
|
@@ -17,7 +17,7 @@ module SimpleCov
|
|
|
17
17
|
#
|
|
18
18
|
def define(name, &blk)
|
|
19
19
|
name = name.to_sym
|
|
20
|
-
raise "SimpleCov Profile '#{name}' is already defined" unless self[name].nil?
|
|
20
|
+
raise SimpleCov::ConfigurationError, "SimpleCov Profile '#{name}' is already defined" unless self[name].nil?
|
|
21
21
|
|
|
22
22
|
self[name] = blk
|
|
23
23
|
end
|
|
@@ -26,10 +26,39 @@ module SimpleCov
|
|
|
26
26
|
# Applies the profile of given name on SimpleCov.configure
|
|
27
27
|
#
|
|
28
28
|
def load(name)
|
|
29
|
+
SimpleCov.configure(&fetch_proc(name))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#
|
|
33
|
+
# Returns the proc registered for the given profile name, autoloading
|
|
34
|
+
# bundled or plugin-gem profiles on first lookup. Raises if the profile
|
|
35
|
+
# cannot be located.
|
|
36
|
+
#
|
|
37
|
+
# Lookup order:
|
|
38
|
+
# 1. already registered via #define
|
|
39
|
+
# 2. require "simplecov/profiles/<name>" (bundled profiles)
|
|
40
|
+
# 3. require "simplecov-profile-<name>" (third-party plugin gems)
|
|
41
|
+
#
|
|
42
|
+
def fetch_proc(name)
|
|
29
43
|
name = name.to_sym
|
|
30
|
-
|
|
44
|
+
autoload_profile(name) unless key?(name)
|
|
45
|
+
return self[name] if key?(name)
|
|
46
|
+
|
|
47
|
+
raise SimpleCov::ConfigurationError, "Could not find SimpleCov Profile called '#{name}'"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
31
51
|
|
|
32
|
-
|
|
52
|
+
def autoload_profile(name)
|
|
53
|
+
require "simplecov/profiles/#{name}"
|
|
54
|
+
rescue LoadError
|
|
55
|
+
begin
|
|
56
|
+
# simplecov:disable — third-party gem fallback (no such gem in test env)
|
|
57
|
+
require "simplecov-profile-#{name}"
|
|
58
|
+
# simplecov:enable
|
|
59
|
+
rescue LoadError
|
|
60
|
+
# fall through; #fetch_proc raises the user-facing error
|
|
61
|
+
end
|
|
33
62
|
end
|
|
34
63
|
end
|
|
35
64
|
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class Result
|
|
5
|
+
# When a resultset references source files that don't exist on the local
|
|
6
|
+
# filesystem they're silently dropped — which produces an empty `0 / 0
|
|
7
|
+
# (100.00%)` report that looks like success but isn't. Emit a single
|
|
8
|
+
# warning summarizing the drop and, when every entry was lost, point at
|
|
9
|
+
# the typical cause (`SimpleCov.collate` invoked from a machine or path
|
|
10
|
+
# different from where the resultsets were generated). See #980.
|
|
11
|
+
class MissingSourceFilesReporter
|
|
12
|
+
def initialize(missing_paths, input_size:, every_entry_dropped:)
|
|
13
|
+
@missing_paths = missing_paths
|
|
14
|
+
@input_size = input_size
|
|
15
|
+
@every_entry_dropped = every_entry_dropped
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def warn!
|
|
19
|
+
warn SimpleCov::Color.colorize(message, :yellow)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def message
|
|
23
|
+
@every_entry_dropped ? all_missing_warning : partial_missing_warning
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def all_missing_warning
|
|
29
|
+
"SimpleCov dropped all #{@missing_paths.size} source file(s) from the result — " \
|
|
30
|
+
"none of the paths in the resultset exist on this filesystem: " \
|
|
31
|
+
"#{summary}. If you're running `SimpleCov.collate`, the source " \
|
|
32
|
+
"files must be available at the same absolute paths as when the individual resultsets " \
|
|
33
|
+
"were generated."
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def partial_missing_warning
|
|
37
|
+
"SimpleCov dropped #{@missing_paths.size} source file(s) from the result because " \
|
|
38
|
+
"they don't exist on this filesystem: #{summary}. They were " \
|
|
39
|
+
"tracked in the resultset but have since moved or been removed."
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def summary
|
|
43
|
+
sample = @missing_paths.first(5).join(", ")
|
|
44
|
+
remaining = @missing_paths.size - 5
|
|
45
|
+
remaining.positive? ? "#{sample} (+#{remaining} more)" : sample
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
class Result
|
|
5
|
+
# Constructs `SimpleCov::SourceFile` instances from a raw coverage
|
|
6
|
+
# hash, sorts them by filename, and surfaces filenames whose source
|
|
7
|
+
# is no longer present on disk so the caller can warn about the
|
|
8
|
+
# silent drop (see #980).
|
|
9
|
+
class SourceFileBuilder
|
|
10
|
+
attr_reader :missing_source_files
|
|
11
|
+
|
|
12
|
+
def initialize(original_result, not_loaded_files:)
|
|
13
|
+
@original_result = original_result
|
|
14
|
+
@not_loaded_files = not_loaded_files
|
|
15
|
+
@missing_source_files = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
SimpleCov::FileList.new(
|
|
20
|
+
@original_result
|
|
21
|
+
.filter_map { |filename, coverage| build_source_file(filename, coverage) }
|
|
22
|
+
.sort_by(&:filename)
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def build_source_file(filename, coverage)
|
|
29
|
+
unless File.file?(filename)
|
|
30
|
+
@missing_source_files << filename
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
SimpleCov::SourceFile.new(
|
|
35
|
+
filename,
|
|
36
|
+
stringify_outer_keys(coverage),
|
|
37
|
+
loaded: !@not_loaded_files.include?(filename)
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# `Coverage.result` returns symbol keys (`:lines`, `:branches`,
|
|
42
|
+
# `:methods`); resultsets loaded from disk are already string-keyed.
|
|
43
|
+
# SourceFile reads with strings, and handles both Array and
|
|
44
|
+
# stringified-Array branch/method keys via `restore_ruby_data_structure`,
|
|
45
|
+
# so only the outer hash needs normalizing.
|
|
46
|
+
def stringify_outer_keys(coverage)
|
|
47
|
+
coverage.transform_keys(&:to_s)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|