simplecov 0.18.5 → 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.
@@ -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.
@@ -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
@@ -28,7 +28,7 @@ at_exit do
28
28
  end
29
29
 
30
30
  # Autoload config from ~/.simplecov if present
31
- require "simplecov/load_global_config"
31
+ require_relative "load_global_config"
32
32
 
33
33
  # Autoload config from .simplecov if present
34
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"
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Process
4
+ class << self
5
+ def fork_with_simplecov(&block)
6
+ if defined?(SimpleCov) && SimpleCov.running
7
+ fork_without_simplecov do
8
+ SimpleCov.at_fork.call(Process.pid)
9
+ block.call if block_given?
10
+ end
11
+ else
12
+ fork_without_simplecov(&block)
13
+ end
14
+ end
15
+
16
+ alias fork_without_simplecov fork
17
+ alias fork fork_with_simplecov
18
+ end
19
+ end
@@ -25,9 +25,11 @@ module SimpleCov
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
- def initialize(original_result)
28
+ def initialize(original_result, command_name: nil, created_at: nil)
29
29
  result = adapt_result(original_result)
30
30
  @original_result = result.freeze
31
+ @command_name = command_name
32
+ @created_at = created_at
31
33
  @files = SimpleCov::FileList.new(result.map do |filename, coverage|
32
34
  SimpleCov::SourceFile.new(filename, JSON.parse(JSON.dump(coverage))) if File.file?(filename)
33
35
  end.compact.sort_by(&:filename))
@@ -70,15 +72,15 @@ module SimpleCov
70
72
  }
71
73
  end
72
74
 
75
+ def time_since_creation
76
+ Time.now - created_at
77
+ end
78
+
73
79
  # Loads a SimpleCov::Result#to_hash dump
74
80
  def self.from_hash(hash)
75
- command_name, data = hash.first
76
-
77
- result = SimpleCov::Result.new(data["coverage"])
78
-
79
- result.command_name = command_name
80
- result.created_at = Time.at(data["timestamp"])
81
- result
81
+ hash.map do |command_name, data|
82
+ new(data.fetch("coverage"), command_name: command_name, created_at: Time.at(data["timestamp"]))
83
+ end
82
84
  end
83
85
 
84
86
  private
@@ -86,7 +88,7 @@ module SimpleCov
86
88
  # We changed the format of the raw result data in simplecov, as people are likely
87
89
  # to have "old" resultsets lying around (but not too old so that they're still
88
90
  # considered we can adapt them).
89
- # See https://github.com/colszowka/simplecov/pull/824#issuecomment-576049747
91
+ # See https://github.com/simplecov-ruby/simplecov/pull/824#issuecomment-576049747
90
92
  def adapt_result(result)
91
93
  if pre_simplecov_0_18_result?(result)
92
94
  adapt_pre_simplecov_0_18_result(result)
@@ -103,9 +105,9 @@ module SimpleCov
103
105
  end
104
106
 
105
107
  def adapt_pre_simplecov_0_18_result(result)
106
- result.map do |file_path, line_coverage_data|
107
- [file_path, {"lines" => line_coverage_data}]
108
- end.to_h
108
+ result.transform_values do |line_coverage_data|
109
+ {"lines" => line_coverage_data}
110
+ end
109
111
  end
110
112
 
111
113
  def coverage