simplecov 0.18.1 → 0.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +224 -169
- data/CONTRIBUTING.md +3 -3
- data/ISSUE_TEMPLATE.md +1 -1
- data/README.md +67 -20
- data/doc/alternate-formatters.md +7 -2
- data/doc/commercial-services.md +5 -0
- data/lib/minitest/simplecov_plugin.rb +15 -0
- data/lib/simplecov.rb +117 -118
- data/lib/simplecov/combine.rb +2 -2
- data/lib/simplecov/combine/branches_combiner.rb +2 -2
- data/lib/simplecov/configuration.rb +52 -3
- data/lib/simplecov/coverage_statistics.rb +5 -5
- data/lib/simplecov/defaults.rb +8 -12
- data/lib/simplecov/exit_codes.rb +5 -0
- data/lib/simplecov/exit_codes/exit_code_handling.rb +29 -0
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +50 -0
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +38 -0
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +53 -0
- data/lib/simplecov/filter.rb +7 -5
- data/lib/simplecov/formatter.rb +2 -2
- data/lib/simplecov/formatter/multi_formatter.rb +5 -7
- data/lib/simplecov/lines_classifier.rb +1 -1
- data/lib/simplecov/no_defaults.rb +1 -1
- data/lib/simplecov/process.rb +19 -0
- data/lib/simplecov/result.rb +14 -12
- data/lib/simplecov/result_merger.rb +2 -7
- data/lib/simplecov/source_file.rb +46 -1
- data/lib/simplecov/useless_results_remover.rb +5 -3
- data/lib/simplecov/version.rb +1 -1
- metadata +18 -10
data/lib/simplecov/combine.rb
CHANGED
@@ -9,8 +9,8 @@ module SimpleCov
|
|
9
9
|
#
|
10
10
|
# Combine two coverage based on the given combiner_module.
|
11
11
|
#
|
12
|
-
# Combiners should always be called
|
13
|
-
# as it takes care of short
|
12
|
+
# Combiners should always be called through this interface,
|
13
|
+
# as it takes care of short-circuiting of one of the coverages is nil.
|
14
14
|
#
|
15
15
|
# @return [Hash]
|
16
16
|
def combine(combiner_module, coverage_a, coverage_b)
|
@@ -10,9 +10,9 @@ module SimpleCov
|
|
10
10
|
module_function
|
11
11
|
|
12
12
|
#
|
13
|
-
# Return merged branches or the existed
|
13
|
+
# Return merged branches or the existed brach if other is missing.
|
14
14
|
#
|
15
|
-
# Branches inside files are always same if they
|
15
|
+
# Branches inside files are always same if they exist, the difference only in coverage count.
|
16
16
|
# Branch coverage report for any conditional case is built from hash, it's key is a condition and
|
17
17
|
# it's body is a hash << keys from condition and value is coverage rate >>.
|
18
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...}
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require "fileutils"
|
4
4
|
require "docile"
|
5
|
-
|
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.
|
247
|
-
coverage.
|
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.
|
@@ -19,7 +19,7 @@ module SimpleCov
|
|
19
19
|
[
|
20
20
|
covered + file_coverage_statistics.covered,
|
21
21
|
missed + file_coverage_statistics.missed,
|
22
|
-
# gotta remultiply with loc because files have different
|
22
|
+
# gotta remultiply with loc because files have different strength and loc
|
23
23
|
# giving them a different "weight" in total
|
24
24
|
total_strength + (file_coverage_statistics.strength * file_coverage_statistics.total)
|
25
25
|
]
|
@@ -35,14 +35,14 @@ module SimpleCov
|
|
35
35
|
@covered = covered
|
36
36
|
@missed = missed
|
37
37
|
@total = covered + missed
|
38
|
-
@percent = compute_percent(covered, total)
|
39
|
-
@strength = compute_strength(total_strength,
|
38
|
+
@percent = compute_percent(covered, missed, total)
|
39
|
+
@strength = compute_strength(total_strength, total)
|
40
40
|
end
|
41
41
|
|
42
42
|
private
|
43
43
|
|
44
|
-
def compute_percent(covered, total)
|
45
|
-
return 100.0 if
|
44
|
+
def compute_percent(covered, missed, total)
|
45
|
+
return 100.0 if missed.zero?
|
46
46
|
|
47
47
|
covered * 100.0 / total
|
48
48
|
end
|
data/lib/simplecov/defaults.rb
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
# Load default formatter gem
|
4
4
|
require "simplecov-html"
|
5
5
|
require "pathname"
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
26
|
-
next if SimpleCov.pid != Process.pid
|
25
|
+
next if SimpleCov.external_at_exit?
|
27
26
|
|
28
|
-
|
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
|
-
|
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
|
data/lib/simplecov/exit_codes.rb
CHANGED
@@ -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
|
data/lib/simplecov/filter.rb
CHANGED
@@ -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
|
-
|
38
|
+
case filter_argument
|
39
|
+
when String
|
38
40
|
SimpleCov::StringFilter
|
39
|
-
|
41
|
+
when Regexp
|
40
42
|
SimpleCov::RegexFilter
|
41
|
-
|
43
|
+
when Array
|
42
44
|
SimpleCov::ArrayFilter
|
43
|
-
|
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
|
data/lib/simplecov/formatter.rb
CHANGED
@@ -6,12 +6,10 @@ module SimpleCov
|
|
6
6
|
module InstanceMethods
|
7
7
|
def format(result)
|
8
8
|
formatters.map do |formatter|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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(
|
28
|
+
new(Array(args))
|
31
29
|
end
|
32
30
|
end
|
33
31
|
end
|