simplecov 0.18.0 → 0.19.0

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.
@@ -9,8 +9,8 @@ module SimpleCov
9
9
  #
10
10
  # Combine two coverage based on the given combiner_module.
11
11
  #
12
- # Combiners should always be called throught his interface,
13
- # as it takes care of short circuting of one of the coverages is nil.
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
14
  #
15
15
  # @return [Hash]
16
16
  def combine(combiner_module, coverage_a, coverage_b)
@@ -10,9 +10,9 @@ module SimpleCov
10
10
  module_function
11
11
 
12
12
  #
13
- # Return merged branches or the existed branche if other is missing.
13
+ # Return merged branches or the existed brach if other is missing.
14
14
  #
15
- # Branches inside files are always same if they exists, the difference only in coverage count.
15
+ # Branches inside files are always same if they exist, the difference only in coverage count.
16
16
  # Branch coverage report for any conditional case is built from hash, it's key is a condition and
17
17
  # it's body is a hash << keys from condition and value is coverage rate >>.
18
18
  # ex: branches =>{ [:if, 3, 8, 6, 8, 36] => {[:then, 4, 8, 6, 8, 12] => 1, [:else, 5, 8, 6, 8, 36]=>2}, other conditions...}
@@ -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
  #
@@ -196,6 +196,52 @@ module SimpleCov
196
196
  @at_exit ||= proc { SimpleCov.result.format! }
197
197
  end
198
198
 
199
+ # gets or sets the enabled_for_subprocess configuration
200
+ # when true, this will inject SimpleCov code into Process.fork
201
+ def enable_for_subprocesses(value = nil)
202
+ @enable_for_subprocesses = value unless value.nil?
203
+ @enable_for_subprocesses || false
204
+ end
205
+
206
+ # gets the enabled_for_subprocess configuration
207
+ def enabled_for_subprocesses?
208
+ enable_for_subprocesses
209
+ end
210
+
211
+ #
212
+ # Gets or sets the behavior to start a new forked Process.
213
+ #
214
+ # By default, it will add " (Process #{pid})" to the command_name, and start SimpleCov in quiet mode
215
+ #
216
+ # Configure with:
217
+ #
218
+ # SimpleCov.at_fork do |pid|
219
+ # SimpleCov.start do
220
+ # # This needs a unique name so it won't be ovewritten
221
+ # SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
222
+ # # be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
223
+ # SimpleCov.print_error_status = false
224
+ # SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
225
+ # SimpleCov.minimum_coverage 0
226
+ # # start
227
+ # SimpleCov.start
228
+ # end
229
+ # end
230
+ #
231
+ def at_fork(&block)
232
+ @at_fork = block if block_given?
233
+ @at_fork ||= lambda { |pid|
234
+ # This needs a unique name so it won't be ovewritten
235
+ SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
236
+ # be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
237
+ SimpleCov.print_error_status = false
238
+ SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
239
+ SimpleCov.minimum_coverage 0
240
+ # start
241
+ SimpleCov.start
242
+ }
243
+ end
244
+
199
245
  #
200
246
  # Returns the project name - currently assuming the last dirname in
201
247
  # the SimpleCov.root is this.
@@ -239,17 +285,20 @@ module SimpleCov
239
285
  #
240
286
  # Default is 0% (disabled)
241
287
  #
288
+
289
+ # rubocop:disable Metrics/CyclomaticComplexity
242
290
  def minimum_coverage(coverage = nil)
243
291
  return @minimum_coverage ||= {} unless coverage
244
292
 
245
293
  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|
294
+ coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) }
295
+ coverage.each_value do |percent|
248
296
  minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
249
297
  end
250
298
 
251
299
  @minimum_coverage = coverage
252
300
  end
301
+ # rubocop:enable Metrics/CyclomaticComplexity
253
302
 
254
303
  #
255
304
  # Defines the maximum coverage drop at once allowed for the testsuite to pass.
@@ -19,7 +19,7 @@ module SimpleCov
19
19
  [
20
20
  covered + file_coverage_statistics.covered,
21
21
  missed + file_coverage_statistics.missed,
22
- # gotta remultiply with loc because files have different strenght and loc
22
+ # gotta remultiply with loc because files have different strength and loc
23
23
  # giving them a different "weight" in total
24
24
  total_strength + (file_coverage_statistics.strength * file_coverage_statistics.total)
25
25
  ]
@@ -35,14 +35,14 @@ module SimpleCov
35
35
  @covered = covered
36
36
  @missed = missed
37
37
  @total = covered + missed
38
- @percent = compute_percent(covered, total)
39
- @strength = compute_strength(total_strength, @total)
38
+ @percent = compute_percent(covered, missed, total)
39
+ @strength = compute_strength(total_strength, total)
40
40
  end
41
41
 
42
42
  private
43
43
 
44
- def compute_percent(covered, total)
45
- return 100.0 if total.zero?
44
+ def compute_percent(covered, missed, total)
45
+ return 100.0 if missed.zero?
46
46
 
47
47
  covered * 100.0 / total
48
48
  end
@@ -3,11 +3,11 @@
3
3
  # Load default formatter gem
4
4
  require "simplecov-html"
5
5
  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"
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
@@ -22,17 +22,13 @@ end
22
22
  SimpleCov::CommandGuesser.original_run_command = "#{$PROGRAM_NAME} #{ARGV.join(' ')}"
23
23
 
24
24
  at_exit do
25
- # If we are in a different process than called start, don't interfere.
26
- next if SimpleCov.pid != Process.pid
25
+ next if SimpleCov.external_at_exit?
27
26
 
28
- # If SimpleCov is no longer running then don't run exit tasks
29
- next unless SimpleCov.running
30
-
31
- SimpleCov.run_exit_tasks!
27
+ SimpleCov.at_exit_behavior
32
28
  end
33
29
 
34
30
  # Autoload config from ~/.simplecov if present
35
- require "simplecov/load_global_config"
31
+ require_relative "load_global_config"
36
32
 
37
33
  # Autoload config from .simplecov if present
38
34
  # 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,50 @@
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_diff > maximum_coverage_drop
15
+ end
16
+
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
+ )
23
+ end
24
+
25
+ def exit_code
26
+ SimpleCov::ExitCodes::MAXIMUM_COVERAGE_DROP
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :result, :maximum_coverage_drop
32
+
33
+ def last_run
34
+ return @last_run if defined?(@last_run)
35
+
36
+ @last_run = SimpleCov::LastRun.read
37
+ end
38
+
39
+ def coverage_diff
40
+ raise "Trying to access coverage_diff although there is no last run" unless last_run
41
+
42
+ @coverage_diff ||= last_run[:result][:covered_percent] - covered_percent
43
+ end
44
+
45
+ def covered_percent
46
+ SimpleCov.round_coverage(result.covered_percent)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,38 @@
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
+ covered_percentages.any? { |p| p < minimum_coverage_by_file }
13
+ end
14
+
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
+ )
22
+ end
23
+
24
+ def exit_code
25
+ SimpleCov::ExitCodes::MINIMUM_COVERAGE
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :result, :minimum_coverage_by_file
31
+
32
+ def covered_percentages
33
+ @covered_percentages ||=
34
+ result.covered_percentages.map { |percentage| SimpleCov.round_coverage(percentage) }
35
+ end
36
+ end
37
+ end
38
+ 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
@@ -14,6 +14,7 @@ module SimpleCov
14
14
  #
15
15
  class Filter
16
16
  attr_reader :filter_argument
17
+
17
18
  def initialize(filter_argument)
18
19
  @filter_argument = filter_argument
19
20
  end
@@ -34,13 +35,14 @@ module SimpleCov
34
35
  end
35
36
 
36
37
  def self.class_for_argument(filter_argument)
37
- if filter_argument.is_a?(String)
38
+ case filter_argument
39
+ when String
38
40
  SimpleCov::StringFilter
39
- elsif filter_argument.is_a?(Regexp)
41
+ when Regexp
40
42
  SimpleCov::RegexFilter
41
- elsif filter_argument.is_a?(Array)
43
+ when Array
42
44
  SimpleCov::ArrayFilter
43
- elsif filter_argument.is_a?(Proc)
45
+ when Proc
44
46
  SimpleCov::BlockFilter
45
47
  else
46
48
  raise ArgumentError, "You have provided an unrecognized filter type"
@@ -50,7 +52,7 @@ module SimpleCov
50
52
 
51
53
  class StringFilter < SimpleCov::Filter
52
54
  # Returns true when the given source file's filename matches the
53
- # string configured when initializing this Filter with StringFilter.new('somestring)
55
+ # string configured when initializing this Filter with StringFilter.new('somestring')
54
56
  def matches?(source_file)
55
57
  source_file.project_filename.include?(filter_argument)
56
58
  end
@@ -6,5 +6,5 @@ module SimpleCov
6
6
  end
7
7
  end
8
8
 
9
- require "simplecov/formatter/simple_formatter"
10
- require "simplecov/formatter/multi_formatter"
9
+ require_relative "formatter/simple_formatter"
10
+ require_relative "formatter/multi_formatter"
@@ -6,12 +6,10 @@ module SimpleCov
6
6
  module InstanceMethods
7
7
  def format(result)
8
8
  formatters.map do |formatter|
9
- begin
10
- formatter.new.format(result)
11
- rescue StandardError => e
12
- warn("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{e.backtrace.first})")
13
- nil
14
- end
9
+ formatter.new.format(result)
10
+ rescue StandardError => e
11
+ warn("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{e.backtrace.first})")
12
+ nil
15
13
  end
16
14
  end
17
15
  end
@@ -27,7 +25,7 @@ module SimpleCov
27
25
 
28
26
  def self.[](*args)
29
27
  warn "#{Kernel.caller.first}: [DEPRECATION] ::[] is deprecated. Use ::new instead."
30
- new(Array([*args]))
28
+ new(Array(args))
31
29
  end
32
30
  end
33
31
  end
@@ -13,7 +13,7 @@ module SimpleCov
13
13
  WHITESPACE_OR_COMMENT_LINE = Regexp.union(WHITESPACE_LINE, COMMENT_LINE)
14
14
 
15
15
  def self.no_cov_line
16
- /^(\s*)#(\s*)(\:#{SimpleCov.nocov_token}\:)/o
16
+ /^(\s*)#(\s*)(:#{SimpleCov.nocov_token}:)/o
17
17
  end
18
18
 
19
19
  def self.no_cov_line?(line)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  ENV["SIMPLECOV_NO_DEFAULTS"] = "yes, no defaults"
4
- require "simplecov"
4
+ require_relative "../simplecov"