The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!perl -w

use strict;
use Test::More tests =>32;
use Test::Exception;

use Data::Util qw(:all);

use constant PP_ONLY => $INC{'Data/Util/PurePerl.pm'};

sub get_subname{
	return scalar get_code_info(@_);
}

sub foo{
	42;
}
sub bar{
	52;
}
{
	package Base;
	sub foo{
		'Base::foo';
	}
	package Foo;
	our @ISA = qw(Base);
	use Data::Util qw(install_subroutine);

	sub baz{}

	package Callable;
	use overload
		'&{}' => 'codify',
	;
	sub new{
		my $class = shift;
		bless {@_} => $class;
	}
	sub codify{
		my $self = shift;
		$self->{code};
	}
}

is_deeply get_subname(\&foo), 'main::foo', 'get_code_info()';
is_deeply [get_code_info(\&foo)], [qw(main foo)];

is_deeply get_subname(\&Foo::baz), 'Foo::baz', 'get_code_info()';
is_deeply [get_code_info(\&Foo::baz)], [qw(Foo baz)];

is_deeply get_subname(\&undefined_subr), 'main::undefined_subr';
is_deeply [get_code_info(\&undefined_subr)], [qw(main undefined_subr)];

no warnings 'redefine';

Foo->foo(); # touch the chache

Foo->install_subroutine(foo => \&foo);

is Foo::foo(), foo(), 'as function';
is(Foo->foo(), foo(), 'as method');

Foo->install_subroutine(foo => \&bar);

is Foo::foo(), bar(), 'redefined';

Foo->install_subroutine(foo => sub{ 314 });

is Foo::foo(), 314, 'install anonymous subr';
SKIP:{
	skip 'in testing perl only', 1 if PP_ONLY;
	is get_subname(\&Foo::foo), 'Foo::foo', '...named';
}

Foo->install_subroutine(foo => \&foo);

is Foo::foo(), foo();
SKIP:{
	skip 'in testing perl only', 1 if PP_ONLY;
	is get_subname(\&Foo::foo), 'main::foo';
}

{
	my $count = 0;
	Foo->install_subroutine(foo => sub{ ++$count });
}

is Foo::foo(), 1, 'install closure';
is Foo::foo(), 2;


SKIP:{
	skip 'in testing perl only', 2 if PP_ONLY;

	Foo->install_subroutine(foo => sub{});
	is get_subname(\&Foo::foo), 'Foo::foo', 'name an anonymous subr';

	Foo->install_subroutine(bar => \&Foo::foo);
	is get_subname(\&Foo::bar), 'Foo::foo', 'does not name a named subr';
}

# exception

Foo->install_subroutine(foo => \&undefined_subr);
dies_ok{
	Foo->foo();
} 'install undefined subroutine';


Foo->install_subroutine(ov1 => Callable->new(code => sub{ 'overloaded' }));
is Foo::ov1(), 'overloaded', 'overload';

Foo->install_subroutine(ov2 => Callable->new(code => sub{ die 'dies in codify' }));

throws_ok{
	Foo::ov2();
} qr/dies in codify/;

dies_ok{
	Foo->install_subroutine(ov3 => Callable->new(code => []));
};
dies_ok{
	Foo->install_subroutine(ov4 => Callable->new(code => undef));
};

use warnings FATAL => 'redefine';

throws_ok{
	get_code_info(undef);
} qr/CODE reference/;

throws_ok{
	install_subroutine();
} qr/^Usage: /;

dies_ok{
	Foo->install_subroutine('foo');
};

throws_ok{
	Data::Util::install_subroutine(undef, foo => \&foo);
} qr/package name/;

throws_ok{
	Foo->install_subroutine(PI => 3.14);
} qr/CODE reference/;

throws_ok{
	Foo->install_subroutine(undef, sub{});
} qr/\b name\b /xms;
throws_ok{
	Foo->install_subroutine([], sub{});
} qr/\b name\b /xms;

# multiple installation

install_subroutine(__PACKAGE__, f1 => sub{ 1 }, f2 => sub{ 2 }, f3 => sub{ 3 });
is f1(), 1, 'multiple installation(1)';
is f2(), 2, 'multiple installation(2)';
is f3(), 3, 'multiple installation(3)';;