| File: | blib/lib/App/Test/Generator/Mutant.pm |
| Coverage: | 100.0% |
| line | stmt | bran | cond | sub | time | code |
|---|---|---|---|---|---|---|
| 1 | package App::Test::Generator::Mutant; | |||||
| 2 | ||||||
| 3 | 16 16 16 | 128416 14 191 | use strict; | |||
| 4 | 16 16 16 | 20 25 261 | use warnings; | |||
| 5 | 16 16 16 | 25 11 2268 | use Carp qw(croak); | |||
| 6 | ||||||
| 7 | our $VERSION = '0.36'; | |||||
| 8 | ||||||
| 9 - 100 | =head1 VERSION
Version 0.36
=head1 DESCRIPTION
Represents a single mutant - a specific transformation of a source file
that a test suite should be able to detect. Each mutant carries the
metadata needed to identify it, locate it in the source, and apply the
transformation to a fresh L<PPI::Document> copy at test time.
=head2 new
Construct a new mutant object.
my $mutant = App::Test::Generator::Mutant->new(
id => 'NUM_BOUNDARY_10_5_!=',
description => 'Numeric boundary flip == to !=',
original => '==',
line => 10,
type => 'comparison',
group => 'NUM_BOUNDARY:10',
transform => sub {
my ($doc) = @_;
# ... modify $doc in place ...
},
);
=head3 Arguments
=over 4
=item * C<id>
A unique string identifying this mutant. Required.
=item * C<description>
A human-readable description of the mutation. Required.
=item * C<original>
The original source token or expression being mutated. Required.
=item * C<line>
The line number in the source file where the mutation occurs. Required.
=item * C<transform>
A CODE reference that accepts a L<PPI::Document> and applies the
mutation to it in place. Required.
=item * C<type>
An optional string classifying the mutation kind e.g. C<comparison>,
C<boolean>.
=item * C<group>
An optional string grouping related mutants together e.g. all mutations
on the same source line.
=back
=head3 Returns
A blessed hashref representing the mutant. Croaks if any required
attribute is missing or if C<transform> is not a CODE reference.
=head3 API specification
=head4 input
{
id => { type => SCALAR },
description => { type => SCALAR },
original => { type => SCALAR },
line => { type => SCALAR },
transform => { type => CODEREF },
type => { type => SCALAR, optional => 1 },
group => { type => SCALAR, optional => 1 },
}
=head4 output
{
type => OBJECT,
isa => 'App::Test::Generator::Mutant',
}
=cut | |||||
| 101 | ||||||
| 102 | sub new { | |||||
| 103 | 752 | 218710 | my ($class, %args) = @_; | |||
| 104 | ||||||
| 105 | # Validate all required attributes are present | |||||
| 106 | 752 | 604 | for my $required (qw(id description original line transform)) { | |||
| 107 | croak "Missing required attribute: $required" | |||||
| 108 | 3740 | 2778 | unless exists $args{$required}; | |||
| 109 | } | |||||
| 110 | ||||||
| 111 | # Ensure transform is actually executable | |||||
| 112 | croak 'transform must be a CODE reference' | |||||
| 113 | 742 | 746 | unless ref($args{transform}) eq 'CODE'; | |||
| 114 | ||||||
| 115 | 737 | 795 | return bless \%args, $class; | |||
| 116 | } | |||||
| 117 | ||||||
| 118 - 134 | =head2 id
Return the unique identifier for this mutant.
my $id = $mutant->id;
=head3 API specification
=head4 input
{ self => { type => OBJECT, isa => 'App::Test::Generator::Mutant' } }
=head4 output
{ type => SCALAR }
=cut | |||||
| 135 | ||||||
| 136 | 89 | 10533 | sub id { $_[0]->{id} } | |||
| 137 | ||||||
| 138 - 154 | =head2 description
Return the human-readable description of the mutation.
my $desc = $mutant->description;
=head3 API specification
=head4 input
{ self => { type => OBJECT, isa => 'App::Test::Generator::Mutant' } }
=head4 output
{ type => SCALAR }
=cut | |||||
| 155 | ||||||
| 156 | 47 | 7543 | sub description { $_[0]->{description} } | |||
| 157 | ||||||
| 158 - 174 | =head2 original
Return the original source token or expression that is being mutated.
my $orig = $mutant->original;
=head3 API specification
=head4 input
{ self => { type => OBJECT, isa => 'App::Test::Generator::Mutant' } }
=head4 output
{ type => SCALAR }
=cut | |||||
| 175 | ||||||
| 176 | 10 | 327 | sub original { $_[0]->{original} } | |||
| 177 | ||||||
| 178 - 194 | =head2 line
Return the line number in the source file where the mutation occurs.
my $line = $mutant->line;
=head3 API specification
=head4 input
{ self => { type => OBJECT, isa => 'App::Test::Generator::Mutant' } }
=head4 output
{ type => SCALAR }
=cut | |||||
| 195 | ||||||
| 196 | 31 | 909 | sub line { $_[0]->{line} } | |||
| 197 | ||||||
| 198 - 216 | =head2 transform
Return the CODE reference that applies the mutation to a
L<PPI::Document> copy.
my $xform = $mutant->transform;
$xform->($doc_copy);
=head3 API specification
=head4 input
{ self => { type => OBJECT, isa => 'App::Test::Generator::Mutant' } }
=head4 output
{ type => CODEREF }
=cut | |||||
| 217 | ||||||
| 218 | 61 | 49385 | sub transform { $_[0]->{transform} } | |||
| 219 | ||||||
| 220 - 237 | =head2 type
Return the optional mutation type classification string,
e.g. C<comparison> or C<boolean>.
my $type = $mutant->type;
=head3 API specification
=head4 input
{ self => { type => OBJECT, isa => 'App::Test::Generator::Mutant' } }
=head4 output
{ type => SCALAR, optional => 1 }
=cut | |||||
| 238 | ||||||
| 239 | 42 | 1840 | sub type { $_[0]->{type} } | |||
| 240 | ||||||
| 241 - 258 | =head2 group
Return the optional group string that clusters related mutants,
e.g. all mutations targeting the same source line.
my $group = $mutant->group;
=head3 API specification
=head4 input
{ self => { type => OBJECT, isa => 'App::Test::Generator::Mutant' } }
=head4 output
{ type => SCALAR, optional => 1 }
=cut | |||||
| 259 | ||||||
| 260 | 27 | 662 | sub group { $_[0]->{group} } | |||
| 261 | ||||||
| 262 - 277 | =head1 AUTHOR Nigel Horne, C<< <njh at nigelhorne.com> >> Portions of this module's initial design and documentation were created with the assistance of AI. =head1 LICENCE AND COPYRIGHT Copyright 2025-2026 Nigel Horne. Usage is subject to the terms of GPL2. If you use it, please let me know. =cut | |||||
| 278 | ||||||
| 279 | 1; | |||||