simplecov 0.20.0 → 0.21.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.
data/README.md CHANGED
@@ -295,7 +295,6 @@ information to be lost.
295
295
  Add branch coverage measurement statistics to your results. Supported in CRuby versions 2.5+.
296
296
 
297
297
  ```ruby
298
- # or in configure or just SimpleCov.enable_coverage :branch
299
298
  SimpleCov.start do
300
299
  enable_coverage :branch
301
300
  end
@@ -341,6 +340,22 @@ Hence, we recommend looking at both metrics together. Branch coverage might also
341
340
  overall metric to look at - while you might be missing only 10% of your lines that might
342
341
  account for 50% of your branches for instance.
343
342
 
343
+ ## Primary Coverage
344
+
345
+ By default, the primary coverage type is `line`. To set the primary coverage to something else, use the following:
346
+
347
+ ```ruby
348
+ # or in configure SimpleCov.primary_coverage :branch
349
+ SimpleCov.start do
350
+ enable_coverage :branch
351
+ primary_coverage :branch
352
+ end
353
+ ```
354
+
355
+ Primary coverage determines what will come in first all output, and the type of coverage to check if you don't specify the type of coverage when customizing exit behavior (`SimpleCov.minimum_coverage 90`).
356
+
357
+ Note that coverage must first be enabled for non-default coverage types.
358
+
344
359
  ## Filters
345
360
 
346
361
  Filters can be used to remove selected files from your coverage data. By default, a filter is applied that removes all
@@ -787,30 +802,36 @@ to help ensure coverage is relatively consistent, rather than being skewed by pa
787
802
 
788
803
  ```ruby
789
804
  SimpleCov.minimum_coverage_by_file 80
805
+ # same as above (the default is to check line coverage by file)
806
+ SimpleCov.minimum_coverage_by_file line: 80
807
+ # check for a minimum line coverage by file of 90% and minimum 80% branch coverage
808
+ SimpleCov.minimum_coverage_by_file line: 90, branch: 80
790
809
  ```
791
810
 
792
- (not yet supported for branch coverage)
793
-
794
811
  ### Maximum coverage drop
795
812
 
796
813
  You can define the maximum coverage drop percentage at once. SimpleCov will return non-zero if exceeded.
797
814
 
798
815
  ```ruby
799
816
  SimpleCov.maximum_coverage_drop 5
817
+ # same as above (the default is to check line drop)
818
+ SimpleCov.maximum_coverage_drop line: 5
819
+ # check for a maximum line drop of 5% and maximum 10% branch drop
820
+ SimpleCov.maximum_coverage_drop line: 5, branch: 10
800
821
  ```
801
822
 
802
- (not yet supported for branch coverage)
803
-
804
823
  ### Refuse dropping coverage
805
824
 
806
825
  You can also entirely refuse dropping coverage between test runs:
807
826
 
808
827
  ```ruby
809
828
  SimpleCov.refuse_coverage_drop
829
+ # same as above (the default is to only refuse line drop)
830
+ SimpleCov.refuse_coverage_drop :line
831
+ # refuse drop for line and branch
832
+ SimpleCov.refuse_coverage_drop :line, :branch
810
833
  ```
811
834
 
812
- (not yet supported for branch coverage)
813
-
814
835
  ## Using your own formatter
815
836
 
816
837
  You can use your own formatter with:
@@ -3,7 +3,7 @@
3
3
  require "English"
4
4
 
5
5
  # Coverage may be inaccurate under JRUBY.
6
- if defined?(JRUBY_VERSION) && defined?(JRuby)
6
+ if defined?(JRUBY_VERSION) && defined?(JRuby) && !org.jruby.RubyInstanceConfig.FULL_TRACE_ENABLED
7
7
 
8
8
  # @see https://github.com/jruby/jruby/issues/1196
9
9
  # @see https://github.com/metricfu/metric_fu/pull/226
@@ -11,11 +11,9 @@ if defined?(JRUBY_VERSION) && defined?(JRuby)
11
11
  # @see https://github.com/simplecov-ruby/simplecov/issues/86
12
12
  # @see https://jira.codehaus.org/browse/JRUBY-6106
13
13
 
14
- unless org.jruby.RubyInstanceConfig.FULL_TRACE_ENABLED
15
- warn 'Coverage may be inaccurate; set the "--debug" command line option,' \
16
- ' or do JRUBY_OPTS="--debug"' \
17
- ' or set the "debug.fullTrace=true" option in your .jrubyrc'
18
- end
14
+ warn 'Coverage may be inaccurate; set the "--debug" command line option,' \
15
+ ' or do JRUBY_OPTS="--debug"' \
16
+ ' or set the "debug.fullTrace=true" option in your .jrubyrc'
19
17
  end
20
18
 
21
19
  #
@@ -63,6 +61,7 @@ module SimpleCov
63
61
 
64
62
  #
65
63
  # Collate a series of SimpleCov result files into a single SimpleCov output.
64
+ #
66
65
  # You can optionally specify configuration with a block:
67
66
  # SimpleCov.collate Dir["simplecov-resultset-*/.resultset.json"]
68
67
  # OR
@@ -80,18 +79,17 @@ module SimpleCov
80
79
  # available config options, or checkout the README for more in-depth
81
80
  # information about coverage collation
82
81
  #
83
- def collate(result_filenames, profile = nil, &block)
84
- raise "There's no reports to be merged" if result_filenames.empty?
82
+ # By default `collate` ignores the merge_timeout so all results of all files specified will be
83
+ # merged together. If you want to honor the merge_timeout then provide the keyword argument
84
+ # `ignore_timeout: false`.
85
+ #
86
+ def collate(result_filenames, profile = nil, ignore_timeout: true, &block)
87
+ raise "There are no reports to be merged" if result_filenames.empty?
85
88
 
86
89
  initial_setup(profile, &block)
87
90
 
88
- results = result_filenames.flat_map do |filename|
89
- # Re-create each included instance of SimpleCov::Result from the stored run data.
90
- Result.from_hash(JSON.parse(File.read(filename)) || {})
91
- end
92
-
93
91
  # Use the ResultMerger to produce a single, merged result, ready to use.
94
- @result = ResultMerger.merge_and_store(*results)
92
+ @result = ResultMerger.merge_and_store(*result_filenames, ignore_timeout: ignore_timeout)
95
93
 
96
94
  run_exit_tasks!
97
95
  end
@@ -285,7 +283,10 @@ module SimpleCov
285
283
  # @api private
286
284
  #
287
285
  def write_last_run(result)
288
- SimpleCov::LastRun.write(result: {covered_percent: round_coverage(result.covered_percent)})
286
+ SimpleCov::LastRun.write(result:
287
+ result.coverage_statistics.transform_values do |stats|
288
+ round_coverage(stats.percent)
289
+ end)
289
290
  end
290
291
 
291
292
  #
@@ -10,7 +10,7 @@ module SimpleCov
10
10
  # defined here are usable from SimpleCov directly. Please check out
11
11
  # SimpleCov documentation for further info.
12
12
  #
13
- module Configuration # rubocop:disable Metrics/ModuleLength
13
+ module Configuration
14
14
  attr_writer :filters, :groups, :formatter, :print_error_status
15
15
 
16
16
  #
@@ -191,7 +191,7 @@ module SimpleCov
191
191
  # end
192
192
  #
193
193
  def at_exit(&block)
194
- return proc {} unless running || block_given?
194
+ return Proc.new unless running || block_given?
195
195
 
196
196
  @at_exit = block if block_given?
197
197
  @at_exit ||= proc { SimpleCov.result.format! }
@@ -287,20 +287,22 @@ module SimpleCov
287
287
  #
288
288
  # Default is 0% (disabled)
289
289
  #
290
-
291
- # rubocop:disable Metrics/CyclomaticComplexity
292
290
  def minimum_coverage(coverage = nil)
293
291
  return @minimum_coverage ||= {} unless coverage
294
292
 
295
- coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric)
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)
296
301
  coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) }
297
302
  coverage.each_value do |percent|
298
- minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
303
+ minimum_possible_coverage_exceeded(coverage_setting) if percent && percent > 100
299
304
  end
300
-
301
- @minimum_coverage = coverage
302
305
  end
303
- # rubocop:enable Metrics/CyclomaticComplexity
304
306
 
305
307
  #
306
308
  # Defines the maximum coverage drop at once allowed for the testsuite to pass.
@@ -309,7 +311,13 @@ module SimpleCov
309
311
  # Default is 100% (disabled)
310
312
  #
311
313
  def maximum_coverage_drop(coverage_drop = nil)
312
- @maximum_coverage_drop ||= (coverage_drop || 100).to_f.round(2)
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
313
321
  end
314
322
 
315
323
  #
@@ -320,16 +328,23 @@ module SimpleCov
320
328
  # Default is 0% (disabled)
321
329
  #
322
330
  def minimum_coverage_by_file(coverage = nil)
323
- minimum_possible_coverage_exceeded("minimum_coverage_by_file") if coverage && coverage > 100
324
- @minimum_coverage_by_file ||= (coverage || 0).to_f.round(2)
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
325
338
  end
326
339
 
327
340
  #
328
341
  # Refuses any coverage drop. That is, coverage is only allowed to increase.
329
342
  # SimpleCov will return non-zero if the coverage decreases.
330
343
  #
331
- def refuse_coverage_drop
332
- maximum_coverage_drop 0
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)
333
348
  end
334
349
 
335
350
  #
@@ -376,7 +391,7 @@ module SimpleCov
376
391
  # @param [Symbol] criterion
377
392
  #
378
393
  def coverage_criterion(criterion = nil)
379
- return @coverage_criterion ||= DEFAULT_COVERAGE_CRITERION unless criterion
394
+ return @coverage_criterion ||= primary_coverage unless criterion
380
395
 
381
396
  raise_if_criterion_unsupported(criterion)
382
397
 
@@ -389,8 +404,17 @@ module SimpleCov
389
404
  coverage_criteria << criterion
390
405
  end
391
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
+
392
416
  def coverage_criteria
393
- @coverage_criteria ||= Set[DEFAULT_COVERAGE_CRITERION]
417
+ @coverage_criteria ||= Set[primary_coverage]
394
418
  end
395
419
 
396
420
  def coverage_criterion_enabled?(criterion)
@@ -11,15 +11,18 @@ module SimpleCov
11
11
  def failing?
12
12
  return false unless maximum_coverage_drop && last_run
13
13
 
14
- coverage_diff > maximum_coverage_drop
14
+ coverage_drop_violations.any?
15
15
  end
16
16
 
17
17
  def report
18
- $stderr.printf(
19
- "Coverage has dropped by %<drop_percent>.2f%% since the last time (maximum allowed: %<max_drop>.2f%%).\n",
20
- drop_percent: coverage_diff,
21
- max_drop: maximum_coverage_drop
22
- )
18
+ coverage_drop_violations.each do |violation|
19
+ $stderr.printf(
20
+ "%<criterion>s coverage has dropped by %<drop_percent>.2f%% since the last time (maximum allowed: %<max_drop>.2f%%).\n",
21
+ criterion: violation[:criterion].capitalize,
22
+ drop_percent: SimpleCov.round_coverage(violation[:drop_percent]),
23
+ max_drop: violation[:max_drop]
24
+ )
25
+ end
23
26
  end
24
27
 
25
28
  def exit_code
@@ -36,14 +39,34 @@ module SimpleCov
36
39
  @last_run = SimpleCov::LastRun.read
37
40
  end
38
41
 
39
- def coverage_diff
40
- raise "Trying to access coverage_diff although there is no last run" unless last_run
42
+ def coverage_drop_violations
43
+ @coverage_drop_violations ||=
44
+ compute_coverage_drop_data.select do |achieved|
45
+ achieved.fetch(:max_drop) < achieved.fetch(:drop_percent)
46
+ end
47
+ end
41
48
 
42
- @coverage_diff ||= last_run[:result][:covered_percent] - covered_percent
49
+ def compute_coverage_drop_data
50
+ maximum_coverage_drop.map do |criterion, percent|
51
+ {
52
+ criterion: criterion,
53
+ max_drop: percent,
54
+ drop_percent: last_coverage(criterion) -
55
+ SimpleCov.round_coverage(
56
+ result.coverage_statistics.fetch(criterion).percent
57
+ )
58
+ }
59
+ end
43
60
  end
44
61
 
45
- def covered_percent
46
- SimpleCov.round_coverage(result.covered_percent)
62
+ def last_coverage(criterion)
63
+ last_coverage_percent = last_run[:result][criterion]
64
+
65
+ if !last_coverage_percent && criterion == "line"
66
+ last_run[:result][:covered_percent]
67
+ else
68
+ last_coverage_percent
69
+ end
47
70
  end
48
71
  end
49
72
  end
@@ -9,16 +9,18 @@ module SimpleCov
9
9
  end
10
10
 
11
11
  def failing?
12
- covered_percentages.any? { |p| p < minimum_coverage_by_file }
12
+ minimum_violations.any?
13
13
  end
14
14
 
15
15
  def report
16
- $stderr.printf(
17
- "File (%<file>s) is only (%<least_covered_percentage>.2f%%) covered. This is below the expected minimum coverage per file of (%<min_coverage>.2f%%).\n",
18
- file: result.least_covered_file,
19
- least_covered_percentage: covered_percentages.min,
20
- min_coverage: minimum_coverage_by_file
21
- )
16
+ minimum_violations.each do |violation|
17
+ $stderr.printf(
18
+ "%<criterion>s coverage by file (%<covered>.2f%%) is below the expected minimum coverage (%<minimum_coverage>.2f%%).\n",
19
+ covered: SimpleCov.round_coverage(violation.fetch(:actual)),
20
+ minimum_coverage: violation.fetch(:minimum_expected),
21
+ criterion: violation.fetch(:criterion).capitalize
22
+ )
23
+ end
22
24
  end
23
25
 
24
26
  def exit_code
@@ -29,9 +31,23 @@ module SimpleCov
29
31
 
30
32
  attr_reader :result, :minimum_coverage_by_file
31
33
 
32
- def covered_percentages
33
- @covered_percentages ||=
34
- result.covered_percentages.map { |percentage| SimpleCov.round_coverage(percentage) }
34
+ def minimum_violations
35
+ @minimum_violations ||=
36
+ compute_minimum_coverage_data.select do |achieved|
37
+ achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
38
+ end
39
+ end
40
+
41
+ def compute_minimum_coverage_data
42
+ minimum_coverage_by_file.flat_map do |criterion, expected_percent|
43
+ result.coverage_statistics_by_file.fetch(criterion).map do |actual_coverage|
44
+ {
45
+ criterion: criterion,
46
+ minimum_expected: expected_percent,
47
+ actual: SimpleCov.round_coverage(actual_coverage.percent)
48
+ }
49
+ end
50
+ end
35
51
  end
36
52
  end
37
53
  end
@@ -27,6 +27,10 @@ module SimpleCov
27
27
  @coverage_statistics ||= compute_coverage_statistics
28
28
  end
29
29
 
30
+ def coverage_statistics_by_file
31
+ @coverage_statistics_by_file ||= compute_coverage_statistics_by_file
32
+ end
33
+
30
34
  # Returns the count of lines that have coverage
31
35
  def covered_lines
32
36
  coverage_statistics[:line]&.covered
@@ -100,14 +104,16 @@ module SimpleCov
100
104
 
101
105
  private
102
106
 
103
- def compute_coverage_statistics
104
- total_coverage_statistics = @files.each_with_object(line: [], branch: []) do |file, together|
105
- together[:line] << file.coverage_statistics[:line]
106
- together[:branch] << file.coverage_statistics[:branch] if SimpleCov.branch_coverage?
107
+ def compute_coverage_statistics_by_file
108
+ @files.each_with_object(line: [], branch: []) do |file, together|
109
+ together[:line] << file.coverage_statistics.fetch(:line)
110
+ together[:branch] << file.coverage_statistics.fetch(:branch) if SimpleCov.branch_coverage?
107
111
  end
112
+ end
108
113
 
109
- coverage_statistics = {line: CoverageStatistics.from(total_coverage_statistics[:line])}
110
- coverage_statistics[:branch] = CoverageStatistics.from(total_coverage_statistics[:branch]) if SimpleCov.branch_coverage?
114
+ def compute_coverage_statistics
115
+ coverage_statistics = {line: CoverageStatistics.from(coverage_statistics_by_file[:line])}
116
+ coverage_statistics[:branch] = CoverageStatistics.from(coverage_statistics_by_file[:branch]) if SimpleCov.branch_coverage?
111
117
  coverage_statistics
112
118
  end
113
119
  end
@@ -20,13 +20,13 @@ module SimpleCov
20
20
  # Explicitly set the command name that was used for this coverage result. Defaults to SimpleCov.command_name
21
21
  attr_writer :command_name
22
22
 
23
- def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches, :coverage_statistics
23
+ def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches, :coverage_statistics, :coverage_statistics_by_file
24
24
  def_delegator :files, :lines_of_code, :total_lines
25
25
 
26
26
  # Initialize a new SimpleCov::Result from given Coverage.result (a Hash of filenames each containing an array of
27
27
  # coverage data)
28
28
  def initialize(original_result, command_name: nil, created_at: nil)
29
- result = adapt_result(original_result)
29
+ result = original_result
30
30
  @original_result = result.freeze
31
31
  @command_name = command_name
32
32
  @created_at = created_at
@@ -72,10 +72,6 @@ module SimpleCov
72
72
  }
73
73
  end
74
74
 
75
- def time_since_creation
76
- Time.now - created_at
77
- end
78
-
79
75
  # Loads a SimpleCov::Result#to_hash dump
80
76
  def self.from_hash(hash)
81
77
  hash.map do |command_name, data|
@@ -85,31 +81,6 @@ module SimpleCov
85
81
 
86
82
  private
87
83
 
88
- # We changed the format of the raw result data in simplecov, as people are likely
89
- # to have "old" resultsets lying around (but not too old so that they're still
90
- # considered we can adapt them).
91
- # See https://github.com/simplecov-ruby/simplecov/pull/824#issuecomment-576049747
92
- def adapt_result(result)
93
- if pre_simplecov_0_18_result?(result)
94
- adapt_pre_simplecov_0_18_result(result)
95
- else
96
- result
97
- end
98
- end
99
-
100
- # pre 0.18 coverage data pointed from file directly to an array of line coverage
101
- def pre_simplecov_0_18_result?(result)
102
- _key, data = result.first
103
-
104
- data.is_a?(Array)
105
- end
106
-
107
- def adapt_pre_simplecov_0_18_result(result)
108
- result.transform_values do |line_coverage_data|
109
- {"lines" => line_coverage_data}
110
- end
111
- end
112
-
113
84
  def coverage
114
85
  keys = original_result.keys & filenames
115
86
  Hash[keys.zip(original_result.values_at(*keys))]