simplecov 1.1.0 → 1.2.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/README.md +165 -57
- data/exe/simplecov +0 -3
- data/lib/minitest/simplecov_plugin.rb +12 -9
- data/lib/simplecov/atomic_file.rb +16 -19
- data/lib/simplecov/autostart.rb +5 -7
- 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/badge/svg.rb +70 -0
- data/lib/simplecov/cli/badge.rb +62 -0
- data/lib/simplecov/cli/clean.rb +23 -24
- data/lib/simplecov/cli/command_helpers.rb +55 -19
- data/lib/simplecov/cli/completions/scripts.rb +114 -0
- data/lib/simplecov/cli/completions.rb +83 -0
- data/lib/simplecov/cli/coverage.rb +25 -28
- data/lib/simplecov/cli/coverage_file.rb +58 -14
- 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 +35 -67
- data/lib/simplecov/cli/dotfile.rb +83 -38
- 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 +34 -39
- data/lib/simplecov/cli/open.rb +12 -15
- data/lib/simplecov/cli/patch/changed_lines.rb +149 -0
- data/lib/simplecov/cli/patch/output.rb +135 -0
- data/lib/simplecov/cli/patch.rb +169 -0
- data/lib/simplecov/cli/ratchet/output.rb +44 -0
- data/lib/simplecov/cli/ratchet.rb +83 -0
- data/lib/simplecov/cli/report.rb +18 -24
- data/lib/simplecov/cli/run.rb +8 -9
- data/lib/simplecov/cli/serve/report_preparer.rb +7 -6
- data/lib/simplecov/cli/serve/static_file_handler.rb +62 -56
- data/lib/simplecov/cli/serve.rb +27 -24
- 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 +37 -0
- data/lib/simplecov/cli/uncovered.rb +70 -32
- data/lib/simplecov/cli/usage.rb +152 -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 +43 -92
- data/lib/simplecov/color.rb +21 -35
- data/lib/simplecov/combine/branches_combiner.rb +7 -31
- data/lib/simplecov/combine/coverage_accumulator.rb +49 -142
- data/lib/simplecov/combine/identity_interner.rb +10 -13
- data/lib/simplecov/combine/interned_counts.rb +7 -9
- data/lib/simplecov/combine/lines_combiner.rb +20 -44
- data/lib/simplecov/combine/methods_combiner.rb +9 -35
- data/lib/simplecov/combine/results_combiner.rb +4 -21
- data/lib/simplecov/combine.rb +0 -6
- data/lib/simplecov/command_guesser.rb +20 -34
- data/lib/simplecov/configuration/baseline.rb +31 -0
- data/lib/simplecov/configuration/coverage.rb +91 -79
- data/lib/simplecov/configuration/coverage_criteria.rb +43 -54
- data/lib/simplecov/configuration/deprecations.rb +21 -0
- data/lib/simplecov/configuration/eval_coverage.rb +6 -10
- data/lib/simplecov/configuration/filters.rb +40 -73
- data/lib/simplecov/configuration/formatting.rb +74 -83
- data/lib/simplecov/configuration/groups.rb +19 -13
- data/lib/simplecov/configuration/history.rb +44 -0
- data/lib/simplecov/configuration/ignored_entries.rb +31 -22
- data/lib/simplecov/configuration/merging.rb +94 -62
- 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 +33 -62
- data/lib/simplecov/configuration/view_coverage.rb +38 -0
- data/lib/simplecov/configuration.rb +70 -76
- 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 +2 -5
- data/lib/simplecov/coverage_statistics.rb +14 -24
- data/lib/simplecov/coverage_violations.rb +157 -64
- data/lib/simplecov/current_run.rb +61 -0
- data/lib/simplecov/defaults.rb +4 -23
- data/lib/simplecov/deprecation.rb +45 -27
- data/lib/simplecov/directive.rb +46 -62
- data/lib/simplecov/exit_codes/baseline_check.rb +46 -0
- data/lib/simplecov/exit_codes/check.rb +17 -11
- data/lib/simplecov/exit_codes/exit_code_handling.rb +21 -8
- data/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +7 -10
- 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 +7 -12
- data/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +11 -10
- data/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +7 -9
- data/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +16 -17
- data/lib/simplecov/exit_codes.rb +17 -7
- data/lib/simplecov/exit_handling.rb +94 -59
- data/lib/simplecov/file_list.rb +20 -51
- data/lib/simplecov/filter.rb +43 -66
- data/lib/simplecov/formatter/base.rb +38 -51
- data/lib/simplecov/formatter/baseline_formatter.rb +98 -0
- data/lib/simplecov/formatter/coverage_json_writer.rb +29 -31
- data/lib/simplecov/formatter/html_formatter/public/index.html +17 -10
- data/lib/simplecov/formatter/html_formatter/viewer_data_validator.rb +74 -17
- data/lib/simplecov/formatter/html_formatter.rb +24 -41
- data/lib/simplecov/formatter/json_formatter/errors_formatter.rb +50 -16
- data/lib/simplecov/formatter/json_formatter/production_section_formatter.rb +48 -0
- data/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +43 -29
- data/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +14 -10
- data/lib/simplecov/formatter/json_formatter.rb +7 -13
- data/lib/simplecov/formatter/multi_formatter.rb +5 -11
- data/lib/simplecov/formatter/simple_formatter.rb +2 -8
- data/lib/simplecov/formatter.rb +14 -10
- data/lib/simplecov/group_names.rb +13 -12
- data/lib/simplecov/history.rb +115 -0
- data/lib/simplecov/last_run.rb +6 -9
- data/lib/simplecov/lines_classifier.rb +31 -16
- data/lib/simplecov/load_global_config.rb +7 -7
- data/lib/simplecov/parallel_adapters/base.rb +13 -41
- data/lib/simplecov/parallel_adapters/generic.rb +7 -18
- data/lib/simplecov/parallel_adapters/parallel_tests.rb +21 -42
- data/lib/simplecov/parallel_adapters.rb +24 -53
- data/lib/simplecov/parallel_coordination.rb +39 -49
- data/lib/simplecov/parallel_result_merger/worker_payload.rb +33 -0
- data/lib/simplecov/parallel_result_merger.rb +67 -126
- data/lib/simplecov/process.rb +14 -32
- 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/rails.rb +1 -12
- data/lib/simplecov/profiles/root_filter.rb +0 -4
- data/lib/simplecov/profiles/strict.rb +9 -19
- data/lib/simplecov/profiles.rb +11 -33
- data/lib/simplecov/report_deferral.rb +11 -15
- data/lib/simplecov/report_stamp.rb +6 -9
- data/lib/simplecov/result/filter_config.rb +19 -0
- data/lib/simplecov/result/missing_source_files_reporter.rb +8 -9
- data/lib/simplecov/result/serialization.rb +43 -0
- data/lib/simplecov/result/source_file_builder.rb +7 -12
- data/lib/simplecov/result.rb +68 -117
- data/lib/simplecov/result_adapter.rb +58 -68
- data/lib/simplecov/result_merger/contexts.rb +22 -0
- data/lib/simplecov/result_merger/legacy_format_adapter.rb +4 -8
- data/lib/simplecov/result_merger/resultset_file.rb +20 -27
- data/lib/simplecov/result_merger/resultset_run_identity.rb +23 -25
- data/lib/simplecov/result_merger/resultset_store.rb +23 -14
- data/lib/simplecov/result_merger/unloaded_files.rb +24 -32
- data/lib/simplecov/result_merger.rb +59 -58
- data/lib/simplecov/result_processing.rb +74 -119
- data/lib/simplecov/run_identity.rb +16 -18
- data/lib/simplecov/simulate_coverage.rb +35 -50
- data/lib/simplecov/source_file/branch.rb +8 -33
- data/lib/simplecov/source_file/branch_builder.rb +28 -47
- data/lib/simplecov/source_file/builder_context.rb +6 -13
- data/lib/simplecov/source_file/line.rb +13 -33
- data/lib/simplecov/source_file/line_builder.rb +9 -15
- data/lib/simplecov/source_file/method.rb +3 -7
- data/lib/simplecov/source_file/method_builder.rb +6 -15
- data/lib/simplecov/source_file/ruby_data_parser.rb +59 -66
- data/lib/simplecov/source_file/skip_chunks.rb +19 -28
- data/lib/simplecov/source_file/source_loader.rb +45 -35
- data/lib/simplecov/source_file/statistics.rb +10 -9
- data/lib/simplecov/source_file.rb +21 -51
- data/lib/simplecov/static_coverage_extractor/condition_folding.rb +63 -114
- data/lib/simplecov/static_coverage_extractor/location_conventions.rb +76 -121
- data/lib/simplecov/static_coverage_extractor/method_collector.rb +11 -24
- data/lib/simplecov/static_coverage_extractor/prism_compat.rb +15 -24
- data/lib/simplecov/static_coverage_extractor/value_position.rb +25 -38
- data/lib/simplecov/static_coverage_extractor/visitor.rb +47 -64
- data/lib/simplecov/static_coverage_extractor.rb +46 -62
- 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 +18 -31
- data/lib/simplecov/useless_results_remover.rb +7 -6
- 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 +1 -0
- data/lib/simplecov.rb +59 -114
- data/lib/simplecov_json_formatter.rb +1 -0
- data/man/simplecov.1 +349 -0
- data/sig/simplecov.rbs +523 -595
- metadata +72 -7
- data/schemas/coverage-v1.0.schema.json +0 -306
- data/schemas/coverage.schema.json +0 -306
|
@@ -6,37 +6,35 @@ module SimpleCov
|
|
|
6
6
|
module Formatter
|
|
7
7
|
# @api private
|
|
8
8
|
#
|
|
9
|
-
# Shared scaffolding for formatters that write a coverage report to
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# override `message_prefix` (e.g. JSON prepends "JSON ").
|
|
9
|
+
# Shared scaffolding for formatters that write a coverage report to an
|
|
10
|
+
# output directory and emit a "Coverage report generated for X to Y"
|
|
11
|
+
# summary on stderr. Subclasses override `format`, and may override
|
|
12
|
+
# `message_prefix`.
|
|
14
13
|
class Base
|
|
15
|
-
# `output_dir` defaults to `SimpleCov.coverage_path
|
|
16
|
-
#
|
|
17
|
-
# somewhere else (handy for tests that don't want to clobber the
|
|
18
|
-
# project's `coverage/` directory).
|
|
14
|
+
# `output_dir` defaults to `SimpleCov.coverage_path`. Pass it explicitly
|
|
15
|
+
# to write somewhere else.
|
|
19
16
|
def initialize(silent: false, output_dir: nil)
|
|
20
17
|
@silent = silent
|
|
21
18
|
@output_dir = output_dir
|
|
22
19
|
end
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
private
|
|
25
22
|
|
|
26
23
|
# The one home of the "status lines go to stderr, not through warn"
|
|
27
|
-
# decision (
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
# trackers, raise-on-warning test setups) nor vanishes under `-W0`.
|
|
32
|
-
# Subclasses call this at the end of their `format`.
|
|
24
|
+
# decision (#1225). stderr rather than stdout because this is a status
|
|
25
|
+
# message, not the program's output, so it stays out of pipelines like
|
|
26
|
+
# `rspec -f json`. And `$stderr.puts` rather than `warn` so the line
|
|
27
|
+
# neither reaches `Warning.warn` hooks nor vanishes under `-W0`.
|
|
33
28
|
def emit_status(result)
|
|
34
29
|
$stderr.puts output_message(result) unless @silent # rubocop:disable Style/StderrPuts
|
|
30
|
+
rescue IOError
|
|
31
|
+
# A parallel runner can close a worker's stderr before its at_exit hooks
|
|
32
|
+
# run (rspec-conductor does). Losing the status line must not abort the
|
|
33
|
+
# exit tasks that follow report generation.
|
|
35
34
|
end
|
|
36
35
|
|
|
37
|
-
# Subclasses override to prepend a marker (e.g. "JSON ") to the
|
|
38
|
-
#
|
|
39
|
-
# historically been the unmarked default.
|
|
36
|
+
# Subclasses override to prepend a marker (e.g. "JSON ") to the summary
|
|
37
|
+
# line. Empty for the HTML formatter, historically the unmarked default.
|
|
40
38
|
def message_prefix
|
|
41
39
|
""
|
|
42
40
|
end
|
|
@@ -45,15 +43,11 @@ module SimpleCov
|
|
|
45
43
|
@output_dir || SimpleCov.coverage_path
|
|
46
44
|
end
|
|
47
45
|
|
|
48
|
-
# The path shown in the "Coverage report generated for X to Y"
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
# hyperlinks paths) can act on — e.g. `coverage/index.html`
|
|
54
|
-
# instead of the bare directory `coverage`. Paths outside cwd
|
|
55
|
-
# stay absolute; a `../../../tmp/cov` display would be more
|
|
56
|
-
# confusing than the absolute form. See issue #197.
|
|
46
|
+
# The path shown in the "Coverage report generated for X to Y" status line.
|
|
47
|
+
# Rendered relative to cwd when `output_path` lives inside cwd, with the
|
|
48
|
+
# formatter's `entry_point_filename` appended so the line points at a
|
|
49
|
+
# concrete file a terminal can hyperlink. Paths outside cwd stay
|
|
50
|
+
# absolute; a `../../../tmp/cov` display would be more confusing (#197).
|
|
57
51
|
def displayable_output_path
|
|
58
52
|
directory = relative_or_absolute_output_path
|
|
59
53
|
entry_point = entry_point_filename
|
|
@@ -65,47 +59,40 @@ module SimpleCov
|
|
|
65
59
|
relative = Pathname.new(absolute).relative_path_from(Pathname.pwd).to_s
|
|
66
60
|
relative.start_with?("..") ? absolute : relative
|
|
67
61
|
rescue ArgumentError
|
|
68
|
-
# Pathname#relative_path_from raises across mixed absolute/
|
|
69
|
-
#
|
|
70
|
-
# absolute form on any unresolvable case.
|
|
62
|
+
# Pathname#relative_path_from raises across mixed absolute/relative
|
|
63
|
+
# inputs, and across Windows drives.
|
|
71
64
|
output_path
|
|
72
65
|
end
|
|
73
66
|
|
|
74
|
-
# Subclasses override to name the report's entry-point file
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
# that has no single canonical entry point.
|
|
67
|
+
# Subclasses override to name the report's entry-point file, which gets
|
|
68
|
+
# appended to the directory in the status line. The empty body answers
|
|
69
|
+
# nil, which leaves the bare directory in place for any third-party
|
|
70
|
+
# formatter that has no single canonical entry point.
|
|
79
71
|
def entry_point_filename
|
|
80
|
-
nil
|
|
81
72
|
end
|
|
82
73
|
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# to Y") is always first; per-criterion lines follow in the
|
|
86
|
-
# order of `result.coverage_statistics` (which is the same
|
|
87
|
-
# insertion order as `SourceFile#coverage_statistics`, which in
|
|
88
|
-
# turn reflects what the user enabled).
|
|
74
|
+
# One summary line per criterion the run actually measured, in the order
|
|
75
|
+
# of `result.coverage_statistics`, which reflects what the user enabled.
|
|
89
76
|
def output_message(result)
|
|
90
77
|
header = "#{message_prefix}Coverage report generated for #{result.command_name} to #{displayable_output_path}"
|
|
91
|
-
body
|
|
78
|
+
body = result.coverage_statistics.filter_map { |criterion, stat| stats_line(criterion, stat) }
|
|
92
79
|
[header, *body].join("\n")
|
|
93
80
|
end
|
|
94
81
|
|
|
95
|
-
# Returns nil for branch/method criteria that have nothing to
|
|
96
|
-
#
|
|
97
|
-
# Showing "Branch coverage: 0 / 0 (100.00%)" is noise; the older
|
|
98
|
-
# output specifically suppressed it.
|
|
82
|
+
# Returns nil for branch/method criteria that have nothing to measure.
|
|
83
|
+
# Showing "Branch coverage: 0 / 0 (100.00%)" is noise.
|
|
99
84
|
def stats_line(criterion, stat)
|
|
100
|
-
return if criterion
|
|
85
|
+
return if !criterion.equal?(:line) && !stat.total.positive?
|
|
101
86
|
|
|
102
87
|
percent = SimpleCov.round_coverage(stat.percent)
|
|
88
|
+
# `Symbol#capitalize` answers a Symbol, which `%<label>s` renders as its
|
|
89
|
+
# capitalized name, so the label needs no `to_s` step of its own.
|
|
103
90
|
Kernel.format(
|
|
104
91
|
"%<label>s coverage: %<covered>d / %<total>d (%<percent>s)",
|
|
105
|
-
label: criterion.
|
|
92
|
+
label: criterion.capitalize,
|
|
106
93
|
covered: stat.covered,
|
|
107
94
|
total: stat.total,
|
|
108
|
-
percent:
|
|
95
|
+
percent: Color.colorize_percent(percent)
|
|
109
96
|
)
|
|
110
97
|
end
|
|
111
98
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
require_relative "../atomic_file"
|
|
5
|
+
|
|
6
|
+
module SimpleCov
|
|
7
|
+
module Formatter
|
|
8
|
+
# Auto-ratchets the per-file coverage baseline as a formatter, for teams
|
|
9
|
+
# that want floors to tighten on every run instead of by deliberate
|
|
10
|
+
# `simplecov ratchet` invocations. The semantics are exactly the CLI's, and
|
|
11
|
+
# the file is rewritten only when a floor actually moved.
|
|
12
|
+
#
|
|
13
|
+
# The exit checks still judge the run against the floors as they were when
|
|
14
|
+
# it started (the baseline is read once per path), and ratcheting never
|
|
15
|
+
# loosens a floor, so tightening before the checks cannot flip a failing
|
|
16
|
+
# file to passing.
|
|
17
|
+
class BaselineFormatter < Base
|
|
18
|
+
def format(result)
|
|
19
|
+
emit_status(ratchet(result))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def ratchet(result)
|
|
25
|
+
current = current_floors(result)
|
|
26
|
+
existing = SimpleCov.baseline
|
|
27
|
+
return generate(current) unless existing
|
|
28
|
+
|
|
29
|
+
outcome = existing.ratchet(current)
|
|
30
|
+
changed = outcome.tightened.any? || outcome.pruned.any?
|
|
31
|
+
write(outcome.baseline) if changed
|
|
32
|
+
note = summary(outcome, changed)
|
|
33
|
+
regressed = outcome.regressed
|
|
34
|
+
regressed.empty? ? note : "#{note}#{below_floors(regressed)}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def generate(current)
|
|
38
|
+
baseline = Baseline.generate(current)
|
|
39
|
+
write(baseline)
|
|
40
|
+
files = baseline.entries.size
|
|
41
|
+
"Coverage baseline generated to #{displayable_output_path} (#{files} #{files.eql?(1) ? "file" : "files"})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def summary(outcome, changed)
|
|
45
|
+
return "Coverage baseline unchanged at #{displayable_output_path}" unless changed
|
|
46
|
+
|
|
47
|
+
"Coverage baseline ratcheted to #{displayable_output_path} " \
|
|
48
|
+
"(#{outcome.tightened.size} tightened, #{outcome.pruned.size} pruned)"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def below_floors(regressed)
|
|
52
|
+
noun = regressed.size.eql?(1) ? "1 file below its floor" : "#{regressed.size} files below their floors"
|
|
53
|
+
", #{noun}: #{regressed.join(", ")}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# `SourceFile#coverage_statistics` answers for disabled criteria too, as
|
|
57
|
+
# empty statistics, so the criteria are gated on the configuration rather
|
|
58
|
+
# than on the stats' presence.
|
|
59
|
+
def current_floors(result)
|
|
60
|
+
criteria = measured_criteria
|
|
61
|
+
floors = {} #: Baseline::current_floors
|
|
62
|
+
result.files.each do |file|
|
|
63
|
+
floors[file.project_filename] = criteria.to_h { |criterion| [criterion, floor_of(file, criterion)] }
|
|
64
|
+
end
|
|
65
|
+
floors
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def measured_criteria
|
|
69
|
+
criteria = [] #: Array[Symbol]
|
|
70
|
+
criteria << :line if SimpleCov.line_coverage?
|
|
71
|
+
criteria << :branch if SimpleCov.branch_coverage?
|
|
72
|
+
criteria << :method if SimpleCov.method_coverage?
|
|
73
|
+
criteria
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def floor_of(file, criterion)
|
|
77
|
+
stats = file.coverage_statistics(criterion)
|
|
78
|
+
{percent: SimpleCov.round_coverage(stats.percent), missed: stats.missed}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def write(baseline)
|
|
82
|
+
AtomicFile.write(output_path, baseline.to_yaml)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The baseline lives where the exit check reads it, not under the coverage
|
|
86
|
+
# directory: it is checked-in policy, not a report artifact.
|
|
87
|
+
def output_path
|
|
88
|
+
File.expand_path(SimpleCov.baseline_file, SimpleCov.root)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# `format` hands the ratchet's summary to `emit_status`, which passes it
|
|
92
|
+
# through to here in the result's place.
|
|
93
|
+
def output_message(message)
|
|
94
|
+
message
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -6,23 +6,17 @@ require_relative "../atomic_file"
|
|
|
6
6
|
|
|
7
7
|
module SimpleCov
|
|
8
8
|
module Formatter
|
|
9
|
-
# Shared writer for the coverage.json artifact, used by JSONFormatter
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# mode, so no CRLF translation on Windows depending on which
|
|
14
|
-
# formatter wrote last) and gives both formatters the
|
|
15
|
-
# concurrent-overwrite warning of issue #1171.
|
|
9
|
+
# Shared writer for the coverage.json artifact, used by JSONFormatter as its
|
|
10
|
+
# report and by HTMLFormatter as the side file feeding `simplecov serve` and
|
|
11
|
+
# external tools. Centralizing the write keeps the two copies byte-identical
|
|
12
|
+
# and gives both formatters the concurrent-overwrite warning of #1171.
|
|
16
13
|
module CoverageJSONWriter
|
|
17
14
|
FILENAME = "coverage.json"
|
|
18
15
|
|
|
19
|
-
# The previous report can embed the project's entire source text,
|
|
20
|
-
# so the overwrite check bounds how much of it is read looking for
|
|
21
|
-
# the meta object before falling back to a full parse.
|
|
22
16
|
META_SCAN_BYTES = 64 * 1024
|
|
23
17
|
private_constant :META_SCAN_BYTES
|
|
24
18
|
|
|
25
|
-
|
|
19
|
+
extend self
|
|
26
20
|
|
|
27
21
|
def write(output_path, hash, result)
|
|
28
22
|
path = File.join(output_path, FILENAME)
|
|
@@ -32,20 +26,16 @@ module SimpleCov
|
|
|
32
26
|
end
|
|
33
27
|
|
|
34
28
|
# Warns when the existing coverage.json has a timestamp newer than this
|
|
35
|
-
# process's start time
|
|
36
|
-
#
|
|
37
|
-
#
|
|
29
|
+
# process's start time, a strong signal that a sibling test process wrote it
|
|
30
|
+
# while we were running. A matching command_name means the same merged
|
|
31
|
+
# result, which is what both formatters configured together produce, so
|
|
32
|
+
# there is nothing to lose by overwriting (#1171).
|
|
38
33
|
def warn_if_concurrent_overwrite(path, result)
|
|
39
34
|
start_time = SimpleCov.process_start_time or return
|
|
40
35
|
existing = existing_meta(path) or return
|
|
41
|
-
return unless existing
|
|
36
|
+
return unless existing.fetch(:timestamp) > start_time
|
|
42
37
|
|
|
43
|
-
|
|
44
|
-
# they are configured together the file found here was just written
|
|
45
|
-
# by our own run, not a concurrent one. A matching command_name
|
|
46
|
-
# means the same merged result, so there's nothing to lose by
|
|
47
|
-
# overwriting. See issue #1171.
|
|
48
|
-
return if existing[:command_name] == result.command_name
|
|
38
|
+
return if existing.fetch(:command_name).eql?(result.command_name)
|
|
49
39
|
|
|
50
40
|
warn "simplecov: #{path} was written at #{existing[:timestamp].iso8601} — after " \
|
|
51
41
|
"this process started at #{start_time.iso8601}. Overwriting " \
|
|
@@ -58,11 +48,22 @@ module SimpleCov
|
|
|
58
48
|
return nil unless File.exist?(path)
|
|
59
49
|
|
|
60
50
|
meta = parse_meta(path) or return nil
|
|
61
|
-
timestamp = meta[:timestamp] or return nil
|
|
51
|
+
timestamp = parse_time(meta[:timestamp]) or return nil
|
|
62
52
|
|
|
63
|
-
{timestamp:
|
|
53
|
+
{timestamp: timestamp, command_name: meta[:command_name]}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Our own writer emits an ISO 8601 string, but coverage.json is whatever
|
|
57
|
+
# wrote it last: third-party formatters rewrite it in their own shape with
|
|
58
|
+
# an epoch integer, and `Time.iso8601` raised TypeError on that, taking the
|
|
59
|
+
# whole report down on the next run. Anything but the two spellings disables
|
|
60
|
+
# the concurrency check for this file instead of failing the write (#1285).
|
|
61
|
+
def parse_time(value)
|
|
62
|
+
case value
|
|
63
|
+
when String then Time.iso8601(value)
|
|
64
|
+
when Numeric then Time.at(value)
|
|
65
|
+
end
|
|
64
66
|
rescue ArgumentError
|
|
65
|
-
# An unparseable timestamp disables the check for this file.
|
|
66
67
|
nil
|
|
67
68
|
end
|
|
68
69
|
|
|
@@ -70,17 +71,14 @@ module SimpleCov
|
|
|
70
71
|
parse_meta_head(path) || parse_meta_full(path)
|
|
71
72
|
end
|
|
72
73
|
|
|
73
|
-
# The meta object is flat and sits at the head of every file this
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
# inside a meta string) falls back to the full parse.
|
|
74
|
+
# The meta object is flat and sits at the head of every file this module
|
|
75
|
+
# writes, so the common case parses just that slice instead of a
|
|
76
|
+
# multi-megabyte report. A miss falls back to the full parse.
|
|
77
77
|
def parse_meta_head(path)
|
|
78
78
|
head = File.read(path, META_SCAN_BYTES).to_s
|
|
79
79
|
slice = head[/"meta"\s*:\s*(\{.*?\})/m, 1]
|
|
80
80
|
return nil unless slice
|
|
81
81
|
|
|
82
|
-
# The captured slice is always a JSON object, so a successful parse
|
|
83
|
-
# is always a Hash.
|
|
84
82
|
JSON.parse(slice, symbolize_names: true)
|
|
85
83
|
rescue JSON::ParserError
|
|
86
84
|
nil
|
|
@@ -88,7 +86,7 @@ module SimpleCov
|
|
|
88
86
|
|
|
89
87
|
def parse_meta_full(path)
|
|
90
88
|
parsed = JSON.parse(File.read(path), symbolize_names: true)
|
|
91
|
-
parsed.is_a?(Hash)
|
|
89
|
+
parsed[:meta] if parsed.is_a?(Hash)
|
|
92
90
|
rescue JSON::ParserError
|
|
93
91
|
nil
|
|
94
92
|
end
|