simplecov 0.22.0 → 1.0.2

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 (119) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +1058 -508
  4. data/doc/alternate-formatters.md +0 -5
  5. data/doc/commercial-services.md +5 -5
  6. data/exe/simplecov +11 -0
  7. data/lib/minitest/simplecov_plugin.rb +13 -5
  8. data/lib/simplecov/autostart.rb +11 -0
  9. data/lib/simplecov/cli/clean.rb +47 -0
  10. data/lib/simplecov/cli/coverage.rb +93 -0
  11. data/lib/simplecov/cli/diff.rb +152 -0
  12. data/lib/simplecov/cli/dotfile.rb +105 -0
  13. data/lib/simplecov/cli/merge.rb +118 -0
  14. data/lib/simplecov/cli/open.rb +50 -0
  15. data/lib/simplecov/cli/report.rb +89 -0
  16. data/lib/simplecov/cli/run.rb +36 -0
  17. data/lib/simplecov/cli/serve.rb +149 -0
  18. data/lib/simplecov/cli/uncovered.rb +108 -0
  19. data/lib/simplecov/cli.rb +150 -0
  20. data/lib/simplecov/color.rb +79 -0
  21. data/lib/simplecov/combine/branches_combiner.rb +28 -5
  22. data/lib/simplecov/combine/files_combiner.rb +47 -1
  23. data/lib/simplecov/combine/lines_combiner.rb +19 -17
  24. data/lib/simplecov/combine/methods_combiner.rb +49 -0
  25. data/lib/simplecov/combine/results_combiner.rb +7 -5
  26. data/lib/simplecov/command_guesser.rb +46 -32
  27. data/lib/simplecov/configuration/coverage.rb +175 -0
  28. data/lib/simplecov/configuration/coverage_criteria.rb +158 -0
  29. data/lib/simplecov/configuration/filters.rb +197 -0
  30. data/lib/simplecov/configuration/formatting.rb +129 -0
  31. data/lib/simplecov/configuration/ignored_entries.rb +63 -0
  32. data/lib/simplecov/configuration/merging.rb +164 -0
  33. data/lib/simplecov/configuration/thresholds.rb +178 -0
  34. data/lib/simplecov/configuration.rb +105 -412
  35. data/lib/simplecov/coverage_statistics.rb +15 -9
  36. data/lib/simplecov/coverage_violations.rb +149 -0
  37. data/lib/simplecov/defaults.rb +29 -22
  38. data/lib/simplecov/deprecation.rb +45 -0
  39. data/lib/simplecov/directive.rb +167 -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 +15 -0
  47. data/lib/simplecov/exit_handling.rb +158 -0
  48. data/lib/simplecov/file_list.rb +70 -19
  49. data/lib/simplecov/filter.rb +69 -24
  50. data/lib/simplecov/formatter/base.rb +102 -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 +85 -0
  58. data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +90 -0
  59. data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +128 -0
  60. data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
  61. data/lib/simplecov/formatter/json_formatter.rb +89 -0
  62. data/lib/simplecov/formatter/multi_formatter.rb +11 -7
  63. data/lib/simplecov/formatter/simple_formatter.rb +11 -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 +25 -13
  67. data/lib/simplecov/load_global_config.rb +9 -4
  68. data/lib/simplecov/parallel_adapters/base.rb +61 -0
  69. data/lib/simplecov/parallel_adapters/generic.rb +44 -0
  70. data/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
  71. data/lib/simplecov/parallel_adapters.rb +83 -0
  72. data/lib/simplecov/parallel_coordination.rb +129 -0
  73. data/lib/simplecov/process.rb +52 -12
  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 +108 -19
  84. data/lib/simplecov/result_adapter.rb +105 -8
  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 +59 -91
  89. data/lib/simplecov/result_processing.rb +179 -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 +9 -4
  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 +60 -0
  98. data/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
  99. data/lib/simplecov/source_file/skip_chunks.rb +75 -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/condition_folding.rb +49 -0
  104. data/lib/simplecov/static_coverage_extractor/location_conventions.rb +247 -0
  105. data/lib/simplecov/static_coverage_extractor/method_collector.rb +55 -0
  106. data/lib/simplecov/static_coverage_extractor/value_position.rb +82 -0
  107. data/lib/simplecov/static_coverage_extractor/visitor.rb +196 -0
  108. data/lib/simplecov/static_coverage_extractor.rb +110 -0
  109. data/lib/simplecov/useless_results_remover.rb +16 -7
  110. data/lib/simplecov/version.rb +1 -1
  111. data/lib/simplecov-html.rb +4 -0
  112. data/lib/simplecov.rb +178 -380
  113. data/lib/simplecov_json_formatter.rb +4 -0
  114. data/schemas/coverage-v1.0.schema.json +306 -0
  115. data/schemas/coverage.schema.json +306 -0
  116. data/sig/simplecov.rbs +1638 -0
  117. metadata +94 -57
  118. data/CHANGELOG.md +0 -191
  119. 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,164 @@ 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
91
- end
92
-
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
33
+ @coverage_path = nil unless @coverage_path_explicit # invalidate cache
34
+ @coverage_dir_explicit = true unless dir.nil?
35
+ @coverage_dir = dir || "coverage"
105
36
  end
106
37
 
107
38
  #
108
- # Sets the configured formatters.
109
- #
110
- def formatters=(formatters)
111
- @formatter = SimpleCov::Formatter::MultiFormatter.new(formatters)
112
- end
113
-
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.
114
43
  #
115
- # Gets the configured formatters.
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.
116
51
  #
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
59
 
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
60
+ @coverage_path ||= File.expand_path(coverage_dir, root)
131
61
  end
132
62
 
133
63
  #
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
-
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").
147
67
  #
148
- # Returns the configured groups. Add groups using SimpleCov.add_group
149
- #
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.
176
81
  #
177
82
  def configure(&block)
178
- Docile.dsl_eval(self, &block)
83
+ # Both locals are read in the ensure clause, where flow analysis
84
+ # cannot see the assignments below; anchor their types up front.
85
+ saved = nil # : Hash[Symbol, untyped]?
86
+ block_context = block.binding.receiver # : untyped
87
+
88
+ # If the block was defined in our own context, instance_exec is
89
+ # sufficient. The `_ =` erases the block's self-binding, which RBS
90
+ # can express but `instance_exec`'s core signature cannot accept.
91
+ return instance_exec(&(_ = block)) if equal?(block_context)
92
+
93
+ # Copy the caller's instance variables in so that references like @filter
94
+ # inside the block resolve to the caller's values, not ours.
95
+ saved = swap_ivars_from(block_context)
96
+ instance_exec(&(_ = block))
97
+ ensure
98
+ # @type var block_context: untyped
99
+ # @type var saved: Hash[Symbol, untyped]?
100
+ restore_ivars(block_context, saved) if saved
179
101
  end
180
102
 
181
103
  #
182
104
  # 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
105
+ # By default, it stores/merges the current result and formats only
106
+ # from the final reporting process.
192
107
  #
193
108
  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
109
+ @at_exit = block if block
110
+ configured = @at_exit
111
+ return configured if configured
112
+ return proc {} unless active_session?
113
+
114
+ @at_exit = proc do
115
+ result = SimpleCov.result
116
+ result.format! if result && SimpleCov.merge_finalization_owner? && SimpleCov.final_result_process?
117
+ end
206
118
  end
207
119
 
208
- # gets the enabled_for_subprocess configuration
209
- def enabled_for_subprocesses?
210
- enable_for_subprocesses
120
+ # Whether SimpleCov has anything to do at exit: the Coverage module
121
+ # is actively tracking, or a `@result` has already been assembled
122
+ # (e.g. by `SimpleCov.collate`, which never starts Coverage).
123
+ def active_session?
124
+ SimpleCov.result? || (defined?(Coverage) && Coverage.running?)
211
125
  end
212
126
 
213
127
  #
214
128
  # 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
129
+ # Defaults to adding " (subprocess: #{serial})" to command_name and
130
+ # starting SimpleCov in quiet mode.
232
131
  #
233
132
  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
133
+ @at_fork = block if block
134
+ @at_fork ||= lambda { |_pid|
135
+ # Needs a name that's unique per worker within a run yet identical
136
+ # across runs. Build it from SimpleCov's stable fork serial rather
137
+ # than the OS pid: with the pid, every run produced uniquely-named
138
+ # results that never overwrote the previous run's, so they piled up
139
+ # in .resultset.json until merge_timeout and the merged report's
140
+ # file set drifted from run to run. See issue #1171.
141
+ SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{SimpleCov.subprocess_serial})"
142
+ # be quiet, the parent process will use the regular formatter
143
+ SimpleCov.print_errors false
240
144
  SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
241
145
  SimpleCov.minimum_coverage 0
242
- # start
243
146
  SimpleCov.start
244
147
  }
245
148
  end
246
149
 
247
150
  #
248
- # Returns the project name - currently assuming the last dirname in
249
- # the SimpleCov.root is this.
151
+ # Returns the project name defaults to the last dirname in
152
+ # SimpleCov.root, capitalized with underscores → spaces.
250
153
  #
251
154
  def project_name(new_name = nil)
252
- return @project_name if defined?(@project_name) && @project_name && new_name.nil?
155
+ current = defined?(@project_name) ? @project_name : nil
156
+ return current if current && new_name.nil?
253
157
 
254
158
  @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
159
+ @project_name ||= File.basename(root).capitalize.tr("_", " ")
462
160
  end
463
161
 
464
162
  private
465
163
 
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)"
164
+ # Copy instance variables from block_context into self, saving any
165
+ # of ours that would be clobbered. Returns the saved values for
166
+ # later restoration.
167
+ def swap_ivars_from(block_context)
168
+ saved = {} # : Hash[Symbol, untyped]
169
+ our_ivars = instance_variables
170
+ block_context.instance_variables.each do |ivar|
171
+ saved[ivar] = instance_variable_get(ivar) if our_ivars.include?(ivar)
172
+ instance_variable_set(ivar, block_context.instance_variable_get(ivar))
471
173
  end
472
- # rubocop:enable Style/IfUnlessModifier
174
+ saved
473
175
  end
474
176
 
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"
177
+ # Copy instance variables back to block_context and restore our saved values.
178
+ def restore_ivars(block_context, saved)
179
+ block_context.instance_variables.each do |ivar|
180
+ block_context.instance_variable_set(ivar, instance_variable_get(ivar))
497
181
  end
182
+ saved.each { |ivar, value| instance_variable_set(ivar, value) }
498
183
  end
499
184
  end
500
185
  end
186
+
187
+ require_relative "configuration/coverage"
188
+ require_relative "configuration/coverage_criteria"
189
+ require_relative "configuration/filters"
190
+ require_relative "configuration/formatting"
191
+ require_relative "configuration/ignored_entries"
192
+ require_relative "configuration/merging"
193
+ require_relative "configuration/thresholds"
@@ -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
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
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