Module: Evilution::Compare::Categorizer Private

Defined in:
lib/evilution/compare/categorizer.rb

This module is part of a private API. You should avoid using this module if possible, as it may be removed or be changed in the future.

Constant Summary collapse

ALIVE =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%i[survived].freeze
DEAD =

This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.

%i[killed timeout error].freeze

Class Method Summary collapse

Class Method Details

.bucket_single_sided(against_record, current_record, a_kind, c_kind, buckets) ⇒ 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.



77
78
79
80
81
82
83
84
# File 'lib/evilution/compare/categorizer.rb', line 77

def bucket_single_sided(against_record, current_record, a_kind, c_kind, buckets)
  # peer_status is the peer record's status symbol, or nil if peer absent.
  # When the peer is excluded, its status symbol (e.g. :neutral) flows through.
  a_peer = current_record && current_record.status
  c_peer = against_record && against_record.status
  buckets[:alive_only_against] << { record: against_record, peer_status: a_peer } if a_kind == :alive
  buckets[:alive_only_current] << { record: current_record, peer_status: c_peer } if c_kind == :alive
end

.call(against, current) ⇒ Hash

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.

Returns bucketed comparison result with keys:

  • :alive_only_against => Array<{record: Record, peer_status: Symbol|nil}> records that survived in against but not in current (or absent in current). peer_status is the current-side record's status symbol, or nil when no current-side record exists for that fingerprint.
  • :alive_only_current => Array<{record: Record, peer_status: Symbol|nil}> mirror of the above from the current side.
  • :shared_alive => Array<{against: Record, current: Record}> mutations that survived in both runs.
  • :shared_dead => Array<{against: Record, current: Record}> mutations killed/timed-out/errored in both runs.
  • :excluded_against => Integer count of against records with non-actionable statuses (neutral, equivalent, unresolved, unparseable).
  • :excluded_current => Integer mirror for the current side.

Parameters:

  • against (Array<Record>)

    prior run (baseline)

  • current (Array<Record>)

    current run

Returns:

  • (Hash)

    bucketed comparison result with keys:

    • :alive_only_against => Array<{record: Record, peer_status: Symbol|nil}> records that survived in against but not in current (or absent in current). peer_status is the current-side record's status symbol, or nil when no current-side record exists for that fingerprint.
    • :alive_only_current => Array<{record: Record, peer_status: Symbol|nil}> mirror of the above from the current side.
    • :shared_alive => Array<{against: Record, current: Record}> mutations that survived in both runs.
    • :shared_dead => Array<{against: Record, current: Record}> mutations killed/timed-out/errored in both runs.
    • :excluded_against => Integer count of against records with non-actionable statuses (neutral, equivalent, unresolved, unparseable).
    • :excluded_current => Integer mirror for the current side.


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/evilution/compare/categorizer.rb', line 31

def call(against, current)
  # Duplicate fingerprints within one side should not happen (Normalizer
  # invariant). If they do, last write wins — we do not dedupe proactively.
  against_by_fp = index_by_fingerprint(against)
  current_by_fp = index_by_fingerprint(current)

  buckets = {
    alive_only_against: [],
    alive_only_current: [],
    shared_alive: [],
    shared_dead: [],
    excluded_against: 0,
    excluded_current: 0
  }

  (against_by_fp.keys | current_by_fp.keys).each do |fp|
    classify(against_by_fp[fp], current_by_fp[fp], buckets)
  end

  sort_buckets!(buckets)
  buckets
end

.classify(against_record, current_record, buckets) ⇒ 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.

Dispatches one fingerprint pair into buckets. Either record may be nil (fingerprint present on only one side).



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/evilution/compare/categorizer.rb', line 56

def classify(against_record, current_record, buckets)
  count_excluded(against_record, current_record, buckets)
  a_kind = kind_of(against_record)
  c_kind = kind_of(current_record)

  if a_kind == :alive && c_kind == :alive
    buckets[:shared_alive] << { against: against_record, current: current_record }
  elsif a_kind == :dead && c_kind == :dead
    buckets[:shared_dead] << { against: against_record, current: current_record }
  else
    bucket_single_sided(against_record, current_record, a_kind, c_kind, buckets)
  end
  # A dead-only fingerprint (dead on one side, absent on the other) is
  # intentionally not bucketed and not counted as excluded.
end

.count_excluded(against_record, current_record, buckets) ⇒ 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.



72
73
74
75
# File 'lib/evilution/compare/categorizer.rb', line 72

def count_excluded(against_record, current_record, buckets)
  buckets[:excluded_against] += 1 if against_record && kind_of(against_record) == :excluded
  buckets[:excluded_current] += 1 if current_record && kind_of(current_record) == :excluded
end

.index_by_fingerprint(records) ⇒ 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.



106
107
108
# File 'lib/evilution/compare/categorizer.rb', line 106

def index_by_fingerprint(records)
  records.to_h { |r| [r.fingerprint, r] }
end

.kind_of(record) ⇒ 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.

Returns :alive, :dead, :excluded, or nil (for nil records).



87
88
89
90
91
92
93
# File 'lib/evilution/compare/categorizer.rb', line 87

def kind_of(record)
  return nil if record.nil?
  return :alive if ALIVE.include?(record.status)
  return :dead  if DEAD.include?(record.status)

  :excluded
end

.sort_buckets!(buckets) ⇒ 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.



95
96
97
98
99
100
# File 'lib/evilution/compare/categorizer.rb', line 95

def sort_buckets!(buckets)
  buckets[:alive_only_against].sort_by! { |e| sort_key(e[:record]) }
  buckets[:alive_only_current].sort_by! { |e| sort_key(e[:record]) }
  buckets[:shared_alive].sort_by!       { |e| sort_key(e[:against]) }
  buckets[:shared_dead].sort_by!        { |e| sort_key(e[:against]) }
end

.sort_key(record) ⇒ 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.



102
103
104
# File 'lib/evilution/compare/categorizer.rb', line 102

def sort_key(record)
  [record.file_path, record.line, record.fingerprint]
end