simplecov 0.18.2 → 0.19.1
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 -170
- data/CONTRIBUTING.md +3 -3
- data/ISSUE_TEMPLATE.md +1 -1
- data/README.md +67 -20
- data/doc/alternate-formatters.md +12 -2
- data/doc/commercial-services.md +5 -0
- data/lib/minitest/simplecov_plugin.rb +15 -0
- data/lib/simplecov.rb +119 -118
- data/lib/simplecov/configuration.rb +53 -3
- 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 +16 -8
@@ -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,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.
|
247
|
-
coverage.
|
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.
|
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
|
@@ -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
|
data/lib/simplecov/result.rb
CHANGED
@@ -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
|
76
|
-
|
77
|
-
|
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/
|
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.
|
107
|
-
|
108
|
-
end
|
108
|
+
result.transform_values do |line_coverage_data|
|
109
|
+
{"lines" => line_coverage_data}
|
110
|
+
end
|
109
111
|
end
|
110
112
|
|
111
113
|
def coverage
|