We have a lot of tests/shell tests, that use the nft binary. However, it's useful to also write unit tests, that can test the internal C code specifically. Since no such tests exist yet, it would be cumbersome to add a test. Add two binaries, that can be used as a place for such tests. Currently there are no real tests there, it's only to show how it works, and that those dummy tests pass (including that the linking and execution works). To access the internals, build an intermediate static library src/libnftables-static.la, which then makes up the public, dynamic src/libnftables.la library. There are two tests: - tests/unit/test-libnftables-static - tests/unit/test-libnftables The former statically links with src/libnftables-static.la and can test internal API from headers under "include/*.h". The latter dynamically links with src/libnftables.la, and can only test the public API from includes/nftables/libnftables.h. You can run the unit tests alone with `make check-TESTS`. Calling 'VALGRIND=y make check' works as expected. Also add a LOG_COMPILER script "tools/test-runner.sh". This wraps the execution of the tests. Even for manual testing, you likely don't want to run "tests/unit/test-*" directly, but rather $ ./tools/test-runner.sh tests/unit/test-libnftables This sets up an unshared namespace and honors VALGRIND=y to run the test under valgrind. Set NFT_TEST_WRAPPER=gdb to start with a debugger. Signed-off-by: Thomas Haller <thaller@xxxxxxxxxx> --- .gitignore | 8 +- Makefile.am | 69 ++++++-- tests/unit/nft-test.h | 14 ++ tests/unit/test-libnftables-static.c | 16 ++ tests/unit/test-libnftables.c | 21 +++ tools/test-runner.sh | 228 +++++++++++++++++++++++++++ 6 files changed, 346 insertions(+), 10 deletions(-) create mode 100644 tests/unit/nft-test.h create mode 100644 tests/unit/test-libnftables-static.c create mode 100644 tests/unit/test-libnftables.c create mode 100755 tools/test-runner.sh diff --git a/.gitignore b/.gitignore index 51429020ceb6..369678a13987 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,13 @@ libtool # Generated by tests *.payload.got -tests/build/tests.log +test-suite.log +tests/**/*.log +tests/**/*.trs +tests/**/*.valgrind-log + +tests/unit/test-libnftables +tests/unit/test-libnftables-static # Debian package build temporary files build-stamp diff --git a/Makefile.am b/Makefile.am index f06fab8e3b9f..074d2076c53d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,12 +31,18 @@ lib_LTLIBRARIES = noinst_LTLIBRARIES = sbin_PROGRAMS = check_PROGRAMS = +noinst_PROGRAMS = check_LTLIBRARIES = dist_man_MANS = CLEANFILES = +TESTS = +test_programs = check_local = check_more = +TESTS_ENVIRONMENT = VERBOSE="$(V)" NFT_TEST_MAKE=n +LOG_COMPILER = "$(srcdir)/tools/test-runner.sh" --srcdir "$(abs_srcdir)" --builddir "$(abs_builddir)" -- + ############################################################################### pkginclude_HEADERS = \ @@ -205,11 +211,9 @@ endif ############################################################################### -lib_LTLIBRARIES += src/libnftables.la +noinst_LTLIBRARIES += src/libnftables-static.la -src_libnftables_la_SOURCES = \ - src/libnftables.map \ - \ +src_libnftables_static_la_SOURCES = \ src/cache.c \ src/cmd.c \ src/ct.c \ @@ -257,20 +261,36 @@ src_libnftables_la_SOURCES = \ $(NULL) if BUILD_JSON -src_libnftables_la_SOURCES += \ +src_libnftables_static_la_SOURCES += \ src/json.c \ src/parser_json.c \ $(NULL) endif +src_libnftables_static_la_LIBADD = \ + src/libparser.la \ + $(LIBMINIGMP_LIBS) \ + $(LIBMNL_LIBS) \ + $(LIBNFTNL_LIBS) \ + $(XTABLES_LIBS) \ + $(JANSSON_LIBS) \ + $(NULL) + +############################################################################### + +lib_LTLIBRARIES += src/libnftables.la + +src_libnftables_la_SOURCES = \ + src/libnftables.map \ + $(NULL) + src_libnftables_la_LDFLAGS = \ -version-info "${libnftables_LIBVERSION}" \ -Wl,--version-script="$(srcdir)/src//libnftables.map" \ $(NULL) src_libnftables_la_LIBADD = \ - src/libparser.la \ - $(LIBMINIGMP_LIBS) \ + src/libnftables-static.la \ $(LIBMNL_LIBS) \ $(LIBNFTNL_LIBS) \ $(XTABLES_LIBS) \ @@ -303,6 +323,22 @@ examples_nft_json_file_LDADD = src/libnftables.la ############################################################################### +EXTRA_DIST += tests/unit/nft-test.h + +############################################################################### + +test_programs += tests/unit/test-libnftables-static + +tests_unit_test_libnftables_static_LDADD = src/libnftables-static.la + +############################################################################### + +test_programs += tests/unit/test-libnftables + +tests_unit_test_libnftables_LDADD = src/libnftables.la + +############################################################################### + if BUILD_MAN dist_man_MANS += \ @@ -390,6 +426,16 @@ dist_pkgsysconf_DATA = \ ############################################################################### +EXTRA_DIST += \ + tests/build \ + tests/json_echo \ + tests/monitor \ + tests/py \ + tests/shell \ + $(NULL) + +############################################################################### + EXTRA_DIST += \ py/pyproject.toml \ py/setup.cfg \ @@ -403,7 +449,6 @@ EXTRA_DIST += \ EXTRA_DIST += \ files \ - tests \ tools \ $(NULL) @@ -412,7 +457,13 @@ pkgconfig_DATA = libnftables.pc ############################################################################### -build-all: all $(check_PROGRAMS) $(check_LTLIBRARIES) +check_PROGRAMS += $(test_programs) + +TESTS += $(test_programs) + +############################################################################### + +build-all: all $(check_PROGRAMS) $(test_programs) $(check_LTLIBRARIES) .PHONY: build-all diff --git a/tests/unit/nft-test.h b/tests/unit/nft-test.h new file mode 100644 index 000000000000..cab97b42c669 --- /dev/null +++ b/tests/unit/nft-test.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __NFT_TEST__H__ +#define __NFT_TEST__H__ + +#undef NDEBUG + +#include <nft.h> + +#include <assert.h> +#include <stdio.h> +#include <string.h> + +#endif /* __NFT_TEST__H__ */ diff --git a/tests/unit/test-libnftables-static.c b/tests/unit/test-libnftables-static.c new file mode 100644 index 000000000000..e34fcfd77f39 --- /dev/null +++ b/tests/unit/test-libnftables-static.c @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "nft-test.h" + +#include <datatype.h> + +static void test_datatype(void) +{ + assert(!datatype_lookup(-1)); +} + +int main(int argc, char **argv) +{ + test_datatype(); + return 0; +} diff --git a/tests/unit/test-libnftables.c b/tests/unit/test-libnftables.c new file mode 100644 index 000000000000..100558cd5e0f --- /dev/null +++ b/tests/unit/test-libnftables.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "nft-test.h" + +#include <nftables/libnftables.h> + +static void test_nft_ctx(void) +{ + struct nft_ctx *ctx; + + ctx = nft_ctx_new(0); + assert(ctx); + + nft_ctx_free(ctx); +} + +int main(int argc, char **argv) +{ + test_nft_ctx(); + return 0; +} diff --git a/tools/test-runner.sh b/tools/test-runner.sh new file mode 100755 index 000000000000..18658ba4adf1 --- /dev/null +++ b/tools/test-runner.sh @@ -0,0 +1,228 @@ +#!/bin/bash + +set -e + +die() { + printf '%s\n' "$*" + exit 1 +} + +as_bool() { + if [ "$#" -eq 0 ] ; then + return 1 + fi + case "$1" in + y|Y|yes|Yes|YES|1|true|True|TRUE) + return 0 + ;; + n|N|no|No|NO|0|false|False|FALSE) + return 1 + ;; + *) + # Fallback to the next in the list. + shift + rc=0 + as_bool "$@" || rc=$? + return "$rc" + esac +} + +as_bool_str() { + if as_bool "$@" ; then + printf y + else + printf n + fi +} + +usage() { + echo " $0 [OPTIONS] TEST" + echo + echo "Run TEST" + echo + echo "Options:" + echo " --srcdir dir: Sets SRCDIR=dir." + echo " --builddir dir: Sets BUILDDIR=dir." + echo " -V|--valgrind: Sets VALGRIND=y." + echo " -G|--gdb: Sets NFT_TEST_WRAPPER=gdb." + echo " -m|--make: Sets NFT_TEST_MAKE=y." + echo " --: Separates options from test name." + echo " TEST: the path of the test executable to run." + echo + echo "Environment variables:" + echo " SRCDIR: set to \$(srcdir) of the project." + echo " BUILDDIR: set to \$(builddir) of the project." + echo " VERBOSE: for verbose output." + echo " VALGRIND: if set to TRUE, run the test under valgrind. NFT_TEST_UNDER_VALGRIND is ignored." + echo " NFT_TEST_UNSHARE_CMD: override the command to unshare the netns." + echo " Set to empty to not unshare." + echo " NFT_TEST_WRAPPER: usually empty. For manual testing, this is prepended to TEST." + echo " Set for example got \"gdb\"." + echo " NFT_TEST_MAKE: if true, call \`make\` on the test first." +} + +usage_and_die() { + usage + echo + die "$@" +} + +TIMESTAMP=$(date '+%Y%m%d-%H%M%S.%3N') + +# SRCDIR +# BUILDDIR +VERBOSE="$(as_bool_str "$VERBOSE")" +VALGRIND="$(as_bool_str "$VALGRIND")" +NFT_TEST_UNSHARE_CMD="${NFT_TEST_UNSHARE_CMD-unshare -m -U --map-root-user -n}" +NFT_TEST_WRAPPER="${NFT_TEST_WRAPPER}" +NFT_TEST_MAKE="$(as_bool_str "$NFT_TEST_MAKE")" + +unset TEST + +while [ $# -gt 0 ] ; do + A="$1" + shift + case "$A" in + -h|--help) + usage + exit 0 + ;; + --srcdir) + SRCDIR="$1" + shift + ;; + --builddir) + BUILDDIR="$1" + shift + ;; + -V|--valgrind) + VALGRIND=y + ;; + -G|--gdb) + NFT_TEST_WRAPPER=gdb + ;; + -m|--make) + NFT_TEST_MAKE=y + ;; + --) + if [ $# -ne 1 ] ; then + usage_and_die "Requires a TEST argument after --" + fi + TEST="$1" + shift + ;; + *) + if [ -n "${TEST+x}" ] ; then + usage_and_die "Unknown argument \"$A\"" + fi + TEST="$A" + ;; + esac +done + +if [ -z "$TEST" ] ; then + usage_and_die "Missing test argument. See --help" +fi + +if [ -z "${SRCDIR+x}" ] ; then + SRCDIR="$(readlink -f "$(dirname "$0")/..")" +fi +if [ ! -d "$SRCDIR" ] ; then + die "Invalid \$SRCDIR=\"$SRCDIR\"" +fi + +if [ -z "${BUILDDIR+x}" ] ; then + re='^(.*/|)(tests/unit/test-[^/]*)$' + if [[ "$TEST" =~ $re ]] ; then + BUILDDIR="$(readlink -f "${BASH_REMATCH[1]:-.}")" || : + else + BUILDDIR="$SRCDIR" + fi +fi +if [ ! -d "$BUILDDIR" ] ; then + die "Invalid \$BUILDDIR=\"$BUILDDIR\"" +fi + +export TESTDIR="$(dirname "$TEST")" +export SRCDIR +export BUILDDIR + +if [ "$VALGRIND" = y ] ; then + export NFT_TEST_UNDER_VALGRIND=1 +fi + +run_unit() { + local TEST="$1" + + if [ "$NFT_TEST_MAKE" = y ] ; then + local d="$(readlink -f "$BUILDDIR")" + local tf="$(readlink -f "$TEST")" + local t="${tf#$d/}" + + if [ "$tf" != "$d/$t" ] ; then + die "Cannot detect paths for making \"$TEST\" in \"$BUILDDIR\" (\"$d\" and \"$t\")" + fi + # Don't use "make -C" to avoid the extra "Entering/Leaving directory" messages + (cd "$d" && make "$t" ) || die "Making \"$TEST\" failed" + fi + + if [ "$VALGRIND" != y ] ; then + rc_test=0 + $NFT_TEST_UNSHARE_CMD \ + libtool \ + --mode=execute \ + $NFT_TEST_WRAPPER \ + "$TEST" || rc_test=$? + if [ "$rc_test" -ne 0 -a "$rc_test" -ne 77 ] ; then + die "exec \"$TEST\" failed with exit code $rc_test" + fi + exit "$rc_test" + fi + + LOGFILE="$TEST.valgrind-log" + + rc_test=0 + $NFT_TEST_UNSHARE_CMD \ + libtool \ + --mode=execute \ + valgrind \ + --quiet \ + --error-exitcode=122 \ + --leak-check=full \ + --gen-suppressions=all \ + --num-callers=100 \ + --log-file="$LOGFILE" \ + --vgdb-prefix="${TMP:-/tmp}/vgdb-pipe-nft-test-runner-$TIMESTAMP-$$" \ + $NFT_TEST_VALGRIND_OPTS \ + "$TEST" \ + || rc_test=$? + + if [ -s "$LOGFILE" ] ; then + if [ "$rc_test" -eq 0 -o "$rc_test" -eq 122 ] ; then + echo "valgrind failed. Logfile at \"$LOGFILE\" :" + cat "$LOGFILE" + rc_test=122 + else + echo "valgrind also failed. Check logfile at \"$LOGFILE\"" + fi + elif [ ! -f "$LOGFILE" ] ; then + echo "valgrind logfile \"$LOGFILE\" missing" + if [ "$rc_test" -eq 0 ] ; then + rc_test=122 + fi + else + rm -rf "$LOGFILE" + fi + + exit "$rc_test" +} + +case "$TEST" in + tests/unit/test-* | \ + */tests/unit/test-* ) + run_unit "$TEST" + ;; + *) + die "Unrecognized test \"$TEST\"" + ;; +esac -- 2.41.0