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
@@ -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,145 @@ 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")
33
+ @coverage_path = nil unless @coverage_path_explicit # invalidate cache
34
+ @coverage_dir = dir || "coverage"
39
35
  end
40
36
 
41
37
  #
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
-
38
+ # Returns the full path to the output directory. By default
39
+ # constructed from `SimpleCov.root` + `SimpleCov.coverage_dir`, but
40
+ # callers can override with an arbitrary absolute path — handy for
41
+ # out-of-tree build directories. See #716.
54
42
  #
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.
43
+ # Reading is pure: the directory is only created when a path is
44
+ # explicitly assigned (the user has signaled they intend to write
45
+ # there). The codepaths that actually write into the directory
46
+ # (formatters, `LastRun`, `ResultsetStore`) ensure existence
47
+ # themselves, so read-only CLI subcommands that interpolate the
48
+ # path into status text don't materialize a stray `coverage/`
49
+ # directory.
58
50
  #
59
- def track_files(glob)
60
- @tracked_files = glob
61
- end
51
+ def coverage_path(path = nil)
52
+ if path
53
+ @coverage_path = File.expand_path(path)
54
+ @coverage_path_explicit = true
55
+ FileUtils.mkdir_p @coverage_path
56
+ end
62
57
 
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)
58
+ @coverage_path ||= File.expand_path(coverage_dir, root)
69
59
  end
70
60
 
71
61
  #
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.
62
+ # The name of the command (a.k.a. Test Suite) currently running.
63
+ # Used for result merging and caching. Auto-detected; set explicitly
64
+ # with SimpleCov.command_name("test:units").
84
65
  #
85
- # You can specify it manually with SimpleCov.command_name("test:units") - please
86
- # also check out the corresponding section in README.rdoc
87
66
  def command_name(name = nil)
88
67
  @name = name unless name.nil?
89
68
  @name ||= SimpleCov::CommandGuesser.guess
90
69
  @name
91
70
  end
92
71
 
93
- #
94
- # Gets or sets the configured formatter.
95
- #
96
- # Configure with: SimpleCov.formatter(SimpleCov::Formatter::SimpleFormatter)
97
- #
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)
122
- 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
-
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")
144
- end
145
- alias skip_token nocov_token
146
-
147
- #
148
- # Returns the configured groups. Add groups using SimpleCov.add_group
149
- #
150
- def groups
151
- @groups ||= {}
152
- end
153
-
154
- #
155
72
  # Returns the hash of available profiles
156
- #
157
73
  def profiles
158
74
  @profiles ||= SimpleCov::Profiles.new
159
75
  end
160
76
 
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
77
  #
170
- # SimpleCov.configure do
171
- # add_filter 'foobar'
172
- # end
173
- #
174
- # This is equivalent to SimpleCov.add_filter 'foobar' and thus makes it easier to set a bunch of configure
175
- # options at once.
78
+ # Allows you to configure simplecov in a block instead of
79
+ # prepending SimpleCov to each config method.
176
80
  #
177
81
  def configure(&block)
178
- Docile.dsl_eval(self, &block)
82
+ block_context = block.binding.receiver
83
+
84
+ # If the block was defined in our own context, instance_exec is sufficient
85
+ return instance_exec(&block) if equal?(block_context)
86
+
87
+ # Copy the caller's instance variables in so that references like @filter
88
+ # inside the block resolve to the caller's values, not ours.
89
+ saved = swap_ivars_from(block_context)
90
+ instance_exec(&block)
91
+ ensure
92
+ restore_ivars(block_context, saved) if defined?(saved) && saved
179
93
  end
180
94
 
181
95
  #
182
96
  # 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
97
+ # By default, it calls SimpleCov.result.format!
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?
100
+ @at_exit = block if block
101
+ return @at_exit if @at_exit
102
+ return proc {} unless active_session?
204
103
 
205
- @enable_for_subprocesses = value || false
104
+ @at_exit = proc { SimpleCov.result.format! }
206
105
  end
207
106
 
208
- # gets the enabled_for_subprocess configuration
209
- def enabled_for_subprocesses?
210
- enable_for_subprocesses
107
+ # Whether SimpleCov has anything to do at exit: the Coverage module
108
+ # is actively tracking, or a `@result` has already been assembled
109
+ # (e.g. by `SimpleCov.collate`, which never starts Coverage).
110
+ def active_session?
111
+ SimpleCov.result? || (defined?(Coverage) && Coverage.running?)
211
112
  end
212
113
 
213
114
  #
214
115
  # 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
116
+ # Defaults to adding " (subprocess: #{pid})" to command_name and
117
+ # starting SimpleCov in quiet mode.
232
118
  #
233
119
  def at_fork(&block)
234
- @at_fork = block if block_given?
120
+ @at_fork = block if block
235
121
  @at_fork ||= lambda { |pid|
236
- # This needs a unique name so it won't be ovewritten
122
+ # This needs a unique name so it won't be overwritten
237
123
  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
+ # be quiet, the parent process will use the regular formatter
125
+ SimpleCov.print_errors false
240
126
  SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
241
127
  SimpleCov.minimum_coverage 0
242
- # start
243
128
  SimpleCov.start
244
129
  }
245
130
  end
246
131
 
247
132
  #
248
- # Returns the project name - currently assuming the last dirname in
249
- # the SimpleCov.root is this.
133
+ # Returns the project name defaults to the last dirname in
134
+ # SimpleCov.root, capitalized with underscores → spaces.
250
135
  #
251
136
  def project_name(new_name = nil)
252
137
  return @project_name if defined?(@project_name) && @project_name && new_name.nil?
253
138
 
254
139
  @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
140
+ @project_name ||= File.basename(root).capitalize.tr("_", " ")
462
141
  end
463
142
 
464
143
  private
465
144
 
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)"
145
+ # Copy instance variables from block_context into self, saving any
146
+ # of ours that would be clobbered. Returns the saved values for
147
+ # later restoration.
148
+ def swap_ivars_from(block_context)
149
+ saved = {}
150
+ our_ivars = instance_variables
151
+ block_context.instance_variables.each do |ivar|
152
+ saved[ivar] = instance_variable_get(ivar) if our_ivars.include?(ivar)
153
+ instance_variable_set(ivar, block_context.instance_variable_get(ivar))
471
154
  end
472
- # rubocop:enable Style/IfUnlessModifier
155
+ saved
473
156
  end
474
157
 
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"
158
+ # Copy instance variables back to block_context and restore our saved values.
159
+ def restore_ivars(block_context, saved)
160
+ block_context.instance_variables.each do |ivar|
161
+ block_context.instance_variable_set(ivar, instance_variable_get(ivar))
497
162
  end
163
+ saved.each { |ivar, value| instance_variable_set(ivar, value) }
498
164
  end
499
165
  end
500
166
  end
167
+
168
+ require_relative "configuration/coverage"
169
+ require_relative "configuration/coverage_criteria"
170
+ require_relative "configuration/filters"
171
+ require_relative "configuration/formatting"
172
+ require_relative "configuration/ignored_entries"
173
+ require_relative "configuration/merging"
174
+ require_relative "configuration/thresholds"
@@ -8,34 +8,37 @@ 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
13
  # * strength - average hits per/coverable (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
15
16
 
16
17
  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|
18
+ sum_covered, sum_missed, sum_omitted, sum_total_strength =
19
+ coverage_statistics.reduce([0, 0, 0, 0.0]) do |(covered, missed, omitted, total_strength), stats|
19
20
  [
20
- covered + file_coverage_statistics.covered,
21
- missed + file_coverage_statistics.missed,
21
+ covered + stats.covered,
22
+ missed + stats.missed,
23
+ omitted + stats.omitted,
22
24
  # gotta remultiply with loc because files have different strength and loc
23
25
  # giving them a different "weight" in total
24
- total_strength + (file_coverage_statistics.strength * file_coverage_statistics.total)
26
+ total_strength + (stats.strength * stats.total)
25
27
  ]
26
28
  end
27
29
 
28
- new(covered: sum_covered, missed: sum_missed, total_strength: sum_total_strength)
30
+ new(covered: sum_covered, missed: sum_missed, omitted: sum_omitted, total_strength: sum_total_strength)
29
31
  end
30
32
 
31
33
  # Requires only covered, missed and strength to be initialized.
32
34
  #
33
35
  # Other values are computed by this class.
34
- def initialize(covered:, missed:, total_strength: 0.0)
36
+ def initialize(covered:, missed:, omitted: 0, total_strength: 0.0, percent: nil)
35
37
  @covered = covered
36
38
  @missed = missed
39
+ @omitted = omitted
37
40
  @total = covered + missed
38
- @percent = compute_percent(covered, missed, total)
41
+ @percent = percent || compute_percent(covered, missed, total)
39
42
  @strength = compute_strength(total_strength, total)
40
43
  end
41
44