App-Test-Generator
view release on metacpan or search on metacpan
t/extended_tests.t view on Meta::CPAN
my ($out) = capture(sub {
eval { App::Test::Generator->generate($path) };
});
is($@, '', 'transforms schema does not croak');
};
subtest 'Generator: _valid_type returns 1 for all supported types' => sub {
for my $type (qw(string boolean integer number float hashref arrayref object int bool)) {
is(App::Test::Generator::_valid_type($type), 1,
"'$type' is a valid type");
}
};
subtest 'Generator: _valid_type returns 0 for unknown types' => sub {
for my $type (qw(unknown blob xml json list map set)) {
is(App::Test::Generator::_valid_type($type), 0,
"'$type' is not a valid type");
}
};
subtest 'Generator: _valid_type returns 0 for undef' => sub {
is(App::Test::Generator::_valid_type(undef), 0, 'undef -> 0');
};
subtest 'Generator: _has_positions returns 0 for empty hashref' => sub {
is(App::Test::Generator::_has_positions({}), 0, 'empty -> 0');
};
subtest 'Generator: _has_positions returns 0 for undef' => sub {
is(App::Test::Generator::_has_positions(undef), 0, 'undef -> 0');
};
subtest 'Generator: _has_positions returns 1 when position present' => sub {
is(App::Test::Generator::_has_positions({
x => { type => 'string', position => 0 }
}), 1, 'position present -> 1');
};
subtest 'Generator: _has_positions returns 0 when no position' => sub {
is(App::Test::Generator::_has_positions({
x => { type => 'string' }
}), 0, 'no position -> 0');
};
subtest 'Generator: _has_positions returns 0 for scalar value spec' => sub {
is(App::Test::Generator::_has_positions({
x => 'string'
}), 0, 'scalar spec -> 0');
};
# ==================================================================
# CoverageGuidedFuzzer â branch coverage
# ==================================================================
subtest 'CoverageGuidedFuzzer: mutate handles all scalar types' => sub {
use_ok('App::Test::Generator::CoverageGuidedFuzzer');
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'string' } },
target_sub => sub { 1 },
iterations => 0,
seed => 42,
);
# Exercise _mutate with each scalar type
for my $val (42, 3.14, 'hello', '', undef) {
lives_ok(
sub {
# Access _mutate directly
App::Test::Generator::CoverageGuidedFuzzer::_mutate($f, $val)
},
defined($val) ? "mutate('$val') lives" : 'mutate(undef) lives',
);
}
};
subtest 'CoverageGuidedFuzzer: mutate handles arrayref' => sub {
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'arrayref' } },
target_sub => sub { 1 },
iterations => 0,
seed => 42,
);
my $result;
lives_ok(
sub {
$result = App::Test::Generator::CoverageGuidedFuzzer::_mutate($f, [1, 2, 3])
},
'mutate([1,2,3]) lives',
);
is(ref($result), 'ARRAY', 'mutated arrayref is still an arrayref');
};
subtest 'CoverageGuidedFuzzer: mutate handles hashref' => sub {
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'hashref' } },
target_sub => sub { 1 },
iterations => 0,
seed => 42,
);
my $result;
lives_ok(
sub {
$result = App::Test::Generator::CoverageGuidedFuzzer::_mutate($f, { a => 1, b => 2 })
},
'mutate({a=>1}) lives',
);
is(ref($result), 'HASH', 'mutated hashref is still a hashref');
};
subtest 'CoverageGuidedFuzzer: mutate passes blessed ref through unchanged' => sub {
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'string' } },
target_sub => sub { 1 },
iterations => 0,
seed => 42,
);
my $obj = bless {}, 'FakeClass';
my $result = App::Test::Generator::CoverageGuidedFuzzer::_mutate($f, $obj);
is($result, $obj, 'blessed ref passed through unchanged');
};
subtest 'CoverageGuidedFuzzer: _rand_int returns a numeric value' => sub {
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'integer', min => 5, max => 10 } },
target_sub => sub { 1 },
iterations => 0,
seed => 42,
);
for (1..20) {
my $val = App::Test::Generator::CoverageGuidedFuzzer::_rand_int(
$f, { min => 5, max => 10 }
);
ok(looks_like_number($val), "_rand_int returns numeric value (got $val)");
}
};
subtest 'CoverageGuidedFuzzer: _rand_num returns value within bounds' => sub {
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'number' } },
target_sub => sub { 1 },
iterations => 0,
seed => 42,
);
for (1..10) {
my $val = App::Test::Generator::CoverageGuidedFuzzer::_rand_num(
$f, { min => 0, max => 1 }
);
ok($val >= 0 && $val <= 1,
"_rand_num($val) within [0, 1]");
}
};
subtest 'CoverageGuidedFuzzer: _validate_value correctly validates types' => sub {
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'string' } },
target_sub => sub { 1 },
iterations => 0,
seed => 42,
);
# integer
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, 42, { type => 'integer' }), 1, 'integer 42: valid');
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, 3.14, { type => 'integer' }), 0, 'float 3.14: invalid integer');
# number
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, '3.14', { type => 'number' }), 1, 'string 3.14: valid number');
# boolean
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, '1', { type => 'boolean' }), 1, '"1": valid boolean');
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, '2', { type => 'boolean' }), 0, '"2": invalid boolean');
# string with min/max
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, 'hi', { type => 'string', min => 1, max => 10 }), 1, 'string in range: valid');
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, '', { type => 'string', min => 1 }), 0, 'empty string below min: invalid');
# arrayref
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, [1,2], { type => 'arrayref' }), 1, 'arrayref: valid');
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, 'str', { type => 'arrayref' }), 0, 'string: invalid arrayref');
# hashref
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, {a=>1}, { type => 'hashref' }), 1, 'hashref: valid');
};
subtest 'CoverageGuidedFuzzer: _validate_value returns 0 for undef' => sub {
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'string' } },
target_sub => sub { 1 },
iterations => 0,
seed => 42,
);
is(App::Test::Generator::CoverageGuidedFuzzer::_validate_value(
$f, undef, { type => 'string' }), 0, 'undef: always invalid');
};
# ==================================================================
# Stateful tests â verify state accumulates correctly across calls
# ==================================================================
subtest 'CoverageGuidedFuzzer: corpus accumulates across multiple run() calls' => sub {
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'string' } },
target_sub => sub { length($_[0] // '') },
iterations => 5,
seed => 42,
);
$f->run();
my $size1 = scalar @{$f->corpus()};
$f->run();
my $size2 = scalar @{$f->corpus()};
ok($size2 >= $size1, 'corpus grows or stays same across runs');
};
subtest 'CoverageGuidedFuzzer: stats accumulate across run() calls' => sub {
my $f = App::Test::Generator::CoverageGuidedFuzzer->new(
schema => { input => { type => 'string' } },
target_sub => sub { 1 },
iterations => 5,
seed => 42,
);
my $r1 = $f->run();
my $r2 = $f->run();
ok($r2->{total_iterations} >= $r1->{total_iterations},
'total_iterations increases across runs');
};
subtest 'Mutator: generate_mutants is idempotent â same results on two calls' => sub {
my $tmpdir = tempdir(CLEANUP => 1);
my $lib = File::Spec->catdir($tmpdir, 'lib');
mkdir $lib or die $!;
my $pm = File::Spec->catfile($lib, 'Idempotent.pm');
open my $fh, '>', $pm or die $!;
print $fh "package Idempotent;\nsub foo { if(\$x > 0) { return 1; } return 0; }\n1;\n";
close $fh;
my $mutator = App::Test::Generator::Mutator->new(
file => $pm,
lib_dir => $lib,
);
my @m1 = $mutator->generate_mutants();
my @m2 = $mutator->generate_mutants();
is(scalar @m1, scalar @m2, 'generate_mutants count is idempotent');
};
subtest 'Planner: plan_all is idempotent â same results on two calls' => sub {
my $p = App::Test::Generator::Planner->new(
schemas => {
foo => { accessor => { type => 'get' }, output => {} },
bar => { output => { type => 'boolean' } },
},
package => 'Foo',
);
my $plan1 = $p->plan_all();
my $plan2 = $p->plan_all();
is_deeply($plan1, $plan2, 'plan_all is idempotent');
};
done_testing();
( run in 1.530 second using v1.01-cache-2.11-cpan-71847e10f99 )