simplecov 0.17.1 → 0.18.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # Helper that tries to find out what test suite is running (for SimpleCov.command_name)
5
- #
6
3
  module SimpleCov
4
+ #
5
+ # Helper that tries to find out what test suite is running (for SimpleCov.command_name)
6
+ #
7
7
  module CommandGuesser
8
8
  class << self
9
9
  # Storage for the original command line call that invoked the test suite.
@@ -22,6 +22,7 @@ module SimpleCov
22
22
  def from_env
23
23
  # If being run from inside parallel_tests set the command name according to the process number
24
24
  return unless ENV["PARALLEL_TEST_GROUPS"] && ENV["TEST_ENV_NUMBER"]
25
+
25
26
  number = ENV["TEST_ENV_NUMBER"]
26
27
  number = "1" if number.empty?
27
28
  "(#{number}/#{ENV['PARALLEL_TEST_GROUPS']})"
@@ -48,6 +49,8 @@ module SimpleCov
48
49
  "RSpec"
49
50
  elsif defined?(Test::Unit)
50
51
  "Unit Tests"
52
+ elsif defined?(Minitest)
53
+ "Minitest"
51
54
  elsif defined?(MiniTest)
52
55
  "MiniTest"
53
56
  else
@@ -3,14 +3,15 @@
3
3
  require "fileutils"
4
4
  require "docile"
5
5
  require "simplecov/formatter/multi_formatter"
6
- #
7
- # Bundles the configuration options used for SimpleCov. All methods
8
- # defined here are usable from SimpleCov directly. Please check out
9
- # SimpleCov documentation for further info.
10
- #
6
+
11
7
  module SimpleCov
12
- module Configuration # rubocop:disable ModuleLength
13
- attr_writer :filters, :groups, :formatter
8
+ #
9
+ # Bundles the configuration options used for SimpleCov. All methods
10
+ # defined here are usable from SimpleCov directly. Please check out
11
+ # SimpleCov documentation for further info.
12
+ #
13
+ module Configuration # rubocop:disable Metrics/ModuleLength
14
+ attr_writer :filters, :groups, :formatter, :print_error_status
14
15
 
15
16
  #
16
17
  # The root for the project. This defaults to the
@@ -20,6 +21,7 @@ module SimpleCov
20
21
  #
21
22
  def root(root = nil)
22
23
  return @root if defined?(@root) && root.nil?
24
+
23
25
  @root = File.expand_path(root || Dir.getwd)
24
26
  end
25
27
 
@@ -30,6 +32,7 @@ module SimpleCov
30
32
  #
31
33
  def coverage_dir(dir = nil)
32
34
  return @coverage_dir if defined?(@coverage_dir) && dir.nil?
35
+
33
36
  @coverage_path = nil # invalidate cache
34
37
  @coverage_dir = (dir || "coverage")
35
38
  end
@@ -93,8 +96,10 @@ module SimpleCov
93
96
  #
94
97
  def formatter(formatter = nil)
95
98
  return @formatter if defined?(@formatter) && formatter.nil?
99
+
96
100
  @formatter = formatter
97
101
  raise "No formatter configured. Please specify a formatter using SimpleCov.formatter = SimpleCov::Formatter::SimpleFormatter" unless @formatter
102
+
98
103
  @formatter
99
104
  end
100
105
 
@@ -116,6 +121,14 @@ module SimpleCov
116
121
  end
117
122
  end
118
123
 
124
+ #
125
+ # Whether we should print non-success status codes. This can be
126
+ # configured with the #print_error_status= method.
127
+ #
128
+ def print_error_status
129
+ defined?(@print_error_status) ? @print_error_status : true
130
+ end
131
+
119
132
  #
120
133
  # Certain code blocks (i.e. Ruby-implementation specific code) can be excluded from
121
134
  # the coverage metrics by wrapping it inside # :nocov: comment blocks. The nocov token
@@ -125,6 +138,7 @@ module SimpleCov
125
138
  #
126
139
  def nocov_token(nocov_token = nil)
127
140
  return @nocov_token if defined?(@nocov_token) && nocov_token.nil?
141
+
128
142
  @nocov_token = (nocov_token || "nocov")
129
143
  end
130
144
  alias skip_token nocov_token
@@ -160,7 +174,6 @@ module SimpleCov
160
174
  # options at once.
161
175
  #
162
176
  def configure(&block)
163
- return false unless SimpleCov.usable?
164
177
  Docile.dsl_eval(self, &block)
165
178
  end
166
179
 
@@ -178,6 +191,7 @@ module SimpleCov
178
191
  #
179
192
  def at_exit(&block)
180
193
  return proc {} unless running || block_given?
194
+
181
195
  @at_exit = block if block_given?
182
196
  @at_exit ||= proc { SimpleCov.result.format! }
183
197
  end
@@ -188,6 +202,7 @@ module SimpleCov
188
202
  #
189
203
  def project_name(new_name = nil)
190
204
  return @project_name if defined?(@project_name) && @project_name && new_name.nil?
205
+
191
206
  @project_name = new_name if new_name.is_a?(String)
192
207
  @project_name ||= File.basename(root.split("/").last).capitalize.tr("_", " ")
193
208
  end
@@ -225,6 +240,7 @@ module SimpleCov
225
240
  # Default is 0% (disabled)
226
241
  #
227
242
  def minimum_coverage(coverage = nil)
243
+ minimum_possible_coverage_exceeded("minimum_coverage") if coverage && coverage > 100
228
244
  @minimum_coverage ||= (coverage || 0).to_f.round(2)
229
245
  end
230
246
 
@@ -246,6 +262,7 @@ module SimpleCov
246
262
  # Default is 0% (disabled)
247
263
  #
248
264
  def minimum_coverage_by_file(coverage = nil)
265
+ minimum_possible_coverage_exceeded("minimum_coverage_by_file") if coverage && coverage > 100
249
266
  @minimum_coverage_by_file ||= (coverage || 0).to_f.round(2)
250
267
  end
251
268
 
@@ -287,8 +304,60 @@ module SimpleCov
287
304
  groups[group_name] = parse_filter(filter_argument, &filter_proc)
288
305
  end
289
306
 
307
+ SUPPORTED_COVERAGE_CRITERIA = %i[line branch].freeze
308
+ DEFAULT_COVERAGE_CRITERION = :line
309
+ #
310
+ # Define which coverage criterion should be evaluated.
311
+ #
312
+ # Possible coverage criteria:
313
+ # * :line - coverage based on lines aka has this line been executed?
314
+ # * :branch - coverage based on branches aka has this branch (think conditions) been executed?
315
+ #
316
+ # If not set the default is is `:line`
317
+ #
318
+ # @param [Symbol] criterion
319
+ #
320
+ def coverage_criterion(criterion = nil)
321
+ return @coverage_criterion ||= DEFAULT_COVERAGE_CRITERION unless criterion
322
+
323
+ raise_if_criterion_unsupported(criterion)
324
+
325
+ @coverage_criterion = criterion
326
+ end
327
+
328
+ def enable_coverage(criterion)
329
+ raise_if_criterion_unsupported(criterion)
330
+
331
+ coverage_criteria << criterion
332
+ end
333
+
334
+ def coverage_criteria
335
+ @coverage_criteria ||= Set[DEFAULT_COVERAGE_CRITERION]
336
+ end
337
+
338
+ def branch_coverage?
339
+ branch_coverage_supported? && coverage_criteria.member?(:branch)
340
+ end
341
+
342
+ def branch_coverage_supported?
343
+ require "coverage"
344
+ !Coverage.method(:start).arity.zero?
345
+ end
346
+
290
347
  private
291
348
 
349
+ def raise_if_criterion_unsupported(criterion)
350
+ raise_criterion_unsupported(criterion) unless SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
351
+ end
352
+
353
+ def raise_criterion_unsupported(criterion)
354
+ raise "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
355
+ end
356
+
357
+ def minimum_possible_coverage_exceeded(coverage_option)
358
+ warn "The coverage you set for #{coverage_option} is greater than 100%"
359
+ end
360
+
292
361
  #
293
362
  # The actual filter processor. Not meant for direct use
294
363
  #
@@ -42,7 +42,7 @@ loop do
42
42
  begin
43
43
  load filename
44
44
  rescue LoadError, StandardError
45
- $stderr.puts "Warning: Error occurred while trying to load #{filename}. " \
45
+ warn "Warning: Error occurred while trying to load #{filename}. " \
46
46
  "Error message: #{$!.message}"
47
47
  end
48
48
  break
@@ -1,30 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # An array of SimpleCov SourceFile instances with additional collection helper
4
- # methods for calculating coverage across them etc.
5
3
  module SimpleCov
4
+ # An array of SimpleCov SourceFile instances with additional collection helper
5
+ # methods for calculating coverage across them etc.
6
6
  class FileList < Array
7
7
  # Returns the count of lines that have coverage
8
8
  def covered_lines
9
9
  return 0.0 if empty?
10
+
10
11
  map { |f| f.covered_lines.count }.inject(:+)
11
12
  end
12
13
 
13
14
  # Returns the count of lines that have been missed
14
15
  def missed_lines
15
16
  return 0.0 if empty?
17
+
16
18
  map { |f| f.missed_lines.count }.inject(:+)
17
19
  end
18
20
 
19
21
  # Returns the count of lines that are not relevant for coverage
20
22
  def never_lines
21
23
  return 0.0 if empty?
24
+
22
25
  map { |f| f.never_lines.count }.inject(:+)
23
26
  end
24
27
 
25
28
  # Returns the count of skipped lines
26
29
  def skipped_lines
27
30
  return 0.0 if empty?
31
+
28
32
  map { |f| f.skipped_lines.count }.inject(:+)
29
33
  end
30
34
 
@@ -36,7 +40,7 @@ module SimpleCov
36
40
 
37
41
  # Finds the least covered file and returns that file's name
38
42
  def least_covered_file
39
- sort_by(&:covered_percent).first.filename
43
+ min_by(&:covered_percent).filename
40
44
  end
41
45
 
42
46
  # Returns the overall amount of relevant lines of code across all files in this list
@@ -48,6 +52,7 @@ module SimpleCov
48
52
  # @return [Float]
49
53
  def covered_percent
50
54
  return 100.0 if empty? || lines_of_code.zero?
55
+
51
56
  Float(covered_lines * 100.0 / lines_of_code)
52
57
  end
53
58
 
@@ -55,7 +60,29 @@ module SimpleCov
55
60
  # @return [Float]
56
61
  def covered_strength
57
62
  return 0.0 if empty? || lines_of_code.zero?
63
+
58
64
  Float(map { |f| f.covered_strength * f.lines_of_code }.inject(:+) / lines_of_code)
59
65
  end
66
+
67
+ # Return total count of branches in all files
68
+ def total_branches
69
+ return 0 if empty?
70
+
71
+ map { |file| file.total_branches.count }.inject(:+)
72
+ end
73
+
74
+ # Return total count of covered branches
75
+ def covered_branches
76
+ return 0 if empty?
77
+
78
+ map { |file| file.covered_branches.count }.inject(:+)
79
+ end
80
+
81
+ # Return total count of covered branches
82
+ def missed_branches
83
+ return 0 if empty?
84
+
85
+ map { |file| file.missed_branches.count }.inject(:+)
86
+ end
60
87
  end
61
88
  end
@@ -18,7 +18,7 @@ module SimpleCov
18
18
  @filter_argument = filter_argument
19
19
  end
20
20
 
21
- def matches?(_)
21
+ def matches?(_source_file)
22
22
  raise "The base filter class is not intended for direct use"
23
23
  end
24
24
 
@@ -29,6 +29,7 @@ module SimpleCov
29
29
 
30
30
  def self.build_filter(filter_argument)
31
31
  return filter_argument if filter_argument.is_a?(SimpleCov::Filter)
32
+
32
33
  class_for_argument(filter_argument).new(filter_argument)
33
34
  end
34
35
 
@@ -8,8 +8,8 @@ module SimpleCov
8
8
  formatters.map do |formatter|
9
9
  begin
10
10
  formatter.new.format(result)
11
- rescue => e
12
- STDERR.puts("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{e.backtrace.first})")
11
+ rescue StandardError => e
12
+ warn("Formatter #{formatter} failed with #{e.class}: #{e.message} (#{e.backtrace.first})")
13
13
  nil
14
14
  end
15
15
  end
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # A ridiculously simple formatter for SimpleCov results.
5
- #
6
3
  module SimpleCov
7
4
  module Formatter
5
+ #
6
+ # A ridiculously simple formatter for SimpleCov results.
7
+ #
8
8
  class SimpleFormatter
9
9
  # Takes a SimpleCov::Result and generates a string out of it
10
10
  def format(result)
11
- output = "".dup
11
+ output = +""
12
12
  result.groups.each do |name, files|
13
13
  output << "Group: #{name}\n"
14
14
  output << "=" * 40
@@ -11,8 +11,10 @@ module SimpleCov
11
11
 
12
12
  def read
13
13
  return nil unless File.exist?(last_run_path)
14
+
14
15
  json = File.read(last_run_path)
15
16
  return nil if json.strip.empty?
17
+
16
18
  JSON.parse(json)
17
19
  end
18
20
 
@@ -8,8 +8,8 @@ module SimpleCov
8
8
  RELEVANT = 0
9
9
  NOT_RELEVANT = nil
10
10
 
11
- WHITESPACE_LINE = /^\s*$/
12
- COMMENT_LINE = /^\s*#/
11
+ WHITESPACE_LINE = /^\s*$/.freeze
12
+ COMMENT_LINE = /^\s*#/.freeze
13
13
  WHITESPACE_OR_COMMENT_LINE = Regexp.union(WHITESPACE_LINE, COMMENT_LINE)
14
14
 
15
15
  def self.no_cov_line
@@ -1,13 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # Profiles are SimpleCov configuration procs that can be easily
5
- # loaded using SimpleCov.start :rails and defined using
6
- # SimpleCov.profiles.define :foo do
7
- # # SimpleCov configuration here, same as in SimpleCov.configure
8
- # end
9
- #
10
3
  module SimpleCov
4
+ #
5
+ # Profiles are SimpleCov configuration procs that can be easily
6
+ # loaded using SimpleCov.start :rails and defined using
7
+ # SimpleCov.profiles.define :foo do
8
+ # # SimpleCov configuration here, same as in SimpleCov.configure
9
+ # end
10
+ #
11
11
  class Profiles < Hash
12
12
  #
13
13
  # Define a SimpleCov profile:
@@ -18,6 +18,7 @@ module SimpleCov
18
18
  def define(name, &blk)
19
19
  name = name.to_sym
20
20
  raise "SimpleCov Profile '#{name}' is already defined" unless self[name].nil?
21
+
21
22
  self[name] = blk
22
23
  end
23
24
 
@@ -27,6 +28,7 @@ module SimpleCov
27
28
  def load(name)
28
29
  name = name.to_sym
29
30
  raise "Could not find SimpleCov Profile called '#{name}'" unless key?(name)
31
+
30
32
  SimpleCov.configure(&self[name])
31
33
  end
32
34
  end
@@ -5,7 +5,7 @@ require "forwardable"
5
5
 
6
6
  module SimpleCov
7
7
  #
8
- # A simplecov code coverage result, initialized from the Hash Ruby 1.9's built-in coverage
8
+ # A simplecov code coverage result, initialized from the Hash Ruby's built-in coverage
9
9
  # library generates (Coverage.result).
10
10
  #
11
11
  class Result
@@ -20,7 +20,7 @@ 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
23
+ def_delegators :files, :covered_percent, :covered_percentages, :least_covered_file, :covered_strength, :covered_lines, :missed_lines, :total_branches, :covered_branches, :missed_branches
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
@@ -28,7 +28,7 @@ module SimpleCov
28
28
  def initialize(original_result)
29
29
  @original_result = original_result.freeze
30
30
  @files = SimpleCov::FileList.new(original_result.map do |filename, coverage|
31
- SimpleCov::SourceFile.new(filename, coverage) if File.file?(filename)
31
+ SimpleCov::SourceFile.new(filename, JSON.parse(JSON.dump(coverage), :symbolize_names => true)) if File.file?(filename.to_s)
32
32
  end.compact.sort_by(&:filename))
33
33
  filter!
34
34
  end
@@ -67,12 +67,29 @@ module SimpleCov
67
67
  # Loads a SimpleCov::Result#to_hash dump
68
68
  def self.from_hash(hash)
69
69
  command_name, data = hash.first
70
- result = SimpleCov::Result.new(data["coverage"])
70
+
71
+ result = SimpleCov::Result.new(
72
+ symbolize_names_of_coverage_results(data["coverage"])
73
+ )
74
+
71
75
  result.command_name = command_name
72
76
  result.created_at = Time.at(data["timestamp"])
73
77
  result
74
78
  end
75
79
 
80
+ # Manage symbolize the keys of coverage hash.
81
+ # JSON.parse gives coverage hash with stringified keys what breaks some logics
82
+ # inside the process that expects them as symboles.
83
+ #
84
+ # @return [Hash]
85
+ def self.symbolize_names_of_coverage_results(coverage_data)
86
+ coverage_data.each_with_object({}) do |(file_name, file_coverage_result), coverage_results|
87
+ coverage_results[file_name] = file_coverage_result.each_with_object({}) do |(k, v), cov_elem|
88
+ cov_elem[k.to_sym] = v
89
+ end
90
+ end
91
+ end
92
+
76
93
  private
77
94
 
78
95
  def coverage