App-Test-Generator
view release on metacpan or search on metacpan
wraps a condition's content in PPI::Statement::Expression, so an
operator's immediate parent is never literally
PPI::Structure::Condition. Operators inside if/unless/while/until
conditions were always tagged 'expression'. Now uses the same
ancestor-walking _in_conditional() helper already shared by
BooleanNegation and ReturnUndef.
- Fix Analyzer::SideEffect.pm's mutates_self and mutates_globals
detection ($self->{field} = ... assignment and %ENV/%SIG/@ARGV
mutation) matching against the raw method body instead of the
comment/string-stripped $code_only, so a field-assignment-like
fragment inside a string literal or comment could be mistaken for
an actual mutation. Both checks now match against $code_only, same
as the keyword/operator counts already fixed in a prior release.
- Fix Emitter::Perl.pm's _emit_method_tests() having no dispatch
branch for the boundary_tests plan flag: TestStrategy.pm sets
$plan{boundary_tests} when a method's schema carries non-empty
_yamltest_hints, but the corresponding $TEST_BOUNDARY constant was
defined and never read, so methods planned for boundary testing
silently got zero generated test code for it. Added the dispatch
line plus a new _emit_boundary_test() that emits one smoke-test
block per boundary_values/invalid_inputs hint value.
{ href => { type => 'any', optional => 1 } }
#### output
{ type => 'string' }
## render\_arrayref\_map
Render a hashref whose values are arrayrefs into a Perl source-code
fragment suitable for use as a hash literal in a generated test file.
my $code = render_arrayref_map({ name => ['', 'a' x 100] });
### Arguments
- `$href`
A hashref whose values are arrayrefs. Keys whose values are not
arrayrefs are silently skipped.
#### input
{ href => { type => 'any', optional => 1 } }
#### output
{ type => 'string' }
## perl\_quote
Convert any Perl value into a source-code fragment that reproduces that value
when evaluated in a generated test file.
### Arguments
- `$v`
Any Perl value. May be undef, a scalar, an arrayref, a Regexp, or a blessed
object. All types are handled â undef becomes `'undef'`, the strings
`'true'`/`'false'` become the Perl boolean constants `!!1`/`!!0`,
numbers are unquoted, other strings are single-quoted, arrayrefs recurse,
bin/test-generator-mutate view on Meta::CPAN
=head2 --changed_only
Only mutate files that were changed in the most recent commit, as
determined by C<git diff --name-only HEAD~1 HEAD>. Files not changed
in the current commit retain their mutation results from the previous
dashboard run. This significantly reduces CI runtime while preserving
accuracy for the files that actually changed.
=head2 --exclude <path>
Exclude files matching the given path fragment from mutation testing.
May be specified multiple times. For example:
--exclude lib/Devel --exclude lib/App/Test/Generator/Sample
=head2 --base_sha <sha>
The git commit SHA to use as the base when computing which files have
changed under C<--changed_only>. Defaults to C<HEAD~1>.
Use this when automated commits (such as coverage snapshots or generated
lib/App/Test/Generator.pm view on Meta::CPAN
croak("Boolean is expected to return $expected_str");
}
} else {
$corpus_code .= "is(\$obj->$function($input_str), $expected_str, " . q_wrap($desc) . ");\n";
}
}
}
}
}
# Prepare seed/iterations code fragment for the generated test
my $seed_code = '';
if (defined $seed) {
# ensure integer-ish
$seed = int($seed);
$seed_code = "srand($seed);\n";
}
my $determinism_code = 'my $result2;' .
'eval { $result2 = do { ' . (defined($position_code) ? $position_code : $call_code) . " }; };\n" .
'is_deeply($result2, $result, "deterministic result for same input");' .
lib/App/Test/Generator.pm view on Meta::CPAN
my @pairs = map {
perl_quote($_) . ' => ' . perl_quote($href->{$_})
} sort keys %{$href};
return join(', ', @pairs);
}
=head2 render_arrayref_map
Render a hashref whose values are arrayrefs into a Perl source-code
fragment suitable for use as a hash literal in a generated test file.
my $code = render_arrayref_map({ name => ['', 'a' x 100] });
=head3 Arguments
=over 4
=item * C<$href>
A hashref whose values are arrayrefs. Keys whose values are not
lib/App/Test/Generator.pm view on Meta::CPAN
# --------------------------------------------------
# q_wrap
#
# Purpose: Wrap a string in the most readable
# q{} form that does not require escaping,
# falling back to single-quoted form with
# escaped apostrophes if no delimiter is
# available.
#
# Entry: $s - the string to wrap. May be undef.
# Exit: Returns a Perl source-code fragment that
# evaluates to the original string value,
# or the string 'undef' if $s is undef.
#
# Notes: index() returns -1 when not found and
# any value >= 0 when found, including 0
# for a delimiter at the start of the
# string. We compare against $INDEX_NOT_FOUND
# to make this boundary explicit and to
# prevent off-by-one mutation survivors.
# See GitHub issue #1.
lib/App/Test/Generator.pm view on Meta::CPAN
# Replace NUL bytes with \0 â valid only in
# double-quoted string context in generated code
$s =~ s/\0/\\0/g;
return $s;
}
=head2 perl_quote
Convert any Perl value into a source-code fragment that reproduces that value
when evaluated in a generated test file.
=head3 Arguments
=over 4
=item * C<$v>
Any Perl value. May be undef, a scalar, an arrayref, a Regexp, or a blessed
object. All types are handled â undef becomes C<'undef'>, the strings
lib/App/Test/Generator/Analyzer/SideEffect.pm view on Meta::CPAN
mutates_globals => 0,
performs_io => 0,
calls_external => 0,
mutation_fields => [],
);
# --------------------------------------------------
# Detect assignment to $self->{field} â any such
# assignment means the method mutates its own state.
# Matched against $code_only so a field-assignment-like
# fragment appearing inside a string literal or comment
# is not mistaken for an actual mutation.
# --------------------------------------------------
my %seen_fields;
while($code_only =~ /\$self->\{(\w+)\}\s*=/g) {
$result{mutates_self} = 1;
# Deduplicate field names in case the same field
# is assigned more than once in the method body
push @{ $result{mutation_fields} }, $1
unless $seen_fields{$1}++;
lib/App/Test/Generator/SchemaExtractor.pm view on Meta::CPAN
}
}
return \%params;
}
# --------------------------------------------------
# _map_formal_input_type
#
# Purpose: Extract and normalise the type string
# from a parameter spec fragment such as
# "type => 'scalar | scalarref'".
# Handles union types by returning the
# canonical ATG type for the first
# recognised alternative.
#
# Entry: $spec - text content of a { } block
# from a =head3|4 Input spec.
#
# Exit: Canonical type string, or undef when
# no 'type' key is present or the value
t/Generator.t view on Meta::CPAN
subtest 'perl_sq() replaces NUL bytes' => sub {
is(App::Test::Generator::perl_sq("a\0b"), 'a\\0b', 'NUL replaced with \\0');
};
subtest 'perl_sq() leaves plain string unchanged' => sub {
is(App::Test::Generator::perl_sq('hello'), 'hello', 'plain string unchanged');
};
# ------------------------------------------------------------------
# perl_quote â convert a Perl value to source-code fragment
# ------------------------------------------------------------------
subtest 'perl_quote() returns undef literal for undef' => sub {
is(App::Test::Generator::perl_quote(undef), 'undef', 'undef -> "undef"');
};
subtest 'perl_quote() converts true/false string booleans' => sub {
is(App::Test::Generator::perl_quote('true'), '!!1', '"true" -> "!!1"');
is(App::Test::Generator::perl_quote('false'), '!!0', '"false" -> "!!0"');
};
t/Mutant_unit.t view on Meta::CPAN
subtest 'description() returns the stored description' => sub {
my $m = _mutant(description => 'Flip > to <');
is($m->description, 'Flip > to <', 'description returned correctly');
};
# ==================================================================
# original()
#
# POD spec:
# Returns the original source fragment string.
# ==================================================================
subtest 'original() returns the stored original value' => sub {
my $m = _mutant(original => '>=');
is($m->original, '>=', 'original returned correctly');
};
subtest 'original() can hold arbitrary Perl code snippets' => sub {
my $m = _mutant(original => 'return $self->{name}');
is($m->original, 'return $self->{name}',
t/function.t view on Meta::CPAN
my $e = bless {}, 'App::Test::Generator::SchemaExtractor';
is($e->_extract_function_name("sub foo { return 1; }"), 'foo', 'a simple one-line sub declaration is matched');
is($e->_extract_function_name("\n\tsub bar_baz2 {\n\t\treturn;\n\t}"), 'bar_baz2',
'leading whitespace and extra spaces before the name are tolerated');
is($e->_extract_function_name("my \$x = 1; sub foo {}"), undef,
'returns undef when "sub NAME" does not occur at the very start of the string');
is($e->_extract_function_name(''), undef, 'returns undef for an empty string');
};
subtest 'SchemaExtractor::_map_formal_input_type - maps formal spec type fragments to canonical ATG types' => sub {
my $e = bless {}, 'App::Test::Generator::SchemaExtractor';
is($e->_map_formal_input_type(q{type=>'scalar'}), 'string', 'scalar maps to string');
is($e->_map_formal_input_type(q{type => "Integer"}), 'integer', 'case is folded before lookup');
is($e->_map_formal_input_type(q{type => 'scalar | scalarref'}), 'string',
'a union type resolves to the first recognised alternative');
is($e->_map_formal_input_type(q{type => 'bogus'}), undef, 'an unrecognised type name returns undef');
is($e->_map_formal_input_type(q{name => 'x'}), undef, 'a spec fragment with no type key returns undef');
};
subtest 'SchemaExtractor::_analyze_output - dispatch isolation, conditional _validate_output, and empty fallback' => sub {
my $e = bless {}, 'App::Test::Generator::SchemaExtractor';
my @detectors = qw(_analyze_output_from_pod _analyze_output_from_code _enhance_boolean_detection _detect_list_context _detect_void_context _detect_chaining_pattern _detect_error_conventions);
my @calls;
for my $sub (@detectors, '_validate_output') {
Test::Mockingbird::mock('App::Test::Generator::SchemaExtractor', $sub, sub { push @calls, $sub; return; });
}
( run in 4.242 seconds using v1.01-cache-2.11-cpan-b16cb0d3907 )