simplecov 0.17.0 → 0.18.3

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -1
  3. data/CODE_OF_CONDUCT.md +76 -0
  4. data/README.md +275 -76
  5. data/doc/alternate-formatters.md +5 -0
  6. data/lib/minitest/simplecov_plugin.rb +11 -0
  7. data/lib/simplecov.rb +235 -62
  8. data/lib/simplecov/combine.rb +30 -0
  9. data/lib/simplecov/combine/branches_combiner.rb +32 -0
  10. data/lib/simplecov/combine/files_combiner.rb +24 -0
  11. data/lib/simplecov/combine/lines_combiner.rb +43 -0
  12. data/lib/simplecov/combine/results_combiner.rb +60 -0
  13. data/lib/simplecov/command_guesser.rb +6 -3
  14. data/lib/simplecov/configuration.rb +110 -9
  15. data/lib/simplecov/coverage_statistics.rb +56 -0
  16. data/lib/simplecov/defaults.rb +4 -5
  17. data/lib/simplecov/file_list.rb +66 -13
  18. data/lib/simplecov/filter.rb +2 -1
  19. data/lib/simplecov/formatter/multi_formatter.rb +2 -2
  20. data/lib/simplecov/formatter/simple_formatter.rb +4 -4
  21. data/lib/simplecov/last_run.rb +3 -1
  22. data/lib/simplecov/lines_classifier.rb +2 -2
  23. data/lib/simplecov/profiles.rb +9 -7
  24. data/lib/simplecov/result.rb +39 -6
  25. data/lib/simplecov/result_adapter.rb +30 -0
  26. data/lib/simplecov/result_merger.rb +18 -11
  27. data/lib/simplecov/simulate_coverage.rb +29 -0
  28. data/lib/simplecov/source_file.rb +272 -126
  29. data/lib/simplecov/source_file/branch.rb +84 -0
  30. data/lib/simplecov/source_file/line.rb +72 -0
  31. data/lib/simplecov/useless_results_remover.rb +16 -0
  32. data/lib/simplecov/version.rb +1 -1
  33. metadata +33 -166
  34. data/lib/simplecov/jruby_fix.rb +0 -44
  35. data/lib/simplecov/railtie.rb +0 -9
  36. data/lib/simplecov/railties/tasks.rake +0 -13
  37. data/lib/simplecov/raw_coverage.rb +0 -41
@@ -54,3 +54,8 @@ A formatter that prints the coverage of the file under test when you run a singl
54
54
  *by [Yosuke Kabuto](https://github.com/ysksn)*
55
55
 
56
56
  t_wada AA formatter for SimpleCov
57
+
58
+ #### [simplecov-material(https://github.com/chiefpansancolt/simplecov-material)
59
+ *by [Chiefpansancolt](https://github.com/chiefpansancolt)*
60
+
61
+ A Material Designed HTML formatter with clean and easy search of files with a tabular left Navigation.
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # How minitest plugins. See https://github.com/colszowka/simplecov/pull/756 for why we need this.
4
+ # https://github.com/seattlerb/minitest#writing-extensions
5
+ module Minitest
6
+ def self.plugin_simplecov_init(_options)
7
+ Minitest.after_run do
8
+ SimpleCov.at_exit_behavior if SimpleCov.respond_to?(:at_exit_behavior)
9
+ end
10
+ end
11
+ end
@@ -2,9 +2,6 @@
2
2
 
3
3
  require "English"
4
4
 
5
- #
6
- # Code coverage for ruby 1.9. Please check out README for a full introduction.
7
- #
8
5
  # Coverage may be inaccurate under JRUBY.
9
6
  if defined?(JRUBY_VERSION) && defined?(JRuby)
10
7
 
@@ -20,6 +17,10 @@ if defined?(JRUBY_VERSION) && defined?(JRuby)
20
17
  ' or set the "debug.fullTrace=true" option in your .jrubyrc'
21
18
  end
22
19
  end
20
+
21
+ #
22
+ # Code coverage for ruby. Please check out README for a full introduction.
23
+ #
23
24
  module SimpleCov
24
25
  class << self
25
26
  attr_accessor :running
@@ -44,35 +45,49 @@ module SimpleCov
44
45
  # Please check out the RDoc for SimpleCov::Configuration to find about available config options
45
46
  #
46
47
  def start(profile = nil, &block)
47
- if SimpleCov.usable?
48
- load_profile(profile) if profile
49
- configure(&block) if block_given?
50
- @result = nil
51
- self.running = true
52
- self.pid = Process.pid
53
- Coverage.start
54
- else
55
- warn "WARNING: SimpleCov is activated, but you're not running Ruby 1.9+ - no coverage analysis will happen"
56
- warn "Starting with SimpleCov 1.0.0, even no-op compatibility with Ruby <= 1.8 will be entirely dropped."
57
- false
58
- end
48
+ require "coverage"
49
+ initial_setup(profile, &block)
50
+ @result = nil
51
+ self.pid = Process.pid
52
+
53
+ start_coverage_measurement
59
54
  end
60
55
 
61
56
  #
62
- # Finds files that were to be tracked but were not loaded and initializes
63
- # the line-by-line coverage to zero (if relevant) or nil (comments / whitespace etc).
57
+ # Collate a series of SimpleCov result files into a single SimpleCov output.
58
+ # You can optionally specify configuration with a block:
59
+ # SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"]
60
+ # OR
61
+ # SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"], 'rails' # using rails profile
62
+ # OR
63
+ # SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"] do
64
+ # add_filter 'test'
65
+ # end
66
+ # OR
67
+ # SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"], 'rails' do
68
+ # add_filter 'test'
69
+ # end
64
70
  #
65
- def add_not_loaded_files(result)
66
- if tracked_files
67
- result = result.dup
68
- Dir[tracked_files].each do |file|
69
- absolute = File.expand_path(file)
71
+ # Please check out the RDoc for SimpleCov::Configuration to find about
72
+ # available config options, or checkout the README for more in-depth
73
+ # information about coverage collation
74
+ #
75
+ def collate(result_filenames, profile = nil, &block)
76
+ raise "There's no reports to be merged" if result_filenames.empty?
77
+
78
+ initial_setup(profile, &block)
70
79
 
71
- result[absolute] ||= LinesClassifier.new.classify(File.foreach(absolute))
80
+ results = result_filenames.flat_map do |filename|
81
+ # Re-create each included instance of SimpleCov::Result from the stored run data.
82
+ (JSON.parse(File.read(filename)) || {}).map do |command_name, coverage|
83
+ SimpleCov::Result.from_hash(command_name => coverage)
72
84
  end
73
85
  end
74
86
 
75
- result
87
+ # Use the ResultMerger to produce a single, merged result, ready to use.
88
+ @result = SimpleCov::ResultMerger.merge_and_store(*results)
89
+
90
+ run_exit_tasks!
76
91
  end
77
92
 
78
93
  #
@@ -83,9 +98,8 @@ module SimpleCov
83
98
  return @result if result?
84
99
 
85
100
  # Collect our coverage result
86
- if running
87
- @result = SimpleCov::Result.new add_not_loaded_files(Coverage.result)
88
- end
101
+
102
+ process_coverage_result if running
89
103
 
90
104
  # If we're using merging of results, store the current result
91
105
  # first (if there is one), then merge the results and return those
@@ -147,22 +161,6 @@ module SimpleCov
147
161
  load_profile(name)
148
162
  end
149
163
 
150
- #
151
- # Checks whether we're on a proper version of Ruby (likely 1.9+) which
152
- # provides coverage support
153
- #
154
- def usable?
155
- return @usable if defined?(@usable) && !@usable.nil?
156
-
157
- @usable = begin
158
- require "coverage"
159
- require "simplecov/jruby_fix"
160
- true
161
- rescue LoadError
162
- false
163
- end
164
- end
165
-
166
164
  #
167
165
  # Clear out the previously cached .result. Primarily useful in testing
168
166
  #
@@ -191,11 +189,21 @@ module SimpleCov
191
189
  end
192
190
  end
193
191
 
192
+ def at_exit_behavior
193
+ # If we are in a different process than called start, don't interfere.
194
+ return if SimpleCov.pid != Process.pid
195
+
196
+ # If SimpleCov is no longer running then don't run exit tasks
197
+ SimpleCov.run_exit_tasks! if SimpleCov.running
198
+ end
199
+
194
200
  # @api private
195
201
  #
196
202
  # Called from at_exit block
197
203
  #
198
204
  def run_exit_tasks!
205
+ set_exit_exception
206
+
199
207
  exit_status = SimpleCov.exit_status_from_exception
200
208
 
201
209
  SimpleCov.at_exit.call
@@ -206,8 +214,8 @@ module SimpleCov
206
214
 
207
215
  # Force exit with stored status (see github issue #5)
208
216
  # unless it's nil or 0 (see github issue #281)
209
- if exit_status && exit_status > 0
210
- $stderr.printf("SimpleCov failed with exit %d", exit_status)
217
+ if exit_status&.positive?
218
+ $stderr.printf("SimpleCov failed with exit %<exit_status>d\n", exit_status: exit_status) if print_error_status
211
219
  Kernel.exit exit_status
212
220
  end
213
221
  end
@@ -220,11 +228,9 @@ module SimpleCov
220
228
  def process_result(result, exit_status)
221
229
  return exit_status if exit_status != SimpleCov::ExitCodes::SUCCESS # Existing errors
222
230
 
223
- covered_percent = result.covered_percent.round(2)
231
+ covered_percent = result.covered_percent.floor(2)
224
232
  result_exit_status = result_exit_status(result, covered_percent)
225
- if result_exit_status == SimpleCov::ExitCodes::SUCCESS # No result errors
226
- write_last_run(covered_percent)
227
- end
233
+ write_last_run(covered_percent) if result_exit_status == SimpleCov::ExitCodes::SUCCESS # No result errors
228
234
  final_result_process? ? result_exit_status : SimpleCov::ExitCodes::SUCCESS
229
235
  end
230
236
 
@@ -232,17 +238,26 @@ module SimpleCov
232
238
  #
233
239
  # rubocop:disable Metrics/MethodLength
234
240
  def result_exit_status(result, covered_percent)
235
- covered_percentages = result.covered_percentages.map { |percentage| percentage.round(2) }
236
- if covered_percent < SimpleCov.minimum_coverage
237
- $stderr.printf("Coverage (%.2f%%) is below the expected minimum coverage (%.2f%%).\n", covered_percent, SimpleCov.minimum_coverage)
241
+ covered_percentages = result.covered_percentages.map { |percentage| percentage.floor(2) }
242
+ if (minimum_violations = minimum_coverage_violated(result)).any?
243
+ report_minimum_violated(minimum_violations)
238
244
  SimpleCov::ExitCodes::MINIMUM_COVERAGE
239
245
  elsif covered_percentages.any? { |p| p < SimpleCov.minimum_coverage_by_file }
240
- $stderr.printf("File (%s) is only (%.2f%%) covered. This is below the expected minimum coverage per file of (%.2f%%).\n", result.least_covered_file, covered_percentages.min, SimpleCov.minimum_coverage_by_file)
246
+ $stderr.printf(
247
+ "File (%<file>s) is only (%<least_covered_percentage>.2f%%) covered. This is below the expected minimum coverage per file of (%<min_coverage>.2f%%).\n",
248
+ file: result.least_covered_file,
249
+ least_covered_percentage: covered_percentages.min,
250
+ min_coverage: SimpleCov.minimum_coverage_by_file
251
+ )
241
252
  SimpleCov::ExitCodes::MINIMUM_COVERAGE
242
253
  elsif (last_run = SimpleCov::LastRun.read)
243
- coverage_diff = last_run["result"]["covered_percent"] - covered_percent
254
+ coverage_diff = last_run[:result][:covered_percent] - covered_percent
244
255
  if coverage_diff > SimpleCov.maximum_coverage_drop
245
- $stderr.printf("Coverage has dropped by %.2f%% since the last time (maximum allowed: %.2f%%).\n", coverage_diff, SimpleCov.maximum_coverage_drop)
256
+ $stderr.printf(
257
+ "Coverage has dropped by %<drop_percent>.2f%% since the last time (maximum allowed: %<max_drop>.2f%%).\n",
258
+ drop_percent: coverage_diff,
259
+ max_drop: SimpleCov.maximum_coverage_drop
260
+ )
246
261
  SimpleCov::ExitCodes::MAXIMUM_COVERAGE_DROP
247
262
  else
248
263
  SimpleCov::ExitCodes::SUCCESS
@@ -257,7 +272,8 @@ module SimpleCov
257
272
  # @api private
258
273
  #
259
274
  def final_result_process?
260
- !defined?(ParallelTests) || ParallelTests.last_process?
275
+ # checking for ENV["TEST_ENV_NUMBER"] to determine if the tess are being run in parallel
276
+ !defined?(ParallelTests) || !ENV["TEST_ENV_NUMBER"] || ParallelTests.number_of_running_processes <= 1
261
277
  end
262
278
 
263
279
  #
@@ -265,6 +281,7 @@ module SimpleCov
265
281
  #
266
282
  def wait_for_other_processes
267
283
  return unless defined?(ParallelTests) && final_result_process?
284
+
268
285
  ParallelTests.wait_for_other_processes_to_finish
269
286
  end
270
287
 
@@ -272,16 +289,168 @@ module SimpleCov
272
289
  # @api private
273
290
  #
274
291
  def write_last_run(covered_percent)
275
- SimpleCov::LastRun.write(:result => {:covered_percent => covered_percent})
292
+ SimpleCov::LastRun.write(result: {covered_percent: covered_percent})
293
+ end
294
+
295
+ private
296
+
297
+ def initial_setup(profile, &block)
298
+ load_profile(profile) if profile
299
+ configure(&block) if block_given?
300
+ self.running = true
301
+ end
302
+
303
+ #
304
+ # Trigger Coverage.start depends on given config coverage_criterion
305
+ #
306
+ # With Positive branch it supports all coverage measurement types
307
+ # With Negative branch it supports only line coverage measurement type
308
+ #
309
+ def start_coverage_measurement
310
+ # This blog post gives a good run down of the coverage criterias introduced
311
+ # in Ruby 2.5: https://blog.bigbinary.com/2018/04/11/ruby-2-5-supports-measuring-branch-and-method-coverages.html
312
+ # There is also a nice writeup of the different coverage criteria made in this
313
+ # comment https://github.com/colszowka/simplecov/pull/692#discussion_r281836176 :
314
+ # Ruby < 2.5:
315
+ # https://github.com/ruby/ruby/blob/v1_9_3_374/ext/coverage/coverage.c
316
+ # traditional mode (Array)
317
+ #
318
+ # Ruby 2.5:
319
+ # https://bugs.ruby-lang.org/issues/13901
320
+ # https://github.com/ruby/ruby/blob/v2_5_3/ext/coverage/coverage.c
321
+ # default: traditional/compatible mode (Array)
322
+ # :lines - like traditional mode but using Hash
323
+ # :branches
324
+ # :methods
325
+ # :all - same as lines + branches + methods
326
+ #
327
+ # Ruby >= 2.6:
328
+ # https://bugs.ruby-lang.org/issues/15022
329
+ # https://github.com/ruby/ruby/blob/v2_6_3/ext/coverage/coverage.c
330
+ # default: traditional/compatible mode (Array)
331
+ # :lines - like traditional mode but using Hash
332
+ # :branches
333
+ # :methods
334
+ # :oneshot_lines - can not be combined with lines
335
+ # :all - same as lines + branches + methods
336
+ #
337
+ if coverage_start_arguments_supported?
338
+ start_coverage_with_criteria
339
+ else
340
+ Coverage.start
341
+ end
342
+ end
343
+
344
+ def start_coverage_with_criteria
345
+ start_arguments = coverage_criteria.map do |criterion|
346
+ [lookup_corresponding_ruby_coverage_name(criterion), true]
347
+ end.to_h
348
+
349
+ Coverage.start(start_arguments)
350
+ end
351
+
352
+ CRITERION_TO_RUBY_COVERAGE = {
353
+ branch: :branches,
354
+ line: :lines
355
+ }.freeze
356
+ def lookup_corresponding_ruby_coverage_name(criterion)
357
+ CRITERION_TO_RUBY_COVERAGE.fetch(criterion)
358
+ end
359
+
360
+ #
361
+ # Finds files that were to be tracked but were not loaded and initializes
362
+ # the line-by-line coverage to zero (if relevant) or nil (comments / whitespace etc).
363
+ #
364
+ def add_not_loaded_files(result)
365
+ if tracked_files
366
+ result = result.dup
367
+ Dir[tracked_files].each do |file|
368
+ absolute_path = File.expand_path(file)
369
+ result[absolute_path] ||= SimulateCoverage.call(absolute_path)
370
+ end
371
+ end
372
+
373
+ result
374
+ end
375
+
376
+ #
377
+ # Call steps that handle process coverage result
378
+ #
379
+ # @return [Hash]
380
+ #
381
+ def process_coverage_result
382
+ adapt_coverage_result
383
+ remove_useless_results
384
+ result_with_not_loaded_files
385
+ end
386
+
387
+ #
388
+ # Unite the result so it wouldn't matter what coverage type was called
389
+ #
390
+ # @return [Hash]
391
+ #
392
+ def adapt_coverage_result
393
+ @result = SimpleCov::ResultAdapter.call(Coverage.result)
394
+ end
395
+
396
+ #
397
+ # Filter coverage result
398
+ # The result before filter also has result of coverage for files
399
+ # are not related to the project like loaded gems coverage.
400
+ #
401
+ # @return [Hash]
402
+ #
403
+ def remove_useless_results
404
+ @result = SimpleCov::UselessResultsRemover.call(@result)
405
+ end
406
+
407
+ #
408
+ # Initialize result with files that are not included by coverage
409
+ # and added inside the config block
410
+ #
411
+ # @return [Hash]
412
+ #
413
+ def result_with_not_loaded_files
414
+ @result = SimpleCov::Result.new(add_not_loaded_files(@result))
415
+ end
416
+
417
+ def minimum_coverage_violated(result)
418
+ coverage_achieved = minimum_coverage.map do |criterion, percent|
419
+ {
420
+ criterion: criterion,
421
+ minimum_expected: percent,
422
+ actual: result.coverage_statistics[criterion].percent
423
+ }
424
+ end
425
+
426
+ coverage_achieved.select do |achieved|
427
+ achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
428
+ end
429
+ end
430
+
431
+ def report_minimum_violated(violations)
432
+ violations.each do |violation|
433
+ $stderr.printf(
434
+ "%<criterion>s coverage (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
435
+ covered: violation.fetch(:actual).floor(2),
436
+ minimum_coverage: violation.fetch(:minimum_expected),
437
+ criterion: violation.fetch(:criterion).capitalize
438
+ )
439
+ end
276
440
  end
277
441
  end
278
442
  end
279
443
 
280
444
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__)))
445
+ require "set"
446
+ require "forwardable"
281
447
  require "simplecov/configuration"
282
- SimpleCov.send :extend, SimpleCov::Configuration
448
+ SimpleCov.extend SimpleCov::Configuration
449
+ require "simplecov/coverage_statistics"
283
450
  require "simplecov/exit_codes"
284
451
  require "simplecov/profiles"
452
+ require "simplecov/source_file/line"
453
+ require "simplecov/source_file/branch"
285
454
  require "simplecov/source_file"
286
455
  require "simplecov/file_list"
287
456
  require "simplecov/result"
@@ -289,13 +458,17 @@ require "simplecov/filter"
289
458
  require "simplecov/formatter"
290
459
  require "simplecov/last_run"
291
460
  require "simplecov/lines_classifier"
292
- require "simplecov/raw_coverage"
293
461
  require "simplecov/result_merger"
294
462
  require "simplecov/command_guesser"
295
463
  require "simplecov/version"
464
+ require "simplecov/result_adapter"
465
+ require "simplecov/combine"
466
+ require "simplecov/combine/branches_combiner"
467
+ require "simplecov/combine/files_combiner"
468
+ require "simplecov/combine/lines_combiner"
469
+ require "simplecov/combine/results_combiner"
470
+ require "simplecov/useless_results_remover"
471
+ require "simplecov/simulate_coverage"
296
472
 
297
473
  # Load default config
298
474
  require "simplecov/defaults" unless ENV["SIMPLECOV_NO_DEFAULTS"]
299
-
300
- # Load Rails integration (only for Rails 3, see #113)
301
- require "simplecov/railtie" if defined? Rails::Railtie
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ # Functionally for combining coverage results
5
+ #
6
+ module Combine
7
+ module_function
8
+
9
+ #
10
+ # Combine two coverage based on the given combiner_module.
11
+ #
12
+ # Combiners should always be called through this interface,
13
+ # as it takes care of short-circuiting of one of the coverages is nil.
14
+ #
15
+ # @return [Hash]
16
+ def combine(combiner_module, coverage_a, coverage_b)
17
+ return existing_coverage(coverage_a, coverage_b) if empty_coverage?(coverage_a, coverage_b)
18
+
19
+ combiner_module.combine(coverage_a, coverage_b)
20
+ end
21
+
22
+ def empty_coverage?(coverage_a, coverage_b)
23
+ !(coverage_a && coverage_b)
24
+ end
25
+
26
+ def existing_coverage(coverage_a, coverage_b)
27
+ coverage_a || coverage_b
28
+ end
29
+ end
30
+ end