diff options
-rw-r--r-- | packet.c | 54 | ||||
-rw-r--r-- | packet.h | 2 | ||||
-rw-r--r-- | tap.c | 4 | ||||
-rw-r--r-- | test/.gitignore | 2 | ||||
-rw-r--r-- | test/Makefile | 24 | ||||
-rw-r--r-- | test/build/all | 61 | ||||
-rwxr-xr-x | test/build/build.py | 109 | ||||
-rw-r--r-- | test/build/clang_tidy | 17 | ||||
-rw-r--r-- | test/build/cppcheck | 17 | ||||
-rwxr-xr-x | test/build/static_checkers.sh | 26 | ||||
-rw-r--r-- | test/lib/exeter | 58 | ||||
-rw-r--r-- | test/pasta_options/log_to_file | 10 | ||||
-rwxr-xr-x | test/run | 18 | ||||
-rwxr-xr-x | test/smoke/smoke.sh | 33 |
14 files changed, 306 insertions, 129 deletions
@@ -91,14 +91,17 @@ static int packet_check_range(const struct pool *p, const char *ptr, size_t len, return 0; } /** - * pool_full() - Is a packet pool full? + * pool_can_fit() - Can a new packet fit in the pool? * @p: Pointer to packet pool + * @data: check data can fit in the pool * - * Return: true if the pool is full, false if more packets can be added + * Return: true if @data can be added, false otherwise */ -bool pool_full(const struct pool *p) +bool pool_can_fit(const struct pool *p, struct iov_tail *data) { - return p->count >= p->size; + iov_tail_prune(data); + + return p->count + data->cnt + (data->cnt > 1) <= p->size; } /** @@ -111,11 +114,9 @@ bool pool_full(const struct pool *p) void packet_add_do(struct pool *p, struct iov_tail *data, const char *func, int line) { - size_t idx = p->count; - const char *start; - size_t len; + size_t idx = p->count, i, offset; - if (pool_full(p)) { + if (!pool_can_fit(p, data)) { debug("add packet index %zu to pool with size %zu, %s:%i", idx, p->size, func, line); return; @@ -124,18 +125,30 @@ void packet_add_do(struct pool *p, struct iov_tail *data, if (!iov_tail_prune(data)) return; - ASSERT(data->cnt == 1); /* we don't support iovec */ + if (data->cnt > 1) { + p->pkt[idx].iov_base = NULL; + p->pkt[idx].iov_len = data->cnt; + idx++; + } - len = data->iov[0].iov_len - data->off; - start = (char *)data->iov[0].iov_base + data->off; + offset = data->off; + for (i = 0; i < data->cnt; i++) { + const char *start; + size_t len; - if (packet_check_range(p, start, len, func, line)) - return; + len = data->iov[i].iov_len - offset; + start = (char *)data->iov[i].iov_base + offset; + offset = 0; - p->pkt[idx].iov_base = (void *)start; - p->pkt[idx].iov_len = len; + if (packet_check_range(p, start, len, func, line)) + return; - p->count++; + p->pkt[idx].iov_base = (void *)start; + p->pkt[idx].iov_len = len; + idx++; + } + + p->count = idx; } /** @@ -165,9 +178,14 @@ bool packet_get_do(const struct pool *p, size_t idx, return false; } - data->cnt = 1; + if (p->pkt[idx].iov_base) { + data->cnt = 1; + data->iov = &p->pkt[idx]; + } else { + data->cnt = p->pkt[idx].iov_len; + data->iov = &p->pkt[idx + 1]; + } data->off = 0; - data->iov = &p->pkt[idx]; for (i = 0; i < data->cnt; i++) { ASSERT_WITH_MSG(!packet_check_range(p, data->iov[i].iov_base, @@ -37,7 +37,7 @@ void packet_add_do(struct pool *p, struct iov_tail *data, const char *func, int line); bool packet_get_do(const struct pool *p, const size_t idx, struct iov_tail *data, const char *func, int line); -bool pool_full(const struct pool *p); +bool pool_can_fit(const struct pool *p, struct iov_tail *data); void pool_flush(struct pool *p); #define packet_add(p, data) \ @@ -1103,14 +1103,14 @@ void tap_add_packet(struct ctx *c, struct iov_tail *data, switch (ntohs(eh->h_proto)) { case ETH_P_ARP: case ETH_P_IP: - if (pool_full(pool_tap4)) { + if (!pool_can_fit(pool_tap4, data)) { tap4_handler(c, pool_tap4, now); pool_flush(pool_tap4); } packet_add(pool_tap4, data); break; case ETH_P_IPV6: - if (pool_full(pool_tap6)) { + if (!pool_can_fit(pool_tap6, data)) { tap6_handler(c, pool_tap6, now); pool_flush(pool_tap6); } diff --git a/test/.gitignore b/test/.gitignore index 3573444..9412f0d 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -11,3 +11,5 @@ nstool rampstream guest-key guest-key.pub +/exeter/ +*.bats diff --git a/test/Makefile b/test/Makefile index bf63db8..a774285 100644 --- a/test/Makefile +++ b/test/Makefile @@ -5,6 +5,8 @@ # Copyright Red Hat # Author: David Gibson <david@gibson.dropbear.id.au> +BATS = bats -j $(shell nproc) +EXETOOL = exeter/exetool/exetool WGET = wget -c DEBIAN_IMGS = debian-8.11.0-openstack-amd64.qcow2 \ @@ -50,18 +52,24 @@ 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 podman \ +DOWNLOAD_ASSETS = exeter mbuto podman \ $(DEBIAN_IMGS) $(FEDORA_IMGS) $(OPENSUSE_IMGS) $(UBUNTU_IMGS) TESTDATA_ASSETS = small.bin big.bin medium.bin \ rampstream LOCAL_ASSETS = mbuto.img mbuto.mem.img podman/bin/podman QEMU_EFI.fd \ $(DEBIAN_IMGS:%=prepared-%) $(FEDORA_IMGS:%=prepared-%) \ $(UBUNTU_NEW_IMGS:%=prepared-%) \ - nstool guest-key guest-key.pub \ + nstool guest-key guest-key.pub $(EXETOOL) \ $(TESTDATA_ASSETS) ASSETS = $(DOWNLOAD_ASSETS) $(LOCAL_ASSETS) +EXETER_PYPATH = exeter/py3 +EXETER_BATS = smoke/smoke.sh.bats \ + build/build.py.bats build/static_checkers.sh.bats +BATS_FILES = $(EXETER_BATS) \ + podman/test/system/505-networking-pasta.bats + CFLAGS = -Wall -Werror -Wextra -pedantic -std=c99 assets: $(ASSETS) @@ -70,6 +78,11 @@ assets: $(ASSETS) pull-%: % git -C $* pull +exeter: + git clone https://gitlab.com/dgibson/exeter.git + +exeter/exetool/exetool: pull-exeter + mbuto: git clone git://mbuto.sh/mbuto @@ -115,6 +128,12 @@ medium.bin: big.bin: dd if=/dev/urandom bs=1M count=10 of=$@ +$(EXETER_BATS): %.bats: % $(EXETOOL) + PYTHONPATH=$(EXETER_PYPATH) $(EXETOOL) bats -- $< > $@ + +bats: $(BATS_FILES) pull-podman + PYTHONPATH=$(EXETER_PYPATH) CONTAINERS_HELPER_BINARY_DIR=.. $(BATS) $(BATS_FILES) + check: assets ./run @@ -124,6 +143,7 @@ debug: assets clean: rm -f perf.js *~ rm -f $(LOCAL_ASSETS) + rm -f $(EXETER_BATS) rm -rf test_logs rm -f prepared-*.qcow2 prepared-*.img diff --git a/test/build/all b/test/build/all deleted file mode 100644 index 1f79e0d..0000000 --- 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/build/build.py b/test/build/build.py new file mode 100755 index 0000000..e49287c --- /dev/null +++ b/test/build/build.py @@ -0,0 +1,109 @@ +#! /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.py - Test build and install targets +# +# Copyright Red Hat +# Author: David Gibson <david@gibson.dropbear.id.au> + +import contextlib +import os +from pathlib import Path +import subprocess +import tempfile +from typing import Iterable, Iterator + +import exeter + +def sh(cmd): + """Run given command in a shell""" + subprocess.run(cmd, shell=True) + + +@contextlib.contextmanager +def clone_sources() -> Iterator[str]: + """Create a temporary copy of the passt sources. + + When the context enters create a temporary directory and copy the + passt sources into it. Clean it up when the context exits. + """ + + os.chdir('..') # Move from test/ to repo base + with tempfile.TemporaryDirectory(ignore_cleanup_errors=False) as tmpdir: + sh(f"cp --parents -d $(git ls-files) {tmpdir}") + os.chdir(tmpdir) + yield tmpdir + + +def test_make(target: str, expected_files: list[str]) -> None: + """Test `make {target}` + + Arguments: + target -- make target to invoke + expected_files -- files make is expected to create + + Verifies that + 1) `make target` completes successfully + 2) expected_files care created by `make target` + 3) expected_files are removed by `make clean` + """ + + ex_paths = [Path(f) for f in expected_files] + with clone_sources(): + for p in ex_paths: + assert not p.exists(), f"{p} existed before make" + sh(f'make {target} CFLAGS="-Werror"') + for p in ex_paths: + assert p.exists(), f"{p} wasn't made" + sh('make clean') + for p in ex_paths: + assert not p.exists(), f"{p} existed after make clean" + + +exeter.register('make_passt', test_make, 'passt', ['passt']) +exeter.register('make_pasta', test_make, 'pasta', ['pasta']) +exeter.register('make_qrap', test_make, 'qrap', ['qrap']) +exeter.register('make_all', test_make, 'all', ['passt', 'pasta', 'qrap']) + + +@exeter.test +def test_install_uninstall() -> None: + """Test `make install` and `make uninstall` + + Tests that `make install` installs the expected files to the + install prefix, and that `make uninstall` removes them again. + """ + + with clone_sources(): + with tempfile.TemporaryDirectory(ignore_cleanup_errors=False) \ + as prefix: + bindir = Path(prefix) / 'bin' + mandir = Path(prefix) / 'share/man' + progs = ['passt', 'pasta', 'qrap'] + + # Install + sh(f'make install CFLAGS="-Werror" prefix={prefix}') + + for prog in progs: + exe = bindir / prog + assert exe.is_file(), f"{exe} does not exist as a regular file" + sh(f'man -M {mandir} -W {prog}') + + # Uninstall + sh(f'make uninstall prefix={prefix}') + + for prog in progs: + exe = bindir / prog + assert not exe.exists(), f"{exe} exists after uninstall" + sh(f'! man -M {mandir} -W {prog}') + + +if __name__ == '__main__': + exeter.main() diff --git a/test/build/clang_tidy b/test/build/clang_tidy deleted file mode 100644 index 40573bf..0000000 --- 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 0e1dbce..0000000 --- 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/build/static_checkers.sh b/test/build/static_checkers.sh new file mode 100755 index 0000000..42806e7 --- /dev/null +++ b/test/build/static_checkers.sh @@ -0,0 +1,26 @@ +#! /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/build/static_checkers.sh - Run static checkers +# +# Copyright Red Hat +# Author: David Gibson <david@gibson.dropbear.id.au> + +. $(dirname $0)/../exeter/sh/exeter.sh + +exeter_register cppcheck make -C .. cppcheck +exeter_set_description cppcheck "passt sources pass cppcheck" + +exeter_register clang_tidy make -C .. clang-tidy +exeter_set_description clang_tidy "passt sources pass clang-tidy" + +exeter_main "$@" + + diff --git a/test/lib/exeter b/test/lib/exeter new file mode 100644 index 0000000..3b19bea --- /dev/null +++ b/test/lib/exeter @@ -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/exeter - Run exeter tests within the rest of passt's tests +# +# Copyright Red Hat +# Author: David Gibson <david@gibson.dropbear.id.au> + +EXETOOL="$BASEPATH/exeter/exetool/exetool" + +# is_exeter() - Determine if a test file is an exeter program +# $@: Command line to invoke test program +is_exeter() { + $EXETOOL probe -- "$@" +} + +# exeter() - Run each test in an exeter program, logging each test separately +# $@: Command line to invoke exeter test program +exeter() { + STATESETUP="${STATEBASE}/$1" + mkdir -p "${STATESETUP}" + + context_setup_host host + layout_host + + cd test + + __ntests=$($EXETOOL list -- "$@" | wc -l) + if [ $? != 0 ]; then + info "Failed to get exeter manifest for $@" + pause_continue \ + "Press any key to pause test session" \ + "Resuming in " \ + "Paused, press any key to continue" \ + 5 + return + fi + + status_file_start "$* (exeter)" ${__ntests} + [ ${CI} -eq 1 ] && video_link "${1}" + + for __testid in $($EXETOOL list -- "$@"); do + __desc="$($EXETOOL desc -- "$@" -- ${__testid})" + status_test_start "${__desc}" + context_run host "$@" "${__testid}" && status_test_ok || status_test_fail + done + + cd .. + + teardown_context_watch ${PANE_HOST} host +} diff --git a/test/pasta_options/log_to_file b/test/pasta_options/log_to_file index 3ead06c..db78b04 100644 --- a/test/pasta_options/log_to_file +++ b/test/pasta_options/log_to_file @@ -30,19 +30,19 @@ endef test Log creation -set PORTS -t 10001,10002 -u 10001,10002 +set PORTS -t 10001,10002 -u 10001,10002 -T none -U none set LOG_FILE __STATEDIR__/pasta.log -passt ./pasta -l __LOG_FILE__ -- /bin/true +passt ./pasta __PORTS__ -l __LOG_FILE__ -- /bin/true check [ -s __LOG_FILE__ ] test Log truncated on creation -passt ./pasta -l __LOG_FILE__ -- /bin/true & wait +passt ./pasta __PORTS__ -l __LOG_FILE__ -- /bin/true & wait pout PID2 echo $! check head -1 __LOG_FILE__ | grep '^pasta .* [(]__PID2__[)]$' 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 -l1 -P 10001 -C 10002 -6; done' +passtb ./pasta __PORTS__ --config-net -d -f -l __LOG_FILE__ --log-size $((100 * 1024)) -- sh -c 'while true; do tcp_crr --nolog -l1 -P 10001 -C 10002 -6; done' sleep 1 flood_log_client @@ -67,7 +67,7 @@ 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)) +passt ./pasta __PORTS__ --config-net -d -l __LOG_FILE__ --log-size $((100 * 1024)) flood_log_server flood_log_client @@ -43,6 +43,9 @@ KERNEL=${KERNEL:-"/boot/vmlinuz-$(uname -r)"} COMMIT="$(git log --oneline --no-decorate -1)" +# Let exeter tests written in Python find their modules +export PYTHONPATH=${BASEPATH}/exeter/py3 + . lib/util . lib/context . lib/setup @@ -53,6 +56,7 @@ COMMIT="$(git log --oneline --no-decorate -1)" . lib/layout_ugly . lib/test . lib/video +. lib/exeter # cleanup() - Remove temporary files cleanup() { @@ -67,11 +71,9 @@ run() { perf_init [ ${CI} -eq 1 ] && video_start ci - setup build - test build/all - test build/cppcheck - test build/clang_tidy - teardown build + exeter smoke/smoke.sh + exeter build/build.py + exeter build/static_checkers.sh setup pasta test pasta/ndp @@ -223,6 +225,10 @@ run_selected() { __setup= for __test; do + if is_exeter "test/${__test}"; then + exeter "${__test}" + continue + fi # HACK: the migrate tests need the setup repeated for # each test if [ "${__test%%/*}" != "${__setup}" -o \ @@ -234,7 +240,7 @@ run_selected() { test "${__test}" done - teardown "${__setup}" + [ -n "${__setup}" ] && teardown "${__setup}" log "PASS: ${STATUS_PASS}, FAIL: ${STATUS_FAIL}, SKIPPED: ${STATUS_SKIPPED}" diff --git a/test/smoke/smoke.sh b/test/smoke/smoke.sh new file mode 100755 index 0000000..a642fb9 --- /dev/null +++ b/test/smoke/smoke.sh @@ -0,0 +1,33 @@ +#! /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/smoke/smoke.sh - Basic smoke tests +# +# Copyright Red Hat +# Author: David Gibson <david@gibson.dropbear.id.au> + +. $(dirname $0)/../exeter/sh/exeter.sh + +PASST=$(dirname $0)/../../passt +PASTA=$(dirname $0)/../../pasta + +exeter_register passt_version $PASST --version +exeter_set_description passt_version "Check passt --version works" + +exeter_register pasta_version $PASTA --version +exeter_set_description pasta_version "Check pasta --version works" + +exeter_register passt_help $PASST --help +exeter_set_description passt_help "Check passt --help works" + +exeter_register pasta_help $PASTA --help +exeter_set_description pasta_help "Check pasta --help works" + +exeter_main "$@" |