Module: SimpleCov::CLI::CoverageFile
Overview
Shared input boundary for commands that consume JSONFormatter's coverage.json. It validates only the stable outer shape, so older schema versions remain readable.
Constant Summary collapse
- CRITERIA =
{ line: {label: "Line", percent: "lines_covered_percent", covered: "covered_lines", missed: "missed_lines", total: "total_lines"}, branch: {label: "Branch", percent: "branches_covered_percent", covered: "covered_branches", missed: "missed_branches", total: "total_branches"}, method: {label: "Method", percent: "methods_covered_percent", covered: "covered_methods", missed: "missed_methods", total: "total_methods"} }.freeze
Instance Method Summary collapse
-
#exact_index(coverage_hash) ⇒ Object
The report's entries under exact, realpath-normalized keys, for programmatic resolution where a suffix fallback could bind a path to the wrong entry.
- #load_coverage(path, command:, stderr:) ⇒ Object
- #load_document(path, command:, stderr:) ⇒ Object
-
#lookup(coverage_hash, path) ⇒ Object
An exact match, the absolute path or the literal string passed, wins over a suffix match, so a nested "vendor/lib/foo.rb" can't shadow the real "lib/foo.rb" just by sitting earlier in the report.
-
#normalize(key) ⇒ Object
A key whose file no longer exists keeps its literal spelling.
-
#not_found_message(coverage_hash, path, input) ⇒ Object
An ambiguous subpath names its candidates: "no entry" would send the user hunting for a typo in a path that exists twice.
- #report_invalid(stderr, command, path, reason) ⇒ Object
- #report_missing(stderr, command, path) ⇒ Object
- #report_unreadable(stderr, command, path, reason) ⇒ Object
-
#suffix_matches(coverage_hash, path) ⇒ Object
The suffix's leading slash keeps "foo.rb" from matching "barfoo.rb", and hoisting it keeps the scan from allocating a string per entry.
Instance Method Details
#exact_index(coverage_hash) ⇒ Object
The report's entries under exact, realpath-normalized keys, for programmatic resolution where a suffix fallback could bind a path to the wrong entry. The realpath step keeps a symlinked project root from splitting a resolved path apart from the report's keys.
47 48 49 50 51 52 53 54 |
# File 'lib/simplecov/cli/coverage_file.rb', line 47 def exact_index(coverage_hash) index = {} #: Hash[String, untyped] coverage_hash.each do |key, payload| index[key] ||= payload index[normalize(key)] ||= payload end index end |
#load_coverage(path, command:, stderr:) ⇒ Object
83 84 85 86 87 88 89 90 91 92 |
# File 'lib/simplecov/cli/coverage_file.rb', line 83 def load_coverage(path, command:, stderr:) document = load_document(path, command: command, stderr: stderr) return unless document none = {} #: Hash[String, untyped] coverage = document.fetch("coverage", none) return coverage if coverage.is_a?(Hash) report_invalid(stderr, command, path, '"coverage" must be an object') end |
#load_document(path, command:, stderr:) ⇒ Object
73 74 75 76 77 78 79 80 81 |
# File 'lib/simplecov/cli/coverage_file.rb', line 73 def load_document(path, command:, stderr:) CoverageJSON.load(path) rescue Errno::ENOENT report_missing(stderr, command, path) rescue CoverageJSON::Error => e report_invalid(stderr, command, path, e.) rescue SystemCallError => e report_unreadable(stderr, command, path, e.) end |
#lookup(coverage_hash, path) ⇒ Object
An exact match, the absolute path or the literal string passed, wins over a suffix match, so a nested "vendor/lib/foo.rb" can't shadow the real "lib/foo.rb" just by sitting earlier in the report. A subpath can end more than one key, and a collision is left as "not found" for the caller rather than scoring whichever key happens to sit first.
27 28 29 30 31 32 33 34 |
# File 'lib/simplecov/cli/coverage_file.rb', line 27 def lookup(coverage_hash, path) absolute = File.(path) return [absolute, coverage_hash.fetch(absolute)] if coverage_hash.key?(absolute) return [path, coverage_hash.fetch(path)] if coverage_hash.key?(path) matches = suffix_matches(coverage_hash, path) matches.first if matches.one? end |
#normalize(key) ⇒ Object
A key whose file no longer exists keeps its literal spelling.
57 58 59 60 61 |
# File 'lib/simplecov/cli/coverage_file.rb', line 57 def normalize(key) File.realdirpath(key) rescue SystemCallError key end |
#not_found_message(coverage_hash, path, input) ⇒ Object
An ambiguous subpath names its candidates: "no entry" would send the user hunting for a typo in a path that exists twice.
65 66 67 68 69 70 71 |
# File 'lib/simplecov/cli/coverage_file.rb', line 65 def (coverage_hash, path, input) matches = suffix_matches(coverage_hash, path).keys return "no entry for #{path} in #{input}" if matches.size < 2 "#{path} matches #{matches.size} files in #{input}: #{matches.sort.join(", ")} " \ "(use a longer path to pick one)" end |
#report_invalid(stderr, command, path, reason) ⇒ Object
94 95 96 97 |
# File 'lib/simplecov/cli/coverage_file.rb', line 94 def report_invalid(stderr, command, path, reason) detail = reason.lines.first.to_s.strip stderr.puts("simplecov #{command}: input file #{path.inspect} isn't valid JSON (#{detail})") end |
#report_missing(stderr, command, path) ⇒ Object
99 100 101 |
# File 'lib/simplecov/cli/coverage_file.rb', line 99 def report_missing(stderr, command, path) stderr.puts("simplecov #{command}: #{path} not found") end |
#report_unreadable(stderr, command, path, reason) ⇒ Object
103 104 105 106 |
# File 'lib/simplecov/cli/coverage_file.rb', line 103 def report_unreadable(stderr, command, path, reason) detail = reason.lines.first.to_s.strip stderr.puts("simplecov #{command}: cannot read #{path.inspect} (#{detail})") end |
#suffix_matches(coverage_hash, path) ⇒ Object
The suffix's leading slash keeps "foo.rb" from matching "barfoo.rb", and hoisting it keeps the scan from allocating a string per entry.
38 39 40 41 |
# File 'lib/simplecov/cli/coverage_file.rb', line 38 def suffix_matches(coverage_hash, path) suffix = "/#{path}" coverage_hash.select { |fname, _| fname.end_with?(suffix) } end |