simplecov 0.18.2 → 0.19.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,53 @@ 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
+ return @enable_for_subprocesses if defined?(@enable_for_subprocesses) && value.nil?
203
+
204
+ @enable_for_subprocesses = value || false
205
+ end
206
+
207
+ # gets the enabled_for_subprocess configuration
208
+ def enabled_for_subprocesses?
209
+ enable_for_subprocesses
210
+ end
211
+
212
+ #
213
+ # Gets or sets the behavior to start a new forked Process.
214
+ #
215
+ # By default, it will add " (Process #{pid})" to the command_name, and start SimpleCov in quiet mode
216
+ #
217
+ # Configure with:
218
+ #
219
+ # SimpleCov.at_fork do |pid|
220
+ # SimpleCov.start do
221
+ # # This needs a unique name so it won't be ovewritten
222
+ # SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
223
+ # # be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
224
+ # SimpleCov.print_error_status = false
225
+ # SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
226
+ # SimpleCov.minimum_coverage 0
227
+ # # start
228
+ # SimpleCov.start
229
+ # end
230
+ # end
231
+ #
232
+ def at_fork(&block)
233
+ @at_fork = block if block_given?
234
+ @at_fork ||= lambda { |pid|
235
+ # This needs a unique name so it won't be ovewritten
236
+ SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
237
+ # be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
238
+ SimpleCov.print_error_status = false
239
+ SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
240
+ SimpleCov.minimum_coverage 0
241
+ # start
242
+ SimpleCov.start
243
+ }
244
+ end
245
+
199
246
  #
200
247
  # Returns the project name - currently assuming the last dirname in
201
248
  # the SimpleCov.root is this.
@@ -239,17 +286,20 @@ module SimpleCov
239
286
  #
240
287
  # Default is 0% (disabled)
241
288
  #
289
+
290
+ # rubocop:disable Metrics/CyclomaticComplexity
242
291
  def minimum_coverage(coverage = nil)
243
292
  return @minimum_coverage ||= {} unless coverage
244
293
 
245
294
  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|
295
+ coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) }
296
+ coverage.each_value do |percent|
248
297
  minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
249
298
  end
250
299
 
251
300
  @minimum_coverage = coverage
252
301
  end
302
+ # rubocop:enable Metrics/CyclomaticComplexity
253
303
 
254
304
  #
255
305
  # 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
@@ -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"
@@ -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