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
|
@@ -2,25 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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 by file (%<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
|
|
5
|
+
# Fails when any individual file falls below the configured minimum
|
|
6
|
+
# coverage for any criterion.
|
|
7
|
+
class MinimumCoverageByFileCheck < Check
|
|
8
|
+
def initialize(result, minimum_coverage_by_file, overrides = {})
|
|
9
|
+
super(result, minimum_coverage_by_file)
|
|
10
|
+
@overrides = overrides
|
|
24
11
|
end
|
|
25
12
|
|
|
26
13
|
def exit_code
|
|
@@ -29,25 +16,19 @@ module SimpleCov
|
|
|
29
16
|
|
|
30
17
|
private
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def minimum_violations
|
|
35
|
-
@minimum_violations ||=
|
|
36
|
-
compute_minimum_coverage_data.select do |achieved|
|
|
37
|
-
achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
|
|
38
|
-
end
|
|
19
|
+
def compute_violations
|
|
20
|
+
SimpleCov::CoverageViolations.minimum_by_file(result, thresholds, @overrides)
|
|
39
21
|
end
|
|
40
22
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
23
|
+
def report_violation(violation)
|
|
24
|
+
ExitCodes.print_error format(
|
|
25
|
+
"%<criterion>s coverage by file (%<actual>s) is below the expected minimum coverage " \
|
|
26
|
+
"(%<expected>.2f%%) in %<filename>s.",
|
|
27
|
+
criterion: violation.fetch(:criterion).capitalize,
|
|
28
|
+
actual: SimpleCov::Color.colorize_percent(violation.fetch(:actual)),
|
|
29
|
+
expected: violation.fetch(:expected),
|
|
30
|
+
filename: violation.fetch(:project_filename)
|
|
31
|
+
)
|
|
51
32
|
end
|
|
52
33
|
end
|
|
53
34
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ExitCodes
|
|
5
|
+
# Fails when any configured group falls below its minimum coverage
|
|
6
|
+
# threshold for any criterion.
|
|
7
|
+
class MinimumCoverageByGroupCheck < Check
|
|
8
|
+
def exit_code
|
|
9
|
+
SimpleCov::ExitCodes::MINIMUM_COVERAGE
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def compute_violations
|
|
15
|
+
SimpleCov::CoverageViolations.minimum_by_group(result, thresholds)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def report_violation(violation)
|
|
19
|
+
ExitCodes.print_error format(
|
|
20
|
+
"%<criterion>s coverage by group (%<actual>s) is below the expected minimum coverage " \
|
|
21
|
+
"(%<expected>.2f%%) in %<group_name>s.",
|
|
22
|
+
criterion: violation.fetch(:criterion).capitalize,
|
|
23
|
+
actual: SimpleCov::Color.colorize_percent(violation.fetch(:actual)),
|
|
24
|
+
expected: violation.fetch(:expected),
|
|
25
|
+
group_name: violation.fetch(:group_name)
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -2,51 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
5
|
+
# Fails when the overall (project-wide) coverage for any criterion is
|
|
6
|
+
# below the configured minimum.
|
|
7
|
+
class MinimumOverallCoverageCheck < Check
|
|
26
8
|
def exit_code
|
|
27
9
|
SimpleCov::ExitCodes::MINIMUM_COVERAGE
|
|
28
10
|
end
|
|
29
11
|
|
|
30
12
|
private
|
|
31
13
|
|
|
32
|
-
|
|
14
|
+
WORST_FILES_LIMIT = 5
|
|
15
|
+
private_constant :WORST_FILES_LIMIT
|
|
16
|
+
|
|
17
|
+
def compute_violations
|
|
18
|
+
SimpleCov::CoverageViolations.minimum_overall(result, thresholds)
|
|
19
|
+
end
|
|
33
20
|
|
|
34
|
-
def
|
|
35
|
-
|
|
21
|
+
def report_violation(violation)
|
|
22
|
+
criterion = violation.fetch(:criterion)
|
|
23
|
+
actual = violation.fetch(:actual)
|
|
24
|
+
ExitCodes.print_error format(
|
|
25
|
+
"%<criterion>s coverage (%<actual>s) is below the expected minimum coverage (%<expected>.2f%%).",
|
|
26
|
+
criterion: criterion.capitalize,
|
|
27
|
+
actual: SimpleCov::Color.colorize_percent(actual),
|
|
28
|
+
expected: violation.fetch(:expected)
|
|
29
|
+
)
|
|
30
|
+
report_worst_files(criterion)
|
|
36
31
|
end
|
|
37
32
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
def report_worst_files(criterion)
|
|
34
|
+
worst = worst_files_for(criterion)
|
|
35
|
+
return if worst.empty?
|
|
36
|
+
|
|
37
|
+
ExitCodes.print_error " Lowest-coverage files (#{criterion}):"
|
|
38
|
+
worst.each do |path, percent|
|
|
39
|
+
ExitCodes.print_error format(
|
|
40
|
+
" %<percent>s %<path>s",
|
|
41
|
+
percent: SimpleCov::Color.colorize_percent(percent, format("%6.2f%%", percent)),
|
|
42
|
+
path: path
|
|
43
|
+
)
|
|
45
44
|
end
|
|
45
|
+
end
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
def worst_files_for(criterion)
|
|
48
|
+
stats_key = SimpleCov.coverage_statistics_key(criterion)
|
|
49
|
+
with_stats = result.files.filter_map do |source_file|
|
|
50
|
+
stats = source_file.coverage_statistics[stats_key]
|
|
51
|
+
[source_file.project_filename, stats.percent] if stats
|
|
49
52
|
end
|
|
53
|
+
with_stats.sort_by { |_path, percent| percent }.first(WORST_FILES_LIMIT)
|
|
50
54
|
end
|
|
51
55
|
end
|
|
52
56
|
end
|
data/lib/simplecov/exit_codes.rb
CHANGED
|
@@ -1,15 +1,35 @@
|
|
|
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
|
|
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
|
+
rescue IOError
|
|
22
|
+
# A parallel runner can close a worker's stderr before its at_exit
|
|
23
|
+
# hooks run (rspec-conductor does). The violation still has to set
|
|
24
|
+
# the exit status even when its explanation cannot be printed.
|
|
25
|
+
end
|
|
9
26
|
end
|
|
10
27
|
end
|
|
11
28
|
|
|
29
|
+
require_relative "exit_codes/check"
|
|
12
30
|
require_relative "exit_codes/exit_code_handling"
|
|
13
31
|
require_relative "exit_codes/maximum_coverage_drop_check"
|
|
32
|
+
require_relative "exit_codes/maximum_overall_coverage_check"
|
|
14
33
|
require_relative "exit_codes/minimum_coverage_by_file_check"
|
|
34
|
+
require_relative "exit_codes/minimum_coverage_by_group_check"
|
|
15
35
|
require_relative "exit_codes/minimum_overall_coverage_check"
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "English"
|
|
4
|
+
|
|
5
|
+
# `at_exit` orchestration: post-suite report generation, threshold
|
|
6
|
+
# checks, deferral when a sibling subprocess already wrote a fresher
|
|
7
|
+
# report, and exit-status propagation.
|
|
8
|
+
module SimpleCov
|
|
9
|
+
class << self
|
|
10
|
+
# @api private
|
|
11
|
+
CoverageLimits = Data.define(
|
|
12
|
+
:minimum_coverage,
|
|
13
|
+
:minimum_coverage_by_file,
|
|
14
|
+
:minimum_coverage_by_file_overrides,
|
|
15
|
+
:minimum_coverage_by_group,
|
|
16
|
+
:maximum_coverage,
|
|
17
|
+
:maximum_coverage_drop
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
def at_exit_behavior
|
|
21
|
+
# If we are in a different process than called start, don't interfere.
|
|
22
|
+
return if SimpleCov.pid != Process.pid
|
|
23
|
+
|
|
24
|
+
# If Coverage is no longer running (e.g. someone manually stopped it
|
|
25
|
+
# or a test consumed the result) then don't run exit tasks.
|
|
26
|
+
return unless Coverage.running?
|
|
27
|
+
|
|
28
|
+
# Capture the suite's exit status BEFORE the deferral probe: its
|
|
29
|
+
# freshness check rescues filesystem errors, and completing any
|
|
30
|
+
# rescue inside an at_exit handler resets $ERROR_INFO to nil.
|
|
31
|
+
error_exit_status = exit_status_from_exception
|
|
32
|
+
|
|
33
|
+
# Stand down when we'd only clobber a fresher report (#581).
|
|
34
|
+
return if defer_to_existing_report?
|
|
35
|
+
|
|
36
|
+
SimpleCov.run_exit_tasks!(error_exit_status)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @api private — called from the at_exit block (which pre-captures
|
|
40
|
+
# the status, see there) and by `collate` (the default argument).
|
|
41
|
+
def run_exit_tasks!(error_exit_status = exit_status_from_exception)
|
|
42
|
+
at_exit.call
|
|
43
|
+
|
|
44
|
+
exit_and_report_previous_error(error_exit_status) if previous_error?(error_exit_status)
|
|
45
|
+
process_results_and_report_error if ready_to_process_results?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @api private — returns the exit status from the exit exception.
|
|
49
|
+
def exit_status_from_exception
|
|
50
|
+
@exit_exception = $ERROR_INFO
|
|
51
|
+
return nil unless @exit_exception
|
|
52
|
+
|
|
53
|
+
if @exit_exception.is_a?(SystemExit)
|
|
54
|
+
@exit_exception.status
|
|
55
|
+
else
|
|
56
|
+
SimpleCov::ExitCodes::EXCEPTION
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# @api private — strict boolean so rspec-mocks 4's predicate matcher
|
|
61
|
+
# accepts it. test_unit sets status 0 on success, so SUCCESS must
|
|
62
|
+
# also be treated as "not a previous error".
|
|
63
|
+
def previous_error?(error_exit_status)
|
|
64
|
+
!!(error_exit_status && error_exit_status != SimpleCov::ExitCodes::SUCCESS)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# @api private
|
|
68
|
+
def exit_and_report_previous_error(exit_status)
|
|
69
|
+
if print_errors
|
|
70
|
+
ExitCodes.print_error SimpleCov::Color.colorize(
|
|
71
|
+
"Stopped processing SimpleCov as a previous error not related to SimpleCov has been detected",
|
|
72
|
+
:yellow
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
Kernel.exit(exit_status)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @api private — the process that owns final merge processing is the
|
|
79
|
+
# only one that reports against thresholds, and only when its
|
|
80
|
+
# `wait_for_other_processes` confirmed every sibling reported.
|
|
81
|
+
# When the wait times out, the merged total is partial and
|
|
82
|
+
# comparing it against `minimum_coverage` / `maximum_coverage`
|
|
83
|
+
# would surface a spurious "below minimum" violation about the
|
|
84
|
+
# missing slice rather than a real shortfall.
|
|
85
|
+
def ready_to_process_results?
|
|
86
|
+
merge_finalization_owner? && result? &&
|
|
87
|
+
(collating_result? || parallel_results_complete?)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def process_results_and_report_error
|
|
91
|
+
exit_status = process_result(result)
|
|
92
|
+
|
|
93
|
+
# Force exit with stored status (see github issue #5)
|
|
94
|
+
return unless exit_status.positive?
|
|
95
|
+
|
|
96
|
+
if print_errors
|
|
97
|
+
ExitCodes.print_error SimpleCov::Color.colorize(
|
|
98
|
+
"SimpleCov failed with exit #{exit_status} due to a coverage related error", :red
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
Kernel.exit exit_status
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @api private — `exit_status = SimpleCov.process_result(SimpleCov.result)`.
|
|
105
|
+
def process_result(result)
|
|
106
|
+
result_exit_status = result_exit_status(result)
|
|
107
|
+
write_last_run(result) if result_exit_status == SimpleCov::ExitCodes::SUCCESS
|
|
108
|
+
result_exit_status
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def result_exit_status(result)
|
|
112
|
+
ExitCodes::ExitCodeHandling.call(result, coverage_limits: build_coverage_limits)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
private
|
|
116
|
+
|
|
117
|
+
def build_coverage_limits
|
|
118
|
+
CoverageLimits.new(
|
|
119
|
+
minimum_coverage: minimum_coverage,
|
|
120
|
+
minimum_coverage_by_file: minimum_coverage_by_file,
|
|
121
|
+
minimum_coverage_by_file_overrides: minimum_coverage_by_file_overrides,
|
|
122
|
+
minimum_coverage_by_group: minimum_coverage_by_group,
|
|
123
|
+
maximum_coverage: maximum_coverage,
|
|
124
|
+
maximum_coverage_drop: maximum_coverage_drop
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
data/lib/simplecov/file_list.rb
CHANGED
|
@@ -23,8 +23,12 @@ module SimpleCov
|
|
|
23
23
|
@files = files
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
# The per-criterion coverage statistics across all files. With no argument
|
|
27
|
+
# returns the `{line:, branch:, method:}` Hash; pass a criterion symbol
|
|
28
|
+
# (`:line` / `:branch` / `:method`) to get that one CoverageStatistics.
|
|
29
|
+
def coverage_statistics(criterion = nil)
|
|
30
|
+
stats = (@coverage_statistics ||= compute_coverage_statistics)
|
|
31
|
+
criterion ? stats[criterion] : stats
|
|
28
32
|
end
|
|
29
33
|
|
|
30
34
|
def coverage_statistics_by_file
|
|
@@ -43,16 +47,12 @@ module SimpleCov
|
|
|
43
47
|
|
|
44
48
|
# Returns the count of lines that are not relevant for coverage
|
|
45
49
|
def never_lines
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
map { |f| f.never_lines.count }.inject(:+)
|
|
50
|
+
sum { |f| f.never_lines.size }
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
# Returns the count of skipped lines
|
|
52
54
|
def skipped_lines
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
map { |f| f.skipped_lines.count }.inject(:+)
|
|
55
|
+
sum { |f| f.skipped_lines.size }
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
# Computes the coverage based upon lines covered and lines missed for each file
|
|
@@ -61,9 +61,13 @@ module SimpleCov
|
|
|
61
61
|
map(&:covered_percent)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
# Finds the least covered file and returns that file's name
|
|
64
|
+
# Finds the least covered file and returns that file's name, or nil
|
|
65
|
+
# for an empty list (e.g. a fully filtered result).
|
|
65
66
|
def least_covered_file
|
|
66
|
-
|
|
67
|
+
# `covered_percent` is nil only for an unmeasured criterion, and :line
|
|
68
|
+
# is always measured, so the `|| 0.0` arm never fires at runtime; it
|
|
69
|
+
# exists to satisfy min_by's Comparable requirement.
|
|
70
|
+
min_by { |file| file.covered_percent || 0.0 }&.filename
|
|
67
71
|
end
|
|
68
72
|
|
|
69
73
|
# Returns the overall amount of relevant lines of code across all files in this list
|
|
@@ -71,16 +75,18 @@ module SimpleCov
|
|
|
71
75
|
coverage_statistics[:line]&.total
|
|
72
76
|
end
|
|
73
77
|
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
|
|
78
|
+
# The coverage across all files in percent, for the given criterion (line
|
|
79
|
+
# by default). Returns nil if the criterion was not measured.
|
|
80
|
+
# @return [Float, nil]
|
|
81
|
+
def covered_percent(criterion = :line)
|
|
82
|
+
coverage_statistics(criterion)&.percent
|
|
78
83
|
end
|
|
79
84
|
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
# The strength (average hits per relevant unit) for the given criterion
|
|
86
|
+
# (line by default).
|
|
87
|
+
# @return [Float, nil]
|
|
88
|
+
def covered_strength(criterion = :line)
|
|
89
|
+
coverage_statistics(criterion)&.strength
|
|
84
90
|
end
|
|
85
91
|
|
|
86
92
|
# Return total count of branches in all files
|
|
@@ -102,19 +108,59 @@ module SimpleCov
|
|
|
102
108
|
coverage_statistics[:branch]&.percent
|
|
103
109
|
end
|
|
104
110
|
|
|
111
|
+
# Return total count of methods in all files
|
|
112
|
+
def total_methods
|
|
113
|
+
coverage_statistics[:method]&.total
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Return total count of covered methods
|
|
117
|
+
def covered_methods
|
|
118
|
+
coverage_statistics[:method]&.covered
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Return total count of missed methods
|
|
122
|
+
def missed_methods
|
|
123
|
+
coverage_statistics[:method]&.missed
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def method_covered_percent
|
|
127
|
+
coverage_statistics[:method]&.percent
|
|
128
|
+
end
|
|
129
|
+
|
|
105
130
|
private
|
|
106
131
|
|
|
132
|
+
# Seed the result hash with one entry per criterion the user
|
|
133
|
+
# enabled — so an empty FileList (e.g. a group with no files) still
|
|
134
|
+
# yields the right shape — then fold each file's stats into the
|
|
135
|
+
# matching bucket. `SourceFile#coverage_statistics` always reports
|
|
136
|
+
# all three criteria; FileList is the layer that filters to the
|
|
137
|
+
# enabled set so disabled criteria don't surface in totals, JSON,
|
|
138
|
+
# or the HTML report.
|
|
107
139
|
def compute_coverage_statistics_by_file
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
140
|
+
seed = enabled_criteria_for_reporting.to_h do |criterion|
|
|
141
|
+
bucket = [] #: Array[CoverageStatistics]
|
|
142
|
+
[criterion, bucket]
|
|
143
|
+
end
|
|
144
|
+
@files.each_with_object(seed) do |file, together|
|
|
145
|
+
file.coverage_statistics.each do |criterion, stats|
|
|
146
|
+
together[criterion] << stats if together.key?(criterion)
|
|
147
|
+
end
|
|
111
148
|
end
|
|
112
149
|
end
|
|
113
150
|
|
|
114
151
|
def compute_coverage_statistics
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
152
|
+
coverage_statistics_by_file.transform_values { |stats| CoverageStatistics.from(stats) }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# `:line` (or its `:oneshot_line` synonym) is reported when either
|
|
156
|
+
# criterion is enabled; the JRuby-gated branch/method criteria are
|
|
157
|
+
# reported when they pass their own engine-support check.
|
|
158
|
+
def enabled_criteria_for_reporting
|
|
159
|
+
criteria = [] #: Array[SimpleCov::criterion]
|
|
160
|
+
criteria << :line if SimpleCov.line_coverage?
|
|
161
|
+
criteria << :branch if SimpleCov.branch_coverage?
|
|
162
|
+
criteria << :method if SimpleCov.method_coverage?
|
|
163
|
+
criteria
|
|
118
164
|
end
|
|
119
165
|
end
|
|
120
166
|
end
|
data/lib/simplecov/filter.rb
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
#
|
|
5
5
|
# Base filter class. Inherit from this to create custom filters,
|
|
6
|
-
# and overwrite the
|
|
6
|
+
# and overwrite the matches?(source_file) instance method
|
|
7
7
|
#
|
|
8
8
|
# # A sample class that rejects all source files.
|
|
9
9
|
# class StupidFilter < SimpleCov::Filter
|
|
10
|
-
# def
|
|
10
|
+
# def matches?(source_file)
|
|
11
11
|
# false
|
|
12
12
|
# end
|
|
13
13
|
# end
|
|
@@ -20,52 +20,110 @@ module SimpleCov
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def matches?(_source_file)
|
|
23
|
-
raise "The base filter class is not intended for direct use"
|
|
23
|
+
raise NotImplementedError, "The base filter class is not intended for direct use"
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
# Whether this filter's verdict depends only on the file's path, so it can
|
|
27
|
+
# be decided before that file has any coverage. Used when recording which
|
|
28
|
+
# tracked files a process did not load, where no coverage exists yet.
|
|
29
|
+
# Defaults to false so a custom filter is never guessed at. See #1250.
|
|
30
|
+
def path_only?
|
|
31
|
+
false
|
|
29
32
|
end
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def self.class_for_argument(filter_argument)
|
|
34
|
+
# `string_filter` selects the semantics of bare String arguments —
|
|
35
|
+
# StringFilter's segment-substring match for `add_filter`/`skip`,
|
|
36
|
+
# GlobFilter for `cover` — and threads through Array elements so a
|
|
37
|
+
# list gets the same treatment as its members.
|
|
38
|
+
def self.build_filter(filter_argument, string_filter: SimpleCov::StringFilter)
|
|
38
39
|
case filter_argument
|
|
39
|
-
when
|
|
40
|
-
|
|
41
|
-
when Regexp
|
|
42
|
-
SimpleCov::RegexFilter
|
|
40
|
+
when SimpleCov::Filter then filter_argument
|
|
41
|
+
when String then string_filter.new(filter_argument)
|
|
43
42
|
when Array
|
|
44
|
-
SimpleCov::ArrayFilter
|
|
45
|
-
|
|
46
|
-
SimpleCov::BlockFilter
|
|
47
|
-
else
|
|
48
|
-
raise ArgumentError, "You have provided an unrecognized filter type"
|
|
43
|
+
SimpleCov::ArrayFilter.new(filter_argument.map { |arg| build_filter(arg, string_filter: string_filter) })
|
|
44
|
+
else class_for_argument(filter_argument).new(filter_argument)
|
|
49
45
|
end
|
|
50
46
|
end
|
|
47
|
+
|
|
48
|
+
def self.class_for_argument(filter_argument)
|
|
49
|
+
filter_classes_by_argument_type.find { |type, _| filter_argument.is_a?(type) }&.last ||
|
|
50
|
+
raise(SimpleCov::ConfigurationError, "You have provided an unrecognized filter type")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.filter_classes_by_argument_type
|
|
54
|
+
@filter_classes_by_argument_type ||= {
|
|
55
|
+
String => SimpleCov::StringFilter,
|
|
56
|
+
Regexp => SimpleCov::RegexFilter,
|
|
57
|
+
Array => SimpleCov::ArrayFilter,
|
|
58
|
+
Proc => SimpleCov::BlockFilter
|
|
59
|
+
}.freeze
|
|
60
|
+
end
|
|
61
|
+
private_class_method :filter_classes_by_argument_type
|
|
51
62
|
end
|
|
52
63
|
|
|
64
|
+
# Filter that matches when the source file's project path contains the
|
|
65
|
+
# configured string at a path-segment boundary.
|
|
53
66
|
class StringFilter < SimpleCov::Filter
|
|
54
67
|
# Returns true when the given source file's filename matches the
|
|
55
|
-
# string configured when initializing this Filter with StringFilter.new('somestring')
|
|
68
|
+
# string configured when initializing this Filter with StringFilter.new('somestring').
|
|
69
|
+
# Matching is path-segment-aware: the argument must appear immediately after a "/"
|
|
70
|
+
# and be followed by "/" or end-of-string, so "lib" matches "/lib/foo.rb" but not
|
|
71
|
+
# "/app/models/library.rb".
|
|
56
72
|
def matches?(source_file)
|
|
57
|
-
source_file.project_filename.
|
|
73
|
+
source_file.project_filename.match?(segment_pattern)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def path_only?
|
|
77
|
+
true
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def segment_pattern
|
|
83
|
+
@segment_pattern ||= compute_segment_pattern
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def compute_segment_pattern
|
|
87
|
+
normalized = filter_argument.delete_prefix("/")
|
|
88
|
+
escaped = Regexp.escape(normalized)
|
|
89
|
+
boundary = '(?:\A|/)'
|
|
90
|
+
|
|
91
|
+
if normalized.end_with?("/")
|
|
92
|
+
# Trailing slash signals directory-only matching.
|
|
93
|
+
/#{boundary}#{escaped}/
|
|
94
|
+
elsif normalized.include?(".") && !normalized.include?("/")
|
|
95
|
+
# Bare filename pattern (e.g. "test.rb" matches "faked_test.rb"):
|
|
96
|
+
# allow a substring match within a single path segment. Multi-segment
|
|
97
|
+
# arguments must not get this relaxation, or "app/models/user.rb"
|
|
98
|
+
# would also match "webapp/models/user.rb".
|
|
99
|
+
%r{#{boundary}[^/]*#{escaped}(?=[/.]|\z)}
|
|
100
|
+
else
|
|
101
|
+
# Directory or path: require a segment-boundary match so "lib"
|
|
102
|
+
# matches "lib/" but not "library/".
|
|
103
|
+
%r{#{boundary}#{escaped}(?=[/.]|\z)}
|
|
104
|
+
end
|
|
58
105
|
end
|
|
59
106
|
end
|
|
60
107
|
|
|
108
|
+
# Filter that matches when the source file's project path matches the
|
|
109
|
+
# configured Regexp.
|
|
61
110
|
class RegexFilter < SimpleCov::Filter
|
|
62
111
|
# Returns true when the given source file's filename matches the
|
|
63
|
-
# regex configured when initializing this Filter with RegexFilter.new(/someregex/)
|
|
112
|
+
# regex configured when initializing this Filter with RegexFilter.new(/someregex/).
|
|
113
|
+
# Uses `Regexp#match?` so the predicate returns a real boolean — `=~`
|
|
114
|
+
# would return the match position (an Integer or nil), which trips
|
|
115
|
+
# rspec-mocks 4's stricter predicate-matcher type check.
|
|
64
116
|
def matches?(source_file)
|
|
65
|
-
(source_file.project_filename
|
|
117
|
+
filter_argument.match?(source_file.project_filename)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def path_only?
|
|
121
|
+
true
|
|
66
122
|
end
|
|
67
123
|
end
|
|
68
124
|
|
|
125
|
+
# Filter that matches when the configured block returns truthy for the
|
|
126
|
+
# source file.
|
|
69
127
|
class BlockFilter < SimpleCov::Filter
|
|
70
128
|
# Returns true if the block given when initializing this filter with BlockFilter.new {|src_file| ... }
|
|
71
129
|
# returns true for the given source file.
|
|
@@ -74,6 +132,22 @@ module SimpleCov
|
|
|
74
132
|
end
|
|
75
133
|
end
|
|
76
134
|
|
|
135
|
+
# Filter that matches when the source file's project path matches the
|
|
136
|
+
# configured shell glob (e.g. "lib/**/*.rb"). Used by `cover` and
|
|
137
|
+
# `skip` when callers want glob semantics instead of the substring
|
|
138
|
+
# match of `StringFilter`.
|
|
139
|
+
class GlobFilter < SimpleCov::Filter
|
|
140
|
+
def matches?(source_file)
|
|
141
|
+
File.fnmatch?(filter_argument, source_file.project_filename, File::FNM_PATHNAME | File::FNM_EXTGLOB)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def path_only?
|
|
145
|
+
true
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Filter that matches when any of its component filters (built from the
|
|
150
|
+
# array's elements) match the source file.
|
|
77
151
|
class ArrayFilter < SimpleCov::Filter
|
|
78
152
|
def initialize(filter_argument)
|
|
79
153
|
filter_objects = filter_argument.map do |arg|
|
|
@@ -83,6 +157,11 @@ module SimpleCov
|
|
|
83
157
|
super(filter_objects)
|
|
84
158
|
end
|
|
85
159
|
|
|
160
|
+
# Path-decidable only when every component filter is.
|
|
161
|
+
def path_only?
|
|
162
|
+
filter_argument.all?(&:path_only?)
|
|
163
|
+
end
|
|
164
|
+
|
|
86
165
|
# Returns true if any of the filters in the array match the given source file.
|
|
87
166
|
# Configure this Filter like StringFilter.new(['some/path', /^some_regex/, Proc.new {|src_file| ... }])
|
|
88
167
|
def matches?(source_files_list)
|