TER1 (Statement): 100.00%
TER2 (Branch): 96.55%
TER3 (LCSAJ): 100.0% (13/13)
Approximate LCSAJ segments: 59
● Covered — this LCSAJ path was executed during testing.
● Not covered — this LCSAJ path was never executed. These are the paths to focus on.
Multiple dots on a line indicate that multiple control-flow paths begin at that line. Hovering over any dot shows:
start → end → jump
Uncovered paths show [NOT COVERED] in the tooltip.
1: package App::Test::Generator::Model::Method; 2: 3: use strict; 4: use warnings; 5: 6: use Carp qw(croak); 7: use Readonly; 8: 9: Readonly my $HIGH_CONFIDENCE_THRESHOLD => 40; 10: Readonly my $MEDIUM_CONFIDENCE_THRESHOLD => 20; 11: 12: our $VERSION = '0.36'; 13: 14: =head1 VERSION 15: 16: Version 0.36 17: 18: =cut 19: 20: sub new { 21: my ($class, %args) = @_; 22: croak 'name required' unless defined $args{name}; 23: croak 'source required' unless defined $args{source}; 24: 25: my $self = { 26: name => $args{name}, 27: source => $args{source}, 28: # parameters => [], 29: evidence => [], 30: return_type => undef, 31: classification => undef, 32: confidence => undef, 33: }; 34: 35: return bless $self, $class; 36: } 37: 38: # Read-only accessors â name and source are immutable after construction 39: sub name { $_[0]->{name} } 40: sub source { $_[0]->{source} } 41: 42: sub return_type { 43: my ($self, $val) = @_; 44: $self->{return_type} = $val if @_ > 1;Mutants (Total: 3, Killed: 3, Survived: 0)
45: return $self->{return_type};
Mutants (Total: 2, Killed: 2, Survived: 0)
46: } 47: 48: sub classification { 49: my ($self, $val) = @_; 50: $self->{classification} = $val if @_ > 1;
Mutants (Total: 3, Killed: 3, Survived: 0)
51: return $self->{classification};
Mutants (Total: 2, Killed: 2, Survived: 0)
52: } 53: 54: sub confidence { 55: my ($self, $val) = @_; 56: $self->{confidence} = $val if @_ > 1;
Mutants (Total: 3, Killed: 3, Survived: 0)
57: return $self->{confidence};
Mutants (Total: 2, Killed: 2, Survived: 0)
58: } 59: 60: sub add_evidence { 61: my ($self, %args) = @_; 62: 63: # Validate category â must be one of the three recognised kinds 64: my %valid_categories = map { $_ => 1 } qw(return input effect); 65: 66: my $cat = $args{category} // ''; 67: croak "Invalid evidence category '$cat'" unless $valid_categories{$cat}; 68: 69: # Validate signal â must be a known signal name to catch typos early. 70: # Signals are per-category; we validate the full set across all categories. 71: my %valid_signals = map { $_ => 1 } qw( 72: returns_property returns_constant returns_self 73: legacy_type context_aware error_pattern 74: input_validated input_typed input_optional 75: has_side_effect no_side_effect 76: ); 77: 78: my $sig = $args{signal} // ''; 79: croak "Invalid evidence signal '$sig'" unless $valid_signals{$sig}; 80: 81: push @{ $self->{evidence} }, { 82: category => $args{category}, 83: signal => $args{signal}, 84: value => $args{value}, 85: weight => defined $args{weight} ? $args{weight} : 1, 86: }; 87: } 88: 89: sub evidence { 90: my $self = $_[0]; 91: return @{ $self->{evidence} }; 92: } 93: 94: sub evidence_ref { 95: my $self = $_[0]; 96: return $self->{evidence};
Mutants (Total: 2, Killed: 2, Survived: 0)
97: } 98: 99: sub resolve_return_type { ●100 → 103 → 129●100 → 103 → 0 100: my $self = $_[0]; 101: my %score = (property => 0, constant => 0, object => 0); 102: 103: for my $ev (@{ $self->{evidence} }) { 104: next unless $ev->{category} eq 'return'; 105: if($ev->{signal} eq 'returns_property') {
Mutants (Total: 1, Killed: 1, Survived: 0)
106: $score{property} += $ev->{weight}; 107: } elsif($ev->{signal} eq 'returns_constant') { 108: $score{constant} += $ev->{weight}; 109: } elsif($ev->{signal} eq 'returns_self') { 110: $score{object} += $ev->{weight}; 111: } elsif($ev->{signal} eq 'legacy_type') { 112: # Legacy type hint â map to nearest score bucket if recognisable 113: my $t = $ev->{value} // ''; 114: if($t eq 'object') { $score{object} += $ev->{weight} }
Mutants (Total: 1, Killed: 1, Survived: 0)
115: elsif($t eq 'self') { $score{object} += $ev->{weight} } 116: else { $score{property} += $ev->{weight} } 117: } elsif($ev->{signal} eq 'context_aware') { 118: # Context-aware return suggests getter behaviour 119: $score{property} += $ev->{weight}; 120: } elsif($ev->{signal} eq 'error_pattern') { 121: # Error pattern return doesn't strongly imply a type â 122: # give a small nudge toward property (scalar return) 123: $score{property} += $ev->{weight}; 124: } 125: # Unknown signals are ignored â they may be used by external consumers 126: } 127: 128: # Tie-break alphabetically â deterministic but arbitrary ●129 → 132 → 0 129: my ($winner) = sort { ($score{$b} || 0) <=> ($score{$a} || 0) || $a cmp $b } keys %score; 130: 131: $self->{return_type} = $winner || 'unknown'; 132: return $self->{return_type};
Mutants (Total: 2, Killed: 2, Survived: 0)
133: } 134: 135: sub resolve_confidence { 136: my $self = $_[0]; 137: 138: my $total = 0; 139: $total += $_->{weight} for @{ $self->{evidence} }; 140: 141: my $level = $total >= $HIGH_CONFIDENCE_THRESHOLD ? 'high' : $total >= $MEDIUM_CONFIDENCE_THRESHOLD ? 'medium' : 'low';
Mutants (Total: 3, Killed: 3, Survived: 0)
142: 143: $self->{confidence} = { score => $total, level => $level }; 144: 145: return $self->{confidence};
Mutants (Total: 2, Killed: 2, Survived: 0)
146: } 147: 148: sub resolve_classification { ●149 → 154 → 164●149 → 154 → 0 149: my $self = $_[0]; 150: 151: # Return_type must be resolved before classification can be determined 152: $self->resolve_return_type() unless defined $self->{return_type}; 153: 154: if($self->{return_type} eq 'object') {
Mutants (Total: 1, Killed: 1, Survived: 0)
155: $self->{classification} = 'chainable'; 156: } elsif ($self->{return_type} eq 'property') { 157: $self->{classification} = 'getter'; 158: } elsif ($self->{return_type} eq 'constant') { 159: $self->{classification} = 'constant'; 160: } else { 161: $self->{classification} = 'unknown'; 162: } 163: ●164 → 164 → 0 164: return $self->{classification};
Mutants (Total: 2, Killed: 2, Survived: 0)
165: } 166: 167: sub absorb_legacy_output { ●168 → 172 → 181●168 → 172 → 0 168: my ($self, $output) = @_; 169: 170: return unless $output && ref $output eq 'HASH'; 171: 172: if ($output->{type}) {
Mutants (Total: 1, Killed: 1, Survived: 0)
173: $self->add_evidence( 174: category => 'return', 175: signal => 'legacy_type', 176: value => $output->{type}, 177: weight => 20, 178: ); 179: } 180: ●181 → 181 → 189●181 → 181 → 0 181: if ($output->{_returns_self}) {
Mutants (Total: 1, Killed: 1, Survived: 0)
182: $self->add_evidence( 183: category => 'return', 184: signal => 'returns_self', 185: weight => 25, 186: ); 187: } 188: ●189 → 189 → 197●189 → 189 → 0 189: if ($output->{_context_aware}) {
Mutants (Total: 1, Killed: 1, Survived: 0)
190: $self->add_evidence( 191: category => 'return', 192: signal => 'context_aware', 193: weight => 15, 194: ); 195: } 196: ●197 → 197 → 0 197: if ($output->{_error_return}) {
Mutants (Total: 1, Killed: 1, Survived: 0)
198: $self->add_evidence( 199: category => 'return', 200: signal => 'error_pattern', 201: value => $output->{_error_return}, 202: weight => 15, 203: ); 204: } 205: } 206: 207: 1;