[PATCH nft 1/1] tests/shell: add NFT_TEST_FAIL_ON_SKIP_EXCEPT for allow-list of skipped tests (XFAIL)

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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.

  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.

  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.

  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




[Index of Archives]     [Netfitler Users]     [Berkeley Packet Filter]     [LARTC]     [Bugtraq]     [Yosemite Forum]

  Powered by Linux