summaryrefslogtreecommitdiffstats
path: root/t
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 21:06:40 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 21:06:40 +0000
commitd827c6cf1631209f5042a9d1d8a7ecc24223c8a0 (patch)
tree91a431d301efd0e524bdfb0c46e97d591a9d7b03 /t
parentInitial commit. (diff)
downloaddebhelper-upstream.tar.xz
debhelper-upstream.zip
Adding upstream version 13.11.4.upstream/13.11.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rwxr-xr-xt/Dh_Lib/00-use.t20
-rwxr-xr-xt/Dh_Lib/control-parsing.t36
-rw-r--r--t/Dh_Lib/debian/changelog5
-rw-r--r--t/Dh_Lib/debian/control19
-rwxr-xr-xt/Dh_Lib/path.t26
-rw-r--r--t/Test/DH.pm212
-rwxr-xr-xt/buildsystems/01-build-system-basic-api.t43
-rwxr-xr-xt/buildsystems/02-make-jobserver-makeflags.t57
-rwxr-xr-xt/buildsystems/03-bs-auto-buildable.t218
-rwxr-xr-xt/buildsystems/04-dh_auto_do_autoconf.t88
-rwxr-xr-xt/buildsystems/05-load-build-system.t55
-rwxr-xr-xt/buildsystems/06-buildsystem-mkdir-rmdir.t51
-rwxr-xr-xt/buildsystems/autoconf/configure84
-rwxr-xr-xt/buildsystems/buildsystem_tests.t293
-rw-r--r--t/buildsystems/debian/changelog5
-rw-r--r--t/buildsystems/debian/control12
-rwxr-xr-xt/buildsystems/load-bs.pl16
-rw-r--r--t/buildsystems/parallel.mk21
-rw-r--r--t/debhelper-compat/debian/control12
-rwxr-xr-xt/debhelper-compat/syntax.t82
-rwxr-xr-xt/dh-lib.t63
-rwxr-xr-xt/dh-sequencer.t282
-rwxr-xr-xt/dh_compress.t114
-rwxr-xr-xt/dh_install/01-basics.t33
-rwxr-xr-xt/dh_install/02-bugs-53XXXX.t65
-rwxr-xr-xt/dh_install/03-866570-dont-install-from-host.t24
-rwxr-xr-xt/dh_install/04-sourcedir.t47
-rw-r--r--t/dh_installchangelogs/debian/control10
-rwxr-xr-xt/dh_installchangelogs/dh_installchangelogs.t252
-rwxr-xr-xt/dh_installdocs/01-868204-ignore-broken-symlinks.t30
-rw-r--r--t/dh_installdocs/debian/changelog5
-rw-r--r--t/dh_installdocs/debian/control21
-rw-r--r--t/dh_installdocs/debian/copyright1
-rw-r--r--t/dh_installdocs/debian/docfile1
-rwxr-xr-xt/dh_installdocs/dh_installdocs.t75
-rw-r--r--t/dh_installgsettings/debian/changelog5
-rw-r--r--t/dh_installgsettings/debian/control17
-rwxr-xr-xt/dh_installgsettings/dh_installgsettings.t49
-rw-r--r--t/dh_installinit/debian/bar.other.init4
-rw-r--r--t/dh_installinit/debian/changelog5
-rw-r--r--t/dh_installinit/debian/control20
-rw-r--r--t/dh_installinit/debian/foo.service5
-rwxr-xr-xt/dh_installinit/dh_installinit.t44
-rwxr-xr-xt/dh_installman/01-basics.t94
-rw-r--r--t/dh_installman/libmanpage.so.1.2.3.pod17
-rw-r--r--t/dh_installman/libmanpage.so.9.2.31
-rw-r--r--t/dh_installman/manpage-compressed.pod17
-rw-r--r--t/dh_installman/manpage-perl.pod17
-rw-r--r--t/dh_installman/manpage-uncompressed.pod17
-rw-r--r--t/dh_installpam/debian/changelog5
-rw-r--r--t/dh_installpam/debian/control10
-rw-r--r--t/dh_installpam/debian/foo.pam1
-rwxr-xr-xt/dh_installpam/dh_installpam.t38
-rw-r--r--t/dh_installsystemd/debian/changelog5
-rw-r--r--t/dh_installsystemd/debian/control20
-rw-r--r--t/dh_installsystemd/debian/foo.init4
-rw-r--r--t/dh_installsystemd/debian/foo.service8
-rw-r--r--t/dh_installsystemd/debian/foo2.service8
-rwxr-xr-xt/dh_installsystemd/dh_installsystemd.t255
-rwxr-xr-xt/dh_installsystemd/dh_installsystemd_tmpfiles.t87
-rwxr-xr-xt/dh_installsystemd/dh_systemd.t123
-rw-r--r--t/dh_installsystemd/simple/debian/changelog5
-rw-r--r--t/dh_installsystemd/simple/debian/control10
-rw-r--r--t/dh_installsystemd/simple/debian/foo.service8
-rw-r--r--t/dh_installsystemduser/debian/baz.user.service8
-rw-r--r--t/dh_installsystemduser/debian/changelog5
-rw-r--r--t/dh_installsystemduser/debian/control10
-rw-r--r--t/dh_installsystemduser/debian/foo.user.service8
-rwxr-xr-xt/dh_installsystemduser/dh_installsystemduser.t112
-rwxr-xr-xt/dh_link/01-basic.t50
-rwxr-xr-xt/dh_link/02-346405.t29
-rwxr-xr-xt/dh_link/03-894229.t55
-rwxr-xr-xt/dh_missing/01-no-missing.t28
-rwxr-xr-xt/dh_missing/02-fail-on-missing.t33
-rwxr-xr-xt/dh_missing/03-dh_install-redirection.t30
-rwxr-xr-xt/dh_missing/04-not-installed-glob.t37
-rw-r--r--t/dh_missing/template/Makefile7
-rw-r--r--t/dh_missing/template/debian/changelog5
-rw-r--r--t/dh_missing/template/debian/control20
-rw-r--r--t/dh_missing/template/debian/foo.install1
-rw-r--r--t/dh_missing/template/file-for-foo1
-rwxr-xr-xt/dh_usrlocal/01-basic.t157
-rwxr-xr-xt/maintscript.t70
-rwxr-xr-xt/override_target.t30
-rwxr-xr-xt/pod.t10
-rwxr-xr-xt/size.t32
l---------t/syntax/syntax-libs.t1
-rwxr-xr-xt/syntax/syntax-progs.t28
88 files changed, 4044 insertions, 0 deletions
diff --git a/t/Dh_Lib/00-use.t b/t/Dh_Lib/00-use.t
new file mode 100755
index 0000000..dc8a966
--- /dev/null
+++ b/t/Dh_Lib/00-use.t
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Cwd;
+use Test::More tests => 2;
+
+use File::Temp qw(tempdir);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+
+my $test_dir = tempdir(CLEANUP => 1);
+
+chdir($test_dir);
+
+# Packages that need to be able to (at least) load without requring
+# d/control or d/compat.
+
+use_ok('Debian::Debhelper::Dh_Lib', '!dirname');
+use_ok('Debian::Debhelper::Buildsystem');
diff --git a/t/Dh_Lib/control-parsing.t b/t/Dh_Lib/control-parsing.t
new file mode 100755
index 0000000..0658256
--- /dev/null
+++ b/t/Dh_Lib/control-parsing.t
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+use Cwd qw(abs_path);
+use File::Basename qw(dirname);
+
+BEGIN {
+ my $dir = dirname(abs_path(__FILE__));
+ unshift(@INC, dirname($dir));
+ chdir($dir) or error("chdir($dir) failed: $!");
+};
+
+use Test::DH;
+
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 10);
+
+is_deeply([getpackages()], [qw(foo-any foo-all)], 'packages list correct and in order');
+is_deeply([getpackages('both')], [qw(foo-any foo-all)], 'packages list correct and in order');
+is_deeply([getpackages('arch')], [qw(foo-any)], 'arch:linux-any');
+is_deeply([getpackages('indep')], [qw(foo-all)], 'arch:all');
+
+
+is(package_section('foo-any'), 'devel', 'binary section');
+is(package_section('foo-all'), 'misc', 'binary section (inherit from source)');
+
+
+is(package_declared_arch('foo-any'), 'linux-any', 'binary architecture (linux-any');
+is(package_declared_arch('foo-all'), 'all', 'binary architecture (all)');
+
+ok(! package_is_arch_all('foo-any'), 'foo-any is not arch:all');
+ok(package_is_arch_all('foo-all'), 'foo-all is arch:all');
diff --git a/t/Dh_Lib/debian/changelog b/t/Dh_Lib/debian/changelog
new file mode 100644
index 0000000..5850f0e
--- /dev/null
+++ b/t/Dh_Lib/debian/changelog
@@ -0,0 +1,5 @@
+foo (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <testing@nowhere> Mon, 11 Jul 2016 18:10:59 +0200
diff --git a/t/Dh_Lib/debian/control b/t/Dh_Lib/debian/control
new file mode 100644
index 0000000..b1f0aa3
--- /dev/null
+++ b/t/Dh_Lib/debian/control
@@ -0,0 +1,19 @@
+# Comment before the source field
+
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Standards-Version: 3.9.8
+
+Package: foo-any
+Section: devel
+Architecture: linux-any
+Description: package foo-any
+ Package foo-any
+
+Package: foo-all
+Architecture: all
+Description: package foo-all
+ Package foo-all
+
diff --git a/t/Dh_Lib/path.t b/t/Dh_Lib/path.t
new file mode 100755
index 0000000..e5f9061
--- /dev/null
+++ b/t/Dh_Lib/path.t
@@ -0,0 +1,26 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+use Cwd qw(abs_path);
+use File::Basename qw(dirname);
+use File::Temp qw(tempdir);
+
+BEGIN {
+ my $dir = dirname(abs_path(__FILE__));
+ unshift(@INC, dirname($dir));
+ chdir($dir) or error("chdir($dir) failed: $!");
+};
+
+use Test::DH;
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 3);
+
+ok(!is_empty_dir(__FILE__), "is_empty_dir(file) is false");
+ok(!is_empty_dir(dirname(__FILE__)), "is_empty_dir(non-empty) is false");
+
+my $tempdir = tempdir(CLEANUP => 1);
+ok(is_empty_dir($tempdir), "is_empty_dir(new-temp-dir) is true");
diff --git a/t/Test/DH.pm b/t/Test/DH.pm
new file mode 100644
index 0000000..8d9e71f
--- /dev/null
+++ b/t/Test/DH.pm
@@ -0,0 +1,212 @@
+package Test::DH;
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use Cwd qw(getcwd realpath);
+use Errno qw(EEXIST);
+use Exporter qw(import);
+
+use File::Temp qw(tempdir);
+use File::Path qw(remove_tree make_path);
+use File::Basename qw(dirname);
+
+our $ROOT_DIR;
+
+BEGIN {
+ my $res = realpath(__FILE__) or die('Cannot resolve ' . __FILE__ . ": $!");
+ $ROOT_DIR = dirname(dirname(dirname($res)));
+};
+
+use lib "$ROOT_DIR/lib";
+
+# These should be done before Dh_lib is loaded.
+BEGIN {
+ $ENV{PATH} = "$ROOT_DIR:$ENV{PATH}" if $ENV{PATH} !~ m{\Q$ROOT_DIR\E/?:};
+ $ENV{PERL5LIB} = join(':', "${ROOT_DIR}/lib", (grep {defined} $ENV{PERL5LIB}))
+ if not $ENV{PERL5LIB} or $ENV{PERL5LIB} !~ m{\Q$ROOT_DIR\E(?:/lib)?/?:};
+ $ENV{DH_DATAFILES} = "${ROOT_DIR}/t/fixtures:${ROOT_DIR}";
+ # Nothing in the tests requires root.
+ $ENV{DEB_RULES_REQUIRES_ROOT} = 'no';
+ # Disable colors for good measure
+ $ENV{DH_COLORS} = 'never';
+ $ENV{DPKG_COLORS} = 'never';
+
+ # Drop DEB_BUILD_PROFILES and DEB_BUILD_OPTIONS so they don't interfere
+ delete($ENV{DEB_BUILD_PROFILES});
+ delete($ENV{DEB_BUILD_OPTIONS});
+};
+
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+our @EXPORT = qw(
+ each_compat_up_to_and_incl_subtest each_compat_subtest
+ each_compat_from_and_above_subtest run_dh_tool
+ create_empty_file readlines copy_file
+ error find_script non_deprecated_compat_levels
+ each_compat_from_x_to_and_incl_y_subtest
+);
+
+our ($TEST_DH_COMPAT);
+
+my $START_DIR = getcwd();
+my $TEST_DIR;
+
+
+sub copy_file {
+ my ($src, $dest, $mode) = @_;
+ $mode //= 0644;
+ return Debian::Debhelper::Dh_Lib::_install_file_to_path($mode, $src, $dest);
+}
+
+sub run_dh_tool {
+ my (@cmd) = @_;
+ my $compat = $TEST_DH_COMPAT;
+ my $options = ref($cmd[0]) ? shift(@cmd) : {};
+ my $pid;
+
+ $pid = fork() // BAIL_OUT("fork failed: $!");
+ if (not $pid) {
+ $ENV{DH_COMPAT} = $compat;
+ $ENV{DH_INTERNAL_TESTSUITE_SILENT_WARNINGS} = 1;
+ if (defined(my $env = $options->{env})) {
+ for my $k (sort(keys(%{$env}))) {
+ if (defined($env->{$k})) {
+ $ENV{$k} = $env->{$k};
+ } else {
+ delete($ENV{$k});
+ }
+ }
+ }
+ if ($options->{quiet}) {
+ open(STDOUT, '>', '/dev/null') or error("Reopen stdout: $!");
+ open(STDERR, '>', '/dev/null') or error("Reopen stderr: $!");
+ } else {
+ # If run under prove/TAP, we don't want to confuse the test runner.
+ open(STDOUT, '>&', *STDERR) or error("Redirect stdout to stderr: $!");
+ }
+ exec(@cmd);
+ }
+ waitpid($pid, 0) == $pid or BAIL_OUT("waitpid($pid) failed: $!");
+ return 1 if not $?;
+ return 0;
+}
+
+sub _prepare_test_root {
+ my $dir = tempdir(CLEANUP => 1);
+ if (not mkdir("$dir/debian", 0777)) {
+ error("mkdir $dir/debian failed: $!")
+ if $! != EEXIST;
+ } else {
+ # auto seed it
+ my @files = qw(
+ debian/control
+ debian/compat
+ debian/changelog
+ );
+ for my $file (@files) {
+ copy_file($file, "${dir}/${file}");
+ }
+ if (@::TEST_DH_EXTRA_TEMPLATE_FILES) {
+ my $test_dir = ($TEST_DIR //= dirname($0));
+ my $fixture_dir = $::TEST_DH_FIXTURE_DIR // '.';
+ my $actual_dir = "$test_dir/$fixture_dir";
+ for my $file (@::TEST_DH_EXTRA_TEMPLATE_FILES) {
+ if (index($file, '/') > -1) {
+ my $install_dir = dirname($file);
+ mkdirs($install_dir);
+ }
+ copy_file("${actual_dir}/${file}", "${dir}/${file}");
+ }
+ }
+ }
+ return $dir;
+}
+
+sub each_compat_from_x_to_and_incl_y_subtest($$&) {
+ my ($compat, $high_compat, $code) = @_;
+ my $lowest = Debian::Debhelper::Dh_Lib::MIN_COMPAT_LEVEL;
+ my $highest = Debian::Debhelper::Dh_Lib::MAX_COMPAT_LEVEL;
+ error("compat $compat is no longer support! Min compat $lowest")
+ if $compat < $lowest;
+ error("$high_compat is from the future! Max known is $highest")
+ if $high_compat > $highest;
+ subtest '' => sub {
+ # Keep $dir alive until the test is over
+ my $dir = _prepare_test_root;
+ chdir($dir) or error("chdir($dir): $!");
+ while ($compat <= $high_compat) {
+ local $TEST_DH_COMPAT = $compat;
+ $code->($compat);
+ ++$compat;
+ }
+ chdir($START_DIR) or error("chdir($START_DIR): $!");
+ };
+ return;
+}
+
+sub each_compat_up_to_and_incl_subtest($&) {
+ unshift(@_, Debian::Debhelper::Dh_Lib::MIN_COMPAT_LEVEL);
+ goto \&each_compat_from_x_to_and_incl_y_subtest;
+}
+
+sub each_compat_from_and_above_subtest($&) {
+ splice(@_, 1, 0, Debian::Debhelper::Dh_Lib::MAX_COMPAT_LEVEL);
+ goto \&each_compat_from_x_to_and_incl_y_subtest;
+}
+
+sub each_compat_subtest(&) {
+ unshift(@_,
+ Debian::Debhelper::Dh_Lib::MIN_COMPAT_LEVEL,
+ Debian::Debhelper::Dh_Lib::MAX_COMPAT_LEVEL);
+ goto \&each_compat_from_x_to_and_incl_y_subtest;
+}
+
+sub create_empty_file {
+ my ($file, $chmod) = @_;
+ open(my $fd, '>', $file) or die("open($file): $!\n");
+ close($fd) or die("close($file): $!\n");
+ if (defined($chmod)) {
+ chmod($chmod, $file)
+ or die(sprintf('chmod(%04o, %s): %s', $chmod, $file, $!));
+ }
+ return 1;
+}
+
+sub readlines {
+ my ($h) = @_;
+ my @lines = <$h>;
+ close $h;
+ chop @lines;
+ return \@lines;
+}
+
+# In *inst order (find_script will shuffle them around for *rm order)
+my @SNIPPET_FILE_TEMPLATES = (
+ 'debian/#PACKAGE#.#SCRIPT#.debhelper',
+ 'debian/.debhelper/generated/#PACKAGE#/#SCRIPT#.service',
+);
+
+sub find_script {
+ my ($package, $script) = @_;
+ my @files;
+ for my $template (@SNIPPET_FILE_TEMPLATES) {
+ my $file = ($template =~ s/#PACKAGE#/$package/r);
+ $file =~ s/#SCRIPT#/$script/;
+ push(@files, $file) if -f $file;
+ }
+ if ($script eq 'postrm' or $script eq 'prerm') {
+ @files = reverse(@files);
+ }
+ return @files;
+}
+
+sub non_deprecated_compat_levels() {
+ my $start = Debian::Debhelper::Dh_Lib::LOWEST_NON_DEPRECATED_COMPAT_LEVEL;
+ my $end = Debian::Debhelper::Dh_Lib::MAX_COMPAT_LEVEL;
+ return ($start..$end);
+}
+
+1;
diff --git a/t/buildsystems/01-build-system-basic-api.t b/t/buildsystems/01-build-system-basic-api.t
new file mode 100755
index 0000000..dfb26d2
--- /dev/null
+++ b/t/buildsystems/01-build-system-basic-api.t
@@ -0,0 +1,43 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 12;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+use Debian::Debhelper::Buildsystem;
+
+my $BS_CLASS = 'Debian::Debhelper::Buildsystem';
+
+
+build_system_path_apis();
+
+# Bulk tests
+sub build_system_path_apis {
+ ### Test Buildsystem class API methods
+ is( $BS_CLASS->canonpath("path/to/the/./nowhere/../../somewhere"),
+ "path/to/somewhere", "canonpath no1" );
+ is( $BS_CLASS->canonpath("path/to/../forward/../../somewhere"),
+ "somewhere","canonpath no2" );
+ is( $BS_CLASS->canonpath("path/to/../../../somewhere"),
+ "../somewhere","canonpath no3" );
+ is( $BS_CLASS->canonpath("./"), ".", "canonpath no4" );
+ is( $BS_CLASS->canonpath("/absolute/path/./somewhere/../to/nowhere"),
+ "/absolute/path/to/nowhere", "canonpath no5" );
+ is( $BS_CLASS->_rel2rel("path/my/file", "path/my", "/tmp"),
+ "file", "_rel2rel no1" );
+ is( $BS_CLASS->_rel2rel("path/dir/file", "path/my", "/tmp"),
+ "../dir/file", "_rel2rel no2" );
+ is( $BS_CLASS->_rel2rel("file", "/root/path/my", "/root"),
+ "/root/file", "_rel2rel abs no3" );
+ is( $BS_CLASS->_rel2rel(".", ".", "/tmp"), ".", "_rel2rel no4" );
+ is( $BS_CLASS->_rel2rel("path", "path/", "/tmp"), ".", "_rel2rel no5" );
+ is( $BS_CLASS->_rel2rel("/absolute/path", "anybase", "/tmp"),
+ "/absolute/path", "_rel2rel abs no6");
+ is( $BS_CLASS->_rel2rel("relative/path", "/absolute/base", "/tmp"),
+ "/tmp/relative/path", "_rel2rel abs no7");
+}
+
diff --git a/t/buildsystems/02-make-jobserver-makeflags.t b/t/buildsystems/02-make-jobserver-makeflags.t
new file mode 100755
index 0000000..9469759
--- /dev/null
+++ b/t/buildsystems/02-make-jobserver-makeflags.t
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 9;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+# Test clean_jobserver_makeflags.
+
+test_clean_jobserver_makeflags('--jobserver-fds=103,104 -j',
+ undef,
+ 'unset makeflags');
+
+test_clean_jobserver_makeflags('-a --jobserver-fds=103,104 -j -b',
+ '-a -b',
+ 'clean makeflags');
+
+test_clean_jobserver_makeflags(' --jobserver-fds=1,2 -j ',
+ undef,
+ 'unset makeflags');
+
+test_clean_jobserver_makeflags('-a -j -b',
+ '-a -j -b',
+ 'clean makeflags does not remove -j');
+
+test_clean_jobserver_makeflags('-a --jobs -b',
+ '-a --jobs -b',
+ 'clean makeflags does not remove --jobs');
+
+test_clean_jobserver_makeflags('-j6',
+ '-j6',
+ 'clean makeflags does not remove -j6');
+
+test_clean_jobserver_makeflags('-a -j6 --jobs=7',
+ '-a -j6 --jobs=7',
+ 'clean makeflags does not remove -j or --jobs');
+
+test_clean_jobserver_makeflags('-j6 --jobserver-fds=103,104 --jobs=8',
+ '-j6 --jobs=8',
+ 'jobserver options removed');
+
+test_clean_jobserver_makeflags('-j6 --jobserver-auth=103,104 --jobs=8',
+ '-j6 --jobs=8',
+ 'jobserver options removed');
+
+sub test_clean_jobserver_makeflags {
+ my ($orig, $expected, $test) = @_;
+
+ local $ENV{MAKEFLAGS} = $orig;
+ clean_jobserver_makeflags();
+ is($ENV{MAKEFLAGS}, $expected, $test);
+}
+
diff --git a/t/buildsystems/03-bs-auto-buildable.t b/t/buildsystems/03-bs-auto-buildable.t
new file mode 100755
index 0000000..df4405b
--- /dev/null
+++ b/t/buildsystems/03-bs-auto-buildable.t
@@ -0,0 +1,218 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 187;
+
+use File::Temp qw(tempdir);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+use Debian::Debhelper::Dh_Buildsystems;
+
+my @STEPS = qw(configure build test install clean);
+
+### Test check_auto_buildable() of each buildsystem
+sub test_check_auto_buildable {
+ my ($bs, $config, $expected) = @_;
+
+ if (! ref $expected) {
+ my %all_steps;
+ $all_steps{$_} = $expected foreach (@STEPS);
+ $expected = \%all_steps;
+ }
+ for my $step (@STEPS) {
+ my $e = 0;
+ if (exists $expected->{$step}) {
+ $e = $expected->{$step};
+ } elsif (exists $expected->{default}) {
+ $e = $expected->{default};
+ }
+ is( $bs->check_auto_buildable($step), $e,
+ $bs->NAME() . "($config): check_auto_buildable($step) == $e" );
+ }
+}
+
+sub test_autoselection {
+ my ($testname, $expected, %args) = @_;
+ for my $step (@STEPS) {
+ my $bs = load_buildsystem({'enable-thirdparty' => 0}, $step, @_);
+ my $e = $expected;
+ $e = $expected->{$step} if ref $expected;
+ if (defined $bs) {
+ is( $bs->NAME(), $e, "autoselection($testname): $step=".((defined $e)?$e:'undef') );
+ }
+ else {
+ is ( undef, $e, "autoselection($testname): $step=".((defined $e)?$e:'undef') );
+ }
+ &{$args{"code_$step"}}() if exists $args{"code_$step"};
+ }
+}
+
+my $TEMP_DIR = tempdir('tmp.XXXXXXX', CLEANUP => 1);
+my $sourcedir = "${TEMP_DIR}/source";
+my $builddir = "${TEMP_DIR}/build";
+my %options = (
+ 'builddir' => $builddir,
+ 'sourcedir' => $sourcedir,
+);
+make_path($sourcedir, $builddir);
+use Config;
+my $libpath = $ENV{AUTOPKGTEST_TMP} ? $Config{vendorlib} : "$Test::DH::ROOT_DIR/lib";
+my @bs = load_all_buildsystems([ $libpath ], %options);
+my %bs;
+my @names = map { $_->NAME() } @bs;
+
+ok(@Debian::Debhelper::Dh_Buildsystems::BUILDSYSTEMS >= 1, "some build systems are built in" );
+is_deeply( \@names, \@Debian::Debhelper::Dh_Buildsystems::BUILDSYSTEMS, "load_all_buildsystems() loads all built-in buildsystems" );
+
+# check_auto_buildable() fails with numeric 0
+for my $bs (@bs) {
+ test_check_auto_buildable($bs, "fails with numeric 0", 0);
+ $bs{$bs->NAME()} = $bs;
+}
+
+run_auto_buildable_tests();
+
+remove_tree($sourcedir, $builddir);
+make_path($sourcedir, $builddir);
+
+run_autoselection_tests();
+
+
+#### Bulk of test code ####
+
+sub run_auto_buildable_tests {
+ create_empty_file("${sourcedir}/configure", 0755);
+ test_check_auto_buildable($bs{autoconf}, "configure", { configure => 1, clean => 1 });
+ rm_files("${sourcedir}/configure");
+
+ create_empty_file("${sourcedir}/CMakeLists.txt");
+ test_check_auto_buildable($bs{'cmake+makefile'}, "CMakeLists.txt", { configure => 1, clean => 1 });
+ rm_files("${sourcedir}/CMakeLists.txt");
+
+ create_empty_file("${sourcedir}/Makefile.PL");
+ test_check_auto_buildable($bs{perl_makemaker}, "Makefile.PL", { configure => 1 });
+ rm_files("${sourcedir}/Makefile.PL");
+
+ create_empty_file("${sourcedir}/meson.build");
+ test_check_auto_buildable($bs{'meson+ninja'}, "meson.build", { configure => 1, clean => 1 });
+ # Leave meson.build
+
+ create_empty_file("${builddir}/build.ninja");
+ test_check_auto_buildable($bs{ninja}, "build.ninja", { configure => 1, build => 1, clean => 1, install => 1, test => 1 });
+ # Leave ninja.build
+
+ # Meson + ninja
+ test_check_auto_buildable($bs{'meson+ninja'}, "meson.build+build.ninja", { configure => 1, build => 1, clean => 1, install => 1, test => 1 });
+ rm_files("${sourcedir}/meson.build", "${builddir}/build.ninja");
+
+ # With Makefile
+ create_empty_file("$builddir/Makefile");
+ test_check_auto_buildable($bs{makefile}, "Makefile", 1);
+
+ # ... +autoconf
+ create_empty_file("${sourcedir}/configure", 0755);
+ test_check_auto_buildable($bs{autoconf}, "configure+Makefile", { configure => 1, test => 1, build => 1, install => 1, clean => 1 });
+ rm_files("${sourcedir}/configure");
+
+ # ... +cmake
+ create_empty_file("${sourcedir}/CMakeLists.txt");
+ test_check_auto_buildable($bs{'cmake+makefile'}, "CMakeLists.txt+Makefile", 1);
+ create_empty_file("$builddir/CMakeCache.txt"); # strong evidence that cmake was run
+ test_check_auto_buildable($bs{'cmake+makefile'}, "CMakeCache.txt+Makefile", 2);
+ rm_files("${builddir}/Makefile", "${sourcedir}/CMakeLists.txt");
+
+ # Makefile.PL forces in-source
+ #(see note in check_auto_buildable() why always 1 here)
+ create_empty_file("${sourcedir}/Makefile.PL");
+ create_empty_file("${sourcedir}/Makefile");
+ test_check_auto_buildable($bs{perl_makemaker}, "Makefile.PL+Makefile", 1);
+ rm_files("${sourcedir}/Makefile.PL", "${sourcedir}/Makefile");
+
+ # Perl Build.PL - handles always
+ test_check_auto_buildable($bs{perl_build}, "no Build.PL", 0);
+ create_empty_file("${sourcedir}/Build.PL");
+ test_check_auto_buildable($bs{perl_build}, "Build.PL", { configure => 1 });
+ create_empty_file("${sourcedir}/Build"); # forced in source
+ test_check_auto_buildable($bs{perl_build}, "Build.PL+Build", 1);
+ rm_files("${sourcedir}/Build.PL", "${sourcedir}/Build");
+
+ # Python Distutils
+ test_check_auto_buildable($bs{python_distutils}, "no setup.py", 0);
+ create_empty_file("${sourcedir}/setup.py");
+ test_check_auto_buildable($bs{python_distutils}, "setup.py", 1);
+ rm_files("${sourcedir}/setup.py");
+}
+
+sub run_autoselection_tests {
+ # Auto-select nothing when no supported build system can be found
+ # (see #557006).
+ test_autoselection("auto-selects nothing", undef, %options);
+
+ # Autoconf
+ create_empty_file("${sourcedir}/configure", 0755);
+ create_empty_file("${builddir}/Makefile");
+ test_autoselection("autoconf",
+ { configure => "autoconf", build => "autoconf",
+ test => "autoconf", install => "autoconf",
+ clean => "autoconf"
+ }, %options);
+ rm_files("${sourcedir}/configure", "${builddir}/Makefile");
+
+
+ # Perl Makemaker (build, test, clean fail with builddir set [not supported])
+ create_empty_file("${sourcedir}/Makefile.PL");
+ create_empty_file("${sourcedir}/Makefile");
+ test_autoselection("perl_makemaker", "perl_makemaker", %options);
+ rm_files("${sourcedir}/Makefile.PL", "${sourcedir}/Makefile");
+
+
+ # Makefile
+ create_empty_file("$builddir/Makefile");
+ test_autoselection("makefile", "makefile", %options);
+ rm_files("$builddir/Makefile");
+
+ # Python Distutils
+ create_empty_file("${sourcedir}/setup.py");
+ test_autoselection("python_distutils", "python_distutils", %options);
+ rm_files("${sourcedir}/setup.py");
+
+ # Perl Build
+ create_empty_file("${sourcedir}/Build.PL");
+ create_empty_file("${sourcedir}/Build");
+ test_autoselection("perl_build", "perl_build", %options);
+ rm_files("${sourcedir}/Build.PL", "${sourcedir}/Build");
+
+ # CMake
+ create_empty_file("${sourcedir}/CMakeLists.txt");
+ test_autoselection("cmake without CMakeCache.txt",
+ { configure => "cmake+makefile", build => "makefile",
+ test => "makefile", install => "makefile",
+ clean => "makefile"
+ },
+ %options,
+ code_configure => sub {
+ create_empty_file("$builddir/Makefile");
+ });
+ rm_files("${sourcedir}/CMakeLists.txt", "$builddir/Makefile");
+
+ create_empty_file("${sourcedir}/CMakeLists.txt");
+ test_autoselection("cmake with CMakeCache.txt",
+ "cmake+makefile",
+ %options,
+ code_configure => sub {
+ create_empty_file("$builddir/Makefile");
+ create_empty_file("$builddir/CMakeCache.txt");
+ });
+ rm_files("${sourcedir}/CMakeLists.txt", "$builddir/Makefile", "$builddir/CMakeCache.txt");
+
+ create_empty_file("${sourcedir}/CMakeLists.txt");
+ create_empty_file("$builddir/Makefile");
+ test_autoselection("cmake and existing Makefile", "makefile", %options);
+ rm_files("${sourcedir}/CMakeLists.txt", "$builddir/Makefile");
+
+};
+
diff --git a/t/buildsystems/04-dh_auto_do_autoconf.t b/t/buildsystems/04-dh_auto_do_autoconf.t
new file mode 100755
index 0000000..a1346e4
--- /dev/null
+++ b/t/buildsystems/04-dh_auto_do_autoconf.t
@@ -0,0 +1,88 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 31;
+
+use File::Temp qw(tempdir);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+use Debian::Debhelper::Dh_Buildsystems;
+
+# 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);
+
+$Test::DH::TEST_DH_COMPAT = 10;
+
+# Build the autoconf test package
+sub dh_auto_do_autoconf {
+ my ($sourcedir, $builddir, %args) = @_;
+
+ my (@lines, @extra_args);
+ my $buildpath = $sourcedir;
+ my @dh_auto_args = ("-D", $sourcedir);
+ my $dh_auto_str = "-D $sourcedir";
+ if ($builddir) {
+ push @dh_auto_args, "-B", $builddir;
+ $dh_auto_str .= " -B $builddir";
+ $buildpath = $builddir;
+ }
+
+ my $do_dh_auto = sub {
+ my ($step) = @_;
+ my @extra_args;
+ my $extra_str = "";
+ if (exists $args{"${step}_args"}) {
+ push @extra_args, @{$args{"${step}_args"}};
+ $extra_str .= " $_" foreach (@extra_args);
+ }
+ ok(run_dh_tool({ 'quiet' => 1 }, "dh_auto_${step}", @dh_auto_args, '--', @extra_args),
+ "dh_auto_$step $dh_auto_str$extra_str");
+ return @extra_args;
+ };
+
+ @extra_args = &$do_dh_auto('configure');
+ ok ( -f "$buildpath/Makefile", "$buildpath/Makefile exists" );
+ @lines=();
+ if ( ok(open(FILE, '<', "$buildpath/stamp_configure"), "$buildpath/stamp_configure exists") ) {
+ @lines = @{readlines(\*FILE)};
+ close(FILE);
+ }
+ is_deeply( \@lines, \@extra_args, "$buildpath/stamp_configure contains extra args" );
+
+ &$do_dh_auto('build');
+ ok ( -f "$buildpath/stamp_build", "$buildpath/stamp_build exists" );
+ &$do_dh_auto('test');
+ @lines=();
+ if ( ok(open(FILE, '<', "$buildpath/stamp_test"), "$buildpath/stamp_test exists") ) {
+ @lines = @{readlines(\*FILE)};
+ close(FILE);
+ }
+ is_deeply( \@lines, [ "VERBOSE=1" ],
+ "$buildpath/stamp_test contains VERBOSE=1" );
+ &$do_dh_auto('install');
+ @lines=();
+ if ( ok(open(FILE, '<', "$buildpath/stamp_install"), "$buildpath/stamp_install exists") ) {
+ @lines = @{readlines(\*FILE)};
+ close(FILE);
+ }
+ is_deeply( \@lines, [ "DESTDIR=".Cwd::getcwd()."/debian/testpackage" ],
+ "$buildpath/stamp_install contains DESTDIR" );
+ &$do_dh_auto('clean');
+ if ($builddir) {
+ ok ( ! -e "$buildpath", "builddir $buildpath was removed" );
+ }
+ else {
+ ok ( ! -e "$buildpath/Makefile" && ! -e "$buildpath/stamp_configure", "Makefile and stamps gone" );
+ }
+ ok ( -x "$sourcedir/configure", "configure script renamins after clean" );
+}
+
+dh_auto_do_autoconf('autoconf');
+dh_auto_do_autoconf('autoconf', 'bld/dir', configure_args => [ "--extra-autoconf-configure-arg" ]);
+ok ( ! -e 'bld', "bld got deleted too" );
+
diff --git a/t/buildsystems/05-load-build-system.t b/t/buildsystems/05-load-build-system.t
new file mode 100755
index 0000000..23aed9f
--- /dev/null
+++ b/t/buildsystems/05-load-build-system.t
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Cwd;
+use Test::More tests => 3;
+
+use File::Temp qw(tempdir);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+use Debian::Debhelper::Buildsystem;
+
+my $DIR = dirname($0);
+my $SCRIPT = './load-bs.pl'; # relative to $DIR
+my $BS_CWD = Cwd::realpath($DIR) or error("cannot resolve ${DIR}: $!");
+my $BS_CLASS = 'Debian::Debhelper::Buildsystem';
+my $bs = $BS_CLASS->new();
+my $default_builddir = $bs->DEFAULT_BUILD_DIRECTORY();
+delete($ENV{'TEST_DH_SYSTEM'});
+delete($ENV{'TEST_DH_STEP'});
+
+# NOTE: disabling parallel building explicitly (it might get automatically
+# enabled if run under dpkg-buildpackage -jX) to make output deterministic.
+is_deeply( try_load_bs(undef, 'configure', '--builddirectory=autoconf/bld dir', '--sourcedirectory',
+ 'autoconf', '--max-parallel=1'),
+ [ 'NAME=autoconf', 'builddir=autoconf/bld dir', "cwd=$BS_CWD", 'makecmd=make', 'parallel=1', 'sourcedir=autoconf' ],
+ "autoconf autoselection and sourcedir/builddir" );
+
+is_deeply( try_load_bs('autoconf', 'build', '-Sautoconf', '-D', 'autoconf', '--max-parallel=1'),
+ [ 'NAME=autoconf', 'builddir=undef', "cwd=$BS_CWD", 'makecmd=make', 'parallel=1', 'sourcedir=autoconf' ],
+ "forced autoconf and sourcedir" );
+
+is_deeply( try_load_bs('autoconf', 'build', '-B', '-Sautoconf', '--max-parallel=1'),
+ [ 'NAME=autoconf', "builddir=$default_builddir", "cwd=$BS_CWD", 'makecmd=make', 'parallel=1', 'sourcedir=.' ],
+ "forced autoconf and default build directory" );
+
+sub try_load_bs {
+ my ($system, $step, @params) = @_;
+ my @lines;
+ my $pid = open(my $fd, '-|') // die("fork: $!");
+
+ if (not $pid) {
+ chdir($DIR) or die("chdir($DIR): $!");
+ $ENV{'TEST_DH_SYSTEM'} = $system if defined($system);
+ $ENV{'TEST_DH_STEP'} = $step if defined($step);
+ exec($^X, $SCRIPT, @params);
+ }
+ @lines = map { chomp; $_ } <$fd>;
+ close($fd); # Ignore error
+ return \@lines;
+}
+
diff --git a/t/buildsystems/06-buildsystem-mkdir-rmdir.t b/t/buildsystems/06-buildsystem-mkdir-rmdir.t
new file mode 100755
index 0000000..c13b253
--- /dev/null
+++ b/t/buildsystems/06-buildsystem-mkdir-rmdir.t
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Cwd;
+use Test::More tests => 6;
+
+use File::Temp qw(tempdir);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use File::Path qw(make_path);
+use Test::DH;
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+use Debian::Debhelper::Buildsystem;
+
+chdir(dirname($0)) or die("chdir: $!");
+my $TEMP_DIR = tempdir('tmp.XXXXXXX', CLEANUP => 1);
+my $sourcedir = $TEMP_DIR;
+my $builddir = "${TEMP_DIR}/build";
+my $BS_CLASS = 'Debian::Debhelper::Buildsystem';
+
+# Tests
+
+do_rmdir_builddir($sourcedir, $builddir);
+ok ( ! -e $builddir, "testing rmdir_builddir() 1: builddir parent '$builddir' deleted" );
+ok ( -d $sourcedir, "testing rmdir_builddir() 1: sourcedir '$sourcedir' remains" );
+
+$builddir = "$sourcedir/bld";
+do_rmdir_builddir($sourcedir, "$builddir/dir");
+ok ( ! -e $builddir, "testing rmdir_builddir() 2: builddir parent '$builddir' deleted" );
+ok ( -d $sourcedir, "testing rmdir_builddir() 2: sourcedir '$sourcedir' remains" );
+
+$builddir = "$sourcedir/bld";
+
+make_path($builddir, "$builddir/dir");
+create_empty_file("$builddir/afile");
+create_empty_file("$builddir/dir/afile2");
+do_rmdir_builddir($sourcedir, "$builddir/dir");
+ok ( ! -e "$builddir/dir", "testing rmdir_builddir() 3: builddir '$builddir/dir' not empty, but deleted" );
+ok ( -d $builddir, "testing rmdir_builddir() 3: builddir parent '$builddir' not empty, remains" );
+
+
+### Test Buildsystem::rmdir_builddir()
+sub do_rmdir_builddir {
+ my ($sourcedir, $builddir) = @_;
+ my $system;
+ $system = $BS_CLASS->new(builddir => $builddir, sourcedir => $sourcedir);
+ $system->mkdir_builddir();
+ $system->rmdir_builddir();
+}
+
diff --git a/t/buildsystems/autoconf/configure b/t/buildsystems/autoconf/configure
new file mode 100755
index 0000000..5039783
--- /dev/null
+++ b/t/buildsystems/autoconf/configure
@@ -0,0 +1,84 @@
+#!/usr/bin/perl
+
+# Emulate autoconf behaviour and do some checks
+
+use strict;
+use warnings;
+
+my @OPTIONS=qw(
+ ^--build=.*$
+ ^--prefix=/usr$
+ ^--includedir=\$\{prefix\}/include$
+ ^--mandir=\$\{prefix\}/share/man$
+ ^--infodir=\$\{prefix\}/share/info$
+ ^--sysconfdir=/etc$
+ ^--localstatedir=/var$
+ ^--libdir=\$\{prefix\}/lib/.*$
+ ^--disable-option-checking$
+ ^--disable-silent-rules$
+ ^--disable-maintainer-mode$
+ ^--disable-dependency-tracking$
+);
+
+# Not always passed (e.g. --libexecdir is skipped in compat 12)
+my @OPTIONAL_ARGUMENTS = qw(
+ ^--libexecdir=\$\{prefix\}/lib/.*$
+);
+
+# Verify if all command line arguments were passed
+my @options = map { { regex => qr/$_/,
+ str => $_,
+ found => 0 } } @OPTIONS;
+push(@options, map { { regex => qr/$_/,
+ str => $_,
+ found => 1 } } @OPTIONAL_ARGUMENTS);
+my @extra_args;
+ARGV_LOOP: foreach my $arg (@ARGV) {
+ foreach my $opt (@options) {
+ if ($arg =~ $opt->{regex}) {
+ $opt->{found} = 1;
+ next ARGV_LOOP;
+ }
+ }
+ # Extra / unrecognized argument
+ push @extra_args, $arg;
+}
+
+my @notfound = grep { ! $_->{found} and $_ } @options;
+if (@notfound) {
+ print STDERR "Error: the following default options were NOT passed\n";
+ print STDERR " ", $_->{str}, "\n" foreach (@notfound);
+ exit 1;
+}
+
+# Create a simple Makefile
+open(MAKEFILE, ">", "Makefile");
+print MAKEFILE <<EOF;
+CONFIGURE := $0
+all: stamp_configure \$(CONFIGURE)
+ \@echo Package built > stamp_build
+
+# Tests if dh_auto_test executes 'check' target if 'test' does not exist
+check: \$(CONFIGURE) stamp_build
+ \@echo VERBOSE=\$(VERBOSE) > stamp_test
+
+install: stamp_build
+ \@echo DESTDIR=\$(DESTDIR) > stamp_install
+
+# Tests whether dh_auto_clean executes distclean but does not touch
+# this target
+clean:
+ echo "This should not have been executed" >&2 && exit 1
+
+distclean:
+ \@rm -f stamp_* Makefile
+
+.PHONY: all check install clean distclean
+EOF
+close MAKEFILE;
+
+open(STAMP, ">", "stamp_configure");
+print STAMP $_, "\n" foreach (@extra_args);
+close STAMP;
+
+exit 0;
diff --git a/t/buildsystems/buildsystem_tests.t b/t/buildsystems/buildsystem_tests.t
new file mode 100755
index 0000000..eaa2380
--- /dev/null
+++ b/t/buildsystems/buildsystem_tests.t
@@ -0,0 +1,293 @@
+#!/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");
+}
diff --git a/t/buildsystems/debian/changelog b/t/buildsystems/debian/changelog
new file mode 100644
index 0000000..f902d89
--- /dev/null
+++ b/t/buildsystems/debian/changelog
@@ -0,0 +1,5 @@
+testpackage (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <testing@nowhere> Tue, 09 Jun 2009 15:35:32 +0300
diff --git a/t/buildsystems/debian/control b/t/buildsystems/debian/control
new file mode 100644
index 0000000..9c3134b
--- /dev/null
+++ b/t/buildsystems/debian/control
@@ -0,0 +1,12 @@
+Source: testsrcpackage
+Section: devel
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Standards-Version: 3.8.1
+Build-Depends: debhelper-compat (= 12)
+Rules-Requires-Root: no
+
+Package: testpackage
+Architecture: all
+Description: short description
+ Long description
diff --git a/t/buildsystems/load-bs.pl b/t/buildsystems/load-bs.pl
new file mode 100755
index 0000000..aeb5ea8
--- /dev/null
+++ b/t/buildsystems/load-bs.pl
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Debian::Debhelper::Dh_Buildsystems;
+
+buildsystems_init();
+my $system = $ENV{'TEST_DH_SYSTEM'};
+my $step = $ENV{'TEST_DH_STEP'};
+my $bs = load_buildsystem($system, $step);
+if (defined $bs) {
+ print 'NAME=', $bs->NAME(), "\n";
+ print $_, "=", (defined $bs->{$_}) ? $bs->{$_} : 'undef', "\n"
+ foreach (sort keys %$bs);
+}
+
diff --git a/t/buildsystems/parallel.mk b/t/buildsystems/parallel.mk
new file mode 100644
index 0000000..3e0d201
--- /dev/null
+++ b/t/buildsystems/parallel.mk
@@ -0,0 +1,21 @@
+all: FIRST SECOND
+
+TMPFILE ?= $(CURDIR)/parallel.mk.lock
+
+rmtmpfile:
+ @rm -f "$(TMPFILE)"
+
+FIRST: rmtmpfile
+ @c=0; \
+ while [ $$c -le 5 ] && \
+ ([ ! -e "$(TMPFILE)" ] || [ "`cat "$(TMPFILE)"`" != "SECOND" ]); do \
+ c=$$(($$c+1)); \
+ sleep 0.1; \
+ done; \
+ rm -f "$(TMPFILE)"; \
+ if [ $$c -gt 5 ]; then exit 10; else exit 0; fi
+
+SECOND: rmtmpfile
+ @echo $@ > "$(TMPFILE)"
+
+.PHONY: all FIRST SECOND rmtmpfile
diff --git a/t/debhelper-compat/debian/control b/t/debhelper-compat/debian/control
new file mode 100644
index 0000000..1f18f4d
--- /dev/null
+++ b/t/debhelper-compat/debian/control
@@ -0,0 +1,12 @@
+Source: foo
+Section: misc
+Priority: optional
+Build-Depends: BUILD_DEPENDS
+Maintainer: Test <testing@nowhere>
+Rules-Requires-Root: no
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
diff --git a/t/debhelper-compat/syntax.t b/t/debhelper-compat/syntax.t
new file mode 100755
index 0000000..7fe0307
--- /dev/null
+++ b/t/debhelper-compat/syntax.t
@@ -0,0 +1,82 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use File::Temp qw(tempdir);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+my $TEST_DIR = dirname(__FILE__);
+
+sub test_build_depends {
+ my ($level, $build_depends) = @_;
+ my $dir = tempdir(CLEANUP => 1);
+ if (not mkdir("$dir/debian", 0777)) {
+ error("mkdir $dir/debian failed: $!");
+ }
+ open my $in, '<', "$TEST_DIR/debian/control" or
+ error("open $TEST_DIR/debian/control failed: $!");
+ open my $out, '>', "$dir/debian/control" or
+ error("open $dir/debian/control failed: $!");
+ while (<$in>) {
+ s/BUILD_DEPENDS/$build_depends/;
+ print $out $_ or
+ error("write to $dir/debian/control failed: $!");
+ }
+ close($out) or
+ error("close $dir/debian/control failed: $!");
+ close($in);
+
+ my $start_dir = Test::DH::getcwd();
+ chdir($dir) or error("chdir($dir): $!");
+
+ plan(tests => 5);
+
+ local $ENV{DH_INTERNAL_TESTSUITE_SILENT_WARNINGS} = 1;
+ Debian::Debhelper::Dh_Lib::resetpackages;
+ Debian::Debhelper::Dh_Lib::resetcompat;
+ my @pkgs = getpackages;
+ ok(scalar @pkgs == 1);
+ ok($pkgs[0] eq 'foo');
+
+ ok(compat($level));
+ ok(compat($level + 1));
+ ok(!compat($level - 1));
+
+ chdir($start_dir) or
+ error("chdir($start_dir): $!");
+}
+
+my @levels = non_deprecated_compat_levels;
+plan(tests => scalar @levels);
+
+for my $level (@levels) {
+ subtest "compat $level" => sub {
+ plan(tests => 7);
+ subtest 'only' => sub {
+ test_build_depends($level, "debhelper-compat (= $level)");
+ };
+ subtest 'first' => sub {
+ test_build_depends($level, "debhelper-compat (= $level), bar");
+ };
+ subtest 'second' => sub {
+ test_build_depends($level, "bar, debhelper-compat (= $level)");
+ };
+ subtest 'first-nl' => sub {
+ test_build_depends($level, "debhelper-compat (= $level),\n bar");
+ };
+ subtest 'second-nl' => sub {
+ test_build_depends($level, "bar,\n debhelper-compat (= $level)");
+ };
+ subtest 'nl-first' => sub {
+ test_build_depends($level, "\n debhelper-compat (= $level),\n bar");
+ };
+ subtest 'nl-second' => sub {
+ test_build_depends($level, "\n bar,\n debhelper-compat (= $level)");
+ };
+ };
+}
diff --git a/t/dh-lib.t b/t/dh-lib.t
new file mode 100755
index 0000000..6e6f3cb
--- /dev/null
+++ b/t/dh-lib.t
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+package Debian::Debhelper::Dh_Lib::Test;
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(__FILE__);
+use Test::DH;
+
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 2);
+
+
+sub ok_autoscript_result {
+ ok(-f 'debian/testpackage.postinst.debhelper');
+ open(my $fd, '<', 'debian/testpackage.postinst.debhelper') or die("open test-poinst: $!");
+ my (@c) = <$fd>;
+ close($fd);
+ like(join('',@c), qr{update-rc\.d test-script test parms with"quote >/dev/null});
+}
+
+
+each_compat_subtest {
+
+ ok(autoscript('testpackage', 'postinst', 'postinst-init',
+ 's/#SCRIPT#/test-script/g; s/#INITPARMS#/test parms with\\"quote/g'));
+ ok_autoscript_result;
+
+ ok(rm_files('debian/testpackage.postinst.debhelper'));
+
+ ok(autoscript('testpackage', 'postinst', 'postinst-init',
+ sub { s/\#SCRIPT\#/test-script/g; s/\#INITPARMS\#/test parms with"quote/g } ));
+ ok_autoscript_result;
+
+ ok(rm_files('debian/testpackage.postinst.debhelper'));
+
+ ok(autoscript('testpackage', 'postinst', 'postinst-init',
+ { 'SCRIPT' => 'test-script', 'INITPARMS' => 'test parms with"quote' } ));
+ ok_autoscript_result;
+
+ ok(rm_files('debian/testpackage.postinst.debhelper'));
+};
+
+$ENV{'FOO'} = "test";
+my @SUBST_TEST_OK = (
+ ['unchanged', 'unchanged'],
+ ["unchanged\${\n}", "unchanged\${\n}"], # Newline is not an allowed part of ${}
+ ['raw dollar-sign ${}', 'raw dollar-sign $'],
+ ['${Dollar}${Space}${Dollar}', '$ $'],
+ ['Hello ${env:FOO}', 'Hello test'],
+ ['${Dollar}{Space}${}{Space}', '${Space}${Space}'], # We promise that ${Dollar}/${} never cause recursion
+ ['/usr/lib/${DEB_HOST_MULTIARCH}', '/usr/lib/' . dpkg_architecture_value('DEB_HOST_MULTIARCH')],
+);
+
+each_compat_subtest {
+ for my $test (@SUBST_TEST_OK) {
+ my ($input, $expected_output) = @{$test};
+ my $actual_output = Debian::Debhelper::Dh_Lib::_variable_substitution($input, 'test');
+ is($actual_output, $expected_output, qq{${input}" => "${actual_output}" (should be: "${expected_output})"});
+ }
+};
diff --git a/t/dh-sequencer.t b/t/dh-sequencer.t
new file mode 100755
index 0000000..f864386
--- /dev/null
+++ b/t/dh-sequencer.t
@@ -0,0 +1,282 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+use Debian::Debhelper::Sequence;
+use Debian::Debhelper::SequencerUtil;
+
+# Shorten variants of the sequences.
+my @bd = (qw{
+ dh_testdir
+ dh_auto_configure
+ dh_auto_build
+ dh_auto_test
+});
+my @i = (qw{
+ dh_testroot
+ dh_prep
+ dh_auto_install
+
+ dh_install
+ dh_missing
+});
+my @ba = (
+ {
+ 'command' => 'dh_strip',
+ 'command-options' => [],
+ 'sequence-limitation' => SEQUENCE_TYPE_ARCH_ONLY,
+ },
+ {
+ 'command' => 'dh_makeshlibs',
+ 'command-options' => [],
+ 'sequence-limitation' => SEQUENCE_TYPE_ARCH_ONLY,
+ },
+ {
+ 'command' => 'dh_shlibdeps',
+ 'command-options' => [],
+ 'sequence-limitation' => SEQUENCE_TYPE_ARCH_ONLY,
+ }
+);
+my @b=qw{
+ dh_installdeb
+ dh_gencontrol
+ dh_builddeb
+};
+my @c=qw{
+ dh_testdir
+ dh_auto_clean
+ dh_clean
+};
+
+my %sequences;
+
+sub _add_sequence {
+ my @args = @_;
+ my $seq = Debian::Debhelper::Sequence->new(@args);
+ my $name = $seq->name;
+ $sequences{$name} = $seq;
+ if ($seq->allowed_subsequences eq SEQUENCE_ARCH_INDEP_SUBSEQUENCES) {
+ for my $subseq ((SEQUENCE_TYPE_ARCH_ONLY, SEQUENCE_TYPE_INDEP_ONLY)) {
+ my $subname = "${name}-${subseq}";
+ $sequences{$subname} = $seq;
+ }
+ }
+ return;
+}
+
+_add_sequence('build', SEQUENCE_ARCH_INDEP_SUBSEQUENCES, @bd);
+_add_sequence('install', SEQUENCE_ARCH_INDEP_SUBSEQUENCES, to_rules_target("build"), @i);
+_add_sequence('binary', SEQUENCE_ARCH_INDEP_SUBSEQUENCES, to_rules_target("install"), @ba, @b);
+_add_sequence('clean', SEQUENCE_NO_SUBSEQUENCES, @c);
+
+sub _cmd_names {
+ my (@input) = @_;
+ my @cmds;
+ for my $cmd (@input) {
+ if (ref($cmd) eq 'HASH') {
+ push(@cmds, $cmd->{'command'});
+ } else {
+ push(@cmds, $cmd);
+ }
+ }
+ return \@cmds;
+}
+
+my %sequences_unpacked = (
+ 'build-indep' => _cmd_names(@bd),
+ 'build-arch' => _cmd_names(@bd),
+ 'build' => _cmd_names(@bd),
+
+ 'install-indep' => _cmd_names(@bd, @i),
+ 'install-arch' => _cmd_names(@bd, @i),
+ 'install' => _cmd_names(@bd, @i),
+
+ 'binary-indep' => _cmd_names(@bd, @i, @b),
+ 'binary-arch' => _cmd_names(@bd, @i, @ba, @b),
+ 'binary' => _cmd_names(@bd, @i, @ba, @b),
+
+ 'clean' => _cmd_names(@c),
+);
+
+plan tests => 21 + 3 * scalar(keys(%sequences));
+
+# We will horse around with %EXPLICIT_TARGETS in this test; it should
+# definitely not attempt to read d/rules or the test will be break.
+$Debian::Debhelper::SequencerUtil::RULES_PARSED = 1;
+
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'build')],
+ [[], $sequences_unpacked{'build'}],
+ 'Inlined build sequence matches build-indep/build-arch');
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'install')],
+ [[], $sequences_unpacked{'install'}],
+ 'Inlined install sequence matches build-indep/build-arch + install commands');
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'binary-arch')],
+ [[], $sequences_unpacked{'binary-arch'}],
+ 'Inlined binary-arch sequence has all the commands');
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'binary-indep')],
+ [[], $sequences_unpacked{'binary-indep'}],
+ 'Inlined binary-indep sequence has all the commands except @bd');
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'binary')],
+ [[], $sequences_unpacked{'binary'}],
+ 'Inlined binary sequence has all the commands');
+
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'binary', 0, { 'build' => 1, 'build-arch' => 1, 'build-indep' => 1})],
+ [[], _cmd_names(@i, @ba, @b)],
+ 'Inlined binary sequence with build-* done has @i, @ba and @b');
+
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'binary', 0, { 'build-arch' => 1, 'build-indep' => 1})],
+ [[], _cmd_names(@i, @ba, @b)],
+ 'Inlined binary sequence with build-* done has @i, @ba and @b');
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'binary', 0, {}, 0)],
+ [[], _cmd_names(@bd, @i, @ba, @b)],
+ 'Inlined binary sequence and arch:all + arch:any is reduced to @bd, @i, @ba and @b');
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'binary', 0, {}, FLAG_OPT_SOURCE_BUILDS_NO_ARCH_PACKAGES)],
+ [[], _cmd_names(@bd, @i, @b)],
+ 'Inlined binary sequence and not arch:any is reduced to @bd, @i and @b');
+
+is_deeply(
+ [unpack_sequence(\%sequences, 'binary', 0, {}, FLAG_OPT_SOURCE_BUILDS_NO_INDEP_PACKAGES)],
+ [[], _cmd_names(@bd, @i, @ba, @b)],
+ 'Inlined binary sequence and not arch:all is reduced to @bd, @i, @ba and @b');
+
+
+{
+ local $Debian::Debhelper::SequencerUtil::EXPLICIT_TARGETS{'build-arch'} = 1;
+ local $Debian::Debhelper::SequencerUtil::EXPLICIT_TARGETS{'build-indep'} = 1;
+
+ is_deeply(
+ [unpack_sequence(\%sequences, 'binary', 0, { 'build-arch' => 1, 'build-indep' => 1})],
+ [[], _cmd_names(@i, @ba, @b)],
+ 'Inlined binary sequence with build-* done has @i, @ba and @b');
+ my $actual = [unpack_sequence(\%sequences, 'binary')];
+ # @i should be "-i"-only, @ba + @b should be both.
+ # Unfortunately, unpack_sequence cannot show that.
+ my $expected = [[to_rules_target('build-arch'), to_rules_target('build-indep')], _cmd_names(@i, @ba, @b)];
+ # Permit some fuzz on the order between build-arch and build-arch
+ if ($actual->[0][0] eq to_rules_target('build-indep')) {
+ $expected->[0][0] = to_rules_target('build-indep');
+ $expected->[0][1] = to_rules_target('build-arch');
+ }
+ is_deeply(
+ $actual,
+ $expected,
+ 'Inlined binary sequence with explicit build-* has explicit d/rules build-{arch,indep} + @i, @ba, @b');
+
+ is_deeply(
+ [unpack_sequence(\%sequences, 'binary', 0, { 'build' => 1})],
+ [[], _cmd_names(@i, @ba, @b)],
+ 'Inlined binary sequence with explicit build-* but done build has only @i, @ba and @b');
+}
+
+{
+ local $Debian::Debhelper::SequencerUtil::EXPLICIT_TARGETS{'build-indep'} = 1;
+ is_deeply(
+ [ unpack_sequence(\%sequences, 'binary', 0, { 'build-arch' => 1 }) ],
+ [ [to_rules_target('build-indep')], _cmd_names(@i, @ba, @b) ],
+ 'Inlined binary sequence with build-arch done and build-indep explicit has d/rules build-indep + @i, @ba and @b');
+
+ is_deeply(
+ [ unpack_sequence(\%sequences, 'binary-arch', 0, { 'build-arch' => 1 }) ],
+ [ [], _cmd_names(@i, @ba, @b) ],
+ 'Inlined binary-arch sequence with build-arch done and build-indep explicit has @i, @ba and @b');
+
+
+ is_deeply(
+ [ unpack_sequence(\%sequences, 'binary-indep', 0, { 'build-arch' => 1 }) ],
+ [ [to_rules_target('build-indep')], _cmd_names(@i, @b) ],
+ 'Inlined binary-indep sequence with build-arch done and build-indep explicit has d/rules build-indep + @i and @b');
+}
+
+{
+ local $Debian::Debhelper::SequencerUtil::EXPLICIT_TARGETS{'build'} = 1;
+
+ is_deeply(
+ [unpack_sequence(\%sequences, 'binary')],
+ [[to_rules_target('build')], _cmd_names(@i, @ba, @b)],
+ 'Inlined binary sequence has all the commands but build target is opaque');
+
+ is_deeply(
+ [unpack_sequence(\%sequences, 'binary', 0, { 'build' => 1, 'build-arch' => 1, 'build-indep' => 1})],
+ [[], _cmd_names(@i, @ba, @b)],
+ 'Inlined binary sequence has all the commands with build-* done and not build-target');
+
+ is_deeply(
+ [unpack_sequence(\%sequences, 'build')],
+ [[], $sequences_unpacked{'build'}],
+ 'build sequence is inlineable');
+
+
+ # Compat <= 8 ignores explicit targets!
+ for my $seq_name (sort(keys(%sequences))) {
+ is_deeply(
+ [unpack_sequence(\%sequences, $seq_name, 1)],
+ [[], $sequences_unpacked{$seq_name}],
+ "Compat <= 8 ignores explicit build target in sequence ${seq_name}");
+ }
+}
+
+{
+ local $Debian::Debhelper::SequencerUtil::EXPLICIT_TARGETS{'install-arch'} = 1;
+
+ is_deeply(
+ [unpack_sequence(\%sequences, 'binary')],
+ # @bd_minimal, @bd and @i should be "-i"-only, @ba + @b should be both.
+ # Unfortunately, unpack_sequence cannot show that.
+ [[to_rules_target('install-arch')], _cmd_names(@bd, @i, @ba, @b)],
+ 'Inlined binary sequence has all the commands');
+
+ # Compat <= 8 ignores explicit targets!
+ for my $seq_name (sort(keys(%sequences))) {
+ is_deeply(
+ [unpack_sequence(\%sequences, $seq_name, 1)],
+ [[], $sequences_unpacked{$seq_name}],
+ "Compat <= 8 ignores explicit install-arch target in sequence ${seq_name}");
+ }
+}
+
+{
+ local $Debian::Debhelper::SequencerUtil::EXPLICIT_TARGETS{'install-arch'} = 1;
+ local $Debian::Debhelper::SequencerUtil::EXPLICIT_TARGETS{'build'} = 1;
+
+ my $actual = [unpack_sequence(\%sequences, 'binary')];
+ # @i should be "-i"-only, @ba + @b should be both.
+ # Unfortunately, unpack_sequence cannot show that.
+ my $expected = [[to_rules_target('build'), to_rules_target('install-arch')], _cmd_names(@i, @ba, @b)];
+ # Permit some fuzz on the order between build and install-arch
+ if ($actual->[0][0] eq to_rules_target('install-arch')) {
+ $expected->[0][0] = to_rules_target('install-arch');
+ $expected->[0][1] = to_rules_target('build');
+ }
+ is_deeply(
+ $actual,
+ $expected,
+ 'Inlined binary sequence has all the commands');
+
+ # Compat <= 8 ignores explicit targets!
+ for my $seq_name (sort(keys(%sequences))) {
+ is_deeply(
+ [unpack_sequence(\%sequences, $seq_name, 1)],
+ [[], $sequences_unpacked{$seq_name}],
+ "Compat <= 8 ignores explicit build + install-arch targets in sequence ${seq_name}");
+ }
+}
diff --git a/t/dh_compress.t b/t/dh_compress.t
new file mode 100755
index 0000000..1eb6156
--- /dev/null
+++ b/t/dh_compress.t
@@ -0,0 +1,114 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use File::Basename qw(dirname);
+use lib dirname(__FILE__);
+use Test::DH;
+
+use File::Path qw(make_path remove_tree);
+use Test::More;
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+my $PREFIX = 'debian/debhelper/usr/share/doc/debhelper';
+
+plan tests => 2;
+
+each_compat_subtest {
+ # we are testing compressing doc txt files
+ # foo.txt is 2k and bar.txt is 5k
+ mk_test_dir();
+
+ # default operation, bar.txt becomes bar.txt.gz and foo.txt is
+ # unchanged
+ ok(run_dh_tool('dh_compress'));
+
+ is_deeply(
+ [map { s{${PREFIX}/}{}; $_ } sort glob "$PREFIX/*"],
+ [qw|bar.txt.gz foo.txt|],
+ '5k txt doc compressed, 2k txt doc not compressed'
+ );
+
+ mk_test_dir();
+
+ # now if I want to pass both on the command line to dh_compress,
+ # it should compress both
+ ok(run_dh_tool('dh_compress', '--',
+ 'usr/share/doc/debhelper/foo.txt',
+ 'usr/share/doc/debhelper/bar.txt'));
+
+ is_deeply(
+ [map { s{${PREFIX}/}{}; $_ } sort glob "$PREFIX/*"],
+ [qw|bar.txt.gz foo.txt.gz|],
+ 'both 5k and 2k txt docs compressed'
+ );
+
+ mk_test_dir();
+
+ # absolute paths should also work
+ ok(run_dh_tool('dh_compress', '--',
+ '/usr/share/doc/debhelper/foo.txt',
+ '/usr/share/doc/debhelper/bar.txt'));
+
+ is_deeply(
+ [map { s{${PREFIX}/}{}; $_ } sort glob "$PREFIX/*"],
+ [qw|bar.txt.gz foo.txt.gz|],
+ 'both 5k and 2k txt docs compressed by absolute path args'
+ );
+
+ rm_test_dir();
+
+ mk_test_dir();
+
+ is(system('cp', '-la', "${PREFIX}/bar.txt", "${PREFIX}/hardlink.txt"), 0,
+ 'create hardlink');
+
+ ok(run_dh_tool('dh_compress'));
+
+ is_deeply(
+ [map { s{${PREFIX}/}{}; $_ } sort glob "$PREFIX/*"],
+ [qw|bar.txt.gz foo.txt hardlink.txt.gz|],
+ 'the 5k and its hardlink txt docs compressed'
+ );
+
+ # Verify that the hardlink is preserved.
+ my ($dev1, $inode1) = stat("${PREFIX}/bar.txt.gz") // error("stat ${PREFIX}/bar.txt.gz: $!");
+ my ($dev2, $inode2) = stat("${PREFIX}/hardlink.txt.gz") // error("stat ${PREFIX}/hardlink.txt.gz: $!");
+
+ is($dev1, $dev2, 'Still hardlinked');
+ is($inode1, $inode2, 'Still hardlinked');
+
+ rm_test_dir();
+};
+
+each_compat_from_and_above_subtest(12, sub {
+ make_path("${PREFIX}/examples");
+ create_file_of_size("${PREFIX}/examples/foo.py", 5120);
+ ok(run_dh_tool('dh_compress'));
+ ok(-f "${PREFIX}/examples/foo.py", "${PREFIX}/examples/foo.py is not compressed");
+ ok(! -f "${PREFIX}/examples/foo.py.gz", "${PREFIX}/examples/foo.py is not compressed");
+});
+
+sub create_file_of_size {
+ my ($filename, $size) = @_;
+ open(my $fh, '>', $filename) or error("open($filename) failed: $!");
+ print {$fh} 'X' x $size;
+ close($fh) or error("close($filename) failed: $!");
+}
+
+sub mk_test_dir {
+ rm_test_dir();
+
+ make_path($PREFIX);
+
+ create_file_of_size("${PREFIX}/foo.txt", 2048);
+ create_file_of_size("${PREFIX}/bar.txt", 5120);
+}
+
+sub rm_test_dir {
+ remove_tree('debian/debhelper');
+
+ rm_files('debian/debhelper.debhelper.log');
+}
+
diff --git a/t/dh_install/01-basics.t b/t/dh_install/01-basics.t
new file mode 100755
index 0000000..b266196
--- /dev/null
+++ b/t/dh_install/01-basics.t
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 2);
+
+
+each_compat_subtest {
+ my ($compat) = @_;
+ # regular specification of file not in debian/tmp
+ create_empty_file('dh_install');
+ ok(run_dh_tool('dh_install', 'dh_install', 'usr/bin'));
+ ok(-e "debian/debhelper/usr/bin/dh_install");
+ remove_tree('debian/debhelper', 'debian/tmp');
+};
+
+each_compat_subtest {
+ my ($compat) = @_;
+ # specification of file in subdir, not in debian/tmp
+ make_path('bar/usr/bin');
+ create_empty_file('bar/usr/bin/foo');
+ ok(run_dh_tool('dh_install', 'bar/usr/bin/foo'));
+ ok(-e "debian/debhelper/bar/usr/bin/foo");
+ remove_tree('debian/debhelper', 'debian/tmp');
+};
+
diff --git a/t/dh_install/02-bugs-53XXXX.t b/t/dh_install/02-bugs-53XXXX.t
new file mode 100755
index 0000000..1c42489
--- /dev/null
+++ b/t/dh_install/02-bugs-53XXXX.t
@@ -0,0 +1,65 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 4);
+
+each_compat_from_and_above_subtest(7, sub {
+ my ($compat) = @_;
+ # #537140: debian/tmp is explcitly specified despite being searched by
+ # default in v7+
+
+ make_path('debian/tmp/usr/bin');
+ create_empty_file('debian/tmp/usr/bin/foo');
+ create_empty_file('debian/tmp/usr/bin/bar');
+ ok(run_dh_tool('dh_install', 'debian/tmp/usr/bin/foo'));
+ ok(-e "debian/debhelper/usr/bin/foo", "#537140 [${compat}]");
+ ok(! -e "debian/debhelper/usr/bin/bar", "#537140 [${compat}]");
+ remove_tree('debian/debhelper', 'debian/tmp');
+});
+
+each_compat_from_and_above_subtest(7, sub {
+ my ($compat) = @_;
+ # #534565: glob expands to dangling symlink -> should install the dangling link
+ make_path('debian/tmp/usr/bin');
+ make_symlink_raw_target('broken', 'debian/tmp/usr/bin/foo');
+ create_empty_file('debian/tmp/usr/bin/bar');
+ ok(run_dh_tool('dh_install', 'usr/bin/*'));
+ ok(-l "debian/debhelper/usr/bin/foo", "#534565 [${compat}]");
+ ok(!-e "debian/debhelper/usr/bin/foo", "#534565 [${compat}]");
+ ok(-e "debian/debhelper/usr/bin/bar", "#534565 [${compat}]");
+ ok(!-l "debian/debhelper/usr/bin/bar", "#534565 [${compat}]");
+ remove_tree('debian/debhelper', 'debian/tmp');
+});
+
+each_compat_subtest {
+ my ($compat) = @_;
+ # #537017: --sourcedir=debian/tmp/foo is used
+ make_path('debian/tmp/foo/usr/bin');
+ create_empty_file('debian/tmp/foo/usr/bin/foo');
+ create_empty_file('debian/tmp/foo/usr/bin/bar');
+ ok(run_dh_tool('dh_install', '--sourcedir=debian/tmp/foo', 'usr/bin/bar'));
+ ok(-e "debian/debhelper/usr/bin/bar", "#537017 [${compat}]");
+ ok(!-e "debian/debhelper/usr/bin/foo", "#537017 [${compat}]");
+ remove_tree('debian/debhelper', 'debian/tmp');
+};
+
+each_compat_from_and_above_subtest(7, sub {
+ my ($compat) = @_;
+ # #535367: installation of entire top-level directory from debian/tmp
+ make_path('debian/tmp/usr/bin');
+ create_empty_file('debian/tmp/usr/bin/foo');
+ create_empty_file('debian/tmp/usr/bin/bar');
+ ok(run_dh_tool('dh_install', 'usr'));
+ ok(-e "debian/debhelper/usr/bin/foo", "#535367 [${compat}]");
+ ok(-e "debian/debhelper/usr/bin/bar", "#535367 [${compat}]");
+ remove_tree('debian/debhelper', 'debian/tmp');
+});
+
diff --git a/t/dh_install/03-866570-dont-install-from-host.t b/t/dh_install/03-866570-dont-install-from-host.t
new file mode 100755
index 0000000..92345d1
--- /dev/null
+++ b/t/dh_install/03-866570-dont-install-from-host.t
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 1);
+
+each_compat_subtest {
+ my ($compat) = @_;
+ # #866570 - leading slashes must *not* pull things from the root FS.
+ make_path('bin');
+ create_empty_file('bin/grep-i-licious');
+ ok(run_dh_tool('dh_install', '/bin/grep*'));
+ ok(-e "debian/debhelper/bin/grep-i-licious", "#866570 [${compat}]");
+ ok(!-e "debian/debhelper/bin/grep", "#866570 [${compat}]");
+ remove_tree('debian/debhelper', 'debian/tmp');
+};
+
diff --git a/t/dh_install/04-sourcedir.t b/t/dh_install/04-sourcedir.t
new file mode 100755
index 0000000..83bd006
--- /dev/null
+++ b/t/dh_install/04-sourcedir.t
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 3);
+
+
+each_compat_subtest {
+ my ($compat) = @_;
+ # redundant --sourcedir=debian/tmp in v7+
+ make_path('debian/tmp/usr/bin');
+ create_empty_file('debian/tmp/usr/bin/foo');
+ create_empty_file('debian/tmp/usr/bin/bar');
+ ok(run_dh_tool('dh_install', '--sourcedir=debian/tmp', 'usr/bin/foo'));
+ ok(-e "debian/debhelper/usr/bin/foo");
+ ok(! -e "debian/debhelper/usr/bin/bar");
+ remove_tree('debian/debhelper', 'debian/tmp');
+};
+
+each_compat_subtest {
+ my ($compat) = @_;
+ # #534565: fallback use of debian/tmp in v7+
+ make_path('debian/tmp/usr/bin');
+ create_empty_file('debian/tmp/usr/bin/foo');
+ create_empty_file('debian/tmp/usr/bin/bar');
+ ok(run_dh_tool('dh_install', 'usr'));
+ ok(-e "debian/debhelper/usr/bin/foo", "#534565 [${compat}]");
+ ok(-e "debian/debhelper/usr/bin/bar", "#534565 [${compat}]");
+ remove_tree('debian/debhelper', 'debian/tmp');
+};
+
+each_compat_subtest {
+ my ($compat) = @_;
+ # specification of file in source directory not in debian/tmp
+ make_path('bar/usr/bin');
+ create_empty_file('bar/usr/bin/foo');
+ ok(run_dh_tool('dh_install', '--sourcedir=bar', 'usr/bin/foo'));
+ ok(-e "debian/debhelper/usr/bin/foo");
+ remove_tree('debian/debhelper', 'debian/tmp');
+};
diff --git a/t/dh_installchangelogs/debian/control b/t/dh_installchangelogs/debian/control
new file mode 100644
index 0000000..4f4238e
--- /dev/null
+++ b/t/dh_installchangelogs/debian/control
@@ -0,0 +1,10 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
diff --git a/t/dh_installchangelogs/dh_installchangelogs.t b/t/dh_installchangelogs/dh_installchangelogs.t
new file mode 100755
index 0000000..989a552
--- /dev/null
+++ b/t/dh_installchangelogs/dh_installchangelogs.t
@@ -0,0 +1,252 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use POSIX qw(locale_h);
+use Test::More;
+use Time::Piece;
+use Time::Seconds qw(ONE_MONTH ONE_YEAR);
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+
+use constant TEST_DIR => dirname($0);
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+));
+
+# Force Time::Piece to generate dch-compliant timestamps (i.e. in English).
+setlocale(LC_ALL, "C.UTF-8");
+
+use constant CUTOFF_DATE_STR => "2019-07-06"; # oldstable = Debian 10 Buster
+use constant CUTOFF_DATE => Time::Piece->strptime(CUTOFF_DATE_STR, "%Y-%m-%d");
+use constant MIN_NUM_ENTRIES => 4;
+
+sub install_changelog {
+ my ($latest_offset_years, $num_years, $is_binnmu) = @_;
+ $is_binnmu //= 0;
+
+ my $entry_date_first = CUTOFF_DATE->add_years($latest_offset_years);
+ my $entry_date_stop = $entry_date_first->add_years(-$num_years);
+
+ my $changelog = "${\TEST_DIR}/debian/changelog";
+
+ open(my $fd, ">", $changelog) or error("open($changelog): $!");
+
+ if ($is_binnmu) {
+ my $nmu_date = $entry_date_first->add_months(-1);
+ my $nmu_entry = entry_text($nmu_date, 1);
+ print($fd $nmu_entry);
+ }
+
+ # Add one entry every three months ~= four per year.
+ my $entry_date = $entry_date_first;
+ while ($entry_date > $entry_date_stop) {
+ my $entry = entry_text($entry_date, 0);
+ print($fd $entry);
+
+ $entry_date = $entry_date->add_months(-3);
+ }
+ close($fd);
+}
+
+sub entry_text {
+ my ($entry_date, $is_binnmu) = @_;
+ my $entry_date_str = $entry_date->strftime("%a, %d %b %Y %T %z");
+ my $ver = $entry_date->year . "." . $entry_date->mon . "-1";
+ my $binnmu_text = "";
+
+ if ($is_binnmu) {
+ $binnmu_text = " binary-only=yes";
+ $ver .= "+b1";
+ }
+
+ my $entry = "";
+ $entry .= "foo ($ver) unstable; urgency=low$binnmu_text\n\n";
+ $entry .= " * New release.\n\n";
+ $entry .= " -- Test <testing\@nowhere> $entry_date_str\n\n";
+
+ return $entry;
+}
+
+sub changelog_lines_pkg {
+ return changelog_lines("debian/changelog");
+}
+sub changelog_lines_installed {
+ return changelog_lines("debian/foo/usr/share/doc/foo/changelog.Debian");
+}
+sub changelog_lines_binnmu {
+ return changelog_lines("debian/foo/usr/share/doc/foo/changelog.Debian.all");
+}
+sub changelog_lines {
+ my ($changelog) = @_;
+ open(my $fd, $changelog) or error("open($changelog): $!");
+ my @lines = @{readlines($fd)};
+ @lines = grep(!/^$/, @lines);
+ return @lines;
+}
+
+sub dates_in_lines {
+ my @lines = @_;
+ my @lines_dates = grep(/^ -- /, @lines);
+ @lines_dates = map { (my $l = $_) =~ s/^\s*--\s+.*?\s+<[^>]*>\s+[A-Za-z]+, +//; $l } @lines_dates;
+ @lines_dates = map { Time::Piece->strptime($_, "%d %b %Y %T %z") } @lines_dates;
+ return @lines_dates;
+}
+
+plan(tests => 8);
+
+# Test changelog with only recent entries (< oldstable)
+my $years_after_cutoff = 2;
+my $years_of_changelog = 2;
+install_changelog($years_after_cutoff, $years_of_changelog);
+each_compat_subtest {
+ my @lines_orig = changelog_lines_pkg();
+ ok(run_dh_tool("dh_installchangelogs"));
+ my @lines = changelog_lines_installed();
+ my @comments = grep(/^#/, @lines);
+
+ is(@lines, @lines_orig);
+ is(@comments, 0);
+};
+
+# Test changelog with both recent and old entries
+$years_after_cutoff = 1;
+$years_of_changelog = 4;
+install_changelog($years_after_cutoff, $years_of_changelog);
+each_compat_subtest {
+ my @lines_orig = changelog_lines_pkg();
+ ok(run_dh_tool("dh_installchangelogs"));
+ my @lines = changelog_lines_installed();
+ my @entries = dates_in_lines(@lines);
+ my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+ my @comments = grep(/^#/, @lines);
+
+ cmp_ok(@lines, "<", @lines_orig);
+ cmp_ok(@entries, ">", 1);
+ is(@entries_old, 0);
+ cmp_ok(@comments, ">=", 1);
+};
+
+# Test changelog with only old entries
+$years_after_cutoff = -1;
+$years_of_changelog = 2;
+install_changelog($years_after_cutoff, $years_of_changelog);
+each_compat_subtest {
+ my @lines_orig = changelog_lines_pkg();
+ ok(run_dh_tool("dh_installchangelogs"));
+ my @lines = changelog_lines_installed();
+ my @entries = dates_in_lines(@lines);
+ my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+ my @comments = grep(/^#/, @lines);
+
+ cmp_ok(@lines, "<", @lines_orig);
+ is(@entries, MIN_NUM_ENTRIES);
+ is(@entries_old, MIN_NUM_ENTRIES);
+ cmp_ok(@comments, ">=", 1);
+};
+
+# Test changelog with only recent entries (< oldstable) + binNUM
+$years_after_cutoff = 2;
+$years_of_changelog = 2;
+install_changelog($years_after_cutoff, $years_of_changelog, 1);
+each_compat_subtest {
+ my @lines_orig = changelog_lines_pkg();
+ my @entries_orig = dates_in_lines(@lines_orig);
+ ok(run_dh_tool("dh_installchangelogs"));
+ my @lines = changelog_lines_installed();
+ my @entries = dates_in_lines(@lines);
+ my @entries_nmu = dates_in_lines(changelog_lines_binnmu());
+ my @comments = grep(/^#/, @lines);
+
+ is(@entries, @entries_orig-1);
+ is($entries[0], $entries_orig[1]);
+ is(@comments, 0);
+
+ is(@entries_nmu, 1);
+};
+
+# Test changelog with both recent and old entries + binNMU
+$years_after_cutoff = 1;
+$years_of_changelog = 4;
+install_changelog($years_after_cutoff, $years_of_changelog, 1);
+each_compat_subtest {
+ my @lines_orig = changelog_lines_pkg();
+ my @entries_orig = dates_in_lines(@lines_orig);
+ ok(run_dh_tool("dh_installchangelogs"));
+ my @lines = changelog_lines_installed();
+ my @entries = dates_in_lines(@lines);
+ my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+ my @entries_nmu = dates_in_lines(changelog_lines_binnmu());
+ my @comments = grep(/^#/, @lines);
+
+ cmp_ok(@entries, "<", @entries_orig-1);
+ is($entries[0], $entries_orig[1]);
+ is(@entries_old, 0);
+ cmp_ok(@comments, ">=", 1);
+
+ is(@entries_nmu, 1);
+};
+
+# Test changelog with only old entries + binNMU
+$years_after_cutoff = -1;
+$years_of_changelog = 2;
+install_changelog($years_after_cutoff, $years_of_changelog, 1);
+each_compat_subtest {
+ my @lines_orig = changelog_lines_pkg();
+ my @entries_orig = dates_in_lines(@lines_orig);
+ ok(run_dh_tool("dh_installchangelogs"));
+ my @lines = changelog_lines_installed();
+ my @entries = dates_in_lines(@lines);
+ my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+ my @entries_nmu = dates_in_lines(changelog_lines_binnmu());
+ my @comments = grep(/^#/, @lines);
+
+ is(@entries, MIN_NUM_ENTRIES);
+ is($entries[0], $entries_orig[1]);
+ is(@entries_old, MIN_NUM_ENTRIES);
+ cmp_ok(@comments, ">=", 1);
+
+ is(@entries_nmu, 1);
+};
+
+# Test changelog with both recent and old entries + --no-trim
+$years_after_cutoff = 1;
+$years_of_changelog = 4;
+install_changelog($years_after_cutoff, $years_of_changelog);
+each_compat_subtest {
+ my @lines_orig = changelog_lines_pkg();
+ ok(run_dh_tool("dh_installchangelogs", "--no-trim"));
+ my @lines = changelog_lines_installed();
+ my @entries = dates_in_lines(@lines);
+ my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+ my @comments = grep(/^#/, @lines);
+
+ is(@lines, @lines_orig);
+ cmp_ok(@entries, ">", 1);
+ cmp_ok(@entries_old, ">", 1);
+ is(@comments, 0);
+};
+
+# Test changelog with both recent and old entries + notrimdch
+$years_after_cutoff = 1;
+$years_of_changelog = 4;
+install_changelog($years_after_cutoff, $years_of_changelog);
+each_compat_subtest {
+ my @lines_orig = changelog_lines_pkg();
+ $ENV{DEB_BUILD_OPTIONS} = "notrimdch";
+ ok(run_dh_tool("dh_installchangelogs"));
+ my @lines = changelog_lines_installed();
+ my @entries = dates_in_lines(@lines);
+ my @entries_old = grep { $_ < CUTOFF_DATE } @entries;
+ my @comments = grep(/^#/, @lines);
+
+ is(@lines, @lines_orig);
+ cmp_ok(@entries, ">", 1);
+ cmp_ok(@entries_old, ">", 1);
+ is(@comments, 0);
+};
+
+unlink("${\TEST_DIR}/debian/changelog");
diff --git a/t/dh_installdocs/01-868204-ignore-broken-symlinks.t b/t/dh_installdocs/01-868204-ignore-broken-symlinks.t
new file mode 100755
index 0000000..b9801d2
--- /dev/null
+++ b/t/dh_installdocs/01-868204-ignore-broken-symlinks.t
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 1);
+
+each_compat_subtest {
+ my ($compat) = @_;
+ # #868204 - dh_installdocs did not replace dangling symlink
+ make_path('debian/debhelper/usr/share/doc/debhelper');
+ make_symlink_raw_target('../to/nowhere/bar',
+ 'debian/debhelper/usr/share/doc/debhelper/README.Debian');
+ create_empty_file('debian/README.Debian');
+
+ ok(-l 'debian/debhelper/usr/share/doc/debhelper/README.Debian');
+ ok(!-e 'debian/debhelper/usr/share/doc/debhelper/README.Debian');
+
+ ok(run_dh_tool('dh_installdocs'));
+ ok(!-l 'debian/debhelper/usr/share/doc/debhelper/README.Debian', "#868204 [${compat}]");
+ ok(-f 'debian/debhelper/usr/share/doc/debhelper/README.Debian', "#868204 [${compat}]");
+ remove_tree('debian/debhelper', 'debian/tmp');
+};
+
diff --git a/t/dh_installdocs/debian/changelog b/t/dh_installdocs/debian/changelog
new file mode 100644
index 0000000..5850f0e
--- /dev/null
+++ b/t/dh_installdocs/debian/changelog
@@ -0,0 +1,5 @@
+foo (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <testing@nowhere> Mon, 11 Jul 2016 18:10:59 +0200
diff --git a/t/dh_installdocs/debian/control b/t/dh_installdocs/debian/control
new file mode 100644
index 0000000..7e9a228
--- /dev/null
+++ b/t/dh_installdocs/debian/control
@@ -0,0 +1,21 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Rules-Requires-Root: no
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
+
+Package: bar
+Architecture: all
+Description: package bar
+ Package bar
+
+Package: baz
+Architecture: all
+Description: package baz
+ Package baz
diff --git a/t/dh_installdocs/debian/copyright b/t/dh_installdocs/debian/copyright
new file mode 100644
index 0000000..7e6ffb0
--- /dev/null
+++ b/t/dh_installdocs/debian/copyright
@@ -0,0 +1 @@
+Some test copyright file
diff --git a/t/dh_installdocs/debian/docfile b/t/dh_installdocs/debian/docfile
new file mode 100644
index 0000000..2719eec
--- /dev/null
+++ b/t/dh_installdocs/debian/docfile
@@ -0,0 +1 @@
+This file must not be empty, or dh_installdocs won't install it.
diff --git a/t/dh_installdocs/dh_installdocs.t b/t/dh_installdocs/dh_installdocs.t
new file mode 100755
index 0000000..64f1254
--- /dev/null
+++ b/t/dh_installdocs/dh_installdocs.t
@@ -0,0 +1,75 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+
+use File::Path qw(remove_tree);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/docfile
+ debian/copyright
+));
+
+plan(tests => 5);
+
+
+my $NODOC_PROFILE = {
+ 'env' => {
+ 'DEB_BUILD_PROFILES' => 'nodoc',
+ },
+};
+
+my $doc = "debian/docfile";
+
+each_compat_subtest {
+ ok(run_dh_tool('dh_installdocs', '-pbar', $doc));
+ ok(-e "debian/bar/usr/share/doc/bar/docfile");
+ remove_tree(qw(debian/foo debian/bar debian/baz));
+};
+
+each_compat_subtest {
+ #regression in debhelper 9.20160702 (#830309)
+ ok(run_dh_tool('dh_installdocs', '-pbaz', '--link-doc=foo', $doc));
+
+ ok(-l "debian/baz/usr/share/doc/baz");
+ ok(readlink("debian/baz/usr/share/doc/baz") eq 'foo');
+ ok(-e "debian/baz/usr/share/doc/foo/docfile");
+ remove_tree(qw(debian/foo debian/bar debian/baz));
+};
+
+each_compat_subtest {
+ ok(run_dh_tool('dh_installdocs', '-pfoo', '--link-doc=bar', $doc));
+
+ ok(-l "debian/foo/usr/share/doc/foo");
+ ok(readlink("debian/foo/usr/share/doc/foo") eq 'bar');
+ ok(-e "debian/foo/usr/share/doc/bar/docfile");
+ remove_tree(qw(debian/foo debian/bar debian/baz));
+};
+
+# ... and with nodoc
+
+each_compat_subtest {
+ # docs are ignored, but copyright file is still there
+ ok(run_dh_tool($NODOC_PROFILE, 'dh_installdocs', $doc));
+ for my $pkg (qw(foo bar baz)) {
+ ok(! -e "debian/$pkg/usr/share/doc/$pkg/docfile");
+ ok(-e "debian/$pkg/usr/share/doc/$pkg/copyright");
+ }
+ remove_tree(qw(debian/foo debian/bar debian/baz));
+};
+
+each_compat_subtest {
+ # docs are ignored, but symlinked doc dir is still there
+ ok(run_dh_tool($NODOC_PROFILE, 'dh_installdocs', '-pfoo', '--link-doc=bar', $doc));
+ ok(-l "debian/foo/usr/share/doc/foo");
+ ok(readlink("debian/foo/usr/share/doc/foo") eq 'bar');
+ ok(! -e "debian/foo/usr/share/doc/bar/docfile");
+ remove_tree(qw(debian/foo debian/bar debian/baz));
+};
+
diff --git a/t/dh_installgsettings/debian/changelog b/t/dh_installgsettings/debian/changelog
new file mode 100644
index 0000000..5850f0e
--- /dev/null
+++ b/t/dh_installgsettings/debian/changelog
@@ -0,0 +1,5 @@
+foo (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <testing@nowhere> Mon, 11 Jul 2016 18:10:59 +0200
diff --git a/t/dh_installgsettings/debian/control b/t/dh_installgsettings/debian/control
new file mode 100644
index 0000000..c3cb827
--- /dev/null
+++ b/t/dh_installgsettings/debian/control
@@ -0,0 +1,17 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Standards-Version: 3.9.8
+
+Package: has-settings
+Architecture: all
+Depends: ${misc:Depends}
+Description: package has-settings
+ This package has a GSettings schema.
+
+Package: no-settings
+Architecture: all
+Depends: ${misc:Depends}
+Description: package no-settings
+ This package doesn't have a GSettings schema.
diff --git a/t/dh_installgsettings/dh_installgsettings.t b/t/dh_installgsettings/dh_installgsettings.t
new file mode 100755
index 0000000..fe76e8d
--- /dev/null
+++ b/t/dh_installgsettings/dh_installgsettings.t
@@ -0,0 +1,49 @@
+#!/usr/bin/perl
+use strict;
+use Test::More tests => 1;
+
+use autodie;
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+));
+
+my $SCHEMAS = 'usr/share/glib-2.0/schemas';
+
+sub touch {
+ my $path = shift;
+ open(my $fh, '>>', $path);
+ close $fh;
+}
+
+sub slurp {
+ my $path = shift;
+ local $/ = undef;
+ open(my $fh, '<', $path);
+ my $contents = <$fh>;
+ close $fh;
+ return $contents;
+}
+
+each_compat_subtest {
+ make_path("debian/has-settings/$SCHEMAS");
+ touch("debian/has-settings/$SCHEMAS/com.example.HasSettings.xml");
+ make_path("debian/has-unimportant-settings/$SCHEMAS");
+ touch("debian/no-settings.substvars");
+ ok(run_dh_tool('dh_installgsettings', '-phas-settings'), 'run for has-settings');
+ ok(run_dh_tool('dh_installgsettings', '-pno-settings'), 'run for no-settings');
+ remove_tree(qw(debian/has-settings debian/has-unimportant-settings));
+ like(slurp('debian/has-settings.substvars'),
+ qr{^misc:Depends=dconf-gsettings-backend \| gsettings-backend$}m,
+ 'has-settings should depend on a backend');
+ unlike(slurp('debian/no-settings.substvars'),
+ qr{^misc:Depends=dconf-gsettings-backend \| gsettings-backend$}m,
+ 'no-settings should not depend on a backend');
+};
+
diff --git a/t/dh_installinit/debian/bar.other.init b/t/dh_installinit/debian/bar.other.init
new file mode 100644
index 0000000..ea948c5
--- /dev/null
+++ b/t/dh_installinit/debian/bar.other.init
@@ -0,0 +1,4 @@
+#!/bin/sh
+cat << 'EOF'
+I am init script to be installed into package "bar" into /etc/init.d/other path.
+EOF \ No newline at end of file
diff --git a/t/dh_installinit/debian/changelog b/t/dh_installinit/debian/changelog
new file mode 100644
index 0000000..5850f0e
--- /dev/null
+++ b/t/dh_installinit/debian/changelog
@@ -0,0 +1,5 @@
+foo (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <testing@nowhere> Mon, 11 Jul 2016 18:10:59 +0200
diff --git a/t/dh_installinit/debian/control b/t/dh_installinit/debian/control
new file mode 100644
index 0000000..48d4de2
--- /dev/null
+++ b/t/dh_installinit/debian/control
@@ -0,0 +1,20 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
+
+Package: bar
+Architecture: all
+Description: package bar
+ Package bar
+
+Package: baz
+Architecture: all
+Description: package baz
+ Package baz
diff --git a/t/dh_installinit/debian/foo.service b/t/dh_installinit/debian/foo.service
new file mode 100644
index 0000000..aa21636
--- /dev/null
+++ b/t/dh_installinit/debian/foo.service
@@ -0,0 +1,5 @@
+[Unit]
+Description=A unit
+
+[Service]
+ExecStart=/bin/true
diff --git a/t/dh_installinit/dh_installinit.t b/t/dh_installinit/dh_installinit.t
new file mode 100755
index 0000000..7a7817c
--- /dev/null
+++ b/t/dh_installinit/dh_installinit.t
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+use strict;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.service
+ debian/bar.other.init
+));
+
+plan(tests => 2);
+
+each_compat_up_to_and_incl_subtest(10, sub {
+ make_path(qw(debian/foo debian/bar debian/baz));
+ ok(run_dh_tool('dh_installinit'));
+ ok(-e "debian/foo/lib/systemd/system/foo.service");
+ ok(find_script('foo', 'postinst'));
+ ok(run_dh_tool('dh_clean'));
+
+});
+
+each_compat_from_and_above_subtest(11, sub {
+ make_path(qw(debian/foo debian/bar debian/baz));
+
+ ok(run_dh_tool('dh_installinit'));
+ ok(run_dh_tool({'quiet' => 1}, 'dh_installinit', '--name=other'));
+ ok(! -e "debian/foo/lib/systemd/system/foo.service");
+ ok(!find_script('foo', 'postinst'));
+ ok(run_dh_tool('dh_clean'));
+
+ make_path(qw(debian/foo/lib/systemd/system/ debian/bar debian/baz));
+ copy_file('debian/foo.service', 'debian/foo/lib/systemd/system/foo.service');
+ ok(run_dh_tool('dh_installinit'));
+ ok(!find_script('foo', 'postinst'));
+ ok(run_dh_tool('dh_clean'));
+});
+
diff --git a/t/dh_installman/01-basics.t b/t/dh_installman/01-basics.t
new file mode 100755
index 0000000..141059a
--- /dev/null
+++ b/t/dh_installman/01-basics.t
@@ -0,0 +1,94 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+sub has_man_db_tool {
+ my ($tool) = @_;
+ open(my $old_stderr, '>&', *STDERR) or error("dup(STDERR, tmp_fd): $!");
+ open(*STDERR, '>', '/dev/null') or error("re-open stderr as /dev/null: $!");
+
+ my $res = defined(`$tool --version`);
+ open(*STDERR, '>&', $old_stderr) or error("dup(tmp_fd, STDERR): $!");
+ close($old_stderr);
+ return $res;
+}
+
+if (has_man_db_tool('man') || has_man_db_tool('man-recode')) {
+ plan(tests => 2);
+} else {
+ plan(skip_all => 'Test requires man or man-recode');
+}
+
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ manpage-uncompressed.pod
+ manpage-compressed.pod
+ manpage-perl.pod
+ libmanpage.so.1.2.3.pod
+ libmanpage.so.9.2.3
+));
+
+each_compat_subtest {
+ my ($compat) = @_;
+ if (! -d 'generated-manpages') {
+ # Static data that can be reused. Generate only in the first test
+ make_path('generated-manpages');
+ for my $basename (qw(manpage-uncompressed manpage-compressed manpage-perl libmanpage.so.1.2.3)) {
+ doit('pod2man', '--utf8', '-c', 'Debhelper', '-r', '1.0', "${basename}.pod",
+ "generated-manpages/${basename}.1");
+ }
+ doit('pod2man', '--utf8', '-c', 'Debhelper', '-r', '1.0', '-s', '3', 'manpage-perl.pod',
+ 'generated-manpages/manpage-perl.3perl');
+ doit('gzip', '-9n', 'generated-manpages/manpage-compressed.1');
+ }
+ ok(run_dh_tool('dh_installman', 'generated-manpages/manpage-uncompressed.1',
+ 'generated-manpages/manpage-compressed.1.gz',
+ 'generated-manpages/manpage-perl.3perl',
+ 'generated-manpages/libmanpage.so.1.2.3.1',
+ 'libmanpage.so.9.2.3'));
+
+ ok(-e 'debian/debhelper/usr/share/man/man1/manpage-uncompressed.1');
+ ok(-e 'debian/debhelper/usr/share/man/man1/manpage-compressed.1');
+ ok(-e 'debian/debhelper/usr/share/man/man3/manpage-perl.3');
+ ok(-e 'debian/debhelper/usr/share/man/man1/libmanpage.so.1.2.3.1');
+ ok(! -e 'debian/debhelper/usr/share/man/man9/libmanpage.so.9.2.9.2.3');
+ ok(-l 'debian/debhelper/usr/share/man/man3/libmanpage.so.9.2.3');
+ remove_tree('debian/debhelper', 'debian/tmp', 'debian/.debhelper');
+};
+
+each_compat_subtest {
+ my ($compat) = @_;
+ if (! -d 'generated-manpages') {
+ # Static data that can be reused. Generate only in the first test
+ make_path('generated-manpages');
+ for my $basename (qw(manpage-uncompressed manpage-compressed libmanpage.so.1.2.3)) {
+ doit('pod2man', '--utf8', '-c', 'Debhelper', '-r', '1.0', "${basename}.pod",
+ "generated-manpages/${basename}.1");
+ }
+ doit('pod2man', '--utf8', '-c', 'Debhelper', '-r', '1.0', '-s', '3', 'manpage-perl.pod',
+ 'generated-manpages/manpage-perl.3perl');
+ doit('gzip', '-9n', 'generated-manpages/manpage-compressed.1');
+ }
+ mkdirs('debian/debhelper/usr/share/man/man1');
+ mkdirs('debian/debhelper/usr/share/man/man3');
+ copy_file('generated-manpages/manpage-uncompressed.1', 'debian/debhelper/usr/share/man/man1/manpage-uncompressed.1');
+ copy_file('generated-manpages/manpage-compressed.1.gz', 'debian/debhelper/usr/share/man/man1/manpage-compressed.1.gz');
+ copy_file('generated-manpages/manpage-perl.3perl', 'debian/debhelper/usr/share/man/man3/manpage-perl.3perl');
+ copy_file('generated-manpages/libmanpage.so.1.2.3.1', 'debian/debhelper/usr/share/man/man1/libmanpage.so.1.2.3.1');
+ copy_file('libmanpage.so.9.2.3', 'debian/debhelper/usr/share/man/man3/libmanpage.so.9.2.3');
+ ok(run_dh_tool('dh_installman'));
+ ok(-e 'debian/debhelper/usr/share/man/man1/manpage-uncompressed.1');
+ ok(-e 'debian/debhelper/usr/share/man/man1/manpage-compressed.1');
+ ok(-e 'debian/debhelper/usr/share/man/man3/manpage-perl.3perl');
+ ok(-e 'debian/debhelper/usr/share/man/man1/libmanpage.so.1.2.3.1');
+ ok(! -e 'debian/debhelper/usr/share/man/man9/libmanpage.so.9.2.9.2.3');
+ ok(-l 'debian/debhelper/usr/share/man/man3/libmanpage.so.9.2.3');
+ remove_tree('debian/debhelper', 'debian/tmp', 'debian/.debhelper');
+};
+
diff --git a/t/dh_installman/libmanpage.so.1.2.3.pod b/t/dh_installman/libmanpage.so.1.2.3.pod
new file mode 100644
index 0000000..0b68dec
--- /dev/null
+++ b/t/dh_installman/libmanpage.so.1.2.3.pod
@@ -0,0 +1,17 @@
+=head1 NAME
+
+libmanpage.so.1.2.3 - Something very exciting
+
+=head1 SYNOPSIS
+
+This tool does not exist but would be awesome.
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+=head1 AUTHORS
+
+Niels Thykier <niels@thykier.net>
+
+=cut
diff --git a/t/dh_installman/libmanpage.so.9.2.3 b/t/dh_installman/libmanpage.so.9.2.3
new file mode 100644
index 0000000..07658f3
--- /dev/null
+++ b/t/dh_installman/libmanpage.so.9.2.3
@@ -0,0 +1 @@
+.so man1/manpage-uncompressed.1
diff --git a/t/dh_installman/manpage-compressed.pod b/t/dh_installman/manpage-compressed.pod
new file mode 100644
index 0000000..d2c0596
--- /dev/null
+++ b/t/dh_installman/manpage-compressed.pod
@@ -0,0 +1,17 @@
+=head1 NAME
+
+manpage - Something very exciting
+
+=head1 SYNOPSIS
+
+This tool does not exist but would be awesome.
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+=head1 AUTHORS
+
+Niels Thykier <niels@thykier.net>
+
+=cut
diff --git a/t/dh_installman/manpage-perl.pod b/t/dh_installman/manpage-perl.pod
new file mode 100644
index 0000000..9b66d9d
--- /dev/null
+++ b/t/dh_installman/manpage-perl.pod
@@ -0,0 +1,17 @@
+=head3 NAME
+
+manpage-uncompressed - Something very exciting
+
+=head3 SYNOPSIS
+
+This tool does not exist but would be awesome.
+
+=head3 SEE ALSO
+
+L<debhelper(7)>
+
+=head3 AUTHORS
+
+Niels Thykier <niels@thykier.net>
+
+=cut
diff --git a/t/dh_installman/manpage-uncompressed.pod b/t/dh_installman/manpage-uncompressed.pod
new file mode 100644
index 0000000..c95037c
--- /dev/null
+++ b/t/dh_installman/manpage-uncompressed.pod
@@ -0,0 +1,17 @@
+=head1 NAME
+
+manpage-uncompressed - Something very exciting
+
+=head1 SYNOPSIS
+
+This tool does not exist but would be awesome.
+
+=head1 SEE ALSO
+
+L<debhelper(7)>
+
+=head1 AUTHORS
+
+Niels Thykier <niels@thykier.net>
+
+=cut
diff --git a/t/dh_installpam/debian/changelog b/t/dh_installpam/debian/changelog
new file mode 100644
index 0000000..5850f0e
--- /dev/null
+++ b/t/dh_installpam/debian/changelog
@@ -0,0 +1,5 @@
+foo (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <testing@nowhere> Mon, 11 Jul 2016 18:10:59 +0200
diff --git a/t/dh_installpam/debian/control b/t/dh_installpam/debian/control
new file mode 100644
index 0000000..c266f51
--- /dev/null
+++ b/t/dh_installpam/debian/control
@@ -0,0 +1,10 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@example.org>
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
diff --git a/t/dh_installpam/debian/foo.pam b/t/dh_installpam/debian/foo.pam
new file mode 100644
index 0000000..fab1c8c
--- /dev/null
+++ b/t/dh_installpam/debian/foo.pam
@@ -0,0 +1 @@
+@include common-auth
diff --git a/t/dh_installpam/dh_installpam.t b/t/dh_installpam/dh_installpam.t
new file mode 100755
index 0000000..4e76799
--- /dev/null
+++ b/t/dh_installpam/dh_installpam.t
@@ -0,0 +1,38 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Test::More;
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 2);
+
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.pam
+));
+
+each_compat_up_to_and_incl_subtest(13, sub {
+ make_path(qw(debian/foo));
+ ok(run_dh_tool('dh_installpam'));
+
+ ok(-f 'debian/foo/etc/pam.d/foo');
+ ok(! -f 'debian/foo/usr/lib/pam.d/foo');
+
+ ok(run_dh_tool('dh_clean'));
+});
+
+each_compat_from_and_above_subtest(14, sub {
+ make_path(qw(debian/foo));
+ ok(run_dh_tool('dh_installpam'));
+
+ ok(! -f 'debian/foo/etc/pam.d/foo');
+ ok(-f 'debian/foo/usr/lib/pam.d/foo');
+
+ ok(run_dh_tool('dh_clean'));
+});
diff --git a/t/dh_installsystemd/debian/changelog b/t/dh_installsystemd/debian/changelog
new file mode 100644
index 0000000..5850f0e
--- /dev/null
+++ b/t/dh_installsystemd/debian/changelog
@@ -0,0 +1,5 @@
+foo (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <testing@nowhere> Mon, 11 Jul 2016 18:10:59 +0200
diff --git a/t/dh_installsystemd/debian/control b/t/dh_installsystemd/debian/control
new file mode 100644
index 0000000..48d4de2
--- /dev/null
+++ b/t/dh_installsystemd/debian/control
@@ -0,0 +1,20 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
+
+Package: bar
+Architecture: all
+Description: package bar
+ Package bar
+
+Package: baz
+Architecture: all
+Description: package baz
+ Package baz
diff --git a/t/dh_installsystemd/debian/foo.init b/t/dh_installsystemd/debian/foo.init
new file mode 100644
index 0000000..2b77921
--- /dev/null
+++ b/t/dh_installsystemd/debian/foo.init
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+some script
+
diff --git a/t/dh_installsystemd/debian/foo.service b/t/dh_installsystemd/debian/foo.service
new file mode 100644
index 0000000..2b48a78
--- /dev/null
+++ b/t/dh_installsystemd/debian/foo.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=A unit
+
+[Service]
+ExecStart=/bin/true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/t/dh_installsystemd/debian/foo2.service b/t/dh_installsystemd/debian/foo2.service
new file mode 100644
index 0000000..42b9445
--- /dev/null
+++ b/t/dh_installsystemd/debian/foo2.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Another unit
+
+[Service]
+ExecStart=/bin/true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/t/dh_installsystemd/dh_installsystemd.t b/t/dh_installsystemd/dh_installsystemd.t
new file mode 100755
index 0000000..bbb4eb0
--- /dev/null
+++ b/t/dh_installsystemd/dh_installsystemd.t
@@ -0,0 +1,255 @@
+#!/usr/bin/perl
+use strict;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 4);
+
+sub write_file {
+ my ($path, $content) = @_;
+
+ my $dir = dirname($path);
+ mkdirs($dir);
+
+ open(my $fd, '>>', $path) or error("open($path) failed: $!");
+ print {$fd} $content . '\n';
+ close($fd) or error("close($path) failed: $!");
+}
+
+sub unit_is_enabled {
+ my ($package, $unit, $num_enables) = @_;
+ my @output;
+ my $matches;
+ my @postinst_snippets = find_script($package, 'postinst');
+ @output=`cat @postinst_snippets` if @postinst_snippets;
+ # Match exactly one tab; the "dont-enable" script has an "enable"
+ # line for re-enabling the service if the admin had it enabled.
+ # But we do not want to include that in our count.
+ $matches = grep { m{^\tif deb-systemd-helper .* was-enabled .*'\Q$unit\E\.service'} } @output;
+ ok($matches == $num_enables) or diag("$unit appears to have been enabled $matches times (expected $num_enables)");
+}
+
+sub unit_is_started {
+ my ($package, $unit, $num_starts, $num_stops) = @_;
+ my @output;
+ my $matches;
+ $num_stops = $num_stops // $num_starts;
+ my @postinst_snippets = find_script($package, 'postinst');
+ @output=`cat @postinst_snippets` if @postinst_snippets;
+ $matches = grep { m{deb-systemd-invoke \$_dh_action .*'\Q$unit\E.service'} } @output;
+ ok($matches == $num_starts) or diag("$unit appears to have been started $matches times (expected $num_starts)");
+ my @prerm_snippets = find_script($package, 'prerm');
+ @output=`cat @prerm_snippets` if @prerm_snippets;
+ $matches = grep { m{deb-systemd-invoke stop .*'\Q$unit\E.service'} } @output;
+ ok($matches == $num_stops) or diag("$unit appears to have been stopped $matches times (expected $num_stops)");
+}
+
+
+#
+# Test a simple source package defining a single binary package
+#
+our $TEST_DH_FIXTURE_DIR = 'simple';
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.service
+));
+
+each_compat_subtest {
+ ok(run_dh_tool('dh_installsystemd'));
+ ok(-e 'debian/foo/lib/systemd/system/foo.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ ok(run_dh_tool('dh_installsystemd', '--no-start'));
+ ok(-e 'debian/foo/lib/systemd/system/foo.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 0, 0);
+ ok(run_dh_tool('dh_clean'));
+
+ ok(run_dh_tool('dh_installsystemd', '--no-start', 'foo.service'));
+ ok(-e 'debian/foo/lib/systemd/system/foo.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 0, 0);
+ ok(run_dh_tool('dh_clean'));
+};
+
+
+#
+# Test a more complex source package defining three binary packages
+#
+$TEST_DH_FIXTURE_DIR = '.';
+@TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.service
+ debian/foo2.service
+));
+
+each_compat_subtest {
+ ok(run_dh_tool( 'dh_installsystemd'));
+ ok(-e 'debian/foo/lib/systemd/system/foo.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 1);
+ unit_is_enabled('foo', 'foo2', 0);
+ unit_is_started('foo', 'foo2', 0);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_installsystemd'));
+ ok(-e 'debian/foo/lib/systemd/system/foo.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 1);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 1);
+ ok(run_dh_tool('dh_clean'));
+
+
+ # lib -> usr/lib (if we ever support that)
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_installsystemd'));
+ ok(-e 'debian/foo/lib/systemd/system/foo2.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 1);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ # lib -> usr/lib with both no-empty (if we ever suport that)
+ make_path('debian/foo/lib/systemd/system/');
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/bar.service');
+ ok(run_dh_tool('dh_installsystemd'));
+ ok(-e 'debian/foo/lib/systemd/system/bar.service');
+ ok(-e 'debian/foo/lib/systemd/system/foo2.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 1);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_installsystemd', '--no-start'));
+ ok(-e 'debian/foo/lib/systemd/system/foo.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 0, 0);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 0, 0);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_installsystemd', '-p', 'foo', '--no-start', 'foo.service'));
+ ok(run_dh_tool('dh_installsystemd', '-p', 'foo', 'foo2.service'));
+ ok(-e 'debian/foo/lib/systemd/system/foo.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 0, 0);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_installsystemd', '-p', 'foo', '--no-enable', 'foo.service'));
+ ok(run_dh_tool('dh_installsystemd', '-p', 'foo', 'foo2.service'));
+ ok(-e 'debian/foo/lib/systemd/system/foo.service');
+ ok(find_script('foo', 'postinst'));
+ unit_is_enabled('foo', 'foo', 0);
+ unit_is_started('foo', 'foo', 1, 1);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ ok(run_dh_tool('dh_installsystemd', '--no-restart-after-upgrade'));
+ my @foo_postinst = find_script('foo', 'postinst');
+ ok(@foo_postinst);
+ my $matches = @foo_postinst ? grep { m{deb-systemd-invoke start .*foo.service} } `cat @foo_postinst` : -1;
+ ok($matches == 1);
+ ok(run_dh_tool('dh_clean'));
+
+ # Quoting #764730
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo.service', 'debian/foo/lib/systemd/system/foo\x2dfuse.service');
+ ok(run_dh_tool('dh_installsystemd'));
+ unit_is_enabled('foo', 'foo\x2dfuse', 1);
+ unit_is_started('foo', 'foo\x2dfuse', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ # --name flag #870768
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_installsystemd', '--name=foo'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 1);
+ unit_is_enabled('foo', 'foo2', 0);
+ unit_is_started('foo', 'foo2', 0);
+ ok(run_dh_tool('dh_installsystemd', '--name=foo2'));
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 1);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo.service', 'debian/foo/lib/systemd/system/target.service');
+ make_symlink_raw_target('target.service', 'debian/foo/lib/systemd/system/source.service');
+ ok(run_dh_tool('dh_installsystemd'));
+ unit_is_enabled('foo', 'foo', 1);
+ # Alias= realized by symlinks are not enabled in maintaner scripts
+ unit_is_enabled('foo', 'source', 0);
+ unit_is_enabled('foo', 'target', 1);
+ ok(run_dh_tool('dh_clean'));
+};
+
+each_compat_up_to_and_incl_subtest(11, sub {
+ make_path('debian/foo/lib/systemd/system/');
+ make_path('debian/foo/etc/init.d/');
+ copy_file('debian/foo.service', 'debian/foo/lib/systemd/system/target.service');
+ make_symlink_raw_target('target.service', 'debian/foo/lib/systemd/system/source.service');
+ write_file('debian/foo/etc/init.d/source', '# something');
+ ok(run_dh_tool('dh_installsystemd'));
+ unit_is_enabled('foo', 'foo', 1);
+ # Alias= realized by symlinks are not enabled in maintaner scripts
+ unit_is_enabled('foo', 'source', 0);
+ unit_is_enabled('foo', 'target', 1);
+ # The presence of a sysvinit script for the alias unit inhibits start of both
+ unit_is_started('foo', 'source', 0);
+ unit_is_started('foo', 'target', 0);
+ ok(run_dh_tool('dh_clean'));
+});
+
+each_compat_from_and_above_subtest(12, sub {
+ make_path('debian/foo/lib/systemd/system/');
+ make_path('debian/foo/etc/init.d/');
+ copy_file('debian/foo.service', 'debian/foo/lib/systemd/system/target.service');
+ make_symlink_raw_target('target.service', 'debian/foo/lib/systemd/system/source.service');
+ write_file('debian/foo/etc/init.d/source', '# something');
+ ok(run_dh_tool('dh_installsystemd'));
+ unit_is_enabled('foo', 'foo', 1);
+ # Alias= realized by symlinks are not enabled in maintaner scripts
+ unit_is_enabled('foo', 'source', 0);
+ unit_is_enabled('foo', 'target', 1);
+ unit_is_started('foo', 'source', 0);
+ unit_is_started('foo', 'target', 1);
+ ok(run_dh_tool('dh_clean'));
+});
diff --git a/t/dh_installsystemd/dh_installsystemd_tmpfiles.t b/t/dh_installsystemd/dh_installsystemd_tmpfiles.t
new file mode 100755
index 0000000..0fffe57
--- /dev/null
+++ b/t/dh_installsystemd/dh_installsystemd_tmpfiles.t
@@ -0,0 +1,87 @@
+#!/usr/bin/perl
+use strict;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.service
+ debian/foo.init
+));
+
+plan(tests => 3);
+
+
+# Units are installed and enabled
+each_compat_from_x_to_and_incl_y_subtest(11, 12, sub {
+ make_path('debian/foo/usr/lib/tmpfiles.d');
+ create_empty_file('debian/foo/usr/lib/tmpfiles.d/foo.conf');
+ ok(run_dh_tool('dh_installinit'));
+ ok(run_dh_tool('dh_installsystemd'));
+ ok(-e "debian/foo/etc/init.d/foo");
+ ok(-e "debian/foo/lib/systemd/system/foo.service");
+ my @postinst = find_script('foo', 'postinst');
+ # We should have two snippets (one for the tmpfiles and one for the services).
+ is(scalar(@postinst), 2);
+ if (scalar(@postinst) == 2) {
+ open(my $fd, '<', $postinst[0]) or error("open($postinst[0]) failed: $!");
+ my $early_snippet = readlines($fd);
+ close($fd);
+ open($fd, '<', $postinst[1]) or error("open($postinst[1]) failed: $!");
+ my $late_snippet = readlines($fd);
+ close($fd);
+ ok(! grep { m/(?:invoke|update)-rc.d|deb-systemd-invoke/ } @{$early_snippet});
+ ok(grep { m/(?:invoke|update)-rc.d|deb-systemd-invoke/ } @{$late_snippet});
+ ok(grep { m/systemd-tmpfiles/ } @{$early_snippet});
+ ok(! grep { m/systemd-tmpfiles/ } @{$late_snippet});
+ }
+ ok(run_dh_tool('dh_clean'));
+
+});
+
+each_compat_from_and_above_subtest(13, sub {
+ make_path('debian/foo/usr/lib/tmpfiles.d');
+ create_empty_file('debian/foo/usr/lib/tmpfiles.d/foo.conf');
+ ok(run_dh_tool('dh_installinit'));
+ ok(run_dh_tool('dh_installsystemd'));
+ ok(-e "debian/foo/etc/init.d/foo");
+ ok(-e "debian/foo/lib/systemd/system/foo.service");
+ my @postinst = find_script('foo', 'postinst');
+ # We should have one snippet (one for the services).
+ is(scalar(@postinst), 1);
+ if (scalar(@postinst) == 1) {
+ open(my $fd, '<', $postinst[0]) or error("open($postinst[0]) failed: $!");
+ my $snippet = readlines($fd);
+ close($fd);
+ ok(grep { m/(?:invoke|update)-rc.d|deb-systemd-invoke/ } @{$snippet});
+ ok(! grep { m/systemd-tmpfiles/ } @{$snippet});
+ }
+ ok(run_dh_tool('dh_clean'));
+});
+
+
+each_compat_from_and_above_subtest(13, sub {
+ make_path('debian/foo/usr/lib/tmpfiles.d');
+ create_empty_file('debian/foo/usr/lib/tmpfiles.d/foo.conf');
+ ok(run_dh_tool('dh_installtmpfiles'));
+ # dh_installtmpfiles do not install services
+ ok(!-e "debian/foo/etc/init.d/foo");
+ ok(!-e "debian/foo/lib/systemd/system/foo.service");
+ my @postinst = find_script('foo', 'postinst');
+ # We should have too snippets (one for the tmpfiles and one for the services).
+ is(scalar(@postinst), 1);
+ if (scalar(@postinst) == 1) {
+ open(my $fd, '<', $postinst[0]) or error("open($postinst[0]) failed: $!");
+ my $snippet = readlines($fd);
+ close($fd);
+ ok(! grep { m/(?:invoke|update)-rc.d|deb-systemd-invoke/ } @{$snippet});
+ ok(grep { m/systemd-tmpfiles/ } @{$snippet});
+ }
+ ok(run_dh_tool('dh_clean'));
+});
diff --git a/t/dh_installsystemd/dh_systemd.t b/t/dh_installsystemd/dh_systemd.t
new file mode 100755
index 0000000..c3c9401
--- /dev/null
+++ b/t/dh_installsystemd/dh_systemd.t
@@ -0,0 +1,123 @@
+#!/usr/bin/perl
+use strict;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use File::Path qw(remove_tree make_path);
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.service
+ debian/foo2.service
+));
+
+plan(tests => 1);
+
+sub unit_is_enabled {
+ my ($package, $unit, $num_enables) = @_;
+ my @output;
+ my $matches;
+ @output=`cat debian/$package.postinst.debhelper`;
+ # Match exactly one tab; the "dont-enable" script has an "enable"
+ # line for re-enabling the service if the admin had it enabled.
+ # But we do not want to include that in our count.
+ $matches = grep { m{^\tif deb-systemd-helper .* was-enabled .*'\Q$unit\E\.service'} } @output;
+ ok($matches == $num_enables) or diag("$unit appears to have been enabled $matches times (expected $num_enables)");
+}
+sub unit_is_started {
+ my ($package, $unit, $num_starts, $num_stops) = @_;
+ my @output;
+ my $matches;
+ $num_stops = $num_stops // $num_starts;
+ @output=`cat debian/$package.postinst.debhelper`;
+ $matches = grep { m{deb-systemd-invoke \$_dh_action .*'\Q$unit\E.service'} } @output;
+ ok($matches == $num_starts) or diag("$unit appears to have been started $matches times (expected $num_starts)");
+ @output=`cat debian/$package.prerm.debhelper`;
+ $matches = grep { m{deb-systemd-invoke stop .*'\Q$unit\E.service'} } @output;
+ ok($matches == $num_stops) or diag("$unit appears to have been stopped $matches times (expected $num_stops)");
+}
+
+# Units are installed and enabled
+each_compat_from_x_to_and_incl_y_subtest(10, 10, sub {
+ ok(run_dh_tool('dh_systemd_enable'));
+ ok(run_dh_tool('dh_systemd_start'));
+ ok(-e "debian/foo/lib/systemd/system/foo.service");
+ ok(-e "debian/foo.postinst.debhelper");
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 1);
+ unit_is_enabled('foo', 'foo2', 0);
+ unit_is_started('foo', 'foo2', 0);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_systemd_enable'));
+ ok(run_dh_tool('dh_systemd_start'));
+ ok(-e "debian/foo/lib/systemd/system/foo.service");
+ ok(-e "debian/foo.postinst.debhelper");
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 1);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_systemd_enable'));
+ ok(run_dh_tool('dh_systemd_start', '--no-start'));
+ ok(-e "debian/foo/lib/systemd/system/foo.service");
+ ok(-e "debian/foo.postinst.debhelper");
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 0, 1); # present units are stopped on remove even if no start
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 0, 1);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_systemd_enable'));
+ ok(run_dh_tool('dh_systemd_start', '--no-start', 'debian/foo.service'));
+ ok(run_dh_tool('dh_systemd_start', '-p', 'foo', 'foo2.service'));
+ ok(-e "debian/foo/lib/systemd/system/foo.service");
+ ok(-e "debian/foo.postinst.debhelper");
+ unit_is_enabled('foo', 'foo', 1);
+ unit_is_started('foo', 'foo', 0, 1);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo2.service', 'debian/foo/lib/systemd/system/foo2.service');
+ ok(run_dh_tool('dh_systemd_enable', '--no-enable', 'debian/foo.service'));
+ ok(run_dh_tool('dh_systemd_enable', '-p', 'foo', 'foo2.service'));
+ ok(run_dh_tool('dh_systemd_start'));
+ ok(-e "debian/foo/lib/systemd/system/foo.service");
+ ok(-e "debian/foo.postinst.debhelper");
+ unit_is_enabled('foo', 'foo', 0);
+ unit_is_started('foo', 'foo', 1, 1);
+ unit_is_enabled('foo', 'foo2', 1);
+ unit_is_started('foo', 'foo2', 1);
+ ok(run_dh_tool('dh_clean'));
+
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo.service', 'debian/foo/lib/systemd/system/foo.service');
+ ok(run_dh_tool('dh_systemd_start', '--no-restart-after-upgrade'));
+ my $matches = grep { m{deb-systemd-invoke start .*foo.service} } `cat debian/foo.postinst.debhelper`;
+ ok($matches == 1);
+ ok(run_dh_tool('dh_clean'));
+
+ # Quoting #764730
+ make_path('debian/foo/lib/systemd/system/');
+ copy_file('debian/foo.service', 'debian/foo/lib/systemd/system/foo\x2dfuse.service');
+ ok(run_dh_tool('dh_systemd_enable'));
+ ok(run_dh_tool('dh_systemd_start'));
+ unit_is_enabled('foo', 'foo\x2dfuse', 1);
+ unit_is_started('foo', 'foo\x2dfuse', 1);
+ ok(run_dh_tool('dh_clean'));
+});
+
+
diff --git a/t/dh_installsystemd/simple/debian/changelog b/t/dh_installsystemd/simple/debian/changelog
new file mode 100644
index 0000000..5b1a8fe
--- /dev/null
+++ b/t/dh_installsystemd/simple/debian/changelog
@@ -0,0 +1,5 @@
+foo (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <test@example.org> Mon, 11 Jul 2016 18:10:59 +0200
diff --git a/t/dh_installsystemd/simple/debian/control b/t/dh_installsystemd/simple/debian/control
new file mode 100644
index 0000000..4f4238e
--- /dev/null
+++ b/t/dh_installsystemd/simple/debian/control
@@ -0,0 +1,10 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
diff --git a/t/dh_installsystemd/simple/debian/foo.service b/t/dh_installsystemd/simple/debian/foo.service
new file mode 100644
index 0000000..2b48a78
--- /dev/null
+++ b/t/dh_installsystemd/simple/debian/foo.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=A unit
+
+[Service]
+ExecStart=/bin/true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/t/dh_installsystemduser/debian/baz.user.service b/t/dh_installsystemduser/debian/baz.user.service
new file mode 100644
index 0000000..3af041d
--- /dev/null
+++ b/t/dh_installsystemduser/debian/baz.user.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Baz User Unit
+
+[Service]
+ExecStart=/bin/true
+
+[Install]
+WantedBy=default.target
diff --git a/t/dh_installsystemduser/debian/changelog b/t/dh_installsystemduser/debian/changelog
new file mode 100644
index 0000000..5850f0e
--- /dev/null
+++ b/t/dh_installsystemduser/debian/changelog
@@ -0,0 +1,5 @@
+foo (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <testing@nowhere> Mon, 11 Jul 2016 18:10:59 +0200
diff --git a/t/dh_installsystemduser/debian/control b/t/dh_installsystemduser/debian/control
new file mode 100644
index 0000000..adccbc6
--- /dev/null
+++ b/t/dh_installsystemduser/debian/control
@@ -0,0 +1,10 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@exampe.org>
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
diff --git a/t/dh_installsystemduser/debian/foo.user.service b/t/dh_installsystemduser/debian/foo.user.service
new file mode 100644
index 0000000..7ef597d
--- /dev/null
+++ b/t/dh_installsystemduser/debian/foo.user.service
@@ -0,0 +1,8 @@
+[Unit]
+Description=Foo User Unit
+
+[Service]
+ExecStart=/bin/true
+
+[Install]
+WantedBy=default.target
diff --git a/t/dh_installsystemduser/dh_installsystemduser.t b/t/dh_installsystemduser/dh_installsystemduser.t
new file mode 100755
index 0000000..5171728
--- /dev/null
+++ b/t/dh_installsystemduser/dh_installsystemduser.t
@@ -0,0 +1,112 @@
+#!/usr/bin/perl
+use strict;
+use Test::More;
+use File::Path qw(make_path);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 1);
+
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.user.service
+ debian/baz.user.service
+));
+
+
+sub read_script {
+ my ($package, $name) = @_;
+ my @lines;
+
+ foreach my $script (find_script($package, $name)) {
+ open(my $fh, '<', $script) or die("open($script): $!");
+ push @lines, $_ for <$fh>;
+ close($fh);
+ }
+
+ return @lines;
+}
+
+sub _unit_check_user_enabled {
+ my ($package, $unit, $enabled) = @_;
+ my $verb = $enabled ? "is" : "isnt";
+ my $matches;
+
+ my @postinst = read_script($package, 'postinst');
+ # Match exactly two tab character. The "dont-enable" script has
+ # an "enable" line for re-enabling the service if the admin had it
+ # enabled, but we do not want to include that in our count.
+ $matches = grep { m{^\t\tif deb-systemd-helper( --\w+)* --user was-enabled.*'\Q$unit'} } @postinst;
+ is($matches, $enabled, "$unit $verb enabled");
+}
+
+sub _unit_check_user_started {
+ my ($package, $unit, $started) = @_;
+ my $verb = $started ? "is" : "isnt";
+ my $matches;
+
+ my @postinst = read_script($package, 'postinst');
+ # Match exactly two tab character. The "dont-enable" script has
+ # an "enable" line for re-enabling the service if the admin had it
+ # enabled, but we do not want to include that in our count.
+ $matches = grep { m{deb-systemd-invoke --user restart.*'\Q$unit'} } @postinst;
+ is($matches, $started, "$unit $verb started");
+
+ my @prerm = read_script($package, 'prerm');
+ $matches = grep { m{deb-systemd-invoke --user stop.*'\Q$unit'} } @prerm;
+ is($matches, $started, "$unit $verb stopped");
+}
+
+sub is_enabled { _unit_check_user_enabled(@_, 1); }
+sub isnt_enabled { _unit_check_user_enabled(@_, 0); }
+sub is_started { _unit_check_user_started(@_, 1); }
+sub isnt_started { _unit_check_user_started(@_, 0); }
+
+each_compat_subtest {
+ my ($compat) = @_;
+ make_path('debian/foo/usr/lib/systemd/user/');
+ copy_file('debian/foo.user.service', 'debian/foo/usr/lib/systemd/user/bar.service');
+ ok(run_dh_tool('dh_installsystemduser'));
+ ok(-e 'debian/foo/usr/lib/systemd/user/foo.service');
+ is_enabled('foo', 'foo.service');
+ is_enabled('foo', 'bar.service');
+ if ($compat > 13) {
+ is_started('foo', 'foo.service');
+ is_started('foo', 'bar.service');
+ } else {
+ isnt_started('foo', 'foo.service');
+ isnt_started('foo', 'bar.service');
+ }
+ ok(run_dh_tool('dh_clean'));
+
+ ok(run_dh_tool('dh_installsystemduser'));
+ ok(-e 'debian/foo/usr/lib/systemd/user/foo.service');
+ ok(! -e 'debian/foo/usr/lib/systemd/user/baz.service');
+ is_enabled('foo', 'foo.service');
+ isnt_enabled('foo', 'baz.service');
+ if ($compat > 13) {
+ is_started('foo', 'foo.service');
+ isnt_started('foo', 'baz.service');
+ } else {
+ isnt_started('foo', 'bar.service');
+ isnt_started('foo', 'baz.service');
+ }
+ ok(run_dh_tool('dh_clean'));
+
+ ok(run_dh_tool('dh_installsystemduser', '--name', 'baz'));
+ ok(! -e 'debian/foo/usr/lib/systemd/user/foo.service');
+ ok(-e 'debian/foo/usr/lib/systemd/user/baz.service');
+ isnt_enabled('foo', 'foo.service');
+ is_enabled('foo', 'baz.service');
+ if ($compat > 13) {
+ isnt_started('foo', 'foo.service');
+ is_started('foo', 'baz.service');
+ } else {
+ isnt_started('foo', 'foo.service');
+ isnt_started('foo', 'baz.service');
+ }
+ ok(run_dh_tool('dh_clean'));
+};
diff --git a/t/dh_link/01-basic.t b/t/dh_link/01-basic.t
new file mode 100755
index 0000000..88c8e49
--- /dev/null
+++ b/t/dh_link/01-basic.t
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+plan(tests => 1);
+
+use File::Path qw(remove_tree);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+
+each_compat_subtest {
+
+ remove_tree('debian/debhelper');
+
+ # It used to not make absolute links in this situation, and it should.
+ # #37774
+ ok(run_dh_tool('dh_link', 'etc/foo', 'usr/lib/bar'));
+ ok(readlink("debian/debhelper/usr/lib/bar"), "/etc/foo");
+
+
+ # let's make sure it makes simple relative links ok.
+ ok(run_dh_tool('dh_link', 'usr/bin/foo', 'usr/bin/bar'));
+ ok(readlink("debian/debhelper/usr/bin/bar"), "foo");
+ ok(run_dh_tool('dh_link', 'sbin/foo', 'sbin/bar'));
+ ok(readlink("debian/debhelper/sbin/bar"), "foo");
+
+ # ok, more complex relative links.
+ ok(run_dh_tool('dh_link', 'usr/lib/1', 'usr/bin/2'));
+ ok(readlink("debian/debhelper/usr/bin/2"),"../lib/1");
+
+ # Check conversion of relative symlink to different top-level directory
+ # into absolute symlink. (#244157)
+ system("mkdir -p debian/debhelper/usr/lib; mkdir -p debian/debhelper/lib; touch debian/debhelper/lib/libm.so; cd debian/debhelper/usr/lib; ln -sf ../../lib/libm.so");
+ ok(run_dh_tool('dh_link'));
+ ok(readlink("debian/debhelper/usr/lib/libm.so"), "/lib/libm.so");
+
+ # Link to self.
+ ok(run_dh_tool({ 'quiet' => 1 }, 'dh_link', 'usr/lib/foo', 'usr/lib/foo'));
+ ok(! -l "debian/debhelper/usr/lib/foo");
+
+ # Make sure the link conversion didn't change any of the
+ # previously made links.
+ ok(readlink("debian/debhelper/usr/lib/bar"), "/etc/foo");
+ ok(readlink("debian/debhelper/usr/bin/bar"), "foo");
+ ok(readlink("debian/debhelper/usr/bin/2"),"../lib/1");
+};
+
diff --git a/t/dh_link/02-346405.t b/t/dh_link/02-346405.t
new file mode 100755
index 0000000..95053ca
--- /dev/null
+++ b/t/dh_link/02-346405.t
@@ -0,0 +1,29 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+plan(tests => 1);
+
+use File::Path qw(remove_tree);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+
+each_compat_subtest {
+
+ remove_tree('debian/debhelper');
+
+ # Check links to the current directory and below, they used to be
+ # unnecessarily long (#346405).
+ ok(run_dh_tool('dh_link', 'usr/lib/geant4', 'usr/lib/geant4/a'));
+ ok(readlink("debian/debhelper/usr/lib/geant4/a"), ".");
+ ok(run_dh_tool('dh_link', 'usr/lib', 'usr/lib/geant4/b'));
+ ok(readlink("debian/debhelper/usr/lib/geant4/b"), "..");
+ ok(run_dh_tool('dh_link', 'usr', 'usr/lib/geant4/c'));
+ ok(readlink("debian/debhelper/usr/lib/geant4/c"), "../..");
+ ok(run_dh_tool('dh_link', '/', 'usr/lib/geant4/d'));
+ ok(readlink("debian/debhelper/usr/lib/geant4/d"), "/");
+};
+
diff --git a/t/dh_link/03-894229.t b/t/dh_link/03-894229.t
new file mode 100755
index 0000000..d42a392
--- /dev/null
+++ b/t/dh_link/03-894229.t
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+plan(tests => 1);
+
+use File::Path qw(remove_tree);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+use Test::DH;
+
+
+sub test_tricky {
+ my ($link_name, $denoted_dest, $expected_link_target) = @_;
+ my $tmpdir = 'debian/debhelper';
+ my $link_path = "${tmpdir}/${link_name}";
+
+ make_symlink($link_name, $denoted_dest, $tmpdir);
+ if (ok(-l $link_path, 'Link made in correct directory')) {
+ my $target = readlink($link_path);
+ is($target, $expected_link_target, 'Link points correctly')
+ or diag("Expected ${expected_link_target}, actual ${target}");
+ rm_files($link_path);
+ }
+ return;
+}
+
+sub test_invalid {
+ my ($link_name, $denoted_dest) = @_;
+ eval {
+ make_symlink($link_name, $denoted_dest);
+ };
+ like($@, qr{^(?:\S*:(?: error:)?\s*)?Invalid destination/link name});
+}
+
+each_compat_subtest {
+
+ remove_tree('debian/debhelper/a/b/c');
+
+ mkdirs('debian/debhelper/a/b/c');
+
+ test_invalid('../../wow', 'a');
+ # This is a can be made valid but at the moment we do not support
+ # it.
+ test_invalid('a/b/../link21', 'a');
+
+
+ test_tricky('//a/b/link03', 'a/b/c', 'c');
+ test_tricky('./a/link18', 'a', '.');
+ test_tricky('a/./b/link19', 'a/b', '.');
+};
+
diff --git a/t/dh_missing/01-no-missing.t b/t/dh_missing/01-no-missing.t
new file mode 100755
index 0000000..e3cd311
--- /dev/null
+++ b/t/dh_missing/01-no-missing.t
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+
+our $TEST_DH_FIXTURE_DIR = 'template';
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.install
+ file-for-foo
+ Makefile
+));
+
+plan(tests => 1);
+
+each_compat_subtest {
+ # Verify dh_missing does not fail when all files are installed.
+ ok(run_dh_tool('dh_clean'));
+ is(system('make', 'install'), 0);
+ ok(run_dh_tool('dh_install', '--sourcedir', 'debian/tmp'));
+ ok(run_dh_tool('dh_missing', '--fail-missing'), 'dh_missing failed');
+};
+
diff --git a/t/dh_missing/02-fail-on-missing.t b/t/dh_missing/02-fail-on-missing.t
new file mode 100755
index 0000000..e97b6eb
--- /dev/null
+++ b/t/dh_missing/02-fail-on-missing.t
@@ -0,0 +1,33 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+
+our $TEST_DH_FIXTURE_DIR = 'template';
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.install
+ file-for-foo
+ Makefile
+));
+
+plan(tests => 1);
+
+each_compat_subtest {
+ # Verify dh_missing does fail when not all files are installed.
+ ok(run_dh_tool('dh_clean'));
+ is(system('make', 'installmore'), 0);
+ ok(run_dh_tool('dh_install', '--sourcedir', 'debian/tmp'));
+ ok(!run_dh_tool({ 'quiet' => 1 }, 'dh_missing', '--fail-missing'));
+
+ isnt($?, -1, 'dh_missing was executed');
+ ok(! ($? & 127), 'dh_missing did not die due to a signal');
+ my $exitcode = ($? >> 8);
+ is($exitcode, 255, 'dh_missing exited with exit code 255');
+};
+
diff --git a/t/dh_missing/03-dh_install-redirection.t b/t/dh_missing/03-dh_install-redirection.t
new file mode 100755
index 0000000..2235b08
--- /dev/null
+++ b/t/dh_missing/03-dh_install-redirection.t
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+
+our $TEST_DH_FIXTURE_DIR = 'template';
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.install
+ file-for-foo
+ Makefile
+));
+
+plan(tests => 1);
+
+each_compat_up_to_and_incl_subtest(10, sub {
+ # Verify that dh_install -X --fail-missing is passed through to dh_missing (#863447)
+ # dh_install -Xfile makes file-for-foo not be installed. Then we shouldn't
+ # complain about it not being missing.
+ ok(run_dh_tool('dh_clean'));
+ is(system('make', 'install'), 0);
+ ok(run_dh_tool({ 'quiet' => 1 }, 'dh_install', '--sourcedir', 'debian/tmp',
+ '-X', 'more', '--exclude', 'lots', '--fail-missing'));
+});
+
diff --git a/t/dh_missing/04-not-installed-glob.t b/t/dh_missing/04-not-installed-glob.t
new file mode 100755
index 0000000..b46eca3
--- /dev/null
+++ b/t/dh_missing/04-not-installed-glob.t
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+our $TEST_DH_FIXTURE_DIR = 'template';
+our @TEST_DH_EXTRA_TEMPLATE_FILES = (qw(
+ debian/changelog
+ debian/control
+ debian/foo.install
+ file-for-foo
+ Makefile
+));
+
+plan(tests => 1);
+
+each_compat_subtest {
+ rm_files('debian/not-installed');
+ open(my $fd, '>', 'debian/not-installed') or error("open(d/not-installed): $!");
+ # Non-glob match
+ print {$fd} "usr/bin/file-for-foo\n";
+ # Glob match (note that it must not match the above)
+ print {$fd} "usr/bin/file-for-foo-*\n";
+ # Non-matches (silently ignored)
+ print {$fd} "usr/bin/does-not-exist\n";
+ print {$fd} "usr/bin/does-not-exist-*\n";
+ close($fd) or error("close(d/not-installed: $!");
+ ok(run_dh_tool('dh_clean'));
+ is(system('make', 'installmore'), 0);
+ ok(run_dh_tool('dh_missing', '--fail-missing'));
+};
+
diff --git a/t/dh_missing/template/Makefile b/t/dh_missing/template/Makefile
new file mode 100644
index 0000000..e33e1df
--- /dev/null
+++ b/t/dh_missing/template/Makefile
@@ -0,0 +1,7 @@
+install:
+ install -m 755 -d debian/tmp/usr/bin
+ install -m 644 file-for-foo debian/tmp/usr/bin/file-for-foo
+
+installmore: install
+ install -m 644 file-for-foo debian/tmp/usr/bin/file-for-foo-more
+ install -m 644 file-for-foo debian/tmp/usr/bin/file-for-foo-lots
diff --git a/t/dh_missing/template/debian/changelog b/t/dh_missing/template/debian/changelog
new file mode 100644
index 0000000..5850f0e
--- /dev/null
+++ b/t/dh_missing/template/debian/changelog
@@ -0,0 +1,5 @@
+foo (1.0-1) unstable; urgency=low
+
+ * Initial release. (Closes: #XXXXXX)
+
+ -- Test <testing@nowhere> Mon, 11 Jul 2016 18:10:59 +0200
diff --git a/t/dh_missing/template/debian/control b/t/dh_missing/template/debian/control
new file mode 100644
index 0000000..48d4de2
--- /dev/null
+++ b/t/dh_missing/template/debian/control
@@ -0,0 +1,20 @@
+Source: foo
+Section: misc
+Priority: optional
+Maintainer: Test <testing@nowhere>
+Standards-Version: 3.9.8
+
+Package: foo
+Architecture: all
+Description: package foo
+ Package foo
+
+Package: bar
+Architecture: all
+Description: package bar
+ Package bar
+
+Package: baz
+Architecture: all
+Description: package baz
+ Package baz
diff --git a/t/dh_missing/template/debian/foo.install b/t/dh_missing/template/debian/foo.install
new file mode 100644
index 0000000..eddea57
--- /dev/null
+++ b/t/dh_missing/template/debian/foo.install
@@ -0,0 +1 @@
+usr/bin/*-for-foo \ No newline at end of file
diff --git a/t/dh_missing/template/file-for-foo b/t/dh_missing/template/file-for-foo
new file mode 100644
index 0000000..8773f39
--- /dev/null
+++ b/t/dh_missing/template/file-for-foo
@@ -0,0 +1 @@
+file content \ No newline at end of file
diff --git a/t/dh_usrlocal/01-basic.t b/t/dh_usrlocal/01-basic.t
new file mode 100755
index 0000000..9b05516
--- /dev/null
+++ b/t/dh_usrlocal/01-basic.t
@@ -0,0 +1,157 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+plan(tests => 1);
+
+use File::Path qw(remove_tree);
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+use Test::DH;
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+sub extract_generated_lines {
+ my ($file) = @_;
+ my (@lines, $marker);
+ return if not -f $file;
+ open(my $fd, '<', $file) or error("open($file) failed: $!");
+ while (my $line = <$fd>) {
+ chomp($line);
+ if (defined($marker)) {
+ last if $line eq $marker;
+ push(@lines, $line);
+ next;
+ }
+ if ($line =~ m{\s*<<\s*(\S+)\s*$}) {
+ $marker = $1;
+ }
+ }
+ close($fd);
+ return @lines;
+}
+
+
+sub perform_test {
+ my ($install_dirs, $expected_dirs_postinst, $expected_dirs_prerm) = @_;
+ my (@postinst, @prerm);
+ my @scripts = qw(
+ debian/debhelper.postinst.debhelper
+ debian/debhelper.prerm.debhelper
+ );
+
+ rm_files(@scripts);
+ remove_tree('debian/debhelper');
+ mkdirs(map { "debian/debhelper/$_" } @{$install_dirs});
+
+ ok(run_dh_tool('dh_usrlocal'));
+
+ @postinst = extract_generated_lines("debian/debhelper.postinst.debhelper");
+ @prerm = extract_generated_lines("debian/debhelper.prerm.debhelper");
+
+ is_deeply(\@postinst,
+ [map { "$_ default" } @{$expected_dirs_postinst}],
+ "Correct postinst"
+ ) or do { diag("postinst: $_") for @postinst; };
+ is_deeply(\@prerm,
+ $expected_dirs_prerm,
+ "Correct prerm"
+ ) or do { diag("prerm: $_") for @prerm; };
+}
+
+each_compat_subtest {
+
+ perform_test(
+ ['/usr/local/bar', '/usr/local/foo'],
+ ['/usr/local/bar', '/usr/local/foo'],
+ []
+ );
+
+ perform_test(
+ [
+ '/usr/local/foo/bar',
+ '/usr/local/foo/baz',
+ ],
+ [
+ '/usr/local/foo',
+ '/usr/local/foo/bar',
+ '/usr/local/foo/baz',
+ ],
+ [
+ '/usr/local/foo/bar',
+ '/usr/local/foo/baz',
+ ]
+ );
+
+ perform_test(
+ [qw(
+ /usr/local/a/a/a
+ /usr/local/a/a/b
+ /usr/local/a/b/a
+ /usr/local/a/b/b
+ /usr/local/b/a/a
+ /usr/local/b/a/b
+ /usr/local/b/b/a
+ /usr/local/b/b/b
+ )],
+ [qw(
+ /usr/local/a
+ /usr/local/a/a
+ /usr/local/a/a/a
+ /usr/local/a/a/b
+ /usr/local/a/b
+ /usr/local/a/b/a
+ /usr/local/a/b/b
+ /usr/local/b
+ /usr/local/b/a
+ /usr/local/b/a/a
+ /usr/local/b/a/b
+ /usr/local/b/b
+ /usr/local/b/b/a
+ /usr/local/b/b/b
+ )],
+ [qw(
+ /usr/local/a/a/a
+ /usr/local/a/a/b
+ /usr/local/a/a
+ /usr/local/a/b/a
+ /usr/local/a/b/b
+ /usr/local/a/b
+ /usr/local/b/a/a
+ /usr/local/b/a/b
+ /usr/local/b/a
+ /usr/local/b/b/a
+ /usr/local/b/b/b
+ /usr/local/b/b
+ )]
+ );
+
+ perform_test(
+ [
+ '/usr/local/foo/dir/somewhere',
+ '/usr/local/bar/another-dir/elsewhere',
+ '/usr/local/baz/foo+bar/thing',
+ ],
+ [
+ '/usr/local/bar',
+ '/usr/local/bar/another-dir',
+ '/usr/local/bar/another-dir/elsewhere',
+ '/usr/local/baz',
+ '/usr/local/baz/foo+bar',
+ '/usr/local/baz/foo+bar/thing',
+ '/usr/local/foo',
+ '/usr/local/foo/dir',
+ '/usr/local/foo/dir/somewhere',
+ ],
+ [
+ '/usr/local/bar/another-dir/elsewhere',
+ '/usr/local/bar/another-dir',
+ '/usr/local/baz/foo+bar/thing',
+ '/usr/local/baz/foo+bar',
+ '/usr/local/foo/dir/somewhere',
+ '/usr/local/foo/dir',
+ ]
+ );
+};
+
diff --git a/t/maintscript.t b/t/maintscript.t
new file mode 100755
index 0000000..cec4fab
--- /dev/null
+++ b/t/maintscript.t
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Path qw(remove_tree);
+use File::Basename qw(dirname);
+use lib dirname(__FILE__);
+use Test::DH;
+use Debian::Debhelper::Dh_Lib qw(!dirname);
+
+plan(tests => 2);
+
+each_compat_up_to_and_incl_subtest(10, sub {
+ my @scripts = qw{postinst preinst prerm postrm};
+ my $file = 'debian/maintscript';
+
+ remove_tree('debian/debhelper', 'debian/tmp');
+ rm_files(@scripts, $file);
+
+ open(my $fd, ">", $file) || die("open($file): $!");
+ print {$fd} <<EOF;
+rm_conffile /etc/1
+mv_conffile /etc/2 /etc/3 1.0-1
+EOF
+ close($fd) or die("close($file): $!\n");
+
+ run_dh_tool('dh_installdeb');
+
+ for my $script (@scripts) {
+ my @output=`cat debian/debhelper.$script.debhelper`;
+ ok(grep { m{^dpkg-maintscript-helper rm_conffile /etc/1 -- "\$\@"$} } @output);
+ ok(grep { m{^dpkg-maintscript-helper mv_conffile /etc/2 /etc/3 1\.0-1 -- "\$\@"$} } @output);
+ }
+});
+
+sub test_maintscript_syntax {
+ my ($contents) = @_;
+ my @scripts = map { ("debian/debhelper.${_}.debhelper", "debian/$_") } qw{postinst preinst prerm postrm};
+ my $file = 'debian/maintscript';
+
+
+ open(my $fd, ">", $file) or die("open($file): $!");
+ print {$fd} <<EOF;
+${contents}
+EOF
+ close($fd) or die("close($file): $!\n");
+
+ my $res = run_dh_tool( { 'quiet' => 1 }, 'dh_installdeb');
+
+ remove_tree('debian/debhelper', 'debian/tmp', 'debian/.debhelper');
+ rm_files(@scripts);
+
+ return $res;
+}
+
+# Negative tests
+each_compat_from_and_above_subtest(12, sub {
+ ok(!test_maintscript_syntax('rm_conffile foo 1.0~'), "rm_conffile absolute path check");
+ ok(!test_maintscript_syntax('rm_conffile /foo 1.0\~'), "rm_conffile version check");
+ ok(!test_maintscript_syntax('rm_conffile /foo 1.0~ some_pkg'), "rm_conffile package name check");
+ ok(!test_maintscript_syntax('rm_conffile /foo 1.0~ some-pkg --'), "rm_conffile separator check");
+
+ ok(!test_maintscript_syntax('mv_conffile foo /bar 1.0~'), "mv_conffile absolute (current) path check");
+ ok(!test_maintscript_syntax('mv_conffile /foo bar 1.0~'), "mv_conffile absolute (current) path check");
+ ok(!test_maintscript_syntax('mv_conffile /foo /bar 1.0\~'), "mv_conffile version check");
+ ok(!test_maintscript_syntax('mv_conffile /foo /bar 1.0~ some_pkg'), "mv_conffile package name check");
+ ok(!test_maintscript_syntax('mv_conffile /foo /bar 1.0~ some-pkg -- '), "mv_conffile separator check ");
+});
+
diff --git a/t/override_target.t b/t/override_target.t
new file mode 100755
index 0000000..235f904
--- /dev/null
+++ b/t/override_target.t
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+plan(tests => 1);
+
+# This test is here to detect breakage in
+# dh's rules_explicit_target, which parses
+# slightly internal make output.
+system("mkdir -p t/tmp/debian");
+system("cp debian/control debian/compat debian/changelog t/tmp/debian");
+open (OUT, ">", "t/tmp/debian/rules") || die "$!";
+my $binpath = $ENV{AUTOPKGTEST_TMP} ? '/usr/bin' : '../..';
+print OUT <<EOF;
+#!/usr/bin/make -f
+%:
+ PATH=../..:\$\$PATH PERL5LIB=../../lib $binpath/dh \$@ --without autoreconf
+
+override_dh_update_autotools_config override_dh_strip_nondeterminism:
+
+override_dh_auto_build:
+ echo "override called"
+EOF
+close OUT;
+system("chmod +x t/tmp/debian/rules");
+my @output=`cd t/tmp && debian/rules build 2>&1`;
+ok(grep { m/override called/ } @output) or do {
+ diag($_) for @output;
+};
+system("rm -rf t/tmp");
diff --git a/t/pod.t b/t/pod.t
new file mode 100755
index 0000000..444eed2
--- /dev/null
+++ b/t/pod.t
@@ -0,0 +1,10 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+eval { require Test::Pod; Test::Pod->import; };
+plan skip_all => 'Test::Pod required' if $@;
+
+all_pod_files_ok('debhelper.pod', grep { -x $_ } 'dh', glob 'dh_*');
diff --git a/t/size.t b/t/size.t
new file mode 100755
index 0000000..e759a47
--- /dev/null
+++ b/t/size.t
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+# This may appear arbitrary, but DO NOT CHANGE IT.
+# Debhelper is supposed to consist of small, simple, easy to understand
+# programs. Programs growing in size and complexity without bounds is a
+# bug.
+use strict;
+use warnings;
+use Test::More;
+
+my $binpath = $ENV{AUTOPKGTEST_TMP} ? '/usr/bin' : '.';
+my @progs=grep { -x $_ } glob("$binpath/dh_*");
+
+plan(tests => (@progs + @progs));
+
+foreach my $file (@progs) {
+
+ my $lines=0;
+ my $maxlength=0;
+ open(my $fd, '<', $file) || die "open($file): $!";
+ my $cutting=0;
+ while (<$fd>) {
+ $cutting=1 if /^=/;
+ $cutting=0 if /^=cut/;
+ next if $cutting || /^(?:=|\s*(?:\#.*|[}]\s*)?$)/;
+ $lines++;
+ $maxlength=length($_) if length($_) > $maxlength;
+ }
+ close($fd);
+ print "# $file has $lines lines, max length is $maxlength\n";
+ ok($lines < 200, $file);
+ ok($maxlength < 160, $file);
+}
diff --git a/t/syntax/syntax-libs.t b/t/syntax/syntax-libs.t
new file mode 120000
index 0000000..fedf32e
--- /dev/null
+++ b/t/syntax/syntax-libs.t
@@ -0,0 +1 @@
+syntax-progs.t \ No newline at end of file
diff --git a/t/syntax/syntax-progs.t b/t/syntax/syntax-progs.t
new file mode 100755
index 0000000..93d00bb
--- /dev/null
+++ b/t/syntax/syntax-progs.t
@@ -0,0 +1,28 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Test::More;
+
+use File::Basename qw(dirname);
+use lib dirname(dirname(__FILE__));
+# Need Test::More to set PERL5LIB
+use Test::DH;
+
+use Config;
+my $binpath = $ENV{AUTOPKGTEST_TMP} ? '/usr/bin' : '.';
+my $libpath = $ENV{AUTOPKGTEST_TMP} ? $Config{vendorlib} : 'lib';
+
+my @targets;
+if ($0 =~ m{syntax-progs\.t$}) {
+ @targets = grep { -x $_ } glob("$binpath/dh_*"), "$binpath/dh";
+} else {
+ @targets = (glob("$libpath/Debian/Debhelper/*.pm"), glob("$libpath/Debian/Debhelper/*/*.pm"));
+}
+
+plan(tests => scalar(@targets));
+
+foreach my $file (@targets) {
+ is(system("perl -c $file >/dev/null 2>&1"), 0)
+ or diag("$file failed syntax check");
+}
+