The branch main has been updated by igoro: URL: https://cgit.FreeBSD.org/src/commit/?id=939fec44a79323ba06cf0ad60d4b69300a8abbc6
commit 939fec44a79323ba06cf0ad60d4b69300a8abbc6 Author: Igor Ostapenko <ig...@freebsd.org> AuthorDate: 2025-08-16 23:04:15 +0000 Commit: Igor Ostapenko <ig...@freebsd.org> CommitDate: 2025-08-16 23:19:43 +0000 kyua: Improve required_kmods metadata - Make it platform agnostic - Separate FreeBSD related code - Fix tests - Make it report all non-loaded modules instead of the first occurrence only - Update kyuafile.5 man page Reviewed by: ngie MFC after: 2 weeks Pull Request: https://github.com/freebsd/kyua/pull/270 --- contrib/kyua/doc/kyuafile.5.in | 10 ++++ contrib/kyua/drivers/report_junit_test.cpp | 3 ++ contrib/kyua/engine/atf_list.cpp | 2 - contrib/kyua/engine/requirements.cpp | 57 +++++++++++------------ contrib/kyua/engine/requirements.hpp | 26 +++++++++++ contrib/kyua/integration/cmd_report_junit_test.sh | 4 ++ contrib/kyua/integration/cmd_report_test.sh | 1 + contrib/kyua/model/metadata.cpp | 16 +++---- contrib/kyua/model/metadata.hpp | 4 -- contrib/kyua/model/metadata_test.cpp | 6 ++- contrib/kyua/model/test_case_test.cpp | 2 +- contrib/kyua/model/test_program_test.cpp | 8 ++-- contrib/kyua/os/freebsd/main.cpp | 11 +++++ contrib/kyua/os/freebsd/reqs_checker_kmods.cpp | 50 ++++++++++++++++++++ contrib/kyua/os/freebsd/reqs_checker_kmods.hpp | 54 +++++++++++++++++++++ usr.bin/kyua/Makefile | 3 +- 16 files changed, 203 insertions(+), 54 deletions(-) diff --git a/contrib/kyua/doc/kyuafile.5.in b/contrib/kyua/doc/kyuafile.5.in index ae1e4fe40e32..43f00816d407 100644 --- a/contrib/kyua/doc/kyuafile.5.in +++ b/contrib/kyua/doc/kyuafile.5.in @@ -290,6 +290,16 @@ it can run. .Pp ATF: .Va require.files +.It Va required_kmods +Whitespace-separated list of kernel module names that the test requires to +be loaded before it can run. +This requirement checking is platform-dependent. +It is ignored for a non-supported platform. +Supported platforms: +.Fx . +.Pp +ATF: +.Va require.kmods .It Va required_memory Amount of physical memory that the test needs to run successfully. .Pp diff --git a/contrib/kyua/drivers/report_junit_test.cpp b/contrib/kyua/drivers/report_junit_test.cpp index 0f009c6befd3..1c0929c0fef2 100644 --- a/contrib/kyua/drivers/report_junit_test.cpp +++ b/contrib/kyua/drivers/report_junit_test.cpp @@ -70,6 +70,7 @@ static const char* const default_metadata = "required_configs is empty\n" "required_disk_space = 0\n" "required_files is empty\n" + "required_kmods is empty\n" "required_memory = 0\n" "required_programs is empty\n" "required_user is empty\n" @@ -89,6 +90,7 @@ static const char* const overriden_metadata = "required_configs is empty\n" "required_disk_space = 0\n" "required_files is empty\n" + "required_kmods is empty\n" "required_memory = 0\n" "required_programs is empty\n" "required_user is empty\n" @@ -228,6 +230,7 @@ ATF_TEST_CASE_BODY(junit_metadata__overrides) + "required_configs = config1\n" + "required_disk_space = 456\n" + "required_files = file1\n" + + "required_kmods is empty\n" + "required_memory = 123\n" + "required_programs = prog1\n" + "required_user = root\n" diff --git a/contrib/kyua/engine/atf_list.cpp b/contrib/kyua/engine/atf_list.cpp index e0c4170605d1..5c74a80be913 100644 --- a/contrib/kyua/engine/atf_list.cpp +++ b/contrib/kyua/engine/atf_list.cpp @@ -133,10 +133,8 @@ 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 dff43e531a57..d5838b83f33a 100644 --- a/contrib/kyua/engine/requirements.cpp +++ b/contrib/kyua/engine/requirements.cpp @@ -41,10 +41,6 @@ #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; @@ -224,26 +220,6 @@ 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 @@ -289,9 +265,29 @@ check_required_disk_space(const units::bytes& required_disk_space, } +/// List of registered extra requirement checkers. +/// +/// Use register_reqs_checker() to add an entry to this global list. +static std::vector< std::shared_ptr< engine::reqs_checker > > _reqs_checkers; + + } // anonymous namespace +const std::vector< std::shared_ptr< engine::reqs_checker > > +engine::reqs_checkers() +{ + return _reqs_checkers; +} + +void +engine::register_reqs_checker( + const std::shared_ptr< engine::reqs_checker > checker) +{ + _reqs_checkers.push_back(checker); +} + + /// Checks if all the requirements specified by the test case are met. /// /// \param md The test metadata. @@ -336,12 +332,6 @@ 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; @@ -351,6 +341,13 @@ engine::check_reqs(const model::metadata& md, const config::tree& cfg, if (!reason.empty()) return reason; + // Iterate over extra checkers registered. + for (auto& checker : engine::reqs_checkers()) { + reason = checker->exec(md, cfg, test_suite, work_directory); + if (!reason.empty()) + return reason; + } + INV(reason.empty()); return reason; } diff --git a/contrib/kyua/engine/requirements.hpp b/contrib/kyua/engine/requirements.hpp index a36a938b3034..92e80c5122aa 100644 --- a/contrib/kyua/engine/requirements.hpp +++ b/contrib/kyua/engine/requirements.hpp @@ -44,6 +44,32 @@ namespace engine { std::string check_reqs(const model::metadata&, const utils::config::tree&, const std::string&, const utils::fs::path&); +/// Abstract interface of a requirement checker. +class reqs_checker { +public: + /// Constructor. + reqs_checker() {} + + /// Destructor. + virtual ~reqs_checker() {} + + /// Run the checker. + virtual std::string exec(const model::metadata&, + const utils::config::tree&, + const std::string&, + const utils::fs::path&) const = 0; +}; + +/// Register an extra requirement checker. +/// +/// \param checker A requirement checker. +void register_reqs_checker(const std::shared_ptr< reqs_checker > checker); + +/// Returns the list of registered extra requirement checkers. +/// +/// \return A vector of pointers to extra requirement checkers. +const std::vector< std::shared_ptr< reqs_checker > > reqs_checkers(); + } // namespace engine diff --git a/contrib/kyua/integration/cmd_report_junit_test.sh b/contrib/kyua/integration/cmd_report_junit_test.sh index d86228acf7e5..49b8c5790167 100644 --- a/contrib/kyua/integration/cmd_report_junit_test.sh +++ b/contrib/kyua/integration/cmd_report_junit_test.sh @@ -104,6 +104,7 @@ is_exclusive = false required_configs is empty required_disk_space = 0 required_files is empty +required_kmods is empty required_memory = 0 required_programs is empty required_user is empty @@ -144,6 +145,7 @@ is_exclusive = false required_configs is empty required_disk_space = 0 required_files is empty +required_kmods is empty required_memory = 0 required_programs is empty required_user is empty @@ -222,6 +224,7 @@ is_exclusive = false required_configs is empty required_disk_space = 0 required_files is empty +required_kmods is empty required_memory = 0 required_programs is empty required_user is empty @@ -262,6 +265,7 @@ is_exclusive = false required_configs is empty required_disk_space = 0 required_files is empty +required_kmods is empty required_memory = 0 required_programs is empty required_user is empty diff --git a/contrib/kyua/integration/cmd_report_test.sh b/contrib/kyua/integration/cmd_report_test.sh index 8b2b97f9cb4a..1fc1932d3c47 100644 --- a/contrib/kyua/integration/cmd_report_test.sh +++ b/contrib/kyua/integration/cmd_report_test.sh @@ -258,6 +258,7 @@ Metadata: required_configs is empty required_disk_space = 0 required_files is empty + required_kmods is empty required_memory = 0 required_programs is empty required_user is empty diff --git a/contrib/kyua/model/metadata.cpp b/contrib/kyua/model/metadata.cpp index a5a9a1315964..afb31435a238 100644 --- a/contrib/kyua/model/metadata.cpp +++ b/contrib/kyua/model/metadata.cpp @@ -256,9 +256,7 @@ 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"); @@ -285,9 +283,7 @@ 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 @@ -603,20 +599,20 @@ model::metadata::required_memory(void) const } -#ifdef __FreeBSD__ -/// Returns the list of kmods needed by the test. +/// Returns the list of kernel modules needed by the test. /// -/// \return Set of strings. +/// \return Set of kernel module names. 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"); + return _pimpl->props.lookup< config::strings_set_node >( + "required_kmods"); } else { - return get_defaults().lookup< config::strings_set_node >("required_kmods"); + return get_defaults().lookup< config::strings_set_node >( + "required_kmods"); } } -#endif /// Returns the list of programs needed by the test. diff --git a/contrib/kyua/model/metadata.hpp b/contrib/kyua/model/metadata.hpp index 8af6c7c161af..eee7eaf0f7c4 100644 --- a/contrib/kyua/model/metadata.hpp +++ b/contrib/kyua/model/metadata.hpp @@ -76,9 +76,7 @@ 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; @@ -124,9 +122,7 @@ 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/contrib/kyua/model/metadata_test.cpp b/contrib/kyua/model/metadata_test.cpp index b4c3dff5b029..bdb1d3655c33 100644 --- a/contrib/kyua/model/metadata_test.cpp +++ b/contrib/kyua/model/metadata_test.cpp @@ -57,6 +57,7 @@ ATF_TEST_CASE_BODY(defaults) ATF_REQUIRE(md.required_configs().empty()); ATF_REQUIRE_EQ(units::bytes(0), md.required_disk_space()); ATF_REQUIRE(md.required_files().empty()); + ATF_REQUIRE(md.required_kmods().empty()); ATF_REQUIRE_EQ(units::bytes(0), md.required_memory()); ATF_REQUIRE(md.required_programs().empty()); ATF_REQUIRE(md.required_user().empty()); @@ -322,6 +323,7 @@ ATF_TEST_CASE_BODY(to_properties) props["required_configs"] = ""; props["required_disk_space"] = "0"; props["required_files"] = "bar foo"; + props["required_kmods"] = ""; props["required_memory"] = "1.00K"; props["required_programs"] = ""; props["required_user"] = ""; @@ -412,7 +414,7 @@ ATF_TEST_CASE_BODY(output__defaults) "has_cleanup='false', is_exclusive='false', " "required_configs='', " "required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}", str.str()); } @@ -435,7 +437,7 @@ ATF_TEST_CASE_BODY(output__some_values) "has_cleanup='false', is_exclusive='true', " "required_configs='', " "required_disk_space='0', required_files='bar foo', " - "required_memory='1.00K', " + "required_kmods='', required_memory='1.00K', " "required_programs='', required_user='', timeout='300'}", str.str()); } diff --git a/contrib/kyua/model/test_case_test.cpp b/contrib/kyua/model/test_case_test.cpp index 1e2597d1501e..29df7ee35863 100644 --- a/contrib/kyua/model/test_case_test.cpp +++ b/contrib/kyua/model/test_case_test.cpp @@ -204,7 +204,7 @@ ATF_TEST_CASE_BODY(test_case__output) "has_cleanup='false', " "is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}}", str.str()); } diff --git a/contrib/kyua/model/test_program_test.cpp b/contrib/kyua/model/test_program_test.cpp index ddfbc430387c..f7a84d770fc0 100644 --- a/contrib/kyua/model/test_program_test.cpp +++ b/contrib/kyua/model/test_program_test.cpp @@ -547,7 +547,7 @@ check_output__no_test_cases(void) "description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}, " "test_cases=map()}", str.str()); @@ -597,7 +597,7 @@ check_output__some_test_cases(void) "description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}, " "test_cases=map(" "another-name=test_case{name='another-name', " @@ -605,14 +605,14 @@ check_output__some_test_cases(void) "description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}}, " "the-name=test_case{name='the-name', " "metadata=metadata{allowed_architectures='a', allowed_platforms='foo', " "custom.bar='baz', description='', execenv='', execenv_jail_params='', " "has_cleanup='false', is_exclusive='false', " "required_configs='', required_disk_space='0', required_files='', " - "required_memory='0', " + "required_kmods='', required_memory='0', " "required_programs='', required_user='', timeout='300'}})}", str.str()); } diff --git a/contrib/kyua/os/freebsd/main.cpp b/contrib/kyua/os/freebsd/main.cpp index 13e5dcf0e023..700284b64b78 100644 --- a/contrib/kyua/os/freebsd/main.cpp +++ b/contrib/kyua/os/freebsd/main.cpp @@ -31,6 +31,9 @@ #include "engine/execenv/execenv.hpp" #include "os/freebsd/execenv_jail_manager.hpp" +#include "engine/requirements.hpp" +#include "os/freebsd/reqs_checker_kmods.hpp" + namespace execenv = engine::execenv; /// FreeBSD related features initialization. @@ -50,5 +53,13 @@ freebsd::main(const int, const char* const* const) std::shared_ptr< execenv::manager >(new freebsd::execenv_jail_manager()) ); +#ifdef __FreeBSD__ + engine::register_reqs_checker( + std::shared_ptr< engine::reqs_checker >( + new freebsd::reqs_checker_kmods() + ) + ); +#endif + return 0; } diff --git a/contrib/kyua/os/freebsd/reqs_checker_kmods.cpp b/contrib/kyua/os/freebsd/reqs_checker_kmods.cpp new file mode 100644 index 000000000000..3ae3446a7815 --- /dev/null +++ b/contrib/kyua/os/freebsd/reqs_checker_kmods.cpp @@ -0,0 +1,50 @@ +// Copyright 2025 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/reqs_checker_kmods.hpp" + +#include "model/metadata.hpp" + +extern "C" { +#include "libutil.h" +} + +std::string +freebsd::reqs_checker_kmods::exec(const model::metadata& md, + const utils::config::tree&, + const std::string&, + const utils::fs::path&) const +{ + std::string reason = ""; + for (auto& kmod : md.required_kmods()) + if (!::kld_isloaded((kmod).c_str())) + reason += " " + kmod; + if (!reason.empty()) + reason = "Required kmods are not loaded:" + reason + "."; + return reason; +} diff --git a/contrib/kyua/os/freebsd/reqs_checker_kmods.hpp b/contrib/kyua/os/freebsd/reqs_checker_kmods.hpp new file mode 100644 index 000000000000..8c7c69e35d07 --- /dev/null +++ b/contrib/kyua/os/freebsd/reqs_checker_kmods.hpp @@ -0,0 +1,54 @@ +// Copyright 2025 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/reqs_checker_kmods.hpp +/// FreeBSD kernel module requirement checker. + +#if !defined(FREEBSD_REQS_CHECKER_KMODS) +#define FREEBSD_REQS_CHECKER_KMODS + +#include "engine/requirements.hpp" +#include "model/metadata_fwd.hpp" +#include "utils/config/tree_fwd.hpp" +#include "utils/fs/path_fwd.hpp" + +namespace freebsd { + + +class reqs_checker_kmods : public engine::reqs_checker { +public: + std::string exec(const model::metadata&, + const utils::config::tree&, + const std::string&, + const utils::fs::path&) const; +}; + + +} // namespace freebsd + +#endif // !defined(FREEBSD_REQS_CHECKER_KMODS) diff --git a/usr.bin/kyua/Makefile b/usr.bin/kyua/Makefile index a4f95f1106d9..daefedbf8bca 100644 --- a/usr.bin/kyua/Makefile +++ b/usr.bin/kyua/Makefile @@ -129,7 +129,8 @@ SRCS+= engine/atf.cpp \ engine/execenv/execenv_host.cpp SRCS+= os/freebsd/execenv_jail_manager.cpp \ - os/freebsd/main.cpp + os/freebsd/main.cpp \ + os/freebsd/reqs_checker_kmods.cpp SRCS+= store/dbtypes.cpp \ store/exceptions.cpp \