simplecov 0.22.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +173 -875
- data/exe/simplecov +8 -0
- data/lib/minitest/simplecov_plugin.rb +18 -7
- data/lib/simplecov/atomic_file.rb +67 -0
- data/lib/simplecov/autostart.rb +9 -0
- data/lib/simplecov/baseline/parser.rb +58 -0
- data/lib/simplecov/baseline.rb +170 -0
- data/lib/simplecov/cli/affected/changed_files.rb +57 -0
- data/lib/simplecov/cli/affected/selection.rb +117 -0
- data/lib/simplecov/cli/affected.rb +148 -0
- data/lib/simplecov/cli/annotations.rb +166 -0
- data/lib/simplecov/cli/badge/svg.rb +70 -0
- data/lib/simplecov/cli/badge.rb +62 -0
- data/lib/simplecov/cli/clean.rb +86 -0
- data/lib/simplecov/cli/command_helpers.rb +91 -0
- data/lib/simplecov/cli/completions/scripts.rb +114 -0
- data/lib/simplecov/cli/completions.rb +83 -0
- data/lib/simplecov/cli/coverage.rb +83 -0
- data/lib/simplecov/cli/coverage_file.rb +109 -0
- data/lib/simplecov/cli/dead_code/output.rb +100 -0
- data/lib/simplecov/cli/dead_code.rb +139 -0
- data/lib/simplecov/cli/diff/output.rb +45 -0
- data/lib/simplecov/cli/diff.rb +115 -0
- data/lib/simplecov/cli/dotfile.rb +156 -0
- data/lib/simplecov/cli/git.rb +41 -0
- data/lib/simplecov/cli/history/output.rb +135 -0
- data/lib/simplecov/cli/history.rb +67 -0
- data/lib/simplecov/cli/merge.rb +115 -0
- data/lib/simplecov/cli/open.rb +45 -0
- data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
- data/lib/simplecov/cli/patch/output.rb +151 -0
- data/lib/simplecov/cli/patch.rb +175 -0
- data/lib/simplecov/cli/ratchet/output.rb +44 -0
- data/lib/simplecov/cli/ratchet.rb +83 -0
- data/lib/simplecov/cli/report.rb +91 -0
- data/lib/simplecov/cli/run.rb +35 -0
- data/lib/simplecov/cli/serve/report_preparer.rb +30 -0
- data/lib/simplecov/cli/serve/static_file_handler.rb +128 -0
- data/lib/simplecov/cli/serve.rb +88 -0
- data/lib/simplecov/cli/show/annotator.rb +87 -0
- data/lib/simplecov/cli/show/sweep.rb +36 -0
- data/lib/simplecov/cli/show.rb +133 -0
- data/lib/simplecov/cli/status/facts.rb +87 -0
- data/lib/simplecov/cli/status.rb +93 -0
- data/lib/simplecov/cli/tests/redundancy.rb +78 -0
- data/lib/simplecov/cli/tests.rb +148 -0
- data/lib/simplecov/cli/uncovered/misses.rb +33 -0
- data/lib/simplecov/cli/uncovered.rb +127 -0
- data/lib/simplecov/cli/usage.rb +153 -0
- data/lib/simplecov/cli/watch/live_report.rb +73 -0
- data/lib/simplecov/cli/watch/narrator.rb +55 -0
- data/lib/simplecov/cli/watch/poller.rb +45 -0
- data/lib/simplecov/cli/watch/session.rb +134 -0
- data/lib/simplecov/cli/watch/test_plan.rb +43 -0
- data/lib/simplecov/cli/watch.rb +85 -0
- data/lib/simplecov/cli.rb +114 -0
- data/lib/simplecov/color.rb +65 -0
- data/lib/simplecov/combine/branches_combiner.rb +44 -21
- data/lib/simplecov/combine/coverage_accumulator.rb +175 -0
- data/lib/simplecov/combine/identity_interner.rb +27 -0
- data/lib/simplecov/combine/interned_counts.rb +28 -0
- data/lib/simplecov/combine/lines_combiner.rb +35 -29
- data/lib/simplecov/combine/methods_combiner.rb +48 -0
- data/lib/simplecov/combine/results_combiner.rb +9 -49
- data/lib/simplecov/combine.rb +0 -24
- data/lib/simplecov/command_guesser.rb +99 -42
- data/lib/simplecov/configuration/baseline.rb +31 -0
- data/lib/simplecov/configuration/coverage.rb +184 -0
- data/lib/simplecov/configuration/coverage_criteria.rb +143 -0
- data/lib/simplecov/configuration/deprecations.rb +21 -0
- data/lib/simplecov/configuration/eval_coverage.rb +37 -0
- data/lib/simplecov/configuration/filters.rb +131 -0
- data/lib/simplecov/configuration/formatting.rb +129 -0
- data/lib/simplecov/configuration/groups.rb +48 -0
- data/lib/simplecov/configuration/history.rb +44 -0
- data/lib/simplecov/configuration/ignored_entries.rb +72 -0
- data/lib/simplecov/configuration/merging.rb +197 -0
- data/lib/simplecov/configuration/missed_caps.rb +51 -0
- data/lib/simplecov/configuration/production.rb +23 -0
- data/lib/simplecov/configuration/test_tracking.rb +48 -0
- data/lib/simplecov/configuration/thresholds.rb +150 -0
- data/lib/simplecov/configuration/view_coverage.rb +37 -0
- data/lib/simplecov/configuration.rb +100 -443
- data/lib/simplecov/configuration_error.rb +5 -0
- data/lib/simplecov/context_map/union.rb +81 -0
- data/lib/simplecov/context_map.rb +187 -0
- data/lib/simplecov/coverage_json.rb +21 -0
- data/lib/simplecov/coverage_statistics.rb +24 -28
- data/lib/simplecov/coverage_violations.rb +254 -0
- data/lib/simplecov/current_run.rb +61 -0
- data/lib/simplecov/defaults.rb +20 -28
- data/lib/simplecov/deprecation.rb +63 -0
- data/lib/simplecov/directive/erb.rb +44 -0
- data/lib/simplecov/directive/haml.rb +33 -0
- data/lib/simplecov/directive/indented_template.rb +37 -0
- data/lib/simplecov/directive/slim.rb +35 -0
- data/lib/simplecov/directive/template.rb +29 -0
- data/lib/simplecov/directive.rb +152 -0
- data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
- data/lib/simplecov/exit_codes/check.rb +39 -0
- data/lib/simplecov/exit_codes/exit_code_handling.rb +24 -5
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +17 -67
- data/lib/simplecov/exit_codes/maximum_missed_check.rb +26 -0
- data/lib/simplecov/exit_codes/maximum_missed_per_file_check.rb +34 -0
- data/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +27 -0
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +20 -38
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +28 -0
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +37 -34
- data/lib/simplecov/exit_codes.rb +26 -0
- data/lib/simplecov/exit_handling.rb +163 -0
- data/lib/simplecov/file_list.rb +63 -48
- data/lib/simplecov/filter.rb +98 -42
- data/lib/simplecov/formatter/base.rb +100 -0
- data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +95 -0
- data/lib/simplecov/formatter/html_formatter/public/index.html +93 -0
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +153 -0
- data/lib/simplecov/formatter/html_formatter.rb +88 -0
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +122 -0
- data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +132 -0
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +97 -0
- data/lib/simplecov/formatter/json_formatter.rb +40 -0
- data/lib/simplecov/formatter/multi_formatter.rb +8 -10
- data/lib/simplecov/formatter/simple_formatter.rb +12 -15
- data/lib/simplecov/formatter.rb +21 -1
- data/lib/simplecov/group_names.rb +33 -0
- data/lib/simplecov/history.rb +115 -0
- data/lib/simplecov/last_run.rb +16 -5
- data/lib/simplecov/lines_classifier.rb +67 -19
- data/lib/simplecov/load_global_config.rb +12 -4
- data/lib/simplecov/parallel_adapters/base.rb +50 -0
- data/lib/simplecov/parallel_adapters/generic.rb +33 -0
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +72 -0
- data/lib/simplecov/parallel_adapters.rb +54 -0
- data/lib/simplecov/parallel_coordination.rb +124 -0
- data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
- data/lib/simplecov/parallel_result_merger.rb +171 -0
- data/lib/simplecov/process.rb +34 -12
- data/lib/simplecov/production/error.rb +11 -0
- data/lib/simplecov/production/file_sink.rb +130 -0
- data/lib/simplecov/production.rb +249 -0
- data/lib/simplecov/profiles/bundler_filter.rb +1 -1
- data/lib/simplecov/profiles/hidden_filter.rb +1 -1
- data/lib/simplecov/profiles/rails.rb +13 -10
- data/lib/simplecov/profiles/root_filter.rb +2 -5
- data/lib/simplecov/profiles/strict.rb +22 -0
- data/lib/simplecov/profiles/test_frameworks.rb +1 -4
- data/lib/simplecov/profiles.rb +26 -19
- data/lib/simplecov/report_deferral.rb +45 -0
- data/lib/simplecov/report_stamp.rb +25 -0
- data/lib/simplecov/result/filter_config.rb +19 -0
- data/lib/simplecov/result/missing_source_files_reporter.rb +48 -0
- data/lib/simplecov/result/serialization.rb +43 -0
- data/lib/simplecov/result/source_file_builder.rb +46 -0
- data/lib/simplecov/result.rb +123 -47
- data/lib/simplecov/result_adapter.rb +130 -11
- data/lib/simplecov/result_merger/contexts.rb +22 -0
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +24 -0
- data/lib/simplecov/result_merger/resultset_file.rb +67 -0
- data/lib/simplecov/result_merger/resultset_run_identity.rb +65 -0
- data/lib/simplecov/result_merger/resultset_store.rb +62 -0
- data/lib/simplecov/result_merger/unloaded_files.rb +95 -0
- data/lib/simplecov/result_merger.rb +120 -124
- data/lib/simplecov/result_processing.rb +188 -0
- data/lib/simplecov/run_identity.rb +75 -0
- data/lib/simplecov/simulate_coverage.rb +72 -17
- data/lib/simplecov/source_file/branch.rb +8 -35
- data/lib/simplecov/source_file/branch_builder.rb +95 -0
- data/lib/simplecov/source_file/builder_context.rb +28 -0
- data/lib/simplecov/source_file/line.rb +17 -32
- data/lib/simplecov/source_file/line_builder.rb +37 -0
- data/lib/simplecov/source_file/method.rb +54 -0
- data/lib/simplecov/source_file/method_builder.rb +51 -0
- data/lib/simplecov/source_file/ruby_data_parser.rb +103 -0
- data/lib/simplecov/source_file/skip_chunks.rb +72 -0
- data/lib/simplecov/source_file/source_loader.rb +89 -0
- data/lib/simplecov/source_file/statistics.rb +76 -0
- data/lib/simplecov/source_file.rb +64 -260
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +155 -0
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +191 -0
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +45 -0
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +46 -0
- data/lib/simplecov/static_coverage_extractor/value_position.rb +61 -0
- data/lib/simplecov/static_coverage_extractor/visitor.rb +163 -0
- data/lib/simplecov/static_coverage_extractor.rb +78 -0
- data/lib/simplecov/test_tracker/accessors.rb +34 -0
- data/lib/simplecov/test_tracker/constant_watch.rb +49 -0
- data/lib/simplecov/test_tracker/delta.rb +69 -0
- data/lib/simplecov/test_tracker/framework_hooks.rb +130 -0
- data/lib/simplecov/test_tracker.rb +162 -0
- data/lib/simplecov/unloaded_file_injector.rb +62 -0
- data/lib/simplecov/useless_results_remover.rb +17 -7
- data/lib/simplecov/version.rb +1 -1
- data/lib/simplecov/view_coverage/template_compiler.rb +83 -0
- data/lib/simplecov/view_coverage.rb +55 -0
- data/lib/simplecov-html.rb +5 -0
- data/lib/simplecov.rb +143 -394
- data/lib/simplecov_json_formatter.rb +5 -0
- data/man/simplecov.1 +354 -0
- data/sig/simplecov.rbs +1710 -0
- metadata +183 -61
- 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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ExitCodes
|
|
5
|
+
class MaximumMissedPerFileCheck < Check
|
|
6
|
+
def initialize(result, maximum_missed_per_file, overrides = {}, baseline: nil)
|
|
7
|
+
super(result, maximum_missed_per_file)
|
|
8
|
+
@overrides = overrides
|
|
9
|
+
@baseline = baseline
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def exit_code
|
|
13
|
+
MINIMUM_COVERAGE
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def compute_violations
|
|
19
|
+
CoverageViolations.maximum_missed_by_file(result, thresholds, @overrides, baseline: @baseline)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def violation_lines(violation)
|
|
23
|
+
[format(
|
|
24
|
+
"Missed %<units>s (%<actual>d) exceed the configured maximum_missed_per_file (%<maximum>d) " \
|
|
25
|
+
"in %<filename>s.",
|
|
26
|
+
units: UNITS.fetch(SimpleCov.coverage_statistics_key(violation.fetch(:criterion))),
|
|
27
|
+
actual: violation.fetch(:actual),
|
|
28
|
+
maximum: violation.fetch(:maximum),
|
|
29
|
+
filename: violation.fetch(:project_filename)
|
|
30
|
+
)]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ExitCodes
|
|
5
|
+
class MaximumOverallCoverageCheck < Check
|
|
6
|
+
def exit_code
|
|
7
|
+
MAXIMUM_COVERAGE
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def compute_violations
|
|
13
|
+
CoverageViolations.maximum_overall(result, thresholds)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def violation_lines(violation)
|
|
17
|
+
[format(
|
|
18
|
+
"%<criterion>s coverage (%<actual>s) is above the expected maximum coverage (%<expected>.2f%%). " \
|
|
19
|
+
"Time to bump the threshold!",
|
|
20
|
+
criterion: violation.fetch(:criterion).capitalize,
|
|
21
|
+
actual: Color.colorize_percent(violation.fetch(:actual)),
|
|
22
|
+
expected: violation.fetch(:expected)
|
|
23
|
+
)]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -2,52 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
class MinimumCoverageByFileCheck
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
+
class MinimumCoverageByFileCheck < Check
|
|
6
|
+
# `baseline:` exempts the file-and-criterion pairs it covers: those answer to
|
|
7
|
+
# their own floor via `BaselineCheck` instead.
|
|
8
|
+
def initialize(result, minimum_coverage_by_file, overrides = {}, baseline: nil)
|
|
9
|
+
super(result, minimum_coverage_by_file)
|
|
10
|
+
@overrides = overrides
|
|
11
|
+
@baseline = baseline
|
|
24
12
|
end
|
|
25
13
|
|
|
26
14
|
def exit_code
|
|
27
|
-
|
|
15
|
+
MINIMUM_COVERAGE
|
|
28
16
|
end
|
|
29
17
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
attr_reader :result, :minimum_coverage_by_file
|
|
18
|
+
private
|
|
33
19
|
|
|
34
|
-
def
|
|
35
|
-
@
|
|
36
|
-
compute_minimum_coverage_data.select do |achieved|
|
|
37
|
-
achieved.fetch(:actual) < achieved.fetch(:minimum_expected)
|
|
38
|
-
end
|
|
20
|
+
def compute_violations
|
|
21
|
+
CoverageViolations.minimum_by_file(result, thresholds, @overrides, baseline: @baseline)
|
|
39
22
|
end
|
|
40
23
|
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
24
|
+
def violation_lines(violation)
|
|
25
|
+
[format(
|
|
26
|
+
"%<criterion>s coverage by file (%<actual>s) is below the expected minimum coverage " \
|
|
27
|
+
"(%<expected>.2f%%) in %<filename>s.",
|
|
28
|
+
criterion: violation.fetch(:criterion).capitalize,
|
|
29
|
+
actual: Color.colorize_percent(violation.fetch(:actual)),
|
|
30
|
+
expected: violation.fetch(:expected),
|
|
31
|
+
filename: violation.fetch(:project_filename)
|
|
32
|
+
)]
|
|
51
33
|
end
|
|
52
34
|
end
|
|
53
35
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SimpleCov
|
|
4
|
+
module ExitCodes
|
|
5
|
+
class MinimumCoverageByGroupCheck < Check
|
|
6
|
+
def exit_code
|
|
7
|
+
MINIMUM_COVERAGE
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def compute_violations
|
|
13
|
+
CoverageViolations.minimum_by_group(result, thresholds)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def violation_lines(violation)
|
|
17
|
+
[format(
|
|
18
|
+
"%<criterion>s coverage by group (%<actual>s) is below the expected minimum coverage " \
|
|
19
|
+
"(%<expected>.2f%%) in %<group_name>s.",
|
|
20
|
+
criterion: violation.fetch(:criterion).capitalize,
|
|
21
|
+
actual: Color.colorize_percent(violation.fetch(:actual)),
|
|
22
|
+
expected: violation.fetch(:expected),
|
|
23
|
+
group_name: violation.fetch(:group_name)
|
|
24
|
+
)]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -2,51 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
4
|
module ExitCodes
|
|
5
|
-
class MinimumOverallCoverageCheck
|
|
6
|
-
def
|
|
7
|
-
|
|
8
|
-
@minimum_coverage = minimum_coverage
|
|
5
|
+
class MinimumOverallCoverageCheck < Check
|
|
6
|
+
def exit_code
|
|
7
|
+
MINIMUM_COVERAGE
|
|
9
8
|
end
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
minimum_violations.any?
|
|
13
|
-
end
|
|
10
|
+
private
|
|
14
11
|
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
12
|
+
WORST_FILES_LIMIT = 5
|
|
13
|
+
private_constant :WORST_FILES_LIMIT
|
|
25
14
|
|
|
26
|
-
def
|
|
27
|
-
|
|
15
|
+
def compute_violations
|
|
16
|
+
CoverageViolations.minimum_overall(result, thresholds)
|
|
28
17
|
end
|
|
29
18
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
19
|
+
def violation_lines(violation)
|
|
20
|
+
criterion = violation.fetch(:criterion)
|
|
21
|
+
headline = format(
|
|
22
|
+
"%<criterion>s coverage (%<actual>s) is below the expected minimum coverage (%<expected>.2f%%).",
|
|
23
|
+
criterion: criterion.capitalize,
|
|
24
|
+
actual: Color.colorize_percent(violation.fetch(:actual)),
|
|
25
|
+
expected: violation.fetch(:expected)
|
|
26
|
+
)
|
|
27
|
+
[headline, *worst_files_lines(criterion)]
|
|
36
28
|
end
|
|
37
29
|
|
|
38
|
-
def
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
def worst_files_lines(criterion)
|
|
31
|
+
worst = worst_files_for(criterion)
|
|
32
|
+
return [] if worst.empty?
|
|
33
|
+
|
|
34
|
+
[" Lowest-coverage files (#{criterion}):"] + worst.map do |path, percent|
|
|
35
|
+
format(
|
|
36
|
+
" %<percent>s %<path>s",
|
|
37
|
+
percent: Color.colorize_percent(percent, format("%6.2f%%", percent)),
|
|
38
|
+
path: path
|
|
39
|
+
)
|
|
45
40
|
end
|
|
41
|
+
end
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
43
|
+
# The list is a hint at where to add tests, so a fully covered file has no
|
|
44
|
+
# business on it: without the filter, files at 100% padded the list out to
|
|
45
|
+
# its five entries (#1286).
|
|
46
|
+
def worst_files_for(criterion)
|
|
47
|
+
stats_key = SimpleCov.coverage_statistics_key(criterion)
|
|
48
|
+
with_stats = result.files.filter_map do |source_file|
|
|
49
|
+
stats = source_file.coverage_statistics[stats_key]
|
|
50
|
+
[source_file.project_filename, stats.percent] if stats && stats.percent < 100
|
|
49
51
|
end
|
|
52
|
+
with_stats.sort_by { |_path, percent| percent }.first(WORST_FILES_LIMIT)
|
|
50
53
|
end
|
|
51
54
|
end
|
|
52
55
|
end
|
data/lib/simplecov/exit_codes.rb
CHANGED
|
@@ -6,10 +6,36 @@ module SimpleCov
|
|
|
6
6
|
EXCEPTION = 1
|
|
7
7
|
MINIMUM_COVERAGE = 2
|
|
8
8
|
MAXIMUM_COVERAGE_DROP = 3
|
|
9
|
+
MAXIMUM_COVERAGE = 4
|
|
10
|
+
|
|
11
|
+
# The plural unit each criterion's misses count in, for the checks that report
|
|
12
|
+
# counts rather than percents.
|
|
13
|
+
UNITS = {line: "lines", branch: "branches", method: "methods"}.freeze
|
|
14
|
+
|
|
15
|
+
# Threshold-violation reports and exit-status notices are the output of the
|
|
16
|
+
# enforcement feature, not Ruby warnings: routing them through `Kernel#warn`
|
|
17
|
+
# made `-W0` swallow the explanation for a failing exit code, let
|
|
18
|
+
# `Warning.warn` hooks intercept them mid-at_exit, and fed colorized text to
|
|
19
|
+
# warning logs. `print_errors false` remains the intended opt-out.
|
|
20
|
+
#
|
|
21
|
+
# A parallel runner can close a worker's stderr before its at_exit hooks
|
|
22
|
+
# run, hence the rescue.
|
|
23
|
+
def self.print_error(message)
|
|
24
|
+
$stderr.puts message # rubocop:disable Style/StderrPuts
|
|
25
|
+
rescue IOError
|
|
26
|
+
# The violation still has to set the exit status even when its
|
|
27
|
+
# explanation cannot be printed.
|
|
28
|
+
end
|
|
9
29
|
end
|
|
10
30
|
end
|
|
11
31
|
|
|
32
|
+
require_relative "exit_codes/check"
|
|
33
|
+
require_relative "exit_codes/baseline_check"
|
|
12
34
|
require_relative "exit_codes/exit_code_handling"
|
|
13
35
|
require_relative "exit_codes/maximum_coverage_drop_check"
|
|
36
|
+
require_relative "exit_codes/maximum_missed_check"
|
|
37
|
+
require_relative "exit_codes/maximum_missed_per_file_check"
|
|
38
|
+
require_relative "exit_codes/maximum_overall_coverage_check"
|
|
14
39
|
require_relative "exit_codes/minimum_coverage_by_file_check"
|
|
40
|
+
require_relative "exit_codes/minimum_coverage_by_group_check"
|
|
15
41
|
require_relative "exit_codes/minimum_overall_coverage_check"
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "English"
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# `at_exit` orchestration: post-suite report generation, threshold checks,
|
|
7
|
+
# deferral when a sibling subprocess already wrote a fresher report, and
|
|
8
|
+
# exit-status propagation.
|
|
9
|
+
#
|
|
10
|
+
module SimpleCov
|
|
11
|
+
class << self
|
|
12
|
+
# @api private
|
|
13
|
+
CoverageLimits = Data.define(
|
|
14
|
+
:minimum_coverage,
|
|
15
|
+
:minimum_coverage_by_file,
|
|
16
|
+
:minimum_coverage_by_file_overrides,
|
|
17
|
+
:minimum_coverage_by_group,
|
|
18
|
+
:maximum_coverage,
|
|
19
|
+
:maximum_coverage_drop,
|
|
20
|
+
:maximum_missed,
|
|
21
|
+
:maximum_missed_per_file,
|
|
22
|
+
:maximum_missed_per_file_overrides,
|
|
23
|
+
:baseline
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def at_exit_behavior
|
|
27
|
+
# A different process than the one that called start; don't interfere.
|
|
28
|
+
return unless started_in_this_process?
|
|
29
|
+
|
|
30
|
+
# Coverage is no longer running (someone stopped it manually, or a test
|
|
31
|
+
# consumed the result), so don't run exit tasks.
|
|
32
|
+
return unless Coverage.running?
|
|
33
|
+
|
|
34
|
+
# Captured BEFORE the deferral probe: its freshness check rescues
|
|
35
|
+
# filesystem errors, and completing any rescue inside an at_exit handler
|
|
36
|
+
# resets $ERROR_INFO to nil.
|
|
37
|
+
error_exit_status = exit_status_from_exception
|
|
38
|
+
|
|
39
|
+
# Stand down when we'd only clobber a fresher report (#581).
|
|
40
|
+
return if defer_to_existing_report?
|
|
41
|
+
|
|
42
|
+
run_exit_tasks!(error_exit_status)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @api private -- everything the end of a measured run does, answered as the
|
|
46
|
+
# exit status the process should end with rather than performed as an exit.
|
|
47
|
+
# The one side effect left to the caller is ending the process, which is
|
|
48
|
+
# what makes this whole path answerable from inside a test.
|
|
49
|
+
def run_exit_tasks(error_exit_status = exit_status_from_exception)
|
|
50
|
+
at_exit.call
|
|
51
|
+
|
|
52
|
+
if previous_error?(error_exit_status)
|
|
53
|
+
report_previous_error
|
|
54
|
+
error_exit_status
|
|
55
|
+
elsif ready_to_process_results?
|
|
56
|
+
report_processing_failure(process_result(result))
|
|
57
|
+
else
|
|
58
|
+
ExitCodes::SUCCESS
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @api private -- the exiting adapter over `run_exit_tasks`, called from the
|
|
63
|
+
# at_exit block (which pre-captures the status, see there) and by `collate`.
|
|
64
|
+
def run_exit_tasks!(error_exit_status = exit_status_from_exception)
|
|
65
|
+
status = run_exit_tasks(error_exit_status)
|
|
66
|
+
|
|
67
|
+
# Force exit with stored status (#5).
|
|
68
|
+
Kernel.exit(status) unless successful?(status)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# @api private
|
|
72
|
+
def exit_status_from_exception
|
|
73
|
+
@exit_exception = $ERROR_INFO
|
|
74
|
+
return nil unless @exit_exception
|
|
75
|
+
|
|
76
|
+
if @exit_exception.is_a?(SystemExit)
|
|
77
|
+
@exit_exception.status
|
|
78
|
+
else
|
|
79
|
+
ExitCodes::EXCEPTION
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @api private -- strict boolean so rspec-mocks 4's predicate matcher
|
|
84
|
+
# accepts it. test_unit sets status 0 on success, so SUCCESS must also be
|
|
85
|
+
# treated as "not a previous error".
|
|
86
|
+
def previous_error?(error_exit_status)
|
|
87
|
+
return false unless error_exit_status
|
|
88
|
+
|
|
89
|
+
!successful?(error_exit_status)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @api private -- the notice alone; the status it accompanies is the
|
|
93
|
+
# caller's to answer with.
|
|
94
|
+
def report_previous_error
|
|
95
|
+
return unless print_errors
|
|
96
|
+
|
|
97
|
+
ExitCodes.print_error Color.colorize(
|
|
98
|
+
"Stopped processing SimpleCov as a previous error not related to SimpleCov has been detected",
|
|
99
|
+
:yellow
|
|
100
|
+
)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @api private. The process that owns final merge processing is the only one
|
|
104
|
+
# that reports against thresholds, and only when its
|
|
105
|
+
# `wait_for_other_processes` confirmed every sibling reported. When the wait
|
|
106
|
+
# times out the merged total is partial, and comparing it against the
|
|
107
|
+
# thresholds would surface a spurious violation about the missing slice.
|
|
108
|
+
def ready_to_process_results?
|
|
109
|
+
merge_finalization_owner? && result? &&
|
|
110
|
+
(collating_result? || parallel_results_complete?)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @api private -- answers the status it was handed, explaining it first when
|
|
114
|
+
# it is a coverage failure worth explaining.
|
|
115
|
+
def report_processing_failure(exit_status)
|
|
116
|
+
if exit_status.positive? && print_errors
|
|
117
|
+
ExitCodes.print_error Color.colorize(
|
|
118
|
+
"SimpleCov failed with exit #{exit_status} due to a coverage related error", :red
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
exit_status
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# @api private. The history entry is recorded on the same successful-run
|
|
125
|
+
# condition `.last_run.json` uses, so a run that failed its thresholds
|
|
126
|
+
# becomes neither the drop baseline nor a data point in the trend.
|
|
127
|
+
def process_result(result)
|
|
128
|
+
result_exit_status = result_exit_status(result)
|
|
129
|
+
if successful?(result_exit_status)
|
|
130
|
+
write_last_run(result)
|
|
131
|
+
History.record(result)
|
|
132
|
+
end
|
|
133
|
+
result_exit_status
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# mutant:disable -- a pid is an Integer, and Integers answer ==, eql? and
|
|
137
|
+
# equal? alike for the same value.
|
|
138
|
+
def started_in_this_process?
|
|
139
|
+
SimpleCov.pid == Process.pid
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Asked as a predicate rather than compared against `ExitCodes::SUCCESS`,
|
|
143
|
+
# because two equal Integers are equal through every spelling of the
|
|
144
|
+
# comparison.
|
|
145
|
+
def successful?(status)
|
|
146
|
+
status.zero?
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def result_exit_status(result)
|
|
150
|
+
ExitCodes::ExitCodeHandling.call(result, coverage_limits: build_coverage_limits)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
# Every CoverageLimits member is named after the configuration reader that
|
|
156
|
+
# supplies it, so a new limit only has to be added to the Data definition
|
|
157
|
+
# and the checks.
|
|
158
|
+
def build_coverage_limits
|
|
159
|
+
limits = CoverageLimits.members.to_h { |name| [name, public_send(name)] } #: Hash[Symbol, untyped]
|
|
160
|
+
CoverageLimits.new(**limits)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
data/lib/simplecov/file_list.rb
CHANGED
|
@@ -1,99 +1,79 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module SimpleCov
|
|
4
|
-
# An array of SimpleCov SourceFile instances with additional collection helper
|
|
5
|
-
# methods for calculating coverage across them etc.
|
|
6
4
|
class FileList
|
|
7
5
|
include Enumerable
|
|
8
6
|
extend Forwardable
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# also delegating methods implemented in Enumerable as they have
|
|
14
|
-
# custom Array implementations which are presumably better/more
|
|
15
|
-
# resource efficient
|
|
16
|
-
:size, :map, :count,
|
|
17
|
-
# surprisingly not in Enumerable
|
|
18
|
-
:empty?, :length,
|
|
19
|
-
# still act like we're kinda an array
|
|
20
|
-
:to_a, :to_ary
|
|
8
|
+
# Enumerable's own implementations are skipped where Array has a better
|
|
9
|
+
# one, and `to_a` / `to_ary` keep this acting like the array it wraps.
|
|
10
|
+
def_delegators :@files, :each, :size, :map, :count, :empty?, :length, :to_a, :to_ary
|
|
21
11
|
|
|
22
12
|
def initialize(files)
|
|
23
13
|
@files = files
|
|
24
14
|
end
|
|
25
15
|
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
# With no argument answers the `{line:, branch:, method:}` Hash, and with a
|
|
17
|
+
# criterion symbol that one CoverageStatistics.
|
|
18
|
+
def coverage_statistics(criterion = nil)
|
|
19
|
+
stats = (@coverage_statistics ||= compute_coverage_statistics)
|
|
20
|
+
criterion ? stats[criterion] : stats
|
|
28
21
|
end
|
|
29
22
|
|
|
30
23
|
def coverage_statistics_by_file
|
|
31
24
|
@coverage_statistics_by_file ||= compute_coverage_statistics_by_file
|
|
32
25
|
end
|
|
33
26
|
|
|
34
|
-
# Returns the count of lines that have coverage
|
|
35
27
|
def covered_lines
|
|
36
28
|
coverage_statistics[:line]&.covered
|
|
37
29
|
end
|
|
38
30
|
|
|
39
|
-
# Returns the count of lines that have been missed
|
|
40
31
|
def missed_lines
|
|
41
32
|
coverage_statistics[:line]&.missed
|
|
42
33
|
end
|
|
43
34
|
|
|
44
|
-
# Returns the count of lines that are not relevant for coverage
|
|
45
35
|
def never_lines
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
map { |f| f.never_lines.count }.inject(:+)
|
|
36
|
+
sum { |f| f.never_lines.size }
|
|
49
37
|
end
|
|
50
38
|
|
|
51
|
-
# Returns the count of skipped lines
|
|
52
39
|
def skipped_lines
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
map { |f| f.skipped_lines.count }.inject(:+)
|
|
40
|
+
sum { |f| f.skipped_lines.size }
|
|
56
41
|
end
|
|
57
42
|
|
|
58
|
-
# Computes the coverage based upon lines covered and lines missed for each file
|
|
59
|
-
# Returns an array with all coverage percentages
|
|
60
43
|
def covered_percentages
|
|
61
44
|
map(&:covered_percent)
|
|
62
45
|
end
|
|
63
46
|
|
|
64
|
-
#
|
|
47
|
+
# Nil for an empty list, such as a fully filtered result.
|
|
65
48
|
def least_covered_file
|
|
66
|
-
|
|
49
|
+
# `covered_percent` is nil only for an unmeasured criterion, and :line
|
|
50
|
+
# is always measured, so the `|| 0.0` arm never fires at runtime; it
|
|
51
|
+
# exists to satisfy min_by's Comparable requirement.
|
|
52
|
+
min_by { |file| file.covered_percent || 0.0 }&.filename
|
|
67
53
|
end
|
|
68
54
|
|
|
69
|
-
# Returns the overall amount of relevant lines of code across all files in this list
|
|
70
55
|
def lines_of_code
|
|
71
56
|
coverage_statistics[:line]&.total
|
|
72
57
|
end
|
|
73
58
|
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
coverage_statistics[:line]&.percent
|
|
59
|
+
# Nil if the criterion was not measured.
|
|
60
|
+
def covered_percent(criterion = :line)
|
|
61
|
+
coverage_statistics(criterion)&.percent
|
|
78
62
|
end
|
|
79
63
|
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
coverage_statistics[:line]&.strength
|
|
64
|
+
# The strength (average hits per relevant unit) for the given criterion.
|
|
65
|
+
def covered_strength(criterion = :line)
|
|
66
|
+
coverage_statistics(criterion)&.strength
|
|
84
67
|
end
|
|
85
68
|
|
|
86
|
-
# Return total count of branches in all files
|
|
87
69
|
def total_branches
|
|
88
70
|
coverage_statistics[:branch]&.total
|
|
89
71
|
end
|
|
90
72
|
|
|
91
|
-
# Return total count of covered branches
|
|
92
73
|
def covered_branches
|
|
93
74
|
coverage_statistics[:branch]&.covered
|
|
94
75
|
end
|
|
95
76
|
|
|
96
|
-
# Return total count of covered branches
|
|
97
77
|
def missed_branches
|
|
98
78
|
coverage_statistics[:branch]&.missed
|
|
99
79
|
end
|
|
@@ -102,19 +82,54 @@ module SimpleCov
|
|
|
102
82
|
coverage_statistics[:branch]&.percent
|
|
103
83
|
end
|
|
104
84
|
|
|
105
|
-
|
|
85
|
+
def total_methods
|
|
86
|
+
coverage_statistics[:method]&.total
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def covered_methods
|
|
90
|
+
coverage_statistics[:method]&.covered
|
|
91
|
+
end
|
|
106
92
|
|
|
93
|
+
def missed_methods
|
|
94
|
+
coverage_statistics[:method]&.missed
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def method_covered_percent
|
|
98
|
+
coverage_statistics[:method]&.percent
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
private
|
|
102
|
+
|
|
103
|
+
# Seeded with one entry per criterion the user enabled, so an empty FileList
|
|
104
|
+
# still yields the right shape. `SourceFile#coverage_statistics` always
|
|
105
|
+
# reports all three criteria; FileList is the layer that filters to the
|
|
106
|
+
# enabled set so disabled criteria don't surface in totals, JSON, or the
|
|
107
|
+
# HTML report.
|
|
107
108
|
def compute_coverage_statistics_by_file
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
seed = enabled_criteria_for_reporting.to_h do |criterion|
|
|
110
|
+
bucket = [] #: Array[CoverageStatistics]
|
|
111
|
+
[criterion, bucket]
|
|
112
|
+
end
|
|
113
|
+
each_with_object(seed) do |file, together|
|
|
114
|
+
file.coverage_statistics.each do |criterion, stats|
|
|
115
|
+
together.fetch(criterion) << stats if together.key?(criterion)
|
|
116
|
+
end
|
|
111
117
|
end
|
|
112
118
|
end
|
|
113
119
|
|
|
114
120
|
def compute_coverage_statistics
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
coverage_statistics_by_file.transform_values { |stats| CoverageStatistics.from(stats) }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# `:line` (or its `:oneshot_line` synonym) is reported when either criterion
|
|
125
|
+
# is enabled; the JRuby-gated branch/method criteria are reported when they
|
|
126
|
+
# pass their own engine-support check.
|
|
127
|
+
def enabled_criteria_for_reporting
|
|
128
|
+
criteria = [] #: Array[SimpleCov::criterion]
|
|
129
|
+
criteria << :line if SimpleCov.line_coverage?
|
|
130
|
+
criteria << :branch if SimpleCov.branch_coverage?
|
|
131
|
+
criteria << :method if SimpleCov.method_coverage?
|
|
132
|
+
criteria
|
|
118
133
|
end
|
|
119
134
|
end
|
|
120
135
|
end
|