App-Test-Generator

 view release on metacpan or  search on metacpan

t/extended_tests.t  view on Meta::CPAN

#!/usr/bin/env perl

use strict;
use warnings;
use Test::Most;
use Capture::Tiny qw(capture);
use File::Path    qw(make_path);
use File::Spec;
use File::Temp    qw(tempdir tempfile);
use Scalar::Util  qw(looks_like_number);

# Extended tests targeting:
# 1. Known surviving mutants in _dedup_mutants, _is_redundant_mutation,
#    BooleanNegation, ReturnUndef, Emitter::Perl, Planner
# 2. Branch coverage gaps in Generator render helpers
# 3. LCSAJ/TER3 improvement via additional branch-path coverage
# 4. Stateful behaviour across multiple calls

BEGIN {
	use_ok('App::Test::Generator');
	use_ok('App::Test::Generator::Mutant');
	use_ok('App::Test::Generator::Mutator');
	use_ok('App::Test::Generator::Mutation::BooleanNegation');
	use_ok('App::Test::Generator::Mutation::ReturnUndef');
	use_ok('App::Test::Generator::Planner');
	use_ok('App::Test::Generator::Emitter::Perl');
	use_ok('App::Test::Generator::LCSAJ');
}

# ==================================================================
# Mutator._dedup_mutants — surviving mutant killers
#
# The boolean returns in _dedup_mutants use $seen{$key}++ (truthy
# after first occurrence) and !$seen{$key}++ (falsy on first, truthy
# after). Mutations flip these. The tests below assert exact counts
# to kill negation and undef-return survivors on lines 435-450.
# ==================================================================

{
	no warnings 'once';
	*_dedup_mutants        = \&App::Test::Generator::Mutator::_dedup_mutants;
	*_is_redundant_mutation = \&App::Test::Generator::Mutator::_is_redundant_mutation;
}

my $next_id = 0;
sub _make_mutant {
	my %args = @_;
	return App::Test::Generator::Mutant->new(
		id          => $args{id}          // 'TEST_' . ++$next_id,
		line        => $args{line}        // 1,
		original    => $args{original}    // 'x',
		description => $args{description} // 'test',
		context     => $args{context}     // '',
		line_content => $args{line_content} // '',
		transform   => $args{transform}   // sub {},
	);
}

subtest '_dedup_mutants: single unique mutant returns exactly 1' => sub {
	my $mutants = [
		_make_mutant(line => 5, original => '>', description => 'flip')
	];
	my $result = _dedup_mutants($mutants);
	is(scalar @{$result}, 1, 'single unique mutant: count is exactly 1');
};

subtest '_dedup_mutants: two identical mutants return exactly 1' => sub {
	my $mutants = [
		_make_mutant(line => 5, original => '>', description => 'flip'),
		_make_mutant(line => 5, original => '>', description => 'flip'),

t/extended_tests.t  view on Meta::CPAN

	}
};

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');
};



( run in 0.700 second using v1.01-cache-2.11-cpan-6aa56a78535 )