simplecov 0.20.0 → 0.21.1

Sign up to get free protection for your applications and to get access to all the features.
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))]