simplecov 1.1.1 → 1.3.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 (200) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +148 -5
  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/annotations.rb +166 -0
  13. data/lib/simplecov/cli/badge/svg.rb +70 -0
  14. data/lib/simplecov/cli/badge.rb +62 -0
  15. data/lib/simplecov/cli/clean.rb +23 -24
  16. data/lib/simplecov/cli/command_helpers.rb +55 -19
  17. data/lib/simplecov/cli/completions/scripts.rb +114 -0
  18. data/lib/simplecov/cli/completions.rb +83 -0
  19. data/lib/simplecov/cli/coverage.rb +25 -28
  20. data/lib/simplecov/cli/coverage_file.rb +58 -14
  21. data/lib/simplecov/cli/dead_code/output.rb +100 -0
  22. data/lib/simplecov/cli/dead_code.rb +139 -0
  23. data/lib/simplecov/cli/diff/output.rb +45 -0
  24. data/lib/simplecov/cli/diff.rb +35 -67
  25. data/lib/simplecov/cli/dotfile.rb +83 -38
  26. data/lib/simplecov/cli/git.rb +41 -0
  27. data/lib/simplecov/cli/history/output.rb +135 -0
  28. data/lib/simplecov/cli/history.rb +67 -0
  29. data/lib/simplecov/cli/merge.rb +34 -39
  30. data/lib/simplecov/cli/open.rb +12 -15
  31. data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
  32. data/lib/simplecov/cli/patch/output.rb +151 -0
  33. data/lib/simplecov/cli/patch.rb +175 -0
  34. data/lib/simplecov/cli/ratchet/output.rb +44 -0
  35. data/lib/simplecov/cli/ratchet.rb +83 -0
  36. data/lib/simplecov/cli/report.rb +18 -24
  37. data/lib/simplecov/cli/run.rb +8 -9
  38. data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
  39. data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
  40. data/lib/simplecov/cli/serve.rb +27 -24
  41. data/lib/simplecov/cli/show/annotator.rb +87 -0
  42. data/lib/simplecov/cli/show/sweep.rb +36 -0
  43. data/lib/simplecov/cli/show.rb +133 -0
  44. data/lib/simplecov/cli/status/facts.rb +87 -0
  45. data/lib/simplecov/cli/status.rb +93 -0
  46. data/lib/simplecov/cli/tests/redundancy.rb +78 -0
  47. data/lib/simplecov/cli/tests.rb +148 -0
  48. data/lib/simplecov/cli/uncovered/misses.rb +33 -0
  49. data/lib/simplecov/cli/uncovered.rb +58 -34
  50. data/lib/simplecov/cli/usage.rb +153 -0
  51. data/lib/simplecov/cli/watch/live_report.rb +73 -0
  52. data/lib/simplecov/cli/watch/narrator.rb +55 -0
  53. data/lib/simplecov/cli/watch/poller.rb +45 -0
  54. data/lib/simplecov/cli/watch/session.rb +134 -0
  55. data/lib/simplecov/cli/watch/test_plan.rb +43 -0
  56. data/lib/simplecov/cli/watch.rb +85 -0
  57. data/lib/simplecov/cli.rb +43 -92
  58. data/lib/simplecov/color.rb +19 -37
  59. data/lib/simplecov/combine/branches_combiner.rb +7 -31
  60. data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
  61. data/lib/simplecov/combine/identity_interner.rb +10 -13
  62. data/lib/simplecov/combine/interned_counts.rb +7 -9
  63. data/lib/simplecov/combine/lines_combiner.rb +20 -44
  64. data/lib/simplecov/combine/methods_combiner.rb +9 -35
  65. data/lib/simplecov/combine/results_combiner.rb +4 -21
  66. data/lib/simplecov/combine.rb +0 -6
  67. data/lib/simplecov/command_guesser.rb +20 -34
  68. data/lib/simplecov/configuration/baseline.rb +31 -0
  69. data/lib/simplecov/configuration/coverage.rb +91 -79
  70. data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
  71. data/lib/simplecov/configuration/deprecations.rb +21 -0
  72. data/lib/simplecov/configuration/eval_coverage.rb +7 -11
  73. data/lib/simplecov/configuration/filters.rb +40 -73
  74. data/lib/simplecov/configuration/formatting.rb +74 -83
  75. data/lib/simplecov/configuration/groups.rb +19 -13
  76. data/lib/simplecov/configuration/history.rb +44 -0
  77. data/lib/simplecov/configuration/ignored_entries.rb +31 -22
  78. data/lib/simplecov/configuration/merging.rb +94 -62
  79. data/lib/simplecov/configuration/missed_caps.rb +51 -0
  80. data/lib/simplecov/configuration/production.rb +23 -0
  81. data/lib/simplecov/configuration/test_tracking.rb +48 -0
  82. data/lib/simplecov/configuration/thresholds.rb +33 -62
  83. data/lib/simplecov/configuration/view_coverage.rb +37 -0
  84. data/lib/simplecov/configuration.rb +70 -76
  85. data/lib/simplecov/configuration_error.rb +5 -0
  86. data/lib/simplecov/context_map/union.rb +81 -0
  87. data/lib/simplecov/context_map.rb +187 -0
  88. data/lib/simplecov/coverage_json.rb +2 -5
  89. data/lib/simplecov/coverage_statistics.rb +14 -24
  90. data/lib/simplecov/coverage_violations.rb +160 -65
  91. data/lib/simplecov/current_run.rb +61 -0
  92. data/lib/simplecov/defaults.rb +4 -23
  93. data/lib/simplecov/deprecation.rb +45 -27
  94. data/lib/simplecov/directive/erb.rb +44 -0
  95. data/lib/simplecov/directive/haml.rb +33 -0
  96. data/lib/simplecov/directive/indented_template.rb +37 -0
  97. data/lib/simplecov/directive/slim.rb +35 -0
  98. data/lib/simplecov/directive/template.rb +29 -0
  99. data/lib/simplecov/directive.rb +47 -62
  100. data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
  101. data/lib/simplecov/exit_codes/check.rb +17 -11
  102. data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
  103. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
  104. data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
  105. data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
  106. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +7 -12
  107. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
  108. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
  109. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
  110. data/lib/simplecov/exit_codes.rb +16 -10
  111. data/lib/simplecov/exit_handling.rb +94 -59
  112. data/lib/simplecov/file_list.rb +20 -51
  113. data/lib/simplecov/filter.rb +43 -66
  114. data/lib/simplecov/formatter/base.rb +37 -55
  115. data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
  116. data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
  117. data/lib/simplecov/formatter/html_formatter/public/index.html +15 -9
  118. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
  119. data/lib/simplecov/formatter/html_formatter.rb +24 -41
  120. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
  121. data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
  122. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
  123. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
  124. data/lib/simplecov/formatter/json_formatter.rb +7 -13
  125. data/lib/simplecov/formatter/multi_formatter.rb +5 -11
  126. data/lib/simplecov/formatter/simple_formatter.rb +2 -8
  127. data/lib/simplecov/formatter.rb +14 -10
  128. data/lib/simplecov/group_names.rb +13 -12
  129. data/lib/simplecov/history.rb +115 -0
  130. data/lib/simplecov/last_run.rb +6 -9
  131. data/lib/simplecov/lines_classifier.rb +31 -16
  132. data/lib/simplecov/load_global_config.rb +7 -7
  133. data/lib/simplecov/parallel_adapters/base.rb +13 -41
  134. data/lib/simplecov/parallel_adapters/generic.rb +7 -19
  135. data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
  136. data/lib/simplecov/parallel_adapters.rb +24 -55
  137. data/lib/simplecov/parallel_coordination.rb +39 -49
  138. data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
  139. data/lib/simplecov/parallel_result_merger.rb +67 -126
  140. data/lib/simplecov/process.rb +14 -32
  141. data/lib/simplecov/production/error.rb +11 -0
  142. data/lib/simplecov/production/file_sink.rb +130 -0
  143. data/lib/simplecov/production.rb +249 -0
  144. data/lib/simplecov/profiles/rails.rb +4 -15
  145. data/lib/simplecov/profiles/root_filter.rb +0 -4
  146. data/lib/simplecov/profiles/strict.rb +11 -21
  147. data/lib/simplecov/profiles.rb +11 -33
  148. data/lib/simplecov/report_deferral.rb +11 -15
  149. data/lib/simplecov/report_stamp.rb +6 -9
  150. data/lib/simplecov/result/filter_config.rb +19 -0
  151. data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
  152. data/lib/simplecov/result/serialization.rb +43 -0
  153. data/lib/simplecov/result/source_file_builder.rb +7 -12
  154. data/lib/simplecov/result.rb +74 -117
  155. data/lib/simplecov/result_adapter.rb +56 -68
  156. data/lib/simplecov/result_merger/contexts.rb +22 -0
  157. data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
  158. data/lib/simplecov/result_merger/resultset_file.rb +20 -27
  159. data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
  160. data/lib/simplecov/result_merger/resultset_store.rb +23 -14
  161. data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
  162. data/lib/simplecov/result_merger.rb +59 -58
  163. data/lib/simplecov/result_processing.rb +92 -122
  164. data/lib/simplecov/run_identity.rb +16 -18
  165. data/lib/simplecov/simulate_coverage.rb +34 -50
  166. data/lib/simplecov/source_file/branch.rb +8 -33
  167. data/lib/simplecov/source_file/branch_builder.rb +28 -47
  168. data/lib/simplecov/source_file/builder_context.rb +13 -13
  169. data/lib/simplecov/source_file/line.rb +13 -33
  170. data/lib/simplecov/source_file/line_builder.rb +9 -15
  171. data/lib/simplecov/source_file/method.rb +3 -7
  172. data/lib/simplecov/source_file/method_builder.rb +6 -15
  173. data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
  174. data/lib/simplecov/source_file/skip_chunks.rb +29 -29
  175. data/lib/simplecov/source_file/source_loader.rb +45 -35
  176. data/lib/simplecov/source_file/statistics.rb +20 -9
  177. data/lib/simplecov/source_file.rb +21 -51
  178. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +55 -139
  179. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
  180. data/lib/simplecov/static_coverage_extractor/method_collector.rb +9 -26
  181. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
  182. data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
  183. data/lib/simplecov/static_coverage_extractor/visitor.rb +45 -65
  184. data/lib/simplecov/static_coverage_extractor.rb +43 -75
  185. data/lib/simplecov/test_tracker/accessors.rb +34 -0
  186. data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
  187. data/lib/simplecov/test_tracker/delta.rb +69 -0
  188. data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
  189. data/lib/simplecov/test_tracker.rb +162 -0
  190. data/lib/simplecov/unloaded_file_injector.rb +18 -31
  191. data/lib/simplecov/useless_results_remover.rb +7 -6
  192. data/lib/simplecov/version.rb +1 -1
  193. data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
  194. data/lib/simplecov/view_coverage.rb +55 -0
  195. data/lib/simplecov-html.rb +1 -0
  196. data/lib/simplecov.rb +57 -113
  197. data/lib/simplecov_json_formatter.rb +1 -0
  198. data/man/simplecov.1 +354 -0
  199. data/sig/simplecov.rbs +528 -595
  200. metadata +79 -6
@@ -1,38 +1,44 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # Group configuration is kept separate from inclusion and exclusion filters,
5
- # while sharing their matcher parser through the main Configuration module.
6
4
  module Configuration
7
- # Returns the configured groups. Add groups using SimpleCov.group.
8
5
  def groups
9
6
  @groups ||= {}
10
7
  end
11
8
 
9
+ # The group set `SimpleCov.grouped` bins against when its caller names none.
10
+ # It reads through a method of its own because the keyword it defaults
11
+ # shadows `groups`, so the default has to name a receiver, and every
12
+ # spelling of that receiver is the same call.
13
+ #
14
+ # mutant:disable — for that reason: `SimpleCov.groups`, `self.groups` and
15
+ # `groups()` cannot be told apart.
16
+ def default_groups
17
+ groups
18
+ end
19
+
12
20
  def groups=(new_groups)
13
21
  new_groups = new_groups.to_h { |name, filter| [GroupNames.normalize(name), filter] }
14
22
  GroupNames.validate!(new_groups.keys)
15
23
  @groups = new_groups
16
24
  end
17
25
 
18
- # Define a display group for files. Same matcher grammar as `skip`,
19
- # but instead of dropping the matching files it bins them under
20
- # `group_name` for the formatter. Files matched by no group fall
21
- # into the implicit "Ungrouped" bucket.
26
+ # Same matcher grammar as `skip`, but instead of dropping the matching files
27
+ # it bins them under `group_name` for the formatter. Files matched by no
28
+ # group fall into the implicit "Ungrouped" bucket.
22
29
  def group(group_name, filter_argument = nil, &)
23
30
  group_name = GroupNames.normalize(group_name)
24
31
  GroupNames.validate!([group_name])
25
32
  groups[group_name] = parse_filter(filter_argument, &)
26
33
  end
27
34
 
28
- # DEPRECATED: alias for `group`. Same arguments, same behavior.
29
35
  def add_group(group_name, filter_argument = nil, &block)
30
36
  example = if block
31
- "`SimpleCov.group #{group_name.inspect} { ... }`"
32
- else
33
- "`SimpleCov.group #{group_name.inspect}, #{filter_argument.inspect}`"
34
- end
35
- SimpleCov::Deprecation.warn(
37
+ "`SimpleCov.group #{group_name.inspect} { ... }`"
38
+ else
39
+ "`SimpleCov.group #{group_name.inspect}, #{filter_argument.inspect}`"
40
+ end
41
+ Deprecation.warn(
36
42
  "`SimpleCov.add_group` is deprecated. " \
37
43
  "Replace with `SimpleCov.group` (same arguments, same behavior). Example: #{example}."
38
44
  )
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ DROP_BASELINES = %i[last_run median branch].freeze
6
+
7
+ # How many runs coverage/.history.json keeps, newest kept. 0 disables
8
+ # recording entirely, and anything but a count of runs is refused.
9
+ def history_limit(limit = nil)
10
+ return @history_limit ||= 100 if limit.nil?
11
+
12
+ self.history_limit = limit
13
+ _ = @history_limit
14
+ end
15
+
16
+ def history_limit=(limit)
17
+ unless limit.instance_of?(Integer) && limit >= 0
18
+ raise ConfigurationError, "history_limit takes a non-negative integer, got #{limit.inspect}"
19
+ end
20
+
21
+ @history_limit = limit
22
+ end
23
+
24
+ # What `maximum_coverage_drop` measures the drop against: `:last_run` (the
25
+ # default) the previous run whatever it was, `:median` the median of the
26
+ # recorded history so one run that dipped for an unrelated reason cannot
27
+ # quietly become the baseline, and `:branch` the newest recorded run on the
28
+ # current git branch so a feature branch is compared with itself.
29
+ #
30
+ # The latter two read coverage/.history.json and find nothing to compare
31
+ # against while it is empty, which counts as "no previous run" the way a
32
+ # missing .last_run.json does.
33
+ def drop_baseline(mode = nil)
34
+ return @drop_baseline ||= :last_run if mode.nil?
35
+
36
+ unless DROP_BASELINES.include?(mode)
37
+ raise ConfigurationError,
38
+ "drop_baseline takes one of #{DROP_BASELINES}, got #{mode.inspect}"
39
+ end
40
+
41
+ @drop_baseline = mode
42
+ end
43
+ end
44
+ end
@@ -1,21 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # Coverage-entry filters scoped to branch / method criteria. See
5
- # `ignore_branches` and `ignore_methods`, and #1033 /
6
- # #1046 for the synthetic-entry detection rationale.
7
4
  module Configuration
8
- # Branch types accepted by `ignore_branches`.
9
5
  IGNORABLE_BRANCH_TYPES = %i[implicit_else eval_generated].freeze
10
- # Method types accepted by `ignore_methods`.
11
6
  IGNORABLE_METHOD_TYPES = %i[eval_generated].freeze
12
7
 
13
- # Variadic; multiple calls union. Setting is recorded regardless
14
- # of whether branch coverage is enabled at call time. See #1033, #1046.
8
+ # Deprecated in favour of `coverage(:branch) { ignore :implicit_else }`,
9
+ # which fixes the criterion by context the way the threshold verbs do. One
10
+ # difference rides out the deprecation period: the coverage block enables the
11
+ # criterion it names, while this legacy setter records without enabling.
15
12
  def ignore_branches(*types)
16
- types.each { |type| raise_if_branch_type_unsupported(type) }
17
- ignored_branches.concat(types).uniq!
18
- ignored_branches
13
+ Deprecation.warn("`SimpleCov.ignore_branches` is deprecated. " \
14
+ "Replace with `coverage(:branch) { ignore #{types.map(&:inspect).join(", ")} }`.")
15
+ store_ignored_branches(types)
19
16
  end
20
17
 
21
18
  def ignored_branches
@@ -26,12 +23,10 @@ module SimpleCov
26
23
  ignored_branches.include?(type)
27
24
  end
28
25
 
29
- # See `ignore_branches`. The only supported method-type token today
30
- # is `:eval_generated`; see #1046 for the rationale.
31
26
  def ignore_methods(*types)
32
- types.each { |type| raise_if_method_type_unsupported(type) }
33
- ignored_methods.concat(types).uniq!
34
- ignored_methods
27
+ Deprecation.warn("`SimpleCov.ignore_methods` is deprecated. " \
28
+ "Replace with `coverage(:method) { ignore #{types.map(&:inspect).join(", ")} }`.")
29
+ store_ignored_methods(types)
35
30
  end
36
31
 
37
32
  def ignored_methods
@@ -42,22 +37,36 @@ module SimpleCov
42
37
  ignored_methods.include?(type)
43
38
  end
44
39
 
45
- private
40
+ private
41
+
42
+ # Variadic semantics: multiple calls union, duplicates are no-ops, unknown
43
+ # tokens raise.
44
+ def store_ignored_branches(types)
45
+ types.each { |type| raise_if_branch_type_unsupported(type) }
46
+ ignored_branches.concat(types).uniq!
47
+ ignored_branches
48
+ end
49
+
50
+ def store_ignored_methods(types)
51
+ types.each { |type| raise_if_method_type_unsupported(type) }
52
+ ignored_methods.concat(types).uniq!
53
+ ignored_methods
54
+ end
46
55
 
47
56
  def raise_if_branch_type_unsupported(type)
48
57
  return if IGNORABLE_BRANCH_TYPES.member?(type)
49
58
 
50
- raise SimpleCov::ConfigurationError,
51
- "Unsupported branch type #{type.inspect} for `ignore_branches`. " \
52
- "Supported values are #{IGNORABLE_BRANCH_TYPES.inspect}"
59
+ raise ConfigurationError,
60
+ "Unsupported branch type #{type.inspect} for `ignore_branches`. " \
61
+ "Supported values are #{IGNORABLE_BRANCH_TYPES.inspect}"
53
62
  end
54
63
 
55
64
  def raise_if_method_type_unsupported(type)
56
65
  return if IGNORABLE_METHOD_TYPES.member?(type)
57
66
 
58
- raise SimpleCov::ConfigurationError,
59
- "Unsupported method type #{type.inspect} for `ignore_methods`. " \
60
- "Supported values are #{IGNORABLE_METHOD_TYPES.inspect}"
67
+ raise ConfigurationError,
68
+ "Unsupported method type #{type.inspect} for `ignore_methods`. " \
69
+ "Supported values are #{IGNORABLE_METHOD_TYPES.inspect}"
61
70
  end
62
71
  end
63
72
  end
@@ -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