Module: SimpleCov::SimulateCoverage
- Defined in:
- lib/simplecov/simulate_coverage.rb
Overview
Responsible for producing file coverage metrics.
Class Method Summary collapse
-
.call(absolute_path) ⇒ Hash
Simulate a file coverage report for a file that was tracked but never required.
-
.coverage_stub(path, source_lines) ⇒ Object
Combine ‘Coverage.line_stub` (which gets multi-line statements right) with `LinesClassifier` (which knows about `# :nocov:` toggles and `# simplecov:disable line` ranges).
- .read_lines(path) ⇒ Object
Class Method Details
.call(absolute_path) ⇒ Hash
Simulate a file coverage report for a file that was tracked but never required. Returns the same hash shape as ‘Coverage.result` (lines, branches, methods).
The line classification comes from ‘Coverage.line_stub` — the same classification the runtime would have produced if the file had been required — overlaid with SimpleCov’s ‘# :nocov:` toggles and `# simplecov:disable line` directive ranges, which `Coverage` doesn’t know about. This keeps “relevant lines” identical whether a file was loaded or just tracked, fixing the multi-line statement discrepancy in github.com/simplecov-ruby/simplecov/issues/654.
Branches and methods are enumerated by static analysis (via ‘StaticCoverageExtractor`, which uses Prism). Earlier behavior left both as empty hashes, which made unloaded files invisible to the branch/method denominators while their lines DID count — so a `track_files`/`cover` glob that picked up files without specs silently inflated branch% relative to line%. See github.com/simplecov-ruby/simplecov/issues/1059. When Prism isn’t loadable (Ruby < 3.3 without the prism gem) or the file can’t be parsed, fall back to the old empty hashes — old behavior, old tradeoff.
38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/simplecov/simulate_coverage.rb', line 38 def call(absolute_path) source_lines = read_lines(absolute_path) lines = coverage_stub(absolute_path, source_lines) || LinesClassifier.new.classify(source_lines) synthesized = StaticCoverageExtractor.call(source_lines.join) || {"branches" => {}, "methods" => {}} { "lines" => lines, "branches" => synthesized["branches"], "methods" => synthesized["methods"] } end |
.coverage_stub(path, source_lines) ⇒ Object
Combine ‘Coverage.line_stub` (which gets multi-line statements right) with `LinesClassifier` (which knows about `# :nocov:` toggles and `# simplecov:disable line` ranges). Returns nil — and the caller falls back to `LinesClassifier` alone — when `Coverage` can’t read or parse the file, or when the runtime doesn’t expose ‘line_stub` (JRuby and TruffleRuby).
64 65 66 67 68 69 70 71 72 73 |
# File 'lib/simplecov/simulate_coverage.rb', line 64 def coverage_stub(path, source_lines) return nil unless Coverage.respond_to?(:line_stub) stub = Coverage.line_stub(path) classifier_output = LinesClassifier.new.classify(source_lines) stub.each_index { |idx| stub[idx] = nil if classifier_output[idx].nil? } stub rescue Errno::ENOENT, SyntaxError nil end |
.read_lines(path) ⇒ Object
52 53 54 55 56 |
# File 'lib/simplecov/simulate_coverage.rb', line 52 def read_lines(path) File.readlines(path) rescue Errno::ENOENT [] end |