Module: Evilution::Compare::Categorizer
- Defined in:
- lib/evilution/compare/categorizer.rb
Constant Summary collapse
- ALIVE =
%i[survived].freeze
- DEAD =
%i[killed timeout error].freeze
Class Method Summary collapse
- .bucket_single_sided(against_record, current_record, a_kind, c_kind, buckets) ⇒ Object
-
.call(against, current) ⇒ Hash
Bucketed comparison result with keys: - ‘:alive_only_against` => `Array<Record, peer_status: Symbol|nil>` records that survived in against but not in current (or absent in current).
-
.classify(against_record, current_record, buckets) ⇒ Object
Dispatches one fingerprint pair into buckets.
- .count_excluded(against_record, current_record, buckets) ⇒ Object
- .index_by_fingerprint(records) ⇒ Object
-
.kind_of(record) ⇒ Object
Returns :alive, :dead, :excluded, or nil (for nil records).
- .sort_buckets!(buckets) ⇒ Object
- .sort_key(record) ⇒ Object
Class Method Details
.bucket_single_sided(against_record, current_record, a_kind, c_kind, buckets) ⇒ Object
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
Returns bucketed comparison result with keys:
-
‘:alive_only_against` => `Array<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, peer_status: Symbol|nil>` mirror of the above from the current side.
-
‘:shared_alive` => `Array<Record, current: Record>` mutations that survived in both runs.
-
‘:shared_dead` => `Array<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
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
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
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
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
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
102 103 104 |
# File 'lib/evilution/compare/categorizer.rb', line 102 def sort_key(record) [record.file_path, record.line, record.fingerprint] end |