simplecov 0.16.0 → 0.18.1
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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +110 -1
- data/CODE_OF_CONDUCT.md +76 -0
- data/LICENSE +20 -0
- data/README.md +281 -112
- data/doc/alternate-formatters.md +10 -0
- data/lib/simplecov.rb +248 -63
- data/lib/simplecov/combine.rb +30 -0
- data/lib/simplecov/combine/branches_combiner.rb +32 -0
- data/lib/simplecov/combine/files_combiner.rb +24 -0
- data/lib/simplecov/combine/lines_combiner.rb +43 -0
- data/lib/simplecov/combine/results_combiner.rb +60 -0
- data/lib/simplecov/command_guesser.rb +6 -3
- data/lib/simplecov/configuration.rb +110 -9
- data/lib/simplecov/coverage_statistics.rb +56 -0
- data/lib/simplecov/defaults.rb +6 -2
- data/lib/simplecov/file_list.rb +66 -13
- data/lib/simplecov/filter.rb +2 -1
- data/lib/simplecov/formatter/multi_formatter.rb +2 -2
- data/lib/simplecov/formatter/simple_formatter.rb +4 -4
- data/lib/simplecov/last_run.rb +3 -1
- data/lib/simplecov/lines_classifier.rb +2 -2
- data/lib/simplecov/profiles.rb +9 -7
- data/lib/simplecov/profiles/hidden_filter.rb +5 -0
- data/lib/simplecov/profiles/rails.rb +1 -1
- data/lib/simplecov/result.rb +39 -6
- data/lib/simplecov/result_adapter.rb +30 -0
- data/lib/simplecov/result_merger.rb +18 -11
- data/lib/simplecov/simulate_coverage.rb +29 -0
- data/lib/simplecov/source_file.rb +227 -126
- data/lib/simplecov/source_file/branch.rb +84 -0
- data/lib/simplecov/source_file/line.rb +72 -0
- data/lib/simplecov/useless_results_remover.rb +16 -0
- data/lib/simplecov/version.rb +1 -1
- metadata +32 -53
- data/lib/simplecov/jruby_fix.rb +0 -44
- data/lib/simplecov/railtie.rb +0 -9
- data/lib/simplecov/railties/tasks.rake +0 -13
- data/lib/simplecov/raw_coverage.rb +0 -41
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleCov
|
4
|
+
module Combine
|
5
|
+
#
|
6
|
+
# Combine different branch coverage results on single file.
|
7
|
+
#
|
8
|
+
# Should be called through `SimpleCov.combine`.
|
9
|
+
module BranchesCombiner
|
10
|
+
module_function
|
11
|
+
|
12
|
+
#
|
13
|
+
# Return merged branches or the existed branche if other is missing.
|
14
|
+
#
|
15
|
+
# Branches inside files are always same if they exists, the difference only in coverage count.
|
16
|
+
# Branch coverage report for any conditional case is built from hash, it's key is a condition and
|
17
|
+
# it's body is a hash << keys from condition and value is coverage rate >>.
|
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...}
|
19
|
+
# We create copy of result and update it values depending on the combined branches coverage values.
|
20
|
+
#
|
21
|
+
# @return [Hash]
|
22
|
+
#
|
23
|
+
def combine(coverage_a, coverage_b)
|
24
|
+
coverage_a.merge(coverage_b) do |_condition, branches_inside_a, branches_inside_b|
|
25
|
+
branches_inside_a.merge(branches_inside_b) do |_branch, a_count, b_count|
|
26
|
+
a_count + b_count
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleCov
|
4
|
+
module Combine
|
5
|
+
#
|
6
|
+
# Handle combining two coverage results for same file
|
7
|
+
#
|
8
|
+
# Should be called through `SimpleCov.combine`.
|
9
|
+
module FilesCombiner
|
10
|
+
module_function
|
11
|
+
|
12
|
+
#
|
13
|
+
# Combines the results for 2 coverages of a file.
|
14
|
+
#
|
15
|
+
# @return [Hash]
|
16
|
+
#
|
17
|
+
def combine(coverage_a, coverage_b)
|
18
|
+
combination = {"lines" => Combine.combine(LinesCombiner, coverage_a["lines"], coverage_b["lines"])}
|
19
|
+
combination["branches"] = Combine.combine(BranchesCombiner, coverage_a["branches"], coverage_b["branches"]) if SimpleCov.branch_coverage?
|
20
|
+
combination
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleCov
|
4
|
+
module Combine
|
5
|
+
#
|
6
|
+
# Combine two different lines coverage results on same file
|
7
|
+
#
|
8
|
+
# Should be called through `SimpleCov.combine`.
|
9
|
+
module LinesCombiner
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def combine(coverage_a, coverage_b)
|
13
|
+
coverage_a
|
14
|
+
.zip(coverage_b)
|
15
|
+
.map do |coverage_a_val, coverage_b_val|
|
16
|
+
merge_line_coverage(coverage_a_val, coverage_b_val)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Return depends on coverage in a specific line
|
21
|
+
#
|
22
|
+
# @param [Integer || nil] first_val
|
23
|
+
# @param [Integer || nil] second_val
|
24
|
+
#
|
25
|
+
# Logic:
|
26
|
+
#
|
27
|
+
# => nil + 0 = nil
|
28
|
+
# => nil + nil = nil
|
29
|
+
# => int + int = int
|
30
|
+
#
|
31
|
+
# @return [Integer || nil]
|
32
|
+
def merge_line_coverage(first_val, second_val)
|
33
|
+
sum = first_val.to_i + second_val.to_i
|
34
|
+
|
35
|
+
if sum.zero? && (first_val.nil? || second_val.nil?)
|
36
|
+
nil
|
37
|
+
else
|
38
|
+
sum
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleCov
|
4
|
+
module Combine
|
5
|
+
# There might be reports from different kinds of tests,
|
6
|
+
# e.g. RSpec and Cucumber. We need to combine their results
|
7
|
+
# into unified one. This class does that.
|
8
|
+
# To unite the results on file basis, it leverages
|
9
|
+
# the combine of lines and branches inside each file within given results.
|
10
|
+
module ResultsCombiner
|
11
|
+
module_function
|
12
|
+
|
13
|
+
#
|
14
|
+
# Combine process explanation
|
15
|
+
# => ResultCombiner: define all present files between results and start combine on file level.
|
16
|
+
# ==> FileCombiner: collect result of next combine levels lines and branches.
|
17
|
+
# ===> LinesCombiner: combine lines results.
|
18
|
+
# ===> BranchesCombiner: combine branches results.
|
19
|
+
#
|
20
|
+
# @return [Hash]
|
21
|
+
#
|
22
|
+
def combine(*results)
|
23
|
+
results.reduce({}) do |combined_results, next_result|
|
24
|
+
combine_result_sets(combined_results, next_result)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Manage combining results on files level
|
30
|
+
#
|
31
|
+
# @param [Hash] combined_results
|
32
|
+
# @param [Hash] result
|
33
|
+
#
|
34
|
+
# @return [Hash]
|
35
|
+
#
|
36
|
+
def combine_result_sets(combined_results, result)
|
37
|
+
results_files = combined_results.keys | result.keys
|
38
|
+
|
39
|
+
results_files.each_with_object({}) do |file_name, file_combination|
|
40
|
+
file_combination[file_name] = combine_file_coverage(
|
41
|
+
combined_results[file_name],
|
42
|
+
result[file_name]
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Combine two files coverage results
|
49
|
+
#
|
50
|
+
# @param [Hash] coverage_a
|
51
|
+
# @param [Hash] coverage_b
|
52
|
+
#
|
53
|
+
# @return [Hash]
|
54
|
+
#
|
55
|
+
def combine_file_coverage(coverage_a, coverage_b)
|
56
|
+
Combine.combine(Combine::FilesCombiner, coverage_a, coverage_b)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -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
|
-
|
13
|
-
|
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,7 +240,15 @@ module SimpleCov
|
|
225
240
|
# Default is 0% (disabled)
|
226
241
|
#
|
227
242
|
def minimum_coverage(coverage = nil)
|
228
|
-
@minimum_coverage ||=
|
243
|
+
return @minimum_coverage ||= {} unless coverage
|
244
|
+
|
245
|
+
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|
|
248
|
+
minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
|
249
|
+
end
|
250
|
+
|
251
|
+
@minimum_coverage = coverage
|
229
252
|
end
|
230
253
|
|
231
254
|
#
|
@@ -246,6 +269,7 @@ module SimpleCov
|
|
246
269
|
# Default is 0% (disabled)
|
247
270
|
#
|
248
271
|
def minimum_coverage_by_file(coverage = nil)
|
272
|
+
minimum_possible_coverage_exceeded("minimum_coverage_by_file") if coverage && coverage > 100
|
249
273
|
@minimum_coverage_by_file ||= (coverage || 0).to_f.round(2)
|
250
274
|
end
|
251
275
|
|
@@ -287,8 +311,85 @@ module SimpleCov
|
|
287
311
|
groups[group_name] = parse_filter(filter_argument, &filter_proc)
|
288
312
|
end
|
289
313
|
|
314
|
+
SUPPORTED_COVERAGE_CRITERIA = %i[line branch].freeze
|
315
|
+
DEFAULT_COVERAGE_CRITERION = :line
|
316
|
+
#
|
317
|
+
# Define which coverage criterion should be evaluated.
|
318
|
+
#
|
319
|
+
# Possible coverage criteria:
|
320
|
+
# * :line - coverage based on lines aka has this line been executed?
|
321
|
+
# * :branch - coverage based on branches aka has this branch (think conditions) been executed?
|
322
|
+
#
|
323
|
+
# If not set the default is `:line`
|
324
|
+
#
|
325
|
+
# @param [Symbol] criterion
|
326
|
+
#
|
327
|
+
def coverage_criterion(criterion = nil)
|
328
|
+
return @coverage_criterion ||= DEFAULT_COVERAGE_CRITERION unless criterion
|
329
|
+
|
330
|
+
raise_if_criterion_unsupported(criterion)
|
331
|
+
|
332
|
+
@coverage_criterion = criterion
|
333
|
+
end
|
334
|
+
|
335
|
+
def enable_coverage(criterion)
|
336
|
+
raise_if_criterion_unsupported(criterion)
|
337
|
+
|
338
|
+
coverage_criteria << criterion
|
339
|
+
end
|
340
|
+
|
341
|
+
def coverage_criteria
|
342
|
+
@coverage_criteria ||= Set[DEFAULT_COVERAGE_CRITERION]
|
343
|
+
end
|
344
|
+
|
345
|
+
def coverage_criterion_enabled?(criterion)
|
346
|
+
coverage_criteria.member?(criterion)
|
347
|
+
end
|
348
|
+
|
349
|
+
def clear_coverage_criteria
|
350
|
+
@coverage_criteria = nil
|
351
|
+
end
|
352
|
+
|
353
|
+
def branch_coverage?
|
354
|
+
branch_coverage_supported? && coverage_criterion_enabled?(:branch)
|
355
|
+
end
|
356
|
+
|
357
|
+
def coverage_start_arguments_supported?
|
358
|
+
# safe to cache as within one process this value should never
|
359
|
+
# change
|
360
|
+
return @coverage_start_arguments_supported if defined?(@coverage_start_arguments_supported)
|
361
|
+
|
362
|
+
@coverage_start_arguments_supported = begin
|
363
|
+
require "coverage"
|
364
|
+
!Coverage.method(:start).arity.zero?
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
alias branch_coverage_supported? coverage_start_arguments_supported?
|
369
|
+
|
290
370
|
private
|
291
371
|
|
372
|
+
def raise_if_criterion_disabled(criterion)
|
373
|
+
raise_if_criterion_unsupported(criterion)
|
374
|
+
# rubocop:disable Style/IfUnlessModifier
|
375
|
+
unless coverage_criterion_enabled?(criterion)
|
376
|
+
raise "Coverage criterion #{criterion}, is disabled! Please enable it first through enable_coverage #{criterion} (if supported)"
|
377
|
+
end
|
378
|
+
# rubocop:enable Style/IfUnlessModifier
|
379
|
+
end
|
380
|
+
|
381
|
+
def raise_if_criterion_unsupported(criterion)
|
382
|
+
# rubocop:disable Style/IfUnlessModifier
|
383
|
+
unless SUPPORTED_COVERAGE_CRITERIA.member?(criterion)
|
384
|
+
raise "Unsupported coverage criterion #{criterion}, supported values are #{SUPPORTED_COVERAGE_CRITERIA}"
|
385
|
+
end
|
386
|
+
# rubocop:enable Style/IfUnlessModifier
|
387
|
+
end
|
388
|
+
|
389
|
+
def minimum_possible_coverage_exceeded(coverage_option)
|
390
|
+
warn "The coverage you set for #{coverage_option} is greater than 100%"
|
391
|
+
end
|
392
|
+
|
292
393
|
#
|
293
394
|
# The actual filter processor. Not meant for direct use
|
294
395
|
#
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SimpleCov
|
4
|
+
# Holds the individual data of a coverage result.
|
5
|
+
#
|
6
|
+
# This is uniform across coverage criteria as they all have:
|
7
|
+
#
|
8
|
+
# * total - how many things to cover there are (total relevant loc/branches)
|
9
|
+
# * covered - how many of the coverables are hit
|
10
|
+
# * missed - how many of the coverables are missed
|
11
|
+
# * percent - percentage as covered/missed
|
12
|
+
# * strength - average hits per/coverable (will not exist for one shot lines format)
|
13
|
+
class CoverageStatistics
|
14
|
+
attr_reader :total, :covered, :missed, :strength, :percent
|
15
|
+
|
16
|
+
def self.from(coverage_statistics)
|
17
|
+
sum_covered, sum_missed, sum_total_strength =
|
18
|
+
coverage_statistics.reduce([0, 0, 0.0]) do |(covered, missed, total_strength), file_coverage_statistics|
|
19
|
+
[
|
20
|
+
covered + file_coverage_statistics.covered,
|
21
|
+
missed + file_coverage_statistics.missed,
|
22
|
+
# gotta remultiply with loc because files have different strenght and loc
|
23
|
+
# giving them a different "weight" in total
|
24
|
+
total_strength + (file_coverage_statistics.strength * file_coverage_statistics.total)
|
25
|
+
]
|
26
|
+
end
|
27
|
+
|
28
|
+
new(covered: sum_covered, missed: sum_missed, total_strength: sum_total_strength)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Requires only covered, missed and strength to be initialized.
|
32
|
+
#
|
33
|
+
# Other values are computed by this class.
|
34
|
+
def initialize(covered:, missed:, total_strength: 0.0)
|
35
|
+
@covered = covered
|
36
|
+
@missed = missed
|
37
|
+
@total = covered + missed
|
38
|
+
@percent = compute_percent(covered, total)
|
39
|
+
@strength = compute_strength(total_strength, @total)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def compute_percent(covered, total)
|
45
|
+
return 100.0 if total.zero?
|
46
|
+
|
47
|
+
covered * 100.0 / total
|
48
|
+
end
|
49
|
+
|
50
|
+
def compute_strength(total_strength, total)
|
51
|
+
return 0.0 if total.zero?
|
52
|
+
|
53
|
+
total_strength.to_f / total
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|