git: edb230c4af49 - main - kyua: Add "prepare" command

From: Igor Ostapenko <igoro_at_FreeBSD.org>
Date: Sun, 23 Aug 2026 23:57:42 UTC
The branch main has been updated by igoro:

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

commit edb230c4af499203d7a6894b3711fe6574b26040
Author:     Igor Ostapenko <igoro@FreeBSD.org>
AuthorDate: 2026-08-23 22:42:48 +0000
Commit:     Igor Ostapenko <igoro@FreeBSD.org>
CommitDate: 2026-08-23 22:54:14 +0000

    kyua: Add "prepare" command
    
    This introduces the requirement preparation handler concept, with the
    first handler implemented to load the required kernel modules.
    
    Running without arguments lists all available handlers:
    
      kyua prepare
    
    Currently there are only two handlers:
    
      all: runs all available handlers
      kmods: loads the modules declared by required_kmods metadata
    
    The dry run option lists the required modules without actual loading:
    
      kyua prepare { --dry-run | -n } kmods
    
    The "prepare" command traverses only the given tree of tests, i.e., the
    following invocation lists all required modules for the whole test
    suite:
    
      kyua prepare -k /usr/tests/Kyuafile -n kmods
    
    , while this one is limited to the pf tests only:
    
      kyua prepare -k /usr/tests/sys/netpfil/pf/Kyuafile -n kmods
    
    Reviewed by:    ngie
    Differential Revision:  https://reviews.freebsd.org/D48087
---
 contrib/kyua/cli/cmd_prepare.cpp            |  76 +++++++++++++++
 contrib/kyua/cli/cmd_prepare.hpp            |  53 +++++++++++
 contrib/kyua/cli/main.cpp                   |   2 +
 contrib/kyua/doc/kyuafile.5.in              |   1 +
 contrib/kyua/engine/prepare/prepare.cpp     |  83 ++++++++++++++++
 contrib/kyua/engine/prepare/prepare.hpp     | 107 +++++++++++++++++++++
 contrib/kyua/engine/prepare/prepare_all.cpp |  66 +++++++++++++
 contrib/kyua/engine/prepare/prepare_all.hpp |  53 +++++++++++
 contrib/kyua/model/metadata.cpp             |  47 +++++----
 contrib/kyua/model/metadata.hpp             |   4 +-
 contrib/kyua/os/freebsd/main.cpp            |   8 ++
 contrib/kyua/os/freebsd/prepare_kmods.cpp   | 143 ++++++++++++++++++++++++++++
 contrib/kyua/os/freebsd/prepare_kmods.hpp   |  55 +++++++++++
 usr.bin/kyua/Makefile                       |   8 +-
 14 files changed, 686 insertions(+), 20 deletions(-)

diff --git a/contrib/kyua/cli/cmd_prepare.cpp b/contrib/kyua/cli/cmd_prepare.cpp
new file mode 100644
index 000000000000..a3ba5c3285a1
--- /dev/null
+++ b/contrib/kyua/cli/cmd_prepare.cpp
@@ -0,0 +1,76 @@
+// Copyright 2024 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+//   notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+//   notice, this list of conditions and the following disclaimer in the
+//   documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+//   may be used to endorse or promote products derived from this software
+//   without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER 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.
+
+#include "cli/cmd_prepare.hpp"
+
+#include "cli/common.ipp"
+#include "engine/prepare/prepare.hpp"
+#include "utils/cmdline/options.hpp"
+
+namespace cmdline = utils::cmdline;
+namespace config = utils::config;
+namespace prepare = engine::prepare;
+
+using cli::cmd_prepare;
+
+
+/// Default constructor for cmd_prepare.
+cmd_prepare::cmd_prepare(void) : cli_command(
+    "prepare", "[handler-name ...]", 0, -1,
+    "Prepares environment and declared requirements before testing")
+{
+    add_option(kyuafile_option);
+    add_option(build_root_option);
+    add_option(cmdline::bool_option('n', "dry-run", "Do not alter the system"));
+}
+
+
+/// Entry point for the "prepare" subcommand.
+///
+/// \param ui Object to interact with the I/O of the program.
+/// \param cmdline Representation of the command line to the subcommand.
+/// \param user_config The runtime configuration of the program.
+///
+/// \return 0 if successful, 1 otherwise.
+int
+cmd_prepare::run(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline,
+            const config::tree& user_config)
+{
+    // List available preparation handlers
+    if (cmdline.arguments().empty()) {
+        for (auto& h : prepare::handlers()) {
+            ui->out(h->name(), false);
+            ui->out("\t\t\t", false);
+            ui->out(h->description());
+        }
+        return EXIT_SUCCESS;
+    }
+
+    // Or run the given ones
+    return prepare::run(cmdline.arguments(), ui, cmdline, user_config);
+}
diff --git a/contrib/kyua/cli/cmd_prepare.hpp b/contrib/kyua/cli/cmd_prepare.hpp
new file mode 100644
index 000000000000..322b2baf9e8f
--- /dev/null
+++ b/contrib/kyua/cli/cmd_prepare.hpp
@@ -0,0 +1,53 @@
+// Copyright 2024 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+//   notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+//   notice, this list of conditions and the following disclaimer in the
+//   documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+//   may be used to endorse or promote products derived from this software
+//   without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER 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.
+
+/// \file cli/cmd_prepare.hpp
+/// Provides the cmd_prepare class.
+
+#if !defined(CLI_CMD_PREPARE_HPP)
+#define CLI_CMD_PREPARE_HPP
+
+#include "cli/common.hpp"
+
+namespace cli {
+
+
+/// Implementation of the "prepare" subcommand.
+class cmd_prepare : public cli_command
+{
+public:
+    cmd_prepare(void);
+
+    int run(utils::cmdline::ui*, const utils::cmdline::parsed_cmdline&,
+            const utils::config::tree&);
+};
+
+
+}  // namespace cli
+
+#endif  // !defined(CLI_CMD_PREPARE_HPP)
diff --git a/contrib/kyua/cli/main.cpp b/contrib/kyua/cli/main.cpp
index dd7ce939e162..ad679f0949b3 100644
--- a/contrib/kyua/cli/main.cpp
+++ b/contrib/kyua/cli/main.cpp
@@ -49,6 +49,7 @@ extern "C" {
 #include "cli/cmd_debug.hpp"
 #include "cli/cmd_help.hpp"
 #include "cli/cmd_list.hpp"
+#include "cli/cmd_prepare.hpp"
 #include "cli/cmd_report.hpp"
 #include "cli/cmd_report_html.hpp"
 #include "cli/cmd_report_junit.hpp"
@@ -191,6 +192,7 @@ safe_main(cmdline::ui* ui, int argc, const char* const argv[],
 
     commands.insert(new cli::cmd_debug(), "Workspace");
     commands.insert(new cli::cmd_list(), "Workspace");
+    commands.insert(new cli::cmd_prepare(), "Workspace");
     commands.insert(new cli::cmd_test(), "Workspace");
 
     commands.insert(new cli::cmd_report(), "Reporting");
diff --git a/contrib/kyua/doc/kyuafile.5.in b/contrib/kyua/doc/kyuafile.5.in
index a9106e95d790..eddb8f92cb86 100644
--- a/contrib/kyua/doc/kyuafile.5.in
+++ b/contrib/kyua/doc/kyuafile.5.in
@@ -300,6 +300,7 @@ Supported platforms:
 .Pp
 ATF:
 .Va require.kmods
+(appends to the list declared at the Kyuafile level instead of overwriting it)
 .It Va required_memory
 Amount of physical memory that the test needs to run successfully.
 .Pp
diff --git a/contrib/kyua/engine/prepare/prepare.cpp b/contrib/kyua/engine/prepare/prepare.cpp
new file mode 100644
index 000000000000..218b236450c0
--- /dev/null
+++ b/contrib/kyua/engine/prepare/prepare.cpp
@@ -0,0 +1,83 @@
+// Copyright 2024 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+//   notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+//   notice, this list of conditions and the following disclaimer in the
+//   documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+//   may be used to endorse or promote products derived from this software
+//   without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER 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.
+
+#include "engine/prepare/prepare.hpp"
+
+#include "engine/prepare/prepare_all.hpp"
+
+namespace prepare = engine::prepare;
+
+
+/// List of registered requirement preparation handlers.
+///
+/// Use register_handler() to add an entry to this global list.
+static std::vector< std::shared_ptr< prepare::handler > > _handlers = {
+    std::shared_ptr< prepare::handler >(new prepare::prepare_all())
+};
+
+
+void
+prepare::register_handler(const std::shared_ptr< handler > handler)
+{
+    _handlers.push_back(handler);
+}
+
+
+const std::vector< std::shared_ptr< prepare::handler > >
+prepare::handlers()
+{
+    return _handlers;
+}
+
+
+int
+prepare::run(const std::vector< std::string >& handler_names,
+        cmdline::ui* ui,
+        const cmdline::parsed_cmdline& cmdline,
+        const config::tree& user_config)
+{
+    for (auto& hname : handler_names) {
+        std::shared_ptr< prepare::handler > handler = nullptr;
+        for (auto& h : prepare::handlers())
+            if (h->name() == hname) {
+                handler = h;
+                break;
+            }
+
+        if (handler == nullptr) {
+            ui->out(F("Unknown requirement preparation handler: %s") % hname);
+            return EXIT_FAILURE;
+        }
+
+        if (handler->exec(ui, cmdline, user_config) != 0)
+            // suppress the actual code -- main limits possible exit codes
+            return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}
diff --git a/contrib/kyua/engine/prepare/prepare.hpp b/contrib/kyua/engine/prepare/prepare.hpp
new file mode 100644
index 000000000000..2ee12b11a623
--- /dev/null
+++ b/contrib/kyua/engine/prepare/prepare.hpp
@@ -0,0 +1,107 @@
+// Copyright 2024 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+//   notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+//   notice, this list of conditions and the following disclaimer in the
+//   documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+//   may be used to endorse or promote products derived from this software
+//   without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER 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.
+
+/// \file engine/prepare/prepare.hpp
+/// Requirement preparation subsystem interface.
+
+#if !defined(ENGINE_PREPARE_HPP)
+#define ENGINE_PREPARE_HPP
+
+#include <string>
+#include <vector>
+
+#include "utils/cmdline/parser.ipp"
+#include "utils/cmdline/ui.hpp"
+#include "utils/config/tree.ipp"
+
+namespace cmdline = utils::cmdline;
+namespace config = utils::config;
+
+
+namespace engine {
+namespace prepare {
+
+
+/// Abstract interface of a requirement preparation handler.
+class handler {
+public:
+    /// Constructor.
+    handler() {}
+
+    /// Destructor.
+    virtual ~handler() {}
+
+    /// Returns name of the handler.
+    virtual const std::string& name() const = 0;
+
+    /// Returns short description of the handler.
+    virtual const std::string& description() const = 0;
+
+    /// Runs the requirement preparation handler.
+    ///
+    /// \param ui Object to interact with the I/O of the program.
+    /// \param cmdline Representation of the command line to the subcommand.
+    /// \param user_config The runtime configuration of the program.
+    ///
+    /// \return 0 to indicate success.
+    virtual int exec(cmdline::ui* ui,
+                     const cmdline::parsed_cmdline& cmdline,
+                     const config::tree& user_config) const = 0;
+};
+
+
+/// Registers a requirement preparation handler.
+///
+/// \param handler A requirement preparation handler.
+void register_handler(const std::shared_ptr< handler > handler);
+
+
+/// Returns the list of registered requirement preparation handlers.
+///
+/// \return A vector of pointers to requirement preparation handlers.
+const std::vector< std::shared_ptr< handler > > handlers();
+
+
+/// Run named handlers.
+///
+/// \param handler_names Names of the handlers to run.
+/// \param ui Object to interact with the I/O of the program.
+/// \param cmdline Representation of the command line to the subcommand.
+/// \param user_config The runtime configuration of the program.
+///
+/// \return 0 to indicate success.
+int run(const std::vector< std::string >& handler_names,
+        cmdline::ui* ui,
+        const cmdline::parsed_cmdline& cmdline,
+        const config::tree& user_config);
+
+
+}  // namespace prepare
+}  // namespace engine
+
+#endif  // !defined(ENGINE_PREPARE_HPP)
diff --git a/contrib/kyua/engine/prepare/prepare_all.cpp b/contrib/kyua/engine/prepare/prepare_all.cpp
new file mode 100644
index 000000000000..db8ea330a51e
--- /dev/null
+++ b/contrib/kyua/engine/prepare/prepare_all.cpp
@@ -0,0 +1,66 @@
+// Copyright 2024 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+//   notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+//   notice, this list of conditions and the following disclaimer in the
+//   documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+//   may be used to endorse or promote products derived from this software
+//   without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER 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.
+
+#include "engine/prepare/prepare_all.hpp"
+
+static const std::string _name = "all";
+static const std::string _description = "Run all preparations";
+
+namespace prepare = engine::prepare;
+
+
+const std::string&
+prepare::prepare_all::name() const
+{
+    return _name;
+}
+
+
+const std::string&
+prepare::prepare_all::description() const
+{
+    return _description;
+}
+
+
+int
+prepare::prepare_all::exec(cmdline::ui* ui,
+                           const cmdline::parsed_cmdline& cmdline,
+                           const config::tree& user_config) const
+{
+    for (auto& handler : prepare::handlers()) {
+        if (handler->name() == this->name())
+            continue;
+
+        int error = handler->exec(ui, cmdline, user_config);
+        if (error != EXIT_SUCCESS)
+            return error;
+    }
+
+    return EXIT_SUCCESS;
+}
diff --git a/contrib/kyua/engine/prepare/prepare_all.hpp b/contrib/kyua/engine/prepare/prepare_all.hpp
new file mode 100644
index 000000000000..7c5c54fe269c
--- /dev/null
+++ b/contrib/kyua/engine/prepare/prepare_all.hpp
@@ -0,0 +1,53 @@
+// Copyright 2024 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+//   notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+//   notice, this list of conditions and the following disclaimer in the
+//   documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+//   may be used to endorse or promote products derived from this software
+//   without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER 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.
+
+/// \file engine/prepare/prepare_all.hpp
+/// The requirement preparation handler which runs all registered handlers.
+
+#if !defined(ENGINE_PREPARE_ALL_HPP)
+#define ENGINE_PREPARE_ALL_HPP
+
+#include "engine/prepare/prepare.hpp"
+
+namespace engine {
+namespace prepare {
+
+
+class prepare_all : public prepare::handler {
+public:
+    const std::string& name() const;
+    const std::string& description() const;
+    int exec(cmdline::ui*, const cmdline::parsed_cmdline&,
+             const config::tree&) const;
+};
+
+
+}  // namespace prepare
+}  // namespace engine
+
+#endif  // !defined(ENGINE_PREPARE_ALL_HPP)
diff --git a/contrib/kyua/model/metadata.cpp b/contrib/kyua/model/metadata.cpp
index afb31435a238..708a01667bbb 100644
--- a/contrib/kyua/model/metadata.cpp
+++ b/contrib/kyua/model/metadata.cpp
@@ -255,8 +255,8 @@ init_tree(config::tree& tree)
     tree.define< config::strings_set_node >("required_configs");
     tree.define< bytes_node >("required_disk_space");
     tree.define< paths_set_node >("required_files");
-    tree.define< bytes_node >("required_memory");
     tree.define< config::strings_set_node >("required_kmods");
+    tree.define< bytes_node >("required_memory");
     tree.define< paths_set_node >("required_programs");
     tree.define< user_node >("required_user");
     tree.define< delta_node >("timeout");
@@ -282,8 +282,8 @@ set_defaults(config::tree& tree)
                                          model::strings_set());
     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));
     tree.set< config::strings_set_node >("required_kmods", model::strings_set());
+    tree.set< bytes_node >("required_memory", units::bytes(0));
     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
@@ -585,20 +585,6 @@ model::metadata::required_files(void) const
 }
 
 
-/// Returns the amount of memory required by the test.
-///
-/// \return Number of bytes, or 0 if this does not apply.
-const units::bytes&
-model::metadata::required_memory(void) const
-{
-    if (_pimpl->props.is_set("required_memory")) {
-        return _pimpl->props.lookup< bytes_node >("required_memory");
-    } else {
-        return get_defaults().lookup< bytes_node >("required_memory");
-    }
-}
-
-
 /// Returns the list of kernel modules needed by the test.
 ///
 /// \return Set of kernel module names.
@@ -615,6 +601,20 @@ model::metadata::required_kmods(void) const
 }
 
 
+/// Returns the amount of memory required by the test.
+///
+/// \return Number of bytes, or 0 if this does not apply.
+const units::bytes&
+model::metadata::required_memory(void) const
+{
+    if (_pimpl->props.is_set("required_memory")) {
+        return _pimpl->props.lookup< bytes_node >("required_memory");
+    } else {
+        return get_defaults().lookup< bytes_node >("required_memory");
+    }
+}
+
+
 /// Returns the list of programs needed by the test.
 ///
 /// \return Set of paths.
@@ -1059,6 +1059,21 @@ model::metadata_builder::set_required_files(const model::paths_set& files)
 }
 
 
+/// Sets the list of required kernel modules.
+///
+/// \param vars Set of kernel module names.
+///
+/// \return A reference to this builder.
+///
+/// \throw model::error If the value is invalid.
+model::metadata_builder&
+model::metadata_builder::set_required_kmods(const model::strings_set& kmods)
+{
+    set< config::strings_set_node >(_pimpl->props, "required_kmods", kmods);
+    return *this;
+}
+
+
 /// Sets the amount of memory required by the test.
 ///
 /// \param bytes Number of bytes.
diff --git a/contrib/kyua/model/metadata.hpp b/contrib/kyua/model/metadata.hpp
index eee7eaf0f7c4..d915677eb41d 100644
--- a/contrib/kyua/model/metadata.hpp
+++ b/contrib/kyua/model/metadata.hpp
@@ -75,8 +75,8 @@ public:
     const strings_set& required_configs(void) const;
     const utils::units::bytes& required_disk_space(void) const;
     const paths_set& required_files(void) const;
-    const utils::units::bytes& required_memory(void) const;
     const strings_set& required_kmods(void) const;
+    const utils::units::bytes& required_memory(void) const;
     const paths_set& required_programs(void) const;
     const std::string& required_user(void) const;
     const utils::datetime::delta& timeout(void) const;
@@ -121,8 +121,8 @@ public:
     metadata_builder& set_required_configs(const strings_set&);
     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&);
     metadata_builder& set_required_kmods(const strings_set&);
+    metadata_builder& set_required_memory(const utils::units::bytes&);
     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/contrib/kyua/os/freebsd/main.cpp b/contrib/kyua/os/freebsd/main.cpp
index 700284b64b78..30f1a3fa7142 100644
--- a/contrib/kyua/os/freebsd/main.cpp
+++ b/contrib/kyua/os/freebsd/main.cpp
@@ -34,7 +34,12 @@
 #include "engine/requirements.hpp"
 #include "os/freebsd/reqs_checker_kmods.hpp"
 
+#include "engine/prepare/prepare.hpp"
+#include "os/freebsd/prepare_kmods.hpp"
+
 namespace execenv = engine::execenv;
+namespace prepare = engine::prepare;
+
 
 /// FreeBSD related features initialization.
 ///
@@ -59,6 +64,9 @@ freebsd::main(const int, const char* const* const)
             new freebsd::reqs_checker_kmods()
         )
     );
+
+    prepare::register_handler(
+        std::shared_ptr< prepare::handler >(new freebsd::prepare_kmods()));
 #endif
 
     return 0;
diff --git a/contrib/kyua/os/freebsd/prepare_kmods.cpp b/contrib/kyua/os/freebsd/prepare_kmods.cpp
new file mode 100644
index 000000000000..91771f3aedfd
--- /dev/null
+++ b/contrib/kyua/os/freebsd/prepare_kmods.cpp
@@ -0,0 +1,143 @@
+// Copyright 2024 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+//   notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+//   notice, this list of conditions and the following disclaimer in the
+//   documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+//   may be used to endorse or promote products derived from this software
+//   without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER 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.
+
+#include "os/freebsd/prepare_kmods.hpp"
+
+#include "cli/common.hpp"
+#include "drivers/list_tests.hpp"
+#include "engine/filters.hpp"
+#include "model/metadata.hpp"
+#include "model/test_case.hpp"
+#include "model/test_program.hpp"
+
+#include <system_error>
+
+// FreeBSD kldload syscall
+extern "C" {
+#include <sys/param.h>
+#include <sys/linker.h>
+}
+
+
+namespace {
+
+
+static const std::string _name = "kmods";
+static const std::string _description = "FreeBSD: load modules specified "
+    "in the required_kmods metadata";
+
+
+/// Hooks for list_tests to examine test cases as they come.
+class list_hooks : public drivers::list_tests::base_hooks {
+    /// Collected names of required kernel modules.
+    std::set< std::string >& _modules;
+
+public:
+    /// Initializes the hooks.
+    ///
+    /// \param modules_ The set of modules to fill.
+    list_hooks(std::set< std::string >& modules_) :
+        _modules(modules_)
+    {
+    }
+
+    /// Examine a test case as soon as it is found.
+    ///
+    /// \param test_program The test program containing the test case.
+    /// \param test_case_name The name of the located test case.
+    void
+    got_test_case(const model::test_program& test_program,
+                  const std::string& test_case_name)
+    {
+        auto test_case = test_program.find(test_case_name);
+        auto kmods = test_case.get_metadata().required_kmods();
+        _modules.insert(kmods.begin(), kmods.end());
+    }
+};
+
+
+}  // anonymous namespace
+
+
+namespace freebsd {
+
+
+const std::string&
+prepare_kmods::name() const
+{
+    return _name;
+}
+
+
+const std::string&
+prepare_kmods::description() const
+{
+    return _description;
+}
+
+
+int
+prepare_kmods::exec(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline,
+               const config::tree& user_config) const
+{
+    int err = 0;
+
+    // Collect required modules
+    std::set< std::string > modules;
+    list_hooks hooks(modules);
+    const std::set< engine::test_filter > nofilters;
+    const drivers::list_tests::result result = drivers::list_tests::drive(
+        cli::kyuafile_path(cmdline), cli::build_root_path(cmdline),
+        nofilters, user_config, hooks);
+
+    // Nothing to do
+    if (modules.empty())
+        return 0;
+
+    // Announce the work
+    ui->out("kldload", false);
+    for (auto& m : modules)
+        ui->out(F(" %s") % m, false);
+    ui->out("");
+
+    if (cmdline.has_option("dry-run"))
+        return 0;
+
+    // Load the modules
+    for (auto& m : modules) {
+        if (::kldload(m.c_str()) != -1 || errno == EEXIST)
+            continue;
+        ui->out(F("WARN: cannot kldload %s") % m);
+        err = EINVAL;
+    }
+
+    return err;
+}
+
+
+}  // namespace freebsd
diff --git a/contrib/kyua/os/freebsd/prepare_kmods.hpp b/contrib/kyua/os/freebsd/prepare_kmods.hpp
new file mode 100644
index 000000000000..36a3236a4e2d
--- /dev/null
+++ b/contrib/kyua/os/freebsd/prepare_kmods.hpp
@@ -0,0 +1,55 @@
+// Copyright 2024 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+//   notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+//   notice, this list of conditions and the following disclaimer in the
+//   documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+//   may be used to endorse or promote products derived from this software
+//   without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER 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.
+
+/// \file os/freebsd/prepare_kmods.hpp
+/// The preparation handler which loads FreeBSD modules declared via
+/// "required_kmods" metadata.
+
+#if !defined(FREEBSD_PREPARE_KMODS_HPP)
+#define FREEBSD_PREPARE_KMODS_HPP
+
+#include "engine/prepare/prepare.hpp"
+
+namespace prepare = engine::prepare;
+
+
+namespace freebsd {
+
+
+class prepare_kmods : public prepare::handler {
+public:
+    const std::string& name() const;
+    const std::string& description() const;
+    int exec(cmdline::ui*, const cmdline::parsed_cmdline&,
+             const config::tree&) const;
+};
+
+
+}  // namespace freebsd
+
+#endif  // !defined(FREEBSD_PREPARE_KMODS_HPP)
diff --git a/usr.bin/kyua/Makefile b/usr.bin/kyua/Makefile
index aa0e5cbbcad2..083f3a8ec038 100644
--- a/usr.bin/kyua/Makefile
+++ b/usr.bin/kyua/Makefile
@@ -128,11 +128,14 @@ SRCS+=	engine/atf.cpp			\
 	engine/tap_parser.cpp		\
 	engine/scheduler.cpp		\
 	engine/execenv/execenv.cpp	\
-	engine/execenv/execenv_host.cpp
+	engine/execenv/execenv_host.cpp	\
+	engine/prepare/prepare.cpp	\
+	engine/prepare/prepare_all.cpp
 
 SRCS+=	os/freebsd/execenv_jail_manager.cpp	\
 	os/freebsd/main.cpp			\
-	os/freebsd/reqs_checker_kmods.cpp
+	os/freebsd/reqs_checker_kmods.cpp	\
+	os/freebsd/prepare_kmods.cpp
 
 SRCS+=	store/dbtypes.cpp		\
 	store/exceptions.cpp		\
@@ -157,6 +160,7 @@ SRCS+=	cli/cmd_about.cpp		\
 	cli/cmd_debug.cpp		\
 	cli/cmd_help.cpp		\
 	cli/cmd_list.cpp		\
+	cli/cmd_prepare.cpp		\
 	cli/cmd_report.cpp		\
 	cli/cmd_report_html.cpp		\
 	cli/cmd_report_junit.cpp	\