On Wed, Oct 18, 2023 at 12:33:29AM +0200, Thomas Haller wrote: > Some tests cannot pass, for example due to missing kernel features or > kernel bugs. The tests make an educated guess (feature detection), > whether the failure is due to that, and marks the test as SKIP (exit > 77). The problem is, the test might guess wrong and hide a real issue. > > Add support for a NFT_TEST_FAIL_ON_SKIP_EXCEPT= regex to help with this. > Together with NFT_TEST_FAIL_ON_SKIP=y is enabled, test names that match > the regex are allowed to be skipped. Unexpected skips are treated as > fatal. This allows to maintain a list of tests that are known to be > skipped. > > You can think of this as some sort of XFAIL/XPASS mechanism. The > difference is that usually XFAIL is part of the test. Here the failure > happens due to external conditions, as such you need to configure > NFT_TEST_FAIL_ON_SKIP_EXCEPT= per kernel. Also, usually XFAIL is about > failing tests, while this is about tests that are marked to be skipped. > But we mark them as skipped due to a heuristic, and those we want to > manually keep track off. > > Why is NFT_TEST_FAIL_ON_SKIP_EXCEPT= useful and why doesn't it just work > automatically? It does work automatically (see use case 1 below). Trust > the automatism to the right thing, and don't bother. This is when you > don't trust the automatism and you curate a list of tests that are known > to be be skipped, but no others (see use case 3 below). In particular, > it's for running CI on a downstream kernel, where we expect a static > list of skipped tests and where we want to find any changes. > > Consider this: there are three use case for running the tests. > > 1) The contributor, who wants to run the test suite. The tests make a > best effort to pass and when the test detects that the failure is > likely due to the kernel, then it will skip the test. This is the > default. This is not a good default, it is us that are running this tests infrastructure, we are the target users. This contributor should be running latest kernel either from nf.git and nf-next.git > 2) The maintainer, who runs latest kernel and expects that all tests are > passing. Any SKIP is likely a bug. This is achieved by setting > NFT_TEST_FAIL_ON_SKIP=y. I don't want to remember to set this on, I am running this in my test machines all the time. > 3) The downstream maintainer, who test a distro kernel that is known to > lack certain features. They know that a selected few tests should be > skipped, but most tests should pass. By setting NFT_TEST_FAIL_ON_SKIP=y > and NFT_TEST_FAIL_ON_SKIP_EXCEPT= they can specify which are expected to > be skipped. The regex is kernel/environment specific, and must be > actively curated. Such downstream maintainer should be really concerned about the test failure and track the issue to make sure the fix gets to their downstream kernel. > BONUS) actually, cases 2) and 3) optimally run automated CI tests. > Then the test environment is defined with a particular kernel variant > and changes slowly over time. NFT_TEST_FAIL_ON_SKIP_EXCEPT= allows > to pick up any unexpected changes of the skipped/pass behavior of > tests. > > If a test matches the regex but passes, this is also flagged as a > failure ([XPASS]). The user is required to maintain an accurate list of > XFAIL tests. > > Example: > > $ NFT_TEST_FAIL_ON_SKIP=y \ > NFT_TEST_FAIL_ON_SKIP_EXCEPT='^(tests/shell/testcases/nft-f/0017ct_timeout_obj_0|tests/shell/testcases/listing/0020flowtable_0)$' \ > ./tests/shell/run-tests.sh \ > tests/shell/testcases/nft-f/0017ct_timeout_obj_0 \ > tests/shell/testcases/cache/0006_cache_table_flush \ > tests/shell/testcases/listing/0013objects_0 \ > tests/shell/testcases/listing/0020flowtable_0 > ... > I: [SKIPPED] 1/4 tests/shell/testcases/nft-f/0017ct_timeout_obj_0 > I: [OK] 2/4 tests/shell/testcases/cache/0006_cache_table_flush > W: [FAIL-SKIP] 3/4 tests/shell/testcases/listing/0013objects_0 > W: [XPASS] 4/4 tests/shell/testcases/listing/0020flowtable_0 > > This list of XFAIL tests is maintainable, because on a particular > downstream kernel, the number of tests known to be skipped is small and > relatively static. Also, you can generate the list easily (followed by > manual verification!) via > > $ NFT_TEST_FAIL_ON_SKIP=n ./tests/shell/run-tests.sh -k > $ export NFT_TEST_FAIL_ON_SKIP_EXCEPT="$(cat /tmp/nft-test.latest.*/skip-if-fail-except)" > $ NFT_TEST_FAIL_ON_SKIP=y ./tests/shell/run-tests.sh > > Signed-off-by: Thomas Haller <thaller@xxxxxxxxxx> > --- > Sorry for the overly long commit message. I hope it can be useful and > speak in favor of the patch (and not against it). > > This is related to the "eval-exit-code" patch, as it's about how to > handle tests that are SKIPed. But it's relevant for any skipped test, > and not tied to that work. > > tests/shell/helpers/test-wrapper.sh | 41 +++++++++++++++++++++ > tests/shell/run-tests.sh | 55 ++++++++++++++++++++++------- > 2 files changed, 83 insertions(+), 13 deletions(-) > > diff --git a/tests/shell/helpers/test-wrapper.sh b/tests/shell/helpers/test-wrapper.sh > index 13b918f8b8e1..e8edb7332d24 100755 > --- a/tests/shell/helpers/test-wrapper.sh > +++ b/tests/shell/helpers/test-wrapper.sh > @@ -176,10 +176,49 @@ if [ "$tainted_before" != "$tainted_after" ] ; then > rc_tainted=1 > fi > > +rc_fail_on_skip=0 > +rc_fail_on_skip_xpass=0 > +if [ "$NFT_TEST_FAIL_ON_SKIP" = y ] ; then > + if [ -n "${NFT_TEST_FAIL_ON_SKIP_EXCEPT+x}" ] ; then > + > + OLD_IFS="$IFS" > + IFS=$'\n' > + REGEXES=( $NFT_TEST_FAIL_ON_SKIP_EXCEPT ) > + IFS="$OLD_IFST" > + > + re_match=0 > + for re in "${REGEXES[@]}" ; do > + if [[ "$TEST" =~ $re ]] ; then > + re_match=1 > + break; > + fi > + done > + > + if [ "$re_match" -eq 1 ] ; then > + if [ "$rc_test" -eq 0 ] ; then > + echo "test passed although expected to be skipped (XPASS) by regex NFT_TEST_FAIL_ON_SKIP_EXCEPT /$NFT_TEST_FAIL_ON_SKIP_EXCEPT/" > "$NFT_TEST_TESTTMPDIR/rc-failed-skip" > + rc_fail_on_skip_xpass=1 > + fi > + else > + if [ "$rc_test" -eq 77 ] ; then > + echo "upgrade skip to failure due to NFT_TEST_FAIL_ON_SKIP. Regex NFT_TEST_FAIL_ON_SKIP_EXCEPT /$NFT_TEST_FAIL_ON_SKIP_EXCEPT/ does not match" > "$NFT_TEST_TESTTMPDIR/rc-failed-skip" > + rc_fail_on_skip=1 > + fi > + fi > + else > + if [ "$rc_test" -eq 77 ] ; then > + echo "upgrade skip to failure due to NFT_TEST_FAIL_ON_SKIP" > "$NFT_TEST_TESTTMPDIR/rc-failed-skip" > + rc_fail_on_skip=1 > + fi > + fi > +fi > + > if [ "$rc_valgrind" -ne 0 ] ; then > rc_exit=122 > elif [ "$rc_tainted" -ne 0 ] ; then > rc_exit=123 > +elif [ "$rc_fail_on_skip" -ne 0 ] ; then > + rc_exit=120 > elif [ "$rc_test" -ge 118 -a "$rc_test" -le 124 ] ; then > # Special exit codes are reserved. Coerce them. > rc_exit=125 > @@ -189,6 +228,8 @@ elif [ "$rc_dump" -ne 0 ] ; then > rc_exit=124 > elif [ "$rc_chkdump" -ne 0 ] ; then > rc_exit=121 > +elif [ "$rc_fail_on_skip_xpass" -ne 0 ] ; then > + rc_exit=119 > else > rc_exit=0 > fi > diff --git a/tests/shell/run-tests.sh b/tests/shell/run-tests.sh > index 22105c2e90e2..8fceb2795113 100755 > --- a/tests/shell/run-tests.sh > +++ b/tests/shell/run-tests.sh > @@ -221,6 +221,10 @@ usage() { > echo " kernel modules)." > echo " Parallel jobs requires unshare and are disabled with NFT_TEST_UNSHARE_CMD=\"\"." > echo " NFT_TEST_FAIL_ON_SKIP=*|y: if any jobs are skipped, exit with error." > + echo " NFT_TEST_FAIL_ON_SKIP_EXCEPT=<regex>: only honored with NFT_TEST_FAIL_ON_SKIP=y. This is a regex for" > + echo " tests for which a skip is expected and not fatal (XFAIL). Multiple regex can be separated by" > + echo " newline. This allows that skip a selected set of known tests, while all other tests must pass." > + echo " If a test passes but matches the regex, it is treated as failure too (XPASS)." > echo " NFT_TEST_RANDOM_SEED=<SEED>: The test runner will export the environment variable NFT_TEST_RANDOM_SEED" > echo " set to a random number. This can be used as a stable seed for tests to randomize behavior." > echo " Set this to a fixed value to get reproducible behavior." > @@ -305,6 +309,8 @@ export NFT_TEST_RANDOM_SEED > > TESTS=() > > +TESTS_SKIP_EXCEPT=() > + > SETUP_HOST= > SETUP_HOST_OTHER= > > @@ -639,6 +645,11 @@ msg_info "conf: NFT_TEST_HAS_UNSHARED_MOUNT=$(printf '%q' "$NFT_TEST_HAS_UNSHARE > msg_info "conf: NFT_TEST_KEEP_LOGS=$(printf '%q' "$NFT_TEST_KEEP_LOGS")" > msg_info "conf: NFT_TEST_JOBS=$NFT_TEST_JOBS" > msg_info "conf: NFT_TEST_FAIL_ON_SKIP=$NFT_TEST_FAIL_ON_SKIP" > +if [ -z "${NFT_TEST_FAIL_ON_SKIP_EXCEPT+x}" ] ; then > + msg_info "conf: # NFT_TEST_FAIL_ON_SKIP_EXCEPT unset" > +else > + msg_info "conf: NFT_TEST_FAIL_ON_SKIP_EXCEPT=$(printf '%q' "$NFT_TEST_FAIL_ON_SKIP_EXCEPT")" > +fi > msg_info "conf: NFT_TEST_RANDOM_SEED=$NFT_TEST_RANDOM_SEED" > msg_info "conf: NFT_TEST_SHUFFLE_TESTS=$NFT_TEST_SHUFFLE_TESTS" > msg_info "conf: TMPDIR=$(printf '%q' "$_TMPDIR")" > @@ -707,6 +718,7 @@ kernel_cleanup() { > echo "" > ok=0 > skipped=0 > +skipped_as_fail=0 > failed=0 > > kmem_runs=0 > @@ -767,7 +779,7 @@ print_test_header() { > align_text text right "${#s_idx}" "$testidx_completed" > s_idx="$text/${#TESTS[@]}" > > - align_text text left 12 "[$status]" > + align_text text left 13 "[$status]" > _msg "$msglevel" "$text $s_idx $testfile" > } > > @@ -786,10 +798,19 @@ print_test_result() { > elif [ "$rc_got" -eq 77 ] ; then > ((skipped++)) > result_msg_status="${YELLOW}SKIPPED$RESET" > + TESTS_SKIP_EXCEPT+=( "^$testfile$" ) > else > ((failed++)) > result_msg_level="W" > - if [ "$rc_got" -eq 121 ] ; then > + if [ "$rc_got" -eq 119 ] ; then > + # This means, the test passed, but it was matched by NFT_TEST_FAIL_ON_SKIP_EXCEPT regex. > + # The regex is expected to match *exactly* the tests that are skipped. An unexpected PASS > + # is also treated as a failure. Inspect NFT_TEST_FAIL_ON_SKIP_EXCEPT regex. > + result_msg_status="XPASS" > + elif [ "$rc_got" -eq 120 ] ; then > + ((skipped_as_fail++)) > + result_msg_status="FAIL-SKIP" > + elif [ "$rc_got" -eq 121 ] ; then > result_msg_status="CHK DUMP" > elif [ "$rc_got" -eq 122 ] ; then > result_msg_status="VALGRIND" > @@ -831,8 +852,14 @@ job_start() { > print_test_header I "$testfile" "$testidx" "EXECUTING" > fi > > + # NFT_TEST_FAIL_ON_SKIP_EXCEPT is already exported, if it happens to be set. > NFT_TEST_TESTTMPDIR="${JOBS_TEMPDIR["$testfile"]}" \ > - NFT="$NFT" NFT_REAL="$NFT_REAL" DIFF="$DIFF" DUMPGEN="$DUMPGEN" $NFT_TEST_UNSHARE_CMD "$NFT_TEST_BASEDIR/helpers/test-wrapper.sh" "$testfile" > + NFT="$NFT" \ > + NFT_REAL="$NFT_REAL" \ > + DIFF="$DIFF" \ > + DUMPGEN="$DUMPGEN" \ > + NFT_TEST_FAIL_ON_SKIP="$NFT_TEST_FAIL_ON_SKIP" \ > + $NFT_TEST_UNSHARE_CMD "$NFT_TEST_BASEDIR/helpers/test-wrapper.sh" "$testfile" > local rc_got=$? > > if [ "$NFT_TEST_JOBS" -le 1 ] ; then > @@ -896,12 +923,7 @@ echo "" > kmemleak_found=0 > check_kmemleak_force > > -failed_total="$failed" > -if [ "$NFT_TEST_FAIL_ON_SKIP" = y ] ; then > - failed_total="$((failed_total + skipped))" > -fi > - > -if [ "$failed_total" -gt 0 ] ; then > +if [ "$failed" -gt 0 ] ; then > RR="$RED" > elif [ "$skipped" -gt 0 ] ; then > RR="$YELLOW" > @@ -926,17 +948,24 @@ END_TIME="$(cut -d ' ' -f1 /proc/uptime)" > WALL_TIME="$(awk -v start="$START_TIME" -v end="$END_TIME" "BEGIN { print(end - start) }")" > printf "%s\n" "$WALL_TIME" "$START_TIME" "$END_TIME" > "$NFT_TEST_TMPDIR/times" > > -if [ "$failed_total" -gt 0 -o "$NFT_TEST_KEEP_LOGS" = y ] ; then > +if [ "${#TESTS_SKIP_EXCEPT[@]}" -gt 0 ] ; then > + printf '%s\n' "${TESTS_SKIP_EXCEPT[@]}" | sort > +fi > "$NFT_TEST_TMPDIR/skip-if-fail-except" > + > +if [ "$failed" -gt 0 -o "$NFT_TEST_KEEP_LOGS" = y ] ; then > msg_info "check the temp directory \"$NFT_TEST_TMPDIR\" (\"$NFT_TEST_LATEST\")" > msg_info " ls -lad \"$NFT_TEST_LATEST\"/*/*" > msg_info " grep -R ^ \"$NFT_TEST_LATEST\"/" > + if [ "${#TESTS_SKIP_EXCEPT[@]}" -gt 0 ] ; then > + msg_info " generate XFAIL with NFT_TEST_FAIL_ON_SKIP_EXCEPT=\"\$(cat $NFT_TEST_LATEST/skip-if-fail-except)\"" > + fi > NFT_TEST_TMPDIR= > fi > > -if [ "$failed" -gt 0 ] ; then > +if [ "$failed" -gt 0 -a "$skipped_as_fail" -eq "$failed" ] ; then > + msg_info "Tests were skipped, but failed due to NFT_TEST_FAIL_ON_SKIP=y" > exit 1 > -elif [ "$NFT_TEST_FAIL_ON_SKIP" = y -a "$skipped" -gt 0 ] ; then > - msg_info "some tests were skipped. Fail due to NFT_TEST_FAIL_ON_SKIP=y" > +elif [ "$failed" -gt 0 ] ; then > exit 1 > elif [ "$ok" -eq 0 -a "$skipped" -gt 0 ] ; then > exit 77 > -- > 2.41.0 >