public inbox for passt-dev@passt.top
 help / color / mirror / code / Atom feed
* [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin
@ 2023-06-27  2:54 David Gibson
  2023-06-27  2:54 ` [PATCH 01/27] avocado: Make a duplicate copy of testsuite for comparison purposes David Gibson
                   ` (26 more replies)
  0 siblings, 27 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

I've sent previous series converting some of the passt & pasta
testsuite to use Avocado.  However the jUnit-like structure of avocado
tests wasn't a great fit for our needs (plus Stefano and I just
dislike the style).  Here's another attempt, where instead we use a
new "avocado-classless" plugin to avoid jUnit-ism.

This is definitely a work-in-progress.  Some early observations:

  * It is noticeably less verbose than jUnit style
  * It does avoid some of the confusing stuff that arises from jUnit style
  * The present draft has some different pretty confusing stuff,
    involving nested and stacked function decorators, and some other
    non-entirely-obvious use of higher order functions
     * I hope I can improve this at least a bit, but it will need some thought
  * I was pleasantly surprised at how simple it was to write the
    plugin itself

David Gibson (27):
  avocado: Make a duplicate copy of testsuite for comparison purposes
  avocado: Don't double download assets for test/ and oldtest/
  avocado: Move static checkers to avocado
  avocado: Introduce "avocado-classless" plugin, runner and outline
  avocado, test: Add static checkers for Python code
  avocado: Resolver implementation for avocado-classless plugin
  avocado: Add basic assertion helpers to avocado-classless plugin
  tasst, avocado: Introduce library of common test helpers
  avocado-classless: Test matrices by composition
  tasst: Helper functions for executing commands in different places
  avocado-classless: Allow overriding default timeout
  avocado: Convert build tests to avocado
  tasst: Add helpers for running background commands on sites
  tasst: Add helper to get network interface names for a site
  tasst: Add helpers to run commands with nstool
  tasst: Add ifup and network address helpers to Site
  tasst: Helper for creating veth devices between namespaces
  tasst: Add helper for getting MTU of a network interface
  tasst: Add helper to wait for IP address to appear
  tasst: Add helpers for getting a site's routes
  tasst: Helpers to test transferring data between sites
  tasst: IP address allocation helpers
  tasst: Helpers for running daemons with a pidfile
  tasst: Helpers for testing NDP behaviour
  tasst: Helpers for testing DHCP & DHCPv6 behaviour
  tasst: Helpers to construct a simple network environment for tests
  avocado: Convert basic pasta tests

 Makefile                                      |   9 +
 oldtest/.gitignore                            |  11 +
 oldtest/Makefile                              | 119 +++
 oldtest/README.md                             | 137 +++
 {test => oldtest}/build/all                   |   0
 {test => oldtest}/build/clang_tidy            |   0
 {test => oldtest}/build/cppcheck              |   0
 oldtest/ci                                    |   1 +
 oldtest/demo/passt                            | 245 ++++++
 oldtest/demo/pasta                            | 274 ++++++
 oldtest/demo/podman                           | 819 ++++++++++++++++++
 oldtest/distro/debian                         | 252 ++++++
 oldtest/distro/fedora                         | 396 +++++++++
 oldtest/distro/opensuse                       | 208 +++++
 oldtest/distro/ubuntu                         | 216 +++++
 oldtest/env/mate-terminal.profile             |  42 +
 oldtest/find-arm64-firmware.sh                |  13 +
 oldtest/lib/context                           | 130 +++
 oldtest/lib/layout                            | 259 ++++++
 oldtest/lib/layout_ugly                       | 113 +++
 oldtest/lib/perf_report                       | 272 ++++++
 oldtest/lib/setup                             | 385 ++++++++
 oldtest/lib/setup_ugly                        |  58 ++
 oldtest/lib/term                              | 750 ++++++++++++++++
 oldtest/lib/test                              | 398 +++++++++
 oldtest/lib/util                              | 133 +++
 oldtest/lib/video                             | 152 ++++
 oldtest/memory/passt                          | 187 ++++
 oldtest/nstool.c                              | 565 ++++++++++++
 oldtest/passt.mbuto                           |  83 ++
 oldtest/passt.mem.mbuto                       |  44 +
 oldtest/passt/dhcp                            |  70 ++
 oldtest/passt/ndp                             |  33 +
 oldtest/passt/shutdown                        |  19 +
 oldtest/passt/tcp                             |  76 ++
 oldtest/passt/udp                             |  46 +
 oldtest/passt_in_ns/icmp                      |  32 +
 oldtest/passt_in_ns/shutdown                  |  19 +
 oldtest/passt_in_ns/tcp                       | 256 ++++++
 oldtest/passt_in_ns/udp                       | 138 +++
 {test => oldtest}/pasta/dhcp                  |   0
 {test => oldtest}/pasta/ndp                   |   0
 {test => oldtest}/pasta/tcp                   |   0
 {test => oldtest}/pasta/udp                   |   0
 oldtest/pasta_options/log_to_file             |  93 ++
 oldtest/perf/passt_tcp                        | 215 +++++
 oldtest/perf/passt_udp                        | 165 ++++
 oldtest/perf/pasta_tcp                        | 300 +++++++
 oldtest/perf/pasta_udp                        | 219 +++++
 oldtest/prepare-distro-img.sh                 |  18 +
 oldtest/run                                   | 238 +++++
 oldtest/run_demo                              |   1 +
 oldtest/two_guests/basic                      |  80 ++
 oldtest/valgrind.supp                         |   9 +
 test/.gitignore                               |   2 +
 test/Makefile                                 |  69 +-
 test/avocado/build.py                         |  94 ++
 test/avocado/pasta.py                         | 129 +++
 test/avocado/static_checkers/clang-tidy.sh    |   3 +
 test/avocado/static_checkers/cppcheck.sh      |   3 +
 test/avocado/static_checkers/flake8.sh        |   3 +
 test/avocado/static_checkers/pylint.sh        |   3 +
 test/avocado_classless/.gitignore             |   1 +
 .../avocado_classless/__init__.py             |  11 +
 .../avocado_classless/manifest.py             |  48 +
 .../avocado_classless/plugin.py               | 231 +++++
 .../avocado_classless/test.py                 |  80 ++
 test/avocado_classless/examples.py            |  57 ++
 test/avocado_classless/selftests.py           |  76 ++
 test/avocado_classless/setup.py               |  32 +
 test/lib/layout                               |  31 -
 test/lib/setup                                |  47 -
 test/run                                      |  14 -
 test/tasst/__init__.py                        |  11 +
 test/tasst/address.py                         |  80 ++
 test/tasst/dhcp.py                            | 114 +++
 test/tasst/dhcpv6.py                          |  74 ++
 test/tasst/exesite.py                         | 285 ++++++
 test/tasst/meta/__init__.py                   |  16 +
 test/tasst/meta/static_ifup.py                |  60 ++
 test/tasst/meta/veth.py                       |  86 ++
 test/tasst/ndp.py                             |  99 +++
 test/tasst/nstool.py                          | 184 ++++
 test/tasst/pasta.py                           |  42 +
 test/tasst/scenario/__init__.py               |  12 +
 test/tasst/scenario/simple.py                 |  98 +++
 test/tasst/transfer.py                        | 174 ++++
 test/tasst/typecheck.py                       |  47 +
 88 files changed, 10518 insertions(+), 96 deletions(-)
 create mode 100644 oldtest/.gitignore
 create mode 100644 oldtest/Makefile
 create mode 100644 oldtest/README.md
 rename {test => oldtest}/build/all (100%)
 rename {test => oldtest}/build/clang_tidy (100%)
 rename {test => oldtest}/build/cppcheck (100%)
 create mode 120000 oldtest/ci
 create mode 100644 oldtest/demo/passt
 create mode 100644 oldtest/demo/pasta
 create mode 100644 oldtest/demo/podman
 create mode 100644 oldtest/distro/debian
 create mode 100644 oldtest/distro/fedora
 create mode 100644 oldtest/distro/opensuse
 create mode 100644 oldtest/distro/ubuntu
 create mode 100644 oldtest/env/mate-terminal.profile
 create mode 100755 oldtest/find-arm64-firmware.sh
 create mode 100644 oldtest/lib/context
 create mode 100644 oldtest/lib/layout
 create mode 100644 oldtest/lib/layout_ugly
 create mode 100755 oldtest/lib/perf_report
 create mode 100755 oldtest/lib/setup
 create mode 100755 oldtest/lib/setup_ugly
 create mode 100755 oldtest/lib/term
 create mode 100755 oldtest/lib/test
 create mode 100755 oldtest/lib/util
 create mode 100755 oldtest/lib/video
 create mode 100644 oldtest/memory/passt
 create mode 100644 oldtest/nstool.c
 create mode 100755 oldtest/passt.mbuto
 create mode 100755 oldtest/passt.mem.mbuto
 create mode 100644 oldtest/passt/dhcp
 create mode 100644 oldtest/passt/ndp
 create mode 100644 oldtest/passt/shutdown
 create mode 100644 oldtest/passt/tcp
 create mode 100644 oldtest/passt/udp
 create mode 100644 oldtest/passt_in_ns/icmp
 create mode 100644 oldtest/passt_in_ns/shutdown
 create mode 100644 oldtest/passt_in_ns/tcp
 create mode 100644 oldtest/passt_in_ns/udp
 rename {test => oldtest}/pasta/dhcp (100%)
 rename {test => oldtest}/pasta/ndp (100%)
 rename {test => oldtest}/pasta/tcp (100%)
 rename {test => oldtest}/pasta/udp (100%)
 create mode 100644 oldtest/pasta_options/log_to_file
 create mode 100644 oldtest/perf/passt_tcp
 create mode 100644 oldtest/perf/passt_udp
 create mode 100644 oldtest/perf/pasta_tcp
 create mode 100644 oldtest/perf/pasta_udp
 create mode 100755 oldtest/prepare-distro-img.sh
 create mode 100755 oldtest/run
 create mode 120000 oldtest/run_demo
 create mode 100644 oldtest/two_guests/basic
 create mode 100644 oldtest/valgrind.supp
 create mode 100644 test/avocado/build.py
 create mode 100644 test/avocado/pasta.py
 create mode 100755 test/avocado/static_checkers/clang-tidy.sh
 create mode 100755 test/avocado/static_checkers/cppcheck.sh
 create mode 100755 test/avocado/static_checkers/flake8.sh
 create mode 100755 test/avocado/static_checkers/pylint.sh
 create mode 100644 test/avocado_classless/.gitignore
 create mode 100644 test/avocado_classless/avocado_classless/__init__.py
 create mode 100644 test/avocado_classless/avocado_classless/manifest.py
 create mode 100644 test/avocado_classless/avocado_classless/plugin.py
 create mode 100644 test/avocado_classless/avocado_classless/test.py
 create mode 100644 test/avocado_classless/examples.py
 create mode 100644 test/avocado_classless/selftests.py
 create mode 100644 test/avocado_classless/setup.py
 create mode 100644 test/tasst/__init__.py
 create mode 100644 test/tasst/address.py
 create mode 100644 test/tasst/dhcp.py
 create mode 100644 test/tasst/dhcpv6.py
 create mode 100644 test/tasst/exesite.py
 create mode 100644 test/tasst/meta/__init__.py
 create mode 100644 test/tasst/meta/static_ifup.py
 create mode 100644 test/tasst/meta/veth.py
 create mode 100644 test/tasst/ndp.py
 create mode 100644 test/tasst/nstool.py
 create mode 100644 test/tasst/pasta.py
 create mode 100644 test/tasst/scenario/__init__.py
 create mode 100644 test/tasst/scenario/simple.py
 create mode 100644 test/tasst/transfer.py
 create mode 100644 test/tasst/typecheck.py

-- 
2.41.0


^ permalink raw reply	[flat|nested] 32+ messages in thread

* [PATCH 01/27] avocado: Make a duplicate copy of testsuite for comparison purposes
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 02/27] avocado: Don't double download assets for test/ and oldtest/ David Gibson
                   ` (25 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Subsequent patches will start converting various parts of the testsuite to
use Avocado, rather than our hand-rolled testsuite.  While we're in
transition, it will be useful to run the equivalent tests side by side
in Avocado and the old suite.

To that end, take a snapshot of the old testsuite in the new directory
oldtest/.  Apart from referencing files in the new directory, the only
difference is that for now all tests are skipped.  As we convert tests, the
old versions will be added to oldtest/run so that a reasonably equivalent
side by side comparison of the two frameworks can be made.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 oldtest/.gitignore                |  11 +
 oldtest/Makefile                  | 203 ++++++++
 oldtest/README.md                 | 137 +++++
 oldtest/build/all                 |  61 +++
 oldtest/build/clang_tidy          |  17 +
 oldtest/build/cppcheck            |  17 +
 oldtest/ci                        |   1 +
 oldtest/demo/passt                | 245 +++++++++
 oldtest/demo/pasta                | 274 ++++++++++
 oldtest/demo/podman               | 819 ++++++++++++++++++++++++++++++
 oldtest/distro/debian             | 252 +++++++++
 oldtest/distro/fedora             | 396 +++++++++++++++
 oldtest/distro/opensuse           | 208 ++++++++
 oldtest/distro/ubuntu             | 216 ++++++++
 oldtest/env/mate-terminal.profile |  42 ++
 oldtest/find-arm64-firmware.sh    |  13 +
 oldtest/lib/context               | 130 +++++
 oldtest/lib/layout                | 259 ++++++++++
 oldtest/lib/layout_ugly           | 113 +++++
 oldtest/lib/perf_report           | 272 ++++++++++
 oldtest/lib/setup                 | 385 ++++++++++++++
 oldtest/lib/setup_ugly            |  58 +++
 oldtest/lib/term                  | 750 +++++++++++++++++++++++++++
 oldtest/lib/test                  | 398 +++++++++++++++
 oldtest/lib/util                  | 133 +++++
 oldtest/lib/video                 | 152 ++++++
 oldtest/memory/passt              | 187 +++++++
 oldtest/nstool.c                  | 565 +++++++++++++++++++++
 oldtest/passt.mbuto               |  83 +++
 oldtest/passt.mem.mbuto           |  44 ++
 oldtest/passt/dhcp                |  70 +++
 oldtest/passt/ndp                 |  33 ++
 oldtest/passt/shutdown            |  19 +
 oldtest/passt/tcp                 |  76 +++
 oldtest/passt/udp                 |  46 ++
 oldtest/passt_in_ns/icmp          |  32 ++
 oldtest/passt_in_ns/shutdown      |  19 +
 oldtest/passt_in_ns/tcp           | 256 ++++++++++
 oldtest/passt_in_ns/udp           | 138 +++++
 oldtest/pasta/dhcp                |  46 ++
 oldtest/pasta/ndp                 |  33 ++
 oldtest/pasta/tcp                 |  96 ++++
 oldtest/pasta/udp                 |  59 +++
 oldtest/pasta_options/log_to_file |  93 ++++
 oldtest/perf/passt_tcp            | 215 ++++++++
 oldtest/perf/passt_udp            | 165 ++++++
 oldtest/perf/pasta_tcp            | 300 +++++++++++
 oldtest/perf/pasta_udp            | 219 ++++++++
 oldtest/prepare-distro-img.sh     |  18 +
 oldtest/run                       | 238 +++++++++
 oldtest/run_demo                  |   1 +
 oldtest/two_guests/basic          |  80 +++
 oldtest/valgrind.supp             |   9 +
 53 files changed, 8702 insertions(+)
 create mode 100644 oldtest/.gitignore
 create mode 100644 oldtest/Makefile
 create mode 100644 oldtest/README.md
 create mode 100644 oldtest/build/all
 create mode 100644 oldtest/build/clang_tidy
 create mode 100644 oldtest/build/cppcheck
 create mode 120000 oldtest/ci
 create mode 100644 oldtest/demo/passt
 create mode 100644 oldtest/demo/pasta
 create mode 100644 oldtest/demo/podman
 create mode 100644 oldtest/distro/debian
 create mode 100644 oldtest/distro/fedora
 create mode 100644 oldtest/distro/opensuse
 create mode 100644 oldtest/distro/ubuntu
 create mode 100644 oldtest/env/mate-terminal.profile
 create mode 100755 oldtest/find-arm64-firmware.sh
 create mode 100644 oldtest/lib/context
 create mode 100644 oldtest/lib/layout
 create mode 100644 oldtest/lib/layout_ugly
 create mode 100755 oldtest/lib/perf_report
 create mode 100755 oldtest/lib/setup
 create mode 100755 oldtest/lib/setup_ugly
 create mode 100755 oldtest/lib/term
 create mode 100755 oldtest/lib/test
 create mode 100755 oldtest/lib/util
 create mode 100755 oldtest/lib/video
 create mode 100644 oldtest/memory/passt
 create mode 100644 oldtest/nstool.c
 create mode 100755 oldtest/passt.mbuto
 create mode 100755 oldtest/passt.mem.mbuto
 create mode 100644 oldtest/passt/dhcp
 create mode 100644 oldtest/passt/ndp
 create mode 100644 oldtest/passt/shutdown
 create mode 100644 oldtest/passt/tcp
 create mode 100644 oldtest/passt/udp
 create mode 100644 oldtest/passt_in_ns/icmp
 create mode 100644 oldtest/passt_in_ns/shutdown
 create mode 100644 oldtest/passt_in_ns/tcp
 create mode 100644 oldtest/passt_in_ns/udp
 create mode 100644 oldtest/pasta/dhcp
 create mode 100644 oldtest/pasta/ndp
 create mode 100644 oldtest/pasta/tcp
 create mode 100644 oldtest/pasta/udp
 create mode 100644 oldtest/pasta_options/log_to_file
 create mode 100644 oldtest/perf/passt_tcp
 create mode 100644 oldtest/perf/passt_udp
 create mode 100644 oldtest/perf/pasta_tcp
 create mode 100644 oldtest/perf/pasta_udp
 create mode 100755 oldtest/prepare-distro-img.sh
 create mode 100755 oldtest/run
 create mode 120000 oldtest/run_demo
 create mode 100644 oldtest/two_guests/basic
 create mode 100644 oldtest/valgrind.supp

diff --git a/oldtest/.gitignore b/oldtest/.gitignore
new file mode 100644
index 00000000..48374028
--- /dev/null
+++ b/oldtest/.gitignore
@@ -0,0 +1,11 @@
+test_logs/
+mbuto/
+*.img
+QEMU_EFI.fd
+*.qcow2
+*.raw
+*.raw.xz
+*.bin
+nstool
+guest-key
+guest-key.pub
diff --git a/oldtest/Makefile b/oldtest/Makefile
new file mode 100644
index 00000000..7b00bef4
--- /dev/null
+++ b/oldtest/Makefile
@@ -0,0 +1,203 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Tests makefile
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+WGET = wget -c
+
+DEBIAN_IMGS = debian-8.11.0-openstack-amd64.qcow2 \
+	debian-9-nocloud-amd64-daily-20200210-166.qcow2 \
+	debian-10-nocloud-amd64.qcow2 \
+	debian-10-generic-arm64.qcow2 \
+	debian-10-generic-ppc64el-20220911-1135.qcow2 \
+	debian-11-nocloud-amd64.qcow2 \
+	debian-11-generic-arm64.qcow2 \
+	debian-11-generic-ppc64el.qcow2 \
+	debian-sid-nocloud-amd64-daily.qcow2 \
+	debian-sid-nocloud-arm64-daily.qcow2 \
+	debian-sid-nocloud-ppc64el-daily.qcow2
+
+FEDORA_IMGS = Fedora-Cloud-Base-26-1.5.x86_64.qcow2 \
+	Fedora-Cloud-Base-27-1.6.x86_64.qcow2 \
+	Fedora-Cloud-Base-28-1.1.x86_64.qcow2 \
+	Fedora-Cloud-Base-28-1.1.aarch64.qcow2 \
+	Fedora-Cloud-Base-29-1.2.x86_64.qcow2 \
+	Fedora-Cloud-Base-29-1.2.aarch64.qcow2 \
+	Fedora-Cloud-Base-30-1.2.x86_64.qcow2 \
+	Fedora-Cloud-Base-30-1.2.aarch64.qcow2 \
+	Fedora-Cloud-Base-31-1.9.x86_64.qcow2 \
+	Fedora-Cloud-Base-31-1.9.aarch64.qcow2 \
+	Fedora-Cloud-Base-32-1.6.x86_64.qcow2 \
+	Fedora-Cloud-Base-32-1.6.aarch64.qcow2 \
+	Fedora-Cloud-Base-33-1.2.x86_64.qcow2 \
+	Fedora-Cloud-Base-33-1.2.aarch64.qcow2 \
+	Fedora-Cloud-Base-34-1.2.x86_64.qcow2 \
+	Fedora-Cloud-Base-34-1.2.aarch64.qcow2 \
+	Fedora-Cloud-Base-35-1.2.x86_64.qcow2 \
+	Fedora-Cloud-Base-35-1.2.aarch64.qcow2
+
+OPENSUSE_IMGS = openSUSE-Leap-15.1-JeOS.x86_64-kvm-and-xen.qcow2 \
+	openSUSE-Leap-15.2-JeOS.x86_64-kvm-and-xen.qcow2 \
+	openSUSE-Leap-15.3-JeOS.x86_64-kvm-and-xen.qcow2 \
+	openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz \
+	openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz \
+	openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2
+
+UBUNTU_OLD_IMGS = trusty-server-cloudimg-amd64-disk1.img \
+	trusty-server-cloudimg-i386-disk1.img \
+	trusty-server-cloudimg-ppc64el-disk1.img
+UBUNTU_NEW_IMGS = xenial-server-cloudimg-powerpc-disk1.img \
+	jammy-server-cloudimg-s390x.img
+UBUNTU_IMGS = $(UBUNTU_OLD_IMGS) $(UBUNTU_NEW_IMGS)
+
+DOWNLOAD_ASSETS = mbuto \
+	$(DEBIAN_IMGS) $(FEDORA_IMGS) $(OPENSUSE_IMGS) $(UBUNTU_IMGS)
+TESTDATA_ASSETS = small.bin big.bin medium.bin
+LOCAL_ASSETS = mbuto.img mbuto.mem.img QEMU_EFI.fd \
+	$(DEBIAN_IMGS:%=prepared-%) $(FEDORA_IMGS:%=prepared-%) \
+	$(UBUNTU_NEW_IMGS:%=prepared-%) \
+	nstool guest-key guest-key.pub \
+	$(TESTDATA_ASSETS)
+
+ASSETS = $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS)
+
+CFLAGS = -Wall -Werror -Wextra -pedantic -std=c99
+
+assets: $(ASSETS)
+
+mbuto:
+	git clone git://mbuto.sh/mbuto
+
+guest-key guest-key.pub:
+	ssh-keygen -f guest-key -N ''
+
+mbuto.img: passt.mbuto mbuto guest-key.pub $(TESTDATA_ASSETS)
+	./mbuto/mbuto -p ./$< -c lz4 -f $@
+
+mbuto.mem.img: passt.mem.mbuto mbuto ../passt.avx2
+	./mbuto/mbuto -p ./$< -c lz4 -f $@
+
+nstool: nstool.c
+	$(CC) $(CFLAGS) -o $@ $^
+
+QEMU_EFI.fd:
+	./find-arm64-firmware.sh $@
+
+prepared-%.qcow2: %.qcow2 ./prepare-distro-img.sh
+	qemu-img create -f qcow2 -F qcow2 -b $< $@
+	./prepare-distro-img.sh $@
+
+prepared-%.img: %.img ./prepare-distro-img.sh
+	qemu-img create -f qcow2 -F qcow2 -b $< $@
+	./prepare-distro-img.sh $(IMGTYPE) $@
+
+small.bin:
+	dd if=/dev/urandom bs=2k count=1 of=$@
+
+medium.bin:
+	dd if=/dev/urandom bs=1k count=5 of=$@
+
+big.bin:
+	dd if=/dev/urandom bs=1M count=10 of=$@
+
+check: assets
+	./run
+
+debug: assets
+	DEBUG=1 ./run
+
+clean:
+	rm -f perf.js *~
+	rm -f $(LOCAL_ASSETS)
+	rm -rf test_logs
+	rm -f prepared-*.qcow2 prepared-*.img
+
+realclean: clean
+	rm -rf $(DOWNLOAD_ASSETS)
+
+# Debian downloads
+debian-8.11.0-openstack-%.qcow2:
+	$(WGET) -O $@ https://cloud.debian.org/images/cloud/OpenStack/archive/8.11.0/debian-8.11.0-openstack-$*.qcow2
+
+debian-9-nocloud-%-daily-20200210-166.qcow2:
+	$(WGET) -O $@ https://cloud.debian.org/images/cloud/stretch/daily/20200210-166/debian-9-nocloud-$*-daily-20200210-166.qcow2
+
+debian-10-nocloud-%.qcow2:
+	$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/latest/debian-10-nocloud-$*.qcow2
+
+debian-10-generic-ppc64el-20220911-1135.qcow2:
+	$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/20220911-1135/debian-10-generic-ppc64el-20220911-1135.qcow2
+
+debian-10-generic-%.qcow2:
+	$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/latest/debian-10-generic-$*.qcow2
+
+debian-11-nocloud-%.qcow2:
+	$(WGET) -O $@ https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-nocloud-$*.qcow2
+
+debian-11-generic-%.qcow2:
+	$(WGET) -O $@ https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-$*.qcow2
+
+debian-sid-nocloud-%-daily.qcow2:
+	$(WGET) -O $@ https://cloud.debian.org/images/cloud/sid/daily/latest/debian-sid-nocloud-$*-daily.qcow2
+
+# Fedora downloads
+Fedora-Cloud-Base-26-1.5.%.qcow2:
+	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/26/CloudImages/$*/images/Fedora-Cloud-Base-26-1.5.$*.qcow2
+
+Fedora-Cloud-Base-27-1.6.%.qcow2:
+	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/27/CloudImages/$*/images/Fedora-Cloud-Base-27-1.6.$*.qcow2
+
+Fedora-Cloud-Base-28-1.1.%.qcow2:
+	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/28/Cloud/$*/images/Fedora-Cloud-Base-28-1.1.$*.qcow2
+
+Fedora-Cloud-Base-29-1.2.%.qcow2:
+	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/29/Cloud/$*/images/Fedora-Cloud-Base-29-1.2.$*.qcow2
+
+Fedora-Cloud-Base-30-1.2.%.qcow2:
+	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/30/Cloud/$*/images/Fedora-Cloud-Base-30-1.2.$*.qcow2
+
+Fedora-Cloud-Base-31-1.9.%.qcow2:
+	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/31/Cloud/$*/images/Fedora-Cloud-Base-31-1.9.$*.qcow2
+
+Fedora-Cloud-Base-32-1.6.%.qcow2:
+	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/32/Cloud/$*/images/Fedora-Cloud-Base-32-1.6.$*.qcow2
+
+Fedora-Cloud-Base-33-1.2.%.qcow2:
+	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/33/Cloud/$*/images/Fedora-Cloud-Base-33-1.2.$*.qcow2
+
+Fedora-Cloud-Base-34-1.2.%.qcow2:
+	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/34/Cloud/$*/images/Fedora-Cloud-Base-34-1.2.$*.qcow2
+
+Fedora-Cloud-Base-35-1.2.%.qcow2:
+	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/35/Cloud/$*/images/Fedora-Cloud-Base-35-1.2.$*.qcow2
+
+# OpenSuSE downloads
+openSUSE-Leap-15.1-JeOS.x86_64-kvm-and-xen.qcow2:
+	$(WGET) -O $@ https://download.opensuse.org/distribution/leap/15.1/jeos/openSUSE-Leap-15.1-JeOS.x86_64-kvm-and-xen.qcow2
+
+openSUSE-Leap-15.2-JeOS.x86_64-kvm-and-xen.qcow2:
+	$(WGET) -O $@ https://download.opensuse.org/distribution/leap/15.2/appliances/openSUSE-Leap-15.2-JeOS.x86_64-kvm-and-xen.qcow2
+
+openSUSE-Leap-15.3-JeOS.x86_64-kvm-and-xen.qcow2:
+	$(WGET) -O $@ https://download.opensuse.org/distribution/leap/15.3/appliances/openSUSE-Leap-15.3-JeOS.x86_64-kvm-and-xen.qcow2
+
+openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz:
+	$(WGET) -O $@ http://download.opensuse.org/ports/aarch64/tumbleweed/appliances/openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz
+
+openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz:
+	$(WGET) -O $@ http://download.opensuse.org/ports/armv7hl/tumbleweed/appliances/openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz
+
+openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2:
+	$(WGET) -O $@ https://download.opensuse.org/tumbleweed/appliances/openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2
+
+# Ubuntu downloads
+trusty-server-cloudimg-%-disk1.img:
+	$(WGET) -O $@ https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-$*-disk1.img
+
+xenial-server-cloudimg-powerpc-disk1.img:
+	$(WGET) -O $@ https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-powerpc-disk1.img
+
+jammy-server-cloudimg-s390x.img:
+	$(WGET) -O $@ https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-s390x.img
diff --git a/oldtest/README.md b/oldtest/README.md
new file mode 100644
index 00000000..03c7f579
--- /dev/null
+++ b/oldtest/README.md
@@ -0,0 +1,137 @@
+<!---
+SPDX-License-Identifier: GPL-2.0-or-later
+Copyright (c) 2021-2022 Red Hat GmbH
+Author: Stefano Brivio <sbrivio@redhat.com>
+-->
+
+# Scope
+
+This directory contains test cases for _passt_ and _pasta_ and a simple
+POSIX shell-based framework to define them, and run them as a suite.
+
+These tests can be run as part of a continuous integration workflow, and are
+also used to provide short usage demos, with video recording, for _passt_ and
+_pasta_ basic use cases.
+
+# Run
+
+## Dependencies
+
+### Packages
+
+The tests require some package dependencies commonly available in Linux
+distributions. If some packages are not available, the test groups that need
+them will be selectively skipped.
+
+This is a non-exhaustive list of packages that might not commonly be installed
+on a system, i.e. common utilities such as a shell are not included here.
+
+Example for Debian, and possibly most Debian-based distributions:
+
+    build-essential git jq strace iperf3 qemu-system-x86 tmux sipcalc bc
+    clang-tidy cppcheck isc-dhcp-common psmisc linux-cpupower socat
+    netcat-openbsd fakeroot lz4 lm-sensors qemu-system-arm qemu-system-ppc
+    qemu-system-misc qemu-system-x86 valgrind
+
+NOTE: the tests need a qemu version >= 7.2, or one that contains commit
+13c6be96618c ("net: stream: add unix socket"): this change introduces support
+for UNIX domain sockets as network device back-end, which qemu uses to connect
+to passt.
+
+### Other tools
+
+Test measuring request-response and connect-request-response latencies use
+`neper`, which is not commonly packaged by distributions and needs to be built
+and installed manually:
+
+    git clone https://github.com/google/neper
+    cd neper; make
+    cp tcp_crr tcp_rr udp_rr /usr/local/bin
+
+Virtual machine images are built during test executions using
+[mbuto](https://mbuto.lameexcu.se/), the shell script is sourced via _git_
+as needed, so there's no need to actually install it.
+
+### Kernel parameters
+
+Performance tests use iperf3 with rather large TCP receiving and sending
+windows, to decrease the likelihood of iperf3 itself becoming the bottleneck.
+These values need to be allowed by the kernel of the host running the tests.
+Example for /etc/sysctl.conf:
+
+  net.core.rmem_max = 134217728
+  net.core.wmem_max = 134217728
+
+Further, the passt demo uses perf(1), relying on hardware events for performance
+counters, to display syscall overhead. The kernel needs to allow unprivileged
+users to access these events. Suggested entry for /etc/sysctl.conf:
+
+  kernel.perf_event_paranoid = -1
+
+### Special requirements for continuous integration and demo modes
+
+Running the test suite as continuous integration or demo modes will record the
+terminal with the steps being executed, using asciinema(1), and create binary
+packages.
+
+The following additional packages are commonly needed:
+
+    alien asciinema linux-perf tshark
+
+## Regular test
+
+Just issue:
+
+    ./run
+
+from the `test` directory. Elevated privileges are not needed. Environment
+variable settings: DEBUG=1 enables debugging messages, TRACE=1 enables tracing
+(further debugging messages), PCAP=1 enables packet captures. Example:
+
+    PCAP=1 TRACE=1 ./run
+
+## Running selected tests
+
+Rudimentary support to run a list of selected tests, without support for
+dependencies, is available. Tests need to have a setup function corresponding to
+their path. For example:
+
+    ./run passt/ndp passt/dhcp pasta/ndp
+
+will call the 'passt' setup function (from lib/setup), run the two corresponding
+tests, call the 'passt' teardown function, the 'pasta' setup, run the pasta/ndp
+test, and finally tear down the 'pasta' setup.
+
+Note that requirements on steps implemented by related tests are not handled.
+For example, if the 'passt/tcp' needs guest connectivity set up by the
+'passt/ndp' and 'passt/dhcp' tests, those need to be listed explicitly.
+
+## Continuous integration
+
+Issuing:
+
+    ./ci
+
+will run the whole test suite while recording the execution, and it will also
+build JavaScript fragments used on http://passt.top/ for performance data tables
+and links to specific offsets in the captures.
+
+## Demo mode
+
+Issuing:
+
+    ./demo
+
+will run the demo cases under `demo`, with terminal captures as well.
+
+# Framework
+
+The implementation of the testing framework is under `lib`, and it provides
+facilities for terminal and _tmux_ session management, interpretation of test
+directives, video recording, and suchlike. Test cases are organised in the
+remaining directories.
+
+Test cases can be implemented as POSIX shell scripts, or as a set of directives,
+which are not formally documented here, but should be clear enough from the
+existing cases. The entry point for interpretation of test directives is
+implemented in `lib/test`.
diff --git a/oldtest/build/all b/oldtest/build/all
new file mode 100644
index 00000000..1f79e0d8
--- /dev/null
+++ b/oldtest/build/all
@@ -0,0 +1,61 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/build/all - Build targets, one by one, then all together, check output
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+htools	make cc rm uname getconf mkdir cp rm man
+
+test	Build passt
+host	make clean
+check	! [ -e passt ]
+host	CFLAGS="-Werror" make passt
+check	[ -f passt ]
+
+test	Build pasta
+host	make clean
+check	! [ -e pasta ]
+host	CFLAGS="-Werror" make pasta
+check	[ -h pasta ]
+
+test	Build qrap
+host	make clean
+check	! [ -e qrap ]
+host	CFLAGS="-Werror" make qrap
+check	[ -f qrap ]
+
+test	Build all
+host	make clean
+check	! [ -e passt ]
+check	! [ -e pasta ]
+check	! [ -e qrap ]
+host	CFLAGS="-Werror" make
+check	[ -f passt ]
+check	[ -h pasta ]
+check	[ -f qrap ]
+
+test	Install
+host	mkdir __STATEDIR__/prefix
+host	prefix=__STATEDIR__/prefix make install
+check	[ -f __STATEDIR__/prefix/bin/passt ]
+check	[ -h __STATEDIR__/prefix/bin/pasta ]
+check	[ -f __STATEDIR__/prefix/bin/qrap ]
+check	man -M __STATEDIR__/prefix/share/man -W passt
+check	man -M __STATEDIR__/prefix/share/man -W pasta
+check	man -M __STATEDIR__/prefix/share/man -W qrap
+
+test	Uninstall
+host	prefix=__STATEDIR__/prefix make uninstall
+check	! [ -f __STATEDIR__/prefix/bin/passt ]
+check	! [ -h __STATEDIR__/prefix/bin/pasta ]
+check	! [ -f __STATEDIR__/prefix/bin/qrap ]
+check	! man -M __STATEDIR__/prefix/share/man -W passt 2>/dev/null
+check	! man -M __STATEDIR__/prefix/share/man -W pasta 2>/dev/null
+check	! man -M __STATEDIR__/prefix/share/man -W qrap 2>/dev/null
diff --git a/oldtest/build/clang_tidy b/oldtest/build/clang_tidy
new file mode 100644
index 00000000..40573bfd
--- /dev/null
+++ b/oldtest/build/clang_tidy
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/build/clang_tidy - Run source through clang-tidy(1) linter
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+htools	clang-tidy
+
+test	Run clang-tidy
+host	make clang-tidy
diff --git a/oldtest/build/cppcheck b/oldtest/build/cppcheck
new file mode 100644
index 00000000..0e1dbced
--- /dev/null
+++ b/oldtest/build/cppcheck
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/build/cppcheck - Run source through cppcheck(1) linter
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+htools	cppcheck
+
+test	Run cppcheck
+host	make cppcheck
diff --git a/oldtest/ci b/oldtest/ci
new file mode 120000
index 00000000..e5224d53
--- /dev/null
+++ b/oldtest/ci
@@ -0,0 +1 @@
+run
\ No newline at end of file
diff --git a/oldtest/demo/passt b/oldtest/demo/passt
new file mode 100644
index 00000000..a3c18b86
--- /dev/null
+++ b/oldtest/demo/passt
@@ -0,0 +1,245 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/demo/passt - Quick introduction to passt
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+say	This is a short introduction to 
+em	passt
+say	.
+nl
+nl
+sleep	3
+
+say	Let's fetch the source
+sleep	1
+host	cd __STATEDIR__
+host	git clone git://passt.top/passt
+sleep	1
+
+say	 and build it.
+sleep	1
+host	cd passt
+host	make
+sleep	1
+
+nl
+nl
+say	A quick look at the man page...
+sleep	1
+hostb	man ./passt.1
+sleep	5
+hostb	/ports
+sleep	2
+hostb	n
+sleep	2
+hostb	n
+sleep	10
+
+nl
+say	  '-t' to forward TCP ports.
+sleep	3
+host	q
+
+nl
+nl
+say	Let's create a small initramfs image for the guest.
+guest	cd __STATEDIR__
+guest	git clone git://mbuto.sh/mbuto
+guest	./mbuto/mbuto -f passt.img -p passt/test/passt.mbuto -c lz4
+sleep	2
+
+nl
+nl
+say	We want to isolate passt and guest in a
+nl
+say	  network namespace. For convenience, we'll
+nl
+say	  create it with 'pasta', see also the
+nl
+say	  'pasta' demo above.
+sleep	3
+
+passt	cd __STATEDIR__/passt
+passtb	./pasta -P pasta.pid
+sleep	3
+passt	/sbin/dhclient -4 --no-pid
+sleep	2
+passt	/sbin/dhclient -6 --no-pid
+sleep	2
+
+nl
+nl
+say	Now let's run 'passt' in the new namespace, and
+nl
+say	  enter this namespace from the guest terminal too.
+sleep	3
+guest	cd passt
+gout	TARGET_PID pgrep -P $(cat pasta.pid)
+sleep	1
+
+passtb	./passt -f -t 10001,10003 -s __STATEDIR__/passt.socket
+sleep	2
+
+guest	nsenter -t __TARGET_PID__ -U -n --preserve-credentials
+sleep	5
+
+nl
+nl
+say	We're ready to start qemu
+nl
+sleep	2
+hout	VMLINUZ echo "/boot/vmlinuz-$(uname -r)"
+guest	qemu-system-x86_64 -enable-kvm -m 4096 -cpu host -smp 4 -kernel __VMLINUZ__ -initrd ../passt.img -nographic -serial stdio -nodefaults -append "console=ttyS0" -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket
+sleep	10
+
+nl
+nl
+guest	ip link show
+sleep	3
+say	Guest is up. Let's configure IPv4 first...
+sleep	2
+guest	ip link set dev eth0 up
+sleep	2
+guest	dhclient -4
+sleep	2
+guest	ip addr show
+sleep	5
+
+nl
+say	  SLAAC is already done, but we can also
+nl
+say	  get another address via DHCPv6.
+sleep	3
+guest	dhclient -6
+sleep	3
+
+nl
+nl
+say	Let's try to communicate between host and guest.
+sleep	2
+guestb	socat TCP6-LISTEN:10001 STDIO
+sleep	2
+host	echo "Hello from the host" | socat -u STDIN TCP6:[::1]:10001
+sleep	5
+
+nl
+nl
+say	Now the other way around... using
+nl
+say	   the address of the default gateway.
+sleep	2
+gout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+sleep	5
+hostb	socat TCP4-LISTEN:31337 STDIO
+sleep	2
+guest	echo "Hello from the guest" | socat -u STDIN TCP4:__GW__:31337
+sleep	3
+
+nl
+nl
+say	Let's have a (quick!) look at performance
+nl
+say	  more in the "Performance" section below.
+sleep	3
+
+host	nsenter -t __TARGET_PID__ -U -n --preserve-credentials
+
+guest	/sbin/sysctl -w net.core.rmem_max=536870912
+guest	/sbin/sysctl -w net.core.wmem_max=536870912
+guest	/sbin/sysctl -w net.core.rmem_default=33554432
+guest	/sbin/sysctl -w net.core.wmem_default=33554432
+guest	/sbin/sysctl -w net.ipv4.tcp_rmem="4096 131072 268435456"
+guest	/sbin/sysctl -w net.ipv4.tcp_wmem="4096 131072 268435456"
+guest	/sbin/sysctl -w net.ipv4.tcp_timestamps=0
+
+host	sysctl -w net.ipv4.tcp_rmem="4096 524288 134217728"
+host	sysctl -w net.ipv4.tcp_wmem="4096 524288 134217728"
+host	sysctl -w net.ipv4.tcp_timestamps=0
+
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+nl
+nl
+info	Throughput in Gbps, latency in µs
+th	flow host>guest guest>host
+
+set	OPTS -P4 -w 64M -l 1M -i1 --pacing-timer 100000
+
+tr	TCP/IPv6 throughput
+hostb	sleep 10; iperf3 -c ::1 -p 10001 __OPTS__
+gout	BW iperf3 -s1J -p 10001 | jq -rM ".end.sum_received.bits_per_second"
+bw	__BW__ 2.0 3.0
+sleep	5
+guestb	sleep 10; iperf3 -c __GW6__%__IFNAME__ -p 10002 __OPTS__ -O3
+hout	BW iperf3 -s1J -p 10002 | jq -rM ".end.sum_received.bits_per_second"
+bw	__BW__ 2.0 3.0
+
+tl	TCP/IPv6 RR latency
+guestb	tcp_rr -C 10001 -P 10003 -6 --nolog
+sleep	2
+hout	LAT tcp_rr -C 10001 -P 10003 --nolog -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 800
+sleep	2
+hostb	tcp_rr -6 --nolog
+sleep	2
+gout	LAT tcp_rr --nolog -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 800
+sleep	2
+
+tl	TCP/IPv6 CRR latency
+guestb	tcp_crr -C 10001 -P 10003 -6 --nolog
+sleep	2
+hout	LAT tcp_crr -C 10001 -P 10003 --nolog -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 800
+sleep	2
+hostb	tcp_crr -6 --nolog
+sleep	2
+gout	LAT tcp_crr --nolog -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 800
+sleep	2
+
+tr	TCP/IPv4 throughput
+hostb	sleep 10; iperf3 -c 127.0.0.1 -p 10001 __OPTS__
+gout	BW iperf3 -p 10001 -s1J | jq -rM ".end.sum_received.bits_per_second"
+bw	__BW__ 2.0 3.0
+sleep	5
+guestb	sleep 10; iperf3 -c __GW__ -p 10002 __OPTS__ -O3
+hout	BW iperf3 -s1J -p 10002 | jq -rM ".end.sum_received.bits_per_second"
+bw	__BW__ 2.0 3.0
+
+tl	TCP/IPv4 RR latency
+guestb	tcp_rr -C 10001 -P 10003 -4 --nolog
+sleep	2
+hout	LAT tcp_rr -C 10001 -P 10003 --nolog -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 800
+sleep	2
+hostb	tcp_rr -4 --nolog
+sleep	2
+gout	LAT tcp_rr --nolog -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 800
+sleep	2
+
+tl	TCP/IPv4 CRR latency
+guestb	tcp_crr -C 10001 -P 10003 -4 --nolog
+sleep	2
+hout	LAT tcp_crr -C 10001 -P 10003 --nolog -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 800
+sleep	2
+hostb	tcp_crr -4 --nolog
+sleep	2
+gout	LAT tcp_crr --nolog -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 800
+sleep	2
+
+nl
+nl
+say	Thanks for watching!
+sleep	5
diff --git a/oldtest/demo/pasta b/oldtest/demo/pasta
new file mode 100644
index 00000000..f48585da
--- /dev/null
+++ b/oldtest/demo/pasta
@@ -0,0 +1,274 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/demo/pasta - Quick introduction to pasta
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+say	This is a short introduction to 
+em	pasta
+say	.
+nl
+nl
+sleep	3
+
+say	Let's fetch the source
+sleep	1
+host	cd __STATEDIR__
+host	git clone git://passt.top/passt
+sleep	1
+
+say	 and build it.
+sleep	1
+host	cd passt
+host	make
+sleep	1
+
+nl
+nl
+say	A quick look at the man page...
+sleep	1
+hostb	man ./pasta.1
+sleep	5
+hostb	/pasta
+sleep	2
+hostb	n
+sleep	2
+hostb	n
+sleep	10
+
+nl
+say	  without PID, it will create a namespace.
+sleep	3
+passt	cd __STATEDIR__/passt
+passtb	./pasta -P pasta.pid
+sleep	3
+
+nl
+nl
+say	For convenience, let's enter this namespace
+nl
+say	  from another terminal.
+sleep	3
+ns	cd __STATEDIR__/passt
+nsout	TARGET_PID pgrep -P $(cat pasta.pid)
+sleep	1
+
+ns	nsenter -t __TARGET_PID__ -U -n --preserve-credentials
+sleep	5
+
+nl
+nl
+say	Now, we're ready to configure networking.
+sleep	2
+host	q
+
+nl
+nl
+ns	ip link show
+sleep	3
+say	Let's configure IPv4 first...
+sleep	2
+ns	/sbin/dhclient -4 --no-pid
+sleep	2
+ns	ip addr show
+sleep	5
+
+nl
+say	  SLAAC is already done, but we can also
+nl
+say	  get another address via DHCPv6.
+sleep	3
+ns	/sbin/dhclient -6 --no-pid
+sleep	3
+
+nl
+nl
+say	Let's try to communicate between host and namespace
+sleep	2
+nl
+say	  ...there's no need to configure port forwarding,
+nl
+say	  pasta detects bound ports and forwards them.
+sleep	3
+
+nsb	socat TCP6-LISTEN:31337,bind=[::1] STDOUT
+sleep	2
+host	echo "Hello from the host" | socat -u STDIN TCP6:[::1]:31337
+sleep	5
+
+nl
+nl
+say	Now the other way around...
+nl
+say	  we can use a loopback address
+sleep	2
+hostb	socat TCP6-LISTEN:31337,bind=[::1] STDIO
+sleep	2
+ns	echo "Hello from the namespace" | socat -u STDIN TCP6:[::1]:31337
+sleep	5
+
+nl
+say	  or the address of the default gateway.
+sleep	2
+nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+sleep	5
+hostb	socat TCP4-LISTEN:31337 STDIO
+sleep	2
+ns	echo "Hello from the namespace" | socat -u STDIN TCP4:__GW__:31337
+sleep	3
+
+nl
+nl
+say	UDP...
+sleep	2
+ns	host -t A passt.top
+sleep	3
+say	 seems to work too.
+sleep	3
+
+nl
+nl
+em	pasta
+say	 can also take packet captures.
+sleep	3
+passt	exit
+sleep	2
+passtb	./pasta -p ../demo_pasta.pcap
+sleep	2
+passt	
+passt	/sbin/dhclient -4 --no-pid
+sleep	2
+hostb	tshark -r ../demo_pasta.pcap
+sleep	5
+
+nl
+nl
+say	And there are tons of totally useless
+sleep	1
+bsp	14
+say	absolutely useful features
+nl
+say	  you can find described in the man page.
+sleep	5
+
+nl
+nl
+say	Let's have a (quick!) look at performance
+nl
+say	  more in the "Performance" section below.
+sleep	3
+ns	exit
+passt	exit
+passt	make clean
+passt	CFLAGS="-g" make
+sleep	2
+passtb	perf record -g ./pasta -P pasta.pid
+sleep	2
+
+nsout	TARGET_PID pgrep -P $(cat pasta.pid)
+sleep	1
+ns	nsenter -t __TARGET_PID__ -U -n --preserve-credentials
+sleep	5
+
+nl
+nl
+info	Throughput in Gbps, latency in µs
+th	flow init>ns ns>init
+
+set	OPTS -P4 -l 1M -w 32M -i1 --pacing-timer 100000
+
+tr	TCP/IPv6 throughput
+hostb	sleep 10; iperf3 -c ::1 -p 10001 __OPTS__
+nsout	BW iperf3 -s1J -p 10001 | jq -rM ".end.sum_received.bits_per_second"
+bw	__BW__ 10.0 20.0
+sleep	5
+nsb	sleep 10; iperf3 -c ::1 -p 10001 __OPTS__
+hout	BW iperf3 -s1J -p 10001 | jq -rM ".end.sum_received.bits_per_second"
+bw	__BW__ 10.0 20.0
+
+tl	TCP/IPv6 RR latency
+nsb	tcp_rr -6 --nolog
+sleep	2
+hout	LAT tcp_rr --nolog -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 500
+sleep	2
+hostb	tcp_rr -6 --nolog
+sleep	2
+nsout	LAT tcp_rr --nolog -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 500
+sleep	2
+
+tl	TCP/IPv6 CRR latency
+nsb	tcp_crr -6 --nolog
+sleep	2
+hout	LAT tcp_crr --nolog -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 500
+sleep	2
+hostb	tcp_crr -6 --nolog
+sleep	2
+nsout	LAT tcp_crr --nolog -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 500
+sleep	2
+
+tr	TCP/IPv4 throughput
+hostb	sleep 10; iperf3 -c 127.0.0.1 -p 10001 __OPTS__
+nsout	BW iperf3 -s1J -p 10001 | jq -rM ".end.sum_received.bits_per_second"
+bw	__BW__ 10.0 20.0
+sleep	5
+nsb	sleep 10; iperf3 -c 127.0.0.1 -p 10001 __OPTS__
+hout	BW iperf3 -s1J -p 10001 | jq -rM ".end.sum_received.bits_per_second"
+bw	__BW__ 10.0 20.0
+
+tl	TCP/IPv4 RR latency
+nsb	tcp_rr -4 --nolog
+sleep	2
+hout	LAT tcp_rr --nolog -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 500
+sleep	2
+hostb	tcp_rr -4 --nolog
+sleep	2
+nsout	LAT tcp_rr --nolog -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 500
+sleep	2
+
+tl	TCP/IPv4 CRR latency
+nsb	tcp_crr -4 --nolog
+sleep	2
+hout	LAT tcp_crr --nolog -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 500
+sleep	2
+hostb	tcp_crr -4 --nolog
+sleep	2
+nsout	LAT tcp_crr --nolog -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 1000 500
+sleep	2
+
+sleep	5
+passt	exit
+sleep	2
+killp	PASST
+killp	HOST
+sleep	2
+nsb	perf report -g --max-stack 3
+sleep	10
+
+nl
+nl
+say	I 
+em	knew
+say	 it. 
+em	syscalls
+say	.
+sleep	5
+
+nl
+nl
+say	Thanks for watching!
+sleep	5
diff --git a/oldtest/demo/podman b/oldtest/demo/podman
new file mode 100644
index 00000000..edd403a1
--- /dev/null
+++ b/oldtest/demo/podman
@@ -0,0 +1,819 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/demo/podman - Show pasta operation with Podman
+#
+# Copyright (c) 2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+set	OPTS -Z -w 4M -l 1M -P 2 -t10 --pacing-timer 10000
+
+say	This is an overview of 
+em	Podman
+say	 using 
+em	pasta
+say	.
+nl
+nl
+sleep	3
+
+say	Let's fetch Podman
+sleep	1
+host	git -C __STATEDIR__ clone https://github.com/containers/podman.git
+sleep	1
+
+say	, patch it
+sleep	1
+host	cp ../contrib/podman/0001-libpod-Add-pasta-networking-mode.patch __STATEDIR__/podman
+host	cd __STATEDIR__/podman
+host	git am -3 0001-libpod-Add-pasta-networking-mode.patch
+sleep	1
+
+say	, and build it.
+host	go get github.com/pkg/errors@v0.9.1
+host	go mod vendor
+host	make
+sleep	1
+
+nl
+nl
+say	By default, for 
+em	rootless
+say	 mode, Podman will pick
+nl
+em	slirp4netns
+say	 to operate the network.
+nl
+nl
+say	Let's start a container with it
+sleep	1
+
+ns1	cd __STATEDIR__/podman
+ns1b	./bin/podman run --rm -ti alpine sh
+sleep	2
+
+say	,
+nl
+say	and one with 
+em	pasta
+say	 instead.
+
+ns2	cd __STATEDIR__/podman
+ns2b	./bin/podman run --net=pasta --rm -ti alpine sh
+sleep	2
+
+nl
+nl
+say	We can observe some practical differences:
+nl
+
+ns1b	ip addr show
+sleep	3
+say	- slirp4netns uses a predefined IPv4 address
+hl	NS1
+sleep	2
+
+ns2b	ip addr show
+sleep	3
+say	,
+nl
+say	  pasta copies addresses from the host
+hl	NS2
+sleep	2
+
+nl
+say	- slirp4netns uses 
+em	tap0
+say	 as interface name
+hl	NS1
+sleep	2
+
+say	, pasta
+nl
+say	  takes an interface name from the host
+hl	NS2
+sleep	2
+
+nl
+say	- same for routes:
+
+ns1b	ip route show
+sleep	3
+say	 slirp4netns defines its own
+nl
+say	  gateway address
+hl	NS1
+sleep	2
+
+say	, pasta copies it from the host
+ns2b	ip route show
+ns2b	ip -6 route show
+sleep	5
+
+nl
+nl
+say	Let's check connectivity...
+sleep	2
+ns1b	wget risotto.milane.se
+ns2b	wget myfinge.rs
+sleep	2
+say	 fine.
+sleep	5
+nl
+nl
+
+say	Let's run a service in the container,
+nl
+say	configuring port forwarding first
+sleep	5
+
+ns1b	exit
+sleep	2
+ns1b	podman run --rm -p 8080:8080/tcp -ti alpine sh
+sleep	5
+
+ns2b	exit
+sleep	2
+ns2b	./bin/podman run --net=pasta --rm -p 8081:8081/tcp -ti alpine sh
+sleep	5
+
+nl
+nl
+say	...and now actually start the service
+ns1b	apk add thttpd
+ns2b	apk add thttpd
+ns1b	>index.html cat << EOF
+ns1b	<!doctype html><body>Hello via slirp4netns</body>
+ns1b	EOF
+ns2b	>index.html cat << EOF
+ns2b	<!doctype html><body>Hello via pasta</body>
+ns2b	EOF
+ns1b	thttpd -p 8080
+ns2b	thttpd -p 8081
+
+sleep	3
+say	, then check
+nl
+say	that it's accessible.
+sleep	3
+
+hostb	lynx http://127.0.0.1:8080/
+sleep	5
+hostb	q
+hostb	lynx http://[::1]:8081/
+sleep	5
+hostb	q
+sleep	2
+
+nl
+nl
+say	What about performance, you might ask.
+nl
+say	For simplicity, we'll measure between init
+nl
+say	namespace (the "host") and container. To do
+nl
+say	that, we need to allow the container direct
+nl
+say	access to the host, which needs an extra option
+nl
+say	in slirp4netns. Let's restart that container,
+nl
+say	while also mapping ports for iperf3 and neper.
+nl
+sleep	3
+
+ns1	exit
+
+ns1b	podman run --rm --net=slirp4netns:allow_host_loopback=true,enable_ipv6=true -p 5221-5222:5221-5222/tcp -p 5221-5222:5221-5222/udp -ti alpine sh
+sleep	5
+nl
+nl
+say	pasta allows that by default, so we wouldn't need
+nl
+say	to touch the container using pasta, but let's
+nl
+say	take the chance to look at passing extra options
+nl
+say	there as well.
+nl
+nl
+ns2	exit
+
+say	Options after '--net=pasta:' are the same as
+nl
+say	documented for the command line of pasta(1).
+nl
+say	For example, we can enable packet captures
+sleep	3
+ns2b	./bin/podman run --net=pasta:--pcap,demo.pcap --rm -ti alpine sh
+sleep	5
+
+say	,
+nl
+say	and generate some traffic we can look at.
+nl
+sleep	2
+ns2b	wget -O - lameexcu.se
+sleep	2
+hostb	tshark -r demo.pcap tcp
+sleep	5
+
+nl
+say	But back to performance now. By the way,
+nl
+say	pasta can also forward ports through the
+nl
+say	loopback interface for improved throughput.
+nl
+say	Let's configure that.
+nl
+sleep	2
+ns2b	exit
+sleep	1
+ns2b	./bin/podman run --net=pasta:-T,5213-5214,-U,5213-5214 -p 5223-5224:5223-5224/tcp -p 5223-5224:5223-5224/udp --rm -ti alpine sh
+sleep	5
+
+nl
+say	In slirp4netns mode, Podman enables by
+nl
+say	default the port forwarder from 'rootlesskit'
+nl
+say	for better performance.
+nl
+say	However, it can't be used for non-local
+nl
+say	mappings (traffic without loopback source 
+nl
+em	and
+say	 destination) because it doesn't preserve
+nl
+say	the correct source address as it forwards
+nl
+say	packets to the container.
+sleep	3
+nl
+nl
+say	We'll check non-loopback mappings first for
+nl
+say	both pasta and slirp4netns, then restart the
+nl
+say	slirp4netns container with rootlesskit and
+nl
+say	switch to loopback mappings. pasta doesn't
+nl
+say	have this limitation.
+nl
+nl
+say	One last note: slirp4netns doesn't support
+nl
+say	forwarding of IPv6 ports (to the container):
+nl
+say	github.com/rootless-containers/slirp4netns/issues/253
+nl
+say	so we'll skip IPv6 tests for slirp4netns as
+nl
+say	port forwarder (on the path to the container).
+
+sleep	5
+ns1	exit
+ns1b	podman run --rm --net=slirp4netns:allow_host_loopback=true,enable_ipv6=true,port_handler=slirp4netns -p 5221-5222:5221-5222/tcp -p 5221-5222:5221-5222/udp -ti alpine sh
+sleep	3
+
+nl
+nl
+say	We'll use iperf3(1) for throughput
+sleep	2
+ns1b	apk add iperf3 jq bc
+ns2b	apk add iperf3 jq bc
+sleep	2
+say	 and static
+nl
+say	builds of neper (github.com/google/neper) for
+nl
+say	latency.
+ns1	wget lameexcu.se/tcp_rr; chmod 755 tcp_rr
+ns2	wget lameexcu.se/tcp_rr; chmod 755 tcp_rr
+ns1	wget lameexcu.se/tcp_crr; chmod 755 tcp_crr
+ns2	wget lameexcu.se/tcp_crr; chmod 755 tcp_crr
+ns1	wget lameexcu.se/udp_rr; chmod 755 udp_rr
+ns2	wget lameexcu.se/udp_rr; chmod 755 udp_rr
+sleep	5
+
+nl
+nl
+say	Everything is set now, let's start
+sleep	2
+hout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+hout	ADDR4 ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global").local'
+hout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global").local'
+hout	GW4 ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+
+nl
+nl
+resize	INFO D 15
+info	Throughput in Gbps, latency in µs
+info	  non-loopback (tap) connections
+th	mode slirp4netns pasta
+
+tr	TCP/IPv6 to ns
+#ns1b	iperf3 -s1J -p 5221 | jq -rM ".end.sum_received.bits_per_second" >t1
+#hostb	iperf3 -c __ADDR6__ -p 5221 __OPTS__
+#ns1out	BW cat t1
+#bw	__BW__ 0.0 0.0
+bw	-
+ns2b	iperf3 -s1J -p 5223 | jq -rM ".end.sum_received.bits_per_second" >t1
+hostb	iperf3 -c __ADDR6__ -p 5223 __OPTS__
+sleep	12
+ns2b	
+ns2out	BW cat t1
+bw	__BW__ 0.0 0.0
+hostb	
+
+tl	  RR latency
+#ns1b	./tcp_rr -6 --nolog -C 5221 -P 5222
+#sleep	2
+#hout	LAT tcp_rr --nolog -c -H __ADDR6__ -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+#lat	__LAT__ 100000 100000
+lat	-
+ns2b	./tcp_rr -6 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT tcp_rr --nolog -c -H __ADDR6__ -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	  CRR latency
+#ns1b	./tcp_crr -6 --nolog -C 5221 -P 5222
+#sleep	2
+#hout	LAT tcp_crr --nolog -c -H __ADDR6__ -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+#lat	__LAT__ 100000 100000
+lat	-
+ns2b	./tcp_crr -6 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT tcp_crr --nolog -c -H __ADDR6__ -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	TCP/IPv4 to ns
+ns1b	iperf3 -s1J -p 5221 | jq -rM ".end.sum_received.bits_per_second" >t1
+hostb	iperf3 -c __ADDR4__ -p 5221 __OPTS__
+sleep	12
+ns1b	
+ns1out	BW cat t1
+bw	__BW__ 0.0 0.0
+ns2b	iperf3 -s1J -p 5223 | jq -rM ".end.sum_received.bits_per_second" >t1
+hostb	iperf3 -c __ADDR4__ -p 5223 __OPTS__
+sleep	12
+ns2b	
+ns2out	BW cat t1
+bw	__BW__ 0.0 0.0
+hostb	
+
+tl	  RR latency
+ns1b	./tcp_rr -4 --nolog -C 5221 -P 5222
+sleep	2
+hout	LAT tcp_rr --nolog -c -H __ADDR4__ -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+ns2b	./tcp_rr -4 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT tcp_rr --nolog -c -H __ADDR4__ -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	  CRR latency
+ns1b	./tcp_crr -4 --nolog -C 5221 -P 5222
+sleep	2
+hout	LAT tcp_crr --nolog -c -H __ADDR4__ -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+ns2b	./tcp_crr -4 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT tcp_crr --nolog -c -H __ADDR4__ -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tr	TCP/IPv6 to host
+hostb	iperf3 -s1J -p 5211 | jq -rM ".end.sum_received.bits_per_second" >t1
+ns1b	iperf3 -c fd00::2 -p 5211 __OPTS__
+sleep	12
+hostb	
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+hostb	iperf3 -s1J -p 5213 | jq -rM ".end.sum_received.bits_per_second" >t1
+ns2b	iperf3 -c __GW6__%__IFNAME__ -p 5213 __OPTS__
+sleep	12
+hostb	
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+ns1b	
+ns2b	
+
+tl	  RR latency
+hostb	tcp_rr -6 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./tcp_rr --nolog -c -H fd00::2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	tcp_rr -6 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./tcp_rr --nolog -c -H __GW6__%__IFNAME__ -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	  CRR latency
+hostb	tcp_crr -6 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./tcp_crr --nolog -c -H fd00::2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	tcp_crr -6 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./tcp_crr --nolog -c -H __GW6__%__IFNAME__ -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	TCP/IPv4 to host
+hostb	iperf3 -s1J -p 5211 | jq -rM ".end.sum_received.bits_per_second" >t1
+ns1b	iperf3 -c 10.0.2.2 -p 5211 __OPTS__
+sleep	12
+hostb	
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+hostb	iperf3 -s1J -p 5213 | jq -rM ".end.sum_received.bits_per_second" >t1
+ns2b	iperf3 -c __GW4__ -p 5213 __OPTS__
+sleep	10
+hostb	
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+ns1b	
+ns2b	
+
+tl	  RR latency
+hostb	tcp_rr -4 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./tcp_rr --nolog -c -H 10.0.2.2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	tcp_rr -4 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./tcp_rr --nolog -c -H __GW4__ -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	  CRR latency
+hostb	tcp_crr -4 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./tcp_crr --nolog -c -H 10.0.2.2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	tcp_crr -4 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./tcp_crr --nolog -c -H __GW4__ -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+sleep	5
+
+
+tr	UDP/IPv6 to ns
+#ns1b	iperf3 -s1J -p 5221 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+#hostb	iperf3 -u -c __ADDR6__ -p 5221 -t5 -b 35G
+#sleep	10
+#ns1out	BW cat t1
+#bw	__BW__ 0.0 0.0
+bw	-
+ns2b	iperf3 -s1J -p 5224 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+hostb	iperf3 -u -c __ADDR6__ -p 5224 -t5 -b 35G
+sleep	10
+ns2out	BW cat t1
+bw	__BW__ 0.0 0.0
+
+tl	  RR latency
+#ns1b	./udp_rr -6 --nolog -C 5221 -P 5222
+#sleep	2
+#hout	LAT udp_rr --nolog -c -H __ADDR6__ -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+#lat	__LAT__ 100000 100000
+lat	-
+ns2b	./udp_rr -6 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT udp_rr --nolog -c -H __ADDR6__ -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	UDP/IPv4 to ns
+ns1b	iperf3 -s1J -p 5221 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+hostb	iperf3 -u -c __ADDR4__ -p 5221 -t5 -b 35G
+sleep	10
+ns1out	BW cat t1
+bw	__BW__ 0.0 0.0
+ns2b	iperf3 -s1J -p 5224 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+hostb	iperf3 -u -c __ADDR4__ -p 5224 -t5 -b 35G
+sleep	10
+ns2out	BW cat t1
+bw	__BW__ 0.0 0.0
+
+tl	  RR latency
+ns1b	./udp_rr -6 --nolog -C 5221 -P 5222
+sleep	2
+hout	LAT udp_rr --nolog -c -H __ADDR4__ -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+ns2b	./udp_rr -6 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT udp_rr --nolog -c -H __ADDR4__ -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tr	UDP/IPv6 to host
+hostb	iperf3 -s1J -p 5211 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+ns1b	iperf3 -u -c fd00::2 -p 5211 -t5 -b 35G
+sleep	10
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+hostb	iperf3 -s1J -p 5214 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+ns2b	iperf3 -u -c __GW6__%__IFNAME__ -p 5214 -t5 -b 35G
+sleep	10
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+
+tl	  RR latency
+hostb	udp_rr -6 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./udp_rr --nolog -c -H fd00::2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	udp_rr -6 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./udp_rr --nolog -c -H __GW6__%__IFNAME__ -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	UDP/IPv4 to host
+hostb	iperf3 -s1J -p 5211 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+ns1b	iperf3 -u -c 10.0.2.2 -p 5211 -t5 -b 35G
+sleep	10
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+hostb	iperf3 -s1J -p 5214 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+ns2b	iperf3 -u -c __GW4__ -p 5214 -t5 -b 35G
+sleep	10
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+
+tl	  RR latency
+hostb	udp_rr -6 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./udp_rr --nolog -c -H 10.0.2.2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	udp_rr -6 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./udp_rr --nolog -c -H __GW4__ -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+
+ns1	exit
+ns1	podman run --rm --net=slirp4netns:allow_host_loopback=true,enable_ipv6=true -p 5221-5222:5221-5222/tcp -p 5221-5222:5221-5222/udp -ti alpine sh
+ns1	apk add iperf3 jq bc
+ns1	wget lameexcu.se/tcp_rr; chmod 755 tcp_rr
+ns1	wget lameexcu.se/tcp_crr; chmod 755 tcp_crr
+ns1	wget lameexcu.se/udp_rr; chmod 755 udp_rr
+info	
+info	
+info	  loopback (lo) connections
+th	mode rootlesskit pasta
+
+
+tr	TCP/IPv6 to ns
+ns1b	(iperf3 -s1J -p 5221 | jq -rM ".end.sum_received.bits_per_second" >t1) &
+ns1b	iperf3 -s1J -p 5222 | jq -rM ".end.sum_received.bits_per_second" >t2
+hostb	iperf3 -c ::1 -p 5221 __OPTS__ & iperf3 -c ::1 -p 5222 __OPTS__
+sleep	12
+ns1b	
+ns1out	BW echo "$(cat t1) + $(cat t2)" | bc -l
+bw	__BW__ 0.0 0.0
+ns2b	(iperf3 -s1J -p 5223 | jq -rM ".end.sum_received.bits_per_second" >t1) &
+ns2b	iperf3 -s1J -p 5224 | jq -rM ".end.sum_received.bits_per_second" >t2
+hostb	iperf3 -c ::1 -p 5223 __OPTS__ & iperf3 -c ::1 -p 5224 __OPTS__
+sleep	12
+ns2b	
+ns2out	BW echo "$(cat t1) + $(cat t2)" | bc -l
+bw	__BW__ 0.0 0.0
+hostb	
+
+tl	  RR latency
+ns1b	./tcp_rr -6 --nolog -C 5221 -P 5222
+sleep	2
+hout	LAT tcp_rr --nolog -c -H ::1 -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+ns2b	./tcp_rr -6 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT tcp_rr --nolog -c -H ::1 -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	  CRR latency
+ns1b	./tcp_crr -6 --nolog -C 5221 -P 5222
+sleep	2
+hout	LAT tcp_crr --nolog -c -H ::1 -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+ns2b	./tcp_crr -6 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT tcp_crr --nolog -c -H ::1 -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	TCP/IPv4 to ns
+ns1b	(iperf3 -s1J -p 5221 | jq -rM ".end.sum_received.bits_per_second" >t1) &
+ns1b	iperf3 -s1J -p 5222 | jq -rM ".end.sum_received.bits_per_second" >t2
+hostb	iperf3 -c 127.0.0.1 -p 5221 __OPTS__ & iperf3 -c 127.0.0.1 -p 5222 __OPTS__
+sleep	12
+ns1b	
+ns1out	BW echo "$(cat t1) + $(cat t2)" | bc -l
+bw	__BW__ 0.0 0.0
+ns2b	(iperf3 -s1J -p 5223 | jq -rM ".end.sum_received.bits_per_second" >t1) &
+ns2b	iperf3 -s1J -p 5224 | jq -rM ".end.sum_received.bits_per_second" >t2
+hostb	iperf3 -c 127.0.0.1 -p 5223 __OPTS__ & iperf3 -c 127.0.0.1 -p 5224 __OPTS__
+sleep	12
+ns2b	
+ns2out	BW echo "$(cat t1) + $(cat t2)" | bc -l
+bw	__BW__ 0.0 0.0
+hostb	
+
+tl	  RR latency
+ns1b	./tcp_rr -4 --nolog -C 5221 -P 5222
+sleep	2
+hout	LAT tcp_rr --nolog -c -H 127.0.0.1 -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+ns2b	./tcp_rr -4 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT tcp_rr --nolog -c -H 127.0.0.1 -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	  CRR latency
+ns1b	./tcp_crr -4 --nolog -C 5221 -P 5222
+sleep	2
+hout	LAT tcp_crr --nolog -c -H 127.0.0.1 -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+ns2b	./tcp_crr -4 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT tcp_crr --nolog -c -H 127.0.0.1 -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tr	TCP/IPv6 to host
+hostb	(iperf3 -s1J -p 5211 | jq -rM ".end.sum_received.bits_per_second" >t1) &
+hostb	iperf3 -s1J -p 5212 | jq -rM ".end.sum_received.bits_per_second" >t2
+ns1b	iperf3 -c fd00::2 -p 5211 __OPTS__ & iperf3 -c fd00::2 -p 5212 __OPTS__
+sleep	12
+hostb	
+hout	BW echo "$(cat t1) + $(cat t2)" | bc -l
+bw	__BW__ 0.0 0.0
+hostb	(iperf3 -s1J -p 5213 | jq -rM ".end.sum_received.bits_per_second" >t1) &
+hostb	iperf3 -s1J -p 5214 | jq -rM ".end.sum_received.bits_per_second" >t2
+ns2b	iperf3 -c ::1 -p 5213 __OPTS__ & iperf3 -c ::1 -p 5214 __OPTS__
+sleep	12
+hostb	
+hout	BW echo "$(cat t1) + $(cat t2)" | bc -l
+bw	__BW__ 0.0 0.0
+ns1b	
+ns2b	
+
+tl	  RR latency
+hostb	tcp_rr -6 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./tcp_rr --nolog -c -H fd00::2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	tcp_rr -6 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./tcp_rr --nolog -c -H ::1 -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	  CRR latency
+hostb	tcp_crr -6 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./tcp_crr --nolog -c -H fd00::2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	tcp_crr -6 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./tcp_crr --nolog -c -H ::1 -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	TCP/IPv4 to host
+hostb	(iperf3 -s1J -p 5211 | jq -rM ".end.sum_received.bits_per_second" >t1) &
+hostb	iperf3 -s1J -p 5212 | jq -rM ".end.sum_received.bits_per_second" >t2
+ns1b	iperf3 -c 10.0.2.2 -p 5211 __OPTS__ & iperf3 -c 10.0.2.2 -p 5212 __OPTS__
+sleep	12
+hostb	
+hout	BW echo "$(cat t1) + $(cat t2)" | bc -l
+bw	__BW__ 0.0 0.0
+hostb	(iperf3 -s1J -p 5213 | jq -rM ".end.sum_received.bits_per_second" >t1) &
+hostb	iperf3 -s1J -p 5214 | jq -rM ".end.sum_received.bits_per_second" >t2
+ns2b	iperf3 -c 127.0.0.1 -p 5213 __OPTS__ & iperf3 -c 127.0.0.1 -p 5214 __OPTS__
+sleep	12
+hostb	
+hout	BW echo "$(cat t1) + $(cat t2)" | bc -l
+bw	__BW__ 0.0 0.0
+ns1b	
+ns2b	
+
+tl	  RR latency
+hostb	tcp_rr -4 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./tcp_rr --nolog -c -H 10.0.2.2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	tcp_rr -4 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./tcp_rr --nolog -c -H 127.0.0.1 -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	  CRR latency
+hostb	tcp_crr -4 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./tcp_crr --nolog -c -H 10.0.2.2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	tcp_crr -4 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./tcp_crr --nolog -c -H 127.0.0.1 -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+sleep	5
+
+
+tr	UDP/IPv6 to ns
+ns1b	iperf3 -s1J -p 5221 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+hostb	iperf3 -u -c ::1 -p 5221 -t5 -b 35G
+sleep	10
+ns1out	BW cat t1
+bw	__BW__ 0.0 0.0
+ns2b	iperf3 -s1J -p 5224 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+hostb	iperf3 -u -c ::1 -p 5224 -t5 -b 35G
+sleep	10
+ns2out	BW cat t1
+bw	__BW__ 0.0 0.0
+
+tl	  RR latency
+ns1b	./udp_rr -6 --nolog -C 5221 -P 5222
+sleep	2
+hout	LAT udp_rr --nolog -c -H ::1 -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+ns2b	./udp_rr -6 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT udp_rr --nolog -c -H ::1 -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	UDP/IPv4 to ns
+ns1b	iperf3 -s1J -p 5221 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+hostb	iperf3 -u -c 127.0.0.1 -p 5221 -t5 -b 35G
+sleep	10
+ns1out	BW cat t1
+bw	__BW__ 0.0 0.0
+ns2b	iperf3 -s1J -p 5224 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+hostb	iperf3 -u -c 127.0.0.1 -p 5224 -t5 -b 35G
+sleep	10
+ns2out	BW cat t1
+bw	__BW__ 0.0 0.0
+
+tl	  RR latency
+ns1b	./udp_rr -6 --nolog -C 5221 -P 5222
+sleep	2
+hout	LAT udp_rr --nolog -c -H 127.0.0.1 -C 5221 -P 5222 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+ns2b	./udp_rr -6 --nolog -C 5223 -P 5224
+sleep	2
+hout	LAT udp_rr --nolog -c -H 127.0.0.1 -C 5223 -P 5224 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tr	UDP/IPv6 to host
+hostb	iperf3 -s1J -p 5211 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+ns1b	iperf3 -u -c fd00::2 -p 5211 -t5 -b 35G
+sleep	10
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+hostb	iperf3 -s1J -p 5214 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+ns2b	iperf3 -u -c ::1 -p 5214 -t5 -b 35G
+sleep	10
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+
+tl	  RR latency
+hostb	udp_rr -6 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./udp_rr --nolog -c -H fd00::2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	udp_rr -6 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./udp_rr --nolog -c -H ::1 -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+tl	UDP/IPv4 to host
+hostb	iperf3 -s1J -p 5211 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+ns1b	iperf3 -u -c 10.0.2.2 -p 5211 -t5 -b 35G
+sleep	10
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+hostb	iperf3 -s1J -p 5214 | jq -rM ".intervals[0].sum.bits_per_second" >t1
+ns2b	iperf3 -u -c 127.0.0.1 -p 5214 -t5 -b 35G
+sleep	10
+hout	BW cat t1
+bw	__BW__ 0.0 0.0
+
+tl	  RR latency
+hostb	udp_rr -6 --nolog -C 5211 -P 5212
+sleep	2
+ns1out	LAT ./udp_rr --nolog -c -H 10.0.2.2 -C 5211 -P 5212 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+hostb	udp_rr -6 --nolog -C 5213 -P 5214
+sleep	2
+ns2out	LAT ./udp_rr --nolog -c -H 127.0.0.1 -C 5213 -P 5214 -l 5 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 100000 100000
+
+
+nl
+nl
+say	Thanks for watching!
+sleep	15
diff --git a/oldtest/distro/debian b/oldtest/distro/debian
new file mode 100644
index 00000000..42914f8a
--- /dev/null
+++ b/oldtest/distro/debian
@@ -0,0 +1,252 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/distro/debian - Debian builds, get packages via passt, test pasta
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+set	PIDFILE __STATEDIR__/passt.pid
+htools	cat kill qemu-system-x86_64 qemu-system-aarch64 qemu-system-ppc64
+
+# Quick pasta test: send message from init to ns, and from ns to init
+def	distro_quick_pasta_test
+host	export SHELL="/bin/dash"
+host	dash
+host	(socat -u TCP6-LISTEN:10000 OPEN:/tmp/init_msg,create,trunc; echo "from_init" | socat -u STDIN TCP6:[::1]:9999) &
+hostb	./pasta
+sleep	1
+host	socat -u TCP6-LISTEN:9999 OPEN:/tmp/ns_msg,create,trunc &
+sleep	2
+host	echo "from_ns" | socat -u STDIN TCP6:[::1]:10000
+sleep	2
+host	echo
+sleep	1
+hout	NS_MSG cat /tmp/ns_msg
+check	[ __NS_MSG__ = "from_init" ]
+hostb	exit
+host	echo
+hout	INIT_MSG cat /tmp/init_msg
+check	[ __INIT_MSG__ = "from_ns" ]
+endef
+
+# Start passt, set common variables
+hostb	./passt -s __STATEDIR__/passt.socket -P __PIDFILE__ &
+sleep	1
+host	echo
+
+
+test	Debian GNU/Linux 8 (jessie), amd64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none -drive file=__BASEPATH__/prepared-debian-8.11.0-openstack-amd64.qcow2,if=virtio -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Wno-missing-field-initializers -Wno-missing-braces -Wno-type-limits" make
+
+# TODO: pasta test skipped for the moment: clone() as called by NS_CALL hangs
+# with wrapper provided by glibc 2.19, probably wrong argument order.
+
+hint
+sleep	1
+
+# PIDFILE is cleaned up when the next test starts, read it now
+hout	PID cat __PIDFILE__
+
+
+test	Debian GNU/Linux 9 (stretch, oldoldstable), amd64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-9-nocloud-amd64-daily-20200210-166.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+host	sysctl -w kernel.unprivileged_userns_clone=1
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Debian GNU/Linux 10 (buster, oldstable), amd64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-10-nocloud-amd64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+host	sysctl -w kernel.unprivileged_userns_clone=1
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Debian GNU/Linux 10 (buster, oldstable), aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-10-generic-arm64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+host	sysctl -w kernel.unprivileged_userns_clone=1
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Debian GNU/Linux 10 (buster, oldstable), ppc64le
+
+host	qemu-system-ppc64 -m 2048 -smp 2 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-10-generic-ppc64el-20220911-1135.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+host	sysctl -w kernel.unprivileged_userns_clone=1
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+
+
+test	Debian GNU/Linux 11 (bullseye, stable), amd64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-11-nocloud-amd64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Debian GNU/Linux 11 (bullseye, stable), aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-11-generic-arm64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Debian GNU/Linux 11 (bullseye, stable), ppc64le
+
+host	qemu-system-ppc64 -m 2048 -smp 2 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-11-generic-ppc64el.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+
+
+# HACK: We need some additional space to install gcc-12 on 'sid' images for
+# amd64 and aarch64, but if we use virt-resize to call resize2fs in the
+# preparation step, partitions will be rearranged and we would also need to
+# adjust boot parameters. Instead, resize the images offline first, and expand
+# partitions and filesystems online, later.
+
+test	Debian GNU/Linux sid (experimental), amd64
+
+host	qemu-img resize __BASEPATH__/prepared-debian-sid-nocloud-amd64-daily.qcow2 4G
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-sid-nocloud-amd64-daily.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+sleep	2
+host	growpart /dev/sda 1
+host	resize2fs -p /dev/sda1
+host	export DEBIAN_FRONTEND=noninteractive
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Debian GNU/Linux sid (experimental), aarch64
+
+host	qemu-img resize __BASEPATH__/prepared-debian-sid-nocloud-arm64-daily.qcow2 4G
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-sid-nocloud-arm64-daily.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+sleep	2
+host	growpart /dev/vda 1
+host	resize2fs -p /dev/vda1
+host	export DEBIAN_FRONTEND=noninteractive
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Debian GNU/Linux sid (experimental), ppc64le
+
+host	qemu-system-ppc64 -m 2048 -smp 2 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-debian-sid-nocloud-ppc64el-daily.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+sleep	2
+host	export DEBIAN_FRONTEND=noninteractive
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+
+
+sleep	1
+host	kill __PID__
diff --git a/oldtest/distro/fedora b/oldtest/distro/fedora
new file mode 100644
index 00000000..331f90b7
--- /dev/null
+++ b/oldtest/distro/fedora
@@ -0,0 +1,396 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/distro/fedora - Fedora builds, get packages via passt, test pasta
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+set	PIDFILE __STATEDIR__/passt.pid
+htools	cat kill qemu-system-x86_64
+
+# Quick pasta test: send message from init to ns, and from ns to init
+def	distro_quick_pasta_test
+host	(socat -u TCP6-LISTEN:10000,bind=[::1] OPEN:/tmp/init_msg,create,trunc; echo "from_init" | socat -u STDIN TCP6:[::1]:9999) &
+hostb	./pasta
+sleep	1
+host	PS1='$ '
+host	socat -u TCP6-LISTEN:9999,bind=[::1] OPEN:/tmp/ns_msg,create,trunc &
+sleep	2
+host	echo "from_ns" | socat -u STDIN TCP6:[::1]:10000
+sleep	2
+host	echo
+sleep	1
+hout	NS_MSG cat /tmp/ns_msg
+check	[ __NS_MSG__ = "from_init" ]
+hostb	exit
+host	echo
+hout	INIT_MSG cat /tmp/init_msg
+check	[ __INIT_MSG__ = "from_ns" ]
+endef
+
+# Bracketed paste mode off, needed from Fedora 34
+def	distro_quick_pasta_test_fedora34
+host	bind 'set enable-bracketed-paste off'
+host	(socat -u TCP6-LISTEN:10000,bind=[::1] OPEN:/tmp/init_msg,create,trunc; echo "from_init" | socat -u STDIN TCP6:[::1]:9999) &
+hostb	./pasta
+sleep	1
+host	PS1='$ '
+host	bind 'set enable-bracketed-paste off'
+host	socat -u TCP6-LISTEN:9999,bind=[::1] OPEN:/tmp/ns_msg,create,trunc &
+sleep	2
+host	echo "from_ns" | socat -u STDIN TCP6:[::1]:10000
+sleep	2
+host	echo
+hout	NS_MSG cat /tmp/ns_msg
+check	[ __NS_MSG__ = "from_init" ]
+hostb	exit
+host	echo
+hout	INIT_MSG cat /tmp/init_msg
+check	[ __INIT_MSG__ = "from_ns" ]
+endef
+
+# Start passt, set common variables
+hostb	./passt -s __STATEDIR__/passt.socket -P __PIDFILE__ &
+sleep	1
+host	echo
+
+test	Fedora 26, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-26-1.5.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+# PIDFILE is cleaned up when the next test starts, read it now
+hout	PID cat __PIDFILE__
+
+
+test	Fedora 27, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-27-1.6.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Fedora 28, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-28-1.1.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Fedora 28, aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nodefaults -nographic -vga none -serial stdio __BASEPATH__/prepared-Fedora-Cloud-Base-28-1.1.aarch64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -device virtio-rng-pci -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+test	Fedora 29, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-29-1.2.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Fedora 29, aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nodefaults -nographic -vga none -serial stdio __BASEPATH__/prepared-Fedora-Cloud-Base-29-1.2.aarch64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -device virtio-rng-pci -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+test	Fedora 30, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-30-1.2.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Fedora 30, aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nodefaults -nographic -vga none -serial stdio __BASEPATH__/prepared-Fedora-Cloud-Base-30-1.2.aarch64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -device virtio-rng-pci -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+test	Fedora 31, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-31-1.9.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Fedora 31, aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nodefaults -nographic -vga none -serial stdio __BASEPATH__/prepared-Fedora-Cloud-Base-31-1.9.aarch64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -device virtio-rng-pci -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+test	Fedora 32, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-32-1.6.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Fedora 32, aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nodefaults -nographic -vga none -serial stdio __BASEPATH__/prepared-Fedora-Cloud-Base-32-1.6.aarch64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -device virtio-rng-pci -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+test	Fedora 33, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-33-1.2.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	Fedora 33, aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nodefaults -nographic -vga none -serial stdio __BASEPATH__/prepared-Fedora-Cloud-Base-33-1.2.aarch64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -device virtio-rng-pci -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+test	Fedora 34, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-34-1.2.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test_fedora34
+
+hint
+sleep	1
+
+
+test	Fedora 34, aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nodefaults -nographic -vga none -serial stdio __BASEPATH__/prepared-Fedora-Cloud-Base-34-1.2.aarch64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -device virtio-rng-pci -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test_fedora34
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+test	Fedora 35, x86_64
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __BASEPATH__/prepared-Fedora-Cloud-Base-35-1.2.x86_64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test_fedora34
+
+hint
+sleep	1
+
+
+test	Fedora 35, aarch64
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nodefaults -nographic -vga none -serial stdio __BASEPATH__/prepared-Fedora-Cloud-Base-35-1.2.aarch64.qcow2 -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -device virtio-rng-pci -snapshot
+host	PS1='$ '
+sleep	2
+host	yum -y install make gcc socat
+
+host	make clean
+hout	RET CFLAGS="-Werror" make; echo $?
+check	[ __RET__ -eq 0 ]
+
+distro_quick_pasta_test_fedora34
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+host	kill __PID__
diff --git a/oldtest/distro/opensuse b/oldtest/distro/opensuse
new file mode 100644
index 00000000..eab722b4
--- /dev/null
+++ b/oldtest/distro/opensuse
@@ -0,0 +1,208 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/distro/opensuse - OpenSUSE builds, get packages via passt, test pasta
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+set	PIDFILE __STATEDIR__/passt.pid
+htools	qemu-img virt-edit guestfish head sed cat kill qemu-system-x86_64 qemu-system-aarch64 xzcat tr
+
+# Quick pasta test: send message from init to ns, and from ns to init
+def	distro_quick_pasta_test
+host	(socat -u TCP6-LISTEN:10000 OPEN:/tmp/init_msg,create,trunc; echo "from_init" | socat -u STDIN TCP6:[::1]:9999) &
+hostb	./pasta
+sleep	1
+host	PS1='$ '
+host	socat -u TCP6-LISTEN:9999 OPEN:/tmp/ns_msg,create,trunc &
+sleep	2
+host	echo "from_ns" | socat -u STDIN TCP6:[::1]:10000
+sleep	2
+host	echo
+sleep	1
+hout	NS_MSG cat /tmp/ns_msg
+check	[ __NS_MSG__ = "from_init" ]
+hostb	exit
+host	echo
+hout	INIT_MSG cat /tmp/init_msg
+check	[ __INIT_MSG__ = "from_ns" ]
+endef
+
+# Start passt, set common variables
+hostb	./passt -s __STATEDIR__/passt.socket -P __PIDFILE__ &
+sleep	1
+host	echo
+hout	DNS6 sed -n 's/^nameserver \([^:]*:\)\([^%]*\).*/\1\2/p' /etc/resolv.conf | head -1
+hout	GUEST_FILES ls -1 *.c *.h *.sh passt.1 qrap.1 Makefile README.md | tr '\n' ' '; echo
+
+
+test	OpenSUSE Leap 15.1
+
+set	IMG __STATEDIR__/opensuse-15.1-x86_64.img
+host	qemu-img create -f qcow2 -F qcow2 -b __BASEPATH__/openSUSE-Leap-15.1-JeOS.x86_64-kvm-and-xen.qcow2 __IMG__
+host	guestfish --rw -a __IMG__ -i rm '/usr/lib/systemd/system/systemd-journald.service'
+host	guestfish --rw -a __IMG__ -i rm /etc/systemd/system/default.target.wants/jeos-firstboot.service
+host	virt-edit -a __IMG__ /etc/systemd/system/getty.target.wants/getty@tty1.service -e 's/ExecStart=.*/ExecStart=\/sbin\/agetty --timeout 5000 --autologin root -i -8 --keep-baud 115200,38400,9600 ttyS0 $TERM/g'
+host	guestfish --rw -a __IMG__ -i copy-in __GUEST_FILES__ /root/
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __IMG__ -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket
+host	PS1='$ '
+host	ip link set eth0 up
+sleep	2
+host	echo "DNSSERVERS='__DNS6__'" | netconfig modify -s dns_resolver -i eth0
+# zypper sometimes segfaults, hence the retries
+host	for i in $(seq 1 10); do zypper install -y gcc make socat && break; done; echo
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+# PIDFILE is cleaned up when the next test starts, read it now
+hout	PID cat __PIDFILE__
+
+
+test	OpenSUSE Leap 15.2
+
+set	IMG __STATEDIR__/opensuse-15.2-x86_64.img
+host	qemu-img create -f qcow2 -F qcow2 -b __BASEPATH__/openSUSE-Leap-15.2-JeOS.x86_64-kvm-and-xen.qcow2 __IMG__
+host	guestfish --rw -a __IMG__ -i rm '/usr/lib/systemd/system/systemd-journald.service'
+host	guestfish --rw -a __IMG__ -i rm /etc/systemd/system/default.target.wants/jeos-firstboot.service
+host	virt-edit -a __IMG__ /etc/systemd/system/getty.target.wants/getty@tty1.service -e 's/ExecStart=.*/ExecStart=\/sbin\/agetty --timeout 5000 --autologin root -i -8 --keep-baud 115200,38400,9600 ttyS0 $TERM/g'
+host	guestfish --rw -a __IMG__ -i copy-in __GUEST_FILES__ /root/
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __IMG__ -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket
+host	PS1='$ '
+host	ip link set eth0 up
+sleep	2
+host	echo "DNSSERVERS='__DNS6__'" | netconfig modify -s dns_resolver -i eth0
+# zypper sometimes segfaults, hence the retries
+host	for i in $(seq 1 10); do zypper install -y gcc make socat && break; done; echo
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	OpenSUSE Leap 15.3
+
+set	IMG __STATEDIR__/opensuse-15.3-x86_64.img
+host	qemu-img create -f qcow2 -F qcow2 -b __BASEPATH__/openSUSE-Leap-15.3-JeOS.x86_64-kvm-and-xen.qcow2 __IMG__
+host	guestfish --rw -a __IMG__ -i rm '/usr/lib/systemd/system/systemd-journald.service'
+host	guestfish --rw -a __IMG__ -i rm /etc/systemd/system/default.target.wants/jeos-firstboot.service
+host	virt-edit -a __IMG__ /etc/systemd/system/getty.target.wants/getty@tty1.service -e 's/ExecStart=.*/ExecStart=\/sbin\/agetty --timeout 5000 --autologin root -i -8 --keep-baud 115200,38400,9600 ttyS0 $TERM/g'
+host	guestfish --rw -a __IMG__ -i copy-in __GUEST_FILES__ /root/
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __IMG__ -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket
+# Multiple prompt logins might come up here
+sleep	10
+host	PS1='$ '
+host	ip link set eth0 up
+sleep	2
+host	echo "DNSSERVERS='__DNS6__'" | netconfig modify -s dns_resolver -i eth0
+# zypper sometimes segfaults, hence the retries
+host	for i in $(seq 1 10); do zypper install -y gcc make socat && break; done; echo
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	OpenSUSE Tumbleweed aarch64
+
+set	IMG __STATEDIR__/opensuse-tumbleweed-aarch64.img
+host	xzcat __BASEPATH__/openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz > __IMG__
+host	virt-edit -a __IMG__ -m /dev/sda3 /usr/lib/systemd/system/serial-getty@.service -e 's/ExecStart=.*/ExecStart=\/sbin\/agetty --timeout 5000 --autologin root -i -8 --keep-baud 115200,38400,9600 %I $TERM/g'
+host	guestfish --rw -a __IMG__ -i copy-in __GUEST_FILES__ /root/
+
+host	qemu-system-aarch64 -m 2048 -cpu cortex-a57 -smp 2 -M virt -bios __BASEPATH__/QEMU_EFI.fd -nodefaults -nographic -vga none -serial stdio __IMG__ -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket
+host	PS1='$ '
+host	ip link set enp0s1 up
+sleep	10
+host	echo "DNSSERVERS='__DNS6__'" | netconfig modify -s dns_resolver -i enp0s1
+sleep	10
+# No segfaults ever seen with this
+host	zypper install -y gcc make socat; echo
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	OpenSUSE Tumbleweed armv7l
+
+set	IMG __STATEDIR__/opensuse-tumbleweed-armv7l.img
+set	ZIMAGE __STATEDIR__/opensuse-tumbleweed-armv7l.zimage
+set	INITRD __STATEDIR__/opensuse-tumbleweed-armv7l.initrd
+host	xzcat __BASEPATH__/openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz > __IMG__
+host	guestfish -a __IMG__ -i download /boot/zImage __ZIMAGE__
+host	guestfish -a __IMG__ -i download /boot/initrd __INITRD__
+host	virt-edit -a __IMG__ -m /dev/sda3 /usr/lib/systemd/system/serial-getty@.service -e 's/ExecStart=.*/ExecStart=\/sbin\/agetty --timeout 5000 --autologin root -i -8 --keep-baud 115200,38400,9600 %I $TERM/g'
+host	guestfish --rw -a __IMG__ -i copy-in __GUEST_FILES__ /root/
+
+host	qemu-system-arm -M virt -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none -kernel __ZIMAGE__ -initrd __INITRD__ -append 'root=/dev/sda3' -drive if=none,file=__IMG__,format=raw,id=hd,media=disk -device virtio-scsi-device -device scsi-hd,drive=hd -netdev socket,fd=5,id=passt -device virtio-net-device,netdev=passt
+host	PS1='$ '
+host	ip link set eth0 up
+sleep	10
+host	echo "DNSSERVERS='__DNS6__'" | netconfig modify -s dns_resolver -i eth0
+sleep	10
+host	zypper install -y gcc make socat; echo
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+test	OpenSUSE Tumbleweed
+
+set	IMG __STATEDIR__/opensuse-tumbleweed-x86_64.img
+host	qemu-img create -f qcow2 -F qcow2 -b __BASEPATH__/openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2 __IMG__
+host	guestfish --rw -a __IMG__ -i rm /usr/lib/systemd/system/systemd-journald.service
+host	guestfish --rw -a __IMG__ -i rm /etc/systemd/system/default.target.wants/jeos-firstboot.service
+host	guestfish --rw -a __IMG__ -i rm /usr/lib/systemd/system/serial-getty@.service
+host	virt-edit -a __IMG__ /etc/systemd/system/getty.target.wants/getty@tty1.service -e 's/ExecStart=.*/ExecStart=\/sbin\/agetty --timeout 5000 --autologin root -i -8 --keep-baud 115200,38400,9600 ttyS0 $TERM/g'
+host	guestfish --rw -a __IMG__ -i copy-in __GUEST_FILES__ /root/
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __IMG__ -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket
+host	PS1='$ '
+host	ip link set ens2 up
+sleep	2
+host	echo "DNSSERVERS='__DNS6__'" | netconfig modify -s dns_resolver -i ens2
+# zypper sometimes segfaults, hence the retries
+host	for i in $(seq 1 10); do zypper install -y gcc make socat && break; done; echo
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test
+
+hint
+sleep	1
+
+
+host	kill __PID__
diff --git a/oldtest/distro/ubuntu b/oldtest/distro/ubuntu
new file mode 100644
index 00000000..87504552
--- /dev/null
+++ b/oldtest/distro/ubuntu
@@ -0,0 +1,216 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/distro/ubuntu - Ubuntu builds, get packages via passt, test pasta
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+set	PIDFILE __STATEDIR__/passt.pid
+htools	qemu-img virt-edit guestfish cat kill qemu-system-x86_64 qemu-system-ppc64 qemu-system-s390x
+
+# Quick pasta test: send message from init to ns, and from ns to init
+def	distro_quick_pasta_test
+host	(socat -u TCP6-LISTEN:10000 OPEN:/tmp/init_msg,create,trunc; echo "from_init" | socat -u STDIN TCP6:[::1]:9999) &
+hostb	./pasta
+sleep	1
+host	PS1='$ '
+host	socat -u TCP6-LISTEN:9999 OPEN:/tmp/ns_msg,create,trunc &
+sleep	2
+host	echo "from_ns" | socat -u STDIN TCP6:[::1]:10000
+sleep	2
+host	echo
+sleep	1
+hout	NS_MSG cat /tmp/ns_msg
+check	[ __NS_MSG__ = "from_init" ]
+hostb	exit
+host	echo
+hout	INIT_MSG cat /tmp/init_msg
+check	[ __INIT_MSG__ = "from_ns" ]
+endef
+
+# Quick pasta test: netcat-openbsd version for Ubuntu 16.04 ppc64
+def	distro_quick_pasta_test_netcat
+host	(nc -w1 -6 -l -p 10000 > /tmp/init_msg; echo "from_init" | nc -q0 ::1 9999) &
+hostb	./pasta
+sleep	1
+host	PS1='$ '
+host	nc -w1 -6 -l -p 9999 > /tmp/ns_msg &
+sleep	2
+host	echo "from_ns" | nc -q0 ::1 10000
+sleep	2
+host	echo
+sleep	1
+hout	NS_MSG cat /tmp/ns_msg
+check	[ __NS_MSG__ = "from_init" ]
+hostb	exit
+host	echo
+hout	INIT_MSG cat /tmp/init_msg
+check	[ __INIT_MSG__ = "from_ns" ]
+endef
+
+# With systemd-resolved and TCG, DNS might take a while to work
+def	dns_ready_wait
+host	r=10; while [ \${r} -gt 0 ]; do host ubuntu.com && break; sleep 5; r=\$((r - 1)); done
+endef
+
+# Start passt, set common variables
+hostb	./passt -s __STATEDIR__/passt.socket -P __PIDFILE__ &
+sleep	1
+host	echo
+hout	GUEST_FILES ls -1 *.c *.h *.sh passt.1 qrap.1 Makefile README.md | tr '\n' ' '; echo
+
+
+test	Ubuntu 14.04.5 LTS (Trusty Tahr), amd64
+
+set	IMG __STATEDIR__/ubuntu-14.04-amd64.img
+host	qemu-img create -f qcow2 -F qcow2 -b __BASEPATH__/trusty-server-cloudimg-amd64-disk1.img __IMG__
+host	virt-edit -a __IMG__ /etc/init/ttyS0.conf -e 's/\/getty/\/getty --autologin root/'
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-config.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-final.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init-container.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init-local.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init-nonet.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-log-shutdown.conf
+host	guestfish --rw -a __IMG__ -i copy-in __GUEST_FILES__ /root/
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none -drive file=__IMG__,if=virtio -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket
+host	PS1='$ '
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Wno-missing-field-initializers -Wno-missing-braces -Wno-type-limits" make
+
+# TODO: pasta test skipped for the moment: clone() as called by NS_CALL hangs
+# with wrapper provided by glibc 2.19, probably wrong argument order.
+
+hint
+sleep	1
+
+# PIDFILE is cleaned up when the next test starts, read it now
+hout	PID cat __PIDFILE__
+
+
+test	Ubuntu 14.04.5 LTS (Trusty Tahr), i386
+
+set	IMG __STATEDIR__/ubuntu-14.04-i386.img
+host	qemu-img create -f qcow2 -F qcow2 -b __BASEPATH__/trusty-server-cloudimg-i386-disk1.img __IMG__
+host	virt-edit -a __IMG__ /etc/init/ttyS0.conf -e 's/\/getty/\/getty --autologin root/'
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-config.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-final.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init-container.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init-local.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init-nonet.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-log-shutdown.conf
+host	guestfish --rw -a __IMG__ -i copy-in __GUEST_FILES__ /root/
+
+host	qemu-system-x86_64 -M pc,accel=kvm:tcg -m 1024 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none -drive file=__IMG__,if=virtio -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket
+host	PS1='$ '
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Wno-missing-field-initializers -Wno-missing-braces -Wno-type-limits -Wno-sign-compare" make
+
+# TODO: pasta test skipped for the moment: clone() as called by NS_CALL hangs
+# with wrapper provided by glibc 2.19, probably wrong argument order.
+
+hint
+sleep	1
+
+
+test	Ubuntu 14.04.5 LTS (Trusty Tahr), ppc64le
+
+set	IMG __STATEDIR__/ubuntu-14.04-ppc64le.img
+host	qemu-img create -f qcow2 -F qcow2 -b __BASEPATH__/trusty-server-cloudimg-ppc64el-disk1.img __IMG__
+host	virt-edit -a __IMG__ /etc/init/hvc0.conf -e 's/\/getty/\/getty --autologin root/'
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-config.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-final.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init-container.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init-local.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init-nonet.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-init.conf
+host	guestfish --rw -a __IMG__ -i rm /etc/init/cloud-log-shutdown.conf
+host	guestfish --rw -a __IMG__ -i copy-in __GUEST_FILES__ /root/
+
+host	qemu-system-ppc64 -m 2048 -smp 2 -nographic -serial stdio -nodefaults -no-reboot -nographic -vga none __IMG__ -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket
+host	PS1='$ '
+sleep	2
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Wno-missing-field-initializers -Wno-missing-braces -Wno-type-limits -Wno-sign-compare" make
+
+# TODO: pasta test skipped for the moment: clone() as called by NS_CALL hangs
+# with wrapper provided by glibc 2.19, probably wrong argument order.
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+test	Ubuntu 16.04 LTS (Xenial Xerus), ppc64 (be)
+
+host	qemu-system-ppc64 -m 1024 -M pseries -nographic -nodefaults -serial stdio -no-reboot -nographic -vga none -hda __BASEPATH__/prepared-xenial-server-cloudimg-powerpc-disk1.img -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -snapshot
+host	PS1='$ '
+host	dhclient -4
+# Skip apt-get update here: some updates to xenial-updates around 2022-01-30
+# broke dependencies for libc6 and gcc-5 -- note that powerpc is not officially
+# supported on this version
+
+# socat not available: install netcat-openbsd and run the test with it
+host	apt-get -y install make gcc netcat-openbsd
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+distro_quick_pasta_test_netcat
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+test	Ubuntu 22.04 (Jammy Jellyfish), s390x
+
+host	qemu-system-s390x -m 2048 -smp 2 -serial stdio -nodefaults -nographic __BASEPATH__/prepared-jammy-server-cloudimg-s390x.img -device virtio-net-pci,netdev=s0 -netdev stream,id=s0,server=off,addr.type=unix,addr.path=__STATEDIR__/passt.socket -device virtio-rng-ccw -snapshot
+
+host	export DEBIAN_FRONTEND=noninteractive
+host	service systemd-networkd stop
+host	service systemd-resolved stop
+host	rm /etc/dhcp/dhclient-enter-hooks.d/resolved-enter
+host	dhclient -4
+dns_ready_wait
+host	apt-get update
+host	apt-get -y install make gcc socat
+
+host	make clean
+host	CFLAGS="-Werror" make
+
+host	export SHELL="/bin/dash"
+host	dash
+distro_quick_pasta_test
+
+hint
+sleep	1
+hostb	reset
+sleep	1
+host	echo
+
+
+host	kill __PID__
diff --git a/oldtest/env/mate-terminal.profile b/oldtest/env/mate-terminal.profile
new file mode 100644
index 00000000..a4ce8994
--- /dev/null
+++ b/oldtest/env/mate-terminal.profile
@@ -0,0 +1,42 @@
+# SPDX-FileCopyrightText: 2021 Red Hat GmbH <sbrivio@redhat.com>
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+[/]
+allow-bold=true
+background-color='#121212121212'
+background-darkness=0.5
+background-image=''
+background-type='solid'
+backspace-binding='ascii-del'
+bold-color='#000000000000'
+bold-color-same-as-fg=true
+copy-selection=false
+cursor-blink-mode='on'
+cursor-shape='block'
+custom-command=''
+default-show-menubar=true
+default-size-columns=180
+default-size-rows=40
+delete-binding='escape-sequence'
+exit-action='close'
+font='Bitstream Vera Sans Mono 9'
+foreground-color='#504FA2A26160'
+login-shell=false
+palette='#000000000000:#FFFF8F8F8F8F:#504FA2A26160:#CBCB6C6C0505:#8E8EA3A2FFFF:#C4C45959C4C4:#0000AAAAAAAA:#AAAAAAAAAAAA:#555455545554:#FFFF8F8F8F8F:#504FA2A26160:#CBCB6C6C0505:#8E8EA3A2FFFF:#C4C45959C4C4:#5554FFFFFFFF:#FFFFFFFFFFFF'
+scroll-background=true
+scroll-on-keystroke=true
+scroll-on-output=false
+scrollback-lines=512
+scrollback-unlimited=true
+scrollbar-position='hidden'
+silent-bell=false
+title='Terminal'
+title-mode='replace'
+use-custom-command=false
+use-custom-default-size=true
+use-skey=true
+use-system-font=false
+use-theme-colors=false
+use-urls=true
+visible-name='passt_ci'
+word-chars='-A-Za-z0-9,./?%&#:_=+@~'
diff --git a/oldtest/find-arm64-firmware.sh b/oldtest/find-arm64-firmware.sh
new file mode 100755
index 00000000..31826200
--- /dev/null
+++ b/oldtest/find-arm64-firmware.sh
@@ -0,0 +1,13 @@
+#! /bin/sh
+
+LOCATIONS="/usr/share/qemu-efi-aarch64 /usr/share/edk2/aarch64"
+
+for l in $LOCATIONS; do
+    if [ -f "$l/QEMU_EFI.fd" ]; then
+	ln -s "$l/QEMU_EFI.fd" "$1"
+	exit 0
+    fi
+done
+
+echo "Couldn't find QEMU_EFI.fd" >&2
+exit 1
diff --git a/oldtest/lib/context b/oldtest/lib/context
new file mode 100644
index 00000000..4741a556
--- /dev/null
+++ b/oldtest/lib/context
@@ -0,0 +1,130 @@
+#! /bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/context - Run commands in different contexts (host, guest, namespace etc.)
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+NSTOOL="${BASEPATH}/nstool"
+
+# context_setup_common() - Create outline of a new context
+# $1:	Context name
+context_setup_common() {
+	__name="$1"
+	__log="${LOGDIR}/context_${__name}.log"
+	echo -n "${__name}$ " > "${__log}"
+}
+
+# context_setup_host() - Create a new context for running commands on the host
+# $1:	Context name
+context_setup_host() {
+	__name="$1"
+	__enter="${STATESETUP}/context_${__name}.enter"
+	context_setup_common "${__name}"
+	echo sh -c > "${__enter}"
+}
+
+# context_setup_nstool() - Create a new context for running commands with nstool exec
+# $1:	Context name
+# $2:	nstool control socket
+context_setup_nstool() {
+	__name="$1"
+	__sock="$2"
+	__enter="${STATESETUP}/context_${__name}.enter"
+	# Wait for the ns to be ready
+	${NSTOOL} info -w "${__sock}" > /dev/null
+	context_setup_common "${__name}"
+	echo "${NSTOOL} exec ${__sock} -- sh -c" > "${__enter}"
+}
+
+# context_setup_guest() - Create a new context for running commands in a guest
+# $1:        Context name
+# $2:        CID to use for vsock
+context_setup_guest() {
+	__name="$1"
+	__cid="$2"
+	__enter="${STATESETUP}/context_${__name}.enter"
+	__ssh="${STATESETUP}/context_${__name}.ssh"
+	context_setup_common "${__name}"
+
+	cat > "${__ssh}" <<EOF
+Host ${__name}
+	User root
+	UserKnownHostsFile ${STATESETUP}/context_${__name}.hosts
+	StrictHostKeyChecking no
+	IdentityFile ${BASEPATH}/guest-key
+	IdentityAgent none
+	ProxyCommand socat - VSOCK-CONNECT:${__cid}:22
+EOF
+	echo "ssh -F ${__ssh} ${__name}" > "${__enter}"
+
+	# Wait for the guest to be booted and accepting connections
+	wait_for ssh -F "${__ssh}" "${__name}" :
+}
+
+# context_teardown() - Remove a context (leave log files intact)
+# $1:	Context name
+context_teardown() {
+	__name="$1"
+	__prefix="${STATESETUP}/context_${__name}"
+	rm -f "${__prefix}.enter" "${__prefix}.ssh" "${__prefix}.hosts"
+}
+
+# context_exists() - Test if a context currently exists
+# $1:	Context name
+context_exists() {
+	__name="$1"
+	__enter="${STATESETUP}/context_${__name}.enter"
+	[ -f "${__enter}" ]
+}
+
+# context_run() - Run a shell command in a context, and wait for it to finish
+# $1:	Context name
+# $*:	Command to start
+context_run() {
+	__name="$1"
+	__log="${LOGDIR}/context_${__name}.log"
+	__enter="${STATESETUP}/context_${__name}.enter"
+	__stdout="$(mktemp -u "${STATESETUP}/context_${__name}.stdout.XXXXXXXX")"
+	__stderr="$(mktemp -u "${STATESETUP}/context_${__name}.stderr.XXXXXXXX")"
+	shift
+	echo "$*" >> "${__log}"
+	mkfifo "${__stdout}" "${__stderr}"
+	tee -a "${__log}" < "${__stdout}" &
+	tee -a "${__log}" < "${__stderr}" >&2 &
+	$(cat ${__enter}) "$*" >> "${__stdout}" 2>> "${__stderr}"
+	rc=$?
+	rm "${__stdout}" "${__stderr}"
+	[ ${DEBUG} -eq 1 ] && echo "[Exit code: $rc]" >> "${__log}"
+	echo -n "${__name}$ " >> "${__log}"
+	return $rc
+}
+
+# context_run_bg() - Start a shell command in a context
+# $1:	Context name
+# $*:	Command to start
+context_run_bg() {
+	__name="$1"
+	__pidfile="${STATESETUP}/context_${__name}.pid"
+	context_run "$@" &
+	echo $! > "${__pidfile}"
+}
+
+# context_wait() - Wait for background command in a context to complete
+# $1:	Context name
+# Returns the status of the completed command
+context_wait() {
+	__name="$1"
+	__pidfile="${STATESETUP}/context_${__name}.pid"
+	__pid=$(cat "${__pidfile}")
+	rm "${__pidfile}"
+	wait ${__pid}
+}
diff --git a/oldtest/lib/layout b/oldtest/lib/layout
new file mode 100644
index 00000000..f9a1cf1b
--- /dev/null
+++ b/oldtest/lib/layout
@@ -0,0 +1,259 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/layout - tmux pane layouts
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+# layout_pasta() - Panes for host, pasta, and separate one for namespace
+layout_pasta() {
+	sleep 3
+
+	tmux kill-pane -a -t 0
+	cmd_write 0 clear
+
+	tmux split-window -v -t passt_test
+	tmux split-window -h -t passt_test
+	tmux split-window -h -l '42%' -t passt_test:1.0
+
+	PANE_NS=0
+	PANE_INFO=1
+	PANE_HOST=2
+	PANE_PASST=3
+
+	get_info_cols
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T "test log"
+
+	pane_watch_contexts ${PANE_HOST} host host
+	pane_watch_contexts ${PANE_PASST} pasta passt
+	pane_watch_contexts ${PANE_NS} "namespace" unshare ns
+
+	info_layout "single pasta instance with namespace"
+
+	sleep 1
+}
+
+# layout_passt() - Panes for host, passt, and guest
+layout_passt() {
+	sleep 3
+
+	tmux kill-pane -a -t 0
+	cmd_write 0 clear
+
+	tmux split-window -v -t passt_test
+	tmux split-window -h -t passt_test
+	tmux split-window -h -l '42%' -t passt_test:1.0
+
+	PANE_GUEST=0
+	PANE_INFO=1
+	PANE_HOST=2
+	PANE_PASST=3
+
+	get_info_cols
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T "test log"
+
+	pane_watch_contexts ${PANE_HOST} host host
+	pane_watch_contexts ${PANE_PASST} passt passt
+	pane_watch_contexts ${PANE_GUEST} guest qemu guest
+
+	info_layout "single passt instance with guest"
+
+	sleep 1
+}
+
+# layout_passt_in_pasta() - Host, passt within pasta, namespace and guest
+layout_passt_in_pasta() {
+	sleep 3
+
+	tmux kill-pane -a -t 0
+	cmd_write 0 clear
+
+	tmux split-window -v -l '45%' -t passt_test
+	tmux split-window -h -t passt_test
+	tmux split-window -h -l '42%' -t passt_test:1.0
+	tmux split-window -v -t passt_test:1.0
+
+	PANE_GUEST=0
+	PANE_NS=1
+	PANE_INFO=2
+	PANE_HOST=3
+	PANE_PASST=4
+
+	get_info_cols
+
+	pane_watch_contexts ${PANE_GUEST} "guest" qemu guest
+	pane_watch_contexts ${PANE_NS} "namespace" ns
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T "test log"
+
+	pane_watch_contexts ${PANE_HOST} host host
+
+	pane_watch_contexts ${PANE_PASST} "passt in pasta (namespace)" pasta passt
+
+	info_layout "passt and guest in namespace, connected by pasta"
+
+	sleep 1
+}
+
+# layout_two_guests() - Two guest panes, two passt panes, plus host and log
+layout_two_guests() {
+	sleep 3
+
+	tmux kill-pane -a -t 0
+	cmd_write 0 clear
+
+	tmux split-window -v -t passt_test
+	tmux split-window -h -l '33%'
+	tmux split-window -h -t passt_test:1.1
+
+	tmux split-window -h -l '35%' -t passt_test:1.0
+	tmux split-window -v -t passt_test:1.0
+
+	PANE_GUEST_1=0
+	PANE_GUEST_2=1
+	PANE_INFO=2
+	PANE_HOST=3
+	PANE_PASST_1=4
+	PANE_PASST_2=5
+
+	get_info_cols
+
+	pane_watch_contexts ${PANE_GUEST_1} "guest #1 in namespace #1" qemu_1 guest_1
+	pane_watch_contexts ${PANE_GUEST_2} "guest #2 in namespace #2" qemu_2 guest_2
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T "test log"
+
+	pane_watch_contexts ${PANE_HOST} host host
+	pane_watch_contexts ${PANE_PASST_1} "passt #1 in namespace #1" pasta_1 passt_1
+	pane_watch_contexts ${PANE_PASST_2} "passt #2 in namespace #2" pasta_2 passt_2
+
+	info_layout "two guests, two passt instances, in namespaces"
+
+	sleep 1
+}
+
+# layout_demo_pasta() - Four panes for pasta demo
+layout_demo_pasta() {
+	sleep 3
+
+	cmd_write 0 cd ${BASEPATH}
+	cmd_write 0 clear
+	sleep 1
+	cmd_write 0 clear
+
+	tmux split-window -v -t passt_test
+	tmux split-window -h -t passt_test
+	tmux split-window -h -l '42%' -t passt_test:1.0
+
+	PANE_NS=0
+	PANE_INFO=1
+	PANE_HOST=2
+	PANE_PASST=3
+
+	get_info_cols
+
+	tmux pipe-pane -O -t ${PANE_NS} "cat >> ${LOGDIR}/pane_ns.log"
+	tmux select-pane -t ${PANE_NS} -T "namespace"
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T ""
+
+	tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
+	tmux select-pane -t ${PANE_HOST} -T "host"
+
+	tmux pipe-pane -O -t ${PANE_PASST} "cat >> ${LOGDIR}/pane_passt.log"
+	tmux select-pane -t ${PANE_PASST} -T "pasta"
+
+	sleep 1
+}
+
+# layout_demo_passt() - Four panes for passt demo
+layout_demo_passt() {
+	sleep 3
+
+	cmd_write 0 cd ${BASEPATH}
+	cmd_write 0 clear
+	sleep 1
+	cmd_write 0 clear
+
+	tmux split-window -v -t passt_test
+	tmux split-window -h -t passt_test
+	tmux split-window -h -l '42%' -t passt_test:1.0
+
+	PANE_GUEST=0
+	PANE_INFO=1
+	PANE_HOST=2
+	PANE_PASST=3
+
+	get_info_cols
+
+	tmux pipe-pane -O -t ${PANE_GUEST} "cat >> ${LOGDIR}/pane_guest.log"
+	tmux select-pane -t ${PANE_GUEST} -T "guest"
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T ""
+
+	tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
+	tmux select-pane -t ${PANE_HOST} -T "host"
+
+	tmux pipe-pane -O -t ${PANE_PASST} "cat >> ${LOGDIR}/pane_passt.log"
+	tmux select-pane -t ${PANE_PASST} -T "passt in pasta (namespace)"
+
+	sleep 1
+}
+
+# layout_demo_podman() - Four panes for pasta demo with Podman
+layout_demo_podman() {
+	sleep 3
+
+	cmd_write 0 cd ${BASEPATH}
+	cmd_write 0 clear
+	sleep 1
+	cmd_write 0 clear
+
+	tmux split-window -v -l '65%' -t passt_test
+	tmux split-window -h -t passt_test
+	tmux split-window -h -l '42%' -t passt_test:1.0
+
+	PANE_HOST=0
+	PANE_INFO=1
+	PANE_NS1=2
+	PANE_NS2=3
+
+	get_info_cols
+
+	tmux pipe-pane -O -t ${PANE_NS1} "cat >> ${LOGDIR}/pane_ns1.log"
+	tmux select-pane -t ${PANE_NS1} -T "Podman with slirp4netns"
+
+	tmux pipe-pane -O -t ${PANE_NS2} "cat >> ${LOGDIR}/pane_ns2.log"
+	tmux select-pane -t ${PANE_NS2} -T "Podman with pasta"
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T ""
+
+	tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
+	tmux select-pane -t ${PANE_HOST} -T "host"
+
+	sleep 1
+}
diff --git a/oldtest/lib/layout_ugly b/oldtest/lib/layout_ugly
new file mode 100644
index 00000000..2aaf1ec3
--- /dev/null
+++ b/oldtest/lib/layout_ugly
@@ -0,0 +1,113 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/layout_ugly - screen-scraped tmux pane layouts
+#
+# Copyright (c) 2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+# layout_host() - Simple host commands layout with info and host panes
+layout_host() {
+	sleep 3
+
+	tmux kill-pane -a -t 0
+	cmd_write 0 clear
+
+	tmux split-window -h -l '35%' -t passt_test:1.0
+
+	PANE_HOST=0
+	PANE_INFO=1
+
+	get_info_cols
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T "test log"
+
+	if context_exists host; then
+		pane_watch_contexts 0 host host
+	else
+		tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
+		tmux select-pane -t ${PANE_HOST} -T "host"
+	fi
+
+	info_layout "host commands only"
+
+	sleep 1
+}
+
+# layout_pasta_simple() - Panes for host and pasta
+layout_pasta_simple() {
+	sleep 3
+
+	tmux kill-pane -a -t 0
+	cmd_write 0 clear
+
+	tmux split-window -v -t passt_test
+	tmux split-window -h -t passt_test
+
+	PANE_PASST=0
+	PANE_HOST=1
+	PANE_INFO=2
+
+	get_info_cols
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T "test log"
+
+	if context_exists host; then
+		pane_watch_contexts ${PANE_HOST} host host
+	else
+		tmux pipe-pane -O -t ${PANE_HOST} "cat >> ${LOGDIR}/pane_host.log"
+		tmux select-pane -t ${PANE_HOST} -T "host"
+	fi
+
+	if context_exists passt; then
+		pane_watch_contexts ${PANE_PASST} host host
+	else
+		tmux pipe-pane -O -t ${PANE_PASST} "cat >> ${LOGDIR}/pane_passt.log"
+		tmux select-pane -t ${PANE_PASST} -T "pasta"
+	fi
+
+	info_layout "single pasta instance"
+
+	sleep 1
+}
+
+# layout_memory() - Screen-scraped panes for memory usage tests, big guest pane
+layout_memory() {
+	sleep 3
+
+	tmux kill-pane -a -t 0
+	cmd_write 0 clear
+
+	tmux split-window -h -l '35%' -t passt_test
+
+	PANE_GUEST=0
+	PANE_INFO=1
+
+	get_info_cols
+
+	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
+	tmux send-keys -t ${PANE_INFO} -N 100 C-m
+	tmux select-pane -t ${PANE_INFO} -T "test log"
+
+	if context_exists guest; then
+		pane_watch_contexts ${PANE_GUEST} guest guest
+	else
+		tmux pipe-pane -O -t ${PANE_GUEST} "cat >> ${LOGDIR}/pane_guest.log"
+		tmux select-pane -t ${PANE_GUEST} -T "guest"
+	fi
+
+	info_layout "memory usage"
+
+	sleep 1
+}
diff --git a/oldtest/lib/perf_report b/oldtest/lib/perf_report
new file mode 100755
index 00000000..d51a2b4e
--- /dev/null
+++ b/oldtest/lib/perf_report
@@ -0,0 +1,272 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/perf_report - Prepare JavaScript report for performance tests
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+PERF_INIT=0
+PERF_LINK_COUNT=0
+PERF_JS="${LOGDIR}/web/perf.js"
+
+PERF_TEMPLATE_HTML="document.write('"'
+Throughput in Gbps, latency in µs. Threads are <span style="font-family: monospace;">iperf3</span> processes, <i>passt</i> and <i>pasta</i> are currently single-threaded.<br/>
+Click on numbers to show test execution. Measured at head, commit <span style="font-family: monospace;">__commit__</span>.
+
+<style type="text/CSS">
+table.passt td { border: 0px solid; padding: 6px; line-height: 1; }
+table.passt td { text-align: right; }
+table.passt th { text-align: center; font-weight: bold; }
+table.passt tr:not(:first-of-type) td:not(:first-of-type) { font-family: monospace; font-weight: bolder; }
+table.passt tr:nth-child(3n+0) { background-color: #112315; }
+table.passt tr:not(:nth-child(3n+0)) td { background-color: #101010; }
+table.passt td:nth-child(6n+7) { background-color: #603302; }
+table.passt tr:nth-child(1) { background-color: #363e61; }
+td:empty { visibility: hidden; }
+</style>
+
+<ul>
+<li><p>passt</p>
+<table class="passt" width="70%">
+	<tr>
+		<th/>
+		<th id="perf_passt_tcp" colspan="__passt_tcp_cols__">TCP, __passt_tcp_threads__ at __passt_tcp_freq__ GHz</th>
+		<th id="perf_passt_udp" colspan="__passt_udp_cols__">UDP, __passt_udp_threads__ at __passt_udp_freq__ GHz</th>
+	</tr>
+	<tr>
+		<td align="right">MTU:</td>
+		__passt_tcp_header__
+		__passt_udp_header__
+	</tr>
+	__passt_tcp_LINE__ __passt_udp_LINE__
+</table>
+
+<style type="text/CSS">
+table.pasta td { border: 0px solid; padding: 6px; line-height: 1; }
+table.pasta td { text-align: right; }
+table.pasta th { text-align: center; font-weight: bold; }
+table.pasta tr:not(:first-of-type) td:not(:first-of-type) { font-family: monospace; font-weight: bolder; }
+table.pasta tr:nth-child(3n+0) { background-color: #112315; }
+table.pasta tr:not(:nth-child(3n+0)) td { background-color: #101010; }
+table.pasta td:nth-child(4n+5) { background-color: #603302; }
+table.pasta tr:nth-child(1) { background-color: #363e61; }
+td:empty { visibility: hidden; }
+</style>
+
+</li><li><p>pasta: local connections/traffic</p>
+<table class="pasta" width="70%">
+	<tr>
+		<th/>
+		<th id="perf_pasta_lo_tcp" colspan="__pasta_lo_tcp_cols__">TCP, __pasta_lo_tcp_threads__ at __pasta_lo_tcp_freq__ GHz</th>
+		<th id="perf_pasta_lo_udp" colspan="__pasta_lo_udp_cols__">UDP, __pasta_lo_udp_threads__ at __pasta_lo_udp_freq__ GHz</th>
+	</th>
+	<tr>
+		<td align="right">MTU:</td>
+		__pasta_lo_tcp_header__
+		__pasta_lo_udp_header__
+	</tr>
+	__pasta_lo_tcp_LINE__ __pasta_lo_udp_LINE__
+</table>
+
+</li><li><p>pasta: connections/traffic via tap</p>
+<table class="pasta" width="70%">
+	<tr>
+		<th/>
+		<th id="perf_pasta_tap_tcp" colspan="__pasta_tap_tcp_cols__">TCP, __pasta_tap_tcp_threads__ at __pasta_tap_tcp_freq__ GHz</th>
+		<th id="perf_pasta_tap_udp" colspan="__pasta_tap_udp_cols__">UDP, __pasta_tap_udp_threads__ at __pasta_tap_udp_freq__ GHz</th>
+	</tr>
+	<tr>
+		<td align="right">MTU:</td>
+		__pasta_tap_tcp_header__
+		__pasta_tap_udp_header__
+	</tr>
+	__pasta_tap_tcp_LINE__ __pasta_tap_udp_LINE__
+</table>
+
+</li></ul>'
+
+PERF_TEMPLATE_JS="');
+
+var perf_links = [
+"
+
+PERF_TEMPLATE_POST='];
+
+for (var i = 0; i < perf_links.length; i++) {
+	var obj = document.getElementById(perf_links[i][0]);
+
+	obj.addEventListener("click", function(event) {
+		var ci_video = document.getElementById("ci");
+		var top = ci_video.offsetTop - 5;
+		var seek;
+
+		for (var i = 0; i < perf_links.length; i++) {
+			if (this.id == perf_links[i][0]) {
+				seek = perf_links[i][1];
+			}
+		}
+
+		event.preventDefault();
+		ci_player.dispose();
+		ci_player = AsciinemaPlayer.create("/builds/latest/web/ci.cast",
+						   ci_video,
+						   { cols: 240, rows: 51, poster: "npt:999:0", startAt: seek, autoplay: true });
+
+		window.scrollTo({ top: top, behavior: "smooth" })
+	}, false);
+}
+'
+
+# perf_init() - Process first part of template
+perf_init() {
+        mkdir -p "$(dirname "${PERF_JS}")"
+	echo "${PERF_TEMPLATE_HTML}" > "${PERF_JS}"
+	perf_report_sub commit "$(echo ${COMMIT} | sed "s/'/\\\'/g")"
+	PERF_INIT=1
+}
+
+# perf_fill_lines() - Fill multiple "LINE" directives in template, matching rows
+perf_fill_lines() {
+	while true; do
+		__file_line="$(sed -n '/__.*_LINE__/{=;q}' "${PERF_JS}")"
+		[ -z "${__file_line}" ] && break
+
+		__line_no=0
+		__done=0
+		__line_buf="<tr>"
+		while true; do
+			__match_first_td=0
+			for __t in $(sed -n '/__.*_LINE__/{p;q}' "${PERF_JS}"); do
+				if [ ${__match_first_td} -eq 1 ]; then
+					__matching_line_no=0
+					while true; do
+						__line_part=
+						__var_name="$(echo $__t | sed -n 's/__\(.*\)__/\1_'"${__matching_line_no}"'/p')"
+						[ -z "$(eval echo \$${__var_name})" ] && break
+						__line_part="$(eval echo \$${__var_name})"
+						__td_check="$(echo "${__line_part}" | sed -n 's/^<td>\([^>]*\)<\/td>.*$/\1/p')"
+						if [ "${__td_check}" = "${__td_match}" ]; then
+							__line_part="$(echo "${__line_part}" | sed -n 's/^<td>[^>]*<\/td>\(.*\)$/\1/p')"
+							break
+						fi
+						__matching_line_no=$((__matching_line_no + 1))
+					done
+				else
+					__var_name="$(echo $__t | sed -n 's/__\(.*\)__/\1_'"${__line_no}"'/p')"
+					[ -z "$(eval echo \$${__var_name})" ] && __done=1 && break
+					__line_part="$(eval echo \$${__var_name})"
+					__td_match="$(echo "${__line_part}" | sed -n 's/^<td>\([^>]*\)<\/td>.*$/\1/p')"
+				fi
+				__line_buf="${__line_buf}${__line_part}"
+				__match_first_td=1
+			done
+			[ ${__done} -eq 1 ] && break
+			__line_no=$((__line_no + 1))
+			__line_buf="${__line_buf}</tr><tr>"
+		done
+		__line_buf="${__line_buf}</tr>"
+		__line_buf="$(printf '%s\n' "${__line_buf}" | sed -e 's/[]\/$*.^[]/\\&/g')"
+		sed -i "${__file_line}s/.*/${__line_buf}/" "${PERF_JS}"
+	done
+}
+
+# perf_finish() - Add trailing backslashes and process ending templates
+perf_finish() {
+	PERF_INIT=0
+	perf_fill_lines
+	sed -i 's/^.*$/&\\/g' "${PERF_JS}"
+	echo "${PERF_TEMPLATE_JS}" >> "${PERF_JS}"
+	echo "${PERF_TEMPLATE_POST}" >> "${PERF_JS}"
+}
+
+# perf_report_sub() - Apply simple substitutions in template
+perf_report_sub() {
+	__et="$(printf '%s\n' "${1}" | sed -e 's/[\/&]/\\&/g')"
+	__es="$(printf '%s\n' "${2}" | sed -e 's/[]\/$*.^[]/\\&/g')"
+
+	sed -i 's/__'"${__et}"'__/'"${__es}"'/g' "${PERF_JS}"
+}
+
+# perf_report_append_js() - Append generic string to current template buffer
+perf_report_append_js() {
+	PERF_TEMPLATE_JS="${PERF_TEMPLATE_JS}${@}"
+}
+
+# perf_report() - Start of single test report
+perf_report() {
+	__mode="${1}"
+	__proto="${2}"
+	__threads="${3}"
+	__freq="${4}"
+
+	REPORT_IN="${__mode}_${__proto}"
+
+	[ ${__threads} -eq 1 ] && __threads="one thread" || __threads="${__threads} threads"
+	perf_report_sub "${__mode}_${__proto}_threads" "${__threads}"
+	perf_report_sub "${__mode}_${__proto}_freq" "${__freq}"
+
+	perf_report_append_js "[ 'perf_${__mode}_${__proto}', $(video_time_now) ],"
+}
+
+# perf_th() - Table header for a set of tests
+perf_th() {
+	[ ${PERF_INIT} -eq 0 ] && return
+
+	shift
+
+	__th_buf=
+	__cols_count=0
+	for __arg; do
+		__th_buf="${__th_buf}<td>${__arg}</td>"
+		__cols_count=$((__cols_count + 1))
+	done
+	perf_report_sub "${REPORT_IN}_header" "${__th_buf}"
+	perf_report_sub "${REPORT_IN}_cols" ${__cols_count}
+}
+
+# perf_tr() - Main table row
+perf_tr() {
+	[ ${PERF_INIT} -eq 0 ] && return
+
+	__line_no=0
+	shift
+	while true; do
+		[ -z "$(eval echo \$${REPORT_IN}_LINE_${__line_no})" ] && break
+		__line_no=$((__line_no + 1))
+	done
+	eval ${REPORT_IN}_LINE_${__line_no}="\"<td>${@}</td>\""
+}
+
+# perf_td() - Single cell with test result
+perf_td() {
+	[ ${PERF_INIT} -eq 0 ] && return
+
+	__rewind="${1}"
+	shift
+
+	__line_no=0
+	while true; do
+		[ -z "$(eval echo \$${REPORT_IN}_LINE_${__line_no})" ] && break
+		__line_no=$((__line_no + 1))
+	done
+	__line_no=$((__line_no - 1))
+	[ -z "${1}" ] && __id=0 || __id="perf_${PERF_LINK_COUNT}"
+	eval ${REPORT_IN}_LINE_${__line_no}=\""\${${REPORT_IN}_LINE_${__line_no}}<td id=\"${__id}\">${1}</td>"\"
+	[ -z "${1}" ] && return
+
+	perf_report_append_js "[ '${__id}', $(($(video_time_now) - ${__rewind})) ],"
+	PERF_LINK_COUNT=$((PERF_LINK_COUNT + 1))
+}
+
+# perf_te() - End of a table, currently unused
+pert_te() {
+	:
+}
diff --git a/oldtest/lib/setup b/oldtest/lib/setup
new file mode 100755
index 00000000..9b39b9fe
--- /dev/null
+++ b/oldtest/lib/setup
@@ -0,0 +1,385 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/setup - Set up and tear down passt and pasta environments
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+INITRAMFS="${BASEPATH}/mbuto.img"
+VCPUS="$( [ $(nproc) -ge 8 ] && echo 6 || echo $(( $(nproc) / 2 + 1 )) )"
+__mem_kib="$(sed -n 's/MemTotal:[ ]*\([0-9]*\) kB/\1/p' /proc/meminfo)"
+VMEM="$((${__mem_kib} / 1024 / 4))"
+
+# setup_build() - Set up pane layout for build tests
+setup_build() {
+	context_setup_host host
+
+	layout_host
+}
+
+# setup_passt() - Start qemu and passt
+setup_passt() {
+	context_setup_host host
+	context_setup_host passt
+	context_setup_host qemu
+
+	layout_passt
+
+	# Ports:
+	#
+	#              guest    |        host
+	#         --------------|---------------------
+	#  10001     as server  |  forwarded to guest
+	#  10003                |      as server
+
+	__opts=
+	[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/passt.pcap"
+	[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
+	[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+
+	context_run passt "make clean"
+	context_run passt "make valgrind"
+	context_run_bg passt "valgrind --max-stackframe=$((4 * 1024 * 1024)) --trace-children=yes --vgdb=no --error-exitcode=1 --suppressions=test/valgrind.supp ./passt ${__opts} -s ${STATESETUP}/passt.socket -f -t 10001 -u 10001 -P ${STATESETUP}/passt.pid"
+
+	# pidfile isn't created until passt is listening
+	wait_for [ -f "${STATESETUP}/passt.pid" ]
+
+	GUEST_CID=94557
+	context_run_bg qemu 'qemu-system-$(uname -m)'			   \
+		' -machine accel=kvm'                                      \
+		' -m '${VMEM}' -cpu host -smp '${VCPUS}                    \
+		' -kernel ' "/boot/vmlinuz-$(uname -r)"			   \
+		' -initrd '${INITRAMFS}' -nographic -serial stdio'	   \
+		' -nodefaults'						   \
+		' -append "console=ttyS0 mitigations=off apparmor=0" '	   \
+		' -device virtio-net-pci,netdev=s0 '			   \
+		" -netdev stream,id=s0,server=off,addr.type=unix,addr.path=${STATESETUP}/passt.socket " \
+		" -pidfile ${STATESETUP}/qemu.pid"			   \
+		" -device vhost-vsock-pci,guest-cid=$GUEST_CID"
+
+	context_setup_guest guest $GUEST_CID
+}
+
+# setup_pasta() - Create a network and user namespace, connect pasta to it
+setup_pasta() {
+	context_setup_host host
+	context_setup_host passt
+	context_setup_host unshare
+
+	layout_pasta
+
+	context_run_bg unshare "unshare -rUnpf ${NSTOOL} hold ${STATESETUP}/ns.hold"
+
+	context_setup_nstool ns ${STATESETUP}/ns.hold
+
+	# Ports:
+	#
+	#                 ns        |         host
+	#         ------------------|---------------------
+	#  10002      as server     |    spliced to ns
+	#  10003   spliced to init  |      as server
+
+	__opts=
+	[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/pasta.pcap"
+	[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
+	[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+
+	context_run_bg passt "./pasta ${__opts} -f -t 10002 -T 10003 -u 10002 -U 10003 -P ${STATESETUP}/passt.pid $(${NSTOOL} info -pw ${STATESETUP}/ns.hold)"
+
+	# pidfile isn't created until pasta is ready
+	wait_for [ -f "${STATESETUP}/passt.pid" ]
+}
+
+# setup_passt_in_ns() - Set up namespace (with pasta), run qemu and passt into it
+setup_passt_in_ns() {
+	context_setup_host host
+	context_setup_host pasta
+
+	layout_passt_in_pasta
+
+	# Ports:
+	#
+	#             guest    |         ns         |       host
+	#         -------------|--------------------|-----------------
+	#  10001    as server  | forwarded to guest |  spliced to ns
+	#  10002               |     as server      |  spliced to ns
+	#  10003               |   spliced to init  |    as server
+	#  10011    as server  | forwarded to guest |  spliced to ns
+	#  10012               |     as server      |  spliced to ns
+	#  10013               |   spliced to init  |    as server
+	#
+	#  10021    as server  | forwarded to guest |
+	#  10031    as server  | forwarded to guest |
+
+	__opts=
+	[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/pasta_with_passt.pcap"
+	[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
+	[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+
+	context_run_bg pasta "./pasta ${__opts} -t 10001,10002,10011,10012 -T 10003,10013 -u 10001,10002,10011,10012 -U 10003,10013 -P ${STATESETUP}/pasta.pid --config-net ${NSTOOL} hold ${STATESETUP}/ns.hold"
+	wait_for [ -f "${STATESETUP}/pasta.pid" ]
+
+	context_setup_nstool qemu ${STATESETUP}/ns.hold
+	context_setup_nstool ns ${STATESETUP}/ns.hold
+	context_setup_nstool passt ${STATESETUP}/ns.hold
+
+	__opts=
+	[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/passt_in_pasta.pcap"
+	[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
+	[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+
+	if [ ${VALGRIND} -eq 1 ]; then
+		context_run passt "make clean"
+		context_run passt "make valgrind"
+		context_run_bg passt "valgrind --max-stackframe=$((4 * 1024 * 1024)) --trace-children=yes --vgdb=no --error-exitcode=1 --suppressions=test/valgrind.supp ./passt -f ${__opts} -s ${STATESETUP}/passt.socket -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid"
+	else
+		context_run passt "make clean"
+		context_run passt "make"
+		context_run_bg passt "./passt -f ${__opts} -s ${STATESETUP}/passt.socket -t 10001,10011,10021,10031 -u 10001,10011,10021,10031 -P ${STATESETUP}/passt.pid"
+	fi
+	wait_for [ -f "${STATESETUP}/passt.pid" ]
+
+	GUEST_CID=94557
+	context_run_bg qemu 'qemu-system-$(uname -m)'			   \
+		' -machine accel=kvm'                                      \
+		' -M accel=kvm:tcg'                                        \
+		' -m '${VMEM}' -cpu host -smp '${VCPUS}                    \
+		' -kernel ' "/boot/vmlinuz-$(uname -r)"			   \
+		' -initrd '${INITRAMFS}' -nographic -serial stdio'	   \
+		' -nodefaults'						   \
+		' -append "console=ttyS0 mitigations=off apparmor=0" '	   \
+		' -device virtio-net-pci,netdev=s0 '			   \
+		" -netdev stream,id=s0,server=off,addr.type=unix,addr.path=${STATESETUP}/passt.socket " \
+		" -pidfile ${STATESETUP}/qemu.pid"			   \
+		" -device vhost-vsock-pci,guest-cid=$GUEST_CID"
+
+	context_setup_guest guest $GUEST_CID
+}
+
+# setup_two_guests() - Set up two namespace, run qemu and passt in both of them
+setup_two_guests() {
+	context_setup_host host
+	context_setup_host pasta_1
+	context_setup_host pasta_2
+
+	layout_two_guests
+
+	# Ports:
+	#
+	#         guest #1  |  guest #2 |   ns #1   |    ns #2   |    host
+	#         --------- |-----------|-----------|------------|------------
+	#  10001  as server |           | to guest  |  to init   |  to ns #1
+	#  10002            |           | as server |            |  to ns #1
+	#  10003            |           |  to init  |  to init   |  as server
+	#  10004            | as server |  to init  |  to guest  |  to ns #2
+	#  10005            |           |           |  as server |  to ns #2
+
+	__opts=
+	[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/pasta_1.pcap"
+	[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
+	[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+	context_run_bg pasta_1 "./pasta ${__opts} --trace -l /tmp/pasta1.log -P ${STATESETUP}/pasta_1.pid -t 10001,10002 -T 10003,10004 -u 10001,10002 -U 10003,10004 --config-net ${NSTOOL} hold ${STATESETUP}/ns1.hold"
+	context_setup_nstool passt_1 ${STATESETUP}/ns1.hold
+
+	__opts=
+	[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/pasta_2.pcap"
+	[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
+	[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+	context_run_bg pasta_2 "./pasta ${__opts} --trace -l /tmp/pasta2.log -P ${STATESETUP}/pasta_2.pid -t 10004,10005 -T 10003,10001 -u 10004,10005 -U 10003,10001 --config-net ${NSTOOL} hold ${STATESETUP}/ns2.hold"
+	context_setup_nstool passt_2 ${STATESETUP}/ns2.hold
+
+	context_setup_nstool qemu_1 ${STATESETUP}/ns1.hold
+	context_setup_nstool qemu_2 ${STATESETUP}/ns2.hold
+
+	__ifname="$(context_run qemu_1 "ip -j link show | jq -rM '.[] | select(.link_type == \"ether\").ifname'")"
+
+	sleep 1
+
+	__opts=
+	[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/passt_1.pcap"
+	[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
+	[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+
+	context_run_bg passt_1 "./passt -s ${STATESETUP}/passt_1.socket -P ${STATESETUP}/passt_1.pid -f ${__opts} -t 10001 -u 10001"
+	wait_for [ -f "${STATESETUP}/passt_1.pid" ]
+
+	__opts=
+	[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/passt_2.pcap"
+	[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
+	[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
+
+	context_run_bg passt_2 "./passt -s ${STATESETUP}/passt_2.socket -P ${STATESETUP}/passt_2.pid -f ${__opts} -t 10004 -u 10004"
+	wait_for [ -f "${STATESETUP}/passt_2.pid" ]
+
+	GUEST_1_CID=94557
+	context_run_bg qemu_1 'qemu-system-$(uname -m)'			     \
+		' -M accel=kvm:tcg'                                          \
+		' -m '${VMEM}' -cpu host -smp '${VCPUS}                      \
+		' -kernel ' "/boot/vmlinuz-$(uname -r)"			     \
+		' -initrd '${INITRAMFS}' -nographic -serial stdio'	     \
+		' -nodefaults'						     \
+		' -append "console=ttyS0 mitigations=off apparmor=0" '	     \
+		' -device virtio-net-pci,netdev=s0 '			     \
+		" -netdev stream,id=s0,server=off,addr.type=unix,addr.path=${STATESETUP}/passt_1.socket " \
+		" -pidfile ${STATESETUP}/qemu_1.pid"			     \
+		" -device vhost-vsock-pci,guest-cid=$GUEST_1_CID"
+
+	GUEST_2_CID=94558
+	context_run_bg qemu_2 'qemu-system-$(uname -m)'			     \
+		' -M accel=kvm:tcg'                                          \
+		' -m '${VMEM}' -cpu host -smp '${VCPUS}                      \
+		' -kernel ' "/boot/vmlinuz-$(uname -r)"			     \
+		' -initrd '${INITRAMFS}' -nographic -serial stdio'	     \
+		' -nodefaults'						     \
+		' -append "console=ttyS0 mitigations=off apparmor=0" '	     \
+		' -device virtio-net-pci,netdev=s0 '			     \
+		" -netdev stream,id=s0,server=off,addr.type=unix,addr.path=${STATESETUP}/passt_2.socket " \
+		" -pidfile ${STATESETUP}/qemu_2.pid"			     \
+		" -device vhost-vsock-pci,guest-cid=$GUEST_2_CID"
+
+	context_setup_guest guest_1 ${GUEST_1_CID}
+	context_setup_guest guest_2 ${GUEST_2_CID}
+}
+
+# teardown_context_watch() - Remove contexts and stop panes watching them
+# $1:	Pane number watching
+# $@:	Context names
+teardown_context_watch() {
+	__pane="$1"
+	shift
+	for __c; do
+		context_teardown "${__c}"
+	done
+	tmux send-keys -t ${__pane} "C-c"
+}
+
+# teardown_build() - Nothing to do, yet
+teardown_build() {
+	teardown_context_watch ${PANE_HOST} host
+}
+
+# teardown_passt() - Kill qemu, remove passt PID file
+teardown_passt() {
+	kill $(cat "${STATESETUP}/qemu.pid")
+
+	rm "${STATESETUP}/passt.pid"
+
+	teardown_context_watch ${PANE_HOST} host
+	teardown_context_watch ${PANE_PASST} passt
+	teardown_context_watch ${PANE_GUEST} qemu guest
+}
+
+# teardown_pasta() - Exit namespace, kill pasta process
+teardown_pasta() {
+	${NSTOOL} stop "${STATESETUP}/ns.hold"
+	context_wait unshare
+
+	teardown_context_watch ${PANE_HOST} host
+	teardown_context_watch ${PANE_PASST} passt
+	teardown_context_watch ${PANE_NS} unshare ns
+}
+
+# teardown_passt_in_ns() - Exit namespace, kill qemu and pasta, remove pid file
+teardown_passt_in_ns() {
+	context_run ns kill $(cat "${STATESETUP}/qemu.pid")
+	context_wait qemu
+
+	${NSTOOL} stop "${STATESETUP}/ns.hold"
+	context_wait pasta
+
+	rm "${STATESETUP}/passt.pid" "${STATESETUP}/pasta.pid"
+
+	teardown_context_watch ${PANE_HOST} host
+	teardown_context_watch ${PANE_PASST} pasta passt
+	teardown_context_watch ${PANE_NS} ns
+	teardown_context_watch ${PANE_GUEST} qemu guest
+}
+
+# teardown_two_guests() - Exit namespaces, kill qemu processes, passt and pasta
+teardown_two_guests() {
+	${NSTOOL} exec ${STATESETUP}/ns1.hold -- kill $(cat "${STATESETUP}/qemu_1.pid")
+	${NSTOOL} exec ${STATESETUP}/ns2.hold -- kill $(cat "${STATESETUP}/qemu_2.pid")
+	context_wait qemu_1
+	context_wait qemu_2
+
+	${NSTOOL} exec ${STATESETUP}/ns1.hold -- kill $(cat "${STATESETUP}/passt_1.pid")
+	${NSTOOL} exec ${STATESETUP}/ns2.hold -- kill $(cat "${STATESETUP}/passt_2.pid")
+	context_wait passt_1
+	context_wait passt_2
+	${NSTOOL} stop "${STATESETUP}/ns1.hold"
+	${NSTOOL} stop "${STATESETUP}/ns2.hold"
+	context_wait pasta_1
+	context_wait pasta_2
+
+	rm -f "${STATESETUP}/passt__[12].pid" "${STATESETUP}/pasta_[12].pid"
+
+	teardown_context_watch ${PANE_HOST} host
+	teardown_context_watch ${PANE_GUEST_1} qemu_1 guest_1
+	teardown_context_watch ${PANE_GUEST_2} qemu_2 guest_2
+	teardown_context_watch ${PANE_PASST_1} pasta_1 passt_1
+	teardown_context_watch ${PANE_PASST_2} pasta_2 passt_2
+}
+
+# teardown_demo_passt() - Exit namespace, kill qemu, passt and pasta
+teardown_demo_passt() {
+	tmux send-keys -t ${PANE_GUEST} "C-c"
+	pane_wait GUEST
+
+	tmux send-keys -t ${PANE_GUEST} "C-d"
+	tmux send-keys -t ${PANE_PASST} "C-c"
+
+	pane_wait GUEST
+	pane_wait HOST
+	pane_wait PASST
+
+	tmux kill-pane -a -t 0
+	tmux send-keys -t 0 "C-c"
+}
+
+# teardown_demo_pasta() - Exit perf and namespace from remaining pane
+teardown_demo_pasta() {
+	tmux send-keys -t ${PANE_NS} "q"
+	pane_wait NS
+	tmux send-keys -t ${PANE_NS} "C-d"
+	pane_wait NS
+
+	tmux kill-pane -a -t 0
+	tmux send-keys -t 0 "C-c"
+}
+
+# teardown_demo_podman() - Exit namespaces
+teardown_demo_podman() {
+	tmux send-keys -t ${PANE_NS1} "C-d"
+	tmux send-keys -t ${PANE_NS2} "C-d"
+	pane_wait NS1
+	pane_wait NS2
+
+	tmux kill-pane -a -t 0
+	tmux send-keys -t 0 "C-c"
+}
+
+# setup() - Run setup_*() functions
+# $*:	Suffix list of setup_*() functions to be called
+setup() {
+	for arg do
+		STATESETUP="${STATEBASE}/${arg}"
+		mkdir -p "${STATESETUP}"
+		eval setup_${arg}
+	done
+}
+
+# teardown() - Run teardown_*() functions
+# $*:	Suffix list of teardown_*() functions to be called
+teardown() {
+	for arg do
+		eval teardown_${arg}
+	done
+}
diff --git a/oldtest/lib/setup_ugly b/oldtest/lib/setup_ugly
new file mode 100755
index 00000000..4b2a0774
--- /dev/null
+++ b/oldtest/lib/setup_ugly
@@ -0,0 +1,58 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/setup_ugly - Setup functions using screen-scraping instead of context
+#
+# Copyright (c) 2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+INITRAMFS_MEM="${BASEPATH}/mbuto.mem.img"
+
+# setup_distro() - Set up pane layout for distro tests
+setup_distro() {
+	layout_host
+}
+
+# setup_pasta_options() - Set up layout and host context without starting pasta
+setup_pasta_options() {
+	context_setup_host host
+
+	layout_pasta_simple
+}
+
+# setup_memory() - Start qemu in guest pane, and passt in passt context
+setup_memory() {
+	layout_memory
+
+	pane_or_context_run guest 'qemu-system-$(uname -m)'		   \
+		' -machine accel=kvm'                                      \
+		' -m '${VMEM}' -cpu host -smp '${VCPUS}                    \
+		' -kernel ' "/boot/vmlinuz-$(uname -r)"			   \
+		' -initrd '${INITRAMFS_MEM}' -nographic -serial stdio'	   \
+		' -nodefaults'						   \
+		' -append "console=ttyS0 mitigations=off apparmor=0"'	   \
+		" -pidfile ${STATESETUP}/qemu.pid"
+}
+
+# teardown_distro() - Nothing to do, yet
+teardown_distro() {
+	:
+}
+
+# teardown_pasta_options() - Tear down pasta and host context, no namespace
+teardown_pasta_options() {
+	teardown_context_watch ${PANE_HOST} host
+	teardown_context_watch ${PANE_PASST} passt
+}
+
+# teardown_passt() - Kill qemu with ^C, remove passt PID file
+teardown_memory() {
+	kill $(cat "${STATESETUP}/qemu.pid")
+}
diff --git a/oldtest/lib/term b/oldtest/lib/term
new file mode 100755
index 00000000..aa05bf1e
--- /dev/null
+++ b/oldtest/lib/term
@@ -0,0 +1,750 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/term - Set up tmux sessions and panes, handle terminals and logs
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+STATUS_FILE=
+STATUS_FILE_NTESTS=
+STATUS_FILE_INDEX=0
+STATUS_COLS=
+STATUS_PASS=0
+STATUS_FAIL=0
+
+PR_RED='\033[1;31m'
+PR_GREEN='\033[1;32m'
+PR_YELLOW='\033[1;33m'
+PR_BLUE='\033[1;34m'
+PR_NC='\033[0m'
+PR_DELAY_INIT=100 # ms
+
+# info() - Highlight test log pane, print message to it and to log file
+# $@:	Message to print
+info() {
+	tmux select-pane -t ${PANE_INFO}
+	echo "${@}" >> $STATEBASE/log_pipe
+	echo "${@}" >> "${LOGFILE}"
+}
+
+# info_n() - Highlight, print message to pane and to log file without newline
+# $@:	Message to print
+info_n() {
+	tmux select-pane -t ${PANE_INFO}
+	printf "${@}" >> $STATEBASE/log_pipe
+	printf "${@}" >> "${LOGFILE}"
+}
+
+# info_nolog() - Highlight test log pane, print message to it
+# $@:	Message to print
+info_nolog() {
+	tmux select-pane -t ${PANE_INFO}
+	echo "${@}" >> $STATEBASE/log_pipe
+}
+
+# info_nolog() - Print message to log file
+# $@:	Message to print
+log() {
+	echo "${@}" >> "${LOGFILE}"
+}
+
+# info_nolog_n() - Send message to pane without highlighting it, without newline
+# $@:	Message to print
+info_nolog_n() {
+	tmux send-keys -l -t ${PANE_INFO} "${@}"
+}
+
+# info_sep() - Print given separator, horizontally filling test log pane
+# $1:	Separator character
+info_sep() {
+	tmux send-keys -l -N ${STATUS_COLS} -t ${PANE_INFO} "${1}"
+	tmux send-keys -t ${PANE_INFO} C-m
+}
+
+# sleep_char() - Sleep for typed characted resembling interactive input
+# $1:	Character typed to pane
+sleep_char() {
+	[ ${FAST} -eq 1 ] && return
+
+	if [ "${1}" = " " ]; then
+		PR_DELAY=$((PR_DELAY + 40))
+	elif [ -n "$(printf '%s' "${1}" | tr -d [:alnum:])" ]; then
+		PR_DELAY=$((PR_DELAY + 30))
+	elif [ ${PR_DELAY} -ge 30 ]; then
+		PR_DELAY=$((PR_DELAY / 3 * 2))
+	fi
+
+	sleep "$(printf 0.%03i ${PR_DELAY})" || sleep 1
+}
+
+# display_delay() - Simple delay, omitted if $FAST is set
+display_delay() {
+	[ ${FAST} -eq 1 ] && return
+
+	sleep "${1}" || sleep 1
+}
+
+# switch_pane() - Highlight given pane and reset character delay
+# $1:	Pane number
+switch_pane() {
+	tmux select-pane -t ${1}
+	PR_DELAY=${PR_DELAY_INIT}
+	display_delay "0.2"
+}
+
+# cmd_write() - Write a command to a pane, letter by letter, and execute it
+# $1:	Pane number
+# $@:	Command to issue
+cmd_write() {
+	__pane_no=${1}
+	shift
+
+	switch_pane ${__pane_no}
+
+	__str="${@}"
+	while [ -n "${__str}" ]; do
+		__rem="${__str#?}"
+		__first="${__str%"$__rem"}"
+		if [ "${__first}" = ";" ]; then
+			tmux send-keys -t ${__pane_no} -l '\;'
+		else
+			tmux send-keys -t ${__pane_no} -l "${__first}"
+		fi
+		sleep_char "${__first}"
+		__str="${__rem}"
+	done
+	tmux send-keys -t ${__pane_no} "C-m"
+}
+
+# text_write() - Write text to info pane, letter by letter
+# $1:	Pane number
+# $@:	Command to issue
+text_write() {
+	__str="${@}"
+	while [ -n "${__str}" ]; do
+		__rem="${__str#?}"
+		__first="${__str%"$__rem"}"
+		if [ "${__first}" = ";" ]; then
+			tmux send-keys -t ${PANE_INFO} -l '\;'
+		else
+			tmux send-keys -t ${PANE_INFO} -l "${__first}"
+		fi
+		sleep_char "${__first}"
+		__str="${__rem}"
+	done
+}
+
+# text_backspace() - Slow backspace motion for demo
+# $1:	Number of backspace characters
+text_backspace() {
+	for __count in $(seq 0 ${1}); do
+		tmux send-keys -t ${PANE_INFO} Bspace
+		sleep 0.1
+	done
+}
+
+# em_write() - Write to log pane in red, for demo
+# $@:	Text
+em_write() {
+	info_n "${PR_RED}${@}${PR_NC}"
+}
+
+# pane_kill() - Kill a single pane given its name
+# $1:	Pane name
+pane_kill() {
+	__pane_number=$(eval echo \$PANE_${1})
+	tmux kill-pane -t ${__pane_number}
+}
+
+# pane_highlight() - Highlight a single pane given its name
+# $1:	Pane name
+pane_highlight() {
+	__pane_number=$(eval echo \$PANE_${1})
+	switch_pane ${__pane_number}
+	sleep 3
+}
+
+# pane_resize() - Resize a pane given its name
+# $1:	Pane name
+# $2:	Direction: U, D, L, or R
+# $3:	Adjustment in lines or columns
+pane_resize() {
+	__pane_number=$(eval echo \$PANE_${1})
+	tmux resize-pane -${2} -t ${__pane_number} ${3}
+}
+
+# pane_run() - Issue a command in given pane name
+# $1:	Pane name
+# $@:	Command to issue
+pane_run() {
+	__pane_name="${1}"
+	shift
+
+	__pane_number=$(eval echo \$PANE_${__pane_name})
+
+	eval ${__pane_name}_LAST_CMD=\"\${@}\"
+
+	cmd_write ${__pane_number} "${@}"
+}
+
+# pane_wait() - Wait for command to be done in given pane name
+# $1:	Pane name
+pane_wait() {
+	__lc="$(echo "${1}" | tr [A-Z] [a-z])"
+	sleep 0.1 || sleep 1
+
+	__done=0
+	while
+		__l="$(tail -1 ${LOGDIR}/pane_${__lc}.log | tr -d [:cntrl:])"
+		case ${__l} in
+		'$ ' | '# ' | '# # ' | *"$ " | *"# ") return ;;
+		*" #[m " | *" #[m [K" | *"]# ["*) return ;;
+		*' $ [6n' | *' # [6n' ) return ;;
+		esac
+	do sleep 0.1 || sleep 1; done
+}
+
+# pane_parse() - Print last line, @EMPTY@ if command had no output
+# $1:	Pane name
+pane_parse() {
+	__pane_lc="$(echo "${1}" | tr [A-Z] [a-z])"
+
+	__buf="$(tail -n2 ${LOGDIR}/pane_${__pane_lc}.log | head -n1 | sed 's/^[^\r]*\r\([^\r]\)/\1/' | tr -d '\r\n')"
+
+	[ "# $(eval printf '%s' \"\$${1}_LAST_CMD\")" != "${__buf}" ] && \
+	[ "$ $(eval printf '%s' \"\$${1}_LAST_CMD\")" != "${__buf}" ] &&
+		printf '%s' "${__buf}" || printf '@EMPTY@'
+}
+
+# pane_status() - Wait for command to complete and return its exit status
+# $1:	Pane name
+pane_status() {
+	pane_wait "${1}"
+
+	[ ${DEMO} -eq 1 ] && return 0
+
+	__status="$(pane_parse "${1}")"
+	while ! [ "${__status}" -eq "${__status}" ] 2>/dev/null; do
+		sleep 1
+		pane_run "${1}" 'echo $?'
+		pane_wait "${1}"
+		__status="$(pane_parse "${1}")"
+	done
+	return ${__status}
+}
+
+# pane_watch_context() - Set up pane to watch commands executing in context(s)
+# $1:	Pane number
+# $2:	Description (for pane label)
+# $@:	Context name or names
+pane_watch_contexts() {
+	__pane_number="${1}"
+	__desc="${2}"
+	shift 2
+	__name="${2}"
+
+	tmux select-pane -t ${__pane_number} -T "${__desc}"
+	__cmd="tail -f --retry"
+	for c; do
+		__cmd="${__cmd} ${LOGDIR}/context_${c}.log"
+	done
+	cmd_write ${__pane_number} "${__cmd}"
+}
+
+# pane_or_context_run() - Issue a command in given context or pane
+# $1:	Context or lower-case pane name
+# $@:	Command to issue
+pane_or_context_run() {
+	__name="${1}"
+	shift
+	if context_exists "${__name}"; then
+		# Redirect stdin to stop ssh from eating the test instructions file we have on stdin
+		context_run "${__name}" "$@" >/dev/null 2>&1 < /dev/null
+	else
+		__uc="$(echo "${__name}" | tr [a-z] [A-Z])"
+		pane_run "${__uc}" "$@"
+		pane_status "${__uc}"
+	fi
+}
+
+# pane_or_context_run_bg() - Issue a background command in given context or pane
+# $1:	Context or lower-case pane name
+# $@:	Command to issue
+pane_or_context_run_bg() {
+	__name="${1}"
+	shift
+	if context_exists "${__name}"; then
+		# Redirect stdin to stop ssh from eating the test instructions file we have on stdin
+		context_run_bg "${__name}" "$@" >/dev/null 2>&1 < /dev/null
+	else
+		__uc="$(echo "${__name}" | tr [a-z] [A-Z])"
+		pane_run "${__uc}" "$@"
+	fi
+}
+
+# pane_or_context_output() - Get output from a command in a context or pane
+# $1:	Context or lower-case pane name
+# $@:	Command to issue
+pane_or_context_output() {
+	__name="${1}"
+	shift
+	if context_exists "${__name}"; then
+		# Redirect stdin to stop ssh from eating the test instructions file we have on stdin
+		__output=$(context_run "${__name}" "$@" 2>/dev/null </dev/null)
+		if [ -z "${__output}" ]; then
+			echo "@EMPTY@"
+		else
+			echo "${__output}"
+		fi
+	else
+		__uc="$(echo "${__name}" | tr [a-z] [A-Z])"
+		pane_run "${__uc}" "$@"
+		pane_wait "${__uc}"
+		pane_parse "${__uc}"
+	fi
+}
+
+# pane_or_context_wait() - Wait for a command to be done in a context or pane
+# $1:	Context or lower-case pane name
+pane_or_context_wait() {
+	__name="${1}"
+	shift
+	if context_exists "${__name}"; then
+		context_wait "${__name}"
+	else
+		__uc="$(echo "${__name}" | tr [a-z] [A-Z])"
+		pane_wait "${__uc}"
+	fi
+}
+
+# status_file_end() - Display and log messages when tests from one file are done
+status_file_end() {
+	[ -z "${STATUS_FILE}" ] && return
+
+	info_sep "="
+	log
+	tmux select-pane -t ${PANE_INFO} -T ""
+	STATUS_FILE=
+}
+
+# status_file_start() - Display and log messages when tests from one file start
+status_file_start() {
+	switch_pane ${PANE_INFO}
+
+	status_file_end
+
+	info_nolog "Starting tests in file: ${1}\n"
+	log "=== ${1}"
+	tmux select-pane -t ${PANE_INFO} -T "${1}"
+
+	STATUS_FILE="${1}"
+	STATUS_FILE_NTESTS="${2}"
+	STATUS_FILE_INDEX=0
+}
+
+# status_file_start() - Display and log messages when a single test starts
+status_test_start() {
+	switch_pane ${PANE_INFO}
+
+	info_nolog "Starting test: ${1}"
+	log "> ${1}"
+
+	STATUS_FILE_INDEX=$((STATUS_FILE_INDEX + 1))
+	tmux select-pane -t ${PANE_INFO} -T "${STATUS_FILE} [${STATUS_FILE_INDEX}/${STATUS_FILE_NTESTS}] - ${1}"
+}
+
+# info_check() - Display and log messages for a single test condition check
+info_check() {
+	switch_pane ${PANE_INFO}
+
+	printf "${PR_YELLOW}?${PR_NC} ${@}" >> $STATEBASE/log_pipe
+	printf "? ${@}" >> "${LOGFILE}"
+}
+
+# info_check_passed() - Display and log a new line when a check passes
+info_check_passed() {
+	switch_pane ${PANE_INFO}
+
+	printf "\n" >> $STATEBASE/log_pipe
+	printf "\n" >> ${LOGFILE}
+}
+
+# info_check_failed() - Display and log messages when a check fails
+info_check_failed() {
+	switch_pane ${PANE_INFO}
+
+	printf " ${PR_RED}!${PR_NC}\n" >> $STATEBASE/log_pipe
+	printf " < failed.\n" >> "${LOGFILE}"
+}
+
+# info_passed() - Display, log, and make status bar blink when a test passes
+info_passed() {
+	switch_pane ${PANE_INFO}
+
+	info_nolog "...${PR_GREEN}passed${PR_NC}.\n"
+	log "...passed."
+	log
+
+	for i in `seq 1 3`; do
+		tmux set status-right-style 'bg=colour1 fg=colour2 bold'
+		sleep "0.1"
+		tmux set status-right-style 'bg=colour1 fg=colour233 bold'
+		sleep "0.1"
+	done
+}
+
+# info_failed() - Display, log, and make status bar blink when a test passes
+info_failed() {
+	switch_pane ${PANE_INFO}
+
+	info_nolog "...${PR_RED}failed${PR_NC}.\n"
+	log "...failed."
+	log
+
+	for i in `seq 1 3`; do
+		tmux set status-right-style 'bg=colour1 fg=colour196 bold'
+		sleep "0.1"
+		tmux set status-right-style 'bg=colour1 fg=colour233 bold'
+		sleep "0.1"
+	done
+
+	pause_continue \
+		"Press any key to pause test session"		\
+		"Resuming in "					\
+		"Paused, press any key to continue"		\
+		5
+}
+
+# info_skipped() - Display and log skipped test
+info_skipped() {
+	switch_pane ${PANE_INFO}
+
+	info_nolog "...${PR_YELLOW}skipped${PR_NC}.\n"
+	log "...skipped."
+	log
+}
+
+# info_layout() - Display string for new test layout
+info_layout() {
+	switch_pane ${PANE_INFO}
+
+	info_nolog "Test layout: ${PR_BLUE}${@}${PR_NC}.\n"
+}
+
+# status_test_ok() - Update counter of passed tests, log and display message
+status_test_ok() {
+	STATUS_PASS=$((STATUS_PASS + 1))
+	tmux set status-right "PASS: ${STATUS_PASS} | FAIL: ${STATUS_FAIL} | #(TZ="UTC" date -Iseconds)"
+	info_passed
+}
+
+# status_test_fail() - Update counter of failed tests, log and display message
+status_test_fail() {
+	STATUS_FAIL=$((STATUS_FAIL + 1))
+	tmux set status-right "PASS: ${STATUS_PASS} | FAIL: ${STATUS_FAIL} | #(TZ="UTC" date -Iseconds)"
+	info_failed
+}
+
+# status_test_fail() - Update counter of failed tests, log and display message
+status_test_skip() {
+	info_skipped
+}
+
+# table_header() - Print table header to log pane
+# $1:	Header description
+# $@:	Column headers
+table_header() {
+	perf_th ${@}
+
+	__ifs="${IFS}"
+	IFS=" "
+
+	__desc="${1}"
+	shift
+
+	__max_len=4
+	__count=0
+	for __h in ${@}; do
+		[ ${#__h} -gt ${__max_len} ] && __max_len=${#__h}
+		__count=$((__count + 1))
+	done
+
+	# > xxxx |<
+	__outer_len=$((__max_len + 3))
+	__width_fields=$((__outer_len * __count + 1))
+
+	TABLE_HEADER_LEFT=$((STATUS_COLS - __width_fields))
+	TABLE_CELL_SIZE=$((__max_len + 2))
+	TABLE_COLS=${__count}
+
+	__pad_left=$((TABLE_HEADER_LEFT - ${#__desc} - 2))
+	__buf="$(printf %-${__pad_left}s%s "" "${__desc}: ")"
+	for __h in ${@}; do
+		__pad_left=$(( (TABLE_CELL_SIZE - ${#__h} + 1) / 2))
+		__pad_right=$(( (TABLE_CELL_SIZE - ${#__h}) / 2))
+		__buf="${__buf}$(printf "|%-${__pad_left}s%s%-${__pad_right}s" "" ${__h} "")"
+	done
+
+	info_n "${__buf}|"
+
+	IFS="${__ifs}"
+}
+
+# table_row() - Print main table row to log pane
+# $@:	Column headers
+table_row() {
+	perf_tr ${@}
+
+	__line="${@}"
+	__buf="$(printf %-${TABLE_HEADER_LEFT}s "")"
+	for __i in $(seq 1 ${TABLE_COLS}); do
+		__buf="${__buf}|"
+		for __j in $(seq 1 ${TABLE_CELL_SIZE}); do
+			__buf="${__buf}-"
+		done
+	done
+	info_n "\n${__buf}|\n"
+
+	__pad_left=$(( (TABLE_HEADER_LEFT - ${#__line} + 1) / 2))
+	__pad_right=$(( (TABLE_HEADER_LEFT - ${#__line}) / 2))
+	info_n "$(printf "%-${__pad_left}s%s%-${__pad_right}s|" "" "${__line}" "")"
+}
+
+# table_line() - Print simple line to log pane
+# $@:	Column headers
+table_line() {
+	perf_tr ${@}
+
+	__line="${@}"
+	info_n "\n"
+
+	__pad_left=$(( (TABLE_HEADER_LEFT - ${#__line} + 1) / 2))
+	__pad_right=$(( (TABLE_HEADER_LEFT - ${#__line}) / 2))
+	info_n "$(printf "%-${__pad_left}s%s%-${__pad_right}s|" "" "${__line}" "")"
+}
+
+table_cell() {
+	__len="${1}"
+	shift
+
+	__content="${@}"
+
+	__pad_left=$((TABLE_CELL_SIZE - __len - 1))
+	info_n "$(printf "%-${__pad_left}s%s |" "" "${__content}")"
+}
+
+table_end() {
+	__buf="$(printf %-${TABLE_HEADER_LEFT}s "")"
+	for __i in $(seq 1 ${TABLE_COLS}); do
+		__buf="${__buf}'"
+		for __j in $(seq 1 ${TABLE_CELL_SIZE}); do
+			__buf="${__buf}-"
+		done
+	done
+	info_n "\n${__buf}'\n"
+}
+
+# table_value() - Print generic table value in its own cell
+# $1:	Value, can be '-' to indicate a filler
+# $2:	Scale, exponent of 10
+# $3:	Error value, scaled: if value is less than this, print in red
+# $4:	Warning value, scaled: if value is less than this, print in yellow
+table_value() {
+	[ "${1}" = "-" ] && table_cell 1 "-" && perf_td 0 "" && return 0
+	if [ "${2}" != "0" ]; then
+		__v="$(echo "scale=1; x=( ${1} + 10^$((${2} - 1)) / 2 ) / 10^${2}; if ( x < 1 && x > 0 ) print 0; x" | bc -l)"
+	else
+		__v="${1}"
+	fi
+	perf_td 0 "${__v}"
+
+	__red="${3}"
+	__yellow="${4}"
+	if [ "$(echo "${__v} < ${__red}" | bc -l)" = "1" ]; then
+		table_cell ${#__v} "${PR_RED}${__v}${PR_NC}"
+		return 1
+	elif [ "$(echo "${__v} < ${__yellow}" | bc -l)" = "1" ]; then
+		table_cell ${#__v} "${PR_YELLOW}${__v}${PR_NC}"
+		return 1
+	else
+		table_cell ${#__v} "${PR_GREEN}${__v}${PR_NC}"
+		return 0
+	fi
+}
+
+table_value_throughput() {
+	[ "${1}" = "-" ] && table_cell 1 "-" && perf_td 0 "" && return 0
+	__v="$(echo "scale=1; x=( ${1} + 10^8 / 2 ) / 10^9; if ( x < 1 && x > 0 ) print 0; x" | bc -l)"
+	perf_td 31 "${__v}"
+
+	__red="${2}"
+	__yellow="${3}"
+	if [ "$(echo "${__v} < ${__red}" | bc -l)" = "1" ]; then
+		table_cell ${#__v} "${PR_RED}${__v}${PR_NC}"
+		return 1
+	elif [ "$(echo "${__v} < ${__yellow}" | bc -l)" = "1" ]; then
+		table_cell ${#__v} "${PR_YELLOW}${__v}${PR_NC}"
+		return 1
+	else
+		table_cell ${#__v} "${PR_GREEN}${__v}${PR_NC}"
+		return 0
+	fi
+}
+
+table_value_latency() {
+	[ "${1}" = "-" ] && table_cell 1 "-" && perf_td 0 "" && return 0
+
+	__v="$(echo "scale=6; 1 / ${1} * 10^6" | bc -l)"
+	__v="${__v%.*}"
+
+	perf_td 11 "${__v}"
+
+	__red="${2}"
+	__yellow="${3}"
+	if [ "$(echo "${__v} > ${__red}" | bc -l)" = "1" ]; then
+		table_cell ${#__v} "${PR_RED}${__v}${PR_NC}"
+		return 1
+	elif [ "$(echo "${__v} > ${__yellow}" | bc -l)" = "1" ]; then
+		table_cell ${#__v} "${PR_YELLOW}${__v}${PR_NC}"
+		return 1
+	else
+		table_cell ${#__v} "${PR_GREEN}${__v}${PR_NC}"
+		return 0
+	fi
+}
+
+# pause_continue() - Pause for a while, wait for keystroke, resume on second one
+pause_continue() {
+	tmux select-pane -t ${PANE_INFO}
+	info_nolog "${1}"
+	info_nolog_n "${2}"
+
+	__pause_tmp="${STATEBASE}/pause.tmp"
+	echo > "${__pause_tmp}"
+	tmux pipe-pane -O -t ${PANE_INFO} "cat >> ${__pause_tmp}"
+	__pane_buf=
+	__wait=0
+	sleep 1
+	for __i in $(seq ${4} -1 0); do
+		if [ "$(tail -n1 ${__pause_tmp} | tr -d -c [:print:])" != "${__pane_buf}" ]; then
+			__wait=1
+			break
+		fi
+
+		if [ ${__i} -ne ${4} ]; then
+			tmux send-keys -t ${PANE_INFO} Bspace
+			tmux send-keys -t ${PANE_INFO} Bspace
+			__pane_buf="${__pane_buf}  "
+		fi
+		info_nolog_n "${__i} "
+		__pane_buf="${__pane_buf}${__i} "
+		sleep 1
+	done
+
+	if [ ${__wait} -eq 1 ]; then
+		tmux send-keys -t ${PANE_INFO} Bspace
+		tmux send-keys -t ${PANE_INFO} Bspace
+		info_nolog ""
+		info_nolog "${3}"
+		__pane_buf="$(tail -n1 ${__pause_tmp})"
+		while true; do
+			[ "$(tail -n1 ${__pause_tmp})" != "${__pane_buf}" ] && break
+			sleep 1
+		done
+	fi
+	tmux pipe-pane -O -t ${PANE_INFO} ""
+	rm "${__pause_tmp}"
+	info_nolog ""
+}
+
+# run_term() - Start tmux session, running entry point, with recording if needed
+run_term() {
+	TMUX="tmux new-session -s passt_test -eSTATEBASE=$STATEBASE -ePCAP=$PCAP -eDEBUG=$DEBUG"
+
+	if [ ${CI} -eq 1 ]; then
+		printf '\e[8;50;240t'
+		asciinema rec --overwrite "${STATEBASE}/ci.uncut" -c "$TMUX /bin/sh -c './ci from_term'"
+		video_postprocess "${STATEBASE}/ci.uncut"
+	elif [ ${DEMO} -eq 1 ]; then
+		printf '\e[8;40;130t'
+		asciinema rec --overwrite "${STATEBASE}/demo.uncut" -c "$TMUX /bin/sh -c './run_demo from_term'"
+		video_postprocess "${STATEBASE}/demo.uncut"
+	else
+		$TMUX /bin/sh -c "./run from_term ${*}"
+	fi
+}
+
+# term() - Set up terminal window and panes for regular tests or CI
+term() {
+	tmux set-option default-shell "/bin/sh"
+
+	tmux set status-interval 1
+	tmux rename-window ''
+
+	tmux set window-status-format '#W'
+	tmux set window-status-current-format '#W'
+	tmux set status-left ''
+	tmux set window-status-separator ''
+
+	tmux set window-status-style 'bg=colour1 fg=colour233 bold'
+	tmux set status-style 'bg=colour1 fg=colour233 bold'
+	tmux set status-right-style 'bg=colour1 fg=colour233 bold'
+
+	tmux new-window -n "Testing commit: ${COMMIT}"
+
+	tmux set window-status-format '#W'
+	tmux set window-status-current-format '#W'
+	tmux set status-left ''
+	tmux set window-status-separator ''
+
+	tmux set window-status-current-style 'bg=colour1 fg=colour233 bold'
+	tmux set status-right '#(TZ="UTC" date -Iseconds)'
+	tmux set status-right-length 50
+	tmux set status-right-style 'bg=colour1 fg=colour233 bold'
+
+	tmux set history-limit 500000
+	tmux select-pane -t 0 -T ''
+	tmux set pane-border-format '#T'
+	tmux set pane-border-style 'fg=colour2 bg=colour233'
+	tmux set pane-active-border-style 'fg=colour233 bg=colour4 bold'
+	tmux set pane-border-status bottom
+}
+
+# term_demo() - Set up terminal window and panes for demo
+term_demo() {
+	tmux set-option default-shell "/bin/sh"
+
+	tmux set status-interval 1
+	tmux rename-window ''
+
+	tmux set window-status-format '#W'
+	tmux set window-status-current-format '#W'
+	tmux set status-left ''
+	tmux set window-status-separator ''
+
+	tmux set window-status-style 'bg=colour1 fg=colour15 bold'
+	tmux set status-right ''
+	tmux set status-style 'bg=colour1 fg=colour15 bold'
+	tmux set status-right-style 'bg=colour1 fg=colour15 bold'
+
+	tmux new-window -n "Demo at commit: ${COMMIT}"
+
+	tmux set window-status-format '#W'
+	tmux set window-status-current-format '#W'
+	tmux set status-left ''
+	tmux set window-status-separator ''
+
+	tmux select-pane -t 0 -T ''
+	tmux set pane-border-format '#T'
+	tmux set pane-border-style 'fg=colour2 bg=colour233'
+	tmux set pane-active-border-style 'fg=colour15 bg=colour4 bold'
+	tmux set pane-border-status bottom
+}
diff --git a/oldtest/lib/test b/oldtest/lib/test
new file mode 100755
index 00000000..60bdae96
--- /dev/null
+++ b/oldtest/lib/test
@@ -0,0 +1,398 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/test - List tests and run them, evaluating directives from files
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+# test_iperf3() - Ugly helper for iperf3 directive
+# $1:	Variable name: to put the measure bandwidth into
+# $2:	Source/client context
+# $3:	Destination/server context
+# $4:	Destination name or address for client
+# $5:	Port number, ${i} is translated to process index
+# $6:	Number of processes to run in parallel
+# $7:	Run time, in seconds
+# $@:	Client options
+test_iperf3() {
+	__var="${1}"; shift
+	__cctx="${1}"; shift
+	__sctx="${1}"; shift
+	__dest="${1}"; shift
+	__port="${1}"; shift
+	__procs="$((${1} - 1))"; shift
+	__time="${1}"; shift
+
+	pane_or_context_run "${__sctx}" 'rm -f s*.json'
+
+	pane_or_context_run_bg "${__sctx}" 				\
+		 'for i in $(seq 0 '${__procs}'); do'			\
+		 '	(iperf3 -s1J -p'${__port}' -i'${__time}		\
+		 '	 > s${i}.json) &'				\
+		 '	echo $! > s${i}.pid &'				\
+		 'done'							\
+
+	sleep 1		# Wait for server to be ready
+
+	pane_or_context_run_bg "${__cctx}" 				\
+		 '('							\
+		 '	for i in $(seq 0 '${__procs}'); do'		\
+		 '		iperf3 -c '${__dest}' -p '${__port}	\
+		 '		 -t'${__time}' -i0 -T s${i} '"${@}"' &' \
+		 '	done;'						\
+		 '	wait'						\
+		 ')'
+
+	sleep $((__time + 5))
+
+	# If client fails to deliver control message, tell server we're done
+	pane_or_context_run "${__sctx}" 'kill -INT $(cat s*.pid); rm s*.pid'
+
+	sleep 1		# ...and wait for output to be flushed
+
+	__jval=".end.sum_received.bits_per_second"
+	for __opt in ${@}; do
+		# UDP test
+		[ "${__opt}" = "-u" ] && __jval=".intervals[0].sum.bits_per_second"
+	done
+
+	__bw=$(pane_or_context_output "${__sctx}"			\
+		 'cat s*.json | jq -rMs "map('${__jval}') | add"')
+
+	TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__var}__" "${__bw}" )"
+
+	sleep 3		# Wait for kernel to free up ports
+}
+
+test_one_line() {
+	__line="${1}"
+
+	[ ${DEBUG} -eq 1 ] && info DEBUG: "${__line}"
+
+	# Strip comments
+	__line="${__line%%#*}"
+
+	if [ -n "${TEST_ONE_in_def}" ]; then
+		[ "${__line}" = "endef" ] && TEST_ONE_in_def= && return
+		# Append $__line to variable TEST_ONE_DEF_<definition name>
+		__ifs="${IFS}"
+		IFS=
+		eval TEST_ONE_DEF_$TEST_ONE_in_def=\"\$\(printf \"%s\\n%s\" \"\$TEST_ONE_DEF_$TEST_ONE_in_def\" \"$__line\"\)\"
+		IFS="${__ifs}"
+		return
+	fi
+
+	# tab-split command and arguments, apply variable substitutions
+	__cmd="${__line%%$(printf '\t')*}"
+	__arg="${__line#*$(printf '\t')*}"
+	__arg="$(subs_apply "${TEST_ONE_subs}" "${__arg}")"
+
+	[ ${TEST_ONE_nok} -eq 1 ] && [ "${__cmd}" != "test" ] && continue
+	case ${__cmd} in
+	"def")
+		TEST_ONE_in_def="${__arg}"
+		# Clear variable TEST_ONE_DEF_<definition name>
+		__ifs="${IFS}"
+		IFS= eval TEST_ONE_DEF_$TEST_ONE_in_def=
+		IFS="${__ifs}"
+		;;
+	"test")
+		[ ${TEST_ONE_perf_nok} -eq 0 ] || TEST_ONE_nok=1
+		[ ${TEST_ONE_nok} -eq 1 ] && status_test_fail
+		[ ${TEST_ONE_nok} -eq 0 ] && status_test_ok
+
+		status_test_start "${__arg}"
+		TEST_ONE_nok=0
+		TEST_ONE_perf_nok=0
+		;;
+	"host")
+		pane_or_context_run host "${__arg}" || TEST_ONE_nok=1
+		;;
+	"hostb")
+		pane_or_context_run_bg host "${__arg}"
+		;;
+	"hostw")
+		pane_or_context_wait host || TEST_ONE_nok=1
+		;;
+	"hint")
+		tmux send-keys -t ${PANE_HOST} "C-c"
+		;;
+	"htools")
+		pane_or_context_run host 'which '"${__arg}"' >/dev/null' || TEST_ONE_skip=1
+		;;
+	"passt")
+		pane_or_context_run passt "${__arg}" || TEST_ONE_nok=1
+		;;
+	"passtb")
+		pane_or_context_run_bg passt "${__arg}"
+		;;
+	"passtw")
+		pane_or_context_wait passt || TEST_ONE_nok=1
+		;;
+	"pint")
+		tmux send-keys -t ${PANE_PASST} "C-c"
+		;;
+	"pout")
+		__varname="${__arg%% *}"
+		__output="$(pane_or_context_output passt "${__arg#* }")"
+		TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__varname}__" "${__output}")"
+		;;
+	"guest")
+		pane_or_context_run guest "${__arg}" || TEST_ONE_nok=1
+		;;
+	"guestb")
+		pane_or_context_run_bg guest "${__arg}"
+		;;
+	"guestw")
+		pane_or_context_wait guest || TEST_ONE_nok=1
+		;;
+	"guest1")
+		pane_or_context_run guest_1 "${__arg}" || TEST_ONE_nok=1
+		;;
+	"guest1b")
+		pane_or_context_run_bg guest_1 "${__arg}"
+		;;
+	"guest1w")
+		pane_or_context_wait guest_1 || TEST_ONE_nok=1
+		;;
+	"gtools")
+		pane_or_context_run guest 'which '"${__arg}"' >/dev/null' || TEST_ONE_skip=1
+		;;
+	"g1tools")
+		pane_or_context_run guest_1 'which '"${__arg}"' >/dev/null' || TEST_ONE_skip=1
+		;;
+	"g2tools")
+		pane_or_context_run guest_2 'which '"${__arg}"' >/dev/null' || TEST_ONE_skip=1
+		;;
+	"guest2")
+		pane_or_context_run guest_2 "${__arg}" || TEST_ONE_nok=1
+		;;
+	"guest2b")
+		pane_or_context_run_bg guest_2 "${__arg}"
+		;;
+	"guest2w")
+		pane_or_context_wait guest_2 || TEST_ONE_nok=1
+		;;
+	"ns")
+		pane_or_context_run ns "${__arg}" || TEST_ONE_nok=1
+		;;
+	"ns1")
+		pane_or_context_run ns1 "${__arg}" || TEST_ONE_nok=1
+		;;
+	"ns2")
+		pane_or_context_run ns2 "${__arg}" || TEST_ONE_nok=1
+		;;
+	"nsb")
+		pane_or_context_run_bg ns "${__arg}"
+		;;
+	"ns1b")
+		pane_or_context_run_bg ns1 "${__arg}"
+		;;
+	"ns2b")
+		pane_or_context_run_bg ns2 "${__arg}"
+		;;
+	"nsw")
+		pane_or_context_wait ns || TEST_ONE_nok=1
+		;;
+	"ns1w")
+		pane_or_context_wait ns1 || TEST_ONE_nok=1
+		;;
+	"ns2w")
+		pane_or_context_wait ns2 || TEST_ONE_nok=1
+		;;
+	"nstools")
+		pane_or_context_run ns 'which '"${__arg}"' >/dev/null' || TEST_ONE_skip=1
+		;;
+	"gout")
+		__varname="${__arg%% *}"
+		__output="$(pane_or_context_output guest "${__arg#* }")"
+		TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__varname}__" "${__output}")"
+		;;
+	"g1out")
+		__varname="${__arg%% *}"
+		__output="$(pane_or_context_output guest_1 "${__arg#* }")"
+		TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__varname}__" "${__output}")"
+		;;
+	"g2out")
+		__varname="${__arg%% *}"
+		__output="$(pane_or_context_output guest_2 "${__arg#* }")"
+		TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__varname}__" "${__output}")"
+		;;
+	"hout")
+		__varname="${__arg%% *}"
+		__output="$(pane_or_context_output host "${__arg#* }")"
+		TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__varname}__" "${__output}")"
+		;;
+	"nsout")
+		__varname="${__arg%% *}"
+		__output="$(pane_or_context_output ns "${__arg#* }")"
+		TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__varname}__" "${__output}")"
+		;;
+	"ns1out")
+		__varname="${__arg%% *}"
+		__output="$(pane_or_context_output ns1 "${__arg#* }")"
+		TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__varname}__" "${__output}")"
+		;;
+	"ns2out")
+		__varname="${__arg%% *}"
+		__output="$(pane_or_context_output ns2 "${__arg#* }")"
+		TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__varname}__" "${__output}")"
+		;;
+	"check")
+		info_check "${__arg}"
+		__nok=0
+		eval "${__arg} || __nok=1"
+		if [ ${__nok} -eq 1 ]; then
+			TEST_ONE_nok=1
+			info_check_failed
+		else
+			info_check_passed
+		fi
+		;;
+	"sleep")
+		sleep "${__arg}"
+		;;
+	"info")
+		info "${__arg}"
+		;;
+	"report")
+		perf_report ${__arg}
+		;;
+	"th")
+		table_header ${__arg}
+		;;
+	"tr")
+		table_row "${__arg}"
+		;;
+	"tl")
+		table_line "${__arg}"
+		;;
+	"te")
+		table_end
+		;;
+	"td")
+		table_value ${__arg} || TEST_ONE_perf_nok=1
+		;;
+	"bw")
+		table_value_throughput ${__arg} || TEST_ONE_perf_nok=1
+		;;
+	"lat")
+		table_value_latency ${__arg} || TEST_ONE_perf_nok=1
+		;;
+	"iperf3")
+		test_iperf3 ${__arg}
+		;;
+	"set")
+		TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__${__arg%% *}__" "${__arg#* }")"
+		;;
+
+	# Demo commands
+	"say")
+		text_write "${__arg}"
+		;;
+	"em")
+		em_write "${__arg}"
+		;;
+	"nl")
+		info_nolog ""
+		;;
+	"hl")
+		pane_highlight "${__arg}"
+		;;
+	"bsp")
+		text_backspace "${__arg}"
+		;;
+	"killp")
+		pane_kill "${__arg}"
+		;;
+	"resize")
+		pane_resize ${__arg}
+		;;
+	*)
+		__def_body="$(eval printf \"\$TEST_ONE_DEF_$__cmd\")"
+		if [ -n "${__def_body}" ]; then
+			__ifs="${IFS}"
+			IFS='
+'
+			for __def_line in ${__def_body}; do
+				IFS="${__ifs}" test_one_line "${__def_line}"
+			done
+			IFS="${__ifs}"
+		fi
+		;;
+	esac
+}
+
+# test_one() - Run a single test file evaluating directives
+# $1:	Name of test file, relative to oldtest/ directory
+test_one() {
+	TEST_ONE_dirclean=
+	__test_file="oldtest/${1}"
+
+	__type="$(file -b --mime-type ${__test_file})"
+	if [ "${__type}" = "text/x-shellscript" ]; then
+		status_file_start "${1}" 1
+		"${__test_file}" && status_test_ok || status_test_fail
+		return
+	fi
+
+	if [ ${DEMO} -eq 0 ]; then
+		__ntests="$(grep -c "^test$(printf '\t')" "${__test_file}")"
+		status_file_start "${1}" "${__ntests}"
+	fi
+
+	[ ${CI} -eq 1 ] && video_link "${1}"
+
+	TEST_ONE_subs="$(list_add_pair "" "__BASEPATH__" "${BASEPATH}")"
+	TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__STATESETUP__" "${STATESETUP}")"
+	STATEDIR="${STATEBASE}/${1}"
+	mkdir -p "${STATEDIR}"
+	TEST_ONE_subs="$(list_add_pair "${TEST_ONE_subs}" "__STATEDIR__" "${STATEDIR}")"
+	TEST_ONE_nok=-1
+	TEST_ONE_perf_nok=0
+	TEST_ONE_skip=0
+	TEST_ONE_in_def=
+	while IFS= read -r __line; do
+		test_one_line "${__line}"
+		[ ${TEST_ONE_skip} -eq 1 ] && break
+	done < "${__test_file}"
+
+	for __d in ${TEST_ONE_dirclean}; do
+		rm -rf ${__d}
+	done
+
+	[ ${DEMO} -eq 1 ] && return
+
+	[ ${TEST_ONE_skip} -eq 1 ] && status_test_skip && return
+	[ ${TEST_ONE_perf_nok} -eq 0 ] || TEST_ONE_nok=1
+	[ ${TEST_ONE_nok} -eq 0 ] && status_test_ok || status_test_fail
+}
+
+# test() - Build list of tests to run, in order, then issue test_one()
+# $@:	Test files to run, relative to oldtest/
+test() {
+	__list=
+
+	cd test
+	for __f; do
+		__type="$(file -b --mime-type ${__f})"
+		if [ "${__type}" = "text/x-shellscript" ]; then
+			__list="$(list_add "${__list}" "${__f}")"
+			continue
+		fi
+		__list="$(list_add "${__list}" "${__f}")"
+	done
+	cd ..
+
+	for __f in ${__list}; do
+		test_one "${__f}"
+	done
+}
diff --git a/oldtest/lib/util b/oldtest/lib/util
new file mode 100755
index 00000000..98cb9d8a
--- /dev/null
+++ b/oldtest/lib/util
@@ -0,0 +1,133 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/util - Convenience functions
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+# list_has() - Check whether a tab-separated list contains a given token
+# $1:	List
+# $2:	Token
+# Return: 0 if token was found or is empty, 1 otherwise
+list_has() {
+	[ -z "${2}" ] && return 0
+
+	__ifs="${IFS}"
+	IFS='	'
+	for __t in ${1}; do
+		[ "${__t}" = "${2}" ] && IFS="${__ifs}" && return 0
+	done
+
+	IFS="${__ifs}"
+	return 1
+}
+
+# list_add() - Add token to tab-separated list, unless it's already present
+# $1:	List
+# $2:	Token
+list_add() {
+	list_has "${1}" "${2}" && return
+	[ -n "${1}" ] && printf '%s\t%s\n' "${1}" "${2}" || printf '%s\n' "${2}"
+}
+
+# list_remove_pair() - Drop pair with given key if present
+# $1:	List
+# $2:	Key
+list_remove_pair()
+{
+	__ifs="${IFS}"
+	IFS='	'
+	__skip_next=0
+	for __t in ${1}; do
+		[ ${__skip_next} -eq 1 ] && __skip_next=0 && continue
+		[ "${__t}" = "${2}" ] && __skip_next=1 && continue
+		printf '%s\t' "${__t}"
+	done
+	printf "\n"
+	IFS="${__ifs}"
+}
+
+# list_add_pair() - Add token pair to list, replace if the first one is present
+# $1:	List
+# $2:	First token
+# $3:	Second token
+list_add_pair() {
+	[ -z "${3}" ] && return
+
+
+	if [ -n "${1}" ]; then
+		__new_list="$(list_remove_pair "${1}" "${2}")"
+		printf '%s\t%s\t%s' "${__new_list}" "${2}" "${3}"
+	else
+		printf '%s\t%s' "${2}" "${3}"
+	fi
+	printf "\n"
+}
+
+# list_has_all() - Check whether a list contains all given IFS-separated tokens
+# $1:	List
+# $2:	List of tokens
+# Return: 0 if list of tokens was found or is empty, 1 otherwise
+list_has_all() {
+	[ -z "${2}" ] && return 0
+
+	for __i in ${2}; do
+		list_has "${1}" "${__i}" || return 1
+	done
+	return 0
+}
+
+# file_def() - List of tokens tab-separated line from file, starting with key
+# $1:	Filename
+# $2:	Token
+file_def() {
+	sed -n 's/^'"${2}"'\t\(.*\)/\1/p' "${1}" | tr ' ' '\t'
+}
+
+# subs_apply() - Apply substitutions using a list of token pairs
+# $1:	List of substitutions
+# $2:	String where substitutions have to be applied
+subs_apply() {
+	__ifs="${IFS}"
+	IFS='	'
+	__newarg="${2}"
+	__s=
+	for __t in ${1}; do
+		[ -z "${__s}" ] && __s="${__t}" && continue
+
+		__et="$(printf '%s\n' "$__t" | sed -e 's/[\/&]/\\&/g')"
+		__es="$(printf '%s\n' "$__s" | sed -e 's/[]\/$*.^[]/\\&/g')"
+
+		__newarg="$(printf '%s' "${__newarg}" | sed "s/${__es}/${__et}/g")"
+		__s=
+	done
+
+	printf '%s' "${__newarg}"
+	IFS="${__ifs}"
+}
+
+# get_info_cols() - Get number of columns for info pane
+get_info_cols() {
+	__log_pane_cols=
+	__j=0
+	for __i in $(tmux list-panes -t passt_test:1.0 -F "#{pane_width}"); do
+		[ ${__j} -eq ${PANE_INFO} ] && STATUS_COLS=${__i} && break
+		__j=$((__j + 1))
+	done
+}
+
+# wait_for() - Retry a command until it succeeds
+# $@:	Command to run
+wait_for() {
+        while ! "$@"; do
+                sleep 0.1 || sleep 1
+        done
+}
diff --git a/oldtest/lib/video b/oldtest/lib/video
new file mode 100755
index 00000000..320d4a06
--- /dev/null
+++ b/oldtest/lib/video
@@ -0,0 +1,152 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/lib/video - Session recording, JavaScript fragments with links
+#
+# Copyright (c) 2021-2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+VIDEO_START_SECONDS=
+VIDEO_NAME=
+
+VIDEO_LINKS_TEMPLATE="document.write('"'
+	Skip to:
+'
+
+VIDEO_LINKS_TEMPLATE_JS="
+');
+
+var video___VIDEO_NAME__links = [
+"
+
+VIDEO_LINKS_TEMPLATE_POST='];
+
+for (var i = 0; i < video___VIDEO_NAME__links.length; i++) {
+	var obj = document.getElementById(video___VIDEO_NAME__links[i][0]);
+
+	obj.addEventListener("click", function(event) {
+		var __VIDEO_NAME___div = document.getElementById("__VIDEO_NAME__");
+		var top = __VIDEO_NAME___div.offsetTop - 5;
+		var seek;
+
+		for (var i = 0; i < video___VIDEO_NAME__links.length; i++) {
+			if (this.id == video___VIDEO_NAME__links[i][0]) {
+				seek = video___VIDEO_NAME__links[i][1];
+			}
+		}
+
+		event.preventDefault();
+		__VIDEO_NAME___player.dispose();
+		__VIDEO_NAME___player = AsciinemaPlayer.create(
+			"/builds/latest/web/__VIDEO_NAME__.cast",
+			__VIDEO_NAME___div,
+			{ cols: 240, rows: 51, poster: "npt:999:0", startAt: seek, autoplay: true });
+
+		window.scrollTo({ top: top, behavior: "smooth" })
+	}, false);
+}
+'
+
+VIDEO_LINKS_BUF=
+VIDEO_LINKS_COUNT=0
+
+# video_append_links() - Append generic string to JavaScript links file
+video_append_links()
+{
+        __web="${LOGDIR}/web"
+	printf "${@}" >> "${__web}/${VIDEO_NAME}.js"
+}
+
+# video_append_links() - Append generic string to buffer for links
+video_append_links_js()
+{
+	VIDEO_LINKS_BUF="${VIDEO_LINKS_BUF}${@}"
+}
+
+# video_start() - Mark start of a test in capture, record start timestamp
+video_start() {
+	VIDEO_NAME="${1}"
+        __web="${LOGDIR}/web"
+        mkdir -p "${__web}"
+	echo "${VIDEO_LINKS_TEMPLATE}" > "${__web}/${VIDEO_NAME}.js"
+	VIDEO_START_SECONDS=$(sed -n 's/\([0-9]*\).[0-9]* [0-9]*.[0-9]*/\1/p' /proc/uptime)
+
+	sync
+	[ ${DEMO} -eq 1 ] && tail -1 "${STATEBASE}/demo.uncut" > "${STATEBASE}/${VIDEO_NAME}.start"
+	[ ${CI} -eq 1 ] && tail -1 "${STATEBASE}/ci.uncut" > "${STATEBASE}/${VIDEO_NAME}.start"
+	sync
+
+	tmux refresh-client
+}
+
+# video_stop() - Mark stop of a test in capture, finalise JavaScript fragments
+video_stop() {
+        __web="${LOGDIR}/web"
+	tmux refresh-client
+
+	sync
+	[ ${DEMO} -eq 1 ] && tail -1 "${STATEBASE}/demo.uncut" > "${STATEBASE}/${VIDEO_NAME}.stop"
+	[ ${CI} -eq 1 ] && tail -1 "${STATEBASE}/ci.uncut" > "${STATEBASE}/${VIDEO_NAME}.stop"
+	sync
+
+	sed -i 's/^.*$/&\\/g' "${__web}/${VIDEO_NAME}.js"
+	echo "${VIDEO_LINKS_TEMPLATE_JS}" | sed "s/__VIDEO_NAME__/${VIDEO_NAME}/g" >> "${__web}/${VIDEO_NAME}.js"
+	echo "${VIDEO_LINKS_BUF}" >> "${__web}/${VIDEO_NAME}.js"
+	echo "${VIDEO_LINKS_TEMPLATE_POST}"  | sed "s/__VIDEO_NAME__/${VIDEO_NAME}/g" >> "${__web}/${VIDEO_NAME}.js"
+}
+
+# video_postprocess() - Cut terminal recordings based on .start and .stop files
+video_postprocess() {
+	IFS='
+'
+        __web="${LOGDIR}/web"
+	__cast_name=
+	for __l in $(cat ${1}); do
+		[ -z "${__header}" ] && __header="${__l}" && continue
+
+		if [ -z "${__cast_name}" ]; then
+		        for __cast_cut in "${STATEBASE}/"*.start; do
+				[ "${__l}" != "$(cat "${__cast_cut}")" ] && continue
+				__cast_name="$(basename "${__cast_cut}")"
+                                __cast_name="${__cast_name%.start}"
+				__cast_offset=
+				__stop_line="$(cat "${STATEBASE}/${__cast_name}.stop")"
+				echo "${__header}" > "${__web}/${__cast_name}.cast"
+				break
+			done
+			continue
+		fi
+
+		[ "${__l}" = "${__stop_line}" ] && __cast_name= && continue
+
+		__l_offset="$(echo ${__l%%.*}|tr -c -d '[:digit:]')"
+		__l_rest="${__l#*.}"
+		[ -z "${__cast_offset}" ] && __cast_offset=${__l_offset}
+		__l_offset=$((__l_offset - __cast_offset))
+		printf '[%s.%s\n' "${__l_offset}" "${__l_rest}" >> "${__web}/${__cast_name}".cast
+	done
+	unset IFS
+}
+
+# video_time_now() - Print current video timestamp, in seconds
+video_time_now() {
+	__now=$(sed -n 's/\([0-9]*\).[0-9]* [0-9]*.[0-9]*/\1/p' /proc/uptime)
+	echo $((__now - VIDEO_START_SECONDS))
+}
+
+# video_link() - Append single link to given video chapter
+video_link() {
+	[ ${VIDEO_LINKS_COUNT} -eq 0 ] && __sep="" || __sep=" |"
+	__id="video_link_${VIDEO_LINKS_COUNT}"
+	video_append_links "${__sep} <a id=\"${__id}\" href=\"${1}\">${1}</a>"
+	video_append_links_js "[ '${__id}', $(($(video_time_now) - 1)) ],"
+
+	VIDEO_LINKS_COUNT=$((VIDEO_LINKS_COUNT + 1))
+}
diff --git a/oldtest/memory/passt b/oldtest/memory/passt
new file mode 100644
index 00000000..1193af86
--- /dev/null
+++ b/oldtest/memory/passt
@@ -0,0 +1,187 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/memory/passt - Show memory usage of passt in kernel and userspace
+#
+# Copyright (c) 2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools	sed cat diff nm sort kill tee head tail chroot unshare mount mkdir cp 
+
+def	meminfo_row
+gout	DIFF meminfo_diff /tmp/meminfo.before /tmp/meminfo.after __WHAT__
+tl	__NAME__
+td	__DIFF__ 3 0 0
+endef
+
+def	meminfo_reverse_row
+gout	DIFF meminfo_diff /tmp/meminfo.after /tmp/meminfo.before __WHAT__
+tl	__NAME__
+td	__DIFF__ 3 0 0
+endef
+
+def	nm_row
+gout	SIZE nm_size /tmp/nm.size __WHAT__
+tl	__WHAT__
+td	__SIZE__ 6 0 0
+endef
+
+def	slab_row
+gout	COUNT slab_diff_count /tmp/slabinfo.before /tmp/slabinfo.after __WHAT__
+gout	SIZE slab_size /tmp/slabinfo.before __WHAT__
+gout	DIFF slab_diff_size /tmp/slabinfo.before /tmp/slabinfo.after __WHAT__
+tl	__WHAT__
+td	__COUNT__ 0 0 0
+td	__SIZE__ 0 0 0
+td	__DIFF__ 6 0 0
+endef
+
+def	start_stop_diff
+guest	sed /proc/slabinfo -ne 's/^\([^ ]* *[^ ]* *[^ ]* *[^ ]*\).*/\\\1/p' > /tmp/slabinfo.before
+guest	cat /proc/meminfo > /tmp/meminfo.before
+guest	/bin/passt.avx2 -l /tmp/log -s /tmp/sock -P /tmp/pid __OPTS__ --netns-only
+sleep	2
+guest	cat /proc/meminfo > /tmp/meminfo.after
+guest	sed /proc/slabinfo -ne 's/^\([^ ]* *[^ ]* *[^ ]* *[^ ]*\).*/\\\1/p' > /tmp/slabinfo.after
+guest	kill \$(cat /tmp/pid)
+guest	diff -y --suppress-common-lines /tmp/meminfo.before /tmp/meminfo.after || :
+guest	nm -td -Sr --size-sort -P /bin/passt.avx2 | head -30 | tee /tmp/nm.size
+guest	sed /proc/slabinfo -ne 's/\(.*<objsize>\).*$/\1/p' | tail -1; (diff -y --suppress-common-lines /tmp/slabinfo.before /tmp/slabinfo.after | sort -grk8)
+endef
+
+def	summary
+info	Memory usage summary
+info	
+th	type MiB
+set	WHAT MemFree
+set	NAME used
+meminfo_reverse_row
+set	WHAT AnonPages
+set	NAME userspace
+meminfo_row
+set	WHAT Slab
+set	NAME kernel
+meminfo_row
+te
+endef
+
+
+guest	mkdir /test
+guest	mount -t tmpfs none /test
+guest	mkdir /test/proc /test/dev /test/tmp
+guest	mount -o bind /proc /test/proc
+guest	mount -o bind /dev /test/dev
+guest	cp -Lr /bin /lib /lib64 /usr /sbin /test/
+
+guest	ulimit -Hn 300000
+guest	unshare -rUm -R /test
+guest	chroot .
+
+guest	meminfo_size() { grep "^$2:" $1 | tr -s ' ' | cut -f2 -d ' '; }
+guest	meminfo_diff() { echo $(( $(meminfo_size $2 $3) - $(meminfo_size $1 $3) )); }
+
+guest	nm_size() { grep -m1 "^$2 " $1 | cut -f4 -d ' '; }
+
+guest	slab_count() { grep "^$2 " $1 | tr -s ' ' | cut -f3 -d ' '; }
+guest	slab_size() { grep "^$2 " $1 | tr -s ' ' | cut -f4 -d ' '; }
+guest	slab_diff_count() { echo $(( $(slab_count $2 $3) - $(slab_count $1 $3) )); }
+guest	slab_diff_size() { echo $(( $(slab_count $2 $3) * $(slab_size $2 $3) - $(slab_count $1 $3) * $(slab_size $1 $3) )); }
+
+
+test	Memory usage: all TCP and UDP ports forwarded, IPv4 and IPv6
+set	OPTS -t all -u all
+start_stop_diff
+summary
+
+info	Userspace memory detail
+info	
+th	symbol MiB
+set	WHAT tcp_buf_discard
+nm_row
+set	WHAT tcp6_l2_buf
+nm_row
+set	WHAT tcp4_l2_buf
+nm_row
+set	WHAT tc
+nm_row
+set	WHAT pkt_buf
+nm_row
+set	WHAT udp_splice_map
+nm_row
+set	WHAT udp6_l2_buf
+nm_row
+set	WHAT udp4_l2_buf
+nm_row
+set	WHAT udp_tap_map
+nm_row
+set	WHAT icmp_id_map
+nm_row
+set	WHAT udp_splice_buf
+nm_row
+set	WHAT tc_hash
+nm_row
+set	WHAT pool_tap6_storage
+nm_row
+set	WHAT pool_tap4_storage
+nm_row
+set	WHAT tap6_l4
+nm_row
+set	WHAT tap4_l4
+nm_row
+te
+
+info	Kernel memory detail
+info	
+th	objects count size MiB
+set	WHAT pid
+slab_row
+set	WHAT dentry
+slab_row
+set	WHAT Acpi-Parse
+slab_row
+set	WHAT kmalloc-64
+slab_row
+set	WHAT kmalloc-32
+slab_row
+set	WHAT lsm_file_cache
+slab_row
+set	WHAT filp
+slab_row
+set	WHAT anon_vma_chain
+slab_row
+set	WHAT ep_head
+slab_row
+set	WHAT sock_inode_cache
+slab_row
+set	WHAT signal_cache
+slab_row
+set	WHAT TCPv6
+slab_row
+set	WHAT TCP
+slab_row
+set	WHAT UDPv6
+slab_row
+te
+
+
+test	Memory usage: all TCP ports forwarded, IPv4
+set	OPTS -t all -4
+start_stop_diff
+summary
+
+
+test	Memory usage: all TCP and UDP ports forwarded, IPv4
+set	OPTS -t all -u all -4
+start_stop_diff
+summary
+
+
+test	Memory usage: no ports forwarded
+set	OPTS -t none -u none
+start_stop_diff
+summary
diff --git a/oldtest/nstool.c b/oldtest/nstool.c
new file mode 100644
index 00000000..e6d7d37f
--- /dev/null
+++ b/oldtest/nstool.c
@@ -0,0 +1,565 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/* nstool - maintain a namespace to be entered by other processes
+ *
+ * Copyright Red Hat
+ * Author: David Gibson <david@gibson.dropbear.id.au>
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+#include <sys/prctl.h>
+#include <linux/un.h>
+#include <sched.h>
+#include <linux/capability.h>
+
+#define	ARRAY_SIZE(a)	((int)(sizeof(a) / sizeof((a)[0])))
+
+#define die(...)				\
+	do {					\
+		fprintf(stderr, __VA_ARGS__);	\
+		exit(1);			\
+	} while (0)
+
+struct ns_type {
+	int flag;
+	const char *name;
+};
+
+const struct ns_type nstypes[] = {
+	{ CLONE_NEWCGROUP, "cgroup" },
+	{ CLONE_NEWIPC, "ipc" },
+	{ CLONE_NEWNET, "net" },
+	{ CLONE_NEWNS, "mnt" },
+	{ CLONE_NEWPID, "pid" },
+	{ CLONE_NEWTIME, "time" },
+	{ CLONE_NEWUSER, "user" },
+	{ CLONE_NEWUTS, "uts" },
+};
+
+#define for_each_nst(_nst, _flags)				\
+	for ((_nst) = &nstypes[0];				\
+	     ((_nst) - nstypes) < ARRAY_SIZE(nstypes);		\
+	     (_nst)++)						\
+		if ((_flags) & (_nst)->flag)
+
+#define for_every_nst(_nst)	for_each_nst(_nst, INT_MAX)
+
+#define NSTOOL_MAGIC	0x7570017575601d75ULL
+
+struct holder_info {
+	uint64_t magic;
+	pid_t pid;
+	uid_t uid;
+	gid_t gid;
+	char cwd[PATH_MAX];
+};
+
+static void usage(void)
+{
+	die("Usage:\n"
+	    "  nstool hold SOCK\n"
+	    "    Run within a set of namespaces, open a Unix domain socket\n"
+	    "    (the \"control socket\") at SOCK and wait for requests from\n"
+	    "    other nstool subcommands.\n"
+	    "  nstool info [-pw] pid SOCK\n"
+	    "    Print information about the nstool hold process with control\n"
+	    "    socket at SOCK\n"
+	    "      -p          Print just the holder's PID as seen by the caller\n"
+	    "      -w          Retry connecting to SOCK until it is ready\n"
+	    "  nstool exec [--keep-caps] SOCK [COMMAND [ARGS...]]\n"
+	    "    Execute command or shell in the namespaces of the nstool hold\n"
+	    "    with control socket at SOCK\n"
+	    "      --keep-caps Give all possible capabilities to COMMAND via\n"
+	    "                  the ambient capability mask\n"
+	    "  nstool stop SOCK\n"
+	    "    Instruct the nstool hold with control socket at SOCK to\n"
+	    "    terminate.\n");
+}
+
+static int connect_ctl(const char *sockpath, bool wait,
+		       struct holder_info *info,
+		       struct ucred *peercred)
+{
+	int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNIX);
+	struct sockaddr_un addr = {
+		.sun_family = AF_UNIX,
+	};
+	struct holder_info discard;
+	ssize_t len;
+	int rc;
+
+	if (fd < 0)
+		die("socket(): %s\n", strerror(errno));
+
+	strncpy(addr.sun_path, sockpath, UNIX_PATH_MAX);
+
+	do {
+		rc = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
+		if (rc < 0 &&
+		    (!wait || (errno != ENOENT && errno != ECONNREFUSED)))
+			die("connect() to %s: %s\n", sockpath, strerror(errno));
+	} while (rc < 0);
+
+	if (!info)
+		info = &discard;
+
+	/* Always read the info structure, even if we don't need it,
+	 * so that the holder doesn't get a broken pipe error
+	 */
+	len = read(fd, info, sizeof(*info));
+	if (len < 0)
+		die("read() on control socket %s: %s\n", sockpath, strerror(errno));
+	if ((size_t)len < sizeof(*info))
+		die("short read() on control socket %s\n", sockpath);
+
+	if (info->magic != NSTOOL_MAGIC)
+		die("Control socket %s doesn't appear to belong to nstool\n",
+		    sockpath);
+
+	if (peercred) {
+		socklen_t optlen = sizeof(*peercred);
+
+		rc = getsockopt(fd, SOL_SOCKET, SO_PEERCRED,
+				peercred, &optlen);
+		if (rc < 0)
+			die("getsockopet(SO_PEERCRED) %s: %s\n",
+			    sockpath, strerror(errno));
+	}
+
+	return fd;
+}
+
+static void cmd_hold(int argc, char *argv[])
+{
+	int fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, PF_UNIX);
+	struct sockaddr_un addr = {
+		.sun_family = AF_UNIX,
+	};
+	const char *sockpath = argv[1];
+	struct holder_info info;
+	int rc;
+
+	if (argc != 2)
+		usage();
+
+	if (fd < 0)
+		die("socket(): %s\n", strerror(errno));
+
+	strncpy(addr.sun_path, sockpath, UNIX_PATH_MAX);
+
+	rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+	if (rc < 0)
+		die("bind() to %s: %s\n", sockpath, strerror(errno));
+
+	rc = listen(fd, 0);
+	if (rc < 0)
+		die("listen() on %s: %s\n", sockpath, strerror(errno));
+
+	info.magic = NSTOOL_MAGIC;
+	info.pid = getpid();
+	info.uid = getuid();
+	info.gid = getgid();
+	if (!getcwd(info.cwd, sizeof(info.cwd)))
+		die("getcwd(): %s\n", strerror(errno));
+
+	do {
+		int afd = accept(fd, NULL, NULL);
+		char buf;
+
+		if (afd < 0)
+			die("accept(): %s\n", strerror(errno));
+
+		rc = write(afd, &info, sizeof(info));
+		if (rc < 0)
+			die("write(): %s\n", strerror(errno));
+		if ((size_t)rc < sizeof(info))
+			die("short write() on control socket\n");
+
+		rc = read(afd, &buf, sizeof(buf));
+		if (rc < 0)
+			die("read(): %s\n", strerror(errno));
+	} while (rc == 0);
+
+	unlink(sockpath);
+}
+
+static ssize_t getlink(char *buf, size_t bufsiz, const char *fmt, ...)
+{
+	char linkpath[PATH_MAX];
+	ssize_t linklen;
+	va_list ap;
+
+	va_start(ap, fmt);
+	if (vsnprintf(linkpath, sizeof(linkpath), fmt, ap) >= PATH_MAX)
+		die("Truncated path \"%s\"\n", linkpath);
+	va_end(ap);
+
+	linklen = readlink(linkpath, buf, bufsiz);
+	if (linklen < 0)
+		die("readlink() on %s: %s\n", linkpath, strerror(errno));
+	if ((size_t)linklen >= bufsiz)
+		die("Target of symbolic link %s is too long\n", linkpath);
+
+	return linklen;
+}
+
+static int detect_namespaces(pid_t pid)
+{
+	const struct ns_type *nst;
+	int flags = 0;
+
+	for_every_nst(nst) {
+		char selflink[PATH_MAX], pidlink[PATH_MAX];
+		ssize_t selflen, pidlen;
+
+		selflen = getlink(selflink, sizeof(selflink),
+				  "/proc/self/ns/%s", nst->name);
+		pidlen = getlink(pidlink, sizeof(pidlink),
+				 "/proc/%d/ns/%s", pid, nst->name);
+
+		if ((selflen != pidlen) || memcmp(selflink, pidlink, selflen))
+			flags |= nst->flag;
+	}
+
+	return flags;
+}
+
+static void print_nstypes(int flags)
+{
+	const struct ns_type *nst;
+	bool first = true;
+
+	for_each_nst(nst, flags) {
+		printf("%s%s", first ? "" : ", " , nst->name);
+		first = false;
+		flags &= ~nst->flag;
+	}
+
+	if (flags)
+		printf("%s0x%x", first ? "" : ", ", flags);
+}
+
+static void cmd_info(int argc, char *argv[])
+{
+	const struct option options[] = {
+		{"pid",		no_argument, 	NULL,	'p' },
+		{"wait",	no_argument,	NULL,	'w' },
+		{ 0 },
+	};
+	bool pidonly = false, waitforsock = false;
+	const char *optstring = "pw";
+	struct holder_info info;
+	struct ucred peercred;
+	const char *sockpath;
+	int fd, opt;
+
+	do {
+		opt = getopt_long(argc, argv, optstring, options, NULL);
+
+		switch (opt) {
+		case 'p':
+			pidonly = true;
+			break;
+		case 'w':
+			waitforsock = true;
+			break;
+		case -1:
+			break;
+		default:
+			usage();
+		}
+	} while (opt != -1);
+
+	if (optind != argc - 1) {
+		usage();
+	}
+
+	sockpath = argv[optind];
+
+	fd = connect_ctl(sockpath, waitforsock, &info, &peercred);
+
+	close(fd);
+
+	if (pidonly) {
+		printf("%d\n", peercred.pid);
+	} else {
+		int flags = detect_namespaces(peercred.pid);
+
+		printf("Namespaces: ");
+		print_nstypes(flags);
+		printf("\n");
+
+		printf("As seen from calling context:\n");
+		printf("\tPID:\t%d\n", peercred.pid);
+		printf("\tUID:\t%u\n", peercred.uid);
+		printf("\tGID:\t%u\n", peercred.gid);
+
+		printf("As seen from holding context:\n");
+		printf("\tPID:\t%d\n", info.pid);
+		printf("\tUID:\t%u\n", info.uid);
+		printf("\tGID:\t%u\n", info.gid);
+		printf("\tCWD:\t%s\n", info.cwd);
+	}
+}
+
+static int openns(const char *fmt, ...)
+{
+	char nspath[PATH_MAX];
+	va_list ap;
+	int fd;
+
+	va_start(ap, fmt);
+	if (vsnprintf(nspath, sizeof(nspath), fmt, ap) >= PATH_MAX)
+		die("Truncated path \"%s\"\n", nspath);
+	va_end(ap);
+
+	fd = open(nspath, O_RDONLY | O_CLOEXEC);
+	if (fd < 0)
+		die("open() %s: %s\n", nspath, strerror(errno));
+
+	return fd;
+}
+
+static void wait_for_child(pid_t pid)
+{
+	int status;
+
+	/* Match the child's exit status, if possible */
+	for (;;) {
+		pid_t rc;
+
+		rc = waitpid(pid, &status, WUNTRACED);
+		if (rc < 0)
+			die("waitpid() on %d: %s\n", pid, strerror(errno));
+		if (rc != pid)
+			die("waitpid() on %d returned %d", pid, rc);
+		if (WIFSTOPPED(status)) {
+			/* Stop the parent to patch */
+			kill(getpid(), SIGSTOP);
+			/* We must have resumed, resume the child */
+			kill(pid, SIGCONT);
+			continue;
+		}
+
+		break;
+	}
+
+	if (WIFEXITED(status))
+		exit(WEXITSTATUS(status));
+	else if (WIFSIGNALED(status))
+		kill(getpid(), WTERMSIG(status));
+
+	die("Unexpected status for child %d\n", pid);
+}
+
+static void caps_to_ambient(void)
+{
+	/* Use raw system calls to avoid the overly complex caps
+	 * libraries. */
+	struct __user_cap_header_struct header = {
+		.version = _LINUX_CAPABILITY_VERSION_3,
+		.pid = 0,
+	};
+	struct __user_cap_data_struct payload[_LINUX_CAPABILITY_U32S_3] =
+		{{ 0 }};
+	uint64_t effective, cap;
+
+	if (syscall(SYS_capget, &header, payload) < 0)
+		die("capget(): %s\n", strerror(errno));
+
+	/* First make caps inheritable */
+	payload[0].inheritable = payload[0].permitted;
+	payload[1].inheritable = payload[1].permitted;
+
+	if (syscall(SYS_capset, &header, payload) < 0)
+		die("capset(): %s\n", strerror(errno));
+
+	effective = ((uint64_t)payload[1].effective << 32) | (uint64_t)payload[0].effective;
+
+	for (cap = 0; cap < (sizeof(effective) * 8); cap++) {
+		/* Skip non-existent caps */
+		if (prctl(PR_CAPBSET_READ, cap, 0, 0, 0) < 0)
+			continue;
+
+		if ((effective & (1 << cap))
+		    && prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, cap, 0, 0) < 0)
+			die("prctl(PR_CAP_AMBIENT): %s\n", strerror(errno));
+	}
+}
+
+static void cmd_exec(int argc, char *argv[])
+{
+	enum {
+		OPT_EXEC_KEEPCAPS = CHAR_MAX + 1,
+	};
+	const struct option options[] = {
+		{"keep-caps",	no_argument, 	NULL,	OPT_EXEC_KEEPCAPS },
+		{ 0 },
+	};
+	const char *shargs[] = { NULL, NULL };
+	const char *sockpath = argv[1];
+	int nfd[ARRAY_SIZE(nstypes)];
+	const char *optstring = "";
+	const struct ns_type *nst;
+	int ctlfd, flags, opt, rc;
+	const char *const *xargs;
+	struct holder_info info;
+	bool keepcaps = false;
+	struct ucred peercred;
+	const char *exe;
+	pid_t xpid;
+
+	do {
+		opt = getopt_long(argc, argv, optstring, options, NULL);
+
+		switch (opt) {
+		case OPT_EXEC_KEEPCAPS:
+			keepcaps = true;
+			break;
+		case -1:
+			break;
+		default:
+			usage();
+		}
+	} while (opt != -1);
+
+	if (argc < optind + 1)
+		usage();
+
+	sockpath = argv[optind];
+
+	ctlfd = connect_ctl(sockpath, false, &info, &peercred);
+
+	flags = detect_namespaces(peercred.pid);
+
+	for_each_nst(nst, flags) {
+		int *fd = &nfd[nst - nstypes];
+		*fd = openns("/proc/%d/ns/%s", peercred.pid, nst->name);
+	}
+
+	/* First pass, will get things where we need the privileges of
+	 * the initial userns */
+	for_each_nst(nst, flags) {
+		int fd = nfd[nst - nstypes];
+
+		rc = setns(fd, nst->flag);
+		if (rc == 0) {
+			flags &= ~nst->flag;
+		}
+	}
+
+	/* Second pass, will get things where we need the privileges
+	 * of the target userns */
+	for_each_nst(nst, flags) {
+		int fd = nfd[nst - nstypes];
+
+		rc = setns(fd, nst->flag);
+		if (rc < 0)
+			die("setns() type %s: %s\n",
+			    nst->name, strerror(errno));
+	}
+
+	/* If we've entered a mount ns, our cwd has changed to /.
+	 * Switch to the cwd of the holder, which is probably less
+	 * surprising. */
+	if (flags & CLONE_NEWNS) {
+		rc = chdir(info.cwd);
+		if (rc < 0)
+			die("chdir(\"%s\"): %s\n", info.cwd, strerror(errno));
+	}
+
+	/* Fork to properly enter PID namespace */
+	xpid = fork();
+	if (xpid < 0)
+		die("fork(): %s\n", strerror(errno));
+
+	if (xpid > 0) {
+		/* Close the control socket so the waiting parent
+		 * doesn't block the holder */
+		close(ctlfd);
+		wait_for_child(xpid);
+	}
+
+	/* CHILD */
+	if (argc > optind + 1) {
+		exe = argv[optind + 1];
+		xargs = (const char * const*)(argv + optind + 1);
+	} else {
+		exe = getenv("SHELL");
+		if (!exe)
+			exe = "/bin/sh";
+
+		shargs[0] = exe;
+
+		xargs = shargs;
+	}
+
+	if (keepcaps)
+		caps_to_ambient();
+
+	rc = execvp(exe, (char *const *)xargs);
+	if (rc < 0)
+		die("execv() %s: %s\n", exe, strerror(errno));
+	die("Returned from exec()\n");
+}
+
+static void cmd_stop(int argc, char *argv[])
+{
+	const char *sockpath = argv[1];
+	int fd, rc;
+	char buf = 'Q';
+
+	if (argc != 2)
+		usage();
+
+	fd = connect_ctl(sockpath, false, NULL, NULL);
+
+	rc = write(fd, &buf, sizeof(buf));
+	if (rc < 0)
+		die("write() to %s: %s\n", sockpath, strerror(errno));
+
+	close(fd);
+}
+
+int main(int argc, char *argv[])
+{
+	const char *subcmd = argv[1];
+	int fd;
+
+	if (argc < 2)
+		usage();
+
+	fd = socket(AF_UNIX, SOCK_STREAM, PF_UNIX);
+	if (fd < 0)
+		die("socket(): %s\n", strerror(errno));
+
+	if (strcmp(subcmd, "hold") == 0)
+		cmd_hold(argc - 1, argv + 1);
+	else if (strcmp(subcmd, "info") == 0)
+		cmd_info(argc - 1, argv + 1);
+	else if (strcmp(subcmd, "exec") == 0)
+		cmd_exec(argc - 1, argv + 1);
+	else if (strcmp(subcmd, "stop") == 0)
+		cmd_stop(argc - 1, argv + 1);
+	else
+		usage();
+
+	exit(0);
+}
diff --git a/oldtest/passt.mbuto b/oldtest/passt.mbuto
new file mode 100755
index 00000000..90816d20
--- /dev/null
+++ b/oldtest/passt.mbuto
@@ -0,0 +1,83 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# test/passt.mbuto - mbuto (https://mbuto.sh) profile for test images
+#
+# Copyright (c) 2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+PROGS="${PROGS:-ash,dash,bash ip mount ls insmod mkdir ln cat chmod lsmod
+       modprobe find grep mknod mv rm umount jq iperf3 dhclient hostname
+       sed tr chown sipcalc cut socat dd strace ping tail killall sleep sysctl
+       nproc tcp_rr tcp_crr udp_rr which tee seq bc sshd ssh-keygen cmp}"
+
+KMODS="${KMODS:- virtio_net virtio_pci vmw_vsock_virtio_transport}"
+
+LINKS="${LINKS:-
+	 ash,dash,bash		/init
+	 ash,dash,bash		/bin/sh}"
+
+DIRS="${DIRS} /tmp /sbin /usr/share /var/log /var/lib /etc/ssh /run/sshd /root/.ssh"
+
+COPIES="${COPIES} small.bin,/root/small.bin medium.bin,/root/medium.bin big.bin,/root/big.bin"
+
+FIXUP="${FIXUP}"'
+	cat > /sbin/dhclient-script << EOF
+#!/bin/sh
+LOG=/var/log/dhclient-script.log
+echo \${reason} \${interface} >> \$LOG
+set >> \$LOG
+
+[ -n "\${new_interface_mtu}" ]       && ip link set dev \${interface} mtu \${new_interface_mtu}
+
+[ -n "\${new_ip_address}" ]          && ip addr add \${new_ip_address}/\${new_subnet_mask} dev \${interface}
+[ -n "\${new_routers}" ]             && for r in \${new_routers}; do ip route add default via \${r} dev \${interface}; done
+:> /etc/resolv.conf
+[ -n "\${new_domain_name_servers}" ] && for d in \${new_domain_name_servers}; do echo "nameserver \${d}" >> /etc/resolv.conf; done
+[ -n "\${new_domain_name}" ]         && echo "search \${new_domain_name}" >> /etc/resolf.conf
+[ -n "\${new_domain_search}" ]       && (printf "search"; for d in \${new_domain_search}; do printf " %s" "\${d}"; done; printf "\n") >> /etc/resolv.conf
+[ -n "\${new_ip6_address}" ]         && ip addr add \${new_ip6_address}/\${new_ip6_prefixlen} dev \${interface}
+[ -n "\${new_dhcp6_name_servers}" ]  && for d in \${new_dhcp6_name_servers}; do echo "nameserver \${d}%\${interface}" >> /etc/resolv.conf; done
+[ -n "\${new_dhcp6_domain_search}" ] && (printf "search"; for d in \${new_dhcp6_domain_search}; do printf " %s" "\${d}"; done; printf "\n") >> /etc/resolv.conf
+[ -n "\${new_host_name}" ]           && hostname "\${new_host_name}"
+exit 0
+EOF
+	chmod 755 /sbin/dhclient-script
+	ln -s /sbin /usr/sbin
+	ln -s /bin /usr/bin
+	ln -s /run /var/run
+	:> /etc/fstab
+
+	# sshd(dropbear) via vsock
+	cat > /etc/passwd << EOF
+root:x:0:0:root:/root:/bin/sh
+sshd:x:100:100:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
+EOF
+	cat > /etc/shadow << EOF
+root:::0:99999:7:::
+EOF
+	chmod 000 /etc/shadow
+
+	:> /etc/ssh/sshd_config
+	ssh-keygen -A
+	chmod 700 /root/.ssh
+	chmod 700 /run/sshd
+	# Alternative location for the priv separation dir
+	ln -s /run/sshd /usr/share/empty.sshd
+
+	cat > /root/.ssh/authorized_keys <<EOF
+'"$(cat guest-key.pub 2>/dev/null || :)"'
+EOF
+	chmod 600 /root/.ssh/authorized_keys
+	chmod 700 /root
+	socat VSOCK-LISTEN:22,fork EXEC:"sshd -i -e" 2> /var/log/vsock-ssh.log &
+	sh +m
+'
+
+OUTPUT="KERNEL=__KERNEL__
+INITRD=__INITRD__
+"
diff --git a/oldtest/passt.mem.mbuto b/oldtest/passt.mem.mbuto
new file mode 100755
index 00000000..56f51395
--- /dev/null
+++ b/oldtest/passt.mem.mbuto
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# test/passt.mem.mbuto - mbuto (https://mbuto.sh) profile for memory usage tests
+#
+# Copyright (c) 2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+PROGS="${PROGS:-ash,dash,bash chmod ip mount insmod mkdir ln cat chmod modprobe
+       grep mknod sed chown sleep bc ls ps mount unshare chroot cp kill diff
+       head tail sort tr tee cut nm which}"
+
+KMODS="${KMODS:- dummy}"
+
+NODES="${NODES:-console kmsg null ptmx random urandom zero}"
+
+LINKS="${LINKS:-
+	 ash,dash,bash		/init
+	 ash,dash,bash		/bin/sh}"
+
+DIRS="${DIRS} /tmp /sbin"
+
+COPIES="${COPIES} ../passt.avx2,/bin/passt.avx2"
+
+FIXUP="${FIXUP}"'
+ln -s /bin /usr/bin
+chmod 777 /tmp
+ip link add eth0 type dummy
+ip link set eth0 up
+ip address add 192.0.2.2/24 dev eth0
+ip address add 2001:db8::2/64 dev eth0
+ip route add default via 192.0.2.1
+ip -6 route add default via 2001:db8::1 dev eth0
+sleep 2
+sh +m
+'
+
+OUTPUT="KERNEL=__KERNEL__
+INITRD=__INITRD__
+"
diff --git a/oldtest/passt/dhcp b/oldtest/passt/dhcp
new file mode 100644
index 00000000..72727558
--- /dev/null
+++ b/oldtest/passt/dhcp
@@ -0,0 +1,70 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/passt/dhcp - Check DHCP and DHCPv6 functionality in passt mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools	ip jq dhclient sed tr
+htools	ip jq sed tr head
+
+test	Interface name
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+hout	HOST_IFNAME ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+check	[ -n "__IFNAME__" ]
+
+test	DHCP: address
+guest	/sbin/dhclient -4 __IFNAME__
+gout	ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+hout	HOST_ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[0].local'
+check	[ "__ADDR__" = "__HOST_ADDR__" ]
+
+test	DHCP: route
+gout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hout	HOST_GW ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
+check	[ "__GW__" = "__HOST_GW__" ]
+
+test	DHCP: MTU
+gout	MTU ip -j link show | jq -rM '.[] | select(.ifname == "__IFNAME__").mtu'
+check	[ __MTU__ = 65520 ]
+
+test	DHCP: DNS
+gout	DNS sed -n 's/^nameserver \([0-9]*\.\)\(.*\)/\1\2/p' /etc/resolv.conf | tr '\n' ',' | sed 's/,$//;s/$/\n/'
+hout	HOST_DNS sed -n 's/^nameserver \([0-9]*\.\)\(.*\)/\1\2/p' /etc/resolv.conf | head -n3 | tr '\n' ',' | sed 's/,$//;s/$/\n/'
+check	[ "__DNS__" = "__HOST_DNS__" ] || [ "__DNS__" = "__HOST_GW__" -a "__HOST_DNS__" = "127.0.0.1" ]
+
+# FQDNs should be terminated by dots, but the guest DHCP client might omit them:
+# strip them first
+test	DHCP: search list
+gout	SEARCH sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
+hout	HOST_SEARCH sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
+check	[ "__SEARCH__" = "__HOST_SEARCH__" ]
+
+test	DHCPv6: address
+guest	/sbin/dhclient -6 __IFNAME__
+gout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.prefixlen == 128).local'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
+check	[ "__ADDR6__" = "__HOST_ADDR6__" ]
+
+test	DHCPv6: route
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hout	HOST_GW6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
+check	[ "__GW6__" = "__HOST_GW6__" ]
+
+# Strip interface specifier: interface names might differ between host and guest
+test	DHCPv6: DNS
+gout	DNS6 sed -n 's/^nameserver \([^:]*:\)\([^%]*\).*/\1\2/p' /etc/resolv.conf | tr '\n' ',' | sed 's/,$//;s/$/\n/'
+hout	HOST_DNS6 sed -n 's/^nameserver \([^:]*:\)\([^%]*\).*/\1\2/p' /etc/resolv.conf | tr '\n' ',' | sed 's/,$//;s/$/\n/'
+check	[ "__DNS6__" = "__HOST_DNS6__" ] || [ "__DNS6__" = "__HOST_GW6__" -a "__HOST_DNS6__" = "::1" ]
+
+test	DHCPv6: search list
+gout	SEARCH6 sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
+hout	HOST_SEARCH6 sed 's/\. / /g' /etc/resolv.conf | sed 's/\.$//g' | sed -n 's/^search \(.*\)/\1/p' | tr ' \n' ',' | sed 's/,$//;s/$/\n/'
+check	[ "__SEARCH6__" = "__HOST_SEARCH6__" ]
diff --git a/oldtest/passt/ndp b/oldtest/passt/ndp
new file mode 100644
index 00000000..6de40813
--- /dev/null
+++ b/oldtest/passt/ndp
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/passt/ndp - Check NDP functionality in passt mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools	ip jq sipcalc grep
+htools	ip jq sipcalc grep cut
+
+test	Interface name
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+guest	ip link set dev __IFNAME__ up && sleep 2
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+check	[ -n "__IFNAME__" ]
+
+test	SLAAC: prefix
+gout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local'
+gout	PREFIX6 sipcalc __ADDR6__/64 | grep prefix | cut -d' ' -f4
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
+hout	HOST_PREFIX6 sipcalc __HOST_ADDR6__/64 | grep prefix | cut -d' ' -f4
+check	[ "__PREFIX6__" = "__HOST_PREFIX6__" ]
+
+test	SLAAC: route
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hout	HOST_GW6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
+check	[ __GW6__ = __HOST_GW6__ ]
diff --git a/oldtest/passt/shutdown b/oldtest/passt/shutdown
new file mode 100644
index 00000000..02c6c106
--- /dev/null
+++ b/oldtest/passt/shutdown
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/passt/shutdown - Shut down passt (or pasta) and check exit code (will
+#                       detect valgrind errors amongst others)
+#
+# Copyright (c) 2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+test	shutdown: exit code
+
+hout	PASST_PID cat __STATESETUP__/passt.pid
+host	kill __PASST_PID__
+passtw
diff --git a/oldtest/passt/tcp b/oldtest/passt/tcp
new file mode 100644
index 00000000..91e49e07
--- /dev/null
+++ b/oldtest/passt/tcp
@@ -0,0 +1,76 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/passt/tcp - Check TCP functionality in passt mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools	socat ip jq cmp
+htools	socat ip jq
+
+set	TEMP_BIG __STATEDIR__/test_big.bin
+set	TEMP_SMALL __STATEDIR__/test_small.bin
+
+test	TCP/IPv4: host to guest: big transfer
+guestb	socat -u TCP4-LISTEN:10001,reuseaddr OPEN:test_big.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10001
+guestw
+guest	cmp /root/big.bin test_big.bin
+
+test	TCP/IPv4: guest to host: big transfer
+hostb	socat -u TCP4-LISTEN:10003,bind=127.0.0.1,reuseaddr OPEN:__TEMP_BIG__,create,trunc
+gout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+guest	socat -u OPEN:/root/big.bin TCP4:__GW__:10003
+hostw
+check	cmp __BASEPATH__/big.bin __TEMP_BIG__
+
+test	TCP/IPv4: host to guest: small transfer
+guestb	socat -u TCP4-LISTEN:10001,reuseaddr OPEN:test_small.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10001
+guestw
+guest	cmp /root/small.bin test_small.bin
+
+test	TCP/IPv4: guest to host: small transfer
+hostb	socat -u TCP4-LISTEN:10003,bind=127.0.0.1,reuseaddr OPEN:__TEMP_SMALL__,create,trunc
+sleep	1
+guest	socat -u OPEN:/root/small.bin TCP4:__GW__:10003
+hostw
+check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
+
+
+test	TCP/IPv6: host to guest: big transfer
+guestb	socat -u TCP6-LISTEN:10001,reuseaddr OPEN:test_big.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10001
+guestw
+guest	cmp /root/big.bin test_big.bin
+
+test	TCP/IPv6: guest to host: big transfer
+hostb	socat -u TCP6-LISTEN:10003,bind=[::1],reuseaddr OPEN:__TEMP_BIG__,create,trunc
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+guest	socat -u OPEN:/root/big.bin TCP6:[__GW6__%__IFNAME__]:10003
+hostw
+check	cmp __BASEPATH__/big.bin __TEMP_BIG__
+
+test	TCP/IPv6: host to guest: small transfer
+guestb	socat -u TCP6-LISTEN:10001,reuseaddr OPEN:test_small.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10001
+guestw
+guest	cmp /root/small.bin test_small.bin
+
+test	TCP/IPv6: guest to host: small transfer
+hostb	socat -u TCP6-LISTEN:10003,bind=[::1],reuseaddr OPEN:__TEMP_SMALL__,create,trunc
+sleep	1
+guest	socat -u OPEN:/root/small.bin TCP6:[__GW6__%__IFNAME__]:10003
+hostw
+check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
diff --git a/oldtest/passt/udp b/oldtest/passt/udp
new file mode 100644
index 00000000..80d0fa36
--- /dev/null
+++ b/oldtest/passt/udp
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/passt/udp - Check UDP functionality in passt mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools	socat ip jq cmp
+htools	socat jq
+
+set	TEMP __STATEDIR__/test.bin
+
+test	UDP/IPv4: host to guest
+guestb	socat -u UDP4-LISTEN:10001,null-eof OPEN:test.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10001,shut-null
+guestw
+guest	cmp /root/medium.bin test.bin
+
+test	UDP/IPv4: guest to host
+hostb	socat -u UDP4-LISTEN:10003,bind=127.0.0.1,null-eof OPEN:__TEMP__,create,trunc
+gout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+guest	socat -u OPEN:/root/medium.bin UDP4:__GW__:10003,shut-null
+hostw
+check	cmp __BASEPATH__/medium.bin __TEMP__
+
+test	UDP/IPv6: host to guest
+guestb	socat -u UDP6-LISTEN:10001,null-eof OPEN:test.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10001,shut-null
+guestw
+guest	cmp /root/medium.bin test.bin
+
+test	UDP/IPv6: guest to host
+hostb	socat -u UDP6-LISTEN:10003,bind=[::1],null-eof OPEN:__TEMP__,create,trunc
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+guest	socat -u OPEN:/root/medium.bin UDP6:[__GW6__%__IFNAME__]:10003,shut-null
+hostw
+check	cmp __BASEPATH__/medium.bin __TEMP__
diff --git a/oldtest/passt_in_ns/icmp b/oldtest/passt_in_ns/icmp
new file mode 100644
index 00000000..a2697db0
--- /dev/null
+++ b/oldtest/passt_in_ns/icmp
@@ -0,0 +1,32 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/passt_in_ns/icmp - Check ICMP/ICMPv6 functionality for passt in ns
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+#
+# These tests can work reliably only within an isolated namespace: the host
+# might have a net.ipv4.ping_group_range sysctl value not allowing pasta's gid
+# to create "ping" sockets. Inside the namespace, there's a single group, which
+# is allowed by default to create them.
+
+nstools	ip jq sleep
+gtools	ping ip jq
+
+test	ICMP echo: guest to ns
+nsout	IFNAME_NS ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+ns	ip addr add 192.0.2.1/32 dev __IFNAME_NS__
+guest	ping -c1 -w1 192.0.2.1
+ns	ip addr del 192.0.2.1/32 dev __IFNAME_NS__
+
+test	ICMPv6 echo: guest to ns
+ns	ip addr add 2001:db8::1 dev __IFNAME_NS__ && sleep 2 # DAD
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+guest	ping -c1 -w1 2001:db8::1
+ns	ip addr del 2001:db8::1 dev __IFNAME_NS__
diff --git a/oldtest/passt_in_ns/shutdown b/oldtest/passt_in_ns/shutdown
new file mode 100644
index 00000000..cd6cb190
--- /dev/null
+++ b/oldtest/passt_in_ns/shutdown
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/passt_in_ns/shutdown - Shut down passt and check exit code (will detect
+#                             valgrind errors amongst others)
+#
+# Copyright (c) 2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+test	shutdown: exit code
+
+nsout	PASST_PID cat __STATESETUP__/passt.pid
+ns	kill __PASST_PID__
+passtw
diff --git a/oldtest/passt_in_ns/tcp b/oldtest/passt_in_ns/tcp
new file mode 100644
index 00000000..cdb7060c
--- /dev/null
+++ b/oldtest/passt_in_ns/tcp
@@ -0,0 +1,256 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/passt_in_ns/tcp - Check TCP functionality for passt in ns with pasta
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools	socat ip jq
+htools	socat ip jq
+nstools	socat ip jq
+
+set	TEMP_BIG __STATEDIR__/test_big.bin
+set	TEMP_SMALL __STATEDIR__/test_small.bin
+set	TEMP_NS_BIG __STATEDIR__/test_ns_big.bin
+set	TEMP_NS_SMALL __STATEDIR__/test_ns_small.bin
+
+test	TCP/IPv4: host to guest: big transfer
+guestb	socat -u TCP4-LISTEN:10001 OPEN:test_big.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10001
+guestw
+guest	cmp test_big.bin /root/big.bin
+
+test	TCP/IPv4: host to ns: big transfer
+nsb	socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10002
+nsw
+check	cmp __TEMP_NS_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv4: guest to host: big transfer
+hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
+gout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+sleep	1
+guest	socat -u OPEN:/root/big.bin TCP4:__GW__:10003
+hostw
+check	cmp __TEMP_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv4: guest to ns: big transfer
+nsb	socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
+sleep	1
+guest	socat -u OPEN:/root/big.bin TCP4:__GW__:10002
+nsw
+check	cmp __TEMP_NS_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv4: ns to host (spliced): big transfer
+hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10003
+hostw
+check	cmp __TEMP_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv4: ns to host (via tap): big transfer
+hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP4:__GW__:10003
+hostw
+check	cmp __TEMP_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv4: ns to guest (using loopback address): big transfer
+guestb	socat -u TCP4-LISTEN:10001 OPEN:test_big.bin,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10001
+guestw
+guest	cmp test_big.bin /root/big.bin
+
+test	TCP/IPv4: ns to guest (using namespace address): big transfer
+guestb	socat -u TCP4-LISTEN:10001 OPEN:test_big.bin,create,trunc
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+nsout	ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP4:__ADDR__:10001
+guestw
+guest	cmp test_big.bin /root/big.bin
+
+test	TCP/IPv4: host to guest: small transfer
+guestb	socat -u TCP4-LISTEN:10001 OPEN:test_small.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10001
+guestw
+guest	cmp test_small.bin /root/small.bin
+
+test	TCP/IPv4: host to ns: small transfer
+nsb	socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_SMALL__,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10002
+nsw
+check	cmp __TEMP_NS_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv4: guest to host: small transfer
+hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
+gout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+sleep	1
+guest	socat -u OPEN:/root/small.bin TCP4:__GW__:10003
+hostw
+check	cmp __TEMP_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv4: guest to ns: small transfer
+nsb	socat -u TCP4-LISTEN:10002 OPEN:__TEMP_NS_SMALL__,create,trunc
+sleep	1
+guest	socat -u OPEN:/root/small.bin TCP4:__GW__:10002
+nsw
+check	cmp __TEMP_NS_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv4: ns to host (spliced): small transfer
+hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10003
+hostw
+check	cmp __TEMP_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv4: ns to host (via tap): small transfer
+hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP4:__GW__:10003
+hostw
+check	cmp __TEMP_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv4: ns to guest (using loopback address): small transfer
+guestb	socat -u TCP4-LISTEN:10001 OPEN:test_small.bin,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10001
+guestw
+guest	cmp test_small.bin /root/small.bin
+
+test	TCP/IPv4: ns to guest (using namespace address): small transfer
+guestb	socat -u TCP4-LISTEN:10001 OPEN:test_small.bin,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP4:__ADDR__:10001
+guestw
+guest	cmp test_small.bin /root/small.bin
+
+test	TCP/IPv6: host to guest: big transfer
+guestb	socat -u TCP6-LISTEN:10001 OPEN:test_big.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10001
+guestw
+guest	cmp test_big.bin /root/big.bin
+
+test	TCP/IPv6: host to ns: big transfer
+nsb	socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10002
+nsw
+check	cmp __TEMP_NS_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv6: guest to host: big transfer
+hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+sleep	1
+guest	socat -u OPEN:/root/big.bin TCP6:[__GW6__%__IFNAME__]:10003
+hostw
+check	cmp __TEMP_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv6: guest to ns: big transfer
+nsb	socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_BIG__,create,trunc
+sleep	1
+guest	socat -u OPEN:/root/big.bin TCP6:[__GW6__%__IFNAME__]:10002
+nsw
+check	cmp __TEMP_NS_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv6: ns to host (spliced): big transfer
+hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10003
+hostw
+check	cmp __TEMP_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv6: ns to host (via tap): big transfer
+hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP6:[__GW6__%__IFNAME__]:10003
+hostw
+check	cmp __TEMP_BIG__ __BASEPATH__/big.bin
+
+test	TCP/IPv6: ns to guest (using loopback address): big transfer
+guestb	socat -u TCP6-LISTEN:10001 OPEN:test_big.bin,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10001
+guestw
+guest	cmp test_big.bin /root/big.bin
+
+test	TCP/IPv6: ns to guest (using namespace address): big transfer
+guestb	socat -u TCP6-LISTEN:10001 OPEN:test_big.bin,create,trunc
+nsout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP6:[__ADDR6__]:10001
+guestw
+guest	cmp test_big.bin /root/big.bin
+
+test	TCP/IPv6: host to guest: small transfer
+guestb	socat -u TCP6-LISTEN:10001 OPEN:test_small.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10001
+guestw
+guest	cmp test_small.bin /root/small.bin
+
+test	TCP/IPv6: host to ns: small transfer
+nsb	socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_SMALL__,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10002
+nsw
+check	cmp __TEMP_NS_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv6: guest to host: small transfer
+hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+sleep	1
+guest	socat -u OPEN:/root/small.bin TCP6:[__GW6__%__IFNAME__]:10003
+hostw
+check	cmp __TEMP_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv6: guest to ns: small transfer
+nsb	socat -u TCP6-LISTEN:10002 OPEN:__TEMP_NS_SMALL__
+sleep	1
+guest	socat -u OPEN:/root/small.bin TCP6:[__GW6__%__IFNAME__]:10002
+nsw
+check	cmp __TEMP_NS_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv6: ns to host (spliced): small transfer
+hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10003
+hostw
+check	cmp __TEMP_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv6: ns to host (via tap): small transfer
+hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP6:[__GW6__%__IFNAME__]:10003
+hostw
+check	cmp __TEMP_SMALL__ __BASEPATH__/small.bin
+
+test	TCP/IPv6: ns to guest (using loopback address): small transfer
+guestb	socat -u TCP6-LISTEN:10001 OPEN:test_small.bin,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10001
+guestw
+guest	cmp test_small.bin /root/small.bin
+
+test	TCP/IPv6: ns to guest (using namespace address): small transfer
+guestb	socat -u TCP6-LISTEN:10001 OPEN:test_small.bin,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP6:[__ADDR6__]:10001
+guestw
+guest	cmp test_small.bin /root/small.bin
diff --git a/oldtest/passt_in_ns/udp b/oldtest/passt_in_ns/udp
new file mode 100644
index 00000000..8a025131
--- /dev/null
+++ b/oldtest/passt_in_ns/udp
@@ -0,0 +1,138 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/passt_in_ns/udp - Check UDP functionality for passt in ns and pasta
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools	socat ip jq
+nstools	socat ip jq
+htools	socat ip jq
+
+set	TEMP __STATEDIR__/test.bin
+set	TEMP_NS __STATEDIR__/test_ns.bin
+
+test	UDP/IPv4: host to guest
+guestb	socat -u UDP4-LISTEN:10001,null-eof OPEN:test.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10001,shut-null
+guestw
+guest	cmp test.bin /root/medium.bin
+
+test	UDP/IPv4: host to ns
+nsb	socat -u UDP4-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10002,shut-null
+nsw
+check	cmp __TEMP_NS__ __BASEPATH__/medium.bin
+
+test	UDP/IPv4: guest to host
+hostb	socat -u UDP4-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
+gout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+sleep	1
+guest	socat -u OPEN:/root/medium.bin UDP4:__GW__:10003,shut-null
+hostw
+check	cmp __TEMP__ __BASEPATH__/medium.bin
+
+test	UDP/IPv4: guest to ns
+nsb	socat -u UDP4-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
+sleep	1
+guest	socat -u OPEN:/root/medium.bin UDP4:__GW__:10002,shut-null
+nsw
+check	cmp __TEMP_NS__ __BASEPATH__/medium.bin
+
+test	UDP/IPv4: ns to host (recvmmsg/sendmmsg)
+hostb	socat -u UDP4-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10003,shut-null
+hostw
+check	cmp __TEMP__ __BASEPATH__/medium.bin
+
+test	UDP/IPv4: ns to host (via tap)
+hostb	socat -u UDP4-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP4:__GW__:10003,shut-null
+hostw
+check	cmp __TEMP__ __BASEPATH__/medium.bin
+
+test	UDP/IPv4: ns to guest (using loopback address)
+guestb	socat -u UDP4-LISTEN:10001,null-eof OPEN:test.bin,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10001,shut-null
+guestw
+guest	cmp test.bin /root/medium.bin
+
+test	UDP/IPv4: ns to guest (using namespace address)
+guestb	socat -u UDP4-LISTEN:10001,null-eof OPEN:test.bin,create,trunc
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+nsout	ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP4:__ADDR__:10001,shut-null
+guestw
+guest	cmp test.bin /root/medium.bin
+
+test	UDP/IPv6: host to guest
+guestb	socat -u UDP6-LISTEN:10001,null-eof OPEN:test.bin,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10001,shut-null
+guestw
+guest	cmp test.bin /root/medium.bin
+
+test	UDP/IPv6: host to ns
+nsb	socat -u UDP6-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
+sleep	1
+host	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10002,shut-null
+nsw
+check	cmp __TEMP_NS__ __BASEPATH__/medium.bin
+
+test	UDP/IPv6: guest to host
+hostb	socat -u UDP6-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+sleep	1
+guest	socat -u OPEN:/root/medium.bin UDP6:[__GW6__%__IFNAME__]:10003,shut-null
+hostw
+check	cmp __TEMP__ __BASEPATH__/medium.bin
+
+test	UDP/IPv6: guest to ns
+nsb	socat -u UDP6-LISTEN:10002,null-eof OPEN:__TEMP_NS__,create,trunc
+sleep	1
+guest	socat -u OPEN:/root/medium.bin UDP6:[__GW6__%__IFNAME__]:10002,shut-null
+nsw
+check	cmp __TEMP_NS__ __BASEPATH__/medium.bin
+
+test	UDP/IPv6: ns to host (recvmmsg/sendmmsg)
+hostb	socat -u UDP6-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10003,shut-null
+hostw
+check	cmp __TEMP__ __BASEPATH__/medium.bin
+
+test	UDP/IPv6: ns to host (via tap)
+hostb	socat -u UDP6-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[__GW6__%__IFNAME__]:10003,shut-null
+hostw
+check	cmp __TEMP__ __BASEPATH__/medium.bin
+
+test	UDP/IPv6: ns to guest (using loopback address)
+guestb	socat -u UDP6-LISTEN:10001,null-eof OPEN:test.bin,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10001,shut-null
+guestw
+guest	cmp test.bin /root/medium.bin
+
+test	UDP/IPv6: ns to guest (using namespace address)
+guestb	socat -u UDP6-LISTEN:10001,null-eof OPEN:test.bin,create,trunc
+nsout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[__ADDR6__]:10001,shut-null
+guestw
+guest	cmp test.bin /root/medium.bin
diff --git a/oldtest/pasta/dhcp b/oldtest/pasta/dhcp
new file mode 100644
index 00000000..309001b7
--- /dev/null
+++ b/oldtest/pasta/dhcp
@@ -0,0 +1,46 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/pasta/dhcp - Check DHCP and DHCPv6 functionality in pasta mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+nstools	ip jq /sbin/dhclient
+htools	ip jq
+
+test	Interface name
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+check	[ -n "__IFNAME__" ]
+
+test	DHCP: address
+ns	/sbin/dhclient -4 --no-pid __IFNAME__
+nsout	ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+hout	HOST_ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+check	[ __ADDR__ = __HOST_ADDR__ ]
+
+test	DHCP: route
+nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hout	HOST_GW ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
+check	[ __GW__ = __HOST_GW__ ]
+
+test	DHCP: MTU
+nsout	MTU ip -j link show | jq -rM '.[] | select(.ifname == "__IFNAME__").mtu'
+check	[ __MTU__ = 65520 ]
+
+test	DHCPv6: address
+ns	/sbin/dhclient -6 --no-pid __IFNAME__
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+nsout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.prefixlen == 128).local'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
+check	[ __ADDR6__ = __HOST_ADDR6__ ]
+
+test	DHCPv6: route
+nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hout	HOST_GW6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
+check	[ __GW6__ = __HOST_GW6__ ]
diff --git a/oldtest/pasta/ndp b/oldtest/pasta/ndp
new file mode 100644
index 00000000..bb331102
--- /dev/null
+++ b/oldtest/pasta/ndp
@@ -0,0 +1,33 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/pasta/ndp - Check DHCP and DHCPv6 functionality in pasta mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+nstools	ip jq sipcalc grep cut
+htools	ip jq sipcalc grep cut
+
+test	Interface name
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+check	[ -n "__IFNAME__" ]
+ns	ip link set dev __IFNAME__ up
+sleep	2
+
+test	SLAAC: prefix
+nsout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local'
+nsout	PREFIX6 sipcalc __ADDR6__/64 | grep prefix | cut -d' ' -f4
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global").local'
+hout	HOST_PREFIX6 sipcalc __HOST_ADDR6__/64 | grep prefix | cut -d' ' -f4
+check	[ "__PREFIX6__" = "__HOST_PREFIX6__" ]
+
+test	SLAAC: route
+nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+hout	HOST_GW6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
+check	[ __GW6__ = __HOST_GW6__ ]
diff --git a/oldtest/pasta/tcp b/oldtest/pasta/tcp
new file mode 100644
index 00000000..6ab18c5c
--- /dev/null
+++ b/oldtest/pasta/tcp
@@ -0,0 +1,96 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/pasta/tcp - Check TCP functionality for pasta
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+htools	socat ip jq
+nstools	socat ip jq
+
+set	TEMP_BIG __STATEDIR__/test_big.bin
+set	TEMP_NS_BIG __STATEDIR__/test_ns_big.bin
+set	TEMP_SMALL __STATEDIR__/test_small.bin
+set	TEMP_NS_SMALL __STATEDIR__/test_ns_small.bin
+
+test	TCP/IPv4: host to ns: big transfer
+nsb	socat -u TCP4-LISTEN:10002,bind=127.0.0.1 OPEN:__TEMP_NS_BIG__,create,trunc
+host	socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10002
+nsw
+check	cmp __BASEPATH__/big.bin __TEMP_NS_BIG__
+
+test	TCP/IPv4: ns to host (spliced): big transfer
+hostb	socat -u TCP4-LISTEN:10003,bind=127.0.0.1 OPEN:__TEMP_BIG__,create,trunc
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10003
+hostw
+check	cmp __BASEPATH__/big.bin __TEMP_BIG__
+
+test	TCP/IPv4: ns to host (via tap): big transfer
+hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
+nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP4:__GW__:10003
+hostw
+check	cmp __BASEPATH__/big.bin __TEMP_BIG__
+
+test	TCP/IPv4: host to ns: small transfer
+nsb	socat -u TCP4-LISTEN:10002,bind=127.0.0.1 OPEN:__TEMP_NS_SMALL__,create,trunc
+host	socat OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10002
+nsw
+check	cmp __BASEPATH__/small.bin __TEMP_NS_SMALL__
+
+test	TCP/IPv4: ns to host (spliced): small transfer
+hostb	socat -u TCP4-LISTEN:10003,bind=127.0.0.1 OPEN:__TEMP_SMALL__,create,trunc
+ns	socat OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10003
+hostw
+check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
+
+test	TCP/IPv4: ns to host (via tap): small transfer
+hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
+nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP4:__GW__:10003
+hostw
+check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
+
+test	TCP/IPv6: host to ns: big transfer
+nsb	socat -u TCP6-LISTEN:10002,bind=[::1] OPEN:__TEMP_NS_BIG__,create,trunc
+host	socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10002
+nsw
+check	cmp __BASEPATH__/big.bin __TEMP_NS_BIG__
+
+test	TCP/IPv6: ns to host (spliced): big transfer
+hostb	socat -u TCP6-LISTEN:10003,bind=[::1] OPEN:__TEMP_BIG__,create,trunc
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10003
+hostw
+check	cmp __BASEPATH__/big.bin __TEMP_BIG__
+
+test	TCP/IPv6: ns to host (via tap): big transfer
+hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
+nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+ns	socat -u OPEN:__BASEPATH__/big.bin TCP6:[__GW6__%__IFNAME__]:10003
+hostw
+check	cmp __BASEPATH__/big.bin __TEMP_BIG__
+
+test	TCP/IPv6: host to ns: small transfer
+nsb	socat -u TCP6-LISTEN:10002,bind=[::1] OPEN:__TEMP_NS_SMALL__,create,trunc
+host	socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10002
+nsw
+check	cmp __BASEPATH__/small.bin __TEMP_NS_SMALL__
+
+test	TCP/IPv6: ns to host (spliced): small transfer
+hostb	socat -u TCP6-LISTEN:10003,bind=[::1] OPEN:__TEMP_SMALL__,create,trunc
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10003
+hostw
+check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
+
+test	TCP/IPv6: ns to host (via tap): small transfer
+hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
+ns	socat -u OPEN:__BASEPATH__/small.bin TCP6:[__GW6__%__IFNAME__]:10003
+hostw
+check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
diff --git a/oldtest/pasta/udp b/oldtest/pasta/udp
new file mode 100644
index 00000000..30e3a855
--- /dev/null
+++ b/oldtest/pasta/udp
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/pasta/udp - Check UDP functionality for pasta
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+nstools	socat ip jq
+htools	dd socat ip jq
+
+set	TEMP __STATEDIR__/test.bin
+set	TEMP_NS __STATEDIR__/test_ns.bin
+
+test	UDP/IPv4: host to ns
+nsb	socat -u UDP4-LISTEN:10002,bind=127.0.0.1,null-eof OPEN:__TEMP_NS__,create,trunc
+host	socat OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10002,shut-null
+nsw
+check	cmp __BASEPATH__/medium.bin __TEMP_NS__
+
+test	UDP/IPv4: ns to host (recvmmsg/sendmmsg)
+hostb	socat -u UDP4-LISTEN:10003,bind=127.0.0.1,null-eof OPEN:__TEMP__,create,trunc
+sleep	1
+ns	socat OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10003,shut-null
+hostw
+check	cmp __BASEPATH__/medium.bin __TEMP__
+
+test	UDP/IPv4: ns to host (via tap)
+hostb	socat -u UDP4-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
+nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP4:__GW__:10003,shut-null
+hostw
+check	cmp __BASEPATH__/medium.bin __TEMP__
+
+test	UDP/IPv6: host to ns
+nsb	socat -u UDP6-LISTEN:10002,bind=[::1],null-eof OPEN:__TEMP_NS__,create,trunc
+host	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10002,shut-null
+nsw
+check	cmp __BASEPATH__/medium.bin __TEMP_NS__
+
+test	UDP/IPv6: ns to host (recvmmsg/sendmmsg)
+hostb	socat -u UDP6-LISTEN:10003,bind=[::1],null-eof OPEN:__TEMP__,create,trunc
+sleep	1
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10003,shut-null
+hostw
+check	cmp __BASEPATH__/medium.bin __TEMP__
+
+test	UDP/IPv6: ns to host (via tap)
+hostb	socat -u UDP6-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
+nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+ns	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[__GW6__%__IFNAME__]:10003,shut-null
+hostw
+check	cmp __BASEPATH__/medium.bin __TEMP__
diff --git a/oldtest/pasta_options/log_to_file b/oldtest/pasta_options/log_to_file
new file mode 100644
index 00000000..fcdd5538
--- /dev/null
+++ b/oldtest/pasta_options/log_to_file
@@ -0,0 +1,93 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/pasta_options/log_to_file - Check log creation, rotations and consistency
+#
+# Copyright (c) 2022 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+htools	wc tcp_rr tail cut tr sort
+
+def	flood_log_server
+passtb	tcp_crr --nolog -P 10001 -C 10002 -6
+sleep	1
+endef
+
+def	flood_log_client
+host	tcp_crr --nolog -P 10001 -C 10002 -6 -c -H ::1
+endef
+
+def	check_log_size_mountns
+pout	SIZE cat __LOG_FILE__ | wc -c
+check	[ __SIZE__ -gt $((50 * 1024)) ]
+check	[ __SIZE__ -lt $((100 * 1024)) ]
+endef
+
+test	Log creation
+
+set	PORTS -t 10001,10002 -u 10001,10002
+set	LOG_FILE __STATEDIR__/pasta.log
+
+passt	./pasta -l __LOG_FILE__
+passtb	exit
+sleep	1
+check	[ -s __LOG_FILE__ ]
+
+test	Log truncated on creation
+passt	./pasta -l __LOG_FILE__
+passtb	exit
+sleep	1
+check	[ $(cat __LOG_FILE__ | wc -l) -eq 1 ]
+
+test	Maximum log size
+passtb	./pasta --config-net -d -f -l __LOG_FILE__ --log-size $((100 * 1024)) -- sh -c 'while true; do tcp_crr --nolog -P 10001 -C 10002 -6; done'
+sleep	1
+
+flood_log_client
+check	[ $(cat __LOG_FILE__ | wc -c) -gt $((50 * 1024)) ]
+check	[ $(cat __LOG_FILE__ | wc -c) -lt $((100 * 1024)) ]
+
+flood_log_client
+check	[ $(cat __LOG_FILE__ | wc -c) -gt $((50 * 1024)) ]
+check	[ $(cat __LOG_FILE__ | wc -c) -lt $((100 * 1024)) ]
+
+flood_log_client
+check	[ $(cat __LOG_FILE__ | wc -c) -gt $((50 * 1024)) ]
+check	[ $(cat __LOG_FILE__ | wc -c) -lt $((100 * 1024)) ]
+
+pint
+
+test	Timestamp consistency after rotations
+check	tail -n +2 __LOG_FILE__ | cut -f1 -d' ' | tr -d [.:] | sort -c
+
+test	Maximum log size on tmpfs (no FALLOC_FL_COLLAPSE_RANGE)
+passt	unshare -rUm
+passt	mkdir __STATEDIR__/t
+passt	mount -t tmpfs none __STATEDIR__/t
+set	LOG_FILE __STATEDIR__/t/log
+passt	./pasta --config-net -d -l __LOG_FILE__ --log-size $((100 * 1024))
+
+flood_log_server
+flood_log_client
+check_log_size_mountns
+
+flood_log_server
+flood_log_client
+check_log_size_mountns
+
+flood_log_server
+flood_log_client
+check_log_size_mountns
+
+test	Timestamp consistency after rotations (no FALLOC_FL_COLLAPSE_RANGE)
+check	tail -n +2 __LOG_FILE__ | cut -f1 -d' ' | tr -d [.:] | sort -c
+
+passtb	exit
+sleep	1
+passt	umount __STATEDIR__/t
+passt	exit
diff --git a/oldtest/perf/passt_tcp b/oldtest/perf/passt_tcp
new file mode 100644
index 00000000..7046f3c0
--- /dev/null
+++ b/oldtest/perf/passt_tcp
@@ -0,0 +1,215 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/perf/passt_tcp - Check TCP performance in passt mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools	/sbin/sysctl ip jq nproc seq sleep iperf3 tcp_rr tcp_crr # From neper
+nstools	/sbin/sysctl ip jq nproc seq sleep iperf3 tcp_rr tcp_crr
+htools	bc head sed seq
+
+test	passt: throughput and latency
+
+guest	/sbin/sysctl -w net.core.rmem_max=536870912
+guest	/sbin/sysctl -w net.core.wmem_max=536870912
+guest	/sbin/sysctl -w net.core.rmem_default=33554432
+guest	/sbin/sysctl -w net.core.wmem_default=33554432
+guest	/sbin/sysctl -w net.ipv4.tcp_rmem="4096 131072 268435456"
+guest	/sbin/sysctl -w net.ipv4.tcp_wmem="4096 131072 268435456"
+guest	/sbin/sysctl -w net.ipv4.tcp_timestamps=0
+
+ns	/sbin/sysctl -w net.ipv4.tcp_rmem="4096 524288 134217728"
+ns	/sbin/sysctl -w net.ipv4.tcp_wmem="4096 524288 134217728"
+ns	/sbin/sysctl -w net.ipv4.tcp_timestamps=0
+
+gout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+
+hout	FREQ_PROCFS (echo "scale=1"; sed -n 's/cpu MHz.*: \([0-9]*\)\..*$/(\1+10^2\/2)\/10^3/p' /proc/cpuinfo) | bc -l | head -n1
+hout	FREQ_CPUFREQ (echo "scale=1"; printf '( %i + 10^5 / 2 ) / 10^6\n' $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) ) | bc -l
+hout	FREQ [ -n "__FREQ_CPUFREQ__" ] && echo __FREQ_CPUFREQ__ || echo __FREQ_PROCFS__
+
+set	THREADS 1
+set	STREAMS 8
+set	TIME 10
+hout	OMIT echo __TIME__ / 6 | bc -l
+set	OPTS -Z -P __STREAMS__ -l 1M -O__OMIT__ --pacing-timer 1000000
+
+info	Throughput in Gbps, latency in µs, one thread at __FREQ__ GHz, __STREAMS__ streams
+report	passt tcp __THREADS__ __FREQ__
+
+th	MTU 256B 576B 1280B 1500B 9000B 65520B
+
+
+tr	TCP throughput over IPv6: guest to host
+bw	-
+bw	-
+
+guest	ip link set dev __IFNAME__ mtu 1280
+iperf3	BW guest ns __GW6__%__IFNAME__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 4M
+bw	__BW__ 1.2 1.5
+guest	ip link set dev __IFNAME__ mtu 1500
+iperf3	BW guest ns __GW6__%__IFNAME__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 4M
+bw	__BW__ 1.6 1.8
+guest	ip link set dev __IFNAME__ mtu 9000
+iperf3	BW guest ns __GW6__%__IFNAME__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 8M
+bw	__BW__ 4.0 5.0
+guest	ip link set dev __IFNAME__ mtu 65520
+iperf3	BW guest ns __GW6__%__IFNAME__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 16M
+bw	__BW__ 7.0 8.0
+
+tl	TCP RR latency over IPv6: guest to host
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+nsb	tcp_rr --nolog -6
+gout	LAT tcp_rr --nolog -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 200 150
+
+tl	TCP CRR latency over IPv6: guest to host
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+nsb	tcp_crr --nolog -6
+gout	LAT tcp_crr --nolog -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 500 400
+
+
+tr	TCP throughput over IPv4: guest to host
+guest	ip link set dev __IFNAME__ mtu 256
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 1M
+bw	__BW__ 0.2 0.3
+guest	ip link set dev __IFNAME__ mtu 576
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 1M
+bw	__BW__ 0.5 0.8
+guest	ip link set dev __IFNAME__ mtu 1280
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 4M
+bw	__BW__ 1.2 1.5
+guest	ip link set dev __IFNAME__ mtu 1500
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 4M
+bw	__BW__ 1.6 1.8
+guest	ip link set dev __IFNAME__ mtu 9000
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 8M
+bw	__BW__ 4.0 5.0
+guest	ip link set dev __IFNAME__ mtu 65520
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -w 16M
+bw	__BW__ 7.0 8.0
+
+tl	TCP RR latency over IPv4: guest to host
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+nsb	tcp_rr --nolog -4
+gout	LAT tcp_rr --nolog -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 200 150
+
+tl	TCP CRR latency over IPv4: guest to host
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+nsb	tcp_crr --nolog -4
+gout	LAT tcp_crr --nolog -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 500 400
+
+
+tr	TCP throughput over IPv6: host to guest
+bw	-
+bw	-
+ns	ip link set dev lo mtu 1280
+iperf3	BW ns guest ::1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 1.0 1.2
+ns	ip link set dev lo mtu 1500
+iperf3	BW ns guest ::1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 2.0 3.0
+ns	ip link set dev lo mtu 9000
+iperf3	BW ns guest ::1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 5.0 6.0
+ns	ip link set dev lo mtu 65520
+iperf3	BW ns guest ::1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 6.0 6.8
+ns	ip link set dev lo mtu 65535
+
+tl	TCP RR latency over IPv6: host to guest
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+guestb	tcp_rr --nolog -P 10001 -C 10011 -6
+sleep	1
+nsout	LAT tcp_rr --nolog -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 200 150
+
+tl	TCP CRR latency over IPv6: host to guest
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+guestb	tcp_crr --nolog -P 10001 -C 10011 -6
+sleep	1
+nsout	LAT tcp_crr --nolog -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 500 350
+
+
+tr	TCP throughput over IPv4: host to guest
+ns	ip link set dev lo mtu 256
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 0.3 0.5
+ns	ip link set dev lo mtu 576
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 0.5 1.0
+ns	ip link set dev lo mtu 1280
+ns	ip addr add ::1 dev lo
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 2.0 3.0
+ns	ip link set dev lo mtu 1500
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 2.0 3.0
+ns	ip link set dev lo mtu 9000
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 5.0 6.0
+ns	ip link set dev lo mtu 65520
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 6.0 6.8
+ns	ip link set dev lo mtu 65535
+
+tl	TCP RR latency over IPv4: host to guest
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+guestb	tcp_rr --nolog -P 10001 -C 10011 -4
+sleep	1
+nsout	LAT tcp_rr --nolog -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 200 150
+
+tl	TCP CRR latency over IPv6: host to guest
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+guestb	tcp_crr --nolog -P 10001 -C 10011 -4
+sleep	1
+nsout	LAT tcp_crr --nolog -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 500 300
+
+te
diff --git a/oldtest/perf/passt_udp b/oldtest/perf/passt_udp
new file mode 100644
index 00000000..a117b6a8
--- /dev/null
+++ b/oldtest/perf/passt_udp
@@ -0,0 +1,165 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/perf/passt_udp - Check UDP performance in passt mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+gtools	/sbin/sysctl ip jq nproc sleep iperf3 udp_rr # From neper
+nstools	ip jq sleep iperf3 udp_rr
+htools	bc head sed
+
+test	passt: throughput and latency
+
+guest	/sbin/sysctl -w net.core.rmem_max=16777216
+guest	/sbin/sysctl -w net.core.wmem_max=16777216
+guest	/sbin/sysctl -w net.core.rmem_default=16777216
+guest	/sbin/sysctl -w net.core.wmem_default=16777216
+
+gout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+gout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+
+hout	FREQ_PROCFS (echo "scale=1"; sed -n 's/cpu MHz.*: \([0-9]*\)\..*$/(\1+10^2\/2)\/10^3/p' /proc/cpuinfo) | bc -l | head -n1
+hout	FREQ_CPUFREQ (echo "scale=1"; printf '( %i + 10^5 / 2 ) / 10^6\n' $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) ) | bc -l
+hout	FREQ [ -n "__FREQ_CPUFREQ__" ] && echo __FREQ_CPUFREQ__ || echo __FREQ_PROCFS__
+
+set	THREADS 4
+set	STREAMS 1
+set	TIME 10
+set	OPTS -u -P __STREAMS__ --pacing-timer 1000
+
+info	Throughput in Gbps, latency in µs, __THREADS__ threads at __FREQ__ GHz, one stream each
+
+report	passt udp __THREADS__ __FREQ__
+
+th	MTU 256B 576B 1280B 1500B 9000B 65520B
+
+
+tr	UDP throughput over IPv6: guest to host
+bw	-
+bw	-
+guest	ip link set dev __IFNAME__ mtu 1280
+iperf3	BW guest ns __GW6__%__IFNAME__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 2G
+bw	__BW__ 0.8 1.2
+guest	ip link set dev __IFNAME__ mtu 1500
+iperf3	BW guest ns __GW6__%__IFNAME__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 1.0 1.5
+guest	ip link set dev __IFNAME__ mtu 9000
+iperf3	BW guest ns __GW6__%__IFNAME__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 5G
+bw	__BW__ 4.0 5.0
+guest	ip link set dev __IFNAME__ mtu 65520
+iperf3	BW guest ns __GW6__%__IFNAME__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 7G
+bw	__BW__ 4.0 5.0
+
+tl	UDP RR latency over IPv6: guest to host
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+nsb	udp_rr --nolog -6
+gout	LAT udp_rr --nolog -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 200 150
+
+
+tr	UDP throughput over IPv4: guest to host
+guest	ip link set dev __IFNAME__ mtu 256
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 500M
+bw	__BW__ 0.0 0.0
+guest	ip link set dev __IFNAME__ mtu 576
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 1G
+bw	__BW__ 0.4 0.6
+guest	ip link set dev __IFNAME__ mtu 1280
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 2G
+bw	__BW__ 0.8 1.2
+guest	ip link set dev __IFNAME__ mtu 1500
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 1.0 1.5
+guest	ip link set dev __IFNAME__ mtu 9000
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 6G
+bw	__BW__ 4.0 5.0
+guest	ip link set dev __IFNAME__ mtu 65520
+iperf3	BW guest ns __GW__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 7G
+bw	__BW__ 4.0 5.0
+
+tl	UDP RR latency over IPv4: guest to host
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+nsb	udp_rr --nolog -4
+gout	LAT udp_rr --nolog -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 200 150
+
+
+tr	UDP throughput over IPv6: host to guest
+bw	-
+bw	-
+ns	ip link set dev lo mtu 1280
+iperf3	BW ns guest  ::1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 2G
+bw	__BW__ 0.8 1.2
+ns	ip link set dev lo mtu 1500
+iperf3	BW ns guest ::1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 2G
+bw	__BW__ 1.0 1.5
+ns	ip link set dev lo mtu 9000
+iperf3	BW ns guest ::1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 3.0 4.0
+ns	ip link set dev lo mtu 65520
+iperf3	BW ns guest ::1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 3.0 4.0
+
+tl	UDP RR latency over IPv6: host to guest
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+guestb	udp_rr --nolog -P 10001 -C 10011 -6
+sleep	1
+nsout	LAT udp_rr --nolog -P 10001 -C 10011 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 200 150
+ns	ip link set dev lo mtu 65535
+
+
+tr	UDP throughput over IPv4: host to guest
+ns	ip link set dev lo mtu 256
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 1G
+bw	__BW__ 0.0 0.0
+ns	ip link set dev lo mtu 576
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 1G
+bw	__BW__ 0.4 0.6
+ns	ip link set dev lo mtu 1280
+ns	ip addr add ::1 dev lo
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 0.8 1.2
+ns	ip link set dev lo mtu 1500
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 1.0 1.5
+ns	ip link set dev lo mtu 9000
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 3.0 4.0
+ns	ip link set dev lo mtu 65520
+iperf3	BW ns guest 127.0.0.1 100${i}1 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 3.0 4.0
+
+tl	UDP RR latency over IPv4: host to guest
+lat	-
+lat	-
+lat	-
+lat	-
+lat	-
+guestb	udp_rr --nolog -P 10001 -C 10011 -4
+sleep	1
+nsout	LAT udp_rr --nolog -P 10001 -C 10011 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+lat	__LAT__ 200 150
+ns	ip link set dev lo mtu 65535
+
+te
diff --git a/oldtest/perf/pasta_tcp b/oldtest/perf/pasta_tcp
new file mode 100644
index 00000000..4b13384c
--- /dev/null
+++ b/oldtest/perf/pasta_tcp
@@ -0,0 +1,300 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/perf/pasta_tcp - Check TCP performance in pasta mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+htools	head ip seq bc sleep iperf3 tcp_rr tcp_crr jq sed
+nstools	/sbin/sysctl nproc ip seq sleep iperf3 tcp_rr tcp_crr jq sed
+
+test	pasta: throughput and latency (local connections)
+
+ns	/sbin/sysctl -w net.ipv4.tcp_rmem="131072 524288 134217728"
+ns	/sbin/sysctl -w net.ipv4.tcp_wmem="131072 524288 134217728"
+ns	/sbin/sysctl -w net.ipv4.tcp_timestamps=0
+
+
+set	THREADS 2
+set	STREAMS 2
+set	TIME 10
+hout	OMIT echo __TIME__ / 6 | bc -l
+set	OPTS -Z -w 4M -l 1M -P __STREAMS__ -O__OMIT__ --pacing-timer 10000
+
+hout	FREQ_PROCFS (echo "scale=1"; sed -n 's/cpu MHz.*: \([0-9]*\)\..*$/(\1+10^2\/2)\/10^3/p' /proc/cpuinfo) | bc -l | head -n1
+hout	FREQ_CPUFREQ (echo "scale=1"; printf '( %i + 10^5 / 2 ) / 10^6\n' $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) ) | bc -l
+hout	FREQ [ -n "__FREQ_CPUFREQ__" ] && echo __FREQ_CPUFREQ__ || echo __FREQ_PROCFS__
+
+
+info	Throughput in Gbps, latency in µs, __THREADS__ threads at __FREQ__ GHz, __STREAMS__ streams each
+report	pasta lo_tcp __THREADS__ __FREQ__
+
+th	MTU 1500B 4000B 16384B 65535B
+
+
+tr	TCP throughput over IPv6: ns to host
+ns	ip link set dev lo mtu 1500
+iperf3	BW ns host ::1 100${i}3 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 15.0 20.0
+ns	ip link set dev lo mtu 4000
+iperf3c	ns ::1 100${i}3 __THREADS__ __TIME__ __OPTS__
+iperf3s	BW host 100${i}3 __THREADS__
+bw	__BW__ 15.0 20.0
+ns	ip link set dev lo mtu 16384
+iperf3	BW ns host ::1 100${i}3 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 15.0 20.0
+ns	ip link set dev lo mtu 65535
+iperf3	BW ns host ::1 100${i}3 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 15.0 20.0
+
+tl	TCP RR latency over IPv6: ns to host
+lat	-
+lat	-
+lat	-
+hostb	tcp_rr --nolog -P 10003 -C 10013 -6
+nsout	LAT tcp_rr --nolog -P 10003 -C 10013 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 150 100
+
+tl	TCP CRR latency over IPv6: ns to host
+lat	-
+lat	-
+lat	-
+hostb	tcp_crr --nolog -P 10003 -C 10013 -6
+nsout	LAT tcp_crr --nolog -P 10003 -C 10013 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 500 350
+
+
+tr	TCP throughput over IPv4: ns to host
+ns	ip link set dev lo mtu 1500
+iperf3	BW ns host 127.0.0.1 100${i}3 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 15.0 20.0
+ns	ip link set dev lo mtu 4000
+iperf3	BW ns host 127.0.0.1 100${i}3 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 15.0 20.0
+ns	ip link set dev lo mtu 16384
+iperf3	BW ns host 127.0.0.1 100${i}3 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 15.0 20.0
+ns	ip link set dev lo mtu 65535
+iperf3	BW ns host 127.0.0.1 100${i}3 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 15.0 20.0
+
+tl	TCP RR latency over IPv4: ns to host
+lat	-
+lat	-
+lat	-
+hostb	tcp_rr --nolog -P 10003 -C 10013 -4
+nsout	LAT tcp_rr --nolog -P 10003 -C 10013 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 150 100
+
+tl	TCP CRR latency over IPv4: ns to host
+lat	-
+lat	-
+lat	-
+hostb	tcp_crr --nolog -P 10003 -C 10013 -4
+nsout	LAT tcp_crr --nolog -P 10003 -C 10013 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 500 350
+
+
+tr	TCP throughput over IPv6: host to ns
+bw	-
+bw	-
+bw	-
+iperf3	BW host ns ::1 100${i}2 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 15.0 20.0
+
+tl	TCP RR latency over IPv6: host to ns
+lat	-
+lat	-
+lat	-
+nsb	tcp_rr --nolog -P 10002 -C 10012 -6
+hout	LAT tcp_rr --nolog -P 10002 -C 10012 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 150 100
+
+tl	TCP CRR latency over IPv6: host to ns
+lat	-
+lat	-
+lat	-
+nsb	tcp_crr --nolog -P 10002 -C 10012 -6
+hout	LAT tcp_crr --nolog -P 10002 -C 10012 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 1000 700
+
+
+tr	TCP throughput over IPv4: host to ns
+bw	-
+bw	-
+bw	-
+iperf3	BW host ns 127.0.0.1 100${i}2 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 15.0 20.0
+
+tl	TCP RR latency over IPv4: host to ns
+lat	-
+lat	-
+lat	-
+nsb	tcp_rr --nolog -P 10002 -C 10012 -4
+hout	LAT tcp_rr --nolog -P 10002 -C 10012 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 150 100
+
+tl	TCP CRR latency over IPv4: host to ns
+lat	-
+lat	-
+lat	-
+sleep	1
+nsb	tcp_crr --nolog -P 10002 -C 10012 -4
+hout	LAT tcp_crr --nolog -P 10002 -C 10012 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 1000 700
+
+te
+
+
+test	pasta: throughput and latency (connections via tap)
+
+nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+set	THREADS 1
+set	STREAMS 2
+set	OPTS -Z -P __STREAMS__ -i1 -O__OMIT__ --pacing-timer 100000
+
+info	Throughput in Gbps, latency in µs, one thread at __FREQ__ GHz, __STREAMS__ streams
+report	pasta tap_tcp __THREADS__ __FREQ__
+
+th	MTU 1500B 4000B 16384B 65520B
+
+
+tr	TCP throughput over IPv6: ns to host
+ns	ip link set dev __IFNAME__ mtu 1500
+iperf3	BW ns host __GW6__%__IFNAME__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -w 512k
+bw	__BW__ 0.2 0.4
+ns	ip link set dev __IFNAME__ mtu 4000
+iperf3	BW ns host __GW6__%__IFNAME__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -w 1M
+bw	__BW__ 0.3 0.5
+ns	ip link set dev __IFNAME__ mtu 16384
+iperf3	BW ns host __GW6__%__IFNAME__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -w 8M
+bw	__BW__ 1.5 2.0
+ns	ip link set dev __IFNAME__ mtu 65520
+iperf3	BW ns host __GW6__%__IFNAME__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -w 8M
+bw	__BW__ 2.0 2.5
+
+tl	TCP RR latency over IPv6: ns to host
+lat	-
+lat	-
+lat	-
+hostb	tcp_rr --nolog -P 10003 -C 10013 -6
+nsout	LAT tcp_rr --nolog -P 10003 -C 10013 -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 150 100
+
+tl	TCP CRR latency over IPv6: ns to host
+lat	-
+lat	-
+lat	-
+hostb	tcp_crr --nolog -P 10003 -C 10013 -6
+nsout	LAT tcp_crr --nolog -P 10003 -C 10013 -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 1500 500
+
+
+tr	TCP throughput over IPv4: ns to host
+ns	ip link set dev __IFNAME__ mtu 1500
+iperf3	BW ns host __GW__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -w 512k
+bw	__BW__ 0.2 0.4
+ns	ip link set dev __IFNAME__ mtu 4000
+iperf3s	BW ns host __GW__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -w 1M
+bw	__BW__ 0.3 0.5
+ns	ip link set dev __IFNAME__ mtu 16384
+iperf3	BW ns host __GW__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -w 8M
+bw	__BW__ 1.5 2.0
+ns	ip link set dev __IFNAME__ mtu 65520
+iperf3	BW ns host __GW__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -w 8M
+bw	__BW__ 2.0 2.5
+
+tl	TCP RR latency over IPv4: ns to host
+lat	-
+lat	-
+lat	-
+hostb	tcp_rr --nolog -P 10003 -C 10013 -4
+nsout	LAT tcp_rr --nolog -P 10003 -C 10013 -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 150 100
+
+tl	TCP CRR latency over IPv4: ns to host
+lat	-
+lat	-
+lat	-
+hostb	tcp_crr --nolog -P 10003 -C 10013 -4
+nsout	LAT tcp_crr --nolog -P 10003 -C 10013 -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 1500 500
+
+
+tr	TCP throughput over IPv6: host to ns
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+nsout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local'
+bw	-
+bw	-
+bw	-
+iperf3	BW host ns __ADDR6__ 100${i}2 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 8.0 10.0
+
+tl	TCP RR latency over IPv6: host to ns
+lat	-
+lat	-
+lat	-
+nsb	tcp_rr --nolog -P 10002 -C 10012 -6
+hout	LAT tcp_rr --nolog -P 10002 -C 10012 -6 -c -H __ADDR6__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 150 100
+
+tl	TCP CRR latency over IPv6: host to ns
+lat	-
+lat	-
+lat	-
+sleep	1
+nsb	tcp_crr --nolog -P 10002 -C 10012 -6
+hout	LAT tcp_crr --nolog -P 10002 -C 10012 -6 -c -H __ADDR6__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 5000 10000
+
+
+tr	TCP throughput over IPv4: host to ns
+nsout	ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+bw	-
+bw	-
+bw	-
+iperf3	BW host ns __ADDR__ 100${i}2 __THREADS__ __TIME__ __OPTS__
+bw	__BW__ 8.0 10.0
+
+tl	TCP RR latency over IPv4: host to ns
+lat	-
+lat	-
+lat	-
+nsb	tcp_rr --nolog -P 10002 -C 10012 -4
+hout	LAT tcp_rr --nolog -P 10002 -C 10012 -4 -c -H __ADDR__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 150 100
+
+tl	TCP CRR latency over IPv4: host to ns
+lat	-
+lat	-
+lat	-
+sleep	1
+nsb	tcp_crr --nolog -P 10002 -C 10012 -4
+hout	LAT tcp_crr --nolog -P 10002 -C 10012 -4 -c -H __ADDR__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 5000 10000
+
+te
diff --git a/oldtest/perf/pasta_udp b/oldtest/perf/pasta_udp
new file mode 100644
index 00000000..7007b6fa
--- /dev/null
+++ b/oldtest/perf/pasta_udp
@@ -0,0 +1,219 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/perf/pasta_udp - Check UDP performance in pasta mode
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+htools	bc head ip sleep iperf3 udp_rr jq sed
+nstools	ip sleep iperf3 udp_rr jq sed
+
+test	pasta: throughput and latency (local traffic)
+
+hout	FREQ_PROCFS (echo "scale=1"; sed -n 's/cpu MHz.*: \([0-9]*\)\..*$/(\1+10^2\/2)\/10^3/p' /proc/cpuinfo) | bc -l | head -n1
+hout	FREQ_CPUFREQ (echo "scale=1"; printf '( %i + 10^5 / 2 ) / 10^6\n' $(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq) ) | bc -l
+hout	FREQ [ -n "__FREQ_CPUFREQ__" ] && echo __FREQ_CPUFREQ__ || echo __FREQ_PROCFS__
+
+set	THREADS 1
+set	STREAMS 4
+set	TIME 10
+set	OPTS -u -P __STREAMS__
+
+info	Throughput in Gbps, latency in µs, one thread at __FREQ__ GHz, __STREAMS__ streams
+
+report	pasta lo_udp 1 __FREQ__
+
+th	MTU 1500B 4000B 16384B 65535B
+
+
+tr	UDP throughput over IPv6: ns to host
+ns	ip link set dev lo mtu 1500
+iperf3	BW ns host ::1 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 1.0 1.5
+ns	ip link set dev lo mtu 4000
+iperf3	BW ns host ::1 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 1.2 1.8
+ns	ip link set dev lo mtu 16384
+iperf3	BW ns host ::1 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 10G
+bw	__BW__ 5.0 6.0
+ns	ip link set dev lo mtu 65535
+iperf3	BW ns host ::1 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 15G
+bw	__BW__ 7.0 9.0
+
+tl	UDP RR latency over IPv6: ns to host
+lat	-
+lat	-
+lat	-
+hostb	udp_rr --nolog -P 10003 -C 10013 -6
+nsout	LAT udp_rr --nolog -P 10003 -C 10013 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 200 150
+
+
+tr	UDP throughput over IPv4: ns to host
+ns	ip link set dev lo mtu 1500
+iperf3	BW ns host 127.0.0.1 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 1.0 1.5
+ns	ip link set dev lo mtu 4000
+iperf3	BW ns host 127.0.0.1 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 1.2 1.8
+ns	ip link set dev lo mtu 16384
+iperf3	BW ns host 127.0.0.1 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 10G
+bw	__BW__ 5.0 6.0
+ns	ip link set dev lo mtu 65535
+iperf3	BW ns host 127.0.0.1 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 15G
+bw	__BW__ 7.0 9.0
+
+tl	UDP RR latency over IPv4: ns to host
+lat	-
+lat	-
+lat	-
+hostb	udp_rr --nolog -P 10003 -C 10013 -4
+nsout	LAT udp_rr --nolog -P 10003 -C 10013 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 200 150
+
+
+tr	UDP throughput over IPv6: host to ns
+bw	-
+bw	-
+bw	-
+#iperf3c	host ::1 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 15G
+#iperf3s	BW ns 100${i}2 __THREADS__
+iperf3	BW host ns ::1 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 15G
+bw	__BW__ 7.0 9.0
+
+tl	UDP RR latency over IPv6: host to ns
+lat	-
+lat	-
+lat	-
+nsb	udp_rr --nolog -P 10002 -C 10012 -6
+hout	LAT udp_rr --nolog -P 10002 -C 10012 -6 -c -H ::1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 200 150
+
+
+tr	UDP throughput over IPv4: host to ns
+bw	-
+bw	-
+bw	-
+#iperf3c	host 127.0.0.1 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 15G
+#iperf3s	BW ns 100${i}2 __THREADS__
+iperf3	BW host ns 127.0.0.1 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 15G
+bw	__BW__ 7.0 9.0
+
+tl	UDP RR latency over IPv4: host to ns
+lat	-
+lat	-
+lat	-
+nsb	udp_rr --nolog -P 10002 -C 10012 -4
+hout	LAT udp_rr --nolog -P 10002 -C 10012 -4 -c -H 127.0.0.1 | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 200 150
+
+te
+
+
+
+test	pasta: throughput and latency (traffic via tap)
+
+nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+
+info	Throughput in Gbps, latency in µs, one thread at __FREQ__ GHz, __STREAMS__ streams
+report	pasta tap_udp 1 __FREQ__
+
+th	MTU 1500B 4000B 16384B 65520B
+
+tr	UDP throughput over IPv6: ns to host
+ns	ip link set dev __IFNAME__ mtu 1500
+iperf3	BW ns host __GW6__%__IFNAME__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 2G
+bw	__BW__ 0.3 0.5
+ns	ip link set dev __IFNAME__ mtu 4000
+iperf3	BW ns host __GW6__%__IFNAME__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 0.5 0.8
+ns	ip link set dev __IFNAME__ mtu 16384
+iperf3	BW ns host __GW6__%__IFNAME__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 4G
+bw	__BW__ 3.0 4.0
+ns	ip link set dev __IFNAME__ mtu 65520
+iperf3	BW ns host __GW6__%__IFNAME__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 6G
+bw	__BW__ 6.0 7.0
+
+tl	UDP RR latency over IPv6: ns to host
+lat	-
+lat	-
+lat	-
+hostb	udp_rr --nolog -P 10003 -C 10013 -6
+nsout	LAT udp_rr --nolog -P 10003 -C 10013 -6 -c -H __GW6__%__IFNAME__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 200 150
+
+
+tr	UDP throughput over IPv4: ns to host
+ns	ip link set dev __IFNAME__ mtu 1500
+iperf3	BW ns host __GW__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 2G
+bw	__BW__ 0.3 0.5
+ns	ip link set dev __IFNAME__ mtu 4000
+iperf3	BW ns host __GW__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 3G
+bw	__BW__ 0.5 0.8
+ns	ip link set dev __IFNAME__ mtu 16384
+iperf3	BW ns host __GW__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 4G
+bw	__BW__ 3.0 4.0
+ns	ip link set dev __IFNAME__ mtu 65520
+iperf3	BW ns host __GW__ 100${i}3 __THREADS__ __TIME__ __OPTS__ -b 6G
+bw	__BW__ 6.0 7.0
+
+tl	UDP RR latency over IPv4: ns to host
+lat	-
+lat	-
+lat	-
+hostb	udp_rr --nolog -P 10003 -C 10013 -4
+nsout	LAT udp_rr --nolog -P 10003 -C 10013 -4 -c -H __GW__ | sed -n 's/^throughput=\(.*\)/\1/p'
+hostw
+lat	__LAT__ 200 150
+
+
+tr	UDP throughput over IPv6: host to ns
+nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+nsout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local'
+bw	-
+bw	-
+bw	-
+iperf3	BW host ns __ADDR6__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 15G
+bw	__BW__ 7.0 9.0
+
+tl	UDP RR latency over IPv6: host to ns
+lat	-
+lat	-
+lat	-
+nsb	udp_rr --nolog -P 10002 -C 10012 -6
+hout	LAT udp_rr --nolog -P 10002 -C 10012 -6 -c -H __ADDR6__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 200 150
+
+
+tr	UDP throughput over IPv4: host to ns
+nsout	ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
+bw	-
+bw	-
+bw	-
+iperf3	BW host ns __ADDR__ 100${i}2 __THREADS__ __TIME__ __OPTS__ -b 15G
+bw	__BW__ 7.0 9.0
+
+tl	UDP RR latency over IPv4: host to ns
+lat	-
+lat	-
+lat	-
+nsb	udp_rr --nolog -P 10002 -C 10012 -4
+hout	LAT udp_rr --nolog -P 10002 -C 10012 -4 -c -H __ADDR__ | sed -n 's/^throughput=\(.*\)/\1/p'
+nsw
+lat	__LAT__ 200 150
+
+te
diff --git a/oldtest/prepare-distro-img.sh b/oldtest/prepare-distro-img.sh
new file mode 100755
index 00000000..46bc1269
--- /dev/null
+++ b/oldtest/prepare-distro-img.sh
@@ -0,0 +1,18 @@
+#! /bin/sh -e
+
+IMG="$1"
+PASST_FILES="$(echo ../*.c ../*.h ../*.sh ../*.1 ../Makefile ../README.md)"
+
+virt-edit -a $IMG /lib/systemd/system/serial-getty@.service -e 's/ExecStart=.*/ExecStart=\/sbin\/agetty --autologin root -8 --keep-baud 115200,38400,9600 %I $TERM/g'
+
+guestfish --rw -a $IMG -i <<EOF
+rm-f /usr/lib/systemd/system/cloud-config.service
+rm-f /usr/lib/systemd/system/cloud-init.service
+rm-f /usr/lib/systemd/system/cloud-init-local.service
+rm-f /usr/lib/systemd/system/cloud-final.service
+rm-f /etc/init.d/cloud-config
+rm-f /etc/init.d/cloud-final
+rm-f /etc/init.d/cloud-init
+rm-f /etc/init.d/cloud-init-local
+copy-in $PASST_FILES /root/
+EOF
diff --git a/oldtest/run b/oldtest/run
new file mode 100755
index 00000000..75309f66
--- /dev/null
+++ b/oldtest/run
@@ -0,0 +1,238 @@
+#!/bin/sh -e
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/run - Entry point to run test cases and demo
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+# Start an X terminal and capture a video of the test run (also set for ./ci)
+CI=${CI:-0}
+
+# Start an X terminal and show the demo (also set for ./demo)
+DEMO=${DEMO:-0}
+
+# Base path for output files
+BASEPATH=${BASEPATH:-"$(pwd)"}
+
+# Location of log files for test run
+LOGDIR=${LOGDIR:-"${BASEPATH}/test_logs"}
+LOGFILE=${LOGFILE:-"${LOGDIR}/test.log"}
+
+# If set, skip typing delays while issuing commands in panes
+FAST=${FAST:-1}
+
+# If set, run passt and pasta with debug options
+DEBUG=${DEBUG:-0}
+
+# If set, run passt and pasta with trace options
+TRACE=${TRACE:-0}
+
+# If set, tell passt and pasta to take packet captures
+PCAP=${PCAP:-0}
+
+COMMIT="$(git log --oneline --no-decorate -1)"
+
+. lib/util
+. lib/context
+. lib/setup
+. lib/setup_ugly
+. lib/term
+. lib/perf_report
+. lib/layout
+. lib/layout_ugly
+. lib/test
+. lib/video
+
+# cleanup() - Remove temporary files
+cleanup() {
+	[ ${DEBUG} -eq 1 ] || rm -rf "${STATEBASE}"
+}
+
+# run() - Call setup functions, run tests, handle exit from test session
+run() {
+	mkfifo $STATEBASE/log_pipe
+
+	term
+	perf_init
+	[ ${CI} -eq 1 ]   && video_start ci
+
+	setup build
+#	test build/all
+#	test build/cppcheck
+#	test build/clang_tidy
+	teardown build
+
+#	setup pasta
+#	test pasta/ndp
+#	test pasta/dhcp
+#	test pasta/tcp
+#	test pasta/udp
+#	test passt/shutdown
+#	teardown pasta
+
+#	setup pasta_options
+#	test pasta_options/log_to_file
+#	teardown pasta_options
+
+#	setup memory
+#	test memory/passt
+#	teardown memory
+
+#	setup passt
+#	test passt/ndp
+#	test passt/dhcp
+#	test passt/tcp
+#	test passt/udp
+#	test passt/shutdown
+#	teardown passt
+
+#	VALGRIND=1
+#	setup passt_in_ns
+#	test passt/ndp
+#	test passt/dhcp
+#	test passt_in_ns/icmp
+#	test passt_in_ns/tcp
+#	test passt_in_ns/udp
+#	test passt_in_ns/shutdown
+#	teardown passt_in_ns
+
+#	setup two_guests
+#	test two_guests/basic
+#	teardown two_guests
+
+#	VALGRIND=0
+#	setup passt_in_ns
+#	test passt/ndp
+#	test passt/dhcp
+#	test perf/passt_tcp
+#	test perf/passt_udp
+#	test perf/pasta_tcp
+#	test perf/pasta_udp
+#	test passt_in_ns/shutdown
+#	teardown passt_in_ns
+
+	# TODO: Make those faster by at least pre-installing gcc and make on
+	# non-x86 images, then re-enable.
+skip_distro() {
+	setup distro
+	test distro/debian
+	test distro/fedora
+	test distro/opensuse
+	test distro/ubuntu
+	teardown distro
+}
+
+	perf_finish
+	[ ${CI} -eq 1 ] && video_stop
+
+	log "PASS: ${STATUS_PASS}, FAIL: ${STATUS_FAIL}"
+
+	pause_continue \
+		"Press any key to keep test session open"	\
+		"Closing in "					\
+		"Interrupted, press any key to quit"		\
+		9
+
+	return 0
+}
+
+# run_selected() - Run list of tests, with setup/teardown based on test path
+# $@:	List of tests
+run_selected() {
+	mkfifo $STATEBASE/log_pipe
+
+	term
+	VALGRIND=1
+
+	__setup=
+	for __test; do
+		if [ "${__test%%/*}" != "${__setup}" ]; then
+			[ -n "${__setup}" ] && teardown "${__setup}"
+			__setup="${__test%%/*}"
+			setup "${__setup}"
+		fi
+
+		test "${__test}"
+	done
+	teardown "${__setup}"
+
+	log "PASS: ${STATUS_PASS}, FAIL: ${STATUS_FAIL}"
+
+	pause_continue \
+		"Press any key to keep test session open"	\
+		"Closing in "					\
+		"Interrupted, press any key to quit"		\
+		9
+
+	return 0
+}
+
+# demo() - Simpler path for demo purposes
+demo() {
+	mkfifo $STATEBASE/log_pipe
+
+	FAST=0
+
+	term_demo
+
+	layout_demo_passt
+	video_start demo_passt
+	test demo/passt
+	video_stop
+	teardown demo_passt
+
+	layout_demo_pasta
+	video_start demo_pasta
+	test demo/pasta
+	video_stop
+	teardown demo_pasta
+
+	layout_demo_podman
+	video_start demo_podman
+	test demo/podman
+	video_stop
+	teardown_demo_podman
+
+	return 0
+}
+
+[ "$(basename "${0}")" = "ci" ]       && CI=1
+[ "$(basename "${0}")" = "run_demo" ] && DEMO=1
+
+if [ "${1}" = "from_term" ]; then
+	shift
+
+	exec > ${LOGDIR}/script.log 2>&1
+	[ ${DEBUG} -eq 1 ] && set -x
+	cd ..
+	if [ ${DEMO} -eq 1 ]; then
+		demo
+	elif [ -n "${1}" ]; then
+		run_selected ${*}
+	else
+		run
+	fi
+	tmux kill-session -t passt_test
+	exit
+else
+	rm -rf "${LOGDIR}"
+	mkdir -p "${LOGDIR}"
+	:> "${LOGFILE}"
+	STATEBASE="$(mktemp -d --tmpdir passt-tests-XXXXXX)"
+	trap "cleanup" EXIT
+	run_term ${*}
+fi
+
+[ ${DEMO} -eq 1 ] && exit 0
+
+tail -n1 ${LOGFILE}
+echo "Log at ${LOGFILE}"
+exit $(tail -n1 ${LOGFILE} | sed -n 's/.*FAIL: \(.*\)$/\1/p')
diff --git a/oldtest/run_demo b/oldtest/run_demo
new file mode 120000
index 00000000..e5224d53
--- /dev/null
+++ b/oldtest/run_demo
@@ -0,0 +1 @@
+run
\ No newline at end of file
diff --git a/oldtest/two_guests/basic b/oldtest/two_guests/basic
new file mode 100644
index 00000000..09fbd3ef
--- /dev/null
+++ b/oldtest/two_guests/basic
@@ -0,0 +1,80 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# PASST - Plug A Simple Socket Transport
+#  for qemu/UNIX domain socket mode
+#
+# PASTA - Pack A Subtle Tap Abstraction
+#  for network namespace/tap device mode
+#
+# test/two_guests/basic - Check basic functionality with two guests
+#
+# Copyright (c) 2021 Red Hat GmbH
+# Author: Stefano Brivio <sbrivio@redhat.com>
+
+g1tools	ip jq dhclient socat cat
+g2tools	ip jq dhclient socat cat
+htools	ip jq
+
+test	Interface names
+g1out	IFNAME1 ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+g2out	IFNAME2 ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
+hout	HOST_IFNAME ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
+check	[ -n "__IFNAME1__" ]
+check	[ -n "__IFNAME2__" ]
+
+test	DHCP: addresses
+guest1	ip link set dev __IFNAME1__ up
+guest1	/sbin/dhclient -4 __IFNAME1__
+guest2	ip link set dev __IFNAME2__ up
+guest2	/sbin/dhclient -4 __IFNAME2__
+g1out	ADDR1 ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME1__").addr_info[0].local'
+g2out	ADDR2 ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME2__").addr_info[0].local'
+hout	HOST_ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME__").addr_info[0].local'
+check	[ "__ADDR1__" = "__HOST_ADDR__" ]
+check	[ "__ADDR2__" = "__HOST_ADDR__" ]
+
+test	DHCPv6: addresses
+# Link is up now, wait for DAD to complete
+sleep	2
+guest1	/sbin/dhclient -6 __IFNAME1__
+guest2	/sbin/dhclient -6 __IFNAME2__
+g1out	ADDR1_6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME1__").addr_info[] | select(.prefixlen == 128).local'
+g2out	ADDR2_6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME2__").addr_info[] | select(.prefixlen == 128).local'
+hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
+check	[ "__ADDR1_6__" = "__HOST_ADDR6__" ]
+check	[ "__ADDR2_6__" = "__HOST_ADDR6__" ]
+
+test	TCP/IPv4: guest 1 > guest 2
+g1out	GW1 ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
+guest2b	socat -u TCP4-LISTEN:10004 OPEN:msg,create,trunc
+guest1	echo "Hello_from_guest_1" | socat -u STDIN TCP4:__GW1__:10004
+guest2w
+sleep	1
+g2out	MSG2 cat msg
+check	[ "__MSG2__" = "Hello_from_guest_1" ]
+
+test	TCP/IPv6: guest 2 > guest 1
+g2out	GW2_6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
+guest1b	socat -u TCP6-LISTEN:10001 OPEN:msg,create,trunc
+guest2	echo "Hello_from_guest_2" | socat -u STDIN TCP6:[__GW2_6__%__IFNAME2__]:10001
+guest1w
+sleep	1
+g1out	MSG1 cat msg
+check	[ "__MSG1__" = "Hello_from_guest_2" ]
+
+test	UDP/IPv4: guest 1 > guest 2
+guest2b	socat -u TCP4-LISTEN:10004 OPEN:msg,create,trunc
+guest1	echo "Hello_from_guest_1" | socat -u STDIN TCP4:__GW1__:10004
+guest2w
+sleep	1
+g2out	MSG2 cat msg
+check	[ "__MSG2__" = "Hello_from_guest_1" ]
+
+test	UDP/IPv6: guest 2 > guest 1
+guest1b	socat -u TCP6-LISTEN:10001 OPEN:msg,create,trunc
+guest2	echo "Hello_from_guest_2" | socat -u STDIN TCP6:[__GW2_6__%__IFNAME2__]:10001
+guest1w
+sleep	1
+g1out	MSG1 cat msg
+check	[ "__MSG1__" = "Hello_from_guest_2" ]
diff --git a/oldtest/valgrind.supp b/oldtest/valgrind.supp
new file mode 100644
index 00000000..12280567
--- /dev/null
+++ b/oldtest/valgrind.supp
@@ -0,0 +1,9 @@
+# tcp_sock_consume() calls recv() with MSG_TRUNC and no buffer to discard data
+{
+   passt_recv_MSG_TRUNC_into_NULL_buffer
+   Memcheck:Param
+   socketcall.recvfrom(buf)
+   fun:recv
+   ...
+   fun:tcp_sock_consume*
+}
-- 
@@ -0,0 +1,9 @@
+# tcp_sock_consume() calls recv() with MSG_TRUNC and no buffer to discard data
+{
+   passt_recv_MSG_TRUNC_into_NULL_buffer
+   Memcheck:Param
+   socketcall.recvfrom(buf)
+   fun:recv
+   ...
+   fun:tcp_sock_consume*
+}
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 02/27] avocado: Don't double download assets for test/ and oldtest/
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
  2023-06-27  2:54 ` [PATCH 01/27] avocado: Make a duplicate copy of testsuite for comparison purposes David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 03/27] avocado: Move static checkers to avocado David Gibson
                   ` (24 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

oldtest/ is a snapshot of test/ for comparison purposes.  However this
means its Makefile will make an additional download of everything we
download in test/, which takes rather a lot of space.

Alter oldtest/Makefile to re-use the downloads from test/ via symlinks.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 oldtest/.gitignore |  2 +-
 oldtest/Makefile   | 94 +++-------------------------------------------
 2 files changed, 6 insertions(+), 90 deletions(-)

diff --git a/oldtest/.gitignore b/oldtest/.gitignore
index 48374028..2652ce7f 100644
--- a/oldtest/.gitignore
+++ b/oldtest/.gitignore
@@ -1,5 +1,5 @@
 test_logs/
-mbuto/
+mbuto
 *.img
 QEMU_EFI.fd
 *.qcow2
diff --git a/oldtest/Makefile b/oldtest/Makefile
index 7b00bef4..7949ceda 100644
--- a/oldtest/Makefile
+++ b/oldtest/Makefile
@@ -67,9 +67,6 @@ CFLAGS = -Wall -Werror -Wextra -pedantic -std=c99
 
 assets: $(ASSETS)
 
-mbuto:
-	git clone git://mbuto.sh/mbuto
-
 guest-key guest-key.pub:
 	ssh-keygen -f guest-key -N ''
 
@@ -113,91 +110,10 @@ clean:
 	rm -f $(LOCAL_ASSETS)
 	rm -rf test_logs
 	rm -f prepared-*.qcow2 prepared-*.img
-
-realclean: clean
 	rm -rf $(DOWNLOAD_ASSETS)
 
-# Debian downloads
-debian-8.11.0-openstack-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/OpenStack/archive/8.11.0/debian-8.11.0-openstack-$*.qcow2
-
-debian-9-nocloud-%-daily-20200210-166.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/stretch/daily/20200210-166/debian-9-nocloud-$*-daily-20200210-166.qcow2
-
-debian-10-nocloud-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/latest/debian-10-nocloud-$*.qcow2
-
-debian-10-generic-ppc64el-20220911-1135.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/20220911-1135/debian-10-generic-ppc64el-20220911-1135.qcow2
-
-debian-10-generic-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/latest/debian-10-generic-$*.qcow2
-
-debian-11-nocloud-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-nocloud-$*.qcow2
-
-debian-11-generic-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-$*.qcow2
-
-debian-sid-nocloud-%-daily.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/sid/daily/latest/debian-sid-nocloud-$*-daily.qcow2
-
-# Fedora downloads
-Fedora-Cloud-Base-26-1.5.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/26/CloudImages/$*/images/Fedora-Cloud-Base-26-1.5.$*.qcow2
-
-Fedora-Cloud-Base-27-1.6.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/27/CloudImages/$*/images/Fedora-Cloud-Base-27-1.6.$*.qcow2
-
-Fedora-Cloud-Base-28-1.1.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/28/Cloud/$*/images/Fedora-Cloud-Base-28-1.1.$*.qcow2
-
-Fedora-Cloud-Base-29-1.2.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/29/Cloud/$*/images/Fedora-Cloud-Base-29-1.2.$*.qcow2
-
-Fedora-Cloud-Base-30-1.2.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/30/Cloud/$*/images/Fedora-Cloud-Base-30-1.2.$*.qcow2
-
-Fedora-Cloud-Base-31-1.9.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/31/Cloud/$*/images/Fedora-Cloud-Base-31-1.9.$*.qcow2
-
-Fedora-Cloud-Base-32-1.6.%.qcow2:
-	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/32/Cloud/$*/images/Fedora-Cloud-Base-32-1.6.$*.qcow2
-
-Fedora-Cloud-Base-33-1.2.%.qcow2:
-	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/33/Cloud/$*/images/Fedora-Cloud-Base-33-1.2.$*.qcow2
-
-Fedora-Cloud-Base-34-1.2.%.qcow2:
-	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/34/Cloud/$*/images/Fedora-Cloud-Base-34-1.2.$*.qcow2
-
-Fedora-Cloud-Base-35-1.2.%.qcow2:
-	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/35/Cloud/$*/images/Fedora-Cloud-Base-35-1.2.$*.qcow2
-
-# OpenSuSE downloads
-openSUSE-Leap-15.1-JeOS.x86_64-kvm-and-xen.qcow2:
-	$(WGET) -O $@ https://download.opensuse.org/distribution/leap/15.1/jeos/openSUSE-Leap-15.1-JeOS.x86_64-kvm-and-xen.qcow2
-
-openSUSE-Leap-15.2-JeOS.x86_64-kvm-and-xen.qcow2:
-	$(WGET) -O $@ https://download.opensuse.org/distribution/leap/15.2/appliances/openSUSE-Leap-15.2-JeOS.x86_64-kvm-and-xen.qcow2
-
-openSUSE-Leap-15.3-JeOS.x86_64-kvm-and-xen.qcow2:
-	$(WGET) -O $@ https://download.opensuse.org/distribution/leap/15.3/appliances/openSUSE-Leap-15.3-JeOS.x86_64-kvm-and-xen.qcow2
-
-openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz:
-	$(WGET) -O $@ http://download.opensuse.org/ports/aarch64/tumbleweed/appliances/openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz
-
-openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz:
-	$(WGET) -O $@ http://download.opensuse.org/ports/armv7hl/tumbleweed/appliances/openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz
-
-openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2:
-	$(WGET) -O $@ https://download.opensuse.org/tumbleweed/appliances/openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2
-
-# Ubuntu downloads
-trusty-server-cloudimg-%-disk1.img:
-	$(WGET) -O $@ https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-$*-disk1.img
-
-xenial-server-cloudimg-powerpc-disk1.img:
-	$(WGET) -O $@ https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-powerpc-disk1.img
-
-jammy-server-cloudimg-s390x.img:
-	$(WGET) -O $@ https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-s390x.img
+# "Downloads"
+$(DOWNLOAD_ASSETS): %:
+#debian-%.qcow2 Fedora-%.qcow2 openSUSE-%.qcow2 openSUSE-%.raw.xz trusty-%.img xenial-%.img jammy-%.img:
+	$(MAKE) -C ../test $@
+	ln -s ../test/$@ $@
-- 
@@ -67,9 +67,6 @@ CFLAGS = -Wall -Werror -Wextra -pedantic -std=c99
 
 assets: $(ASSETS)
 
-mbuto:
-	git clone git://mbuto.sh/mbuto
-
 guest-key guest-key.pub:
 	ssh-keygen -f guest-key -N ''
 
@@ -113,91 +110,10 @@ clean:
 	rm -f $(LOCAL_ASSETS)
 	rm -rf test_logs
 	rm -f prepared-*.qcow2 prepared-*.img
-
-realclean: clean
 	rm -rf $(DOWNLOAD_ASSETS)
 
-# Debian downloads
-debian-8.11.0-openstack-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/OpenStack/archive/8.11.0/debian-8.11.0-openstack-$*.qcow2
-
-debian-9-nocloud-%-daily-20200210-166.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/stretch/daily/20200210-166/debian-9-nocloud-$*-daily-20200210-166.qcow2
-
-debian-10-nocloud-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/latest/debian-10-nocloud-$*.qcow2
-
-debian-10-generic-ppc64el-20220911-1135.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/20220911-1135/debian-10-generic-ppc64el-20220911-1135.qcow2
-
-debian-10-generic-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/buster/latest/debian-10-generic-$*.qcow2
-
-debian-11-nocloud-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-nocloud-$*.qcow2
-
-debian-11-generic-%.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-$*.qcow2
-
-debian-sid-nocloud-%-daily.qcow2:
-	$(WGET) -O $@ https://cloud.debian.org/images/cloud/sid/daily/latest/debian-sid-nocloud-$*-daily.qcow2
-
-# Fedora downloads
-Fedora-Cloud-Base-26-1.5.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/26/CloudImages/$*/images/Fedora-Cloud-Base-26-1.5.$*.qcow2
-
-Fedora-Cloud-Base-27-1.6.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/27/CloudImages/$*/images/Fedora-Cloud-Base-27-1.6.$*.qcow2
-
-Fedora-Cloud-Base-28-1.1.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/28/Cloud/$*/images/Fedora-Cloud-Base-28-1.1.$*.qcow2
-
-Fedora-Cloud-Base-29-1.2.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/29/Cloud/$*/images/Fedora-Cloud-Base-29-1.2.$*.qcow2
-
-Fedora-Cloud-Base-30-1.2.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/30/Cloud/$*/images/Fedora-Cloud-Base-30-1.2.$*.qcow2
-
-Fedora-Cloud-Base-31-1.9.%.qcow2:
-	$(WGET) -O $@ http://archives.fedoraproject.org/pub/archive/fedora/linux/releases/31/Cloud/$*/images/Fedora-Cloud-Base-31-1.9.$*.qcow2
-
-Fedora-Cloud-Base-32-1.6.%.qcow2:
-	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/32/Cloud/$*/images/Fedora-Cloud-Base-32-1.6.$*.qcow2
-
-Fedora-Cloud-Base-33-1.2.%.qcow2:
-	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/33/Cloud/$*/images/Fedora-Cloud-Base-33-1.2.$*.qcow2
-
-Fedora-Cloud-Base-34-1.2.%.qcow2:
-	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/34/Cloud/$*/images/Fedora-Cloud-Base-34-1.2.$*.qcow2
-
-Fedora-Cloud-Base-35-1.2.%.qcow2:
-	$(WGET) -O $@ https://archives.fedoraproject.org/pub/archive/fedora/linux/releases/35/Cloud/$*/images/Fedora-Cloud-Base-35-1.2.$*.qcow2
-
-# OpenSuSE downloads
-openSUSE-Leap-15.1-JeOS.x86_64-kvm-and-xen.qcow2:
-	$(WGET) -O $@ https://download.opensuse.org/distribution/leap/15.1/jeos/openSUSE-Leap-15.1-JeOS.x86_64-kvm-and-xen.qcow2
-
-openSUSE-Leap-15.2-JeOS.x86_64-kvm-and-xen.qcow2:
-	$(WGET) -O $@ https://download.opensuse.org/distribution/leap/15.2/appliances/openSUSE-Leap-15.2-JeOS.x86_64-kvm-and-xen.qcow2
-
-openSUSE-Leap-15.3-JeOS.x86_64-kvm-and-xen.qcow2:
-	$(WGET) -O $@ https://download.opensuse.org/distribution/leap/15.3/appliances/openSUSE-Leap-15.3-JeOS.x86_64-kvm-and-xen.qcow2
-
-openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz:
-	$(WGET) -O $@ http://download.opensuse.org/ports/aarch64/tumbleweed/appliances/openSUSE-Tumbleweed-ARM-JeOS-efi.aarch64.raw.xz
-
-openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz:
-	$(WGET) -O $@ http://download.opensuse.org/ports/armv7hl/tumbleweed/appliances/openSUSE-Tumbleweed-ARM-JeOS-efi.armv7l.raw.xz
-
-openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2:
-	$(WGET) -O $@ https://download.opensuse.org/tumbleweed/appliances/openSUSE-Tumbleweed-JeOS.x86_64-kvm-and-xen.qcow2
-
-# Ubuntu downloads
-trusty-server-cloudimg-%-disk1.img:
-	$(WGET) -O $@ https://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-$*-disk1.img
-
-xenial-server-cloudimg-powerpc-disk1.img:
-	$(WGET) -O $@ https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-powerpc-disk1.img
-
-jammy-server-cloudimg-s390x.img:
-	$(WGET) -O $@ https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-s390x.img
+# "Downloads"
+$(DOWNLOAD_ASSETS): %:
+#debian-%.qcow2 Fedora-%.qcow2 openSUSE-%.qcow2 openSUSE-%.raw.xz trusty-%.img xenial-%.img jammy-%.img:
+	$(MAKE) -C ../test $@
+	ln -s ../test/$@ $@
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 03/27] avocado: Move static checkers to avocado
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
  2023-06-27  2:54 ` [PATCH 01/27] avocado: Make a duplicate copy of testsuite for comparison purposes David Gibson
  2023-06-27  2:54 ` [PATCH 02/27] avocado: Don't double download assets for test/ and oldtest/ David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 04/27] avocado: Introduce "avocado-classless" plugin, runner and outline David Gibson
                   ` (23 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Move these, the simplest of our tests, to be run from avocado rather than
our hand-rolled test harness.  We include logic in the Makefile to install
avocado in an isolated venv if it's not available on the system.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 Makefile                                   |  9 +++++
 oldtest/run                                |  4 +--
 test/.gitignore                            |  1 +
 test/Makefile                              | 38 +++++++++++++++++++---
 test/avocado/static_checkers/clang-tidy.sh |  3 ++
 test/avocado/static_checkers/cppcheck.sh   |  3 ++
 test/build/clang_tidy                      | 17 ----------
 test/build/cppcheck                        | 17 ----------
 test/run                                   |  2 --
 9 files changed, 52 insertions(+), 42 deletions(-)
 create mode 100755 test/avocado/static_checkers/clang-tidy.sh
 create mode 100755 test/avocado/static_checkers/cppcheck.sh
 delete mode 100644 test/build/clang_tidy
 delete mode 100644 test/build/cppcheck

diff --git a/Makefile b/Makefile
index a5256f58..7b911f65 100644
--- a/Makefile
+++ b/Makefile
@@ -135,6 +135,7 @@ clean:
 	$(RM) $(BIN) *~ *.o seccomp.h pasta.1 \
 		passt.tar passt.tar.gz *.deb *.rpm \
 		passt.pid README.plain.md
+	$(MAKE) -C test clean
 
 install: $(BIN) $(MANPAGES) docs
 	mkdir -p $(DESTDIR)$(bindir) $(DESTDIR)$(man1dir)
@@ -294,3 +295,11 @@ cppcheck: $(SRCS) $(HEADERS)
 	--suppress=unusedStructMember					\
 	$(filter -D%,$(FLAGS) $(CFLAGS) $(CPPFLAGS))			\
 	.
+
+.PHONY: avocado
+avocado:
+	$(MAKE) -C test avocado
+
+.PHONY: check
+check:
+	$(MAKE) -C test check
diff --git a/oldtest/run b/oldtest/run
index 75309f66..56fcd1b3 100755
--- a/oldtest/run
+++ b/oldtest/run
@@ -66,8 +66,8 @@ run() {
 
 	setup build
 #	test build/all
-#	test build/cppcheck
-#	test build/clang_tidy
+	test build/cppcheck
+	test build/clang_tidy
 	teardown build
 
 #	setup pasta
diff --git a/test/.gitignore b/test/.gitignore
index 48374028..bf84c336 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -9,3 +9,4 @@ QEMU_EFI.fd
 nstool
 guest-key
 guest-key.pub
+/venv/
diff --git a/test/Makefile b/test/Makefile
index 7b00bef4..d4d0c1a3 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -102,19 +102,19 @@ medium.bin:
 big.bin:
 	dd if=/dev/urandom bs=1M count=10 of=$@
 
-check: assets
+check-legacy: assets
 	./run
 
 debug: assets
 	DEBUG=1 ./run
 
-clean:
-	rm -f perf.js *~
+clean-legacy:
+	rm -f perf.js
 	rm -f $(LOCAL_ASSETS)
 	rm -rf test_logs
 	rm -f prepared-*.qcow2 prepared-*.img
 
-realclean: clean
+realclean: clean-legacy
 	rm -rf $(DOWNLOAD_ASSETS)
 
 # Debian downloads
@@ -201,3 +201,33 @@ xenial-server-cloudimg-powerpc-disk1.img:
 
 jammy-server-cloudimg-s390x.img:
 	$(WGET) -O $@ https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-s390x.img
+
+#
+# New style tests based on Avocado
+#
+
+PYTHON = python3
+VENV = venv
+
+AVOCADO := $(shell which avocado)
+ifeq ($(AVOCADO),)
+AVOCADO := $(VENV)/bin/avocado
+endif
+
+$(VENV):
+	$(PYTHON) -m venv $(VENV)
+	$(VENV)/bin/pip install avocado-framework
+
+.PHONY: avocado-assets
+avocado-assets:
+
+.PHONY: avocado
+avocado: avocado-assets $(VENV)
+	$(AVOCADO) run avocado
+
+.PHONY: check
+check: avocado check-legacy
+
+clean: clean-legacy
+	$(RM) -r $(VENV)
+	find -name *~ | xargs $(RM)
diff --git a/test/avocado/static_checkers/clang-tidy.sh b/test/avocado/static_checkers/clang-tidy.sh
new file mode 100755
index 00000000..fe20d264
--- /dev/null
+++ b/test/avocado/static_checkers/clang-tidy.sh
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+make -C .. clang-tidy
diff --git a/test/avocado/static_checkers/cppcheck.sh b/test/avocado/static_checkers/cppcheck.sh
new file mode 100755
index 00000000..85670dd9
--- /dev/null
+++ b/test/avocado/static_checkers/cppcheck.sh
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+make -C .. cppcheck
diff --git a/test/build/clang_tidy b/test/build/clang_tidy
deleted file mode 100644
index 40573bfd..00000000
--- a/test/build/clang_tidy
+++ /dev/null
@@ -1,17 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-#
-# PASST - Plug A Simple Socket Transport
-#  for qemu/UNIX domain socket mode
-#
-# PASTA - Pack A Subtle Tap Abstraction
-#  for network namespace/tap device mode
-#
-# test/build/clang_tidy - Run source through clang-tidy(1) linter
-#
-# Copyright (c) 2021 Red Hat GmbH
-# Author: Stefano Brivio <sbrivio@redhat.com>
-
-htools	clang-tidy
-
-test	Run clang-tidy
-host	make clang-tidy
diff --git a/test/build/cppcheck b/test/build/cppcheck
deleted file mode 100644
index 0e1dbced..00000000
--- a/test/build/cppcheck
+++ /dev/null
@@ -1,17 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-#
-# PASST - Plug A Simple Socket Transport
-#  for qemu/UNIX domain socket mode
-#
-# PASTA - Pack A Subtle Tap Abstraction
-#  for network namespace/tap device mode
-#
-# test/build/cppcheck - Run source through cppcheck(1) linter
-#
-# Copyright (c) 2021 Red Hat GmbH
-# Author: Stefano Brivio <sbrivio@redhat.com>
-
-htools	cppcheck
-
-test	Run cppcheck
-host	make cppcheck
diff --git a/test/run b/test/run
index 8f4f8459..ce24f446 100755
--- a/test/run
+++ b/test/run
@@ -66,8 +66,6 @@ run() {
 
 	setup build
 	test build/all
-	test build/cppcheck
-	test build/clang_tidy
 	teardown build
 
 	setup pasta
-- 
@@ -66,8 +66,6 @@ run() {
 
 	setup build
 	test build/all
-	test build/cppcheck
-	test build/clang_tidy
 	teardown build
 
 	setup pasta
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 04/27] avocado: Introduce "avocado-classless" plugin, runner and outline
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (2 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 03/27] avocado: Move static checkers to avocado David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 05/27] avocado, test: Add static checkers for Python code David Gibson
                   ` (22 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

We want to use avocado for tests, but we don't like its default jUnit style
of test presentation.  So, we plan to use some custom plugins to make
test writing more approachable.  Start with the implementation of the
"runner" plugin.  We also add some supporting pieces: an interim trivial
resolver plugin and setup.py so we can actually do some stuff with that
runner.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/.gitignore                               |   1 +
 test/Makefile                                 |   6 +-
 test/avocado_classless/.gitignore             |   1 +
 .../avocado_classless/__init__.py             |  11 +
 .../avocado_classless/plugin.py               | 216 ++++++++++++++++++
 test/avocado_classless/examples.py            |  16 ++
 test/avocado_classless/setup.py               |  32 +++
 7 files changed, 282 insertions(+), 1 deletion(-)
 create mode 100644 test/avocado_classless/.gitignore
 create mode 100644 test/avocado_classless/avocado_classless/__init__.py
 create mode 100644 test/avocado_classless/avocado_classless/plugin.py
 create mode 100644 test/avocado_classless/examples.py
 create mode 100644 test/avocado_classless/setup.py

diff --git a/test/.gitignore b/test/.gitignore
index bf84c336..d376dac3 100644
--- a/test/.gitignore
+++ b/test/.gitignore
@@ -10,3 +10,4 @@ nstool
 guest-key
 guest-key.pub
 /venv/
+__pycache__/
diff --git a/test/Makefile b/test/Makefile
index d4d0c1a3..9b3af410 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -208,8 +208,10 @@ jammy-server-cloudimg-s390x.img:
 
 PYTHON = python3
 VENV = venv
+PLUGIN = avocado_classless
 
-AVOCADO := $(shell which avocado)
+# Put this back if/when the plugin becomes available in upstream/system avocado
+#AVOCADO := $(shell which avocado)
 ifeq ($(AVOCADO),)
 AVOCADO := $(VENV)/bin/avocado
 endif
@@ -217,6 +219,7 @@ endif
 $(VENV):
 	$(PYTHON) -m venv $(VENV)
 	$(VENV)/bin/pip install avocado-framework
+	$(VENV)/bin/pip install -e ./$(PLUGIN)
 
 .PHONY: avocado-assets
 avocado-assets:
@@ -231,3 +234,4 @@ check: avocado check-legacy
 clean: clean-legacy
 	$(RM) -r $(VENV)
 	find -name *~ | xargs $(RM)
+	find -name __pycache__ -or -name '*.egg-info' | xargs $(RM) -r
diff --git a/test/avocado_classless/.gitignore b/test/avocado_classless/.gitignore
new file mode 100644
index 00000000..865e3c86
--- /dev/null
+++ b/test/avocado_classless/.gitignore
@@ -0,0 +1 @@
+avocado_classless.egg-info/
diff --git a/test/avocado_classless/avocado_classless/__init__.py b/test/avocado_classless/avocado_classless/__init__.py
new file mode 100644
index 00000000..738185c1
--- /dev/null
+++ b/test/avocado_classless/avocado_classless/__init__.py
@@ -0,0 +1,11 @@
+#! /usr/bin/python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Avocado plugin to allow tests as plain Python functions, no
+confusing classes or other jUnit-derived needless OO stuff.
+"""
diff --git a/test/avocado_classless/avocado_classless/plugin.py b/test/avocado_classless/avocado_classless/plugin.py
new file mode 100644
index 00000000..48b89ce9
--- /dev/null
+++ b/test/avocado_classless/avocado_classless/plugin.py
@@ -0,0 +1,216 @@
+#! /usr/bin/python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Implementation of the Avocado resolver and runner for classless tests.
+"""
+
+import importlib
+import multiprocessing
+import os.path
+import sys
+import time
+import traceback
+
+from avocado.core.extension_manager import PluginPriority
+from avocado.core.test import Test, TestID
+from avocado.core.nrunner.app import BaseRunnerApp
+from avocado.core.nrunner.runnable import Runnable
+from avocado.core.nrunner.runner import (
+    RUNNER_RUN_CHECK_INTERVAL,
+    RUNNER_RUN_STATUS_INTERVAL,
+    BaseRunner,
+)
+from avocado.core.plugin_interfaces import Resolver
+from avocado.core.resolver import (
+    ReferenceResolution,
+    ReferenceResolutionResult,
+    check_file
+)
+from avocado.core.utils import messages
+
+
+SHBANG = b'#! /usr/bin/env avocado-runner-avocado-classless'
+DEFAULT_TIMEOUT = 5.0
+
+
+def load_mod(path):
+    """Load a module containing classless tests"""
+    modname = os.path.basename(path)[:-3]
+    moddir = os.path.abspath(os.path.dirname(path))
+
+    try:
+        sys.path.insert(0, moddir)
+        return importlib.import_module(modname)
+    finally:
+        if moddir in sys.path:
+            sys.path.remove(moddir)
+
+
+class ClasslessResolver(Resolver):
+    """Resolver plugin for classless tests"""
+    # pylint: disable=R0903
+
+    name = "avocado-classless"
+    description = "Resolver for classless tests (not jUnit style)"
+    priority = PluginPriority.HIGH
+
+    def resolve(self, reference):
+        path, _ = reference.rsplit(':', 1)
+
+        # First check it looks like a Python file
+        filecheck = check_file(path, reference)
+        if filecheck is not True:
+            return filecheck
+
+        # Then check it looks like an avocado-classless file
+        with open(path, 'rb') as testfile:
+            shbang = testfile.readline()
+        if shbang.strip() != SHBANG:
+            return ReferenceResolution(
+                reference,
+                ReferenceResolutionResult.NOTFOUND,
+                info=f'{path} does not have first line "{SHBANG}" line',
+            )
+
+        return ReferenceResolution(
+            reference,
+            ReferenceResolutionResult.SUCCESS,
+            [Runnable("avocado-classless", reference)]
+        )
+
+
+def run_classless(runnable, queue):
+    """Invoked within isolating process, run classless tests"""
+    try:
+        path, testname = runnable.uri.rsplit(':', 1)
+        mod = load_mod(path)
+        test = getattr(mod, testname)
+
+        class ClasslessTest(Test):
+            """Shim class for classless tests"""
+            def test(self):
+                """Execute classless test"""
+                test()
+
+        result_dir = runnable.output_dir
+        instance = ClasslessTest(
+            name=TestID(0, runnable.uri),
+            config=runnable.config,
+            base_logdir=result_dir,
+        )
+
+        messages.start_logging(runnable.config, queue)
+
+        instance.run_avocado()
+
+        state = instance.get_state()
+        fail_reason = state.get("fail_reason")
+        queue.put(
+            messages.FinishedMessage.get(
+                state["status"].lower(),
+                fail_reason=fail_reason,
+                fail_class=state.get("fail_class"),
+                traceback=state.get("traceback"),
+            )
+        )
+    except Exception as exc:  # pylint: disable=W0718
+        queue.put(messages.StderrMessage.get(traceback.format_exc()))
+        queue.put(
+            messages.FinishedMessage.get(
+                "error",
+                fail_reason=str(exc),
+                fail_class=exc.__class__.__name__,
+                traceback=traceback.format_exc(),
+            )
+        )
+
+
+class ClasslessRunner(BaseRunner):
+    """Runner for classless tests
+
+    When creating the Runnable, use the following attributes:
+
+     * kind: should be 'avocado-classless';
+
+     * uri: path to a test file, then ':' then a function name within that file
+
+     * args: not used;
+
+     * kwargs: not used;
+
+    Example:
+
+       runnable = Runnable(kind='avocado-classless',
+                           uri='example.py:test_example')
+    """
+
+    name = "avocado-classless"
+    description = "Runner for classless tests (not jUnit style)"
+
+    CONFIGURATION_USED = [
+        "core.show",
+        "job.run.store_logging_stream",
+    ]
+
+    def run(self, runnable):
+        yield messages.StartedMessage.get()
+        try:
+            queue = multiprocessing.SimpleQueue()
+            process = multiprocessing.Process(
+                target=run_classless, args=(runnable, queue)
+            )
+            process.start()
+
+            time_started = time.monotonic()
+            timeout = DEFAULT_TIMEOUT
+            next_status_time = None
+            while True:
+                time.sleep(RUNNER_RUN_CHECK_INTERVAL)
+                now = time.monotonic()
+                if queue.empty():
+                    if (
+                        next_status_time is None
+                        or now > next_status_time
+                    ):
+                        next_status_time = now + RUNNER_RUN_STATUS_INTERVAL
+                        yield messages.RunningMessage.get()
+                    if (now - time_started) > timeout:
+                        process.terminate()
+                        yield messages.FinishedMessage.get("interrupted",
+                                                           "timeout")
+                        break
+                else:
+                    message = queue.get()
+                    yield message
+                    if message.get("status") == "finished":
+                        break
+        except Exception as exc:  # pylint: disable=W0718
+            yield messages.StderrMessage.get(traceback.format_exc())
+            yield messages.FinishedMessage.get(
+                "error",
+                fail_reason=str(exc),
+                fail_class=exc.__class__.__name__,
+                traceback=traceback.format_exc(),
+            )
+
+
+class RunnerApp(BaseRunnerApp):
+    """Runner app for classless tests"""
+    PROG_NAME = "avocado-runner-avocado-classless"
+    PROG_DESCRIPTION = "nrunner application for classless tests"
+    RUNNABLE_KINDS_CAPABLE = ["avocado-classless"]
+
+
+def main():
+    """Run some avocado-classless tests"""
+    app = RunnerApp(print)
+    app.run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/test/avocado_classless/examples.py b/test/avocado_classless/examples.py
new file mode 100644
index 00000000..3895ee81
--- /dev/null
+++ b/test/avocado_classless/examples.py
@@ -0,0 +1,16 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+"""
+Example avocado-classless style tests
+"""
+
+import sys
+
+
+def trivial_pass():
+    print("Passes, trivially")
+
+
+def trivial_fail():
+    print("Fails, trivially", file=sys.stderr)
+    assert False
diff --git a/test/avocado_classless/setup.py b/test/avocado_classless/setup.py
new file mode 100644
index 00000000..94fcc127
--- /dev/null
+++ b/test/avocado_classless/setup.py
@@ -0,0 +1,32 @@
+#! /usr/bin/env python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Setup script for avocado-classless plugin
+"""
+
+from setuptools import setup, find_packages
+
+NAME = "avocado-classless"
+PKGNAME = "avocado_classless"
+
+resolver = f"{PKGNAME}.plugin:ClasslessResolver"
+runner = f"{PKGNAME}.plugin:ClasslessRunner"
+runscript = f"{PKGNAME}.plugin:main"
+
+if __name__ == "__main__":
+    setup(
+        name="avocado-classless",
+        version="0.1",
+        description="Avocado Classless Test Type",
+        packages=find_packages(),
+        entry_points={
+            "avocado.plugins.resolver": [f"{NAME} = {resolver}"],
+            "avocado.plugins.runnable.runner": [f"{NAME} = {runner}"],
+            "console_scripts": [f"avocado-runner-{NAME} = {runscript}"],
+        },
+    )
-- 
@@ -0,0 +1,32 @@
+#! /usr/bin/env python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Setup script for avocado-classless plugin
+"""
+
+from setuptools import setup, find_packages
+
+NAME = "avocado-classless"
+PKGNAME = "avocado_classless"
+
+resolver = f"{PKGNAME}.plugin:ClasslessResolver"
+runner = f"{PKGNAME}.plugin:ClasslessRunner"
+runscript = f"{PKGNAME}.plugin:main"
+
+if __name__ == "__main__":
+    setup(
+        name="avocado-classless",
+        version="0.1",
+        description="Avocado Classless Test Type",
+        packages=find_packages(),
+        entry_points={
+            "avocado.plugins.resolver": [f"{NAME} = {resolver}"],
+            "avocado.plugins.runnable.runner": [f"{NAME} = {runner}"],
+            "console_scripts": [f"avocado-runner-{NAME} = {runscript}"],
+        },
+    )
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 05/27] avocado, test: Add static checkers for Python code
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (3 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 04/27] avocado: Introduce "avocado-classless" plugin, runner and outline David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 06/27] avocado: Resolver implementation for avocado-classless plugin David Gibson
                   ` (21 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

We use both cppcheck and clang-tidy to lint our C code.  Now that we're
introducing Python code in the tests, use static checkers flake8 and pylint
for it too.  Run them from our testsuite.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/Makefile                          | 16 ++++++++++++++++
 test/avocado/static_checkers/flake8.sh |  3 +++
 test/avocado/static_checkers/pylint.sh |  3 +++
 3 files changed, 22 insertions(+)
 create mode 100755 test/avocado/static_checkers/flake8.sh
 create mode 100755 test/avocado/static_checkers/pylint.sh

diff --git a/test/Makefile b/test/Makefile
index 9b3af410..00b84856 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -209,6 +209,7 @@ jammy-server-cloudimg-s390x.img:
 PYTHON = python3
 VENV = venv
 PLUGIN = avocado_classless
+PYPKGS = $(PLUGIN)/$(PLUGIN) $(wildcard $(PLUGIN)/*.py)
 
 # Put this back if/when the plugin becomes available in upstream/system avocado
 #AVOCADO := $(shell which avocado)
@@ -219,6 +220,8 @@ endif
 $(VENV):
 	$(PYTHON) -m venv $(VENV)
 	$(VENV)/bin/pip install avocado-framework
+	$(VENV)/bin/pip install flake8
+	$(VENV)/bin/pip install pylint
 	$(VENV)/bin/pip install -e ./$(PLUGIN)
 
 .PHONY: avocado-assets
@@ -228,6 +231,19 @@ avocado-assets:
 avocado: avocado-assets $(VENV)
 	$(AVOCADO) run avocado
 
+flake8: $(VENV)
+	$(VENV)/bin/flake8 $(PYPKGS)
+
+# pylint suppressions:
+#
+# C0116 missing-function-docstring
+#    We have a bunch of trivial functions for which docstrings would be overkill
+# 
+pylint: $(VENV)
+	$(VENV)/bin/pylint \
+                --disable=C0116 \
+	    $(PYPKGS)
+
 .PHONY: check
 check: avocado check-legacy
 
diff --git a/test/avocado/static_checkers/flake8.sh b/test/avocado/static_checkers/flake8.sh
new file mode 100755
index 00000000..66f59fb0
--- /dev/null
+++ b/test/avocado/static_checkers/flake8.sh
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+make flake8
diff --git a/test/avocado/static_checkers/pylint.sh b/test/avocado/static_checkers/pylint.sh
new file mode 100755
index 00000000..a52f19f8
--- /dev/null
+++ b/test/avocado/static_checkers/pylint.sh
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+make pylint
-- 
@@ -0,0 +1,3 @@
+#! /bin/sh
+
+make pylint
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 06/27] avocado: Resolver implementation for avocado-classless plugin
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (4 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 05/27] avocado, test: Add static checkers for Python code David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 07/27] avocado: Add basic assertion helpers to " David Gibson
                   ` (20 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

The current resolver for the avocado-classless plugin requires you to give
a full function name for every test you want to run.  Implement a more
"normal" resolver which will run all suitable tests matching a regular
expression.

In the test files, tests are marked by using a decorator to put them in a
special "manifest" of tests in the file.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/Makefile                                 |  4 ++
 .../avocado_classless/manifest.py             | 43 +++++++++++++++++++
 .../avocado_classless/plugin.py               | 35 +++++++++++----
 .../avocado_classless/test.py                 | 18 ++++++++
 test/avocado_classless/examples.py            |  8 +++-
 test/avocado_classless/selftests.py           | 22 ++++++++++
 6 files changed, 120 insertions(+), 10 deletions(-)
 create mode 100644 test/avocado_classless/avocado_classless/manifest.py
 create mode 100644 test/avocado_classless/avocado_classless/test.py
 create mode 100644 test/avocado_classless/selftests.py

diff --git a/test/Makefile b/test/Makefile
index 00b84856..fc8d3a6f 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -231,6 +231,10 @@ avocado-assets:
 avocado: avocado-assets $(VENV)
 	$(AVOCADO) run avocado
 
+.PHONY: avocado-meta
+avocado-meta: avocado-assets $(VENV)
+	$(AVOCADO) run $(PLUGIN)/selftests.py
+
 flake8: $(VENV)
 	$(VENV)/bin/flake8 $(PYPKGS)
 
diff --git a/test/avocado_classless/avocado_classless/manifest.py b/test/avocado_classless/avocado_classless/manifest.py
new file mode 100644
index 00000000..8f7fe688
--- /dev/null
+++ b/test/avocado_classless/avocado_classless/manifest.py
@@ -0,0 +1,43 @@
+#! /usr/bin/python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Manage the manifest of classless tests in a module
+"""
+
+import sys
+
+MANIFEST = "__avocado_classless__"
+
+
+def manifest(mod):
+    """Return avocado-classless manifest for a Python module"""
+    if not hasattr(mod, MANIFEST):
+        setattr(mod, MANIFEST, {})
+    return getattr(mod, MANIFEST)
+
+
+class ManifestTestInfo:  # pylint: disable=R0903
+    """Metadata about a classless test"""
+
+    def __init__(self, func):
+        self.func = func
+
+    def run_test(self):
+        self.func()
+
+
+def manifest_add(mod, name, func):
+    """Register a function as a classless test"""
+
+    if isinstance(mod, str):
+        mod = sys.modules[mod]
+
+    mfest = manifest(mod)
+    if name in mfest:
+        raise ValueError(f"Duplicate classless test name {name}")
+    mfest[name] = ManifestTestInfo(func)
diff --git a/test/avocado_classless/avocado_classless/plugin.py b/test/avocado_classless/avocado_classless/plugin.py
index 48b89ce9..442642d5 100644
--- a/test/avocado_classless/avocado_classless/plugin.py
+++ b/test/avocado_classless/avocado_classless/plugin.py
@@ -12,10 +12,12 @@ Implementation of the Avocado resolver and runner for classless tests.
 import importlib
 import multiprocessing
 import os.path
+import re
 import sys
 import time
 import traceback
 
+
 from avocado.core.extension_manager import PluginPriority
 from avocado.core.test import Test, TestID
 from avocado.core.nrunner.app import BaseRunnerApp
@@ -33,6 +35,7 @@ from avocado.core.resolver import (
 )
 from avocado.core.utils import messages
 
+from .manifest import manifest
 
 SHBANG = b'#! /usr/bin/env avocado-runner-avocado-classless'
 DEFAULT_TIMEOUT = 5.0
@@ -60,7 +63,10 @@ class ClasslessResolver(Resolver):
     priority = PluginPriority.HIGH
 
     def resolve(self, reference):
-        path, _ = reference.rsplit(':', 1)
+        if ':' in reference:
+            path, pattern = reference.rsplit(':', 1)
+        else:
+            path, pattern = reference, ''
 
         # First check it looks like a Python file
         filecheck = check_file(path, reference)
@@ -77,25 +83,31 @@ class ClasslessResolver(Resolver):
                 info=f'{path} does not have first line "{SHBANG}" line',
             )
 
+        mod = load_mod(path)
+        mfest = manifest(mod)
+
+        pattern = re.compile(pattern)
+        runnables = []
+        for name in mfest.keys():
+            if pattern.search(name):
+                runnables.append(Runnable("avocado-classless",
+                                          f"{path}:{name}"))
+
         return ReferenceResolution(
             reference,
             ReferenceResolutionResult.SUCCESS,
-            [Runnable("avocado-classless", reference)]
+            runnables,
         )
 
 
-def run_classless(runnable, queue):
+def run_classless(runnable, testinfo, queue):
     """Invoked within isolating process, run classless tests"""
     try:
-        path, testname = runnable.uri.rsplit(':', 1)
-        mod = load_mod(path)
-        test = getattr(mod, testname)
-
         class ClasslessTest(Test):
             """Shim class for classless tests"""
             def test(self):
                 """Execute classless test"""
-                test()
+                testinfo.run_test()
 
         result_dir = runnable.output_dir
         instance = ClasslessTest(
@@ -159,10 +171,15 @@ class ClasslessRunner(BaseRunner):
 
     def run(self, runnable):
         yield messages.StartedMessage.get()
+
         try:
+            path, testname = runnable.uri.rsplit(':', 1)
+            mod = load_mod(path)
+            testinfo = manifest(mod)[testname]
+
             queue = multiprocessing.SimpleQueue()
             process = multiprocessing.Process(
-                target=run_classless, args=(runnable, queue)
+                target=run_classless, args=(runnable, testinfo, queue)
             )
             process.start()
 
diff --git a/test/avocado_classless/avocado_classless/test.py b/test/avocado_classless/avocado_classless/test.py
new file mode 100644
index 00000000..eb2caef2
--- /dev/null
+++ b/test/avocado_classless/avocado_classless/test.py
@@ -0,0 +1,18 @@
+#! /usr/bin/env python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test writer facing interface to avocodo-classless
+"""
+
+from .manifest import manifest_add
+
+
+def test(func):
+    """Function decorator to mark a function as a classless test"""
+    manifest_add(func.__module__, func.__name__, func)
+    return func
diff --git a/test/avocado_classless/examples.py b/test/avocado_classless/examples.py
index 3895ee81..a4856124 100644
--- a/test/avocado_classless/examples.py
+++ b/test/avocado_classless/examples.py
@@ -1,16 +1,22 @@
 #! /usr/bin/env avocado-runner-avocado-classless
 
 """
-Example avocado-classless style tests
+Example avocado-classless style tests.  Note that some of these
+are expected to fail.
+
 """
 
 import sys
 
+from avocado_classless.test import test
+
 
+@test
 def trivial_pass():
     print("Passes, trivially")
 
 
+@test
 def trivial_fail():
     print("Fails, trivially", file=sys.stderr)
     assert False
diff --git a/test/avocado_classless/selftests.py b/test/avocado_classless/selftests.py
new file mode 100644
index 00000000..26d02378
--- /dev/null
+++ b/test/avocado_classless/selftests.py
@@ -0,0 +1,22 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+"""
+Self tests for avocado-classless plugins
+"""
+
+from avocado_classless.manifest import manifest_add
+from avocado_classless.test import test
+
+
+@test
+def trivial():
+    pass
+
+
+@test
+def assert_true():
+    assert True
+
+
+# Check re-registering a function under a different name
+manifest_add(__name__, "trivial2", trivial)
-- 
@@ -0,0 +1,22 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+"""
+Self tests for avocado-classless plugins
+"""
+
+from avocado_classless.manifest import manifest_add
+from avocado_classless.test import test
+
+
+@test
+def trivial():
+    pass
+
+
+@test
+def assert_true():
+    assert True
+
+
+# Check re-registering a function under a different name
+manifest_add(__name__, "trivial2", trivial)
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 07/27] avocado: Add basic assertion helpers to avocado-classless plugin
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (5 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 06/27] avocado: Resolver implementation for avocado-classless plugin David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 08/27] tasst, avocado: Introduce library of common test helpers David Gibson
                   ` (19 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

With avocado-classless we obviously can't use the built in assertion
methods from unittest / avocado-instrumented.  Add our own non-OOP helpers
for this.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 .../avocado_classless/test.py                 | 43 +++++++++++++++++++
 test/avocado_classless/selftests.py           | 43 ++++++++++++++++++-
 2 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/test/avocado_classless/avocado_classless/test.py b/test/avocado_classless/avocado_classless/test.py
index eb2caef2..0602f9c9 100644
--- a/test/avocado_classless/avocado_classless/test.py
+++ b/test/avocado_classless/avocado_classless/test.py
@@ -9,6 +9,8 @@
 Test writer facing interface to avocodo-classless
 """
 
+from collections import Counter
+
 from .manifest import manifest_add
 
 
@@ -16,3 +18,44 @@ def test(func):
     """Function decorator to mark a function as a classless test"""
     manifest_add(func.__module__, func.__name__, func)
     return func
+
+#
+# Assertion helpers without unnecessary OOP nonsense
+#
+
+
+def assert_eq(left, right):
+    """assert_eq(left, right)
+
+    If left != right, fail with message showing both values
+    """
+    assert (left == right), f'{left} != {right}'
+
+
+def assert_in(member, group):
+    """assert_in(member, group)
+
+    If member not in group, fail with a message showing both values
+    """
+    assert member in group, f'{member} not in {group}'
+
+
+def assert_eq_unordered(left, right):
+    """assert_eq_unordered(left, right)
+
+    Fails if sequences left and right are different, ignoring order.
+    """
+    assert Counter(left) == Counter(right), \
+        f'{left} != {right} (ignoring order)'
+
+
+def assert_raises(exc, func, *args, **kwargs):
+    """assert_raises(exc, func, *args, **kwargs)
+
+    If func(*args, **kwargs) does not raise exc, fail
+    """
+    try:
+        func(*args, **kwargs)
+        raise AssertionError(f'Expected {exc.__name__} exception')
+    except exc:
+        pass
diff --git a/test/avocado_classless/selftests.py b/test/avocado_classless/selftests.py
index 26d02378..12a4a5a2 100644
--- a/test/avocado_classless/selftests.py
+++ b/test/avocado_classless/selftests.py
@@ -4,8 +4,12 @@
 Self tests for avocado-classless plugins
 """
 
+import types
+
 from avocado_classless.manifest import manifest_add
-from avocado_classless.test import test
+from avocado_classless.test import (
+    assert_eq, assert_eq_unordered, assert_in, assert_raises, test
+)
 
 
 @test
@@ -20,3 +24,40 @@ def assert_true():
 
 # Check re-registering a function under a different name
 manifest_add(__name__, "trivial2", trivial)
+
+
+@test
+def test_assert_eq():
+    assert_eq(1, 1)
+
+
+@test
+def test_assert_in():
+    assert_in(1, [1, 2, 3])
+    assert_raises(AssertionError, assert_in, 'x', 'hello')
+
+
+@test
+def test_assert_raises():
+    def boom(exc):
+        raise exc
+    assert_raises(ValueError, boom, ValueError)
+
+
+@test
+def test_assert_eq_unordered():
+    assert_eq_unordered([1, 2, 3], (1, 2, 3))
+    assert_eq_unordered([1, 2, 3], [2, 3, 1])
+    assert_eq_unordered([1, 1, 1], (1, 1, 1))
+    assert_raises(AssertionError, assert_eq_unordered, [1, 2, 3], [1, 2, 4])
+    assert_raises(AssertionError, assert_eq_unordered, [1, 1], [1, 1, 1])
+
+
+# Test we report attempting to double register the same test name
+@test
+def test_double_register():
+    def double_register():
+        mod = types.ModuleType('dummy')
+        manifest_add(mod, "test", trivial)
+        manifest_add(mod, "test", assert_true)
+    assert_raises(ValueError, double_register)
-- 
@@ -4,8 +4,12 @@
 Self tests for avocado-classless plugins
 """
 
+import types
+
 from avocado_classless.manifest import manifest_add
-from avocado_classless.test import test
+from avocado_classless.test import (
+    assert_eq, assert_eq_unordered, assert_in, assert_raises, test
+)
 
 
 @test
@@ -20,3 +24,40 @@ def assert_true():
 
 # Check re-registering a function under a different name
 manifest_add(__name__, "trivial2", trivial)
+
+
+@test
+def test_assert_eq():
+    assert_eq(1, 1)
+
+
+@test
+def test_assert_in():
+    assert_in(1, [1, 2, 3])
+    assert_raises(AssertionError, assert_in, 'x', 'hello')
+
+
+@test
+def test_assert_raises():
+    def boom(exc):
+        raise exc
+    assert_raises(ValueError, boom, ValueError)
+
+
+@test
+def test_assert_eq_unordered():
+    assert_eq_unordered([1, 2, 3], (1, 2, 3))
+    assert_eq_unordered([1, 2, 3], [2, 3, 1])
+    assert_eq_unordered([1, 1, 1], (1, 1, 1))
+    assert_raises(AssertionError, assert_eq_unordered, [1, 2, 3], [1, 2, 4])
+    assert_raises(AssertionError, assert_eq_unordered, [1, 1], [1, 1, 1])
+
+
+# Test we report attempting to double register the same test name
+@test
+def test_double_register():
+    def double_register():
+        mod = types.ModuleType('dummy')
+        manifest_add(mod, "test", trivial)
+        manifest_add(mod, "test", assert_true)
+    assert_raises(ValueError, double_register)
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 08/27] tasst, avocado: Introduce library of common test helpers
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (6 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 07/27] avocado: Add basic assertion helpers to " David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 09/27] avocado-classless: Test matrices by composition David Gibson
                   ` (18 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Create a Python package "tasst" with common helper code for use in passt
and pasta.  Initially it just has some trivial typechecking helpers.

Extend the avocado-meta tests to include selftests within the tasst
library.  This lets us test the functionality of the library itself without
involving actual passt or pasta.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/Makefile           |  8 ++++---
 test/tasst/__init__.py  | 11 ++++++++++
 test/tasst/typecheck.py | 47 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 3 deletions(-)
 create mode 100644 test/tasst/__init__.py
 create mode 100644 test/tasst/typecheck.py

diff --git a/test/Makefile b/test/Makefile
index fc8d3a6f..c06d63b6 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -209,7 +209,8 @@ jammy-server-cloudimg-s390x.img:
 PYTHON = python3
 VENV = venv
 PLUGIN = avocado_classless
-PYPKGS = $(PLUGIN)/$(PLUGIN) $(wildcard $(PLUGIN)/*.py)
+PYPKGS = $(PLUGIN)/$(PLUGIN) $(wildcard $(PLUGIN)/*.py) \
+	tasst
 
 # Put this back if/when the plugin becomes available in upstream/system avocado
 #AVOCADO := $(shell which avocado)
@@ -229,11 +230,11 @@ avocado-assets:
 
 .PHONY: avocado
 avocado: avocado-assets $(VENV)
-	$(AVOCADO) run avocado
+	PYTHONPATH=. $(AVOCADO) run avocado
 
 .PHONY: avocado-meta
 avocado-meta: avocado-assets $(VENV)
-	$(AVOCADO) run $(PLUGIN)/selftests.py
+	PYTHONPATH=. $(AVOCADO) run $(PLUGIN)/selftests.py tasst
 
 flake8: $(VENV)
 	$(VENV)/bin/flake8 $(PYPKGS)
@@ -251,6 +252,7 @@ pylint: $(VENV)
 .PHONY: check
 check: avocado check-legacy
 
+.PHONY: clean
 clean: clean-legacy
 	$(RM) -r $(VENV)
 	find -name *~ | xargs $(RM)
diff --git a/test/tasst/__init__.py b/test/tasst/__init__.py
new file mode 100644
index 00000000..c1d5d9dd
--- /dev/null
+++ b/test/tasst/__init__.py
@@ -0,0 +1,11 @@
+#! /usr/bin/env python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+library of test helpers for passt & pasta
+"""
diff --git a/test/tasst/typecheck.py b/test/tasst/typecheck.py
new file mode 100644
index 00000000..f97fc401
--- /dev/null
+++ b/test/tasst/typecheck.py
@@ -0,0 +1,47 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+Type checking and related helpers
+"""
+
+from avocado_classless.test import test, assert_eq, assert_raises
+
+
+def typecheck(val, ty_):
+    """typecheck(val, ty_)
+
+    Return val, raising TypeError if it does not have type ty_.
+    """
+    if not isinstance(val, ty_):
+        raise TypeError(f'Expected {ty_} instead of {type(val)}')
+    return val
+
+
+@test
+def test_typecheck():
+    assert_eq(typecheck(17, int), 17)
+    assert_eq(typecheck("hello", str), "hello")
+    assert_raises(TypeError, typecheck, 17, str)
+
+
+def typecheck_default(val, ty_, default):
+    """typecheck_default(val, ty_, default)
+
+    If val is None, return default.  Otherwise return typecheck(val, ty_).
+    """
+    if val is None:
+        return default
+    return typecheck(val, ty_)
+
+
+@test
+def test_typecheck_default():
+    assert_eq(typecheck_default(17, int, None), 17)
+    assert_eq(typecheck_default(None, int, 17), 17)
-- 
@@ -0,0 +1,47 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+Type checking and related helpers
+"""
+
+from avocado_classless.test import test, assert_eq, assert_raises
+
+
+def typecheck(val, ty_):
+    """typecheck(val, ty_)
+
+    Return val, raising TypeError if it does not have type ty_.
+    """
+    if not isinstance(val, ty_):
+        raise TypeError(f'Expected {ty_} instead of {type(val)}')
+    return val
+
+
+@test
+def test_typecheck():
+    assert_eq(typecheck(17, int), 17)
+    assert_eq(typecheck("hello", str), "hello")
+    assert_raises(TypeError, typecheck, 17, str)
+
+
+def typecheck_default(val, ty_, default):
+    """typecheck_default(val, ty_, default)
+
+    If val is None, return default.  Otherwise return typecheck(val, ty_).
+    """
+    if val is None:
+        return default
+    return typecheck(val, ty_)
+
+
+@test
+def test_typecheck_default():
+    assert_eq(typecheck_default(17, int, None), 17)
+    assert_eq(typecheck_default(None, int, 17), 17)
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 09/27] avocado-classless: Test matrices by composition
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (7 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 08/27] tasst, avocado: Introduce library of common test helpers David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 10/27] tasst: Helper functions for executing commands in different places David Gibson
                   ` (17 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

It's sometimes useful to have a batch of tests to be run in a variety of
different scenarios.  In particular it's sometimes useful to reuse those
tests for scenarios that aren't known at the time of writing the tests.

Introduce the 'test_output' decorator.  This creates a number of tests by
feeding the output of the decorated function (representing the scenario
setup) into all of the listed tests.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 .../avocado_classless/test.py                 | 13 ++++++++
 test/avocado_classless/examples.py            | 31 ++++++++++++++++++-
 test/avocado_classless/selftests.py           | 15 ++++++++-
 3 files changed, 57 insertions(+), 2 deletions(-)

diff --git a/test/avocado_classless/avocado_classless/test.py b/test/avocado_classless/avocado_classless/test.py
index 0602f9c9..e5251960 100644
--- a/test/avocado_classless/avocado_classless/test.py
+++ b/test/avocado_classless/avocado_classless/test.py
@@ -19,6 +19,19 @@ def test(func):
     manifest_add(func.__module__, func.__name__, func)
     return func
 
+
+def test_output(*tests):
+    """Decorate a function to test its output with a list of other functions"""
+    def dec(func):
+        for testfn in tests:
+            manifest_add(func.__module__,
+                         f"{testfn.__name__}∘{func.__name__}",
+                         lambda testfn=testfn: testfn(func()))
+
+        return func
+    return dec
+
+
 #
 # Assertion helpers without unnecessary OOP nonsense
 #
diff --git a/test/avocado_classless/examples.py b/test/avocado_classless/examples.py
index a4856124..a0e883f3 100644
--- a/test/avocado_classless/examples.py
+++ b/test/avocado_classless/examples.py
@@ -8,7 +8,7 @@ are expected to fail.
 
 import sys
 
-from avocado_classless.test import test
+from avocado_classless.test import test, test_output
 
 
 @test
@@ -20,3 +20,32 @@ def trivial_pass():
 def trivial_fail():
     print("Fails, trivially", file=sys.stderr)
     assert False
+
+
+# Some test_output checks
+def is_integer(val):
+    assert isinstance(val, int)
+
+
+def is_positive(val):
+    assert val > 0
+
+
+@test_output(is_integer, is_positive)
+def positive_integer():
+    return 17
+
+
+@test_output(is_integer, is_positive)
+def negative_integer():
+    return -17
+
+
+@test_output(is_integer, is_positive)
+def positive_fraction():
+    return 3.5
+
+
+@test_output(is_integer, is_positive)
+def negative_fraction():
+    return -3.5
diff --git a/test/avocado_classless/selftests.py b/test/avocado_classless/selftests.py
index 12a4a5a2..6b6d40ff 100644
--- a/test/avocado_classless/selftests.py
+++ b/test/avocado_classless/selftests.py
@@ -8,7 +8,7 @@ import types
 
 from avocado_classless.manifest import manifest_add
 from avocado_classless.test import (
-    assert_eq, assert_eq_unordered, assert_in, assert_raises, test
+    assert_eq, assert_eq_unordered, assert_in, assert_raises, test, test_output
 )
 
 
@@ -61,3 +61,16 @@ def test_double_register():
         manifest_add(mod, "test", trivial)
         manifest_add(mod, "test", assert_true)
     assert_raises(ValueError, double_register)
+
+
+def is_int(val):
+    assert isinstance(val, int)
+
+
+def is_positive(val):
+    assert val > 0
+
+
+@test_output(is_int, is_positive)
+def seventeen():
+    return 17
-- 
@@ -8,7 +8,7 @@ import types
 
 from avocado_classless.manifest import manifest_add
 from avocado_classless.test import (
-    assert_eq, assert_eq_unordered, assert_in, assert_raises, test
+    assert_eq, assert_eq_unordered, assert_in, assert_raises, test, test_output
 )
 
 
@@ -61,3 +61,16 @@ def test_double_register():
         manifest_add(mod, "test", trivial)
         manifest_add(mod, "test", assert_true)
     assert_raises(ValueError, double_register)
+
+
+def is_int(val):
+    assert isinstance(val, int)
+
+
+def is_positive(val):
+    assert val > 0
+
+
+@test_output(is_int, is_positive)
+def seventeen():
+    return 17
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 10/27] tasst: Helper functions for executing commands in different places
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (8 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 09/27] avocado-classless: Test matrices by composition David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 11/27] avocado-classless: Allow overriding default timeout David Gibson
                   ` (16 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Add a library "tasst" for use in avocado tests of passt & pasta.  We start
by adding the outline of logic to run commands in various places (e.g.
namespaces, VMs).  We add some avocado tests for the test library itself,
tagged 'meta' to distinguish it from tests for passt/pasta proper.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/Makefile         |  8 +++-
 test/tasst/exesite.py | 99 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 105 insertions(+), 2 deletions(-)
 create mode 100644 test/tasst/exesite.py

diff --git a/test/Makefile b/test/Makefile
index c06d63b6..0e641024 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -243,10 +243,14 @@ flake8: $(VENV)
 #
 # C0116 missing-function-docstring
 #    We have a bunch of trivial functions for which docstrings would be overkill
-# 
+# C0103 invalid-name
+#    As well as complaining about bad formatting style, this complains
+#    about short identifiers (< 3 characters?).  For many locals and
+#    some methods, this is stupid.
 pylint: $(VENV)
 	$(VENV)/bin/pylint \
-                --disable=C0116 \
+	        --disable=C0116 \
+	        --disable=C0103 \
 	    $(PYPKGS)
 
 .PHONY: check
diff --git a/test/tasst/exesite.py b/test/tasst/exesite.py
new file mode 100644
index 00000000..9f037f77
--- /dev/null
+++ b/test/tasst/exesite.py
@@ -0,0 +1,99 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+tasst/exesite.py - Manage simulated network sites for testing
+"""
+
+
+import contextlib
+
+import avocado
+from avocado.utils.process import CmdError
+from avocado_classless.test import assert_eq, assert_raises, test_output
+
+
+class Site(contextlib.AbstractContextManager):
+    """
+    A (usually virtual or simulated) location where we can execute
+    commands and configure networks.
+
+    """
+
+    def __init__(self, name):
+        self.name = name   # For debugging
+
+    def hostify(self, cmd, **kwargs):
+        raise NotImplementedError
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *exc_details):
+        pass
+
+    def output(self, cmd, strip_trail_nl=False, **kwargs):
+        kwargs['strip_trail_nl'] = strip_trail_nl
+        cmd, kwargs = self.hostify(cmd, **kwargs)
+        return avocado.utils.process.system_output(cmd, **kwargs)
+
+    def fg(self, cmd, **kwargs):
+        cmd, kwargs = self.hostify(cmd, **kwargs)
+        return avocado.utils.process.system(cmd, **kwargs)
+
+    def require_cmds(self, *cmds):
+        missing = [c for c in cmds
+                   if self.fg(f'type {c}', ignore_status=True) != 0]
+        if missing:
+            raise avocado.TestCancel(
+                f"Missing commands {', '.join(missing)} on {self.name}")
+
+
+def test_site(sitefn):
+    def test_true(s):
+        with s as site:
+            site.fg('true')
+
+    def test_false(s):
+        with s as site:
+            assert_raises(CmdError, site.fg, 'false')
+
+    def test_echo(s):
+        with s as site:
+            msg = 'Hello tasst'
+            out = site.output(f'echo {msg}')
+            assert_eq(out, msg.encode('utf-8') + b'\n')
+
+    def test_timeout(s):
+        with s as site:
+            site.fg('sleep infinity', timeout=0.1, ignore_status=True)
+
+    return test_output(test_true, test_false, test_echo, test_timeout)(sitefn)
+
+
+class RealHost(Site):
+    """Represents the host on which the tests are running (as opposed
+    to some simulated host created by the tests)
+
+    """
+
+    def __init__(self):
+        super().__init__('REAL_HOST')
+
+    def hostify(self, cmd, *, sudo=False, **kwargs):
+        assert not sudo, "BUG: Shouldn't run commands with privilege on host"
+        return cmd, kwargs
+
+
+REAL_HOST = RealHost()
+
+
+@test_site
+def real_host():
+    return REAL_HOST
-- 
@@ -0,0 +1,99 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+tasst/exesite.py - Manage simulated network sites for testing
+"""
+
+
+import contextlib
+
+import avocado
+from avocado.utils.process import CmdError
+from avocado_classless.test import assert_eq, assert_raises, test_output
+
+
+class Site(contextlib.AbstractContextManager):
+    """
+    A (usually virtual or simulated) location where we can execute
+    commands and configure networks.
+
+    """
+
+    def __init__(self, name):
+        self.name = name   # For debugging
+
+    def hostify(self, cmd, **kwargs):
+        raise NotImplementedError
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, *exc_details):
+        pass
+
+    def output(self, cmd, strip_trail_nl=False, **kwargs):
+        kwargs['strip_trail_nl'] = strip_trail_nl
+        cmd, kwargs = self.hostify(cmd, **kwargs)
+        return avocado.utils.process.system_output(cmd, **kwargs)
+
+    def fg(self, cmd, **kwargs):
+        cmd, kwargs = self.hostify(cmd, **kwargs)
+        return avocado.utils.process.system(cmd, **kwargs)
+
+    def require_cmds(self, *cmds):
+        missing = [c for c in cmds
+                   if self.fg(f'type {c}', ignore_status=True) != 0]
+        if missing:
+            raise avocado.TestCancel(
+                f"Missing commands {', '.join(missing)} on {self.name}")
+
+
+def test_site(sitefn):
+    def test_true(s):
+        with s as site:
+            site.fg('true')
+
+    def test_false(s):
+        with s as site:
+            assert_raises(CmdError, site.fg, 'false')
+
+    def test_echo(s):
+        with s as site:
+            msg = 'Hello tasst'
+            out = site.output(f'echo {msg}')
+            assert_eq(out, msg.encode('utf-8') + b'\n')
+
+    def test_timeout(s):
+        with s as site:
+            site.fg('sleep infinity', timeout=0.1, ignore_status=True)
+
+    return test_output(test_true, test_false, test_echo, test_timeout)(sitefn)
+
+
+class RealHost(Site):
+    """Represents the host on which the tests are running (as opposed
+    to some simulated host created by the tests)
+
+    """
+
+    def __init__(self):
+        super().__init__('REAL_HOST')
+
+    def hostify(self, cmd, *, sudo=False, **kwargs):
+        assert not sudo, "BUG: Shouldn't run commands with privilege on host"
+        return cmd, kwargs
+
+
+REAL_HOST = RealHost()
+
+
+@test_site
+def real_host():
+    return REAL_HOST
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 11/27] avocado-classless: Allow overriding default timeout
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (9 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 10/27] tasst: Helper functions for executing commands in different places David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 12/27] avocado: Convert build tests to avocado David Gibson
                   ` (15 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Currently the avocado-classless plugin always uses a fixed (short) timeout
for tests.  However the appropriate timeout really depends on what's being
tested.

Allow tests to set different timeouts, by placing not a plain function, but
an object representing metadata into the classless test manifest.  Then
tests can use a parameterized version of the decorator to put additional
information in that metadata.  For now, just add the timeout there, but
we might add other things in future.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 .../avocado_classless/manifest.py                  | 11 ++++++++---
 test/avocado_classless/avocado_classless/plugin.py |  4 +---
 test/avocado_classless/avocado_classless/test.py   | 14 ++++++++++----
 test/avocado_classless/examples.py                 |  8 +++++++-
 4 files changed, 26 insertions(+), 11 deletions(-)

diff --git a/test/avocado_classless/avocado_classless/manifest.py b/test/avocado_classless/avocado_classless/manifest.py
index 8f7fe688..34c815cd 100644
--- a/test/avocado_classless/avocado_classless/manifest.py
+++ b/test/avocado_classless/avocado_classless/manifest.py
@@ -12,6 +12,7 @@ Manage the manifest of classless tests in a module
 import sys
 
 MANIFEST = "__avocado_classless__"
+DEFAULT_TIMEOUT = 60.0
 
 
 def manifest(mod):
@@ -24,14 +25,18 @@ def manifest(mod):
 class ManifestTestInfo:  # pylint: disable=R0903
     """Metadata about a classless test"""
 
-    def __init__(self, func):
+    def __init__(self, func, timeout=DEFAULT_TIMEOUT):
         self.func = func
+        self.__timeout = float(timeout)
 
     def run_test(self):
         self.func()
 
+    def timeout(self):
+        return self.__timeout
 
-def manifest_add(mod, name, func):
+
+def manifest_add(mod, name, func, **kwargs):
     """Register a function as a classless test"""
 
     if isinstance(mod, str):
@@ -40,4 +45,4 @@ def manifest_add(mod, name, func):
     mfest = manifest(mod)
     if name in mfest:
         raise ValueError(f"Duplicate classless test name {name}")
-    mfest[name] = ManifestTestInfo(func)
+    mfest[name] = ManifestTestInfo(func, **kwargs)
diff --git a/test/avocado_classless/avocado_classless/plugin.py b/test/avocado_classless/avocado_classless/plugin.py
index 442642d5..e2c9b1eb 100644
--- a/test/avocado_classless/avocado_classless/plugin.py
+++ b/test/avocado_classless/avocado_classless/plugin.py
@@ -38,7 +38,6 @@ from avocado.core.utils import messages
 from .manifest import manifest
 
 SHBANG = b'#! /usr/bin/env avocado-runner-avocado-classless'
-DEFAULT_TIMEOUT = 5.0
 
 
 def load_mod(path):
@@ -184,7 +183,6 @@ class ClasslessRunner(BaseRunner):
             process.start()
 
             time_started = time.monotonic()
-            timeout = DEFAULT_TIMEOUT
             next_status_time = None
             while True:
                 time.sleep(RUNNER_RUN_CHECK_INTERVAL)
@@ -196,7 +194,7 @@ class ClasslessRunner(BaseRunner):
                     ):
                         next_status_time = now + RUNNER_RUN_STATUS_INTERVAL
                         yield messages.RunningMessage.get()
-                    if (now - time_started) > timeout:
+                    if (now - time_started) > testinfo.timeout():
                         process.terminate()
                         yield messages.FinishedMessage.get("interrupted",
                                                            "timeout")
diff --git a/test/avocado_classless/avocado_classless/test.py b/test/avocado_classless/avocado_classless/test.py
index e5251960..04fa52ab 100644
--- a/test/avocado_classless/avocado_classless/test.py
+++ b/test/avocado_classless/avocado_classless/test.py
@@ -14,10 +14,16 @@ from collections import Counter
 from .manifest import manifest_add
 
 
-def test(func):
-    """Function decorator to mark a function as a classless test"""
-    manifest_add(func.__module__, func.__name__, func)
-    return func
+def classless_test(**kwargs):
+    """Mark a function as a classless test with options"""
+    def dec(func):
+        manifest_add(func.__module__, func.__name__, func, **kwargs)
+        return func
+    return dec
+
+
+# Simple version of classless_test with default options
+test = classless_test()
 
 
 def test_output(*tests):
diff --git a/test/avocado_classless/examples.py b/test/avocado_classless/examples.py
index a0e883f3..8625c9d3 100644
--- a/test/avocado_classless/examples.py
+++ b/test/avocado_classless/examples.py
@@ -7,8 +7,9 @@ are expected to fail.
 """
 
 import sys
+import time
 
-from avocado_classless.test import test, test_output
+from avocado_classless.test import classless_test, test, test_output
 
 
 @test
@@ -49,3 +50,8 @@ def positive_fraction():
 @test_output(is_integer, is_positive)
 def negative_fraction():
     return -3.5
+
+
+@classless_test(timeout=1.0)
+def timeout():
+    time.sleep(3)
-- 
@@ -7,8 +7,9 @@ are expected to fail.
 """
 
 import sys
+import time
 
-from avocado_classless.test import test, test_output
+from avocado_classless.test import classless_test, test, test_output
 
 
 @test
@@ -49,3 +50,8 @@ def positive_fraction():
 @test_output(is_integer, is_positive)
 def negative_fraction():
     return -3.5
+
+
+@classless_test(timeout=1.0)
+def timeout():
+    time.sleep(3)
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 12/27] avocado: Convert build tests to avocado
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (10 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 11/27] avocado-classless: Allow overriding default timeout David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 13/27] tasst: Add helpers for running background commands on sites David Gibson
                   ` (14 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Move the build and make install tests from the old hand-rolled test
harness to avocado.  The avocado versions are safer, in that they make a
private copy of the sources before building, so they won't interfere with
concurrent tests or builds in the original source tree.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 oldtest/run           |  2 +-
 test/Makefile         |  2 +-
 test/avocado/build.py | 94 +++++++++++++++++++++++++++++++++++++++++++
 test/build/all        | 61 ----------------------------
 test/lib/setup        |  7 ----
 test/run              |  4 --
 6 files changed, 96 insertions(+), 74 deletions(-)
 create mode 100644 test/avocado/build.py
 delete mode 100644 test/build/all

diff --git a/oldtest/run b/oldtest/run
index 56fcd1b3..a16bc49b 100755
--- a/oldtest/run
+++ b/oldtest/run
@@ -65,7 +65,7 @@ run() {
 	[ ${CI} -eq 1 ]   && video_start ci
 
 	setup build
-#	test build/all
+	test build/all
 	test build/cppcheck
 	test build/clang_tidy
 	teardown build
diff --git a/test/Makefile b/test/Makefile
index 0e641024..58159c83 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -210,7 +210,7 @@ PYTHON = python3
 VENV = venv
 PLUGIN = avocado_classless
 PYPKGS = $(PLUGIN)/$(PLUGIN) $(wildcard $(PLUGIN)/*.py) \
-	tasst
+	tasst $(shell find avocado -name '*.py')
 
 # Put this back if/when the plugin becomes available in upstream/system avocado
 #AVOCADO := $(shell which avocado)
diff --git a/test/avocado/build.py b/test/avocado/build.py
new file mode 100644
index 00000000..4cce77a6
--- /dev/null
+++ b/test/avocado/build.py
@@ -0,0 +1,94 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+avocado/build.py - Test passt & pasta build targets
+"""
+
+
+import contextlib
+import os
+import os.path
+import shutil
+from tempfile import TemporaryDirectory
+
+from avocado_classless.test import assert_raises, test
+
+from tasst.exesite import CmdError, REAL_HOST
+
+
+@contextlib.contextmanager
+def clone_source_tree():
+    REAL_HOST.require_cmds('git', 'make')
+
+    with TemporaryDirectory(ignore_cleanup_errors=False) as tmpdir:
+        os.chdir('..')
+        # Make a temporary copy of the sources
+        srcfiles = REAL_HOST.output('git ls-files') \
+                            .decode('utf-8').splitlines()
+        for src in srcfiles:
+            dst = os.path.join(tmpdir, src)
+            os.makedirs(os.path.dirname(dst), exist_ok=True)
+            shutil.copy(src, dst)
+        os.chdir(tmpdir)
+        yield tmpdir
+
+
+def build_target(target, outputs):
+    with clone_source_tree():
+        for o in outputs:
+            assert not os.path.exists(o)
+        REAL_HOST.fg(f'make {target} CFLAGS="-Werror"')
+        for o in outputs:
+            assert os.path.exists(o)
+        REAL_HOST.fg('make clean')
+        for o in outputs:
+            assert not os.path.exists(o)
+
+
+@test
+def test_make_passt():
+    build_target('passt', ['passt'])
+
+
+@test
+def test_make_pasta():
+    build_target('pasta', ['pasta'])
+
+
+@test
+def test_make_qrap():
+    build_target('qrap', ['qrap'])
+
+
+@test
+def test_make_all():
+    build_target('all', ['passt', 'pasta', 'qrap'])
+
+
+@test
+def test_make_install_uninstall():
+    with clone_source_tree():
+        with TemporaryDirectory(ignore_cleanup_errors=False) as prefix:
+            bindir = os.path.join(prefix, 'bin')
+            mandir = os.path.join(prefix, 'share', 'man')
+            exes = ['passt', 'pasta', 'qrap']
+
+            # Install
+            REAL_HOST.fg(f'make install CFLAGS="-Werror" prefix={prefix}')
+
+            for t in exes:
+                assert os.path.isfile(os.path.join(bindir, t))
+                REAL_HOST.fg(f'man -M {mandir} -W passt')
+
+            # Uninstall
+            REAL_HOST.fg(f'make uninstall prefix={prefix}')
+
+            for t in exes:
+                assert not os.path.exists(os.path.join(bindir, t))
+                assert_raises(CmdError, REAL_HOST.fg,
+                              f'man -M {mandir} -W passt')
diff --git a/test/build/all b/test/build/all
deleted file mode 100644
index 1f79e0d8..00000000
--- a/test/build/all
+++ /dev/null
@@ -1,61 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-#
-# PASST - Plug A Simple Socket Transport
-#  for qemu/UNIX domain socket mode
-#
-# PASTA - Pack A Subtle Tap Abstraction
-#  for network namespace/tap device mode
-#
-# test/build/all - Build targets, one by one, then all together, check output
-#
-# Copyright (c) 2021 Red Hat GmbH
-# Author: Stefano Brivio <sbrivio@redhat.com>
-
-htools	make cc rm uname getconf mkdir cp rm man
-
-test	Build passt
-host	make clean
-check	! [ -e passt ]
-host	CFLAGS="-Werror" make passt
-check	[ -f passt ]
-
-test	Build pasta
-host	make clean
-check	! [ -e pasta ]
-host	CFLAGS="-Werror" make pasta
-check	[ -h pasta ]
-
-test	Build qrap
-host	make clean
-check	! [ -e qrap ]
-host	CFLAGS="-Werror" make qrap
-check	[ -f qrap ]
-
-test	Build all
-host	make clean
-check	! [ -e passt ]
-check	! [ -e pasta ]
-check	! [ -e qrap ]
-host	CFLAGS="-Werror" make
-check	[ -f passt ]
-check	[ -h pasta ]
-check	[ -f qrap ]
-
-test	Install
-host	mkdir __STATEDIR__/prefix
-host	prefix=__STATEDIR__/prefix make install
-check	[ -f __STATEDIR__/prefix/bin/passt ]
-check	[ -h __STATEDIR__/prefix/bin/pasta ]
-check	[ -f __STATEDIR__/prefix/bin/qrap ]
-check	man -M __STATEDIR__/prefix/share/man -W passt
-check	man -M __STATEDIR__/prefix/share/man -W pasta
-check	man -M __STATEDIR__/prefix/share/man -W qrap
-
-test	Uninstall
-host	prefix=__STATEDIR__/prefix make uninstall
-check	! [ -f __STATEDIR__/prefix/bin/passt ]
-check	! [ -h __STATEDIR__/prefix/bin/pasta ]
-check	! [ -f __STATEDIR__/prefix/bin/qrap ]
-check	! man -M __STATEDIR__/prefix/share/man -W passt 2>/dev/null
-check	! man -M __STATEDIR__/prefix/share/man -W pasta 2>/dev/null
-check	! man -M __STATEDIR__/prefix/share/man -W qrap 2>/dev/null
diff --git a/test/lib/setup b/test/lib/setup
index 9b39b9fe..5386805f 100755
--- a/test/lib/setup
+++ b/test/lib/setup
@@ -18,13 +18,6 @@ VCPUS="$( [ $(nproc) -ge 8 ] && echo 6 || echo $(( $(nproc) / 2 + 1 )) )"
 __mem_kib="$(sed -n 's/MemTotal:[ ]*\([0-9]*\) kB/\1/p' /proc/meminfo)"
 VMEM="$((${__mem_kib} / 1024 / 4))"
 
-# setup_build() - Set up pane layout for build tests
-setup_build() {
-	context_setup_host host
-
-	layout_host
-}
-
 # setup_passt() - Start qemu and passt
 setup_passt() {
 	context_setup_host host
diff --git a/test/run b/test/run
index ce24f446..b8000224 100755
--- a/test/run
+++ b/test/run
@@ -64,10 +64,6 @@ run() {
 	perf_init
 	[ ${CI} -eq 1 ]   && video_start ci
 
-	setup build
-	test build/all
-	teardown build
-
 	setup pasta
 	test pasta/ndp
 	test pasta/dhcp
-- 
@@ -64,10 +64,6 @@ run() {
 	perf_init
 	[ ${CI} -eq 1 ]   && video_start ci
 
-	setup build
-	test build/all
-	teardown build
-
 	setup pasta
 	test pasta/ndp
 	test pasta/dhcp
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 13/27] tasst: Add helpers for running background commands on sites
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (11 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 12/27] avocado: Convert build tests to avocado David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 14/27] tasst: Add helper to get network interface names for a site David Gibson
                   ` (13 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

We provide a wrapper around the Avocado utils library SubProcess because we
want to check for failure of background processes by default.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/exesite.py | 73 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 72 insertions(+), 1 deletion(-)

diff --git a/test/tasst/exesite.py b/test/tasst/exesite.py
index 9f037f77..e69db8ad 100644
--- a/test/tasst/exesite.py
+++ b/test/tasst/exesite.py
@@ -18,6 +18,35 @@ import avocado
 from avocado.utils.process import CmdError
 from avocado_classless.test import assert_eq, assert_raises, test_output
 
+from tasst.typecheck import typecheck
+
+
+class SiteProcess(contextlib.AbstractContextManager):
+    """
+    A background process running on a Site
+    """
+
+    def __init__(self, site, cmd, subp, *,
+                 ignore_status, context_timeout):
+        self.site = typecheck(site, Site)
+        self.cmd = typecheck(cmd, str)
+        self.subproc = typecheck(subp, avocado.utils.process.SubProcess)
+        self.ignore_status = typecheck(ignore_status, bool)
+        self.context_timeout = float(context_timeout)
+
+    def __enter__(self):
+        self.subproc.start()
+        return self
+
+    def __exit__(self, *exc_details):
+        result = self.subproc.run(timeout=self.context_timeout)
+        if not self.ignore_status and result.exit_status != 0:
+            siteinfo = f'[{self.site.name} site]'
+            raise avocado.utils.process.CmdError(self.cmd, result, siteinfo)
+
+    def run(self, **kwargs):
+        return self.subproc.run(**kwargs)
+
 
 class Site(contextlib.AbstractContextManager):
     """
@@ -47,6 +76,16 @@ class Site(contextlib.AbstractContextManager):
         cmd, kwargs = self.hostify(cmd, **kwargs)
         return avocado.utils.process.system(cmd, **kwargs)
 
+    def subprocess(self, cmd, **kwargs):
+        cmd, kwargs = self.hostify(cmd, **kwargs)
+        return avocado.utils.process.SubProcess(cmd, **kwargs)
+
+    def bg(self, cmd, context_timeout=1.0, ignore_status=False, **kwargs):
+        subproc = self.subprocess(cmd, **kwargs)
+        return SiteProcess(self, cmd, subproc,
+                           context_timeout=context_timeout,
+                           ignore_status=ignore_status)
+
     def require_cmds(self, *cmds):
         missing = [c for c in cmds
                    if self.fg(f'type {c}', ignore_status=True) != 0]
@@ -74,7 +113,39 @@ def test_site(sitefn):
         with s as site:
             site.fg('sleep infinity', timeout=0.1, ignore_status=True)
 
-    return test_output(test_true, test_false, test_echo, test_timeout)(sitefn)
+    def test_bg_true(s):
+        with s as site:
+            with site.bg('true'):
+                pass
+
+    def test_bg_false(s):
+        with s as site:
+            def run_false():
+                with site.bg('false'):
+                    pass
+            assert_raises(CmdError, run_false)
+
+    def test_bg_echo(s):
+        msg = 'Hello tasst'
+        with s as site:
+            with site.bg(f'echo {msg}') as proc:
+                res = proc.run()
+        assert_eq(res.stdout, msg.encode('utf-8') + b'\n')
+
+    def test_bg_timeout(s):
+        with s as site:
+            with site.bg('sleep infinity', ignore_status=True) as proc:
+                proc.run(timeout=0.1)
+
+    def test_bg_context_timeout(s):
+        with s as site:
+            with site.bg('sleep infinity', context_timeout=0.1,
+                         ignore_status=True):
+                pass
+
+    return test_output(test_true, test_false, test_echo, test_timeout,
+                       test_bg_true, test_bg_false, test_bg_echo,
+                       test_bg_timeout, test_bg_context_timeout)(sitefn)
 
 
 class RealHost(Site):
-- 
@@ -18,6 +18,35 @@ import avocado
 from avocado.utils.process import CmdError
 from avocado_classless.test import assert_eq, assert_raises, test_output
 
+from tasst.typecheck import typecheck
+
+
+class SiteProcess(contextlib.AbstractContextManager):
+    """
+    A background process running on a Site
+    """
+
+    def __init__(self, site, cmd, subp, *,
+                 ignore_status, context_timeout):
+        self.site = typecheck(site, Site)
+        self.cmd = typecheck(cmd, str)
+        self.subproc = typecheck(subp, avocado.utils.process.SubProcess)
+        self.ignore_status = typecheck(ignore_status, bool)
+        self.context_timeout = float(context_timeout)
+
+    def __enter__(self):
+        self.subproc.start()
+        return self
+
+    def __exit__(self, *exc_details):
+        result = self.subproc.run(timeout=self.context_timeout)
+        if not self.ignore_status and result.exit_status != 0:
+            siteinfo = f'[{self.site.name} site]'
+            raise avocado.utils.process.CmdError(self.cmd, result, siteinfo)
+
+    def run(self, **kwargs):
+        return self.subproc.run(**kwargs)
+
 
 class Site(contextlib.AbstractContextManager):
     """
@@ -47,6 +76,16 @@ class Site(contextlib.AbstractContextManager):
         cmd, kwargs = self.hostify(cmd, **kwargs)
         return avocado.utils.process.system(cmd, **kwargs)
 
+    def subprocess(self, cmd, **kwargs):
+        cmd, kwargs = self.hostify(cmd, **kwargs)
+        return avocado.utils.process.SubProcess(cmd, **kwargs)
+
+    def bg(self, cmd, context_timeout=1.0, ignore_status=False, **kwargs):
+        subproc = self.subprocess(cmd, **kwargs)
+        return SiteProcess(self, cmd, subproc,
+                           context_timeout=context_timeout,
+                           ignore_status=ignore_status)
+
     def require_cmds(self, *cmds):
         missing = [c for c in cmds
                    if self.fg(f'type {c}', ignore_status=True) != 0]
@@ -74,7 +113,39 @@ def test_site(sitefn):
         with s as site:
             site.fg('sleep infinity', timeout=0.1, ignore_status=True)
 
-    return test_output(test_true, test_false, test_echo, test_timeout)(sitefn)
+    def test_bg_true(s):
+        with s as site:
+            with site.bg('true'):
+                pass
+
+    def test_bg_false(s):
+        with s as site:
+            def run_false():
+                with site.bg('false'):
+                    pass
+            assert_raises(CmdError, run_false)
+
+    def test_bg_echo(s):
+        msg = 'Hello tasst'
+        with s as site:
+            with site.bg(f'echo {msg}') as proc:
+                res = proc.run()
+        assert_eq(res.stdout, msg.encode('utf-8') + b'\n')
+
+    def test_bg_timeout(s):
+        with s as site:
+            with site.bg('sleep infinity', ignore_status=True) as proc:
+                proc.run(timeout=0.1)
+
+    def test_bg_context_timeout(s):
+        with s as site:
+            with site.bg('sleep infinity', context_timeout=0.1,
+                         ignore_status=True):
+                pass
+
+    return test_output(test_true, test_false, test_echo, test_timeout,
+                       test_bg_true, test_bg_false, test_bg_echo,
+                       test_bg_timeout, test_bg_context_timeout)(sitefn)
 
 
 class RealHost(Site):
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 14/27] tasst: Add helper to get network interface names for a site
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (12 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 13/27] tasst: Add helpers for running background commands on sites David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 15/27] tasst: Add helpers to run commands with nstool David Gibson
                   ` (12 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Start adding convenience functions for handling sites as places with
network setup with a simple helper which lists the network interface names
for a site.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>

# Conflicts:
#	test/tasst/exesite.py
---
 test/tasst/exesite.py | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/test/tasst/exesite.py b/test/tasst/exesite.py
index e69db8ad..811b670e 100644
--- a/test/tasst/exesite.py
+++ b/test/tasst/exesite.py
@@ -13,10 +13,13 @@ tasst/exesite.py - Manage simulated network sites for testing
 
 
 import contextlib
+import json
 
 import avocado
 from avocado.utils.process import CmdError
-from avocado_classless.test import assert_eq, assert_raises, test_output
+from avocado_classless.test import (
+    assert_eq, assert_in, assert_raises, test_output
+)
 
 from tasst.typecheck import typecheck
 
@@ -93,6 +96,11 @@ class Site(contextlib.AbstractContextManager):
             raise avocado.TestCancel(
                 f"Missing commands {', '.join(missing)} on {self.name}")
 
+    def ifs(self):
+        self.require_cmds('ip')
+        info = json.loads(self.output('ip -j link show'))
+        return [i['ifname'] for i in info]
+
 
 def test_site(sitefn):
     def test_true(s):
@@ -143,9 +151,14 @@ def test_site(sitefn):
                          ignore_status=True):
                 pass
 
+    def test_has_lo(s):
+        with s as site:
+            assert_in('lo', site.ifs())
+
     return test_output(test_true, test_false, test_echo, test_timeout,
                        test_bg_true, test_bg_false, test_bg_echo,
-                       test_bg_timeout, test_bg_context_timeout)(sitefn)
+                       test_bg_timeout, test_bg_context_timeout,
+                       test_has_lo)(sitefn)
 
 
 class RealHost(Site):
-- 
@@ -13,10 +13,13 @@ tasst/exesite.py - Manage simulated network sites for testing
 
 
 import contextlib
+import json
 
 import avocado
 from avocado.utils.process import CmdError
-from avocado_classless.test import assert_eq, assert_raises, test_output
+from avocado_classless.test import (
+    assert_eq, assert_in, assert_raises, test_output
+)
 
 from tasst.typecheck import typecheck
 
@@ -93,6 +96,11 @@ class Site(contextlib.AbstractContextManager):
             raise avocado.TestCancel(
                 f"Missing commands {', '.join(missing)} on {self.name}")
 
+    def ifs(self):
+        self.require_cmds('ip')
+        info = json.loads(self.output('ip -j link show'))
+        return [i['ifname'] for i in info]
+
 
 def test_site(sitefn):
     def test_true(s):
@@ -143,9 +151,14 @@ def test_site(sitefn):
                          ignore_status=True):
                 pass
 
+    def test_has_lo(s):
+        with s as site:
+            assert_in('lo', site.ifs())
+
     return test_output(test_true, test_false, test_echo, test_timeout,
                        test_bg_true, test_bg_false, test_bg_echo,
-                       test_bg_timeout, test_bg_context_timeout)(sitefn)
+                       test_bg_timeout, test_bg_context_timeout,
+                       test_has_lo)(sitefn)
 
 
 class RealHost(Site):
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 15/27] tasst: Add helpers to run commands with nstool
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (13 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 14/27] tasst: Add helper to get network interface names for a site David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 16/27] tasst: Add ifup and network address helpers to Site David Gibson
                   ` (11 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Use our existing nstool C helper, add python wrappers to easily run
commands in various namespaces.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/Makefile         |   2 +-
 test/tasst/exesite.py |   8 ++
 test/tasst/nstool.py  | 168 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 177 insertions(+), 1 deletion(-)
 create mode 100644 test/tasst/nstool.py

diff --git a/test/Makefile b/test/Makefile
index 58159c83..da542d33 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -226,7 +226,7 @@ $(VENV):
 	$(VENV)/bin/pip install -e ./$(PLUGIN)
 
 .PHONY: avocado-assets
-avocado-assets:
+avocado-assets: nstool
 
 .PHONY: avocado
 avocado: avocado-assets $(VENV)
diff --git a/test/tasst/exesite.py b/test/tasst/exesite.py
index 811b670e..2e15129f 100644
--- a/test/tasst/exesite.py
+++ b/test/tasst/exesite.py
@@ -161,6 +161,14 @@ def test_site(sitefn):
                        test_has_lo)(sitefn)
 
 
+def test_isolated_site(sitefn):
+    def test_isolated_net(s):
+        with s as site:
+            assert_eq(site.ifs(), ['lo'])
+
+    return test_output(test_isolated_net)(test_site(sitefn))
+
+
 class RealHost(Site):
     """Represents the host on which the tests are running (as opposed
     to some simulated host created by the tests)
diff --git a/test/tasst/nstool.py b/test/tasst/nstool.py
new file mode 100644
index 00000000..f05c420d
--- /dev/null
+++ b/test/tasst/nstool.py
@@ -0,0 +1,168 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+nstool.py - Run commands in namespaces via 'nstool'
+"""
+
+import contextlib
+import os
+import tempfile
+
+from avocado.utils.process import CmdError
+from avocado_classless.test import assert_eq, assert_raises, test_output
+
+from tasst.exesite import Site, REAL_HOST, test_isolated_site, test_site
+from tasst.typecheck import typecheck
+
+# FIXME: Can this be made more portable?  # pylint: disable=W0511
+UNIX_PATH_MAX = 108
+
+NSTOOL_BIN = './nstool'
+
+
+class NsToolSite(Site):
+    """A bundle of Linux namespaces managed by nstool"""
+
+    def __init__(self, name, sockpath, parent=REAL_HOST):
+        if len(sockpath) > UNIX_PATH_MAX:
+            raise ValueError(
+                f'Unix domain socket path "{sockpath}" is too long'
+            )
+
+        super().__init__(name)
+        self.sockpath = typecheck(sockpath, str)
+        self.parent = typecheck(parent, Site)
+        self._pid = None
+
+    def __enter__(self):
+        pid = self.parent.output(f'{NSTOOL_BIN} info -wp {self.sockpath}',
+                                 verbose=False, timeout=1)
+        self._pid = int(pid)
+        return self
+
+    def __exit__(self, *exc_details):
+        pass
+
+    # PID of the nstool hold process as seen by the test host
+    def pid(self):
+        return self._pid
+
+    # PID of the nstool hold process as seen by another site
+    # (important when using PID namespaces)
+    def relative_pid(self, relative_to):
+        relpid = relative_to.output(f'{NSTOOL_BIN} info -p {self.sockpath}')
+        return int(relpid)
+
+    def hostify(self, cmd, *, sudo=False, **kwargs):
+        nst_args = self.sockpath
+        if sudo:
+            nst_args = '--keep-caps ' + nst_args
+        return f'{NSTOOL_BIN} exec {nst_args} -- {cmd}', kwargs
+
+
+@contextlib.contextmanager
+def unshare_site(nsname, unshare_opts, parent=REAL_HOST, sudo=False):
+    unshare_opts = typecheck(unshare_opts, str)
+    parent = typecheck(parent, Site)
+    sudo = typecheck(sudo, bool)
+    parent.require_cmds('unshare', NSTOOL_BIN)
+
+    # Create path for temporary nstool Unix socket
+    #
+    # Using Avocado's workdir often gives paths that are too lonhg for
+    # Unix sockets
+    with tempfile.TemporaryDirectory() as tmpd:
+        sockpath = os.path.join(tmpd, nsname)
+        holdcmd = f'unshare {unshare_opts} -- {NSTOOL_BIN} hold {sockpath}'
+        with parent.bg(holdcmd, sudo=sudo) as holder:
+            try:
+                with NsToolSite(nsname, sockpath, parent=parent) as site:
+                    yield site
+            finally:
+                try:
+                    parent.fg(f'{NSTOOL_BIN} stop {sockpath}')
+                finally:
+                    try:
+                        holder.run(timeout=0.1)
+                    finally:
+                        try:
+                            os.remove(sockpath)
+                        except FileNotFoundError:
+                            pass
+
+
+TEST_EXC = ValueError
+
+
+def test_sockdir_cleanup(s):
+    def mess(sockpaths):
+        with s as site:
+            ns = site
+            while isinstance(ns, NsToolSite):
+                sockpaths.append(ns.sockpath)
+                ns = ns.parent
+            raise TEST_EXC
+
+    sockpaths = []
+    assert_raises(TEST_EXC, mess, sockpaths)
+    assert sockpaths
+    for path in sockpaths:
+        assert not os.path.exists(os.path.dirname(path))
+
+
+def test_userns(nstool_site):
+    REAL_HOST.require_cmds('capsh')
+    with nstool_site as ns:
+        ns.require_cmds('capsh')
+        capcmd = 'capsh --has-p=CAP_SETUID'
+        assert_raises(CmdError, REAL_HOST.fg, capcmd)
+        ns.fg(capcmd, sudo=True)
+
+
+@test_output(test_userns, test_sockdir_cleanup)
+@test_isolated_site
+def userns_site():
+    return unshare_site('usernetns', '-Ucn')
+
+
+@test_output(test_sockdir_cleanup)
+@test_isolated_site
+@contextlib.contextmanager
+def nested_site():
+    with unshare_site('userns', '-Uc') as userns:
+        with unshare_site('netns', '-n', parent=userns, sudo=True) as netns:
+            yield netns
+
+
+def test_relative_pid(s):
+    with s as site:
+        # The holder is init (pid 1) within its own pidns
+        assert_eq(site.relative_pid(site), 1)
+
+
+@test_output(test_relative_pid, test_sockdir_cleanup)
+@test_isolated_site
+def pidns_site():
+    return unshare_site('pidns', '-Upfn')
+
+
+@test_site
+@contextlib.contextmanager
+def connect_site():
+    with tempfile.TemporaryDirectory() as tmpd:
+        sockpath = os.path.join(tmpd, 'nons')
+        holdcmd = f'{NSTOOL_BIN} hold {sockpath}'
+        try:
+            with REAL_HOST.bg(holdcmd, ignore_status=True,
+                              context_timeout=0.1):
+                with NsToolSite("fake ns", sockpath) as site:
+                    yield site
+        finally:
+            os.remove(sockpath)
-- 
@@ -0,0 +1,168 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+nstool.py - Run commands in namespaces via 'nstool'
+"""
+
+import contextlib
+import os
+import tempfile
+
+from avocado.utils.process import CmdError
+from avocado_classless.test import assert_eq, assert_raises, test_output
+
+from tasst.exesite import Site, REAL_HOST, test_isolated_site, test_site
+from tasst.typecheck import typecheck
+
+# FIXME: Can this be made more portable?  # pylint: disable=W0511
+UNIX_PATH_MAX = 108
+
+NSTOOL_BIN = './nstool'
+
+
+class NsToolSite(Site):
+    """A bundle of Linux namespaces managed by nstool"""
+
+    def __init__(self, name, sockpath, parent=REAL_HOST):
+        if len(sockpath) > UNIX_PATH_MAX:
+            raise ValueError(
+                f'Unix domain socket path "{sockpath}" is too long'
+            )
+
+        super().__init__(name)
+        self.sockpath = typecheck(sockpath, str)
+        self.parent = typecheck(parent, Site)
+        self._pid = None
+
+    def __enter__(self):
+        pid = self.parent.output(f'{NSTOOL_BIN} info -wp {self.sockpath}',
+                                 verbose=False, timeout=1)
+        self._pid = int(pid)
+        return self
+
+    def __exit__(self, *exc_details):
+        pass
+
+    # PID of the nstool hold process as seen by the test host
+    def pid(self):
+        return self._pid
+
+    # PID of the nstool hold process as seen by another site
+    # (important when using PID namespaces)
+    def relative_pid(self, relative_to):
+        relpid = relative_to.output(f'{NSTOOL_BIN} info -p {self.sockpath}')
+        return int(relpid)
+
+    def hostify(self, cmd, *, sudo=False, **kwargs):
+        nst_args = self.sockpath
+        if sudo:
+            nst_args = '--keep-caps ' + nst_args
+        return f'{NSTOOL_BIN} exec {nst_args} -- {cmd}', kwargs
+
+
+@contextlib.contextmanager
+def unshare_site(nsname, unshare_opts, parent=REAL_HOST, sudo=False):
+    unshare_opts = typecheck(unshare_opts, str)
+    parent = typecheck(parent, Site)
+    sudo = typecheck(sudo, bool)
+    parent.require_cmds('unshare', NSTOOL_BIN)
+
+    # Create path for temporary nstool Unix socket
+    #
+    # Using Avocado's workdir often gives paths that are too lonhg for
+    # Unix sockets
+    with tempfile.TemporaryDirectory() as tmpd:
+        sockpath = os.path.join(tmpd, nsname)
+        holdcmd = f'unshare {unshare_opts} -- {NSTOOL_BIN} hold {sockpath}'
+        with parent.bg(holdcmd, sudo=sudo) as holder:
+            try:
+                with NsToolSite(nsname, sockpath, parent=parent) as site:
+                    yield site
+            finally:
+                try:
+                    parent.fg(f'{NSTOOL_BIN} stop {sockpath}')
+                finally:
+                    try:
+                        holder.run(timeout=0.1)
+                    finally:
+                        try:
+                            os.remove(sockpath)
+                        except FileNotFoundError:
+                            pass
+
+
+TEST_EXC = ValueError
+
+
+def test_sockdir_cleanup(s):
+    def mess(sockpaths):
+        with s as site:
+            ns = site
+            while isinstance(ns, NsToolSite):
+                sockpaths.append(ns.sockpath)
+                ns = ns.parent
+            raise TEST_EXC
+
+    sockpaths = []
+    assert_raises(TEST_EXC, mess, sockpaths)
+    assert sockpaths
+    for path in sockpaths:
+        assert not os.path.exists(os.path.dirname(path))
+
+
+def test_userns(nstool_site):
+    REAL_HOST.require_cmds('capsh')
+    with nstool_site as ns:
+        ns.require_cmds('capsh')
+        capcmd = 'capsh --has-p=CAP_SETUID'
+        assert_raises(CmdError, REAL_HOST.fg, capcmd)
+        ns.fg(capcmd, sudo=True)
+
+
+@test_output(test_userns, test_sockdir_cleanup)
+@test_isolated_site
+def userns_site():
+    return unshare_site('usernetns', '-Ucn')
+
+
+@test_output(test_sockdir_cleanup)
+@test_isolated_site
+@contextlib.contextmanager
+def nested_site():
+    with unshare_site('userns', '-Uc') as userns:
+        with unshare_site('netns', '-n', parent=userns, sudo=True) as netns:
+            yield netns
+
+
+def test_relative_pid(s):
+    with s as site:
+        # The holder is init (pid 1) within its own pidns
+        assert_eq(site.relative_pid(site), 1)
+
+
+@test_output(test_relative_pid, test_sockdir_cleanup)
+@test_isolated_site
+def pidns_site():
+    return unshare_site('pidns', '-Upfn')
+
+
+@test_site
+@contextlib.contextmanager
+def connect_site():
+    with tempfile.TemporaryDirectory() as tmpd:
+        sockpath = os.path.join(tmpd, 'nons')
+        holdcmd = f'{NSTOOL_BIN} hold {sockpath}'
+        try:
+            with REAL_HOST.bg(holdcmd, ignore_status=True,
+                              context_timeout=0.1):
+                with NsToolSite("fake ns", sockpath) as site:
+                    yield site
+        finally:
+            os.remove(sockpath)
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 16/27] tasst: Add ifup and network address helpers to Site
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (14 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 15/27] tasst: Add helpers to run commands with nstool David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 17/27] tasst: Helper for creating veth devices between namespaces David Gibson
                   ` (10 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Add a helper to bring network interfaces up on a site, and to retrieve
configured IP addresses.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/exesite.py | 33 +++++++++++++++++++++++++++++++--
 test/tasst/nstool.py  | 11 +++++++++--
 2 files changed, 40 insertions(+), 4 deletions(-)

diff --git a/test/tasst/exesite.py b/test/tasst/exesite.py
index 2e15129f..246f9b3f 100644
--- a/test/tasst/exesite.py
+++ b/test/tasst/exesite.py
@@ -13,12 +13,13 @@ tasst/exesite.py - Manage simulated network sites for testing
 
 
 import contextlib
+import ipaddress
 import json
 
 import avocado
 from avocado.utils.process import CmdError
 from avocado_classless.test import (
-    assert_eq, assert_in, assert_raises, test_output
+    assert_eq, assert_eq_unordered, assert_in, assert_raises, test_output
 )
 
 from tasst.typecheck import typecheck
@@ -101,6 +102,28 @@ class Site(contextlib.AbstractContextManager):
         info = json.loads(self.output('ip -j link show'))
         return [i['ifname'] for i in info]
 
+    def ifup(self, ifname):
+        self.require_cmds('ip')
+        self.fg(f'ip link set {ifname} up', sudo=True)
+
+    def addrinfos(self, ifname, **criteria):
+        self.require_cmds('ip')
+        info = json.loads(self.output(f'ip -j addr show {ifname}'))
+        assert len(info) == 1  # We specified a specific interface
+
+        ais = list(ai for ai in info[0]['addr_info'])
+        for key, value in criteria.items():
+            ais = [ai for ai in ais if key in ai and ai[key] == value]
+
+        return ais
+
+    def addrs(self, ifname, **criteria):
+        self.require_cmds('ip')
+        # Return just the parsed, non-tentative addresses
+        return [ipaddress.ip_interface(f'{ai["local"]}/{ai["prefixlen"]}')
+                for ai in self.addrinfos(ifname, **criteria)
+                if 'tentative' not in ai]
+
 
 def test_site(sitefn):
     def test_true(s):
@@ -155,10 +178,16 @@ def test_site(sitefn):
         with s as site:
             assert_in('lo', site.ifs())
 
+    def test_lo_addrs(s):
+        expected = [ipaddress.ip_interface(a)
+                    for a in ['127.0.0.1/8', '::1/128']]
+        with s as site:
+            assert_eq_unordered(site.addrs('lo'), expected)
+
     return test_output(test_true, test_false, test_echo, test_timeout,
                        test_bg_true, test_bg_false, test_bg_echo,
                        test_bg_timeout, test_bg_context_timeout,
-                       test_has_lo)(sitefn)
+                       test_has_lo, test_lo_addrs)(sitefn)
 
 
 def test_isolated_site(sitefn):
diff --git a/test/tasst/nstool.py b/test/tasst/nstool.py
index f05c420d..7fa46893 100644
--- a/test/tasst/nstool.py
+++ b/test/tasst/nstool.py
@@ -128,8 +128,11 @@ def test_userns(nstool_site):
 
 @test_output(test_userns, test_sockdir_cleanup)
 @test_isolated_site
+@contextlib.contextmanager
 def userns_site():
-    return unshare_site('usernetns', '-Ucn')
+    with unshare_site('usernetns', '-Ucn') as ns:
+        ns.ifup('lo')
+        yield ns
 
 
 @test_output(test_sockdir_cleanup)
@@ -138,6 +141,7 @@ def userns_site():
 def nested_site():
     with unshare_site('userns', '-Uc') as userns:
         with unshare_site('netns', '-n', parent=userns, sudo=True) as netns:
+            netns.ifup('lo')
             yield netns
 
 
@@ -149,8 +153,11 @@ def test_relative_pid(s):
 
 @test_output(test_relative_pid, test_sockdir_cleanup)
 @test_isolated_site
+@contextlib.contextmanager
 def pidns_site():
-    return unshare_site('pidns', '-Upfn')
+    with unshare_site('pidns', '-Upfn') as ns:
+        ns.ifup('lo')
+        yield ns
 
 
 @test_site
-- 
@@ -128,8 +128,11 @@ def test_userns(nstool_site):
 
 @test_output(test_userns, test_sockdir_cleanup)
 @test_isolated_site
+@contextlib.contextmanager
 def userns_site():
-    return unshare_site('usernetns', '-Ucn')
+    with unshare_site('usernetns', '-Ucn') as ns:
+        ns.ifup('lo')
+        yield ns
 
 
 @test_output(test_sockdir_cleanup)
@@ -138,6 +141,7 @@ def userns_site():
 def nested_site():
     with unshare_site('userns', '-Uc') as userns:
         with unshare_site('netns', '-n', parent=userns, sudo=True) as netns:
+            netns.ifup('lo')
             yield netns
 
 
@@ -149,8 +153,11 @@ def test_relative_pid(s):
 
 @test_output(test_relative_pid, test_sockdir_cleanup)
 @test_isolated_site
+@contextlib.contextmanager
 def pidns_site():
-    return unshare_site('pidns', '-Upfn')
+    with unshare_site('pidns', '-Upfn') as ns:
+        ns.ifup('lo')
+        yield ns
 
 
 @test_site
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 17/27] tasst: Helper for creating veth devices between namespaces
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (15 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 16/27] tasst: Add ifup and network address helpers to Site David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 18/27] tasst: Add helper for getting MTU of a network interface David Gibson
                   ` (9 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/meta/__init__.py | 16 ++++++++++++++++
 test/tasst/meta/veth.py     | 33 +++++++++++++++++++++++++++++++++
 test/tasst/nstool.py        |  9 +++++++++
 3 files changed, 58 insertions(+)
 create mode 100644 test/tasst/meta/__init__.py
 create mode 100644 test/tasst/meta/veth.py

diff --git a/test/tasst/meta/__init__.py b/test/tasst/meta/__init__.py
new file mode 100644
index 00000000..55039426
--- /dev/null
+++ b/test/tasst/meta/__init__.py
@@ -0,0 +1,16 @@
+#! /usr/bin/python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""Test A Simple Socket Transport
+
+meta/ - Test pieces of the test infrastructure.
+
+Usually, tests for the test infrastructure should go next to the
+implementation of the thing being tested.  Sometimes that's
+inconvenient or impossible (usually because it would cause a circular
+module dependency).  In that case those tests can go here.
+"""
diff --git a/test/tasst/meta/veth.py b/test/tasst/meta/veth.py
new file mode 100644
index 00000000..9cef6271
--- /dev/null
+++ b/test/tasst/meta/veth.py
@@ -0,0 +1,33 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+meta/veth.py - Test various veth configurations
+"""
+
+import contextlib
+
+from avocado_classless.test import assert_eq_unordered, test
+
+from tasst.nstool import unshare_site
+
+
+@contextlib.contextmanager
+def unconfigured_veth():
+    with unshare_site('ns1', '-Un') as ns1:
+        with unshare_site('ns2', '-n', parent=ns1, sudo=True) as ns2:
+            ns1.veth('veth1', 'veth2', ns2)
+            yield (ns1, ns2)
+
+
+@test
+def test_ifs():
+    with unconfigured_veth() as (ns1, ns2):
+        assert_eq_unordered(ns1.ifs(), ['lo', 'veth1'])
+        assert_eq_unordered(ns2.ifs(), ['lo', 'veth2'])
diff --git a/test/tasst/nstool.py b/test/tasst/nstool.py
index 7fa46893..710c5ebd 100644
--- a/test/tasst/nstool.py
+++ b/test/tasst/nstool.py
@@ -66,6 +66,15 @@ class NsToolSite(Site):
             nst_args = '--keep-caps ' + nst_args
         return f'{NSTOOL_BIN} exec {nst_args} -- {cmd}', kwargs
 
+    def veth(self, ifname, peername, peer=None):
+        self.fg(f'ip link add {ifname} type veth peer name {peername}',
+                sudo=True)
+        if peer is not None:
+            if not isinstance(peer, NsToolSite):
+                raise TypeError
+            self.fg(f'ip link set {peername} netns {peer.relative_pid(self)}',
+                    sudo=True)
+
 
 @contextlib.contextmanager
 def unshare_site(nsname, unshare_opts, parent=REAL_HOST, sudo=False):
-- 
@@ -66,6 +66,15 @@ class NsToolSite(Site):
             nst_args = '--keep-caps ' + nst_args
         return f'{NSTOOL_BIN} exec {nst_args} -- {cmd}', kwargs
 
+    def veth(self, ifname, peername, peer=None):
+        self.fg(f'ip link add {ifname} type veth peer name {peername}',
+                sudo=True)
+        if peer is not None:
+            if not isinstance(peer, NsToolSite):
+                raise TypeError
+            self.fg(f'ip link set {peername} netns {peer.relative_pid(self)}',
+                    sudo=True)
+
 
 @contextlib.contextmanager
 def unshare_site(nsname, unshare_opts, parent=REAL_HOST, sudo=False):
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 18/27] tasst: Add helper for getting MTU of a network interface
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (16 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 17/27] tasst: Helper for creating veth devices between namespaces David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 19/27] tasst: Add helper to wait for IP address to appear David Gibson
                   ` (8 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/exesite.py   | 11 ++++++++++-
 test/tasst/meta/veth.py |  9 ++++++++-
 2 files changed, 18 insertions(+), 2 deletions(-)

diff --git a/test/tasst/exesite.py b/test/tasst/exesite.py
index 246f9b3f..63872c34 100644
--- a/test/tasst/exesite.py
+++ b/test/tasst/exesite.py
@@ -124,6 +124,11 @@ class Site(contextlib.AbstractContextManager):
                 for ai in self.addrinfos(ifname, **criteria)
                 if 'tentative' not in ai]
 
+    def mtu(self, ifname):
+        self.require_cmds('ip')
+        (info,) = json.loads(self.output(f'ip -j link show {ifname}'))
+        return info['mtu']
+
 
 def test_site(sitefn):
     def test_true(s):
@@ -184,10 +189,14 @@ def test_site(sitefn):
         with s as site:
             assert_eq_unordered(site.addrs('lo'), expected)
 
+    def test_lo_mtu(s):
+        with s as site:
+            assert_eq(site.mtu('lo'), 65536)
+
     return test_output(test_true, test_false, test_echo, test_timeout,
                        test_bg_true, test_bg_false, test_bg_echo,
                        test_bg_timeout, test_bg_context_timeout,
-                       test_has_lo, test_lo_addrs)(sitefn)
+                       test_has_lo, test_lo_addrs, test_lo_mtu)(sitefn)
 
 
 def test_isolated_site(sitefn):
diff --git a/test/tasst/meta/veth.py b/test/tasst/meta/veth.py
index 9cef6271..053bd9c8 100644
--- a/test/tasst/meta/veth.py
+++ b/test/tasst/meta/veth.py
@@ -13,7 +13,7 @@ meta/veth.py - Test various veth configurations
 
 import contextlib
 
-from avocado_classless.test import assert_eq_unordered, test
+from avocado_classless.test import assert_eq, assert_eq_unordered, test
 
 from tasst.nstool import unshare_site
 
@@ -31,3 +31,10 @@ def test_ifs():
     with unconfigured_veth() as (ns1, ns2):
         assert_eq_unordered(ns1.ifs(), ['lo', 'veth1'])
         assert_eq_unordered(ns2.ifs(), ['lo', 'veth2'])
+
+
+@test
+def test_mtu():
+    with unconfigured_veth() as (ns1, ns2):
+        assert_eq(ns1.mtu('veth1'), 1500)
+        assert_eq(ns2.mtu('veth2'), 1500)
-- 
@@ -13,7 +13,7 @@ meta/veth.py - Test various veth configurations
 
 import contextlib
 
-from avocado_classless.test import assert_eq_unordered, test
+from avocado_classless.test import assert_eq, assert_eq_unordered, test
 
 from tasst.nstool import unshare_site
 
@@ -31,3 +31,10 @@ def test_ifs():
     with unconfigured_veth() as (ns1, ns2):
         assert_eq_unordered(ns1.ifs(), ['lo', 'veth1'])
         assert_eq_unordered(ns2.ifs(), ['lo', 'veth2'])
+
+
+@test
+def test_mtu():
+    with unconfigured_veth() as (ns1, ns2):
+        assert_eq(ns1.mtu('veth1'), 1500)
+        assert_eq(ns2.mtu('veth2'), 1500)
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 19/27] tasst: Add helper to wait for IP address to appear
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (17 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 18/27] tasst: Add helper for getting MTU of a network interface David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 20/27] tasst: Add helpers for getting a site's routes David Gibson
                   ` (7 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Add a helper to the Site() class to wait for an address with specified
characteristics to be ready on an interface.  In particular this is useful
for waiting for IPv6 SLAAC & DAD (Duplicate Address Detection) to complete.

Because DAD is not going to be useful in many of our scenarios, also extend
Site.ifup() to allow DAD to be switched to optimistic mode or disabled.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/exesite.py          | 22 ++++++++++++++++++-
 test/tasst/meta/static_ifup.py | 40 ++++++++++++++++++++++++++++++++++
 test/tasst/meta/veth.py        | 26 ++++++++++++++++++++++
 3 files changed, 87 insertions(+), 1 deletion(-)
 create mode 100644 test/tasst/meta/static_ifup.py

diff --git a/test/tasst/exesite.py b/test/tasst/exesite.py
index 63872c34..423fccbd 100644
--- a/test/tasst/exesite.py
+++ b/test/tasst/exesite.py
@@ -102,8 +102,22 @@ class Site(contextlib.AbstractContextManager):
         info = json.loads(self.output('ip -j link show'))
         return [i['ifname'] for i in info]
 
-    def ifup(self, ifname):
+    def ifup(self, ifname, *addrs, dad=None):
         self.require_cmds('ip')
+        if dad == 'disable':
+            self.fg(f'sysctl net.ipv6.conf.{ifname}.accept_dad=0', sudo=True)
+        elif dad == 'optimistic':
+            self.fg(f'sysctl net.ipv6.conf.{ifname}.optimistic_dad=1',
+                    sudo=True)
+        elif dad is not None:
+            raise ValueError
+
+        for a in addrs:
+            if not isinstance(a, ipaddress.IPv4Interface) \
+               and not isinstance(a, ipaddress.IPv6Interface):
+                raise TypeError
+            self.fg(f'ip addr add {a.with_prefixlen} dev {ifname}', sudo=True)
+
         self.fg(f'ip link set {ifname} up', sudo=True)
 
     def addrinfos(self, ifname, **criteria):
@@ -129,6 +143,12 @@ class Site(contextlib.AbstractContextManager):
         (info,) = json.loads(self.output(f'ip -j link show {ifname}'))
         return info['mtu']
 
+    def addr_wait(self, ifname, **criteria):
+        while True:
+            addrs = self.addrs(ifname, **criteria)
+            if addrs:
+                return addrs
+
 
 def test_site(sitefn):
     def test_true(s):
diff --git a/test/tasst/meta/static_ifup.py b/test/tasst/meta/static_ifup.py
new file mode 100644
index 00000000..0896c747
--- /dev/null
+++ b/test/tasst/meta/static_ifup.py
@@ -0,0 +1,40 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+meta/static_ifup - Static address configuration
+"""
+
+import contextlib
+import ipaddress
+
+from avocado_classless.test import assert_eq_unordered, test
+
+from tasst.nstool import unshare_site
+
+
+IFNAME = 'testveth'
+IFNAME_PEER = 'vethpeer'
+TEST_IPS = [ipaddress.ip_interface('192.0.2.1/24'),
+            ipaddress.ip_interface('2001:db8:9a55::1/112'),
+            ipaddress.ip_interface('10.1.2.3/8')]
+
+
+@contextlib.contextmanager
+def setup_ns():
+    with unshare_site('ns', '-Un') as ns:
+        ns.veth(IFNAME, IFNAME_PEER)
+        ns.ifup(IFNAME, *TEST_IPS, dad='disable')
+        yield ns
+
+
+@test
+def test_addr():
+    with setup_ns() as ns:
+        assert_eq_unordered(ns.addrs(IFNAME, scope='global'), TEST_IPS)
diff --git a/test/tasst/meta/veth.py b/test/tasst/meta/veth.py
index 053bd9c8..cedcf059 100644
--- a/test/tasst/meta/veth.py
+++ b/test/tasst/meta/veth.py
@@ -12,6 +12,7 @@ meta/veth.py - Test various veth configurations
 """
 
 import contextlib
+import ipaddress
 
 from avocado_classless.test import assert_eq, assert_eq_unordered, test
 
@@ -38,3 +39,28 @@ def test_mtu():
     with unconfigured_veth() as (ns1, ns2):
         assert_eq(ns1.mtu('veth1'), 1500)
         assert_eq(ns2.mtu('veth2'), 1500)
+
+
+@test
+def test_slaac(dad=None):
+    TESTMAC = '02:aa:bb:cc:dd:ee'
+    TESTIP = ipaddress.ip_interface('fe80::aa:bbff:fecc:ddee/64')
+
+    with unconfigured_veth() as (ns1, ns2):
+        ns1.fg(f'ip link set dev veth1 address {TESTMAC}', sudo=True)
+
+        ns1.ifup('veth1', dad=dad)
+        ns2.ifup('veth2')
+
+        addrs = ns1.addr_wait('veth1', family='inet6', scope='link')
+        assert_eq(addrs, [TESTIP])
+
+
+@test
+def test_optimistic_dad():
+    test_slaac(dad='optimistic')
+
+
+@test
+def test_no_dad():
+    test_slaac(dad='disable')
-- 
@@ -12,6 +12,7 @@ meta/veth.py - Test various veth configurations
 """
 
 import contextlib
+import ipaddress
 
 from avocado_classless.test import assert_eq, assert_eq_unordered, test
 
@@ -38,3 +39,28 @@ def test_mtu():
     with unconfigured_veth() as (ns1, ns2):
         assert_eq(ns1.mtu('veth1'), 1500)
         assert_eq(ns2.mtu('veth2'), 1500)
+
+
+@test
+def test_slaac(dad=None):
+    TESTMAC = '02:aa:bb:cc:dd:ee'
+    TESTIP = ipaddress.ip_interface('fe80::aa:bbff:fecc:ddee/64')
+
+    with unconfigured_veth() as (ns1, ns2):
+        ns1.fg(f'ip link set dev veth1 address {TESTMAC}', sudo=True)
+
+        ns1.ifup('veth1', dad=dad)
+        ns2.ifup('veth2')
+
+        addrs = ns1.addr_wait('veth1', family='inet6', scope='link')
+        assert_eq(addrs, [TESTIP])
+
+
+@test
+def test_optimistic_dad():
+    test_slaac(dad='optimistic')
+
+
+@test
+def test_no_dad():
+    test_slaac(dad='disable')
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 20/27] tasst: Add helpers for getting a site's routes
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (18 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 19/27] tasst: Add helper to wait for IP address to appear David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 21/27] tasst: Helpers to test transferring data between sites David Gibson
                   ` (6 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/exesite.py          | 13 +++++++++++++
 test/tasst/meta/static_ifup.py | 20 ++++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/test/tasst/exesite.py b/test/tasst/exesite.py
index 423fccbd..1f34eee9 100644
--- a/test/tasst/exesite.py
+++ b/test/tasst/exesite.py
@@ -149,6 +149,19 @@ class Site(contextlib.AbstractContextManager):
             if addrs:
                 return addrs
 
+    def _routes(self, ipv, **criteria):
+        routes = json.loads(self.output(f'ip -j -{ipv} route'))
+        for key, value in criteria.items():
+            routes = [r for r in routes if key in r and r[key] == value]
+
+        return routes
+
+    def routes4(self, **criteria):
+        return self._routes('4', **criteria)
+
+    def routes6(self, **criteria):
+        return self._routes('6', **criteria)
+
 
 def test_site(sitefn):
     def test_true(s):
diff --git a/test/tasst/meta/static_ifup.py b/test/tasst/meta/static_ifup.py
index 0896c747..f5fcc14f 100644
--- a/test/tasst/meta/static_ifup.py
+++ b/test/tasst/meta/static_ifup.py
@@ -38,3 +38,23 @@ def setup_ns():
 def test_addr():
     with setup_ns() as ns:
         assert_eq_unordered(ns.addrs(IFNAME, scope='global'), TEST_IPS)
+
+
+@test
+def test_routes4():
+    with setup_ns() as ns:
+        expected_routes = [i.network for i in TEST_IPS
+                           if isinstance(i, ipaddress.IPv4Interface)]
+        actual_routes = [ipaddress.ip_interface(r['dst']).network
+                         for r in ns.routes4(dev=IFNAME)]
+        assert_eq_unordered(expected_routes, actual_routes)
+
+
+@test
+def test_routes6():
+    with setup_ns() as ns:
+        expected_routes = [i.network for i in TEST_IPS
+                           if isinstance(i, ipaddress.IPv6Interface)]
+        actual_routes = [ipaddress.ip_interface(r['dst']).network
+                         for r in ns.routes6(dev=IFNAME)]
+        assert_eq_unordered(expected_routes, actual_routes)
-- 
@@ -38,3 +38,23 @@ def setup_ns():
 def test_addr():
     with setup_ns() as ns:
         assert_eq_unordered(ns.addrs(IFNAME, scope='global'), TEST_IPS)
+
+
+@test
+def test_routes4():
+    with setup_ns() as ns:
+        expected_routes = [i.network for i in TEST_IPS
+                           if isinstance(i, ipaddress.IPv4Interface)]
+        actual_routes = [ipaddress.ip_interface(r['dst']).network
+                         for r in ns.routes4(dev=IFNAME)]
+        assert_eq_unordered(expected_routes, actual_routes)
+
+
+@test
+def test_routes6():
+    with setup_ns() as ns:
+        expected_routes = [i.network for i in TEST_IPS
+                           if isinstance(i, ipaddress.IPv6Interface)]
+        actual_routes = [ipaddress.ip_interface(r['dst']).network
+                         for r in ns.routes6(dev=IFNAME)]
+        assert_eq_unordered(expected_routes, actual_routes)
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 21/27] tasst: Helpers to test transferring data between sites
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (19 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 20/27] tasst: Add helpers for getting a site's routes David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 22/27] tasst: IP address allocation helpers David Gibson
                   ` (5 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Many of our existing tests are based on using socat to transfer between
various locations connected via pasta or passt.  Add helpers to make
avocado tests performing similar transfers.  Add meta tests to verify those
work as expected when we don't have pasta or passt involved yet.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/Makefile          |   2 +-
 test/tasst/transfer.py | 175 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 176 insertions(+), 1 deletion(-)
 create mode 100644 test/tasst/transfer.py

diff --git a/test/Makefile b/test/Makefile
index da542d33..953eacf2 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -226,7 +226,7 @@ $(VENV):
 	$(VENV)/bin/pip install -e ./$(PLUGIN)
 
 .PHONY: avocado-assets
-avocado-assets: nstool
+avocado-assets: nstool small.bin medium.bin big.bin
 
 .PHONY: avocado
 avocado: avocado-assets $(VENV)
diff --git a/test/tasst/transfer.py b/test/tasst/transfer.py
new file mode 100644
index 00000000..788c1d52
--- /dev/null
+++ b/test/tasst/transfer.py
@@ -0,0 +1,175 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+transfer.py - Helpers for testing data transfers
+"""
+
+import contextlib
+import ipaddress
+import time
+
+from avocado_classless.test import assert_eq, test_output
+
+from tasst.nstool import unshare_site
+
+
+# HACK: how long to wait for the server to be ready and listening (s)
+SERVER_READY_DELAY = 0.05  # 1/20th of a second
+
+
+# socat needs IPv6 addresses in square brackets
+def socat_fmt(ip):
+    if isinstance(ip, ipaddress.IPv6Address):
+        return f'[{ip}]'
+    if isinstance(ip, ipaddress.IPv4Address):
+        return f'{ip}'
+    raise TypeError
+
+
+def socat_upload(datafile, cs, ss, connect, listen):
+    cs.require_cmds('socat', 'cat')
+    ss.require_cmds('socat')
+
+    with ss.bg(f'socat -u {listen} STDOUT', verbose=False) as server:
+        time.sleep(SERVER_READY_DELAY)
+        cs.fg(f'socat -u OPEN:{datafile} {connect}')
+        res = server.run()
+    srcdata = cs.output(f'cat {datafile}', verbose=False)
+    assert_eq(srcdata, res.stdout)
+
+
+def socat_download(datafile, cs, ss, connect, listen):
+    cs.require_cmds('socat')
+    ss.require_cmds('socat', 'cat')
+
+    with ss.bg(f'socat -u OPEN:{datafile} {listen}'):
+        time.sleep(SERVER_READY_DELAY)
+        dstdata = cs.output(f'socat -u {connect} STDOUT', verbose=False)
+    srcdata = ss.output(f'cat {datafile}', verbose=False)
+    assert_eq(srcdata, dstdata)
+
+
+# pylint: disable=R0913
+def _tcp_socat(connectip, connectport, listenip, listenport, fromip):
+    v6 = isinstance(connectip, ipaddress.IPv6Address)
+    if listenport is None:
+        listenport = connectport
+    if v6:
+        connect = f'TCP6:[{connectip}]:{connectport},ipv6only'
+        listen = f'TCP6-LISTEN:{listenport},ipv6only'
+    else:
+        connect = f'TCP4:{connectip}:{connectport}'
+        listen = f'TCP4-LISTEN:{listenport}'
+    if listenip is not None:
+        listen += f',bind={socat_fmt(listenip)}'
+    if fromip is not None:
+        connect += f',bind={socat_fmt(fromip)}'
+    return (connect, listen)
+
+
+def tcp_upload(datafile, cs, ss, connectip, connectport,
+               listenip=None, listenport=None, fromip=None):
+    connect, listen = _tcp_socat(connectip, connectport, listenip, listenport,
+                                 fromip)
+    socat_upload(datafile, cs, ss, connect, listen)
+
+
+def tcp_download(datafile, cs, ss, connectip, connectport,
+                 listenip=None, listenport=None, fromip=None):
+    connect, listen = _tcp_socat(connectip, connectport, listenip, listenport,
+                                 fromip)
+    socat_download(datafile, cs, ss, connect, listen)
+
+
+def udp_transfer(datafile, cs, ss, connectip, connectport,
+                 listenip=None, listenport=None, fromip=None):
+    v6 = isinstance(connectip, ipaddress.IPv6Address)
+    if listenport is None:
+        listenport = connectport
+    if v6:
+        connect = f'UDP6:[{connectip}]:{connectport},ipv6only,shut-null'
+        listen = f'UDP6-LISTEN:{listenport},ipv6only,null-eof'
+    else:
+        connect = f'UDP4:{connectip}:{connectport},shut-null'
+        listen = f'UDP4-LISTEN:{listenport},null-eof'
+    if listenip is not None:
+        listen += f',bind={socat_fmt(listenip)}'
+    if fromip is not None:
+        connect += f',bind={socat_fmt(fromip)}'
+
+    socat_upload(datafile, cs, ss, connect, listen)
+
+
+def test_tcp_uploads(datafile, ip4, ip6, port,
+                     listenip4=None, listenip6=None, listenport=None,
+                     fromip4=None, fromip6=None):
+    if listenport is None:
+        listenport = port
+
+    def dec(sitefn):
+        def tcp4_upload(sites):
+            with sites as (cs, ss):
+                tcp_upload(datafile, cs, ss, ip4, port,
+                           listenip=listenip4, listenport=listenport,
+                           fromip=fromip4)
+
+        def tcp6_upload(sites):
+            with sites as (cs, ss):
+                tcp_upload(datafile, cs, ss, ip6, port,
+                           listenip=listenip6, listenport=listenport,
+                           fromip=fromip6)
+
+        return test_output(tcp4_upload, tcp6_upload)(sitefn)
+
+    return dec
+
+
+def test_udp_transfers(datafile, ip4, ip6, port,
+                       listenip4=None, listenip6=None, listenport=None,
+                       fromip4=None, fromip6=None):
+    if listenport is None:
+        listenport = port
+
+    def dec(sitefn):
+        def udp4_transfer(sites):
+            with sites as (cs, ss):
+                udp_transfer(datafile, cs, ss, ip4, port,
+                             listenip=listenip4, listenport=listenport,
+                             fromip=fromip4)
+
+        def udp6_transfer(sites):
+            with sites as (cs, ss):
+                udp_transfer(datafile, cs, ss, ip6, port,
+                             listenip=listenip6, listenport=listenport,
+                             fromip=fromip6)
+        return test_output(udp4_transfer, udp6_transfer)(sitefn)
+
+    return dec
+
+
+def test_transfers(ip4, ip6, port, **kwargs):
+    def dec(sitefn):
+        sitefn = test_tcp_uploads('small.bin', ip4, ip6, port,
+                                  **kwargs)(sitefn)
+        sitefn = test_udp_transfers('medium.bin', ip4, ip6, port,
+                                    **kwargs)(sitefn)
+        return sitefn
+
+    return dec
+
+
+@test_transfers(ip4=ipaddress.ip_address('127.0.0.1'),
+                ip6=ipaddress.ip_address('::1'),
+                port=10000)
+@contextlib.contextmanager
+def local_only():
+    with unshare_site('ns', '-Un') as ns:
+        ns.ifup('lo')
+        yield (ns, ns)
-- 
@@ -0,0 +1,175 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+transfer.py - Helpers for testing data transfers
+"""
+
+import contextlib
+import ipaddress
+import time
+
+from avocado_classless.test import assert_eq, test_output
+
+from tasst.nstool import unshare_site
+
+
+# HACK: how long to wait for the server to be ready and listening (s)
+SERVER_READY_DELAY = 0.05  # 1/20th of a second
+
+
+# socat needs IPv6 addresses in square brackets
+def socat_fmt(ip):
+    if isinstance(ip, ipaddress.IPv6Address):
+        return f'[{ip}]'
+    if isinstance(ip, ipaddress.IPv4Address):
+        return f'{ip}'
+    raise TypeError
+
+
+def socat_upload(datafile, cs, ss, connect, listen):
+    cs.require_cmds('socat', 'cat')
+    ss.require_cmds('socat')
+
+    with ss.bg(f'socat -u {listen} STDOUT', verbose=False) as server:
+        time.sleep(SERVER_READY_DELAY)
+        cs.fg(f'socat -u OPEN:{datafile} {connect}')
+        res = server.run()
+    srcdata = cs.output(f'cat {datafile}', verbose=False)
+    assert_eq(srcdata, res.stdout)
+
+
+def socat_download(datafile, cs, ss, connect, listen):
+    cs.require_cmds('socat')
+    ss.require_cmds('socat', 'cat')
+
+    with ss.bg(f'socat -u OPEN:{datafile} {listen}'):
+        time.sleep(SERVER_READY_DELAY)
+        dstdata = cs.output(f'socat -u {connect} STDOUT', verbose=False)
+    srcdata = ss.output(f'cat {datafile}', verbose=False)
+    assert_eq(srcdata, dstdata)
+
+
+# pylint: disable=R0913
+def _tcp_socat(connectip, connectport, listenip, listenport, fromip):
+    v6 = isinstance(connectip, ipaddress.IPv6Address)
+    if listenport is None:
+        listenport = connectport
+    if v6:
+        connect = f'TCP6:[{connectip}]:{connectport},ipv6only'
+        listen = f'TCP6-LISTEN:{listenport},ipv6only'
+    else:
+        connect = f'TCP4:{connectip}:{connectport}'
+        listen = f'TCP4-LISTEN:{listenport}'
+    if listenip is not None:
+        listen += f',bind={socat_fmt(listenip)}'
+    if fromip is not None:
+        connect += f',bind={socat_fmt(fromip)}'
+    return (connect, listen)
+
+
+def tcp_upload(datafile, cs, ss, connectip, connectport,
+               listenip=None, listenport=None, fromip=None):
+    connect, listen = _tcp_socat(connectip, connectport, listenip, listenport,
+                                 fromip)
+    socat_upload(datafile, cs, ss, connect, listen)
+
+
+def tcp_download(datafile, cs, ss, connectip, connectport,
+                 listenip=None, listenport=None, fromip=None):
+    connect, listen = _tcp_socat(connectip, connectport, listenip, listenport,
+                                 fromip)
+    socat_download(datafile, cs, ss, connect, listen)
+
+
+def udp_transfer(datafile, cs, ss, connectip, connectport,
+                 listenip=None, listenport=None, fromip=None):
+    v6 = isinstance(connectip, ipaddress.IPv6Address)
+    if listenport is None:
+        listenport = connectport
+    if v6:
+        connect = f'UDP6:[{connectip}]:{connectport},ipv6only,shut-null'
+        listen = f'UDP6-LISTEN:{listenport},ipv6only,null-eof'
+    else:
+        connect = f'UDP4:{connectip}:{connectport},shut-null'
+        listen = f'UDP4-LISTEN:{listenport},null-eof'
+    if listenip is not None:
+        listen += f',bind={socat_fmt(listenip)}'
+    if fromip is not None:
+        connect += f',bind={socat_fmt(fromip)}'
+
+    socat_upload(datafile, cs, ss, connect, listen)
+
+
+def test_tcp_uploads(datafile, ip4, ip6, port,
+                     listenip4=None, listenip6=None, listenport=None,
+                     fromip4=None, fromip6=None):
+    if listenport is None:
+        listenport = port
+
+    def dec(sitefn):
+        def tcp4_upload(sites):
+            with sites as (cs, ss):
+                tcp_upload(datafile, cs, ss, ip4, port,
+                           listenip=listenip4, listenport=listenport,
+                           fromip=fromip4)
+
+        def tcp6_upload(sites):
+            with sites as (cs, ss):
+                tcp_upload(datafile, cs, ss, ip6, port,
+                           listenip=listenip6, listenport=listenport,
+                           fromip=fromip6)
+
+        return test_output(tcp4_upload, tcp6_upload)(sitefn)
+
+    return dec
+
+
+def test_udp_transfers(datafile, ip4, ip6, port,
+                       listenip4=None, listenip6=None, listenport=None,
+                       fromip4=None, fromip6=None):
+    if listenport is None:
+        listenport = port
+
+    def dec(sitefn):
+        def udp4_transfer(sites):
+            with sites as (cs, ss):
+                udp_transfer(datafile, cs, ss, ip4, port,
+                             listenip=listenip4, listenport=listenport,
+                             fromip=fromip4)
+
+        def udp6_transfer(sites):
+            with sites as (cs, ss):
+                udp_transfer(datafile, cs, ss, ip6, port,
+                             listenip=listenip6, listenport=listenport,
+                             fromip=fromip6)
+        return test_output(udp4_transfer, udp6_transfer)(sitefn)
+
+    return dec
+
+
+def test_transfers(ip4, ip6, port, **kwargs):
+    def dec(sitefn):
+        sitefn = test_tcp_uploads('small.bin', ip4, ip6, port,
+                                  **kwargs)(sitefn)
+        sitefn = test_udp_transfers('medium.bin', ip4, ip6, port,
+                                    **kwargs)(sitefn)
+        return sitefn
+
+    return dec
+
+
+@test_transfers(ip4=ipaddress.ip_address('127.0.0.1'),
+                ip6=ipaddress.ip_address('::1'),
+                port=10000)
+@contextlib.contextmanager
+def local_only():
+    with unshare_site('ns', '-Un') as ns:
+        ns.ifup('lo')
+        yield (ns, ns)
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 22/27] tasst: IP address allocation helpers
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (20 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 21/27] tasst: Helpers to test transferring data between sites David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 23/27] tasst: Helpers for running daemons with a pidfile David Gibson
                   ` (4 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

A bunch of our test scenarious will require us to allocate IPv4 and IPv6
addresses in example networks.  Make helpers to do this easily.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/address.py   | 80 +++++++++++++++++++++++++++++++++++++++++
 test/tasst/meta/veth.py | 20 +++++++++++
 test/tasst/transfer.py  |  5 ++-
 3 files changed, 102 insertions(+), 3 deletions(-)
 create mode 100644 test/tasst/address.py

diff --git a/test/tasst/address.py b/test/tasst/address.py
new file mode 100644
index 00000000..24f118c3
--- /dev/null
+++ b/test/tasst/address.py
@@ -0,0 +1,80 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+address.py - Address allocation helpers
+"""
+
+import ipaddress
+
+from avocado_classless.test import assert_eq, test
+
+
+# Loopback addresses, for convenience
+LOOPBACK4 = ipaddress.ip_address('127.0.0.1')
+LOOPBACK6 = ipaddress.ip_address('::1')
+
+# Documentation test networks defined by RFC 5737
+TEST_NET_1 = ipaddress.ip_network('192.0.2.0/24')
+TEST_NET_2 = ipaddress.ip_network('198.51.100.0/24')
+TEST_NET_3 = ipaddress.ip_network('203.0.113.0/24')
+
+# Documentation test network defined by RFC 3849
+TEST_NET6 = ipaddress.ip_network('2001:db8::/32')
+# Some subnets of that for our usage
+TEST_NET6_TASST_A = ipaddress.ip_network('2001:db8:9a55:aaaa::/64')
+TEST_NET6_TASST_B = ipaddress.ip_network('2001:db8:9a55:bbbb::/64')
+TEST_NET6_TASST_C = ipaddress.ip_network('2001:db8:9a55:cccc::/64')
+
+
+class IpiAllocator:  # pylint: disable=R0903
+    """IP address allocator"""
+
+    DEFAULT_NETS = [TEST_NET_1, TEST_NET6_TASST_A]
+
+    def __init__(self, *nets):
+        if not nets:
+            nets = self.DEFAULT_NETS
+
+        self.nets = [ipaddress.ip_network(n) for n in nets]
+        self.hostses = [n.hosts() for n in self.nets]
+
+    def next_ipis(self):
+        addrs = [next(h) for h in self.hostses]
+        return [ipaddress.ip_interface(f'{a}/{n.prefixlen}')
+                for a, n in zip(addrs, self.nets)]
+
+
+@test
+def ipa_test(nets=None, count=12):
+    if nets is None:
+        ipa = IpiAllocator()
+        nets = IpiAllocator.DEFAULT_NETS
+    else:
+        ipa = IpiAllocator(*nets)
+
+    addrsets = [set() for i in range(len(nets))]
+    for i in range(count):
+        addrs = ipa.next_ipis()
+        # Check we got as many addresses as expected
+        assert_eq(len(addrs), len(nets))
+        for s, a, n in zip(addrsets, addrs, nets):
+            # Check the addresses belong to the right network
+            assert_eq(a.network, ipaddress.ip_network(n))
+            s.add(a)
+
+    print(addrsets)
+    # Check the addresses are unique
+    for s in addrsets:
+        assert_eq(len(s), count)
+
+
+@test
+def ipa_test_custom():
+    ipa_test(nets=['10.55.0.0/16', '192.168.55.0/24', 'fd00:9a57:a000::/48'])
diff --git a/test/tasst/meta/veth.py b/test/tasst/meta/veth.py
index cedcf059..b2aafc2c 100644
--- a/test/tasst/meta/veth.py
+++ b/test/tasst/meta/veth.py
@@ -17,6 +17,8 @@ import ipaddress
 from avocado_classless.test import assert_eq, assert_eq_unordered, test
 
 from tasst.nstool import unshare_site
+from tasst.transfer import test_transfers
+from tasst.address import IpiAllocator
 
 
 @contextlib.contextmanager
@@ -64,3 +66,21 @@ def test_optimistic_dad():
 @test
 def test_no_dad():
     test_slaac(dad='disable')
+
+
+ipa = IpiAllocator()
+NS1_IP4, NS1_IP6 = ipa.next_ipis()
+NS2_IP4, NS2_IP6 = ipa.next_ipis()
+
+
+@test_transfers(ip4=NS2_IP4.ip, ip6=NS2_IP6.ip, port=10000)
+@contextlib.contextmanager
+def configured_veth():
+    with unconfigured_veth() as (ns1, ns2):
+        ns1.ifup('lo')
+        ns1.ifup('veth1', NS1_IP4, NS1_IP6, dad='disable')
+
+        ns2.ifup('lo')
+        ns2.ifup('veth2', NS2_IP4, NS2_IP6, dad='disable')
+
+        yield (ns1, ns2)
diff --git a/test/tasst/transfer.py b/test/tasst/transfer.py
index 788c1d52..5399bb54 100644
--- a/test/tasst/transfer.py
+++ b/test/tasst/transfer.py
@@ -17,6 +17,7 @@ import time
 
 from avocado_classless.test import assert_eq, test_output
 
+from tasst.address import LOOPBACK4, LOOPBACK6
 from tasst.nstool import unshare_site
 
 
@@ -165,9 +166,7 @@ def test_transfers(ip4, ip6, port, **kwargs):
     return dec
 
 
-@test_transfers(ip4=ipaddress.ip_address('127.0.0.1'),
-                ip6=ipaddress.ip_address('::1'),
-                port=10000)
+@test_transfers(ip4=LOOPBACK4, ip6=LOOPBACK6, port=10000)
 @contextlib.contextmanager
 def local_only():
     with unshare_site('ns', '-Un') as ns:
-- 
@@ -17,6 +17,7 @@ import time
 
 from avocado_classless.test import assert_eq, test_output
 
+from tasst.address import LOOPBACK4, LOOPBACK6
 from tasst.nstool import unshare_site
 
 
@@ -165,9 +166,7 @@ def test_transfers(ip4, ip6, port, **kwargs):
     return dec
 
 
-@test_transfers(ip4=ipaddress.ip_address('127.0.0.1'),
-                ip6=ipaddress.ip_address('::1'),
-                port=10000)
+@test_transfers(ip4=LOOPBACK4, ip6=LOOPBACK6, port=10000)
 @contextlib.contextmanager
 def local_only():
     with unshare_site('ns', '-Un') as ns:
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 23/27] tasst: Helpers for running daemons with a pidfile
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (21 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 22/27] tasst: IP address allocation helpers David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 24/27] tasst: Helpers for testing NDP behaviour David Gibson
                   ` (3 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

When running commands through nstool, stopping them directly with the
stop() method isn't reliable, because that just kills the nstool exec
process not the actual process.  At least for programs that create their
own pidfile we can do better by using that to kill off the process when
we're done, including during cleanup on failure.  This extends the
Site.bg() method with a pidfile parameter which will allow cleanup by this
means.

There's also the case of processes that start in the foreground then
daemonize themselves.  We'd like to clean those up via pidfile as well, so
add a Site.daemon() method to do exactly that.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/exesite.py | 31 +++++++++++++++++++++++++++----
 1 file changed, 27 insertions(+), 4 deletions(-)

diff --git a/test/tasst/exesite.py b/test/tasst/exesite.py
index 1f34eee9..6414c73f 100644
--- a/test/tasst/exesite.py
+++ b/test/tasst/exesite.py
@@ -22,7 +22,7 @@ from avocado_classless.test import (
     assert_eq, assert_eq_unordered, assert_in, assert_raises, test_output
 )
 
-from tasst.typecheck import typecheck
+from tasst.typecheck import typecheck, typecheck_default
 
 
 class SiteProcess(contextlib.AbstractContextManager):
@@ -31,18 +31,32 @@ class SiteProcess(contextlib.AbstractContextManager):
     """
 
     def __init__(self, site, cmd, subp, *,
-                 ignore_status, context_timeout):
+                 ignore_status, context_timeout, pidfile):
         self.site = typecheck(site, Site)
         self.cmd = typecheck(cmd, str)
         self.subproc = typecheck(subp, avocado.utils.process.SubProcess)
         self.ignore_status = typecheck(ignore_status, bool)
         self.context_timeout = float(context_timeout)
+        self.pidfile = typecheck_default(pidfile, str, None)
+        self.pid = None
+
+        if pidfile is not None:
+            site.require_cmds('cat', 'kill')
 
     def __enter__(self):
         self.subproc.start()
+        if self.pidfile is not None:
+            # Wait for the PID file to be written
+            pidstr = None
+            while not pidstr:
+                pidstr = self.site.output(f'cat {self.pidfile}',
+                                          ignore_status=True)
+            self.pid = int(pidstr)
         return self
 
     def __exit__(self, *exc_details):
+        if self.pid is not None and self.subproc.poll() is None:
+            self.site.fg(f'kill -TERM {self.pid}')
         result = self.subproc.run(timeout=self.context_timeout)
         if not self.ignore_status and result.exit_status != 0:
             siteinfo = f'[{self.site.name} site]'
@@ -84,11 +98,20 @@ class Site(contextlib.AbstractContextManager):
         cmd, kwargs = self.hostify(cmd, **kwargs)
         return avocado.utils.process.SubProcess(cmd, **kwargs)
 
-    def bg(self, cmd, context_timeout=1.0, ignore_status=False, **kwargs):
+    def bg(self, cmd, *,
+           context_timeout=1.0, ignore_status=False, pidfile=None, **kwargs):
         subproc = self.subprocess(cmd, **kwargs)
         return SiteProcess(self, cmd, subproc,
                            context_timeout=context_timeout,
-                           ignore_status=ignore_status)
+                           ignore_status=ignore_status, pidfile=pidfile)
+
+    @contextlib.contextmanager
+    def daemon(self, cmd, *, pidfile, **kwargs):
+        self.require_cmds('cat', 'kill')
+        self.fg(cmd, **kwargs)
+        yield
+        pid = int(self.output(f'cat {pidfile}'))
+        self.fg(f'kill -TERM {pid}')
 
     def require_cmds(self, *cmds):
         missing = [c for c in cmds
-- 
@@ -22,7 +22,7 @@ from avocado_classless.test import (
     assert_eq, assert_eq_unordered, assert_in, assert_raises, test_output
 )
 
-from tasst.typecheck import typecheck
+from tasst.typecheck import typecheck, typecheck_default
 
 
 class SiteProcess(contextlib.AbstractContextManager):
@@ -31,18 +31,32 @@ class SiteProcess(contextlib.AbstractContextManager):
     """
 
     def __init__(self, site, cmd, subp, *,
-                 ignore_status, context_timeout):
+                 ignore_status, context_timeout, pidfile):
         self.site = typecheck(site, Site)
         self.cmd = typecheck(cmd, str)
         self.subproc = typecheck(subp, avocado.utils.process.SubProcess)
         self.ignore_status = typecheck(ignore_status, bool)
         self.context_timeout = float(context_timeout)
+        self.pidfile = typecheck_default(pidfile, str, None)
+        self.pid = None
+
+        if pidfile is not None:
+            site.require_cmds('cat', 'kill')
 
     def __enter__(self):
         self.subproc.start()
+        if self.pidfile is not None:
+            # Wait for the PID file to be written
+            pidstr = None
+            while not pidstr:
+                pidstr = self.site.output(f'cat {self.pidfile}',
+                                          ignore_status=True)
+            self.pid = int(pidstr)
         return self
 
     def __exit__(self, *exc_details):
+        if self.pid is not None and self.subproc.poll() is None:
+            self.site.fg(f'kill -TERM {self.pid}')
         result = self.subproc.run(timeout=self.context_timeout)
         if not self.ignore_status and result.exit_status != 0:
             siteinfo = f'[{self.site.name} site]'
@@ -84,11 +98,20 @@ class Site(contextlib.AbstractContextManager):
         cmd, kwargs = self.hostify(cmd, **kwargs)
         return avocado.utils.process.SubProcess(cmd, **kwargs)
 
-    def bg(self, cmd, context_timeout=1.0, ignore_status=False, **kwargs):
+    def bg(self, cmd, *,
+           context_timeout=1.0, ignore_status=False, pidfile=None, **kwargs):
         subproc = self.subprocess(cmd, **kwargs)
         return SiteProcess(self, cmd, subproc,
                            context_timeout=context_timeout,
-                           ignore_status=ignore_status)
+                           ignore_status=ignore_status, pidfile=pidfile)
+
+    @contextlib.contextmanager
+    def daemon(self, cmd, *, pidfile, **kwargs):
+        self.require_cmds('cat', 'kill')
+        self.fg(cmd, **kwargs)
+        yield
+        pid = int(self.output(f'cat {pidfile}'))
+        self.fg(f'kill -TERM {pid}')
 
     def require_cmds(self, *cmds):
         missing = [c for c in cmds
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 24/27] tasst: Helpers for testing NDP behaviour
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (22 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 23/27] tasst: Helpers for running daemons with a pidfile David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 25/27] tasst: Helpers for testing DHCP & DHCPv6 behaviour David Gibson
                   ` (2 subsequent siblings)
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Signed-iff-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/ndp.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 99 insertions(+)
 create mode 100644 test/tasst/ndp.py

diff --git a/test/tasst/ndp.py b/test/tasst/ndp.py
new file mode 100644
index 00000000..e14d7a4c
--- /dev/null
+++ b/test/tasst/ndp.py
@@ -0,0 +1,99 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+ndp.py - Helpers for testing NDP
+"""
+
+import contextlib
+import ipaddress
+import os
+import tempfile
+
+from avocado_classless.test import assert_eq, test_output
+
+from tasst.address import IpiAllocator, TEST_NET6_TASST_A
+from tasst.nstool import unshare_site
+
+
+def test_ndp(ifname, network):
+    def dec(sitefn):
+        def test_ndp_addr(setup):
+            with setup as (site, _):
+                # Wait for NDP to do its thing
+                (addr,) = site.addr_wait(ifname, family='inet6',
+                                         scope='global')
+
+                # The SLAAC address is derived from the guest ns MAC,
+                # so probably won't exactly match the host address (we
+                # need DHCPv6 for that).  It should be in the right
+                # network though.
+                assert_eq(addr.network, network)
+
+        def test_ndp_route(setup):
+            with setup as (site, gateway):
+                defroutes = site.routes6(dst='default')
+                while not defroutes:
+                    defroutes = site.routes6(dst='default')
+
+                assert_eq(len(defroutes), 1)
+                gw = ipaddress.ip_address(defroutes[0]['gateway'])
+                assert_eq(gw, gateway)
+
+        return test_output(test_ndp_addr, test_ndp_route)(sitefn)
+    return dec
+
+
+IFNAME = 'clientif'
+NETWORK = TEST_NET6_TASST_A
+ipa = IpiAllocator(NETWORK)
+(ROUTER_IP6,) = ipa.next_ipis()
+
+
+@test_ndp(IFNAME, NETWORK)
+@contextlib.contextmanager
+def setup_radvd():
+    router_ifname = 'routerif'
+
+    with unshare_site('client', '-Un') as client, \
+         unshare_site('router', '-n', parent=client, sudo=True) as router:
+        with tempfile.TemporaryDirectory() as tmpdir:
+            router.require_cmds('radvd')
+
+            client.veth(IFNAME, router_ifname, router)
+
+            # Configure the simulated router
+            confpath = os.path.join(tmpdir, 'radvd.conf')
+            pidfile = os.path.join(tmpdir, 'radvd.pid')
+            open(confpath, 'w', encoding='UTF-8').write(
+                f'''
+                interface {router_ifname} {{
+                AdvSendAdvert on;
+                prefix {NETWORK} {{
+                }};
+                }};
+                '''
+            )
+
+            router.ifup('lo')
+            router.ifup('routerif', ROUTER_IP6)
+
+            # Configure the client
+            client.ifup('lo')
+            client.ifup(IFNAME)
+
+            # Get the router's link-local-address
+            (router_ll,) = router.addr_wait(router_ifname,
+                                            family='inet6', scope='link')
+
+            # Run radvd
+            router.fg(f'radvd -c -C {confpath}')
+            radvd_cmd = f'radvd -C {confpath} -p {pidfile} -n -d 5'
+            with router.bg(radvd_cmd, sudo=True, pidfile=pidfile):
+                yield client, router_ll.ip
-- 
@@ -0,0 +1,99 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+ndp.py - Helpers for testing NDP
+"""
+
+import contextlib
+import ipaddress
+import os
+import tempfile
+
+from avocado_classless.test import assert_eq, test_output
+
+from tasst.address import IpiAllocator, TEST_NET6_TASST_A
+from tasst.nstool import unshare_site
+
+
+def test_ndp(ifname, network):
+    def dec(sitefn):
+        def test_ndp_addr(setup):
+            with setup as (site, _):
+                # Wait for NDP to do its thing
+                (addr,) = site.addr_wait(ifname, family='inet6',
+                                         scope='global')
+
+                # The SLAAC address is derived from the guest ns MAC,
+                # so probably won't exactly match the host address (we
+                # need DHCPv6 for that).  It should be in the right
+                # network though.
+                assert_eq(addr.network, network)
+
+        def test_ndp_route(setup):
+            with setup as (site, gateway):
+                defroutes = site.routes6(dst='default')
+                while not defroutes:
+                    defroutes = site.routes6(dst='default')
+
+                assert_eq(len(defroutes), 1)
+                gw = ipaddress.ip_address(defroutes[0]['gateway'])
+                assert_eq(gw, gateway)
+
+        return test_output(test_ndp_addr, test_ndp_route)(sitefn)
+    return dec
+
+
+IFNAME = 'clientif'
+NETWORK = TEST_NET6_TASST_A
+ipa = IpiAllocator(NETWORK)
+(ROUTER_IP6,) = ipa.next_ipis()
+
+
+@test_ndp(IFNAME, NETWORK)
+@contextlib.contextmanager
+def setup_radvd():
+    router_ifname = 'routerif'
+
+    with unshare_site('client', '-Un') as client, \
+         unshare_site('router', '-n', parent=client, sudo=True) as router:
+        with tempfile.TemporaryDirectory() as tmpdir:
+            router.require_cmds('radvd')
+
+            client.veth(IFNAME, router_ifname, router)
+
+            # Configure the simulated router
+            confpath = os.path.join(tmpdir, 'radvd.conf')
+            pidfile = os.path.join(tmpdir, 'radvd.pid')
+            open(confpath, 'w', encoding='UTF-8').write(
+                f'''
+                interface {router_ifname} {{
+                AdvSendAdvert on;
+                prefix {NETWORK} {{
+                }};
+                }};
+                '''
+            )
+
+            router.ifup('lo')
+            router.ifup('routerif', ROUTER_IP6)
+
+            # Configure the client
+            client.ifup('lo')
+            client.ifup(IFNAME)
+
+            # Get the router's link-local-address
+            (router_ll,) = router.addr_wait(router_ifname,
+                                            family='inet6', scope='link')
+
+            # Run radvd
+            router.fg(f'radvd -c -C {confpath}')
+            radvd_cmd = f'radvd -C {confpath} -p {pidfile} -n -d 5'
+            with router.bg(radvd_cmd, sudo=True, pidfile=pidfile):
+                yield client, router_ll.ip
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 25/27] tasst: Helpers for testing DHCP & DHCPv6 behaviour
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (23 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 24/27] tasst: Helpers for testing NDP behaviour David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 26/27] tasst: Helpers to construct a simple network environment for tests David Gibson
  2023-06-27  2:54 ` [PATCH 27/27] avocado: Convert basic pasta tests David Gibson
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/dhcp.py   | 114 +++++++++++++++++++++++++++++++++++++++++++
 test/tasst/dhcpv6.py |  74 ++++++++++++++++++++++++++++
 2 files changed, 188 insertions(+)
 create mode 100644 test/tasst/dhcp.py
 create mode 100644 test/tasst/dhcpv6.py

diff --git a/test/tasst/dhcp.py b/test/tasst/dhcp.py
new file mode 100644
index 00000000..b8c18b8f
--- /dev/null
+++ b/test/tasst/dhcp.py
@@ -0,0 +1,114 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+dhcp.py - Helpers for testing DHCP
+"""
+
+import contextlib
+import ipaddress
+import os
+import tempfile
+
+from avocado_classless.test import assert_eq, test_output
+
+from tasst.address import IpiAllocator, TEST_NET_1
+from tasst.nstool import unshare_site
+
+
+DHCLIENT = '/sbin/dhclient'
+
+
+@contextlib.contextmanager
+def dhclient(site, ifname, ipv='4'):
+    site.require_cmds(DHCLIENT)
+
+    with tempfile.TemporaryDirectory() as tmpdir:
+        pidfile = os.path.join(tmpdir, 'dhclient.pid')
+        leasefile = os.path.join(tmpdir, 'dhclient.leases')
+
+        # We need '-nc' because we may be running with
+        # capabilities but not UID 0.  Without -nc dhclient drops
+        # capabilities before invoking dhclient-script, so it's
+        # unable to actually configure the interface
+        opts = f'-{ipv} -v -nc -pf {pidfile} -lf {leasefile} {ifname}'
+        with site.daemon(f'{DHCLIENT} {opts}', sudo=True, pidfile=pidfile):
+            yield
+
+
+def test_dhcp(ifname, expected_addr, gateway, mtu):
+    def dec(setupfn):
+        def test_addr(setup):
+            with setup as site, dhclient(site, ifname):
+                (actual_addr,) = site.addrs(ifname, family='inet',
+                                            scope='global')
+                assert_eq(actual_addr.ip, expected_addr)
+
+        def test_route(setup):
+            with setup as site, dhclient(site, ifname):
+                (defroute,) = site.routes4(dst='default')
+                assert_eq(ipaddress.ip_address(defroute['gateway']), gateway)
+
+        def test_mtu(setup):
+            with setup as site, dhclient(site, ifname):
+                assert_eq(site.mtu(ifname), mtu)
+
+        return test_output(test_addr, test_route, test_mtu)(setupfn)
+
+    return dec
+
+
+DHCPD = 'dhcpd'
+SUBNET = TEST_NET_1
+ipa = IpiAllocator(SUBNET)
+(SERVER_IP4,) = ipa.next_ipis()
+(CLIENT_IP4,) = ipa.next_ipis()
+IFNAME = 'clientif'
+
+
+@contextlib.contextmanager
+def setup_dhcpd_common(ifname, server_ifname):
+    with unshare_site('client', '-Un') as client, \
+         unshare_site('server', '-n', parent=client, sudo=True) as server:
+        server.require_cmds(DHCPD)
+
+        client.veth(ifname, server_ifname, server)
+
+        with tempfile.TemporaryDirectory() as tmpdir:
+            yield (client, server, tmpdir)
+
+
+@test_dhcp(IFNAME, CLIENT_IP4.ip, SERVER_IP4.ip, 1500)
+@contextlib.contextmanager
+def setup_dhcpd():
+    server_ifname = 'serverif'
+
+    with setup_dhcpd_common(IFNAME, server_ifname) as (client, server, tmpdir):
+        # Configure dhcpd
+        confpath = os.path.join(tmpdir, 'dhcpd.conf')
+        open(confpath, 'w', encoding='UTF-8').write(
+            f'''subnet {SUBNET.network_address} netmask {SUBNET.netmask} {{
+            option routers {SERVER_IP4.ip};
+            range {CLIENT_IP4.ip} {CLIENT_IP4.ip};
+            }}'''
+        )
+        pidfile = os.path.join(tmpdir, 'dhcpd.pid')
+        leasepath = os.path.join(tmpdir, 'dhcpd.leases')
+        open(leasepath, 'wb').write(b'')
+
+        server.ifup('lo')
+        server.ifup(server_ifname, SERVER_IP4)
+
+        opts = f'-f -d -4 -cf {confpath} -lf {leasepath} -pf {pidfile}'
+        server.fg(f'{DHCPD} -t {opts}')  # test config
+        with server.bg(f'{DHCPD} {opts}', sudo=True, ignore_status=True,
+                       pidfile=pidfile):
+            # Configure the client
+            client.ifup('lo')
+            yield client
diff --git a/test/tasst/dhcpv6.py b/test/tasst/dhcpv6.py
new file mode 100644
index 00000000..816c0ea9
--- /dev/null
+++ b/test/tasst/dhcpv6.py
@@ -0,0 +1,74 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+dhcpv6.py - Helpers for testing DHCPv6
+"""
+
+import contextlib
+import os
+
+from avocado_classless.test import assert_in, test_output
+
+from tasst.address import IpiAllocator, TEST_NET6_TASST_A
+from tasst.dhcp import dhclient, setup_dhcpd_common
+
+
+def dhclientv6(site, ifname):
+    return dhclient(site, ifname, '6')
+
+
+def test_dhcpv6(ifname, expected_addr):
+    def dec(setupfn):
+        def test_addr(setup):
+            with setup as site, dhclientv6(site, ifname):
+                addrs = [a.ip for a in site.addrs(ifname, family='inet6',
+                                                  scope='global')]
+            assert_in(expected_addr, addrs)  # Might also have a SLAAC address
+
+        return test_output(test_addr)(setupfn)
+    return dec
+
+
+DHCPD = 'dhcpd'
+SUBNET = TEST_NET6_TASST_A
+ipa = IpiAllocator(SUBNET)
+(SERVER_IP6,) = ipa.next_ipis()
+(CLIENT_IP6,) = ipa.next_ipis()
+IFNAME = 'clientif'
+
+
+@test_dhcpv6(IFNAME, CLIENT_IP6.ip)
+@contextlib.contextmanager
+def setup_dhcpdv6():
+    server_ifname = 'serverif'
+
+    with setup_dhcpd_common(IFNAME, server_ifname) as (client, server, tmpdir):
+        # Sort out link local addressing
+        server.ifup('lo')
+        server.ifup(server_ifname, SERVER_IP6)
+        client.ifup('lo')
+        client.ifup(IFNAME)
+        server.addr_wait(server_ifname, family='inet6', scope='link')
+
+        # Configure the DHCP server
+        confpath = os.path.join(tmpdir, 'dhcpd.conf')
+        open(confpath, 'w', encoding='UTF-8').write(
+            f'''subnet6 {SUBNET} {{
+            range6 {CLIENT_IP6.ip} {CLIENT_IP6.ip};
+            }}''')
+        pidfile = os.path.join(tmpdir, 'dhcpd.pid')
+        leasepath = os.path.join(tmpdir, 'dhcpd.leases')
+        open(leasepath, 'wb').write(b'')
+
+        opts = f'-f -d -6 -cf {confpath} -lf {leasepath} -pf {pidfile}'
+        server.fg(f'{DHCPD} -t {opts}')  # test config
+        with server.bg(f'{DHCPD} {opts}', sudo=True, ignore_status=True,
+                       pidfile=pidfile):
+            yield client
-- 
@@ -0,0 +1,74 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+dhcpv6.py - Helpers for testing DHCPv6
+"""
+
+import contextlib
+import os
+
+from avocado_classless.test import assert_in, test_output
+
+from tasst.address import IpiAllocator, TEST_NET6_TASST_A
+from tasst.dhcp import dhclient, setup_dhcpd_common
+
+
+def dhclientv6(site, ifname):
+    return dhclient(site, ifname, '6')
+
+
+def test_dhcpv6(ifname, expected_addr):
+    def dec(setupfn):
+        def test_addr(setup):
+            with setup as site, dhclientv6(site, ifname):
+                addrs = [a.ip for a in site.addrs(ifname, family='inet6',
+                                                  scope='global')]
+            assert_in(expected_addr, addrs)  # Might also have a SLAAC address
+
+        return test_output(test_addr)(setupfn)
+    return dec
+
+
+DHCPD = 'dhcpd'
+SUBNET = TEST_NET6_TASST_A
+ipa = IpiAllocator(SUBNET)
+(SERVER_IP6,) = ipa.next_ipis()
+(CLIENT_IP6,) = ipa.next_ipis()
+IFNAME = 'clientif'
+
+
+@test_dhcpv6(IFNAME, CLIENT_IP6.ip)
+@contextlib.contextmanager
+def setup_dhcpdv6():
+    server_ifname = 'serverif'
+
+    with setup_dhcpd_common(IFNAME, server_ifname) as (client, server, tmpdir):
+        # Sort out link local addressing
+        server.ifup('lo')
+        server.ifup(server_ifname, SERVER_IP6)
+        client.ifup('lo')
+        client.ifup(IFNAME)
+        server.addr_wait(server_ifname, family='inet6', scope='link')
+
+        # Configure the DHCP server
+        confpath = os.path.join(tmpdir, 'dhcpd.conf')
+        open(confpath, 'w', encoding='UTF-8').write(
+            f'''subnet6 {SUBNET} {{
+            range6 {CLIENT_IP6.ip} {CLIENT_IP6.ip};
+            }}''')
+        pidfile = os.path.join(tmpdir, 'dhcpd.pid')
+        leasepath = os.path.join(tmpdir, 'dhcpd.leases')
+        open(leasepath, 'wb').write(b'')
+
+        opts = f'-f -d -6 -cf {confpath} -lf {leasepath} -pf {pidfile}'
+        server.fg(f'{DHCPD} -t {opts}')  # test config
+        with server.bg(f'{DHCPD} {opts}', sudo=True, ignore_status=True,
+                       pidfile=pidfile):
+            yield client
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 26/27] tasst: Helpers to construct a simple network environment for tests
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (24 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 25/27] tasst: Helpers for testing DHCP & DHCPv6 behaviour David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-06-27  2:54 ` [PATCH 27/27] avocado: Convert basic pasta tests David Gibson
  26 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

This constructs essentially the simplest sensible network for passt/pasta
to operate in.  We have one netns "simhost" to represent the host where we
will run passt or pasta, and a second "gw" to represent its default
gateway.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 test/tasst/scenario/__init__.py | 12 ++++
 test/tasst/scenario/simple.py   | 98 +++++++++++++++++++++++++++++++++
 2 files changed, 110 insertions(+)
 create mode 100644 test/tasst/scenario/__init__.py
 create mode 100644 test/tasst/scenario/simple.py

diff --git a/test/tasst/scenario/__init__.py b/test/tasst/scenario/__init__.py
new file mode 100644
index 00000000..4ea4584d
--- /dev/null
+++ b/test/tasst/scenario/__init__.py
@@ -0,0 +1,12 @@
+#! /usr/bin/python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+scenario/ - Helpers to set up various sample network topologies
+"""
diff --git a/test/tasst/scenario/simple.py b/test/tasst/scenario/simple.py
new file mode 100644
index 00000000..503a388a
--- /dev/null
+++ b/test/tasst/scenario/simple.py
@@ -0,0 +1,98 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+scenario/simple.py - Smallest sensible network to use passt/pasta
+"""
+
+import contextlib
+
+from tasst.nstool import unshare_site
+from tasst.address import IpiAllocator, TEST_NET_2, TEST_NET6_TASST_B
+from tasst.exesite import Site
+from tasst.transfer import test_transfers
+from tasst.typecheck import typecheck
+
+
+class __SimpleNet:  # pylint: disable=R0903
+    """A simple network setup scenario
+
+    The sample network has 2 sites (network namespaces) connected with
+    a veth link:
+            [simhost]   <-veth->    [gw]
+
+    gw is set up as the default router for simhost.
+
+    simhost has addresses:
+        self.IP4 (IPv4), self.IP6 (IPv6), self.ip6_ll (IPv6 link local)
+
+    gw has addresses:
+        self.GW_IP4 (IPv4), self.GW_IP6 (IPv6),
+            self.gw_ip6_ll (IPv6 link local)
+        self.REMOTE_IP4 (IPv4), self.REMOTE_IP6 (IPv6)
+
+    The "remote" addresses are on a different subnet from the others,
+    so the only way for simhost to reach them is via its default
+    route.  This helps to exercise that we're actually using that,
+    rather than just local net routes.
+
+    """
+
+    IFNAME = 'veth'
+    ipa_local = IpiAllocator()
+    (IP4, IP6) = ipa_local.next_ipis()
+    (GW_IP4, GW_IP6) = ipa_local.next_ipis()
+
+    ipa_remote = IpiAllocator(TEST_NET_2, TEST_NET6_TASST_B)
+    (REMOTE_IP4, REMOTE_IP6) = ipa_remote.next_ipis()
+
+    def __init__(self, hostsite, gwsite):
+        self.simhost = typecheck(hostsite, Site)
+        self.gw = typecheck(gwsite, Site)
+
+        ifname = self.IFNAME
+        self.gw_ifname = 'gw' + ifname
+        self.simhost.veth(self.IFNAME, self.gw_ifname, self.gw)
+
+        self.gw.ifup('lo')
+        self.gw.ifup(self.gw_ifname, self.GW_IP4, self.GW_IP6,
+                     self.REMOTE_IP4, self.REMOTE_IP6)
+
+        self.simhost.ifup('lo')
+        self.simhost.ifup(ifname, self.IP4, self.IP6)
+
+        # Once link is up on both sides, SLAAC will run
+        self.gw_ip6_ll = self.gw.addr_wait(self.gw_ifname,
+                                           family='inet6', scope='link')[0]
+        self.ip6_ll = self.simhost.addr_wait(ifname,
+                                             family='inet6', scope='link')[0]
+
+        # Set up the default route
+        self.simhost.fg(f'ip -4 route add default via {self.GW_IP4.ip}',
+                        sudo=True)
+        self.simhost.fg(
+            f'ip -6 route add default via {self.gw_ip6_ll.ip} dev {ifname}',
+            sudo=True
+        )
+
+
+@contextlib.contextmanager
+def simple_net():
+    with unshare_site('simhost', '-Ucnpf --mount-proc') as simhost, \
+         unshare_site('gw', '-n', parent=simhost, sudo=True) as gw:
+        yield __SimpleNet(simhost, gw)
+
+
+@test_transfers(ip4=__SimpleNet.REMOTE_IP4.ip,
+                ip6=__SimpleNet.REMOTE_IP6.ip,
+                port=10000)
+@contextlib.contextmanager
+def simple_net_transfers():
+    with simple_net() as net:
+        yield (net.simhost, net.gw)
-- 
@@ -0,0 +1,98 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+scenario/simple.py - Smallest sensible network to use passt/pasta
+"""
+
+import contextlib
+
+from tasst.nstool import unshare_site
+from tasst.address import IpiAllocator, TEST_NET_2, TEST_NET6_TASST_B
+from tasst.exesite import Site
+from tasst.transfer import test_transfers
+from tasst.typecheck import typecheck
+
+
+class __SimpleNet:  # pylint: disable=R0903
+    """A simple network setup scenario
+
+    The sample network has 2 sites (network namespaces) connected with
+    a veth link:
+            [simhost]   <-veth->    [gw]
+
+    gw is set up as the default router for simhost.
+
+    simhost has addresses:
+        self.IP4 (IPv4), self.IP6 (IPv6), self.ip6_ll (IPv6 link local)
+
+    gw has addresses:
+        self.GW_IP4 (IPv4), self.GW_IP6 (IPv6),
+            self.gw_ip6_ll (IPv6 link local)
+        self.REMOTE_IP4 (IPv4), self.REMOTE_IP6 (IPv6)
+
+    The "remote" addresses are on a different subnet from the others,
+    so the only way for simhost to reach them is via its default
+    route.  This helps to exercise that we're actually using that,
+    rather than just local net routes.
+
+    """
+
+    IFNAME = 'veth'
+    ipa_local = IpiAllocator()
+    (IP4, IP6) = ipa_local.next_ipis()
+    (GW_IP4, GW_IP6) = ipa_local.next_ipis()
+
+    ipa_remote = IpiAllocator(TEST_NET_2, TEST_NET6_TASST_B)
+    (REMOTE_IP4, REMOTE_IP6) = ipa_remote.next_ipis()
+
+    def __init__(self, hostsite, gwsite):
+        self.simhost = typecheck(hostsite, Site)
+        self.gw = typecheck(gwsite, Site)
+
+        ifname = self.IFNAME
+        self.gw_ifname = 'gw' + ifname
+        self.simhost.veth(self.IFNAME, self.gw_ifname, self.gw)
+
+        self.gw.ifup('lo')
+        self.gw.ifup(self.gw_ifname, self.GW_IP4, self.GW_IP6,
+                     self.REMOTE_IP4, self.REMOTE_IP6)
+
+        self.simhost.ifup('lo')
+        self.simhost.ifup(ifname, self.IP4, self.IP6)
+
+        # Once link is up on both sides, SLAAC will run
+        self.gw_ip6_ll = self.gw.addr_wait(self.gw_ifname,
+                                           family='inet6', scope='link')[0]
+        self.ip6_ll = self.simhost.addr_wait(ifname,
+                                             family='inet6', scope='link')[0]
+
+        # Set up the default route
+        self.simhost.fg(f'ip -4 route add default via {self.GW_IP4.ip}',
+                        sudo=True)
+        self.simhost.fg(
+            f'ip -6 route add default via {self.gw_ip6_ll.ip} dev {ifname}',
+            sudo=True
+        )
+
+
+@contextlib.contextmanager
+def simple_net():
+    with unshare_site('simhost', '-Ucnpf --mount-proc') as simhost, \
+         unshare_site('gw', '-n', parent=simhost, sudo=True) as gw:
+        yield __SimpleNet(simhost, gw)
+
+
+@test_transfers(ip4=__SimpleNet.REMOTE_IP4.ip,
+                ip6=__SimpleNet.REMOTE_IP6.ip,
+                port=10000)
+@contextlib.contextmanager
+def simple_net_transfers():
+    with simple_net() as net:
+        yield (net.simhost, net.gw)
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* [PATCH 27/27] avocado: Convert basic pasta tests
  2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
                   ` (25 preceding siblings ...)
  2023-06-27  2:54 ` [PATCH 26/27] tasst: Helpers to construct a simple network environment for tests David Gibson
@ 2023-06-27  2:54 ` David Gibson
  2023-07-05  0:30   ` Stefano Brivio
  26 siblings, 1 reply; 32+ messages in thread
From: David Gibson @ 2023-06-27  2:54 UTC (permalink / raw)
  To: passt-dev, Stefano Brivio; +Cc: crosa, jarichte, David Gibson

Convert the old-style tests for pasta (DHCP, NDP, TCP and UDP transfers)
to using avocado.  There are a few differences in what we test, but this
should generally improve coverage:

 * We run in a constructed network environment, so we no longer depend on
   the real host's networking configuration
 * We do independent setup for each individual test
 * We add explicit tests for --config-net, which we use to accelerate that
   setup for the TCP and UDP tests
 * The TCP and UDP tests now test transfers between the guest and a
   (simulated) remote site that's on a different network from the simulated
   pasta host.  This better matches the typical passt/pasta usecase

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 oldtest/run                   |  14 ++--
 test/Makefile                 |   1 +
 test/avocado/pasta.py         | 129 ++++++++++++++++++++++++++++++++++
 test/lib/layout               |  31 --------
 test/lib/setup                |  40 -----------
 test/pasta/dhcp               |  46 ------------
 test/pasta/ndp                |  33 ---------
 test/pasta/tcp                |  96 -------------------------
 test/pasta/udp                |  59 ----------------
 test/run                      |   8 ---
 test/tasst/dhcpv6.py          |   4 +-
 test/tasst/pasta.py           |  42 +++++++++++
 test/tasst/scenario/simple.py |  44 ++++++------
 13 files changed, 203 insertions(+), 344 deletions(-)
 create mode 100644 test/avocado/pasta.py
 delete mode 100644 test/pasta/dhcp
 delete mode 100644 test/pasta/ndp
 delete mode 100644 test/pasta/tcp
 delete mode 100644 test/pasta/udp
 create mode 100644 test/tasst/pasta.py

diff --git a/oldtest/run b/oldtest/run
index a16bc49b..f1157f90 100755
--- a/oldtest/run
+++ b/oldtest/run
@@ -70,13 +70,13 @@ run() {
 	test build/clang_tidy
 	teardown build
 
-#	setup pasta
-#	test pasta/ndp
-#	test pasta/dhcp
-#	test pasta/tcp
-#	test pasta/udp
-#	test passt/shutdown
-#	teardown pasta
+	setup pasta
+	test pasta/ndp
+	test pasta/dhcp
+	test pasta/tcp
+	test pasta/udp
+	test passt/shutdown
+	teardown pasta
 
 #	setup pasta_options
 #	test pasta_options/log_to_file
diff --git a/test/Makefile b/test/Makefile
index 953eacf2..9c3114c2 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -227,6 +227,7 @@ $(VENV):
 
 .PHONY: avocado-assets
 avocado-assets: nstool small.bin medium.bin big.bin
+	$(MAKE) -C .. pasta
 
 .PHONY: avocado
 avocado: avocado-assets $(VENV)
diff --git a/test/avocado/pasta.py b/test/avocado/pasta.py
new file mode 100644
index 00000000..891313f5
--- /dev/null
+++ b/test/avocado/pasta.py
@@ -0,0 +1,129 @@
+#! /usr/bin/env avocado-runner-avocado-classless
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+avocado/pasta.py - Basic tests for pasta mode
+"""
+
+import contextlib
+import ipaddress
+import os
+import tempfile
+
+from avocado_classless.test import assert_eq, assert_eq_unordered, test
+
+from tasst.address import LOOPBACK4, LOOPBACK6
+from tasst.dhcp import test_dhcp
+from tasst.dhcpv6 import test_dhcpv6
+from tasst.ndp import test_ndp
+from tasst.nstool import unshare_site
+from tasst.pasta import Pasta
+from tasst.scenario.simple import (
+    simple_net, IFNAME, HOST_NET6, IP4, IP6, GW_IP4, REMOTE_IP4, REMOTE_IP6
+)
+from tasst.transfer import test_transfers
+
+
+IN_FWD_PORT = 10002
+SPLICE_FWD_PORT = 10003
+FWD_OPTS = f'-t {IN_FWD_PORT} -u {IN_FWD_PORT} \
+             -T {SPLICE_FWD_PORT} -U {SPLICE_FWD_PORT}'
+
+
+@contextlib.contextmanager
+def pasta_unconfigured(opts=FWD_OPTS):
+    with simple_net() as simnet:
+        with unshare_site('pastans', '-Ucnpf --mount-proc',
+                          parent=simnet.simhost, sudo=True) as guestns:
+            with tempfile.TemporaryDirectory() as tmpdir:
+                pidfile = os.path.join(tmpdir, 'pasta.pid')
+
+                with Pasta(simnet.simhost, pidfile, opts, ns=guestns) as pasta:
+                    yield simnet, pasta.ns
+
+
+@test
+def test_ifname():
+    with pasta_unconfigured() as (_, ns):
+        assert_eq_unordered(ns.ifs(), ('lo', IFNAME))
+
+
+@test_ndp(IFNAME, HOST_NET6)
+@contextlib.contextmanager
+def pasta_ndp():
+    with pasta_unconfigured() as (simnet, guestns):
+        guestns.ifup(IFNAME)
+        yield guestns, simnet.gw_ip6_ll.ip
+
+
+@test_dhcp(IFNAME, IP4.ip, GW_IP4.ip, 65520)
+@test_dhcpv6(IFNAME, IP6.ip)
+@contextlib.contextmanager
+def pasta_dhcp():
+    with pasta_unconfigured() as (_, guestns):
+        yield guestns
+
+
+@contextlib.contextmanager
+def pasta_configured():
+    with pasta_unconfigured(FWD_OPTS + ' --config-net') as (simnet, ns):
+        # Wait for DAD to complete on the --config-net address
+        ns.addr_wait(IFNAME, family='inet6', scope='global')
+        yield simnet, ns
+
+
+@test
+def test_config_net_addr():
+    with pasta_configured() as (_, ns):
+        addrs = ns.addrs(IFNAME, scope='global')
+        assert_eq_unordered(addrs, [IP4, IP6])
+
+
+@test
+def test_config_net_route4():
+    with pasta_configured() as (_, ns):
+        (defroute,) = ns.routes4(dst='default')
+        gateway = ipaddress.ip_address(defroute['gateway'])
+        assert_eq(gateway, GW_IP4.ip)
+
+
+@test
+def test_config_net_route6():
+    with pasta_configured() as (simnet, ns):
+        (defroute,) = ns.routes6(dst='default')
+        gateway = ipaddress.ip_address(defroute['gateway'])
+        assert_eq(gateway, simnet.gw_ip6_ll.ip)
+
+
+@test
+def test_config_net_mtu():
+    with pasta_configured() as (_, ns):
+        mtu = ns.mtu(IFNAME)
+        assert_eq(mtu, 65520)
+
+
+@test_transfers(ip4=REMOTE_IP4.ip, ip6=REMOTE_IP6.ip, port=10000)
+@contextlib.contextmanager
+def outward_transfer():
+    with pasta_configured() as (simnet, ns):
+        yield ns, simnet.gw
+
+
+@test_transfers(ip4=IP4.ip, ip6=IP6.ip, port=IN_FWD_PORT,
+                fromip4=REMOTE_IP4.ip, fromip6=REMOTE_IP6.ip)
+@contextlib.contextmanager
+def inward_transfer():
+    with pasta_configured() as (simnet, ns):
+        yield simnet.gw, ns
+
+
+@test_transfers(ip4=LOOPBACK4, ip6=LOOPBACK6, port=SPLICE_FWD_PORT,
+                listenip4=LOOPBACK4, listenip6=LOOPBACK6)
+@contextlib.contextmanager
+def spliced_transfer():
+    with pasta_configured() as (simnet, ns):
+        yield ns, simnet.simhost
diff --git a/test/lib/layout b/test/lib/layout
index f9a1cf1b..e2b1db06 100644
--- a/test/lib/layout
+++ b/test/lib/layout
@@ -13,37 +13,6 @@
 # Copyright (c) 2021 Red Hat GmbH
 # Author: Stefano Brivio <sbrivio@redhat.com>
 
-# layout_pasta() - Panes for host, pasta, and separate one for namespace
-layout_pasta() {
-	sleep 3
-
-	tmux kill-pane -a -t 0
-	cmd_write 0 clear
-
-	tmux split-window -v -t passt_test
-	tmux split-window -h -t passt_test
-	tmux split-window -h -l '42%' -t passt_test:1.0
-
-	PANE_NS=0
-	PANE_INFO=1
-	PANE_HOST=2
-	PANE_PASST=3
-
-	get_info_cols
-
-	tmux send-keys -l -t ${PANE_INFO} 'while cat '"$STATEBASE/log_pipe"'; do :; done'
-	tmux send-keys -t ${PANE_INFO} -N 100 C-m
-	tmux select-pane -t ${PANE_INFO} -T "test log"
-
-	pane_watch_contexts ${PANE_HOST} host host
-	pane_watch_contexts ${PANE_PASST} pasta passt
-	pane_watch_contexts ${PANE_NS} "namespace" unshare ns
-
-	info_layout "single pasta instance with namespace"
-
-	sleep 1
-}
-
 # layout_passt() - Panes for host, passt, and guest
 layout_passt() {
 	sleep 3
diff --git a/test/lib/setup b/test/lib/setup
index 5386805f..7abf2064 100755
--- a/test/lib/setup
+++ b/test/lib/setup
@@ -61,36 +61,6 @@ setup_passt() {
 	context_setup_guest guest $GUEST_CID
 }
 
-# setup_pasta() - Create a network and user namespace, connect pasta to it
-setup_pasta() {
-	context_setup_host host
-	context_setup_host passt
-	context_setup_host unshare
-
-	layout_pasta
-
-	context_run_bg unshare "unshare -rUnpf ${NSTOOL} hold ${STATESETUP}/ns.hold"
-
-	context_setup_nstool ns ${STATESETUP}/ns.hold
-
-	# Ports:
-	#
-	#                 ns        |         host
-	#         ------------------|---------------------
-	#  10002      as server     |    spliced to ns
-	#  10003   spliced to init  |      as server
-
-	__opts=
-	[ ${PCAP} -eq 1 ] && __opts="${__opts} -p ${LOGDIR}/pasta.pcap"
-	[ ${DEBUG} -eq 1 ] && __opts="${__opts} -d"
-	[ ${TRACE} -eq 1 ] && __opts="${__opts} --trace"
-
-	context_run_bg passt "./pasta ${__opts} -f -t 10002 -T 10003 -u 10002 -U 10003 -P ${STATESETUP}/passt.pid $(${NSTOOL} info -pw ${STATESETUP}/ns.hold)"
-
-	# pidfile isn't created until pasta is ready
-	wait_for [ -f "${STATESETUP}/passt.pid" ]
-}
-
 # setup_passt_in_ns() - Set up namespace (with pasta), run qemu and passt into it
 setup_passt_in_ns() {
 	context_setup_host host
@@ -270,16 +240,6 @@ teardown_passt() {
 	teardown_context_watch ${PANE_GUEST} qemu guest
 }
 
-# teardown_pasta() - Exit namespace, kill pasta process
-teardown_pasta() {
-	${NSTOOL} stop "${STATESETUP}/ns.hold"
-	context_wait unshare
-
-	teardown_context_watch ${PANE_HOST} host
-	teardown_context_watch ${PANE_PASST} passt
-	teardown_context_watch ${PANE_NS} unshare ns
-}
-
 # teardown_passt_in_ns() - Exit namespace, kill qemu and pasta, remove pid file
 teardown_passt_in_ns() {
 	context_run ns kill $(cat "${STATESETUP}/qemu.pid")
diff --git a/test/pasta/dhcp b/test/pasta/dhcp
deleted file mode 100644
index 309001b7..00000000
--- a/test/pasta/dhcp
+++ /dev/null
@@ -1,46 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-#
-# PASST - Plug A Simple Socket Transport
-#  for qemu/UNIX domain socket mode
-#
-# PASTA - Pack A Subtle Tap Abstraction
-#  for network namespace/tap device mode
-#
-# test/pasta/dhcp - Check DHCP and DHCPv6 functionality in pasta mode
-#
-# Copyright (c) 2021 Red Hat GmbH
-# Author: Stefano Brivio <sbrivio@redhat.com>
-
-nstools	ip jq /sbin/dhclient
-htools	ip jq
-
-test	Interface name
-nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
-check	[ -n "__IFNAME__" ]
-
-test	DHCP: address
-ns	/sbin/dhclient -4 --no-pid __IFNAME__
-nsout	ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
-hout	HOST_ADDR ip -j -4 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[0].local'
-check	[ __ADDR__ = __HOST_ADDR__ ]
-
-test	DHCP: route
-nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
-hout	HOST_GW ip -j -4 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
-check	[ __GW__ = __HOST_GW__ ]
-
-test	DHCP: MTU
-nsout	MTU ip -j link show | jq -rM '.[] | select(.ifname == "__IFNAME__").mtu'
-check	[ __MTU__ = 65520 ]
-
-test	DHCPv6: address
-ns	/sbin/dhclient -6 --no-pid __IFNAME__
-hout	HOST_IFNAME6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").dev] | .[0]'
-nsout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.prefixlen == 128).local'
-hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__HOST_IFNAME6__").addr_info[] | select(.scope == "global").local'
-check	[ __ADDR6__ = __HOST_ADDR6__ ]
-
-test	DHCPv6: route
-nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
-hout	HOST_GW6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
-check	[ __GW6__ = __HOST_GW6__ ]
diff --git a/test/pasta/ndp b/test/pasta/ndp
deleted file mode 100644
index bb331102..00000000
--- a/test/pasta/ndp
+++ /dev/null
@@ -1,33 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-#
-# PASST - Plug A Simple Socket Transport
-#  for qemu/UNIX domain socket mode
-#
-# PASTA - Pack A Subtle Tap Abstraction
-#  for network namespace/tap device mode
-#
-# test/pasta/ndp - Check DHCP and DHCPv6 functionality in pasta mode
-#
-# Copyright (c) 2021 Red Hat GmbH
-# Author: Stefano Brivio <sbrivio@redhat.com>
-
-nstools	ip jq sipcalc grep cut
-htools	ip jq sipcalc grep cut
-
-test	Interface name
-nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
-check	[ -n "__IFNAME__" ]
-ns	ip link set dev __IFNAME__ up
-sleep	2
-
-test	SLAAC: prefix
-nsout	ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global" and .prefixlen == 64).local'
-nsout	PREFIX6 sipcalc __ADDR6__/64 | grep prefix | cut -d' ' -f4
-hout	HOST_ADDR6 ip -j -6 addr show|jq -rM '.[] | select(.ifname == "__IFNAME__").addr_info[] | select(.scope == "global").local'
-hout	HOST_PREFIX6 sipcalc __HOST_ADDR6__/64 | grep prefix | cut -d' ' -f4
-check	[ "__PREFIX6__" = "__HOST_PREFIX6__" ]
-
-test	SLAAC: route
-nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
-hout	HOST_GW6 ip -j -6 route show|jq -rM '[.[] | select(.dst == "default").gateway] | .[0]'
-check	[ __GW6__ = __HOST_GW6__ ]
diff --git a/test/pasta/tcp b/test/pasta/tcp
deleted file mode 100644
index 6ab18c5c..00000000
--- a/test/pasta/tcp
+++ /dev/null
@@ -1,96 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-#
-# PASST - Plug A Simple Socket Transport
-#  for qemu/UNIX domain socket mode
-#
-# PASTA - Pack A Subtle Tap Abstraction
-#  for network namespace/tap device mode
-#
-# test/pasta/tcp - Check TCP functionality for pasta
-#
-# Copyright (c) 2021 Red Hat GmbH
-# Author: Stefano Brivio <sbrivio@redhat.com>
-
-htools	socat ip jq
-nstools	socat ip jq
-
-set	TEMP_BIG __STATEDIR__/test_big.bin
-set	TEMP_NS_BIG __STATEDIR__/test_ns_big.bin
-set	TEMP_SMALL __STATEDIR__/test_small.bin
-set	TEMP_NS_SMALL __STATEDIR__/test_ns_small.bin
-
-test	TCP/IPv4: host to ns: big transfer
-nsb	socat -u TCP4-LISTEN:10002,bind=127.0.0.1 OPEN:__TEMP_NS_BIG__,create,trunc
-host	socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10002
-nsw
-check	cmp __BASEPATH__/big.bin __TEMP_NS_BIG__
-
-test	TCP/IPv4: ns to host (spliced): big transfer
-hostb	socat -u TCP4-LISTEN:10003,bind=127.0.0.1 OPEN:__TEMP_BIG__,create,trunc
-ns	socat -u OPEN:__BASEPATH__/big.bin TCP4:127.0.0.1:10003
-hostw
-check	cmp __BASEPATH__/big.bin __TEMP_BIG__
-
-test	TCP/IPv4: ns to host (via tap): big transfer
-hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
-nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
-ns	socat -u OPEN:__BASEPATH__/big.bin TCP4:__GW__:10003
-hostw
-check	cmp __BASEPATH__/big.bin __TEMP_BIG__
-
-test	TCP/IPv4: host to ns: small transfer
-nsb	socat -u TCP4-LISTEN:10002,bind=127.0.0.1 OPEN:__TEMP_NS_SMALL__,create,trunc
-host	socat OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10002
-nsw
-check	cmp __BASEPATH__/small.bin __TEMP_NS_SMALL__
-
-test	TCP/IPv4: ns to host (spliced): small transfer
-hostb	socat -u TCP4-LISTEN:10003,bind=127.0.0.1 OPEN:__TEMP_SMALL__,create,trunc
-ns	socat OPEN:__BASEPATH__/small.bin TCP4:127.0.0.1:10003
-hostw
-check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
-
-test	TCP/IPv4: ns to host (via tap): small transfer
-hostb	socat -u TCP4-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
-nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
-ns	socat -u OPEN:__BASEPATH__/small.bin TCP4:__GW__:10003
-hostw
-check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
-
-test	TCP/IPv6: host to ns: big transfer
-nsb	socat -u TCP6-LISTEN:10002,bind=[::1] OPEN:__TEMP_NS_BIG__,create,trunc
-host	socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10002
-nsw
-check	cmp __BASEPATH__/big.bin __TEMP_NS_BIG__
-
-test	TCP/IPv6: ns to host (spliced): big transfer
-hostb	socat -u TCP6-LISTEN:10003,bind=[::1] OPEN:__TEMP_BIG__,create,trunc
-ns	socat -u OPEN:__BASEPATH__/big.bin TCP6:[::1]:10003
-hostw
-check	cmp __BASEPATH__/big.bin __TEMP_BIG__
-
-test	TCP/IPv6: ns to host (via tap): big transfer
-hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_BIG__,create,trunc
-nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
-nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
-ns	socat -u OPEN:__BASEPATH__/big.bin TCP6:[__GW6__%__IFNAME__]:10003
-hostw
-check	cmp __BASEPATH__/big.bin __TEMP_BIG__
-
-test	TCP/IPv6: host to ns: small transfer
-nsb	socat -u TCP6-LISTEN:10002,bind=[::1] OPEN:__TEMP_NS_SMALL__,create,trunc
-host	socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10002
-nsw
-check	cmp __BASEPATH__/small.bin __TEMP_NS_SMALL__
-
-test	TCP/IPv6: ns to host (spliced): small transfer
-hostb	socat -u TCP6-LISTEN:10003,bind=[::1] OPEN:__TEMP_SMALL__,create,trunc
-ns	socat -u OPEN:__BASEPATH__/small.bin TCP6:[::1]:10003
-hostw
-check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
-
-test	TCP/IPv6: ns to host (via tap): small transfer
-hostb	socat -u TCP6-LISTEN:10003 OPEN:__TEMP_SMALL__,create,trunc
-ns	socat -u OPEN:__BASEPATH__/small.bin TCP6:[__GW6__%__IFNAME__]:10003
-hostw
-check	cmp __BASEPATH__/small.bin __TEMP_SMALL__
diff --git a/test/pasta/udp b/test/pasta/udp
deleted file mode 100644
index 30e3a855..00000000
--- a/test/pasta/udp
+++ /dev/null
@@ -1,59 +0,0 @@
-# SPDX-License-Identifier: GPL-2.0-or-later
-#
-# PASST - Plug A Simple Socket Transport
-#  for qemu/UNIX domain socket mode
-#
-# PASTA - Pack A Subtle Tap Abstraction
-#  for network namespace/tap device mode
-#
-# test/pasta/udp - Check UDP functionality for pasta
-#
-# Copyright (c) 2021 Red Hat GmbH
-# Author: Stefano Brivio <sbrivio@redhat.com>
-
-nstools	socat ip jq
-htools	dd socat ip jq
-
-set	TEMP __STATEDIR__/test.bin
-set	TEMP_NS __STATEDIR__/test_ns.bin
-
-test	UDP/IPv4: host to ns
-nsb	socat -u UDP4-LISTEN:10002,bind=127.0.0.1,null-eof OPEN:__TEMP_NS__,create,trunc
-host	socat OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10002,shut-null
-nsw
-check	cmp __BASEPATH__/medium.bin __TEMP_NS__
-
-test	UDP/IPv4: ns to host (recvmmsg/sendmmsg)
-hostb	socat -u UDP4-LISTEN:10003,bind=127.0.0.1,null-eof OPEN:__TEMP__,create,trunc
-sleep	1
-ns	socat OPEN:__BASEPATH__/medium.bin UDP4:127.0.0.1:10003,shut-null
-hostw
-check	cmp __BASEPATH__/medium.bin __TEMP__
-
-test	UDP/IPv4: ns to host (via tap)
-hostb	socat -u UDP4-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
-nsout	GW ip -j -4 route show|jq -rM '.[] | select(.dst == "default").gateway'
-ns	socat -u OPEN:__BASEPATH__/medium.bin UDP4:__GW__:10003,shut-null
-hostw
-check	cmp __BASEPATH__/medium.bin __TEMP__
-
-test	UDP/IPv6: host to ns
-nsb	socat -u UDP6-LISTEN:10002,bind=[::1],null-eof OPEN:__TEMP_NS__,create,trunc
-host	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10002,shut-null
-nsw
-check	cmp __BASEPATH__/medium.bin __TEMP_NS__
-
-test	UDP/IPv6: ns to host (recvmmsg/sendmmsg)
-hostb	socat -u UDP6-LISTEN:10003,bind=[::1],null-eof OPEN:__TEMP__,create,trunc
-sleep	1
-ns	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[::1]:10003,shut-null
-hostw
-check	cmp __BASEPATH__/medium.bin __TEMP__
-
-test	UDP/IPv6: ns to host (via tap)
-hostb	socat -u UDP6-LISTEN:10003,null-eof OPEN:__TEMP__,create,trunc
-nsout	GW6 ip -j -6 route show|jq -rM '.[] | select(.dst == "default").gateway'
-nsout	IFNAME ip -j link show | jq -rM '.[] | select(.link_type == "ether").ifname'
-ns	socat -u OPEN:__BASEPATH__/medium.bin UDP6:[__GW6__%__IFNAME__]:10003,shut-null
-hostw
-check	cmp __BASEPATH__/medium.bin __TEMP__
diff --git a/test/run b/test/run
index b8000224..f4f5fc84 100755
--- a/test/run
+++ b/test/run
@@ -64,14 +64,6 @@ run() {
 	perf_init
 	[ ${CI} -eq 1 ]   && video_start ci
 
-	setup pasta
-	test pasta/ndp
-	test pasta/dhcp
-	test pasta/tcp
-	test pasta/udp
-	test passt/shutdown
-	teardown pasta
-
 	setup pasta_options
 	test pasta_options/log_to_file
 	teardown pasta_options
diff --git a/test/tasst/dhcpv6.py b/test/tasst/dhcpv6.py
index 816c0ea9..b2166224 100644
--- a/test/tasst/dhcpv6.py
+++ b/test/tasst/dhcpv6.py
@@ -26,13 +26,13 @@ def dhclientv6(site, ifname):
 
 def test_dhcpv6(ifname, expected_addr):
     def dec(setupfn):
-        def test_addr(setup):
+        def test_addr6(setup):
             with setup as site, dhclientv6(site, ifname):
                 addrs = [a.ip for a in site.addrs(ifname, family='inet6',
                                                   scope='global')]
             assert_in(expected_addr, addrs)  # Might also have a SLAAC address
 
-        return test_output(test_addr)(setupfn)
+        return test_output(test_addr6)(setupfn)
     return dec
 
 
diff --git a/test/tasst/pasta.py b/test/tasst/pasta.py
new file mode 100644
index 00000000..80ab20b5
--- /dev/null
+++ b/test/tasst/pasta.py
@@ -0,0 +1,42 @@
+#! /usr/bin/python3
+
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Copyright Red Hat
+# Author: David Gibson <david@gibson.dropbear.id.au>
+
+"""
+Test A Simple Socket Transport
+
+pasta.py - Helpers for starting pasta
+"""
+
+import contextlib
+
+from tasst.exesite import Site
+from tasst.typecheck import typecheck
+
+
+PASTA_BIN = '../pasta'
+
+
+class Pasta(contextlib.AbstractContextManager):
+    """A managed pasta instance"""
+
+    def __init__(self, hostsite, pidfile, extra_args, *, ns):
+        self.hostsite = typecheck(hostsite, Site)
+        self.hostsite.require_cmds(PASTA_BIN)
+        self.pidfile = typecheck(pidfile, str)
+        self.extra_args = typecheck(extra_args, str)
+        self.ns = typecheck(ns, Site)
+        self.proc = None
+
+    def __enter__(self):
+        relpid = self.ns.relative_pid(self.hostsite)
+        cmd = f'{PASTA_BIN} -f -P {self.pidfile} {self.extra_args} {relpid}'
+        self.proc = self.hostsite.bg(cmd, pidfile=self.pidfile)
+        self.proc.__enter__()
+        return self
+
+    def __exit__(self, *exc_details):
+        self.proc.__exit__(*exc_details)
diff --git a/test/tasst/scenario/simple.py b/test/tasst/scenario/simple.py
index 503a388a..57d7b000 100644
--- a/test/tasst/scenario/simple.py
+++ b/test/tasst/scenario/simple.py
@@ -13,13 +13,26 @@ scenario/simple.py - Smallest sensible network to use passt/pasta
 
 import contextlib
 
+from tasst import address
 from tasst.nstool import unshare_site
-from tasst.address import IpiAllocator, TEST_NET_2, TEST_NET6_TASST_B
 from tasst.exesite import Site
 from tasst.transfer import test_transfers
 from tasst.typecheck import typecheck
 
 
+IFNAME = 'veth'
+HOST_NET4 = address.TEST_NET_1
+HOST_NET6 = address.TEST_NET6_TASST_A
+
+ipa_local = address.IpiAllocator(HOST_NET4, HOST_NET6)
+(IP4, IP6) = ipa_local.next_ipis()
+(GW_IP4, GW_IP6) = ipa_local.next_ipis()
+
+ipa_remote = address.IpiAllocator(address.TEST_NET_2,
+                                  address.TEST_NET6_TASST_B)
+(REMOTE_IP4, REMOTE_IP6) = ipa_remote.next_ipis()
+
+
 class __SimpleNet:  # pylint: disable=R0903
     """A simple network setup scenario
 
@@ -44,40 +57,29 @@ class __SimpleNet:  # pylint: disable=R0903
 
     """
 
-    IFNAME = 'veth'
-    ipa_local = IpiAllocator()
-    (IP4, IP6) = ipa_local.next_ipis()
-    (GW_IP4, GW_IP6) = ipa_local.next_ipis()
-
-    ipa_remote = IpiAllocator(TEST_NET_2, TEST_NET6_TASST_B)
-    (REMOTE_IP4, REMOTE_IP6) = ipa_remote.next_ipis()
-
     def __init__(self, hostsite, gwsite):
         self.simhost = typecheck(hostsite, Site)
         self.gw = typecheck(gwsite, Site)
 
-        ifname = self.IFNAME
-        self.gw_ifname = 'gw' + ifname
-        self.simhost.veth(self.IFNAME, self.gw_ifname, self.gw)
+        self.gw_ifname = 'gw' + IFNAME
+        self.simhost.veth(IFNAME, self.gw_ifname, self.gw)
 
         self.gw.ifup('lo')
-        self.gw.ifup(self.gw_ifname, self.GW_IP4, self.GW_IP6,
-                     self.REMOTE_IP4, self.REMOTE_IP6)
+        self.gw.ifup(self.gw_ifname, GW_IP4, GW_IP6, REMOTE_IP4, REMOTE_IP6)
 
         self.simhost.ifup('lo')
-        self.simhost.ifup(ifname, self.IP4, self.IP6)
+        self.simhost.ifup(IFNAME, IP4, IP6)
 
         # Once link is up on both sides, SLAAC will run
         self.gw_ip6_ll = self.gw.addr_wait(self.gw_ifname,
                                            family='inet6', scope='link')[0]
-        self.ip6_ll = self.simhost.addr_wait(ifname,
+        self.ip6_ll = self.simhost.addr_wait(IFNAME,
                                              family='inet6', scope='link')[0]
 
         # Set up the default route
-        self.simhost.fg(f'ip -4 route add default via {self.GW_IP4.ip}',
-                        sudo=True)
+        self.simhost.fg(f'ip -4 route add default via {GW_IP4.ip}', sudo=True)
         self.simhost.fg(
-            f'ip -6 route add default via {self.gw_ip6_ll.ip} dev {ifname}',
+            f'ip -6 route add default via {self.gw_ip6_ll.ip} dev {IFNAME}',
             sudo=True
         )
 
@@ -89,9 +91,7 @@ def simple_net():
         yield __SimpleNet(simhost, gw)
 
 
-@test_transfers(ip4=__SimpleNet.REMOTE_IP4.ip,
-                ip6=__SimpleNet.REMOTE_IP6.ip,
-                port=10000)
+@test_transfers(ip4=REMOTE_IP4.ip, ip6=REMOTE_IP6.ip, port=10000)
 @contextlib.contextmanager
 def simple_net_transfers():
     with simple_net() as net:
-- 
@@ -13,13 +13,26 @@ scenario/simple.py - Smallest sensible network to use passt/pasta
 
 import contextlib
 
+from tasst import address
 from tasst.nstool import unshare_site
-from tasst.address import IpiAllocator, TEST_NET_2, TEST_NET6_TASST_B
 from tasst.exesite import Site
 from tasst.transfer import test_transfers
 from tasst.typecheck import typecheck
 
 
+IFNAME = 'veth'
+HOST_NET4 = address.TEST_NET_1
+HOST_NET6 = address.TEST_NET6_TASST_A
+
+ipa_local = address.IpiAllocator(HOST_NET4, HOST_NET6)
+(IP4, IP6) = ipa_local.next_ipis()
+(GW_IP4, GW_IP6) = ipa_local.next_ipis()
+
+ipa_remote = address.IpiAllocator(address.TEST_NET_2,
+                                  address.TEST_NET6_TASST_B)
+(REMOTE_IP4, REMOTE_IP6) = ipa_remote.next_ipis()
+
+
 class __SimpleNet:  # pylint: disable=R0903
     """A simple network setup scenario
 
@@ -44,40 +57,29 @@ class __SimpleNet:  # pylint: disable=R0903
 
     """
 
-    IFNAME = 'veth'
-    ipa_local = IpiAllocator()
-    (IP4, IP6) = ipa_local.next_ipis()
-    (GW_IP4, GW_IP6) = ipa_local.next_ipis()
-
-    ipa_remote = IpiAllocator(TEST_NET_2, TEST_NET6_TASST_B)
-    (REMOTE_IP4, REMOTE_IP6) = ipa_remote.next_ipis()
-
     def __init__(self, hostsite, gwsite):
         self.simhost = typecheck(hostsite, Site)
         self.gw = typecheck(gwsite, Site)
 
-        ifname = self.IFNAME
-        self.gw_ifname = 'gw' + ifname
-        self.simhost.veth(self.IFNAME, self.gw_ifname, self.gw)
+        self.gw_ifname = 'gw' + IFNAME
+        self.simhost.veth(IFNAME, self.gw_ifname, self.gw)
 
         self.gw.ifup('lo')
-        self.gw.ifup(self.gw_ifname, self.GW_IP4, self.GW_IP6,
-                     self.REMOTE_IP4, self.REMOTE_IP6)
+        self.gw.ifup(self.gw_ifname, GW_IP4, GW_IP6, REMOTE_IP4, REMOTE_IP6)
 
         self.simhost.ifup('lo')
-        self.simhost.ifup(ifname, self.IP4, self.IP6)
+        self.simhost.ifup(IFNAME, IP4, IP6)
 
         # Once link is up on both sides, SLAAC will run
         self.gw_ip6_ll = self.gw.addr_wait(self.gw_ifname,
                                            family='inet6', scope='link')[0]
-        self.ip6_ll = self.simhost.addr_wait(ifname,
+        self.ip6_ll = self.simhost.addr_wait(IFNAME,
                                              family='inet6', scope='link')[0]
 
         # Set up the default route
-        self.simhost.fg(f'ip -4 route add default via {self.GW_IP4.ip}',
-                        sudo=True)
+        self.simhost.fg(f'ip -4 route add default via {GW_IP4.ip}', sudo=True)
         self.simhost.fg(
-            f'ip -6 route add default via {self.gw_ip6_ll.ip} dev {ifname}',
+            f'ip -6 route add default via {self.gw_ip6_ll.ip} dev {IFNAME}',
             sudo=True
         )
 
@@ -89,9 +91,7 @@ def simple_net():
         yield __SimpleNet(simhost, gw)
 
 
-@test_transfers(ip4=__SimpleNet.REMOTE_IP4.ip,
-                ip6=__SimpleNet.REMOTE_IP6.ip,
-                port=10000)
+@test_transfers(ip4=REMOTE_IP4.ip, ip6=REMOTE_IP6.ip, port=10000)
 @contextlib.contextmanager
 def simple_net_transfers():
     with simple_net() as net:
-- 
2.41.0


^ permalink raw reply related	[flat|nested] 32+ messages in thread

* Re: [PATCH 27/27] avocado: Convert basic pasta tests
  2023-06-27  2:54 ` [PATCH 27/27] avocado: Convert basic pasta tests David Gibson
@ 2023-07-05  0:30   ` Stefano Brivio
  2023-07-05  3:27     ` David Gibson
  0 siblings, 1 reply; 32+ messages in thread
From: Stefano Brivio @ 2023-07-05  0:30 UTC (permalink / raw)
  To: David Gibson; +Cc: passt-dev, crosa, jarichte

Sorry for the delay, some (partial) feedback and a few ideas:

On Tue, 27 Jun 2023 12:54:28 +1000
David Gibson <david@gibson.dropbear.id.au> wrote:

> Convert the old-style tests for pasta (DHCP, NDP, TCP and UDP transfers)
> to using avocado.  There are a few differences in what we test, but this
> should generally improve coverage:
> 
>  * We run in a constructed network environment, so we no longer depend on
>    the real host's networking configuration
>  * We do independent setup for each individual test
>  * We add explicit tests for --config-net, which we use to accelerate that
>    setup for the TCP and UDP tests
>  * The TCP and UDP tests now test transfers between the guest and a
>    (simulated) remote site that's on a different network from the simulated
>    pasta host.  This better matches the typical passt/pasta usecase

...this is not necessarily true -- it really depends, but sure, it's
important to have this too.

> Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
> ---
>  oldtest/run                   |  14 ++--
>  test/Makefile                 |   1 +
>  test/avocado/pasta.py         | 129 ++++++++++++++++++++++++++++++++++
>  test/lib/layout               |  31 --------
>  test/lib/setup                |  40 -----------
>  test/pasta/dhcp               |  46 ------------
>  test/pasta/ndp                |  33 ---------
>  test/pasta/tcp                |  96 -------------------------
>  test/pasta/udp                |  59 ----------------
>  test/run                      |   8 ---
>  test/tasst/dhcpv6.py          |   4 +-
>  test/tasst/pasta.py           |  42 +++++++++++
>  test/tasst/scenario/simple.py |  44 ++++++------
>  13 files changed, 203 insertions(+), 344 deletions(-)
>  create mode 100644 test/avocado/pasta.py
>  delete mode 100644 test/pasta/dhcp
>  delete mode 100644 test/pasta/ndp
>  delete mode 100644 test/pasta/tcp
>  delete mode 100644 test/pasta/udp
>  create mode 100644 test/tasst/pasta.py
> 
> diff --git a/oldtest/run b/oldtest/run
> index a16bc49b..f1157f90 100755
> --- a/oldtest/run
> +++ b/oldtest/run
> @@ -70,13 +70,13 @@ run() {
>  	test build/clang_tidy
>  	teardown build
>  
> -#	setup pasta
> -#	test pasta/ndp
> -#	test pasta/dhcp
> -#	test pasta/tcp
> -#	test pasta/udp
> -#	test passt/shutdown
> -#	teardown pasta
> +	setup pasta
> +	test pasta/ndp
> +	test pasta/dhcp
> +	test pasta/tcp
> +	test pasta/udp
> +	test passt/shutdown
> +	teardown pasta
>  
>  #	setup pasta_options
>  #	test pasta_options/log_to_file
> diff --git a/test/Makefile b/test/Makefile
> index 953eacf2..9c3114c2 100644
> --- a/test/Makefile
> +++ b/test/Makefile
> @@ -227,6 +227,7 @@ $(VENV):
>  
>  .PHONY: avocado-assets
>  avocado-assets: nstool small.bin medium.bin big.bin
> +	$(MAKE) -C .. pasta
>  
>  .PHONY: avocado
>  avocado: avocado-assets $(VENV)
> diff --git a/test/avocado/pasta.py b/test/avocado/pasta.py
> new file mode 100644
> index 00000000..891313f5
> --- /dev/null
> +++ b/test/avocado/pasta.py

I'm just focusing on this for the moment, as this is the part I'm mostly
concerned about (writing tests), with a particular accent on what makes
this (still) read-only _code_ for me, and the gaps with the kind of
things I would have naturally expected to write.

That is, I'm mostly isolating negative aspects here.

I'm stressing "code" because I also would have the natural expectation
that it should/must be simpler to _write_ (not necessarily design) tests
rather than the code that end users use, because we can use whatever
language with no strict (only some) constraints on resources and speed.
So in my opinion it doesn't necessarily need to be "code" (and a
general feeling I have from this is that it really is code).

> @@ -0,0 +1,129 @@
> +#! /usr/bin/env avocado-runner-avocado-classless
> +
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +#
> +# Copyright Red Hat
> +# Author: David Gibson <david@gibson.dropbear.id.au>
> +
> +"""
> +avocado/pasta.py - Basic tests for pasta mode
> +"""
> +
> +import contextlib
> +import ipaddress
> +import os
> +import tempfile

So far so good, I probably don't need to read up about every single
import.

> +from avocado_classless.test import assert_eq, assert_eq_unordered, test

Probably a future source of (needless) copy-and-paste. Of course,
assert_eq_unordered is not available in the current tests, and we need
it, plus a syntax to represent that.

Still, conceptually speaking, we shouldn't need this kind of import if
we are writing _a test for passt in the test suite for passt itself_,
and we want to check that the list of interfaces matches "lo" -- it's
something we'll need basically everywhere anyway.

> +from tasst.address import LOOPBACK4, LOOPBACK6

On one hand, probably more readable than a 127.0.0.1 here and there
(even though longer than "::1"), on the other hand there are 256
loopback addresses in IPv4. And there is only one way of writing "::1",
but many ways of remembering/misspelling LOOPBACK6.

> +from tasst.dhcp import test_dhcp
> +from tasst.dhcpv6 import test_dhcpv6
> +from tasst.ndp import test_ndp
> +from tasst.nstool import unshare_site
> +from tasst.pasta import Pasta
> +from tasst.scenario.simple import (
> +    simple_net, IFNAME, HOST_NET6, IP4, IP6, GW_IP4, REMOTE_IP4, REMOTE_IP6
> +)
> +from tasst.transfer import test_transfers

Similar for this -- it would be great to have an infrastructure where
we can just use what we need without looking it up. Rather minor though.

> +
> +
> +IN_FWD_PORT = 10002
> +SPLICE_FWD_PORT = 10003
> +FWD_OPTS = f'-t {IN_FWD_PORT} -u {IN_FWD_PORT} \
> +             -T {SPLICE_FWD_PORT} -U {SPLICE_FWD_PORT}'

I think clear enough.

> +
> +@contextlib.contextmanager

About this decorator: I now have a vague idea what it means after
reading the full series and some Avocado documentation... but I don't
find that very descriptive.

> +def pasta_unconfigured(opts=FWD_OPTS):

Clear.

> +    with simple_net() as simnet:

Also clear.

> +        with unshare_site('pastans', '-Ucnpf --mount-proc',
> +                          parent=simnet.simhost, sudo=True) as guestns:

If I ignore for a moment the implementation detail, it's not very
intuitive why this is nested in the "with" statement before.

Those parameters require a complete understanding of unshare_site(),
and... I now know what "sudo=True" means in Avocado, but I don't
even have sudo on my machine, so at a distracted look I would think I
need to install it and configure sudoers (which would make me as a
developer skip the tests).

So far, what this says is:

- use addressing from simple_net()
- detach user and network namespace, remounting proc in it, preserve (I
  guess?) credentials

> +            with tempfile.TemporaryDirectory() as tmpdir:

...even less intuitive why this statement (tmpdir="$(mktemp -d)") is
nested.

> +                pidfile = os.path.join(tmpdir, 'pasta.pid')
> +
> +                with Pasta(simnet.simhost, pidfile, opts, ns=guestns) as pasta:
> +                    yield simnet, pasta.ns

...and this finally says: start pasta in that (user? network?)
namespace.

So, from my expectation, pseudocode:

setup:
  net: simple_net
  pasta_options: -t 10002 -T 10003 -u 10002 -U 10003
  start pasta

and from my understanding of what this is replacing:

setup_pasta() {
	context_setup_host unshare

	context_run_bg unshare "unshare -rUnpf ${NSTOOL} hold ${STATESETUP}/ns.hold"

	context_setup_nstool ns ${STATESETUP}/ns.hold

	# Ports:
	#
	#                 ns        |         host
	#         ------------------|---------------------
	#  10002      as server     |    spliced to ns
	#  10003   spliced to init  |      as server

	context_run_bg passt "./pasta -f -t 10002 -T 10003 -u 10002 -U 10003 -P ${STATESETUP}/passt.pid $(${NSTOOL} info -pw ${STATESETUP}/ns.hold)"
}

which I actually find less elegant but much clearer, especially before
reading the whole series, I wonder if we could have either a
configuration format, or directives... but, disappointingly, I couldn't
decide on a more detailed proposal yet.

Still, I would aim at something resembling that
pseudocode/configuration above. Of course, with no details it looks
neat, so the comparison is unfair, but I'm under the impression that
even after adding details (that's what I'm trying to figure
out/articulate) it should still be much clearer.

> +
> +
> +@test
> +def test_ifname():

I guess fine (even though it's missing a descriptive name _inline_).

> +    with pasta_unconfigured() as (_, ns):

Ouch... a bit hard to grasp for people not familiar with Python or
Go... plus, conceptually, we _just_ want to say either:

test: "pasta namespace has the interface name we configured"
  enter namespace
  list of interfaces includes $NAME

or:

test: "pasta namespace has a loopback interface"
  list of interfaces in namespace includes $NAME

...and yes, that requires that we start pasta first, but I wonder if it
makes sense to mix the two notions (i.e. whether an object-oriented
approach makes sense at all, actually).

> +        assert_eq_unordered(ns.ifs(), ('lo', IFNAME))

Clear (probably clearer with a "list includes" operator).

> +
> +
> +@test_ndp(IFNAME, HOST_NET6)
> +@contextlib.contextmanager
> +def pasta_ndp():
> +    with pasta_unconfigured() as (simnet, guestns):
> +        guestns.ifup(IFNAME)
> +        yield guestns, simnet.gw_ip6_ll.ip

This really hides what we are checking. Other than that (but it's a big
issue in my opinion) I find it terse and elegant.

> +
> +@test_dhcp(IFNAME, IP4.ip, GW_IP4.ip, 65520)

The test_ndp decorator is probably usable, this one not so much. As we
have more parameters, it would probably be better to have something
more descriptive (even if necessarily more verbose).

> +@test_dhcpv6(IFNAME, IP6.ip)
> +@contextlib.contextmanager
> +def pasta_dhcp():
> +    with pasta_unconfigured() as (_, guestns):
> +        yield guestns
> +
> +
> +@contextlib.contextmanager
> +def pasta_configured():
> +    with pasta_unconfigured(FWD_OPTS + ' --config-net') as (simnet, ns):
> +        # Wait for DAD to complete on the --config-net address

Side note: this shouldn't be needed -- if it is, we should probably fix
something in pasta.

> +        ns.addr_wait(IFNAME, family='inet6', scope='global')
> +        yield simnet, ns

Except for the common points with my observation above, this looks
usable, and:

> +
> +
> +@test
> +def test_config_net_addr():
> +    with pasta_configured() as (_, ns):
> +        addrs = ns.addrs(IFNAME, scope='global')
> +        assert_eq_unordered(addrs, [IP4, IP6])
> +
> +
> +@test
> +def test_config_net_route4():
> +    with pasta_configured() as (_, ns):
> +        (defroute,) = ns.routes4(dst='default')
> +        gateway = ipaddress.ip_address(defroute['gateway'])
> +        assert_eq(gateway, GW_IP4.ip)
> +
> +
> +@test
> +def test_config_net_route6():
> +    with pasta_configured() as (simnet, ns):
> +        (defroute,) = ns.routes6(dst='default')
> +        gateway = ipaddress.ip_address(defroute['gateway'])
> +        assert_eq(gateway, simnet.gw_ip6_ll.ip)
> +
> +
> +@test
> +def test_config_net_mtu():
> +    with pasta_configured() as (_, ns):
> +        mtu = ns.mtu(IFNAME)
> +        assert_eq(mtu, 65520)

these all make intuitive sense to me.

> +
> +
> +@test_transfers(ip4=REMOTE_IP4.ip, ip6=REMOTE_IP6.ip, port=10000)
> +@contextlib.contextmanager
> +def outward_transfer():
> +    with pasta_configured() as (simnet, ns):
> +        yield ns, simnet.gw

This, however, not. I would naturally expect the function implementing
a template of data transfer test to do something with data.

> +
> +
> +@test_transfers(ip4=IP4.ip, ip6=IP6.ip, port=IN_FWD_PORT,
> +                fromip4=REMOTE_IP4.ip, fromip6=REMOTE_IP6.ip)
> +@contextlib.contextmanager
> +def inward_transfer():
> +    with pasta_configured() as (simnet, ns):
> +        yield simnet.gw, ns
> +
> +
> +@test_transfers(ip4=LOOPBACK4, ip6=LOOPBACK6, port=SPLICE_FWD_PORT,
> +                listenip4=LOOPBACK4, listenip6=LOOPBACK6)
> +@contextlib.contextmanager
> +def spliced_transfer():
> +    with pasta_configured() as (simnet, ns):
> +        yield ns, simnet.simhost

I still have to decode these before I can reasonably comment on them.

I'll follow up with a more detailed proposal of a possible
configuration format or perhaps something between a domain-specific
language and an annotated Python script (à la bats), but I'm trying to
consider a few more tests on top of these.

I started looking at "options" tests next, and realised my proposal was
a bit too simplistic. But also that the current version of those tests,
while somewhat verbose and surely clunky, shows in a very clear way
what we're checking and what we expect... and I'm not sure that the
outcome from extending pasta_configured() would be very usable.

All we need for that is:

- define options and network model/addressing

- have a clear syntax of identifying where pasta is starting

- a list of commands (which, themselves, are absolutely obvious and
  natural in a shell script, but I see the value of using Python
  features to check assertions, at least).

-- 
Stefano


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 27/27] avocado: Convert basic pasta tests
  2023-07-05  0:30   ` Stefano Brivio
@ 2023-07-05  3:27     ` David Gibson
  2023-07-07 17:42       ` Stefano Brivio
  0 siblings, 1 reply; 32+ messages in thread
From: David Gibson @ 2023-07-05  3:27 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: passt-dev, crosa, jarichte

[-- Attachment #1: Type: text/plain, Size: 24680 bytes --]

On Wed, Jul 05, 2023 at 02:30:48AM +0200, Stefano Brivio wrote:
> Sorry for the delay, some (partial) feedback and a few ideas:
> 
> On Tue, 27 Jun 2023 12:54:28 +1000
> David Gibson <david@gibson.dropbear.id.au> wrote:
> 
> > Convert the old-style tests for pasta (DHCP, NDP, TCP and UDP transfers)
> > to using avocado.  There are a few differences in what we test, but this
> > should generally improve coverage:
> > 
> >  * We run in a constructed network environment, so we no longer depend on
> >    the real host's networking configuration
> >  * We do independent setup for each individual test
> >  * We add explicit tests for --config-net, which we use to accelerate that
> >    setup for the TCP and UDP tests
> >  * The TCP and UDP tests now test transfers between the guest and a
> >    (simulated) remote site that's on a different network from the simulated
> >    pasta host.  This better matches the typical passt/pasta usecase
> 
> ...this is not necessarily true -- it really depends, but sure, it's
> important to have this too.

Yeah, I guess not.  My point here is that given that we're generally
trying to avoid NAT, it seems to me we should primarily test the
no-NAT case, then have specific tests for the NAT cases.  I'll try to
find a better way to phrase that.

> > Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
> > ---
> >  oldtest/run                   |  14 ++--
> >  test/Makefile                 |   1 +
> >  test/avocado/pasta.py         | 129 ++++++++++++++++++++++++++++++++++
> >  test/lib/layout               |  31 --------
> >  test/lib/setup                |  40 -----------
> >  test/pasta/dhcp               |  46 ------------
> >  test/pasta/ndp                |  33 ---------
> >  test/pasta/tcp                |  96 -------------------------
> >  test/pasta/udp                |  59 ----------------
> >  test/run                      |   8 ---
> >  test/tasst/dhcpv6.py          |   4 +-
> >  test/tasst/pasta.py           |  42 +++++++++++
> >  test/tasst/scenario/simple.py |  44 ++++++------
> >  13 files changed, 203 insertions(+), 344 deletions(-)
> >  create mode 100644 test/avocado/pasta.py
> >  delete mode 100644 test/pasta/dhcp
> >  delete mode 100644 test/pasta/ndp
> >  delete mode 100644 test/pasta/tcp
> >  delete mode 100644 test/pasta/udp
> >  create mode 100644 test/tasst/pasta.py
> > 
> > diff --git a/oldtest/run b/oldtest/run
> > index a16bc49b..f1157f90 100755
> > --- a/oldtest/run
> > +++ b/oldtest/run
> > @@ -70,13 +70,13 @@ run() {
> >  	test build/clang_tidy
> >  	teardown build
> >  
> > -#	setup pasta
> > -#	test pasta/ndp
> > -#	test pasta/dhcp
> > -#	test pasta/tcp
> > -#	test pasta/udp
> > -#	test passt/shutdown
> > -#	teardown pasta
> > +	setup pasta
> > +	test pasta/ndp
> > +	test pasta/dhcp
> > +	test pasta/tcp
> > +	test pasta/udp
> > +	test passt/shutdown
> > +	teardown pasta
> >  
> >  #	setup pasta_options
> >  #	test pasta_options/log_to_file
> > diff --git a/test/Makefile b/test/Makefile
> > index 953eacf2..9c3114c2 100644
> > --- a/test/Makefile
> > +++ b/test/Makefile
> > @@ -227,6 +227,7 @@ $(VENV):
> >  
> >  .PHONY: avocado-assets
> >  avocado-assets: nstool small.bin medium.bin big.bin
> > +	$(MAKE) -C .. pasta
> >  
> >  .PHONY: avocado
> >  avocado: avocado-assets $(VENV)
> > diff --git a/test/avocado/pasta.py b/test/avocado/pasta.py
> > new file mode 100644
> > index 00000000..891313f5
> > --- /dev/null
> > +++ b/test/avocado/pasta.py
> 
> I'm just focusing on this for the moment, as this is the part I'm mostly
> concerned about (writing tests), with a particular accent on what makes
> this (still) read-only _code_ for me, and the gaps with the kind of
> things I would have naturally expected to write.
> 
> That is, I'm mostly isolating negative aspects here.
> 
> I'm stressing "code" because I also would have the natural expectation
> that it should/must be simpler to _write_ (not necessarily design) tests
> rather than the code that end users use, because we can use whatever
> language with no strict (only some) constraints on resources and speed.
> So in my opinion it doesn't necessarily need to be "code" (and a
> general feeling I have from this is that it really is code).

Right, I see/share your concern.  To some extent there's an
unavoidable trade-off here.  Avoiding lots of repetition in the test
cases, leads to "code" in some sense: we need something resembling
function calls and loops at least to re-invoke standard test pieces.
That's not to say we can't make it better than it is in this draft.

> > @@ -0,0 +1,129 @@
> > +#! /usr/bin/env avocado-runner-avocado-classless
> > +
> > +# SPDX-License-Identifier: GPL-2.0-or-later
> > +#
> > +# Copyright Red Hat
> > +# Author: David Gibson <david@gibson.dropbear.id.au>
> > +
> > +"""
> > +avocado/pasta.py - Basic tests for pasta mode
> > +"""
> > +
> > +import contextlib
> > +import ipaddress
> > +import os
> > +import tempfile
> 
> So far so good, I probably don't need to read up about every single
> import.
> 
> > +from avocado_classless.test import assert_eq, assert_eq_unordered, test
> 
> Probably a future source of (needless) copy-and-paste.

Fair point.  I was considering making the idiom here:
	from avocado_classless.test import *
Which will import "all" (in fact a curated list) of things from the
module for use here.  I didn't go that way because pylint whinged
about it, but we could suppress that easily enough.

> Of course,
> assert_eq_unordered is not available in the current tests, and we need
> it, plus a syntax to represent that.

I'm not quite sure what you're getting at here.

> Still, conceptually speaking, we shouldn't need this kind of import if
> we are writing _a test for passt in the test suite for passt itself_,
> and we want to check that the list of interfaces matches "lo" -- it's
> something we'll need basically everywhere anyway.
> 
> > +from tasst.address import LOOPBACK4, LOOPBACK6
> 
> On one hand, probably more readable than a 127.0.0.1 here and there
> (even though longer than "::1"), on the other hand there are 256
> loopback addresses in IPv4. And there is only one way of writing "::1",
> but many ways of remembering/misspelling LOOPBACK6.

Right... so "LOOPBACK6" is longer than "::1", but not than
"ipaddress.ip_address('::1')".  That is, these helpers produce
something that's in an actual IP address type, rather than a bare
string.  I could try moving to just using strings for IP addresses
throughout the tests, but that might cause some different messes.

> > +from tasst.dhcp import test_dhcp
> > +from tasst.dhcpv6 import test_dhcpv6
> > +from tasst.ndp import test_ndp
> > +from tasst.nstool import unshare_site
> > +from tasst.pasta import Pasta
> > +from tasst.scenario.simple import (
> > +    simple_net, IFNAME, HOST_NET6, IP4, IP6, GW_IP4, REMOTE_IP4, REMOTE_IP6
> > +)
> > +from tasst.transfer import test_transfers
> 
> Similar for this -- it would be great to have an infrastructure where
> we can just use what we need without looking it up. Rather minor
> though.

Right, I could introduce "from foo import *" conventions for some of
these too.

> > +
> > +
> > +IN_FWD_PORT = 10002
> > +SPLICE_FWD_PORT = 10003
> > +FWD_OPTS = f'-t {IN_FWD_PORT} -u {IN_FWD_PORT} \
> > +             -T {SPLICE_FWD_PORT} -U {SPLICE_FWD_PORT}'
> 
> I think clear enough.
> 
> > +
> > +@contextlib.contextmanager
> 
> About this decorator: I now have a vague idea what it means after
> reading the full series and some Avocado documentation... but I don't
> find that very descriptive.

Yeah.  I agree that this is uncomfortably cryptic, however this one I
don't see an easy way to remove.  I heavily use this to conveniently
modify and extend context managers.  It allows two things which are
both very convenient:

1) You can do:

	@contextlib.contextmanager
	def foo(...):
	    setup()

	    yield whatever   # <== actual test runs here

	    cleanup()

this will do the cleanup() even if the test blows up.

2) You can do

	@contextlib.contextmanager
	def bar(...):
	    with other_context_1():
	        with other_context_2():
		    yield whatever    # <=== actual test runs here

This effectively makes this a combination of the two existing
contexts, again ensuring that both their cleanup happens if the test
blows up.

It's certainly possible to do this with direct implementations of the
contextmanager protocol but it's much more verbose, and not
significantly less esoteric.

> > +def pasta_unconfigured(opts=FWD_OPTS):
> 
> Clear.
> 
> > +    with simple_net() as simnet:
> 
> Also clear.
> 
> > +        with unshare_site('pastans', '-Ucnpf --mount-proc',
> > +                          parent=simnet.simhost, sudo=True) as guestns:
> 
> If I ignore for a moment the implementation detail, it's not very
> intuitive why this is nested in the "with" statement before.

Hmm.. I'm not sure what's throwing you on this specifically.  We need
both pieces of setup for our test, so we need to nest them within each
other.  They have to go in this order because the parameters to
unshare_site() need something from the simnet.

> Those parameters require a complete understanding of unshare_site(),

Hm, ok.  I could require that this takes keyword arguments, so it
would be something like:
    unshare_site(name='pastans', unshare_opts='-Ucnpf --mount-proc',
                 ..)

Would that help?	

> and... I now know what "sudo=True" means in Avocado, but I don't
> even have sudo on my machine, so at a distracted look I would think I
> need to install it and configure sudoers (which would make me as a
> developer skip the tests).

Yeah.  And it's arguably not entirely accurate either.  I could rework
the 'site' stuff so that I call this, say, 'capable' or 'keep_caps'
instead.  Would that help?

> So far, what this says is:
> 
> - use addressing from simple_net()

Hrm, it's more than that: simple_net() is actually *creating* the
simulated simple network (and cleaning it up once the with clause
ends).

> - detach user and network namespace, remounting proc in it, preserve (I
>   guess?) credentials

Well, if by "preserve credentials" you mean "keep the capabilities you
have because you own the namespace", yes (this is equivalent to
"nsenter --keep-caps", not "nsenter --preserve-credentials").

> 
> > +            with tempfile.TemporaryDirectory() as tmpdir:
> 
> ...even less intuitive why this statement (tmpdir="$(mktemp -d)") is
> nested.

So it's not quite equivalent to tmpdir=$(mktemp -d): it does that at
the start of the with, but it also removes the directory at the end of
the with.

We need the network constructed, the namespace created and the
temporary directory available all at the same time.  So the scope of
each 'with' context needs to overlap - in other words to be nested.
In this case the order of nesting doesn't matter, we could put the
temporary directory 'with' on the outside.

> 
> > +                pidfile = os.path.join(tmpdir, 'pasta.pid')
> > +
> > +                with Pasta(simnet.simhost, pidfile, opts, ns=guestns) as pasta:
> > +                    yield simnet, pasta.ns
> 
> ...and this finally says: start pasta in that (user? network?)
> namespace.

Yes, specifically, start pasta with it's "host" being simnet.simhost,
and its "guest" being guestns.  Again I could enforce keyword
parameters to make the meaning of the arguments a bit clearer.

[Aside, I plan to extent Pasta() so that if you don't pass a guest ns
it will default to having pasta create its own - that ns will still be
available as 'pasta.ns', so the rest of the stuff can remain the same]

Then the "yield" essentially means "now that we've done the setup,
here is where to run the actual specific test".

> So, from my expectation, pseudocode:
> 
> setup:
>   net: simple_net
>   pasta_options: -t 10002 -T 10003 -u 10002 -U 10003
>   start pasta

Hrm, that pseudocode assumes a pretty specific set of possible setups,
and how they relate to each other.  For example I can't see how it
would extend to running two different pasta instances on different
simulated hosts.  That's possible with the approach I'm using here.

> and from my understanding of what this is replacing:
> 
> setup_pasta() {
> 	context_setup_host unshare
> 
> 	context_run_bg unshare "unshare -rUnpf ${NSTOOL} hold ${STATESETUP}/ns.hold"
> 
> 	context_setup_nstool ns ${STATESETUP}/ns.hold
> 
> 	# Ports:
> 	#
> 	#                 ns        |         host
> 	#         ------------------|---------------------
> 	#  10002      as server     |    spliced to ns
> 	#  10003   spliced to init  |      as server
> 
> 	context_run_bg passt "./pasta -f -t 10002 -T 10003 -u 10002 -U 10003 -P ${STATESETUP}/passt.pid $(${NSTOOL} info -pw ${STATESETUP}/ns.hold)"
> }

It's replacing not just that, but also the matching teardown function,
and ensures that the teardown runs if there's an exception in the test
itself (including running the teardowns for the outer contexts if the
setup of the inner contexts failed).

> which I actually find less elegant but much clearer, especially before
> reading the whole series, I wonder if we could have either a
> configuration format, or directives... but, disappointingly, I couldn't
> decide on a more detailed proposal yet.
> 
> Still, I would aim at something resembling that
> pseudocode/configuration above. Of course, with no details it looks
> neat, so the comparison is unfair, but I'm under the impression that
> even after adding details (that's what I'm trying to figure
> out/articulate) it should still be much clearer.

I'm just not convinced that it would be, without losing a great deal
of the flexibility in this system.  Or at least, not clearer than a
more polished version of this proposal.

> > +
> > +
> > +@test
> > +def test_ifname():
> 
> I guess fine (even though it's missing a descriptive name _inline_).

We could put a description in a docstring easily enough.

> > +    with pasta_unconfigured() as (_, ns):
> 
> Ouch... a bit hard to grasp for people not familiar with Python or
> Go... plus, conceptually, we _just_ want to say either:

Yeah.  One of the bits I'm least happy with is what to do in cases
where the information the setup needs to pass to the tests proper is
quite complex: so here it has both the simulated network which is
hosting the pasta instance, and also the namespace within pasta.  This
specific test doesn't need the former, hence the _.  We could use a
suitably named dummy variable which might be a bit more readable,
though it would need a lint suppression (unused variable).

I've considered another approach, though I'm not sure it's an
improvement, which is to use more intermediate structs/classes.  So,
in that approach pasta_unconfigured() would give you a
'PastaTestScenario' instance with fields for the guest ns, host ns and
whatever else seems useful.

> test: "pasta namespace has the interface name we configured"
>   enter namespace
>   list of interfaces includes $NAME
> 
> or:
> 
> test: "pasta namespace has a loopback interface"
>   list of interfaces in namespace includes $NAME

It's more than that though: we're also requesting a very specific
(simulated) environment for the test, which the pseudocode above just
kind of assumes.  'pasta_unconfigured()' (name certainly negotiable)
is handling the setup and teardown of everything we need for a certain
class of pasta tests.  That has 3 namespaces, a veth, and a pasta
instance, all configured in a particular way.

> ...and yes, that requires that we start pasta first, but I wonder if it
> makes sense to mix the two notions (i.e. whether an object-oriented
> approach makes sense at all, actually).

So the OO approach works ok for pretty simple setups - probably why it
became popular with jUnit etc: the class handles the setup, the
methods do the specific tests.  But it breaks down pretty quickly:
   - If you want to use multiple "setup" pieces at once you need to
     use multiple inheritence, which is a nightmare
   - If you want to parameterize parts of the setup, you need to do it
     at the whole class level which involves wierd non-local
     interactions
   - If you want to use multiple setup pieces which are actually the
     same piece repeated with different parameters, you're pretty much
     out of luck

I realize that context managers aren't the most obvious thing, but
they have the huge advantage that every test can directly and
reasonably tersely declare what setup they need to run:

    def test():
	with setup_i_need():
	    with other_setup_i_need():
	       with setup_i_need(param='non default'):
	           # actually do the test

This also handles the necessary teardown at each level, and it still
works if some of the inner setups need paramaters that are derived
fromt the outer setups.

> 
> > +        assert_eq_unordered(ns.ifs(), ('lo', IFNAME))
> 
> Clear (probably clearer with a "list includes" operator).

It's not the same as list includes (that's assert_in()).  This checks
that the lists (strictly, iterables) are identical ignoring order.  So
in this case ['lo', IFNAME] and [IFNAME, 'lo'] would be ok, but ['lo',
'eth99', IFNAME] would not be.

> > +
> > +
> > +@test_ndp(IFNAME, HOST_NET6)
> > +@contextlib.contextmanager
> > +def pasta_ndp():
> > +    with pasta_unconfigured() as (simnet, guestns):
> > +        guestns.ifup(IFNAME)
> > +        yield guestns, simnet.gw_ip6_ll.ip
> 
> This really hides what we are checking. Other than that (but it's a big
> issue in my opinion) I find it terse and elegant.

Yeah, I'm not super happy with this one.  In particular I dislike the
inconsistency: in simple tests like the above we have, loosely the
setup, followed by the test.  In these composed examples we have the
tests then the setup.  So combining a few options I've thought of, I
could do something like this:

	@contextlib.contextmanager
	def pasta_ndp():
	    with pasta_unconfigured() as (simnet, guestns):
	        guestns.ifup(IFNAME)
	        yield NdpTestScenario(ifname=IFNAME, net=HOST_NET6,
		                      router=simnet.gw_ip6_ll.ip,
				      ndpclient=guestns)

	compose_matrix([pasta_ndp], NDP_TESTS)

Here NDP_TESTS would come from the ndp module and have the list of
individual ndp test cases, each of which takes an NdpTestScenario as
parameter.

> > +
> > +@test_dhcp(IFNAME, IP4.ip, GW_IP4.ip, 65520)
> 
> The test_ndp decorator is probably usable, this one not so much. As we
> have more parameters, it would probably be better to have something
> more descriptive (even if necessarily more verbose).

Right.  So would the treatment above help?  Enough?  It would look
something like:

	@contextlib.contextmanager
	def pasta_dhcp():
	    ....
	    yield DhcpTestScenario(ifname=IFNAME, client_ip4=IP4.ip,
	                           server_ip4=GW_IP4.ip, mtu=65520)

	compose_matrix([pasta_dhcp], DHCP_TESTS)

> > +@test_dhcpv6(IFNAME, IP6.ip)
> > +@contextlib.contextmanager
> > +def pasta_dhcp():
> > +    with pasta_unconfigured() as (_, guestns):
> > +        yield guestns
> > +
> > +
> > +@contextlib.contextmanager
> > +def pasta_configured():
> > +    with pasta_unconfigured(FWD_OPTS + ' --config-net') as (simnet, ns):
> > +        # Wait for DAD to complete on the --config-net address
> 
> Side note: this shouldn't be needed -- if it is, we should probably fix
> something in pasta.

It is needed, and yes we probably should, I haven't had a chance to
look into what, exactly.  We may hit that kernel bug I noticed where
permissions to the disable_dad sysctl seem to behave weirdly.

> > +        ns.addr_wait(IFNAME, family='inet6', scope='global')
> > +        yield simnet, ns
> 
> Except for the common points with my observation above, this looks
> usable, and:
> 
> > +
> > +
> > +@test
> > +def test_config_net_addr():
> > +    with pasta_configured() as (_, ns):
> > +        addrs = ns.addrs(IFNAME, scope='global')
> > +        assert_eq_unordered(addrs, [IP4, IP6])
> > +
> > +
> > +@test
> > +def test_config_net_route4():
> > +    with pasta_configured() as (_, ns):
> > +        (defroute,) = ns.routes4(dst='default')
> > +        gateway = ipaddress.ip_address(defroute['gateway'])
> > +        assert_eq(gateway, GW_IP4.ip)
> > +
> > +
> > +@test
> > +def test_config_net_route6():
> > +    with pasta_configured() as (simnet, ns):
> > +        (defroute,) = ns.routes6(dst='default')
> > +        gateway = ipaddress.ip_address(defroute['gateway'])
> > +        assert_eq(gateway, simnet.gw_ip6_ll.ip)
> > +
> > +
> > +@test
> > +def test_config_net_mtu():
> > +    with pasta_configured() as (_, ns):
> > +        mtu = ns.mtu(IFNAME)
> > +        assert_eq(mtu, 65520)
> 
> these all make intuitive sense to me.
> 
> > +
> > +
> > +@test_transfers(ip4=REMOTE_IP4.ip, ip6=REMOTE_IP6.ip, port=10000)
> > +@contextlib.contextmanager
> > +def outward_transfer():
> > +    with pasta_configured() as (simnet, ns):
> > +        yield ns, simnet.gw
> 
> This, however, not. I would naturally expect the function implementing
> a template of data transfer test to do something with data.

I'm not entirely sure what you mean here.  Would the same treatment as
for the ndp and dhcp cases above help?

	@contextlib.contextmanager
	def outward_transfer():
	    with pasta_configured() as (simnet, ns):
	        yield TransferTestScenario(client=ns,
	                                   server=simnet.gw,
	                                   connect_ip4=REMOTE_IP4.ip,
	                                   connect_ip6=REMOTE_IP6.ip,
					   connect_port=10000)

	# variants for inward transfer, and spliced_transfer
	compose_matrix([outward_transfer, inward_transfer, spliced_transfer],
	               TRANSFER_TESTS)

> > +
> > +
> > +@test_transfers(ip4=IP4.ip, ip6=IP6.ip, port=IN_FWD_PORT,
> > +                fromip4=REMOTE_IP4.ip, fromip6=REMOTE_IP6.ip)
> > +@contextlib.contextmanager
> > +def inward_transfer():
> > +    with pasta_configured() as (simnet, ns):
> > +        yield simnet.gw, ns
> > +
> > +
> > +@test_transfers(ip4=LOOPBACK4, ip6=LOOPBACK6, port=SPLICE_FWD_PORT,
> > +                listenip4=LOOPBACK4, listenip6=LOOPBACK6)
> > +@contextlib.contextmanager
> > +def spliced_transfer():
> > +    with pasta_configured() as (simnet, ns):
> > +        yield ns, simnet.simhost
> 
> I still have to decode these before I can reasonably comment on them.
> 
> I'll follow up with a more detailed proposal of a possible
> configuration format or perhaps something between a domain-specific
> language and an annotated Python script (à la bats), but I'm trying to
> consider a few more tests on top of these.
> 
> I started looking at "options" tests next, and realised my proposal was
> a bit too simplistic. But also that the current version of those tests,
> while somewhat verbose and surely clunky, shows in a very clear way
> what we're checking and what we expect... and I'm not sure that the
> outcome from extending pasta_configured() would be very usable.

So, I was only thinking of pasta_configured() and pasta_unconfigured()
as useful for the pretty specific pattern of pasta invocations in this
batch of tests (which is why they're local to that file).  I've also
had at pasta_options (which I'm thinking of as the log-to-file tests,
since that's all they test so far).  I was planning a new helper to
set up a suitable scenario for that, which would be based on
simple_net() and Pasta() with some kind of parameterization for where
to place the logfile.

> All we need for that is:
> 
> - define options and network model/addressing

I think that short statement hides a lot of complexity.

> - have a clear syntax of identifying where pasta is starting
> 
> - a list of commands (which, themselves, are absolutely obvious and
>   natural in a shell script, but I see the value of using Python
>   features to check assertions, at least).

-- 
David Gibson			| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you.  NOT _the_ _other_
				| _way_ _around_!
http://www.ozlabs.org/~dgibson

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 27/27] avocado: Convert basic pasta tests
  2023-07-05  3:27     ` David Gibson
@ 2023-07-07 17:42       ` Stefano Brivio
  2023-07-10  7:45         ` David Gibson
  0 siblings, 1 reply; 32+ messages in thread
From: Stefano Brivio @ 2023-07-07 17:42 UTC (permalink / raw)
  To: David Gibson; +Cc: passt-dev, crosa, jarichte

On Wed, 5 Jul 2023 13:27:17 +1000
David Gibson <david@gibson.dropbear.id.au> wrote:

> On Wed, Jul 05, 2023 at 02:30:48AM +0200, Stefano Brivio wrote:
> > Sorry for the delay, some (partial) feedback and a few ideas:
> > 
> > On Tue, 27 Jun 2023 12:54:28 +1000
> > David Gibson <david@gibson.dropbear.id.au> wrote:
> >   
> > > Convert the old-style tests for pasta (DHCP, NDP, TCP and UDP transfers)
> > > to using avocado.  There are a few differences in what we test, but this
> > > should generally improve coverage:
> > > 
> > >  * We run in a constructed network environment, so we no longer depend on
> > >    the real host's networking configuration
> > >  * We do independent setup for each individual test
> > >  * We add explicit tests for --config-net, which we use to accelerate that
> > >    setup for the TCP and UDP tests
> > >  * The TCP and UDP tests now test transfers between the guest and a
> > >    (simulated) remote site that's on a different network from the simulated
> > >    pasta host.  This better matches the typical passt/pasta usecase  
> > 
> > ...this is not necessarily true -- it really depends, but sure, it's
> > important to have this too.  
> 
> Yeah, I guess not.  My point here is that given that we're generally
> trying to avoid NAT, it seems to me we should primarily test the
> no-NAT case, then have specific tests for the NAT cases.  I'll try to
> find a better way to phrase that.

I don't think it's really needed, I just wanted to make sure we have a
common understanding.

> > > Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
> > > ---
> > >  oldtest/run                   |  14 ++--
> > >  test/Makefile                 |   1 +
> > >  test/avocado/pasta.py         | 129 ++++++++++++++++++++++++++++++++++
> > >  test/lib/layout               |  31 --------
> > >  test/lib/setup                |  40 -----------
> > >  test/pasta/dhcp               |  46 ------------
> > >  test/pasta/ndp                |  33 ---------
> > >  test/pasta/tcp                |  96 -------------------------
> > >  test/pasta/udp                |  59 ----------------
> > >  test/run                      |   8 ---
> > >  test/tasst/dhcpv6.py          |   4 +-
> > >  test/tasst/pasta.py           |  42 +++++++++++
> > >  test/tasst/scenario/simple.py |  44 ++++++------
> > >  13 files changed, 203 insertions(+), 344 deletions(-)
> > >  create mode 100644 test/avocado/pasta.py
> > >  delete mode 100644 test/pasta/dhcp
> > >  delete mode 100644 test/pasta/ndp
> > >  delete mode 100644 test/pasta/tcp
> > >  delete mode 100644 test/pasta/udp
> > >  create mode 100644 test/tasst/pasta.py
> > > 
> > > diff --git a/oldtest/run b/oldtest/run
> > > index a16bc49b..f1157f90 100755
> > > --- a/oldtest/run
> > > +++ b/oldtest/run
> > > @@ -70,13 +70,13 @@ run() {
> > >  	test build/clang_tidy
> > >  	teardown build
> > >  
> > > -#	setup pasta
> > > -#	test pasta/ndp
> > > -#	test pasta/dhcp
> > > -#	test pasta/tcp
> > > -#	test pasta/udp
> > > -#	test passt/shutdown
> > > -#	teardown pasta
> > > +	setup pasta
> > > +	test pasta/ndp
> > > +	test pasta/dhcp
> > > +	test pasta/tcp
> > > +	test pasta/udp
> > > +	test passt/shutdown
> > > +	teardown pasta
> > >  
> > >  #	setup pasta_options
> > >  #	test pasta_options/log_to_file
> > > diff --git a/test/Makefile b/test/Makefile
> > > index 953eacf2..9c3114c2 100644
> > > --- a/test/Makefile
> > > +++ b/test/Makefile
> > > @@ -227,6 +227,7 @@ $(VENV):
> > >  
> > >  .PHONY: avocado-assets
> > >  avocado-assets: nstool small.bin medium.bin big.bin
> > > +	$(MAKE) -C .. pasta
> > >  
> > >  .PHONY: avocado
> > >  avocado: avocado-assets $(VENV)
> > > diff --git a/test/avocado/pasta.py b/test/avocado/pasta.py
> > > new file mode 100644
> > > index 00000000..891313f5
> > > --- /dev/null
> > > +++ b/test/avocado/pasta.py  
> > 
> > I'm just focusing on this for the moment, as this is the part I'm mostly
> > concerned about (writing tests), with a particular accent on what makes
> > this (still) read-only _code_ for me, and the gaps with the kind of
> > things I would have naturally expected to write.
> > 
> > That is, I'm mostly isolating negative aspects here.
> > 
> > I'm stressing "code" because I also would have the natural expectation
> > that it should/must be simpler to _write_ (not necessarily design) tests
> > rather than the code that end users use, because we can use whatever
> > language with no strict (only some) constraints on resources and speed.
> > So in my opinion it doesn't necessarily need to be "code" (and a
> > general feeling I have from this is that it really is code).  
> 
> Right, I see/share your concern.  To some extent there's an
> unavoidable trade-off here.  Avoiding lots of repetition in the test
> cases, leads to "code" in some sense: we need something resembling
> function calls and loops at least to re-invoke standard test pieces.

True, even though there's also the approach, quite commonly implemented
in test suites, of mixing actual code with custom directives or
annotations (e.g. bats does that) to keep things a bit simpler or more
descriptive. But I can't come up with a reasonably good way here, and
it's probably doable to also add this later on, maybe in form of
pre-processing.

> That's not to say we can't make it better than it is in this draft.
> 
> > > @@ -0,0 +1,129 @@
> > > +#! /usr/bin/env avocado-runner-avocado-classless
> > > +
> > > +# SPDX-License-Identifier: GPL-2.0-or-later
> > > +#
> > > +# Copyright Red Hat
> > > +# Author: David Gibson <david@gibson.dropbear.id.au>
> > > +
> > > +"""
> > > +avocado/pasta.py - Basic tests for pasta mode
> > > +"""
> > > +
> > > +import contextlib
> > > +import ipaddress
> > > +import os
> > > +import tempfile  
> > 
> > So far so good, I probably don't need to read up about every single
> > import.
> >   
> > > +from avocado_classless.test import assert_eq, assert_eq_unordered, test  
> > 
> > Probably a future source of (needless) copy-and-paste.  
> 
> Fair point.  I was considering making the idiom here:
> 	from avocado_classless.test import *
> Which will import "all" (in fact a curated list) of things from the
> module for use here.  I didn't go that way because pylint whinged
> about it, but we could suppress that easily enough.

Ah, that's nicer. One would still copy and paste it, but without
thinking too much.

> > Of course,
> > assert_eq_unordered is not available in the current tests, and we need
> > it, plus a syntax to represent that.  
> 
> I'm not quite sure what you're getting at here.

I'm just saying that either we "magically" import things by
pre-processing test scripts or suchlike, or we have an explicit import,
but no matter what, we need that.

The current framework 1. doesn't implement anything like that and 2. it
wouldn't need an "import" anyway because it's a domain-specific
language. But here, we need that, and probably more, and I guess it's
reasonable.

> > Still, conceptually speaking, we shouldn't need this kind of import if
> > we are writing _a test for passt in the test suite for passt itself_,
> > and we want to check that the list of interfaces matches "lo" -- it's
> > something we'll need basically everywhere anyway.
> >   
> > > +from tasst.address import LOOPBACK4, LOOPBACK6  
> > 
> > On one hand, probably more readable than a 127.0.0.1 here and there
> > (even though longer than "::1"), on the other hand there are 256
> > loopback addresses in IPv4. And there is only one way of writing "::1",
> > but many ways of remembering/misspelling LOOPBACK6.  
> 
> Right... so "LOOPBACK6" is longer than "::1", but not than
> "ipaddress.ip_address('::1')".  That is, these helpers produce
> something that's in an actual IP address type, rather than a bare
> string.  I could try moving to just using strings for IP addresses
> throughout the tests, but that might cause some different messes.

To me treating it as string doesn't sound like a bad idea, except when
we want to do calculations/masks (I guess that would be the most
problematic part?)... but then we could probably convert to "addresses"
on the fly as needed?

> > > +from tasst.dhcp import test_dhcp
> > > +from tasst.dhcpv6 import test_dhcpv6
> > > +from tasst.ndp import test_ndp
> > > +from tasst.nstool import unshare_site
> > > +from tasst.pasta import Pasta
> > > +from tasst.scenario.simple import (
> > > +    simple_net, IFNAME, HOST_NET6, IP4, IP6, GW_IP4, REMOTE_IP4, REMOTE_IP6
> > > +)
> > > +from tasst.transfer import test_transfers  
> > 
> > Similar for this -- it would be great to have an infrastructure where
> > we can just use what we need without looking it up. Rather minor
> > though.  
> 
> Right, I could introduce "from foo import *" conventions for some of
> these too.
> 
> > > +
> > > +
> > > +IN_FWD_PORT = 10002
> > > +SPLICE_FWD_PORT = 10003
> > > +FWD_OPTS = f'-t {IN_FWD_PORT} -u {IN_FWD_PORT} \
> > > +             -T {SPLICE_FWD_PORT} -U {SPLICE_FWD_PORT}'  
> > 
> > I think clear enough.
> >   
> > > +
> > > +@contextlib.contextmanager  
> > 
> > About this decorator: I now have a vague idea what it means after
> > reading the full series and some Avocado documentation... but I don't
> > find that very descriptive.  
> 
> Yeah.  I agree that this is uncomfortably cryptic, however this one I
> don't see an easy way to remove.  I heavily use this to conveniently
> modify and extend context managers.  It allows two things which are
> both very convenient:
> 
> 1) You can do:
> 
> 	@contextlib.contextmanager
> 	def foo(...):
> 	    setup()
> 
> 	    yield whatever   # <== actual test runs here
> 
> 	    cleanup()
> 
> this will do the cleanup() even if the test blows up.
> 
> 2) You can do
> 
> 	@contextlib.contextmanager
> 	def bar(...):
> 	    with other_context_1():
> 	        with other_context_2():
> 		    yield whatever    # <=== actual test runs here
> 
> This effectively makes this a combination of the two existing
> contexts, again ensuring that both their cleanup happens if the test
> blows up.
> 
> It's certainly possible to do this with direct implementations of the
> contextmanager protocol but it's much more verbose, and not
> significantly less esoteric.

Right -- maybe a comment could help, then.

> > > +def pasta_unconfigured(opts=FWD_OPTS):  
> > 
> > Clear.
> >   
> > > +    with simple_net() as simnet:  
> > 
> > Also clear.
> >   
> > > +        with unshare_site('pastans', '-Ucnpf --mount-proc',
> > > +                          parent=simnet.simhost, sudo=True) as guestns:  
> > 
> > If I ignore for a moment the implementation detail, it's not very
> > intuitive why this is nested in the "with" statement before.  
> 
> Hmm.. I'm not sure what's throwing you on this specifically.  We need
> both pieces of setup for our test, so we need to nest them within each
> other.  They have to go in this order because the parameters to
> unshare_site() need something from the simnet.

Oh, I didn't realise that unshare_site() needs stuff from simple_net().
-- then this makes sense.

And yes, this itself would be clearer in my opinion if we then had
"tempfile.TemporaryDirectory()" on the same level as you mention later,
because otherwise I'm reading it as something nested, because the
language wants us to do that. It's not the case, these two are
conceptually nested.

I still think it would be easier to read this nesting in a YAML file or
suchlike. But of course this part of a different (and larger) problem,
so maybe we can just live with this for the moment...

> > Those parameters require a complete understanding of unshare_site(),  
> 
> Hm, ok.  I could require that this takes keyword arguments, so it
> would be something like:
>     unshare_site(name='pastans', unshare_opts='-Ucnpf --mount-proc',
>                  ..)
> 
> Would that help?	

Yes, definitely, a bit more to type, but absolutely understandable.

> > and... I now know what "sudo=True" means in Avocado, but I don't
> > even have sudo on my machine, so at a distracted look I would think I
> > need to install it and configure sudoers (which would make me as a
> > developer skip the tests).  
> 
> Yeah.  And it's arguably not entirely accurate either.  I could rework
> the 'site' stuff so that I call this, say, 'capable' or 'keep_caps'
> instead.  Would that help?

'keep_caps' does what it says, that would definitely help.

> > So far, what this says is:
> > 
> > - use addressing from simple_net()  
> 
> Hrm, it's more than that: simple_net() is actually *creating* the
> simulated simple network (and cleaning it up once the with clause
> ends).

Yes, okay, fair point.

> > - detach user and network namespace, remounting proc in it, preserve (I
> >   guess?) credentials  
> 
> Well, if by "preserve credentials" you mean "keep the capabilities you
> have because you own the namespace", yes (this is equivalent to
> "nsenter --keep-caps", not "nsenter --preserve-credentials").

Right, sorry, I meant capabilities. While at it, would it perhaps make
sense to build unshare(1) options in unshare_site() itself?

> > > +            with tempfile.TemporaryDirectory() as tmpdir:  
> > 
> > ...even less intuitive why this statement (tmpdir="$(mktemp -d)") is
> > nested.  
> 
> So it's not quite equivalent to tmpdir=$(mktemp -d): it does that at
> the start of the with, but it also removes the directory at the end of
> the with.

Okay, fair, POSIX shell doesn't have that (it has traps, but you need
to pop/push stuff from them, which means it's less terse anyway).

> We need the network constructed, the namespace created and the
> temporary directory available all at the same time.  So the scope of
> each 'with' context needs to overlap - in other words to be nested.
> In this case the order of nesting doesn't matter, we could put the
> temporary directory 'with' on the outside.
> 
> >   
> > > +                pidfile = os.path.join(tmpdir, 'pasta.pid')
> > > +
> > > +                with Pasta(simnet.simhost, pidfile, opts, ns=guestns) as pasta:
> > > +                    yield simnet, pasta.ns  
> > 
> > ...and this finally says: start pasta in that (user? network?)
> > namespace.  
> 
> Yes, specifically, start pasta with it's "host" being simnet.simhost,
> and its "guest" being guestns.  Again I could enforce keyword
> parameters to make the meaning of the arguments a bit clearer.
> 
> [Aside, I plan to extent Pasta() so that if you don't pass a guest ns
> it will default to having pasta create its own - that ns will still be
> available as 'pasta.ns', so the rest of the stuff can remain the same]
> 
> Then the "yield" essentially means "now that we've done the setup,
> here is where to run the actual specific test".

This last part is quite hard for me to grasp. But maybe an
over-commented test "template" would fix it.

> > So, from my expectation, pseudocode:
> > 
> > setup:
> >   net: simple_net
> >   pasta_options: -t 10002 -T 10003 -u 10002 -U 10003
> >   start pasta  
> 
> Hrm, that pseudocode assumes a pretty specific set of possible setups,
> and how they relate to each other.  For example I can't see how it
> would extend to running two different pasta instances on different
> simulated hosts.

setup:
   net: not_so_simple_net
   site_a:
     pasta_options: -t 10002 -T 10003 -u 10002 -U 10003
     start pasta
   site_b:
     pasta_options: -t 10012 -T 10013 -u 10012 -U 10013
     start pasta

?

>  That's possible with the approach I'm using here.

Sure, and it's less effort compared to what I'm describing, I know.

> > and from my understanding of what this is replacing:
> > 
> > setup_pasta() {
> > 	context_setup_host unshare
> > 
> > 	context_run_bg unshare "unshare -rUnpf ${NSTOOL} hold ${STATESETUP}/ns.hold"
> > 
> > 	context_setup_nstool ns ${STATESETUP}/ns.hold
> > 
> > 	# Ports:
> > 	#
> > 	#                 ns        |         host
> > 	#         ------------------|---------------------
> > 	#  10002      as server     |    spliced to ns
> > 	#  10003   spliced to init  |      as server
> > 
> > 	context_run_bg passt "./pasta -f -t 10002 -T 10003 -u 10002 -U 10003 -P ${STATESETUP}/passt.pid $(${NSTOOL} info -pw ${STATESETUP}/ns.hold)"
> > }  
> 
> It's replacing not just that, but also the matching teardown function,
> and ensures that the teardown runs if there's an exception in the test
> itself (including running the teardowns for the outer contexts if the
> setup of the inner contexts failed).

Still, I think the equivalent in POSIX shell (we kind of have it
already) would be clearer. Obvious downside: a lot more to write, and
it's bug-prone. Maybe we could solve this particular issue by a very
verbosely documented example.

> > which I actually find less elegant but much clearer, especially before
> > reading the whole series, I wonder if we could have either a
> > configuration format, or directives... but, disappointingly, I couldn't
> > decide on a more detailed proposal yet.
> > 
> > Still, I would aim at something resembling that
> > pseudocode/configuration above. Of course, with no details it looks
> > neat, so the comparison is unfair, but I'm under the impression that
> > even after adding details (that's what I'm trying to figure
> > out/articulate) it should still be much clearer.  
> 
> I'm just not convinced that it would be, without losing a great deal
> of the flexibility in this system.  Or at least, not clearer than a
> more polished version of this proposal.
> 
> > > +
> > > +
> > > +@test
> > > +def test_ifname():  
> > 
> > I guess fine (even though it's missing a descriptive name _inline_).  
> 
> We could put a description in a docstring easily enough.

I guess needed.

> > > +    with pasta_unconfigured() as (_, ns):  
> > 
> > Ouch... a bit hard to grasp for people not familiar with Python or
> > Go... plus, conceptually, we _just_ want to say either:  
> 
> Yeah.  One of the bits I'm least happy with is what to do in cases
> where the information the setup needs to pass to the tests proper is
> quite complex: so here it has both the simulated network which is
> hosting the pasta instance, and also the namespace within pasta.  This
> specific test doesn't need the former, hence the _.  We could use a
> suitably named dummy variable which might be a bit more readable,
> though it would need a lint suppression (unused variable).
> 
> I've considered another approach, though I'm not sure it's an
> improvement, which is to use more intermediate structs/classes.  So,
> in that approach pasta_unconfigured() would give you a
> 'PastaTestScenario' instance with fields for the guest ns, host ns and
> whatever else seems useful.

I'm not entirely sure what that entails, but I guess it might be more
readable overall (also because we'll probably need more fields...? How
do you explicitly get a reference to a PID, for example?)

> > test: "pasta namespace has the interface name we configured"
> >   enter namespace
> >   list of interfaces includes $NAME
> > 
> > or:
> > 
> > test: "pasta namespace has a loopback interface"
> >   list of interfaces in namespace includes $NAME  
> 
> It's more than that though: we're also requesting a very specific
> (simulated) environment for the test, which the pseudocode above just
> kind of assumes.  'pasta_unconfigured()' (name certainly negotiable)
> is handling the setup and teardown of everything we need for a certain
> class of pasta tests.  That has 3 namespaces, a veth, and a pasta
> instance, all configured in a particular way.
> 
> > ...and yes, that requires that we start pasta first, but I wonder if it
> > makes sense to mix the two notions (i.e. whether an object-oriented
> > approach makes sense at all, actually).  
> 
> So the OO approach works ok for pretty simple setups - probably why it
> became popular with jUnit etc: the class handles the setup, the
> methods do the specific tests.  But it breaks down pretty quickly:
>    - If you want to use multiple "setup" pieces at once you need to
>      use multiple inheritence, which is a nightmare

Argh. Yes.

>    - If you want to parameterize parts of the setup, you need to do it
>      at the whole class level which involves wierd non-local
>      interactions
>    - If you want to use multiple setup pieces which are actually the
>      same piece repeated with different parameters, you're pretty much
>      out of luck
> 
> I realize that context managers aren't the most obvious thing, but
> they have the huge advantage that every test can directly and
> reasonably tersely declare what setup they need to run:
> 
>     def test():
> 	with setup_i_need():
> 	    with other_setup_i_need():
> 	       with setup_i_need(param='non default'):
> 	           # actually do the test
> 
> This also handles the necessary teardown at each level, and it still
> works if some of the inner setups need paramaters that are derived
> fromt the outer setups.

Yes yes definitely.

> > > +        assert_eq_unordered(ns.ifs(), ('lo', IFNAME))  
> > 
> > Clear (probably clearer with a "list includes" operator).  
> 
> It's not the same as list includes (that's assert_in()).  This checks
> that the lists (strictly, iterables) are identical ignoring order.  So
> in this case ['lo', IFNAME] and [IFNAME, 'lo'] would be ok, but ['lo',
> 'eth99', IFNAME] would not be.
> 
> > > +
> > > +
> > > +@test_ndp(IFNAME, HOST_NET6)
> > > +@contextlib.contextmanager
> > > +def pasta_ndp():
> > > +    with pasta_unconfigured() as (simnet, guestns):
> > > +        guestns.ifup(IFNAME)
> > > +        yield guestns, simnet.gw_ip6_ll.ip  
> > 
> > This really hides what we are checking. Other than that (but it's a big
> > issue in my opinion) I find it terse and elegant.  
> 
> Yeah, I'm not super happy with this one.  In particular I dislike the
> inconsistency: in simple tests like the above we have, loosely the
> setup, followed by the test.  In these composed examples we have the
> tests then the setup.  So combining a few options I've thought of, I
> could do something like this:
> 
> 	@contextlib.contextmanager
> 	def pasta_ndp():
> 	    with pasta_unconfigured() as (simnet, guestns):
> 	        guestns.ifup(IFNAME)
> 	        yield NdpTestScenario(ifname=IFNAME, net=HOST_NET6,
> 		                      router=simnet.gw_ip6_ll.ip,
> 				      ndpclient=guestns)
> 
> 	compose_matrix([pasta_ndp], NDP_TESTS)
> 
> Here NDP_TESTS would come from the ndp module and have the list of
> individual ndp test cases, each of which takes an NdpTestScenario as
> parameter.

Probably easier to understand, but it doesn't solve the problem that
"yield" doesn't mean "we're checking this". Maybe it could be worked
around with naming though, I haven't tried.

> > > +
> > > +@test_dhcp(IFNAME, IP4.ip, GW_IP4.ip, 65520)  
> > 
> > The test_ndp decorator is probably usable, this one not so much. As we
> > have more parameters, it would probably be better to have something
> > more descriptive (even if necessarily more verbose).  
> 
> Right.  So would the treatment above help?  Enough?  It would look
> something like:
> 
> 	@contextlib.contextmanager
> 	def pasta_dhcp():
> 	    ....
> 	    yield DhcpTestScenario(ifname=IFNAME, client_ip4=IP4.ip,
> 	                           server_ip4=GW_IP4.ip, mtu=65520)
> 
> 	compose_matrix([pasta_dhcp], DHCP_TESTS)

Much better, I think.

> > > +@test_dhcpv6(IFNAME, IP6.ip)
> > > +@contextlib.contextmanager
> > > +def pasta_dhcp():
> > > +    with pasta_unconfigured() as (_, guestns):
> > > +        yield guestns
> > > +
> > > +
> > > +@contextlib.contextmanager
> > > +def pasta_configured():
> > > +    with pasta_unconfigured(FWD_OPTS + ' --config-net') as (simnet, ns):
> > > +        # Wait for DAD to complete on the --config-net address  
> > 
> > Side note: this shouldn't be needed -- if it is, we should probably fix
> > something in pasta.  
> 
> It is needed, and yes we probably should, I haven't had a chance to
> look into what, exactly.  We may hit that kernel bug I noticed where
> permissions to the disable_dad sysctl seem to behave weirdly.

Ah, right, that rings a bell.

> > > +        ns.addr_wait(IFNAME, family='inet6', scope='global')
> > > +        yield simnet, ns  
> > 
> > Except for the common points with my observation above, this looks
> > usable, and:
> >   
> > > +
> > > +
> > > +@test
> > > +def test_config_net_addr():
> > > +    with pasta_configured() as (_, ns):
> > > +        addrs = ns.addrs(IFNAME, scope='global')
> > > +        assert_eq_unordered(addrs, [IP4, IP6])
> > > +
> > > +
> > > +@test
> > > +def test_config_net_route4():
> > > +    with pasta_configured() as (_, ns):
> > > +        (defroute,) = ns.routes4(dst='default')
> > > +        gateway = ipaddress.ip_address(defroute['gateway'])
> > > +        assert_eq(gateway, GW_IP4.ip)
> > > +
> > > +
> > > +@test
> > > +def test_config_net_route6():
> > > +    with pasta_configured() as (simnet, ns):
> > > +        (defroute,) = ns.routes6(dst='default')
> > > +        gateway = ipaddress.ip_address(defroute['gateway'])
> > > +        assert_eq(gateway, simnet.gw_ip6_ll.ip)
> > > +
> > > +
> > > +@test
> > > +def test_config_net_mtu():
> > > +    with pasta_configured() as (_, ns):
> > > +        mtu = ns.mtu(IFNAME)
> > > +        assert_eq(mtu, 65520)  
> > 
> > these all make intuitive sense to me.
> >   
> > > +
> > > +
> > > +@test_transfers(ip4=REMOTE_IP4.ip, ip6=REMOTE_IP6.ip, port=10000)
> > > +@contextlib.contextmanager
> > > +def outward_transfer():
> > > +    with pasta_configured() as (simnet, ns):
> > > +        yield ns, simnet.gw  
> > 
> > This, however, not. I would naturally expect the function implementing
> > a template of data transfer test to do something with data.  
> 
> I'm not entirely sure what you mean here.  Would the same treatment as
> for the ndp and dhcp cases above help?
> 
> 	@contextlib.contextmanager
> 	def outward_transfer():
> 	    with pasta_configured() as (simnet, ns):
> 	        yield TransferTestScenario(client=ns,
> 	                                   server=simnet.gw,
> 	                                   connect_ip4=REMOTE_IP4.ip,
> 	                                   connect_ip6=REMOTE_IP6.ip,
> 					   connect_port=10000)
> 
> 	# variants for inward transfer, and spliced_transfer
> 	compose_matrix([outward_transfer, inward_transfer, spliced_transfer],
> 	               TRANSFER_TESTS)

Yes, now I actually understand what outward_transfer() does: it calls
TransferTestScenario(). I really had no idea otherwise.

> > > +
> > > +
> > > +@test_transfers(ip4=IP4.ip, ip6=IP6.ip, port=IN_FWD_PORT,
> > > +                fromip4=REMOTE_IP4.ip, fromip6=REMOTE_IP6.ip)
> > > +@contextlib.contextmanager
> > > +def inward_transfer():
> > > +    with pasta_configured() as (simnet, ns):
> > > +        yield simnet.gw, ns
> > > +
> > > +
> > > +@test_transfers(ip4=LOOPBACK4, ip6=LOOPBACK6, port=SPLICE_FWD_PORT,
> > > +                listenip4=LOOPBACK4, listenip6=LOOPBACK6)
> > > +@contextlib.contextmanager
> > > +def spliced_transfer():
> > > +    with pasta_configured() as (simnet, ns):
> > > +        yield ns, simnet.simhost  
> > 
> > I still have to decode these before I can reasonably comment on them.
> > 
> > I'll follow up with a more detailed proposal of a possible
> > configuration format or perhaps something between a domain-specific
> > language and an annotated Python script (à la bats), but I'm trying to
> > consider a few more tests on top of these.
> > 
> > I started looking at "options" tests next, and realised my proposal was
> > a bit too simplistic. But also that the current version of those tests,
> > while somewhat verbose and surely clunky, shows in a very clear way
> > what we're checking and what we expect... and I'm not sure that the
> > outcome from extending pasta_configured() would be very usable.  
> 
> So, I was only thinking of pasta_configured() and pasta_unconfigured()
> as useful for the pretty specific pattern of pasta invocations in this
> batch of tests (which is why they're local to that file).  I've also
> had at pasta_options (which I'm thinking of as the log-to-file tests,
> since that's all they test so far).  I was planning a new helper to
> set up a suitable scenario for that, which would be based on
> simple_net() and Pasta() with some kind of parameterization for where
> to place the logfile.

Hmm, yes, I guess that would be better than the alternative I was afraid
of (extending pasta_configured()).

> > All we need for that is:
> > 
> > - define options and network model/addressing  
> 
> I think that short statement hides a lot of complexity.

Eh, yes, but if you take this part by itself, you wouldn't naturally
jump to Python decorators. :)

> > - have a clear syntax of identifying where pasta is starting
> > 
> > - a list of commands (which, themselves, are absolutely obvious and
> >   natural in a shell script, but I see the value of using Python
> >   features to check assertions, at least).  

-- 
Stefano


^ permalink raw reply	[flat|nested] 32+ messages in thread

* Re: [PATCH 27/27] avocado: Convert basic pasta tests
  2023-07-07 17:42       ` Stefano Brivio
@ 2023-07-10  7:45         ` David Gibson
  0 siblings, 0 replies; 32+ messages in thread
From: David Gibson @ 2023-07-10  7:45 UTC (permalink / raw)
  To: Stefano Brivio; +Cc: passt-dev, crosa, jarichte

[-- Attachment #1: Type: text/plain, Size: 35957 bytes --]

On Fri, Jul 07, 2023 at 07:42:39PM +0200, Stefano Brivio wrote:
> On Wed, 5 Jul 2023 13:27:17 +1000
> David Gibson <david@gibson.dropbear.id.au> wrote:
> 
> > On Wed, Jul 05, 2023 at 02:30:48AM +0200, Stefano Brivio wrote:
> > > Sorry for the delay, some (partial) feedback and a few ideas:
> > > 
> > > On Tue, 27 Jun 2023 12:54:28 +1000
> > > David Gibson <david@gibson.dropbear.id.au> wrote:
> > >   
> > > > Convert the old-style tests for pasta (DHCP, NDP, TCP and UDP transfers)
> > > > to using avocado.  There are a few differences in what we test, but this
> > > > should generally improve coverage:
> > > > 
> > > >  * We run in a constructed network environment, so we no longer depend on
> > > >    the real host's networking configuration
> > > >  * We do independent setup for each individual test
> > > >  * We add explicit tests for --config-net, which we use to accelerate that
> > > >    setup for the TCP and UDP tests
> > > >  * The TCP and UDP tests now test transfers between the guest and a
> > > >    (simulated) remote site that's on a different network from the simulated
> > > >    pasta host.  This better matches the typical passt/pasta usecase  
> > > 
> > > ...this is not necessarily true -- it really depends, but sure, it's
> > > important to have this too.  
> > 
> > Yeah, I guess not.  My point here is that given that we're generally
> > trying to avoid NAT, it seems to me we should primarily test the
> > no-NAT case, then have specific tests for the NAT cases.  I'll try to
> > find a better way to phrase that.
> 
> I don't think it's really needed, I just wanted to make sure we have a
> common understanding.

Ok.

> > > > Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
> > > > ---
> > > >  oldtest/run                   |  14 ++--
> > > >  test/Makefile                 |   1 +
> > > >  test/avocado/pasta.py         | 129 ++++++++++++++++++++++++++++++++++
> > > >  test/lib/layout               |  31 --------
> > > >  test/lib/setup                |  40 -----------
> > > >  test/pasta/dhcp               |  46 ------------
> > > >  test/pasta/ndp                |  33 ---------
> > > >  test/pasta/tcp                |  96 -------------------------
> > > >  test/pasta/udp                |  59 ----------------
> > > >  test/run                      |   8 ---
> > > >  test/tasst/dhcpv6.py          |   4 +-
> > > >  test/tasst/pasta.py           |  42 +++++++++++
> > > >  test/tasst/scenario/simple.py |  44 ++++++------
> > > >  13 files changed, 203 insertions(+), 344 deletions(-)
> > > >  create mode 100644 test/avocado/pasta.py
> > > >  delete mode 100644 test/pasta/dhcp
> > > >  delete mode 100644 test/pasta/ndp
> > > >  delete mode 100644 test/pasta/tcp
> > > >  delete mode 100644 test/pasta/udp
> > > >  create mode 100644 test/tasst/pasta.py
> > > > 
> > > > diff --git a/oldtest/run b/oldtest/run
> > > > index a16bc49b..f1157f90 100755
> > > > --- a/oldtest/run
> > > > +++ b/oldtest/run
> > > > @@ -70,13 +70,13 @@ run() {
> > > >  	test build/clang_tidy
> > > >  	teardown build
> > > >  
> > > > -#	setup pasta
> > > > -#	test pasta/ndp
> > > > -#	test pasta/dhcp
> > > > -#	test pasta/tcp
> > > > -#	test pasta/udp
> > > > -#	test passt/shutdown
> > > > -#	teardown pasta
> > > > +	setup pasta
> > > > +	test pasta/ndp
> > > > +	test pasta/dhcp
> > > > +	test pasta/tcp
> > > > +	test pasta/udp
> > > > +	test passt/shutdown
> > > > +	teardown pasta
> > > >  
> > > >  #	setup pasta_options
> > > >  #	test pasta_options/log_to_file
> > > > diff --git a/test/Makefile b/test/Makefile
> > > > index 953eacf2..9c3114c2 100644
> > > > --- a/test/Makefile
> > > > +++ b/test/Makefile
> > > > @@ -227,6 +227,7 @@ $(VENV):
> > > >  
> > > >  .PHONY: avocado-assets
> > > >  avocado-assets: nstool small.bin medium.bin big.bin
> > > > +	$(MAKE) -C .. pasta
> > > >  
> > > >  .PHONY: avocado
> > > >  avocado: avocado-assets $(VENV)
> > > > diff --git a/test/avocado/pasta.py b/test/avocado/pasta.py
> > > > new file mode 100644
> > > > index 00000000..891313f5
> > > > --- /dev/null
> > > > +++ b/test/avocado/pasta.py  
> > > 
> > > I'm just focusing on this for the moment, as this is the part I'm mostly
> > > concerned about (writing tests), with a particular accent on what makes
> > > this (still) read-only _code_ for me, and the gaps with the kind of
> > > things I would have naturally expected to write.
> > > 
> > > That is, I'm mostly isolating negative aspects here.
> > > 
> > > I'm stressing "code" because I also would have the natural expectation
> > > that it should/must be simpler to _write_ (not necessarily design) tests
> > > rather than the code that end users use, because we can use whatever
> > > language with no strict (only some) constraints on resources and speed.
> > > So in my opinion it doesn't necessarily need to be "code" (and a
> > > general feeling I have from this is that it really is code).  
> > 
> > Right, I see/share your concern.  To some extent there's an
> > unavoidable trade-off here.  Avoiding lots of repetition in the test
> > cases, leads to "code" in some sense: we need something resembling
> > function calls and loops at least to re-invoke standard test pieces.
> 
> True, even though there's also the approach, quite commonly implemented
> in test suites, of mixing actual code with custom directives or
> annotations (e.g. bats does that) to keep things a bit simpler or more
> descriptive. But I can't come up with a reasonably good way here, and
> it's probably doable to also add this later on, maybe in form of
> pre-processing.

Right... there's two aspects to that too.  In bats we have directives
giving the structure of the testsuite - that is listing which test is
which, then the contents of each test is more or less just code.  It
would also be possible to have helper directives within the test,
although in that case it doesn't really differ than much from just
being a function call in whatever language it is.

Is it one of those cases specifically you're looking at?  It would be
not that complex to redesign the "avocado-classless" plugin thingy to
be more declarative in terms of defining tests, which might make it
more appealing to you.  I think the major trade-off of doing so is
that that would force the tests to occupy a different file from
e.g. test library code.  That's no real change for the "real" tests of
passt & pasta, but would make things a bit more clunky for the tests
of the test library.

> > That's not to say we can't make it better than it is in this draft.
> > 
> > > > @@ -0,0 +1,129 @@
> > > > +#! /usr/bin/env avocado-runner-avocado-classless
> > > > +
> > > > +# SPDX-License-Identifier: GPL-2.0-or-later
> > > > +#
> > > > +# Copyright Red Hat
> > > > +# Author: David Gibson <david@gibson.dropbear.id.au>
> > > > +
> > > > +"""
> > > > +avocado/pasta.py - Basic tests for pasta mode
> > > > +"""
> > > > +
> > > > +import contextlib
> > > > +import ipaddress
> > > > +import os
> > > > +import tempfile  
> > > 
> > > So far so good, I probably don't need to read up about every single
> > > import.
> > >   
> > > > +from avocado_classless.test import assert_eq, assert_eq_unordered, test  
> > > 
> > > Probably a future source of (needless) copy-and-paste.  
> > 
> > Fair point.  I was considering making the idiom here:
> > 	from avocado_classless.test import *
> > Which will import "all" (in fact a curated list) of things from the
> > module for use here.  I didn't go that way because pylint whinged
> > about it, but we could suppress that easily enough.
> 
> Ah, that's nicer. One would still copy and paste it, but without
> thinking too much.

Ok, I've started moving towards that for the next draft.

> > > Of course,
> > > assert_eq_unordered is not available in the current tests, and we need
> > > it, plus a syntax to represent that.  
> > 
> > I'm not quite sure what you're getting at here.
> 
> I'm just saying that either we "magically" import things by
> pre-processing test scripts or suchlike, or we have an explicit import,
> but no matter what, we need that.
> 
> The current framework 1. doesn't implement anything like that and 2. it
> wouldn't need an "import" anyway because it's a domain-specific
> language. But here, we need that, and probably more, and I guess it's
> reasonable.
> 
> > > Still, conceptually speaking, we shouldn't need this kind of import if
> > > we are writing _a test for passt in the test suite for passt itself_,
> > > and we want to check that the list of interfaces matches "lo" -- it's
> > > something we'll need basically everywhere anyway.
> > >   
> > > > +from tasst.address import LOOPBACK4, LOOPBACK6  
> > > 
> > > On one hand, probably more readable than a 127.0.0.1 here and there
> > > (even though longer than "::1"), on the other hand there are 256
> > > loopback addresses in IPv4. And there is only one way of writing "::1",
> > > but many ways of remembering/misspelling LOOPBACK6.  
> > 
> > Right... so "LOOPBACK6" is longer than "::1", but not than
> > "ipaddress.ip_address('::1')".  That is, these helpers produce
> > something that's in an actual IP address type, rather than a bare
> > string.  I could try moving to just using strings for IP addresses
> > throughout the tests, but that might cause some different messes.
> 
> To me treating it as string doesn't sound like a bad idea, except when
> we want to do calculations/masks (I guess that would be the most
> problematic part?)... but then we could probably convert to "addresses"
> on the fly as needed?

I'll see what I can do along these lines.  One thing that might get
messy is the question of which things want a bare address (127.0.0.1),
and which things what an address+netmask (127.0.0.1/8).  Although,
TBH, that's already kinda messy.

> > > > +from tasst.dhcp import test_dhcp
> > > > +from tasst.dhcpv6 import test_dhcpv6
> > > > +from tasst.ndp import test_ndp
> > > > +from tasst.nstool import unshare_site
> > > > +from tasst.pasta import Pasta
> > > > +from tasst.scenario.simple import (
> > > > +    simple_net, IFNAME, HOST_NET6, IP4, IP6, GW_IP4, REMOTE_IP4, REMOTE_IP6
> > > > +)
> > > > +from tasst.transfer import test_transfers  
> > > 
> > > Similar for this -- it would be great to have an infrastructure where
> > > we can just use what we need without looking it up. Rather minor
> > > though.  
> > 
> > Right, I could introduce "from foo import *" conventions for some of
> > these too.
> > 
> > > > +
> > > > +
> > > > +IN_FWD_PORT = 10002
> > > > +SPLICE_FWD_PORT = 10003
> > > > +FWD_OPTS = f'-t {IN_FWD_PORT} -u {IN_FWD_PORT} \
> > > > +             -T {SPLICE_FWD_PORT} -U {SPLICE_FWD_PORT}'  
> > > 
> > > I think clear enough.
> > >   
> > > > +
> > > > +@contextlib.contextmanager  
> > > 
> > > About this decorator: I now have a vague idea what it means after
> > > reading the full series and some Avocado documentation... but I don't
> > > find that very descriptive.  
> > 
> > Yeah.  I agree that this is uncomfortably cryptic, however this one I
> > don't see an easy way to remove.  I heavily use this to conveniently
> > modify and extend context managers.  It allows two things which are
> > both very convenient:
> > 
> > 1) You can do:
> > 
> > 	@contextlib.contextmanager
> > 	def foo(...):
> > 	    setup()
> > 
> > 	    yield whatever   # <== actual test runs here
> > 
> > 	    cleanup()
> > 
> > this will do the cleanup() even if the test blows up.
> > 
> > 2) You can do
> > 
> > 	@contextlib.contextmanager
> > 	def bar(...):
> > 	    with other_context_1():
> > 	        with other_context_2():
> > 		    yield whatever    # <=== actual test runs here
> > 
> > This effectively makes this a combination of the two existing
> > contexts, again ensuring that both their cleanup happens if the test
> > blows up.
> > 
> > It's certainly possible to do this with direct implementations of the
> > contextmanager protocol but it's much more verbose, and not
> > significantly less esoteric.
> 
> Right -- maybe a comment could help, then.

Hrm.. where, though.  The point is that this is a technique I'm using
heavily throughout, not just in a particular place.

> > > > +def pasta_unconfigured(opts=FWD_OPTS):  
> > > 
> > > Clear.
> > >   
> > > > +    with simple_net() as simnet:  
> > > 
> > > Also clear.
> > >   
> > > > +        with unshare_site('pastans', '-Ucnpf --mount-proc',
> > > > +                          parent=simnet.simhost, sudo=True) as guestns:  
> > > 
> > > If I ignore for a moment the implementation detail, it's not very
> > > intuitive why this is nested in the "with" statement before.  
> > 
> > Hmm.. I'm not sure what's throwing you on this specifically.  We need
> > both pieces of setup for our test, so we need to nest them within each
> > other.  They have to go in this order because the parameters to
> > unshare_site() need something from the simnet.
> 
> Oh, I didn't realise that unshare_site() needs stuff from simple_net().
> -- then this makes sense.

Right.  This (so far) is the case where we explicitly create a guest
ns for pasta, rather than having pasta make one for us.  We want that
to be a child of the ns which is simulating the "host" for pasta, and
that "simhost" ns is created as part of simple_net().

> And yes, this itself would be clearer in my opinion if we then had
> "tempfile.TemporaryDirectory()" on the same level as you mention later,
> because otherwise I'm reading it as something nested, because the
> language wants us to do that. It's not the case, these two are
> conceptually nested.

So, in this case I nested it because I hadn't hit the bits that needed
the tmpdir until this point.  I probably could get both the simple_net
and the tmpdir as part of the same outermost 'with'.  I'll take a look.

> I still think it would be easier to read this nesting in a YAML file or
> suchlike. But of course this part of a different (and larger) problem,
> so maybe we can just live with this for the moment...
> 
> > > Those parameters require a complete understanding of unshare_site(),  
> > 
> > Hm, ok.  I could require that this takes keyword arguments, so it
> > would be something like:
> >     unshare_site(name='pastans', unshare_opts='-Ucnpf --mount-proc',
> >                  ..)
> > 
> > Would that help?	
> 
> Yes, definitely, a bit more to type, but absolutely understandable.

Ok.  I'll move towards using keyword arguments everywhere.

> > > and... I now know what "sudo=True" means in Avocado, but I don't
> > > even have sudo on my machine, so at a distracted look I would think I
> > > need to install it and configure sudoers (which would make me as a
> > > developer skip the tests).  
> > 
> > Yeah.  And it's arguably not entirely accurate either.  I could rework
> > the 'site' stuff so that I call this, say, 'capable' or 'keep_caps'
> > instead.  Would that help?
> 
> 'keep_caps' does what it says, that would definitely help.

Ok, I'll have a look at this.

> > > So far, what this says is:
> > > 
> > > - use addressing from simple_net()  
> > 
> > Hrm, it's more than that: simple_net() is actually *creating* the
> > simulated simple network (and cleaning it up once the with clause
> > ends).
> 
> Yes, okay, fair point.
> 
> > > - detach user and network namespace, remounting proc in it, preserve (I
> > >   guess?) credentials  
> > 
> > Well, if by "preserve credentials" you mean "keep the capabilities you
> > have because you own the namespace", yes (this is equivalent to
> > "nsenter --keep-caps", not "nsenter --preserve-credentials").
> 
> Right, sorry, I meant capabilities. While at it, would it perhaps make
> sense to build unshare(1) options in unshare_site() itself?

You mean take some sort of Python description of what we want to
unshare, then generate the unshare(1) options from that?  I guess we
could, I'm just a bit dubious about the worth of doing that when we
don't actually need to do anything other than pass some options
verbatim to unshare(1).

> > > > +            with tempfile.TemporaryDirectory() as tmpdir:  
> > > 
> > > ...even less intuitive why this statement (tmpdir="$(mktemp -d)") is
> > > nested.  
> > 
> > So it's not quite equivalent to tmpdir=$(mktemp -d): it does that at
> > the start of the with, but it also removes the directory at the end of
> > the with.
> 
> Okay, fair, POSIX shell doesn't have that (it has traps, but you need
> to pop/push stuff from them, which means it's less terse anyway).

Right, and it's at the granularity of a whole script/process.  'with'
gives us full control of the scope in which we need the setup.

> > We need the network constructed, the namespace created and the
> > temporary directory available all at the same time.  So the scope of
> > each 'with' context needs to overlap - in other words to be nested.
> > In this case the order of nesting doesn't matter, we could put the
> > temporary directory 'with' on the outside.
> > 
> > >   
> > > > +                pidfile = os.path.join(tmpdir, 'pasta.pid')
> > > > +
> > > > +                with Pasta(simnet.simhost, pidfile, opts, ns=guestns) as pasta:
> > > > +                    yield simnet, pasta.ns  
> > > 
> > > ...and this finally says: start pasta in that (user? network?)
> > > namespace.  
> > 
> > Yes, specifically, start pasta with it's "host" being simnet.simhost,
> > and its "guest" being guestns.  Again I could enforce keyword
> > parameters to make the meaning of the arguments a bit clearer.
> > 
> > [Aside, I plan to extent Pasta() so that if you don't pass a guest ns
> > it will default to having pasta create its own - that ns will still be
> > available as 'pasta.ns', so the rest of the stuff can remain the same]
> > 
> > Then the "yield" essentially means "now that we've done the setup,
> > here is where to run the actual specific test".
> 
> This last part is quite hard for me to grasp. But maybe an
> over-commented test "template" would fix it.
> 
> > > So, from my expectation, pseudocode:
> > > 
> > > setup:
> > >   net: simple_net
> > >   pasta_options: -t 10002 -T 10003 -u 10002 -U 10003
> > >   start pasta  
> > 
> > Hrm, that pseudocode assumes a pretty specific set of possible setups,
> > and how they relate to each other.  For example I can't see how it
> > would extend to running two different pasta instances on different
> > simulated hosts.
> 
> setup:
>    net: not_so_simple_net
>    site_a:
>      pasta_options: -t 10002 -T 10003 -u 10002 -U 10003
>      start pasta
>    site_b:
>      pasta_options: -t 10012 -T 10013 -u 10012 -U 10013
>      start pasta
> 
> ?
> 
> >  That's possible with the approach I'm using here.
> 
> Sure, and it's less effort compared to what I'm describing, I know.

Right.  Basically I feel like something processing this would more or
less be translating a heirarchy of thingies in yaml to a heirarchy of
function calls to do the setup.  That seems like a fair bit of
boilerplate hassle to achieve a pretty small improvement in syntax
readability over just making those function calls directly.

> > > and from my understanding of what this is replacing:
> > > 
> > > setup_pasta() {
> > > 	context_setup_host unshare
> > > 
> > > 	context_run_bg unshare "unshare -rUnpf ${NSTOOL} hold ${STATESETUP}/ns.hold"
> > > 
> > > 	context_setup_nstool ns ${STATESETUP}/ns.hold
> > > 
> > > 	# Ports:
> > > 	#
> > > 	#                 ns        |         host
> > > 	#         ------------------|---------------------
> > > 	#  10002      as server     |    spliced to ns
> > > 	#  10003   spliced to init  |      as server
> > > 
> > > 	context_run_bg passt "./pasta -f -t 10002 -T 10003 -u 10002 -U 10003 -P ${STATESETUP}/passt.pid $(${NSTOOL} info -pw ${STATESETUP}/ns.hold)"
> > > }  
> > 
> > It's replacing not just that, but also the matching teardown function,
> > and ensures that the teardown runs if there's an exception in the test
> > itself (including running the teardowns for the outer contexts if the
> > setup of the inner contexts failed).
> 
> Still, I think the equivalent in POSIX shell (we kind of have it
> already) would be clearer. Obvious downside: a lot more to write, and
> it's bug-prone. Maybe we could solve this particular issue by a very
> verbosely documented example.
> 
> > > which I actually find less elegant but much clearer, especially before
> > > reading the whole series, I wonder if we could have either a
> > > configuration format, or directives... but, disappointingly, I couldn't
> > > decide on a more detailed proposal yet.
> > > 
> > > Still, I would aim at something resembling that
> > > pseudocode/configuration above. Of course, with no details it looks
> > > neat, so the comparison is unfair, but I'm under the impression that
> > > even after adding details (that's what I'm trying to figure
> > > out/articulate) it should still be much clearer.  
> > 
> > I'm just not convinced that it would be, without losing a great deal
> > of the flexibility in this system.  Or at least, not clearer than a
> > more polished version of this proposal.
> > 
> > > > +
> > > > +
> > > > +@test
> > > > +def test_ifname():  
> > > 
> > > I guess fine (even though it's missing a descriptive name _inline_).  
> > 
> > We could put a description in a docstring easily enough.
> 
> I guess needed.

I'm not sure what you mean by that.

> > > > +    with pasta_unconfigured() as (_, ns):  
> > > 
> > > Ouch... a bit hard to grasp for people not familiar with Python or
> > > Go... plus, conceptually, we _just_ want to say either:  
> > 
> > Yeah.  One of the bits I'm least happy with is what to do in cases
> > where the information the setup needs to pass to the tests proper is
> > quite complex: so here it has both the simulated network which is
> > hosting the pasta instance, and also the namespace within pasta.  This
> > specific test doesn't need the former, hence the _.  We could use a
> > suitably named dummy variable which might be a bit more readable,
> > though it would need a lint suppression (unused variable).
> > 
> > I've considered another approach, though I'm not sure it's an
> > improvement, which is to use more intermediate structs/classes.  So,
> > in that approach pasta_unconfigured() would give you a
> > 'PastaTestScenario' instance with fields for the guest ns, host ns and
> > whatever else seems useful.
> 
> I'm not entirely sure what that entails, but I guess it might be more
> readable overall (also because we'll probably need more fields...? How
> do you explicitly get a reference to a PID, for example?)
> 
> > > test: "pasta namespace has the interface name we configured"
> > >   enter namespace
> > >   list of interfaces includes $NAME
> > > 
> > > or:
> > > 
> > > test: "pasta namespace has a loopback interface"
> > >   list of interfaces in namespace includes $NAME  
> > 
> > It's more than that though: we're also requesting a very specific
> > (simulated) environment for the test, which the pseudocode above just
> > kind of assumes.  'pasta_unconfigured()' (name certainly negotiable)
> > is handling the setup and teardown of everything we need for a certain
> > class of pasta tests.  That has 3 namespaces, a veth, and a pasta
> > instance, all configured in a particular way.
> > 
> > > ...and yes, that requires that we start pasta first, but I wonder if it
> > > makes sense to mix the two notions (i.e. whether an object-oriented
> > > approach makes sense at all, actually).  
> > 
> > So the OO approach works ok for pretty simple setups - probably why it
> > became popular with jUnit etc: the class handles the setup, the
> > methods do the specific tests.  But it breaks down pretty quickly:
> >    - If you want to use multiple "setup" pieces at once you need to
> >      use multiple inheritence, which is a nightmare
> 
> Argh. Yes.
> 
> >    - If you want to parameterize parts of the setup, you need to do it
> >      at the whole class level which involves wierd non-local
> >      interactions
> >    - If you want to use multiple setup pieces which are actually the
> >      same piece repeated with different parameters, you're pretty much
> >      out of luck
> > 
> > I realize that context managers aren't the most obvious thing, but
> > they have the huge advantage that every test can directly and
> > reasonably tersely declare what setup they need to run:
> > 
> >     def test():
> > 	with setup_i_need():
> > 	    with other_setup_i_need():
> > 	       with setup_i_need(param='non default'):
> > 	           # actually do the test
> > 
> > This also handles the necessary teardown at each level, and it still
> > works if some of the inner setups need paramaters that are derived
> > fromt the outer setups.
> 
> Yes yes definitely.
> 
> > > > +        assert_eq_unordered(ns.ifs(), ('lo', IFNAME))  
> > > 
> > > Clear (probably clearer with a "list includes" operator).  
> > 
> > It's not the same as list includes (that's assert_in()).  This checks
> > that the lists (strictly, iterables) are identical ignoring order.  So
> > in this case ['lo', IFNAME] and [IFNAME, 'lo'] would be ok, but ['lo',
> > 'eth99', IFNAME] would not be.
> > 
> > > > +
> > > > +
> > > > +@test_ndp(IFNAME, HOST_NET6)
> > > > +@contextlib.contextmanager
> > > > +def pasta_ndp():
> > > > +    with pasta_unconfigured() as (simnet, guestns):
> > > > +        guestns.ifup(IFNAME)
> > > > +        yield guestns, simnet.gw_ip6_ll.ip  
> > > 
> > > This really hides what we are checking. Other than that (but it's a big
> > > issue in my opinion) I find it terse and elegant.  
> > 
> > Yeah, I'm not super happy with this one.  In particular I dislike the
> > inconsistency: in simple tests like the above we have, loosely the
> > setup, followed by the test.  In these composed examples we have the
> > tests then the setup.  So combining a few options I've thought of, I
> > could do something like this:
> > 
> > 	@contextlib.contextmanager
> > 	def pasta_ndp():
> > 	    with pasta_unconfigured() as (simnet, guestns):
> > 	        guestns.ifup(IFNAME)
> > 	        yield NdpTestScenario(ifname=IFNAME, net=HOST_NET6,
> > 		                      router=simnet.gw_ip6_ll.ip,
> > 				      ndpclient=guestns)
> > 
> > 	compose_matrix([pasta_ndp], NDP_TESTS)
> > 
> > Here NDP_TESTS would come from the ndp module and have the list of
> > individual ndp test cases, each of which takes an NdpTestScenario as
> > parameter.
> 
> Probably easier to understand, but it doesn't solve the problem that
> "yield" doesn't mean "we're checking this". Maybe it could be worked
> around with naming though, I haven't tried.

Hm, can't really do that - "yield" is a keyword, not a function or
identifier.

> > > > +
> > > > +@test_dhcp(IFNAME, IP4.ip, GW_IP4.ip, 65520)  
> > > 
> > > The test_ndp decorator is probably usable, this one not so much. As we
> > > have more parameters, it would probably be better to have something
> > > more descriptive (even if necessarily more verbose).  
> > 
> > Right.  So would the treatment above help?  Enough?  It would look
> > something like:
> > 
> > 	@contextlib.contextmanager
> > 	def pasta_dhcp():
> > 	    ....
> > 	    yield DhcpTestScenario(ifname=IFNAME, client_ip4=IP4.ip,
> > 	                           server_ip4=GW_IP4.ip, mtu=65520)
> > 
> > 	compose_matrix([pasta_dhcp], DHCP_TESTS)
> 
> Much better, I think.

Ok, I'll move towards that throughout.

> > > > +@test_dhcpv6(IFNAME, IP6.ip)
> > > > +@contextlib.contextmanager
> > > > +def pasta_dhcp():
> > > > +    with pasta_unconfigured() as (_, guestns):
> > > > +        yield guestns
> > > > +
> > > > +
> > > > +@contextlib.contextmanager
> > > > +def pasta_configured():
> > > > +    with pasta_unconfigured(FWD_OPTS + ' --config-net') as (simnet, ns):
> > > > +        # Wait for DAD to complete on the --config-net address  
> > > 
> > > Side note: this shouldn't be needed -- if it is, we should probably fix
> > > something in pasta.  
> > 
> > It is needed, and yes we probably should, I haven't had a chance to
> > look into what, exactly.  We may hit that kernel bug I noticed where
> > permissions to the disable_dad sysctl seem to behave weirdly.
> 
> Ah, right, that rings a bell.
> 
> > > > +        ns.addr_wait(IFNAME, family='inet6', scope='global')
> > > > +        yield simnet, ns  
> > > 
> > > Except for the common points with my observation above, this looks
> > > usable, and:
> > >   
> > > > +
> > > > +
> > > > +@test
> > > > +def test_config_net_addr():
> > > > +    with pasta_configured() as (_, ns):
> > > > +        addrs = ns.addrs(IFNAME, scope='global')
> > > > +        assert_eq_unordered(addrs, [IP4, IP6])
> > > > +
> > > > +
> > > > +@test
> > > > +def test_config_net_route4():
> > > > +    with pasta_configured() as (_, ns):
> > > > +        (defroute,) = ns.routes4(dst='default')
> > > > +        gateway = ipaddress.ip_address(defroute['gateway'])
> > > > +        assert_eq(gateway, GW_IP4.ip)
> > > > +
> > > > +
> > > > +@test
> > > > +def test_config_net_route6():
> > > > +    with pasta_configured() as (simnet, ns):
> > > > +        (defroute,) = ns.routes6(dst='default')
> > > > +        gateway = ipaddress.ip_address(defroute['gateway'])
> > > > +        assert_eq(gateway, simnet.gw_ip6_ll.ip)
> > > > +
> > > > +
> > > > +@test
> > > > +def test_config_net_mtu():
> > > > +    with pasta_configured() as (_, ns):
> > > > +        mtu = ns.mtu(IFNAME)
> > > > +        assert_eq(mtu, 65520)  
> > > 
> > > these all make intuitive sense to me.
> > >   
> > > > +
> > > > +
> > > > +@test_transfers(ip4=REMOTE_IP4.ip, ip6=REMOTE_IP6.ip, port=10000)
> > > > +@contextlib.contextmanager
> > > > +def outward_transfer():
> > > > +    with pasta_configured() as (simnet, ns):
> > > > +        yield ns, simnet.gw  
> > > 
> > > This, however, not. I would naturally expect the function implementing
> > > a template of data transfer test to do something with data.  
> > 
> > I'm not entirely sure what you mean here.  Would the same treatment as
> > for the ndp and dhcp cases above help?
> > 
> > 	@contextlib.contextmanager
> > 	def outward_transfer():
> > 	    with pasta_configured() as (simnet, ns):
> > 	        yield TransferTestScenario(client=ns,
> > 	                                   server=simnet.gw,
> > 	                                   connect_ip4=REMOTE_IP4.ip,
> > 	                                   connect_ip6=REMOTE_IP6.ip,
> > 					   connect_port=10000)
> > 
> > 	# variants for inward transfer, and spliced_transfer
> > 	compose_matrix([outward_transfer, inward_transfer, spliced_transfer],
> > 	               TRANSFER_TESTS)
> 
> Yes, now I actually understand what outward_transfer() does: it calls
> TransferTestScenario(). I really had no idea otherwise.

Well.. sort of.  The idea here is that 'TransferTestScenario' doesn't
itself *do* anything - it's just a container for all the information
relevant to running transfer tests in a specific setup.  All the
individual transfer test functions then use the information in here to
do their thing.

> > > > +
> > > > +
> > > > +@test_transfers(ip4=IP4.ip, ip6=IP6.ip, port=IN_FWD_PORT,
> > > > +                fromip4=REMOTE_IP4.ip, fromip6=REMOTE_IP6.ip)
> > > > +@contextlib.contextmanager
> > > > +def inward_transfer():
> > > > +    with pasta_configured() as (simnet, ns):
> > > > +        yield simnet.gw, ns
> > > > +
> > > > +
> > > > +@test_transfers(ip4=LOOPBACK4, ip6=LOOPBACK6, port=SPLICE_FWD_PORT,
> > > > +                listenip4=LOOPBACK4, listenip6=LOOPBACK6)
> > > > +@contextlib.contextmanager
> > > > +def spliced_transfer():
> > > > +    with pasta_configured() as (simnet, ns):
> > > > +        yield ns, simnet.simhost  
> > > 
> > > I still have to decode these before I can reasonably comment on them.
> > > 
> > > I'll follow up with a more detailed proposal of a possible
> > > configuration format or perhaps something between a domain-specific
> > > language and an annotated Python script (à la bats), but I'm trying to
> > > consider a few more tests on top of these.
> > > 
> > > I started looking at "options" tests next, and realised my proposal was
> > > a bit too simplistic. But also that the current version of those tests,
> > > while somewhat verbose and surely clunky, shows in a very clear way
> > > what we're checking and what we expect... and I'm not sure that the
> > > outcome from extending pasta_configured() would be very usable.  
> > 
> > So, I was only thinking of pasta_configured() and pasta_unconfigured()
> > as useful for the pretty specific pattern of pasta invocations in this
> > batch of tests (which is why they're local to that file).  I've also
> > had at pasta_options (which I'm thinking of as the log-to-file tests,
> > since that's all they test so far).  I was planning a new helper to
> > set up a suitable scenario for that, which would be based on
> > simple_net() and Pasta() with some kind of parameterization for where
> > to place the logfile.
> 
> Hmm, yes, I guess that would be better than the alternative I was afraid
> of (extending pasta_configured()).
> 
> > > All we need for that is:
> > > 
> > > - define options and network model/addressing  
> > 
> > I think that short statement hides a lot of complexity.
> 
> Eh, yes, but if you take this part by itself, you wouldn't naturally
> jump to Python decorators. :)

Indeed, and that's not where the decorators are arising from.  With
the revisions I'm already planning on there will only be two
decorators:

    @test(), which is used to declare a particular function as a
    standalone test (possibly with options like timeout).  This could
    be done instead with explicit "register_test('name', testfn)"
    calls, but the decorator form seems more natural to me.

    @contextlib.contextmanager, which is a way of much more tersely
    writing "context managers", which are essentially a matched pair
    of setup/teardown functions.  I realize that they're kind of
    obscure, but it's a huge reduction in the amount of boilerplate:

Suppose we have an existing setup foo(), and want to extend it with
one extra step.  Using the decorator it's:

@contextlib.contextmanager
def bar(param):
    with foo(param) as f:
       additional_setup(f)
       yield f

To open code it would be roughly:

class Bar(contextlib.AbstractContextManager):
    def __init__(self, param):
       self.foo = foo(param)

    def __enter__(self):
        f = self.foo.__enter__()
	try:
	    additional_setup(f)
	except Exception as e:
	    self.foo.__exit__(... not sure what goes here ...)
	return f

    def __exit__(self, *exc_info):
        self.foo.__exit__(*exc_info)

.. and it just gets worse with more complex examples.

> > > - have a clear syntax of identifying where pasta is starting
> > > 
> > > - a list of commands (which, themselves, are absolutely obvious and
> > >   natural in a shell script, but I see the value of using Python
> > >   features to check assertions, at least).  
> 

-- 
David Gibson			| I'll have my music baroque, and my code
david AT gibson.dropbear.id.au	| minimalist, thank you.  NOT _the_ _other_
				| _way_ _around_!
http://www.ozlabs.org/~dgibson

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]

^ permalink raw reply	[flat|nested] 32+ messages in thread

end of thread, other threads:[~2023-07-11  8:04 UTC | newest]

Thread overview: 32+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-06-27  2:54 [PATCH 00/27] RFC: Start converting passt & pasta tests to Avocado using special plugin David Gibson
2023-06-27  2:54 ` [PATCH 01/27] avocado: Make a duplicate copy of testsuite for comparison purposes David Gibson
2023-06-27  2:54 ` [PATCH 02/27] avocado: Don't double download assets for test/ and oldtest/ David Gibson
2023-06-27  2:54 ` [PATCH 03/27] avocado: Move static checkers to avocado David Gibson
2023-06-27  2:54 ` [PATCH 04/27] avocado: Introduce "avocado-classless" plugin, runner and outline David Gibson
2023-06-27  2:54 ` [PATCH 05/27] avocado, test: Add static checkers for Python code David Gibson
2023-06-27  2:54 ` [PATCH 06/27] avocado: Resolver implementation for avocado-classless plugin David Gibson
2023-06-27  2:54 ` [PATCH 07/27] avocado: Add basic assertion helpers to " David Gibson
2023-06-27  2:54 ` [PATCH 08/27] tasst, avocado: Introduce library of common test helpers David Gibson
2023-06-27  2:54 ` [PATCH 09/27] avocado-classless: Test matrices by composition David Gibson
2023-06-27  2:54 ` [PATCH 10/27] tasst: Helper functions for executing commands in different places David Gibson
2023-06-27  2:54 ` [PATCH 11/27] avocado-classless: Allow overriding default timeout David Gibson
2023-06-27  2:54 ` [PATCH 12/27] avocado: Convert build tests to avocado David Gibson
2023-06-27  2:54 ` [PATCH 13/27] tasst: Add helpers for running background commands on sites David Gibson
2023-06-27  2:54 ` [PATCH 14/27] tasst: Add helper to get network interface names for a site David Gibson
2023-06-27  2:54 ` [PATCH 15/27] tasst: Add helpers to run commands with nstool David Gibson
2023-06-27  2:54 ` [PATCH 16/27] tasst: Add ifup and network address helpers to Site David Gibson
2023-06-27  2:54 ` [PATCH 17/27] tasst: Helper for creating veth devices between namespaces David Gibson
2023-06-27  2:54 ` [PATCH 18/27] tasst: Add helper for getting MTU of a network interface David Gibson
2023-06-27  2:54 ` [PATCH 19/27] tasst: Add helper to wait for IP address to appear David Gibson
2023-06-27  2:54 ` [PATCH 20/27] tasst: Add helpers for getting a site's routes David Gibson
2023-06-27  2:54 ` [PATCH 21/27] tasst: Helpers to test transferring data between sites David Gibson
2023-06-27  2:54 ` [PATCH 22/27] tasst: IP address allocation helpers David Gibson
2023-06-27  2:54 ` [PATCH 23/27] tasst: Helpers for running daemons with a pidfile David Gibson
2023-06-27  2:54 ` [PATCH 24/27] tasst: Helpers for testing NDP behaviour David Gibson
2023-06-27  2:54 ` [PATCH 25/27] tasst: Helpers for testing DHCP & DHCPv6 behaviour David Gibson
2023-06-27  2:54 ` [PATCH 26/27] tasst: Helpers to construct a simple network environment for tests David Gibson
2023-06-27  2:54 ` [PATCH 27/27] avocado: Convert basic pasta tests David Gibson
2023-07-05  0:30   ` Stefano Brivio
2023-07-05  3:27     ` David Gibson
2023-07-07 17:42       ` Stefano Brivio
2023-07-10  7:45         ` David Gibson

Code repositories for project(s) associated with this public inbox

	https://passt.top/passt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for IMAP folder(s).