Module: SimpleCov::ResultMerger::UnloadedFiles

Extended by:
UnloadedFiles
Included in:
UnloadedFiles
Defined in:
lib/simplecov/result_merger/unloaded_files.rb

Overview

The merge step's half of unloaded-file handling: the policy of when to inject and which criteria simulated files carry. The mechanism, expanding globs and simulating each file, is SimpleCov::UnloadedFileInjector.

Injection moved here from the individual processes because only the union of what they all loaded says what was really never loaded. Doing it per process meant N workers simulated the same file up to N times and the merge threw all but one away (#1250).

Instance Method Summary collapse

Instance Method Details

#carries?(coverage, criterion) ⇒ Boolean

A simulated file should have the same shape as the files it is being merged alongside, so the criteria come from the merged data rather than from this process's configuration, which is not necessarily what any contributing process measured under: simplecov merge never ran SimpleCov.start at all. Giving injected files fewer tables than their neighbours is what inflates the percentage #1059 fixed.

Falls back to the configuration when there is nothing to be consistent with, which is a merge of resultsets that carried no files.

Returns:

  • (Boolean)


59
60
61
62
63
# File 'lib/simplecov/result_merger/unloaded_files.rb', line 59

def carries?(coverage, criterion)
  return SimpleCov.public_send(CRITERION_PREDICATES.fetch(criterion)) if coverage.empty?

  coverage.any? { |_filename, file_coverage| file_coverage[criterion] }
end

#carry_tracked(entry, existing, incoming) ⇒ Object

Concurrent workers sharing a command name may have been told to track different sets, so keep both rather than letting the later write win.



32
33
34
35
# File 'lib/simplecov/result_merger/unloaded_files.rb', line 32

def carry_tracked(entry, existing, incoming)
  tracked = Array(existing["tracked_files"]) | Array(incoming["tracked_files"])
  tracked.empty? ? entry : entry.merge("tracked_files" => tracked)
end

#collector(into) ⇒ Object

A collector for the merge to hand merge_valid_results, gathering the tracked paths of every resultset that survives the merge timeout. Absent from resultsets written before this was recorded, in which case those processes injected their own unloaded files already.



20
21
22
# File 'lib/simplecov/result_merger/unloaded_files.rb', line 20

def collector(into)
  ->(surviving) { into.merge(tracked_in(surviving)) }
end

#inject(coverage, tracked_files) ⇒ Object

Simulates each tracked file the merged coverage doesn't already carry. The paths come from the resultsets rather than this process's own configuration, which a standalone collate would not have. Idempotent, so resultsets that already carry their unloaded files pass through untouched.



42
43
44
45
46
47
48
# File 'lib/simplecov/result_merger/unloaded_files.rb', line 42

def inject(coverage, tracked_files)
  SimpleCov.inject_unloaded_files(
    coverage, tracked_files.to_a,
    synthesize: carries?(coverage, "branches") || carries?(coverage, "methods"),
    lines: carries?(coverage, "lines")
  )
end

#never_executed(coverage) ⇒ Object

Which files no contributing process ever loaded. Injection reports the ones it added, but a file can also arrive already simulated from a resultset this merge didn't inject into, so the merged line counts are still consulted.

A file is judged only when it has a relevant line. A branch-only or method-only run reports no line data at all for the files it loaded, and a loaded file with no executable lines reports every line as nil. Either would otherwise read as "never executed" and mark a genuinely loaded file not loaded, turning its branch and method coverage into #902's 0%.



83
84
85
86
87
88
89
90
91
92
# File 'lib/simplecov/result_merger/unloaded_files.rb', line 83

def never_executed(coverage)
  coverage.each_with_object(Set.new) do |(filename, file_coverage), set|
    counts = Array(file_coverage["lines"]) #: Array[Integer?]
    # A line count is an Integer or nil, and every Integer is truthy, so a
    # truthy element is exactly a relevant line.
    next unless counts.any?

    set << filename unless Combine::CoverageAccumulator.executed?(counts)
  end
end

#tracked_in(resultset) ⇒ Object



24
25
26
27
28
# File 'lib/simplecov/result_merger/unloaded_files.rb', line 24

def tracked_in(resultset)
  resultset.each_with_object(Set.new) do |(_command_name, data), set|
    set.merge(Array(data["tracked_files"]))
  end
end