simplecov 0.22.0 → 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 (210) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +173 -875
  4. data/exe/simplecov +8 -0
  5. data/lib/minitest/simplecov_plugin.rb +18 -7
  6. data/lib/simplecov/atomic_file.rb +67 -0
  7. data/lib/simplecov/autostart.rb +9 -0
  8. data/lib/simplecov/baseline/parser.rb +58 -0
  9. data/lib/simplecov/baseline.rb +170 -0
  10. data/lib/simplecov/cli/affected/changed_files.rb +57 -0
  11. data/lib/simplecov/cli/affected/selection.rb +117 -0
  12. data/lib/simplecov/cli/affected.rb +148 -0
  13. data/lib/simplecov/cli/annotations.rb +166 -0
  14. data/lib/simplecov/cli/badge/svg.rb +70 -0
  15. data/lib/simplecov/cli/badge.rb +62 -0
  16. data/lib/simplecov/cli/clean.rb +86 -0
  17. data/lib/simplecov/cli/command_helpers.rb +91 -0
  18. data/lib/simplecov/cli/completions/scripts.rb +114 -0
  19. data/lib/simplecov/cli/completions.rb +83 -0
  20. data/lib/simplecov/cli/coverage.rb +83 -0
  21. data/lib/simplecov/cli/coverage_file.rb +109 -0
  22. data/lib/simplecov/cli/dead_code/output.rb +100 -0
  23. data/lib/simplecov/cli/dead_code.rb +139 -0
  24. data/lib/simplecov/cli/diff/output.rb +45 -0
  25. data/lib/simplecov/cli/diff.rb +115 -0
  26. data/lib/simplecov/cli/dotfile.rb +156 -0
  27. data/lib/simplecov/cli/git.rb +41 -0
  28. data/lib/simplecov/cli/history/output.rb +135 -0
  29. data/lib/simplecov/cli/history.rb +67 -0
  30. data/lib/simplecov/cli/merge.rb +115 -0
  31. data/lib/simplecov/cli/open.rb +45 -0
  32. data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
  33. data/lib/simplecov/cli/patch/output.rb +151 -0
  34. data/lib/simplecov/cli/patch.rb +175 -0
  35. data/lib/simplecov/cli/ratchet/output.rb +44 -0
  36. data/lib/simplecov/cli/ratchet.rb +83 -0
  37. data/lib/simplecov/cli/report.rb +91 -0
  38. data/lib/simplecov/cli/run.rb +35 -0
  39. data/lib/simplecov/cli/serve/report_preparer.rb +30 -0
  40. data/lib/simplecov/cli/serve/static_file_handler.rb +128 -0
  41. data/lib/simplecov/cli/serve.rb +88 -0
  42. data/lib/simplecov/cli/show/annotator.rb +87 -0
  43. data/lib/simplecov/cli/show/sweep.rb +36 -0
  44. data/lib/simplecov/cli/show.rb +133 -0
  45. data/lib/simplecov/cli/status/facts.rb +87 -0
  46. data/lib/simplecov/cli/status.rb +93 -0
  47. data/lib/simplecov/cli/tests/redundancy.rb +78 -0
  48. data/lib/simplecov/cli/tests.rb +148 -0
  49. data/lib/simplecov/cli/uncovered/misses.rb +33 -0
  50. data/lib/simplecov/cli/uncovered.rb +127 -0
  51. data/lib/simplecov/cli/usage.rb +153 -0
  52. data/lib/simplecov/cli/watch/live_report.rb +73 -0
  53. data/lib/simplecov/cli/watch/narrator.rb +55 -0
  54. data/lib/simplecov/cli/watch/poller.rb +45 -0
  55. data/lib/simplecov/cli/watch/session.rb +134 -0
  56. data/lib/simplecov/cli/watch/test_plan.rb +43 -0
  57. data/lib/simplecov/cli/watch.rb +85 -0
  58. data/lib/simplecov/cli.rb +114 -0
  59. data/lib/simplecov/color.rb +65 -0
  60. data/lib/simplecov/combine/branches_combiner.rb +44 -21
  61. data/lib/simplecov/combine/coverage_accumulator.rb +175 -0
  62. data/lib/simplecov/combine/identity_interner.rb +27 -0
  63. data/lib/simplecov/combine/interned_counts.rb +28 -0
  64. data/lib/simplecov/combine/lines_combiner.rb +35 -29
  65. data/lib/simplecov/combine/methods_combiner.rb +48 -0
  66. data/lib/simplecov/combine/results_combiner.rb +9 -49
  67. data/lib/simplecov/combine.rb +0 -24
  68. data/lib/simplecov/command_guesser.rb +99 -42
  69. data/lib/simplecov/configuration/baseline.rb +31 -0
  70. data/lib/simplecov/configuration/coverage.rb +184 -0
  71. data/lib/simplecov/configuration/coverage_criteria.rb +143 -0
  72. data/lib/simplecov/configuration/deprecations.rb +21 -0
  73. data/lib/simplecov/configuration/eval_coverage.rb +37 -0
  74. data/lib/simplecov/configuration/filters.rb +131 -0
  75. data/lib/simplecov/configuration/formatting.rb +129 -0
  76. data/lib/simplecov/configuration/groups.rb +48 -0
  77. data/lib/simplecov/configuration/history.rb +44 -0
  78. data/lib/simplecov/configuration/ignored_entries.rb +72 -0
  79. data/lib/simplecov/configuration/merging.rb +197 -0
  80. data/lib/simplecov/configuration/missed_caps.rb +51 -0
  81. data/lib/simplecov/configuration/production.rb +23 -0
  82. data/lib/simplecov/configuration/test_tracking.rb +48 -0
  83. data/lib/simplecov/configuration/thresholds.rb +150 -0
  84. data/lib/simplecov/configuration/view_coverage.rb +37 -0
  85. data/lib/simplecov/configuration.rb +100 -443
  86. data/lib/simplecov/configuration_error.rb +5 -0
  87. data/lib/simplecov/context_map/union.rb +81 -0
  88. data/lib/simplecov/context_map.rb +187 -0
  89. data/lib/simplecov/coverage_json.rb +21 -0
  90. data/lib/simplecov/coverage_statistics.rb +24 -28
  91. data/lib/simplecov/coverage_violations.rb +254 -0
  92. data/lib/simplecov/current_run.rb +61 -0
  93. data/lib/simplecov/defaults.rb +20 -28
  94. data/lib/simplecov/deprecation.rb +63 -0
  95. data/lib/simplecov/directive/erb.rb +44 -0
  96. data/lib/simplecov/directive/haml.rb +33 -0
  97. data/lib/simplecov/directive/indented_template.rb +37 -0
  98. data/lib/simplecov/directive/slim.rb +35 -0
  99. data/lib/simplecov/directive/template.rb +29 -0
  100. data/lib/simplecov/directive.rb +152 -0
  101. data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
  102. data/lib/simplecov/exit_codes/check.rb +39 -0
  103. data/lib/simplecov/exit_codes/exit_code_handling.rb +24 -5
  104. data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +17 -67
  105. data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
  106. data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
  107. data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +27 -0
  108. data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +20 -38
  109. data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +28 -0
  110. data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +37 -34
  111. data/lib/simplecov/exit_codes.rb +26 -0
  112. data/lib/simplecov/exit_handling.rb +163 -0
  113. data/lib/simplecov/file_list.rb +63 -48
  114. data/lib/simplecov/filter.rb +98 -42
  115. data/lib/simplecov/formatter/base.rb +100 -0
  116. data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
  117. data/lib/simplecov/formatter/coverage_json_writer.rb +95 -0
  118. data/lib/simplecov/formatter/html_formatter/public/index.html +93 -0
  119. data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +153 -0
  120. data/lib/simplecov/formatter/html_formatter.rb +88 -0
  121. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +122 -0
  122. data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
  123. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +132 -0
  124. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +97 -0
  125. data/lib/simplecov/formatter/json_formatter.rb +40 -0
  126. data/lib/simplecov/formatter/multi_formatter.rb +8 -10
  127. data/lib/simplecov/formatter/simple_formatter.rb +12 -15
  128. data/lib/simplecov/formatter.rb +21 -1
  129. data/lib/simplecov/group_names.rb +33 -0
  130. data/lib/simplecov/history.rb +115 -0
  131. data/lib/simplecov/last_run.rb +16 -5
  132. data/lib/simplecov/lines_classifier.rb +67 -19
  133. data/lib/simplecov/load_global_config.rb +12 -4
  134. data/lib/simplecov/parallel_adapters/base.rb +50 -0
  135. data/lib/simplecov/parallel_adapters/generic.rb +33 -0
  136. data/lib/simplecov/parallel_adapters/parallel_tests.rb +72 -0
  137. data/lib/simplecov/parallel_adapters.rb +54 -0
  138. data/lib/simplecov/parallel_coordination.rb +124 -0
  139. data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
  140. data/lib/simplecov/parallel_result_merger.rb +171 -0
  141. data/lib/simplecov/process.rb +34 -12
  142. data/lib/simplecov/production/error.rb +11 -0
  143. data/lib/simplecov/production/file_sink.rb +130 -0
  144. data/lib/simplecov/production.rb +249 -0
  145. data/lib/simplecov/profiles/bundler_filter.rb +1 -1
  146. data/lib/simplecov/profiles/hidden_filter.rb +1 -1
  147. data/lib/simplecov/profiles/rails.rb +13 -10
  148. data/lib/simplecov/profiles/root_filter.rb +2 -5
  149. data/lib/simplecov/profiles/strict.rb +22 -0
  150. data/lib/simplecov/profiles/test_frameworks.rb +1 -4
  151. data/lib/simplecov/profiles.rb +26 -19
  152. data/lib/simplecov/report_deferral.rb +45 -0
  153. data/lib/simplecov/report_stamp.rb +25 -0
  154. data/lib/simplecov/result/filter_config.rb +19 -0
  155. data/lib/simplecov/result/missing_source_files_reporter.rb +48 -0
  156. data/lib/simplecov/result/serialization.rb +43 -0
  157. data/lib/simplecov/result/source_file_builder.rb +46 -0
  158. data/lib/simplecov/result.rb +123 -47
  159. data/lib/simplecov/result_adapter.rb +130 -11
  160. data/lib/simplecov/result_merger/contexts.rb +22 -0
  161. data/lib/simplecov/result_merger/legacy_format_adapter.rb +24 -0
  162. data/lib/simplecov/result_merger/resultset_file.rb +67 -0
  163. data/lib/simplecov/result_merger/resultset_run_identity.rb +65 -0
  164. data/lib/simplecov/result_merger/resultset_store.rb +62 -0
  165. data/lib/simplecov/result_merger/unloaded_files.rb +95 -0
  166. data/lib/simplecov/result_merger.rb +120 -124
  167. data/lib/simplecov/result_processing.rb +188 -0
  168. data/lib/simplecov/run_identity.rb +75 -0
  169. data/lib/simplecov/simulate_coverage.rb +72 -17
  170. data/lib/simplecov/source_file/branch.rb +8 -35
  171. data/lib/simplecov/source_file/branch_builder.rb +95 -0
  172. data/lib/simplecov/source_file/builder_context.rb +28 -0
  173. data/lib/simplecov/source_file/line.rb +17 -32
  174. data/lib/simplecov/source_file/line_builder.rb +37 -0
  175. data/lib/simplecov/source_file/method.rb +54 -0
  176. data/lib/simplecov/source_file/method_builder.rb +51 -0
  177. data/lib/simplecov/source_file/ruby_data_parser.rb +103 -0
  178. data/lib/simplecov/source_file/skip_chunks.rb +72 -0
  179. data/lib/simplecov/source_file/source_loader.rb +89 -0
  180. data/lib/simplecov/source_file/statistics.rb +76 -0
  181. data/lib/simplecov/source_file.rb +64 -260
  182. data/lib/simplecov/static_coverage_extractor/condition_folding.rb +155 -0
  183. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +191 -0
  184. data/lib/simplecov/static_coverage_extractor/method_collector.rb +45 -0
  185. data/lib/simplecov/static_coverage_extractor/prism_compat.rb +46 -0
  186. data/lib/simplecov/static_coverage_extractor/value_position.rb +61 -0
  187. data/lib/simplecov/static_coverage_extractor/visitor.rb +163 -0
  188. data/lib/simplecov/static_coverage_extractor.rb +78 -0
  189. data/lib/simplecov/test_tracker/accessors.rb +34 -0
  190. data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
  191. data/lib/simplecov/test_tracker/delta.rb +69 -0
  192. data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
  193. data/lib/simplecov/test_tracker.rb +162 -0
  194. data/lib/simplecov/unloaded_file_injector.rb +62 -0
  195. data/lib/simplecov/useless_results_remover.rb +17 -7
  196. data/lib/simplecov/version.rb +1 -1
  197. data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
  198. data/lib/simplecov/view_coverage.rb +55 -0
  199. data/lib/simplecov-html.rb +5 -0
  200. data/lib/simplecov.rb +143 -394
  201. data/lib/simplecov_json_formatter.rb +5 -0
  202. data/man/simplecov.1 +354 -0
  203. data/sig/simplecov.rbs +1710 -0
  204. metadata +183 -61
  205. data/CHANGELOG.md +0 -191
  206. data/doc/alternate-formatters.md +0 -71
  207. data/doc/commercial-services.md +0 -25
  208. data/doc/editor-integration.md +0 -18
  209. data/lib/simplecov/combine/files_combiner.rb +0 -24
  210. data/lib/simplecov/default_formatter.rb +0 -20
@@ -1,30 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- # Functionally for combining coverage results
5
- #
6
4
  module Combine
7
- module_function
8
-
9
- #
10
- # Combine two coverage based on the given combiner_module.
11
- #
12
- # Combiners should always be called through this interface,
13
- # as it takes care of short-circuiting of one of the coverages is nil.
14
- #
15
- # @return [Hash]
16
- def combine(combiner_module, coverage_a, coverage_b)
17
- return existing_coverage(coverage_a, coverage_b) if empty_coverage?(coverage_a, coverage_b)
18
-
19
- combiner_module.combine(coverage_a, coverage_b)
20
- end
21
-
22
- def empty_coverage?(coverage_a, coverage_b)
23
- !(coverage_a && coverage_b)
24
- end
25
-
26
- def existing_coverage(coverage_a, coverage_b)
27
- coverage_a || coverage_b
28
- end
29
5
  end
30
6
  end
@@ -1,63 +1,120 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleCov
4
- #
5
- # Helper that tries to find out what test suite is running (for SimpleCov.command_name)
6
- #
7
4
  module CommandGuesser
8
5
  class << self
9
- # Storage for the original command line call that invoked the test suite.
10
- # This has got to be stored as early as possible because i.e. rake and test/unit 2
11
- # have a habit of tampering with ARGV, which makes i.e. the automatic distinction
12
- # between rails unit/functional/integration tests impossible without this cached
13
- # item.
14
- attr_accessor :original_run_command
6
+ # The original command line call that invoked the test suite. This has to
7
+ # be stored as early as possible because rake and test/unit 2 have a habit
8
+ # of tampering with ARGV.
9
+ attr_reader :original_run_command
10
+
11
+ # Assigning the flattened command clears any separately recorded program
12
+ # name so the two can never describe different runs. Callers that know the
13
+ # real one assign it immediately afterwards, as `defaults.rb` does.
14
+ def original_run_command=(command)
15
+ @original_program_name = nil
16
+ @original_run_command = command
17
+ end
18
+
19
+ # The invoked program on its own. `original_run_command` joins
20
+ # `$PROGRAM_NAME` and `ARGV` with a space, so a program path containing
21
+ # one (`/opt/My Ruby/bin/rspec`) cannot be recovered from it afterwards.
22
+ # Falls back to the first token for callers that set only the flattened
23
+ # command, which is every caller outside `defaults.rb`.
24
+ attr_writer :original_program_name
25
+
26
+ def original_program_name
27
+ @original_program_name || original_run_command.to_s[/\A\s*(\S+)/, 1]
28
+ end
15
29
 
16
30
  def guess
17
- from_env || from_command_line_options || from_defined_constants
31
+ framework = from_executable_name || from_command_line_options || from_defined_constants
32
+ [framework, parallel_data].compact.join(" ")
18
33
  end
19
34
 
20
- private
35
+ private
21
36
 
22
- def from_env
23
- # If being run from inside parallel_tests set the command name according to the process number
24
- return unless ENV["PARALLEL_TEST_GROUPS"] && ENV["TEST_ENV_NUMBER"]
37
+ # When parallel_tests (or a compatible runner) is driving the suite, tag
38
+ # the command name with this worker's position in the pool.
39
+ def parallel_data
40
+ groups, number = ENV.values_at("PARALLEL_TEST_GROUPS", "TEST_ENV_NUMBER")
41
+ return unless groups && number
25
42
 
26
- number = ENV["TEST_ENV_NUMBER"]
43
+ # parallel_tests sets the first worker's TEST_ENV_NUMBER to "" rather
44
+ # than "1"; restore the position so the rendered label reads cleanly.
27
45
  number = "1" if number.empty?
28
- "(#{number}/#{ENV['PARALLEL_TEST_GROUPS']})"
46
+ "(#{number}/#{groups})"
29
47
  end
30
48
 
49
+ # The command is `"#{$PROGRAM_NAME} #{ARGV.join(' ')}"`, so a keyword only
50
+ # carries meaning when it begins a path segment or an argument. Matching
51
+ # it as a bare substring lets any part of the interpreter path decide the
52
+ # suite name: a Ruby installed under `latest/bin` (as mise does) contains
53
+ # "test/", so every run through it was labelled "Unit Tests".
54
+ #
55
+ # A backslash bounds a segment too, or a Windows `spec\foo_spec.rb` would
56
+ # lose a match. The `test/` patterns below still spell their separator as
57
+ # `/` only, which is how it has always been.
58
+ SEGMENT_START = %r{(?<![^\s/\\])}
59
+ private_constant :SEGMENT_START
60
+ SEGMENT_END = %r{(?=[\s/\\]|\z)}
61
+ private_constant :SEGMENT_END
62
+
63
+ COMMAND_LINE_FRAMEWORKS = {
64
+ %r{#{SEGMENT_START}test/functional/} => "Functional Tests",
65
+ %r{#{SEGMENT_START}test/\{.*functional.*\}/} => "Functional Tests",
66
+ %r{#{SEGMENT_START}test/integration/} => "Integration Tests",
67
+ %r{#{SEGMENT_START}test/} => "Unit Tests",
68
+ /#{SEGMENT_START}spec#{SEGMENT_END}/ => "RSpec",
69
+ /#{SEGMENT_START}cucumber#{SEGMENT_END}/ => "Cucumber Features",
70
+ /#{SEGMENT_START}features#{SEGMENT_END}/ => "Cucumber Features"
71
+ }.freeze
72
+ private_constant :COMMAND_LINE_FRAMEWORKS
73
+
31
74
  def from_command_line_options
32
- case original_run_command
33
- when /test\/functional\//, /test\/\{.*functional.*\}\//
34
- "Functional Tests"
35
- when /test\/integration\//
36
- "Integration Tests"
37
- when /test\//
38
- "Unit Tests"
39
- when /spec/
40
- "RSpec"
41
- when /cucumber/, /features/
42
- "Cucumber Features"
43
- end
75
+ # A command that was never recorded matches nothing, which `match?`
76
+ # answers for a nil of its own accord.
77
+ COMMAND_LINE_FRAMEWORKS.find { |pattern, _| pattern.match?(original_run_command) }&.last
44
78
  end
45
79
 
80
+ # Which binary was invoked outranks everything else on the command line.
81
+ # `rspec features` is an RSpec suite whose examples live in features/, not
82
+ # a Cucumber run, and only the executable name says so. Generic runners
83
+ # (`ruby`, `rake`, a bare `*_test.rb` script) are absent on purpose: they
84
+ # name no framework, so those runs fall through to the path patterns.
85
+ EXECUTABLE_FRAMEWORKS = {
86
+ "rspec" => "RSpec",
87
+ "cucumber" => "Cucumber Features"
88
+ }.freeze
89
+ private_constant :EXECUTABLE_FRAMEWORKS
90
+
91
+ def from_executable_name
92
+ program = original_program_name
93
+ return unless program
94
+
95
+ # The extension is trimmed so Windows' rspec.bat is recognized too.
96
+ EXECUTABLE_FRAMEWORKS[File.basename(program, ".*")]
97
+ end
98
+
99
+ # Inner array literals after the first are flagged uncovered by Ruby's
100
+ # Coverage module even though the constant evaluates as a whole, a known
101
+ # quirk with multi-line array literals.
102
+ DEFINED_CONSTANT_FRAMEWORKS = [
103
+ ["RSpec", -> { defined?(::RSpec) }],
104
+ ["Unit Tests", -> { defined?(Test::Unit) }], # simplecov:disable
105
+ ["Minitest", -> { defined?(::Minitest) }], # simplecov:disable
106
+ ["MiniTest", -> { defined?(MiniTest) }] # simplecov:disable
107
+ ].freeze
108
+ private_constant :DEFINED_CONSTANT_FRAMEWORKS
109
+
46
110
  def from_defined_constants
47
- # If the command regexps fail, let's try checking defined constants.
48
- if defined?(RSpec)
49
- "RSpec"
50
- elsif defined?(Test::Unit)
51
- "Unit Tests"
52
- elsif defined?(Minitest)
53
- "Minitest"
54
- elsif defined?(MiniTest)
55
- "MiniTest"
56
- else
57
- # TODO: Provide link to docs/wiki article
58
- warn "SimpleCov failed to recognize the test framework and/or suite used. Please specify manually using SimpleCov.command_name 'Unit Tests'."
59
- "Unknown Test Framework"
60
- end
111
+ DEFINED_CONSTANT_FRAMEWORKS.each { |name, defined_check| return name if defined_check.call }
112
+
113
+ warn(
114
+ "SimpleCov failed to recognize the test framework and/or suite used. " \
115
+ "Please specify manually using SimpleCov.command_name 'Unit Tests'."
116
+ )
117
+ "Unknown Test Framework"
61
118
  end
62
119
  end
63
120
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ # The baseline's path, relative to `SimpleCov.root`; an absolute path is
6
+ # honored as given. The file's presence is the opt-in: generate it with
7
+ # `simplecov ratchet`, check it in, and the exit check holds every listed
8
+ # file to its own floor (#1268).
9
+ def baseline_file(path = nil)
10
+ return @baseline_file ||= Baseline::DEFAULT_FILENAME unless path
11
+
12
+ self.baseline_file = path
13
+ _ = @baseline_file
14
+ end
15
+
16
+ def baseline_file=(path)
17
+ @baseline_file = path
18
+ end
19
+
20
+ # Memoized per resolved path so the exit check and the JSON formatter's
21
+ # errors section read the file once, while a `baseline_file` or `root` change
22
+ # between reads is still picked up.
23
+ def baseline
24
+ resolved = File.expand_path(baseline_file, root)
25
+ return @baseline if @baseline_path.eql?(resolved)
26
+
27
+ @baseline_path = resolved
28
+ @baseline = Baseline.read_if_exists(resolved)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ COVERAGE_THRESHOLD_OPTIONS = %i[minimum maximum exact maximum_drop ignore minimum_per_file
6
+ maximum_missed maximum_missed_per_file].freeze
7
+
8
+ def coverage(criterion, primary: false, enabled: true, oneshot: false, **thresholds, &block)
9
+ criterion = enable_coverage_criterion(criterion, enabled: enabled, oneshot: oneshot)
10
+ # The cast admits :eval, which primary_coverage rejects at runtime.
11
+ primary_coverage(_ = criterion) if primary
12
+
13
+ configurator = CoverageCriterion.new(self, criterion)
14
+ apply_threshold_options(configurator, thresholds)
15
+ configurator.instance_eval(&block) if block
16
+
17
+ criterion
18
+ end
19
+
20
+ private
21
+
22
+ def apply_threshold_options(configurator, options)
23
+ options.each do |verb, value|
24
+ unless COVERAGE_THRESHOLD_OPTIONS.include?(verb)
25
+ raise ConfigurationError,
26
+ "Unknown `coverage` option #{verb.inspect}. " \
27
+ "Supported options are #{COVERAGE_THRESHOLD_OPTIONS.inspect}."
28
+ end
29
+
30
+ configurator.public_send(verb, value)
31
+ end
32
+ end
33
+
34
+ def enable_coverage_criterion(criterion, enabled:, oneshot:)
35
+ criterion = resolve_criterion_variant(criterion, oneshot)
36
+ enabled ? enable_coverage(criterion) : disable_coverage(criterion)
37
+ criterion
38
+ end
39
+
40
+ def resolve_criterion_variant(criterion, oneshot)
41
+ return criterion unless oneshot
42
+
43
+ raise ConfigurationError, "`oneshot: true` is only valid for `coverage :line`" unless criterion.equal?(:line)
44
+
45
+ ONESHOT_LINE_COVERAGE_CRITERION
46
+ end
47
+
48
+ # @api private
49
+ def store_overall_threshold(setting, criterion, percent)
50
+ raise_on_invalid_coverage({criterion => percent}, setting)
51
+ public_send(setting)[criterion] = percent
52
+ end
53
+
54
+ def store_missed_cap(setting, criterion, count)
55
+ raise_if_criterion_disabled(criterion)
56
+ raise_on_invalid_missed_cap(count, setting)
57
+ public_send(setting)[criterion] = count
58
+ end
59
+
60
+ def store_maximum_missed_per_file(criterion, count, target)
61
+ raise_if_criterion_disabled(criterion)
62
+ raise_on_invalid_missed_cap(count, "maximum_missed_per_file")
63
+ return maximum_missed_per_file[criterion] = count if target.nil?
64
+
65
+ unless target.is_a?(String) || target.is_a?(Regexp)
66
+ raise ConfigurationError, "`only:` must be a String path or Regexp, got #{target.inspect}"
67
+ end
68
+
69
+ (maximum_missed_per_file_overrides[target] ||= {})[criterion] = count
70
+ end
71
+
72
+ def store_minimum_per_file(criterion, percent, target)
73
+ raise_on_invalid_coverage({criterion => percent}, "minimum_coverage_by_file")
74
+ return minimum_coverage_by_file[criterion] = percent if target.nil?
75
+
76
+ unless target.is_a?(String) || target.is_a?(Regexp)
77
+ raise ConfigurationError, "`only:` must be a String path or Regexp, got #{target.inspect}"
78
+ end
79
+
80
+ (minimum_coverage_by_file_overrides[target] ||= {})[criterion] = percent
81
+ end
82
+
83
+ def store_minimum_per_group(criterion, percent, group_name)
84
+ raise_on_invalid_coverage({criterion => percent}, "minimum_coverage_by_group")
85
+ # Normalized like `group` does, so `per: group(:Models)` finds the group
86
+ # defined as `group "Models"` at check time.
87
+ (minimum_coverage_by_group[GroupNames.normalize(group_name)] ||= {})[criterion] = percent
88
+ end
89
+
90
+ #
91
+ # Receiver for a `coverage <criterion> do ... end` block.
92
+ #
93
+ class CoverageCriterion
94
+ def initialize(config, criterion)
95
+ @config = config
96
+ @criterion = criterion
97
+ end
98
+
99
+ def minimum(percent, per: nil)
100
+ case per
101
+ when nil then @config.__send__(:store_overall_threshold, :minimum_coverage, @criterion, percent)
102
+ when :file then @config.__send__(:store_minimum_per_file, @criterion, percent, nil)
103
+ when String, Regexp then @config.__send__(:store_minimum_per_file, @criterion, percent, per)
104
+ when GroupTarget then @config.__send__(:store_minimum_per_group, @criterion, percent, per.name)
105
+ else raise_invalid_per(per)
106
+ end
107
+ end
108
+
109
+ def maximum(percent)
110
+ @config.__send__(:store_overall_threshold, :maximum_coverage, @criterion, percent)
111
+ end
112
+
113
+ def exact(percent)
114
+ minimum(percent)
115
+ maximum(percent)
116
+ end
117
+
118
+ def maximum_drop(percent)
119
+ @config.__send__(:store_overall_threshold, :maximum_coverage_drop, @criterion, percent)
120
+ end
121
+
122
+ def maximum_missed(count, per: nil)
123
+ case per
124
+ when nil then @config.__send__(:store_missed_cap, :maximum_missed, @criterion, count)
125
+ when :file then @config.__send__(:store_maximum_missed_per_file, @criterion, count, nil)
126
+ when String, Regexp then @config.__send__(:store_maximum_missed_per_file, @criterion, count, per)
127
+ when GroupTarget
128
+ raise ConfigurationError,
129
+ "maximum_missed does not support `per: group(...)` yet; see docs/Roadmap.md"
130
+ else raise_invalid_per(per)
131
+ end
132
+ end
133
+
134
+ def group(name)
135
+ GroupTarget.new(name: name)
136
+ end
137
+
138
+ # The flatten serves the one-liner keyword form, whose value arrives as a
139
+ # single argument (`ignore: %i[implicit_else]`).
140
+ def ignore(*types)
141
+ case @criterion
142
+ when :branch then @config.__send__(:store_ignored_branches, types.flatten)
143
+ when :method then @config.__send__(:store_ignored_methods, types.flatten)
144
+ else
145
+ raise ConfigurationError,
146
+ "`ignore` is supported for `coverage :branch` and `coverage :method`, not #{@criterion.inspect}"
147
+ end
148
+ end
149
+
150
+ def minimum_per_file(percent, only: nil)
151
+ Deprecation.warn("`minimum_per_file` is deprecated. " \
152
+ "Replace with `minimum #{percent}, per: #{(only || :file).inspect}`.")
153
+ @config.__send__(:store_minimum_per_file, @criterion, percent, only)
154
+ end
155
+
156
+ def minimum_per_group(percent, only:)
157
+ Deprecation.warn("`minimum_per_group` is deprecated. " \
158
+ "Replace with `minimum #{percent}, per: group(#{only.inspect})`.")
159
+ @config.__send__(:store_minimum_per_group, @criterion, percent, only)
160
+ end
161
+
162
+ def maximum_missed_per_file(count, only: nil)
163
+ Deprecation.warn("`maximum_missed_per_file` is deprecated. " \
164
+ "Replace with `maximum_missed #{count}, per: #{(only || :file).inspect}`.")
165
+ @config.__send__(:store_maximum_missed_per_file, @criterion, count, only)
166
+ end
167
+
168
+ def primary
169
+ # @criterion is Symbol-wide because this receiver is also built for
170
+ # :eval; primary_coverage validates at runtime.
171
+ @config.primary_coverage(_ = @criterion)
172
+ end
173
+
174
+ GroupTarget = Data.define(:name)
175
+
176
+ private
177
+
178
+ def raise_invalid_per(per)
179
+ raise ConfigurationError,
180
+ "`per:` must be :file, a String path, a Regexp, or group(\"Name\"), got #{per.inspect}"
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ SUPPORTED_COVERAGE_CRITERIA = %i[line branch method oneshot_line].freeze
6
+ DEFAULT_COVERAGE_CRITERION = :line
7
+ ONESHOT_LINE_COVERAGE_CRITERION = :oneshot_line
8
+ LINE_COVERAGE_ALTERNATIVES = {line: :oneshot_line, oneshot_line: :line}.freeze #: line_coverage_alternatives
9
+ private_constant :LINE_COVERAGE_ALTERNATIVES
10
+
11
+ def enable_coverage(*criteria)
12
+ criteria.each do |criterion|
13
+ criterion.equal?(:eval) ? enable_eval_coverage : add_coverage_criterion(_ = criterion)
14
+ end
15
+ end
16
+
17
+ # Disabling every criterion raises at `start_tracking`, not here, so config
18
+ # files that toggle criteria in arbitrary order don't have to worry about
19
+ # transient empty states.
20
+ def disable_coverage(criterion)
21
+ return disable_eval_coverage if criterion.equal?(:eval)
22
+
23
+ raise_if_criterion_unsupported(_ = criterion)
24
+ coverage_criteria.delete(_ = criterion)
25
+ @primary_coverage = nil if @primary_coverage.equal?(criterion)
26
+ end
27
+
28
+ def primary_coverage(criterion = nil)
29
+ if criterion.nil?
30
+ @primary_coverage ||= default_primary_coverage
31
+ else
32
+ self.primary_coverage = criterion
33
+ end
34
+ end
35
+
36
+ def primary_coverage=(criterion)
37
+ raise_if_criterion_disabled(criterion)
38
+ @primary_coverage = criterion
39
+ end
40
+
41
+ def coverage_criteria
42
+ @coverage_criteria ||= Set[DEFAULT_COVERAGE_CRITERION]
43
+ end
44
+
45
+ def coverage_criterion_enabled?(criterion)
46
+ coverage_criteria.member?(criterion)
47
+ end
48
+
49
+ def clear_coverage_criteria
50
+ @coverage_criteria = nil
51
+ @primary_coverage = nil
52
+ end
53
+
54
+ def validate_coverage_criteria!
55
+ return unless coverage_criteria.empty?
56
+
57
+ raise ConfigurationError,
58
+ "At least one coverage criterion must be enabled. " \
59
+ "Re-enable one with `enable_coverage :line`, `:branch`, or `:method`."
60
+ end
61
+
62
+ # Whether this run produces line data at all. The oneshot variant counts:
63
+ # `ResultAdapter` turns its executed-line list back into a line array. A
64
+ # branch-only or method-only run produces none, and `Coverage.result`
65
+ # entries for the files it loaded carry no `"lines"` key.
66
+ def line_coverage?
67
+ coverage_criterion_enabled?(DEFAULT_COVERAGE_CRITERION) ||
68
+ coverage_criterion_enabled?(ONESHOT_LINE_COVERAGE_CRITERION)
69
+ end
70
+
71
+ def branch_coverage?
72
+ branch_coverage_supported? && coverage_criterion_enabled?(:branch)
73
+ end
74
+
75
+ def branch_coverage_supported?
76
+ coverage_criterion_supported?(:branches)
77
+ end
78
+
79
+ def method_coverage?
80
+ method_coverage_supported? && coverage_criterion_enabled?(:method)
81
+ end
82
+
83
+ def method_coverage_supported?
84
+ coverage_criterion_supported?(:methods)
85
+ end
86
+
87
+ # Older Rubies don't expose `Coverage.supported?`, so fall back to the
88
+ # historical engine check that line/branch/method were unavailable on JRuby.
89
+ # `:eval` was added later, so its fallback is "always unsupported".
90
+ def coverage_criterion_supported?(criterion)
91
+ load_coverage
92
+ return Coverage.supported?(criterion) if Coverage.respond_to?(:supported?)
93
+
94
+ !criterion.eql?(:eval) && !RUBY_ENGINE.eql?("jruby")
95
+ end
96
+
97
+ # Loading `simplecov/configuration` on its own leaves Coverage undefined, and
98
+ # this question is asked at configuration time.
99
+ def load_coverage
100
+ require "coverage"
101
+ end
102
+
103
+ private
104
+
105
+ def add_coverage_criterion(criterion)
106
+ raise_if_criterion_unsupported(criterion)
107
+ incompatible = LINE_COVERAGE_ALTERNATIVES[criterion]
108
+ disable_coverage(incompatible) if incompatible
109
+ coverage_criteria << criterion
110
+ end
111
+
112
+ # Answering `:line` even when it is disabled would propagate broken state
113
+ # into `minimum_coverage 90`, so fall back to whichever criterion the user
114
+ # actually enabled, in insertion order.
115
+ def default_primary_coverage
116
+ return DEFAULT_COVERAGE_CRITERION if coverage_criterion_enabled?(DEFAULT_COVERAGE_CRITERION)
117
+
118
+ _ = coverage_criteria.first
119
+ end
120
+
121
+ def raise_if_criterion_disabled(criterion)
122
+ if criterion.equal?(:eval)
123
+ raise ConfigurationError,
124
+ "Coverage criterion :eval only toggles measuring eval'd code; " \
125
+ "it cannot carry thresholds or serve as the primary criterion"
126
+ end
127
+
128
+ raise_if_criterion_unsupported(criterion)
129
+ return if coverage_criterion_enabled?(criterion)
130
+
131
+ raise ConfigurationError,
132
+ "Coverage criterion #{criterion}, is disabled! " \
133
+ "Please enable it first through enable_coverage #{criterion} (if supported)"
134
+ end
135
+
136
+ def raise_if_criterion_unsupported(criterion)
137
+ return if SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
138
+
139
+ raise ConfigurationError,
140
+ "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../deprecation"
4
+
5
+ module SimpleCov
6
+ module Configuration
7
+ DEPRECATION_MODES = Deprecation::MODES
8
+
9
+ # The configuration DSL migrates by warn-and-delegate, which means a project
10
+ # can run on deprecated spellings indefinitely without noticing.
11
+ # `deprecations :raise` turns every deprecated API into a ConfigurationError,
12
+ # so a project that has migrated can guard in CI against old spellings
13
+ # creeping back. There is deliberately no silencing mode: a deprecation you
14
+ # cannot see is a migration you never make.
15
+ def deprecations(mode = nil)
16
+ return Deprecation.mode unless mode
17
+
18
+ Deprecation.mode = mode
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module Configuration
5
+ # The standalone eval-coverage toggle, kept apart from the criteria-set logic
6
+ # in `coverage_criteria.rb` because `:eval` is a boolean toggle, never a
7
+ # member of the enabled-criteria set.
8
+ def coverage_for_eval_supported?
9
+ coverage_criterion_supported?(:eval)
10
+ end
11
+
12
+ def coverage_for_eval_enabled?
13
+ @coverage_for_eval_enabled ||= false
14
+ end
15
+
16
+ def enable_coverage_for_eval
17
+ Deprecation.warn("`SimpleCov.enable_coverage_for_eval` is deprecated. " \
18
+ "Replace with `SimpleCov.enable_coverage :eval`.")
19
+ enable_eval_coverage
20
+ end
21
+
22
+ private
23
+
24
+ def enable_eval_coverage
25
+ if coverage_for_eval_supported?
26
+ @coverage_for_eval_enabled = true
27
+ else
28
+ warn "Coverage for eval is not available on this Ruby"
29
+ end
30
+ end
31
+
32
+ # No support check needed: off is a safe state on every Ruby.
33
+ def disable_eval_coverage
34
+ @coverage_for_eval_enabled = false
35
+ end
36
+ end
37
+ end