Compare commits

..

No commits in common. "009089ee8a94ad5066b83c5a4cdecfd762a400aa" and "b46149b8513c4107f932ea408cdbca725bc74ec1" have entirely different histories.

11 changed files with 54 additions and 277 deletions

View file

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from debian.deb822 import Deb822, Release from debian.deb822 import Deb822
import email.utils
import os import os
import sys import sys
import shutil import shutil
@ -15,7 +14,6 @@ have_qemu = os.getenv("HAVE_QEMU", "yes") == "yes"
have_unshare = os.getenv("HAVE_UNSHARE", "yes") == "yes" have_unshare = os.getenv("HAVE_UNSHARE", "yes") == "yes"
have_binfmt = os.getenv("HAVE_BINFMT", "yes") == "yes" have_binfmt = os.getenv("HAVE_BINFMT", "yes") == "yes"
run_ma_same_tests = os.getenv("RUN_MA_SAME_TESTS", "yes") == "yes" run_ma_same_tests = os.getenv("RUN_MA_SAME_TESTS", "yes") == "yes"
cmd = os.getenv("CMD", "./mmdebstrap")
default_dist = os.getenv("DEFAULT_DIST", "unstable") default_dist = os.getenv("DEFAULT_DIST", "unstable")
all_dists = ["oldstable", "stable", "testing", "unstable"] all_dists = ["oldstable", "stable", "testing", "unstable"]
@ -35,21 +33,11 @@ all_variants = [
default_format = "auto" default_format = "auto"
all_formats = ["auto", "directory", "tar", "squashfs", "ext2", "null"] all_formats = ["auto", "directory", "tar", "squashfs", "ext2", "null"]
only_dists = []
mirror = os.getenv("mirror", "http://127.0.0.1/debian") mirror = os.getenv("mirror", "http://127.0.0.1/debian")
hostarch = subprocess.check_output(["dpkg", "--print-architecture"]).decode().strip() hostarch = subprocess.check_output(["dpkg", "--print-architecture"]).decode().strip()
release_path = f"./shared/cache/debian/dists/{default_dist}/Release"
if not os.path.exists(release_path):
print("path doesn't exist:", release_path, file=sys.stderr)
print("run ./make_mirror.sh first", file=sys.stderr)
exit(1)
if os.getenv("SOURCE_DATE_EPOCH") is not None:
s_d_e = os.getenv("SOURCE_DATE_EPOCH")
else:
with open(release_path) as f:
rel = Release(f)
s_d_e = str(email.utils.mktime_tz(email.utils.parsedate_tz(rel["Date"])))
separator = ( separator = (
"------------------------------------------------------------------------------" "------------------------------------------------------------------------------"
) )
@ -87,7 +75,6 @@ def main():
help="exit after first num failures or errors.", help="exit after first num failures or errors.",
) )
parser.add_argument("--mode", metavar="mode", help="only run tests with this mode") parser.add_argument("--mode", metavar="mode", help="only run tests with this mode")
parser.add_argument("--dist", metavar="dist", help="only run tests with this dist")
args = parser.parse_args() args = parser.parse_args()
# copy over files from git or as distributed # copy over files from git or as distributed
@ -118,10 +105,16 @@ def main():
"/usr/share/mmdebstrap/hooks", "shared/hooks", dirs_exist_ok=True "/usr/share/mmdebstrap/hooks", "shared/hooks", dirs_exist_ok=True
) )
onlyrun = None
if len(sys.argv) > 1:
onlyrun = sys.argv[1]
tests = [] tests = []
with open("coverage.txt") as f: with open("coverage.txt") as f:
for test in Deb822.iter_paragraphs(f): for test in Deb822.iter_paragraphs(f):
name = test["Test"] name = test["Test"]
if args.test and name not in args.test:
continue
dists = test.get("Dists", default_dist) dists = test.get("Dists", default_dist)
if dists == "any": if dists == "any":
dists = all_dists dists = all_dists
@ -151,7 +144,11 @@ def main():
else: else:
formats = formats.split() formats = formats.split()
for dist in dists: for dist in dists:
if only_dists and dist not in only_dists:
continue
for mode in modes: for mode in modes:
if args.mode and args.mode != mode:
continue
for variant in variants: for variant in variants:
for fmt in formats: for fmt in formats:
skipreason = skip( skipreason = skip(
@ -175,60 +172,27 @@ def main():
tt = "null" tt = "null"
tests.append((tt, name, dist, mode, variant, fmt)) tests.append((tt, name, dist, mode, variant, fmt))
torun = []
num_tests = len(tests)
if args.test:
# check if all given tests are either a valid name or a valid number
for test in args.test:
if test in [name for (_, name, _, _, _, _) in tests]:
continue
if not test.isdigit():
print(f"cannot find test named {test}", file=sys.stderr)
exit(1)
if int(test) >= len(tests) or int(test) <= 0 or str(int(test)) != test:
print(f"test number {test} doesn't exist", file=sys.stderr)
exit(1)
for i, (_, name, _, _, _, _) in enumerate(tests):
# if either the number or test name matches, then we use this test,
# otherwise we skip it
if name in args.test:
torun.append(i)
if str(i + 1) in args.test:
torun.append(i)
num_tests = len(torun)
starttime = time.time() starttime = time.time()
skipped = defaultdict(list) skipped = defaultdict(list)
failed = [] failed = []
num_success = 0 num_success = 0
num_finished = 0
for i, (test, name, dist, mode, variant, fmt) in enumerate(tests): for i, (test, name, dist, mode, variant, fmt) in enumerate(tests):
if torun and i not in torun:
continue
print(separator, file=sys.stderr) print(separator, file=sys.stderr)
print("(%d/%d) %s" % (i + 1, len(tests), name), file=sys.stderr) print("(%d/%d) %s" % (i + 1, len(tests), name), file=sys.stderr)
print("dist: %s" % dist, file=sys.stderr) print("dist: %s" % dist, file=sys.stderr)
print("mode: %s" % mode, file=sys.stderr) print("mode: %s" % mode, file=sys.stderr)
print("variant: %s" % variant, file=sys.stderr) print("variant: %s" % variant, file=sys.stderr)
print("format: %s" % fmt, file=sys.stderr) print("format: %s" % fmt, file=sys.stderr)
if num_finished > 0: if i > 0:
currenttime = time.time() currenttime = time.time()
timeleft = timedelta( timeleft = timedelta(
seconds=int( seconds=int((len(tests) - i) * (currenttime - starttime) / i)
(num_tests - num_finished)
* (currenttime - starttime)
/ num_finished
)
) )
print("time left: %s" % timeleft, file=sys.stderr) print("time left: %s" % timeleft, file=sys.stderr)
if failed:
print("failed: %d" % len(failed))
num_finished += 1
with open("tests/" + name) as fin, open("shared/test.sh", "w") as fout: with open("tests/" + name) as fin, open("shared/test.sh", "w") as fout:
for line in fin: for line in fin:
line = line.replace("{{ CMD }}", cmd) for e in ["CMD", "SOURCE_DATE_EPOCH"]:
line = line.replace("{{ SOURCE_DATE_EPOCH }}", s_d_e) line = line.replace("{{ " + e + " }}", os.getenv(e))
line = line.replace("{{ DIST }}", dist) line = line.replace("{{ DIST }}", dist)
line = line.replace("{{ MIRROR }}", mirror) line = line.replace("{{ MIRROR }}", mirror)
line = line.replace("{{ MODE }}", mode) line = line.replace("{{ MODE }}", mode)
@ -251,18 +215,11 @@ def main():
print(f"skipped because of {reason}", file=sys.stderr) print(f"skipped because of {reason}", file=sys.stderr)
continue continue
print(separator, file=sys.stderr) print(separator, file=sys.stderr)
if args.dist and args.dist != dist:
print(f"skipping because of --dist={args.dist}", file=sys.stderr)
continue
if args.mode and args.mode != mode:
print(f"skipping because of --mode={args.mode}", file=sys.stderr)
continue
proc = subprocess.Popen(argv) proc = subprocess.Popen(argv)
try: try:
proc.wait() proc.wait()
except KeyboardInterrupt: except KeyboardInterrupt:
proc.terminate() proc.kill()
proc.wait()
break break
print(separator, file=sys.stderr) print(separator, file=sys.stderr)
if proc.returncode != 0: if proc.returncode != 0:
@ -280,7 +237,7 @@ def main():
file=sys.stderr, file=sys.stderr,
) )
if skipped: if skipped:
print("skipped %d:" % sum([len(v) for v in skipped.values()]), file=sys.stderr) print("skipped %d:" % len(skipped), file=sys.stderr)
for reason, l in skipped.items(): for reason, l in skipped.items():
print(f"skipped because of {reason}:", file=sys.stderr) print(f"skipped because of {reason}:", file=sys.stderr)
for t in l: for t in l:

View file

@ -271,8 +271,6 @@ Skip-If:
variant == "standard" and dist in ["oldstable", "stable"] # #864082, #1004557, #1004558 variant == "standard" and dist in ["oldstable", "stable"] # #864082, #1004557, #1004558
variant == "important" and dist == "oldstable" # /var/lib/systemd/catalog/database differs variant == "important" and dist == "oldstable" # /var/lib/systemd/catalog/database differs
Test: create-directory-dry-run
Test: create-tarball-dry-run Test: create-tarball-dry-run
Variants: any Variants: any
Modes: any Modes: any
@ -318,7 +316,3 @@ Skip-If:
Test: no-sbin-in-path Test: no-sbin-in-path
Modes: fakechroot Modes: fakechroot
Test: dev-ptmx
Modes: root unshare
Needs-QEMU: true

View file

@ -8,8 +8,8 @@ fi
rootdir="$1" rootdir="$1"
env APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get indextargets --no-release-info --format '$(REPO_URI)' \ env APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get indextargets --no-release-info \
| sed -ne 's/^file:\/\+//p' \ | sed -ne 's/^Repo-URI: file:\/\+//p' \
| sort -u \ | sort -u \
| while read path; do | while read path; do
mkdir -p "$rootdir/run/mmdebstrap" mkdir -p "$rootdir/run/mmdebstrap"

View file

@ -660,7 +660,6 @@ for dist in oldstable stable testing unstable; do
set -eu set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH export SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH
echo "SOURCE_DATE_EPOCH=\$SOURCE_DATE_EPOCH"
tmpdir="\$(mktemp -d)" tmpdir="\$(mktemp -d)"
chmod 755 "\$tmpdir" chmod 755 "\$tmpdir"
debootstrap --no-merged-usr --variant=$variant $dist "\$tmpdir" $mirror debootstrap --no-merged-usr --variant=$variant $dist "\$tmpdir" $mirror

View file

@ -1091,20 +1091,6 @@ sub run_chroot {
); );
next; next;
} }
if ($fname eq "ptmx") {
# We must not bind-mount ptmx from the outside or
# otherwise posix_openpt() will fail. Instead
# /dev/ptmx must refer to /dev/pts/ptmx either by
# symlink or by bind-mounting. We choose a symlink.
symlink '/dev/pts/ptmx',
"$options->{root}/dev/ptmx"
or error "cannot create /dev/pts/ptmx symlink";
push @cleanup_tasks, sub {
unlink "$options->{root}/dev/ptmx"
or error "unlink /dev/ptmx";
};
next;
}
if (!-e "/dev/$fname") { if (!-e "/dev/$fname") {
warning("skipping creation of ./dev/$fname because" warning("skipping creation of ./dev/$fname because"
. " /dev/$fname does not exist" . " /dev/$fname does not exist"
@ -1147,13 +1133,13 @@ sub run_chroot {
. " /dev directory is missing in the target"); . " /dev directory is missing in the target");
next; next;
} }
if (!-e "/dev/$fname" && $fname ne "pts/") { if (!-e "/dev/$fname") {
warning("skipping creation of ./dev/$fname because" warning("skipping creation of ./dev/$fname because"
. " /dev/$fname does not exist" . " /dev/$fname does not exist"
. " on the outside"); . " on the outside");
next; next;
} }
if (!-d "/dev/$fname" && $fname ne "pts/") { if (!-d "/dev/$fname") {
warning("skipping creation of ./dev/$fname because" warning("skipping creation of ./dev/$fname because"
. " /dev/$fname on the outside is not a" . " /dev/$fname on the outside is not a"
. " directory"); . " directory");
@ -1201,32 +1187,9 @@ sub run_chroot {
"$options->{root}/dev/$fname") "$options->{root}/dev/$fname")
or warning("umount ./dev/$fname failed: $?"); or warning("umount ./dev/$fname failed: $?");
}; };
if ($fname eq "pts/") {
# We cannot just bind-mount /dev/pts from the host as
# doing so will make posix_openpt() fail. Instead, we
# need to mount a new devpts.
# We need ptmxmode=666 because /dev/ptmx is a symlink
# to /dev/pts/ptmx and without it posix_openpt() will
# fail if we are not the root user.
# See also:
# kernel.org/doc/Documentation/filesystems/devpts.txt
# salsa.debian.org/debian/schroot/-/merge_requests/2
# https://bugs.debian.org/856877
# https://bugs.debian.org/817236
0 == system(
'mount',
'-t',
'devpts',
'none',
"$options->{root}/dev/pts",
'-o',
'noexec,nosuid,uid=5,mode=620,ptmxmode=666'
) or error "mount /dev/pts failed";
} else {
0 == system('mount', '-o', 'bind', "/dev/$fname", 0 == system('mount', '-o', 'bind', "/dev/$fname",
"$options->{root}/dev/$fname") "$options->{root}/dev/$fname")
or error "mount ./dev/$fname failed: $?"; or error "mount ./dev/$fname failed: $?";
}
} else { } else {
error "unsupported type: $type"; error "unsupported type: $type";
} }
@ -4933,8 +4896,8 @@ sub main() {
} }
# initialize gpg trustdb with empty one # initialize gpg trustdb with empty one
{ {
0 == system(@gpgcmd, '--update-trustdb') `@gpgcmd --update-trustdb >/dev/null 2>/dev/null`;
or error "gpg failed to initialize trustdb:: $?"; $? == 0 or error "gpg failed to initialize trustdb: $?";
} }
# find all the fingerprints of the keys apt currently # find all the fingerprints of the keys apt currently
# knows about # knows about

View file

@ -31,7 +31,7 @@ qemu-img create -f qcow2 -b "$(realpath $cachedir)/debian-$DEFAULT_DIST.qcow" -F
# to connect to serial use: # to connect to serial use:
# minicom -D 'unix#/tmp/ttyS0' # minicom -D 'unix#/tmp/ttyS0'
ret=0 ret=0
timeout --foreground 20m qemu-system-x86_64 \ timeout 20m qemu-system-x86_64 \
-cpu host \ -cpu host \
-no-user-config \ -no-user-config \
-M accel=kvm:tcg -m 4G -nographic \ -M accel=kvm:tcg -m 4G -nographic \

View file

@ -5,7 +5,7 @@ if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2 echo "this test modifies the system and should only be run inside a container" >&2
exit 1 exit 1
fi fi
echo 0 > /proc/sys/fs/binfmt_misc/qemu-aarch64 apt-get remove --yes qemu-user-static binfmt-support qemu-user
ret=0 ret=0
{{ CMD }} --mode={{ MODE }} --variant=apt --architectures=arm64 {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }} || ret=$? {{ CMD }} --mode={{ MODE }} --variant=apt --architectures=arm64 {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }} || ret=$?
if [ "$ret" = 0 ]; then if [ "$ret" = 0 ]; then

View file

@ -3,8 +3,6 @@ set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }} export SOURCE_DATE_EPOCH={{ SOURCE_DATE_EPOCH }}
echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH"
# we create the apt user ourselves or otherwise its uid/gid will differ # we create the apt user ourselves or otherwise its uid/gid will differ
# compared to the one chosen in debootstrap because of different installation # compared to the one chosen in debootstrap because of different installation
# order in comparison to the systemd users # order in comparison to the systemd users

View file

@ -1,11 +1,8 @@
cat << END > shared/test.sh
#!/bin/sh #!/bin/sh
set -eu set -eu
export LC_ALL=C.UTF-8 export LC_ALL=C.UTF-8
{{ CMD }} --mode={{ MODE }} --dry-run --variant=apt \ {{ CMD }} --mode={{ MODE }} --dry-run --variant=apt --setup-hook="exit 1" --essential-hook="exit 1" --customize-hook="exit 1" {{ DIST }} /tmp/debian-chroot {{ MIRROR }}
--setup-hook="exit 1" \
--essential-hook="exit 1" \
--customize-hook="exit 1" \
{{ DIST }} /tmp/debian-chroot {{ MIRROR }}
rm /tmp/debian-chroot/dev/console rm /tmp/debian-chroot/dev/console
rm /tmp/debian-chroot/dev/fd rm /tmp/debian-chroot/dev/fd
rm /tmp/debian-chroot/dev/full rm /tmp/debian-chroot/dev/full
@ -26,3 +23,21 @@ rm /tmp/debian-chroot/var/lib/apt/lists/lock
rm /tmp/debian-chroot/var/lib/dpkg/status rm /tmp/debian-chroot/var/lib/dpkg/status
# the rest should be empty directories that we can rmdir recursively # the rest should be empty directories that we can rmdir recursively
find /tmp/debian-chroot -depth -print0 | xargs -0 rmdir find /tmp/debian-chroot -depth -print0 | xargs -0 rmdir
END
if [ "$HAVE_QEMU" = "yes" ]; then
./run_qemu.sh
runtests=$((runtests+1))
elif [ "{{ MODE }}" = "root" ]; then
./run_null.sh SUDO
runtests=$((runtests+1))
else
./run_null.sh
runtests=$((runtests+1))
fi
# test all --dry-run variants
# we are testing all variants here because with 0.7.5 we had a bug:
# mmdebstrap sid /dev/null --simulate ==> E: cannot read /var/cache/apt/archives/
for variant in extract custom essential apt minbase buildd important standard; do
for mode in root unshare fakechroot proot chrootless; do

View file

@ -5,18 +5,14 @@ if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2 echo "this test modifies the system and should only be run inside a container" >&2
exit 1 exit 1
fi fi
# https://www.etalabs.net/sh_tricks.html
quote () { printf %s\\n "$1" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/" ; }
adduser --gecos user --disabled-password user adduser --gecos user --disabled-password user
sysctl -w kernel.unprivileged_userns_clone=1 sysctl -w kernel.unprivileged_userns_clone=1
homedir=$(runuser -u user -- sh -c 'cd && pwd') homedir=$(runuser -u user -- sh -c 'cd && pwd')
# apt:test/integration/test-apt-key runuser -u user -- mkdir "$homedir/tmp"
TMPDIR_ADD="This is fü\$\$ing cràzy, \$(apt -v)\$!" runuser -u user -- env TMPDIR="$homedir/tmp" {{ CMD }} --mode=unshare --variant=apt \
runuser -u user -- mkdir "$homedir/$TMPDIR_ADD" --setup-hook='case "$1" in "'"$homedir/tmp/mmdebstrap."'"??????????) exit 0;; *) exit 1;; esac' \
runuser -u user -- env TMPDIR="$homedir/$TMPDIR_ADD" {{ CMD }} --mode=unshare --variant=apt \
--setup-hook='case "$1" in '"$(quote "$homedir/$TMPDIR_ADD/mmdebstrap.")"'??????????) exit 0;; *) echo "$1"; exit 1;; esac' \
{{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }} {{ DIST }} /tmp/debian-chroot.tar {{ MIRROR }}
tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt - tar -tf /tmp/debian-chroot.tar | sort | diff -u tar1.txt -
# use rmdir as a quick check that nothing is remaining in TMPDIR # use rmdir as a quick check that nothing is remaining in TMPDIR
runuser -u user -- rmdir "$homedir/$TMPDIR_ADD" runuser -u user -- rmdir "$homedir/tmp"
rm /tmp/debian-chroot.tar rm /tmp/debian-chroot.tar

View file

@ -1,145 +0,0 @@
#!/bin/sh
set -eu
export LC_ALL=C.UTF-8
if [ {{ MODE }} != unshare ] && [ {{ MODE }} != root ]; then
echo "test requires root or unshare mode" >&2
exit 1
fi
if [ ! -e /mmdebstrap-testenv ]; then
echo "this test modifies the system and should only be run inside a container" >&2
exit 1
fi
if [ "$(id -u)" -eq 0 ] && ! id -u user > /dev/null 2>&1; then
adduser --gecos user --disabled-password user
fi
prefix=
[ "$(id -u)" -eq 0 ] && [ "{{ MODE }}" != "root" ] && prefix="runuser -u user --"
# this mimics what apt does in apt-pkg/deb/dpkgpm.cc/pkgDPkgPM::StartPtyMagic()
cat > /tmp/test.c << 'END'
#define _GNU_SOURCE
#include <stdlib.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <signal.h>
int main() {
int ret;
int fd = posix_openpt(O_RDWR | O_NOCTTY);
if (fd < 0) {
perror("posix_openpt");
return 1;
}
char buf[64]; // 64 is used by apt
ret = ptsname_r(fd, buf, sizeof(buf));
if (ret != 0) {
perror("ptsname_r");
return 1;
}
ret = grantpt(fd);
if (ret == -1) {
perror("grantpt");
return 1;
}
struct termios origtt;
ret = tcgetattr(STDIN_FILENO, &origtt);
if (ret != 0) {
perror("tcgetattr1");
return 1;
}
struct termios tt;
ret = tcgetattr(STDOUT_FILENO, &tt);
if (ret != 0) {
perror("tcgetattr2");
return 1;
}
struct winsize win;
ret = ioctl(STDOUT_FILENO, TIOCGWINSZ, &win);
if (ret < 0) {
perror("ioctl stdout TIOCGWINSZ");
return 1;
}
ret = ioctl(fd, TIOCSWINSZ, &win);
if (ret < 0) {
perror("ioctl fd TIOCGWINSZ");
return 1;
}
ret = tcsetattr(fd, TCSANOW, &tt);
if (ret != 0) {
perror("tcsetattr1");
return 1;
}
cfmakeraw(&tt);
tt.c_lflag &= ~ECHO;
tt.c_lflag |= ISIG;
sigset_t sigmask;
sigset_t sigmask_old;
ret = sigemptyset(&sigmask);
if (ret != 0) {
perror("sigemptyset");
return 1;
}
ret = sigaddset(&sigmask, SIGTTOU);
if (ret != 0) {
perror("sigaddset");
return 1;
}
ret = sigprocmask(SIG_BLOCK,&sigmask, &sigmask_old);
if (ret != 0) {
perror("sigprocmask1");
return 1;
}
ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
if (ret != 0) {
perror("tcsetattr2");
return 1;
}
ret = sigprocmask(SIG_BLOCK,&sigmask_old, NULL);
if (ret != 0) {
perror("sigprocmask2");
return 1;
}
ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &origtt);
if (ret != 0) {
perror("tcsetattr3");
return 1;
}
return 0;
}
END
# use script to create a fake tty
# run all tests as root and as a normal user (the latter requires ptmxmode=666)
script -qfc "$prefix {{ CMD }} --mode={{ MODE }} --variant=apt \
--include=gcc,libc6-dev,python3 \
--customize-hook='chroot \"\$1\" adduser --gecos user --disabled-password user' \
--customize-hook='chroot \"\$1\" python3 -c \"import pty; print(pty.openpty())\"' \
--customize-hook='chroot \"\$1\" runuser -u user -- python3 -c \"import pty; print(pty.openpty())\"' \
--customize-hook='chroot \"\$1\" script -c \"echo foobar\"' \
--customize-hook='chroot \"\$1\" runuser -u user -- env --chdir=/home/user script -c \"echo foobar\"' \
--customize-hook='chroot \"\$1\" apt-get install --yes doc-debian 2>&1 | tee /tmp/log' \
--customize-hook=\"copy-in /tmp/test.c /tmp\" \
--customize-hook='chroot \"\$1\" gcc /tmp/test.c -o /tmp/test' \
--customize-hook='chroot \"\$1\" /tmp/test' \
--customize-hook='chroot \"\$1\" runuser -u user -- /tmp/test' \
--customize-hook='rm \"\$1\"/tmp/test \"\$1\"/tmp/test.c' \
{{ DIST }} /dev/null {{ MIRROR }}" /dev/null
fail=0
grep '^E:' /tmp/log && fail=1
grep 'Can not write log' /tmp/log && fail=1
grep 'posix_openpt' /tmp/log && fail=1
grep 'No such file or directory' /tmp/log && fail=1
if [ $fail -eq 1 ]; then
echo "apt failed to write log:" >&2
cat /tmp/log >&2
exit 1
fi
rm /tmp/test.c /tmp/log