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

use strict;
use warnings FATAL => 'all';
use Test::More tests => 111;

use Config;
use File::Temp 'tempdir';

use File::Spec::Functions 0.83 ':ALL';

my $tmp = tempdir('EIP-XXXXXXXX', CLEANUP => 1, DIR => tmpdir);
my $source = tempdir('EIP-XXXXXXXX', CLEANUP => 1, DIR => tmpdir);
chdir $source;
mkdir 'blib';
for my $subdir (qw/lib arch bin script man1 man3/) {
	mkdir catdir('blib', $subdir);
}

use ExtUtils::Config;
use ExtUtils::InstallPaths;

#########################

# We need to create a well defined environment to test install paths.
# We do this by setting up appropriate Config entries.

my @installstyle = qw(lib perl5);
my $config = ExtUtils::Config->new({
	installstyle	=> catdir(@installstyle),

	installprivlib  => catdir($tmp, @installstyle),
	installarchlib  => catdir($tmp, @installstyle, @Config{qw(version archname)}),
	installbin      => catdir($tmp, 'bin'),
	installscript   => catdir($tmp, 'bin'),
	installman1dir  => catdir($tmp, 'man', 'man1'),
	installman3dir  => catdir($tmp, 'man', 'man3'),
	installhtml1dir => catdir($tmp, 'html'),
	installhtml3dir => catdir($tmp, 'html'),

	installsitelib      => catdir($tmp, 'site', @installstyle, 'site_perl'),
	installsitearch     => catdir($tmp, 'site', @installstyle, 'site_perl', @Config{qw(version archname)}),
	installsitebin      => catdir($tmp, 'site', 'bin'),
	installsitescript   => catdir($tmp, 'site', 'bin'),
	installsiteman1dir  => catdir($tmp, 'site', 'man', 'man1'),
	installsiteman3dir  => catdir($tmp, 'site', 'man', 'man3'),
	installsitehtml1dir => catdir($tmp, 'site', 'html'),
	installsitehtml3dir => catdir($tmp, 'site', 'html'),
});

sub get_ei {
	my %args = @_;
	return ExtUtils::InstallPaths->new(installdirs => 'site', config => $config, dist_name => 'ExtUtils-InstallPaths', %args);
}

isa_ok(get_ei, 'ExtUtils::InstallPaths');

{
	my $elem = catdir(rootdir, qw/foo bar/);
	my $ei = get_ei(install_path => { elem => $elem});
	is($ei->install_path('elem'), $elem, '  can read stored path');
}

{
	my $ei = get_ei(install_base => catdir(rootdir, 'bar'), install_base_relpaths => { 'elem' => catdir(qw/foo bar/) });
 
	is($ei->install_base_relpaths('elem'), catdir(qw/foo bar/), '  can read stored path');
	is($ei->install_destination('lib'), catdir(rootdir, qw/bar lib perl5/), 'destination of other items is not affected');
}
 
 
{
	my $ei = eval { get_ei(prefix_relpaths => { 'site' => { 'elem' => catdir(rootdir, qw/foo bar/)} }) };
	is ($ei, undef, '$ei undefined');
	like($@, qr/Value must be a relative path/, '  emits error if path not relative');
}

{
	my $ei = get_ei(prefix_relpaths => { site => { elem => catdir(qw/foo bar/) } });
 
	my $path = $ei->prefix_relpaths('site', 'elem');
	is($path, catdir(qw(foo bar)), '  can read stored path');
}


# Check that we install into the proper default locations.
{
	my $ei = get_ei();

	test_install_destinations($ei, {
		lib     => catdir($tmp, 'site', @installstyle, 'site_perl'),
		arch	=> catdir($tmp, 'site', @installstyle, 'site_perl', @Config{qw(version archname)}),
		bin     => catdir($tmp, 'site', 'bin'),
		script  => catdir($tmp, 'site', 'bin'),
		bindoc  => catdir($tmp, 'site', 'man', 'man1'),
		libdoc  => catdir($tmp, 'site', 'man', 'man3'),
		binhtml => catdir($tmp, 'site', 'html'),
		libhtml => catdir($tmp, 'site', 'html'),
	}, 'installdirs=site');
	test_install_map($ei, {
		read                      => '',
		write                     => catfile($ei->install_destination('arch'), qw/auto ExtUtils InstallPaths .packlist/),
		catdir('blib', 'lib')     => catdir($tmp, 'site', @installstyle, 'site_perl'),
		catdir('blib', 'arch')    => catdir($tmp, 'site', @installstyle, 'site_perl', @Config{qw(version archname)}),
		catdir('blib', 'bin')     => catdir($tmp, 'site', 'bin'),
		catdir('blib', 'script')  => catdir($tmp, 'site', 'bin'),
	}, 'installdirs=site');
}

# Is installdirs honored?
{
	my $ei = get_ei(installdirs => 'core');
	is($ei->installdirs, 'core');

	test_install_destinations($ei, {
		lib     => catdir($tmp, @installstyle),
		arch	=> catdir($tmp, @installstyle, @Config{qw(version archname)}),
		bin     => catdir($tmp, 'bin'),
		script  => catdir($tmp, 'bin'),
		bindoc  => catdir($tmp, 'man', 'man1'),
		libdoc  => catdir($tmp, 'man', 'man3'),
		binhtml => catdir($tmp, 'html'),
		libhtml => catdir($tmp, 'html'),
	});
}

# Check install_base()
{
	my $install_base = catdir('foo', 'bar');
	my $ei = get_ei(install_base => $install_base);

	is($ei->prefix, undef);
	is($ei->install_base, $install_base);

	test_install_destinations($ei, {
		lib     => catdir($install_base, 'lib', 'perl5'),
		arch	=> catdir($install_base, 'lib', 'perl5', $Config{archname}),
		bin     => catdir($install_base, 'bin'),
		script  => catdir($install_base, 'bin'),
		bindoc  => catdir($install_base, 'man', 'man1'),
		libdoc  => catdir($install_base, 'man', 'man3'),
		binhtml => catdir($install_base, 'html'),
		libhtml => catdir($install_base, 'html'),
	});

	test_install_map($ei, {
		read                      => '',
		write                     => catfile($ei->install_destination('arch'), qw/auto ExtUtils InstallPaths .packlist/),
		catdir('blib', 'lib')     => catdir($install_base, 'lib', 'perl5'),
		catdir('blib', 'arch')    => catdir($install_base, 'lib', 'perl5', $Config{archname}),
		catdir('blib', 'bin')     => catdir($install_base, 'bin'),
		catdir('blib', 'script')  => catdir($install_base, 'bin'),
	}, 'install_base');

	test_install_map($ei, {
		read                       => '',
		write                      => catfile($ei->install_destination('arch'), qw/auto ExtUtils InstallPaths .packlist/),
		catdir('blib', 'lib')     => catdir($install_base, 'lib', 'perl5'),
		catdir('blib', 'arch')    => catdir($install_base, 'lib', 'perl5', $Config{archname}),
		catdir('blib', 'bin')     => catdir($install_base, 'bin'),
		catdir('blib', 'script')  => catdir($install_base, 'bin'),
	}, 'install_base', {
		lib    => catdir(qw/blib lib/),
		arch   => catdir(qw/blib arch/),
		bin    => catdir(qw/blib bin/),
		script => catdir(qw/blib script/),
	});
}


# Basic prefix test.  Ensure everything is under the prefix.
{
	my $prefix = catdir(qw/some prefix/);
	my $ei = get_ei(prefix => $prefix);

	ok(!defined $ei->install_base, 'install_base is not defined');
	is($ei->prefix, $prefix, "The prefix is $prefix");

	test_prefix($ei, $prefix);
#	test_prefix($ei, $prefix, $ei->install_sets('site'));
}

# And now that prefix honors installdirs.
{
	my $prefix = catdir(qw/some prefix/);
	my $ei = get_ei(prefix => $prefix, installdirs => 'core');

	is($ei->installdirs, 'core');
	test_prefix($ei, $prefix);
}

{
	my $ei = get_ei;
# Try a config setting which would result in installation locations outside
# the prefix.  Ensure it doesn't.
	# Get the prefix defaults
	my @types = $ei->install_types;

	# Create a configuration involving weird paths that are outside of
	# the configured prefix.
	my @prefixes = ([qw(foo bar)], [qw(biz)], []);

	my %test_config;
	foreach my $type (@types) {
		my $prefix = shift @prefixes || [qw(foo bar)];
		$test_config{$type} = catdir(rootdir, @$prefix, @{$ei->prefix_relpaths('site', $type)});
	}

	# Poke at the innards of E::IP to change the default install locations.
	my $prefix = catdir('another', 'prefix');
	my $config = ExtUtils::Config->new({ siteprefixexp => catdir(rootdir, 'wierd', 'prefix')});
	$ei = get_ei(install_sets => { site => \%test_config }, config => $config, prefix => $prefix);

	test_prefix($ei, $prefix, \%test_config);
}

# Check that we can use install_base after setting prefix.
{
	my $install_base = catdir('foo', 'bar');
	my $ei = get_ei(install_base => $install_base, prefix => 'whatever');

	test_install_destinations($ei, {
		lib     => catdir($install_base, 'lib', 'perl5'),
		arch	=> catdir($install_base, 'lib', 'perl5', $Config{archname}),
		bin     => catdir($install_base, 'bin'),
		script  => catdir($install_base, 'bin'),
		bindoc  => catdir($install_base, 'man', 'man1'),
		libdoc  => catdir($install_base, 'man', 'man3'),
		binhtml => catdir($install_base, 'html'),
		libhtml => catdir($install_base, 'html'),
	});
}

sub dir_contains {
	my ($first, $second) = @_;
	# File::Spec doesn't have an easy way to check whether one directory
	# is inside another, unfortunately.

	($first, $second) = map { canonpath($_) } ($first, $second);
	my @first_dirs = splitdir($first);
	my @second_dirs = splitdir($second);

	return 0 if @second_dirs < @first_dirs;

	my $is_same = ( case_tolerant() ? sub { lc(shift()) eq lc(shift()) } : sub { shift() eq shift() });

	while (@first_dirs) {
		return 0 unless $is_same->(shift @first_dirs, shift @second_dirs);
	}

	return 1;
}


sub test_prefix {
	my ($ei, $prefix, $test_config) = @_;

	local $Test::Builder::Level = $Test::Builder::Level + 1;

	foreach my $type (qw/lib arch bin script bindoc libdoc binhtml libhtml/) {
		my $dest = $ei->install_destination($type);
		ok dir_contains($prefix, $dest), "$type prefixed";

		SKIP: {
			skip("'$type' not configured", 1) unless $test_config && $test_config->{$type};

			have_same_ending($dest, $test_config->{$type}, "  suffix correctish ($test_config->{$type} + $prefix = $dest)");
		}
	}
}

sub have_same_ending {
	my ($dir1, $dir2, $message) = @_;

	$dir1 =~ s{/$}{} if $^O eq 'cygwin'; # remove any trailing slash
	my (undef, $dirs1, undef) = splitpath $dir1;
	my @dir1 = splitdir $dirs1;

	$dir2 =~ s{/$}{} if $^O eq 'cygwin'; # remove any trailing slash
	my (undef, $dirs2, undef) = splitpath $dir2;
	my @dir2 = splitdir $dirs2;

	is $dir1[-1], $dir2[-1], $message;
}

sub test_install_destinations {
	my ($build, $expect) = @_;

	local $Test::Builder::Level = $Test::Builder::Level + 1;

	while(my ($type, $expect) = each %$expect) {
		is($build->install_destination($type), $expect, "$type destination");
	}
}

sub test_install_map {
	my ($paths, $expect, $case, @args) = @_;

	local $Test::Builder::Level = $Test::Builder::Level + 1;

	my $map = $paths->install_map(@args);
	while(my ($type, $expect) = each %$expect) {
		is($map->{$type}, $expect, "$type destination for $case");
	}
}