diff --git a/Makefile.am b/Makefile.am
index ee9c4e15bc11b3862a378ffe8c560df5d8adf571..b8d1470b89cfa540c81706232ca309682b6cf61b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -117,9 +117,9 @@ nobase_insttests_LTLIBRARIES         = tests/red/libhelper.la                  \
                                        tests/green/libnotgl.la                 \
                                        tests/lib/libnotgl.la                   \
                                        tests/shim/libnotgl.la
+nobase_dist_insttests_DATA           = tests/CapsuleTest.pm
 # Install the source code so we can rebuild it inside a container if we want to
-nobase_dist_insttests_DATA           = tests/libtest.sh                        \
-                                       tests/notgl.h                           \
+nobase_dist_insttests_DATA          += tests/notgl.h                           \
                                        tests/notgl-green.c                     \
                                        tests/notgl-red.c                       \
                                        tests/notgl-ref.c                       \
@@ -188,10 +188,10 @@ tests/shim/lib%.so.c: tests/shim/lib%.so.c.excluded tests/shim/lib%.so.c.dlopen
 
 LOG_DRIVER                           = env AM_TAP_AWK='$(AWK)'                 \
                                        $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
-LOG_COMPILER                         = $(top_srcdir)/build-aux/tap-test.sh
-TEST_EXTENSIONS                      = .sh
-SH_LOG_DRIVER                        = $(LOG_DRIVER)
-SH_LOG_COMPILER                      = $(LOG_COMPILER)
+LOG_COMPILER                         =
+TEST_EXTENSIONS                      = .pl
+PL_LOG_DRIVER                        = $(LOG_DRIVER)
+PL_LOG_COMPILER                      = $(PERL)
 AM_TESTS_ENVIRONMENT                 = export CAPSULE_TESTS_UNINSTALLED=1;     \
                                        export G_TEST_SRCDIR="$(abs_srcdir)";   \
                                        export G_TEST_BUILDDIR="$(abs_builddir)";\
@@ -205,7 +205,7 @@ tests_notgl_helper_user_LDADD        = tests/lib/libnotgl.la                   \
 test_extra_scripts                   =
 
 test_programs                        =
-test_scripts                         = tests/notgl.sh
+test_scripts                         = tests/notgl.pl
 nobase_insttests_PROGRAMS            = $(test_programs)                        \
                                        $(test_extra_programs)
 nobase_dist_insttests_SCRIPTS        = $(test_scripts)                         \
@@ -221,7 +221,7 @@ $(testmeta_DATA): tests/%.test: tests/% Makefile
 		echo "[Test]"; \
 		echo "Type=session"; \
 		echo "Output=TAP"; \
-		echo "Exec=$(insttestsdir)/tests/$* --tap"; \
+		echo "Exec=$(insttestsdir)/tests/$*"; \
 	) > $@.tmp && mv $@.tmp $@
 
 # ============================================================================
diff --git a/build-aux/tap-test.sh b/build-aux/tap-test.sh
deleted file mode 100755
index 67858ebcf363b7eb701d259bed0074c383d52ac4..0000000000000000000000000000000000000000
--- a/build-aux/tap-test.sh
+++ /dev/null
@@ -1,13 +0,0 @@
-#!/bin/sh
-# Wrapper to make GTest tests output TAP syntax, because Automake's test
-# drivers do not currently support passing the same command-line argument
-# to each test executable. All GTest tests produce TAP output if invoked
-# with the --tap option.
-#
-# Usage: "tap-test.sh test-foo --verbose ..." is equivalent to
-# "test-foo --tap --verbose ..."
-
-set -e
-t="$1"
-shift
-exec "$t" --tap "$@"
diff --git a/configure.ac b/configure.ac
index 875aa62d8b5fba6e27907f265a67d0a51a66747f..5b7262e716ba1484daf0eeb8abee3e296f9e5e03 100644
--- a/configure.ac
+++ b/configure.ac
@@ -23,6 +23,8 @@ AC_INIT([libcapsule],
 AC_CONFIG_AUX_DIR([build-aux])
 AC_CONFIG_MACRO_DIR([m4])
 AC_REQUIRE_AUX_FILE([tap-driver.sh])
+AC_ARG_VAR([PERL])
+AC_PATH_PROG([PERL], [perl], [perl])
 
 m4_pattern_forbid([^AX_], [Please install GNU autoconf-archive])
 
diff --git a/debian/control b/debian/control
index 8d8fc4baee98b0b9ba3dec95725991e66435badd..3212398c05aae97d3e88de28423131bbbae815a4 100644
--- a/debian/control
+++ b/debian/control
@@ -12,6 +12,7 @@ Build-Depends:
  dh-autoreconf,
  gtk-doc-tools,
  libelf-dev,
+ libipc-run-perl,
  xsltproc,
 Vcs-Git: https://gitlab.collabora.com/vivek/libcapsule.git
 Vcs-Browser: https://gitlab.collabora.com/vivek/libcapsule
diff --git a/tests/CapsuleTest.pm b/tests/CapsuleTest.pm
new file mode 100644
index 0000000000000000000000000000000000000000..dc653473022ed44586572a23fe1c039b3ab6f095
--- /dev/null
+++ b/tests/CapsuleTest.pm
@@ -0,0 +1,150 @@
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+package CapsuleTest;
+
+use strict;
+use warnings;
+
+use Cwd qw(abs_path);
+use FindBin;
+use Exporter qw(import);
+use IPC::Run qw(run);
+use Test::More;
+
+our @EXPORT = qw(
+    diag_multiline
+    run_ok
+    run_verbose
+    skip_all_unless_bwrap
+    $builddir
+    $srcdir
+);
+
+=encoding utf8
+
+=head1 NAME
+
+CapsuleTest - utilities for libcapsule automated and manual tests
+
+=head1 EXPORTED VARIABLES
+
+=over
+
+=cut
+
+=item $srcdir
+
+An appropriate directory to find non-generated files: the top directory
+of the source tree, or the root directory of this package's GLib-style
+installed-tests.
+
+=cut
+
+# G_TEST_* convention stolen from GLib, even though we aren't using GTest
+our $srcdir = $ENV{G_TEST_SRCDIR};
+$srcdir = abs_path($FindBin::Bin) unless defined $srcdir;
+
+=item $builddir
+
+An appropriate directory to find non-generated files: the top directory
+of the build tree, or the root directory of this package's GLib-style
+installed-tests.
+
+=cut
+
+our $builddir = $ENV{G_TEST_BUILDDIR};
+$builddir = abs_path($FindBin::Bin) unless defined $builddir;
+
+diag "Source or installation directory: $srcdir";
+diag "Build or installation directory: $builddir";
+
+=back
+
+=head1 EXPORTED FUNCTIONS
+
+=over
+
+=item diag_multiline(I<TEXT>)
+
+Split I<TEXT> into lines and emit them as TAP diagnostics.
+
+=cut
+
+sub diag_multiline {
+    foreach my $line (split /^/m, shift) {
+        chomp $line;
+        diag "    $line";
+    }
+}
+
+=item run_ok(I<ARGV>, ...)
+
+A TAP assertion that the given command exits 0. I<ARGV> is an
+array-reference containing arguments. Subsequent parameters are
+passed to C<IPC::Run::run> and can be used to redirect output.
+
+=cut
+
+sub run_ok {
+    my $argv = shift;
+    my $debug = join(' ', @$argv);
+    diag($debug);
+    ok(run($argv, @_), qq{"$debug" should succeed});
+}
+
+=item run_verbose(I<ARGV>, ...)
+
+Log the given command, run it, and return the same thing as
+C<IPC::Run::run>. I<ARGV> is an array-reference containing arguments.
+Subsequent parameters are passed to C<IPC::Run::run> and can be used
+to redirect output.
+
+=cut
+
+sub run_verbose {
+    my $argv = shift;
+    my $debug = join(' ', @$argv);
+    diag($debug);
+    return run($argv, @_);
+}
+
+=item skip_all_unless_bwrap()
+
+If we cannot run B<bwrap>(1), log a TAP report that all tests have been
+skipped (as if via C<plan skip_all =E<gt> ...>), and exit.
+
+=cut
+
+sub skip_all_unless_bwrap {
+    if (! run([qw(
+                bwrap --ro-bind / / --unshare-ipc --unshare-net
+                --unshare-pid --unshare-user --unshare-uts true
+            )], '>&2')) {
+        plan(skip_all => 'Cannot run bwrap');
+    }
+}
+
+=back
+
+=head1 SEE ALSO
+
+B<Test::More>(3pm), B<bwrap>(1)
+
+=cut
+
+1;
diff --git a/tests/libtest.sh b/tests/libtest.sh
deleted file mode 100644
index fa40f70e6c12d0b0c562a0ac3d200ce989768d51..0000000000000000000000000000000000000000
--- a/tests/libtest.sh
+++ /dev/null
@@ -1,167 +0,0 @@
-# vim:set ft=sh sts=4 sw=4 et:
-
-# Copyright © 2017 Collabora Ltd
-
-# This file is part of libcapsule.
-
-# libcapsule is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation; either version 2.1 of the
-# License, or (at your option) any later version.
-
-# libcapsule 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 Lesser General Public License for more details.
-
-# You should have received a copy of the GNU Lesser General Public
-# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
-
-exec {test_tap_fd}>&1
-exec >&2
-test_tempdir="$(mktemp -d /tmp/libcapsule.XXXXXXXX)"
-trap 'rm -fr --one-file-system $test_tempdir' EXIT
-mkdir "$test_tempdir/host"
-export CAPSULE_PREFIX="$test_tempdir/host"
-
-test_num=0
-any_failed=0
-
-capture_tap () {
-    "$@" >&${test_tap_fd}
-}
-
-echo_tap () {
-    echo "$*" >&${test_tap_fd}
-}
-
-skip_all () {
-    echo_tap "1..0 # SKIP - $*"
-    exit 0
-}
-
-pass () {
-    test_num=$((test_num + 1))
-    echo_tap "ok $test_num - $*"
-}
-
-fail () {
-    test_num=$((test_num + 1))
-    echo_tap "not ok $test_num - $*"
-    any_failed=1
-}
-
-skip () {
-    test_num=$((test_num + 1))
-    echo_tap "ok $test_num # SKIP - $*"
-}
-
-ok () {
-    local condition="$1"
-    shift
-
-    if $condition; then
-        pass "$*"
-    else
-        fail "$*"
-    fi
-}
-
-is () {
-    local got="$1"
-    local expected="$2"
-    shift 2
-
-    if [ "x$got" = "x$expected" ]; then
-        pass "$* ($got)"
-    else
-        echo_tap "# Got: $got"
-        echo_tap "# Expected: $expected"
-        fail "$* ($got != $expected)"
-    fi
-}
-
-like () {
-    local got="$1"
-    local expected="$2"
-    shift 2
-
-    if [[ $got == $expected ]]; then
-        pass "$* ($got matches $expected)"
-    else
-        echo_tap "# Got: $got"
-        echo_tap "# Expected extglob: $expected"
-        fail "$* ($got does not match $expected)"
-    fi
-}
-
-isnt () {
-    local got="$1"
-    local unexpected="$2"
-    shift 2
-
-    if [ "x$got" != "x$unexpected" ]; then
-        echo_tap "# Got: $got"
-        echo_tap "# Expected: anything but $unexpected"
-        fail "$* (expected anything but $unexpected)"
-    else
-        pass "$* ($got)"
-    fi
-}
-
-shell_is () {
-    local command="$1"
-    local expected_status="$2"
-    local expected="$3"
-    local status=0
-    shift 3
-
-    echo_tap "# \$($command)..."
-    got="$(eval "$command")" || status="$?"
-
-    if [ "x$status" != "x$expected_status" ]; then
-        fail "$* (status $status != $expected_status)"
-    fi
-
-    if [ "x$got" = "x$expected" ]; then
-        pass "$* ($got)"
-    else
-        echo_tap "# Got: $got"
-        echo_tap "# Expected: $expected"
-        fail "$* ($got != $expected)"
-    fi
-}
-
-shell_like () {
-    local command="$1"
-    local expected_status="$2"
-    local expected="$3"
-    local status=0
-    shift 3
-
-    echo_tap "# \$($command)..."
-    got="$(eval "$command")" || status="$?"
-
-    if [ "x$status" != "x$expected_status" ]; then
-        fail "$* (status $status != $expected_status)"
-    fi
-
-    if [[ $got == $expected ]]; then
-        pass "$* ($got matches $expected)"
-    else
-        echo_tap "# Got: $got"
-        echo_tap "# Expected extglob: $expected"
-        fail "$* ($got does not match extglob $expected)"
-    fi
-}
-
-run_verbose () {
-    echo_tap "# \$($*)..."
-    "$@"
-}
-
-done_testing () {
-    echo_tap "# End of tests"
-    echo_tap "1..$test_num"
-    exit $any_failed
-}
diff --git a/tests/notgl.pl b/tests/notgl.pl
new file mode 100755
index 0000000000000000000000000000000000000000..4d8ce0d8ba1b67ae9ee92d244344898e5e2d5c24
--- /dev/null
+++ b/tests/notgl.pl
@@ -0,0 +1,163 @@
+#!/usr/bin/env perl
+
+# notgl.pl — exercise libcapsule using an imitation of libGLX
+#
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+use warnings;
+use strict;
+
+use File::Temp qw();
+use IPC::Run qw(run);
+use Test::More;
+
+use FindBin;
+use lib $FindBin::Bin;
+use CapsuleTest;
+
+skip_all_unless_bwrap;
+
+my $capsule_prefix = File::Temp->newdir();
+my $libs = '';
+my $notgl_user = "$builddir/tests/notgl-user";
+my $notgl_helper_user = "$builddir/tests/notgl-helper-user";
+my $stdout;
+
+if (exists $ENV{CAPSULE_TESTS_UNINSTALLED}) {
+    diag 'Running uninstalled: yes';
+    # We need to bypass libtool because it plays around with
+    # LD_LIBRARY_PATH, and so do we.
+    $libs = '/.libs';
+    run_ok([
+        "$builddir/libtool", qw(--mode=execute ls -1),
+        "$builddir/tests/notgl-user"
+    ], '>', \$notgl_user) or BAIL_OUT 'Cannot find notgl-user: $?';
+    chomp $notgl_user;
+    run_ok([
+        "$builddir/libtool", qw(--mode=execute ls -1),
+        "$builddir/tests/notgl-helper-user"
+    ], '>', \$notgl_helper_user) or BAIL_OUT 'Cannot find notgl-helper-user: $?';
+    chomp $notgl_helper_user;
+}
+else {
+    diag 'Running uninstalled: no';
+}
+
+diag 'Without special measures:';
+run_ok(['env', "LD_LIBRARY_PATH=$builddir/tests/lib$libs", $notgl_user],
+    '>', \$stdout);
+diag_multiline $stdout;
+like($stdout, qr/^NotGL implementation: reference$/m);
+like($stdout, qr/^NotGL helper implementation: container \(reference\)$/m);
+like($stdout,
+    qr/^notgl_extension_both: reference implementation of common extension$/m);
+like($stdout, qr/^notgl_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgl_extension_green: \(not found\)$/m);
+
+diag 'Without special measures (linked to libhelper):';
+run_ok(['env', "LD_LIBRARY_PATH=$builddir/tests/lib$libs",
+        $notgl_helper_user],
+    '>', \$stdout);
+diag_multiline $stdout;
+like($stdout, qr/^NotGL implementation: reference$/m);
+like($stdout, qr/^NotGL helper implementation: container \(reference\)$/m);
+like($stdout,
+    qr/^notgl_extension_both: reference implementation of common extension$/m);
+like($stdout, qr/^notgl_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgl_extension_green: \(not found\)$/m);
+like($stdout,
+    qr/^NotGL helper implementation as seen by executable: container \(reference\)$/m);
+
+diag 'With libcapsule loading red implementation:';
+# We mount the "host system" on $capsule_prefix.
+#
+# In the "container", the shim libnotgl is picked up from tests/shim because
+# it was prepended to the LD_LIBRARY_PATH. The helper library used by
+# libnotgl, libhelper, is picked up from tests/lib as usual.
+#
+# In the "host system", there is a tmpfs over the build or installation
+# directory to avoid tests/shim being found *again*, and the "red"
+# implementations of libnotgl and libhelper is mounted over tests/lib.
+run_ok([qw(bwrap
+        --ro-bind / /
+        --dev-bind /dev /dev
+        --ro-bind /), $capsule_prefix,
+        '--tmpfs', "$capsule_prefix$builddir",
+        '--ro-bind', "$builddir/tests/red",
+            "$capsule_prefix$builddir/tests/lib",
+        '--setenv', 'CAPSULE_PREFIX', $capsule_prefix,
+        '--setenv', 'LD_LIBRARY_PATH',
+            "$builddir/tests/shim$libs:$builddir/tests/lib$libs",
+        $notgl_user],
+    '>', \$stdout);
+diag_multiline $stdout;
+
+# Functions from libnotgl get dispatched through the shim to the "red"
+# implementation from the "host system". This mirrors functions from libGL
+# being dispatched through the shim to the AMD implementation of libGL.
+like($stdout, qr/^NotGL implementation: red$/m);
+
+# When the "red" implementation of libnotgl calls functions from libhelper,
+# implementation from the "host system". This mirrors functions from
+# libstdc++ that are called by the host libGL ending up in the host libstdc++.
+like($stdout, qr/^NotGL helper implementation: host \(red\)$/m);
+
+# We can dlsym() for an implemementation of an extension that is part of
+# the ABI of the shim and the reference implementation.
+like($stdout,
+    qr/^notgl_extension_both: red implementation of common extension$/m);
+
+# We can also dlsym() for an implemementation of an extension that is only
+# available in the "red" implementation.
+like($stdout, qr/^notgl_extension_red: red-only extension$/m);
+like($stdout, qr/^notgl_extension_green: \(not found\)$/m);
+
+diag 'With libcapsule loading green implementation:';
+# Similar to the above, but now the host system is using the "green"
+# implementation of libnotgl, mirroring the NVIDIA implementation of libGL.
+run_ok([qw(bwrap
+        --ro-bind / /
+        --dev-bind /dev /dev
+        --ro-bind /), $capsule_prefix,
+        '--tmpfs', "$capsule_prefix$builddir",
+        '--ro-bind', "$builddir/tests/green",
+            "$capsule_prefix$builddir/tests/lib",
+        '--setenv', 'CAPSULE_PREFIX', $capsule_prefix,
+        '--setenv', 'LD_LIBRARY_PATH',
+            "$builddir/tests/shim$libs:$builddir/tests/lib$libs",
+        $notgl_helper_user],
+    '>', \$stdout);
+diag_multiline $stdout;
+like($stdout, qr/^NotGL implementation: green$/m);
+like($stdout, qr/^NotGL helper implementation: host \(green\)$/m);
+like($stdout,
+    qr/^notgl_extension_both: green implementation of common extension$/m);
+like($stdout, qr/^notgl_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgl_extension_green: green-only extension$/m);
+
+# Also, this program is linked directly to libhelper, mirroring a program
+# that is linked directly to libstdc++ in the libGL case. It sees the
+# container's libhelper, not the host's - even though libnotgl sees the
+# host's libhelper when it looks up the same symbol. (This is the point
+# of libcapsule.)
+like($stdout,
+    qr/^NotGL helper implementation as seen by executable: container \(reference\)$/m);
+
+done_testing;
+
+# vim:set sw=4 sts=4 et:
diff --git a/tests/notgl.sh b/tests/notgl.sh
deleted file mode 100755
index beded1c6d41f0cfcb352cc207596c2788c11e524..0000000000000000000000000000000000000000
--- a/tests/notgl.sh
+++ /dev/null
@@ -1,171 +0,0 @@
-#!/bin/bash
-
-# Copyright © 2017 Collabora Ltd
-
-# This file is part of libcapsule.
-
-# libcapsule is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation; either version 2.1 of the
-# License, or (at your option) any later version.
-
-# libcapsule 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 Lesser General Public License for more details.
-
-# You should have received a copy of the GNU Lesser General Public
-# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
-
-set -eu -o pipefail
-
-. "$(dirname "$0")"/libtest.sh
-
-if ! bwrap --ro-bind / / true; then
-    skip_all "1..0 # SKIP - cannot run bwrap"
-fi
-
-: ${G_TEST_SRCDIR:="$(cd "$(dirname "$0")"/..; pwd)"}
-: ${G_TEST_BUILDDIR:="$(cd "$(dirname "$0")"/..; pwd)"}
-
-echo_tap "# Source or installation directory: $G_TEST_SRCDIR"
-echo_tap "# Build or installation directory: $G_TEST_BUILDDIR"
-
-if [ -n "${CAPSULE_TESTS_UNINSTALLED:-}" ]; then
-    echo_tap "# Running uninstalled: yes"
-    # We need to bypass libtool because it plays around with
-    # LD_LIBRARY_PATH, and so do we.
-    libs=/.libs
-    notgl_user="$("$G_TEST_BUILDDIR/libtool" --mode=execute ls -1 "$G_TEST_BUILDDIR/tests/notgl-user")"
-    notgl_helper_user="$("$G_TEST_BUILDDIR/libtool" --mode=execute ls -1 "$G_TEST_BUILDDIR/tests/notgl-helper-user")"
-else
-    echo_tap "# Running uninstalled: no"
-    libs=
-    notgl_user="$G_TEST_BUILDDIR/tests/notgl-user"
-    notgl_helper_user="$G_TEST_BUILDDIR/tests/notgl-helper-user"
-fi
-
-echo_tap
-echo_tap "# Without special measures:"
-run_verbose env LD_LIBRARY_PATH="$G_TEST_BUILDDIR/tests/lib$libs" "$notgl_user" > "$test_tempdir/output"
-capture_tap sed -e 's/^/#   /' "$test_tempdir/output"
-shell_is \
-    'sed -ne "s/^NotGL implementation: //p" $test_tempdir/output' \
-    0 reference
-shell_is \
-    'sed -ne "s/^NotGL helper implementation: //p" $test_tempdir/output' \
-    0 "container (reference)"
-shell_is \
-    'sed -ne "s/^notgl_extension_both: //p" $test_tempdir/output' \
-    0 "reference implementation of common extension"
-shell_is \
-    'sed -ne "s/^notgl_extension_red: //p" $test_tempdir/output' \
-    0 "(not found)"
-shell_is \
-    'sed -ne "s/^notgl_extension_green: //p" $test_tempdir/output' \
-    0 "(not found)"
-
-echo_tap
-run_verbose env LD_LIBRARY_PATH="$G_TEST_BUILDDIR/tests/lib$libs" "$notgl_helper_user" > "$test_tempdir/output"
-capture_tap sed -e 's/^/#   /' "$test_tempdir/output"
-shell_is \
-    'sed -ne "s/^NotGL implementation: //p" $test_tempdir/output' \
-    0 reference
-shell_is \
-    'sed -ne "s/^NotGL helper implementation: //p" $test_tempdir/output' \
-    0 "container (reference)"
-shell_is \
-    'sed -ne "s/^notgl_extension_both: //p" $test_tempdir/output' \
-    0 "reference implementation of common extension"
-shell_is \
-    'sed -ne "s/^notgl_extension_red: //p" $test_tempdir/output' \
-    0 "(not found)"
-shell_is \
-    'sed -ne "s/^NotGL helper implementation as seen by executable: //p" $test_tempdir/output' \
-    0 "container (reference)"
-
-echo_tap
-echo_tap "# With libcapsule:"
-# We mount the "host system" on $test_tempdir/host.
-#
-# In the "container", the shim libnotgl is picked up from tests/shim because
-# it was prepended to the LD_LIBRARY_PATH. The helper library used by
-# libnotgl, libhelper, is picked up from tests/lib as usual.
-#
-# In the "host system", there is a tmpfs over the build or installation
-# directory to avoid tests/shim being found *again*, and the "red"
-# implementations of libnotgl and libhelper is mounted over tests/lib.
-run_verbose bwrap \
-    --ro-bind / / \
-    --dev-bind /dev /dev \
-    --ro-bind / "$CAPSULE_PREFIX" \
-    --tmpfs "$CAPSULE_PREFIX$G_TEST_BUILDDIR" \
-    --ro-bind "$G_TEST_BUILDDIR/tests/red" "$CAPSULE_PREFIX$G_TEST_BUILDDIR/tests/lib" \
-    --setenv LD_LIBRARY_PATH "$G_TEST_BUILDDIR/tests/shim$libs:$G_TEST_BUILDDIR/tests/lib$libs" \
-    "$notgl_user" > "$test_tempdir/output"
-capture_tap sed -e 's/^/#   /' "$test_tempdir/output"
-# Functions from libnotgl get dispatched through the shim to the "red"
-# implementation from the "host system". This mirrors functions from libGL
-# being dispatched through the shim to the AMD implementation of libGL.
-shell_is \
-    'sed -ne "s/^NotGL implementation: //p" $test_tempdir/output' \
-    0 red
-# When the "red" implementation of libnotgl calls functions from libhelper,
-# implementation from the "host system". This mirrors functions from
-# libstdc++ that are called by the host libGL ending up in the host libstdc++.
-shell_is \
-    'sed -ne "s/^NotGL helper implementation: //p" $test_tempdir/output' \
-    0 "host (red)"
-# We can dlsym() for an implemementation of an extension that is part of
-# the ABI of the shim and the reference implementation.
-shell_is \
-    'sed -ne "s/^notgl_extension_both: //p" $test_tempdir/output' \
-    0 "red implementation of common extension"
-# We can also dlsym() for an implemementation of an extension that is only
-# available in the "red" implementation.
-shell_is \
-    'sed -ne "s/^notgl_extension_red: //p" $test_tempdir/output' \
-    0 "red-only extension"
-shell_is \
-    'sed -ne "s/^notgl_extension_green: //p" $test_tempdir/output' \
-    0 "(not found)"
-
-echo_tap
-# Similar to the above, but now the host system is using the "green"
-# implementation of libnotgl, mirroring the NVIDIA implementation of libGL.
-run_verbose bwrap \
-    --ro-bind / / \
-    --dev-bind /dev /dev \
-    --ro-bind / "$CAPSULE_PREFIX" \
-    --tmpfs "$CAPSULE_PREFIX$G_TEST_BUILDDIR" \
-    --ro-bind "$G_TEST_BUILDDIR/tests/green" "$CAPSULE_PREFIX$G_TEST_BUILDDIR/tests/lib" \
-    --setenv LD_LIBRARY_PATH "$G_TEST_BUILDDIR/tests/shim$libs:$G_TEST_BUILDDIR/tests/lib$libs" \
-    "$notgl_helper_user" > "$test_tempdir/output"
-capture_tap sed -e 's/^/#   /' "$test_tempdir/output"
-shell_is \
-    'sed -ne "s/^NotGL implementation: //p" $test_tempdir/output' \
-    0 green
-shell_is \
-    'sed -ne "s/^NotGL helper implementation: //p" $test_tempdir/output' \
-    0 "host (green)"
-shell_is \
-    'sed -ne "s/^notgl_extension_both: //p" $test_tempdir/output' \
-    0 "green implementation of common extension"
-shell_is \
-    'sed -ne "s/^notgl_extension_red: //p" $test_tempdir/output' \
-    0 "(not found)"
-shell_is \
-    'sed -ne "s/^notgl_extension_green: //p" $test_tempdir/output' \
-    0 "green-only extension"
-# Also, this program is linked directly to libhelper, mirroring a program
-# that is linked directly to libstdc++ in the libGL case. It sees the
-# container's libhelper, not the host's - even though libnotgl sees the
-# host's libhelper when it looks up the same symbol. (This is the point
-# of libcapsule.)
-shell_is \
-    'sed -ne "s/^NotGL helper implementation as seen by executable: //p" $test_tempdir/output' \
-    0 "container (reference)"
-
-done_testing
-
-# vim:set sw=4 sts=4 et: