simplecov 0.18.3 → 0.20.0
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 +4 -4
- data/CHANGELOG.md +227 -172
- data/CONTRIBUTING.md +3 -3
- data/ISSUE_TEMPLATE.md +1 -1
- data/README.md +84 -22
- data/doc/alternate-formatters.md +12 -2
- data/doc/commercial-services.md +5 -0
- data/lib/minitest/simplecov_plugin.rb +7 -3
- data/lib/simplecov.rb +118 -125
- data/lib/simplecov/configuration.rb +54 -3
- data/lib/simplecov/default_formatter.rb +20 -0
- data/lib/simplecov/defaults.rb +12 -10
- 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 +3 -3
- 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/line.rb +1 -1
- data/lib/simplecov/useless_results_remover.rb +5 -3
- data/lib/simplecov/version.rb +1 -1
- metadata +30 -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
|
#
|
@@ -22,6 +22,7 @@ module SimpleCov
|
|
22
22
|
def root(root = nil)
|
23
23
|
return @root if defined?(@root) && root.nil?
|
24
24
|
|
25
|
+
@coverage_path = nil # invalidate cache
|
25
26
|
@root = File.expand_path(root || Dir.getwd)
|
26
27
|
end
|
27
28
|
|
@@ -196,6 +197,53 @@ module SimpleCov
|
|
196
197
|
@at_exit ||= proc { SimpleCov.result.format! }
|
197
198
|
end
|
198
199
|
|
200
|
+
# gets or sets the enabled_for_subprocess configuration
|
201
|
+
# when true, this will inject SimpleCov code into Process.fork
|
202
|
+
def enable_for_subprocesses(value = nil)
|
203
|
+
return @enable_for_subprocesses if defined?(@enable_for_subprocesses) && value.nil?
|
204
|
+
|
205
|
+
@enable_for_subprocesses = value || false
|
206
|
+
end
|
207
|
+
|
208
|
+
# gets the enabled_for_subprocess configuration
|
209
|
+
def enabled_for_subprocesses?
|
210
|
+
enable_for_subprocesses
|
211
|
+
end
|
212
|
+
|
213
|
+
#
|
214
|
+
# Gets or sets the behavior to start a new forked Process.
|
215
|
+
#
|
216
|
+
# By default, it will add " (Process #{pid})" to the command_name, and start SimpleCov in quiet mode
|
217
|
+
#
|
218
|
+
# Configure with:
|
219
|
+
#
|
220
|
+
# SimpleCov.at_fork do |pid|
|
221
|
+
# SimpleCov.start do
|
222
|
+
# # This needs a unique name so it won't be ovewritten
|
223
|
+
# SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
|
224
|
+
# # be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
|
225
|
+
# SimpleCov.print_error_status = false
|
226
|
+
# SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
|
227
|
+
# SimpleCov.minimum_coverage 0
|
228
|
+
# # start
|
229
|
+
# SimpleCov.start
|
230
|
+
# end
|
231
|
+
# end
|
232
|
+
#
|
233
|
+
def at_fork(&block)
|
234
|
+
@at_fork = block if block_given?
|
235
|
+
@at_fork ||= lambda { |pid|
|
236
|
+
# This needs a unique name so it won't be ovewritten
|
237
|
+
SimpleCov.command_name "#{SimpleCov.command_name} (subprocess: #{pid})"
|
238
|
+
# be quiet, the parent process will be in charge of using the regular formatter and checking coverage totals
|
239
|
+
SimpleCov.print_error_status = false
|
240
|
+
SimpleCov.formatter SimpleCov::Formatter::SimpleFormatter
|
241
|
+
SimpleCov.minimum_coverage 0
|
242
|
+
# start
|
243
|
+
SimpleCov.start
|
244
|
+
}
|
245
|
+
end
|
246
|
+
|
199
247
|
#
|
200
248
|
# Returns the project name - currently assuming the last dirname in
|
201
249
|
# the SimpleCov.root is this.
|
@@ -239,17 +287,20 @@ module SimpleCov
|
|
239
287
|
#
|
240
288
|
# Default is 0% (disabled)
|
241
289
|
#
|
290
|
+
|
291
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
242
292
|
def minimum_coverage(coverage = nil)
|
243
293
|
return @minimum_coverage ||= {} unless coverage
|
244
294
|
|
245
295
|
coverage = {DEFAULT_COVERAGE_CRITERION => coverage} if coverage.is_a?(Numeric)
|
246
|
-
coverage.
|
247
|
-
coverage.
|
296
|
+
coverage.each_key { |criterion| raise_if_criterion_disabled(criterion) }
|
297
|
+
coverage.each_value do |percent|
|
248
298
|
minimum_possible_coverage_exceeded("minimum_coverage") if percent && percent > 100
|
249
299
|
end
|
250
300
|
|
251
301
|
@minimum_coverage = coverage
|
252
302
|
end
|
303
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
253
304
|
|
254
305
|
#
|
255
306
|
# Defines the maximum coverage drop at once allowed for the testsuite to pass.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "simplecov-html"
|
4
|
+
module SimpleCov
|
5
|
+
module Formatter
|
6
|
+
class << self
|
7
|
+
def from_env(env)
|
8
|
+
formatters = [SimpleCov::Formatter::HTMLFormatter]
|
9
|
+
|
10
|
+
# When running under a CI that uses CodeClimate, JSON output is expected
|
11
|
+
if env.fetch("CC_TEST_REPORTER_ID", nil)
|
12
|
+
require "simplecov_json_formatter"
|
13
|
+
formatters.push(SimpleCov::Formatter::JSONFormatter)
|
14
|
+
end
|
15
|
+
|
16
|
+
formatters
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/simplecov/defaults.rb
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Load default formatter gem
|
4
|
-
require "simplecov-html"
|
5
4
|
require "pathname"
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
require_relative "default_formatter"
|
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
|
14
|
-
formatter SimpleCov::Formatter::
|
14
|
+
formatter SimpleCov::Formatter::MultiFormatter.new(
|
15
|
+
SimpleCov::Formatter.from_env(ENV)
|
16
|
+
)
|
17
|
+
|
15
18
|
load_profile "bundler_filter"
|
16
19
|
load_profile "hidden_filter"
|
17
20
|
# Exclude files outside of SimpleCov.root
|
@@ -22,14 +25,13 @@ end
|
|
22
25
|
SimpleCov::CommandGuesser.original_run_command = "#{$PROGRAM_NAME} #{ARGV.join(' ')}"
|
23
26
|
|
24
27
|
at_exit do
|
25
|
-
|
26
|
-
next if defined?(Minitest)
|
28
|
+
next if SimpleCov.external_at_exit?
|
27
29
|
|
28
30
|
SimpleCov.at_exit_behavior
|
29
31
|
end
|
30
32
|
|
31
33
|
# Autoload config from ~/.simplecov if present
|
32
|
-
|
34
|
+
require_relative "load_global_config"
|
33
35
|
|
34
36
|
# Autoload config from .simplecov if present
|
35
37
|
# 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
|
@@ -13,18 +13,18 @@ module SimpleCov
|
|
13
13
|
WHITESPACE_OR_COMMENT_LINE = Regexp.union(WHITESPACE_LINE, COMMENT_LINE)
|
14
14
|
|
15
15
|
def self.no_cov_line
|
16
|
-
/^(\s*)#(\s*)(
|
16
|
+
/^(\s*)#(\s*)(:#{SimpleCov.nocov_token}:)/o
|
17
17
|
end
|
18
18
|
|
19
19
|
def self.no_cov_line?(line)
|
20
|
-
line
|
20
|
+
no_cov_line.match?(line)
|
21
21
|
rescue ArgumentError
|
22
22
|
# E.g., line contains an invalid byte sequence in UTF-8
|
23
23
|
false
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.whitespace_line?(line)
|
27
|
-
line
|
27
|
+
WHITESPACE_OR_COMMENT_LINE.match?(line)
|
28
28
|
rescue ArgumentError
|
29
29
|
# E.g., line contains an invalid byte sequence in UTF-8
|
30
30
|
false
|
@@ -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
|