xref: /freebsd-src/contrib/kyua/engine/atf.cpp (revision 47fb5d2b13c5ca8e1a3b05ed223d62ea4b43b3e8)
1b0d29bc4SBrooks Davis // Copyright 2014 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/atf.hpp"
30b0d29bc4SBrooks Davis 
31b0d29bc4SBrooks Davis extern "C" {
32b0d29bc4SBrooks Davis #include <unistd.h>
33b0d29bc4SBrooks Davis }
34b0d29bc4SBrooks Davis 
35b0d29bc4SBrooks Davis #include <cerrno>
36b0d29bc4SBrooks Davis #include <cstdlib>
37b0d29bc4SBrooks Davis #include <fstream>
38b0d29bc4SBrooks Davis 
39b0d29bc4SBrooks Davis #include "engine/atf_list.hpp"
40b0d29bc4SBrooks Davis #include "engine/atf_result.hpp"
41b0d29bc4SBrooks Davis #include "engine/exceptions.hpp"
42257e70f1SIgor Ostapenko #include "engine/execenv/execenv.hpp"
43b0d29bc4SBrooks Davis #include "model/test_case.hpp"
44b0d29bc4SBrooks Davis #include "model/test_program.hpp"
45b0d29bc4SBrooks Davis #include "model/test_result.hpp"
46b0d29bc4SBrooks Davis #include "utils/defs.hpp"
47b0d29bc4SBrooks Davis #include "utils/env.hpp"
48b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
49b0d29bc4SBrooks Davis #include "utils/fs/path.hpp"
50b0d29bc4SBrooks Davis #include "utils/logging/macros.hpp"
51b0d29bc4SBrooks Davis #include "utils/optional.ipp"
52b0d29bc4SBrooks Davis #include "utils/process/exceptions.hpp"
53b0d29bc4SBrooks Davis #include "utils/process/operations.hpp"
54b0d29bc4SBrooks Davis #include "utils/process/status.hpp"
55b0d29bc4SBrooks Davis #include "utils/stream.hpp"
56b0d29bc4SBrooks Davis 
57b0d29bc4SBrooks Davis namespace config = utils::config;
58257e70f1SIgor Ostapenko namespace execenv = engine::execenv;
59b0d29bc4SBrooks Davis namespace fs = utils::fs;
60b0d29bc4SBrooks Davis namespace process = utils::process;
61b0d29bc4SBrooks Davis 
62b0d29bc4SBrooks Davis using utils::optional;
63b0d29bc4SBrooks Davis 
64b0d29bc4SBrooks Davis 
65b0d29bc4SBrooks Davis namespace {
66b0d29bc4SBrooks Davis 
67b0d29bc4SBrooks Davis 
68b0d29bc4SBrooks Davis /// Basename of the file containing the result written by the ATF test case.
69b0d29bc4SBrooks Davis static const char* result_name = "result.atf";
70b0d29bc4SBrooks Davis 
71b0d29bc4SBrooks Davis 
72b0d29bc4SBrooks Davis /// Magic numbers returned by exec_list when exec(2) fails.
73b0d29bc4SBrooks Davis enum list_exit_code {
74b0d29bc4SBrooks Davis     exit_eacces = 90,
75b0d29bc4SBrooks Davis     exit_enoent,
76b0d29bc4SBrooks Davis     exit_enoexec,
77b0d29bc4SBrooks Davis };
78b0d29bc4SBrooks Davis 
79b0d29bc4SBrooks Davis 
80b0d29bc4SBrooks Davis }  // anonymous namespace
81b0d29bc4SBrooks Davis 
82b0d29bc4SBrooks Davis 
83b0d29bc4SBrooks Davis /// Executes a test program's list operation.
84b0d29bc4SBrooks Davis ///
85b0d29bc4SBrooks Davis /// This method is intended to be called within a subprocess and is expected
86b0d29bc4SBrooks Davis /// to terminate execution either by exec(2)ing the test program or by
87b0d29bc4SBrooks Davis /// exiting with a failure.
88b0d29bc4SBrooks Davis ///
89b0d29bc4SBrooks Davis /// \param test_program The test program to execute.
90b0d29bc4SBrooks Davis /// \param vars User-provided variables to pass to the test program.
91b0d29bc4SBrooks Davis void
92b0d29bc4SBrooks Davis engine::atf_interface::exec_list(const model::test_program& test_program,
93b0d29bc4SBrooks Davis                                  const config::properties_map& vars) const
94b0d29bc4SBrooks Davis {
95b0d29bc4SBrooks Davis     utils::setenv("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value");
96b0d29bc4SBrooks Davis 
97b0d29bc4SBrooks Davis     process::args_vector args;
98b0d29bc4SBrooks Davis     for (config::properties_map::const_iterator iter = vars.begin();
99b0d29bc4SBrooks Davis          iter != vars.end(); ++iter) {
100b0d29bc4SBrooks Davis         args.push_back(F("-v%s=%s") % (*iter).first % (*iter).second);
101b0d29bc4SBrooks Davis     }
102b0d29bc4SBrooks Davis 
103b0d29bc4SBrooks Davis     args.push_back("-l");
104b0d29bc4SBrooks Davis     try {
105b0d29bc4SBrooks Davis         process::exec_unsafe(test_program.absolute_path(), args);
106b0d29bc4SBrooks Davis     } catch (const process::system_error& e) {
107b0d29bc4SBrooks Davis         if (e.original_errno() == EACCES)
108b0d29bc4SBrooks Davis             ::_exit(exit_eacces);
109b0d29bc4SBrooks Davis         else if (e.original_errno() == ENOENT)
110b0d29bc4SBrooks Davis             ::_exit(exit_enoent);
111b0d29bc4SBrooks Davis         else if (e.original_errno() == ENOEXEC)
112b0d29bc4SBrooks Davis             ::_exit(exit_enoexec);
113b0d29bc4SBrooks Davis         throw;
114b0d29bc4SBrooks Davis     }
115b0d29bc4SBrooks Davis }
116b0d29bc4SBrooks Davis 
117b0d29bc4SBrooks Davis 
118b0d29bc4SBrooks Davis /// Computes the test cases list of a test program.
119b0d29bc4SBrooks Davis ///
120b0d29bc4SBrooks Davis /// \param status The termination status of the subprocess used to execute
121b0d29bc4SBrooks Davis ///     the exec_test() method or none if the test timed out.
122b0d29bc4SBrooks Davis /// \param stdout_path Path to the file containing the stdout of the test.
123b0d29bc4SBrooks Davis /// \param stderr_path Path to the file containing the stderr of the test.
124b0d29bc4SBrooks Davis ///
125b0d29bc4SBrooks Davis /// \return A list of test cases.
126b0d29bc4SBrooks Davis ///
127b0d29bc4SBrooks Davis /// \throw error If there is a problem parsing the test case list.
128b0d29bc4SBrooks Davis model::test_cases_map
129b0d29bc4SBrooks Davis engine::atf_interface::parse_list(const optional< process::status >& status,
130b0d29bc4SBrooks Davis                                   const fs::path& stdout_path,
131b0d29bc4SBrooks Davis                                   const fs::path& stderr_path) const
132b0d29bc4SBrooks Davis {
133b0d29bc4SBrooks Davis     const std::string stderr_contents = utils::read_file(stderr_path);
134b0d29bc4SBrooks Davis     if (!stderr_contents.empty())
135b0d29bc4SBrooks Davis         LW("Test case list wrote to stderr: " + stderr_contents);
136b0d29bc4SBrooks Davis 
137b0d29bc4SBrooks Davis     if (!status)
138b0d29bc4SBrooks Davis         throw engine::error("Test case list timed out");
139b0d29bc4SBrooks Davis     if (status.get().exited()) {
140b0d29bc4SBrooks Davis         const int exitstatus = status.get().exitstatus();
141b0d29bc4SBrooks Davis         if (exitstatus == EXIT_SUCCESS) {
142b0d29bc4SBrooks Davis             // Nothing to do; fall through.
143b0d29bc4SBrooks Davis         } else if (exitstatus == exit_eacces) {
144b0d29bc4SBrooks Davis             throw engine::error("Permission denied to run test program");
145b0d29bc4SBrooks Davis         } else if (exitstatus == exit_enoent) {
146b0d29bc4SBrooks Davis             throw engine::error("Cannot find test program");
147b0d29bc4SBrooks Davis         } else if (exitstatus == exit_enoexec) {
148b0d29bc4SBrooks Davis             throw engine::error("Invalid test program format");
149b0d29bc4SBrooks Davis         } else {
150b0d29bc4SBrooks Davis             throw engine::error("Test program did not exit cleanly");
151b0d29bc4SBrooks Davis         }
152b0d29bc4SBrooks Davis     } else {
153b0d29bc4SBrooks Davis         throw engine::error("Test program received signal");
154b0d29bc4SBrooks Davis     }
155b0d29bc4SBrooks Davis 
156b0d29bc4SBrooks Davis     std::ifstream input(stdout_path.c_str());
157b0d29bc4SBrooks Davis     if (!input)
158b0d29bc4SBrooks Davis         throw engine::load_error(stdout_path, "Cannot open file for read");
159b0d29bc4SBrooks Davis     const model::test_cases_map test_cases = parse_atf_list(input);
160b0d29bc4SBrooks Davis 
161b0d29bc4SBrooks Davis     if (!stderr_contents.empty())
162b0d29bc4SBrooks Davis         throw engine::error("Test case list wrote to stderr");
163b0d29bc4SBrooks Davis 
164b0d29bc4SBrooks Davis     return test_cases;
165b0d29bc4SBrooks Davis }
166b0d29bc4SBrooks Davis 
167b0d29bc4SBrooks Davis 
168b0d29bc4SBrooks Davis /// Executes a test case of the test program.
169b0d29bc4SBrooks Davis ///
170b0d29bc4SBrooks Davis /// This method is intended to be called within a subprocess and is expected
171b0d29bc4SBrooks Davis /// to terminate execution either by exec(2)ing the test program or by
172b0d29bc4SBrooks Davis /// exiting with a failure.
173b0d29bc4SBrooks Davis ///
174b0d29bc4SBrooks Davis /// \param test_program The test program to execute.
175b0d29bc4SBrooks Davis /// \param test_case_name Name of the test case to invoke.
176b0d29bc4SBrooks Davis /// \param vars User-provided variables to pass to the test program.
177b0d29bc4SBrooks Davis /// \param control_directory Directory where the interface may place control
178b0d29bc4SBrooks Davis ///     files.
179b0d29bc4SBrooks Davis void
180b0d29bc4SBrooks Davis engine::atf_interface::exec_test(const model::test_program& test_program,
181b0d29bc4SBrooks Davis                                  const std::string& test_case_name,
182b0d29bc4SBrooks Davis                                  const config::properties_map& vars,
183b0d29bc4SBrooks Davis                                  const fs::path& control_directory) const
184b0d29bc4SBrooks Davis {
185b0d29bc4SBrooks Davis     utils::setenv("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value");
186b0d29bc4SBrooks Davis 
187b0d29bc4SBrooks Davis     process::args_vector args;
188b0d29bc4SBrooks Davis     for (config::properties_map::const_iterator iter = vars.begin();
189b0d29bc4SBrooks Davis          iter != vars.end(); ++iter) {
190b0d29bc4SBrooks Davis         args.push_back(F("-v%s=%s") % (*iter).first % (*iter).second);
191b0d29bc4SBrooks Davis     }
192b0d29bc4SBrooks Davis 
193b0d29bc4SBrooks Davis     args.push_back(F("-r%s") % (control_directory / result_name));
194b0d29bc4SBrooks Davis     args.push_back(test_case_name);
195257e70f1SIgor Ostapenko 
196257e70f1SIgor Ostapenko     auto e = execenv::get(test_program, test_case_name);
197257e70f1SIgor Ostapenko     e->init();
198257e70f1SIgor Ostapenko     e->exec(args);
199*47fb5d2bSBrooks Davis     __builtin_unreachable();
200b0d29bc4SBrooks Davis }
201b0d29bc4SBrooks Davis 
202b0d29bc4SBrooks Davis 
203b0d29bc4SBrooks Davis /// Executes a test cleanup routine of the test program.
204b0d29bc4SBrooks Davis ///
205b0d29bc4SBrooks Davis /// This method is intended to be called within a subprocess and is expected
206b0d29bc4SBrooks Davis /// to terminate execution either by exec(2)ing the test program or by
207b0d29bc4SBrooks Davis /// exiting with a failure.
208b0d29bc4SBrooks Davis ///
209b0d29bc4SBrooks Davis /// \param test_program The test program to execute.
210b0d29bc4SBrooks Davis /// \param test_case_name Name of the test case to invoke.
211b0d29bc4SBrooks Davis /// \param vars User-provided variables to pass to the test program.
212b0d29bc4SBrooks Davis void
213b0d29bc4SBrooks Davis engine::atf_interface::exec_cleanup(
214b0d29bc4SBrooks Davis     const model::test_program& test_program,
215b0d29bc4SBrooks Davis     const std::string& test_case_name,
216b0d29bc4SBrooks Davis     const config::properties_map& vars,
217b0d29bc4SBrooks Davis     const fs::path& /* control_directory */) const
218b0d29bc4SBrooks Davis {
219b0d29bc4SBrooks Davis     utils::setenv("__RUNNING_INSIDE_ATF_RUN", "internal-yes-value");
220b0d29bc4SBrooks Davis 
221b0d29bc4SBrooks Davis     process::args_vector args;
222b0d29bc4SBrooks Davis     for (config::properties_map::const_iterator iter = vars.begin();
223b0d29bc4SBrooks Davis          iter != vars.end(); ++iter) {
224b0d29bc4SBrooks Davis         args.push_back(F("-v%s=%s") % (*iter).first % (*iter).second);
225b0d29bc4SBrooks Davis     }
226b0d29bc4SBrooks Davis 
227b0d29bc4SBrooks Davis     args.push_back(F("%s:cleanup") % test_case_name);
228257e70f1SIgor Ostapenko 
229257e70f1SIgor Ostapenko     auto e = execenv::get(test_program, test_case_name);
230257e70f1SIgor Ostapenko     e->exec(args);
231*47fb5d2bSBrooks Davis     __builtin_unreachable();
232b0d29bc4SBrooks Davis }
233b0d29bc4SBrooks Davis 
234b0d29bc4SBrooks Davis 
235b0d29bc4SBrooks Davis /// Computes the result of a test case based on its termination status.
236b0d29bc4SBrooks Davis ///
237b0d29bc4SBrooks Davis /// \param status The termination status of the subprocess used to execute
238b0d29bc4SBrooks Davis ///     the exec_test() method or none if the test timed out.
239b0d29bc4SBrooks Davis /// \param control_directory Directory where the interface may have placed
240b0d29bc4SBrooks Davis ///     control files.
241b0d29bc4SBrooks Davis ///
242b0d29bc4SBrooks Davis /// \return A test result.
243b0d29bc4SBrooks Davis model::test_result
244b0d29bc4SBrooks Davis engine::atf_interface::compute_result(
245b0d29bc4SBrooks Davis     const optional< process::status >& status,
246b0d29bc4SBrooks Davis     const fs::path& control_directory,
247b0d29bc4SBrooks Davis     const fs::path& /* stdout_path */,
248b0d29bc4SBrooks Davis     const fs::path& /* stderr_path */) const
249b0d29bc4SBrooks Davis {
250b0d29bc4SBrooks Davis     return calculate_atf_result(status, control_directory / result_name);
251b0d29bc4SBrooks Davis }
252