App-Test-Generator

 view release on metacpan or  search on metacpan

t/Mutation-BooleanNegation.t  view on Meta::CPAN

# ==================================================================
# mutate -- ID uniqueness across same-line returns (col differentiates)
# ==================================================================
subtest 'mutate: IDs are unique across same file' => sub {
	my $m   = _mutation();
	my $doc = _doc(<<'CODE');
sub a { return $x; }
sub b { return $y; }
sub c { return $z; }
CODE

	my @mutants = $m->mutate($doc);
	is(scalar @mutants, 3, 'three mutants for three subs');

	my %ids = map { $_->id => 1 } @mutants;
	is(scalar keys %ids, 3, 'all IDs distinct across different lines');

	done_testing();
};

# ==================================================================
# mutate -- transform applies negation correctly
# ==================================================================
subtest 'mutate: transform negates return expression' => sub {
	my $m   = _mutation();
	my $src = 'sub foo { return $ok; }';
	my $doc = _doc($src);

	my @mutants = $m->mutate($doc);
	is(scalar @mutants, 1, 'one mutant produced');

	# Apply the transform to a fresh copy of the document
	my $copy = _doc($src);
	$mutants[0]->transform->($copy);

	# The transformed document must contain the negation
	my $transformed = $copy->serialize;
	like($transformed, qr/!\(/, 'transform inserts negation operator');
	like($transformed, qr/!\(\s*\$ok\s*\)/, 'negation wraps original expression');

	done_testing();
};

# ==================================================================
# mutate -- transform targets correct line when multiple returns exist
# ==================================================================
subtest 'mutate: transform targets correct return statement' => sub {
	my $m   = _mutation();
	my $src = <<'CODE';
sub check {
	my ($x) = @_;
	return 0 unless $x;
	return $x > 0;
}
CODE

	my $doc = _doc($src);
	my @mutants = $m->mutate($doc);
	is(scalar @mutants, 2, 'two mutants for two return-with-expression statements');

	# Apply each transform to a fresh copy and verify only one return is negated
	for my $mut (@mutants) {
		my $copy        = _doc($src);
		$mut->transform->($copy);
		my $transformed = $copy->serialize;

		# Exactly one negation must appear in the transformed source
		my @negs = ($transformed =~ /!\(/g);
		is(scalar @negs, 1,
			"transform for mutant ${\$mut->id} negates exactly one return");
	}

	done_testing();
};

# ==================================================================
# mutate -- transform does not modify the original document
# ==================================================================
subtest 'mutate: transform does not modify original document' => sub {
	my $m   = _mutation();
	my $src = 'sub foo { return $ok; }';
	my $doc = _doc($src);

	my @mutants = $m->mutate($doc);

	# Capture original serialisation before transform
	my $before = $doc->serialize;

	# Apply transform to a different copy
	my $copy = _doc($src);
	$mutants[0]->transform->($copy);

	# Original document must be unchanged
	is($doc->serialize, $before, 'original document not modified by transform');

	done_testing();
};

# ==================================================================
# mutate -- return value is a list (current API)
# ==================================================================
subtest 'mutate: returns a list' => sub {
	my $m   = _mutation();
	my $doc = _doc('sub foo { return $x; return $y; }');

	# Current API returns a flat list -- verify it can be assigned to an array
	my @mutants = $m->mutate($doc);
	is(scalar @mutants, 2, 'mutate returns flat list assignable to array');

	# TODO: API should return arrayref for efficiency -- see note at end of file

	done_testing();
};

# ==================================================================
# mutate -- group field is set correctly
# ==================================================================
subtest 'mutate: group field contains line number' => sub {
	my $m   = _mutation();
	my $doc = _doc(<<'CODE');
sub foo {
	return $a;
	return $b;
}
CODE

	my @mutants = $m->mutate($doc);
	is(scalar @mutants, 2, 'two mutants produced');

	for my $mut (@mutants) {
		like($mut->group, qr/^BOOL_NEGATE:\d+$/,
			'group has correct format for mutant ' . $mut->id);

		# Group must contain the same line number as the ID
		my ($id_line)    = $mut->id    =~ /BOOL_NEGATE_(\d+)/;
		my ($group_line) = $mut->group =~ /BOOL_NEGATE:(\d+)/;
		is($id_line, $group_line,
			'group line matches ID line for mutant ' . $mut->id);
	}

	done_testing();
};

# ==================================================================
# mutate -- various expression types are handled
# ==================================================================
subtest 'mutate: various expression types' => sub {
	my $m = _mutation();

	# Numeric literal
	my @mutants = $m->mutate(_doc('sub foo { return 1; }'));
	is(scalar @mutants, 1, 'numeric literal return produces mutant');

	# String literal
	@mutants = $m->mutate(_doc("sub foo { return 'ok'; }"));
	is(scalar @mutants, 1, 'string literal return produces mutant');

	# Method call
	@mutants = $m->mutate(_doc('sub foo { return $self->is_valid(); }'));
	is(scalar @mutants, 1, 'method call return produces mutant');

	# Comparison expression
	@mutants = $m->mutate(_doc('sub foo { return $x > 0; }'));
	is(scalar @mutants, 1, 'comparison return produces mutant');

	# Undef return



( run in 0.492 second using v1.01-cache-2.11-cpan-e1769b4cff6 )