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

(cherry picked from commit 939fec44a79323ba06cf0ad60d4b69300a8abbc6)
This commit is contained in:
Igor Ostapenko 2025-08-16 23:04:15 +00:00 committed by Franco Fichtner
parent 84a5cebd36
commit 7668caa0cd
16 changed files with 203 additions and 54 deletions

View file

@ -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

View file

@ -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"

View file

@ -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") {

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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&);

View file

@ -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());
}

View file

@ -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());
}

View file

@ -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());
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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)

View file

@ -133,7 +133,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 \