simplecov 1.1.0 → 1.2.0

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.
Files changed (196) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +165 -57
  3. data/exe/simplecov +0 -3
  4. data/lib/minitest/simplecov_plugin.rb +12 -9
  5. data/lib/simplecov/atomic_file.rb +16 -19
  6. data/lib/simplecov/autostart.rb +5 -7
  7. data/lib/simplecov/baseline/parser.rb +58 -0
  8. data/lib/simplecov/baseline.rb +170 -0
  9. data/lib/simplecov/cli/affected/changed_files.rb +57 -0
  10. data/lib/simplecov/cli/affected/selection.rb +117 -0
  11. data/lib/simplecov/cli/affected.rb +148 -0
  12. data/lib/simplecov/cli/badge/svg.rb +70 -0
  13. data/lib/simplecov/cli/badge.rb +62 -0
  14. data/lib/simplecov/cli/clean.rb +23 -24
  15. data/lib/simplecov/cli/command_helpers.rb +55 -19
  16. data/lib/simplecov/cli/completions/scripts.rb +114 -0
  17. data/lib/simplecov/cli/completions.rb +83 -0
  18. data/lib/simplecov/cli/coverage.rb +25 -28
  19. data/lib/simplecov/cli/coverage_file.rb +58 -14
  20. data/lib/simplecov/cli/dead_code/output.rb +100 -0
  21. data/lib/simplecov/cli/dead_code.rb +139 -0
  22. data/lib/simplecov/cli/diff/output.rb +45 -0
  23. data/lib/simplecov/cli/diff.rb +35 -67
  24. data/lib/simplecov/cli/dotfile.rb +83 -38
  25. data/lib/simplecov/cli/git.rb +41 -0
  26. data/lib/simplecov/cli/history/output.rb +135 -0
  27. data/lib/simplecov/cli/history.rb +67 -0
  28. data/lib/simplecov/cli/merge.rb +34 -39
  29. data/lib/simplecov/cli/open.rb +12 -15
  30. data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
  31. data/lib/simplecov/cli/patch/output.rb +135 -0
  32. data/lib/simplecov/cli/patch.rb +169 -0
  33. data/lib/simplecov/cli/ratchet/output.rb +44 -0
  34. data/lib/simplecov/cli/ratchet.rb +83 -0
  35. data/lib/simplecov/cli/report.rb +18 -24
  36. data/lib/simplecov/cli/run.rb +8 -9
  37. data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
  38. data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
  39. data/lib/simplecov/cli/serve.rb +27 -24
  40. data/lib/simplecov/cli/show/annotator.rb +87 -0
  41. data/lib/simplecov/cli/show/sweep.rb +36 -0
  42. data/lib/simplecov/cli/show.rb +133 -0
  43. data/lib/simplecov/cli/status/facts.rb +87 -0
  44. data/lib/simplecov/cli/status.rb +93 -0
  45. data/lib/simplecov/cli/tests/redundancy.rb +78 -0
  46. data/lib/simplecov/cli/tests.rb +148 -0
  47. data/lib/simplecov/cli/uncovered/misses.rb +37 -0
  48. data/lib/simplecov/cli/uncovered.rb +70 -32
  49. data/lib/simplecov/cli/usage.rb +152 -0
  50. data/lib/simplecov/cli/watch/live_report.rb +73 -0
  51. data/lib/simplecov/cli/watch/narrator.rb +55 -0
  52. data/lib/simplecov/cli/watch/poller.rb +45 -0
  53. data/lib/simplecov/cli/watch/session.rb +134 -0
  54. data/lib/simplecov/cli/watch/test_plan.rb +43 -0
  55. data/lib/simplecov/cli/watch.rb +85 -0
  56. data/lib/simplecov/cli.rb +43 -92
  57. data/lib/simplecov/color.rb +21 -35
  58. data/lib/simplecov/combine/branches_combiner.rb +7 -31
  59. data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
  60. data/lib/simplecov/combine/identity_interner.rb +10 -13
  61. data/lib/simplecov/combine/interned_counts.rb +7 -9
  62. data/lib/simplecov/combine/lines_combiner.rb +20 -44
  63. data/lib/simplecov/combine/methods_combiner.rb +9 -35
  64. data/lib/simplecov/combine/results_combiner.rb +4 -21
  65. data/lib/simplecov/combine.rb +0 -6
  66. data/lib/simplecov/command_guesser.rb +20 -34
  67. data/lib/simplecov/configuration/baseline.rb +31 -0
  68. data/lib/simplecov/configuration/coverage.rb +91 -79
  69. data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
  70. data/lib/simplecov/configuration/deprecations.rb +21 -0
  71. data/lib/simplecov/configuration/eval_coverage.rb +6 -10
  72. data/lib/simplecov/configuration/filters.rb +40 -73
  73. data/lib/simplecov/configuration/formatting.rb +74 -83
  74. data/lib/simplecov/configuration/groups.rb +19 -13
  75. data/lib/simplecov/configuration/history.rb +44 -0
  76. data/lib/simplecov/configuration/ignored_entries.rb +31 -22
  77. data/lib/simplecov/configuration/merging.rb +94 -62
  78. data/lib/simplecov/configuration/missed_caps.rb +51 -0
  79. data/lib/simplecov/configuration/production.rb +23 -0
  80. data/lib/simplecov/configuration/test_tracking.rb +48 -0
  81. data/lib/simplecov/configuration/thresholds.rb +33 -62
  82. data/lib/simplecov/configuration/view_coverage.rb +38 -0
  83. data/lib/simplecov/configuration.rb +70 -76
  84. data/lib/simplecov/configuration_error.rb +5 -0
  85. data/lib/simplecov/context_map/union.rb +81 -0
  86. data/lib/simplecov/context_map.rb +187 -0
  87. data/lib/simplecov/coverage_json.rb +2 -5
  88. data/lib/simplecov/coverage_statistics.rb +14 -24
  89. data/lib/simplecov/coverage_violations.rb +157 -64
  90. data/lib/simplecov/current_run.rb +61 -0
  91. data/lib/simplecov/defaults.rb +4 -23
  92. data/lib/simplecov/deprecation.rb +45 -27
  93. data/lib/simplecov/directive.rb +46 -62
  94. data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
  95. data/lib/simplecov/exit_codes/check.rb +17 -11
  96. data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
  97. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
  98. data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
  99. data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
  100. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +7 -12
  101. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
  102. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
  103. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
  104. data/lib/simplecov/exit_codes.rb +17 -7
  105. data/lib/simplecov/exit_handling.rb +94 -59
  106. data/lib/simplecov/file_list.rb +20 -51
  107. data/lib/simplecov/filter.rb +43 -66
  108. data/lib/simplecov/formatter/base.rb +38 -51
  109. data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
  110. data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
  111. data/lib/simplecov/formatter/html_formatter/public/index.html +17 -10
  112. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
  113. data/lib/simplecov/formatter/html_formatter.rb +24 -41
  114. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
  115. data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
  116. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
  117. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
  118. data/lib/simplecov/formatter/json_formatter.rb +7 -13
  119. data/lib/simplecov/formatter/multi_formatter.rb +5 -11
  120. data/lib/simplecov/formatter/simple_formatter.rb +2 -8
  121. data/lib/simplecov/formatter.rb +14 -10
  122. data/lib/simplecov/group_names.rb +13 -12
  123. data/lib/simplecov/history.rb +115 -0
  124. data/lib/simplecov/last_run.rb +6 -9
  125. data/lib/simplecov/lines_classifier.rb +31 -16
  126. data/lib/simplecov/load_global_config.rb +7 -7
  127. data/lib/simplecov/parallel_adapters/base.rb +13 -41
  128. data/lib/simplecov/parallel_adapters/generic.rb +7 -18
  129. data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
  130. data/lib/simplecov/parallel_adapters.rb +24 -53
  131. data/lib/simplecov/parallel_coordination.rb +39 -49
  132. data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
  133. data/lib/simplecov/parallel_result_merger.rb +67 -126
  134. data/lib/simplecov/process.rb +14 -32
  135. data/lib/simplecov/production/error.rb +11 -0
  136. data/lib/simplecov/production/file_sink.rb +130 -0
  137. data/lib/simplecov/production.rb +249 -0
  138. data/lib/simplecov/profiles/rails.rb +1 -12
  139. data/lib/simplecov/profiles/root_filter.rb +0 -4
  140. data/lib/simplecov/profiles/strict.rb +9 -19
  141. data/lib/simplecov/profiles.rb +11 -33
  142. data/lib/simplecov/report_deferral.rb +11 -15
  143. data/lib/simplecov/report_stamp.rb +6 -9
  144. data/lib/simplecov/result/filter_config.rb +19 -0
  145. data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
  146. data/lib/simplecov/result/serialization.rb +43 -0
  147. data/lib/simplecov/result/source_file_builder.rb +7 -12
  148. data/lib/simplecov/result.rb +68 -117
  149. data/lib/simplecov/result_adapter.rb +58 -68
  150. data/lib/simplecov/result_merger/contexts.rb +22 -0
  151. data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
  152. data/lib/simplecov/result_merger/resultset_file.rb +20 -27
  153. data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
  154. data/lib/simplecov/result_merger/resultset_store.rb +23 -14
  155. data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
  156. data/lib/simplecov/result_merger.rb +59 -58
  157. data/lib/simplecov/result_processing.rb +74 -119
  158. data/lib/simplecov/run_identity.rb +16 -18
  159. data/lib/simplecov/simulate_coverage.rb +35 -50
  160. data/lib/simplecov/source_file/branch.rb +8 -33
  161. data/lib/simplecov/source_file/branch_builder.rb +28 -47
  162. data/lib/simplecov/source_file/builder_context.rb +6 -13
  163. data/lib/simplecov/source_file/line.rb +13 -33
  164. data/lib/simplecov/source_file/line_builder.rb +9 -15
  165. data/lib/simplecov/source_file/method.rb +3 -7
  166. data/lib/simplecov/source_file/method_builder.rb +6 -15
  167. data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
  168. data/lib/simplecov/source_file/skip_chunks.rb +19 -28
  169. data/lib/simplecov/source_file/source_loader.rb +45 -35
  170. data/lib/simplecov/source_file/statistics.rb +10 -9
  171. data/lib/simplecov/source_file.rb +21 -51
  172. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +63 -114
  173. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
  174. data/lib/simplecov/static_coverage_extractor/method_collector.rb +11 -24
  175. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
  176. data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
  177. data/lib/simplecov/static_coverage_extractor/visitor.rb +47 -64
  178. data/lib/simplecov/static_coverage_extractor.rb +46 -62
  179. data/lib/simplecov/test_tracker/accessors.rb +34 -0
  180. data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
  181. data/lib/simplecov/test_tracker/delta.rb +69 -0
  182. data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
  183. data/lib/simplecov/test_tracker.rb +162 -0
  184. data/lib/simplecov/unloaded_file_injector.rb +18 -31
  185. data/lib/simplecov/useless_results_remover.rb +7 -6
  186. data/lib/simplecov/version.rb +1 -1
  187. data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
  188. data/lib/simplecov/view_coverage.rb +55 -0
  189. data/lib/simplecov-html.rb +1 -0
  190. data/lib/simplecov.rb +59 -114
  191. data/lib/simplecov_json_formatter.rb +1 -0
  192. data/man/simplecov.1 +349 -0
  193. data/sig/simplecov.rbs +523 -595
  194. metadata +72 -7
  195. data/schemas/coverage-v1.0.schema.json +0 -306
  196. data/schemas/coverage.schema.json +0 -306
@@ -1,16 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- #
5
- # Helper that tries to find out what test suite is running (for SimpleCov.command_name)
6
- #
7
4
  module CommandGuesser
8
5
  class << self
9
- # Storage for the original command line call that invoked the test suite.
10
- # This has got to be stored as early as possible because i.e. rake and test/unit 2
11
- # have a habit of tampering with ARGV, which makes i.e. the automatic distinction
12
- # between rails unit/functional/integration tests impossible without this cached
13
- # item.
6
+ # The original command line call that invoked the test suite. This has to
7
+ # be stored as early as possible because rake and test/unit 2 have a habit
8
+ # of tampering with ARGV.
14
9
  attr_reader :original_run_command
15
10
 
16
11
  # Assigning the flattened command clears any separately recorded program
@@ -37,10 +32,10 @@ module SimpleCov
37
32
  [framework, parallel_data].compact.join(" ")
38
33
  end
39
34
 
40
- private
35
+ private
41
36
 
42
- # When parallel_tests (or a compatible runner) is driving the suite,
43
- # tag the command name with this worker's position in the pool.
37
+ # When parallel_tests (or a compatible runner) is driving the suite, tag
38
+ # the command name with this worker's position in the pool.
44
39
  def parallel_data
45
40
  groups, number = ENV.values_at("PARALLEL_TEST_GROUPS", "TEST_ENV_NUMBER")
46
41
  return unless groups && number
@@ -52,17 +47,14 @@ module SimpleCov
52
47
  end
53
48
 
54
49
  # The command is `"#{$PROGRAM_NAME} #{ARGV.join(' ')}"`, so a keyword only
55
- # carries meaning when it begins a path segment or an argument. Matching it
56
- # as a bare substring lets any part of the *interpreter* path decide the
50
+ # carries meaning when it begins a path segment or an argument. Matching
51
+ # it as a bare substring lets any part of the interpreter path decide the
57
52
  # suite name: a Ruby installed under `latest/bin` (as mise does) contains
58
- # "test/", so every run through it was labelled "Unit Tests". The same
59
- # applies within ARGV, where `spec/greatest/` used to match just as wrongly.
53
+ # "test/", so every run through it was labelled "Unit Tests".
60
54
  #
61
55
  # A backslash bounds a segment too, or a Windows `spec\foo_spec.rb` would
62
- # lose a match the bare substrings used to make. The `test/` patterns below
63
- # still spell their separator as `/` only, so a backslash `test\functional\`
64
- # goes unrecognised there, but that is how it has always been rather than
65
- # something the anchoring took away.
56
+ # lose a match. The `test/` patterns below still spell their separator as
57
+ # `/` only, which is how it has always been.
66
58
  SEGMENT_START = %r{(?<![^\s/\\])}
67
59
  private_constant :SEGMENT_START
68
60
  SEGMENT_END = %r{(?=[\s/\\]|\z)}
@@ -80,15 +72,16 @@ module SimpleCov
80
72
  private_constant :COMMAND_LINE_FRAMEWORKS
81
73
 
82
74
  def from_command_line_options
83
- COMMAND_LINE_FRAMEWORKS.find { |pattern, _| pattern.match?(original_run_command.to_s) }&.last
75
+ # A command that was never recorded matches nothing, which `match?`
76
+ # answers for a nil of its own accord.
77
+ COMMAND_LINE_FRAMEWORKS.find { |pattern, _| pattern.match?(original_run_command) }&.last
84
78
  end
85
79
 
86
80
  # Which binary was invoked outranks everything else on the command line.
87
81
  # `rspec features` is an RSpec suite whose examples live in features/, not
88
82
  # a Cucumber run, and only the executable name says so. Generic runners
89
83
  # (`ruby`, `rake`, a bare `*_test.rb` script) are absent on purpose: they
90
- # name no framework, so those runs fall through to the path patterns, which
91
- # is what keeps the test/functional vs test/integration split working.
84
+ # name no framework, so those runs fall through to the path patterns.
92
85
  EXECUTABLE_FRAMEWORKS = {
93
86
  "rspec" => "RSpec",
94
87
  "cucumber" => "Cucumber Features"
@@ -99,36 +92,29 @@ module SimpleCov
99
92
  program = original_program_name
100
93
  return unless program
101
94
 
102
- # Trim any extension so Windows' rspec.bat is recognized too.
95
+ # The extension is trimmed so Windows' rspec.bat is recognized too.
103
96
  EXECUTABLE_FRAMEWORKS[File.basename(program, ".*")]
104
97
  end
105
98
 
106
99
  # Inner array literals after the first are flagged uncovered by Ruby's
107
- # Coverage module even though the constant evaluates as a whole known
100
+ # Coverage module even though the constant evaluates as a whole, a known
108
101
  # quirk with multi-line array literals.
109
102
  DEFINED_CONSTANT_FRAMEWORKS = [
110
- ["RSpec", -> { defined?(::RSpec) }],
103
+ ["RSpec", -> { defined?(::RSpec) }],
111
104
  ["Unit Tests", -> { defined?(Test::Unit) }], # simplecov:disable
112
- ["Minitest", -> { defined?(::Minitest) }], # simplecov:disable
113
- ["MiniTest", -> { defined?(MiniTest) }] # simplecov:disable
105
+ ["Minitest", -> { defined?(::Minitest) }], # simplecov:disable
106
+ ["MiniTest", -> { defined?(MiniTest) }] # simplecov:disable
114
107
  ].freeze
115
108
  private_constant :DEFINED_CONSTANT_FRAMEWORKS
116
109
 
117
- # If the command regexps fail, let's try checking defined constants.
118
110
  def from_defined_constants
119
- # simplecov:disable branch — first iter returns when ::RSpec is defined; later branches unreachable
120
111
  DEFINED_CONSTANT_FRAMEWORKS.each { |name, defined_check| return name if defined_check.call }
121
- # simplecov:enable branch
122
112
 
123
- # TODO: Provide link to docs/wiki article
124
- # simplecov:disable — only fires when no framework is detected, which
125
- # is impossible while our own specs are running under rspec
126
113
  warn(
127
114
  "SimpleCov failed to recognize the test framework and/or suite used. " \
128
115
  "Please specify manually using SimpleCov.command_name 'Unit Tests'."
129
116
  )
130
117
  "Unknown Test Framework"
131
- # simplecov:enable
132
118
  end
133
119
  end
134
120
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ # The baseline's path, relative to `SimpleCov.root`; an absolute path is
6
+ # honored as given. The file's presence is the opt-in: generate it with
7
+ # `simplecov ratchet`, check it in, and the exit check holds every listed
8
+ # file to its own floor (#1268).
9
+ def baseline_file(path = nil)
10
+ return @baseline_file ||= Baseline::DEFAULT_FILENAME unless path
11
+
12
+ self.baseline_file = path
13
+ _ = @baseline_file
14
+ end
15
+
16
+ def baseline_file=(path)
17
+ @baseline_file = path
18
+ end
19
+
20
+ # Memoized per resolved path so the exit check and the JSON formatter's
21
+ # errors section read the file once, while a `baseline_file` or `root` change
22
+ # between reads is still picked up.
23
+ def baseline
24
+ resolved = File.expand_path(baseline_file, root)
25
+ return @baseline if @baseline_path.eql?(resolved)
26
+
27
+ @baseline_path = resolved
28
+ @baseline = Baseline.read_if_exists(resolved)
29
+ end
30
+ end
31
+ end
@@ -1,49 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # The `coverage` configuration method configures each coverage criterion
5
- # (`:line`, `:branch`, `:method`, `:eval`) uniformly in one place: naming a
6
- # criterion enables it,
7
- # and every threshold is declared with the same syntax regardless of which
8
- # criterion it applies to. Because the criterion is fixed by the enclosing
9
- # `coverage` call, threshold values are always plain percentages — there is
10
- # no per-criterion Hash competing with the value for a slot.
11
- #
12
- # SimpleCov.start do
13
- # coverage :line do
14
- # minimum 90
15
- # minimum_per_file 80
16
- # minimum_per_file 100, only: "app/mailers/request_mailer.rb"
17
- # maximum_drop 5
18
- # end
19
- #
20
- # coverage :branch, minimum: 80
21
- # coverage :method, minimum: 100
22
- # end
23
- #
24
- # Line coverage is enabled by default, so `coverage :line` is only needed to
25
- # set line thresholds or options. Thresholds feed the same internal stores
26
- # as the flat `minimum_coverage` family, so enforcement is unchanged.
27
4
  module Configuration
28
- # One-liner keyword options `coverage` accepts, each forwarding to the
29
- # `CoverageCriterion` verb of the same name. `minimum_per_group` is omitted
30
- # because it needs an `only:` target, so it's block-only.
31
- COVERAGE_THRESHOLD_OPTIONS = %i[minimum maximum exact maximum_drop minimum_per_file].freeze
5
+ COVERAGE_THRESHOLD_OPTIONS = %i[minimum maximum exact maximum_drop ignore minimum_per_file
6
+ maximum_missed maximum_missed_per_file].freeze
32
7
 
33
- #
34
- # Configure (and, unless `enabled: false`, enable) a coverage criterion.
35
- #
36
- # Threshold options mirror the block verbs for one-liner use:
37
- # coverage :branch, minimum: 80, maximum_drop: 5
38
- #
39
- # `primary: true` makes this the report's leading criterion (and the one a
40
- # bare `minimum_coverage 90` targets). `oneshot: true` (valid only for
41
- # `:line`) selects the faster oneshot-lines mode.
42
- #
43
8
  def coverage(criterion, primary: false, enabled: true, oneshot: false, **thresholds, &block)
44
9
  criterion = enable_coverage_criterion(criterion, enabled: enabled, oneshot: oneshot)
45
- # The cast admits :eval, which primary_coverage rejects at runtime
46
- # (it is a standalone toggle, never in the enabled-criteria set).
10
+ # The cast admits :eval, which primary_coverage rejects at runtime.
47
11
  primary_coverage(_ = criterion) if primary
48
12
 
49
13
  configurator = CoverageCriterion.new(self, criterion)
@@ -53,25 +17,20 @@ module SimpleCov
53
17
  criterion
54
18
  end
55
19
 
56
- private
20
+ private
57
21
 
58
- # Forward the one-liner threshold keywords (`coverage :branch, minimum: 80`)
59
- # to the matching `CoverageCriterion` verbs, rejecting anything that isn't a
60
- # recognized threshold option.
61
22
  def apply_threshold_options(configurator, options)
62
23
  options.each do |verb, value|
63
24
  unless COVERAGE_THRESHOLD_OPTIONS.include?(verb)
64
- raise SimpleCov::ConfigurationError,
65
- "Unknown `coverage` option #{verb.inspect}. " \
66
- "Supported options are #{COVERAGE_THRESHOLD_OPTIONS.inspect}."
25
+ raise ConfigurationError,
26
+ "Unknown `coverage` option #{verb.inspect}. " \
27
+ "Supported options are #{COVERAGE_THRESHOLD_OPTIONS.inspect}."
67
28
  end
68
29
 
69
30
  configurator.public_send(verb, value)
70
31
  end
71
32
  end
72
33
 
73
- # Enable or disable the criterion (or its oneshot / eval variant) and
74
- # return the criterion symbol that thresholds should be stored under.
75
34
  def enable_coverage_criterion(criterion, enabled:, oneshot:)
76
35
  criterion = resolve_criterion_variant(criterion, oneshot)
77
36
  enabled ? enable_coverage(criterion) : disable_coverage(criterion)
@@ -81,27 +40,41 @@ module SimpleCov
81
40
  def resolve_criterion_variant(criterion, oneshot)
82
41
  return criterion unless oneshot
83
42
 
84
- unless criterion == :line
85
- raise SimpleCov::ConfigurationError, "`oneshot: true` is only valid for `coverage :line`"
86
- end
43
+ raise ConfigurationError, "`oneshot: true` is only valid for `coverage :line`" unless criterion.equal?(:line)
87
44
 
88
45
  ONESHOT_LINE_COVERAGE_CRITERION
89
46
  end
90
47
 
91
- # @api private — threshold-store writers used by CoverageCriterion. They
92
- # write the same `@minimum_coverage` / `@maximum_coverage` / ... hashes the
93
- # flat threshold methods populate, so the exit-code checks are unchanged.
48
+ # @api private
94
49
  def store_overall_threshold(setting, criterion, percent)
95
- raise_on_invalid_coverage({criterion => percent}, setting.to_s)
50
+ raise_on_invalid_coverage({criterion => percent}, setting)
96
51
  public_send(setting)[criterion] = percent
97
52
  end
98
53
 
54
+ def store_missed_cap(setting, criterion, count)
55
+ raise_if_criterion_disabled(criterion)
56
+ raise_on_invalid_missed_cap(count, setting)
57
+ public_send(setting)[criterion] = count
58
+ end
59
+
60
+ def store_maximum_missed_per_file(criterion, count, target)
61
+ raise_if_criterion_disabled(criterion)
62
+ raise_on_invalid_missed_cap(count, "maximum_missed_per_file")
63
+ return maximum_missed_per_file[criterion] = count if target.nil?
64
+
65
+ unless target.is_a?(String) || target.is_a?(Regexp)
66
+ raise ConfigurationError, "`only:` must be a String path or Regexp, got #{target.inspect}"
67
+ end
68
+
69
+ (maximum_missed_per_file_overrides[target] ||= {})[criterion] = count
70
+ end
71
+
99
72
  def store_minimum_per_file(criterion, percent, target)
100
73
  raise_on_invalid_coverage({criterion => percent}, "minimum_coverage_by_file")
101
74
  return minimum_coverage_by_file[criterion] = percent if target.nil?
102
75
 
103
76
  unless target.is_a?(String) || target.is_a?(Regexp)
104
- raise SimpleCov::ConfigurationError, "`only:` must be a String path or Regexp, got #{target.inspect}"
77
+ raise ConfigurationError, "`only:` must be a String path or Regexp, got #{target.inspect}"
105
78
  end
106
79
 
107
80
  (minimum_coverage_by_file_overrides[target] ||= {})[criterion] = percent
@@ -109,17 +82,13 @@ module SimpleCov
109
82
 
110
83
  def store_minimum_per_group(criterion, percent, group_name)
111
84
  raise_on_invalid_coverage({criterion => percent}, "minimum_coverage_by_group")
112
- # Normalize like `group` does, so `only: :Models` finds the group
113
- # defined as `group "Models"` at check time instead of storing a
114
- # Symbol key no lookup ever matches.
85
+ # Normalized like `group` does, so `per: group(:Models)` finds the group
86
+ # defined as `group "Models"` at check time.
115
87
  (minimum_coverage_by_group[GroupNames.normalize(group_name)] ||= {})[criterion] = percent
116
88
  end
117
89
 
118
90
  #
119
- # Receiver for a `coverage <criterion> do ... end` block. Each verb writes a
120
- # threshold for the single criterion the block configures, so the value is
121
- # always a plain percentage (`minimum_per_file 100` is unambiguous) and the
122
- # syntax is identical across line, branch, and method coverage.
91
+ # Receiver for a `coverage <criterion> do ... end` block.
123
92
  #
124
93
  class CoverageCriterion
125
94
  def initialize(config, criterion)
@@ -127,46 +96,89 @@ module SimpleCov
127
96
  @criterion = criterion
128
97
  end
129
98
 
130
- # Overall (suite-wide) minimum for this criterion.
131
- def minimum(percent)
132
- @config.send(:store_overall_threshold, :minimum_coverage, @criterion, percent)
99
+ def minimum(percent, per: nil)
100
+ case per
101
+ when nil then @config.__send__(:store_overall_threshold, :minimum_coverage, @criterion, percent)
102
+ when :file then @config.__send__(:store_minimum_per_file, @criterion, percent, nil)
103
+ when String, Regexp then @config.__send__(:store_minimum_per_file, @criterion, percent, per)
104
+ when GroupTarget then @config.__send__(:store_minimum_per_group, @criterion, percent, per.name)
105
+ else raise_invalid_per(per)
106
+ end
133
107
  end
134
108
 
135
- # Overall maximum: fails the build if coverage rises above it. Paired with
136
- # `minimum` (or via `exact`) this pins coverage so an unexpected jump fails.
137
109
  def maximum(percent)
138
- @config.send(:store_overall_threshold, :maximum_coverage, @criterion, percent)
110
+ @config.__send__(:store_overall_threshold, :maximum_coverage, @criterion, percent)
139
111
  end
140
112
 
141
- # Pin coverage to an exact figure (sets both `minimum` and `maximum`).
142
113
  def exact(percent)
143
114
  minimum(percent)
144
115
  maximum(percent)
145
116
  end
146
117
 
147
- # Maximum allowed drop between runs (`maximum_drop 0` refuses any drop).
148
118
  def maximum_drop(percent)
149
- @config.send(:store_overall_threshold, :maximum_coverage_drop, @criterion, percent)
119
+ @config.__send__(:store_overall_threshold, :maximum_coverage_drop, @criterion, percent)
120
+ end
121
+
122
+ def maximum_missed(count, per: nil)
123
+ case per
124
+ when nil then @config.__send__(:store_missed_cap, :maximum_missed, @criterion, count)
125
+ when :file then @config.__send__(:store_maximum_missed_per_file, @criterion, count, nil)
126
+ when String, Regexp then @config.__send__(:store_maximum_missed_per_file, @criterion, count, per)
127
+ when GroupTarget
128
+ raise ConfigurationError,
129
+ "maximum_missed does not support `per: group(...)` yet; see docs/Roadmap.md"
130
+ else raise_invalid_per(per)
131
+ end
132
+ end
133
+
134
+ def group(name)
135
+ GroupTarget.new(name: name)
136
+ end
137
+
138
+ # The flatten serves the one-liner keyword form, whose value arrives as a
139
+ # single argument (`ignore: %i[implicit_else]`).
140
+ def ignore(*types)
141
+ case @criterion
142
+ when :branch then @config.__send__(:store_ignored_branches, types.flatten)
143
+ when :method then @config.__send__(:store_ignored_methods, types.flatten)
144
+ else
145
+ raise ConfigurationError,
146
+ "`ignore` is supported for `coverage :branch` and `coverage :method`, not #{@criterion.inspect}"
147
+ end
150
148
  end
151
149
 
152
- # Per-file minimum. With no `only:`, sets the default applied to every
153
- # file; with `only:` (a String path or Regexp), overrides that default
154
- # for the matching files.
155
150
  def minimum_per_file(percent, only: nil)
156
- @config.send(:store_minimum_per_file, @criterion, percent, only)
151
+ Deprecation.warn("`minimum_per_file` is deprecated. " \
152
+ "Replace with `minimum #{percent}, per: #{(only || :file).inspect}`.")
153
+ @config.__send__(:store_minimum_per_file, @criterion, percent, only)
157
154
  end
158
155
 
159
- # Per-group minimum for the named group (defined via `group`).
160
156
  def minimum_per_group(percent, only:)
161
- @config.send(:store_minimum_per_group, @criterion, percent, only)
157
+ Deprecation.warn("`minimum_per_group` is deprecated. " \
158
+ "Replace with `minimum #{percent}, per: group(#{only.inspect})`.")
159
+ @config.__send__(:store_minimum_per_group, @criterion, percent, only)
160
+ end
161
+
162
+ def maximum_missed_per_file(count, only: nil)
163
+ Deprecation.warn("`maximum_missed_per_file` is deprecated. " \
164
+ "Replace with `maximum_missed #{count}, per: #{(only || :file).inspect}`.")
165
+ @config.__send__(:store_maximum_missed_per_file, @criterion, count, only)
162
166
  end
163
167
 
164
- # Make this criterion the report's primary (leading) criterion.
165
168
  def primary
166
169
  # @criterion is Symbol-wide because this receiver is also built for
167
170
  # :eval; primary_coverage validates at runtime.
168
171
  @config.primary_coverage(_ = @criterion)
169
172
  end
173
+
174
+ GroupTarget = Data.define(:name)
175
+
176
+ private
177
+
178
+ def raise_invalid_per(per)
179
+ raise ConfigurationError,
180
+ "`per:` must be :file, a String path, a Regexp, or group(\"Name\"), got #{per.inspect}"
181
+ end
170
182
  end
171
183
  end
172
184
  end
@@ -1,10 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # Selection and validation of the coverage criteria Ruby's `Coverage`
5
- # library should track. Supports `:line` (the historical default),
6
- # `:branch`, `:method`, and `:oneshot_line`. The standalone `:eval`
7
- # toggle lives in `eval_coverage.rb`.
8
4
  module Configuration
9
5
  SUPPORTED_COVERAGE_CRITERIA = %i[line branch method oneshot_line].freeze
10
6
  DEFAULT_COVERAGE_CRITERION = :line
@@ -12,34 +8,36 @@ module SimpleCov
12
8
  LINE_COVERAGE_ALTERNATIVES = {line: :oneshot_line, oneshot_line: :line}.freeze #: line_coverage_alternatives
13
9
  private_constant :LINE_COVERAGE_ALTERNATIVES
14
10
 
15
- # Enable one or more coverage criteria. `:eval` is accepted as a
16
- # shorthand for the standalone eval-coverage toggle.
17
11
  def enable_coverage(*criteria)
18
- criteria.each { |criterion| criterion == :eval ? enable_eval_coverage : add_coverage_criterion(criterion) }
12
+ criteria.each do |criterion|
13
+ criterion.equal?(:eval) ? enable_eval_coverage : add_coverage_criterion(_ = criterion)
14
+ end
19
15
  end
20
16
 
21
- # Remove `criterion` from the set of enabled coverage criteria.
22
- # `:eval` turns the standalone eval-coverage toggle back off,
23
- # mirroring `enable_coverage`. Disabling every criterion raises at
24
- # `start_tracking` (not here), so config files that toggle criteria
25
- # in arbitrary order don't have to worry about transient empty states.
17
+ # Disabling every criterion raises at `start_tracking`, not here, so config
18
+ # files that toggle criteria in arbitrary order don't have to worry about
19
+ # transient empty states.
26
20
  def disable_coverage(criterion)
27
- return disable_eval_coverage if criterion == :eval
21
+ return disable_eval_coverage if criterion.equal?(:eval)
28
22
 
29
- raise_if_criterion_unsupported(criterion)
30
- coverage_criteria.delete(criterion)
31
- @primary_coverage = nil if @primary_coverage == criterion
23
+ raise_if_criterion_unsupported(_ = criterion)
24
+ coverage_criteria.delete(_ = criterion)
25
+ @primary_coverage = nil if @primary_coverage.equal?(criterion)
32
26
  end
33
27
 
34
28
  def primary_coverage(criterion = nil)
35
29
  if criterion.nil?
36
30
  @primary_coverage ||= default_primary_coverage
37
31
  else
38
- raise_if_criterion_disabled(criterion)
39
- @primary_coverage = criterion
32
+ self.primary_coverage = criterion
40
33
  end
41
34
  end
42
35
 
36
+ def primary_coverage=(criterion)
37
+ raise_if_criterion_disabled(criterion)
38
+ @primary_coverage = criterion
39
+ end
40
+
43
41
  def coverage_criteria
44
42
  @coverage_criteria ||= Set[DEFAULT_COVERAGE_CRITERION]
45
43
  end
@@ -48,20 +46,17 @@ module SimpleCov
48
46
  coverage_criteria.member?(criterion)
49
47
  end
50
48
 
51
- # Reset the criteria back to the lazy default (`Set[:line]`).
52
49
  def clear_coverage_criteria
53
50
  @coverage_criteria = nil
54
51
  @primary_coverage = nil
55
52
  end
56
53
 
57
- # @api private — called from `SimpleCov.start_tracking` to fail
58
- # fast when the user has disabled every coverage criterion.
59
54
  def validate_coverage_criteria!
60
55
  return unless coverage_criteria.empty?
61
56
 
62
- raise SimpleCov::ConfigurationError,
63
- "At least one coverage criterion must be enabled. " \
64
- "Re-enable one with `enable_coverage :line`, `:branch`, or `:method`."
57
+ raise ConfigurationError,
58
+ "At least one coverage criterion must be enabled. " \
59
+ "Re-enable one with `enable_coverage :line`, `:branch`, or `:method`."
65
60
  end
66
61
 
67
62
  # Whether this run produces line data at all. The oneshot variant counts:
@@ -89,23 +84,23 @@ module SimpleCov
89
84
  coverage_criterion_supported?(:methods)
90
85
  end
91
86
 
92
- # Ask the Coverage runtime itself whether a criterion is supported
93
- # (Ruby >= 3.2). Older Rubies don't expose `Coverage.supported?`, so
94
- # fall back to the historical engine check that line/branch/method
95
- # were unavailable on JRuby. `:eval` was added later, so on older
96
- # Rubies its fallback is "always unsupported" rather than the
97
- # JRuby-only one above. The fallback arm is unreachable from the
98
- # dogfood report, which runs on a newer Ruby.
99
- # simplecov:disable
87
+ # Older Rubies don't expose `Coverage.supported?`, so fall back to the
88
+ # historical engine check that line/branch/method were unavailable on JRuby.
89
+ # `:eval` was added later, so its fallback is "always unsupported".
100
90
  def coverage_criterion_supported?(criterion)
101
- require "coverage"
91
+ load_coverage
102
92
  return Coverage.supported?(criterion) if Coverage.respond_to?(:supported?)
103
93
 
104
- criterion != :eval && RUBY_ENGINE != "jruby"
94
+ !criterion.eql?(:eval) && !RUBY_ENGINE.eql?("jruby")
95
+ end
96
+
97
+ # Loading `simplecov/configuration` on its own leaves Coverage undefined, and
98
+ # this question is asked at configuration time.
99
+ def load_coverage
100
+ require "coverage"
105
101
  end
106
- # simplecov:enable
107
102
 
108
- private
103
+ private
109
104
 
110
105
  def add_coverage_criterion(criterion)
111
106
  raise_if_criterion_unsupported(criterion)
@@ -114,41 +109,35 @@ module SimpleCov
114
109
  coverage_criteria << criterion
115
110
  end
116
111
 
117
- # If `:line` is enabled, it's the default primary; otherwise fall
118
- # back to whichever criterion the user actually enabled (in
119
- # insertion order). Returning `:line` even when disabled would
120
- # propagate broken state into `minimum_coverage 90`.
112
+ # Answering `:line` even when it is disabled would propagate broken state
113
+ # into `minimum_coverage 90`, so fall back to whichever criterion the user
114
+ # actually enabled, in insertion order.
121
115
  def default_primary_coverage
122
116
  return DEFAULT_COVERAGE_CRITERION if coverage_criterion_enabled?(DEFAULT_COVERAGE_CRITERION)
123
117
 
124
- # Set#first types as nilable, but an empty criteria set is rejected at
125
- # start_tracking before any caller can observe a nil here.
126
118
  _ = coverage_criteria.first
127
119
  end
128
120
 
129
121
  def raise_if_criterion_disabled(criterion)
130
- # `coverage :eval` by itself IS supported — it's a standalone
131
- # toggle, never in the enabled-criteria set — so the generic
132
- # "unsupported criterion" message below would mislead here.
133
- if criterion == :eval
134
- raise SimpleCov::ConfigurationError,
135
- "Coverage criterion :eval only toggles measuring eval'd code; " \
136
- "it cannot carry thresholds or serve as the primary criterion"
122
+ if criterion.equal?(:eval)
123
+ raise ConfigurationError,
124
+ "Coverage criterion :eval only toggles measuring eval'd code; " \
125
+ "it cannot carry thresholds or serve as the primary criterion"
137
126
  end
138
127
 
139
128
  raise_if_criterion_unsupported(criterion)
140
129
  return if coverage_criterion_enabled?(criterion)
141
130
 
142
- raise SimpleCov::ConfigurationError,
143
- "Coverage criterion #{criterion}, is disabled! " \
144
- "Please enable it first through enable_coverage #{criterion} (if supported)"
131
+ raise ConfigurationError,
132
+ "Coverage criterion #{criterion}, is disabled! " \
133
+ "Please enable it first through enable_coverage #{criterion} (if supported)"
145
134
  end
146
135
 
147
136
  def raise_if_criterion_unsupported(criterion)
148
137
  return if SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
149
138
 
150
- raise SimpleCov::ConfigurationError,
151
- "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
139
+ raise ConfigurationError,
140
+ "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
152
141
  end
153
142
  end
154
143
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../deprecation"
4
+
5
+ module SimpleCov
6
+ module Configuration
7
+ DEPRECATION_MODES = Deprecation::MODES
8
+
9
+ # The configuration DSL migrates by warn-and-delegate, which means a project
10
+ # can run on deprecated spellings indefinitely without noticing.
11
+ # `deprecations :raise` turns every deprecated API into a ConfigurationError,
12
+ # so a project that has migrated can guard in CI against old spellings
13
+ # creeping back. There is deliberately no silencing mode: a deprecation you
14
+ # cannot see is a migration you never make.
15
+ def deprecations(mode = nil)
16
+ return Deprecation.mode unless mode
17
+
18
+ Deprecation.mode = mode
19
+ end
20
+ end
21
+ end
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # The standalone eval-coverage toggle (`enable_coverage :eval`),
5
- # instrumenting `eval`'d code on Rubies that support it. Kept separate
6
- # from the criteria-set logic in `coverage_criteria.rb` because `:eval`
7
- # is a boolean toggle, never a member of the enabled-criteria set.
8
4
  module Configuration
5
+ # The standalone eval-coverage toggle, kept apart from the criteria-set logic
6
+ # in `coverage_criteria.rb` because `:eval` is a boolean toggle, never a
7
+ # member of the enabled-criteria set.
9
8
  def coverage_for_eval_supported?
10
9
  coverage_criterion_supported?(:eval)
11
10
  end
@@ -14,17 +13,14 @@ module SimpleCov
14
13
  @coverage_for_eval_enabled ||= false
15
14
  end
16
15
 
17
- # DEPRECATED: prefer `enable_coverage :eval`.
18
16
  def enable_coverage_for_eval
19
- SimpleCov::Deprecation.warn("`SimpleCov.enable_coverage_for_eval` is deprecated. " \
20
- "Replace with `SimpleCov.enable_coverage :eval`.")
17
+ Deprecation.warn("`SimpleCov.enable_coverage_for_eval` is deprecated. " \
18
+ "Replace with `SimpleCov.enable_coverage :eval`.")
21
19
  enable_eval_coverage
22
20
  end
23
21
 
24
- private
22
+ private
25
23
 
26
- # Shared implementation backing both `enable_coverage :eval` and
27
- # the deprecated `enable_coverage_for_eval`.
28
24
  def enable_eval_coverage
29
25
  if coverage_for_eval_supported?
30
26
  @coverage_for_eval_enabled = true