#!/usr/bin/perl

use Test::More tests => 85;

use strict;
use warnings;
use IPC::Open2;
use Cwd ();
use File::Temp qw(tempfile);
use File::Basename ();

# Let the tests to be run from anywhere but currect directory
# is expected to be the one where this test lives in.
chdir File::Basename::dirname($0) or die "Unable to chdir to ".File::Basename::dirname($0);

use_ok( 'Debian::Debhelper::Dh_Lib' );
use_ok( 'Debian::Debhelper::Buildsystem' );
use_ok( 'Debian::Debhelper::Dh_Buildsystems' );

my $TOPDIR = $ENV{AUTOPKGTEST_TMP} ? '/usr/bin' : '../..';
my @STEPS = qw(configure build test install clean);
my $BS_CLASS = 'Debian::Debhelper::Buildsystem';

my ($bs);
my ($tmp, @tmp, %tmp);
my ($default_builddir);

### Common subs ####
sub readlines {
	my $h=shift;
	my @lines = <$h>;
	close $h;
	chop @lines;
	return \@lines;
}

sub process_stdout {
	my ($cmdline, $stdin) = @_;
	my ($reader, $writer);

	my $pid = open2($reader, $writer, $cmdline) or die "Unable to exec $cmdline";
	print $writer $stdin if $stdin;
	close $writer;
	waitpid($pid, 0);
	$? = $? >> 8; # exit status
	return readlines($reader);
}

sub write_debian_rules {
	my $contents=shift;
	my $backup;

	if (-f "debian/rules") {
		(undef, $backup) = tempfile(DIR => ".", OPEN => 0);
		rename "debian/rules", $backup;
	}
	# Write debian/rules if requested
	if ($contents) {
		open(my $f, ">", "debian/rules");
		print $f $contents;;
		close($f);
		chmod 0755, "debian/rules";
	}
	return $backup;
}

### Test Buildsystem class path API methods under different configurations
sub test_buildsystem_paths_api {
	my ($bs, $config, $expected)=@_;

	my $api_is = sub {
		my ($got, $name)=@_;
		is( $got, $expected->{$name}, "paths API ($config): $name")
	};

	&$api_is( $bs->get_sourcedir(), 'get_sourcedir()' );
	&$api_is( $bs->get_sourcepath("a/b"), 'get_sourcepath(a/b)' );
	&$api_is( $bs->get_builddir(), 'get_builddir()' );
	&$api_is( $bs->get_buildpath(), 'get_buildpath()' );
	&$api_is( $bs->get_buildpath("a/b"), 'get_buildpath(a/b)' );
	&$api_is( $bs->get_source_rel2builddir(), 'get_source_rel2builddir()' );
	&$api_is( $bs->get_source_rel2builddir("a/b"), 'get_source_rel2builddir(a/b)' );
	&$api_is( $bs->get_build_rel2sourcedir(), 'get_build_rel2sourcedir()' );
	&$api_is( $bs->get_build_rel2sourcedir("a/b"), 'get_build_rel2sourcedir(a/b)' );
}

# Defaults
$bs = $BS_CLASS->new();
$default_builddir = $bs->DEFAULT_BUILD_DIRECTORY();
%tmp = (
	"get_sourcedir()" => ".",
	"get_sourcepath(a/b)" => "./a/b",
	"get_builddir()" => undef,
	"get_buildpath()" => ".",
	"get_buildpath(a/b)" =>  "./a/b",
	"get_source_rel2builddir()" => ".",
	"get_source_rel2builddir(a/b)" => "./a/b",
	"get_build_rel2sourcedir()" => ".",
	"get_build_rel2sourcedir(a/b)" => "./a/b",
);
test_buildsystem_paths_api($bs, "no builddir, no sourcedir", \%tmp);

# builddir=bld/dir
$bs = $BS_CLASS->new(builddir => "bld/dir");
%tmp = (
	"get_sourcedir()" => ".",
	"get_sourcepath(a/b)" => "./a/b",
	"get_builddir()" => "bld/dir",
	"get_buildpath()" => "bld/dir",
	"get_buildpath(a/b)" =>  "bld/dir/a/b",
	"get_source_rel2builddir()" => "../..",
	"get_source_rel2builddir(a/b)" => "../../a/b",
	"get_build_rel2sourcedir()" => "bld/dir",
	"get_build_rel2sourcedir(a/b)" => "bld/dir/a/b",
);
test_buildsystem_paths_api($bs, "builddir=bld/dir, no sourcedir", \%tmp);

# Default builddir, sourcedir=autoconf
$bs = $BS_CLASS->new(builddir => undef, sourcedir => "autoconf");
%tmp = (
	"get_sourcedir()" => "autoconf",
	"get_sourcepath(a/b)" => "autoconf/a/b",
	"get_builddir()" => "$default_builddir",
	"get_buildpath()" => "$default_builddir",
	"get_buildpath(a/b)" =>  "$default_builddir/a/b",
	"get_source_rel2builddir()" => "../autoconf",
	"get_source_rel2builddir(a/b)" => "../autoconf/a/b",
	"get_build_rel2sourcedir()" => "../$default_builddir",
	"get_build_rel2sourcedir(a/b)" => "../$default_builddir/a/b",
);
test_buildsystem_paths_api($bs, "default builddir, sourcedir=autoconf", \%tmp);

# sourcedir=autoconf (builddir should be dropped)
$bs = $BS_CLASS->new(builddir => "autoconf", sourcedir => "autoconf");
%tmp = (
	"get_sourcedir()" => "autoconf",
	"get_sourcepath(a/b)" => "autoconf/a/b",
	"get_builddir()" => undef,
	"get_buildpath()" => "autoconf",
	"get_buildpath(a/b)" =>  "autoconf/a/b",
	"get_source_rel2builddir()" => ".",
	"get_source_rel2builddir(a/b)" => "./a/b",
	"get_build_rel2sourcedir()" => ".",
	"get_build_rel2sourcedir(a/b)" => "./a/b",
);
test_buildsystem_paths_api($bs, "no builddir, sourcedir=autoconf", \%tmp);

# Prefer out of source tree building when
# sourcedir=builddir=autoconf hence builddir should be dropped.
$bs->prefer_out_of_source_building(builddir => "autoconf");
test_buildsystem_paths_api($bs, "out of source preferred, sourcedir=builddir", \%tmp);

# builddir=bld/dir, sourcedir=autoconf. Should be the same as sourcedir=autoconf.
$bs = $BS_CLASS->new(builddir => "bld/dir", sourcedir => "autoconf");
$bs->enforce_in_source_building();
test_buildsystem_paths_api($bs, "in source enforced, sourcedir=autoconf", \%tmp);

# builddir=../bld/dir (relative to the curdir)
$bs = $BS_CLASS->new(builddir => "bld/dir/", sourcedir => "autoconf");
%tmp = (
	"get_sourcedir()" => "autoconf",
	"get_sourcepath(a/b)" => "autoconf/a/b",
	"get_builddir()" => "bld/dir",
	"get_buildpath()" => "bld/dir",
	"get_buildpath(a/b)" =>  "bld/dir/a/b",
	"get_source_rel2builddir()" => "../../autoconf",
	"get_source_rel2builddir(a/b)" => "../../autoconf/a/b",
	"get_build_rel2sourcedir()" => "../bld/dir",
	"get_build_rel2sourcedir(a/b)" => "../bld/dir/a/b",
);
test_buildsystem_paths_api($bs, "builddir=../bld/dir, sourcedir=autoconf", \%tmp);

#### Test parallel building and related options / routines
@tmp = ( $ENV{MAKEFLAGS}, $ENV{DEB_BUILD_OPTIONS} );


# Test parallel building with makefile build system.
$ENV{MAKEFLAGS} = "";
$ENV{DEB_BUILD_OPTIONS} = "";

sub do_parallel_mk {
	my $dh_opts=shift || "";
	my $make_opts=shift || "";
	return process_stdout(
		"LANG=C LC_ALL=C LC_MESSAGES=C $TOPDIR/dh_auto_build -Smakefile $dh_opts " .
		"-- -s -f parallel.mk $make_opts 2>&1 >/dev/null", "");
}

sub test_isnt_parallel {
	my ($got, $desc) = @_;
	my @makemsgs = grep /^make[\d\[\]]*:/, @$got;
	if (@makemsgs) {
		like( $makemsgs[0], qr/Error 10/, $desc );
	}
	else {
		ok( scalar(@makemsgs) > 0, $desc );
	}
}

sub test_is_parallel {
	my ($got, $desc) = @_;
	is_deeply( $got, [] , $desc );
	is( $?, 0, "(exit status=0) $desc");
}


test_isnt_parallel( do_parallel_mk(),
	"No parallel by default" );
test_isnt_parallel( do_parallel_mk("parallel"),
	"No parallel by default with --parallel" );
test_isnt_parallel( do_parallel_mk("--max-parallel=5"),
	"No parallel by default with --max-parallel=5" );

$ENV{DEB_BUILD_OPTIONS}="parallel=5";
{
	local $ENV{DH_COMPAT} = 9;
	# compat 9 is not parallel by default
	test_isnt_parallel( do_parallel_mk(),
						"DEB_BUILD_OPTIONS=parallel=5 without parallel options [c9]" );
}

# compat 10+ are parallel by default
test_is_parallel( do_parallel_mk(),
				  "DEB_BUILD_OPTIONS=parallel=5 without parallel options [current compat]" );
test_is_parallel( do_parallel_mk("--parallel"),
	"DEB_BUILD_OPTIONS=parallel=5 with --parallel" );
test_is_parallel( do_parallel_mk("--max-parallel=2"),
	"DEB_BUILD_OPTIONS=parallel=5 with --max-parallel=2" );
test_isnt_parallel( do_parallel_mk("--max-parallel=1"),
	"DEB_BUILD_OPTIONS=parallel=5 with --max-parallel=1" );
test_isnt_parallel( do_parallel_mk("--no-parallel"),
	"DEB_BUILD_OPTIONS=parallel=5 with --no-parallel" );

$ENV{MAKEFLAGS} = "--jobserver-fds=105,106 -j";
$ENV{DEB_BUILD_OPTIONS}="";
test_isnt_parallel( do_parallel_mk(),
	"makefile.pm (no parallel): no make warnings about unavailable jobserver" );
$ENV{DEB_BUILD_OPTIONS}="parallel=5";
test_is_parallel( do_parallel_mk("--parallel"),
	"DEB_BUILD_OPTIONS=parallel=5: no make warnings about unavail parent jobserver" );

$ENV{MAKEFLAGS} = "-j2";
$ENV{DEB_BUILD_OPTIONS}="";
test_isnt_parallel( do_parallel_mk(),
	"MAKEFLAGS=-j2: dh_auto_build ignores MAKEFLAGS" );
test_isnt_parallel( do_parallel_mk("--max-parallel=1"),
	"MAKEFLAGS=-j2 with --max-parallel=1: dh_auto_build enforces -j1" );

# Test dh dpkg-buildpackage -jX detection
sub do_rules_for_parallel {
	my $cmdline=shift || "";
	my $stdin=shift || "";
	return process_stdout("LANG=C LC_ALL=C LC_MESSAGES=C PATH=$TOPDIR:\$PATH " .
		"make -f - $cmdline 2>&1 >/dev/null", $stdin);
}

doit("ln", "-sf", "parallel.mk", "Makefile");

# Test if dh+override+$(MAKE) legacy punctuation hack work as before
$ENV{MAKEFLAGS} = "-j5";
$ENV{DEB_BUILD_OPTIONS} = "parallel=5";

$tmp = write_debian_rules(<<'EOF');
#!/usr/bin/make -f
export DEB_RULES_REQUIRES_ROOT:=no
override_dh_auto_build:
	$(MAKE)
%:
	@dh_clean > /dev/null 2>&1
	@+dh $@ --buildsystem=makefile --without autoreconf 2>/dev/null
	@dh_clean > /dev/null 2>&1
EOF
test_is_parallel( do_rules_for_parallel("build", "include debian/rules"),
	"legacy punctuation hacks: +dh, override with \$(MAKE)" );
unlink "debian/rules";

if (defined $tmp) {
	rename($tmp, "debian/rules");
}
else {
	unlink("debian/rules");
}

# Clean up after parallel testing
END {
	system("rm", "-f", "Makefile");
}
$ENV{MAKEFLAGS} = $tmp[0] if defined $tmp[0];
$ENV{DEB_BUILD_OPTIONS} = $tmp[1] if defined $tmp[1];

END {
	system("$TOPDIR/dh_clean");
}
