simplecov 1.0.0 → 1.0.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 +4 -4
- data/lib/simplecov/cli/coverage.rb +5 -3
- data/lib/simplecov/cli/diff.rb +2 -1
- data/lib/simplecov/cli/dotfile.rb +9 -4
- data/lib/simplecov/cli/merge.rb +5 -3
- data/lib/simplecov/cli/report.rb +11 -6
- data/lib/simplecov/cli/run.rb +1 -1
- data/lib/simplecov/cli/serve.rb +17 -7
- data/lib/simplecov/cli/uncovered.rb +2 -1
- data/lib/simplecov/combine/branches_combiner.rb +2 -1
- data/lib/simplecov/combine/results_combiner.rb +2 -1
- data/lib/simplecov/configuration/coverage.rb +6 -2
- data/lib/simplecov/configuration/coverage_criteria.rb +3 -1
- data/lib/simplecov/configuration/formatting.rb +17 -7
- data/lib/simplecov/configuration/thresholds.rb +13 -9
- data/lib/simplecov/configuration.rb +22 -13
- data/lib/simplecov/coverage_statistics.rb +4 -1
- data/lib/simplecov/coverage_violations.rb +2 -1
- data/lib/simplecov/defaults.rb +3 -3
- data/lib/simplecov/directive.rb +8 -3
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +1 -1
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +1 -1
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +1 -1
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +1 -1
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +3 -3
- data/lib/simplecov/exit_codes.rb +12 -0
- data/lib/simplecov/exit_handling.rb +3 -3
- data/lib/simplecov/file_list.rb +12 -5
- data/lib/simplecov/formatter/base.rb +2 -1
- data/lib/simplecov/formatter/html_formatter.rb +13 -7
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +9 -3
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +1 -1
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +1 -1
- data/lib/simplecov/formatter/json_formatter.rb +4 -2
- data/lib/simplecov/formatter/multi_formatter.rb +7 -2
- data/lib/simplecov/formatter/simple_formatter.rb +3 -1
- data/lib/simplecov/process.rb +22 -0
- data/lib/simplecov/result_adapter.rb +2 -1
- data/lib/simplecov/result_processing.rb +4 -4
- data/lib/simplecov/simulate_coverage.rb +2 -2
- data/lib/simplecov/source_file/line.rb +2 -2
- data/lib/simplecov/source_file/method.rb +1 -1
- data/lib/simplecov/source_file/method_builder.rb +3 -1
- data/lib/simplecov/source_file/source_loader.rb +2 -2
- data/lib/simplecov/source_file.rb +2 -2
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +158 -0
- data/lib/simplecov/static_coverage_extractor/visitor.rb +9 -45
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov.rb +24 -11
- data/sig/simplecov.rbs +1638 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 14c29bfd96c91bb39e5893969a5f35a320e0d3b23b3ae47161aabc21a182f69f
|
|
4
|
+
data.tar.gz: 2436a11c76e48df0df5c7432e1c1163f0bf36f9090e04d035017c99769e74b7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13bdde3f26f987d864caf3c2a861c479cac8e03c12fcf12752aad70b0ca55793e1dc529490d5ad9fc6250c780d6131fd78264ddaf0ca3158ae4cea5ffdb3b3b5
|
|
7
|
+
data.tar.gz: 21239f67622ae18b306783d6a355dd728247128ece00935601334b82f4f7f8948150768d0ac949c1db5c859c23187b4a9f568b7a45a49bf5a7a1ab40a8178951
|
|
@@ -28,7 +28,7 @@ module SimpleCov
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def parse(args, stderr:)
|
|
31
|
-
opts = {input: SimpleCov::CLI.default_input, json: false, no_color: false}
|
|
31
|
+
opts = {input: SimpleCov::CLI.default_input, json: false, no_color: false} #: Hash[Symbol, untyped]
|
|
32
32
|
rest =
|
|
33
33
|
OptionParser.new do |o|
|
|
34
34
|
o.on("--input PATH") { |v| opts[:input] = v }
|
|
@@ -45,7 +45,8 @@ module SimpleCov
|
|
|
45
45
|
return stderr.puts("simplecov coverage: #{opts[:input]} not found") && nil unless File.exist?(opts[:input])
|
|
46
46
|
|
|
47
47
|
data = JSON.parse(File.read(opts[:input]))
|
|
48
|
-
|
|
48
|
+
none = {} #: Hash[String, untyped]
|
|
49
|
+
match = lookup(data.fetch("coverage", none), opts[:path])
|
|
49
50
|
return match if match
|
|
50
51
|
|
|
51
52
|
stderr.puts("simplecov coverage: no entry for #{opts[:path]} in #{opts[:input]}")
|
|
@@ -65,7 +66,8 @@ module SimpleCov
|
|
|
65
66
|
def emit(match, opts, stdout)
|
|
66
67
|
filename, payload = match
|
|
67
68
|
if opts[:json]
|
|
68
|
-
|
|
69
|
+
entry = {filename => payload} #: Hash[untyped, untyped]
|
|
70
|
+
stdout.puts(JSON.pretty_generate(entry))
|
|
69
71
|
else
|
|
70
72
|
print_human(filename, payload, stdout, SimpleCov::CLI.color_enabled?(opts, stdout))
|
|
71
73
|
end
|
data/lib/simplecov/cli/diff.rb
CHANGED
|
@@ -68,7 +68,8 @@ module SimpleCov
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def load_coverage(path, stderr)
|
|
71
|
-
|
|
71
|
+
no_coverage = {} #: Hash[String, untyped]
|
|
72
|
+
return normalize_keys(JSON.parse(File.read(path)).fetch("coverage", no_coverage)) if File.exist?(path)
|
|
72
73
|
|
|
73
74
|
stderr.puts("simplecov diff: #{path} not found")
|
|
74
75
|
nil
|
|
@@ -57,8 +57,8 @@ module SimpleCov
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def with_simplecov_loaded
|
|
60
|
-
previous_no_defaults = ENV.fetch("SIMPLECOV_NO_DEFAULTS", nil)
|
|
61
|
-
previous_cli = ENV.fetch("SIMPLECOV_CLI", nil)
|
|
60
|
+
previous_no_defaults = ENV.fetch("SIMPLECOV_NO_DEFAULTS", nil) #: String?
|
|
61
|
+
previous_cli = ENV.fetch("SIMPLECOV_CLI", nil) #: String?
|
|
62
62
|
ENV["SIMPLECOV_NO_DEFAULTS"] = "1"
|
|
63
63
|
# SIMPLECOV_CLI lets a project's `.simplecov` opt some config into
|
|
64
64
|
# CLI-only behavior — e.g. simplecov itself sets `coverage_dir`
|
|
@@ -67,6 +67,8 @@ module SimpleCov
|
|
|
67
67
|
require "simplecov"
|
|
68
68
|
yield
|
|
69
69
|
ensure
|
|
70
|
+
# @type var previous_no_defaults: String?
|
|
71
|
+
# @type var previous_cli: String?
|
|
70
72
|
ENV["SIMPLECOV_NO_DEFAULTS"] = previous_no_defaults
|
|
71
73
|
ENV["SIMPLECOV_CLI"] = previous_cli
|
|
72
74
|
end
|
|
@@ -79,20 +81,23 @@ module SimpleCov
|
|
|
79
81
|
def load_with_start_neutered(path)
|
|
80
82
|
klass = SimpleCov.singleton_class
|
|
81
83
|
names = %i[start_tracking install_at_exit_hook]
|
|
82
|
-
stash = names.to_h { |name| [name, klass.instance_method(name)] }
|
|
84
|
+
stash = names.to_h { |name| [name, klass.instance_method(name)] } #: Hash[Symbol, UnboundMethod]
|
|
83
85
|
# define_method over an existing method emits a "method redefined"
|
|
84
86
|
# warning under $VERBOSE; the override and restore are intentional.
|
|
85
87
|
silence_verbose { names.each { |name| klass.define_method(name) { nil } } }
|
|
86
88
|
load path
|
|
87
89
|
ensure
|
|
90
|
+
# @type var stash: Hash[Symbol, UnboundMethod]
|
|
91
|
+
# @type var klass: Class
|
|
88
92
|
silence_verbose { stash.each { |name, method| klass.define_method(name, method) } }
|
|
89
93
|
end
|
|
90
94
|
|
|
91
95
|
def silence_verbose
|
|
92
|
-
previous = $VERBOSE
|
|
96
|
+
previous = $VERBOSE #: bool?
|
|
93
97
|
$VERBOSE = nil
|
|
94
98
|
yield
|
|
95
99
|
ensure
|
|
100
|
+
# @type var previous: bool?
|
|
96
101
|
$VERBOSE = previous
|
|
97
102
|
end
|
|
98
103
|
end
|
data/lib/simplecov/cli/merge.rb
CHANGED
|
@@ -57,7 +57,8 @@ module SimpleCov
|
|
|
57
57
|
# generic "no mergeable results" into a message that points at
|
|
58
58
|
# the specific input causing the failure.
|
|
59
59
|
def parse_inputs(files, stderr)
|
|
60
|
-
|
|
60
|
+
parsed = {} #: Hash[String, Hash[String, untyped]]
|
|
61
|
+
files.each_with_object(parsed) do |path, memo|
|
|
61
62
|
data = parse_input(path, stderr) or return nil
|
|
62
63
|
|
|
63
64
|
memo[path] = data
|
|
@@ -85,8 +86,9 @@ module SimpleCov
|
|
|
85
86
|
# mistake for "no merge happened." Surface the overlap so the
|
|
86
87
|
# operator can rename the workers or accept the merge knowingly.
|
|
87
88
|
def warn_about_duplicate_command_names(parsed, stderr)
|
|
88
|
-
files_per_command =
|
|
89
|
-
|
|
89
|
+
files_per_command = {} #: Hash[String, Array[String]]
|
|
90
|
+
parsed.each do |path, data|
|
|
91
|
+
data.each_key { |command_name| (files_per_command[command_name] ||= []) << path }
|
|
90
92
|
end
|
|
91
93
|
files_per_command.each do |command_name, paths|
|
|
92
94
|
next if paths.size < 2
|
data/lib/simplecov/cli/report.rb
CHANGED
|
@@ -45,8 +45,9 @@ module SimpleCov
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def emit_text(stdout, data, color)
|
|
48
|
-
|
|
49
|
-
data.fetch("
|
|
48
|
+
none = {} #: Hash[String, untyped]
|
|
49
|
+
emit_totals(stdout, "All Files", data.fetch("total", none), color)
|
|
50
|
+
data.fetch("groups", none).each { |name, group| emit_totals(stdout, name, group, color) }
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def emit_totals(stdout, label, totals, color)
|
|
@@ -66,17 +67,21 @@ module SimpleCov
|
|
|
66
67
|
end
|
|
67
68
|
|
|
68
69
|
def emit_json(stdout, data)
|
|
69
|
-
|
|
70
|
-
data.fetch("
|
|
70
|
+
none = {} #: Hash[String, untyped]
|
|
71
|
+
payload = {"All Files" => collect_section(data.fetch("total", none))}
|
|
72
|
+
data.fetch("groups", none).each { |name, group| payload[name] = collect_section(group) }
|
|
71
73
|
stdout.puts(JSON.pretty_generate(payload))
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
def collect_section(totals)
|
|
75
|
-
|
|
77
|
+
sections = {} #: Hash[String, untyped]
|
|
78
|
+
SECTIONS.each_with_object(sections) do |(_, key), out|
|
|
76
79
|
section = totals[key]
|
|
77
80
|
next unless section.is_a?(Hash) && section["total"].to_i.positive?
|
|
78
81
|
|
|
79
|
-
out[key] = {
|
|
82
|
+
out[key.to_s] = {
|
|
83
|
+
"percent" => section["percent"], "covered" => section["covered"], "total" => section["total"]
|
|
84
|
+
}
|
|
80
85
|
end
|
|
81
86
|
end
|
|
82
87
|
end
|
data/lib/simplecov/cli/run.rb
CHANGED
|
@@ -8,7 +8,7 @@ module SimpleCov
|
|
|
8
8
|
# without a test_helper that already calls SimpleCov.start (e.g.
|
|
9
9
|
# plain `bundle exec rake test` on an unconfigured library).
|
|
10
10
|
module Run
|
|
11
|
-
AUTOSTART = File.expand_path("../autostart", __dir__)
|
|
11
|
+
AUTOSTART = File.expand_path("../autostart", __dir__.to_s)
|
|
12
12
|
|
|
13
13
|
module_function
|
|
14
14
|
|
data/lib/simplecov/cli/serve.rb
CHANGED
|
@@ -36,16 +36,26 @@ module SimpleCov
|
|
|
36
36
|
return error(stderr, "#{dir} doesn't exist; run your test suite first") unless File.directory?(dir)
|
|
37
37
|
|
|
38
38
|
require "socket"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
39
|
+
with_server(opts) do |server|
|
|
40
|
+
announce(stdout, server, dir)
|
|
41
|
+
serve_loop(server, dir, stdout)
|
|
42
|
+
0
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def with_server(opts)
|
|
47
|
+
# The receiver cast works around an rbs stdlib gap: TCPSocket's
|
|
48
|
+
# explicit `self.new` shadows TCPServer#initialize's (host, port) form.
|
|
49
|
+
server = (_ = TCPServer).new(opts[:host], opts[:port]) #: TCPServer
|
|
50
|
+
begin
|
|
51
|
+
yield server
|
|
52
|
+
ensure
|
|
53
|
+
server.close
|
|
54
|
+
end
|
|
45
55
|
end
|
|
46
56
|
|
|
47
57
|
def parse(args)
|
|
48
|
-
opts = {port: 0, host: "127.0.0.1"}
|
|
58
|
+
opts = {port: 0, host: "127.0.0.1"} #: Hash[Symbol, untyped]
|
|
49
59
|
OptionParser.new do |o|
|
|
50
60
|
o.on("--port N", Integer) { |v| opts[:port] = v }
|
|
51
61
|
o.on("--host HOST") { |v| opts[:host] = v }
|
|
@@ -33,7 +33,8 @@ module SimpleCov
|
|
|
33
33
|
def report(opts, keys, stdout, stderr)
|
|
34
34
|
return 1 unless (data = Report.load_data(opts[:input], stderr))
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
none = {} #: Hash[String, untyped]
|
|
37
|
+
files = rank(data.fetch("coverage", none), opts[:threshold], keys).first(opts[:top])
|
|
37
38
|
return stdout.puts(empty_message(opts[:json])) || 0 if files.empty?
|
|
38
39
|
|
|
39
40
|
emit(stdout, files, opts)
|
|
@@ -24,7 +24,8 @@ module SimpleCov
|
|
|
24
24
|
# @return [Hash]
|
|
25
25
|
#
|
|
26
26
|
def combine(coverage_a, coverage_b)
|
|
27
|
-
|
|
27
|
+
merged = {} #: Hash[untyped, [untyped, Hash[untyped, untyped]]]
|
|
28
|
+
combined = [coverage_a, coverage_b].each_with_object(merged) do |coverage, memo|
|
|
28
29
|
coverage.each do |condition, branches_inside|
|
|
29
30
|
condition_key = tuple_identity(condition)
|
|
30
31
|
condition_tuple, merged_branches = memo[condition_key] ||= [condition, {}]
|
|
@@ -21,7 +21,8 @@ module SimpleCov
|
|
|
21
21
|
# @return [Hash]
|
|
22
22
|
#
|
|
23
23
|
def combine(*results)
|
|
24
|
-
|
|
24
|
+
initial = {} #: Hash[untyped, untyped]
|
|
25
|
+
results.reduce(initial) do |combined_results, next_result|
|
|
25
26
|
combine_result_sets(combined_results, next_result)
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -42,7 +42,9 @@ module SimpleCov
|
|
|
42
42
|
#
|
|
43
43
|
def coverage(criterion, primary: false, enabled: true, oneshot: false, **thresholds, &block)
|
|
44
44
|
criterion = enable_coverage_criterion(criterion, enabled: enabled, oneshot: oneshot)
|
|
45
|
-
primary_coverage
|
|
45
|
+
# The cast admits :eval, which primary_coverage rejects at runtime
|
|
46
|
+
# (it is enable-only and never in the enabled-criteria set).
|
|
47
|
+
primary_coverage(_ = criterion) if primary
|
|
46
48
|
|
|
47
49
|
configurator = CoverageCriterion.new(self, criterion)
|
|
48
50
|
apply_threshold_options(configurator, thresholds)
|
|
@@ -164,7 +166,9 @@ module SimpleCov
|
|
|
164
166
|
|
|
165
167
|
# Make this criterion the report's primary (leading) criterion.
|
|
166
168
|
def primary
|
|
167
|
-
@
|
|
169
|
+
# @criterion is Symbol-wide because this receiver is also built for
|
|
170
|
+
# :eval; primary_coverage validates at runtime.
|
|
171
|
+
@config.primary_coverage(_ = @criterion)
|
|
168
172
|
end
|
|
169
173
|
end
|
|
170
174
|
end
|
|
@@ -134,7 +134,9 @@ module SimpleCov
|
|
|
134
134
|
def default_primary_coverage
|
|
135
135
|
return DEFAULT_COVERAGE_CRITERION if coverage_criterion_enabled?(DEFAULT_COVERAGE_CRITERION)
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
# Set#first types as nilable, but an empty criteria set is rejected at
|
|
138
|
+
# start_tracking before any caller can observe a nil here.
|
|
139
|
+
_ = coverage_criteria.first
|
|
138
140
|
end
|
|
139
141
|
|
|
140
142
|
def raise_if_criterion_disabled(criterion)
|
|
@@ -17,22 +17,32 @@ module SimpleCov
|
|
|
17
17
|
# overhead.
|
|
18
18
|
#
|
|
19
19
|
def formatter(formatter = :__no_arg__)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
case formatter
|
|
21
|
+
when :__no_arg__
|
|
22
|
+
@formatter
|
|
23
|
+
else
|
|
24
|
+
@formatter = formatter || nil # normalize `false` to `nil`
|
|
25
|
+
end
|
|
23
26
|
end
|
|
24
27
|
|
|
25
28
|
# Sets the configured formatters. Pass `[]` to opt out of
|
|
26
29
|
# formatting entirely; see `formatter` for the rationale.
|
|
27
30
|
def formatters(formatters = :__no_arg__)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
case formatters
|
|
32
|
+
when :__no_arg__
|
|
33
|
+
configured = formatter
|
|
34
|
+
configured ? [configured] : []
|
|
35
|
+
else
|
|
36
|
+
self.formatters = formatters
|
|
37
|
+
formatters
|
|
38
|
+
end
|
|
32
39
|
end
|
|
33
40
|
|
|
34
41
|
# Sets the configured formatters. Equivalent to `formatters [...]`.
|
|
42
|
+
# Accepts a single formatter as well as an Array, matching the pre-1.0 behavior
|
|
43
|
+
# where `MultiFormatter.new` normalized its input.
|
|
35
44
|
def formatters=(formatters)
|
|
45
|
+
formatters = Array(formatters)
|
|
36
46
|
@formatter = formatters.empty? ? nil : SimpleCov::Formatter::MultiFormatter.new(formatters)
|
|
37
47
|
end
|
|
38
48
|
|
|
@@ -123,7 +123,11 @@ module SimpleCov
|
|
|
123
123
|
# so downstream code only has one shape to handle.
|
|
124
124
|
def partition_per_file_thresholds(coverage)
|
|
125
125
|
coverage.each_key { |key| validate_per_file_key(key) }
|
|
126
|
-
|
|
126
|
+
pairs = coverage.partition { |key, _| key.is_a?(Symbol) }
|
|
127
|
+
# The assertions restate what the partition predicate guarantees:
|
|
128
|
+
# Symbol keys carry per-criterion Numeric defaults, the rest are paths.
|
|
129
|
+
defaults = pairs[0].to_h #: coverage_thresholds
|
|
130
|
+
raw = pairs[1].to_h #: Hash[String | Regexp, Numeric | coverage_thresholds]
|
|
127
131
|
overrides = raw.transform_values { |value| value.is_a?(Numeric) ? {primary_coverage => value} : value }
|
|
128
132
|
[defaults, overrides]
|
|
129
133
|
end
|
|
@@ -143,11 +147,11 @@ module SimpleCov
|
|
|
143
147
|
# `minimum_coverage_by_file` argument so the deprecation warning can be
|
|
144
148
|
# copy-pasted verbatim into the user's config.
|
|
145
149
|
def per_file_coverage_replacement(defaults, overrides)
|
|
146
|
-
by_criterion =
|
|
147
|
-
defaults.each { |criterion, percent| by_criterion[criterion] << "minimum_per_file #{percent}" }
|
|
150
|
+
by_criterion = {} #: Hash[Symbol, Array[String]]
|
|
151
|
+
defaults.each { |criterion, percent| (by_criterion[criterion] ||= []) << "minimum_per_file #{percent}" }
|
|
148
152
|
overrides.each do |target, criteria|
|
|
149
153
|
criteria.each do |criterion, percent|
|
|
150
|
-
by_criterion[criterion] << "minimum_per_file #{percent}, only: #{target.inspect}"
|
|
154
|
+
(by_criterion[criterion] ||= []) << "minimum_per_file #{percent}, only: #{target.inspect}"
|
|
151
155
|
end
|
|
152
156
|
end
|
|
153
157
|
render_coverage_blocks(by_criterion)
|
|
@@ -155,11 +159,11 @@ module SimpleCov
|
|
|
155
159
|
|
|
156
160
|
# Same, for a (deprecated) `minimum_coverage_by_group` argument.
|
|
157
161
|
def per_group_coverage_replacement(coverage)
|
|
158
|
-
by_criterion =
|
|
159
|
-
coverage.each do |group_name,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
by_criterion[criterion] << "minimum_per_group #{percent}, only: #{group_name.inspect}"
|
|
162
|
+
by_criterion = {} #: Hash[Symbol, Array[String]]
|
|
163
|
+
coverage.each do |group_name, thresholds|
|
|
164
|
+
normalized = (thresholds.is_a?(Numeric) ? {primary_coverage => thresholds} : thresholds) #: coverage_thresholds
|
|
165
|
+
normalized.each do |criterion, percent|
|
|
166
|
+
(by_criterion[criterion] ||= []) << "minimum_per_group #{percent}, only: #{group_name.inspect}"
|
|
163
167
|
end
|
|
164
168
|
end
|
|
165
169
|
render_coverage_blocks(by_criterion)
|
|
@@ -51,9 +51,10 @@ module SimpleCov
|
|
|
51
51
|
#
|
|
52
52
|
def coverage_path(path = nil)
|
|
53
53
|
if path
|
|
54
|
-
|
|
54
|
+
expanded = File.expand_path(path)
|
|
55
|
+
@coverage_path = expanded
|
|
55
56
|
@coverage_path_explicit = true
|
|
56
|
-
FileUtils.mkdir_p
|
|
57
|
+
FileUtils.mkdir_p expanded
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
@coverage_path ||= File.expand_path(coverage_dir, root)
|
|
@@ -65,9 +66,8 @@ module SimpleCov
|
|
|
65
66
|
# with SimpleCov.command_name("test:units").
|
|
66
67
|
#
|
|
67
68
|
def command_name(name = nil)
|
|
68
|
-
@
|
|
69
|
-
@
|
|
70
|
-
@name
|
|
69
|
+
@command_name = name unless name.nil?
|
|
70
|
+
@command_name ||= SimpleCov::CommandGuesser.guess
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
# Returns the hash of available profiles
|
|
@@ -80,17 +80,24 @@ module SimpleCov
|
|
|
80
80
|
# prepending SimpleCov to each config method.
|
|
81
81
|
#
|
|
82
82
|
def configure(&block)
|
|
83
|
-
|
|
83
|
+
# Both locals are read in the ensure clause, where flow analysis
|
|
84
|
+
# cannot see the assignments below; anchor their types up front.
|
|
85
|
+
saved = nil # : Hash[Symbol, untyped]?
|
|
86
|
+
block_context = block.binding.receiver # : untyped
|
|
84
87
|
|
|
85
|
-
# If the block was defined in our own context, instance_exec is
|
|
86
|
-
|
|
88
|
+
# If the block was defined in our own context, instance_exec is
|
|
89
|
+
# sufficient. The `_ =` erases the block's self-binding, which RBS
|
|
90
|
+
# can express but `instance_exec`'s core signature cannot accept.
|
|
91
|
+
return instance_exec(&(_ = block)) if equal?(block_context)
|
|
87
92
|
|
|
88
93
|
# Copy the caller's instance variables in so that references like @filter
|
|
89
94
|
# inside the block resolve to the caller's values, not ours.
|
|
90
95
|
saved = swap_ivars_from(block_context)
|
|
91
|
-
instance_exec(&block)
|
|
96
|
+
instance_exec(&(_ = block))
|
|
92
97
|
ensure
|
|
93
|
-
|
|
98
|
+
# @type var block_context: untyped
|
|
99
|
+
# @type var saved: Hash[Symbol, untyped]?
|
|
100
|
+
restore_ivars(block_context, saved) if saved
|
|
94
101
|
end
|
|
95
102
|
|
|
96
103
|
#
|
|
@@ -100,7 +107,8 @@ module SimpleCov
|
|
|
100
107
|
#
|
|
101
108
|
def at_exit(&block)
|
|
102
109
|
@at_exit = block if block
|
|
103
|
-
|
|
110
|
+
configured = @at_exit
|
|
111
|
+
return configured if configured
|
|
104
112
|
return proc {} unless active_session?
|
|
105
113
|
|
|
106
114
|
@at_exit = proc do
|
|
@@ -144,7 +152,8 @@ module SimpleCov
|
|
|
144
152
|
# SimpleCov.root, capitalized with underscores → spaces.
|
|
145
153
|
#
|
|
146
154
|
def project_name(new_name = nil)
|
|
147
|
-
|
|
155
|
+
current = defined?(@project_name) ? @project_name : nil
|
|
156
|
+
return current if current && new_name.nil?
|
|
148
157
|
|
|
149
158
|
@project_name = new_name if new_name.is_a?(String)
|
|
150
159
|
@project_name ||= File.basename(root).capitalize.tr("_", " ")
|
|
@@ -156,7 +165,7 @@ module SimpleCov
|
|
|
156
165
|
# of ours that would be clobbered. Returns the saved values for
|
|
157
166
|
# later restoration.
|
|
158
167
|
def swap_ivars_from(block_context)
|
|
159
|
-
saved = {}
|
|
168
|
+
saved = {} # : Hash[Symbol, untyped]
|
|
160
169
|
our_ivars = instance_variables
|
|
161
170
|
block_context.instance_variables.each do |ivar|
|
|
162
171
|
saved[ivar] = instance_variable_get(ivar) if our_ivars.include?(ivar)
|
|
@@ -14,9 +14,12 @@ module SimpleCov
|
|
|
14
14
|
class CoverageStatistics
|
|
15
15
|
attr_reader :total, :covered, :missed, :omitted, :strength, :percent
|
|
16
16
|
|
|
17
|
+
# Seed for the reduce in `.from`: covered, missed, omitted, strength.
|
|
18
|
+
ZERO_STATS = [0, 0, 0, 0.0].freeze #: [Integer, Integer, Integer, Float]
|
|
19
|
+
|
|
17
20
|
def self.from(coverage_statistics)
|
|
18
21
|
sum_covered, sum_missed, sum_omitted, sum_total_strength =
|
|
19
|
-
coverage_statistics.reduce(
|
|
22
|
+
coverage_statistics.reduce(ZERO_STATS) do |(covered, missed, omitted, total_strength), stats|
|
|
20
23
|
[
|
|
21
24
|
covered + stats.covered,
|
|
22
25
|
missed + stats.missed,
|
|
@@ -46,7 +46,8 @@ module SimpleCov
|
|
|
46
46
|
def minimum_by_group(result, thresholds)
|
|
47
47
|
thresholds.flat_map do |group_name, minimums|
|
|
48
48
|
group = lookup_group(result, group_name)
|
|
49
|
-
|
|
49
|
+
none = [] # : Array[Hash[Symbol, untyped]]
|
|
50
|
+
group ? group_minimum_violations(group_name, group, minimums) : none
|
|
50
51
|
end
|
|
51
52
|
end
|
|
52
53
|
|
data/lib/simplecov/defaults.rb
CHANGED
|
@@ -41,12 +41,12 @@ loop do
|
|
|
41
41
|
# it are intercepted and converted to configuration, with a deprecation
|
|
42
42
|
# warning. See `SimpleCov.with_dot_simplecov_autoload` and issue #581.
|
|
43
43
|
SimpleCov.with_dot_simplecov_autoload do
|
|
44
|
-
load filename
|
|
45
|
-
rescue LoadError, StandardError
|
|
44
|
+
load filename.to_s
|
|
45
|
+
rescue LoadError, StandardError => e
|
|
46
46
|
# simplecov:disable — only fires when .simplecov is unreadable
|
|
47
47
|
# or raises during load
|
|
48
48
|
warn "Warning: Error occurred while trying to load #{filename}. " \
|
|
49
|
-
"Error message: #{
|
|
49
|
+
"Error message: #{e.message}"
|
|
50
50
|
# simplecov:enable
|
|
51
51
|
end
|
|
52
52
|
break
|
data/lib/simplecov/directive.rb
CHANGED
|
@@ -55,8 +55,11 @@ module SimpleCov
|
|
|
55
55
|
# An unclosed `disable` block extends to the end of the file.
|
|
56
56
|
def self.disabled_ranges(src_lines)
|
|
57
57
|
lines = src_lines.to_a
|
|
58
|
-
ranges = CATEGORIES.to_h
|
|
59
|
-
|
|
58
|
+
ranges = CATEGORIES.to_h do |category|
|
|
59
|
+
empty = [] # : Array[Range[Integer]]
|
|
60
|
+
[category, empty]
|
|
61
|
+
end
|
|
62
|
+
open_starts = {} # : Hash[Symbol, Integer]
|
|
60
63
|
|
|
61
64
|
directives_in(lines).each { |directive| directive.apply(ranges, open_starts) }
|
|
62
65
|
open_starts.each { |category, start| ranges[category] << (start..lines.size) }
|
|
@@ -91,7 +94,9 @@ module SimpleCov
|
|
|
91
94
|
|
|
92
95
|
new(
|
|
93
96
|
line_number: line_number,
|
|
94
|
-
|
|
97
|
+
# `to_s` is a no-op: the pattern requires the `mode` group, so it
|
|
98
|
+
# is present whenever `match` is; it also satisfies the type checker.
|
|
99
|
+
mode: match[:mode].to_s.to_sym,
|
|
95
100
|
categories: parse_categories(match[:categories]),
|
|
96
101
|
inline: inline?(lines, line_number, column + match.begin(0))
|
|
97
102
|
)
|
|
@@ -15,7 +15,7 @@ module SimpleCov
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def report
|
|
18
|
-
violations.each { |violation|
|
|
18
|
+
violations.each { |violation| ExitCodes.print_error SimpleCov::Color.colorize(message_for(violation), :red) }
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def exit_code
|
|
@@ -32,7 +32,7 @@ module SimpleCov
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def report_violation(violation)
|
|
35
|
-
|
|
35
|
+
ExitCodes.print_error format(
|
|
36
36
|
"%<criterion>s coverage (%<actual>s) is above the expected maximum coverage (%<expected>.2f%%). " \
|
|
37
37
|
"Time to bump the threshold!",
|
|
38
38
|
criterion: violation.fetch(:criterion).capitalize,
|
|
@@ -17,7 +17,7 @@ module SimpleCov
|
|
|
17
17
|
|
|
18
18
|
def report
|
|
19
19
|
violations.each do |violation|
|
|
20
|
-
|
|
20
|
+
ExitCodes.print_error format(
|
|
21
21
|
"%<criterion>s coverage by file (%<actual>s) is below the expected minimum coverage " \
|
|
22
22
|
"(%<expected>.2f%%) in %<filename>s.",
|
|
23
23
|
criterion: violation.fetch(:criterion).capitalize,
|
|
@@ -16,7 +16,7 @@ module SimpleCov
|
|
|
16
16
|
|
|
17
17
|
def report
|
|
18
18
|
violations.each do |violation|
|
|
19
|
-
|
|
19
|
+
ExitCodes.print_error format(
|
|
20
20
|
"%<criterion>s coverage by group (%<actual>s) is below the expected minimum coverage " \
|
|
21
21
|
"(%<expected>.2f%%) in %<group_name>s.",
|
|
22
22
|
criterion: violation.fetch(:criterion).capitalize,
|
|
@@ -34,7 +34,7 @@ module SimpleCov
|
|
|
34
34
|
def report_violation(violation)
|
|
35
35
|
criterion = violation.fetch(:criterion)
|
|
36
36
|
actual = violation.fetch(:actual)
|
|
37
|
-
|
|
37
|
+
ExitCodes.print_error format(
|
|
38
38
|
"%<criterion>s coverage (%<actual>s) is below the expected minimum coverage (%<expected>.2f%%).",
|
|
39
39
|
criterion: criterion.capitalize,
|
|
40
40
|
actual: SimpleCov::Color.colorize_percent(actual),
|
|
@@ -47,9 +47,9 @@ module SimpleCov
|
|
|
47
47
|
worst = worst_files_for(criterion)
|
|
48
48
|
return if worst.empty?
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
ExitCodes.print_error " Lowest-coverage files (#{criterion}):"
|
|
51
51
|
worst.each do |path, percent|
|
|
52
|
-
|
|
52
|
+
ExitCodes.print_error format(
|
|
53
53
|
" %<percent>s %<path>s",
|
|
54
54
|
percent: SimpleCov::Color.colorize_percent(percent, format("%6.2f%%", percent)),
|
|
55
55
|
path: path
|
data/lib/simplecov/exit_codes.rb
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
|
+
# Exit statuses SimpleCov sets when coverage checks fail, and the output
|
|
5
|
+
# helper the enforcement machinery reports through.
|
|
4
6
|
module ExitCodes
|
|
5
7
|
SUCCESS = 0
|
|
6
8
|
EXCEPTION = 1
|
|
7
9
|
MINIMUM_COVERAGE = 2
|
|
8
10
|
MAXIMUM_COVERAGE_DROP = 3
|
|
9
11
|
MAXIMUM_COVERAGE = 4
|
|
12
|
+
|
|
13
|
+
# Threshold-violation reports and exit-status notices are the output of
|
|
14
|
+
# the enforcement feature, not Ruby warnings: routing them through
|
|
15
|
+
# `Kernel#warn` made `-W0` swallow the explanation for a failing exit
|
|
16
|
+
# code, let `Warning.warn` hooks (warning trackers, raise-on-warning
|
|
17
|
+
# test setups) intercept them mid-`at_exit`, and fed colorized text to
|
|
18
|
+
# warning logs. `print_errors false` remains the intended opt-out.
|
|
19
|
+
def self.print_error(message)
|
|
20
|
+
$stderr.puts message # rubocop:disable Style/StderrPuts
|
|
21
|
+
end
|
|
10
22
|
end
|
|
11
23
|
end
|
|
12
24
|
|
|
@@ -56,7 +56,7 @@ module SimpleCov
|
|
|
56
56
|
def warn_about_deferred_report
|
|
57
57
|
return unless print_errors
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
ExitCodes.print_error SimpleCov::Color.colorize(
|
|
60
60
|
"Skipping SimpleCov report — this process tracked no application code and a newer " \
|
|
61
61
|
"report already exists at #{coverage_path}. This usually means SimpleCov.start ran in a " \
|
|
62
62
|
"parent process (e.g. a Rakefile or Rails' Bundler.require) that shelled out to the test " \
|
|
@@ -97,7 +97,7 @@ module SimpleCov
|
|
|
97
97
|
# @api private
|
|
98
98
|
def exit_and_report_previous_error(exit_status)
|
|
99
99
|
if print_errors
|
|
100
|
-
|
|
100
|
+
ExitCodes.print_error SimpleCov::Color.colorize(
|
|
101
101
|
"Stopped processing SimpleCov as a previous error not related to SimpleCov has been detected",
|
|
102
102
|
:yellow
|
|
103
103
|
)
|
|
@@ -124,7 +124,7 @@ module SimpleCov
|
|
|
124
124
|
return unless exit_status.positive?
|
|
125
125
|
|
|
126
126
|
if print_errors
|
|
127
|
-
|
|
127
|
+
ExitCodes.print_error SimpleCov::Color.colorize(
|
|
128
128
|
"SimpleCov failed with exit #{exit_status} due to a coverage related error", :red
|
|
129
129
|
)
|
|
130
130
|
end
|