simplecov 0.18.5 → 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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "fileutils"
4
4
  require "docile"
5
- require "simplecov/formatter/multi_formatter"
5
+ require_relative "formatter/multi_formatter"
6
6
 
7
7
  module SimpleCov
8
8
  #
@@ -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
  #
@@ -22,6 +22,7 @@ module SimpleCov
22
22
  def root(root = nil)
23
23
  return @root if defined?(@root) && root.nil?
24
24
 
25
+ @coverage_path = nil # invalidate cache
25
26
  @root = File.expand_path(root || Dir.getwd)
26
27
  end
27
28
 
@@ -190,12 +191,59 @@ module SimpleCov
190
191
  # end
191
192
  #
192
193
  def at_exit(&block)
193
- return proc {} unless running || block_given?
194
+ return Proc.new unless running || block_given?
194
195
 
195
196
  @at_exit = block if block_given?
196
197
  @at_exit ||= proc { SimpleCov.result.format! }
197
198
  end
198
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
206
+ end
207
+
208
+ # gets the enabled_for_subprocess configuration
209
+ def enabled_for_subprocesses?
210
+ enable_for_subprocesses
211
+ end
212
+
213
+ #
214
+ # 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
232
+ #
233
+ 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
240
+ SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
241
+ SimpleCov.minimum_coverage 0
242
+ # start
243
+ SimpleCov.start
244
+ }
245
+ end
246
+
199
247
  #
200
248
  # Returns the project name - currently assuming the last dirname in
201
249
  # the SimpleCov.root is this.
@@ -242,15 +290,20 @@ module SimpleCov
242
290
  def minimum_coverage(coverage = nil)
243
291
  return @minimum_coverage ||= {} unless coverage
244
292
 
245
- coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric)
246
- coverage.keys.each { |criterion| raise_if_criterion_disabled(criterion) }
247
- coverage.values.each do |percent|
248
- minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
249
- end
293
+ coverage = {primary_coverage => coverage} if coverage.is_a?(Numeric)
294
+
295
+ raise_on_invalid_coverage(coverage, "minimum_coverage")
250
296
 
251
297
  @minimum_coverage = coverage
252
298
  end
253
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
+
254
307
  #
255
308
  # Defines the maximum coverage drop at once allowed for the testsuite to pass.
256
309
  # SimpleCov will return non-zero if the coverage decreases by more than this threshold.
@@ -258,7 +311,13 @@ module SimpleCov
258
311
  # Default is 100% (disabled)
259
312
  #
260
313
  def maximum_coverage_drop(coverage_drop = nil)
261
- @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
262
321
  end
263
322
 
264
323
  #
@@ -269,16 +328,23 @@ module SimpleCov
269
328
  # Default is 0% (disabled)
270
329
  #
271
330
  def minimum_coverage_by_file(coverage = nil)
272
- minimum_possible_coverage_exceeded("minimum_coverage_by_file") if coverage && coverage > 100
273
- @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
274
338
  end
275
339
 
276
340
  #
277
341
  # Refuses any coverage drop. That is, coverage is only allowed to increase.
278
342
  # SimpleCov will return non-zero if the coverage decreases.
279
343
  #
280
- def refuse_coverage_drop
281
- 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)
282
348
  end
283
349
 
284
350
  #
@@ -325,7 +391,7 @@ module SimpleCov
325
391
  # @param [Symbol] criterion
326
392
  #
327
393
  def coverage_criterion(criterion = nil)
328
- return @coverage_criterion ||= DEFAULT_COVERAGE_CRITERION unless criterion
394
+ return @coverage_criterion ||= primary_coverage unless criterion
329
395
 
330
396
  raise_if_criterion_unsupported(criterion)
331
397
 
@@ -338,8 +404,17 @@ module SimpleCov
338
404
  coverage_criteria << criterion
339
405
  end
340
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
+
341
416
  def coverage_criteria
342
- @coverage_criteria ||= Set[DEFAULT_COVERAGE_CRITERION]
417
+ @coverage_criteria ||= Set[primary_coverage]
343
418
  end
344
419
 
345
420
  def coverage_criterion_enabled?(criterion)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "simplecov-html"
4
+ module SimpleCov
5
+ module Formatter
6
+ class << self
7
+ def from_env(env)
8
+ formatters = [SimpleCov::Formatter::HTMLFormatter]
9
+
10
+ # When running under a CI that uses CodeClimate, JSON output is expected
11
+ if env.fetch("CC_TEST_REPORTER_ID", nil)
12
+ require "simplecov_json_formatter"
13
+ formatters.push(SimpleCov::Formatter::JSONFormatter)
14
+ end
15
+
16
+ formatters
17
+ end
18
+ end
19
+ end
20
+ end
@@ -1,17 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Load default formatter gem
4
- require "simplecov-html"
5
4
  require "pathname"
6
- require "simplecov/profiles/root_filter"
7
- require "simplecov/profiles/test_frameworks"
8
- require "simplecov/profiles/bundler_filter"
9
- require "simplecov/profiles/hidden_filter"
10
- require "simplecov/profiles/rails"
5
+ require_relative "default_formatter"
6
+ require_relative "profiles/root_filter"
7
+ require_relative "profiles/test_frameworks"
8
+ require_relative "profiles/bundler_filter"
9
+ require_relative "profiles/hidden_filter"
10
+ require_relative "profiles/rails"
11
11
 
12
12
  # Default configuration
13
13
  SimpleCov.configure do
14
- formatter SimpleCov::Formatter::HTMLFormatter
14
+ formatter SimpleCov::Formatter::MultiFormatter.new(
15
+ SimpleCov::Formatter.from_env(ENV)
16
+ )
17
+
15
18
  load_profile "bundler_filter"
16
19
  load_profile "hidden_filter"
17
20
  # Exclude files outside of SimpleCov.root
@@ -28,7 +31,7 @@ at_exit do
28
31
  end
29
32
 
30
33
  # Autoload config from ~/.simplecov if present
31
- require "simplecov/load_global_config"
34
+ require_relative "load_global_config"
32
35
 
33
36
  # Autoload config from .simplecov if present
34
37
  # Recurse upwards until we find .simplecov or reach the root directory
@@ -8,3 +8,8 @@ module SimpleCov
8
8
  MAXIMUM_COVERAGE_DROP = 3
9
9
  end
10
10
  end
11
+
12
+ require_relative "exit_codes/exit_code_handling"
13
+ require_relative "exit_codes/maximum_coverage_drop_check"
14
+ require_relative "exit_codes/minimum_coverage_by_file_check"
15
+ require_relative "exit_codes/minimum_overall_coverage_check"
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module ExitCodes
5
+ module ExitCodeHandling
6
+ module_function
7
+
8
+ def call(result, coverage_limits:)
9
+ checks = coverage_checks(result, coverage_limits)
10
+
11
+ failing_check = checks.find(&:failing?)
12
+ if failing_check
13
+ failing_check.report
14
+ failing_check.exit_code
15
+ else
16
+ SimpleCov::ExitCodes::SUCCESS
17
+ end
18
+ end
19
+
20
+ def coverage_checks(result, coverage_limits)
21
+ [
22
+ MinimumOverallCoverageCheck.new(result, coverage_limits.minimum_coverage),
23
+ MinimumCoverageByFileCheck.new(result, coverage_limits.minimum_coverage_by_file),
24
+ MaximumCoverageDropCheck.new(result, coverage_limits.maximum_coverage_drop)
25
+ ]
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module ExitCodes
5
+ class MaximumCoverageDropCheck
6
+ def initialize(result, maximum_coverage_drop)
7
+ @result = result
8
+ @maximum_coverage_drop = maximum_coverage_drop
9
+ end
10
+
11
+ def failing?
12
+ return false unless maximum_coverage_drop && last_run
13
+
14
+ coverage_drop_violations.any?
15
+ end
16
+
17
+ def report
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
26
+ end
27
+
28
+ def exit_code
29
+ SimpleCov::ExitCodes::MAXIMUM_COVERAGE_DROP
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :result, :maximum_coverage_drop
35
+
36
+ def last_run
37
+ return @last_run if defined?(@last_run)
38
+
39
+ @last_run = SimpleCov::LastRun.read
40
+ end
41
+
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
48
+
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
60
+ end
61
+
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
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module ExitCodes
5
+ class MinimumCoverageByFileCheck
6
+ def initialize(result, minimum_coverage_by_file)
7
+ @result = result
8
+ @minimum_coverage_by_file = minimum_coverage_by_file
9
+ end
10
+
11
+ def failing?
12
+ minimum_violations.any?
13
+ end
14
+
15
+ def report
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
24
+ end
25
+
26
+ def exit_code
27
+ SimpleCov::ExitCodes::MINIMUM_COVERAGE
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :result, :minimum_coverage_by_file
33
+
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
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ module ExitCodes
5
+ class MinimumOverallCoverageCheck
6
+ def initialize(result, minimum_coverage)
7
+ @result = result
8
+ @minimum_coverage = minimum_coverage
9
+ end
10
+
11
+ def failing?
12
+ minimum_violations.any?
13
+ end
14
+
15
+ def report
16
+ minimum_violations.each do |violation|
17
+ $stderr.printf(
18
+ "%<criterion>s coverage (%<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
24
+ end
25
+
26
+ def exit_code
27
+ SimpleCov::ExitCodes::MINIMUM_COVERAGE
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :result, :minimum_coverage
33
+
34
+ def minimum_violations
35
+ @minimum_violations ||= calculate_minimum_violations
36
+ end
37
+
38
+ def calculate_minimum_violations
39
+ coverage_achieved = minimum_coverage.map do |criterion, percent|
40
+ {
41
+ criterion: criterion,
42
+ minimum_expected: percent,
43
+ actual: result.coverage_statistics.fetch(criterion).percent
44
+ }
45
+ end
46
+
47
+ coverage_achieved.select do |achieved|
48
+ achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end