App-Test-Generator

 view release on metacpan or  search on metacpan

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

# ==================================================================
# mutate -- transform flips if to unless
# ==================================================================
subtest 'mutate: transform flips if to unless' => sub {
	my $m   = _mutation();
	my $src = 'sub foo { if($x) { return 1; } }';
	my $doc = _doc($src);

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

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

	my $transformed = $copy->serialize;

	# The if keyword must have been replaced by unless
	like($transformed,   qr/\bunless\b/, 'transform inserts unless');
	unlike($transformed, qr/\bif\b/,     'transform removes if');
};

# ==================================================================
# mutate -- transform flips unless to if
# ==================================================================
subtest 'mutate: transform flips unless to if' => sub {
	my $m   = _mutation();
	my $src = 'sub foo { unless($err) { proceed(); } }';
	my $doc = _doc($src);

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

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

	my $transformed = $copy->serialize;

	# The unless keyword must have been replaced by if
	like($transformed,   qr/\bif\b/,     'transform inserts if');
	unlike($transformed, qr/\bunless\b/, 'transform removes unless');
};

# ==================================================================
# mutate -- transform targets correct conditional when multiple exist
# ==================================================================
subtest 'mutate: transform targets correct conditional' => sub {
	my $m   = _mutation();
	my $src = <<'CODE';
sub check {
	if($a) { return 1; }
	if($b) { return 2; }
}
CODE

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

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

		# Count how many unless keywords appear — exactly one should be added
		my @unless_count = ($transformed =~ /\bunless\b/g);
		is(scalar @unless_count, 1,
			"transform for mutant ${\$mut->id} flips exactly one if to unless");
	}
};

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

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

	# Capture original serialisation before any 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');
};

# ==================================================================
# mutate -- original field captures the condition content
# ==================================================================
subtest 'mutate: original captures condition content' => sub {
	my $m   = _mutation();
	my $doc = _doc('sub foo { if($x > 0) { return 1; } }');

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

	# Original should contain the condition expression
	like($mutants[0]->original, qr/\$x.*>.*0/,
		'original contains the condition expression');
};

# ==================================================================
# mutate -- conditional without a condition block is skipped
# ==================================================================
subtest 'mutate: conditional without condition block skipped' => sub {
	my $m = _mutation();

	# A well-formed if always has a condition -- test that the guard works
	# by verifying normal operation first
	my @mutants = $m->mutate(_doc('sub foo { if($x) { 1; } }'));
	is(scalar @mutants, 1, 'normal if with condition produces one mutant');
};

# ==================================================================
# mutate -- ID uniqueness: group contains same line as ID
# ==================================================================
subtest 'mutate: group line matches ID line' => sub {
	my $m   = _mutation();
	my $doc = _doc(<<'CODE');
sub foo {
	if($a)     { return 1; }
	unless($b) { return 0; }
}
CODE

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

	for my $mut (@mutants) {
		my ($id_line)    = $mut->id    =~ /COND_INV_(\d+)/;
		my ($group_line) = $mut->group =~ /COND_INV:(\d+)/;
		is($id_line, $group_line, "group line matches ID line for mutant ${\$mut->id}");
		is($mut->line, $id_line, "line() matches ID line for mutant ${\$mut->id}");
	}
};

# ==================================================================
# mutate -- returns a list (current API)
# ==================================================================
subtest 'mutate: returns a list' => sub {
	my $m   = _mutation();
	my $doc = _doc('sub foo { if($a) { 1; } unless($b) { 2; } }');

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

	# TODO: API should return arrayref for efficiency — same change
	# needed across all Mutation::* subclasses simultaneously
};

# ==================================================================
# applies_to() — new function
# ==================================================================

subtest 'applies_to() returns 1 for document containing if statement' => sub {
	require PPI;
	my $m   = App::Test::Generator::Mutation::ConditionalInversion->new();
	my $doc = PPI::Document->new(\'sub foo { if($x > 0) { return 1; } }');
	is($m->applies_to($doc), 1, 'if statement -> applies_to returns 1');
};

subtest 'applies_to() returns 1 for document containing unless statement' => sub {
	require PPI;
	my $m   = App::Test::Generator::Mutation::ConditionalInversion->new();
	my $doc = PPI::Document->new(\'sub foo { unless($x) { return 0; } }');
	is($m->applies_to($doc), 1, 'unless statement -> applies_to returns 1');
};



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