simplecov 0.17.1 → 0.18.0.beta1

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.
@@ -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