simplecov 0.22.0 → 1.0.0.rc1

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +81 -1
  3. data/LICENSE +1 -1
  4. data/README.md +1009 -511
  5. data/doc/alternate-formatters.md +0 -5
  6. data/doc/commercial-services.md +5 -5
  7. data/exe/simplecov +11 -0
  8. data/lib/minitest/simplecov_plugin.rb +13 -5
  9. data/lib/simplecov/autostart.rb +11 -0
  10. data/lib/simplecov/cli/clean.rb +47 -0
  11. data/lib/simplecov/cli/coverage.rb +91 -0
  12. data/lib/simplecov/cli/diff.rb +151 -0
  13. data/lib/simplecov/cli/dotfile.rb +100 -0
  14. data/lib/simplecov/cli/merge.rb +116 -0
  15. data/lib/simplecov/cli/open.rb +50 -0
  16. data/lib/simplecov/cli/report.rb +84 -0
  17. data/lib/simplecov/cli/run.rb +36 -0
  18. data/lib/simplecov/cli/serve.rb +139 -0
  19. data/lib/simplecov/cli/uncovered.rb +107 -0
  20. data/lib/simplecov/cli.rb +150 -0
  21. data/lib/simplecov/color.rb +74 -0
  22. data/lib/simplecov/combine/branches_combiner.rb +3 -2
  23. data/lib/simplecov/combine/files_combiner.rb +7 -1
  24. data/lib/simplecov/combine/lines_combiner.rb +19 -17
  25. data/lib/simplecov/combine/methods_combiner.rb +26 -0
  26. data/lib/simplecov/combine/results_combiner.rb +5 -4
  27. data/lib/simplecov/command_guesser.rb +46 -32
  28. data/lib/simplecov/configuration/coverage.rb +171 -0
  29. data/lib/simplecov/configuration/coverage_criteria.rb +156 -0
  30. data/lib/simplecov/configuration/filters.rb +195 -0
  31. data/lib/simplecov/configuration/formatting.rb +119 -0
  32. data/lib/simplecov/configuration/ignored_entries.rb +63 -0
  33. data/lib/simplecov/configuration/merging.rb +74 -0
  34. data/lib/simplecov/configuration/thresholds.rb +174 -0
  35. data/lib/simplecov/configuration.rb +79 -405
  36. data/lib/simplecov/coverage_statistics.rb +12 -9
  37. data/lib/simplecov/coverage_violations.rb +148 -0
  38. data/lib/simplecov/defaults.rb +27 -20
  39. data/lib/simplecov/directive.rb +162 -0
  40. data/lib/simplecov/exit_codes/exit_code_handling.rb +8 -2
  41. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +19 -57
  42. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
  43. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +17 -27
  44. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
  45. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +38 -21
  46. data/lib/simplecov/exit_codes.rb +3 -0
  47. data/lib/simplecov/exit_handling.rb +158 -0
  48. data/lib/simplecov/file_list.rb +61 -17
  49. data/lib/simplecov/filter.rb +69 -24
  50. data/lib/simplecov/formatter/base.rb +101 -0
  51. data/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
  52. data/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
  53. data/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
  54. data/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
  55. data/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
  56. data/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
  57. data/lib/simplecov/formatter/html_formatter.rb +79 -0
  58. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +84 -0
  59. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +127 -0
  60. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
  61. data/lib/simplecov/formatter/json_formatter.rb +77 -0
  62. data/lib/simplecov/formatter/multi_formatter.rb +4 -5
  63. data/lib/simplecov/formatter/simple_formatter.rb +9 -11
  64. data/lib/simplecov/formatter.rb +4 -0
  65. data/lib/simplecov/last_run.rb +10 -3
  66. data/lib/simplecov/lines_classifier.rb +26 -13
  67. data/lib/simplecov/load_global_config.rb +9 -4
  68. data/lib/simplecov/parallel_adapters/base.rb +51 -0
  69. data/lib/simplecov/parallel_adapters/generic.rb +42 -0
  70. data/lib/simplecov/parallel_adapters/parallel_tests.rb +77 -0
  71. data/lib/simplecov/parallel_adapters.rb +83 -0
  72. data/lib/simplecov/parallel_coordination.rb +95 -0
  73. data/lib/simplecov/process.rb +20 -14
  74. data/lib/simplecov/profiles/bundler_filter.rb +1 -1
  75. data/lib/simplecov/profiles/hidden_filter.rb +1 -1
  76. data/lib/simplecov/profiles/rails.rb +24 -10
  77. data/lib/simplecov/profiles/root_filter.rb +6 -5
  78. data/lib/simplecov/profiles/strict.rb +32 -0
  79. data/lib/simplecov/profiles/test_frameworks.rb +1 -4
  80. data/lib/simplecov/profiles.rb +32 -3
  81. data/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
  82. data/lib/simplecov/result/source_file_builder.rb +51 -0
  83. data/lib/simplecov/result.rb +97 -19
  84. data/lib/simplecov/result_adapter.rb +68 -6
  85. data/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
  86. data/lib/simplecov/result_merger/resultset_file.rb +38 -0
  87. data/lib/simplecov/result_merger/resultset_store.rb +50 -0
  88. data/lib/simplecov/result_merger.rb +46 -90
  89. data/lib/simplecov/result_processing.rb +162 -0
  90. data/lib/simplecov/simulate_coverage.rb +54 -8
  91. data/lib/simplecov/source_file/branch.rb +1 -3
  92. data/lib/simplecov/source_file/branch_builder.rb +114 -0
  93. data/lib/simplecov/source_file/builder_context.rb +28 -0
  94. data/lib/simplecov/source_file/line.rb +7 -2
  95. data/lib/simplecov/source_file/line_builder.rb +43 -0
  96. data/lib/simplecov/source_file/method.rb +52 -0
  97. data/lib/simplecov/source_file/method_builder.rb +58 -0
  98. data/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
  99. data/lib/simplecov/source_file/skip_chunks.rb +77 -0
  100. data/lib/simplecov/source_file/source_loader.rb +63 -0
  101. data/lib/simplecov/source_file/statistics.rb +57 -0
  102. data/lib/simplecov/source_file.rb +66 -232
  103. data/lib/simplecov/static_coverage_extractor/visitor.rb +193 -0
  104. data/lib/simplecov/static_coverage_extractor.rb +111 -0
  105. data/lib/simplecov/useless_results_remover.rb +16 -7
  106. data/lib/simplecov/version.rb +1 -1
  107. data/lib/simplecov-html.rb +4 -0
  108. data/lib/simplecov.rb +131 -377
  109. data/lib/simplecov_json_formatter.rb +4 -0
  110. data/schemas/coverage-v1.0.schema.json +300 -0
  111. data/schemas/coverage.schema.json +300 -0
  112. metadata +88 -56
  113. data/lib/simplecov/default_formatter.rb +0 -20
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../formatter/multi_formatter"
4
+
5
+ module SimpleCov
6
+ # Formatter selection (`formatter` / `formatters`), reporting toggles
7
+ # (`print_errors`), and the deprecated `# :nocov:` token hook.
8
+ module Configuration
9
+ attr_writer :formatter, :print_error_status
10
+
11
+ #
12
+ # Gets or sets the configured formatter. Pass `false` (or `nil`) to
13
+ # opt out of formatting entirely — worker processes in big parallel
14
+ # CI setups (see #964) only need their `.resultset.json` on disk so
15
+ # a final `SimpleCov.collate` job can produce the report; running
16
+ # them without a formatter saves the per-job HTML/multi-formatter
17
+ # overhead.
18
+ #
19
+ def formatter(formatter = :__no_arg__)
20
+ return @formatter if formatter == :__no_arg__
21
+
22
+ @formatter = formatter || nil # normalize `false` to `nil`
23
+ end
24
+
25
+ # Sets the configured formatters. Pass `[]` to opt out of
26
+ # formatting entirely; see `formatter` for the rationale.
27
+ def formatters(formatters = :__no_arg__)
28
+ return Array(formatter) if formatters == :__no_arg__
29
+
30
+ self.formatters = formatters
31
+ formatters
32
+ end
33
+
34
+ # Sets the configured formatters. Equivalent to `formatters [...]`.
35
+ def formatters=(formatters)
36
+ @formatter = formatters.empty? ? nil : SimpleCov::Formatter::MultiFormatter.new(formatters)
37
+ end
38
+
39
+ #
40
+ # Get or set whether SimpleCov colorizes its stderr diagnostics. Accepts
41
+ # `true` (always on), `false` (always off), or `:auto` (default: defer
42
+ # to `SimpleCov::Color`, which checks `$stderr.tty?` with `NO_COLOR`
43
+ # and `FORCE_COLOR` overrides). An explicit `true`/`false` wins over
44
+ # both auto-detection and the env vars, which is the right escape
45
+ # hatch when stderr is being piped through a wrapper that still
46
+ # renders ANSI in its own terminal (parallel_tests with
47
+ # `--combine-stderr`, log multiplexers, some CI runners). See #1157.
48
+ #
49
+ def color(value = :__no_arg__)
50
+ return defined?(@color) ? @color : :auto if value == :__no_arg__
51
+
52
+ @color = value
53
+ end
54
+
55
+ #
56
+ # Get or set whether SimpleCov prints its own diagnostic warnings to
57
+ # stderr. Covers per-check threshold violations, the trailing
58
+ # "SimpleCov failed with exit ..." summary, and the deferred-report /
59
+ # previous-error notices. Defaults to true. Set to false to silence
60
+ # SimpleCov entirely when parsing tooling output (see issue #1155).
61
+ #
62
+ def print_errors(value = :__no_arg__)
63
+ return defined?(@print_error_status) ? @print_error_status : true if value == :__no_arg__
64
+
65
+ @print_error_status = value
66
+ end
67
+
68
+ #
69
+ # Get or set whether `coverage.json` includes the full source-text
70
+ # array for every file. Defaults to true. Set to false when a
71
+ # downstream tool reads the project's source files directly and
72
+ # only needs the coverage metrics, so `coverage.json` doesn't carry
73
+ # a copy of the source tree (which dominates the payload on larger
74
+ # projects).
75
+ #
76
+ # The HTML viewer's `coverage_data.js` always includes source —
77
+ # the client-side renderer needs it. Only `coverage.json` honors
78
+ # this setting.
79
+ #
80
+ # SimpleCov.start do
81
+ # source_in_json false
82
+ # end
83
+ #
84
+ def source_in_json(value = :__no_arg__)
85
+ return defined?(@source_in_json) ? @source_in_json : true if value == :__no_arg__
86
+
87
+ @source_in_json = value
88
+ end
89
+
90
+ # DEPRECATED: alias for `print_errors`. Same value, same behavior.
91
+ def print_error_status
92
+ warn "#{Kernel.caller.first}: [DEPRECATION] `SimpleCov.print_error_status` is deprecated. " \
93
+ "Replace with `SimpleCov.print_errors` (same value)."
94
+ defined?(@print_error_status) ? @print_error_status : true
95
+ end
96
+
97
+ #
98
+ # DEPRECATED: configure `# :nocov:` token override. Prefer
99
+ # `# simplecov:disable` / `# simplecov:enable` block comments (see
100
+ # SimpleCov::Directive). The `# :nocov:` toggle and this hook will
101
+ # be removed in a future release.
102
+ #
103
+ def nocov_token(nocov_token = nil)
104
+ warn "#{Kernel.caller.first}: [DEPRECATION] `SimpleCov.nocov_token` and `SimpleCov.skip_token` are deprecated. " \
105
+ "Replace with `# simplecov:disable` / `# simplecov:enable` block comments."
106
+ current_nocov_token(nocov_token)
107
+ end
108
+ alias skip_token nocov_token
109
+
110
+ # Internal accessor used by SimpleCov to recognise `# :nocov:`
111
+ # markers without emitting the public-API deprecation warning. Will
112
+ # be removed alongside the deprecated `nocov_token` setter.
113
+ def current_nocov_token(value = nil)
114
+ return @nocov_token if defined?(@nocov_token) && value.nil?
115
+
116
+ @nocov_token = value || "nocov"
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
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
+ module Configuration
8
+ # Branch types accepted by `ignore_branches`.
9
+ IGNORABLE_BRANCH_TYPES = %i[implicit_else eval_generated].freeze
10
+ # Method types accepted by `ignore_methods`.
11
+ IGNORABLE_METHOD_TYPES = %i[eval_generated].freeze
12
+
13
+ # Variadic; multiple calls union. Setting is recorded regardless
14
+ # of whether branch coverage is enabled at call time. See #1033, #1046.
15
+ def ignore_branches(*types)
16
+ types.each { |type| raise_if_branch_type_unsupported(type) }
17
+ ignored_branches.concat(types).uniq!
18
+ ignored_branches
19
+ end
20
+
21
+ def ignored_branches
22
+ @ignored_branches ||= []
23
+ end
24
+
25
+ def ignored_branch?(type)
26
+ ignored_branches.include?(type)
27
+ end
28
+
29
+ # See `ignore_branches`. The only supported method-type token today
30
+ # is `:eval_generated`; see #1046 for the rationale.
31
+ def ignore_methods(*types)
32
+ types.each { |type| raise_if_method_type_unsupported(type) }
33
+ ignored_methods.concat(types).uniq!
34
+ ignored_methods
35
+ end
36
+
37
+ def ignored_methods
38
+ @ignored_methods ||= []
39
+ end
40
+
41
+ def ignored_method?(type)
42
+ ignored_methods.include?(type)
43
+ end
44
+
45
+ private
46
+
47
+ def raise_if_branch_type_unsupported(type)
48
+ return if IGNORABLE_BRANCH_TYPES.member?(type)
49
+
50
+ raise SimpleCov::ConfigurationError,
51
+ "Unsupported branch type #{type.inspect} for `ignore_branches`. " \
52
+ "Supported values are #{IGNORABLE_BRANCH_TYPES.inspect}"
53
+ end
54
+
55
+ def raise_if_method_type_unsupported(type)
56
+ return if IGNORABLE_METHOD_TYPES.member?(type)
57
+
58
+ raise SimpleCov::ConfigurationError,
59
+ "Unsupported method type #{type.inspect} for `ignore_methods`. " \
60
+ "Supported values are #{IGNORABLE_METHOD_TYPES.inspect}"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,74 @@
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
+ warn "#{Kernel.caller.first}: [DEPRECATION] `SimpleCov.enable_for_subprocesses` is deprecated. " \
40
+ "Replace with `SimpleCov.merge_subprocesses` (same value, same behavior)."
41
+ return @enable_for_subprocesses if defined?(@enable_for_subprocesses) && value.nil?
42
+
43
+ @enable_for_subprocesses = value || false
44
+ end
45
+
46
+ #
47
+ # Get or set whether to merge results from multiple test suites
48
+ # (test:units, test:functionals, cucumber, ...) into a single
49
+ # coverage report. Defaults to true.
50
+ #
51
+ def merging(use = nil)
52
+ @use_merging = use unless use.nil?
53
+ @use_merging = true unless defined?(@use_merging) && @use_merging == false
54
+ @use_merging
55
+ end
56
+
57
+ # DEPRECATED: alias for `merging`. Same value, same behavior.
58
+ def use_merging(use = nil)
59
+ warn "#{Kernel.caller.first}: [DEPRECATION] `SimpleCov.use_merging` is deprecated. " \
60
+ "Replace with `SimpleCov.merging` (same value, same behavior)."
61
+ @use_merging = use unless use.nil?
62
+ @use_merging = true unless defined?(@use_merging) && @use_merging == false
63
+ end
64
+
65
+ #
66
+ # Defines the maximum age (in seconds) of a resultset to still be
67
+ # included in merged results. Default is 600 seconds (10 minutes).
68
+ #
69
+ def merge_timeout(seconds = nil)
70
+ @merge_timeout = seconds if seconds.is_a?(Integer)
71
+ @merge_timeout ||= 600
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,174 @@
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
+ coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
18
+ raise_on_invalid_coverage(coverage, "minimum_coverage")
19
+ @minimum_coverage = coverage
20
+ end
21
+
22
+ def raise_on_invalid_coverage(coverage, coverage_setting)
23
+ coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) }
24
+ coverage.each_value do |percent|
25
+ minimum_possible_coverage_exceeded(coverage_setting) if percent && percent > 100
26
+ end
27
+ end
28
+
29
+ #
30
+ # Defines the maximum overall coverage allowed for the testsuite to
31
+ # pass. Useful paired with `minimum_coverage` (or via
32
+ # `expected_coverage`) to pin coverage to an exact value, so an
33
+ # unexpected jump up fails the build. See #187.
34
+ #
35
+ def maximum_coverage(coverage = nil)
36
+ return @maximum_coverage ||= {} unless coverage
37
+
38
+ coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
39
+ raise_on_invalid_coverage(coverage, "maximum_coverage")
40
+ @maximum_coverage = coverage
41
+ end
42
+
43
+ #
44
+ # Pins the suite to an exact coverage figure by setting both
45
+ # `minimum_coverage` and `maximum_coverage`. See #187.
46
+ #
47
+ def expected_coverage(coverage = nil)
48
+ return minimum_coverage if coverage.nil?
49
+
50
+ minimum_coverage(coverage)
51
+ maximum_coverage(coverage)
52
+ end
53
+
54
+ #
55
+ # Defines the maximum coverage drop at once allowed for the
56
+ # testsuite to pass. Default is 100% (disabled).
57
+ #
58
+ def maximum_coverage_drop(coverage_drop = nil)
59
+ return @maximum_coverage_drop ||= {} unless coverage_drop
60
+
61
+ coverage_drop = {primary_coverage => coverage_drop} if coverage_drop.is_a?(Numeric)
62
+ raise_on_invalid_coverage(coverage_drop, "maximum_coverage_drop")
63
+ @maximum_coverage_drop = coverage_drop
64
+ end
65
+
66
+ #
67
+ # Defines the minimum coverage per file required for the testsuite
68
+ # to pass. Accepts a Numeric (global threshold on the primary
69
+ # criterion), a Symbol-keyed Hash (per-criterion globals), or a
70
+ # Hash mixing Symbol keys with String / Regexp keys to declare
71
+ # per-path overrides. See README and #575.
72
+ #
73
+ def minimum_coverage_by_file(coverage = nil)
74
+ return @minimum_coverage_by_file ||= {} unless coverage
75
+
76
+ coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
77
+ defaults, overrides = partition_per_file_thresholds(coverage)
78
+
79
+ warn "#{Kernel.caller.first}: [DEPRECATION] `SimpleCov.minimum_coverage_by_file` is deprecated. " \
80
+ "Replace it with:\n#{per_file_coverage_replacement(defaults, overrides)}"
81
+
82
+ raise_on_invalid_coverage(defaults, "minimum_coverage_by_file")
83
+ overrides.each_value { |criteria| raise_on_invalid_coverage(criteria, "minimum_coverage_by_file") }
84
+
85
+ @minimum_coverage_by_file = defaults
86
+ @minimum_coverage_by_file_overrides = overrides
87
+ end
88
+
89
+ # Returns the per-path overrides set via `minimum_coverage_by_file`.
90
+ def minimum_coverage_by_file_overrides
91
+ @minimum_coverage_by_file_overrides ||= {}
92
+ end
93
+
94
+ #
95
+ # Defines the minimum coverage per group required for the testsuite
96
+ # to pass. Default is 0% (disabled).
97
+ #
98
+ def minimum_coverage_by_group(coverage = nil)
99
+ return @minimum_coverage_by_group ||= {} unless coverage
100
+
101
+ warn "#{Kernel.caller.first}: [DEPRECATION] `SimpleCov.minimum_coverage_by_group` is deprecated. " \
102
+ "Replace it with:\n#{per_group_coverage_replacement(coverage)}"
103
+ @minimum_coverage_by_group = coverage.dup.transform_values do |group_coverage|
104
+ group_coverage = {primary_coverage => group_coverage} if group_coverage.is_a?(Numeric)
105
+ raise_on_invalid_coverage(group_coverage, "minimum_coverage_by_group")
106
+ group_coverage
107
+ end
108
+ end
109
+
110
+ #
111
+ # Refuses any coverage drop. Coverage is only allowed to increase.
112
+ #
113
+ def refuse_coverage_drop(*criteria)
114
+ criteria = coverage_criteria if criteria.empty?
115
+ maximum_coverage_drop(criteria.to_h { |c| [c, 0] })
116
+ end
117
+
118
+ private
119
+
120
+ # Split a `minimum_coverage_by_file` argument into Symbol-keyed
121
+ # criterion defaults and String/Regexp-keyed per-path overrides;
122
+ # normalize Numeric override values to `{primary_coverage => N}`
123
+ # so downstream code only has one shape to handle.
124
+ def partition_per_file_thresholds(coverage)
125
+ coverage.each_key { |key| validate_per_file_key(key) }
126
+ defaults, raw = coverage.partition { |key, _| key.is_a?(Symbol) }.map(&:to_h)
127
+ overrides = raw.transform_values { |value| value.is_a?(Numeric) ? {primary_coverage => value} : value }
128
+ [defaults, overrides]
129
+ end
130
+
131
+ def validate_per_file_key(key)
132
+ return if key.is_a?(Symbol) || key.is_a?(String) || key.is_a?(Regexp)
133
+
134
+ raise SimpleCov::ConfigurationError,
135
+ "minimum_coverage_by_file keys must be Symbol (criterion), String, or Regexp; got #{key.inspect}"
136
+ end
137
+
138
+ def minimum_possible_coverage_exceeded(coverage_option)
139
+ warn "The coverage you set for #{coverage_option} is greater than 100%"
140
+ end
141
+
142
+ # Render the `coverage` configuration equivalent to a (deprecated)
143
+ # `minimum_coverage_by_file` argument so the deprecation warning can be
144
+ # copy-pasted verbatim into the user's config.
145
+ def per_file_coverage_replacement(defaults, overrides)
146
+ by_criterion = Hash.new { |hash, criterion| hash[criterion] = [] }
147
+ defaults.each { |criterion, percent| by_criterion[criterion] << "minimum_per_file #{percent}" }
148
+ overrides.each do |target, criteria|
149
+ criteria.each do |criterion, percent|
150
+ by_criterion[criterion] << "minimum_per_file #{percent}, only: #{target.inspect}"
151
+ end
152
+ end
153
+ render_coverage_blocks(by_criterion)
154
+ end
155
+
156
+ # Same, for a (deprecated) `minimum_coverage_by_group` argument.
157
+ def per_group_coverage_replacement(coverage)
158
+ by_criterion = Hash.new { |hash, criterion| hash[criterion] = [] }
159
+ coverage.each do |group_name, group_coverage|
160
+ group_coverage = {primary_coverage => group_coverage} if group_coverage.is_a?(Numeric)
161
+ group_coverage.each do |criterion, percent|
162
+ by_criterion[criterion] << "minimum_per_group #{percent}, only: #{group_name.inspect}"
163
+ end
164
+ end
165
+ render_coverage_blocks(by_criterion)
166
+ end
167
+
168
+ def render_coverage_blocks(by_criterion)
169
+ by_criterion.map do |criterion, statements|
170
+ " coverage(#{criterion.inspect}) { #{statements.join('; ')} }"
171
+ end.join("\n")
172
+ end
173
+ end
174
+ end