xref: /freebsd/contrib/kyua/engine/requirements.cpp (revision 257e70f1d5ee61037c8c59b116538d3b6b1427a2)
1b0d29bc4SBrooks Davis // Copyright 2012 The Kyua Authors.
2b0d29bc4SBrooks Davis // All rights reserved.
3b0d29bc4SBrooks Davis //
4b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6b0d29bc4SBrooks Davis // met:
7b0d29bc4SBrooks Davis //
8b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer.
10b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
12b0d29bc4SBrooks Davis //   documentation and/or other materials provided with the distribution.
13b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14b0d29bc4SBrooks Davis //   may be used to endorse or promote products derived from this software
15b0d29bc4SBrooks Davis //   without specific prior written permission.
16b0d29bc4SBrooks Davis //
17b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28b0d29bc4SBrooks Davis 
29b0d29bc4SBrooks Davis #include "engine/requirements.hpp"
30b0d29bc4SBrooks Davis 
31*257e70f1SIgor Ostapenko #include "engine/execenv/execenv.hpp"
32b0d29bc4SBrooks Davis #include "model/metadata.hpp"
33b0d29bc4SBrooks Davis #include "model/types.hpp"
34b0d29bc4SBrooks Davis #include "utils/config/nodes.ipp"
35b0d29bc4SBrooks Davis #include "utils/config/tree.ipp"
36b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
37b0d29bc4SBrooks Davis #include "utils/fs/operations.hpp"
38b0d29bc4SBrooks Davis #include "utils/fs/path.hpp"
39b0d29bc4SBrooks Davis #include "utils/memory.hpp"
40b0d29bc4SBrooks Davis #include "utils/passwd.hpp"
41b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
42b0d29bc4SBrooks Davis #include "utils/units.hpp"
43b0d29bc4SBrooks Davis 
44b0d29bc4SBrooks Davis namespace config = utils::config;
45b0d29bc4SBrooks Davis namespace fs = utils::fs;
46b0d29bc4SBrooks Davis namespace passwd = utils::passwd;
47b0d29bc4SBrooks Davis namespace units = utils::units;
48b0d29bc4SBrooks Davis 
49b0d29bc4SBrooks Davis 
50b0d29bc4SBrooks Davis namespace {
51b0d29bc4SBrooks Davis 
52b0d29bc4SBrooks Davis 
53b0d29bc4SBrooks Davis /// Checks if all required configuration variables are present.
54b0d29bc4SBrooks Davis ///
55b0d29bc4SBrooks Davis /// \param required_configs Set of required variable names.
56b0d29bc4SBrooks Davis /// \param user_config Runtime user configuration.
57b0d29bc4SBrooks Davis /// \param test_suite_name Name of the test suite the test belongs to.
58b0d29bc4SBrooks Davis ///
59b0d29bc4SBrooks Davis /// \return Empty if all variables are present or an error message otherwise.
60b0d29bc4SBrooks Davis static std::string
check_required_configs(const model::strings_set & required_configs,const config::tree & user_config,const std::string & test_suite_name)61b0d29bc4SBrooks Davis check_required_configs(const model::strings_set& required_configs,
62b0d29bc4SBrooks Davis                        const config::tree& user_config,
63b0d29bc4SBrooks Davis                        const std::string& test_suite_name)
64b0d29bc4SBrooks Davis {
65b0d29bc4SBrooks Davis     for (model::strings_set::const_iterator iter = required_configs.begin();
66b0d29bc4SBrooks Davis          iter != required_configs.end(); iter++) {
67b0d29bc4SBrooks Davis         std::string property;
68b0d29bc4SBrooks Davis         // TODO(jmmv): All this rewrite logic belongs in the ATF interface.
69b0d29bc4SBrooks Davis         if ((*iter) == "unprivileged-user" || (*iter) == "unprivileged_user")
70b0d29bc4SBrooks Davis             property = "unprivileged_user";
71b0d29bc4SBrooks Davis         else
72b0d29bc4SBrooks Davis             property = F("test_suites.%s.%s") % test_suite_name % (*iter);
73b0d29bc4SBrooks Davis 
74b0d29bc4SBrooks Davis         if (!user_config.is_set(property))
75b0d29bc4SBrooks Davis             return F("Required configuration property '%s' not defined") %
76b0d29bc4SBrooks Davis                 (*iter);
77b0d29bc4SBrooks Davis     }
78b0d29bc4SBrooks Davis     return "";
79b0d29bc4SBrooks Davis }
80b0d29bc4SBrooks Davis 
81b0d29bc4SBrooks Davis 
82b0d29bc4SBrooks Davis /// Checks if the allowed architectures match the current architecture.
83b0d29bc4SBrooks Davis ///
84b0d29bc4SBrooks Davis /// \param allowed_architectures Set of allowed architectures.
85b0d29bc4SBrooks Davis /// \param user_config Runtime user configuration.
86b0d29bc4SBrooks Davis ///
87b0d29bc4SBrooks Davis /// \return Empty if the current architecture is in the list or an error
88b0d29bc4SBrooks Davis /// message otherwise.
89b0d29bc4SBrooks Davis static std::string
check_allowed_architectures(const model::strings_set & allowed_architectures,const config::tree & user_config)90b0d29bc4SBrooks Davis check_allowed_architectures(const model::strings_set& allowed_architectures,
91b0d29bc4SBrooks Davis                             const config::tree& user_config)
92b0d29bc4SBrooks Davis {
93b0d29bc4SBrooks Davis     if (!allowed_architectures.empty()) {
94b0d29bc4SBrooks Davis         const std::string architecture =
95b0d29bc4SBrooks Davis             user_config.lookup< config::string_node >("architecture");
96b0d29bc4SBrooks Davis         if (allowed_architectures.find(architecture) ==
97b0d29bc4SBrooks Davis             allowed_architectures.end())
98b0d29bc4SBrooks Davis             return F("Current architecture '%s' not supported") % architecture;
99b0d29bc4SBrooks Davis     }
100b0d29bc4SBrooks Davis     return "";
101b0d29bc4SBrooks Davis }
102b0d29bc4SBrooks Davis 
103b0d29bc4SBrooks Davis 
104*257e70f1SIgor Ostapenko /// Checks if test's execenv matches the user configuration.
105*257e70f1SIgor Ostapenko ///
106*257e70f1SIgor Ostapenko /// \param execenv Execution environment name a test is designed for.
107*257e70f1SIgor Ostapenko /// \param user_config Runtime user configuration.
108*257e70f1SIgor Ostapenko ///
109*257e70f1SIgor Ostapenko /// \return Empty if the execenv is in the list or an error message otherwise.
110*257e70f1SIgor Ostapenko static std::string
check_execenv(const std::string & execenv,const config::tree & user_config)111*257e70f1SIgor Ostapenko check_execenv(const std::string& execenv, const config::tree& user_config)
112*257e70f1SIgor Ostapenko {
113*257e70f1SIgor Ostapenko     std::string name = execenv;
114*257e70f1SIgor Ostapenko     if (name.empty())
115*257e70f1SIgor Ostapenko         name = engine::execenv::default_execenv_name; // if test claims nothing
116*257e70f1SIgor Ostapenko 
117*257e70f1SIgor Ostapenko     std::set< std::string > execenvs;
118*257e70f1SIgor Ostapenko     try {
119*257e70f1SIgor Ostapenko         execenvs = user_config.lookup< config::strings_set_node >("execenvs");
120*257e70f1SIgor Ostapenko     } catch (const config::unknown_key_error&) {
121*257e70f1SIgor Ostapenko         // okay, user config does not define it, empty set then
122*257e70f1SIgor Ostapenko     }
123*257e70f1SIgor Ostapenko 
124*257e70f1SIgor Ostapenko     if (execenvs.find(name) == execenvs.end())
125*257e70f1SIgor Ostapenko         return F("'%s' execenv is not supported or not allowed by "
126*257e70f1SIgor Ostapenko             "the runtime user configuration") % name;
127*257e70f1SIgor Ostapenko 
128*257e70f1SIgor Ostapenko     return "";
129*257e70f1SIgor Ostapenko }
130*257e70f1SIgor Ostapenko 
131*257e70f1SIgor Ostapenko 
132b0d29bc4SBrooks Davis /// Checks if the allowed platforms match the current architecture.
133b0d29bc4SBrooks Davis ///
134b0d29bc4SBrooks Davis /// \param allowed_platforms Set of allowed platforms.
135b0d29bc4SBrooks Davis /// \param user_config Runtime user configuration.
136b0d29bc4SBrooks Davis ///
137b0d29bc4SBrooks Davis /// \return Empty if the current platform is in the list or an error message
138b0d29bc4SBrooks Davis /// otherwise.
139b0d29bc4SBrooks Davis static std::string
check_allowed_platforms(const model::strings_set & allowed_platforms,const config::tree & user_config)140b0d29bc4SBrooks Davis check_allowed_platforms(const model::strings_set& allowed_platforms,
141b0d29bc4SBrooks Davis                         const config::tree& user_config)
142b0d29bc4SBrooks Davis {
143b0d29bc4SBrooks Davis     if (!allowed_platforms.empty()) {
144b0d29bc4SBrooks Davis         const std::string platform =
145b0d29bc4SBrooks Davis             user_config.lookup< config::string_node >("platform");
146b0d29bc4SBrooks Davis         if (allowed_platforms.find(platform) == allowed_platforms.end())
147b0d29bc4SBrooks Davis             return F("Current platform '%s' not supported") % platform;
148b0d29bc4SBrooks Davis     }
149b0d29bc4SBrooks Davis     return "";
150b0d29bc4SBrooks Davis }
151b0d29bc4SBrooks Davis 
152b0d29bc4SBrooks Davis 
153b0d29bc4SBrooks Davis /// Checks if the current user matches the required user.
154b0d29bc4SBrooks Davis ///
155b0d29bc4SBrooks Davis /// \param required_user Name of the required user category.
156b0d29bc4SBrooks Davis /// \param user_config Runtime user configuration.
157b0d29bc4SBrooks Davis ///
158b0d29bc4SBrooks Davis /// \return Empty if the current user fits the required user characteristics or
159b0d29bc4SBrooks Davis /// an error message otherwise.
160b0d29bc4SBrooks Davis static std::string
check_required_user(const std::string & required_user,const config::tree & user_config)161b0d29bc4SBrooks Davis check_required_user(const std::string& required_user,
162b0d29bc4SBrooks Davis                     const config::tree& user_config)
163b0d29bc4SBrooks Davis {
164b0d29bc4SBrooks Davis     if (!required_user.empty()) {
165b0d29bc4SBrooks Davis         const passwd::user user = passwd::current_user();
166b0d29bc4SBrooks Davis         if (required_user == "root") {
167b0d29bc4SBrooks Davis             if (!user.is_root())
168b0d29bc4SBrooks Davis                 return "Requires root privileges";
169b0d29bc4SBrooks Davis         } else if (required_user == "unprivileged") {
170b0d29bc4SBrooks Davis             if (user.is_root())
171b0d29bc4SBrooks Davis                 if (!user_config.is_set("unprivileged_user"))
172b0d29bc4SBrooks Davis                     return "Requires an unprivileged user but the "
173b0d29bc4SBrooks Davis                         "unprivileged-user configuration variable is not "
174b0d29bc4SBrooks Davis                         "defined";
175b0d29bc4SBrooks Davis         } else
176b0d29bc4SBrooks Davis             UNREACHABLE_MSG("Value of require.user not properly validated");
177b0d29bc4SBrooks Davis     }
178b0d29bc4SBrooks Davis     return "";
179b0d29bc4SBrooks Davis }
180b0d29bc4SBrooks Davis 
181b0d29bc4SBrooks Davis 
182b0d29bc4SBrooks Davis /// Checks if all required files exist.
183b0d29bc4SBrooks Davis ///
184b0d29bc4SBrooks Davis /// \param required_files Set of paths.
185b0d29bc4SBrooks Davis ///
186b0d29bc4SBrooks Davis /// \return Empty if the required files all exist or an error message otherwise.
187b0d29bc4SBrooks Davis static std::string
check_required_files(const model::paths_set & required_files)188b0d29bc4SBrooks Davis check_required_files(const model::paths_set& required_files)
189b0d29bc4SBrooks Davis {
190b0d29bc4SBrooks Davis     for (model::paths_set::const_iterator iter = required_files.begin();
191b0d29bc4SBrooks Davis          iter != required_files.end(); iter++) {
192b0d29bc4SBrooks Davis         INV((*iter).is_absolute());
193b0d29bc4SBrooks Davis         if (!fs::exists(*iter))
194b0d29bc4SBrooks Davis             return F("Required file '%s' not found") % *iter;
195b0d29bc4SBrooks Davis     }
196b0d29bc4SBrooks Davis     return "";
197b0d29bc4SBrooks Davis }
198b0d29bc4SBrooks Davis 
199b0d29bc4SBrooks Davis 
200b0d29bc4SBrooks Davis /// Checks if all required programs exist.
201b0d29bc4SBrooks Davis ///
202b0d29bc4SBrooks Davis /// \param required_programs Set of paths.
203b0d29bc4SBrooks Davis ///
204b0d29bc4SBrooks Davis /// \return Empty if the required programs all exist or an error message
205b0d29bc4SBrooks Davis /// otherwise.
206b0d29bc4SBrooks Davis static std::string
check_required_programs(const model::paths_set & required_programs)207b0d29bc4SBrooks Davis check_required_programs(const model::paths_set& required_programs)
208b0d29bc4SBrooks Davis {
209b0d29bc4SBrooks Davis     for (model::paths_set::const_iterator iter = required_programs.begin();
210b0d29bc4SBrooks Davis          iter != required_programs.end(); iter++) {
211b0d29bc4SBrooks Davis         if ((*iter).is_absolute()) {
212b0d29bc4SBrooks Davis             if (!fs::exists(*iter))
213b0d29bc4SBrooks Davis                 return F("Required program '%s' not found") % *iter;
214b0d29bc4SBrooks Davis         } else {
215b0d29bc4SBrooks Davis             if (!fs::find_in_path((*iter).c_str()))
216b0d29bc4SBrooks Davis                 return F("Required program '%s' not found in PATH") % *iter;
217b0d29bc4SBrooks Davis         }
218b0d29bc4SBrooks Davis     }
219b0d29bc4SBrooks Davis     return "";
220b0d29bc4SBrooks Davis }
221b0d29bc4SBrooks Davis 
222b0d29bc4SBrooks Davis 
223b0d29bc4SBrooks Davis /// Checks if the current system has the specified amount of memory.
224b0d29bc4SBrooks Davis ///
225b0d29bc4SBrooks Davis /// \param required_memory Amount of required physical memory, or zero if not
226b0d29bc4SBrooks Davis ///     applicable.
227b0d29bc4SBrooks Davis ///
228b0d29bc4SBrooks Davis /// \return Empty if the current system has the required amount of memory or an
229b0d29bc4SBrooks Davis /// error message otherwise.
230b0d29bc4SBrooks Davis static std::string
check_required_memory(const units::bytes & required_memory)231b0d29bc4SBrooks Davis check_required_memory(const units::bytes& required_memory)
232b0d29bc4SBrooks Davis {
233b0d29bc4SBrooks Davis     if (required_memory > 0) {
234b0d29bc4SBrooks Davis         const units::bytes physical_memory = utils::physical_memory();
235b0d29bc4SBrooks Davis         if (physical_memory > 0 && physical_memory < required_memory)
236b0d29bc4SBrooks Davis             return F("Requires %s bytes of physical memory but only %s "
237b0d29bc4SBrooks Davis                      "available") %
238b0d29bc4SBrooks Davis                 required_memory.format() % physical_memory.format();
239b0d29bc4SBrooks Davis     }
240b0d29bc4SBrooks Davis     return "";
241b0d29bc4SBrooks Davis }
242b0d29bc4SBrooks Davis 
243b0d29bc4SBrooks Davis 
244b0d29bc4SBrooks Davis /// Checks if the work directory's file system has enough free disk space.
245b0d29bc4SBrooks Davis ///
246b0d29bc4SBrooks Davis /// \param required_disk_space Amount of required free disk space, or zero if
247b0d29bc4SBrooks Davis ///     not applicable.
248b0d29bc4SBrooks Davis /// \param work_directory Path to where the test case will be run.
249b0d29bc4SBrooks Davis ///
250b0d29bc4SBrooks Davis /// \return Empty if the file system where the work directory is hosted has
251b0d29bc4SBrooks Davis /// enough free disk space or an error message otherwise.
252b0d29bc4SBrooks Davis static std::string
check_required_disk_space(const units::bytes & required_disk_space,const fs::path & work_directory)253b0d29bc4SBrooks Davis check_required_disk_space(const units::bytes& required_disk_space,
254b0d29bc4SBrooks Davis                           const fs::path& work_directory)
255b0d29bc4SBrooks Davis {
256b0d29bc4SBrooks Davis     if (required_disk_space > 0) {
257b0d29bc4SBrooks Davis         const units::bytes free_disk_space = fs::free_disk_space(
258b0d29bc4SBrooks Davis             work_directory);
259b0d29bc4SBrooks Davis         if (free_disk_space < required_disk_space)
260b0d29bc4SBrooks Davis             return F("Requires %s bytes of free disk space but only %s "
261b0d29bc4SBrooks Davis                      "available") %
262b0d29bc4SBrooks Davis                 required_disk_space.format() % free_disk_space.format();
263b0d29bc4SBrooks Davis     }
264b0d29bc4SBrooks Davis     return "";
265b0d29bc4SBrooks Davis }
266b0d29bc4SBrooks Davis 
267b0d29bc4SBrooks Davis 
268b0d29bc4SBrooks Davis }  // anonymous namespace
269b0d29bc4SBrooks Davis 
270b0d29bc4SBrooks Davis 
271b0d29bc4SBrooks Davis /// Checks if all the requirements specified by the test case are met.
272b0d29bc4SBrooks Davis ///
273b0d29bc4SBrooks Davis /// \param md The test metadata.
274b0d29bc4SBrooks Davis /// \param cfg The engine configuration.
275b0d29bc4SBrooks Davis /// \param test_suite Name of the test suite the test belongs to.
276b0d29bc4SBrooks Davis /// \param work_directory Path to where the test case will be run.
277b0d29bc4SBrooks Davis ///
278b0d29bc4SBrooks Davis /// \return A string describing the reason for skipping the test, or empty if
279b0d29bc4SBrooks Davis /// the test should be executed.
280b0d29bc4SBrooks Davis std::string
check_reqs(const model::metadata & md,const config::tree & cfg,const std::string & test_suite,const fs::path & work_directory)281b0d29bc4SBrooks Davis engine::check_reqs(const model::metadata& md, const config::tree& cfg,
282b0d29bc4SBrooks Davis                    const std::string& test_suite,
283b0d29bc4SBrooks Davis                    const fs::path& work_directory)
284b0d29bc4SBrooks Davis {
285b0d29bc4SBrooks Davis     std::string reason;
286b0d29bc4SBrooks Davis 
287b0d29bc4SBrooks Davis     reason = check_required_configs(md.required_configs(), cfg, test_suite);
288b0d29bc4SBrooks Davis     if (!reason.empty())
289b0d29bc4SBrooks Davis         return reason;
290b0d29bc4SBrooks Davis 
291b0d29bc4SBrooks Davis     reason = check_allowed_architectures(md.allowed_architectures(), cfg);
292b0d29bc4SBrooks Davis     if (!reason.empty())
293b0d29bc4SBrooks Davis         return reason;
294b0d29bc4SBrooks Davis 
295*257e70f1SIgor Ostapenko     reason = check_execenv(md.execenv(), cfg);
296*257e70f1SIgor Ostapenko     if (!reason.empty())
297*257e70f1SIgor Ostapenko         return reason;
298*257e70f1SIgor Ostapenko 
299b0d29bc4SBrooks Davis     reason = check_allowed_platforms(md.allowed_platforms(), cfg);
300b0d29bc4SBrooks Davis     if (!reason.empty())
301b0d29bc4SBrooks Davis         return reason;
302b0d29bc4SBrooks Davis 
303b0d29bc4SBrooks Davis     reason = check_required_user(md.required_user(), cfg);
304b0d29bc4SBrooks Davis     if (!reason.empty())
305b0d29bc4SBrooks Davis         return reason;
306b0d29bc4SBrooks Davis 
307b0d29bc4SBrooks Davis     reason = check_required_files(md.required_files());
308b0d29bc4SBrooks Davis     if (!reason.empty())
309b0d29bc4SBrooks Davis         return reason;
310b0d29bc4SBrooks Davis 
311b0d29bc4SBrooks Davis     reason = check_required_programs(md.required_programs());
312b0d29bc4SBrooks Davis     if (!reason.empty())
313b0d29bc4SBrooks Davis         return reason;
314b0d29bc4SBrooks Davis 
315b0d29bc4SBrooks Davis     reason = check_required_memory(md.required_memory());
316b0d29bc4SBrooks Davis     if (!reason.empty())
317b0d29bc4SBrooks Davis         return reason;
318b0d29bc4SBrooks Davis 
319b0d29bc4SBrooks Davis     reason = check_required_disk_space(md.required_disk_space(),
320b0d29bc4SBrooks Davis                                        work_directory);
321b0d29bc4SBrooks Davis     if (!reason.empty())
322b0d29bc4SBrooks Davis         return reason;
323b0d29bc4SBrooks Davis 
324b0d29bc4SBrooks Davis     INV(reason.empty());
325b0d29bc4SBrooks Davis     return reason;
326b0d29bc4SBrooks Davis }
327