lib/App/Test/Generator/Model/Method.pm

Structural Coverage (Approximate)

TER1 (Statement): 100.00%
TER2 (Branch): 96.55%
TER3 (LCSAJ): 100.0% (13/13)
Approximate LCSAJ segments: 59

LCSAJ Legend

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.

Mutant Testing Legend

Survived (tests missed this) Killed (tests detected this) No mutation
    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 → 129100 → 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 → 164149 → 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 → 181168 → 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 → 189181 → 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 → 197189 → 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;