| 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 | 20 20 20 | 199623 18 241 | use strict; | |||
| 4 | 20 20 20 | 32 11 346 | use warnings; | |||
| 5 | 20 20 20 | 31 15 3027 | use Carp qw(croak); | |||
| 6 | ||||||
| 7 | our $VERSION = '0.41'; | |||||
| 8 | ||||||
| 9 - 117 | =head1 VERSION
Version 0.41
=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.
=item * C<context>
An optional string classifying the syntactic context the mutation was
taken from, e.g. C<conditional> when the mutated code sits inside an
C<if>/C<unless>/C<while>/C<until> condition, or C<statement>/C<expression>
otherwise. Used by L<App::Test::Generator::Mutator>'s fast-mode dedup to
recognise mutations that Perl's own boolean coercion already makes
redundant.
=item * C<line_content>
An optional string holding the raw source text of the line the mutation
targets. Used by L<App::Test::Generator::Mutator>'s fast-mode dedup to
skip mutations on comment-only lines.
=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 => 'string' },
original => { type => SCALAR },
line => { type => SCALAR },
transform => { type => CODEREF },
type => { type => SCALAR, optional => 1 },
group => { type => SCALAR, optional => 1 },
context => { type => SCALAR, optional => 1 },
line_content => { type => SCALAR, optional => 1 },
}
=head4 output
{
type => 'object',
isa => 'App::Test::Generator::Mutant',
}
=cut | |||||
| 118 | ||||||
| 119 | sub new { | |||||
| 120 | 1000 | 379405 | my ($class, %args) = @_; | |||
| 121 | ||||||
| 122 | # Validate all required attributes are present | |||||
| 123 | 1000 | 929 | for my $required (qw(id description original line transform)) { | |||
| 124 | croak "Missing required attribute: $required" | |||||
| 125 | 4980 | 3926 | unless exists $args{$required}; | |||
| 126 | } | |||||
| 127 | ||||||
| 128 | # Ensure transform is actually executable | |||||
| 129 | 990 | 1038 | croak 'transform must be a CODE reference' unless ref($args{transform}) eq 'CODE'; | |||
| 130 | ||||||
| 131 | 985 | 1368 | return bless \%args, $class; | |||
| 132 | } | |||||
| 133 | ||||||
| 134 - 150 | =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 | |||||
| 151 | ||||||
| 152 | sub id { | |||||
| 153 | 113 | 9760 | return $_[0]->{id}; | |||
| 154 | } | |||||
| 155 | ||||||
| 156 - 172 | =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 | |||||
| 173 | ||||||
| 174 | 181 | 8167 | sub description { $_[0]->{description} } | |||
| 175 | ||||||
| 176 - 192 | =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 | |||||
| 193 | ||||||
| 194 | 304 | 943 | sub original { $_[0]->{original} } | |||
| 195 | ||||||
| 196 - 212 | =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 | |||||
| 213 | ||||||
| 214 | 808 | 3426 | sub line { $_[0]->{line} } | |||
| 215 | ||||||
| 216 - 234 | =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 | |||||
| 235 | ||||||
| 236 | 72 | 59525 | sub transform { $_[0]->{transform} } | |||
| 237 | ||||||
| 238 - 255 | =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 | |||||
| 256 | ||||||
| 257 | 42 | 1730 | sub type { $_[0]->{type} } | |||
| 258 | ||||||
| 259 - 276 | =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 | |||||
| 277 | ||||||
| 278 | 27 | 759 | sub group { $_[0]->{group} } | |||
| 279 | ||||||
| 280 - 297 | =head2 context
Return the optional syntactic-context classification string,
e.g. C<conditional> or C<statement>.
my $context = $mutant->context;
=head3 API specification
=head4 input
{ self => { type => OBJECT, isa => 'App::Test::Generator::Mutant' } }
=head4 output
{ type => SCALAR, optional => 1 }
=cut | |||||
| 298 | ||||||
| 299 | 260 | 5840 | sub context { $_[0]->{context} } | |||
| 300 | ||||||
| 301 - 317 | =head2 line_content
Return the optional raw source text of the line this mutant targets.
my $text = $mutant->line_content;
=head3 API specification
=head4 input
{ self => { type => 'object', isa => 'App::Test::Generator::Mutant' } }
=head4 output
{ type => 'string', optional => 1 }
=cut | |||||
| 318 | ||||||
| 319 | 271 | 8582 | sub line_content { $_[0]->{line_content} } | |||
| 320 | ||||||
| 321 - 336 | =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 | |||||
| 337 | ||||||
| 338 | 1; | |||||