[PATCH v2 00/22] RFC: Proof-of-concept based exeter+Avocado tests
Here's a rough proof of concept showing how we could run tests for passt with Avocado and the exeter library I recently created. It includes Cleber's patch adding some basic Avocado tests and builds on that. The current draft is pretty janky: * The build rules to download and install the necessary pieces are messy * We create the Avocado job files from the exeter sources in the Makefile. Ideally Avocado would eventually be extended to handle this itself * The names that Avocado sees for each test are overlong * There's some hacks to make sure things are executed from the right working directory But, it's a starting point. Stefano, If you could look particularly at 6/22 and 22/22 which add the real tests for passt/pasta, that would be great. The more specific you can be about what you find ugly about how the tests are written, then better I can try to address that. I suspect it will be easier to actually apply the series, then look at the new test files (test/build/build.py, and test/pasta/pasta.py particularly). From there you can look at as much of the support library as you need to, rather than digging through the actual patches to look for that. Cleber, If you could look at 4..6/22 particularly to review how I'm connecting the actual tests to the Avocado runner, that would be helpful. Cleber Rosa (1): test: run static checkers with Avocado and JSON definitions David Gibson (21): nstool: Fix some trivial typos nstool: Propagate SIGTERM to processes executed in the namespace test: Extend make targets to run Avocado tests test: Exeter based static tests test: Add exeter+Avocado based build tests test: Add linters for Python code tasst: Introduce library of common test helpers tasst: "snh" module for simulated network hosts 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 SimNetHost 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 SimNetHost's routes tasst: Helpers to test transferring data between sites tasst: IP address allocation helpers 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 test/.gitignore | 2 + test/Makefile | 63 ++++++- test/avocado/static_checkers.json | 16 ++ test/build/.gitignore | 2 + test/build/build.py | 105 +++++++++++ test/build/static_checkers.sh | 28 +++ test/meta/.gitignore | 1 + test/meta/lint.sh | 28 +++ test/nstool.c | 30 ++- test/pasta/.gitignore | 1 + test/pasta/pasta.py | 138 ++++++++++++++ test/run_avocado | 51 ++++++ test/tasst/.gitignore | 1 + test/tasst/__init__.py | 11 ++ test/tasst/__main__.py | 22 +++ test/tasst/address.py | 79 ++++++++ test/tasst/dhcp.py | 132 ++++++++++++++ test/tasst/dhcpv6.py | 89 +++++++++ test/tasst/ndp.py | 116 ++++++++++++ test/tasst/nstool.py | 186 +++++++++++++++++++ test/tasst/pasta.py | 52 ++++++ test/tasst/scenario/__init__.py | 12 ++ test/tasst/scenario/simple.py | 109 +++++++++++ test/tasst/selftest/__init__.py | 16 ++ test/tasst/selftest/static_ifup.py | 60 ++++++ test/tasst/selftest/veth.py | 106 +++++++++++ test/tasst/snh.py | 283 +++++++++++++++++++++++++++++ test/tasst/transfer.py | 194 ++++++++++++++++++++ 28 files changed, 1928 insertions(+), 5 deletions(-) create mode 100644 test/avocado/static_checkers.json create mode 100644 test/build/.gitignore create mode 100644 test/build/build.py create mode 100644 test/build/static_checkers.sh create mode 100644 test/meta/.gitignore create mode 100644 test/meta/lint.sh create mode 100644 test/pasta/.gitignore create mode 100644 test/pasta/pasta.py create mode 100755 test/run_avocado create mode 100644 test/tasst/.gitignore create mode 100644 test/tasst/__init__.py create mode 100644 test/tasst/__main__.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/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/selftest/__init__.py create mode 100644 test/tasst/selftest/static_ifup.py create mode 100644 test/tasst/selftest/veth.py create mode 100644 test/tasst/snh.py create mode 100644 test/tasst/transfer.py -- 2.45.2
Signed-off-by: David Gibson
Particularly in shell it's sometimes natural to save the pid from a process
run and later kill it. If doing this with nstool exec, however, it will
kill nstool itself, not the program it is running, which isn't usually what
you want or expect.
Address this by having nstool propagate SIGTERM to its child process. It
may make sense to propagate some other signals, but some introduce extra
complications, so we'll worry about them when and if it seems useful.
Signed-off-by: David Gibson
On Mon, 5 Aug 2024 22:36:41 +1000
David Gibson
Particularly in shell it's sometimes natural to save the pid from a process run and later kill it. If doing this with nstool exec, however, it will kill nstool itself, not the program it is running, which isn't usually what you want or expect.
Address this by having nstool propagate SIGTERM to its child process. It may make sense to propagate some other signals, but some introduce extra complications, so we'll worry about them when and if it seems useful.
Signed-off-by: David Gibson
--- test/nstool.c | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/test/nstool.c b/test/nstool.c index a6aca981..fc357d8a 100644 --- a/test/nstool.c +++ b/test/nstool.c @@ -345,17 +345,39 @@ static int openns(const char *fmt, ...) return fd; }
+static pid_t sig_pid; +static void sig_handler(int signum) +{ + int err; + + err = kill(sig_pid, signum); + if (err) + die("Propagating %s: %s\n", strsignal(signum), strerror(errno));
As I've just been bitten by this, f30ed68c5273 ("pasta: Save errno on signal handler entry, restore on return when needed"), I was kind of wondering if we should save and restore errno, regardless of the fact it's not needed here (if kill() affects ernno, we won't return). On the other hand this handler at the moment is simple enough that we would notice if it's needed because of some further changes. -- Stefano
From: Cleber Rosa
Add a new 'avocado' target to the test/ Makefile, which will install
avocado into a Python venv, and run the Avocado based tests with it.
Signed-off-by: David Gibson
Introduce some trivial testcases based on the exeter library. These run
the C static checkers, which is redundant with the included Avocado json
file, but are useful as an example. We extend the make avocado target to
generate Avocado job files from the exeter tests and include them in the
test run.
Signed-off-by: David Gibson
Add a new test script to run the equivalent of the tests in build/all
using exeter and Avocado. This new version of the tests is more robust
than the original, since it makes a temporary copy of the source tree so
will not be affected by concurrent manual builds.
Signed-off-by: David Gibson
On Mon, 5 Aug 2024 22:36:45 +1000
David Gibson
Add a new test script to run the equivalent of the tests in build/all using exeter and Avocado. This new version of the tests is more robust than the original, since it makes a temporary copy of the source tree so will not be affected by concurrent manual builds.
I think this is much more readable than the previous Python attempt. On the other hand, I guess it's not an ideal candidate for a fair comparison because this is exactly the kind of stuff where shell scripting shines: it's a simple test that needs a few basic shell commands. On that subject, the shell test is about half the lines of code (just skipping headers, it's 48 lines instead of 90... and yes, this version now uses a copy of the source code, but that would be two lines). In terms of time overhead, dropping delays to make the display capture nice (a feature that we would anyway lose with exeter plus Avocado, if I understood correctly): $ time (make clean; make passt; make clean; make pasta; make clean; make qrap; make clean; make; d=$(mktemp -d); prefix=$d make install; prefix=$d make uninstall; ) [...] real 0m17.449s user 0m15.616s sys 0m2.136s compared to: $ time ./run [...] real 0m18.217s user 0m0.010s sys 0m0.001s ...which I would call essentially no overhead. I didn't try out this version yet, I suspect it would be somewhere in between.
Signed-off-by: David Gibson
--- test/Makefile | 19 +++++--- test/build/.gitignore | 1 + test/build/build.py | 105 ++++++++++++++++++++++++++++++++++++++++++ test/run_avocado | 2 +- 4 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 test/build/build.py diff --git a/test/Makefile b/test/Makefile index dae25312..d24fce14 100644 --- a/test/Makefile +++ b/test/Makefile @@ -64,15 +64,19 @@ LOCAL_ASSETS = mbuto.img mbuto.mem.img podman/bin/podman QEMU_EFI.fd \ ASSETS = $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS)
EXETER_SH = build/static_checkers.sh -EXETER_JOBS = $(EXETER_SH:%.sh=%.json) +EXETER_PY = build/build.py +EXETER_JOBS = $(EXETER_SH:%.sh=%.json) $(EXETER_PY:%.py=%.json)
AVOCADO_JOBS = $(EXETER_JOBS) avocado/static_checkers.json
-SYSTEM_PYTHON = python3 +PYTHON = python3 VENV = venv -PYTHON = $(VENV)/bin/python3 PIP = $(VENV)/bin/pip3 -RUN_AVOCADO = cd .. && test/$(PYTHON) test/run_avocado +PYPATH = exeter/py3 +SPACE = $(subst ,, ) +PYPATH_TEST = $(subst $(SPACE),:,$(PYPATH)) +PYPATH_BASE = $(subst $(SPACE),:,$(PYPATH:%=test/%)) +RUN_AVOCADO = cd .. && PYTHONPATH=$(PYPATH_BASE) test/$(VENV)/bin/python3 test/run_avocado
At least intuitively, I would have no clue what this all does. But it doesn't matter so much, I could try to find out the day that something doesn't work.
CFLAGS = -Wall -Werror -Wextra -pedantic -std=c99
@@ -131,13 +135,16 @@ big.bin: dd if=/dev/urandom bs=1M count=10 of=$@
.PHONY: venv -venv: - $(SYSTEM_PYTHON) -m venv $(VENV) +venv: pull-exeter + $(PYTHON) -m venv $(VENV) $(PIP) install avocado-framework
%.json: %.sh pull-exeter cd ..; sh test/$< --avocado > test/$@
+%.json: %.py pull-exeter + cd ..; PYTHONPATH=$(PYPATH_BASE) $(PYTHON) test/$< --avocado > test/$@ +
Same here.
.PHONY: avocado avocado: venv $(AVOCADO_JOBS) $(RUN_AVOCADO) $(AVOCADO_JOBS) diff --git a/test/build/.gitignore b/test/build/.gitignore index a6c57f5f..4ef40dd0 100644 --- a/test/build/.gitignore +++ b/test/build/.gitignore @@ -1 +1,2 @@ *.json +build.exeter diff --git a/test/build/build.py b/test/build/build.py new file mode 100644 index 00000000..79668672 --- /dev/null +++ b/test/build/build.py @@ -0,0 +1,105 @@ +#! /usr/bin/env python3 +# +# 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/build.sh - Test build and install targets +# +# Copyright Red Hat +# Author: David Gibson
+ +import contextlib +import os.path +import shutil +import subprocess +import tempfile + +import exeter + + +def host_run(*cmd, **kwargs): + return subprocess.run(cmd, check=True, encoding='UTF-8', **kwargs) + + +def host_out(*cmd, **kwargs): + return host_run(*cmd, capture_output=True, **kwargs).stdout
A vague idea only, so far, but I guess it's fine to have some amount of boilerplate.
+ + +@contextlib.contextmanager +def clone_source_tree(): + with tempfile.TemporaryDirectory(ignore_cleanup_errors=False) as tmpdir: + # Make a temporary copy of the sources + srcfiles = host_out('git', 'ls-files').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
This all makes sense. Of course it would be more readable in shell script (including the trap to remove the temporary directory on failure/interrupt), but I think it's as clear as it can get in any other language.
+ + +def build_target(target, outputs): + with clone_source_tree(): + for o in outputs: + assert not os.path.exists(o) + host_run('make', f'{target}', 'CFLAGS="-Werror"')
Compared to: host CFLAGS="-Werror" make I would say it's not great, but again, it makes sense, and it's as good as it gets, I suppose.
+ for o in outputs: + assert os.path.exists(o) + host_run('make', 'clean') + for o in outputs: + assert not os.path.exists(o)
Same here, check [ -f passt ] check [ -h pasta ] check [ -f qrap ]
+ + +@exeter.test +def test_make_passt(): + build_target('passt', ['passt']) + + +@exeter.test +def test_make_pasta(): + build_target('pasta', ['pasta']) + + +@exeter.test +def test_make_qrap(): + build_target('qrap', ['qrap']) + + +@exeter.test +def test_make_all(): + build_target('all', ['passt', 'pasta', 'qrap'])
These all make sense and look relatively readable (while not as... writable as shell commands "everybody" is familiar with).
+ +@exeter.test +def test_make_install_uninstall(): + with clone_source_tree(): + with tempfile.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 + host_run('make', 'install', 'CFLAGS="-Werror"', f'prefix={prefix}') + + for t in exes: + assert os.path.isfile(os.path.join(bindir, t)) + host_run('man', '-M', f'{mandir}', '-W', 'passt') + + # Uninstall + host_run('make', 'uninstall', f'prefix={prefix}') + + for t in exes: + assert not os.path.exists(os.path.join(bindir, t)) + cmd = ['man', '-M', f'{mandir}', '-W', 'passt']
Same, up to here: it's much more readable and obvious to write in shell script, but I don't find it impossible to grasp in Python, either.
+ exeter.assert_raises(subprocess.CalledProcessError, + host_run, *cmd)
This, I have no idea why. Why is it only in this loop? How does it affect the control flow?
+ + +if __name__ == '__main__': + exeter.main() diff --git a/test/run_avocado b/test/run_avocado index d518b9ec..26a226ce 100755 --- a/test/run_avocado +++ b/test/run_avocado @@ -41,7 +41,7 @@ def main(): "resolver.references": references, "runner.identifier_format": "{args}", } - suite = TestSuite.from_config(config, name="static_checkers") + suite = TestSuite.from_config(config, name="all") with Job(config, [suite]) as j: return j.run()
Patch 22/22 will take me a bit longer (I'm just looking at these two for the moment, as you suggested). -- Stefano
On Wed, Aug 07, 2024 at 12:11:26AM +0200, Stefano Brivio wrote:
On Mon, 5 Aug 2024 22:36:45 +1000 David Gibson
wrote: Add a new test script to run the equivalent of the tests in build/all using exeter and Avocado. This new version of the tests is more robust than the original, since it makes a temporary copy of the source tree so will not be affected by concurrent manual builds.
I think this is much more readable than the previous Python attempt.
That's encouraging.
On the other hand, I guess it's not an ideal candidate for a fair comparison because this is exactly the kind of stuff where shell scripting shines: it's a simple test that needs a few basic shell commands.
Right.
On that subject, the shell test is about half the lines of code (just skipping headers, it's 48 lines instead of 90... and yes, this version
Even ignoring the fact that this case is particularly suited to shell, I don't think that's really an accurate comparison, but getting to one is pretty hard. The existing test isn't 48 lines of shell, but of "passt test DSL". There are several hundred additional lines of shell to interpret that. Now obviously we don't need all of that for just this test. Likewise the new Python test needs at least exeter - that's only a couple of hundred lines - but also Avocado (huge, but only a small amount is really relevant here).
now uses a copy of the source code, but that would be two lines).
I feel like it would be a bit more than two lines, to copy exactly what youwant, and to clean up after yourself.
In terms of time overhead, dropping delays to make the display capture nice (a feature that we would anyway lose with exeter plus Avocado, if I understood correctly):
Yes. Unlike you, I'm really not convinced of the value of the display capture versus log files, at least in the majority of cases. I certainly don't think it's worth slowing down the test running in the normal case.
$ time (make clean; make passt; make clean; make pasta; make clean; make qrap; make clean; make; d=$(mktemp -d); prefix=$d make install; prefix=$d make uninstall; ) [...] real 0m17.449s user 0m15.616s sys 0m2.136s
On my system: [...] real 0m20.325s user 0m15.595s sys 0m5.287s
compared to:
$ time ./run [...] real 0m18.217s user 0m0.010s sys 0m0.001s
...which I would call essentially no overhead. I didn't try out this version yet, I suspect it would be somewhere in between.
Well.. $ time PYTHONPATH=test/exeter/py3 test/venv/bin/avocado run test/build/build.json [...] RESULTS : PASS 5 | ERROR 0 | FAIL 0 | SKIP 0 | WARN 0 | INTERRUPT 0 | CANCEL 0 JOB TIME : 10.85 s real 0m11.000s user 0m23.439s sys 0m7.315s Because parallel. It looks like the avocado start up time is reasonably substantial too, so that should look better with a larger set of tests.
Signed-off-by: David Gibson
--- test/Makefile | 19 +++++--- test/build/.gitignore | 1 + test/build/build.py | 105 ++++++++++++++++++++++++++++++++++++++++++ test/run_avocado | 2 +- 4 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 test/build/build.py diff --git a/test/Makefile b/test/Makefile index dae25312..d24fce14 100644 --- a/test/Makefile +++ b/test/Makefile @@ -64,15 +64,19 @@ LOCAL_ASSETS = mbuto.img mbuto.mem.img podman/bin/podman QEMU_EFI.fd \ ASSETS = $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS)
EXETER_SH = build/static_checkers.sh -EXETER_JOBS = $(EXETER_SH:%.sh=%.json) +EXETER_PY = build/build.py +EXETER_JOBS = $(EXETER_SH:%.sh=%.json) $(EXETER_PY:%.py=%.json)
AVOCADO_JOBS = $(EXETER_JOBS) avocado/static_checkers.json
-SYSTEM_PYTHON = python3 +PYTHON = python3 VENV = venv -PYTHON = $(VENV)/bin/python3 PIP = $(VENV)/bin/pip3 -RUN_AVOCADO = cd .. && test/$(PYTHON) test/run_avocado +PYPATH = exeter/py3 +SPACE = $(subst ,, ) +PYPATH_TEST = $(subst $(SPACE),:,$(PYPATH)) +PYPATH_BASE = $(subst $(SPACE),:,$(PYPATH:%=test/%)) +RUN_AVOCADO = cd .. && PYTHONPATH=$(PYPATH_BASE) test/$(VENV)/bin/python3 test/run_avocado
At least intuitively, I would have no clue what this all does. But it doesn't matter so much, I could try to find out the day that something doesn't work.
Yeah, this makefile stuff is very mucky, I'm certainly hoping this can be improved.
CFLAGS = -Wall -Werror -Wextra -pedantic -std=c99
@@ -131,13 +135,16 @@ big.bin: dd if=/dev/urandom bs=1M count=10 of=$@
.PHONY: venv -venv: - $(SYSTEM_PYTHON) -m venv $(VENV) +venv: pull-exeter + $(PYTHON) -m venv $(VENV) $(PIP) install avocado-framework
%.json: %.sh pull-exeter cd ..; sh test/$< --avocado > test/$@
+%.json: %.py pull-exeter + cd ..; PYTHONPATH=$(PYPATH_BASE) $(PYTHON) test/$< --avocado > test/$@ +
Same here.
It looks messy because of the (interim, I hope) path & cwd wrangling stuff. But the basis is very simple. We run the exeter program: $(PYTHON) test/$< with the '--avocado' flag --avocado and send the output to a json file > $@ Later..
.PHONY: avocado avocado: venv $(AVOCADO_JOBS) $(RUN_AVOCADO) $(AVOCADO_JOBS)
..we feed that json file to avocado to actually run the tests.
diff --git a/test/build/.gitignore b/test/build/.gitignore index a6c57f5f..4ef40dd0 100644 --- a/test/build/.gitignore +++ b/test/build/.gitignore @@ -1 +1,2 @@ *.json +build.exeter diff --git a/test/build/build.py b/test/build/build.py new file mode 100644 index 00000000..79668672 --- /dev/null +++ b/test/build/build.py @@ -0,0 +1,105 @@ +#! /usr/bin/env python3 +# +# 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/build.sh - Test build and install targets +# +# Copyright Red Hat +# Author: David Gibson
+ +import contextlib +import os.path +import shutil +import subprocess +import tempfile + +import exeter + + +def host_run(*cmd, **kwargs): + return subprocess.run(cmd, check=True, encoding='UTF-8', **kwargs) + + +def host_out(*cmd, **kwargs): + return host_run(*cmd, capture_output=True, **kwargs).stdout A vague idea only, so far, but I guess it's fine to have some amount of boilerplate.
Right. These are loosely equivalent to the implementation of the "host" and "hout" directives in the existing DSL.
+@contextlib.contextmanager +def clone_source_tree(): + with tempfile.TemporaryDirectory(ignore_cleanup_errors=False) as tmpdir: + # Make a temporary copy of the sources + srcfiles = host_out('git', 'ls-files').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
This all makes sense.
Of course it would be more readable in shell script (including the trap to remove the temporary directory on failure/interrupt), but I think it's as clear as it can get in any other language.
+ + +def build_target(target, outputs): + with clone_source_tree(): + for o in outputs: + assert not os.path.exists(o) + host_run('make', f'{target}', 'CFLAGS="-Werror"')
Compared to:
host CFLAGS="-Werror" make
I would say it's not great, but again, it makes sense, and it's as good as it gets, I suppose.
I don't think that's a fair comparison. The Python equivalent to the DSL line is just: host_run('make', f'{target}', 'CFLAGS="-Werror"') The loop before it is verifying that the targets didn't exist before the make - i.e. we won't spuriously pass because of a stile build. The shell version didn't do that. The with clone_source_tree(): is essentially equivalent saying which (elswhere defined) setup we want. Invoking that explicitly in each test is more verbose, but makes it much easier to see what setup each test needs, and much easier to have lots of different tests with lots of different setups.
+ for o in outputs: + assert os.path.exists(o) + host_run('make', 'clean') + for o in outputs: + assert not os.path.exists(o)
Same here,
check [ -f passt ] check [ -h pasta ] check [ -f qrap ]
+ + +@exeter.test +def test_make_passt(): + build_target('passt', ['passt']) + + +@exeter.test +def test_make_pasta(): + build_target('pasta', ['pasta']) + + +@exeter.test +def test_make_qrap(): + build_target('qrap', ['qrap']) + + +@exeter.test +def test_make_all(): + build_target('all', ['passt', 'pasta', 'qrap'])
These all make sense and look relatively readable (while not as... writable as shell commands "everybody" is familiar with).
So, unlike the shell version I'm using a parameterized helper rather than copy-pasting each case. So, that's a readability / brevity trade-off independent of the shell vs. python difference.
+ +@exeter.test +def test_make_install_uninstall(): + with clone_source_tree(): + with tempfile.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 + host_run('make', 'install', 'CFLAGS="-Werror"', f'prefix={prefix}') + + for t in exes: + assert os.path.isfile(os.path.join(bindir, t)) + host_run('man', '-M', f'{mandir}', '-W', 'passt') + + # Uninstall + host_run('make', 'uninstall', f'prefix={prefix}') + + for t in exes: + assert not os.path.exists(os.path.join(bindir, t)) + cmd = ['man', '-M', f'{mandir}', '-W', 'passt']
Same, up to here: it's much more readable and obvious to write in shell script, but I don't find it impossible to grasp in Python, either.
+ exeter.assert_raises(subprocess.CalledProcessError, + host_run, *cmd)
This, I have no idea why. Why is it only in this loop? How does it affect the control flow?
So, this is essentially check ! man -M ... Now that we've uninstalled, we're re-running (host_run), the same man command (*cmd) as we used before, and checking that it fails (raises the CalledProcessError exception). Come to think of it, I can definitely write this more simply. I'll improve it in the next spin. [snip]
Patch 22/22 will take me a bit longer (I'm just looking at these two for the moment, as you suggested).
Sure. -- David Gibson (he or they) | 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
On Wed, 7 Aug 2024 20:51:08 +1000
David Gibson
On Wed, Aug 07, 2024 at 12:11:26AM +0200, Stefano Brivio wrote:
On Mon, 5 Aug 2024 22:36:45 +1000 David Gibson
wrote: Add a new test script to run the equivalent of the tests in build/all using exeter and Avocado. This new version of the tests is more robust than the original, since it makes a temporary copy of the source tree so will not be affected by concurrent manual builds.
I think this is much more readable than the previous Python attempt.
That's encouraging.
On the other hand, I guess it's not an ideal candidate for a fair comparison because this is exactly the kind of stuff where shell scripting shines: it's a simple test that needs a few basic shell commands.
Right.
On that subject, the shell test is about half the lines of code (just skipping headers, it's 48 lines instead of 90... and yes, this version
Even ignoring the fact that this case is particularly suited to shell, I don't think that's really an accurate comparison, but getting to one is pretty hard.
The existing test isn't 48 lines of shell, but of "passt test DSL". There are several hundred additional lines of shell to interpret that.
Yeah, but the 48 lines is all I have to look at, which is what matters I would argue. That's exactly why I wrote that interpreter. Here, it's 90 lines of *test file*.
Now obviously we don't need all of that for just this test. Likewise the new Python test needs at least exeter - that's only a couple of hundred lines - but also Avocado (huge, but only a small amount is really relevant here).
now uses a copy of the source code, but that would be two lines).
I feel like it would be a bit more than two lines, to copy exactly what youwant, and to clean up after yourself.
host mkdir __STATEDIR__/sources host cp --parents $(git ls-files) __STATEDIR__/sources ...which is actually an improvement on the original as __STATEDIR__ can be handled in a centralised way, if one wants to keep that after the single test case, after the whole test run, or not at all.
In terms of time overhead, dropping delays to make the display capture nice (a feature that we would anyway lose with exeter plus Avocado, if I understood correctly):
Yes. Unlike you, I'm really not convinced of the value of the display capture versus log files, at least in the majority of cases.
Well, but I use that... By the way, openQA nowadays takes periodic screenshots. That's certainly not as useful, but I'm indeed not the only one who benefits from _seeing_ tests as they run instead of correlating log files from different contexts, especially when you have a client, a server, and what you're testing in between.
I certainly don't think it's worth slowing down the test running in the normal case.
It doesn't significantly slow things down, but it certainly makes it more complicated to run test cases in parallel... which you can't do anyway for throughput and latency tests (which take 22 out of the 37 minutes of a current CI run), unless you set up VMs with CPU pinning and cgroups, or a server farm. I mean, I see the value of running things in parallel in a general case, but I don't think you should just ignore everything else.
$ time (make clean; make passt; make clean; make pasta; make clean; make qrap; make clean; make; d=$(mktemp -d); prefix=$d make install; prefix=$d make uninstall; ) [...] real 0m17.449s user 0m15.616s sys 0m2.136s
On my system: [...] real 0m20.325s user 0m15.595s sys 0m5.287s
compared to:
$ time ./run [...] real 0m18.217s user 0m0.010s sys 0m0.001s
...which I would call essentially no overhead. I didn't try out this version yet, I suspect it would be somewhere in between.
Well..
$ time PYTHONPATH=test/exeter/py3 test/venv/bin/avocado run test/build/build.json [...] RESULTS : PASS 5 | ERROR 0 | FAIL 0 | SKIP 0 | WARN 0 | INTERRUPT 0 | CANCEL 0 JOB TIME : 10.85 s
real 0m11.000s user 0m23.439s sys 0m7.315s
Because parallel. It looks like the avocado start up time is reasonably substantial too, so that should look better with a larger set of tests.
With the current set of tests, I doubt it's ever going to pay off. Even if you run the non-perf tests in 10% of the time, it's going to be 24 minutes instead of 37. I guess it will start making sense with larger matrices of network environments, or with more test cases (but really a lot of them).
Signed-off-by: David Gibson
--- test/Makefile | 19 +++++--- test/build/.gitignore | 1 + test/build/build.py | 105 ++++++++++++++++++++++++++++++++++++++++++ test/run_avocado | 2 +- 4 files changed, 120 insertions(+), 7 deletions(-) create mode 100644 test/build/build.py diff --git a/test/Makefile b/test/Makefile index dae25312..d24fce14 100644 --- a/test/Makefile +++ b/test/Makefile @@ -64,15 +64,19 @@ LOCAL_ASSETS = mbuto.img mbuto.mem.img podman/bin/podman QEMU_EFI.fd \ ASSETS = $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS)
EXETER_SH = build/static_checkers.sh -EXETER_JOBS = $(EXETER_SH:%.sh=%.json) +EXETER_PY = build/build.py +EXETER_JOBS = $(EXETER_SH:%.sh=%.json) $(EXETER_PY:%.py=%.json)
AVOCADO_JOBS = $(EXETER_JOBS) avocado/static_checkers.json
-SYSTEM_PYTHON = python3 +PYTHON = python3 VENV = venv -PYTHON = $(VENV)/bin/python3 PIP = $(VENV)/bin/pip3 -RUN_AVOCADO = cd .. && test/$(PYTHON) test/run_avocado +PYPATH = exeter/py3 +SPACE = $(subst ,, ) +PYPATH_TEST = $(subst $(SPACE),:,$(PYPATH)) +PYPATH_BASE = $(subst $(SPACE),:,$(PYPATH:%=test/%)) +RUN_AVOCADO = cd .. && PYTHONPATH=$(PYPATH_BASE) test/$(VENV)/bin/python3 test/run_avocado
At least intuitively, I would have no clue what this all does. But it doesn't matter so much, I could try to find out the day that something doesn't work.
Yeah, this makefile stuff is very mucky, I'm certainly hoping this can be improved.
CFLAGS = -Wall -Werror -Wextra -pedantic -std=c99
@@ -131,13 +135,16 @@ big.bin: dd if=/dev/urandom bs=1M count=10 of=$@
.PHONY: venv -venv: - $(SYSTEM_PYTHON) -m venv $(VENV) +venv: pull-exeter + $(PYTHON) -m venv $(VENV) $(PIP) install avocado-framework
%.json: %.sh pull-exeter cd ..; sh test/$< --avocado > test/$@
+%.json: %.py pull-exeter + cd ..; PYTHONPATH=$(PYPATH_BASE) $(PYTHON) test/$< --avocado > test/$@ +
Same here.
It looks messy because of the (interim, I hope) path & cwd wrangling stuff. But the basis is very simple. We run the exeter program: $(PYTHON) test/$< with the '--avocado' flag --avocado and send the output to a json file
$@
Later..
.PHONY: avocado avocado: venv $(AVOCADO_JOBS) $(RUN_AVOCADO) $(AVOCADO_JOBS)
..we feed that json file to avocado to actually run the tests.
diff --git a/test/build/.gitignore b/test/build/.gitignore index a6c57f5f..4ef40dd0 100644 --- a/test/build/.gitignore +++ b/test/build/.gitignore @@ -1 +1,2 @@ *.json +build.exeter diff --git a/test/build/build.py b/test/build/build.py new file mode 100644 index 00000000..79668672 --- /dev/null +++ b/test/build/build.py @@ -0,0 +1,105 @@ +#! /usr/bin/env python3 +# +# 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/build.sh - Test build and install targets +# +# Copyright Red Hat +# Author: David Gibson
+ +import contextlib +import os.path +import shutil +import subprocess +import tempfile + +import exeter + + +def host_run(*cmd, **kwargs): + return subprocess.run(cmd, check=True, encoding='UTF-8', **kwargs) + + +def host_out(*cmd, **kwargs): + return host_run(*cmd, capture_output=True, **kwargs).stdout A vague idea only, so far, but I guess it's fine to have some amount of boilerplate.
Right. These are loosely equivalent to the implementation of the "host" and "hout" directives in the existing DSL.
+@contextlib.contextmanager +def clone_source_tree(): + with tempfile.TemporaryDirectory(ignore_cleanup_errors=False) as tmpdir: + # Make a temporary copy of the sources + srcfiles = host_out('git', 'ls-files').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
This all makes sense.
Of course it would be more readable in shell script (including the trap to remove the temporary directory on failure/interrupt), but I think it's as clear as it can get in any other language.
+ + +def build_target(target, outputs): + with clone_source_tree(): + for o in outputs: + assert not os.path.exists(o) + host_run('make', f'{target}', 'CFLAGS="-Werror"')
Compared to:
host CFLAGS="-Werror" make
I would say it's not great, but again, it makes sense, and it's as good as it gets, I suppose.
I don't think that's a fair comparison. The Python equivalent to the DSL line is just: host_run('make', f'{target}', 'CFLAGS="-Werror"')
Yes, that's exactly what I meant...
The loop before it is verifying that the targets didn't exist before the make - i.e. we won't spuriously pass because of a stile build. The shell version didn't do that. The with clone_source_tree(): is essentially equivalent saying which (elswhere defined) setup we want. Invoking that explicitly in each test is more verbose, but makes it much easier to see what setup each test needs, and much easier to have lots of different tests with lots of different setups.
No, absolutely, the rest is actually clear enough, I guess.
+ for o in outputs: + assert os.path.exists(o) + host_run('make', 'clean') + for o in outputs: + assert not os.path.exists(o)
Same here,
check [ -f passt ] check [ -h pasta ] check [ -f qrap ]
+ + +@exeter.test +def test_make_passt(): + build_target('passt', ['passt']) + + +@exeter.test +def test_make_pasta(): + build_target('pasta', ['pasta']) + + +@exeter.test +def test_make_qrap(): + build_target('qrap', ['qrap']) + + +@exeter.test +def test_make_all(): + build_target('all', ['passt', 'pasta', 'qrap'])
These all make sense and look relatively readable (while not as... writable as shell commands "everybody" is familiar with).
So, unlike the shell version I'm using a parameterized helper rather than copy-pasting each case. So, that's a readability / brevity trade-off independent of the shell vs. python difference.
+ +@exeter.test +def test_make_install_uninstall(): + with clone_source_tree(): + with tempfile.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 + host_run('make', 'install', 'CFLAGS="-Werror"', f'prefix={prefix}') + + for t in exes: + assert os.path.isfile(os.path.join(bindir, t)) + host_run('man', '-M', f'{mandir}', '-W', 'passt') + + # Uninstall + host_run('make', 'uninstall', f'prefix={prefix}') + + for t in exes: + assert not os.path.exists(os.path.join(bindir, t)) + cmd = ['man', '-M', f'{mandir}', '-W', 'passt']
Same, up to here: it's much more readable and obvious to write in shell script, but I don't find it impossible to grasp in Python, either.
+ exeter.assert_raises(subprocess.CalledProcessError, + host_run, *cmd)
This, I have no idea why. Why is it only in this loop? How does it affect the control flow?
So, this is essentially check ! man -M ...
Now that we've uninstalled, we're re-running (host_run), the same man command (*cmd) as we used before, and checking that it fails (raises the CalledProcessError exception).
Come to think of it, I can definitely write this more simply. I'll improve it in the next spin.
[snip]
Patch 22/22 will take me a bit longer (I'm just looking at these two for the moment, as you suggested).
Sure.
-- Stefano
On Wed, Aug 07, 2024 at 03:06:44PM +0200, Stefano Brivio wrote:
On Wed, 7 Aug 2024 20:51:08 +1000 David Gibson
wrote: On Wed, Aug 07, 2024 at 12:11:26AM +0200, Stefano Brivio wrote:
On Mon, 5 Aug 2024 22:36:45 +1000 David Gibson
wrote: Add a new test script to run the equivalent of the tests in build/all using exeter and Avocado. This new version of the tests is more robust than the original, since it makes a temporary copy of the source tree so will not be affected by concurrent manual builds.
I think this is much more readable than the previous Python attempt.
That's encouraging.
On the other hand, I guess it's not an ideal candidate for a fair comparison because this is exactly the kind of stuff where shell scripting shines: it's a simple test that needs a few basic shell commands.
Right.
On that subject, the shell test is about half the lines of code (just skipping headers, it's 48 lines instead of 90... and yes, this version
Even ignoring the fact that this case is particularly suited to shell, I don't think that's really an accurate comparison, but getting to one is pretty hard.
The existing test isn't 48 lines of shell, but of "passt test DSL". There are several hundred additional lines of shell to interpret that.
Yeah, but the 48 lines is all I have to look at, which is what matters I would argue. That's exactly why I wrote that interpreter.
Here, it's 90 lines of *test file*.
Fair point. Fwiw, it's down to 77 so far for my next draft.
Now obviously we don't need all of that for just this test. Likewise the new Python test needs at least exeter - that's only a couple of hundred lines - but also Avocado (huge, but only a small amount is really relevant here).
now uses a copy of the source code, but that would be two lines).
I feel like it would be a bit more than two lines, to copy exactly what youwant, and to clean up after yourself.
host mkdir __STATEDIR__/sources host cp --parents $(git ls-files) __STATEDIR__/sources
...which is actually an improvement on the original as __STATEDIR__ can be handled in a centralised way, if one wants to keep that after the single test case, after the whole test run, or not at all.
Huh, I didn't know about cp --parents, which does exactly what's needed. In the Python library there are, alas, several things that do almost but not quite what's needed. I guess I could just invoke 'cp --parents' myself.
In terms of time overhead, dropping delays to make the display capture nice (a feature that we would anyway lose with exeter plus Avocado, if I understood correctly):
Yes. Unlike you, I'm really not convinced of the value of the display capture versus log files, at least in the majority of cases.
Well, but I use that...
By the way, openQA nowadays takes periodic screenshots. That's certainly not as useful, but I'm indeed not the only one who benefits from _seeing_ tests as they run instead of correlating log files from different contexts, especially when you have a client, a server, and what you're testing in between.
If you have to correlate multiple logs that's a pain, yes. My approach here is, as much as possible, to have a single "log" (actually stdout & stderr) from the top level test logic, so the logical ordering is kind of built in.
I certainly don't think it's worth slowing down the test running in the normal case.
It doesn't significantly slow things down,
It does if you explicitly add delays to make the display capture nice as mentioned above.
but it certainly makes it more complicated to run test cases in parallel... which you can't do anyway for throughput and latency tests (which take 22 out of the 37 minutes of a current CI run), unless you set up VMs with CPU pinning and cgroups, or a server farm.
So, yes, the perf tests take the majority of the runtime for CI, but I'm less concerned about runtime for CI tests. I'm more interested in runtime for a subset of functional tests you can run repeatedly while developing. I routinely disable the perf and other slow tests, to get a subset taking 5-7 minutes. That's ok, but I'm pretty confident I can get better coverage in significantly less time using parallel tests.
I mean, I see the value of running things in parallel in a general case, but I don't think you should just ignore everything else.
$ time (make clean; make passt; make clean; make pasta; make clean; make qrap; make clean; make; d=$(mktemp -d); prefix=$d make install; prefix=$d make uninstall; ) [...] real 0m17.449s user 0m15.616s sys 0m2.136s
On my system: [...] real 0m20.325s user 0m15.595s sys 0m5.287s
compared to:
$ time ./run [...] real 0m18.217s user 0m0.010s sys 0m0.001s
...which I would call essentially no overhead. I didn't try out this version yet, I suspect it would be somewhere in between.
Well..
$ time PYTHONPATH=test/exeter/py3 test/venv/bin/avocado run test/build/build.json [...] RESULTS : PASS 5 | ERROR 0 | FAIL 0 | SKIP 0 | WARN 0 | INTERRUPT 0 | CANCEL 0 JOB TIME : 10.85 s
real 0m11.000s user 0m23.439s sys 0m7.315s
Because parallel. It looks like the avocado start up time is reasonably substantial too, so that should look better with a larger set of tests.
With the current set of tests, I doubt it's ever going to pay off. Even if you run the non-perf tests in 10% of the time, it's going to be 24 minutes instead of 37.
Including the perf tests, probably not. Excluding them (which is extremely useful when actively coding) I think it will.
I guess it will start making sense with larger matrices of network environments, or with more test cases (but really a lot of them).
We could certainly do with a lot more tests, though I expect it will take a while to get them. -- David Gibson (he or they) | 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
On Thu, 8 Aug 2024 11:28:50 +1000
David Gibson
On Wed, Aug 07, 2024 at 03:06:44PM +0200, Stefano Brivio wrote:
On Wed, 7 Aug 2024 20:51:08 +1000 David Gibson
wrote: On Wed, Aug 07, 2024 at 12:11:26AM +0200, Stefano Brivio wrote:
On Mon, 5 Aug 2024 22:36:45 +1000 David Gibson
wrote: Add a new test script to run the equivalent of the tests in build/all using exeter and Avocado. This new version of the tests is more robust than the original, since it makes a temporary copy of the source tree so will not be affected by concurrent manual builds.
I think this is much more readable than the previous Python attempt.
That's encouraging.
On the other hand, I guess it's not an ideal candidate for a fair comparison because this is exactly the kind of stuff where shell scripting shines: it's a simple test that needs a few basic shell commands.
Right.
On that subject, the shell test is about half the lines of code (just skipping headers, it's 48 lines instead of 90... and yes, this version
Even ignoring the fact that this case is particularly suited to shell, I don't think that's really an accurate comparison, but getting to one is pretty hard.
The existing test isn't 48 lines of shell, but of "passt test DSL". There are several hundred additional lines of shell to interpret that.
Yeah, but the 48 lines is all I have to look at, which is what matters I would argue. That's exactly why I wrote that interpreter.
Here, it's 90 lines of *test file*.
Fair point. Fwiw, it's down to 77 so far for my next draft.
Now obviously we don't need all of that for just this test. Likewise the new Python test needs at least exeter - that's only a couple of hundred lines - but also Avocado (huge, but only a small amount is really relevant here).
now uses a copy of the source code, but that would be two lines).
I feel like it would be a bit more than two lines, to copy exactly what youwant, and to clean up after yourself.
host mkdir __STATEDIR__/sources host cp --parents $(git ls-files) __STATEDIR__/sources
...which is actually an improvement on the original as __STATEDIR__ can be handled in a centralised way, if one wants to keep that after the single test case, after the whole test run, or not at all.
Huh, I didn't know about cp --parents, which does exactly what's needed. In the Python library there are, alas, several things that do almost but not quite what's needed. I guess I could just invoke 'cp --parents' myself.
In terms of time overhead, dropping delays to make the display capture nice (a feature that we would anyway lose with exeter plus Avocado, if I understood correctly):
Yes. Unlike you, I'm really not convinced of the value of the display capture versus log files, at least in the majority of cases.
Well, but I use that...
By the way, openQA nowadays takes periodic screenshots. That's certainly not as useful, but I'm indeed not the only one who benefits from _seeing_ tests as they run instead of correlating log files from different contexts, especially when you have a client, a server, and what you're testing in between.
If you have to correlate multiple logs that's a pain, yes. My approach here is, as much as possible, to have a single "log" (actually stdout & stderr) from the top level test logic, so the logical ordering is kind of built in.
That's not necessarily helpful: if I have a client and a server, things are much clearer to me if I have two different logs, side-by-side. Even more so if you have a guest, a host, and a namespace "in between". I see the difference as I'm often digging through Podman CI's logs, where there's a single log (including stdout and stderr), because bats doesn't offer a context functionality like we have right now. It's sometimes really not easy to understand what's going on in Podman's tests without copying and pasting into an editor and manually marking things.
I certainly don't think it's worth slowing down the test running in the normal case.
It doesn't significantly slow things down,
It does if you explicitly add delays to make the display capture nice as mentioned above.
Okay, I didn't realise the amount of eye-candy I left in even when ${FAST} is set (which probably only makes sense when run as './ci'). With the patch attached I get: $ time ./run [...] real 17m17.686s user 0m0.010s sys 0m0.014s I also cut the duration of throughput and latency tests down to one second. After we fixed lot of issues in passt, and some in QEMU and kernel, results are now surprisingly consistent. Still, a significant part of it is Podman's tests (which I'm working on speeding up, for the sake of Podman's own CI), and performance tests anyway. Without those: $ time ./run [...] real 5m57.612s user 0m0.011s sys 0m0.009s
but it certainly makes it more complicated to run test cases in parallel... which you can't do anyway for throughput and latency tests (which take 22 out of the 37 minutes of a current CI run), unless you set up VMs with CPU pinning and cgroups, or a server farm.
So, yes, the perf tests take the majority of the runtime for CI, but I'm less concerned about runtime for CI tests. I'm more interested in runtime for a subset of functional tests you can run repeatedly while developing. I routinely disable the perf and other slow tests, to get a subset taking 5-7 minutes. That's ok, but I'm pretty confident I can get better coverage in significantly less time using parallel tests.
Probably, yes, but still I would like to point out that the difference between five and ten minutes is not as relevant in terms of workflow as the difference between one and five minutes.
I mean, I see the value of running things in parallel in a general case, but I don't think you should just ignore everything else.
$ time (make clean; make passt; make clean; make pasta; make clean; make qrap; make clean; make; d=$(mktemp -d); prefix=$d make install; prefix=$d make uninstall; ) [...] real 0m17.449s user 0m15.616s sys 0m2.136s
On my system: [...] real 0m20.325s user 0m15.595s sys 0m5.287s
compared to:
$ time ./run [...] real 0m18.217s user 0m0.010s sys 0m0.001s
...which I would call essentially no overhead. I didn't try out this version yet, I suspect it would be somewhere in between.
Well..
$ time PYTHONPATH=test/exeter/py3 test/venv/bin/avocado run test/build/build.json [...] RESULTS : PASS 5 | ERROR 0 | FAIL 0 | SKIP 0 | WARN 0 | INTERRUPT 0 | CANCEL 0 JOB TIME : 10.85 s
real 0m11.000s user 0m23.439s sys 0m7.315s
Because parallel. It looks like the avocado start up time is reasonably substantial too, so that should look better with a larger set of tests.
With the current set of tests, I doubt it's ever going to pay off. Even if you run the non-perf tests in 10% of the time, it's going to be 24 minutes instead of 37.
Including the perf tests, probably not. Excluding them (which is extremely useful when actively coding) I think it will.
I guess it will start making sense with larger matrices of network environments, or with more test cases (but really a lot of them).
We could certainly do with a lot more tests, though I expect it will take a while to get them.
-- Stefano
We use both cppcheck and clang-tidy to lint our C code. Now that we're
introducing Python code in the tests, use linters pycodestyle and flake8.
Add a "make meta" target to run tests of the test infrastructure. For now
it just has the linters, but we'll add more in future.
Signed-off-by: David Gibson
Create a Python package "tasst" with common helper code for use in passt
and pasta. Initially it just has a placeholder selftest.
Extend the 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
Add to the tasst library a SimNetHost class used to represent a
simulated network host of some type (e.g. namespaces, VMs). For now
all it does is lets you execute commands, either foreground or
background in the context of the simulated host. We add some "meta"
exeter tests for it.
Signed-off-by: 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
Use our existing nstool C helper, add python wrappers to easily run
commands in various namespaces.
Signed-off-by: David Gibson
Add a helper to bring network interfaces up on an snh, and to retrieve
configured IP addresses.
Signed-off-by: David Gibson
Signed-off-by: David Gibson
Signed-off-by: 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
Signed-off-by: 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 selftests to verify those
work as expected when we don't have pasta or passt involved yet.
Signed-off-by: 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
Signed-iff-by: David Gibson
Signed-off-by: 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
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. Thus testing the no NAT case that passt/pasta emphasizes.
(We need to add tests for the NAT cases back in).
Signed-off-by: David Gibson
On Mon, Aug 05, 2024 at 10:36:39PM +1000, David Gibson wrote:
Here's a rough proof of concept showing how we could run tests for passt with Avocado and the exeter library I recently created. It includes Cleber's patch adding some basic Avocado tests and builds on that.
The current draft is pretty janky: * The build rules to download and install the necessary pieces are messy * We create the Avocado job files from the exeter sources in the Makefile. Ideally Avocado would eventually be extended to handle this itself * The names that Avocado sees for each test are overlong * There's some hacks to make sure things are executed from the right working directory
But, it's a starting point.
Stefano,
If you could look particularly at 6/22 and 22/22 which add the real tests for passt/pasta, that would be great. The more specific you can be about what you find ugly about how the tests are written, then better I can try to address that.
I suspect it will be easier to actually apply the series, then look at the new test files (test/build/build.py, and test/pasta/pasta.py particularly). From there you can look at as much of the support library as you need to, rather than digging through the actual patches to look for that.
Forgot to mention. Patches 1 & 2 should be good to go regardless of what we do with the rest of the testing stuff. -- David Gibson (he or they) | 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
On Tue, 6 Aug 2024 22:28:19 +1000
David Gibson
On Mon, Aug 05, 2024 at 10:36:39PM +1000, David Gibson wrote:
Here's a rough proof of concept showing how we could run tests for passt with Avocado and the exeter library I recently created. It includes Cleber's patch adding some basic Avocado tests and builds on that.
The current draft is pretty janky: * The build rules to download and install the necessary pieces are messy * We create the Avocado job files from the exeter sources in the Makefile. Ideally Avocado would eventually be extended to handle this itself * The names that Avocado sees for each test are overlong * There's some hacks to make sure things are executed from the right working directory
But, it's a starting point.
Stefano,
If you could look particularly at 6/22 and 22/22 which add the real tests for passt/pasta, that would be great. The more specific you can be about what you find ugly about how the tests are written, then better I can try to address that.
I suspect it will be easier to actually apply the series, then look at the new test files (test/build/build.py, and test/pasta/pasta.py particularly). From there you can look at as much of the support library as you need to, rather than digging through the actual patches to look for that.
Forgot to mention. Patches 1 & 2 should be good to go regardless of what we do with the rest of the testing stuff.
Applied up to 2/22. -- Stefano
participants (2)
-
David Gibson
-
Stefano Brivio