git: 83a1ee578c9d - main - atf, kyua: Implement require.kmods.

From: Dag-Erling Smørgrav <des_at_FreeBSD.org>
Date: Sat, 31 May 2025 12:28:25 UTC
The branch main has been updated by des:

URL: https://cgit.FreeBSD.org/src/commit/?id=83a1ee578c9d1ab7013e997289c7cd470c0e6902

commit 83a1ee578c9d1ab7013e997289c7cd470c0e6902
Author:     Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2025-05-31 12:27:30 +0000
Commit:     Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2025-05-31 12:27:30 +0000

    atf, kyua: Implement require.kmods.
    
    This adds a metadata variable, require.kmods, and corresponding functions
    or methods in C, C++, and shell, which allow a test to specify that it
    requires particular kernel modules to run.  If the kernel modules are not
    present, the test is skipped.  One might want to consider a kyua option
    which makes it attempt to load the modules instead.
    
    Differential Revision:  https://reviews.freebsd.org/D47470
---
 contrib/atf/atf-c++/atf-c++.3        | 10 +++++-
 contrib/atf/atf-c++/tests.cpp        |  7 +++++
 contrib/atf/atf-c++/tests.hpp        |  1 +
 contrib/atf/atf-c/atf-c.3            | 14 ++++++++-
 contrib/atf/atf-c/tc.c               | 61 ++++++++++++++++++++++++++++++++++++
 contrib/atf/atf-c/tc.h               |  3 ++
 contrib/atf/atf-sh/atf-sh.3          | 13 +++++++-
 contrib/atf/atf-sh/libatf-sh.subr    | 12 +++++++
 contrib/atf/doc/atf-test-case.4      | 10 +++++-
 contrib/kyua/engine/atf_list.cpp     |  4 +++
 contrib/kyua/engine/requirements.cpp | 30 ++++++++++++++++++
 contrib/kyua/model/metadata.cpp      | 22 +++++++++++++
 contrib/kyua/model/metadata.hpp      |  6 ++++
 lib/atf/libatf-c/Makefile            |  2 ++
 libexec/atf/atf-sh/Makefile          |  1 +
 tests/sys/fs/tarfs/tarfs_test.sh     | 18 ++++++++++-
 tests/sys/net/if_wg.sh               | 15 +++------
 usr.bin/kyua/Makefile                |  2 +-
 18 files changed, 214 insertions(+), 17 deletions(-)

diff --git a/contrib/atf/atf-c++/atf-c++.3 b/contrib/atf/atf-c++/atf-c++.3
index fead776755af..bb83e6214622 100644
--- a/contrib/atf/atf-c++/atf-c++.3
+++ b/contrib/atf/atf-c++/atf-c++.3
@@ -22,7 +22,7 @@
 .\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-.Dd March 6, 2017
+.Dd May 11, 2025
 .Dt ATF-C++ 3
 .Os
 .Sh NAME
@@ -277,6 +277,14 @@ It is possible to get the path to the test case's source directory from any
 of its three components by querying the
 .Sq srcdir
 configuration variable.
+.Ss Requiring kernel modules
+Aside from the
+.Va require.kmods
+meta-data variable available in the header only, one can also check for
+additional kernel modules in the test case's body by using the
+.Fn require_kmod
+function, which takes the name of a single module.
+If it is not found, the test case will be automatically skipped.
 .Ss Requiring programs
 Aside from the
 .Va require.progs
diff --git a/contrib/atf/atf-c++/tests.cpp b/contrib/atf/atf-c++/tests.cpp
index ede9609e32d6..a9f5d4a5fe2c 100644
--- a/contrib/atf/atf-c++/tests.cpp
+++ b/contrib/atf/atf-c++/tests.cpp
@@ -318,6 +318,13 @@ impl::tc::cleanup(void)
 {
 }
 
+void
+impl::tc::require_kmod(const std::string& kmod)
+    const
+{
+    atf_tc_require_kmod(kmod.c_str());
+}
+
 void
 impl::tc::require_prog(const std::string& prog)
     const
diff --git a/contrib/atf/atf-c++/tests.hpp b/contrib/atf/atf-c++/tests.hpp
index a03cc852dcf8..8fdf15a9f05d 100644
--- a/contrib/atf/atf-c++/tests.hpp
+++ b/contrib/atf/atf-c++/tests.hpp
@@ -80,6 +80,7 @@ protected:
     virtual void body(void) const = 0;
     virtual void cleanup(void) const;
 
+    void require_kmod(const std::string&) const;
     void require_prog(const std::string&) const;
 
     friend struct tc_impl;
diff --git a/contrib/atf/atf-c/atf-c.3 b/contrib/atf/atf-c/atf-c.3
index c38e068bb380..6369cb5697f8 100644
--- a/contrib/atf/atf-c/atf-c.3
+++ b/contrib/atf/atf-c/atf-c.3
@@ -22,7 +22,7 @@
 .\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-.Dd February 23, 2021
+.Dd May 11, 2025
 .Dt ATF-C 3
 .Os
 .Sh NAME
@@ -77,6 +77,8 @@
 .Nm atf_tc_fail ,
 .Nm atf_tc_fail_nonfatal ,
 .Nm atf_tc_pass ,
+.Nm atf_tc_require_kmod ,
+.Nm atf_tc_require_prog ,
 .Nm atf_tc_skip ,
 .Nm atf_utils_cat_file ,
 .Nm atf_utils_compare_file ,
@@ -145,6 +147,8 @@
 .Fn atf_tc_fail "reason"
 .Fn atf_tc_fail_nonfatal "reason"
 .Fn atf_tc_pass
+.Fn atf_tc_require_kmod "kmod"
+.Fn atf_tc_require_prog "prog"
 .Fn atf_tc_skip "reason"
 .Ft void
 .Fo atf_utils_cat_file
@@ -362,6 +366,14 @@ It is possible to get the path to the test case's source directory from any
 of its three components by querying the
 .Sq srcdir
 configuration variable.
+.Ss Requiring kernel modules
+Aside from the
+.Va require.kmods
+meta-data variable available in the header only, one can also check for
+additional kernel modules in the test case's body by using the
+.Fn atf_tc_require_kmod
+function, which takes the name of a single kernel module.
+If it is not found, the test case will be automatically skipped.
 .Ss Requiring programs
 Aside from the
 .Va require.progs
diff --git a/contrib/atf/atf-c/tc.c b/contrib/atf/atf-c/tc.c
index 84a8beb4fa13..1cef6adbd82d 100644
--- a/contrib/atf/atf-c/tc.c
+++ b/contrib/atf/atf-c/tc.c
@@ -26,6 +26,10 @@
 #include "atf-c/tc.h"
 
 #include <sys/types.h>
+#ifdef __FreeBSD__
+#include <sys/linker.h>
+#include <sys/module.h>
+#endif
 #include <sys/stat.h>
 #include <sys/uio.h>
 
@@ -103,6 +107,9 @@ static void format_reason_fmt(atf_dynstr_t *, const char *, const size_t,
 static void errno_test(struct context *, const char *, const size_t,
                        const int, const char *, const bool,
                        void (*)(struct context *, atf_dynstr_t *));
+#ifdef __FreeBSD__
+static atf_error_t check_kmod(struct context *, const char *);
+#endif
 static atf_error_t check_prog_in_dir(const char *, void *);
 static atf_error_t check_prog(struct context *, const char *);
 
@@ -460,6 +467,39 @@ errno_test(struct context *ctx, const char *file, const size_t line,
     }
 }
 
+#ifdef __FreeBSD__
+static atf_error_t
+check_kmod(struct context *ctx, const char *kmod)
+{
+    struct kld_file_stat fstat = { .version = sizeof(fstat) };
+    struct module_stat mstat = { .version = sizeof(mstat) };
+    atf_dynstr_t reason;
+    size_t len = strlen(kmod);
+    int fid, mid;
+
+    for (fid = kldnext(0); fid > 0; fid = kldnext(fid)) {
+	if (kldstat(fid, &fstat) != 0)
+	    continue;
+	if (strcmp(fstat.name, kmod) == 0)
+	    goto done;
+	if (strncmp(fstat.name, kmod, len) == 0 &&
+	    strcmp(fstat.name + len, ".ko") == 0)
+	    goto done;
+	for (mid = kldfirstmod(fid); mid > 0; mid = modfnext(mid)) {
+	    if (modstat(mid, &mstat) != 0)
+		continue;
+	    if (strcmp(mstat.name, kmod) == 0)
+		goto done;
+	}
+    }
+    format_reason_fmt(&reason, NULL, 0, "The required kmod %s "
+	"is not loaded", kmod);
+    fail_requirement(ctx, &reason);
+done:
+    return atf_no_error();
+}
+#endif
+
 struct prog_found_pair {
     const char *prog;
     bool found;
@@ -829,6 +869,9 @@ static void _atf_tc_fail_check(struct context *, const char *, const size_t,
 static void _atf_tc_fail_requirement(struct context *, const char *,
     const size_t, const char *, va_list) ATF_DEFS_ATTRIBUTE_NORETURN;
 static void _atf_tc_pass(struct context *) ATF_DEFS_ATTRIBUTE_NORETURN;
+#ifdef __FreeBSD__
+static void _atf_tc_require_kmod(struct context *, const char *);
+#endif
 static void _atf_tc_require_prog(struct context *, const char *);
 static void _atf_tc_skip(struct context *, const char *, va_list)
     ATF_DEFS_ATTRIBUTE_NORETURN;
@@ -908,6 +951,14 @@ _atf_tc_pass(struct context *ctx)
     UNREACHABLE;
 }
 
+#ifdef __FreeBSD__
+static void
+_atf_tc_require_kmod(struct context *ctx, const char *kmod)
+{
+    check_fatal_error(check_kmod(ctx, kmod));
+}
+#endif
+
 static void
 _atf_tc_require_prog(struct context *ctx, const char *prog)
 {
@@ -1154,6 +1205,16 @@ atf_tc_pass(void)
     _atf_tc_pass(&Current);
 }
 
+#ifdef __FreeBSD__
+void
+atf_tc_require_kmod(const char *kmod)
+{
+    PRE(Current.tc != NULL);
+
+    _atf_tc_require_kmod(&Current, kmod);
+}
+#endif
+
 void
 atf_tc_require_prog(const char *prog)
 {
diff --git a/contrib/atf/atf-c/tc.h b/contrib/atf/atf-c/tc.h
index 3fb695562ac8..5109102750c7 100644
--- a/contrib/atf/atf-c/tc.h
+++ b/contrib/atf/atf-c/tc.h
@@ -106,6 +106,9 @@ void atf_tc_fail_nonfatal(const char *, ...)
     ATF_DEFS_ATTRIBUTE_FORMAT_PRINTF(1, 2);
 void atf_tc_pass(void)
     ATF_DEFS_ATTRIBUTE_NORETURN;
+#ifdef __FreeBSD__
+void atf_tc_require_kmod(const char *);
+#endif
 void atf_tc_require_prog(const char *);
 void atf_tc_skip(const char *, ...)
     ATF_DEFS_ATTRIBUTE_FORMAT_PRINTF(1, 2)
diff --git a/contrib/atf/atf-sh/atf-sh.3 b/contrib/atf/atf-sh/atf-sh.3
index c3080c296826..5e53ceb45a06 100644
--- a/contrib/atf/atf-sh/atf-sh.3
+++ b/contrib/atf/atf-sh/atf-sh.3
@@ -22,7 +22,7 @@
 .\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-.Dd January 27, 2021
+.Dd May 11, 2025
 .Dt ATF-SH 3
 .Os
 .Sh NAME
@@ -43,6 +43,7 @@
 .Nm atf_get_srcdir ,
 .Nm atf_init_test_cases ,
 .Nm atf_pass ,
+.Nm atf_require_kmod ,
 .Nm atf_require_prog ,
 .Nm atf_set ,
 .Nm atf_skip ,
@@ -90,6 +91,8 @@
 .Nm atf_init_test_cases
 .Qq name
 .Nm atf_pass
+.Nm atf_require_kmod
+.Qq kmod_name
 .Nm atf_require_prog
 .Qq prog_name
 .Nm atf_set
@@ -205,6 +208,14 @@ function.
 It is interesting to note that this can be used inside
 .Nm atf_init_test_cases
 to silently include additional helper files from the source directory.
+.Ss Requiring kernel modules
+Aside from the
+.Va require.kmods
+meta-data variable available in the header only, one can also check for
+additional kernel modules in the test case's body by using the
+.Nm atf_require_kmod
+function, which takes the name of a single kernel module.
+If it is not found, the test case will be automatically skipped.
 .Ss Requiring programs
 Aside from the
 .Va require.progs
diff --git a/contrib/atf/atf-sh/libatf-sh.subr b/contrib/atf/atf-sh/libatf-sh.subr
index af2b75e5fe34..b89060f8b00b 100644
--- a/contrib/atf/atf-sh/libatf-sh.subr
+++ b/contrib/atf/atf-sh/libatf-sh.subr
@@ -353,6 +353,18 @@ atf_require_prog()
     esac
 }
 
+#
+# atf_require_kmod kmod
+#
+#   Checks that the given kmod is loaded.  If it is not, automatically
+#   skips the test case with an appropriate message.
+#
+atf_require_kmod()
+{
+    kldstat -q "${1}" || \
+        atf_skip "The required kmod ${1} is not loaded"
+}
+
 #
 # atf_set varname val1 [.. valN]
 #
diff --git a/contrib/atf/doc/atf-test-case.4 b/contrib/atf/doc/atf-test-case.4
index 46690bdcb0ef..020d7035d198 100644
--- a/contrib/atf/doc/atf-test-case.4
+++ b/contrib/atf/doc/atf-test-case.4
@@ -22,7 +22,7 @@
 .\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-.Dd March 6, 2017
+.Dd May 11, 2025
 .Dt ATF-TEST-CASE 4
 .Os
 .Sh NAME
@@ -223,6 +223,14 @@ The value can have a size suffix such as
 or
 .Sq T
 to make the amount of bytes easier to type and read.
+.It require.kmods
+Type: textual.
+Optional.
+.Pp
+A whitespace separated list of kernel modules that must be present to
+execute the test case.
+If any of the required kernel modules is not found, the test case is
+.Em skipped .
 .It require.progs
 Type: textual.
 Optional.
diff --git a/contrib/kyua/engine/atf_list.cpp b/contrib/kyua/engine/atf_list.cpp
index f6ec20d7709a..e0c4170605d1 100644
--- a/contrib/kyua/engine/atf_list.cpp
+++ b/contrib/kyua/engine/atf_list.cpp
@@ -133,6 +133,10 @@ engine::parse_atf_metadata(const model::properties_map& props)
                 mdbuilder.set_string("required_disk_space", value);
             } else if (name == "require.files") {
                 mdbuilder.set_string("required_files", value);
+#ifdef __FreeBSD__
+            } else if (name == "require.kmods") {
+                mdbuilder.set_string("required_kmods", value);
+#endif
             } else if (name == "require.machine") {
                 mdbuilder.set_string("allowed_platforms", value);
             } else if (name == "require.memory") {
diff --git a/contrib/kyua/engine/requirements.cpp b/contrib/kyua/engine/requirements.cpp
index a6a4cae7511c..dff43e531a57 100644
--- a/contrib/kyua/engine/requirements.cpp
+++ b/contrib/kyua/engine/requirements.cpp
@@ -41,6 +41,10 @@
 #include "utils/sanity.hpp"
 #include "utils/units.hpp"
 
+#ifdef __FreeBSD__
+#include <libutil.h>
+#endif
+
 namespace config = utils::config;
 namespace fs = utils::fs;
 namespace passwd = utils::passwd;
@@ -220,6 +224,26 @@ check_required_programs(const model::paths_set& required_programs)
 }
 
 
+#ifdef __FreeBSD__
+/// Checks if all required kmods are loaded.
+///
+/// \param required_programs Set of kmods.
+///
+/// \return Empty if the required kmods are all loaded or an error
+/// message otherwise.
+static std::string
+check_required_kmods(const model::strings_set& required_kmods)
+{
+    for (model::strings_set::const_iterator iter = required_kmods.begin();
+         iter != required_kmods.end(); iter++) {
+        if (!kld_isloaded((*iter).c_str()))
+            return F("Required kmod '%s' not loaded") % *iter;
+    }
+    return "";
+}
+#endif
+
+
 /// Checks if the current system has the specified amount of memory.
 ///
 /// \param required_memory Amount of required physical memory, or zero if not
@@ -312,6 +336,12 @@ engine::check_reqs(const model::metadata& md, const config::tree& cfg,
     if (!reason.empty())
         return reason;
 
+#ifdef __FreeBSD__
+    reason = check_required_kmods(md.required_kmods());
+    if (!reason.empty())
+        return reason;
+#endif
+
     reason = check_required_memory(md.required_memory());
     if (!reason.empty())
         return reason;
diff --git a/contrib/kyua/model/metadata.cpp b/contrib/kyua/model/metadata.cpp
index 85248d596727..a5a9a1315964 100644
--- a/contrib/kyua/model/metadata.cpp
+++ b/contrib/kyua/model/metadata.cpp
@@ -256,6 +256,9 @@ init_tree(config::tree& tree)
     tree.define< bytes_node >("required_disk_space");
     tree.define< paths_set_node >("required_files");
     tree.define< bytes_node >("required_memory");
+#ifdef __FreeBSD__
+    tree.define< config::strings_set_node >("required_kmods");
+#endif
     tree.define< paths_set_node >("required_programs");
     tree.define< user_node >("required_user");
     tree.define< delta_node >("timeout");
@@ -282,6 +285,9 @@ set_defaults(config::tree& tree)
     tree.set< bytes_node >("required_disk_space", units::bytes(0));
     tree.set< paths_set_node >("required_files", model::paths_set());
     tree.set< bytes_node >("required_memory", units::bytes(0));
+#ifdef __FreeBSD__
+    tree.set< config::strings_set_node >("required_kmods", model::strings_set());
+#endif
     tree.set< paths_set_node >("required_programs", model::paths_set());
     tree.set< user_node >("required_user", "");
     // TODO(jmmv): We shouldn't be setting a default timeout like this.  See
@@ -597,6 +603,22 @@ model::metadata::required_memory(void) const
 }
 
 
+#ifdef __FreeBSD__
+/// Returns the list of kmods needed by the test.
+///
+/// \return Set of strings.
+const model::strings_set&
+model::metadata::required_kmods(void) const
+{
+    if (_pimpl->props.is_set("required_kmods")) {
+        return _pimpl->props.lookup< config::strings_set_node >("required_kmods");
+    } else {
+	return get_defaults().lookup< config::strings_set_node >("required_kmods");
+    }
+}
+#endif
+
+
 /// Returns the list of programs needed by the test.
 ///
 /// \return Set of paths.
diff --git a/contrib/kyua/model/metadata.hpp b/contrib/kyua/model/metadata.hpp
index 263ecc86106d..8af6c7c161af 100644
--- a/contrib/kyua/model/metadata.hpp
+++ b/contrib/kyua/model/metadata.hpp
@@ -76,6 +76,9 @@ public:
     const utils::units::bytes& required_disk_space(void) const;
     const paths_set& required_files(void) const;
     const utils::units::bytes& required_memory(void) const;
+#ifdef __FreeBSD__
+    const strings_set& required_kmods(void) const;
+#endif
     const paths_set& required_programs(void) const;
     const std::string& required_user(void) const;
     const utils::datetime::delta& timeout(void) const;
@@ -121,6 +124,9 @@ public:
     metadata_builder& set_required_disk_space(const utils::units::bytes&);
     metadata_builder& set_required_files(const paths_set&);
     metadata_builder& set_required_memory(const utils::units::bytes&);
+#ifdef __FreeBSD__
+    metadata_builder& set_required_kmods(const strings_set&);
+#endif
     metadata_builder& set_required_programs(const paths_set&);
     metadata_builder& set_required_user(const std::string&);
     metadata_builder& set_string(const std::string&, const std::string&);
diff --git a/lib/atf/libatf-c/Makefile b/lib/atf/libatf-c/Makefile
index 9fd36dc913f3..4cfee2ed824f 100644
--- a/lib/atf/libatf-c/Makefile
+++ b/lib/atf/libatf-c/Makefile
@@ -119,6 +119,8 @@ MLINKS+=	atf-c.3 ATF_CHECK.3 \
 		atf-c.3 atf_tc_fail.3 \
 		atf-c.3 atf_tc_fail_nonfatal.3 \
 		atf-c.3 atf_tc_pass.3 \
+		atf-c.3 atf_tc_require_kmod.3 \
+		atf-c.3 atf_tc_require_prog.3 \
 		atf-c.3 atf_tc_skip.3 \
 		atf-c.3 atf_utils_cat_file.3 \
 		atf-c.3 atf_utils_compare_file.3 \
diff --git a/libexec/atf/atf-sh/Makefile b/libexec/atf/atf-sh/Makefile
index ba949fd3072e..2e821684d8a8 100644
--- a/libexec/atf/atf-sh/Makefile
+++ b/libexec/atf/atf-sh/Makefile
@@ -54,6 +54,7 @@ MLINKS+=	\
 		atf-sh.3 atf_get_srcdir.3 \
 		atf-sh.3 atf_init_test_cases.3 \
 		atf-sh.3 atf_pass.3 \
+		atf-sh.3 atf_require_kmod.3 \
 		atf-sh.3 atf_require_prog.3 \
 		atf-sh.3 atf_set.3 \
 		atf-sh.3 atf_skip.3 \
diff --git a/tests/sys/fs/tarfs/tarfs_test.sh b/tests/sys/fs/tarfs/tarfs_test.sh
index f1322033fbad..20baadfea5c5 100644
--- a/tests/sys/fs/tarfs/tarfs_test.sh
+++ b/tests/sys/fs/tarfs/tarfs_test.sh
@@ -48,7 +48,6 @@ tarsum() {
 }
 
 tarfs_setup() {
-	kldload -n tarfs || atf_skip "This test requires tarfs and could not load it"
 	mkdir "${mnt}"
 }
 
@@ -60,6 +59,7 @@ atf_test_case tarfs_basic cleanup
 tarfs_basic_head() {
 	atf_set "descr" "Basic function test"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_basic_body() {
 	tarfs_setup
@@ -87,6 +87,7 @@ atf_test_case tarfs_basic_gnu cleanup
 tarfs_basic_gnu_head() {
 	atf_set "descr" "Basic function test using GNU tar"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 	atf_set "require.progs" "gtar"
 }
 tarfs_basic_gnu_body() {
@@ -101,6 +102,7 @@ atf_test_case tarfs_notdir_device cleanup
 tarfs_notdir_device_head() {
 	atf_set "descr" "Regression test for PR 269519 and 269561"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_notdir_device_body() {
 	tarfs_setup
@@ -121,6 +123,7 @@ atf_test_case tarfs_notdir_device_gnu cleanup
 tarfs_notdir_device_gnu_head() {
 	atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 	atf_set "require.progs" "gtar"
 }
 tarfs_notdir_device_gnu_body() {
@@ -135,6 +138,7 @@ atf_test_case tarfs_notdir_dot cleanup
 tarfs_notdir_dot_head() {
 	atf_set "descr" "Regression test for PR 269519 and 269561"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_notdir_dot_body() {
 	tarfs_setup
@@ -155,6 +159,7 @@ atf_test_case tarfs_notdir_dot_gnu cleanup
 tarfs_notdir_dot_gnu_head() {
 	atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 	atf_set "require.progs" "gtar"
 }
 tarfs_notdir_dot_gnu_body() {
@@ -169,6 +174,7 @@ atf_test_case tarfs_notdir_dotdot cleanup
 tarfs_notdir_dotdot_head() {
 	atf_set "descr" "Regression test for PR 269519 and 269561"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_notdir_dotdot_body() {
 	tarfs_setup
@@ -189,6 +195,7 @@ atf_test_case tarfs_notdir_dotdot_gnu cleanup
 tarfs_notdir_dotdot_gnu_head() {
 	atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 	atf_set "require.progs" "gtar"
 }
 tarfs_notdir_dotdot_gnu_body() {
@@ -203,6 +210,7 @@ atf_test_case tarfs_notdir_file cleanup
 tarfs_notdir_file_head() {
 	atf_set "descr" "Regression test for PR 269519 and 269561"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_notdir_file_body() {
 	tarfs_setup
@@ -223,6 +231,7 @@ atf_test_case tarfs_notdir_file_gnu cleanup
 tarfs_notdir_file_gnu_head() {
 	atf_set "descr" "Regression test for PR 269519 and 269561 using GNU tar"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 	atf_set "require.progs" "gtar"
 }
 tarfs_notdir_file_gnu_body() {
@@ -237,6 +246,7 @@ atf_test_case tarfs_emptylink cleanup
 tarfs_emptylink_head() {
 	atf_set "descr" "Regression test for PR 277360: empty link target"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_emptylink_body() {
 	tarfs_setup
@@ -256,6 +266,7 @@ atf_test_case tarfs_linktodir cleanup
 tarfs_linktodir_head() {
 	atf_set "descr" "Regression test for PR 277360: link to directory"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_linktodir_body() {
 	tarfs_setup
@@ -276,6 +287,7 @@ atf_test_case tarfs_linktononexistent cleanup
 tarfs_linktononexistent_head() {
 	atf_set "descr" "Regression test for PR 277360: link to nonexistent target"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_linktononexistent_body() {
 	tarfs_setup
@@ -293,6 +305,7 @@ atf_test_case tarfs_checksum cleanup
 tarfs_checksum_head() {
 	atf_set "descr" "Verify that the checksum covers header padding"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_checksum_body() {
 	tarfs_setup
@@ -313,6 +326,7 @@ atf_test_case tarfs_long_names cleanup
 tarfs_long_names_head() {
 	atf_set "descr" "Verify that tarfs supports long file names"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_long_names_body() {
 	tarfs_setup
@@ -337,6 +351,7 @@ atf_test_case tarfs_long_paths cleanup
 tarfs_long_paths_head() {
 	atf_set "descr" "Verify that tarfs supports long paths"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 }
 tarfs_long_paths_body() {
 	tarfs_setup
@@ -361,6 +376,7 @@ atf_test_case tarfs_git_archive cleanup
 tarfs_git_archive_head() {
 	atf_set "descr" "Verify that tarfs supports archives created by git"
 	atf_set "require.user" "root"
+	atf_set "require.kmods" "tarfs"
 	atf_set "require.progs" "git"
 }
 tarfs_git_archive_body() {
diff --git a/tests/sys/net/if_wg.sh b/tests/sys/net/if_wg.sh
index e5df6afface1..6d2f56dc8f2e 100644
--- a/tests/sys/net/if_wg.sh
+++ b/tests/sys/net/if_wg.sh
@@ -34,6 +34,7 @@ wg_basic_head()
 {
 	atf_set descr 'Create a wg(4) tunnel over an epair and pass traffic between jails'
 	atf_set require.user root
+	atf_set require.kmods if_wg
 }
 
 wg_basic_body()
@@ -41,8 +42,6 @@ wg_basic_body()
 	local epair pri1 pri2 pub1 pub2 wg1 wg2
         local endpoint1 endpoint2 tunnel1 tunnel2
 
-	kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
-
 	pri1=$(wg genkey)
 	pri2=$(wg genkey)
 
@@ -175,6 +174,7 @@ wg_basic_netmap_head()
 {
 	atf_set descr 'Create a wg(4) tunnel over an epair and pass traffic between jails with netmap'
 	atf_set require.user root
+	atf_set require.kmods if_wg netmap
 }
 
 wg_basic_netmap_body()
@@ -183,9 +183,6 @@ wg_basic_netmap_body()
         local endpoint1 endpoint2 tunnel1 tunnel2 tunnel3 tunnel4
 	local pid status
 
-	kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
-	kldload -n netmap || atf_skip "This test requires netmap and could not load it"
-
 	pri1=$(wg genkey)
 	pri2=$(wg genkey)
 
@@ -268,6 +265,7 @@ wg_key_peerdev_shared_head()
 {
 	atf_set descr 'Create a wg(4) interface with a shared pubkey between device and a peer'
 	atf_set require.user root
+	atf_set require.kmods if_wg
 }
 
 wg_key_peerdev_shared_body()
@@ -275,8 +273,6 @@ wg_key_peerdev_shared_body()
 	local epair pri1 pub1 wg1
         local endpoint1 tunnel1
 
-	kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
-
 	pri1=$(wg genkey)
 
 	endpoint1=192.168.2.1
@@ -316,8 +312,6 @@ wg_key_peerdev_makeshared_body()
 	local epair pri1 pub1 pri2 wg1 wg2
         local endpoint1 tunnel1
 
-	kldload -n if_wg || atf_skip "This test requires if_wg and could not load it"
-
 	pri1=$(wg genkey)
 	pri2=$(wg genkey)
 
@@ -361,6 +355,7 @@ wg_vnet_parent_routing_head()
 {
 	atf_set descr 'Create a wg(4) tunnel without epairs and pass traffic between jails'
 	atf_set require.user root
+	atf_set require.kmods if_wg
 }
 
 wg_vnet_parent_routing_body()
@@ -368,8 +363,6 @@ wg_vnet_parent_routing_body()
 	local pri1 pri2 pub1 pub2 wg1 wg2
         local tunnel1 tunnel2
 
-	kldload -n if_wg
-
 	pri1=$(wg genkey)
 	pri2=$(wg genkey)
 
diff --git a/usr.bin/kyua/Makefile b/usr.bin/kyua/Makefile
index 24ceba841ba4..d3a7b9b61f64 100644
--- a/usr.bin/kyua/Makefile
+++ b/usr.bin/kyua/Makefile
@@ -13,7 +13,7 @@ KYUA_SRCDIR=	${SRCTOP}/contrib/kyua
 PACKAGE=	tests
 PROG_CXX=	kyua
 SRCS=		main.cpp
-LIBADD=		lutok sqlite3
+LIBADD=		lutok sqlite3 util
 
 MAN=		kyua-about.1 \
 		kyua-config.1 \