summaryrefslogtreecommitdiffstats
path: root/t/scripts
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:42:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:42:30 +0000
commit75808db17caf8b960b351e3408e74142f4c85aac (patch)
tree7989e9c09a4240248bf4658a22208a0a52d991c4 /t/scripts
parentInitial commit. (diff)
downloadlintian-75808db17caf8b960b351e3408e74142f4c85aac.tar.xz
lintian-75808db17caf8b960b351e3408e74142f4c85aac.zip
Adding upstream version 2.117.0.upstream/2.117.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 't/scripts')
l---------t/scripts/01-critic/bin.t1
l---------t/scripts/01-critic/lib.t1
l---------t/scripts/01-critic/private.t1
-rwxr-xr-xt/scripts/01-critic/runner.pl83
l---------t/scripts/01-critic/test-scripts.t1
l---------t/scripts/02-minimum-version/bin.t1
l---------t/scripts/02-minimum-version/lib.t1
l---------t/scripts/02-minimum-version/private.t1
-rwxr-xr-xt/scripts/02-minimum-version/runner.pl31
l---------t/scripts/02-minimum-version/test-scripts.t1
l---------t/scripts/03-strict/bin.t1
l---------t/scripts/03-strict/lib.t1
l---------t/scripts/03-strict/private.t1
-rwxr-xr-xt/scripts/03-strict/runner.pl47
l---------t/scripts/03-strict/test-scripts.t1
-rw-r--r--t/scripts/Lintian/Lab/data/changes/lintian_2.5.10_amd64.changes98
-rw-r--r--t/scripts/Lintian/Lab/data/changes/lintian_2.5.10_source.changes94
-rw-r--r--t/scripts/Lintian/Lab/data/changes/lintian_2.5.11_amd64.changes272
-rw-r--r--t/scripts/Lintian/Lab/data/changes/lintian_2.5.7_amd64.changes264
-rw-r--r--t/scripts/Lintian/Lab/data/changes/lintian_2.5.8_amd64.changes106
-rw-r--r--t/scripts/Lintian/Lab/data/changes/lintian_2.5.9_amd64.changes101
-rwxr-xr-xt/scripts/Lintian/Relation/01-basic.t19
-rwxr-xr-xt/scripts/Lintian/Relation/02-architecture.t19
-rwxr-xr-xt/scripts/Lintian/Relation/03-duplicates.t29
-rwxr-xr-xt/scripts/Lintian/Relation/04-multiarch.t64
-rwxr-xr-xt/scripts/Lintian/Relation/05-invalid.t66
-rwxr-xr-xt/scripts/Lintian/Relation/06-build-profiles.t24
-rw-r--r--t/scripts/Lintian/Relation/07-implies.t46
-rw-r--r--t/scripts/Lintian/Util/data/pgp-eof-missing-sign5
-rw-r--r--t/scripts/Lintian/Util/data/pgp-leading-unsigned14
-rw-r--r--t/scripts/Lintian/Util/data/pgp-malformed-header11
-rw-r--r--t/scripts/Lintian/Util/data/pgp-no-end-pgp-header7
-rw-r--r--t/scripts/Lintian/Util/data/pgp-sig-before-start7
-rw-r--r--t/scripts/Lintian/Util/data/pgp-trailing-unsigned14
-rw-r--r--t/scripts/Lintian/Util/data/pgp-two-signatures16
-rw-r--r--t/scripts/Lintian/Util/data/pgp-two-signed-msgs19
-rw-r--r--t/scripts/Lintian/Util/data/pgp-unexpected-header6
-rwxr-xr-xt/scripts/Lintian/Util/dctrl-parser.t64
-rwxr-xr-xt/scripts/Lintian/Util/path.t69
-rwxr-xr-xt/scripts/check-load.t50
-rw-r--r--t/scripts/harness/calibrated.t54
-rw-r--r--t/scripts/harness/check-result.t117
-rw-r--r--t/scripts/harness/desc-fields.t172
-rw-r--r--t/scripts/harness/diagnostic-value.t102
-rw-r--r--t/scripts/harness/get-tagnames.t63
-rw-r--r--t/scripts/harness/hintdiff.t207
-rw-r--r--t/scripts/harness/hintextract.t98
-rw-r--r--t/scripts/harness/match-glob.t67
-rw-r--r--t/scripts/harness/no-watch-file-in-native.t70
-rw-r--r--t/scripts/harness/prepare.t159
-rw-r--r--t/scripts/harness/sort-order.t89
-rw-r--r--t/scripts/harness/tag-coverage.t171
-rw-r--r--t/scripts/harness/watch-file-in-non-native.t70
-rwxr-xr-xt/scripts/pod-coverage.t30
-rwxr-xr-xt/scripts/pod-spelling.t140
-rwxr-xr-xt/scripts/pod-synopsis.t32
-rwxr-xr-xt/scripts/pod.t37
-rwxr-xr-xt/scripts/profile-load.t27
-rwxr-xr-xt/scripts/profiles-coverage.t81
-rw-r--r--t/scripts/run-private-scripts.t79
-rw-r--r--t/scripts/spellintian-textual-content.t74
-rwxr-xr-xt/scripts/spellintian.t169
-rw-r--r--t/scripts/tags/fields.t180
-rwxr-xr-xt/scripts/version.t38
64 files changed, 3983 insertions, 0 deletions
diff --git a/t/scripts/01-critic/bin.t b/t/scripts/01-critic/bin.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/01-critic/bin.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/01-critic/lib.t b/t/scripts/01-critic/lib.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/01-critic/lib.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/01-critic/private.t b/t/scripts/01-critic/private.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/01-critic/private.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/01-critic/runner.pl b/t/scripts/01-critic/runner.pl
new file mode 100755
index 0000000..27f7b83
--- /dev/null
+++ b/t/scripts/01-critic/runner.pl
@@ -0,0 +1,83 @@
+#!/usr/bin/perl
+
+# Simple critic test runner that guesses it task from $0.
+# NB: If you change anything in this script, consider if
+# others.t need an update as well.
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use IPC::Run3;
+use POSIX qw(ENOENT);
+
+use
+ if $ENV{'LINTIAN_COVERAGE'}, 'Test::More',
+ 'skip_all' => 'Not needed for coverage of Lintian';
+
+use Test::Lintian;
+use Test::More;
+
+plan skip_all => 'Only UNRELEASED versions are criticised'
+ if should_skip();
+
+eval 'use Test::Perl::Critic 1.00';
+plan skip_all => 'Test::Perl::Critic 1.00 required to run this test' if $@;
+
+eval 'use Perl::Tidy 20181120';
+# Actually we could just disable the perltidy check, but I am not
+# sure how to do that without making it ignore our perlcriticrc file.
+plan skip_all => 'Perl::Tidy 20180220 required to run this test' if $@;
+
+eval 'use PPIx::Regexp';
+diag('libppix-regexp-perl is needed to enable some checks') if $@;
+
+const my $DOT => q{.};
+
+my @test_paths = program_name_to_perl_paths($0);
+$ENV{'LINTIAN_BASE'} //= $DOT;
+my $critic_profile = "$ENV{'LINTIAN_BASE'}/.perlcriticrc";
+Test::Perl::Critic->import(-profile => $critic_profile);
+
+run_critic(@test_paths);
+
+exit(0);
+
+sub run_critic {
+ my (@args) = @_;
+
+ all_critic_ok(@args);
+
+ # For some reason, perltidy has started to leave behind a
+ # "perltidy.LOG" which is rather annoying. Lets have the tests
+ # unconditionally kill those.
+ my $err = unlink('perltidy.LOG');
+ if ($err) {
+ # Since this test is run in parallel, there is an
+ # race-condition between checking for the file and actually
+ # deleting. So just remove the file and ignore ENOENT
+ # problems.
+ die($err) if $err->errno != ENOENT;
+ }
+ return 1;
+}
+
+sub should_skip {
+ my $skip = 1;
+
+ my @command = qw{dpkg-parsechangelog -c0};
+ my $output;
+
+ run3(\@command, \undef, \$output);
+
+ $skip = 0
+ if $output =~ /^Distribution: UNRELEASED$/m;
+
+ return $skip;
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/01-critic/test-scripts.t b/t/scripts/01-critic/test-scripts.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/01-critic/test-scripts.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/02-minimum-version/bin.t b/t/scripts/02-minimum-version/bin.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/02-minimum-version/bin.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/02-minimum-version/lib.t b/t/scripts/02-minimum-version/lib.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/02-minimum-version/lib.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/02-minimum-version/private.t b/t/scripts/02-minimum-version/private.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/02-minimum-version/private.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/02-minimum-version/runner.pl b/t/scripts/02-minimum-version/runner.pl
new file mode 100755
index 0000000..b6dbac4
--- /dev/null
+++ b/t/scripts/02-minimum-version/runner.pl
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use Test::More;
+use Test::Lintian;
+
+plan skip_all => 'Not needed for coverage of Lintian'
+ if $ENV{'LINTIAN_COVERAGE'};
+eval 'use Test::MinimumVersion';
+plan skip_all => 'Test::MinimumVersion required to run this test' if $@;
+
+const my $DOT => q{.};
+
+# squeeze => 5.10.1, Wheezy => 5.14.2, stretch => 5.24.1
+our $REQUIRED = 'v5.24.1';
+
+my @test_paths = program_name_to_perl_paths($0);
+$ENV{'LINTIAN_BASE'} //= $DOT;
+
+all_minimum_version_ok($REQUIRED, { paths => \@test_paths, no_plan => 1});
+
+done_testing();
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/02-minimum-version/test-scripts.t b/t/scripts/02-minimum-version/test-scripts.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/02-minimum-version/test-scripts.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/03-strict/bin.t b/t/scripts/03-strict/bin.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/03-strict/bin.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/03-strict/lib.t b/t/scripts/03-strict/lib.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/03-strict/lib.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/03-strict/private.t b/t/scripts/03-strict/private.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/03-strict/private.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/03-strict/runner.pl b/t/scripts/03-strict/runner.pl
new file mode 100755
index 0000000..79f4933
--- /dev/null
+++ b/t/scripts/03-strict/runner.pl
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use Test::More;
+
+use Test::Lintian;
+
+const my $DOT => q{.};
+
+if ($ENV{'LINTIAN_COVERAGE'}) {
+ plan 'skip_all' => 'Not needed for coverage of Lintian';
+}
+
+eval 'use Test::Strict';
+plan skip_all => 'Test::Strict required to run this test' if $@;
+
+{
+ no warnings 'once';
+ $Test::Strict::TEST_WARNINGS = 1;
+}
+
+$ENV{'LINTIAN_BASE'} //= $DOT;
+# Files in commands check for the presence of LINTIAN_INCLUDE_DIRS in
+# BEGIN, so make sure it is present for them.
+$ENV{'LINTIAN_INCLUDE_DIRS'} = $ENV{'LINTIAN_BASE'};
+
+if ($0 =~ m{^(?:.*/)?reporting\.t$}) {
+ # html_reports loads ./config, so we have do chdir before checking it.
+ my $folder = "$ENV{LINTIAN_BASE}/reporting";
+ chdir($folder)
+ or die "Cannot change directory $folder";
+
+ all_perl_files_ok($DOT);
+
+} else {
+ my @test_paths = program_name_to_perl_paths($0);
+ all_perl_files_ok(@test_paths);
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/03-strict/test-scripts.t b/t/scripts/03-strict/test-scripts.t
new file mode 120000
index 0000000..c316f2d
--- /dev/null
+++ b/t/scripts/03-strict/test-scripts.t
@@ -0,0 +1 @@
+runner.pl \ No newline at end of file
diff --git a/t/scripts/Lintian/Lab/data/changes/lintian_2.5.10_amd64.changes b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.10_amd64.changes
new file mode 100644
index 0000000..d316ead
--- /dev/null
+++ b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.10_amd64.changes
@@ -0,0 +1,98 @@
+Format: 1.8
+Date: Sat, 30 Jun 2012 17:21:27 +0200
+Source: lintian
+Binary: lintian
+Architecture: source all
+Version: 2.5.10
+Distribution: unstable
+Urgency: low
+Maintainer: Debian Lintian Maintainers <lintian-maint@debian.org>
+Changed-By: Niels Thykier <niels@thykier.net>
+Description:
+ lintian - Debian package checker
+Closes: 673276 678775
+Changes:
+ lintian (2.5.10) unstable; urgency=low
+ .
+ * Summary of tag changes:
+ + Added:
+ - incompatible-java-bytecode-format
+ - unknown-java-class-version
+ .
+ * checks/changes-file.desc:
+ + [NT] Fix typo in tag description. Thanks to Luca
+ Falavigna for spotting it.
+ * checks/files:
+ + [NT] Use new gzip decompressor from L::Util.
+ * checks/java{,.desc}:
+ + [NT] Catch unknown Java class versions.
+ + [NT] Catch uses of Java7 byte code as an experimental
+ check. It lacks the proper checks for cases where
+ packages have the proper dependencies for Java7 byte
+ code. (Closes: #673276)
+ .
+ * collection/*:
+ + [NT] Add missing load of the FileHandle module.
+ * collection/java-info{,.desc}:
+ + [NT] Use Archive::Zip instead of zipinfo + unzip to
+ extract information from Jar files.
+ + [NT] Limit java-info to files classified as "Zip Archive"
+ by file(1).
+ * collection/objdump-info:
+ + [NT] Only call readelf once per binary.
+ + [NT] Use xargs + a new helper to reduce the number of
+ readelf calls even further.
+ * collection/objdump-info-helper:
+ + [NT] New file.
+ * collection/strings:
+ + [NT] Optimize for the "common case" file names with a
+ fall-back to previous behaviour with special file names.
+ * collection/strings-helper:
+ + [NT] New file.
+ .
+ * debian/control:
+ + [NT] Add (Build-)Dependency on libarchive-zip-perl.
+ + [NT] Remove runtime dependency on unzip (replaced by
+ libarchive-zip-perl).
+ + [NT] Add explicit Build-Dependency on xz-utils instead
+ of relying on dpkg-dev's dependency on it.
+ + [NT] Add versioned xz-utils as alternative to xz-lzma
+ or lzma. (Closes: #678775)
+ + [NT] Add missing suggests for lzma packages. lzma is
+ only used for source packages compressed with lzma.
+ + [NT] Add suggests on libperlio-gzip-perl.
+ .
+ * frontend/lintian:
+ + [NT] Check if some collections can be skipped for existing
+ entries. This is mostly useful for static labs, where
+ certain checks can be now be rerun without having to run
+ "unpacked".
+ + [NT] Emit run times of collections and checks with debug
+ level 2 (or higher).
+ + [NT] Load lintian's libraries earlier.
+ + [NT] Add new option "--jobs" to control the number of
+ unpacking jobs Lintian will schedule in parallel.
+ + [NT] Improve Lintian's job scheduler to schedule unpacking
+ jobs on a group level rather than just a package level.
+ .
+ * lib/Lintian/Collect/[Binary,Package}.pm:
+ + [NT] Use new gzip decompressor from L::Util.
+ * lib/Lintian/CollScript.pm:
+ + [NT] New file.
+ * lib/Lintian/Util.pm:
+ + [NT] Add gzip decompressor preferring libperlio-perl if
+ available, but with fallback to a "fork+exec" of gzip.
+ + [NT] Load Digest::SHA immediately like Digest::MD5.
+Checksums-Sha1:
+ ea252bf1960aed48a93766b7bdc6a0c9f18f51d1 2556 lintian_2.5.10.dsc
+ 4fc358f2aeb6b797f580b75a254390f7cf2f7551 1105393 lintian_2.5.10.tar.gz
+ 736dc7b2ef09398327ea270bdcb1b51ccf479758 707676 lintian_2.5.10_all.deb
+Checksums-Sha256:
+ b29adb3b1eadbc00be0274f09f5345954e1340672e76f7b3f56af2b153a08b74 2556 lintian_2.5.10.dsc
+ 249f1a23d14d824151181728cc026e0971a5960ce7f7b1b383cc1e42e4244961 1105393 lintian_2.5.10.tar.gz
+ d1405f3aeffe079c75d2a2ee56666b3e6f20eb3e3ce5c77f7888bf4838400f31 707676 lintian_2.5.10_all.deb
+Files:
+ 8767bfbf1b004474936eb319b60f8dae 2556 devel optional lintian_2.5.10.dsc
+ 5dd1d5ca1ab71f672b34992e249064ca 1105393 devel optional lintian_2.5.10.tar.gz
+ e45c47e1110e05f76dc7df35f5f501c4 707676 devel optional lintian_2.5.10_all.deb
+
diff --git a/t/scripts/Lintian/Lab/data/changes/lintian_2.5.10_source.changes b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.10_source.changes
new file mode 100644
index 0000000..4281fa1
--- /dev/null
+++ b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.10_source.changes
@@ -0,0 +1,94 @@
+Format: 1.8
+Date: Sun, 17 Jun 2012 23:25:06 +0200
+Source: lintian
+Binary: lintian
+Architecture: source
+Version: 2.5.10
+Distribution: UNRELEASED
+Urgency: low
+Maintainer: Debian Lintian Maintainers <lintian-maint@debian.org>
+Changed-By: Niels Thykier <niels@thykier.net>
+Description:
+ lintian - Debian package checker
+Closes: 673276 678775
+Changes:
+ lintian (2.5.10) UNRELEASED; urgency=low
+ .
+ * Summary of tag changes:
+ + Added:
+ - incompatible-java-bytecode-format
+ - unknown-java-class-version
+ .
+ * checks/changes-file.desc:
+ + [NT] Fix typo in tag description. Thanks to Luca
+ Falavigna for spotting it.
+ * checks/files:
+ + [NT] Use new gzip decompressor from L::Util.
+ * checks/java{,.desc}:
+ + [NT] Catch unknown Java class versions.
+ + [NT] Catch uses of Java7 byte code as an experimental
+ check. It lacks the proper checks for cases where
+ packages have the proper dependencies for Java7 byte
+ code. (Closes: #673276)
+ .
+ * collection/*:
+ + [NT] Add missing loads of FileHandle module.
+ * collection/java-info{,.desc}:
+ + [NT] Use Archive::Zip instead of zipinfo + unzip to
+ extract information from Jar files.
+ + [NT] Limit java-info to files classified as "Zip Archive"
+ by file(1).
+ * collection/objdump-info:
+ + [NT] Only call readelf once per binary.
+ + [NT] Use xargs + a new helper to reduce the number of
+ readelf calls even further.
+ * collection/objdump-info-helper:
+ + [NT] New file.
+ * collection/strings:
+ + [NT] Optimize for the "common case" file names with a
+ fall-back to previous behaviour with special file names.
+ * collection/strings-helper:
+ + [NT] New file.
+ .
+ * debian/control:
+ + [NT] Add (Build-)Dependency on libarchive-zip-perl.
+ + [NT] Remove runtime dependency on unzip (replaced by
+ libarchive-zip-perl).
+ + [NT] Add explicit Build-Dependency on xz-utils instead
+ of relying on dpkg-dev's dependency on it.
+ + [NT] Add versioned xz-utils as alternative to xz-lzma
+ or lzma. (Closes: #678775)
+ + [NT] Add missing suggests for lzma packages. lzma is
+ only used for source packages compressed with lzma.
+ + [NT] Add suggests on libperlio-gzip-perl.
+ .
+ * frontend/lintian:
+ + [NT] Check if some collections can be skipped for existing
+ entries. This is mostly useful for static labs, where
+ certain checks can be now be rerun without having to run
+ "unpacked".
+ + [NT] Emit run times of collections and checks with debug
+ level 2 (or higher).
+ + [NT] Load lintian's libraries earlier.
+ + [NT] Add new option "--jobs" to control the number of
+ unpacking jobs Lintian will schedule in parallel.
+ + [NT] Improve Lintian's job scheduler to schedule unpacking
+ jobs on a group level rather than just a package level.
+ .
+ * lib/Lintian/Collect/[Binary,Package}.pm:
+ + [NT] Use new gzip decompressor from L::Util.
+ * lib/Lintian/CollScript.pm:
+ + [NT] New file.
+ * lib/Lintian/Util.pm:
+ + [NT] Add gzip decompressor preferring libperlio-perl if
+ available, but with fallback to a "fork+exec" of gzip.
+ + [NT] Load Digest::SHA immediately like Digest::MD5.
+Checksums-Sha1:
+ b4b96b4d27c2cda31acf6d01b2e0654b6a09a132 1670 lintian_2.5.10.dsc
+ 8ea77c3b4a81fcbb63bac6a0847c687d4ddf2534 1105415 lintian_2.5.10.tar.gz
+Checksums-Sha256:
+ e15ca14cfbf30e0086cb5942b395f34a7d933e5668b3c7fa2b3900fed1ee98d2 1670 lintian_2.5.10.dsc
+ 5b91379747dfb6a779e4117db4a5bfb41154e97854de11b21475ddd273343c9c 1105415 lintian_2.5.10.tar.gz
+Files:
+ dda8d51e21900fd23393d2d4401e13d8 1670 devel optional lintian_2.5.10.dsc
+ b99b2e42c3b933efb8b147745dabdc46 1105415 devel optional lintian_2.5.10.tar.gz
diff --git a/t/scripts/Lintian/Lab/data/changes/lintian_2.5.11_amd64.changes b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.11_amd64.changes
new file mode 100644
index 0000000..23e21f8
--- /dev/null
+++ b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.11_amd64.changes
@@ -0,0 +1,272 @@
+Format: 1.8
+Date: Tue, 11 Dec 2012 22:12:06 +0100
+Source: lintian
+Binary: lintian
+Architecture: source all
+Version: 2.5.11
+Distribution: experimental
+Urgency: low
+Maintainer: Debian Lintian Maintainers <lintian-maint@debian.org>
+Changed-By: Niels Thykier <niels@thykier.net>
+Description:
+ lintian - Debian package checker
+Closes: 623265 658474 660655 672273 673073 677874 677890 678639 678896 680391 681410 681769 681894 683224 683516 685497 686179 686352 687464 688320 688494 690014 690910 691489 692232 692616 693013 693442 693589 694328
+Changes:
+ lintian (2.5.11) experimental; urgency=low
+ .
+ * Summary of tag changes:
+ + Added:
+ - conffile-has-bad-file-type
+ - debug-package-for-multi-arch-same-pkg-not-coinstallable
+ - dm-upload-allowed-is-obsolete
+ - field-name-typo-in-dep5-copyright
+ - font-adobe-copyrighted-fragment
+ - license-problem-json-evil
+ - maintainer-script-has-unexpanded-debhelper-token
+ - shlibs-uses-obsolete-relation
+ - untranslatable-debconf-templates
+ + Removed:
+ - apparently-truncated-elf-binary
+ - data.tar.xz-member-without-dpkg-pre-depends
+ - debhelper-overrides-need-versioned-build-depends
+ - no-source-field
+ - preinst-uses-dpkg-maintscript-helper-without-predepends
+ .
+ * checks/*:
+ + [NT] Remove assumption that lintian will chdir into the
+ lab before calling the check.
+ + [NT] Be better at avoiding false-positive spelling errors
+ for references to packages that also happen to be common
+ spelling mistake. Thanks to Paul Tagliamonte for the
+ report. (Closes: #687464)
+ * checks/binaries{,.desc}:
+ + [NT] Merge apparently-truncated-elf-binary into
+ apparently-corrupted-elf-binary.
+ + [NT] Remove some references to objdump in tag descriptions
+ as Lintian uses readelf.
+ + [JW,NT] Update the "extract SONAME" shell snippet to properly
+ handle SONAMEs with uppercase letters.
+ + [JW] Recognise any path with matching the GNU multi-arch
+ triplet as a "Multi-arch: same"-safe directory. Thanks to
+ Matthias Klose for the report. (Closes: #681410)
+ * checks/changelog-file:
+ + [NT] Emit "missing changelog" for packages that are missing
+ their usr/share/doc/<pkg>/ dir and do not have a doc symlink.
+ Thanks to Faheem Mitha for the report. (Closes: #683224)
+ * checks/conffiles{,.desc}:
+ + [NT] Remove leading slash on the filename when emitting
+ file-in-etc-rc.d-marked-as-conffile.
+ + [NT] Add check for "non-file" conffiles. Thanks to Guillem
+ Jover for the report. (Closes: #690910)
+ * checks/control-file.desc:
+ + [NT] Bump obsolete-relation-form-in-source to serious as these
+ forms are now "must not" instead of "should not".
+ * checks/copyright.desc:
+ + [NT] Bump debian-copyright-file-uses-obsolete-national-encoding
+ to serious as copyright files must now be UTF-8 encoded.
+ * checks/cruft{,.desc}:
+ + [NT] Detect MS-DOS executables as windows binaries.
+ + [NT] Bump the version of config.{guess,sub} needed for
+ triggering the "outdated-autotools-helper-file" tag for arm64
+ support. Thanks to Paul Wise for the report and the
+ investigative work. (Closes: #690014)
+ + [RA,NT] Extend the description of the tags {outdated,ancient}-
+ autotools-helper-file to mention that dh-autoreconf might be
+ helpful tool.
+ + [NT] Apply patch from Bastien Roucariès to detect file licensed
+ under the "Good, not Evil"-JSON license. (Closes: #692616)
+ * checks/deb-format{,.desc}:
+ + [NT] Retire data.tar.xz tag. (Closes: #680391)
+ * checks/debhelper{,.desc}:
+ + [JW,NT] Consider missing versioned build-depends on
+ debhelper for compat 8 (or less) a pedantic issue.
+ (Closes: #681894)
+ + [NT] Retire debhelper-overrides-need-versioned-build-depends.
+ * checks/fields{,.desc}:
+ + [NT] Retire no-source-field since Lintian cannot emit it any
+ more due to dpkg-source refusing to extract such source
+ packages.
+ + [NT] Add tag for using the obsolete DMUA field. Thanks to
+ Ansgar Burchardt for the report. (Closes: #688494)
+ + [NT] Apply patches from Bernhard R. Link to check for
+ unintentional whitespace and use of non-cannical URIs in
+ Vcs-* fields. (Closes: #681769)
+ + [NT] Fix false-positive caused by insignificant whitespace.
+ Thanks to Dima Kogan for the report. (Closes: #693589)
+ * checks/files:
+ + [RG] Recognise smarty3 as smarty itself.
+ + [NT] Consider "tasksel tasks" as a meta package.
+ (Closes: #691489)
+ + [NT] Add patch from Bastien Roucariès to check for adobe font
+ license issues. (Closes: #694328)
+ * checks/group-checks{,.desc}:
+ + [NT] Detect debug packages not co-installable with itself,
+ when it provides debug symbols for a Multi-Arch: same package.
+ Thanks to Carsten Hey for the report. (Closes: #678896)
+ * checks/infofiles:
+ + [NT] Use L::Util's gzip decompressor rather than zcat.
+ * checks/init.d{,.desc}:
+ + [NT] Move file-in-etc-rc.d-marked-as-conffile to conffiles
+ check.
+ + [NT] Use L::Collect's conffile API instead of accessing the
+ "conffiles" control file directly.
+ * checks/java.desc:
+ + [NT] Remove the "experimental" marker of the Java byte-code
+ check.
+ * checks/manpages{,.desc}:
+ + [CW,NT] Manually do redirects and chdir rather than
+ invoking a shell when calling man and lexgrog.
+ + [CW,NT] Be stricter with missing roff preprocessors by
+ setting MANROFFSEQ to the empty string when calling man.
+ + [CW,NT] Pass -Tutf8 -Z to man to skip an unused part of
+ the groff pipeline. (Closes: #677874)
+ + [CW,NT] Use the L::Util gzip decompressor to open gzipped
+ manpages.
+ * checks/menu-format{,.desc}:
+ + [NT] Move a table of categories to a data file.
+ + [NT] Update description of menu-icon-missing. Lintian is now
+ sometimes able to find the icon in dependencies (if they are
+ built from the same source). Thanks to Ryan Kavanagh for the
+ report and the suggested patch. (Closes: #683516)
+ * checks/md5sums:
+ + [NT] Use L::Collect's conffile API instead of accessing the
+ "conffiles" control file directly.
+ * checks/po-debconf{,.desc}:
+ + [NT] Check for untranslatable templates that should be
+ translatable. Thanks to David Prévot for the report and the
+ patch. (Closes: #686179)
+ * checks/scripts{,.desc}:
+ + [NT] Retire check for dpkg-maintscript-helper in preinst.
+ (Closes: #685497)
+ + [NT] Fix false positive "executable-not-elf-or-script" when
+ the file is an executable hardlink to a script.
+ + [NT] Check maintainer scripts for unexpanded #DEBHELPER# tokens.
+ Thanks to Cyril "KiBi" Brulebois for the suggestion.
+ + [NT] Fix false-positive for removal of device files as /dev/shm
+ is not a device. Thanks to Steve Langasek for the report and
+ Roger Leigh for the extra info. (Closes: #693442)
+ * checks/shared-libs{,.desc}:
+ + [NT] Clearify the description of dev-pkg-without-shlib-symlink
+ to mention that the dev symlink is always expected in /usr.
+ + [NT] Add missing "+" in libtool regex. Thanks to Leo 'costela'
+ Antunes for the report.
+ + [RA,NT] Check for use of obsolete "<" and ">" in shlibs control
+ files. (Closes: #660655)
+ * checks/source-copyright{,.desc}:
+ + [NT] Check for possible misspellings of known field
+ names. (Closes: #678639)
+ + [NT] Fix typo of paragraph. Thanks to Logan Rosen for spotting
+ it. (Closes: #693013)
+ * checks/version-substvars{,.desc}:
+ + [JW,NT] Extend version-substvar-for-external-package to
+ all relations. Previously it was only triggered for
+ strong dependnecy relations. (Closes: #658474)
+ .
+ * collection/deb-format.desc:
+ + [NT] Remove unneeded changelog-file from "Needs-Info".
+ * collection/objdump-info{,-helper,.desc}:
+ + [NT] Change the output format for the collection and
+ bump the version of the collection accordingly.
+ + [NT] Apply patch from Peter Pentchev to ensure set{u,g}id
+ ELF binaries are properly processed, even when Lintian is
+ run as root. (Closes: #686352)
+ .
+ * data/binaries/embedded-libs:
+ + [RG] Check for embedded copies of jsoncpp.
+ * data/fields/archive-sections:
+ + [NT] Add new "tasks" section.
+ * data/menu-format/add-categories:
+ + [NT] New file.
+ * data/output/ftp-master-{,non}fatal:
+ + [NT] Removed, not used at run time.
+ * data/scripts/interpreters:
+ + [NT] Add nodejs and Rscript as a known interpreter.
+ Thanks to Marcelo Jorge Vieira and Sébastien Boisvert
+ for the report. (Closes: #623265, #692232)
+ * data/spelling/corrections:
+ + [NT,RG] Add more corrections.
+ + [RG] Re-sort the corrections.
+ .
+ * debian/control:
+ + [NT] Add (Build-)Depends on libtext-levenshtein-perl.
+ + [NT] Use anonscm.d.o in the Vcs-* fields instead of
+ git.d.o.
+ * debian/lintian.install:
+ + [NT] Install Lintian perl modules in /usr/share/perl5.
+ * debian/rules:
+ + [NT] Add target to generate HTML API doc. Currently
+ this is only run manually.
+ .
+ * frontend/lintian:
+ + [NT] Remove chdir calls for checks.
+ + [NT] Retire depreciated command line and config options.
+ + [NT] Refactor unpackaging into Lintian::Unpacker.
+ + [NT] Make parameter for --jobs optional. In its absence
+ Lintian will not limit the number of parallel jobs.
+ + [JW,NT] During the unpack phase, emit the name of the
+ group currently being unpacked when --verbose is given.
+ (Closes: #677890)
+ + [JW] Fix typo of Parallelization.
+ + [NT] Fix regression where --suppress-tags{,-from-file}
+ was ignored if -C or -X was passed. Thanks to Thorsten
+ Glaser for reporting it. (Closes: #688320)
+ .
+ * lib/Lintian/Collect{,/Source}.pm:
+ + [NT] Add optional parameter to field (and X_field)
+ methods that denotes the default value if a field is
+ missing. This avoid some boiler plate for callers
+ of the methods.
+ * lib/Lintian/Collect/Binary.pm:
+ + [NT] Bump API for objdump method.
+ + [NT] Add API for checking if a file is considered a
+ conffile.
+ * lib/Lintian/Collect/Package.pm:
+ + [NT] Strip leading slash off files extracted from tar.
+ * lib/Lintian/Command.pm:
+ + [NT] Work around a leak in IPC::Run (see #301774).
+ * lib/Lintian/Internal/FrontendUtil.pm:
+ + [NT] Always use Dpkg::Vendor to determine the default
+ vendor. Previously dpkg-vendor would be preferred if
+ available.
+ * lib/Lintian/Lab/Entry.pm:
+ + [NT] Use the L::Collect during creation instead of
+ manually reading the dsc for source packages.
+ * lib/Lintian/Profile.pm:
+ + [NT] Add support for the new optional "Load-Checks"
+ field in profiles.
+ + [NT] Ensure that the "lintian" check is always loaded
+ as these tags are not emitted by check modules. Also
+ enable the tags from the check by default.
+ * lib/Lintian/Tags.pm:
+ + [NT] Be explicit about the reason when rejecting an
+ override. Thanks to Yves-Alexis Perez for the report.
+ (Closes: #673073)
+ * lib/Lintian/Unpacker.pm:
+ + [NT] New file.
+ .
+ * man/lintian.pod.in:
+ + [NT] Remove documentation about removed options.
+ .
+ * reporting/graphs/{statistics,tags}.gpi:
+ + [JP] New file.
+ * reporting/html_reports:
+ + [JP,NT] Generate graphs of the collected history data.
+ (Closes: #672273)
+ + [NT] Make the mirror name configurable.
+ .
+ * vendors/ubuntu/main/data/changes-file/known-dists:
+ + [NT] Add "raring" as known Ubuntu distribution. Thanks to
+ Dmitry Shachnev. (LP: #1068208)
+Checksums-Sha1:
+ 850fcd4ceaf870dd6ac276c15c43b104dba52e9b 2602 lintian_2.5.11.dsc
+ c83143fc76461efbdfd687ea63964c650de9511e 1140318 lintian_2.5.11.tar.gz
+ 2148b6ee048eb28026990c33a25d16d2ea555d7e 733808 lintian_2.5.11_all.deb
+Checksums-Sha256:
+ 482bf299f9e3e77467b06fe7a49e719d961584a4826db73a84333fe709a209a5 2602 lintian_2.5.11.dsc
+ 91f96295eac39c4711a1e53715f9c4324539665ef8aa4c1500af5ba5efd39cd5 1140318 lintian_2.5.11.tar.gz
+ 9f33060a392f8cf1adf8ed37041d66fe2a270d01732386ea5fbb3fe700c4670d 733808 lintian_2.5.11_all.deb
+Files:
+ 8b4bd5c4bea641f0ff2f0d957fbbea78 2602 devel optional lintian_2.5.11.dsc
+ 90000a9fc6b5a7061f63154a946f9b79 1140318 devel optional lintian_2.5.11.tar.gz
+ 5bd3ff3eb29a80322a57cdbe2676bfbd 733808 devel optional lintian_2.5.11_all.deb
+
diff --git a/t/scripts/Lintian/Lab/data/changes/lintian_2.5.7_amd64.changes b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.7_amd64.changes
new file mode 100644
index 0000000..1c4353a
--- /dev/null
+++ b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.7_amd64.changes
@@ -0,0 +1,264 @@
+Format: 1.8
+Date: Mon, 14 May 2012 23:45:08 +0200
+Source: lintian
+Binary: lintian
+Architecture: source all
+Version: 2.5.7
+Distribution: unstable
+Urgency: low
+Maintainer: Debian Lintian Maintainers <lintian-maint@debian.org>
+Changed-By: Niels Thykier <niels@thykier.net>
+Description:
+ lintian - Debian package checker
+Closes: 614034 628189 648777 649277 649852 650536 657402 660845 663516 664061 664471 664600 666207 666765 668546 671024 671537 672615
+Changes:
+ lintian (2.5.7) unstable; urgency=low
+ .
+ * Summary of tag changes:
+ + Added:
+ - apache2-configuration-files-need-conf-suffix
+ - apache2-deprecated-auth-config
+ - apache2-module-depends-on-real-apache2-package
+ - apache2-module-does-not-depend-on-apache2-api
+ - apache2-module-does-not-ship-load-file
+ - apache2-reverse-dependency-calls-invoke-rc.d
+ - apache2-reverse-dependency-calls-wrapper-script
+ - apache2-reverse-dependency-ships-file-in-not-allowed-directory
+ - apache2-reverse-dependency-uses-obsolete-directory
+ - apache2-unparseable-dependency
+ - apache2-unsupported-dependency
+ - diff-contains-quilt-control-dir
+ - hardening-no-fortify-functions
+ - hardening-no-relro
+ - hardening-no-stackprotector
+ - non-standard-apache2-configuration-name
+ - non-standard-apache2-module-package-name
+ - rc-version-greater-than-expected-version
+ - udeb-uses-unsupported-compression-for-data-tarball
+ - web-application-depends-on-apache2-data-package
+ - web-application-should-not-depend-unconditionally-on-apache2
+ + Removed:
+ - ancient-dpkg-long-filenames-check
+ - ancient-dpkg-predepends-check
+ - bad-ubuntu-distribution-in-changes-file
+ - binary-nmu-uses-old-version-style
+ - debian-control-with-duplicate-fields
+ - doc-base-file-references-usr-doc
+ - duplicate-fields-in-templates
+ - manpage-for-non-x11-binary-in-wrong-directory
+ - manpage-for-x11-binary-in-wrong-directory
+ - missing-dependency-on-install-info
+ - obsolete-field
+ - old-app-defaults-directory
+ - old-style-copyright-file
+ - old-style-example-dir
+ - package-installs-file-to-usr-x11r6-bin
+ - package-installs-font-to-usr-x11r6
+ - package-uses-obsolete-file
+ - postinst-should-not-set-usr-doc-link
+ - raster-image-in-scalable-directory
+ - udeb-uses-non-gzip-data-tarball
+ - x11-games-should-be-in-usr-games
+ .
+ * checks/*:
+ + [NT] Remove some old tags that are no longer useful.
+ (Closes: #663516)
+ + [NT] Migrate to sorted_index from sorted_file_info.
+ + [NT] Explicitly import needed subs from L::Util.
+ * checks/apache2{,.desc}:
+ + [NT] New files to check for apache2 related packages. Thanks
+ to Arno Töll and Stefan Fritsch for the patches.
+ (Closes: #668546)
+ + [NT] This check is not enabled by default. It can be used
+ via the debian/extra-apache2 profile.
+ * checks/binaries{,.desc}:
+ + [NT] Move embedded library data to a data file.
+ + [NT] Add ELF hardening checks. Thanks to Kees Cook for
+ report and the patches. (Closes: 650536)
+ + [NT] Replace architecture tables with data files.
+ + [JW] Check for missing Python3 numpy ABI dependency.
+ (Closes: #671024)
+ * checks/changelog-file:
+ + [NT] Output the correct line number for the "line-too-long"
+ tag. Thanks to Arno Töll for the report. (Closes: #657402)
+ * checks/changes-file{,.desc}:
+ + [NT] Remove Ubuntu specific handling of distribution names.
+ Instead replace it with a more generalized one that derivatives
+ can reuse by extending vendor specific data files. Thanks to
+ Daniel Dehennin for the suggestion. (Closes: #648777)
+ * checks/control-file:
+ + [NT] Rewrote parts to use Lintian::Collect for fetching data.
+ * checks/cruft{,.desc}:
+ + [NT] Check for quilt control dirs in the debian packaging files.
+ * checks/deb-format{,.desc}:
+ + [NT] Replace old udeb compression tag with a more general
+ one. (Closes: #664600)
+ + [NT] Remove logic for checking if a deb is meant for
+ Ubuntu. Instead unconditionally emit the tag and let the
+ vendor profiles handle it.
+ * checks/debconf:
+ + [NT] Special case debconf providers for purge-debconf tag.
+ Generally they cannot use db_purge in postrm (for obvious
+ reasons), so the tag will be a false-positive in such
+ cases.
+ * checks/fields{,.desc}:
+ + [NT] Add devref reference.
+ + [NT] Remove special handling of the Ubuntu specific field,
+ "original-maintainer". This field is now handled by vendor
+ specific data files. (Closes: #649852)
+ + [JW,NT] Check for common mistakes with preview release and
+ release candidate versions. For non-native packages, this
+ check is only done on initial uploads of new upstream
+ releases. Thanks to Stefano Rivera and Julian Taylor for
+ their additional suggestions. (Closes: #649277)
+ * checks/filename-length.desc:
+ + [ADB, NT] Reword description of package-has-long-file-name.
+ Thanks to Andreas Beckmann for suggestion.
+ * checks/files{,.desc}:
+ + [NT] Remove "manual" lazy loads of data files.
+ + [NT] Remove code for the uses-FHS-doc-dir tag.
+ + [NT] Extend icon checks to all icon directories and look for
+ raster images in "scalable" icon directories. Thanks to
+ Paul Wise for the report and Felix Geyer for the patches.
+ (Closes: #628189)
+ * checks/group-checks:
+ + [NT] Include Provides when checking for conflict relations.
+ Thanks to Damyan Ivanov for the report. (Closes: #672615)
+ * checks/java:
+ + [NT] Ignore "codeless" jars if they appear to be maven
+ javadoc jars. Thanks to Ludovic Claude for the patch.
+ (Closes: #660845)
+ * checks/lintian.desc:
+ + [NT] Updated the description of the override tags.
+ * checks/manpages{,.desc}:
+ + [RA] Detect hyphen used as minus sign following a groff \f[C] font
+ change. Thanks, Iustin Pop. (Closes: #664471)
+ * checks/menu-format:
+ + [NT] Move menu section lists into a data file.
+ + [NT] If a package is missing a menu icon, check its direct strong
+ dependencies built from the same source (if any) for the icon.
+ This fixes false-positives menu-icon-missing in some cases.
+ * checks/menus{,.desc}:
+ + [NT] Remove "manual" lazy load of data file.
+ * checks/nmu:
+ + [NT] Remove Ubuntu specific code to handle their (lack of) NMUs.
+ These tags are instead suppressed by the Ubuntu profile.
+ * chekcs/rules:
+ + [NT] Fix false-positive "ignores-make-clean-error" tag caused by
+ using make with -C and a dir containing the letter "i". Thanks to
+ Tobias Hansen for the report. (Closes: #671537)
+ * checks/scripts{,.desc}:
+ + [NT] Mention devref 6.4 in command-with-path-in-maintainer-script.
+ Thanks to Arno Töll for the patch.
+ + [NT] Do not emit unusual-interpreter if the package provides the
+ interpreter itself.
+ + [NT] Ignore the lack of exec bit on th debconf shell modules.
+ * checks/standards-version.desc:
+ + [NT] Add references to the Policy upgrading checklist. Thanks to
+ Simon Paillard for the patch.
+ .
+ * collection/*:
+ + [NT] Use Lintian::Collect to access the package index.
+ * collection/bin-pkg-control{,.desc}:
+ + [NT] Compress control-index file and bump version of
+ bin-pkg-control.
+ * collection/copyright-file:
+ + [NT] Remove code to look for old-style copyright file.
+ * collection/file-info{,.desc}:
+ + [NT] Compress file-info output and bump version of file-info.
+ * collection/hardening-info{,.desc}:
+ + [NT] New files. Thanks to Kees Cook for the patch.
+ * collection/index{,.desc}:
+ + [NT] Compress index output and bump version of index.
+ * collection/java-info{,.desc}:
+ + [NT] Compress java-info output and bump version of java-info.
+ * collection/objdump-info:
+ + [NT] Use "fail" from Lintian::Util.pm rather than embedding a
+ copy of it.
+ + [NT] Use Lintian::Collect to find ELF files.
+ + [NT] Replace all usage of objdump with readelf.
+ (Closes: #614034)
+ + [NT] Compress objdump-info output and bump version of objdump-info.
+ * collection/strings{,.desc}:
+ + [NT] Compress strings output and bump version of strings.
+ .
+ * data:
+ + [NT] Move to vendors/debian/ftp-master-auto-reject and replace
+ it with a symlink.
+ * data/binaries/{arch-{64bit-equivs,regex},hardening-tags}:
+ + [NT] New file.
+ * data/binaries/embedded-libs:
+ + [NT] New file.
+ + [NT] Add libav libraries. Thanks to Andres Mejia for the
+ suggestion and the suggested patch. (Closes: #666765)
+ * data/changes-file/{debian-dists -> known-dists}:
+ + [NT] Renamed file.
+ * data/menu-format/menu-sections:
+ + [NT] New file.
+ .
+ * debian/changelog:
+ + [NT] Amend the 2.5.5 to mention that it also added the tag
+ binaries-have-file-conflict.
+ .
+ * frontend/lintian:
+ + [JW] Fix typo in error message.
+ + [JW,NT] Fix handling of "override" option in the lintianrc file.
+ (Closes: #666207)
+ .
+ * lib/Lintian/Architecture.pm:
+ + [NT] Lazily evaluate the data file.
+ * lib/Lintian/Collect/Package.pm:
+ + [NT] Remove an extra level of quoting in index.
+ + [NT] Remove root dir from sorted_index.
+ + [NT] Keep trailing slash in dir names for file_info.
+ * lib/Lintian/Collect/Binary.pm:
+ + [NT] Remove sorted_file_info as sorted_index now produces
+ an identical list.
+ * lib/Lintian/Data.pm:
+ + [NT] Lazily load data files.
+ + [NT] Allow pre-process sub to alter existing value for a key
+ by passing the previous value as third argument.
+ + [NT] Allow vendor specific data files. They will be loaded
+ from LINTIAN_ROOT/vendors/$profile/data.
+ * lib/Lintian/Output{,/*}.pm:
+ + [NT] Replace non-printables with "?" in output.
+ * lib/Lintian/Profile.pm:
+ + [NT] Normalize profile name and replace "parents" with
+ "profile_list". The latter also includes the current profile
+ name.
+ * lib/Lintian/Tag/Info.pm:
+ + [NT] Use Lintian::Data to load the manual-references data
+ file instead using an ad-hoc parser.
+ * lib/{Text_utils => Lintian/Tag/TextUtil}.pm:
+ + [NT] Renamed module.
+ * lib/{Util => Lintian/Util}.pm:
+ + [NT] Renamed Util to Lintian::Util.
+ + [JW] Consider duplicate fields a syntax error in dctrl files.
+ Previously, duplicate fields were silently ignored (except
+ when a separate tag would check for it). (Closes: #664061)
+ + [NT] Stop exported a majority of all subs by default.
+ .
+ * profiles/ubuntu/main.profile:
+ + [NT] Add a number of NMU related tags to the list of disabled
+ tags.
+ .
+ * vendors/ubuntu/main/data/changes-file/known-dists:
+ + [NT] New file based on data/changes-file/ubuntu-dists.
+ + [ADB] Add "quantal" (Quetzal)
+ * vendors/ubuntu/main/data/common/source-fields:
+ + [NT] New file.
+ * vendors/ubuntu/main/data/fields/{binary,udeb}-fields:
+ + [NT] New files.
+Checksums-Sha1:
+ 0b03babd3aa8571eb0af02af768f7c4fade12fbd 2462 lintian_2.5.7.dsc
+ 3af1c36dbe4ae3dc7b70aa375107928c28c8555f 1087847 lintian_2.5.7.tar.gz
+ 2ebf64764da8e9b03cea8555ec6db1cf5da38f59 692506 lintian_2.5.7_all.deb
+Checksums-Sha256:
+ 0dd400eff2da35e2e1b39370a0edf8a918ce3e3cdd68b6be2fcb53ae8a143e5f 2462 lintian_2.5.7.dsc
+ c56d7550e10acb7672708911c7636611d128ab7ec3eded8e70035737581f1a26 1087847 lintian_2.5.7.tar.gz
+ 5fd3554d5e76aa70334a4a56f87c75fe6a287b9723d64330621d7a423fffb2a0 692506 lintian_2.5.7_all.deb
+Files:
+ ab60445e9f6618d0b9349dbc8e3455c3 2462 devel optional lintian_2.5.7.dsc
+ ec47bdf0735e61fffd0a582cd76cdb74 1087847 devel optional lintian_2.5.7.tar.gz
+ af45b86b4b0a254ab0cb46fab4de2bbf 692506 devel optional lintian_2.5.7_all.deb
+
diff --git a/t/scripts/Lintian/Lab/data/changes/lintian_2.5.8_amd64.changes b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.8_amd64.changes
new file mode 100644
index 0000000..bd3d11f
--- /dev/null
+++ b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.8_amd64.changes
@@ -0,0 +1,106 @@
+Format: 1.8
+Date: Tue, 29 May 2012 11:49:47 +0200
+Source: lintian
+Binary: lintian
+Architecture: source all
+Version: 2.5.8
+Distribution: unstable
+Urgency: low
+Maintainer: Debian Lintian Maintainers <lintian-maint@debian.org>
+Changed-By: Niels Thykier <niels@thykier.net>
+Description:
+ lintian - Debian package checker
+Closes: 662134 667895 669911 671387 673106 673109 673112 673198 673352 673449 673451 673611 673613 673862
+Changes:
+ lintian (2.5.8) unstable; urgency=low
+ .
+ Its like 2.5.7, only with less false positives and no FTBFS.
+ .
+ * checks/binaries{,.desc}:
+ + [NT] Fix a too strict regex causing false-positives for
+ biarch packages. This was a regression introduced in
+ version 2.5.7. Thanks to Sven Joachim for reporting it.
+ (Closes: #673106)
+ + [NT] Demote certainty of hardening-no-stackprotector to
+ wild-guess and move it to debian/extra-hardening profile.
+ + [NT] Skip hardening-no-fortify-functions for binaries only
+ using the unprotected memcpy. This greatly reduces the
+ number of false positives for binaries compiled with
+ gcc-4.7 -O2 (or higher optimization). (Closes: #673112)
+ * checks/files:
+ + [NT] Properly handle symlinks in icon directories. Thanks
+ to Nicholas Breen for the report and Felix Geyer for the
+ patches. (Closes: #673352)
+ + [NT] Ignore wrong sizes on images in animations as it may
+ be a method to implement the animation. Thanks to Matthias
+ Klumpp for the report and Felix Geyer for the patch.
+ (Closes: #673862)
+ + [NT] Ignore wrong icon sizes if the size difference is at
+ most 2px. Thanks to Felix Geyer for the patch.
+ * checks/scripts:
+ + [NT] Refactor the %versioned_interpreters into a new
+ data file.
+ * checks/shared-libs:
+ + [NT] Fix false positive "dev-pkg-without-shlib-symlink"
+ for shared libraries using "libtool -release X.Y".
+ Thanks to Sven Joachim for the report. (Closes: #673109)
+ + [NT] Fix false positive "dev-pkg-without-shlib-symlink"
+ for shared libraries installed in /lib. Lintian now
+ correctly expects the dev-symlink beneath /usr/lib.
+ Thanks to Guillem Jover for the report.
+ * checks/source-copyright:
+ + [NT] Use the in-memory contents of the copyright file
+ instead of re-reading the file when parsing it as a
+ DEP-5 copyright file.
+ .
+ * collection/objdump-info{,.desc}:
+ + [NT] Drop -D flag for readelf when looking for symbols.
+ This makes some checks more reliable in Ubuntu. Thanks
+ to Marc Deslauriers for the report and the patch.
+ (Closes: #673451)
+ .
+ * debian/control:
+ + [NT] Add versioned Build-Depends on dpkg-dev (>= 1.16.1~)
+ as the test suite relies on it. Thanks to Luca Falavigna
+ for reporting it.
+ + [NT] Update the description to mention that the version
+ of Lintian is calibrated for version 3.9.3 of the Policy.
+ * debian/lintian.install:
+ + [NT] Remove usr/share/lintian/data - Lintian does not
+ need it and dpkg will not replace the dir with a symlink.
+ .
+ * data/scripts/interpreters:
+ + [NT] Add falcon, gbr3, jython and ngp2 as known interpreters.
+ (Closes: #669911, #671387, #662134, #667895)
+ * data/scripts/versioned-interpreters:
+ + [NT] New file.
+ + [NT] Added python2.7 to the list of known Python 2 interpreters.
+ * data/spelling/corrections:
+ + [RA] Remove corrections for "writeable" and "overwriteable". These
+ spellings are permitted by the OED in UK English. (Closes: #673611)
+ + [NT] Add correction for "pointer".
+ .
+ * lib/Lintian/Collect/*.pm:
+ + [NT] Localize "$_" to avoid truncating caller's variable.
+ (Closes: #673613)
+ .
+ * t:
+ + [NT] Generate empty ".so" files used in the test. This
+ resolves an issue where dpkg-source would exclude them
+ causing a FTBFS. (Closes: #673198)
+ + [NT] Fix test failure in derivatives where some hardening
+ flags are enabled by default. Thanks to Marc Deslauriers
+ for the report and the patch. (Closes: #673449)
+Checksums-Sha1:
+ de2ae82463727f45b3d28dce15b89bbea8fc641c 2485 lintian_2.5.8.dsc
+ 576c06a94e7758e358a43b549b123052a7e49fdc 1092923 lintian_2.5.8.tar.gz
+ d6f247358c070a7e37c9f22c84aee63fbfd816fb 697324 lintian_2.5.8_all.deb
+Checksums-Sha256:
+ 20fd9f4084197aaa923af9a7bb7dba8cc06bd1ed307a16c8b14e275d91e1093f 2485 lintian_2.5.8.dsc
+ b0a6016d9a0e5ba3ed1fc00cabd8b3be75c572fcfee7cd5079d06c64f626d343 1092923 lintian_2.5.8.tar.gz
+ 587c014c2f87ea1359f139e5a37bce1acd0b1552d7c49ca0e54e5e8b24f57c5c 697324 lintian_2.5.8_all.deb
+Files:
+ 71653df24991d1734fea17f3205ef042 2485 devel optional lintian_2.5.8.dsc
+ 1121be8bcccaf75a571e4c17197bebbc 1092923 devel optional lintian_2.5.8.tar.gz
+ 4fd057457a01384ffdc4fa39888f4f4d 697324 devel optional lintian_2.5.8_all.deb
+
diff --git a/t/scripts/Lintian/Lab/data/changes/lintian_2.5.9_amd64.changes b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.9_amd64.changes
new file mode 100644
index 0000000..e3e3b69
--- /dev/null
+++ b/t/scripts/Lintian/Lab/data/changes/lintian_2.5.9_amd64.changes
@@ -0,0 +1,101 @@
+Format: 1.8
+Date: Fri, 15 Jun 2012 21:29:03 +0200
+Source: lintian
+Binary: lintian
+Architecture: source all
+Version: 2.5.9
+Distribution: unstable
+Urgency: low
+Maintainer: Debian Lintian Maintainers <lintian-maint@debian.org>
+Changed-By: Niels Thykier <niels@thykier.net>
+Description:
+ lintian - Debian package checker
+Closes: 675139 675358 675826 675832 676799 677142 677310 677649
+Changes:
+ lintian (2.5.9) unstable; urgency=low
+ .
+ * Summary of tag changes:
+ + Added:
+ - run-parts-cron-filename-contains-illegal-chars
+ + Removed:
+ - run-parts-cron-filename-contains-full-stop
+ .
+ * checks/*:
+ + [NT] Migrate to new Lintian::Path API.
+ * checks/fields:
+ + [NT] Also classify lib*-cli-dev as belonging to the section
+ cli-mono (rather than libdevel). Thanks to Chow Loong Jin
+ for the report and the patch. (Closes: #675358)
+ * checks/files{,.desc}:
+ + [NT] Rename run-parts-cron-filename-contains-full-stop and
+ have it catch "+" in the filename as well. Add reference
+ to policy 9.5.1 in the tag description.
+ * checks/init.d:
+ + [NT] Use the proper regex for matching package names rather
+ than an ad-hoc regex. Thanks to Thorsten Alteholz for the
+ report and suggested patch. (Closes: #677142)
+ * checks/shared-libs:
+ + [NT] Reverse order of two subst expressions to prevent
+ dev-pkg-without-shlib-symlink false positives. Thanks to
+ Aurelien Jarno, Yves-Alexis Perez and Stephen M. Webb for
+ the reports and the patch.
+ (Closes: #675826, #677310, #675832)
+ + [NT] Clarify that dev-pkg-without-shlib-symlink is emitted
+ on library and not "-dev" packages.
+ .
+ * collection/hardening-info:
+ + [NT] Process binaries with hardening-check via xargs. This
+ greatly reduces the processing time for packages with many
+ binaries (like some of the linux binaries). Thanks to
+ Bastian Blank for the heads up.
+ + [NT] Skip kernel modules (.ko files).
+ * collection/hardening-info-helper:
+ + [NT] New file.
+ .
+ * data/*:
+ + [NT] Refresh with tools/data from sid.
+ * data/scripts/interpreters:
+ + [NT] Replace ngp2 with ngraph. Thanks to Koichi Akabe for
+ the report. (Closes: #675139)
+ * data/spelling/corrections:
+ + [NT] Add corrections for usage, invocation(s) and ancient.
+ .
+ * debian/changelog:
+ + [NT] raster-image-in-scalable-directory was added in 2.5.7,
+ not removed.
+ .
+ * frontend/lintian{,-info}:
+ + [NT] Remove assumption that $ENV{HOME} is set/defined. If
+ $ENV{HOME} is not sent, user specific profiles will simply
+ be ignored.
+ * frontend/lintian:
+ + [NT] Attempt to automatically determine what to process, if
+ no packages are given and debian/changelog exists. Thanks
+ to Gilles Filippini for the suggestion. (Closes: #676799)
+ .
+ * lib/Lintian/Check.pm:
+ + [NT] Add regex to match valid package names.
+ * lib/Lintian/Collect/Package.pm:
+ + [NT] sorted_index now returns a list rather than a list ref.
+ + [NT] Represent files (from index) as Lintian::Path objects.
+ * lib/Lintian/Collect/Source.pm:
+ + [NT] Recognise "Package-Type" as an alternative of the "X-"
+ field of same name. Thanks to Christian Perrier for the
+ report. (Closes: #677649)
+ * lib/Lintian/Data.pm:
+ + [NT] Gracefully handle the absence of $ENV{HOME}.
+ * lib/Lintian/Path.pm:
+ + [NT] New file.
+Checksums-Sha1:
+ 5910ab721c5785b01400b3444cb9d6904ad6a876 2485 lintian_2.5.9.dsc
+ 0f0de9cbf56a6702509a95c2c976ca8f6bafef47 1100166 lintian_2.5.9.tar.gz
+ 40be3ed688b6d01c66bf9440fccefb5c32b189f3 702742 lintian_2.5.9_all.deb
+Checksums-Sha256:
+ 9263501f00c3e3a762803375c8febc9cd651a34d4a3710f48e0d13266d3ead8d 2485 lintian_2.5.9.dsc
+ bd5c285d46ab54588819879e10e16d301542162cc886939c29ec2a4da05b7357 1100166 lintian_2.5.9.tar.gz
+ 6caf9ba0dd9b98713356d03818f844be36f4e58af44d14374ec1366631eb3a2a 702742 lintian_2.5.9_all.deb
+Files:
+ 152a018943890d860377d5438d58f6fa 2485 devel optional lintian_2.5.9.dsc
+ d5abfd1dbca688d0bcaa90890052b758 1100166 devel optional lintian_2.5.9.tar.gz
+ 44934b36c0b11b01579cc4713772897c 702742 devel optional lintian_2.5.9_all.deb
+
diff --git a/t/scripts/Lintian/Relation/01-basic.t b/t/scripts/Lintian/Relation/01-basic.t
new file mode 100755
index 0000000..7838957
--- /dev/null
+++ b/t/scripts/Lintian/Relation/01-basic.t
@@ -0,0 +1,19 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 2;
+
+use Lintian::Relation;
+
+my $relation = Lintian::Relation->new->load('pkgA, altA | altB');
+
+ok($relation->satisfies('pkgA'), 'Satisfies');
+ok(!$relation->satisfies('altA'), 'Satisfies alt');
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/Lintian/Relation/02-architecture.t b/t/scripts/Lintian/Relation/02-architecture.t
new file mode 100755
index 0000000..3bdefbe
--- /dev/null
+++ b/t/scripts/Lintian/Relation/02-architecture.t
@@ -0,0 +1,19 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 2;
+
+use Lintian::Relation;
+
+my $relation
+ = Lintian::Relation->new->load_norestriction('pkgA [i386], pkgB [amd64]');
+
+ok($relation->satisfies('pkgA:any'), 'Implies arch alt [i386]');
+ok($relation->satisfies('pkgB:any'), 'Implies arch alt [amd64]');
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/Lintian/Relation/03-duplicates.t b/t/scripts/Lintian/Relation/03-duplicates.t
new file mode 100755
index 0000000..84d22c6
--- /dev/null
+++ b/t/scripts/Lintian/Relation/03-duplicates.t
@@ -0,0 +1,29 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More tests => 2;
+
+use Lintian::Relation;
+
+my $relation_a
+ = Lintian::Relation->new->load_norestriction(
+ 'pkgA, pkgB, pkgC, pkgA | pkgD');
+
+my $relation_b
+ = Lintian::Relation->new->load_norestriction(
+ 'pkgA, pkgB, pkgC, pkgD | pkgE');
+
+is_deeply(
+ $relation_a->redundancies,
+ (['pkgA:any', 'pkgA:any | pkgD:any']),
+ 'Find redundancies'
+);
+is($relation_b->redundancies, 0, 'No redundancies');
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/Lintian/Relation/04-multiarch.t b/t/scripts/Lintian/Relation/04-multiarch.t
new file mode 100755
index 0000000..7f18b6c
--- /dev/null
+++ b/t/scripts/Lintian/Relation/04-multiarch.t
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 12;
+
+use Lintian::Relation;
+
+my $orig = 'pkgA:any, pkgB, pkgC:i386';
+my $relation = Lintian::Relation->new->load($orig);
+
+ok($relation->satisfies('pkgA:any'), 'pkgA:any satisfies pkgA:any');
+
+ok($relation->satisfies('pkgB'), 'pkgB satisfies pkgB');
+
+ok(!$relation->satisfies('pkgC'), 'pkgC:i386 does not satisfy pkgC');
+ok($relation->satisfies('pkgC:i386'), 'pkgC:i386 satisfies pkgC:i386');
+
+ok($relation->satisfies('pkgB:any'), 'pkgB satisfies pkgB:any');
+
+ok(!$relation->satisfies('pkgA'), 'pkgA:any does not satisfy pkgA');
+
+ok(!$relation->satisfies('pkgC:any'), 'pkgC:i386 does not satisfy pkgC:any');
+
+is($relation->to_string, $orig, 'reconstituted eq original');
+
+my @redundancies1
+ = Lintian::Relation->new->load('pkgD, pkgD:any')->redundancies;
+is_deeply(
+ \@redundancies1,
+ [['pkgD', 'pkgD:any']],
+ 'pkgD and pkgD:any are redundant'
+);
+
+TODO: {
+ local $TODO = ':X => :Y cases are not implemented (in general)';
+
+ my @redundancies2
+ = Lintian::Relation->new->load('pkgD:i386, pkgD:any')->redundancies;
+ is_deeply(
+ \@redundancies2,
+ [['pkgD:i386', 'pkgD:any']],
+ 'pkgD:i386 and pkgD:any are redundant'
+ );
+}
+
+my @redundancies3
+ = Lintian::Relation->new->load('pkgD:i386, pkgD')->redundancies;
+is_deeply(\@redundancies3, [],'pkgD:i386 and pkgD are not redundant');
+
+my @redundancies4
+ = Lintian::Relation->new->load('pkgD:i386, pkgD:i386 (>= 1.0)')
+ ->redundancies;
+is_deeply(
+ \@redundancies4,
+ [['pkgD:i386', 'pkgD:i386 (>= 1.0)']],
+ 'Can detect pkgD:i386 redundancies'
+);
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/Lintian/Relation/05-invalid.t b/t/scripts/Lintian/Relation/05-invalid.t
new file mode 100755
index 0000000..9b42d62
--- /dev/null
+++ b/t/scripts/Lintian/Relation/05-invalid.t
@@ -0,0 +1,66 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+use Lintian::Relation;
+
+test_relation(
+ 'pkg%any (>= 1.0) , pkgB | _gf , pkgC(>=2.0)',
+ 'satisfied' => [
+ 'pkgB | _gf', # partly unparsable, but identity holds
+ 'pkgC (>= 1.0)', # regular entry
+ ],
+ 'not-satisfied' => [
+ 'pkg', # unparsable
+ 'pkg%any', # unparsable
+ 'pkgB', # OR relation with unparsable entry
+ '_gf', # OR relation
+ ],
+ 'unparsable' => ['_gf', 'pkg%any (>= 1.0)'],
+ 'reconstituted' => 'pkg%any (>= 1.0), pkgB | _gf, pkgC (>= 2.0)'
+);
+
+done_testing;
+
+sub test_relation {
+ my ($text, %tests) = @_;
+
+ my $relation_under_test = Lintian::Relation->new->load($text);
+
+ my $tests = 0;
+ if (my $reconstituted = $tests{'reconstituted'}) {
+ is($relation_under_test->to_string,
+ $reconstituted, "Reconstitute $text");
+ $tests++;
+ }
+
+ for my $other_relation (@{$tests{'satisfied'} // [] }) {
+ ok($relation_under_test->satisfies($other_relation),
+ "'$text' satisfies '$other_relation'");
+ $tests++;
+ }
+
+ for my $other_relation (@{$tests{'not-satisfied'} // [] }) {
+ ok(
+ !$relation_under_test->satisfies($other_relation),
+ "'$text' does NOT satisfy '$other_relation'"
+ );
+ $tests++;
+ }
+
+ if (my $unparsable = $tests{'unparsable'}) {
+ my @actual = $relation_under_test->unparsable_predicates;
+ is_deeply(\@actual, $unparsable, "Unparsable entries for '$text'");
+ }
+
+ cmp_ok($tests, '>=', 1, "Ran at least one test on '$text'");
+ return;
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/Lintian/Relation/06-build-profiles.t b/t/scripts/Lintian/Relation/06-build-profiles.t
new file mode 100755
index 0000000..824572d
--- /dev/null
+++ b/t/scripts/Lintian/Relation/06-build-profiles.t
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More tests => 3;
+
+use Lintian::Relation;
+
+my $relation = Lintian::Relation->new->load_norestriction(
+ 'pkgA (<= 1.0) <stage1 nocheck> <nobiarch>, pkgB (<< 1.0) <!nodoc>');
+
+ok($relation->satisfies('pkgA:any'),
+ 'Satisfies restrictions <stage1 nocheck> <nobiarch>');
+ok($relation->satisfies('pkgB:any'), 'Satisfies restriction <!nodoc>');
+
+my $rel = Lintian::Relation->new->load('pkgC <foo bar> <baz>');
+
+is($rel->to_string, 'pkgC <foo bar> <baz>', 'Reconstitute pkgC');
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/Lintian/Relation/07-implies.t b/t/scripts/Lintian/Relation/07-implies.t
new file mode 100644
index 0000000..85c7f74
--- /dev/null
+++ b/t/scripts/Lintian/Relation/07-implies.t
@@ -0,0 +1,46 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use Lintian::Relation;
+
+my @TESTS = (
+ # A, B, A->I(B), A->I_I(B), B->I(A), B->I_I(A), line
+ # - with "I" being "satisfies" and "I_I" being "satisfies_inverse".
+ ['foo (= 1.0)', 'foo (= 2.0)', 0, 1, 0, 1, __LINE__],
+ ['foo (>= 1.0)', 'foo (= 2.0)', 0, 0, 1, 0, __LINE__],
+ ['foo (>= 2.0)', 'foo (>= 1.0)', 1, 0, 0, 0, __LINE__],
+ ['foo (>> 1.0)', 'foo (>= 1.0)', 1, 0, 0, 0, __LINE__],
+ ['foo (>> 2.0)', 'foo (>> 1.0)', 1, 0, 0, 0, __LINE__],
+ ['foo (<= 1.0)', 'foo (<= 2.0)', 1, 0, 0, 0, __LINE__],
+ ['foo (<< 1.0)', 'foo (<= 1.0)', 1, 0, 0, 0, __LINE__],
+ ['foo (<< 1.0)', 'foo (<< 2.0)', 1, 0, 0, 0, __LINE__],
+);
+
+plan tests => scalar(@TESTS) * 4;
+
+for my $test (@TESTS) {
+ my ($a_raw, $b_raw, $a_i_b, $a_ii_b, $b_i_a, $b_ii_a, $lno) = @{$test};
+
+ my $relation_a = Lintian::Relation->new->load($a_raw);
+ my $relation_b = Lintian::Relation->new->load($b_raw);
+
+ is($relation_a->satisfies($relation_b),
+ $a_i_b, "$a_raw satisfies $b_raw (case 1, line $lno)");
+ is($relation_a->satisfies_inverse($relation_b),
+ $a_ii_b,"$test->[0] satisfies inverse $test->[1] (case 2, line $lno)");
+
+ is($relation_b->satisfies($relation_a),
+ $b_i_a,"$b_raw satisfies $a_raw (case 3, line $test->[6])");
+ is($relation_b->satisfies_inverse($relation_a),
+ $b_ii_a, "$b_raw satisfies inverse $a_raw (case 4, line $lno)");
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/Lintian/Util/data/pgp-eof-missing-sign b/t/scripts/Lintian/Util/data/pgp-eof-missing-sign
new file mode 100644
index 0000000..78e5ee9
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-eof-missing-sign
@@ -0,0 +1,5 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+# Missing signature block
diff --git a/t/scripts/Lintian/Util/data/pgp-leading-unsigned b/t/scripts/Lintian/Util/data/pgp-leading-unsigned
new file mode 100644
index 0000000..0b6b949
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-leading-unsigned
@@ -0,0 +1,14 @@
+Package: dpkg
+
+# Unsigned above, signed below (bad)
+
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
+
diff --git a/t/scripts/Lintian/Util/data/pgp-malformed-header b/t/scripts/Lintian/Util/data/pgp-malformed-header
new file mode 100644
index 0000000..a9e2e7b
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-malformed-header
@@ -0,0 +1,11 @@
+# Missing a dash in the end
+
+-----BEGIN PGP SIGNED MESSAGE----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE----
+
+Some signature.
+
+-----END PGP SIGNATURE----
diff --git a/t/scripts/Lintian/Util/data/pgp-no-end-pgp-header b/t/scripts/Lintian/Util/data/pgp-no-end-pgp-header
new file mode 100644
index 0000000..6d15d98
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-no-end-pgp-header
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature, missing an end marker.
diff --git a/t/scripts/Lintian/Util/data/pgp-sig-before-start b/t/scripts/Lintian/Util/data/pgp-sig-before-start
new file mode 100644
index 0000000..1d7cf12
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-sig-before-start
@@ -0,0 +1,7 @@
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
diff --git a/t/scripts/Lintian/Util/data/pgp-trailing-unsigned b/t/scripts/Lintian/Util/data/pgp-trailing-unsigned
new file mode 100644
index 0000000..1d29d87
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-trailing-unsigned
@@ -0,0 +1,14 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
+
+# Signed above, unsigned below (bad)
+
+Package: dpkg
+
diff --git a/t/scripts/Lintian/Util/data/pgp-two-signatures b/t/scripts/Lintian/Util/data/pgp-two-signatures
new file mode 100644
index 0000000..0c0b7d6
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-two-signatures
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
+
+-----BEGIN PGP SIGNATURE-----
+
+Another signature.
+
+-----END PGP SIGNATURE-----
+
diff --git a/t/scripts/Lintian/Util/data/pgp-two-signed-msgs b/t/scripts/Lintian/Util/data/pgp-two-signed-msgs
new file mode 100644
index 0000000..c8fcf9d
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-two-signed-msgs
@@ -0,0 +1,19 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: lintian
+
+-----BEGIN PGP SIGNATURE-----
+
+Some signature.
+
+-----END PGP SIGNATURE-----
+
+-----BEGIN PGP SIGNED MESSAGE-----
+
+Package: dpkg
+
+-----BEGIN PGP SIGNATURE-----
+
+Aother signature.
+
+-----END PGP SIGNATURE-----
diff --git a/t/scripts/Lintian/Util/data/pgp-unexpected-header b/t/scripts/Lintian/Util/data/pgp-unexpected-header
new file mode 100644
index 0000000..743ac85
--- /dev/null
+++ b/t/scripts/Lintian/Util/data/pgp-unexpected-header
@@ -0,0 +1,6 @@
+-----BEGIN PGP MESSAGE-----
+
+We are expecting a "SIGNED" message.
+
+-----END PGP MESSAGE-----
+
diff --git a/t/scripts/Lintian/Util/dctrl-parser.t b/t/scripts/Lintian/Util/dctrl-parser.t
new file mode 100755
index 0000000..36d9961
--- /dev/null
+++ b/t/scripts/Lintian/Util/dctrl-parser.t
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Syntax::Keyword::Try;
+use Test::More;
+
+use Lintian::Deb822;
+
+my %TESTS_BAD = (
+ 'pgp-sig-before-start' => qr/PGP signature before message/,
+ 'pgp-two-signatures' => qr/Found two PGP signatures/,
+ 'pgp-unexpected-header' => qr/Unexpected .+ header/,
+ 'pgp-malformed-header' => qr/Malformed PGP header/,
+
+ 'pgp-two-signed-msgs' => qr/Multiple PGP messages/,
+ 'pgp-no-end-pgp-header' => qr/Cannot find END PGP SIGNATURE/,
+ 'pgp-leading-unsigned' => qr/Expected PGP MESSAGE header/,
+ 'pgp-trailing-unsigned' => qr/Data after PGP SIGNATURE/,
+ 'pgp-eof-missing-sign' => qr/Cannot find BEGIN PGP SIGNATURE/,
+);
+
+my $DATADIR = $0;
+$DATADIR =~ s{[^/]+$}{};
+if ($DATADIR) {
+ # invoked in some other dir
+ $DATADIR = "$DATADIR/data";
+} else {
+ # current dir
+ $DATADIR = 'data';
+}
+
+plan skip_all => 'Data files not available'
+ unless -d $DATADIR;
+
+plan tests => scalar keys %TESTS_BAD;
+
+for my $filename (sort keys %TESTS_BAD) {
+
+ my $path = "$DATADIR/$filename";
+
+ my $deb822 = Lintian::Deb822->new;
+
+ try {
+ $deb822->read_file($path);
+
+ } catch {
+ my $error = $@;
+
+ my $fail_regex = $TESTS_BAD{$filename};
+ like($error, $fail_regex, $filename);
+
+ next;
+ }
+
+ fail("$path was parsed successfully");
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/Lintian/Util/path.t b/t/scripts/Lintian/Util/path.t
new file mode 100755
index 0000000..21c359a
--- /dev/null
+++ b/t/scripts/Lintian/Util/path.t
@@ -0,0 +1,69 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use Test::More tests => 18;
+
+const my $EMPTY => q{};
+const my $SLASH => q{/};
+const my $DOT => q{.};
+
+# Lintian::Util exports fail, which clashes with Test::More, so we
+# have to be explicit about the import(s).
+BEGIN {
+ use_ok('Lintian::Util', qw(normalize_pkg_path normalize_link_target));
+}
+
+# Safe - absolute
+is(normalize_link_target('usr/share/java', '/usr/share/ant/file'),
+ 'usr/share/ant/file', 'Safe absolute path');
+is(normalize_link_target('usr/share/ant', $SLASH),
+ $EMPTY, 'Safe absolute root');
+
+# Safe - relative
+is(normalize_link_target('/usr/share/java', './file/.'),
+ 'usr/share/java/file', 'Safe simple same-dir path');
+is(normalize_link_target('/usr/share/java', '../ant/./file'),
+ 'usr/share/ant/file', 'Safe simple relative path');
+is(
+ normalize_link_target(
+ 'usr/share/java', '../../../usr/./share/../share/./ant/file'
+ ),
+ 'usr/share/ant/file',
+ 'Safe absurd relative path'
+);
+is(
+ normalize_pkg_path(
+ 'usr/share/java/../../../usr/./share/../share/./ant/file'),
+ 'usr/share/ant/file',
+ 'Safe absurd single path argument'
+);
+is(normalize_link_target('usr/share/java', $DOT),
+ 'usr/share/java', 'Safe relative dot path');
+is(normalize_link_target($SLASH, $DOT), $EMPTY, 'Safe relative root dot');
+is(normalize_link_target($SLASH, 'usr/..'),
+ $EMPTY, 'Safe absurd relative root path');
+is(normalize_link_target('usr/share/java', '../../../'),
+ $EMPTY, 'Safe absurd relative path to root');
+is(normalize_pkg_path($DOT), $EMPTY, 'Safe single argument root dot');
+is(normalize_pkg_path($SLASH), $EMPTY, 'Safe single argument root slash');
+is(normalize_pkg_path('usr/..'),
+ $EMPTY, 'Safe absurd single relative root path');
+is(normalize_pkg_path('usr/share/java/../../../'),
+ $EMPTY, 'Safe absurd single relative path to root');
+
+# Unsafe
+is(normalize_link_target('/usr/share/ant', '../../../../etc/passwd'),
+ undef, 'Unsafe - relative escape root');
+is(normalize_link_target('/usr/share/ant', '/../etc/passwd'),
+ undef, 'Unsafe - absolute escape root');
+is(normalize_pkg_path('/usr/../../etc/passwd'),
+ undef, 'Unsafe - single path escape root');
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/check-load.t b/t/scripts/check-load.t
new file mode 100755
index 0000000..68408fe
--- /dev/null
+++ b/t/scripts/check-load.t
@@ -0,0 +1,50 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2012 Niels Thykier
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use Test::More import => ['done_testing'];
+
+use Test::Lintian;
+
+const my $DOT => q{.};
+
+# Test that all checks can be loaded (except lintian.desc, which is
+# a special case).
+sub accept_filter {
+ return !m{/lintian\.desc$};
+}
+
+my $opts = {'filter' => \&accept_filter,};
+
+$ENV{'LINTIAN_BASE'} //= $DOT;
+
+load_profile_for_test('debian/main', $ENV{'LINTIAN_BASE'});
+test_load_checks($opts, "$ENV{'LINTIAN_BASE'}/lib/Lintian/Check");
+
+done_testing;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/calibrated.t b/t/scripts/harness/calibrated.t
new file mode 100644
index 0000000..73c6f13
--- /dev/null
+++ b/t/scripts/harness/calibrated.t
@@ -0,0 +1,54 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2019 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+# The harness for Lintian's test suite. For detailed information on
+# the test suite layout and naming conventions, see t/tests/README.
+# For more information about running tests, see
+# doc/tutorial/Lintian/Tutorial/TestSuite.pod
+#
+
+use strict;
+use warnings;
+use v5.10;
+
+use File::Find::Rule;
+use Path::Tiny;
+use Test::More;
+
+my @descpaths = File::Find::Rule->file->name('desc')->in('t/recipes');
+
+# set the testing plan
+plan tests => scalar @descpaths;
+
+for my $descpath (@descpaths) {
+
+ my $testpath = path($descpath)->parent->parent->stringify;
+ my $hintspath = "$testpath/eval/hints";
+ my $literalpath = "$testpath/eval/literal";
+
+ ok(-r $hintspath || -r $literalpath,
+ "Calibrated hints or literal output is readable in $testpath");
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/check-result.t b/t/scripts/harness/check-result.t
new file mode 100644
index 0000000..33fb1ec
--- /dev/null
+++ b/t/scripts/harness/check-result.t
@@ -0,0 +1,117 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2018 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA
+
+use strict;
+use warnings;
+
+BEGIN {
+ $ENV{'LINTIAN_BASE'} //= q{.};
+}
+
+use File::Basename qw(basename);
+use File::stat;
+use File::Temp;
+use Path::Tiny;
+use Test::More;
+
+use lib "$ENV{'LINTIAN_BASE'}/lib";
+use Test::Lintian::Run qw(check_result);
+use Test::Lintian::ConfigFile qw(read_config);
+use Test::Lintian::Templates;
+
+# dummy test name; used in desc and directory name
+my $TESTNAME = 'distribution-multiple-bad';
+
+# temporary work directory
+my $tempdir = Path::Tiny->tempdir();
+my $testpath = $tempdir->child($TESTNAME);
+$testpath->mkpath;
+
+# test description
+my $desctext =<<"EOSTR";
+Testname: $TESTNAME
+Sequence: 2500
+Version: 1.0
+Description: Multiple distributions with at least one bad one
+Check:
+ changes-file
+References: Debian Bug #514853
+EOSTR
+
+my $descpath = $testpath->child('desc');
+$descpath->spew($desctext);
+
+# expected hints
+my $expectedtext =<<'EOSTR';
+distribution-multiple-bad (changes): multiple-distributions-in-changes-file stable foo-backportss bar foo
+distribution-multiple-bad (changes): bad-distribution-in-changes-file foo-backportss
+distribution-multiple-bad (changes): bad-distribution-in-changes-file foo
+distribution-multiple-bad (changes): bad-distribution-in-changes-file bar
+distribution-multiple-bad (changes): backports-upload-has-incorrect-version-number 1.0
+distribution-multiple-bad (changes): backports-changes-missing
+EOSTR
+
+my $expected = $testpath->child('hints');
+$expected->spew($expectedtext);
+
+# actual hints with one line missing
+my $nomatchtext =<<'EOSTR';
+distribution-multiple-bad (changes): multiple-distributions-in-changes-file stable foo-backportss bar foo
+distribution-multiple-bad (changes): bad-distribution-in-changes-file foo-backportss
+distribution-multiple-bad (changes): bad-distribution-in-changes-file bar
+distribution-multiple-bad (changes): backports-upload-has-incorrect-version-number 1.0
+distribution-multiple-bad (changes): backports-changes-missing
+EOSTR
+
+my $nomatch = $testpath->child('hints.nomatch');
+$nomatch->spew($nomatchtext);
+
+# copy of the expected hints
+my $match = $testpath->child('hints.match');
+$match->spew($expected->slurp);
+
+# read test case
+my $testcase = read_config($descpath);
+
+# read test defaults
+my $defaultspath = 't/defaults/desc';
+my $defaults = read_config($defaultspath);
+
+for my $name ($defaults->names) {
+ $testcase->store($name, $defaults->value($name))
+ unless $testcase->declares($name);
+}
+
+# test plan
+plan tests => 2;
+
+# check when hints match
+ok(!scalar check_result($testcase, $testpath, $expected, $match),
+ 'Same hints match');
+
+# check hints do not match
+ok(scalar check_result($testcase, $testpath, $expected, $nomatch),
+ 'Different hints do not match');
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/desc-fields.t b/t/scripts/harness/desc-fields.t
new file mode 100644
index 0000000..12207f1
--- /dev/null
+++ b/t/scripts/harness/desc-fields.t
@@ -0,0 +1,172 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2020 Felix Lechner
+# Copyright (C) 2023 Axel Beckert
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+# The harness for Lintian's test suite. For detailed information on
+# the test suite layout and naming conventions, see t/tests/README.
+# For more information about running tests, see
+# doc/tutorial/Lintian/Tutorial/TestSuite.pod
+#
+
+use strict;
+use warnings;
+use v5.10;
+
+use Const::Fast;
+use File::Find::Rule;
+use List::SomeUtils qw(uniq);
+use List::Util qw(all any);
+use Path::Tiny;
+use Test::More;
+
+BEGIN { $ENV{'LINTIAN_BASE'} //= q{.}; }
+use lib "$ENV{'LINTIAN_BASE'}/lib";
+
+use Lintian::Profile;
+use Test::Lintian::ConfigFile qw(read_config);
+
+const my $FIXED_TESTS_PER_FILE => 7;
+
+my @descpaths = File::Find::Rule->file()->name('desc')->in('t/recipes');
+
+# mandatory fields
+my @mandatory = qw(Testname);
+
+# all known fields
+my @known = qw(
+ Check
+ Default-Lintian-Options
+ Exit-Status
+ Lintian-Command-Line
+ Match-Strategy
+ Options
+ Output-Format
+ Profile
+ See-Also
+ Test-Against
+ Test-Architectures
+ Test-Conflicts
+ Test-Depends
+ Testname
+ Todo
+);
+
+# disallowed fields
+my @disallowed = qw(Test-For Checks References Reference Ref);
+
+# tests per desc
+my $perfile = $FIXED_TESTS_PER_FILE + scalar @mandatory + scalar @disallowed;
+
+# set the testing plan
+my $known_tests = $perfile * scalar @descpaths;
+
+my $profile = Lintian::Profile->new;
+$profile->load(undef, undef, 0);
+
+for my $descpath (@descpaths) {
+
+ # test for duplicate fields
+ my %count;
+ my @lines = path($descpath)->lines_utf8;
+ for my $line (@lines) {
+ my ($field) = $line =~ qr/^(\S+):/;
+ $count{$field} += 1
+ if defined $field;
+ }
+ ok(
+ (all { $count{$_} == 1 } keys %count),
+ "No duplicate fields in $descpath"
+ );
+
+ ok(
+ (
+ all {
+ my $key = $_;
+ any { $key eq $_ } @known;
+ } keys %count
+ ),
+ "No unknown field names are present in $descpath"
+ );
+
+ my $testcase = read_config($descpath);
+
+ # get test path
+ my $testpath = path($descpath)->parent->parent->stringify;
+
+ # get name from encapsulating directory
+ my $name = path($testpath)->basename;
+
+ # name equals encapsulating directory
+ is($testcase->unfolded_value('Testname'),
+ $name, "Test name matches encapsulating directory in $testpath");
+
+ # mandatory fields
+ ok($testcase->declares($_), "Field $_ exists in $name") for @mandatory;
+
+ # disallowed fields
+ ok(!$testcase->declares($_), "Field $_ does not exist in $name")
+ for @disallowed;
+
+ # no test-against without check
+ ok(!$testcase->declares('Test-Against') || $testcase->declares('Check'),
+ "No Test-Against without Check in $name");
+
+ # get checks
+ my @check_names = $testcase->trimmed_list('Check');
+
+ # no duplicates in checks
+ is(
+ (scalar @check_names),
+ (scalar uniq @check_names),
+ "No duplicates in Check in $name"
+ );
+
+ # listed checks exist
+ ok((all { length $profile->check_module_by_name->{$_} } @check_names),
+ "All checks mentioned in $testpath exist");
+
+ # no duplicates in tags against
+ my @against = $testcase->trimmed_list('Test-Against');
+ is(
+ (scalar @against),
+ (scalar uniq @against),
+ "No duplicates in Test-Against in $name"
+ );
+
+ # listed test-against belong to listed checks
+ $known_tests += scalar @against;
+ my @tags = map { @{$profile->tag_names_for_check->{$_} // []} }
+ (@check_names, 'lintian');
+ my %relatedtags = map { $_ => 1 } @tags;
+ for my $tag (@against) {
+ ok(
+ exists $relatedtags{$tag},
+ "Tags $tag in Test-Against belongs to checks listed in $testpath"
+ );
+ }
+}
+
+done_testing($known_tests);
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/diagnostic-value.t b/t/scripts/harness/diagnostic-value.t
new file mode 100644
index 0000000..f95357c
--- /dev/null
+++ b/t/scripts/harness/diagnostic-value.t
@@ -0,0 +1,102 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2020 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+# The harness for Lintian's test suite. For detailed information on
+# the test suite layout and naming conventions, see t/tests/README.
+# For more information about running tests, see
+# doc/tutorial/Lintian/Tutorial/TestSuite.pod
+#
+
+use strict;
+use warnings;
+use v5.10;
+
+use File::Find::Rule;
+use List::Compare;
+use List::SomeUtils qw(uniq);
+use Path::Tiny;
+use Test::More;
+
+use Test::Lintian::ConfigFile qw(read_config);
+use Test::Lintian::Output::Universal qw(tag_name);
+
+my @known_undeclared = qw(
+);
+
+my @descpaths = File::Find::Rule->file()->name('desc')->in('t/recipes/checks');
+
+my @testpaths;
+for my $descpath (@descpaths) {
+
+ my $testpath = path($descpath)->parent->parent->stringify;
+ my $hintspath = "$testpath/eval/hints";
+
+ push(@testpaths, $testpath)
+ if -r $hintspath;
+}
+
+# set the testing plan
+plan tests => scalar @testpaths + 2;
+
+my @undeclared;
+for my $testpath (@testpaths) {
+
+ my $descpath = "$testpath/eval/desc";
+ my $hintspath = "$testpath/eval/hints";
+
+ my $testcase = read_config($descpath);
+ my @testagainst = uniq $testcase->trimmed_list('Test-Against');
+
+ my @lines = path($hintspath)->lines_utf8({ chomp => 1 });
+ my @testfor = uniq map { tag_name($_) } @lines;
+
+ my @combined = (@testfor, @testagainst);
+
+ push(@undeclared, $testpath) unless scalar @combined;
+
+ TODO: {
+ local $TODO = "Recipe does not test for or against tags in $testpath"
+ unless scalar @combined;
+
+ ok(scalar @combined, "Recipe tests for or against tags in $testpath");
+ }
+}
+
+my $missing = scalar @undeclared;
+my $total = scalar @testpaths;
+diag "$missing tests out of $total have no declared diagnostic value.";
+
+diag "Test with unknown purpose: $_" for @undeclared;
+
+my $exceptions = List::Compare->new(\@undeclared, \@known_undeclared);
+my @unknown = $exceptions->get_Lonly;
+my @solved = $exceptions->get_Ronly;
+
+is(scalar @unknown, 0, 'All tests without a declared purpose are known');
+diag "New test without a declared purpose: $_" for @unknown;
+
+is(scalar @solved,0,'Solved test should be removed from known undeclared set');
+diag "Solved test: $_" for @solved;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/get-tagnames.t b/t/scripts/harness/get-tagnames.t
new file mode 100644
index 0000000..bed1966
--- /dev/null
+++ b/t/scripts/harness/get-tagnames.t
@@ -0,0 +1,63 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2019 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA
+
+use strict;
+use warnings;
+
+BEGIN { $ENV{'LINTIAN_BASE'} //= q{.}; }
+
+use List::SomeUtils qw(uniq);
+use Path::Tiny;
+use Test::More;
+
+use lib "$ENV{'LINTIAN_BASE'}/lib";
+use Test::Lintian::Output::Universal qw(get_tag_names);
+
+# dummy hints
+my $hintstext =<<'EOSTR';
+distribution-multiple-bad (changes): bad-distribution-in-changes-file foo-backportss
+distribution-multiple-bad (changes): bad-distribution-in-changes-file foo
+distribution-multiple-bad (changes): bad-distribution-in-changes-file bar
+distribution-multiple-bad (changes): backports-upload-has-incorrect-version-number 1.0
+distribution-multiple-bad (changes): backports-changes-missing
+EOSTR
+my $hintspath = Path::Tiny->tempfile;
+$hintspath->spew($hintstext);
+
+# read tag names from file
+my @actual = sort +uniq +get_tag_names($hintspath->stringify);
+
+my @expected = qw(
+ backports-changes-missing
+ backports-upload-has-incorrect-version-number
+ bad-distribution-in-changes-file
+);
+
+# test plan
+plan tests => 1;
+
+# check when hints match
+is_deeply(\@actual, \@expected, 'Tags read via get_tag_names match');
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/hintdiff.t b/t/scripts/harness/hintdiff.t
new file mode 100644
index 0000000..ed59f38
--- /dev/null
+++ b/t/scripts/harness/hintdiff.t
@@ -0,0 +1,207 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2019 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA
+
+use strict;
+use warnings;
+
+use Capture::Tiny qw(capture_merged);
+use Const::Fast;
+use List::Util qw(none);
+use Path::Tiny;
+use Test::More;
+
+const my $EMPTY => q{};
+const my $SPACE => q{ };
+const my $LINESEP => qr/^/;
+const my $WAIT_STATUS_SHIFT => 8;
+
+# the files below were generated from changelog-file-general on Feb 1, 2019
+
+# original file
+my $original =<<'EOSTR';
+changelog-file-general (source): latest-debian-changelog-entry-without-new-date
+changelog-file-general (binary): possible-missing-colon-in-closes Closes #555555
+changelog-file-general (binary): misspelled-closes-bug #666666
+changelog-file-general (binary): latest-debian-changelog-entry-reuses-existing-version 1:1.0-1 == 1.0-1 (last used: Fri, 01 Feb 2019 12:27:45 -0800)
+changelog-file-general (binary): latest-changelog-entry-without-new-date
+changelog-file-general (binary): improbable-bug-number-in-closes 1234
+changelog-file-general (binary): epoch-changed-but-upstream-version-did-not-go-backwards 1.0 >= 1.0
+changelog-file-general (binary): debian-changelog-line-too-long line 8
+changelog-file-general (binary): debian-changelog-line-too-long line 15
+changelog-file-general (binary): debian-changelog-file-contains-obsolete-user-emacs-settings
+changelog-file-general (binary): debian-changelog-file-contains-invalid-email-address unknown@unknown
+changelog-file-general (binary): changelog-references-temp-security-identifier TEMP-1234567-abcdef
+changelog-file-general (binary): changelog-not-compressed-with-max-compression changelog.Debian.gz
+changelog-file-general (binary): bad-intended-distribution intended to experimental but uploaded to unstable
+EOSTR
+
+# test plan
+plan tests => 8;
+
+# different order
+my $reordered =<<'EOSTR';
+changelog-file-general (binary): changelog-references-temp-security-identifier TEMP-1234567-abcdef
+changelog-file-general (binary): changelog-not-compressed-with-max-compression changelog.Debian.gz
+changelog-file-general (binary): debian-changelog-line-too-long line 15
+changelog-file-general (binary): debian-changelog-file-contains-obsolete-user-emacs-settings
+changelog-file-general (binary): debian-changelog-file-contains-invalid-email-address unknown@unknown
+changelog-file-general (binary): bad-intended-distribution intended to experimental but uploaded to unstable
+changelog-file-general (binary): possible-missing-colon-in-closes Closes #555555
+changelog-file-general (binary): misspelled-closes-bug #666666
+changelog-file-general (binary): improbable-bug-number-in-closes 1234
+changelog-file-general (binary): epoch-changed-but-upstream-version-did-not-go-backwards 1.0 >= 1.0
+changelog-file-general (binary): debian-changelog-line-too-long line 8
+changelog-file-general (binary): latest-debian-changelog-entry-reuses-existing-version 1:1.0-1 == 1.0-1 (last used: Fri, 01 Feb 2019 12:27:45 -0800)
+changelog-file-general (binary): latest-changelog-entry-without-new-date
+changelog-file-general (source): latest-debian-changelog-entry-without-new-date
+EOSTR
+
+ok(hintdiff($original, $reordered) eq $EMPTY, 'Reordered hints on the right');
+ok(hintdiff($reordered, $original) eq $EMPTY, 'Reordered hints on the left');
+
+# lines missing
+my $missing =<<'EOSTR';
+changelog-file-general (source): latest-debian-changelog-entry-without-new-date
+changelog-file-general (binary): possible-missing-colon-in-closes Closes #555555
+changelog-file-general (binary): latest-changelog-entry-without-new-date
+changelog-file-general (binary): improbable-bug-number-in-closes 1234
+changelog-file-general (binary): epoch-changed-but-upstream-version-did-not-go-backwards 1.0 >= 1.0
+changelog-file-general (binary): debian-changelog-line-too-long line 15
+changelog-file-general (binary): debian-changelog-file-contains-obsolete-user-emacs-settings
+changelog-file-general (binary): debian-changelog-file-contains-invalid-email-address unknown@unknown
+changelog-file-general (binary): changelog-references-temp-security-identifier TEMP-1234567-abcdef
+changelog-file-general (binary): bad-intended-distribution intended to experimental but uploaded to unstable
+EOSTR
+
+my $missingright =<<'EOSTR';
+-changelog-file-general (binary): misspelled-closes-bug #666666
+-changelog-file-general (binary): latest-debian-changelog-entry-reuses-existing-version 1:1.0-1 == 1.0-1 (last used: Fri, 01 Feb 2019 12:27:45 -0800)
+-changelog-file-general (binary): debian-changelog-line-too-long line 8
+-changelog-file-general (binary): changelog-not-compressed-with-max-compression changelog.Debian.gz
+EOSTR
+
+ok(hintdiff($original, $missing) eq $missingright,
+ 'Missing hints on the right');
+ok(hintdiff($missing, $original) eq complement($missingright),
+ 'Missing hints on the left');
+
+# lines extra
+my $extra =<<'EOSTR';
+changelog-file-general (source): latest-debian-changelog-entry-without-new-date
+changelog-file-general (binary): possible-missing-colon-in-closes Closes #555555
+changelog-file-general (binary): misspelled-closes-bug #666666
+changelog-file-general (binary): latest-debian-changelog-entry-reuses-existing-version 1:1.0-1 == 1.0-1 (last used: Fri, 01 Feb 2019 12:27:45 -0800)
+changelog-file-general (binary): latest-debian-changelog-entry-reuses-existing-version 1:1.0-1 == 1.0-1 (last used: Fri, 01 Feb 2019 12:27:45 -0800)
+changelog-file-general (binary): latest-changelog-entry-without-new-date
+changelog-file-general (binary): improbable-bug-number-in-closes 1234
+changelog-file-general (binary): epoch-changed-but-upstream-version-did-not-go-backwards 1.0 >= 1.0
+changelog-file-general (binary): debian-changelog-line-too-long line 8
+changelog-file-general (binary): debian-changelog-line-too-long line 15
+changelog-file-general (binary): misspelled-closes-bug #666666
+changelog-file-general (binary): debian-changelog-file-contains-obsolete-user-emacs-settings
+changelog-file-general (source): completely-new never seen before
+changelog-file-general (binary): debian-changelog-file-contains-invalid-email-address unknown@unknown
+changelog-file-general (binary): changelog-references-temp-security-identifier TEMP-1234567-abcdef
+changelog-file-general (binary): changelog-not-compressed-with-max-compression changelog.Debian.gz
+changelog-file-general (binary): bad-intended-distribution intended to experimental but uploaded to unstable
+EOSTR
+
+my $extraright =<<'EOSTR';
++changelog-file-general (source): completely-new never seen before
++changelog-file-general (binary): misspelled-closes-bug #666666
++changelog-file-general (binary): latest-debian-changelog-entry-reuses-existing-version 1:1.0-1 == 1.0-1 (last used: Fri, 01 Feb 2019 12:27:45 -0800)
+EOSTR
+
+ok(hintdiff($original, $extra) eq $extraright, 'Extra hints on the right');
+ok(hintdiff($extra, $original) eq complement($extraright),
+ 'Extra hints on the left');
+
+# lines different
+my $different =<<'EOSTR';
+changelog-file-general (source): latest-debian-changelog-entry-without-new-date
+changelog-file-general (binary): possible-missing-semicolon-in-closes Closes #555555
+changelog-file-general (binary): misspelled-closes-bug #666666
+changelog-file-general (binary): latest-debian-changelog-entry-reuses-existing-version 1:1.0-1 == 1.0-1 (last used: Fri, 01 Feb 2019 12:27:45 -0800)
+changelog-file-general (binary): latest-changelog-entry-without-new-date
+changelog-file-general (binary): improbable-bug-number-in-closes 1234
+changelog-file-general (binary): epoch-changed-but-upstream-version-did-not-go-backwards 1.0 >= 1.0
+changelog-file-general (binary): debian-changelog-line-too-long line 9
+changelog-file-general (binary): debian-changelog-line-too-long line 15
+changelog-file-general (source): debian-changelog-file-contains-obsolete-user-emacs-settings
+changelog-file-general (binary): debian-changelog-file-contains-invalid-irc-address unknown@unknown
+changelog-file-general (binary): changelog-references-temp-security-identifier TEMP-1234567-abcdef
+changelog-file-general (binary): changelog-not-compressed-with-max-compression changelog.Debian.gz
+changelog-file-general (binary): bad-intended-distribution intended to experimental but uploaded to unstable
+EOSTR
+
+my $differentright =<<'EOSTR';
+-changelog-file-general (binary): possible-missing-colon-in-closes Closes #555555
+-changelog-file-general (binary): debian-changelog-line-too-long line 8
+-changelog-file-general (binary): debian-changelog-file-contains-obsolete-user-emacs-settings
+-changelog-file-general (binary): debian-changelog-file-contains-invalid-email-address unknown@unknown
++changelog-file-general (source): debian-changelog-file-contains-obsolete-user-emacs-settings
++changelog-file-general (binary): possible-missing-semicolon-in-closes Closes #555555
++changelog-file-general (binary): debian-changelog-line-too-long line 9
++changelog-file-general (binary): debian-changelog-file-contains-invalid-irc-address unknown@unknown
+EOSTR
+
+ok(hintdiff($original, $different) eq $differentright,
+ 'Different hints on the right');
+ok(hintdiff($different, $original) eq complement($differentright),
+ 'Different hints on the left');
+
+exit;
+
+sub complement {
+ my ($diff) = @_;
+
+ return $EMPTY
+ unless length $diff;
+
+ my @lines = split($LINESEP, $diff);
+ $_ = -$_ for @lines;
+
+ return join($EMPTY, reverse sort @lines);
+}
+
+sub hintdiff {
+ my ($left_contents, $right_contents) = @_;
+
+ my $left_tiny = Path::Tiny->tempfile;
+ my $right_tiny = Path::Tiny->tempfile;
+
+ $left_tiny->spew($left_contents);
+ $right_tiny->spew($right_contents);
+
+ my @command = ('hintdiff', $left_tiny->stringify, $right_tiny->stringify);
+ my ($diff, $status) = capture_merged { system(@command); };
+ $status >>= $WAIT_STATUS_SHIFT;
+
+ die 'Error executing: ' . join($SPACE, @command) . ": $!"
+ if none { $_ eq $status } (0, 1);
+
+ return $diff;
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/hintextract.t b/t/scripts/harness/hintextract.t
new file mode 100644
index 0000000..c2f8cb9
--- /dev/null
+++ b/t/scripts/harness/hintextract.t
@@ -0,0 +1,98 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2019 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA
+
+use strict;
+use warnings;
+
+use Path::Tiny;
+use Test::More;
+
+# the text below was generated from changelog-file-general on Feb 1, 2019
+
+# expected output
+my $expected =<<'EOSTR';
+changelog-file-general (source): latest-debian-changelog-entry-without-new-date
+changelog-file-general (binary): possible-missing-colon-in-closes Closes #555555
+changelog-file-general (binary): misspelled-closes-bug #666666
+changelog-file-general (binary): latest-debian-changelog-entry-reuses-existing-version 1:1.0-1 == 1.0-1 (last used: Fri, 01 Feb 2019 12:27:45 -0800)
+changelog-file-general (binary): latest-changelog-entry-without-new-date
+changelog-file-general (binary): improbable-bug-number-in-closes 1234
+changelog-file-general (binary): epoch-changed-but-upstream-version-did-not-go-backwards 1.0 >= 1.0
+changelog-file-general (binary): debian-changelog-line-too-long line 8
+changelog-file-general (binary): debian-changelog-line-too-long line 15
+changelog-file-general (binary): debian-changelog-file-contains-obsolete-user-emacs-settings
+changelog-file-general (binary): debian-changelog-file-contains-invalid-email-address unknown@unknown
+changelog-file-general (binary): changelog-references-temp-security-identifier TEMP-1234567-abcdef
+changelog-file-general (binary): changelog-not-compressed-with-max-compression changelog.Debian.gz
+changelog-file-general (binary): bad-intended-distribution intended to experimental but uploaded to unstable
+EOSTR
+
+# test plan
+plan tests => 1;
+
+# EWI input
+my $ewi =<<'EOSTR';
+E: changelog-file-general source: latest-debian-changelog-entry-without-new-date
+W: changelog-file-general: changelog-not-compressed-with-max-compression changelog.Debian.gz
+W: changelog-file-general: debian-changelog-file-contains-obsolete-user-emacs-settings
+E: changelog-file-general: debian-changelog-file-contains-invalid-email-address unknown@unknown
+E: changelog-file-general: latest-changelog-entry-without-new-date
+W: changelog-file-general: latest-debian-changelog-entry-reuses-existing-version 1:1.0-1 == 1.0-1 (last used: Fri, 01 Feb 2019 12:27:45 -0800)
+E: changelog-file-general: epoch-changed-but-upstream-version-did-not-go-backwards 1.0 >= 1.0
+E: changelog-file-general: possible-missing-colon-in-closes Closes #555555
+W: changelog-file-general: changelog-references-temp-security-identifier TEMP-1234567-abcdef
+X: changelog-file-general: bad-intended-distribution intended to experimental but uploaded to unstable
+W: changelog-file-general: misspelled-closes-bug #666666
+W: changelog-file-general: improbable-bug-number-in-closes 1234
+W: changelog-file-general: debian-changelog-line-too-long line 8
+W: changelog-file-general: debian-changelog-line-too-long line 15
+EOSTR
+
+ok(
+ hintextract('EWI', $ewi) eq $expected,
+ 'Hints extracted from EWI format matched.'
+);
+
+exit;
+
+sub hintextract {
+ my ($format, $text) = @_;
+
+ my $outpath = Path::Tiny->tempfile;
+
+ my $inpath = Path::Tiny->tempfile;
+ $inpath->spew($text);
+
+ die "Cannot run hintextract: $!"
+ if (
+ system(
+ 'hintextract', '-f',$format,
+ $inpath->stringify,$outpath->stringify
+ )
+ );
+
+ return $outpath->slurp;
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/match-glob.t b/t/scripts/harness/match-glob.t
new file mode 100644
index 0000000..1838537
--- /dev/null
+++ b/t/scripts/harness/match-glob.t
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2022 Axel Beckert
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+# The harness for Lintian's test suite. For detailed information on
+# the test suite layout and naming conventions, see t/tests/README.
+# For more information about running tests, see
+# doc/tutorial/Lintian/Tutorial/TestSuite.pod
+#
+
+use strict;
+use warnings;
+use v5.10;
+
+use Test::More;
+use Lintian::Util qw(match_glob);
+
+is_deeply([match_glob('foo*bar', qw(foo bar foobar foobazbar xfoobazbary))],
+ [qw(foobar foobazbar)],'match_glob() with simple * wildcard');
+
+is_deeply([match_glob('fo?bar', qw(foo bar foobar foobazbar xfoobarbaz))],
+ [qw(foobar)],'match_glob() with simple ? wildcard');
+
+is_deeply(
+ [match_glob('foo*[baz]', qw(foo foo[baz] foobar[baz]))],
+ [qw(foo[baz] foobar[baz])],
+ 'match_glob() with * wildcard and literal brackets'
+);
+
+is_deeply(
+ [match_glob('foo*{baz}', qw(foo foo{baz} foobar{baz} xfoobar{baz}y))],
+ [qw(foo{baz} foobar{baz})],
+ 'match_glob() with * wildcard and literal curly braces'
+);
+
+is_deeply(
+ [match_glob('foo*(baz)', qw[foo foo(baz) foobar(baz) xfoobar(baz)y])],
+ [qw[foo(baz) foobar(baz)]],
+ 'match_glob() with * wildcard and literal parentheses'
+);
+
+is_deeply([match_glob('foo.bar', qw(foo.bar foo|bar foo&bar xfoo.bary))],
+ [qw(foo.bar)],'match_glob() with no wildcard but a literal dot');
+
+done_testing();
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/no-watch-file-in-native.t b/t/scripts/harness/no-watch-file-in-native.t
new file mode 100644
index 0000000..9df968b
--- /dev/null
+++ b/t/scripts/harness/no-watch-file-in-native.t
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2018 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA
+
+use strict;
+use warnings;
+
+BEGIN { $ENV{'LINTIAN_BASE'} //= q{.}; }
+
+use File::Basename qw(basename);
+use File::Temp;
+use File::stat;
+use List::Util qw(max);
+use Test::More;
+
+use lib "$ENV{'LINTIAN_BASE'}/lib";
+use Test::Lintian::Prepare qw(prepare);
+
+# dummy test name; used in desc and directory name
+my $TESTNAME = 'test-of-templates-for-native-package';
+
+# temporary work directory
+my $tempdir = Path::Tiny->tempdir();
+
+# specification path
+my $specpath = $tempdir->child('spec')->child($TESTNAME);
+$specpath->mkpath;
+
+# test description
+my $desctext =<<"EOSTR";
+Testname: $TESTNAME
+Version: 1
+Skeleton: upload-native
+EOSTR
+
+my $descpath = $specpath->child('fill-values');
+$descpath->spew($desctext);
+
+my $runpath = $tempdir->child('run')->child($TESTNAME);
+$runpath->mkpath;
+
+prepare($specpath->stringify, $runpath->stringify, 't');
+
+# test plan
+plan tests => 1;
+
+ok(!-e $runpath->child('debian')->child('watch')->stringify,
+ 'No watch file present');
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/prepare.t b/t/scripts/harness/prepare.t
new file mode 100644
index 0000000..90fcb39
--- /dev/null
+++ b/t/scripts/harness/prepare.t
@@ -0,0 +1,159 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2018 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA
+
+use strict;
+use warnings;
+
+BEGIN { $ENV{'LINTIAN_BASE'} //= q{.}; }
+
+use File::Basename qw(basename);
+use File::Temp;
+use File::stat;
+use IPC::Run3;
+use List::Util qw(max);
+use Test::More;
+
+use lib "$ENV{'LINTIAN_BASE'}/lib";
+use Test::Lintian::Prepare qw(prepare);
+use Test::Lintian::ConfigFile qw(read_config);
+use Test::Lintian::Helper qw(rfc822date);
+
+# dummy test name; used in desc and directory name
+my $TESTNAME = 'shared-libs-non-pic-i386';
+
+# temporary work directory
+my $tempdir = Path::Tiny->tempdir();
+
+# specification path
+my $specpath = $tempdir->child('spec')->child($TESTNAME);
+$specpath->mkpath;
+
+# test description
+my $desctext =<<"EOSTR";
+Testname: $TESTNAME
+Version: 1.0-2
+Skeleton: upload-native
+Test-Architectures: any-amd64 any-i386
+Package-Architecture: any
+Test-Depends: debhelper (>= 9.20151004~)
+Description: Test checks related to non-pic code
+Test-For: shlib-with-non-pic-code
+Check: shared-libs
+EOSTR
+
+my $descpath = $specpath->child('fill-values');
+$descpath->spew($desctext);
+
+my $runpath = $tempdir->child('run')->child($TESTNAME);
+$runpath->mkpath;
+
+prepare($specpath->stringify, $runpath->stringify, 't');
+
+# read resulting test description
+my $testcase = read_config($runpath->child('fill-values')->stringify);
+
+my @testarches = $testcase->trimmed_list('Test-Architectures');
+
+# test plan
+plan tests => 20 + scalar @testarches;
+
+is($testcase->unfolded_value('Testname'), $TESTNAME, 'Correct name');
+
+is($testcase->unfolded_value('Version'), '1.0-2', 'Correct version');
+is($testcase->unfolded_value('Upstream-Version'),
+ '1.0', 'Correct upstream version');
+
+is(
+ $testcase->unfolded_value('Test-Architectures'),
+ 'any-amd64 any-i386',
+ 'Correct test architectures'
+);
+isnt($testcase->unfolded_value('Test-Architectures'),
+ 'any', 'Correct test architectures');
+for my $testarch (@testarches) {
+ my @command
+ = (qw{dpkg-architecture --list-known --match-wildcard}, $testarch);
+ my $output;
+
+ run3(\@command, \undef, \$output);
+ my @known = grep { length } split(/\n/, $output);
+
+ cmp_ok(scalar @known, '>', 1, "Known test architecture $testarch");
+}
+
+is($testcase->unfolded_value('Host-Architecture'),
+ $ENV{'DEB_HOST_ARCH'}, 'Correct host architecture');
+isnt(
+ $testcase->unfolded_value('Host-Architecture'),
+ $testcase->unfolded_value('Test-Architectures'),
+ 'Test and host architectures are different'
+);
+
+is($testcase->unfolded_value('Package-Architecture'),
+ 'any', 'Changed package architecture');
+isnt($testcase->unfolded_value('Package-Architecture'),
+ 'all', 'Not the default package architecture');
+
+is($testcase->unfolded_value('Skeleton'), 'upload-native', 'Correct skeleton');
+
+is(
+ $testcase->unfolded_value('Test-Depends'),
+ 'debhelper (>= 9.20151004~)',
+ 'Correct test dependencies'
+);
+
+is($testcase->unfolded_value('Test-For'),
+ 'shlib-with-non-pic-code','Correct Test-For');
+ok(!$testcase->declares('Test-Against'), 'Correct Test-Against');
+
+is($testcase->unfolded_value('Standards-Version'),
+ $ENV{'POLICY_VERSION'}, 'Correct policy version');
+
+is($testcase->unfolded_value('Type'), 'native', 'Test is native');
+isnt($testcase->unfolded_value('Type'), 'yes', 'Native type not yes.');
+
+is(
+ $testcase->unfolded_value('Dh-Compat-Level'),
+ $ENV{'DEFAULT_DEBHELPER_COMPAT'},
+ 'Default debhelper compat level'
+);
+
+is(
+ $testcase->unfolded_value('Description'),
+ 'Test checks related to non-pic code',
+ 'Correct description'
+);
+isnt(
+ $testcase->unfolded_value('Description'),
+ 'No Description Available',
+ 'Not default description'
+);
+
+is(
+ $testcase->unfolded_value('Author'),
+ 'Debian Lintian Maintainers <lintian-maint@debian.org>',
+ 'Default author'
+);
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/sort-order.t b/t/scripts/harness/sort-order.t
new file mode 100644
index 0000000..a74375f
--- /dev/null
+++ b/t/scripts/harness/sort-order.t
@@ -0,0 +1,89 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2020 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+# The harness for Lintian's test suite. For detailed information on
+# the test suite layout and naming conventions, see t/tests/README.
+# For more information about running tests, see
+# doc/tutorial/Lintian/Tutorial/TestSuite.pod
+#
+
+use strict;
+use warnings;
+use v5.10;
+
+use File::Basename;
+use File::Find::Rule;
+use Path::Tiny;
+use Test::More;
+
+BEGIN { $ENV{'LINTIAN_BASE'} //= q{.}; }
+use lib "$ENV{'LINTIAN_BASE'}/lib";
+
+use Lintian::Profile;
+use Test::Lintian::ConfigFile qw(read_config);
+
+my $checkpath = 't/recipes/checks';
+
+# find all test specifications related to only one check
+my @descpaths = sort File::Find::Rule->file()->name('desc')->in($checkpath);
+
+# set the testing plan
+plan tests => 3 * scalar @descpaths;
+
+my $profile = Lintian::Profile->new;
+$profile->load(undef, undef, 0);
+
+foreach my $descpath (@descpaths) {
+
+ my $testcase = read_config($descpath);
+ my $name = $testcase->unfolded_value('Testname');
+
+ # get test path
+ my $testpath = path($descpath)->parent->stringify;
+
+ ok($testcase->declares('Check'),
+ "Test specification for $name defines a field Check");
+
+ next
+ unless $testcase->declares('Check');
+
+ my @checks = $testcase->trimmed_list('Check');
+
+ # test is only about one check
+ is(scalar @checks, 1,"Test in $testpath is associate only with one check");
+
+ next unless scalar @checks == 1;
+ my $check = $checks[0];
+
+ # get the relative location of folder containing test
+ my $parent = path($testpath)->parent->parent;
+ my $relative = $parent->relative($checkpath)->stringify;
+
+ # relative location should match check
+ is($relative, $check,
+ "Test in $testpath is located in correct folder ($relative != $check)"
+ );
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/tag-coverage.t b/t/scripts/harness/tag-coverage.t
new file mode 100644
index 0000000..366789a
--- /dev/null
+++ b/t/scripts/harness/tag-coverage.t
@@ -0,0 +1,171 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2019 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+# The harness for Lintian's test suite. For detailed information on
+# the test suite layout and naming conventions, see t/tests/README.
+# For more information about running tests, see
+# doc/tutorial/Lintian/Tutorial/TestSuite.pod
+#
+
+use strict;
+use warnings;
+use v5.10;
+
+use Const::Fast;
+use File::Basename;
+use File::Find::Rule;
+use List::Compare;
+use List::SomeUtils qw(uniq);
+use Path::Tiny;
+use Test::More;
+
+use lib ($ENV{'LINTIAN_BASE'} // q{.}) . '/lib';
+
+use Lintian::Profile;
+use Test::Lintian::ConfigFile qw(read_config);
+use Test::Lintian::Output::EWI qw(to_universal);
+use Test::Lintian::Output::Universal qw(tag_name);
+
+const my $SPACE => q{ };
+const my $NEWLINE => qq{\n};
+
+my @known_missing = (
+ qw(
+ changed-by-invalid-for-derivative
+ debian-files-list-in-source
+ debian-rules-missing-recommended-target
+ debian-rules-not-executable
+ elf-warning
+ embedded-pear-module
+ invalid-field-for-derivative
+ invalid-version-number-for-derivative
+ manual-page-in-udeb
+ no-tests
+ old-python-version-field
+ old-source-override-location
+ patch-modifying-debian-files
+ patch-system-but-direct-changes-in-diff
+ quilt-series-references-non-existent-patch
+ source-contains-quilt-control-dir
+ sphinxdoc-but-no-sphinxdoc-depends
+ unpack-message-for-deb-control
+ unpack-message-for-orig
+ uses-deprecated-adttmp
+ ),
+
+# the following tags are not testable due to restrictions in reprotest
+# building the tests causes regressions due to an unknown problem, maybe in docker
+# possibly related to https://github.com/yarnpkg/yarn/pull/1837
+# their functionality is probably better tested in piuparts
+ qw(
+ wrong-file-owner-uid-or-gid
+ bad-owner-for-doc-file
+ non-standard-game-executable-perm
+ rules-silently-require-root
+ )
+);
+
+my $profile = Lintian::Profile->new;
+$profile->load(undef, undef, 0);
+
+# find known checks
+my @known = uniq $profile->known_checks;
+
+my %checktags;
+$checktags{$_} = $profile->tag_names_for_check->{$_}for @known;
+
+my %seen;
+
+# require tags tested in their check; otherwise path could be t/recipes
+my @descpaths = File::Find::Rule->file()->name('desc')->in('t/recipes/checks');
+for my $descpath (@descpaths) {
+
+ my $testcase = read_config($descpath);
+
+ my $testpath = dirname($descpath);
+ my $hintspath = "$testpath/hints";
+
+ next unless -r $hintspath;
+
+ my $universal = path($hintspath)->slurp_utf8;
+
+ print "testcase->{testname}\n";
+ my @lines = split(/$NEWLINE/, $universal);
+ my @testfor = uniq map { tag_name($_) } @lines;
+
+ # diag "Test-For: " . join($SPACE, @testfor);
+
+ if (exists $testcase->{check}) {
+ my @checks = split($SPACE, $testcase->{check});
+ # diag "Checks: " . join($SPACE, @checks);
+ my @related;
+ push(@related, @{$checktags{$_} // []})for @checks;
+ my $lc = List::Compare->new(\@testfor, \@related);
+ @testfor = $lc->get_intersection;
+ }
+
+ $seen{$_} = 1 for @testfor;
+}
+
+# find known tags
+my @wanted = uniq $profile->known_tags;
+my $total = scalar @wanted;
+
+# set the testing plan
+plan tests => scalar @wanted + 2;
+
+for my $name (@wanted) {
+ TODO: {
+ local $TODO = "Tag $name is currently untested"
+ unless exists $seen{$name};
+
+ ok(exists $seen{$name}, "Tag $name appears in tests");
+ }
+}
+
+my @tested = keys %seen;
+
+my $comp = List::Compare->new(\@wanted, \@tested);
+my @missing = $comp->get_Lonly;
+my @extra = $comp->get_Ronly;
+
+my $found = scalar @tested;
+diag 'Missing '
+ . scalar @missing
+ . " out of $total tags for complete test coverage.";
+
+diag "Untested tag: $_" for @missing;
+#diag "Extra: $_" for @extra;
+
+my $exceptions = List::Compare->new(\@missing, \@known_missing);
+my @unknown = $exceptions->get_Lonly;
+my @solved = $exceptions->get_Ronly;
+
+is(scalar @unknown, 0, 'All tags are covered in the testsuite');
+diag "Unknown/missing tag: $_" for @unknown;
+
+is(scalar @solved, 0, 'Solved tags should be removed from known missing set');
+diag "Solved tag: $_" for @solved;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/harness/watch-file-in-non-native.t b/t/scripts/harness/watch-file-in-non-native.t
new file mode 100644
index 0000000..5bea873
--- /dev/null
+++ b/t/scripts/harness/watch-file-in-non-native.t
@@ -0,0 +1,70 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2018 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA
+
+use strict;
+use warnings;
+
+BEGIN { $ENV{'LINTIAN_BASE'} //= q{.}; }
+
+use File::Basename qw(basename);
+use File::Temp;
+use File::stat;
+use List::Util qw(max);
+use Test::More;
+
+use lib "$ENV{'LINTIAN_BASE'}/lib";
+use Test::Lintian::Prepare qw(prepare);
+
+# dummy test name; used in desc and directory name
+my $TESTNAME = 'test-of-templates-for-native-package';
+
+# temporary work directory
+my $tempdir = Path::Tiny->tempdir();
+
+# specification path
+my $specpath = $tempdir->child('spec')->child($TESTNAME);
+$specpath->mkpath;
+
+# test description
+my $desctext =<<"EOSTR";
+Testname: $TESTNAME
+Version: 1-1
+Skeleton: upload-non-native
+EOSTR
+
+my $descpath = $specpath->child('fill-values');
+$descpath->spew($desctext);
+
+my $runpath = $tempdir->child('run')->child($TESTNAME);
+$runpath->mkpath;
+
+prepare($specpath->stringify, $runpath->stringify, 't');
+
+# test plan
+plan tests => 1;
+
+ok(-e $runpath->child('debian')->child('watch')->stringify,
+ 'Watch file present');
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/pod-coverage.t b/t/scripts/pod-coverage.t
new file mode 100755
index 0000000..e440476
--- /dev/null
+++ b/t/scripts/pod-coverage.t
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Test::More;
+
+use Test::Lintian;
+
+plan skip_all => 'Not needed for coverage of Lintian'
+ if $ENV{'LINTIAN_COVERAGE'};
+
+plan skip_all => 'Test::Pod::Coverage 1.08 required for this test'
+ unless eval 'use Test::Pod::Coverage 1.08; 1';
+
+load_profile_for_test;
+
+# exempt checks and screens
+my @modules = grep { !/^Lintian::(?:Check|Screen)::/ } all_modules('lib');
+
+plan tests => scalar @modules;
+
+pod_coverage_ok($_, { coverage_class => 'Pod::Coverage::TrustPod' })
+ for @modules;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/pod-spelling.t b/t/scripts/pod-spelling.t
new file mode 100755
index 0000000..7e2e0f8
--- /dev/null
+++ b/t/scripts/pod-spelling.t
@@ -0,0 +1,140 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+
+use Const::Fast;
+use IPC::Run3;
+use Test::More;
+
+plan skip_all => 'Not needed for coverage of Lintian'
+ if $ENV{'LINTIAN_COVERAGE'};
+
+plan skip_all => 'Need newer version of aspell-en (>= 7.1)'
+ if not check_aspell();
+
+BEGIN { $ENV{'LINTIAN_BASE'} //= q{.}; }
+use lib "$ENV{'LINTIAN_BASE'}/lib";
+
+use Test::Lintian;
+
+eval 'use Test::Spelling';
+plan skip_all => 'Pod spell checking requires Test::Spelling' if $@;
+
+const my $DOT => q{.};
+
+my @GOOD_WORDS;
+while (my $line = <DATA>) {
+ $line =~ s/ \s* (?: [#] .* )? \Z//xsm;
+ push(@GOOD_WORDS, grep { length } split(/\s+/, $line));
+}
+
+add_stopwords(@GOOD_WORDS);
+
+# Hardcode spelling command as Test::Spelling prefers spell over
+# aspell if installed, too. This avoids a "Build-Conflicts: spell".
+set_spell_cmd('aspell list -l en -p /dev/null');
+
+chdir($ENV{'LINTIAN_BASE'} // $DOT)
+ or die("fatal error: could not chdir to $ENV{LINTIAN_BASE}: $!");
+
+my @CHECKS = glob('checks/*[!.]*[!c]');
+my @DIRS= qw(bin doc/tutorial lib private reporting t/scripts t/templates);
+
+all_pod_files_spelling_ok(@CHECKS, @DIRS);
+
+sub check_aspell {
+ # Ubuntu Precise has an old aspell-en, which does not recognise
+ # "basic" stuff like "indices" or "extendable".
+ my $ok = 0;
+
+ my @command = qw{dpkg -l};
+ my $output;
+
+ run3(\@command, \undef, \$output);
+ my @lines = split(/\n/, $output);
+
+ while (defined(my $line = shift @lines)) {
+ if ($line =~ m/^.i \s+ aspell-en \s+ (\S+) \s/xsm) {
+ my $version = $1;
+ require Lintian::Relation::Version;
+ Lintian::Relation::Version->import(qw(versions_gte));
+ # Print the version of aspell-en if it is not new enough
+ $ok = versions_gte($version, '7.1-0~')
+ ||diag("Found aspell-en $version, want 7.1-0~ or newer");
+ }
+ }
+
+ return $ok;
+}
+
+__DATA__
+# List of extra words that aspell doesn't know, but we need it to know
+# about. Comments are stripped and lines are split on white space, so
+# multiple words can appear on the same line
+
+
+# Names of various people that appear in the POD docs
+Russ Allbery
+Barratt
+Braakman
+Brockschmidt
+Geissert
+Lichtenheld
+Niels Thykier
+Bastien ROUCARIES
+Felix Lechner
+Schwarz
+Axel Beckert
+
+lintian Lintian Lintian's # ' # hi emacs
+dpkg
+libapt
+debian Debian DEBIAN
+
+# md is md5 butchered by aspell
+md
+# 'soft'ly which was parsed as soft'ly.
+soft'ly # ' # hi emacs
+
+# "util" is import tag ":util" from Lintian::Output, where aspell
+# dropped the ":".
+util
+
+# This is wrong in general, but it happens to be a package name that
+# we use as an example.
+alot
+
+# Other various names/fields/arguments/variables/expressions that
+# trips aspell. Ordered by nothing in particular
+PTS QA qa uploader uploaders UPLOADER Uploaders changelog changelogs
+desc COND CURVALUE subdirectory subdirectories udeb deb dsc nlist
+olist KEYN BASEDIR METADATA OO TODO dir exitcode nohang substvar
+substvars listref metadata blockingly checksum checksums Nativeness
+src nativeness Indep debfiles diffstat gz env classpath conffiles
+objdump tasksel filename Pre pre hardlink hardlinking hardlinks PROC
+dirs PROFNAME CHECKNAMES COLLMAP ERRHANDLER LPKG unpacker worklist
+BASEPATH stderr stdout stdin ascii html issuedtags subclasses
+showdescription printables overridable processables msg ORed SIGKILLs
+SIGTERM wildcard wildcards ar whitelist blacklist API amd armhf cpu
+linux whitelisted blacklisted shaX sha parsers EWI
+customisation ALGO CLOC CMD DEBFILE DEST DSCFILE FOH NOCLOSE PARENTDIR
+PGP STARTLINE STR UTF bitmask cp debconf rw processable severities
+AND'ing # ' # this is getting old
+superset YYYY dirname operm username whitespace
+Whitespace udebs multiword recognised eqv testsuite methodx multi
+multiarch relationA relationB Multi natively unordered arg CVE autodie
+hashrefs namespace subdir SIGPIPE SIG blocknumber blocksub readwindow
+REMOVESLASH STAMPFILE TAGNAME TCODE TESTDATA BLOCKSIZE jN
+POSIX t1c2pfb init runtime txt executability writability
+INHANDLE OUTHANDLES UTC timestamp faux tag_name READMEs Testname
+debhelper compat dh buildpackage uaccess udev AppStream plugdev dbgsym
+buildinfo dfsg addons xargs
+
+Buildflags
+__END__
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/pod-synopsis.t b/t/scripts/pod-synopsis.t
new file mode 100755
index 0000000..715aed4
--- /dev/null
+++ b/t/scripts/pod-synopsis.t
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use Test::More;
+
+use Test::Lintian;
+
+const my $DOT => q{.};
+
+load_profile_for_test;
+
+plan skip_all => 'Not needed for coverage of Lintian'
+ if $ENV{'LINTIAN_COVERAGE'};
+eval 'use Test::Pod';
+plan skip_all => 'Test::Pod required for testing' if $@;
+eval 'use Test::Synopsis';
+plan skip_all => 'Test::Synopsis required for testing' if $@;
+
+$ENV{'LINTIAN_BASE'} //= $DOT;
+
+my @pod_files = all_pod_files("$ENV{'LINTIAN_BASE'}/lib");
+plan tests => scalar(@pod_files);
+synopsis_ok(@pod_files);
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/pod.t b/t/scripts/pod.t
new file mode 100755
index 0000000..2b50e43
--- /dev/null
+++ b/t/scripts/pod.t
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+#
+# Test POD formatting. Taken essentially verbatim from the examples in the
+# Test::Pod documentation.
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use Test::More;
+
+plan skip_all => 'Not needed for coverage of Lintian'
+ if $ENV{'LINTIAN_COVERAGE'};
+
+eval 'use Test::Pod 1.00';
+
+const my $DOT => q{.};
+
+my $dir = $ENV{'LINTIAN_BASE'} // $DOT;
+
+my @POD_SOURCES = grep { -e } (
+ "$dir/lib",
+ "$dir/doc/tutorial",
+ "$dir/man/lintian.pod",
+ "$dir/man/lintian-annotate-hints.pod",
+ "$dir/man/lintian-explain-tags.pod",
+);
+
+my @POD_FILES = all_pod_files(@POD_SOURCES);
+
+all_pod_files_ok(@POD_FILES);
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/profile-load.t b/t/scripts/profile-load.t
new file mode 100755
index 0000000..9ad0a0c
--- /dev/null
+++ b/t/scripts/profile-load.t
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+# Test that all profiles are loadable...
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use Test::More;
+
+use Test::Lintian;
+
+const my $DOT => q{.};
+
+$ENV{'LINTIAN_BASE'} //= $DOT;
+
+# We could use a plan, but then we had to update every time we added
+# or removed a profile...
+test_load_profiles($ENV{'LINTIAN_BASE'}, $ENV{'LINTIAN_BASE'});
+
+done_testing;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/profiles-coverage.t b/t/scripts/profiles-coverage.t
new file mode 100755
index 0000000..5186ea0
--- /dev/null
+++ b/t/scripts/profiles-coverage.t
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+
+# Test for complete coverage of tags in profiles
+# - side-effect, test that all tags and checks
+# in the profiles are valid.
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use File::Find::Rule;
+use Path::Tiny;
+use Test::More;
+
+use Lintian::Deb822;
+
+const my $EMPTY => q{};
+const my $TESTS_PER_TAG => 3;
+
+# allow commas until all third-party profiles present in Lintian
+# installations, such as dpkg/main.profile, have been converted
+const my $FIELD_SEPARATOR => qr/ \s+ | \s* , \s* /sx;
+
+my $known_tests = 0;
+
+my $root = $ENV{'LINTIAN_BASE'} // q{.};
+
+my %CHECKS;
+my $checkdir = "$root/lib/Lintian/Check";
+
+# find all checks
+my @modulepaths = File::Find::Rule->file->name('*.pm')->in($checkdir);
+for my $modulepath (@modulepaths) {
+ my $relative = path($modulepath)->relative($checkdir)->stringify;
+ my ($name) = ($relative =~ /^(.*)\.pm$/);
+
+ $name =~ s{([[:upper:]])}{-\L$1}g;
+ $name =~ s{^-}{};
+ $name =~ s{/-}{/}g;
+
+ $CHECKS{$name} = [];
+}
+
+my %TAGS;
+
+# find all tags
+my @tag_paths = File::Find::Rule->file->name('*.tag')->in("$root/tags");
+for my $tag_path (@tag_paths) {
+
+ my $deb822 = Lintian::Deb822->new;
+ my @sections = $deb822->read_file($tag_path);
+
+ BAIL_OUT("$tag_path does not have at least one paragraph")
+ unless @sections;
+ my $header = shift @sections;
+
+ ok($header->declares('Tag'), "Field Tag exists in $tag_path");
+ ok($header->declares('Check'), "Field Check exists in $tag_path");
+
+ my $tag_name = $header->value('Tag');
+ my $check_name = $header->value('Check');
+
+ ok(exists $CHECKS{$check_name},
+ "Check $check_name mentioned in $tag_path exists");
+ $CHECKS{$check_name} //= [];
+ push(@{$CHECKS{$check_name}}, $tag_name);
+
+ $TAGS{$tag_name} = 0;
+}
+
+$known_tests += $TESTS_PER_TAG * scalar @tag_paths;
+
+done_testing($known_tests);
+
+exit 0;
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/run-private-scripts.t b/t/scripts/run-private-scripts.t
new file mode 100644
index 0000000..0632ea2
--- /dev/null
+++ b/t/scripts/run-private-scripts.t
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2014-2016 Jakub Wilk <jwilk@jwilk.net>
+# Copyright (C) 2017-2022 Axel Beckert <abe@debian.org>
+#
+# This program is free software. It is distributed under the terms of
+# the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use IPC::Run3;
+use Test::More tests => 2;
+
+const my $DOT => q{.};
+const my $WAIT_STATUS_SHIFT => 8;
+
+$ENV{'LINTIAN_BASE'} //= $DOT;
+my $cmd_dir = "$ENV{LINTIAN_BASE}/private";
+
+sub t {
+ my ($cmd, $expected, $expected_stderr) = @_;
+
+ subtest $cmd => sub {
+ my $command = "$cmd_dir/$cmd";
+ if (-x $command) {
+ plan tests => 3;
+
+ $expected_stderr //= qr/\A\Z/;
+ my $input = undef;
+ my $output;
+ my $error;
+ run3([$command], \$input, \$output, \$error);
+
+ my $status = ($? >> $WAIT_STATUS_SHIFT);
+ is($status, 0, "Exit status 0 of $cmd");
+ like($error, $expected_stderr,
+ "STDERR of $cmd matches $expected_stderr");
+ like($output, $expected, "Expected output of $cmd");
+ } else {
+ plan skip_all =>'due to script not present (likely not installed)';
+ }
+ };
+
+ return;
+}
+
+SKIP: {
+ skip('Only works with git', 1) unless -x '/usr/bin/git' && -d '.git';
+
+ t(
+ 'generate-tag-summary',
+ qr/Assuming commit range to be/,
+ qr/^No tags were added or removed$|\A\Z/
+ );
+}
+t('latest-policy-version', qr/^(\d+\.){3}/);
+
+done_testing();
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/spellintian-textual-content.t b/t/scripts/spellintian-textual-content.t
new file mode 100644
index 0000000..76a8166
--- /dev/null
+++ b/t/scripts/spellintian-textual-content.t
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2014-2016 Jakub Wilk <jwilk@jwilk.net>
+# Copyright (C) 2017-2023 Axel Beckert <abe@debian.org>
+#
+# This program is free software. It is distributed under the terms of
+# the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+# TODO: lintian should probably find these issues itself when running
+# against itself , i.e. without having a specific check in the test
+# suite for this. That way we especially also could use Lintian
+# overrides to declare false positives.
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use IPC::Run3;
+use List::SomeUtils qw(uniq);
+use Test::More tests => 4;
+
+const my $NEWLINE => qq{\n};
+const my $DOT => q{.};
+const my $EMPTY => q{};
+const my $WAIT_STATUS_SHIFT => 8;
+
+$ENV{'LINTIAN_BASE'} //= $DOT;
+
+my $cmd_path = "$ENV{LINTIAN_BASE}/bin/spellintian";
+my @list_of_tag_files = glob('tags/*/*.tag');
+my @list_of_doc_files = (
+ glob('doc/tutorial/Lintian/Tutorial/*.pod doc/examples/tags/m/*.desc'),
+ qw(
+ doc/README.developers.pod
+ doc/lintian.rst
+ doc/releases.md
+ doc/tutorial/Lintian/Tutorial.pod
+ )
+);
+
+sub t {
+ my ($filetype, @files) = @_;
+ my @command = ($cmd_path, @files);
+ my $output;
+ run3(\@command, undef, \$output);
+
+ my $status = ($? >> $WAIT_STATUS_SHIFT);
+ is($status, 0, "Exit status is 0 when checking $filetype");
+ is($output, $EMPTY, "No spelling errors in $filetype");
+
+ return;
+}
+
+t('tags', @list_of_tag_files);
+t('docs', @list_of_doc_files);
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/spellintian.t b/t/scripts/spellintian.t
new file mode 100755
index 0000000..719d65c
--- /dev/null
+++ b/t/scripts/spellintian.t
@@ -0,0 +1,169 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2014-2016 Jakub Wilk <jwilk@jwilk.net>
+# Copyright (C) 2017-2023 Axel Beckert <abe@debian.org>
+#
+# This program is free software. It is distributed under the terms of
+# the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any
+# later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use IPC::Run3;
+use List::SomeUtils qw(uniq);
+use Array::Utils qw(intersect);
+use Test::More tests => 8;
+
+const my $NEWLINE => qq{\n};
+const my $DOT => q{.};
+const my $WAIT_STATUS_SHIFT => 8;
+
+$ENV{'LINTIAN_BASE'} //= $DOT;
+
+my $cmd_path = "$ENV{LINTIAN_BASE}/bin/spellintian";
+my $spelling_data = "$ENV{LINTIAN_BASE}/data/spelling/corrections";
+my @word_lists
+ = qw(/usr/share/dict/american-english /usr/share/dict/british-english);
+
+# See #1019541 why some valid words are ignored and still ok to be
+# listed as a misspelled word.
+my @valid_but_very_seldom_words = qw(bellow singed want's);
+
+# See #865055 why "iff" is wrong. "publically" is a seldom, but valid
+# English word, is used in the OpenSSL license and hence causes quite
+# some false positives, when being added (again).
+my @valid_words = qw(iff publically);
+
+sub t {
+ my ($input, $expected, @options) = @_;
+
+ my @command = ($cmd_path, @options);
+ my $output;
+ run3(\@command, \$input, \$output);
+
+ my $status = ($? >> $WAIT_STATUS_SHIFT);
+ is($status, 0, 'exit status 0');
+ is($output, $expected, 'expected output');
+
+ return;
+}
+
+my $s = "A familar brown gnu allows\nto jump over the lazy dog.\n";
+
+t($s,
+ 'familar -> familiar'
+ . $NEWLINE
+ . '"allows to" -> "allows one to"'
+ . $NEWLINE);
+t(
+ $s,
+ 'familar -> familiar'
+ . $NEWLINE
+ . '"allows to" -> "allows one to"'
+ . $NEWLINE
+ . 'gnu -> GNU'
+ . $NEWLINE,
+ '--picky'
+);
+
+foreach my $word_list (@word_lists) {
+ open(my $wl_fh, '<', $word_list)
+ or die "Can't open $word_list for reading: $!";
+ local $/ = undef; # enable localized slurp mode
+ push(@valid_words, split(/\n/, <$wl_fh>));
+ close $wl_fh;
+}
+
+# Don't list identical words from American and British English twice.
+@valid_words = uniq(@valid_words);
+
+# Ignore words which are valid but very seldom and unlikely to show up
+# in Debian packages.
+foreach my $valid_but_very_seldom_word (@valid_but_very_seldom_words) {
+ @valid_words = grep { !/^$valid_but_very_seldom_word$/ } @valid_words;
+}
+
+my $iff = 0;
+my $publically = 0;
+my @case_sen;
+my @equal;
+my @valid_but_listed_words = qw();
+my @bad_spellings = qw();
+my @good_spellings = qw();
+
+open(my $sp_fh, '<', $spelling_data)
+ or die "Can't open $spelling_data for reading: $!";
+while (my $corr = <$sp_fh>) {
+ next if $corr =~ m{ ^\# | ^$ }x;
+ chomp($corr);
+
+ my ($wrong, $good) = split(/\|\|/, $corr);
+ # Check for corrections equal to original
+ if ($wrong eq $good) {
+ push @equal, $wrong;
+ # Check if case sensitive corrections have been added to the wrong
+ # file (data/spelling/corrections, not data/spelling/corrections-case).
+ # Bad example from #883041: german||German
+ } elsif ($wrong eq lc($good)) {
+ push @case_sen, $wrong;
+ }
+
+ # Needed later, e.g. for checking against lists of valid words.
+ push(@bad_spellings, $wrong);
+ push(@good_spellings, $good);
+}
+close($sp_fh);
+
+ok(
+ scalar(@equal) == 0,
+ "No no-op correction present in ${spelling_data} ("
+ . join(', ', @equal) . ')'
+);
+ok(
+ scalar(@case_sen) == 0,
+ "No case sensitive correction present in ${spelling_data} ("
+ . join(', ', @case_sen) . ')'
+);
+
+# Check if valid words have beeing has been added as correction.
+my %word_count = ();
+foreach my $word (@valid_words, @bad_spellings) {
+ $word_count{$word}++;
+}
+foreach my $word (keys %word_count) {
+ push(@valid_but_listed_words, $word) if $word_count{$word} > 1;
+}
+
+ok(
+ scalar(@valid_but_listed_words) == 0,
+ "No valid word is present in ${spelling_data} ("
+ . join(', ', sort @valid_but_listed_words) . ')'
+);
+
+my @good_bad_ugly = intersect(@bad_spellings, @good_spellings);
+
+ok(
+ scalar(@good_bad_ugly) == 0,
+ 'No bad spelling is listed as good spelling for another bad spelling ('
+ . join(', ', @good_bad_ugly) . ')'
+);
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/tags/fields.t b/t/scripts/tags/fields.t
new file mode 100644
index 0000000..971439c
--- /dev/null
+++ b/t/scripts/tags/fields.t
@@ -0,0 +1,180 @@
+#!/usr/bin/perl
+
+# Copyright (C) 2019-2020 Felix Lechner
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, you can find it on the World Wide
+# Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free
+# Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+# The harness for Lintian's test suite. For detailed information on
+# the test suite layout and naming conventions, see t/tests/README.
+# For more information about running tests, see
+# doc/tutorial/Lintian/Tutorial/TestSuite.pod
+#
+
+use v5.20;
+use warnings;
+use utf8;
+
+use Const::Fast;
+use File::Find::Rule;
+use IPC::Run3;
+use List::SomeUtils qw(true);
+use Path::Tiny;
+use Test::More;
+
+BEGIN { $ENV{'LINTIAN_BASE'} //= q{.}; }
+use lib "$ENV{'LINTIAN_BASE'}/lib";
+
+use Lintian::Deb822;
+use Lintian::Output::HTML;
+use Lintian::Profile;
+
+const my $EMPTY => q{};
+const my $SLASH => q{/};
+const my $FIXED_TESTS_PER_FILE => 8;
+
+my @tag_paths = sort File::Find::Rule->file()->name('*.tag')->in('tags');
+
+diag scalar @tag_paths . ' known tags.';
+
+# mandatory fields
+my @mandatory = qw(Tag Severity Check Explanation);
+
+# disallowed fields
+my @disallowed = qw(Reference References Ref Info Certainty);
+
+# tests per desc
+my $perfile = $FIXED_TESTS_PER_FILE + scalar @mandatory + scalar @disallowed;
+
+# set the testing plan
+plan tests => 1 + $perfile * scalar @tag_paths;
+
+my $profile = Lintian::Profile->new;
+$profile->load(undef, undef, 0);
+
+my @descpaths = sort File::Find::Rule->file()->name('*.desc')->in('tags');
+diag "Illegal desc file name $_" for @descpaths;
+is(scalar @descpaths, 0, 'No tags have the old *.desc name');
+
+for my $tag_path (@tag_paths) {
+
+ my $contents = path($tag_path)->slurp_utf8;
+ my @parts = split(m{\n\n}, $contents);
+
+ # test for duplicate fields
+ my $duplicates = 0;
+
+ for my $part (@parts) {
+ my %count;
+
+ my @lines = split(/\n/, $part);
+ for my $line (@lines) {
+ my ($field) = $line =~ qr/^(\S+):/;
+ $count{$field} += 1
+ if defined $field;
+ }
+
+ $duplicates += true { $count{$_} > 1 } keys %count;
+ }
+
+ is($duplicates, 0, "No duplicate fields in $tag_path");
+
+ my $deb822 = Lintian::Deb822->new;
+
+ my @sections = $deb822->read_file($tag_path);
+ ok(@sections >= 1, "Tag in $tag_path has at least one section");
+
+ my $fields = shift @sections;
+
+ # tag has a name
+ my $tag_name = $fields->value('Tag');
+ BAIL_OUT("Tag described in $tag_path has no name")
+ unless length $tag_name;
+
+ # tagfile is named $tag_name.tag
+ is(path($tag_path)->basename,
+ "$tag_name.tag", "Tagfile for $tag_path is named $tag_name.tag");
+
+ my $check_name = $fields->value('Check');
+
+ # tag is associated with a check
+ ok(length $check_name, "Tag in $tag_path is associated with a check");
+
+ if ($fields->value('Name-Spaced') eq 'yes') {
+
+ $tag_name = $check_name . $SLASH . $tag_name;
+
+ # encapsulating directory is name of check
+ my $subdir = path($tag_path)->parent->relative('tags');
+ is($subdir, $check_name,
+ "Tag in $tag_path is in directory named '$check_name'");
+
+ } else {
+ # encapsulating directory is first letter of tag's name
+ my $parentdir = path($tag_path)->parent->basename;
+ my $firstletter = lc(substr($tag_name, 0, 1));
+ is($parentdir, $firstletter,
+ "Tag $tag_name is in directory named '$firstletter'");
+ }
+
+ # mandatory fields
+ ok($fields->declares($_), "Field $_ exists in $tag_path")for @mandatory;
+
+ # disallowed fields
+ ok(!$fields->declares($_), "Field $_ does not exist in $tag_path")
+ for @disallowed;
+
+ ok(
+ length $profile->check_module_by_name->{$check_name},
+ "Tag in $tag_path is associated with a valid check"
+ );
+
+ ok($fields->value('Renamed-From') !~ m{,},
+ "Old tag names in $tag_path are not separated by commas");
+
+ my $html_output = Lintian::Output::HTML->new;
+
+ my $tag = $profile->get_tag($tag_name);
+ BAIL_OUT("Tag $tag_name was not loaded via profile")
+ unless defined $tag;
+
+ my $html_description;
+ open(my $fh, '>:utf8_strict', \$html_description)
+ or die 'Cannot open scalar';
+ select $fh;
+
+ print "<!DOCTYPE html><head><title>$tag_name</title></head><body>";
+ $html_output->describe_tags($profile->data, [$tag]);
+ say '</body>';
+
+ select *STDOUT;
+ close $fh;
+
+ my $tidy_out;
+ my $tidy_err;
+
+ my @tidy_command = qw(tidy -quiet);
+ run3(\@tidy_command, \$html_description, \$tidy_out, \$tidy_err);
+
+ is($tidy_err, $EMPTY,
+ "No warnings from HTML Tidy for tag description in $tag_path");
+}
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et
diff --git a/t/scripts/version.t b/t/scripts/version.t
new file mode 100755
index 0000000..6e1f23d
--- /dev/null
+++ b/t/scripts/version.t
@@ -0,0 +1,38 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+use Const::Fast;
+use Test::More tests => 13;
+
+use Lintian::Relation::Version qw(:all);
+
+const my $EQUAL => q{=};
+
+ok(versions_equal('1.0', '1.0'), 'Basic equality');
+ok(versions_equal('1.0', '1.00'), '0 == 00');
+ok(versions_gte('1.1', '1.0'), 'Basic >=');
+ok(!versions_lte('1.1', '1.0'), 'Basic <=');
+ok(versions_gt('1.1', '1.0'), 'Basic >');
+ok(!versions_lt('1.1', '1.1'), 'Basic <');
+
+ok(versions_compare('1.1', '<=', '1.1'), 'compare() <=');
+ok(versions_compare('1.2', '>=', '1.1'), 'compare() >=');
+ok(versions_compare('0:1-1', $EQUAL, '1-1'), 'compare() = with epoch 0');
+ok(versions_compare('2.3~', '<<', '2.3'), 'compare() << with tilde');
+ok(!versions_compare('1:1.0', '>>', '1:1.1'), 'compare() >> with equal epoch');
+ok(
+ !versions_compare('1:1.1', '>>', '2:1.0'),
+ 'compare() >> with different epochs'
+);
+ok(
+ versions_compare('1:1.1', '<<', '2:1.1'),
+ 'compare() << with different epochs'
+);
+
+# Local Variables:
+# indent-tabs-mode: nil
+# cperl-indent-level: 4
+# End:
+# vim: syntax=perl sw=4 sts=4 sr et