Class: SimpleCov::ResultAdapter
| Relationships & Source Files | |
| Inherits: | Object |
| Defined in: | lib/simplecov/result_adapter.rb |
Overview
Responsible for adapting the format of the coverage result whether it's default or with statistics
Constant Summary
-
ADDRESS_PATTERN =
private
# File 'lib/simplecov/result_adapter.rb', line 47
Normalize memory addresses in method coverage keys so that results from different processes can be merged. Anonymous class names like "#Class:0x00007ff19ab24790" get inconsistent addresses across runs. Address widths vary by runtime (32-bit hosts: 8 hex chars; 64-bit CRuby: 16; some JVM/TruffleRuby formats may differ), so match any length of hex digits and collapse to a single placeholder.
/0x\h+/ -
ADDRESS_PLACEHOLDER =
private
# File 'lib/simplecov/result_adapter.rb', line 50"0x0" -
SINGLETON_WRAPPER_PATTERN =
private
# File 'lib/simplecov/result_adapter.rb', line 60
Strip the
#wrapper Ruby's Coverage adds to singleton-class method keys.module_functionand class methods get recorded both as singleton ([#<Class:Foo>, <code>:m</code>, …]) and instance/module ([Foo, <code>:m</code>, …]) entries pointing at the same source location; only one of the two is ever reachable at runtime, so we merge them. Only applies to named constants — anonymous-class addresses like#are left alone (handled by ADDRESS_PATTERN above)./\A#<Class:([A-Z_][\w:]*)>\z/
Class Method Summary
- .call
- .new(result) ⇒ ResultAdapter constructor
Instance Attribute Summary
- #result readonly
Instance Method Summary
- #adapt
-
#adapt_one(file_name, cover_statistic)
private
Pre-0.18 resultsets pointed each filename straight at a line-coverage array; everything since uses the
{lines:, branches:, methods:}shape. - #adapt_oneshot_lines_if_needed(file_name, cover_statistic) private
-
#aggregate_duplicated_branches(cover_statistic)
private
Ruby's eval coverage records a fresh set of branch entries for every COMPILE of an eval'd string: a template rendered through multiple view classes (e.g. hanami-view compiles each template once per view) yields several
[:if, id, location]conditions at identical coordinates in the same file, each counting only the renders that flowed through that compile. - #build_line_stub(file_name, oneshot_lines) private
-
#class_display_name(klass)
private
Rendering a class name can execute user code: a singleton class's
to_srenders its attached object via#inspect, which a module can shadow with an incompatible signature (Liquid::Utils definesinspect(value, max_depth = 2)as a module_function, so rendering#raises ArgumentError). - #normalize_method_key(key) private
-
#normalize_method_keys(cover_statistic)
private
Ruby's method coverage records one entry per DEFINED METHOD, not per source location: a block handed to
define_method/define_singleton_methodfrom a shared code path yields a separate[receiver, name, location]entry for every class it's defined on (a module'sincludedhook defining onto each descendant) AND for every name it's defined under (a builder loopingdefine_method keyover a container), all pointing at the same source. - #singleton_wrapper_name(klass) private
Constructor Details
.new(result) ⇒ ResultAdapter
Class Method Details
.call
[ GitHub ]Instance Attribute Details
#result (readonly)
[ GitHub ]# File 'lib/simplecov/result_adapter.rb', line 8
attr_reader :result
Instance Method Details
#adapt
[ GitHub ]#adapt_one(file_name, cover_statistic) (private)
Pre-0.18 resultsets pointed each filename straight at a line-coverage
array; everything since uses the {lines:, branches:, methods:}
shape. Newer entries also need their methods and branches tables
massaged before downstream code reports or merges them.
# File 'lib/simplecov/result_adapter.rb', line 32
def adapt_one(file_name, cover_statistic) return {"lines" => cover_statistic} if cover_statistic.is_a?(Array) adapt_oneshot_lines_if_needed(file_name, cover_statistic) normalize_method_keys(cover_statistic) aggregate_duplicated_branches(cover_statistic) cover_statistic end
#adapt_oneshot_lines_if_needed(file_name, cover_statistic) (private)
[ GitHub ]# File 'lib/simplecov/result_adapter.rb', line 144
def adapt_oneshot_lines_if_needed(file_name, cover_statistic) return unless cover_statistic.key?(:oneshot_lines) oneshot_lines = cover_statistic.delete(:oneshot_lines) line_stub = build_line_stub(file_name, oneshot_lines) oneshot_lines.each { |covered_line| line_stub[covered_line - 1] = 1 } cover_statistic[:lines] = line_stub end
#aggregate_duplicated_branches(cover_statistic) (private)
Ruby's eval coverage records a fresh set of branch entries for every
COMPILE of an eval'd string: a template rendered through multiple view
classes (e.g. hanami-view compiles each template once per view) yields
several [:if, id, location] conditions at identical coordinates in
the same file, each counting only the renders that flowed through that
compile. Reported as-is they inflate the branch denominator and turn a
side covered under a different compile into a phantom miss (issue
#1235). Aggregate them by (type, location) — combining a branches hash
with an empty one dedups within it, since BranchesCombiner keys arms
on location identity. Regular (non-eval) source can never produce two
conditions at the same location, so this is a no-op outside eval.
# File 'lib/simplecov/result_adapter.rb', line 137
def aggregate_duplicated_branches(cover_statistic) branches = cover_statistic[:branches] return unless branches cover_statistic[:branches] = Combine::BranchesCombiner.combine(branches, {}) end
#build_line_stub(file_name, oneshot_lines) (private)
[ GitHub ]# File 'lib/simplecov/result_adapter.rb', line 153
def build_line_stub(file_name, oneshot_lines) Coverage.line_stub(file_name) rescue Errno::ENOENT, SyntaxError Array.new(oneshot_lines.max || 0, nil) end
#class_display_name(klass) (private)
Rendering a class name can execute user code: a singleton class's
to_s renders its attached object via #inspect, which a module can
shadow with an incompatible signature (Liquid::Utils defines
inspect(value, max_depth = 2) as a module_function, so rendering
# raises ArgumentError). A coverage report
must never crash the host suite over that, so on failure rebuild the
singleton wrapper from Module#name via bound methods (which cannot
be shadowed), falling back to the address form, which
ADDRESS_PATTERN then normalizes. See issue #1236.
# File 'lib/simplecov/result_adapter.rb', line 107
def class_display_name(klass) klass.to_s rescue StandardError singleton_wrapper_name(klass) || Object.instance_method(:to_s).bind_call(klass) end
#normalize_method_key(key) (private)
[ GitHub ]# File 'lib/simplecov/result_adapter.rb', line 90
def normalize_method_key(key) normalized_key = key.dup normalized_key[0] = class_display_name(key[0]) .gsub(ADDRESS_PATTERN, ADDRESS_PLACEHOLDER) .sub(SINGLETON_WRAPPER_PATTERN, '\1') normalized_key end
#normalize_method_keys(cover_statistic) (private)
Ruby's method coverage records one entry per DEFINED METHOD, not per
source location: a block handed to define_method /
define_singleton_method from a shared code path yields a separate
[receiver, name, location] entry for every class it's defined on
(a module's included hook defining onto each descendant) AND for
every name it's defined under (a builder looping define_method key
over a container), all pointing at the same source. A file-based
report can only express "was the method at this location ever
executed", so entries are aggregated by location alone, summing
hits — otherwise each receiver or name whose generated copy never
ran shows as a phantom uncovered method on a line whose line
coverage is 100%. Regular defs map one location to one name, so
they are unaffected. The first entry's (normalized) key is kept for
display. See issue #1234.
# File 'lib/simplecov/result_adapter.rb', line 77
def normalize_method_keys(cover_statistic) methods = cover_statistic[:methods] return unless methods aggregated = {} #: Hash[untyped, [untyped, Integer]] methods.each_with_object(aggregated) do |(key, count), memo| location = key[2..] #: Array[untyped] retained_key, existing = memo[location] || [normalize_method_key(key), 0] memo[location] = [retained_key, existing + count] end cover_statistic[:methods] = aggregated.values.to_h end
#singleton_wrapper_name(klass) (private)
[ GitHub ]# File 'lib/simplecov/result_adapter.rb', line 113
def singleton_wrapper_name(klass) return nil unless klass.is_a?(Class) && klass.singleton_class? attached = klass.attached_object # simplecov:disable branch — CRuby only reaches the rescue via a # Module/Class attached object (instance singletons render from the # class name chain without calling user inspect), so the non-Module # arm is defensive. name = Module.instance_method(:name).bind_call(attached) if attached.is_a?(Module) # simplecov:enable branch name && "#<Class:#{name}>" end