simplecov 0.22.0 → 1.1.1

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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +71 -916
  4. data/exe/simplecov +11 -0
  5. data/lib/minitest/simplecov_plugin.rb +13 -5
  6. data/lib/simplecov/atomic_file.rb +70 -0
  7. data/lib/simplecov/autostart.rb +11 -0
  8. data/lib/simplecov/cli/clean.rb +87 -0
  9. data/lib/simplecov/cli/command_helpers.rb +55 -0
  10. data/lib/simplecov/cli/coverage.rb +86 -0
  11. data/lib/simplecov/cli/coverage_file.rb +65 -0
  12. data/lib/simplecov/cli/diff.rb +147 -0
  13. data/lib/simplecov/cli/dotfile.rb +111 -0
  14. data/lib/simplecov/cli/merge.rb +120 -0
  15. data/lib/simplecov/cli/open.rb +48 -0
  16. data/lib/simplecov/cli/report.rb +97 -0
  17. data/lib/simplecov/cli/run.rb +36 -0
  18. data/lib/simplecov/cli/serve/report_preparer.rb +29 -0
  19. data/lib/simplecov/cli/serve/static_file_handler.rb +122 -0
  20. data/lib/simplecov/cli/serve.rb +85 -0
  21. data/lib/simplecov/cli/uncovered.rb +103 -0
  22. data/lib/simplecov/cli.rb +163 -0
  23. data/lib/simplecov/color.rb +83 -0
  24. data/lib/simplecov/combine/branches_combiner.rb +54 -7
  25. data/lib/simplecov/combine/coverage_accumulator.rb +268 -0
  26. data/lib/simplecov/combine/identity_interner.rb +30 -0
  27. data/lib/simplecov/combine/interned_counts.rb +30 -0
  28. data/lib/simplecov/combine/lines_combiner.rb +53 -23
  29. data/lib/simplecov/combine/methods_combiner.rb +74 -0
  30. data/lib/simplecov/combine/results_combiner.rb +13 -36
  31. data/lib/simplecov/combine.rb +5 -23
  32. data/lib/simplecov/command_guesser.rb +104 -33
  33. data/lib/simplecov/configuration/coverage.rb +172 -0
  34. data/lib/simplecov/configuration/coverage_criteria.rb +154 -0
  35. data/lib/simplecov/configuration/eval_coverage.rb +41 -0
  36. data/lib/simplecov/configuration/filters.rb +164 -0
  37. data/lib/simplecov/configuration/formatting.rb +138 -0
  38. data/lib/simplecov/configuration/groups.rb +42 -0
  39. data/lib/simplecov/configuration/ignored_entries.rb +63 -0
  40. data/lib/simplecov/configuration/merging.rb +165 -0
  41. data/lib/simplecov/configuration/thresholds.rb +179 -0
  42. data/lib/simplecov/configuration.rb +83 -420
  43. data/lib/simplecov/coverage_json.rb +24 -0
  44. data/lib/simplecov/coverage_statistics.rb +16 -10
  45. data/lib/simplecov/coverage_violations.rb +159 -0
  46. data/lib/simplecov/defaults.rb +34 -23
  47. data/lib/simplecov/deprecation.rb +45 -0
  48. data/lib/simplecov/directive.rb +167 -0
  49. data/lib/simplecov/exit_codes/check.rb +33 -0
  50. data/lib/simplecov/exit_codes/exit_code_handling.rb +8 -2
  51. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +18 -65
  52. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +32 -0
  53. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +17 -36
  54. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +30 -0
  55. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +37 -33
  56. data/lib/simplecov/exit_codes.rb +20 -0
  57. data/lib/simplecov/exit_handling.rb +128 -0
  58. data/lib/simplecov/file_list.rb +70 -24
  59. data/lib/simplecov/filter.rb +105 -26
  60. data/lib/simplecov/formatter/base.rb +118 -0
  61. data/lib/simplecov/formatter/coverage_json_writer.rb +97 -0
  62. data/lib/simplecov/formatter/html_formatter/public/index.html +87 -0
  63. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +96 -0
  64. data/lib/simplecov/formatter/html_formatter.rb +105 -0
  65. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +88 -0
  66. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +118 -0
  67. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +93 -0
  68. data/lib/simplecov/formatter/json_formatter.rb +46 -0
  69. data/lib/simplecov/formatter/multi_formatter.rb +12 -8
  70. data/lib/simplecov/formatter/simple_formatter.rb +14 -11
  71. data/lib/simplecov/formatter.rb +16 -0
  72. data/lib/simplecov/group_names.rb +32 -0
  73. data/lib/simplecov/last_run.rb +18 -4
  74. data/lib/simplecov/lines_classifier.rb +48 -15
  75. data/lib/simplecov/load_global_config.rb +12 -4
  76. data/lib/simplecov/parallel_adapters/base.rb +78 -0
  77. data/lib/simplecov/parallel_adapters/generic.rb +45 -0
  78. data/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
  79. data/lib/simplecov/parallel_adapters.rb +85 -0
  80. data/lib/simplecov/parallel_coordination.rb +134 -0
  81. data/lib/simplecov/parallel_result_merger.rb +230 -0
  82. data/lib/simplecov/process.rb +52 -12
  83. data/lib/simplecov/profiles/bundler_filter.rb +1 -1
  84. data/lib/simplecov/profiles/hidden_filter.rb +1 -1
  85. data/lib/simplecov/profiles/rails.rb +24 -10
  86. data/lib/simplecov/profiles/root_filter.rb +6 -5
  87. data/lib/simplecov/profiles/strict.rb +32 -0
  88. data/lib/simplecov/profiles/test_frameworks.rb +1 -4
  89. data/lib/simplecov/profiles.rb +32 -3
  90. data/lib/simplecov/report_deferral.rb +49 -0
  91. data/lib/simplecov/report_stamp.rb +28 -0
  92. data/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
  93. data/lib/simplecov/result/source_file_builder.rb +51 -0
  94. data/lib/simplecov/result.rb +145 -26
  95. data/lib/simplecov/result_adapter.rb +139 -8
  96. data/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
  97. data/lib/simplecov/result_merger/resultset_file.rb +74 -0
  98. data/lib/simplecov/result_merger/resultset_run_identity.rb +67 -0
  99. data/lib/simplecov/result_merger/resultset_store.rb +53 -0
  100. data/lib/simplecov/result_merger/unloaded_files.rb +103 -0
  101. data/lib/simplecov/result_merger.rb +109 -114
  102. data/lib/simplecov/result_processing.rb +218 -0
  103. data/lib/simplecov/run_identity.rb +77 -0
  104. data/lib/simplecov/simulate_coverage.rb +83 -12
  105. data/lib/simplecov/source_file/branch.rb +1 -3
  106. data/lib/simplecov/source_file/branch_builder.rb +114 -0
  107. data/lib/simplecov/source_file/builder_context.rb +28 -0
  108. data/lib/simplecov/source_file/line.rb +9 -4
  109. data/lib/simplecov/source_file/line_builder.rb +43 -0
  110. data/lib/simplecov/source_file/method.rb +58 -0
  111. data/lib/simplecov/source_file/method_builder.rb +60 -0
  112. data/lib/simplecov/source_file/ruby_data_parser.rb +110 -0
  113. data/lib/simplecov/source_file/skip_chunks.rb +72 -0
  114. data/lib/simplecov/source_file/source_loader.rb +79 -0
  115. data/lib/simplecov/source_file/statistics.rb +65 -0
  116. data/lib/simplecov/source_file.rb +66 -232
  117. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +239 -0
  118. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +236 -0
  119. data/lib/simplecov/static_coverage_extractor/method_collector.rb +62 -0
  120. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +55 -0
  121. data/lib/simplecov/static_coverage_extractor/value_position.rb +74 -0
  122. data/lib/simplecov/static_coverage_extractor/visitor.rb +183 -0
  123. data/lib/simplecov/static_coverage_extractor.rb +110 -0
  124. data/lib/simplecov/unloaded_file_injector.rb +75 -0
  125. data/lib/simplecov/useless_results_remover.rb +16 -7
  126. data/lib/simplecov/version.rb +1 -1
  127. data/lib/simplecov-html.rb +4 -0
  128. data/lib/simplecov.rb +185 -380
  129. data/lib/simplecov_json_formatter.rb +4 -0
  130. data/sig/simplecov.rbs +1777 -0
  131. metadata +111 -62
  132. data/CHANGELOG.md +0 -191
  133. data/doc/alternate-formatters.md +0 -71
  134. data/doc/commercial-services.md +0 -25
  135. data/doc/editor-integration.md +0 -18
  136. data/lib/simplecov/combine/files_combiner.rb +0 -24
  137. data/lib/simplecov/default_formatter.rb +0 -20
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ # Result merging and subprocess / parallel-test coordination:
5
+ # `merging`, `merge_subprocesses`, `merge_timeout`, `parallel_tests`.
6
+ module Configuration
7
+ #
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.
12
+ #
13
+ def merge_subprocesses(value = nil)
14
+ return @enable_for_subprocesses if defined?(@enable_for_subprocesses) && value.nil?
15
+
16
+ @enable_for_subprocesses = value || false
17
+ end
18
+
19
+ # @api private — predicate used by `start_tracking` to decide
20
+ # whether to install the fork hook.
21
+ def enabled_for_subprocesses?
22
+ defined?(@enable_for_subprocesses) ? @enable_for_subprocesses : false
23
+ end
24
+
25
+ #
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.
30
+ #
31
+ def parallel_tests(value = :__no_arg__)
32
+ return defined?(@parallel_tests) ? @parallel_tests : nil if value == :__no_arg__
33
+
34
+ @parallel_tests = value
35
+ end
36
+
37
+ # DEPRECATED: alias for `merge_subprocesses`. Same value/behavior.
38
+ 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).")
41
+ merge_subprocesses(value)
42
+ end
43
+
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
+ def merging(use = nil)
50
+ @use_merging = use unless use.nil?
51
+ @use_merging = true unless defined?(@use_merging) && @use_merging == false
52
+ @use_merging
53
+ end
54
+
55
+ #
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`.
59
+ #
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.
64
+ #
65
+ def finalize_merge(value = :__no_arg__)
66
+ unless value == :__no_arg__
67
+ @finalize_merge = value
68
+ @finalize_merge_explicit = true
69
+ end
70
+
71
+ return @finalize_merge if defined?(@finalize_merge_explicit) && @finalize_merge_explicit
72
+
73
+ inferred = inferred_finalize_merge?
74
+ warn_about_inferred_finalize_merge unless inferred
75
+ inferred
76
+ end
77
+
78
+ def finalize_merge?
79
+ finalize_merge
80
+ end
81
+
82
+ # @api private
83
+ def merge_finalization_owner?
84
+ collating_result? || (finalize_merge? && final_result_process?)
85
+ end
86
+
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
+ def use_merging(use = nil)
93
+ SimpleCov::Deprecation.warn("`SimpleCov.use_merging` is deprecated. " \
94
+ "Replace with `SimpleCov.merging` (same value, same behavior).")
95
+ merging(use)
96
+ end
97
+
98
+ #
99
+ # Defines the maximum age (in seconds) of a resultset to still be
100
+ # included in merged results. Default is 600 seconds (10 minutes).
101
+ #
102
+ def merge_timeout(seconds = nil)
103
+ @merge_timeout = seconds if seconds.is_a?(Integer)
104
+ @merge_timeout ||= 600
105
+ end
106
+
107
+ #
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.
114
+ #
115
+ def parallel_wait_timeout(seconds = nil)
116
+ @parallel_wait_timeout = seconds if seconds.is_a?(Integer)
117
+ @parallel_wait_timeout ||= 60
118
+ end
119
+
120
+ private
121
+
122
+ def inferred_finalize_merge?
123
+ return true unless merging
124
+
125
+ adapter = SimpleCov::ParallelAdapters.current
126
+ return true unless adapter
127
+ return true unless adapter.expected_worker_count > 1
128
+ return true unless parallel_worker_environment?
129
+ return true unless explicit_custom_coverage_destination?
130
+
131
+ false
132
+ end
133
+
134
+ def parallel_worker_environment?
135
+ ENV.key?("TEST_ENV_NUMBER") || ENV.key?("PARALLEL_TEST_GROUPS")
136
+ end
137
+
138
+ def explicit_custom_coverage_destination?
139
+ return false unless explicit_coverage_destination?
140
+
141
+ coverage_path != File.expand_path("coverage", root)
142
+ end
143
+
144
+ def explicit_coverage_destination?
145
+ (defined?(@coverage_path_explicit) && @coverage_path_explicit) ||
146
+ (defined?(@coverage_dir_explicit) && @coverage_dir_explicit)
147
+ end
148
+
149
+ def warn_about_inferred_finalize_merge
150
+ return if defined?(@finalize_merge_inference_warned) && @finalize_merge_inference_warned
151
+ return unless print_errors
152
+
153
+ @finalize_merge_inference_warned = true
154
+ warn SimpleCov::Color.colorize(inferred_finalize_merge_warning, :yellow)
155
+ end
156
+
157
+ def inferred_finalize_merge_warning
158
+ "SimpleCov inferred `finalize_merge false` because this parallel worker is merging " \
159
+ "into a custom coverage destination. Set `SimpleCov.finalize_merge false` to keep " \
160
+ "external collation ownership, or `SimpleCov.finalize_merge true` if this worker " \
161
+ "should wait, merge, format, enforce thresholds, and write `.last_run.json`. " \
162
+ "See https://github.com/simplecov-ruby/simplecov#merge-finalization-ownership."
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
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
+ 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
+ def minimum_coverage(coverage = nil)
15
+ return @minimum_coverage ||= {} unless coverage
16
+
17
+ @minimum_coverage = normalized_threshold(coverage, "minimum_coverage")
18
+ end
19
+
20
+ def raise_on_invalid_coverage(coverage, coverage_setting)
21
+ coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) }
22
+ coverage.each_value do |percent|
23
+ minimum_possible_coverage_exceeded(coverage_setting) if percent && percent > 100
24
+ end
25
+ end
26
+
27
+ #
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.
32
+ #
33
+ def maximum_coverage(coverage = nil)
34
+ return @maximum_coverage ||= {} unless coverage
35
+
36
+ @maximum_coverage = normalized_threshold(coverage, "maximum_coverage")
37
+ end
38
+
39
+ #
40
+ # Pins the suite to an exact coverage figure by setting both
41
+ # `minimum_coverage` and `maximum_coverage`. See #187.
42
+ #
43
+ def expected_coverage(coverage = nil)
44
+ return minimum_coverage if coverage.nil?
45
+
46
+ minimum_coverage(coverage)
47
+ maximum_coverage(coverage)
48
+ end
49
+
50
+ #
51
+ # Defines the maximum coverage drop at once allowed for the
52
+ # testsuite to pass. Default is 100% (disabled).
53
+ #
54
+ def maximum_coverage_drop(coverage_drop = nil)
55
+ return @maximum_coverage_drop ||= {} unless coverage_drop
56
+
57
+ @maximum_coverage_drop = normalized_threshold(coverage_drop, "maximum_coverage_drop")
58
+ end
59
+
60
+ #
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.
66
+ #
67
+ def minimum_coverage_by_file(coverage = nil)
68
+ return @minimum_coverage_by_file ||= {} unless coverage
69
+
70
+ coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
71
+ defaults, overrides = partition_per_file_thresholds(coverage)
72
+
73
+ SimpleCov::Deprecation.warn("`SimpleCov.minimum_coverage_by_file` is deprecated. " \
74
+ "Replace it with:\n#{per_file_coverage_replacement(defaults, overrides)}")
75
+
76
+ raise_on_invalid_coverage(defaults, "minimum_coverage_by_file")
77
+ overrides.each_value { |criteria| raise_on_invalid_coverage(criteria, "minimum_coverage_by_file") }
78
+
79
+ @minimum_coverage_by_file = defaults
80
+ @minimum_coverage_by_file_overrides = overrides
81
+ end
82
+
83
+ # Returns the per-path overrides set via `minimum_coverage_by_file`.
84
+ def minimum_coverage_by_file_overrides
85
+ @minimum_coverage_by_file_overrides ||= {}
86
+ end
87
+
88
+ #
89
+ # Defines the minimum coverage per group required for the testsuite
90
+ # to pass. Default is 0% (disabled).
91
+ #
92
+ def minimum_coverage_by_group(coverage = nil)
93
+ return @minimum_coverage_by_group ||= {} unless coverage
94
+
95
+ SimpleCov::Deprecation.warn("`SimpleCov.minimum_coverage_by_group` is deprecated. " \
96
+ "Replace it with:\n#{per_group_coverage_replacement(coverage)}")
97
+ @minimum_coverage_by_group = coverage.to_h do |group_name, group_coverage|
98
+ [GroupNames.normalize(group_name), normalized_threshold(group_coverage, "minimum_coverage_by_group")]
99
+ end
100
+ end
101
+
102
+ #
103
+ # Refuses any coverage drop. Coverage is only allowed to increase.
104
+ #
105
+ def refuse_coverage_drop(*criteria)
106
+ criteria = coverage_criteria if criteria.empty?
107
+ maximum_coverage_drop(criteria.to_h { |c| [c, 0] })
108
+ end
109
+
110
+ private
111
+
112
+ # Shared normalize-and-validate step behind every threshold setter:
113
+ # a bare Numeric targets the primary criterion, and the resulting
114
+ # per-criterion hash is validated before it is stored.
115
+ def normalized_threshold(coverage, setting)
116
+ coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
117
+ raise_on_invalid_coverage(coverage, setting)
118
+ coverage
119
+ end
120
+
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.
125
+ def partition_per_file_thresholds(coverage)
126
+ 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]
132
+ overrides = raw.transform_values { |value| value.is_a?(Numeric) ? {primary_coverage => value} : value }
133
+ [defaults, overrides]
134
+ end
135
+
136
+ def validate_per_file_key(key)
137
+ return if key.is_a?(Symbol) || key.is_a?(String) || key.is_a?(Regexp)
138
+
139
+ raise SimpleCov::ConfigurationError,
140
+ "minimum_coverage_by_file keys must be Symbol (criterion), String, or Regexp; got #{key.inspect}"
141
+ end
142
+
143
+ def minimum_possible_coverage_exceeded(coverage_option)
144
+ warn "The coverage you set for #{coverage_option} is greater than 100%"
145
+ end
146
+
147
+ # Render the `coverage` configuration equivalent to a (deprecated)
148
+ # `minimum_coverage_by_file` argument so the deprecation warning can be
149
+ # copy-pasted verbatim into the user's config.
150
+ def per_file_coverage_replacement(defaults, overrides)
151
+ by_criterion = {} #: Hash[Symbol, Array[String]]
152
+ defaults.each { |criterion, percent| (by_criterion[criterion] ||= []) << "minimum_per_file #{percent}" }
153
+ overrides.each do |target, criteria|
154
+ criteria.each do |criterion, percent|
155
+ (by_criterion[criterion] ||= []) << "minimum_per_file #{percent}, only: #{target.inspect}"
156
+ end
157
+ end
158
+ render_coverage_blocks(by_criterion)
159
+ end
160
+
161
+ # Same, for a (deprecated) `minimum_coverage_by_group` argument.
162
+ def per_group_coverage_replacement(coverage)
163
+ by_criterion = {} #: Hash[Symbol, Array[String]]
164
+ coverage.each do |group_name, thresholds|
165
+ normalized = (thresholds.is_a?(Numeric) ? {primary_coverage => thresholds} : thresholds) #: coverage_thresholds
166
+ normalized.each do |criterion, percent|
167
+ (by_criterion[criterion] ||= []) << "minimum_per_group #{percent}, only: #{group_name.inspect}"
168
+ end
169
+ end
170
+ render_coverage_blocks(by_criterion)
171
+ end
172
+
173
+ def render_coverage_blocks(by_criterion)
174
+ by_criterion.map do |criterion, statements|
175
+ " coverage(#{criterion.inspect}) { #{statements.join('; ')} }"
176
+ end.join("\n")
177
+ end
178
+ end
179
+ end