Class: SimpleCov::ContextMap
- Inherits:
-
Object
- Object
- SimpleCov::ContextMap
- Defined in:
- lib/simplecov/context_map.rb,
lib/simplecov/context_map/union.rb
Overview
Records which context covered which line: a list of context ids and, per source file, a bitmap of covered lines for each context that touched the file. A context is any labeled region of execution, and the vocabulary is deliberately the general one (coverage.py calls the same idea dynamic contexts), so the stored format doesn't bake simplecov's current use into a name.
The naive shape of this data, a list of ids on every line, is O(contexts x lines) strings. Context ids are interned instead, and each context's covered lines within a file are a single Integer bitmap with bit N set when line N+1 was executed, which packs a thousand-line file into ~125 bytes and makes the union of two recordings a bitwise OR.
Serialized into .resultset.json as {"version" => 1, "contexts" => [...ids...], "files" => {path => {context index => bitmap as hex}}}. The
format is tolerated, not trusted: .from_hash returns nil for anything
malformed or future-versioned, which the merge treats the same as a map
that was never recorded.
Defined Under Namespace
Classes: Union
Constant Summary collapse
- VERSION =
Bumped on any change an older reader could misread, so
.from_hashcan treat a future format as absent instead of answering from it wrongly. 1
Class Method Summary collapse
-
.from_hash(data) ⇒ Object
nil when the data is not a well-formed map of this format version.
Instance Method Summary collapse
-
#absorb(other) ⇒ Object
Other's contexts are re-interned, so maps recorded by different processes merge by id, and a context both sides saw contributes one entry.
- #contexts ⇒ Object
-
#covering(path, line) ⇒ Object
Sorted for stable output, since recording order varies between runs and merges.
- #empty? ⇒ Boolean
-
#initialize ⇒ ContextMap
constructor
A new instance of ContextMap.
- #intern(context_id) ⇒ Object private
-
#record(context_id, lines_by_file) ⇒ Object
lines_by_filemaps a source path to the bitmap of lines the context executed there. -
#serialized_bitmaps_for(path) ⇒ Object
Index string => hex bitmap, the wire encoding
to_hwrites. -
#to_h(only: nil) ⇒ Object
only:restricts the files to a given set of paths, so the map follows the same universe as the coverage it sits beside.
Constructor Details
#initialize ⇒ ContextMap
Returns a new instance of ContextMap.
29 30 31 32 33 |
# File 'lib/simplecov/context_map.rb', line 29 def initialize @contexts = [] #: Array[String] @indices = {} #: Hash[String, Integer] @files = {} #: Hash[String, Hash[Integer, Integer]] end |
Class Method Details
.from_hash(data) ⇒ Object
nil when the data is not a well-formed map of this format version.
All-or-nothing on purpose: a partially salvaged map would answer
covering queries with silent gaps, and the merge already drops an
absent map consistently.
119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/simplecov/context_map.rb', line 119 def from_hash(data) # `eql?` rather than `==`: a version written as 1.0 is not this format, # and reading it as if it were is what the version gate exists to stop. return nil unless data.instance_of?(Hash) && data["version"].eql?(VERSION) contexts = data["contexts"] files = data["files"] return nil unless contexts.instance_of?(Array) && contexts.all?(String) && files.instance_of?(Hash) build(contexts, files) end |
Instance Method Details
#absorb(other) ⇒ Object
Other's contexts are re-interned, so maps recorded by different processes merge by id, and a context both sides saw contributes one entry.
74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/simplecov/context_map.rb', line 74 def absorb(other) remap = other.interned_contexts.map { |context_id| intern(context_id) } other.file_tables.each do |path, table| target = (@files[path] ||= {}) table.each do |index, bitmap| key = remap.fetch(index) target[key] = (target[key] || 0) | bitmap end end self end |
#contexts ⇒ Object
64 65 66 |
# File 'lib/simplecov/context_map.rb', line 64 def contexts @contexts.dup end |
#covering(path, line) ⇒ Object
Sorted for stable output, since recording order varies between runs and
merges. The path is resolved against SimpleCov.root, so callers can
pass either an absolute or a project-relative one.
A line number below 1 needs no guard of its own: it shifts the probe bit right off the end of the bitmap, leaving a mask no line can match.
56 57 58 59 60 61 62 |
# File 'lib/simplecov/context_map.rb', line 56 def covering(path, line) table = @files[File.(path, SimpleCov.root)] return [] unless table bit = 1 << (line - 1) table.filter_map { |index, bitmap| @contexts.fetch(index) if bitmap.anybits?(bit) }.sort end |
#empty? ⇒ Boolean
68 69 70 |
# File 'lib/simplecov/context_map.rb', line 68 def empty? @contexts.empty? end |
#intern(context_id) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
107 108 109 110 111 112 |
# File 'lib/simplecov/context_map.rb', line 107 def intern(context_id) @indices[context_id] ||= begin @contexts << context_id @contexts.size - 1 end end |
#record(context_id, lines_by_file) ⇒ Object
lines_by_file maps a source path to the bitmap of lines the context
executed there. The id is interned even when the delta is empty: the
context ran, and keeping it distinguishes "covered nothing of its own"
from "never recorded".
39 40 41 42 43 44 45 46 47 48 |
# File 'lib/simplecov/context_map.rb', line 39 def record(context_id, lines_by_file) index = intern(context_id) lines_by_file.each do |path, bitmap| next if bitmap.zero? table = (@files[path] ||= {}) table[index] = (table[index] || 0) | bitmap end self end |
#serialized_bitmaps_for(path) ⇒ Object
Index string => hex bitmap, the wire encoding to_h writes.
coverage.json shares the resultset's encoding through this, so the format
has one owner.
102 103 104 |
# File 'lib/simplecov/context_map.rb', line 102 def serialized_bitmaps_for(path) serialize_table(@files[File.(path, SimpleCov.root)] || {}) end |
#to_h(only: nil) ⇒ Object
only: restricts the files to a given set of paths, so the map follows
the same universe as the coverage it sits beside. The context list is
never restricted: which contexts ran is true regardless of which files
survived filtering.
90 91 92 93 94 95 96 97 |
# File 'lib/simplecov/context_map.rb', line 90 def to_h(only: nil) # Selected rather than sliced: `only` is a Set, and splatting one into # `slice` is a call `to_a` answers for, which makes dropping the `to_a` a # mutation nothing can observe. tables = only ? @files.select { |path, _table| only.include?(path) } : @files # rubocop:disable Style/HashSlice serialized = tables.transform_values { |table| serialize_table(table) } {"version" => VERSION, "contexts" => @contexts.dup, "files" => serialized} end |