Class: SimpleCov::Combine::CoverageAccumulator
- Inherits:
-
Object
- Object
- SimpleCov::Combine::CoverageAccumulator
- Defined in:
- lib/simplecov/combine/coverage_accumulator.rb
Overview
Folds any number of resultsets' coverage into one, absorbing them one at a time.
This replaces a pairwise reduce over the resultsets, which rebuilt
the entire accumulated structure on every one of its N-1 steps: a
fresh outer file hash, a fresh lines array for every file, and a fresh
branch / method table for every file whose keys were re-interned from
their tuples each time. On a 160-worker run over ~1,800 files that is
~290,000 whole-file rebuilds to produce ~1,800 files of output.
The accumulated side is private to the fold, so it can be updated in place instead, and the interned tables only have to be turned back into tuple-keyed hashes once, at the end.
Resultsets are absorbed one at a time rather than taken as a list, so
ResultMerger.merge_results can keep reading and discarding them one
file at a time — reading 100s of CI jobs' worth of coverage into memory
at once is what that method is careful not to do.
Defined Under Namespace
Classes: MergedFile
Class Method Summary collapse
-
.executed?(lines) ⇒ Boolean
A file some process actually loaded has at least one executed line; a simulated (never-loaded) file's lines are all
nilor0. -
.fold(pairs) ⇒ Array
Folds
[command_names, coverage]pairs into one merged coverage.
Instance Method Summary collapse
-
#absorb(coverage) ⇒ CoverageAccumulator
Folds one resultset's coverage (filename => per-file coverage) into the accumulator.
-
#initialize ⇒ CoverageAccumulator
constructor
A new instance of CoverageAccumulator.
-
#result ⇒ Hash?
The merged coverage, or
nilwhen nothing was absorbed at all — the caller needs to tell "no results" apart from "results that cover nothing", and only the former means there is no report to build.
Constructor Details
#initialize ⇒ CoverageAccumulator
Returns a new instance of CoverageAccumulator.
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/simplecov/combine/coverage_accumulator.rb', line 69 def initialize @files = {} #: Hash[String, untyped] @absorbed = false # Whether a criterion is enabled can't change mid-fold, so read it once # here rather than once per file. `branch_coverage?` reaches # `Coverage.supported?`, and a large parallel run merges thousands of # files. @branch_coverage = SimpleCov.branch_coverage? @method_coverage = SimpleCov.method_coverage? end |
Class Method Details
.executed?(lines) ⇒ Boolean
A file some process actually loaded has at least one executed line;
a simulated (never-loaded) file's lines are all nil or 0. This is
the signal the merge reconciles synthesized tuples on, and the one
ResultMerger re-derives a merged result's not-loaded set from, so it
lives here rather than being spelled out at each site.
Array() plus the Numeric test rather than a bare
any?(&:positive?) because this reads straight off a parsed
resultset, which is external input: a file written by another
SimpleCov version, or hand-edited, can carry anything under
"lines" — a Hash coerces to pairs, a String to itself. Keeping a
malformed entry to a wrong answer instead of a NoMethodError out
of the middle of a merge is the point.
43 44 45 46 |
# File 'lib/simplecov/combine/coverage_accumulator.rb', line 43 def self.executed?(lines) counts = Array(lines) #: Array[untyped] counts.any? { |count| count.is_a?(Numeric) && count.positive? } end |
.fold(pairs) ⇒ Array
Folds [command_names, coverage] pairs into one merged coverage.
pairs is only ever iterated, so a caller that reads resultsets off
disk can hand in a lazy enumerable and never hold more than one in
memory — which is what ResultMerger.merge_results is careful about.
57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/simplecov/combine/coverage_accumulator.rb', line 57 def self.fold(pairs) accumulator = new command_names = [] #: Array[String] pairs.each do |names, coverage| command_names.concat(names) accumulator.absorb(coverage) end [command_names, accumulator.result] end |
Instance Method Details
#absorb(coverage) ⇒ CoverageAccumulator
Folds one resultset's coverage (filename => per-file coverage) into
the accumulator. A nil coverage is ignored — a resultset that
carried nothing contributes nothing.
87 88 89 90 91 92 93 94 95 96 |
# File 'lib/simplecov/combine/coverage_accumulator.rb', line 87 def absorb(coverage) return self unless coverage @absorbed = true coverage.each do |filename, file_coverage| @files[filename] = merge_file(@files[filename], file_coverage) end self end |
#result ⇒ Hash?
The merged coverage, or nil when nothing was absorbed at all — the
caller needs to tell "no results" apart from "results that cover
nothing", and only the former means there is no report to build.
A file only one resultset carried is returned exactly as it came in, untouched: with nothing to merge it into, copying it would only cost memory.
109 110 111 112 113 114 115 |
# File 'lib/simplecov/combine/coverage_accumulator.rb', line 109 def result return nil unless @absorbed @files.transform_values do |entry| entry.is_a?(MergedFile) ? entry.to_h : entry end end |