From 427b959a1c7937c935781472f28922203e4681a9 Mon Sep 17 00:00:00 2001 From: Ed Santiago Date: Fri, 30 Jun 2023 09:13:06 -0600 Subject: [PATCH] pasta tests: automatically determine test parameters ...from the test name. Eliminates scary duplication. Followup to #19053: instead of cross-checking pasta test args against test name, eliminate the args entirely. Determine them all from the @test name itself. Example: "TCP translated port range forwarding, IPv4, loopback" | | | | | | +-- iftype=loopback | | | | | +-------- ip_ver=4 | | | | +-------------------- bytes=1 | | | +-------------------------- range=3 | | +------------------------------- (ignored) | +------------------------------------------ delta=1 +--------------------------------------------- proto=tcp Signed-off-by: Ed Santiago --- test/system/505-networking-pasta.bats | 302 ++++++++++++++------------ 1 file changed, 169 insertions(+), 133 deletions(-) diff --git a/test/system/505-networking-pasta.bats b/test/system/505-networking-pasta.bats index 512b0c18ba..b93d29db75 100644 --- a/test/system/505-networking-pasta.bats +++ b/test/system/505-networking-pasta.bats @@ -33,85 +33,121 @@ function default_addr() { ip -j -"${ip_ver}" addr show "${ifname}" | jq -rM "${expr}" } +# _set_opt() - meta-helper for pasta_test_do. +# +# Sets an option, but panics if option is already set (e.g. UDP+TCP, IPv4/v6) +function _set_opt() { + local opt_name=$1 + local -n opt_ref=$1 + local newval=$2 + + if [[ -n "$opt_ref" ]]; then + # $kw sneakily inherited from caller + die "'$kw' in test name sets $opt_name='$newval', but $opt_name has already been set to '$opt_ref'" + fi + opt_ref=$newval +} + # pasta_test_do() - Run tests involving clients and servers -# $1: IP version: 4 or 6 -# $2: Interface type: "tap" or "loopback" -# $3: Protocol: "tcp" or "udp" -# $4: Size of port range, 1 for tests over a single port -# $5: Delta for remapped destination ports in guest -# $6: How specifically pasta will bind: "port", "address", or "interface" -# $7: Bytes to transfer, with multiplicative suffixes as supported by dd(1) +# +# This helper function is invoked without arguments; it determines what to do +# based on the @test name. function pasta_test_do() { - local ip_ver="${1}" - local iftype="${2}" - local proto="${3}" - local range="${4}" - local delta="${5}" - local bind_type="${6}" - local bytes="${7}" + local ip_ver iftype proto range delta bind_type bytes - # DO NOT ADD ANY CODE ABOVE THIS LINE! Especially skips: that could - # lead to the crossreference check not running in CI. + # Normalize test name back to human-readable form. BATS gives us a + # sanitized string with non-alnum converted to '-XX' (dash-hexbyte) + # and spaces converted to underscores. Convert all of those to + # spaces, then strip off the boilerplate "blah blah with pasta(1)". + # This then gives us only the important (mutable) part of the test: # - # BATS has no table-driven test generation mechanism, so there's a - # disturbing but unavoidable amount of duplication in the test - # invocations. The code below is a desperate sanity check to confirm - # that the BATS test name agrees with the parameters we're invoked with. - # This test can never, ever fail in gating. It can only fail in CI - # when a new test is added, and should be trivial to fix & re-push. + # test_...pasta..._-2d_TCP_translated_..._forwarding-2c_IPv4-2c_loopback + # -> test ...pasta ...... TCP translated ... forwarding IPv4 loopback + # -> TCP translated forwarding IPv4 loopback + local test_name=$(printf "$(sed \ + -e 's/-\([0-9a-f]\{2\}\)/ /gI' \ + -e 's/_/ /g' \ + -e 's/^.* with pasta[ ]*1//' \ + <<<"${BATS_TEST_NAME}")") + + # We now have the @test name as specified in the script, minus punctuation. + # From each of the name components, determine an action. # - # TODO: a better idea might be to go the other direction: eliminate - # the function args entirely, and determine them from $BATS_TEST_NAME. - # We would still need a way to get $range and $bytes. - local expected_test_name= + # TCP translated port range forwarding IPv4 loopback + # | | | | | | \__ iftype=loopback + # | | | | | \________ ip_ver=4 + # | | | | \____________________ bytes=1 + # | | | \__________________________ range=3 + # | | \_______________________________ (ignored) + # | \__________________________________________ delta=1 + # \______________________________________________ proto=tcp + # + # Each keyword maps to one option. Conflicts ("TCP ... UDP") are fatal + # errors, as are unknown keywords. + for kw in $test_name; do + case $kw in + TCP|UDP) _set_opt proto ${kw,,} ;; + IPv*) _set_opt ip_ver $(expr "$kw" : "IPv\(.\)") ;; + Single) _set_opt range 1 ;; + range) _set_opt range 3 ;; + Address|Interface) _set_opt bind_type ${kw,,} ;; + bound) assert "$bind_type" != "" "WHAT-bound???" ;; + [Tt]ranslated) _set_opt delta 1 ;; + loopback|tap) _set_opt iftype $kw ;; + port) ;; # always occurs with 'forwarding'; ignore + forwarding) _set_opt bytes 1 ;; + large|small) _set_opt bytes $kw ;; + transfer) assert "$bytes" != "" "'transfer' must be preceded by 'large' or 'small'" ;; + *) die "cannot grok '$kw' in test name" ;; + esac + done + + # Sanity checks: all test names must include IPv4/6 and TCP/UDP + test -n "$ip_ver" || die "Test name must include IPv4 or IPv6" + test -n "$proto" || die "Test name must include TCP or UDP" + test -n "$bytes" || die "Test name must include 'forwarding' or 'large/small transfer'" + + # Major decision point: simple forwarding test, or multi-byte transfer? if [[ $bytes -eq 1 ]]; then - # The usual case: a single-byte transfer test. This has many - # variations depending on our input args - if [[ $range -eq 1 ]]; then - # e.g., Single TCP port forwarding, IPv4, loopback - if [[ $delta -ne 0 ]]; then - expected_test_name+="Translated" - elif [[ "${bind_type}" = "port" ]]; then - expected_test_name+="Single" - else - expected_test_name+="${bind_type^}-bound" - fi - expected_test_name+=" ${proto^^} port" - else - # e.g., TCP translated port range forwarding, IPv4, tap - expected_test_name+="${proto^^}" - if [[ $delta -ne 0 ]]; then - expected_test_name+=" translated" - fi - expected_test_name+=" port range" - fi - expected_test_name+=" forwarding, IPv${ip_ver}, ${iftype}" + # Simple forwarding check + # We can't always determine these from the test name. Use sane defaults. + range=${range:-1} + delta=${delta:-0} + bind_type=${bind_type:-port} else - # Multi-byte. 2k is the common size for small, all else is large - local size="large" - if [[ $bytes = "2k" ]]; then - size="small" - fi - expected_test_name="${proto^^}/IPv${ip_ver} $size transfer, ${iftype}" + # Data transfer. Translate small/large to dd-recognizable sizes + case "$bytes" in + small) bytes="2k" ;; + large) case "$proto" in + tcp) bytes="10M" ;; + udp) bytes=$(($(cat /proc/sys/net/core/wmem_default) / 4)) ;; + *) die "Internal error: unknown proto '$proto'" ;; + esac + ;; + *) die "Internal error: unknown transfer size '$bytes'" ;; + esac - # No other input args are variable. - assert "$range" = "1" "range must = 1 on multibyte transfers" - assert "$delta" = "0" "delta must = 0 on multibyte transfers" - assert "$bind_type" = "port" "bind_type must = 'port' on multibyte transfers" + # On data transfers, no other input args can be set in test name. + # Confirm that they are not defined, and set to a suitable default. + kw="something" + _set_opt range 1 + _set_opt delta 0 + _set_opt bind_type port fi - # Normalize test name back to human-readable form: strip common prefix, - # convert '-XX' to chr (dashes, commas) and underscore to space. - # Sorry this is so convoluted. - local actual_test_name=$(printf "$(sed \ - -e 's/^test_podman_networking_with_pasta-281-29_-2d_//' \ - -e 's/-\([0-9a-f]\{2\}\)/\\x\1/gI' \ - -e 's/_/ /g' \ - <<<"${BATS_TEST_NAME}")") - - assert "$actual_test_name" = "$expected_test_name" \ - "INTERNAL ERROR! Mismatch between BATS test name and test args!" + # Dup check: make sure we haven't already run this combination of settings. + # This serves two purposes: + # 1) prevent developer from accidentally copy/pasting the same test + # 2) make sure our test-name-parsing code isn't missing anything important + local tests_run=${BATS_FILE_TMPDIR}/tests_run + touch ${tests_run} + local testid="IPv${ip_ver} $proto $iftype $bind_type range=$range delta=$delta bytes=$bytes" + if grep -q -F -- "$testid" ${tests_run}; then + die "Duplicate test! Have already run $testid" + fi + echo "$testid" >>${tests_run} + # Done figuring out test params. Now do the real work. # Calculate and set addresses, if [ ${ip_ver} -eq 4 ]; then skip_if_no_ipv4 "IPv4 not routable on the host" @@ -303,7 +339,7 @@ function teardown() { run_podman pod create --net=pasta --name "${pname}" run_podman run --pod="${pname}" "${IMAGE}" getent hosts "${pname}" - assert "$(echo ${output} | cut -f1 -d' ')" = "${ip}" "Correct /etc/hsots entry missing" + assert "$(echo ${output} | cut -f1 -d' ')" = "${ip}" "Correct /etc/hosts entry missing" run_podman pod rm "${pname}" run_podman rmi $(pause_image) @@ -449,273 +485,273 @@ function teardown() { ### TCP/IPv4 Port Forwarding ################################################### @test "podman networking with pasta(1) - Single TCP port forwarding, IPv4, tap" { - pasta_test_do 4 tap tcp 1 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Single TCP port forwarding, IPv4, loopback" { - pasta_test_do 4 loopback tcp 1 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - TCP port range forwarding, IPv4, tap" { - pasta_test_do 4 tap tcp 3 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - TCP port range forwarding, IPv4, loopback" { - pasta_test_do 4 loopback tcp 3 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Translated TCP port forwarding, IPv4, tap" { - pasta_test_do 4 tap tcp 1 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Translated TCP port forwarding, IPv4, loopback" { - pasta_test_do 4 loopback tcp 1 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - TCP translated port range forwarding, IPv4, tap" { - pasta_test_do 4 tap tcp 3 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - TCP translated port range forwarding, IPv4, loopback" { - pasta_test_do 4 loopback tcp 3 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Address-bound TCP port forwarding, IPv4, tap" { - pasta_test_do 4 tap tcp 1 0 "address" 1 + pasta_test_do } @test "podman networking with pasta(1) - Address-bound TCP port forwarding, IPv4, loopback" { - pasta_test_do 4 loopback tcp 1 0 "address" 1 + pasta_test_do } @test "podman networking with pasta(1) - Interface-bound TCP port forwarding, IPv4, tap" { - pasta_test_do 4 tap tcp 1 0 "interface" 1 + pasta_test_do } @test "podman networking with pasta(1) - Interface-bound TCP port forwarding, IPv4, loopback" { - pasta_test_do 4 loopback tcp 1 0 "interface" 1 + pasta_test_do } ### TCP/IPv6 Port Forwarding ################################################### @test "podman networking with pasta(1) - Single TCP port forwarding, IPv6, tap" { - pasta_test_do 6 tap tcp 1 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Single TCP port forwarding, IPv6, loopback" { - pasta_test_do 6 loopback tcp 1 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - TCP port range forwarding, IPv6, tap" { - pasta_test_do 6 tap tcp 3 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - TCP port range forwarding, IPv6, loopback" { - pasta_test_do 6 loopback tcp 3 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Translated TCP port forwarding, IPv6, tap" { - pasta_test_do 6 tap tcp 1 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Translated TCP port forwarding, IPv6, loopback" { - pasta_test_do 6 loopback tcp 1 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - TCP translated port range forwarding, IPv6, tap" { - pasta_test_do 6 tap tcp 3 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - TCP translated port range forwarding, IPv6, loopback" { - pasta_test_do 6 loopback tcp 3 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Address-bound TCP port forwarding, IPv6, tap" { - pasta_test_do 6 tap tcp 1 0 "address" 1 + pasta_test_do } @test "podman networking with pasta(1) - Address-bound TCP port forwarding, IPv6, loopback" { - pasta_test_do 6 loopback tcp 1 0 "address" 1 + pasta_test_do } @test "podman networking with pasta(1) - Interface-bound TCP port forwarding, IPv6, tap" { - pasta_test_do 6 tap tcp 1 0 "interface" 1 + pasta_test_do } @test "podman networking with pasta(1) - Interface-bound TCP port forwarding, IPv6, loopback" { - pasta_test_do 6 loopback tcp 1 0 "interface" 1 + pasta_test_do } ### UDP/IPv4 Port Forwarding ################################################### @test "podman networking with pasta(1) - Single UDP port forwarding, IPv4, tap" { - pasta_test_do 4 tap udp 1 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Single UDP port forwarding, IPv4, loopback" { - pasta_test_do 4 loopback udp 1 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - UDP port range forwarding, IPv4, tap" { - pasta_test_do 4 tap udp 3 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - UDP port range forwarding, IPv4, loopback" { - pasta_test_do 4 loopback udp 3 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Translated UDP port forwarding, IPv4, tap" { - pasta_test_do 4 tap udp 1 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Translated UDP port forwarding, IPv4, loopback" { - pasta_test_do 4 loopback udp 1 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - UDP translated port range forwarding, IPv4, tap" { - pasta_test_do 4 tap udp 3 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - UDP translated port range forwarding, IPv4, loopback" { - pasta_test_do 4 loopback udp 3 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Address-bound UDP port forwarding, IPv4, tap" { - pasta_test_do 4 tap udp 1 0 "address" 1 + pasta_test_do } @test "podman networking with pasta(1) - Address-bound UDP port forwarding, IPv4, loopback" { - pasta_test_do 4 loopback udp 1 0 "address" 1 + pasta_test_do } @test "podman networking with pasta(1) - Interface-bound UDP port forwarding, IPv4, tap" { - pasta_test_do 4 tap udp 1 0 "interface" 1 + pasta_test_do } @test "podman networking with pasta(1) - Interface-bound UDP port forwarding, IPv4, loopback" { - pasta_test_do 4 loopback udp 1 0 "interface" 1 + pasta_test_do } ### UDP/IPv6 Port Forwarding ################################################### @test "podman networking with pasta(1) - Single UDP port forwarding, IPv6, tap" { - pasta_test_do 6 tap udp 1 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Single UDP port forwarding, IPv6, loopback" { - pasta_test_do 6 loopback udp 1 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - UDP port range forwarding, IPv6, tap" { - pasta_test_do 6 tap udp 3 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - UDP port range forwarding, IPv6, loopback" { - pasta_test_do 6 loopback udp 3 0 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Translated UDP port forwarding, IPv6, tap" { - pasta_test_do 6 tap udp 1 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Translated UDP port forwarding, IPv6, loopback" { - pasta_test_do 6 loopback udp 1 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - UDP translated port range forwarding, IPv6, tap" { - pasta_test_do 6 tap udp 3 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - UDP translated port range forwarding, IPv6, loopback" { - pasta_test_do 6 loopback udp 3 1 "port" 1 + pasta_test_do } @test "podman networking with pasta(1) - Address-bound UDP port forwarding, IPv6, tap" { - pasta_test_do 6 tap udp 1 0 "address" 1 + pasta_test_do } @test "podman networking with pasta(1) - Address-bound UDP port forwarding, IPv6, loopback" { - pasta_test_do 6 loopback udp 1 0 "address" 1 + pasta_test_do } @test "podman networking with pasta(1) - Interface-bound UDP port forwarding, IPv6, tap" { - pasta_test_do 6 tap udp 1 0 "interface" 1 + pasta_test_do } @test "podman networking with pasta(1) - Interface-bound UDP port forwarding, IPv6, loopback" { - pasta_test_do 6 loopback udp 1 0 "interface" 1 + pasta_test_do } ### TCP/IPv4 transfer ########################################################## @test "podman networking with pasta(1) - TCP/IPv4 small transfer, tap" { - pasta_test_do 4 tap tcp 1 0 "port" 2k + pasta_test_do } @test "podman networking with pasta(1) - TCP/IPv4 small transfer, loopback" { - pasta_test_do 4 loopback tcp 1 0 "port" 2k + pasta_test_do } @test "podman networking with pasta(1) - TCP/IPv4 large transfer, tap" { - pasta_test_do 4 tap tcp 1 0 "port" 10M + pasta_test_do } @test "podman networking with pasta(1) - TCP/IPv4 large transfer, loopback" { - pasta_test_do 4 loopback tcp 1 0 "port" 10M + pasta_test_do } ### TCP/IPv6 transfer ########################################################## @test "podman networking with pasta(1) - TCP/IPv6 small transfer, tap" { - pasta_test_do 6 tap tcp 1 0 "port" 2k + pasta_test_do } @test "podman networking with pasta(1) - TCP/IPv6 small transfer, loopback" { - pasta_test_do 6 loopback tcp 1 0 "port" 2k + pasta_test_do } @test "podman networking with pasta(1) - TCP/IPv6 large transfer, tap" { - pasta_test_do 6 tap tcp 1 0 "port" 10M + pasta_test_do } @test "podman networking with pasta(1) - TCP/IPv6 large transfer, loopback" { - pasta_test_do 6 loopback tcp 1 0 "port" 10M + pasta_test_do } ### UDP/IPv4 transfer ########################################################## @test "podman networking with pasta(1) - UDP/IPv4 small transfer, tap" { - pasta_test_do 4 tap udp 1 0 "port" 2k + pasta_test_do } @test "podman networking with pasta(1) - UDP/IPv4 small transfer, loopback" { - pasta_test_do 4 loopback udp 1 0 "port" 2k + pasta_test_do } @test "podman networking with pasta(1) - UDP/IPv4 large transfer, tap" { - pasta_test_do 4 tap udp 1 0 "port" $(($(cat /proc/sys/net/core/wmem_default) / 4)) + pasta_test_do } @test "podman networking with pasta(1) - UDP/IPv4 large transfer, loopback" { - pasta_test_do 4 loopback udp 1 0 "port" $(($(cat /proc/sys/net/core/wmem_default) / 4)) + pasta_test_do } ### UDP/IPv6 transfer ########################################################## @test "podman networking with pasta(1) - UDP/IPv6 small transfer, tap" { - pasta_test_do 6 tap udp 1 0 "port" 2k + pasta_test_do } @test "podman networking with pasta(1) - UDP/IPv6 small transfer, loopback" { - pasta_test_do 6 loopback udp 1 0 "port" 2k + pasta_test_do } @test "podman networking with pasta(1) - UDP/IPv6 large transfer, tap" { - pasta_test_do 6 tap udp 1 0 "port" $(($(cat /proc/sys/net/core/wmem_default) / 4)) + pasta_test_do } @test "podman networking with pasta(1) - UDP/IPv6 large transfer, loopback" { - pasta_test_do 6 loopback udp 1 0 "port" $(($(cat /proc/sys/net/core/wmem_default) / 4)) + pasta_test_do } ### ICMP, ICMPv6 ###############################################################