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
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "fileutils"
4
- require "docile"
5
- require_relative "formatter/multi_formatter"
6
4
 
7
5
  module SimpleCov
8
6
  #
@@ -11,8 +9,6 @@ module SimpleCov
11
9
  # SimpleCov documentation for further info.
12
10
  #
13
11
  module Configuration
14
- attr_writer :filters, :groups, :formatter, :print_error_status
15
-
16
12
  #
17
13
  # The root for the project. This defaults to the
18
14
  # current working directory.
@@ -22,7 +18,7 @@ module SimpleCov
22
18
  def root(root = nil)
23
19
  return @root if defined?(@root) && root.nil?
24
20
 
25
- @coverage_path = nil # invalidate cache
21
+ @coverage_path = nil unless @coverage_path_explicit # invalidate cache
26
22
  @root = File.expand_path(root || Dir.getwd)
27
23
  end
28
24
 
@@ -34,467 +30,134 @@ module SimpleCov
34
30
  def coverage_dir(dir = nil)
35
31
  return @coverage_dir if defined?(@coverage_dir) && dir.nil?
36
32
 
37
- @coverage_path = nil # invalidate cache
38
- @coverage_dir = (dir || "coverage")
39
- end
40
-
41
- #
42
- # Returns the full path to the output directory using SimpleCov.root
43
- # and SimpleCov.coverage_dir, so you can adjust this by configuring those
44
- # values. Will create the directory if it's missing
45
- #
46
- def coverage_path
47
- @coverage_path ||= begin
48
- coverage_path = File.expand_path(coverage_dir, root)
49
- FileUtils.mkdir_p coverage_path
50
- coverage_path
51
- end
52
- end
53
-
54
- #
55
- # Coverage results will always include files matched by this glob, whether
56
- # or not they were explicitly required. Without this, un-required files
57
- # will not be present in the final report.
58
- #
59
- def track_files(glob)
60
- @tracked_files = glob
61
- end
62
-
63
- #
64
- # Returns the glob that will be used to include files that were not
65
- # explicitly required.
66
- #
67
- def tracked_files
68
- @tracked_files if defined?(@tracked_files)
69
- end
70
-
71
- #
72
- # Returns the list of configured filters. Add filters using SimpleCov.add_filter.
73
- #
74
- def filters
75
- @filters ||= []
76
- end
77
-
78
- # The name of the command (a.k.a. Test Suite) currently running. Used for result
79
- # merging and caching. It first tries to make a guess based upon the command line
80
- # arguments the current test suite is running on and should automatically detect
81
- # unit tests, functional tests, integration tests, rpsec and cucumber and label
82
- # them properly. If it fails to recognize the current command, the command name
83
- # is set to the shell command that the current suite is running on.
84
- #
85
- # You can specify it manually with SimpleCov.command_name("test:units") - please
86
- # also check out the corresponding section in README.rdoc
87
- def command_name(name = nil)
88
- @name = name unless name.nil?
89
- @name ||= SimpleCov::CommandGuesser.guess
90
- @name
33
+ @coverage_path = nil unless @coverage_path_explicit # invalidate cache
34
+ @coverage_dir_explicit = true unless dir.nil?
35
+ @coverage_dir = dir || "coverage"
91
36
  end
92
37
 
93
38
  #
94
- # Gets or sets the configured formatter.
39
+ # Returns the full path to the output directory. By default
40
+ # constructed from `SimpleCov.root` + `SimpleCov.coverage_dir`, but
41
+ # callers can override with an arbitrary absolute path — handy for
42
+ # out-of-tree build directories. See #716.
95
43
  #
96
- # Configure with: SimpleCov.formatter(SimpleCov::Formatter::SimpleFormatter)
44
+ # Reading is pure: the directory is only created when a path is
45
+ # explicitly assigned (the user has signaled they intend to write
46
+ # there). The codepaths that actually write into the directory
47
+ # (formatters, `LastRun`, `ResultsetStore`) ensure existence
48
+ # themselves, so read-only CLI subcommands that interpolate the
49
+ # path into status text don't materialize a stray `coverage/`
50
+ # directory.
97
51
  #
98
- def formatter(formatter = nil)
99
- return @formatter if defined?(@formatter) && formatter.nil?
100
-
101
- @formatter = formatter
102
- raise "No formatter configured. Please specify a formatter using SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter" unless @formatter
103
-
104
- @formatter
105
- end
106
-
107
- #
108
- # Sets the configured formatters.
109
- #
110
- def formatters=(formatters)
111
- @formatter = SimpleCov::Formatter::MultiFormatter.new(formatters)
112
- end
113
-
114
- #
115
- # Gets the configured formatters.
116
- #
117
- def formatters
118
- if @formatter.is_a?(SimpleCov::Formatter::MultiFormatter)
119
- @formatter.formatters
120
- else
121
- Array(formatter)
52
+ def coverage_path(path = nil)
53
+ if path
54
+ expanded = File.expand_path(path)
55
+ @coverage_path = expanded
56
+ @coverage_path_explicit = true
57
+ FileUtils.mkdir_p expanded
122
58
  end
123
- end
124
-
125
- #
126
- # Whether we should print non-success status codes. This can be
127
- # configured with the #print_error_status= method.
128
- #
129
- def print_error_status
130
- defined?(@print_error_status) ? @print_error_status : true
131
- end
132
59
 
133
- #
134
- # Certain code blocks (i.e. Ruby-implementation specific code) can be excluded from
135
- # the coverage metrics by wrapping it inside # :nocov: comment blocks. The nocov token
136
- # can be configured to be any other string using this.
137
- #
138
- # Configure with SimpleCov.nocov_token('skip') or it's alias SimpleCov.skip_token('skip')
139
- #
140
- def nocov_token(nocov_token = nil)
141
- return @nocov_token if defined?(@nocov_token) && nocov_token.nil?
142
-
143
- @nocov_token = (nocov_token || "nocov")
60
+ @coverage_path ||= File.expand_path(coverage_dir, root)
144
61
  end
145
- alias skip_token nocov_token
146
62
 
147
63
  #
148
- # Returns the configured groups. Add groups using SimpleCov.add_group
64
+ # The name of the command (a.k.a. Test Suite) currently running.
65
+ # Used for result merging and caching. Auto-detected; set explicitly
66
+ # with SimpleCov.command_name("test:units").
149
67
  #
150
- def groups
151
- @groups ||= {}
68
+ def command_name(name = nil)
69
+ @command_name = name unless name.nil?
70
+ @command_name ||= SimpleCov::CommandGuesser.guess
152
71
  end
153
72
 
154
- #
155
73
  # Returns the hash of available profiles
156
- #
157
74
  def profiles
158
75
  @profiles ||= SimpleCov::Profiles.new
159
76
  end
160
77
 
161
- def adapters
162
- warn "#{Kernel.caller.first}: [DEPRECATION] #adapters is deprecated. Use #profiles instead."
163
- profiles
164
- end
165
-
166
- #
167
- # Allows you to configure simplecov in a block instead of prepending SimpleCov to all config methods
168
- # you're calling.
169
- #
170
- # SimpleCov.configure do
171
- # add_filter 'foobar'
172
- # end
173
78
  #
174
- # This is equivalent to SimpleCov.add_filter 'foobar' and thus makes it easier to set a bunch of configure
175
- # options at once.
79
+ # Allows you to configure simplecov in a block instead of
80
+ # prepending SimpleCov to each config method. Parameterized blocks retain
81
+ # their caller context and receive this configuration target explicitly.
176
82
  #
177
83
  def configure(&block)
178
- Docile.dsl_eval(self, &block)
84
+ raise ArgumentError, "configuration block required" unless block
85
+
86
+ if block.parameters.empty?
87
+ instance_exec(&(_ = block))
88
+ else
89
+ yield self
90
+ end
91
+ self
179
92
  end
180
93
 
181
94
  #
182
95
  # Gets or sets the behavior to process coverage results.
183
- #
184
- # By default, it will call SimpleCov.result.format!
185
- #
186
- # Configure with:
187
- #
188
- # SimpleCov.at_exit do
189
- # puts "Coverage done"
190
- # SimpleCov.result.format!
191
- # end
96
+ # By default, it stores/merges the current result and formats only
97
+ # from the final reporting process.
192
98
  #
193
99
  def at_exit(&block)
194
- return Proc.new unless running || block_given?
195
-
196
- @at_exit = block if block_given?
197
- @at_exit ||= proc { SimpleCov.result.format! }
198
- end
199
-
200
- # gets or sets the enabled_for_subprocess configuration
201
- # when true, this will inject SimpleCov code into Process.fork
202
- def enable_for_subprocesses(value = nil)
203
- return @enable_for_subprocesses if defined?(@enable_for_subprocesses) && value.nil?
204
-
205
- @enable_for_subprocesses = value || false
100
+ @at_exit = block if block
101
+ configured = @at_exit
102
+ return configured if configured
103
+ return proc {} unless active_session?
104
+
105
+ @at_exit = proc do
106
+ result = SimpleCov.result
107
+ result.format! if result && SimpleCov.merge_finalization_owner?
108
+ end
206
109
  end
207
110
 
208
- # gets the enabled_for_subprocess configuration
209
- def enabled_for_subprocesses?
210
- enable_for_subprocesses
111
+ # Whether SimpleCov has anything to do at exit: the Coverage module
112
+ # is actively tracking, or a `@result` has already been assembled
113
+ # (e.g. by `SimpleCov.collate`, which never starts Coverage).
114
+ def active_session?
115
+ SimpleCov.result? || (defined?(Coverage) && Coverage.running?)
211
116
  end
212
117
 
213
118
  #
214
119
  # Gets or sets the behavior to start a new forked Process.
215
- #
216
- # By default, it will add " (Process #{pid})" to the command_name, and start SimpleCov in quiet mode
217
- #
218
- # Configure with:
219
- #
220
- # SimpleCov.at_fork do |pid|
221
- # SimpleCov.start do
222
- # # This needs a unique name so it won't be ovewritten
223
- # SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
224
- # # be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
225
- # SimpleCov.print_error_status = false
226
- # SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
227
- # SimpleCov.minimum_coverage 0
228
- # # start
229
- # SimpleCov.start
230
- # end
231
- # end
120
+ # Defaults to adding " (subprocess: #{serial})" to command_name and
121
+ # starting SimpleCov in quiet mode.
232
122
  #
233
123
  def at_fork(&block)
234
- @at_fork = block if block_given?
235
- @at_fork ||= lambda { |pid|
236
- # This needs a unique name so it won't be ovewritten
237
- SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
238
- # be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
239
- SimpleCov.print_error_status = false
124
+ @at_fork = block if block
125
+ @at_fork ||= lambda { |_pid|
126
+ # Needs a name that's unique per worker within a run yet identical
127
+ # across runs. Build it from SimpleCov's stable fork serial rather
128
+ # than the OS pid: with the pid, every run produced uniquely-named
129
+ # results that never overwrote the previous run's, so they piled up
130
+ # in .resultset.json until merge_timeout and the merged report's
131
+ # file set drifted from run to run. See issue #1171.
132
+ SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{SimpleCov.subprocess_serial})"
133
+ # be quiet, the parent process will use the regular formatter
134
+ SimpleCov.print_errors false
240
135
  SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
241
136
  SimpleCov.minimum_coverage 0
242
- # start
243
137
  SimpleCov.start
244
138
  }
245
139
  end
246
140
 
247
141
  #
248
- # Returns the project name - currently assuming the last dirname in
249
- # the SimpleCov.root is this.
142
+ # Returns the project name — defaults to the last dirname in
143
+ # SimpleCov.root, capitalized with underscores → spaces.
250
144
  #
251
145
  def project_name(new_name = nil)
252
- return @project_name if defined?(@project_name) && @project_name && new_name.nil?
146
+ current = defined?(@project_name) ? @project_name : nil
147
+ return current if current && new_name.nil?
253
148
 
254
149
  @project_name = new_name if new_name.is_a?(String)
255
- @project_name ||= File.basename(root.split("/").last).capitalize.tr("_", " ")
256
- end
257
-
258
- #
259
- # Defines whether to use result merging so all your test suites (test:units, test:functionals, cucumber, ...)
260
- # are joined and combined into a single coverage report
261
- #
262
- def use_merging(use = nil)
263
- @use_merging = use unless use.nil?
264
- @use_merging = true unless defined?(@use_merging) && @use_merging == false
265
- end
266
-
267
- #
268
- # Defines the maximum age (in seconds) of a resultset to still be included in merged results.
269
- # i.e. If you run cucumber features, then later rake test, if the stored cucumber resultset is
270
- # more seconds ago than specified here, it won't be taken into account when merging (and is also
271
- # purged from the resultset cache)
272
- #
273
- # Of course, this only applies when merging is active (e.g. SimpleCov.use_merging is not false!)
274
- #
275
- # Default is 600 seconds (10 minutes)
276
- #
277
- # Configure with SimpleCov.merge_timeout(3600) # 1hr
278
- #
279
- def merge_timeout(seconds = nil)
280
- @merge_timeout = seconds if seconds.is_a?(Integer)
281
- @merge_timeout ||= 600
282
- end
283
-
284
- #
285
- # Defines the minimum overall coverage required for the testsuite to pass.
286
- # SimpleCov will return non-zero if the current coverage is below this threshold.
287
- #
288
- # Default is 0% (disabled)
289
- #
290
- def minimum_coverage(coverage = nil)
291
- return @minimum_coverage ||= {} unless coverage
292
-
293
- coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
294
-
295
- raise_on_invalid_coverage(coverage, "minimum_coverage")
296
-
297
- @minimum_coverage = coverage
298
- end
299
-
300
- def raise_on_invalid_coverage(coverage, coverage_setting)
301
- coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) }
302
- coverage.each_value do |percent|
303
- minimum_possible_coverage_exceeded(coverage_setting) if percent && percent > 100
304
- end
305
- end
306
-
307
- #
308
- # Defines the maximum coverage drop at once allowed for the testsuite to pass.
309
- # SimpleCov will return non-zero if the coverage decreases by more than this threshold.
310
- #
311
- # Default is 100% (disabled)
312
- #
313
- def maximum_coverage_drop(coverage_drop = nil)
314
- return @maximum_coverage_drop ||= {} unless coverage_drop
315
-
316
- coverage_drop = {primary_coverage => coverage_drop} if coverage_drop.is_a?(Numeric)
317
-
318
- raise_on_invalid_coverage(coverage_drop, "maximum_coverage_drop")
319
-
320
- @maximum_coverage_drop = coverage_drop
321
- end
322
-
323
- #
324
- # Defines the minimum coverage per file required for the testsuite to pass.
325
- # SimpleCov will return non-zero if the current coverage of the least covered file
326
- # is below this threshold.
327
- #
328
- # Default is 0% (disabled)
329
- #
330
- def minimum_coverage_by_file(coverage = nil)
331
- return @minimum_coverage_by_file ||= {} unless coverage
332
-
333
- coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
334
-
335
- raise_on_invalid_coverage(coverage, "minimum_coverage_by_file")
336
-
337
- @minimum_coverage_by_file = coverage
338
- end
339
-
340
- #
341
- # Refuses any coverage drop. That is, coverage is only allowed to increase.
342
- # SimpleCov will return non-zero if the coverage decreases.
343
- #
344
- def refuse_coverage_drop(*criteria)
345
- criteria = coverage_criteria if criteria.empty?
346
-
347
- maximum_coverage_drop(criteria.map { |c| [c, 0] }.to_h)
348
- end
349
-
350
- #
351
- # Add a filter to the processing chain.
352
- # There are four ways to define a filter:
353
- #
354
- # * as a String that will then be matched against all source files' file paths,
355
- # SimpleCov.add_filter 'app/models' # will reject all your models
356
- # * as a block which will be passed the source file in question and should either
357
- # return a true or false value, depending on whether the file should be removed
358
- # SimpleCov.add_filter do |src_file|
359
- # File.basename(src_file.filename) == 'environment.rb'
360
- # end # Will exclude environment.rb files from the results
361
- # * as an array of strings that are matched against all sorce files' file
362
- # paths and then ignored (basically string filter multiple times)
363
- # SimpleCov.add_filter ['app/models', 'app/helpers'] # ignores both dirs
364
- # * as an instance of a subclass of SimpleCov::Filter. See the documentation there
365
- # on how to define your own filter classes
366
- #
367
- def add_filter(filter_argument = nil, &filter_proc)
368
- filters << parse_filter(filter_argument, &filter_proc)
369
- end
370
-
371
- #
372
- # Define a group for files. Works similar to add_filter, only that the first
373
- # argument is the desired group name and files PASSING the filter end up in the group
374
- # (while filters exclude when the filter is applicable).
375
- #
376
- def add_group(group_name, filter_argument = nil, &filter_proc)
377
- groups[group_name] = parse_filter(filter_argument, &filter_proc)
378
- end
379
-
380
- SUPPORTED_COVERAGE_CRITERIA = %i[line branch].freeze
381
- DEFAULT_COVERAGE_CRITERION = :line
382
- #
383
- # Define which coverage criterion should be evaluated.
384
- #
385
- # Possible coverage criteria:
386
- # * :line - coverage based on lines aka has this line been executed?
387
- # * :branch - coverage based on branches aka has this branch (think conditions) been executed?
388
- #
389
- # If not set the default is `:line`
390
- #
391
- # @param [Symbol] criterion
392
- #
393
- def coverage_criterion(criterion = nil)
394
- return @coverage_criterion ||= primary_coverage unless criterion
395
-
396
- raise_if_criterion_unsupported(criterion)
397
-
398
- @coverage_criterion = criterion
399
- end
400
-
401
- def enable_coverage(criterion)
402
- raise_if_criterion_unsupported(criterion)
403
-
404
- coverage_criteria << criterion
405
- end
406
-
407
- def primary_coverage(criterion = nil)
408
- if criterion.nil?
409
- @primary_coverage ||= DEFAULT_COVERAGE_CRITERION
410
- else
411
- raise_if_criterion_disabled(criterion)
412
- @primary_coverage = criterion
413
- end
414
- end
415
-
416
- def coverage_criteria
417
- @coverage_criteria ||= Set[primary_coverage]
418
- end
419
-
420
- def coverage_criterion_enabled?(criterion)
421
- coverage_criteria.member?(criterion)
422
- end
423
-
424
- def clear_coverage_criteria
425
- @coverage_criteria = nil
426
- end
427
-
428
- def branch_coverage?
429
- branch_coverage_supported? && coverage_criterion_enabled?(:branch)
430
- end
431
-
432
- def coverage_start_arguments_supported?
433
- # safe to cache as within one process this value should never
434
- # change
435
- return @coverage_start_arguments_supported if defined?(@coverage_start_arguments_supported)
436
-
437
- @coverage_start_arguments_supported = begin
438
- require "coverage"
439
- !Coverage.method(:start).arity.zero?
440
- end
441
- end
442
-
443
- def branch_coverage_supported?
444
- coverage_start_arguments_supported? && RUBY_ENGINE != "jruby"
445
- end
446
-
447
- def coverage_for_eval_supported?
448
- require "coverage"
449
- defined?(Coverage.supported?) && Coverage.supported?(:eval)
450
- end
451
-
452
- def coverage_for_eval_enabled?
453
- @coverage_for_eval_enabled ||= false
454
- end
455
-
456
- def enable_coverage_for_eval
457
- if coverage_for_eval_supported?
458
- @coverage_for_eval_enabled = true
459
- else
460
- warn "Coverage for eval is not available; Use Ruby 3.2.0 or later"
461
- end
462
- end
463
-
464
- private
465
-
466
- def raise_if_criterion_disabled(criterion)
467
- raise_if_criterion_unsupported(criterion)
468
- # rubocop:disable Style/IfUnlessModifier
469
- unless coverage_criterion_enabled?(criterion)
470
- raise "Coverage criterion #{criterion}, is disabled! Please enable it first through enable_coverage #{criterion} (if supported)"
471
- end
472
- # rubocop:enable Style/IfUnlessModifier
473
- end
474
-
475
- def raise_if_criterion_unsupported(criterion)
476
- # rubocop:disable Style/IfUnlessModifier
477
- unless SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
478
- raise "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
479
- end
480
- # rubocop:enable Style/IfUnlessModifier
481
- end
482
-
483
- def minimum_possible_coverage_exceeded(coverage_option)
484
- warn "The coverage you set for #{coverage_option} is greater than 100%"
485
- end
486
-
487
- #
488
- # The actual filter processor. Not meant for direct use
489
- #
490
- def parse_filter(filter_argument = nil, &filter_proc)
491
- filter = filter_argument || filter_proc
492
-
493
- if filter
494
- SimpleCov::Filter.build_filter(filter)
495
- else
496
- raise ArgumentError, "Please specify either a filter or a block to filter with"
497
- end
150
+ @project_name ||= File.basename(root).capitalize.tr("_", " ")
498
151
  end
499
152
  end
500
153
  end
154
+
155
+ require_relative "configuration/coverage"
156
+ require_relative "configuration/coverage_criteria"
157
+ require_relative "configuration/eval_coverage"
158
+ require_relative "configuration/filters"
159
+ require_relative "configuration/groups"
160
+ require_relative "configuration/formatting"
161
+ require_relative "configuration/ignored_entries"
162
+ require_relative "configuration/merging"
163
+ require_relative "configuration/thresholds"
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module SimpleCov
6
+ # Shared parser for coverage.json consumers. It enforces the encoding and
7
+ # stable outermost shape while leaving command- or viewer-specific fields to
8
+ # their respective callers.
9
+ module CoverageJSON
10
+ class Error < StandardError; end
11
+
12
+ def self.load(path)
13
+ source = File.binread(path).force_encoding(Encoding::UTF_8)
14
+ raise Error, "input is not valid UTF-8" unless source.valid_encoding?
15
+
16
+ document = JSON.parse(source)
17
+ return document if document.is_a?(Hash)
18
+
19
+ raise Error, "top-level value must be an object"
20
+ rescue JSON::ParserError => e
21
+ raise Error, e.message
22
+ end
23
+ end
24
+ end
@@ -8,34 +8,40 @@ module SimpleCov
8
8
  # * total - how many things to cover there are (total relevant loc/branches)
9
9
  # * covered - how many of the coverables are hit
10
10
  # * missed - how many of the coverables are missed
11
+ # * omitted - how many lines cannot be covered (blank lines/comments); only meaningful for line coverage
11
12
  # * percent - percentage as covered/missed
12
- # * strength - average hits per/coverable (will not exist for one shot lines format)
13
+ # * strength - average hits per coverable unit (will not exist for one shot lines format)
13
14
  class CoverageStatistics
14
- attr_reader :total, :covered, :missed, :strength, :percent
15
+ attr_reader :total, :covered, :missed, :omitted, :strength, :percent
16
+
17
+ # Seed for the reduce in `.from`: covered, missed, omitted, strength.
18
+ ZERO_STATS = [0, 0, 0, 0.0].freeze #: [Integer, Integer, Integer, Float]
15
19
 
16
20
  def self.from(coverage_statistics)
17
- sum_covered, sum_missed, sum_total_strength =
18
- coverage_statistics.reduce([0, 0, 0.0]) do |(covered, missed, total_strength), file_coverage_statistics|
21
+ sum_covered, sum_missed, sum_omitted, sum_total_strength =
22
+ coverage_statistics.reduce(ZERO_STATS) do |(covered, missed, omitted, total_strength), stats|
19
23
  [
20
- covered + file_coverage_statistics.covered,
21
- missed + file_coverage_statistics.missed,
24
+ covered + stats.covered,
25
+ missed + stats.missed,
26
+ omitted + stats.omitted,
22
27
  # gotta remultiply with loc because files have different strength and loc
23
28
  # giving them a different "weight" in total
24
- total_strength + (file_coverage_statistics.strength * file_coverage_statistics.total)
29
+ total_strength + (stats.strength * stats.total)
25
30
  ]
26
31
  end
27
32
 
28
- new(covered: sum_covered, missed: sum_missed, total_strength: sum_total_strength)
33
+ new(covered: sum_covered, missed: sum_missed, omitted: sum_omitted, total_strength: sum_total_strength)
29
34
  end
30
35
 
31
36
  # Requires only covered, missed and strength to be initialized.
32
37
  #
33
38
  # Other values are computed by this class.
34
- def initialize(covered:, missed:, total_strength: 0.0)
39
+ def initialize(covered:, missed:, omitted: 0, total_strength: 0.0, percent: nil)
35
40
  @covered = covered
36
41
  @missed = missed
42
+ @omitted = omitted
37
43
  @total = covered + missed
38
- @percent = compute_percent(covered, missed, total)
44
+ @percent = percent || compute_percent(covered, missed, total)
39
45
  @strength = compute_strength(total_strength, total)
40
46
  end
41
47