simplecov 0.22.0 → 1.1.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/LICENSE +1 -1
- data/README.md +71 -916
- data/exe/simplecov +11 -0
- data/lib/minitest/simplecov_plugin.rb +13 -5
- data/lib/simplecov/atomic_file.rb +70 -0
- data/lib/simplecov/autostart.rb +11 -0
- data/lib/simplecov/cli/clean.rb +87 -0
- data/lib/simplecov/cli/command_helpers.rb +55 -0
- data/lib/simplecov/cli/coverage.rb +86 -0
- data/lib/simplecov/cli/coverage_file.rb +65 -0
- data/lib/simplecov/cli/diff.rb +147 -0
- data/lib/simplecov/cli/dotfile.rb +111 -0
- data/lib/simplecov/cli/merge.rb +120 -0
- data/lib/simplecov/cli/open.rb +48 -0
- data/lib/simplecov/cli/report.rb +97 -0
- data/lib/simplecov/cli/run.rb +36 -0
- data/lib/simplecov/cli/serve/report_preparer.rb +29 -0
- data/lib/simplecov/cli/serve/static_file_handler.rb +122 -0
- data/lib/simplecov/cli/serve.rb +85 -0
- data/lib/simplecov/cli/uncovered.rb +103 -0
- data/lib/simplecov/cli.rb +163 -0
- data/lib/simplecov/color.rb +83 -0
- data/lib/simplecov/combine/branches_combiner.rb +54 -7
- data/lib/simplecov/combine/coverage_accumulator.rb +268 -0
- data/lib/simplecov/combine/identity_interner.rb +30 -0
- data/lib/simplecov/combine/interned_counts.rb +30 -0
- data/lib/simplecov/combine/lines_combiner.rb +53 -23
- data/lib/simplecov/combine/methods_combiner.rb +74 -0
- data/lib/simplecov/combine/results_combiner.rb +13 -36
- data/lib/simplecov/combine.rb +5 -23
- data/lib/simplecov/command_guesser.rb +104 -33
- data/lib/simplecov/configuration/coverage.rb +172 -0
- data/lib/simplecov/configuration/coverage_criteria.rb +154 -0
- data/lib/simplecov/configuration/eval_coverage.rb +41 -0
- data/lib/simplecov/configuration/filters.rb +164 -0
- data/lib/simplecov/configuration/formatting.rb +138 -0
- data/lib/simplecov/configuration/groups.rb +42 -0
- data/lib/simplecov/configuration/ignored_entries.rb +63 -0
- data/lib/simplecov/configuration/merging.rb +165 -0
- data/lib/simplecov/configuration/thresholds.rb +179 -0
- data/lib/simplecov/configuration.rb +83 -420
- data/lib/simplecov/coverage_json.rb +24 -0
- data/lib/simplecov/coverage_statistics.rb +16 -10
- data/lib/simplecov/coverage_violations.rb +159 -0
- data/lib/simplecov/defaults.rb +34 -23
- data/lib/simplecov/deprecation.rb +45 -0
- data/lib/simplecov/directive.rb +167 -0
- data/lib/simplecov/exit_codes/check.rb +33 -0
- data/lib/simplecov/exit_codes/exit_code_handling.rb +8 -2
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +18 -65
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +32 -0
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +17 -36
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +30 -0
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +37 -33
- data/lib/simplecov/exit_codes.rb +20 -0
- data/lib/simplecov/exit_handling.rb +128 -0
- data/lib/simplecov/file_list.rb +70 -24
- data/lib/simplecov/filter.rb +105 -26
- data/lib/simplecov/formatter/base.rb +118 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +97 -0
- data/lib/simplecov/formatter/html_formatter/public/index.html +87 -0
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +96 -0
- data/lib/simplecov/formatter/html_formatter.rb +105 -0
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +88 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +118 -0
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +93 -0
- data/lib/simplecov/formatter/json_formatter.rb +46 -0
- data/lib/simplecov/formatter/multi_formatter.rb +12 -8
- data/lib/simplecov/formatter/simple_formatter.rb +14 -11
- data/lib/simplecov/formatter.rb +16 -0
- data/lib/simplecov/group_names.rb +32 -0
- data/lib/simplecov/last_run.rb +18 -4
- data/lib/simplecov/lines_classifier.rb +48 -15
- data/lib/simplecov/load_global_config.rb +12 -4
- data/lib/simplecov/parallel_adapters/base.rb +78 -0
- data/lib/simplecov/parallel_adapters/generic.rb +45 -0
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
- data/lib/simplecov/parallel_adapters.rb +85 -0
- data/lib/simplecov/parallel_coordination.rb +134 -0
- data/lib/simplecov/parallel_result_merger.rb +230 -0
- data/lib/simplecov/process.rb +52 -12
- data/lib/simplecov/profiles/bundler_filter.rb +1 -1
- data/lib/simplecov/profiles/hidden_filter.rb +1 -1
- data/lib/simplecov/profiles/rails.rb +24 -10
- data/lib/simplecov/profiles/root_filter.rb +6 -5
- data/lib/simplecov/profiles/strict.rb +32 -0
- data/lib/simplecov/profiles/test_frameworks.rb +1 -4
- data/lib/simplecov/profiles.rb +32 -3
- data/lib/simplecov/report_deferral.rb +49 -0
- data/lib/simplecov/report_stamp.rb +28 -0
- data/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
- data/lib/simplecov/result/source_file_builder.rb +51 -0
- data/lib/simplecov/result.rb +145 -26
- data/lib/simplecov/result_adapter.rb +139 -8
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
- data/lib/simplecov/result_merger/resultset_file.rb +74 -0
- data/lib/simplecov/result_merger/resultset_run_identity.rb +67 -0
- data/lib/simplecov/result_merger/resultset_store.rb +53 -0
- data/lib/simplecov/result_merger/unloaded_files.rb +103 -0
- data/lib/simplecov/result_merger.rb +109 -114
- data/lib/simplecov/result_processing.rb +218 -0
- data/lib/simplecov/run_identity.rb +77 -0
- data/lib/simplecov/simulate_coverage.rb +83 -12
- data/lib/simplecov/source_file/branch.rb +1 -3
- data/lib/simplecov/source_file/branch_builder.rb +114 -0
- data/lib/simplecov/source_file/builder_context.rb +28 -0
- data/lib/simplecov/source_file/line.rb +9 -4
- data/lib/simplecov/source_file/line_builder.rb +43 -0
- data/lib/simplecov/source_file/method.rb +58 -0
- data/lib/simplecov/source_file/method_builder.rb +60 -0
- data/lib/simplecov/source_file/ruby_data_parser.rb +110 -0
- data/lib/simplecov/source_file/skip_chunks.rb +72 -0
- data/lib/simplecov/source_file/source_loader.rb +79 -0
- data/lib/simplecov/source_file/statistics.rb +65 -0
- data/lib/simplecov/source_file.rb +66 -232
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +239 -0
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +236 -0
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +62 -0
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +55 -0
- data/lib/simplecov/static_coverage_extractor/value_position.rb +74 -0
- data/lib/simplecov/static_coverage_extractor/visitor.rb +183 -0
- data/lib/simplecov/static_coverage_extractor.rb +110 -0
- data/lib/simplecov/unloaded_file_injector.rb +75 -0
- data/lib/simplecov/useless_results_remover.rb +16 -7
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov-html.rb +4 -0
- data/lib/simplecov.rb +185 -380
- data/lib/simplecov_json_formatter.rb +4 -0
- data/sig/simplecov.rbs +1777 -0
- metadata +111 -62
- data/CHANGELOG.md +0 -191
- data/doc/alternate-formatters.md +0 -71
- data/doc/commercial-services.md +0 -25
- data/doc/editor-integration.md +0 -18
- data/lib/simplecov/combine/files_combiner.rb +0 -24
- data/lib/simplecov/default_formatter.rb +0 -20
|
@@ -5,38 +5,68 @@ module SimpleCov
|
|
|
5
5
|
#
|
|
6
6
|
# Combine two different lines coverage results on same file
|
|
7
7
|
#
|
|
8
|
-
# Should be called through `
|
|
8
|
+
# Should be called through `CoverageAccumulator`.
|
|
9
9
|
module LinesCombiner
|
|
10
10
|
module_function
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
# Return depends on coverage in a specific line
|
|
12
|
+
# Folds `source` into `target` rather than building a third array.
|
|
13
|
+
# Only for a `target` the caller owns outright: a caller holding a
|
|
14
|
+
# reference to it (e.g. the parsed `coverage` key of a resultset
|
|
15
|
+
# hash being passed into a second merge) would see it change.
|
|
16
|
+
# `source` is never touched.
|
|
21
17
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
18
|
+
# Two runs of the same source file should agree on which lines
|
|
19
|
+
# are coverage-relevant (`nil` for comments / whitespace, `0`+
|
|
20
|
+
# for executable). When they don't, treat "relevant on either
|
|
21
|
+
# side" as relevant rather than masking a real `0` as `nil`,
|
|
22
|
+
# which would silently drop an uncovered line from the
|
|
23
|
+
# denominator and inflate the percentage:
|
|
24
24
|
#
|
|
25
|
-
# Logic:
|
|
26
|
-
#
|
|
27
|
-
# => nil + 0 = nil
|
|
28
25
|
# => nil + nil = nil
|
|
29
|
-
# =>
|
|
26
|
+
# => nil + int = int (preserves a relevant-but-uncovered 0)
|
|
27
|
+
# => int + int = int (sum)
|
|
28
|
+
#
|
|
29
|
+
# The loop is written out rather than dispatching a block per
|
|
30
|
+
# element because this is the innermost loop of a merge: a
|
|
31
|
+
# 160-worker run over ~1,800 files folds tens of millions of line
|
|
32
|
+
# counts through it, and a call per count is a measurable share of
|
|
33
|
+
# that.
|
|
30
34
|
#
|
|
31
|
-
# @return [
|
|
32
|
-
def
|
|
33
|
-
|
|
35
|
+
# @return [Array] the array to keep — `target` itself once there is one
|
|
36
|
+
def merge_into(target, source)
|
|
37
|
+
return target unless source
|
|
38
|
+
return source.dup unless target
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
size = source.size
|
|
41
|
+
target.concat(Array.new(size - target.size)) if target.size < size
|
|
42
|
+
sum_into(target, source, size)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Split out only to keep `merge_into` short; it is the same loop.
|
|
46
|
+
#
|
|
47
|
+
# The `Integer` tests coerce malformed counts (a "3" written by a
|
|
48
|
+
# hand-edited or foreign resultset, a JSON float) with `to_i`, so
|
|
49
|
+
# external input merges to a wrong answer instead of raising out of
|
|
50
|
+
# the middle of a merge. An `is_a?` per count is far cheaper than
|
|
51
|
+
# the block dispatch this loop exists to avoid, and the well-formed
|
|
52
|
+
# fast path stays branch-for-branch what it was.
|
|
53
|
+
def sum_into(target, source, size)
|
|
54
|
+
index = 0
|
|
55
|
+
while index < size
|
|
56
|
+
if (value = source[index])
|
|
57
|
+
value = value.to_i unless value.is_a?(Integer)
|
|
58
|
+
existing = target[index]
|
|
59
|
+
target[index] = existing.is_a?(Integer) ? existing + value : coerce_add(existing, value)
|
|
60
|
+
end
|
|
61
|
+
index += 1
|
|
39
62
|
end
|
|
63
|
+
target
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The rare arm: `existing` is nil (line not yet relevant in the
|
|
67
|
+
# target) or malformed external input.
|
|
68
|
+
def coerce_add(existing, value)
|
|
69
|
+
existing.nil? ? value : existing.to_i + value
|
|
40
70
|
end
|
|
41
71
|
end
|
|
42
72
|
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../source_file/ruby_data_parser"
|
|
4
|
+
require_relative "identity_interner"
|
|
5
|
+
require_relative "interned_counts"
|
|
6
|
+
|
|
7
|
+
module SimpleCov
|
|
8
|
+
module Combine
|
|
9
|
+
#
|
|
10
|
+
# Combine different method coverage results on a single file.
|
|
11
|
+
#
|
|
12
|
+
# Should be called through `CoverageAccumulator`.
|
|
13
|
+
module MethodsCombiner
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
#
|
|
17
|
+
# Return merged methods or the existing methods if other is missing.
|
|
18
|
+
#
|
|
19
|
+
# Method coverage maps `[class, name, start_line, start_col, end_line,
|
|
20
|
+
# end_col]` keys to hit counts. Keys are matched on their SOURCE
|
|
21
|
+
# identity — the location, ignoring the class and name elements —
|
|
22
|
+
# because Ruby records one entry per defined method: the same
|
|
23
|
+
# `define_method` block defined onto different classes, or under
|
|
24
|
+
# different names, in different processes arrives with different
|
|
25
|
+
# receivers or names for the same source method, and matching on the
|
|
26
|
+
# full key would keep both, letting a never-called copy's 0 shadow a
|
|
27
|
+
# covered method after merge (issue #1234). Combining sums the hit
|
|
28
|
+
# counts for matching methods and preserves methods that only appear
|
|
29
|
+
# in one result.
|
|
30
|
+
#
|
|
31
|
+
# @return [Hash]
|
|
32
|
+
#
|
|
33
|
+
def combine(coverage_a, coverage_b)
|
|
34
|
+
merged = absorb({}, coverage_a) #: Hash[untyped, [untyped, Integer]]
|
|
35
|
+
materialize(absorb(merged, coverage_b))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Folds `coverage` into `target`, an interned table keyed by method
|
|
39
|
+
# source identity. See `BranchesCombiner.absorb` for why a fold keeps
|
|
40
|
+
# its accumulator interned.
|
|
41
|
+
#
|
|
42
|
+
# A `nil` `target` stays `nil` until some resultset actually carries
|
|
43
|
+
# methods, so a merge can tell "no method data anywhere" apart from
|
|
44
|
+
# "method data that covers nothing".
|
|
45
|
+
#
|
|
46
|
+
# @return [Hash, nil] `target`, created on the first coverage seen
|
|
47
|
+
def absorb(target, coverage)
|
|
48
|
+
return target unless coverage
|
|
49
|
+
|
|
50
|
+
target ||= {} #: Hash[untyped, [untyped, Integer]]
|
|
51
|
+
InternedCounts.absorb_counts(target, coverage, identities)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Turns an interned table back into the tuple-keyed hash the rest of
|
|
55
|
+
# SimpleCov reads. Done once, at the end of a fold.
|
|
56
|
+
#
|
|
57
|
+
# @return [Hash]
|
|
58
|
+
def materialize(target)
|
|
59
|
+
target.values.to_h
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Bounded by the project's method count, like `RubyDataParser`'s parse
|
|
63
|
+
# cache.
|
|
64
|
+
def identities
|
|
65
|
+
@identities ||= IdentityInterner.build { |key| source_identity(key) }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def source_identity(key)
|
|
69
|
+
_class_name, _method_name, *location = SourceFile::RubyDataParser.call(key)
|
|
70
|
+
location.freeze
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "coverage_accumulator"
|
|
4
|
+
|
|
3
5
|
module SimpleCov
|
|
4
6
|
module Combine
|
|
5
7
|
# There might be reports from different kinds of tests,
|
|
@@ -12,48 +14,23 @@ module SimpleCov
|
|
|
12
14
|
|
|
13
15
|
#
|
|
14
16
|
# Combine process explanation
|
|
15
|
-
# =>
|
|
16
|
-
# ==>
|
|
17
|
+
# => ResultsCombiner: hand every result to one accumulator.
|
|
18
|
+
# ==> CoverageAccumulator::MergedFile: hold one file's merged coverage.
|
|
17
19
|
# ===> LinesCombiner: combine lines results.
|
|
18
20
|
# ===> BranchesCombiner: combine branches results.
|
|
21
|
+
# ===> MethodsCombiner: combine methods results.
|
|
19
22
|
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
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
|
|
23
|
+
# Callers that read their results one at a time (`ResultMerger`, to
|
|
24
|
+
# keep a big CI run's resultsets out of memory all at once) should
|
|
25
|
+
# drive a `CoverageAccumulator` directly instead of collecting the
|
|
26
|
+
# results to pass here.
|
|
33
27
|
#
|
|
34
28
|
# @return [Hash]
|
|
35
29
|
#
|
|
36
|
-
def
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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)
|
|
30
|
+
def combine(*results)
|
|
31
|
+
accumulator = CoverageAccumulator.new
|
|
32
|
+
results.each { |result| accumulator.absorb(result) }
|
|
33
|
+
accumulator.result || {}
|
|
57
34
|
end
|
|
58
35
|
end
|
|
59
36
|
end
|
data/lib/simplecov/combine.rb
CHANGED
|
@@ -1,30 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
|
-
#
|
|
4
|
+
# Namespace for the pieces that combine coverage results.
|
|
5
|
+
#
|
|
6
|
+
# `CoverageAccumulator` is the entry point: it folds any number of
|
|
7
|
+
# resultsets together, delegating one file's lines, branches and methods to
|
|
8
|
+
# the combiner modules alongside it.
|
|
5
9
|
#
|
|
6
10
|
module Combine
|
|
7
|
-
module_function
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
# Combine two coverage based on the given combiner_module.
|
|
11
|
-
#
|
|
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
|
-
#
|
|
15
|
-
# @return [Hash]
|
|
16
|
-
def combine(combiner_module, coverage_a, coverage_b)
|
|
17
|
-
return existing_coverage(coverage_a, coverage_b) if empty_coverage?(coverage_a, coverage_b)
|
|
18
|
-
|
|
19
|
-
combiner_module.combine(coverage_a, coverage_b)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def empty_coverage?(coverage_a, coverage_b)
|
|
23
|
-
!(coverage_a && coverage_b)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def existing_coverage(coverage_a, coverage_b)
|
|
27
|
-
coverage_a || coverage_b
|
|
28
|
-
end
|
|
29
11
|
end
|
|
30
12
|
end
|
|
@@ -11,53 +11,124 @@ module SimpleCov
|
|
|
11
11
|
# have a habit of tampering with ARGV, which makes i.e. the automatic distinction
|
|
12
12
|
# between rails unit/functional/integration tests impossible without this cached
|
|
13
13
|
# item.
|
|
14
|
-
|
|
14
|
+
attr_reader :original_run_command
|
|
15
|
+
|
|
16
|
+
# Assigning the flattened command clears any separately recorded program
|
|
17
|
+
# name so the two can never describe different runs. Callers that know the
|
|
18
|
+
# real one assign it immediately afterwards, as `defaults.rb` does.
|
|
19
|
+
def original_run_command=(command)
|
|
20
|
+
@original_program_name = nil
|
|
21
|
+
@original_run_command = command
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The invoked program on its own. `original_run_command` joins
|
|
25
|
+
# `$PROGRAM_NAME` and `ARGV` with a space, so a program path containing
|
|
26
|
+
# one (`/opt/My Ruby/bin/rspec`) cannot be recovered from it afterwards.
|
|
27
|
+
# Falls back to the first token for callers that set only the flattened
|
|
28
|
+
# command, which is every caller outside `defaults.rb`.
|
|
29
|
+
attr_writer :original_program_name
|
|
30
|
+
|
|
31
|
+
def original_program_name
|
|
32
|
+
@original_program_name || original_run_command.to_s[/\A\s*(\S+)/, 1]
|
|
33
|
+
end
|
|
15
34
|
|
|
16
35
|
def guess
|
|
17
|
-
|
|
36
|
+
framework = from_executable_name || from_command_line_options || from_defined_constants
|
|
37
|
+
[framework, parallel_data].compact.join(" ")
|
|
18
38
|
end
|
|
19
39
|
|
|
20
40
|
private
|
|
21
41
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
42
|
+
# When parallel_tests (or a compatible runner) is driving the suite,
|
|
43
|
+
# tag the command name with this worker's position in the pool.
|
|
44
|
+
def parallel_data
|
|
45
|
+
groups, number = ENV.values_at("PARALLEL_TEST_GROUPS", "TEST_ENV_NUMBER")
|
|
46
|
+
return unless groups && number
|
|
25
47
|
|
|
26
|
-
|
|
48
|
+
# parallel_tests sets the first worker's TEST_ENV_NUMBER to "" rather
|
|
49
|
+
# than "1"; restore the position so the rendered label reads cleanly.
|
|
27
50
|
number = "1" if number.empty?
|
|
28
|
-
"(#{number}/#{
|
|
51
|
+
"(#{number}/#{groups})"
|
|
29
52
|
end
|
|
30
53
|
|
|
54
|
+
# The command is `"#{$PROGRAM_NAME} #{ARGV.join(' ')}"`, so a keyword only
|
|
55
|
+
# carries meaning when it begins a path segment or an argument. Matching it
|
|
56
|
+
# as a bare substring lets any part of the *interpreter* path decide the
|
|
57
|
+
# suite name: a Ruby installed under `latest/bin` (as mise does) contains
|
|
58
|
+
# "test/", so every run through it was labelled "Unit Tests". The same
|
|
59
|
+
# applies within ARGV, where `spec/greatest/` used to match just as wrongly.
|
|
60
|
+
#
|
|
61
|
+
# A backslash bounds a segment too, or a Windows `spec\foo_spec.rb` would
|
|
62
|
+
# lose a match the bare substrings used to make. The `test/` patterns below
|
|
63
|
+
# still spell their separator as `/` only, so a backslash `test\functional\`
|
|
64
|
+
# goes unrecognised there, but that is how it has always been rather than
|
|
65
|
+
# something the anchoring took away.
|
|
66
|
+
SEGMENT_START = %r{(?<![^\s/\\])}
|
|
67
|
+
private_constant :SEGMENT_START
|
|
68
|
+
SEGMENT_END = %r{(?=[\s/\\]|\z)}
|
|
69
|
+
private_constant :SEGMENT_END
|
|
70
|
+
|
|
71
|
+
COMMAND_LINE_FRAMEWORKS = {
|
|
72
|
+
%r{#{SEGMENT_START}test/functional/} => "Functional Tests",
|
|
73
|
+
%r{#{SEGMENT_START}test/\{.*functional.*\}/} => "Functional Tests",
|
|
74
|
+
%r{#{SEGMENT_START}test/integration/} => "Integration Tests",
|
|
75
|
+
%r{#{SEGMENT_START}test/} => "Unit Tests",
|
|
76
|
+
/#{SEGMENT_START}spec#{SEGMENT_END}/ => "RSpec",
|
|
77
|
+
/#{SEGMENT_START}cucumber#{SEGMENT_END}/ => "Cucumber Features",
|
|
78
|
+
/#{SEGMENT_START}features#{SEGMENT_END}/ => "Cucumber Features"
|
|
79
|
+
}.freeze
|
|
80
|
+
private_constant :COMMAND_LINE_FRAMEWORKS
|
|
81
|
+
|
|
31
82
|
def from_command_line_options
|
|
32
|
-
|
|
33
|
-
when /test\/functional\//, /test\/\{.*functional.*\}\//
|
|
34
|
-
"Functional Tests"
|
|
35
|
-
when /test\/integration\//
|
|
36
|
-
"Integration Tests"
|
|
37
|
-
when /test\//
|
|
38
|
-
"Unit Tests"
|
|
39
|
-
when /spec/
|
|
40
|
-
"RSpec"
|
|
41
|
-
when /cucumber/, /features/
|
|
42
|
-
"Cucumber Features"
|
|
43
|
-
end
|
|
83
|
+
COMMAND_LINE_FRAMEWORKS.find { |pattern, _| pattern.match?(original_run_command.to_s) }&.last
|
|
44
84
|
end
|
|
45
85
|
|
|
86
|
+
# Which binary was invoked outranks everything else on the command line.
|
|
87
|
+
# `rspec features` is an RSpec suite whose examples live in features/, not
|
|
88
|
+
# a Cucumber run, and only the executable name says so. Generic runners
|
|
89
|
+
# (`ruby`, `rake`, a bare `*_test.rb` script) are absent on purpose: they
|
|
90
|
+
# name no framework, so those runs fall through to the path patterns, which
|
|
91
|
+
# is what keeps the test/functional vs test/integration split working.
|
|
92
|
+
EXECUTABLE_FRAMEWORKS = {
|
|
93
|
+
"rspec" => "RSpec",
|
|
94
|
+
"cucumber" => "Cucumber Features"
|
|
95
|
+
}.freeze
|
|
96
|
+
private_constant :EXECUTABLE_FRAMEWORKS
|
|
97
|
+
|
|
98
|
+
def from_executable_name
|
|
99
|
+
program = original_program_name
|
|
100
|
+
return unless program
|
|
101
|
+
|
|
102
|
+
# Trim any extension so Windows' rspec.bat is recognized too.
|
|
103
|
+
EXECUTABLE_FRAMEWORKS[File.basename(program, ".*")]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Inner array literals after the first are flagged uncovered by Ruby's
|
|
107
|
+
# Coverage module even though the constant evaluates as a whole — known
|
|
108
|
+
# quirk with multi-line array literals.
|
|
109
|
+
DEFINED_CONSTANT_FRAMEWORKS = [
|
|
110
|
+
["RSpec", -> { defined?(::RSpec) }],
|
|
111
|
+
["Unit Tests", -> { defined?(Test::Unit) }], # simplecov:disable
|
|
112
|
+
["Minitest", -> { defined?(::Minitest) }], # simplecov:disable
|
|
113
|
+
["MiniTest", -> { defined?(MiniTest) }] # simplecov:disable
|
|
114
|
+
].freeze
|
|
115
|
+
private_constant :DEFINED_CONSTANT_FRAMEWORKS
|
|
116
|
+
|
|
117
|
+
# If the command regexps fail, let's try checking defined constants.
|
|
46
118
|
def from_defined_constants
|
|
47
|
-
#
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end
|
|
119
|
+
# simplecov:disable branch — first iter returns when ::RSpec is defined; later branches unreachable
|
|
120
|
+
DEFINED_CONSTANT_FRAMEWORKS.each { |name, defined_check| return name if defined_check.call }
|
|
121
|
+
# simplecov:enable branch
|
|
122
|
+
|
|
123
|
+
# TODO: Provide link to docs/wiki article
|
|
124
|
+
# simplecov:disable — only fires when no framework is detected, which
|
|
125
|
+
# is impossible while our own specs are running under rspec
|
|
126
|
+
warn(
|
|
127
|
+
"SimpleCov failed to recognize the test framework and/or suite used. " \
|
|
128
|
+
"Please specify manually using SimpleCov.command_name 'Unit Tests'."
|
|
129
|
+
)
|
|
130
|
+
"Unknown Test Framework"
|
|
131
|
+
# simplecov:enable
|
|
61
132
|
end
|
|
62
133
|
end
|
|
63
134
|
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
# The `coverage` configuration method configures each coverage criterion
|
|
5
|
+
# (`:line`, `:branch`, `:method`, `:eval`) uniformly in one place: naming a
|
|
6
|
+
# criterion enables it,
|
|
7
|
+
# and every threshold is declared with the same syntax regardless of which
|
|
8
|
+
# criterion it applies to. Because the criterion is fixed by the enclosing
|
|
9
|
+
# `coverage` call, threshold values are always plain percentages — there is
|
|
10
|
+
# no per-criterion Hash competing with the value for a slot.
|
|
11
|
+
#
|
|
12
|
+
# SimpleCov.start do
|
|
13
|
+
# coverage :line do
|
|
14
|
+
# minimum 90
|
|
15
|
+
# minimum_per_file 80
|
|
16
|
+
# minimum_per_file 100, only: "app/mailers/request_mailer.rb"
|
|
17
|
+
# maximum_drop 5
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# coverage :branch, minimum: 80
|
|
21
|
+
# coverage :method, minimum: 100
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# Line coverage is enabled by default, so `coverage :line` is only needed to
|
|
25
|
+
# set line thresholds or options. Thresholds feed the same internal stores
|
|
26
|
+
# as the flat `minimum_coverage` family, so enforcement is unchanged.
|
|
27
|
+
module Configuration
|
|
28
|
+
# One-liner keyword options `coverage` accepts, each forwarding to the
|
|
29
|
+
# `CoverageCriterion` verb of the same name. `minimum_per_group` is omitted
|
|
30
|
+
# because it needs an `only:` target, so it's block-only.
|
|
31
|
+
COVERAGE_THRESHOLD_OPTIONS = %i[minimum maximum exact maximum_drop minimum_per_file].freeze
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Configure (and, unless `enabled: false`, enable) a coverage criterion.
|
|
35
|
+
#
|
|
36
|
+
# Threshold options mirror the block verbs for one-liner use:
|
|
37
|
+
# coverage :branch, minimum: 80, maximum_drop: 5
|
|
38
|
+
#
|
|
39
|
+
# `primary: true` makes this the report's leading criterion (and the one a
|
|
40
|
+
# bare `minimum_coverage 90` targets). `oneshot: true` (valid only for
|
|
41
|
+
# `:line`) selects the faster oneshot-lines mode.
|
|
42
|
+
#
|
|
43
|
+
def coverage(criterion, primary: false, enabled: true, oneshot: false, **thresholds, &block)
|
|
44
|
+
criterion = enable_coverage_criterion(criterion, enabled: enabled, oneshot: oneshot)
|
|
45
|
+
# The cast admits :eval, which primary_coverage rejects at runtime
|
|
46
|
+
# (it is a standalone toggle, never in the enabled-criteria set).
|
|
47
|
+
primary_coverage(_ = criterion) if primary
|
|
48
|
+
|
|
49
|
+
configurator = CoverageCriterion.new(self, criterion)
|
|
50
|
+
apply_threshold_options(configurator, thresholds)
|
|
51
|
+
configurator.instance_eval(&block) if block
|
|
52
|
+
|
|
53
|
+
criterion
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Forward the one-liner threshold keywords (`coverage :branch, minimum: 80`)
|
|
59
|
+
# to the matching `CoverageCriterion` verbs, rejecting anything that isn't a
|
|
60
|
+
# recognized threshold option.
|
|
61
|
+
def apply_threshold_options(configurator, options)
|
|
62
|
+
options.each do |verb, value|
|
|
63
|
+
unless COVERAGE_THRESHOLD_OPTIONS.include?(verb)
|
|
64
|
+
raise SimpleCov::ConfigurationError,
|
|
65
|
+
"Unknown `coverage` option #{verb.inspect}. " \
|
|
66
|
+
"Supported options are #{COVERAGE_THRESHOLD_OPTIONS.inspect}."
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
configurator.public_send(verb, value)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Enable or disable the criterion (or its oneshot / eval variant) and
|
|
74
|
+
# return the criterion symbol that thresholds should be stored under.
|
|
75
|
+
def enable_coverage_criterion(criterion, enabled:, oneshot:)
|
|
76
|
+
criterion = resolve_criterion_variant(criterion, oneshot)
|
|
77
|
+
enabled ? enable_coverage(criterion) : disable_coverage(criterion)
|
|
78
|
+
criterion
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def resolve_criterion_variant(criterion, oneshot)
|
|
82
|
+
return criterion unless oneshot
|
|
83
|
+
|
|
84
|
+
unless criterion == :line
|
|
85
|
+
raise SimpleCov::ConfigurationError, "`oneshot: true` is only valid for `coverage :line`"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
ONESHOT_LINE_COVERAGE_CRITERION
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# @api private — threshold-store writers used by CoverageCriterion. They
|
|
92
|
+
# write the same `@minimum_coverage` / `@maximum_coverage` / ... hashes the
|
|
93
|
+
# flat threshold methods populate, so the exit-code checks are unchanged.
|
|
94
|
+
def store_overall_threshold(setting, criterion, percent)
|
|
95
|
+
raise_on_invalid_coverage({criterion => percent}, setting.to_s)
|
|
96
|
+
public_send(setting)[criterion] = percent
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def store_minimum_per_file(criterion, percent, target)
|
|
100
|
+
raise_on_invalid_coverage({criterion => percent}, "minimum_coverage_by_file")
|
|
101
|
+
return minimum_coverage_by_file[criterion] = percent if target.nil?
|
|
102
|
+
|
|
103
|
+
unless target.is_a?(String) || target.is_a?(Regexp)
|
|
104
|
+
raise SimpleCov::ConfigurationError, "`only:` must be a String path or Regexp, got #{target.inspect}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
(minimum_coverage_by_file_overrides[target] ||= {})[criterion] = percent
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def store_minimum_per_group(criterion, percent, group_name)
|
|
111
|
+
raise_on_invalid_coverage({criterion => percent}, "minimum_coverage_by_group")
|
|
112
|
+
# Normalize like `group` does, so `only: :Models` finds the group
|
|
113
|
+
# defined as `group "Models"` at check time instead of storing a
|
|
114
|
+
# Symbol key no lookup ever matches.
|
|
115
|
+
(minimum_coverage_by_group[GroupNames.normalize(group_name)] ||= {})[criterion] = percent
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
#
|
|
119
|
+
# Receiver for a `coverage <criterion> do ... end` block. Each verb writes a
|
|
120
|
+
# threshold for the single criterion the block configures, so the value is
|
|
121
|
+
# always a plain percentage (`minimum_per_file 100` is unambiguous) and the
|
|
122
|
+
# syntax is identical across line, branch, and method coverage.
|
|
123
|
+
#
|
|
124
|
+
class CoverageCriterion
|
|
125
|
+
def initialize(config, criterion)
|
|
126
|
+
@config = config
|
|
127
|
+
@criterion = criterion
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Overall (suite-wide) minimum for this criterion.
|
|
131
|
+
def minimum(percent)
|
|
132
|
+
@config.send(:store_overall_threshold, :minimum_coverage, @criterion, percent)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Overall maximum: fails the build if coverage rises above it. Paired with
|
|
136
|
+
# `minimum` (or via `exact`) this pins coverage so an unexpected jump fails.
|
|
137
|
+
def maximum(percent)
|
|
138
|
+
@config.send(:store_overall_threshold, :maximum_coverage, @criterion, percent)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Pin coverage to an exact figure (sets both `minimum` and `maximum`).
|
|
142
|
+
def exact(percent)
|
|
143
|
+
minimum(percent)
|
|
144
|
+
maximum(percent)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Maximum allowed drop between runs (`maximum_drop 0` refuses any drop).
|
|
148
|
+
def maximum_drop(percent)
|
|
149
|
+
@config.send(:store_overall_threshold, :maximum_coverage_drop, @criterion, percent)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Per-file minimum. With no `only:`, sets the default applied to every
|
|
153
|
+
# file; with `only:` (a String path or Regexp), overrides that default
|
|
154
|
+
# for the matching files.
|
|
155
|
+
def minimum_per_file(percent, only: nil)
|
|
156
|
+
@config.send(:store_minimum_per_file, @criterion, percent, only)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Per-group minimum for the named group (defined via `group`).
|
|
160
|
+
def minimum_per_group(percent, only:)
|
|
161
|
+
@config.send(:store_minimum_per_group, @criterion, percent, only)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Make this criterion the report's primary (leading) criterion.
|
|
165
|
+
def primary
|
|
166
|
+
# @criterion is Symbol-wide because this receiver is also built for
|
|
167
|
+
# :eval; primary_coverage validates at runtime.
|
|
168
|
+
@config.primary_coverage(_ = @criterion)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|