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,80 +1,93 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # Result merging and subprocess / parallel-test coordination:
5
- # `merging`, `merge_subprocesses`, `merge_timeout`, `parallel_tests`.
6
4
  module Configuration
7
5
  #
8
- # Get or set whether SimpleCov should hook `Process._fork` to
9
- # attach itself to subprocesses. Required when the suite uses
10
- # parallel test workers (e.g. Rails' `parallelize(workers:)`).
11
- # Defaults to false.
6
+ # Whether SimpleCov should hook `Process._fork` to attach itself to
7
+ # subprocesses. Required when the suite uses parallel test workers (Rails'
8
+ # `parallelize(workers:)`). Defaults to false.
12
9
  #
13
10
  def merge_subprocesses(value = nil)
14
- return @enable_for_subprocesses if defined?(@enable_for_subprocesses) && value.nil?
11
+ return @enable_for_subprocesses if instance_variable_defined?(:@enable_for_subprocesses) && value.nil?
15
12
 
13
+ self.merge_subprocesses = value
14
+ @enable_for_subprocesses
15
+ end
16
+
17
+ # false stands in for nothing, so the answer is always a real opt-in or
18
+ # opt-out.
19
+ def merge_subprocesses=(value)
16
20
  @enable_for_subprocesses = value || false
17
21
  end
18
22
 
19
- # @api private — predicate used by `start_tracking` to decide
20
- # whether to install the fork hook.
23
+ # @api private
21
24
  def enabled_for_subprocesses?
22
- defined?(@enable_for_subprocesses) ? @enable_for_subprocesses : false
25
+ !!@enable_for_subprocesses
23
26
  end
24
27
 
25
28
  #
26
- # Get or set whether SimpleCov should auto-require the
27
- # `parallel_tests` gem when it sees `TEST_ENV_NUMBER` /
28
- # `PARALLEL_TEST_GROUPS` in the environment. Defaults to auto-detect
29
- # (nil). See #1018.
29
+ # Whether SimpleCov should auto-require the `parallel_tests` gem when it
30
+ # sees `TEST_ENV_NUMBER` / `PARALLEL_TEST_GROUPS` in the environment.
31
+ # Defaults to auto-detect (nil). See #1018.
30
32
  #
31
33
  def parallel_tests(value = :__no_arg__)
32
- return defined?(@parallel_tests) ? @parallel_tests : nil if value == :__no_arg__
34
+ return @parallel_tests if value.eql?(:__no_arg__)
35
+
36
+ self.parallel_tests = _ = value
37
+ end
33
38
 
39
+ def parallel_tests=(value)
34
40
  @parallel_tests = value
35
41
  end
36
42
 
37
- # DEPRECATED: alias for `merge_subprocesses`. Same value/behavior.
38
43
  def enable_for_subprocesses(value = nil)
39
- SimpleCov::Deprecation.warn("`SimpleCov.enable_for_subprocesses` is deprecated. " \
40
- "Replace with `SimpleCov.merge_subprocesses` (same value, same behavior).")
44
+ Deprecation.warn("`SimpleCov.enable_for_subprocesses` is deprecated. " \
45
+ "Replace with `SimpleCov.merge_subprocesses` (same value, same behavior).")
41
46
  merge_subprocesses(value)
42
47
  end
43
48
 
44
- #
45
- # Get or set whether to merge results from multiple test suites
46
- # (test:units, test:functionals, cucumber, ...) into a single
47
- # coverage report. Defaults to true.
48
- #
49
49
  def merging(use = nil)
50
- @use_merging = use unless use.nil?
51
- @use_merging = true unless defined?(@use_merging) && @use_merging == false
50
+ self.merging = use unless use.nil?
51
+ # Unset reads as nil, and only an explicit `merging false` turns it off.
52
+ @use_merging = true if @use_merging.nil?
52
53
  @use_merging
53
54
  end
54
55
 
56
+ def merging=(use)
57
+ @use_merging = use
58
+ end
59
+
55
60
  #
56
- # Get or set whether SimpleCov's selected final process owns merge processing:
57
- # waiting for sibling workers, building the merged result, formatting,
58
- # enforcing thresholds, and writing `.last_run.json`.
61
+ # Whether SimpleCov's selected final process owns merge processing: waiting
62
+ # for sibling workers, building the merged result, formatting, enforcing
63
+ # thresholds, and writing `.last_run.json`.
59
64
  #
60
- # Defaults to true, except for recognized multi-worker parallel runs
61
- # that explicitly write to a custom coverage destination while merging
62
- # is enabled. Those runs are likely using an external `SimpleCov.collate`
63
- # step to finalize the merge.
65
+ # Defaults to true, except for recognized multi-worker parallel runs that
66
+ # explicitly write to a custom coverage destination while merging is
67
+ # enabled. Those runs are likely using an external `SimpleCov.collate` step
68
+ # to finalize the merge.
64
69
  #
65
- def finalize_merge(value = :__no_arg__)
66
- unless value == :__no_arg__
67
- @finalize_merge = value
68
- @finalize_merge_explicit = true
70
+ # Splatted rather than defaulted: false is a value someone sets on purpose,
71
+ # so "given nothing" cannot be told from it by looking at the argument.
72
+ def finalize_merge(*value)
73
+ unless value.empty?
74
+ explicit, = value
75
+ self.finalize_merge = _ = explicit
69
76
  end
70
77
 
71
- return @finalize_merge if defined?(@finalize_merge_explicit) && @finalize_merge_explicit
78
+ return @finalize_merge if @finalize_merge_explicit
72
79
 
73
80
  inferred = inferred_finalize_merge?
74
81
  warn_about_inferred_finalize_merge unless inferred
75
82
  inferred
76
83
  end
77
84
 
85
+ # Writing is what makes the answer explicit rather than inferred.
86
+ def finalize_merge=(value)
87
+ @finalize_merge = value
88
+ @finalize_merge_explicit = true
89
+ end
90
+
78
91
  def finalize_merge?
79
92
  finalize_merge
80
93
  end
@@ -84,45 +97,65 @@ module SimpleCov
84
97
  collating_result? || (finalize_merge? && final_result_process?)
85
98
  end
86
99
 
87
- # DEPRECATED: alias for `merging`. Same value, same behavior.
88
- # Delegating (rather than duplicating the body) also fixes the
89
- # return value: this used to return nil where `merging` returns
90
- # false, because the final expression was the skipped guard
91
- # assignment rather than the stored value.
92
100
  def use_merging(use = nil)
93
- SimpleCov::Deprecation.warn("`SimpleCov.use_merging` is deprecated. " \
94
- "Replace with `SimpleCov.merging` (same value, same behavior).")
101
+ Deprecation.warn("`SimpleCov.use_merging` is deprecated. " \
102
+ "Replace with `SimpleCov.merging` (same value, same behavior).")
95
103
  merging(use)
96
104
  end
97
105
 
98
106
  #
99
- # Defines the maximum age (in seconds) of a resultset to still be
100
- # included in merged results. Default is 600 seconds (10 minutes).
107
+ # The maximum age (in seconds) of a resultset to still be included in
108
+ # merged results. Default is 600 seconds.
109
+ #
110
+ # A numeric SIMPLECOV_MERGE_TIMEOUT in the environment takes precedence
111
+ # over the configured value: `simplecov watch` re-runs test subsets across
112
+ # a long session and extends its child runs' merge window this way, where
113
+ # editing every watched project's configuration is not an option.
101
114
  #
102
115
  def merge_timeout(seconds = nil)
103
- @merge_timeout = seconds if seconds.is_a?(Integer)
104
- @merge_timeout ||= 600
116
+ self.merge_timeout = seconds
117
+ # Memoized through a local rather than `|| (@merge_timeout ||= 600)`:
118
+ # steep 2.0's logic-type interpreter crashes on an or-assignment nested in
119
+ # a logical operand, and ivar narrowing doesn't carry across statements
120
+ # the way a local's does.
121
+ configured = @merge_timeout || 600
122
+ @merge_timeout = configured
123
+ env_merge_timeout || configured
124
+ end
125
+
126
+ # Anything but an Integer is ignored, the way the dual method always did.
127
+ def merge_timeout=(seconds)
128
+ @merge_timeout = seconds if seconds.instance_of?(Integer)
129
+ end
130
+
131
+ def env_merge_timeout
132
+ value = ENV.fetch("SIMPLECOV_MERGE_TIMEOUT", nil)
133
+ Integer(value, 10) if value&.match?(/\A\d+\z/)
105
134
  end
106
135
 
107
136
  #
108
- # Defines how long (in seconds) the reporting process waits for the
109
- # remaining parallel-test workers to write their resultsets before it
110
- # proceeds with a partial merge. Default is 60 seconds. Raise it when a
111
- # slow worker routinely finishes well after the others, so its coverage
112
- # is still included and the minimum / maximum coverage checks aren't
113
- # skipped against a partial total.
137
+ # How long (in seconds) the reporting process waits for the remaining
138
+ # parallel-test workers to write their resultsets before it proceeds with a
139
+ # partial merge. Default is 60 seconds. Raise it when a slow worker
140
+ # routinely finishes well after the others, so its coverage is still
141
+ # included and the threshold checks aren't skipped against a partial total.
114
142
  #
115
143
  def parallel_wait_timeout(seconds = nil)
116
- @parallel_wait_timeout = seconds if seconds.is_a?(Integer)
144
+ self.parallel_wait_timeout = seconds
117
145
  @parallel_wait_timeout ||= 60
118
146
  end
119
147
 
120
- private
148
+ # Anything but an Integer is ignored, the way the dual method always did.
149
+ def parallel_wait_timeout=(seconds)
150
+ @parallel_wait_timeout = seconds if seconds.instance_of?(Integer)
151
+ end
152
+
153
+ private
121
154
 
122
155
  def inferred_finalize_merge?
123
156
  return true unless merging
124
157
 
125
- adapter = SimpleCov::ParallelAdapters.current
158
+ adapter = ParallelAdapters.current
126
159
  return true unless adapter
127
160
  return true unless adapter.expected_worker_count > 1
128
161
  return true unless parallel_worker_environment?
@@ -138,20 +171,19 @@ module SimpleCov
138
171
  def explicit_custom_coverage_destination?
139
172
  return false unless explicit_coverage_destination?
140
173
 
141
- coverage_path != File.expand_path("coverage", root)
174
+ !coverage_path.eql?(File.expand_path("coverage", root))
142
175
  end
143
176
 
144
177
  def explicit_coverage_destination?
145
- (defined?(@coverage_path_explicit) && @coverage_path_explicit) ||
146
- (defined?(@coverage_dir_explicit) && @coverage_dir_explicit)
178
+ @coverage_path_explicit || @coverage_dir_explicit
147
179
  end
148
180
 
149
181
  def warn_about_inferred_finalize_merge
150
- return if defined?(@finalize_merge_inference_warned) && @finalize_merge_inference_warned
182
+ return if @finalize_merge_inference_warned
151
183
  return unless print_errors
152
184
 
153
185
  @finalize_merge_inference_warned = true
154
- warn SimpleCov::Color.colorize(inferred_finalize_merge_warning, :yellow)
186
+ warn Color.colorize(inferred_finalize_merge_warning, :yellow)
155
187
  end
156
188
 
157
189
  def inferred_finalize_merge_warning
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ # Caps the total number of misses per criterion. Where a percent minimum
6
+ # asks for a ratio, this asks for an absolute burn-down number, which stays
7
+ # meaningful as the codebase grows and shrinks.
8
+ def maximum_missed(counts = nil)
9
+ return @maximum_missed ||= {} unless counts
10
+
11
+ @maximum_missed = normalized_missed_caps(counts, "maximum_missed")
12
+ end
13
+
14
+ # Unlike a percent minimum, which systematically flatters big files, this
15
+ # holds every file to the same absolute budget. Files with a baseline entry
16
+ # are exempt per covered criterion, as they are from the per-file minimum.
17
+ def maximum_missed_per_file(counts = nil)
18
+ return @maximum_missed_per_file ||= {} unless counts
19
+
20
+ Deprecation.warn("`SimpleCov.maximum_missed_per_file` is deprecated. " \
21
+ "Replace it with:\n#{missed_per_file_replacement(counts)}")
22
+ @maximum_missed_per_file = normalized_missed_caps(counts, "maximum_missed_per_file")
23
+ end
24
+
25
+ def maximum_missed_per_file_overrides
26
+ @maximum_missed_per_file_overrides ||= {}
27
+ end
28
+
29
+ private
30
+
31
+ def normalized_missed_caps(counts, setting)
32
+ counts = {primary_coverage => counts} if counts.is_a?(Numeric)
33
+ counts.each_key { |criterion| raise_if_criterion_disabled(criterion) }
34
+ counts.each_value { |cap| raise_on_invalid_missed_cap(cap, setting) }
35
+ _ = counts
36
+ end
37
+
38
+ def raise_on_invalid_missed_cap(cap, setting)
39
+ return if cap.instance_of?(Integer) && cap >= 0
40
+
41
+ raise ConfigurationError,
42
+ "#{setting} takes a non-negative integer count of misses, got #{cap.inspect}"
43
+ end
44
+
45
+ def missed_per_file_replacement(counts)
46
+ counts = {primary_coverage => counts} if counts.is_a?(Numeric)
47
+ counts.map { |criterion, cap| " coverage(#{criterion.inspect}) { maximum_missed #{cap}, per: :file }" }
48
+ .join("\n")
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ # The path to a production coverage store, the file a `SimpleCov::Production`
6
+ # sink wrote. Relative paths resolve against `SimpleCov.root`. The store is
7
+ # read at report time, and an unreadable or invalid file warns and leaves the
8
+ # section out, because a missing night's data should not fail the suite that
9
+ # measured the tests.
10
+ def production_coverage(path = nil)
11
+ return @production_coverage if path.nil?
12
+
13
+ self.production_coverage = path
14
+ @production_coverage
15
+ end
16
+
17
+ def production_coverage=(path)
18
+ raise ConfigurationError, "production_coverage takes a path, got #{path.inspect}" unless path.is_a?(String)
19
+
20
+ @production_coverage = File.expand_path(path, root)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ TRACK_TESTS_GRANULARITIES = %i[test file].freeze
6
+
7
+ # Enables recording of which test covered which line. Off by default:
8
+ # sampling coverage around every test costs run time, and the recorded map
9
+ # costs space in `.resultset.json`. RSpec examples and Minitest tests are
10
+ # wrapped automatically; other runners wrap their own units of work with
11
+ # `SimpleCov.track_test`. A bare `track_tests` enables, and only an explicit
12
+ # `track_tests false` turns it back off.
13
+ #
14
+ # `granularity` decides what one recorded context stands for: a test
15
+ # (`:test`, the default) or a whole test file (`:file`). File granularity is
16
+ # the batching lever for suites where per-test sampling costs too much.
17
+ def track_tests(enabled = nil, granularity: nil)
18
+ if granularity && !TRACK_TESTS_GRANULARITIES.include?(granularity)
19
+ raise ConfigurationError,
20
+ "Unsupported track_tests granularity #{granularity.inspect}, " \
21
+ "supported values are #{TRACK_TESTS_GRANULARITIES.inspect}"
22
+ end
23
+
24
+ @track_tests_granularity = granularity if granularity
25
+ @track_tests = enabled.nil? || enabled
26
+ end
27
+
28
+ def track_tests?
29
+ !!@track_tests
30
+ end
31
+
32
+ def track_tests_granularity
33
+ @track_tests_granularity || :test
34
+ end
35
+
36
+ # @api private. The map is built from per-line execution count deltas, which
37
+ # `:oneshot_line` does not produce: a line reports only its first hit ever,
38
+ # so every test after the first would show no delta.
39
+ def validate_test_tracking!
40
+ return unless track_tests?
41
+ return if coverage_criterion_enabled?(DEFAULT_COVERAGE_CRITERION)
42
+
43
+ raise ConfigurationError,
44
+ "`track_tests` needs line coverage with execution counts. " \
45
+ "Enable it with `enable_coverage :line` (`:oneshot_line` cannot record per-test data)."
46
+ end
47
+ end
48
+ end
@@ -1,16 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # Coverage threshold configuration: `minimum_coverage`,
5
- # `maximum_coverage`, `expected_coverage`, `maximum_coverage_drop`,
6
- # `minimum_coverage_by_file`, `minimum_coverage_by_group`,
7
- # `refuse_coverage_drop`, and friends.
8
4
  module Configuration
9
- #
10
- # Defines the minimum overall coverage required for the testsuite to pass.
11
- # Returns non-zero if the current coverage is below this threshold.
12
- # Default is 0% (disabled).
13
- #
14
5
  def minimum_coverage(coverage = nil)
15
6
  return @minimum_coverage ||= {} unless coverage
16
7
 
@@ -25,10 +16,10 @@ module SimpleCov
25
16
  end
26
17
 
27
18
  #
28
- # Defines the maximum overall coverage allowed for the testsuite to
29
- # pass. Useful paired with `minimum_coverage` (or via
30
- # `expected_coverage`) to pin coverage to an exact value, so an
31
- # unexpected jump up fails the build. See #187.
19
+ # The maximum overall coverage allowed for the testsuite to pass. Useful
20
+ # paired with `minimum_coverage` (or via `expected_coverage`) to pin
21
+ # coverage to an exact value, so an unexpected jump up fails the build
22
+ # (#187).
32
23
  #
33
24
  def maximum_coverage(coverage = nil)
34
25
  return @maximum_coverage ||= {} unless coverage
@@ -36,10 +27,6 @@ module SimpleCov
36
27
  @maximum_coverage = normalized_threshold(coverage, "maximum_coverage")
37
28
  end
38
29
 
39
- #
40
- # Pins the suite to an exact coverage figure by setting both
41
- # `minimum_coverage` and `maximum_coverage`. See #187.
42
- #
43
30
  def expected_coverage(coverage = nil)
44
31
  return minimum_coverage if coverage.nil?
45
32
 
@@ -47,10 +34,6 @@ module SimpleCov
47
34
  maximum_coverage(coverage)
48
35
  end
49
36
 
50
- #
51
- # Defines the maximum coverage drop at once allowed for the
52
- # testsuite to pass. Default is 100% (disabled).
53
- #
54
37
  def maximum_coverage_drop(coverage_drop = nil)
55
38
  return @maximum_coverage_drop ||= {} unless coverage_drop
56
39
 
@@ -58,11 +41,10 @@ module SimpleCov
58
41
  end
59
42
 
60
43
  #
61
- # Defines the minimum coverage per file required for the testsuite
62
- # to pass. Accepts a Numeric (global threshold on the primary
63
- # criterion), a Symbol-keyed Hash (per-criterion globals), or a
64
- # Hash mixing Symbol keys with String / Regexp keys to declare
65
- # per-path overrides. See README and #575.
44
+ # The minimum coverage per file required for the testsuite to pass.
45
+ # Accepts a Numeric (global threshold on the primary criterion), a
46
+ # Symbol-keyed Hash (per-criterion globals), or a Hash mixing Symbol keys
47
+ # with String / Regexp keys to declare per-path overrides (#575).
66
48
  #
67
49
  def minimum_coverage_by_file(coverage = nil)
68
50
  return @minimum_coverage_by_file ||= {} unless coverage
@@ -70,8 +52,8 @@ module SimpleCov
70
52
  coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
71
53
  defaults, overrides = partition_per_file_thresholds(coverage)
72
54
 
73
- SimpleCov::Deprecation.warn("`SimpleCov.minimum_coverage_by_file` is deprecated. " \
74
- "Replace it with:\n#{per_file_coverage_replacement(defaults, overrides)}")
55
+ Deprecation.warn("`SimpleCov.minimum_coverage_by_file` is deprecated. " \
56
+ "Replace it with:\n#{per_file_coverage_replacement(defaults, overrides)}")
75
57
 
76
58
  raise_on_invalid_coverage(defaults, "minimum_coverage_by_file")
77
59
  overrides.each_value { |criteria| raise_on_invalid_coverage(criteria, "minimum_coverage_by_file") }
@@ -80,91 +62,80 @@ module SimpleCov
80
62
  @minimum_coverage_by_file_overrides = overrides
81
63
  end
82
64
 
83
- # Returns the per-path overrides set via `minimum_coverage_by_file`.
84
65
  def minimum_coverage_by_file_overrides
85
66
  @minimum_coverage_by_file_overrides ||= {}
86
67
  end
87
68
 
88
- #
89
- # Defines the minimum coverage per group required for the testsuite
90
- # to pass. Default is 0% (disabled).
91
- #
92
69
  def minimum_coverage_by_group(coverage = nil)
93
70
  return @minimum_coverage_by_group ||= {} unless coverage
94
71
 
95
- SimpleCov::Deprecation.warn("`SimpleCov.minimum_coverage_by_group` is deprecated. " \
96
- "Replace it with:\n#{per_group_coverage_replacement(coverage)}")
72
+ Deprecation.warn("`SimpleCov.minimum_coverage_by_group` is deprecated. " \
73
+ "Replace it with:\n#{per_group_coverage_replacement(coverage)}")
97
74
  @minimum_coverage_by_group = coverage.to_h do |group_name, group_coverage|
98
75
  [GroupNames.normalize(group_name), normalized_threshold(group_coverage, "minimum_coverage_by_group")]
99
76
  end
100
77
  end
101
78
 
102
- #
103
- # Refuses any coverage drop. Coverage is only allowed to increase.
104
- #
105
79
  def refuse_coverage_drop(*criteria)
106
80
  criteria = coverage_criteria if criteria.empty?
107
81
  maximum_coverage_drop(criteria.to_h { |c| [c, 0] })
108
82
  end
109
83
 
110
- private
84
+ private
111
85
 
112
- # Shared normalize-and-validate step behind every threshold setter:
113
- # a bare Numeric targets the primary criterion, and the resulting
86
+ # A bare Numeric targets the primary criterion, and the resulting
114
87
  # per-criterion hash is validated before it is stored.
115
88
  def normalized_threshold(coverage, setting)
116
89
  coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
117
- raise_on_invalid_coverage(coverage, setting)
118
- coverage
90
+ coverage.tap { |thresholds| raise_on_invalid_coverage(thresholds, setting) }
119
91
  end
120
92
 
121
- # Split a `minimum_coverage_by_file` argument into Symbol-keyed
122
- # criterion defaults and String/Regexp-keyed per-path overrides;
123
- # normalize Numeric override values to `{primary_coverage => N}`
124
- # so downstream code only has one shape to handle.
93
+ # Splits into Symbol-keyed criterion defaults and String/Regexp-keyed
94
+ # per-path overrides, normalizing Numeric override values to
95
+ # `{primary_coverage => N}` so downstream code has one shape to handle.
125
96
  def partition_per_file_thresholds(coverage)
126
97
  coverage.each_key { |key| validate_per_file_key(key) }
127
- pairs = coverage.partition { |key, _| key.is_a?(Symbol) }
128
- # The assertions restate what the partition predicate guarantees:
129
- # Symbol keys carry per-criterion Numeric defaults, the rest are paths.
130
- defaults = pairs[0].to_h #: coverage_thresholds
131
- raw = pairs[1].to_h #: Hash[String | Regexp, Numeric | coverage_thresholds]
98
+ # The assertions restate what the partition predicate guarantees: Symbol
99
+ # keys carry per-criterion Numeric defaults, the rest are paths.
100
+ symbol_pairs, path_pairs = coverage.partition { |key, _| key.instance_of?(Symbol) }
101
+ defaults = symbol_pairs.to_h #: coverage_thresholds
102
+ raw = path_pairs.to_h #: Hash[String | Regexp, Numeric | coverage_thresholds]
132
103
  overrides = raw.transform_values { |value| value.is_a?(Numeric) ? {primary_coverage => value} : value }
133
104
  [defaults, overrides]
134
105
  end
135
106
 
136
107
  def validate_per_file_key(key)
137
- return if key.is_a?(Symbol) || key.is_a?(String) || key.is_a?(Regexp)
108
+ # Symbols have no subclasses; paths and patterns may.
109
+ return if key.instance_of?(Symbol) || key.is_a?(String) || key.is_a?(Regexp)
138
110
 
139
- raise SimpleCov::ConfigurationError,
140
- "minimum_coverage_by_file keys must be Symbol (criterion), String, or Regexp; got #{key.inspect}"
111
+ raise ConfigurationError,
112
+ "minimum_coverage_by_file keys must be Symbol (criterion), String, or Regexp; got #{key.inspect}"
141
113
  end
142
114
 
143
115
  def minimum_possible_coverage_exceeded(coverage_option)
144
116
  warn "The coverage you set for #{coverage_option} is greater than 100%"
145
117
  end
146
118
 
147
- # Render the `coverage` configuration equivalent to a (deprecated)
148
- # `minimum_coverage_by_file` argument so the deprecation warning can be
119
+ # Renders the `coverage` configuration equivalent to a deprecated
120
+ # `minimum_coverage_by_file` argument, so the deprecation warning can be
149
121
  # copy-pasted verbatim into the user's config.
150
122
  def per_file_coverage_replacement(defaults, overrides)
151
123
  by_criterion = {} #: Hash[Symbol, Array[String]]
152
- defaults.each { |criterion, percent| (by_criterion[criterion] ||= []) << "minimum_per_file #{percent}" }
124
+ defaults.each { |criterion, percent| (by_criterion[criterion] ||= []) << "minimum #{percent}, per: :file" }
153
125
  overrides.each do |target, criteria|
154
126
  criteria.each do |criterion, percent|
155
- (by_criterion[criterion] ||= []) << "minimum_per_file #{percent}, only: #{target.inspect}"
127
+ (by_criterion[criterion] ||= []) << "minimum #{percent}, per: #{target.inspect}"
156
128
  end
157
129
  end
158
130
  render_coverage_blocks(by_criterion)
159
131
  end
160
132
 
161
- # Same, for a (deprecated) `minimum_coverage_by_group` argument.
162
133
  def per_group_coverage_replacement(coverage)
163
134
  by_criterion = {} #: Hash[Symbol, Array[String]]
164
135
  coverage.each do |group_name, thresholds|
165
136
  normalized = (thresholds.is_a?(Numeric) ? {primary_coverage => thresholds} : thresholds) #: coverage_thresholds
166
137
  normalized.each do |criterion, percent|
167
- (by_criterion[criterion] ||= []) << "minimum_per_group #{percent}, only: #{group_name.inspect}"
138
+ (by_criterion[criterion] ||= []) << "minimum #{percent}, per: group(#{group_name.inspect})"
168
139
  end
169
140
  end
170
141
  render_coverage_blocks(by_criterion)
@@ -172,7 +143,7 @@ module SimpleCov
172
143
 
173
144
  def render_coverage_blocks(by_criterion)
174
145
  by_criterion.map do |criterion, statements|
175
- " coverage(#{criterion.inspect}) { #{statements.join('; ')} }"
146
+ " coverage(#{criterion.inspect}) { #{statements.join("; ")} }"
176
147
  end.join("\n")
177
148
  end
178
149
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ DEFAULT_VIEW_GLOBS = %w[app/views/**/*.{erb,haml,slim}].freeze
6
+
7
+ # Reports coverage for ActionView templates, defaulting to a Rails app's
8
+ # views. Globs are expanded against `SimpleCov.root`, and one naming an
9
+ # extension no handler is registered for (as the default does whenever a
10
+ # project has no Haml or Slim) leaves those files out rather than reporting
11
+ # them as empty.
12
+ #
13
+ # Templates the suite renders are measured through eval coverage, which this
14
+ # enables, so it needs Ruby 3.2 or later. Templates it never renders are
15
+ # compiled at the end of the run so they appear at 0% instead of going
16
+ # missing.
17
+ def cover_views(*globs)
18
+ globs = globs.flatten.compact
19
+ globs = DEFAULT_VIEW_GLOBS.dup if globs.empty?
20
+ @view_globs = globs
21
+ enable_eval_coverage
22
+ globs
23
+ end
24
+
25
+ # Nil rather than an empty Array when `cover_views` was never called, so
26
+ # "not asked for" stays distinguishable from "asked for, with nothing to
27
+ # match".
28
+ def view_globs
29
+ @view_globs
30
+ end
31
+
32
+ # False when eval coverage couldn't be enabled, where `cover_views` has
33
+ # already warned and the templates would come back empty rather than at 0%.
34
+ def view_coverage?
35
+ !view_globs.nil? && coverage_for_eval_enabled?
36
+ end
37
+ end
38
+ end