xref: /freebsd/contrib/kyua/engine/scheduler.cpp (revision b0d29bc47dba79f6f38e67eabadfb4b32ffd9390)
1*b0d29bc4SBrooks Davis // Copyright 2014 The Kyua Authors.
2*b0d29bc4SBrooks Davis // All rights reserved.
3*b0d29bc4SBrooks Davis //
4*b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without
5*b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are
6*b0d29bc4SBrooks Davis // met:
7*b0d29bc4SBrooks Davis //
8*b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright
9*b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer.
10*b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright
11*b0d29bc4SBrooks Davis //   notice, this list of conditions and the following disclaimer in the
12*b0d29bc4SBrooks Davis //   documentation and/or other materials provided with the distribution.
13*b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors
14*b0d29bc4SBrooks Davis //   may be used to endorse or promote products derived from this software
15*b0d29bc4SBrooks Davis //   without specific prior written permission.
16*b0d29bc4SBrooks Davis //
17*b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20*b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21*b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22*b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23*b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24*b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25*b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26*b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27*b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28*b0d29bc4SBrooks Davis 
29*b0d29bc4SBrooks Davis #include "engine/scheduler.hpp"
30*b0d29bc4SBrooks Davis 
31*b0d29bc4SBrooks Davis extern "C" {
32*b0d29bc4SBrooks Davis #include <unistd.h>
33*b0d29bc4SBrooks Davis }
34*b0d29bc4SBrooks Davis 
35*b0d29bc4SBrooks Davis #include <cstdio>
36*b0d29bc4SBrooks Davis #include <cstdlib>
37*b0d29bc4SBrooks Davis #include <fstream>
38*b0d29bc4SBrooks Davis #include <memory>
39*b0d29bc4SBrooks Davis #include <stdexcept>
40*b0d29bc4SBrooks Davis 
41*b0d29bc4SBrooks Davis #include "engine/config.hpp"
42*b0d29bc4SBrooks Davis #include "engine/exceptions.hpp"
43*b0d29bc4SBrooks Davis #include "engine/requirements.hpp"
44*b0d29bc4SBrooks Davis #include "model/context.hpp"
45*b0d29bc4SBrooks Davis #include "model/metadata.hpp"
46*b0d29bc4SBrooks Davis #include "model/test_case.hpp"
47*b0d29bc4SBrooks Davis #include "model/test_program.hpp"
48*b0d29bc4SBrooks Davis #include "model/test_result.hpp"
49*b0d29bc4SBrooks Davis #include "utils/config/tree.ipp"
50*b0d29bc4SBrooks Davis #include "utils/datetime.hpp"
51*b0d29bc4SBrooks Davis #include "utils/defs.hpp"
52*b0d29bc4SBrooks Davis #include "utils/env.hpp"
53*b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
54*b0d29bc4SBrooks Davis #include "utils/fs/directory.hpp"
55*b0d29bc4SBrooks Davis #include "utils/fs/exceptions.hpp"
56*b0d29bc4SBrooks Davis #include "utils/fs/operations.hpp"
57*b0d29bc4SBrooks Davis #include "utils/fs/path.hpp"
58*b0d29bc4SBrooks Davis #include "utils/logging/macros.hpp"
59*b0d29bc4SBrooks Davis #include "utils/noncopyable.hpp"
60*b0d29bc4SBrooks Davis #include "utils/optional.ipp"
61*b0d29bc4SBrooks Davis #include "utils/passwd.hpp"
62*b0d29bc4SBrooks Davis #include "utils/process/executor.ipp"
63*b0d29bc4SBrooks Davis #include "utils/process/status.hpp"
64*b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
65*b0d29bc4SBrooks Davis #include "utils/stacktrace.hpp"
66*b0d29bc4SBrooks Davis #include "utils/stream.hpp"
67*b0d29bc4SBrooks Davis #include "utils/text/operations.ipp"
68*b0d29bc4SBrooks Davis 
69*b0d29bc4SBrooks Davis namespace config = utils::config;
70*b0d29bc4SBrooks Davis namespace datetime = utils::datetime;
71*b0d29bc4SBrooks Davis namespace executor = utils::process::executor;
72*b0d29bc4SBrooks Davis namespace fs = utils::fs;
73*b0d29bc4SBrooks Davis namespace logging = utils::logging;
74*b0d29bc4SBrooks Davis namespace passwd = utils::passwd;
75*b0d29bc4SBrooks Davis namespace process = utils::process;
76*b0d29bc4SBrooks Davis namespace scheduler = engine::scheduler;
77*b0d29bc4SBrooks Davis namespace text = utils::text;
78*b0d29bc4SBrooks Davis 
79*b0d29bc4SBrooks Davis using utils::none;
80*b0d29bc4SBrooks Davis using utils::optional;
81*b0d29bc4SBrooks Davis 
82*b0d29bc4SBrooks Davis 
83*b0d29bc4SBrooks Davis /// Timeout for the test case cleanup operation.
84*b0d29bc4SBrooks Davis ///
85*b0d29bc4SBrooks Davis /// TODO(jmmv): This is here only for testing purposes.  Maybe we should expose
86*b0d29bc4SBrooks Davis /// this setting as part of the user_config.
87*b0d29bc4SBrooks Davis datetime::delta scheduler::cleanup_timeout(60, 0);
88*b0d29bc4SBrooks Davis 
89*b0d29bc4SBrooks Davis 
90*b0d29bc4SBrooks Davis /// Timeout for the test case listing operation.
91*b0d29bc4SBrooks Davis ///
92*b0d29bc4SBrooks Davis /// TODO(jmmv): This is here only for testing purposes.  Maybe we should expose
93*b0d29bc4SBrooks Davis /// this setting as part of the user_config.
94*b0d29bc4SBrooks Davis datetime::delta scheduler::list_timeout(300, 0);
95*b0d29bc4SBrooks Davis 
96*b0d29bc4SBrooks Davis 
97*b0d29bc4SBrooks Davis namespace {
98*b0d29bc4SBrooks Davis 
99*b0d29bc4SBrooks Davis 
100*b0d29bc4SBrooks Davis /// Magic exit status to indicate that the test case was probably skipped.
101*b0d29bc4SBrooks Davis ///
102*b0d29bc4SBrooks Davis /// The test case was only skipped if and only if we return this exit code and
103*b0d29bc4SBrooks Davis /// we find the skipped_cookie file on disk.
104*b0d29bc4SBrooks Davis static const int exit_skipped = 84;
105*b0d29bc4SBrooks Davis 
106*b0d29bc4SBrooks Davis 
107*b0d29bc4SBrooks Davis /// Text file containing the skip reason for the test case.
108*b0d29bc4SBrooks Davis ///
109*b0d29bc4SBrooks Davis /// This will only be present within unique_work_directory if the test case
110*b0d29bc4SBrooks Davis /// exited with the exit_skipped code.  However, there is no guarantee that the
111*b0d29bc4SBrooks Davis /// file is there (say if the test really decided to exit with code exit_skipped
112*b0d29bc4SBrooks Davis /// on its own).
113*b0d29bc4SBrooks Davis static const char* skipped_cookie = "skipped.txt";
114*b0d29bc4SBrooks Davis 
115*b0d29bc4SBrooks Davis 
116*b0d29bc4SBrooks Davis /// Mapping of interface names to interface definitions.
117*b0d29bc4SBrooks Davis typedef std::map< std::string, std::shared_ptr< scheduler::interface > >
118*b0d29bc4SBrooks Davis     interfaces_map;
119*b0d29bc4SBrooks Davis 
120*b0d29bc4SBrooks Davis 
121*b0d29bc4SBrooks Davis /// Mapping of interface names to interface definitions.
122*b0d29bc4SBrooks Davis ///
123*b0d29bc4SBrooks Davis /// Use register_interface() to add an entry to this global table.
124*b0d29bc4SBrooks Davis static interfaces_map interfaces;
125*b0d29bc4SBrooks Davis 
126*b0d29bc4SBrooks Davis 
127*b0d29bc4SBrooks Davis /// Scans the contents of a directory and appends the file listing to a file.
128*b0d29bc4SBrooks Davis ///
129*b0d29bc4SBrooks Davis /// \param dir_path The directory to scan.
130*b0d29bc4SBrooks Davis /// \param output_file The file to which to append the listing.
131*b0d29bc4SBrooks Davis ///
132*b0d29bc4SBrooks Davis /// \throw engine::error If there are problems listing the files.
133*b0d29bc4SBrooks Davis static void
134*b0d29bc4SBrooks Davis append_files_listing(const fs::path& dir_path, const fs::path& output_file)
135*b0d29bc4SBrooks Davis {
136*b0d29bc4SBrooks Davis     std::ofstream output(output_file.c_str(), std::ios::app);
137*b0d29bc4SBrooks Davis     if (!output)
138*b0d29bc4SBrooks Davis         throw engine::error(F("Failed to open output file %s for append")
139*b0d29bc4SBrooks Davis                             % output_file);
140*b0d29bc4SBrooks Davis     try {
141*b0d29bc4SBrooks Davis         std::set < std::string > names;
142*b0d29bc4SBrooks Davis 
143*b0d29bc4SBrooks Davis         const fs::directory dir(dir_path);
144*b0d29bc4SBrooks Davis         for (fs::directory::const_iterator iter = dir.begin();
145*b0d29bc4SBrooks Davis              iter != dir.end(); ++iter) {
146*b0d29bc4SBrooks Davis             if (iter->name != "." && iter->name != "..")
147*b0d29bc4SBrooks Davis                 names.insert(iter->name);
148*b0d29bc4SBrooks Davis         }
149*b0d29bc4SBrooks Davis 
150*b0d29bc4SBrooks Davis         if (!names.empty()) {
151*b0d29bc4SBrooks Davis             output << "Files left in work directory after failure: "
152*b0d29bc4SBrooks Davis                    << text::join(names, ", ") << '\n';
153*b0d29bc4SBrooks Davis         }
154*b0d29bc4SBrooks Davis     } catch (const fs::error& e) {
155*b0d29bc4SBrooks Davis         throw engine::error(F("Cannot append files listing to %s: %s")
156*b0d29bc4SBrooks Davis                             % output_file % e.what());
157*b0d29bc4SBrooks Davis     }
158*b0d29bc4SBrooks Davis }
159*b0d29bc4SBrooks Davis 
160*b0d29bc4SBrooks Davis 
161*b0d29bc4SBrooks Davis /// Maintenance data held while a test is being executed.
162*b0d29bc4SBrooks Davis ///
163*b0d29bc4SBrooks Davis /// This data structure exists from the moment when a test is executed via
164*b0d29bc4SBrooks Davis /// scheduler::spawn_test() or scheduler::impl::spawn_cleanup() to when it is
165*b0d29bc4SBrooks Davis /// cleaned up with result_handle::cleanup().
166*b0d29bc4SBrooks Davis ///
167*b0d29bc4SBrooks Davis /// This is a base data type intended to be extended for the test and cleanup
168*b0d29bc4SBrooks Davis /// cases so that each contains only the relevant data.
169*b0d29bc4SBrooks Davis struct exec_data : utils::noncopyable {
170*b0d29bc4SBrooks Davis     /// Test program data for this test case.
171*b0d29bc4SBrooks Davis     const model::test_program_ptr test_program;
172*b0d29bc4SBrooks Davis 
173*b0d29bc4SBrooks Davis     /// Name of the test case.
174*b0d29bc4SBrooks Davis     const std::string test_case_name;
175*b0d29bc4SBrooks Davis 
176*b0d29bc4SBrooks Davis     /// Constructor.
177*b0d29bc4SBrooks Davis     ///
178*b0d29bc4SBrooks Davis     /// \param test_program_ Test program data for this test case.
179*b0d29bc4SBrooks Davis     /// \param test_case_name_ Name of the test case.
180*b0d29bc4SBrooks Davis     exec_data(const model::test_program_ptr test_program_,
181*b0d29bc4SBrooks Davis               const std::string& test_case_name_) :
182*b0d29bc4SBrooks Davis         test_program(test_program_), test_case_name(test_case_name_)
183*b0d29bc4SBrooks Davis     {
184*b0d29bc4SBrooks Davis     }
185*b0d29bc4SBrooks Davis 
186*b0d29bc4SBrooks Davis     /// Destructor.
187*b0d29bc4SBrooks Davis     virtual ~exec_data(void)
188*b0d29bc4SBrooks Davis     {
189*b0d29bc4SBrooks Davis     }
190*b0d29bc4SBrooks Davis };
191*b0d29bc4SBrooks Davis 
192*b0d29bc4SBrooks Davis 
193*b0d29bc4SBrooks Davis /// Maintenance data held while a test is being executed.
194*b0d29bc4SBrooks Davis struct test_exec_data : public exec_data {
195*b0d29bc4SBrooks Davis     /// Test program-specific execution interface.
196*b0d29bc4SBrooks Davis     const std::shared_ptr< scheduler::interface > interface;
197*b0d29bc4SBrooks Davis 
198*b0d29bc4SBrooks Davis     /// User configuration passed to the execution of the test.  We need this
199*b0d29bc4SBrooks Davis     /// here to recover it later when chaining the execution of a cleanup
200*b0d29bc4SBrooks Davis     /// routine (if any).
201*b0d29bc4SBrooks Davis     const config::tree user_config;
202*b0d29bc4SBrooks Davis 
203*b0d29bc4SBrooks Davis     /// Whether this test case still needs to have its cleanup routine executed.
204*b0d29bc4SBrooks Davis     ///
205*b0d29bc4SBrooks Davis     /// This is set externally when the cleanup routine is actually invoked to
206*b0d29bc4SBrooks Davis     /// denote that no further attempts shall be made at cleaning this up.
207*b0d29bc4SBrooks Davis     bool needs_cleanup;
208*b0d29bc4SBrooks Davis 
209*b0d29bc4SBrooks Davis     /// The exit_handle for this test once it has completed.
210*b0d29bc4SBrooks Davis     ///
211*b0d29bc4SBrooks Davis     /// This is set externally when the test case has finished, as we need this
212*b0d29bc4SBrooks Davis     /// information to invoke the followup cleanup routine in the right context,
213*b0d29bc4SBrooks Davis     /// as indicated by needs_cleanup.
214*b0d29bc4SBrooks Davis     optional< executor::exit_handle > exit_handle;
215*b0d29bc4SBrooks Davis 
216*b0d29bc4SBrooks Davis     /// Constructor.
217*b0d29bc4SBrooks Davis     ///
218*b0d29bc4SBrooks Davis     /// \param test_program_ Test program data for this test case.
219*b0d29bc4SBrooks Davis     /// \param test_case_name_ Name of the test case.
220*b0d29bc4SBrooks Davis     /// \param interface_ Test program-specific execution interface.
221*b0d29bc4SBrooks Davis     /// \param user_config_ User configuration passed to the test.
222*b0d29bc4SBrooks Davis     test_exec_data(const model::test_program_ptr test_program_,
223*b0d29bc4SBrooks Davis                    const std::string& test_case_name_,
224*b0d29bc4SBrooks Davis                    const std::shared_ptr< scheduler::interface > interface_,
225*b0d29bc4SBrooks Davis                    const config::tree& user_config_) :
226*b0d29bc4SBrooks Davis         exec_data(test_program_, test_case_name_),
227*b0d29bc4SBrooks Davis         interface(interface_), user_config(user_config_)
228*b0d29bc4SBrooks Davis     {
229*b0d29bc4SBrooks Davis         const model::test_case& test_case = test_program->find(test_case_name);
230*b0d29bc4SBrooks Davis         needs_cleanup = test_case.get_metadata().has_cleanup();
231*b0d29bc4SBrooks Davis     }
232*b0d29bc4SBrooks Davis };
233*b0d29bc4SBrooks Davis 
234*b0d29bc4SBrooks Davis 
235*b0d29bc4SBrooks Davis /// Maintenance data held while a test cleanup routine is being executed.
236*b0d29bc4SBrooks Davis ///
237*b0d29bc4SBrooks Davis /// Instances of this object are related to a previous test_exec_data, as
238*b0d29bc4SBrooks Davis /// cleanup routines can only exist once the test has been run.
239*b0d29bc4SBrooks Davis struct cleanup_exec_data : public exec_data {
240*b0d29bc4SBrooks Davis     /// The exit handle of the test.  This is necessary so that we can return
241*b0d29bc4SBrooks Davis     /// the correct exit_handle to the user of the scheduler.
242*b0d29bc4SBrooks Davis     executor::exit_handle body_exit_handle;
243*b0d29bc4SBrooks Davis 
244*b0d29bc4SBrooks Davis     /// The final result of the test's body.  This is necessary to compute the
245*b0d29bc4SBrooks Davis     /// right return value for a test with a cleanup routine: the body result is
246*b0d29bc4SBrooks Davis     /// respected if it is a "bad" result; else the result of the cleanup
247*b0d29bc4SBrooks Davis     /// routine is used if it has failed.
248*b0d29bc4SBrooks Davis     model::test_result body_result;
249*b0d29bc4SBrooks Davis 
250*b0d29bc4SBrooks Davis     /// Constructor.
251*b0d29bc4SBrooks Davis     ///
252*b0d29bc4SBrooks Davis     /// \param test_program_ Test program data for this test case.
253*b0d29bc4SBrooks Davis     /// \param test_case_name_ Name of the test case.
254*b0d29bc4SBrooks Davis     /// \param body_exit_handle_ If not none, exit handle of the body
255*b0d29bc4SBrooks Davis     ///     corresponding to the cleanup routine represented by this exec_data.
256*b0d29bc4SBrooks Davis     /// \param body_result_ If not none, result of the body corresponding to the
257*b0d29bc4SBrooks Davis     ///     cleanup routine represented by this exec_data.
258*b0d29bc4SBrooks Davis     cleanup_exec_data(const model::test_program_ptr test_program_,
259*b0d29bc4SBrooks Davis                       const std::string& test_case_name_,
260*b0d29bc4SBrooks Davis                       const executor::exit_handle& body_exit_handle_,
261*b0d29bc4SBrooks Davis                       const model::test_result& body_result_) :
262*b0d29bc4SBrooks Davis         exec_data(test_program_, test_case_name_),
263*b0d29bc4SBrooks Davis         body_exit_handle(body_exit_handle_), body_result(body_result_)
264*b0d29bc4SBrooks Davis     {
265*b0d29bc4SBrooks Davis     }
266*b0d29bc4SBrooks Davis };
267*b0d29bc4SBrooks Davis 
268*b0d29bc4SBrooks Davis 
269*b0d29bc4SBrooks Davis /// Shared pointer to exec_data.
270*b0d29bc4SBrooks Davis ///
271*b0d29bc4SBrooks Davis /// We require this because we want exec_data to not be copyable, and thus we
272*b0d29bc4SBrooks Davis /// cannot just store it in the map without move constructors.
273*b0d29bc4SBrooks Davis typedef std::shared_ptr< exec_data > exec_data_ptr;
274*b0d29bc4SBrooks Davis 
275*b0d29bc4SBrooks Davis 
276*b0d29bc4SBrooks Davis /// Mapping of active PIDs to their maintenance data.
277*b0d29bc4SBrooks Davis typedef std::map< int, exec_data_ptr > exec_data_map;
278*b0d29bc4SBrooks Davis 
279*b0d29bc4SBrooks Davis 
280*b0d29bc4SBrooks Davis /// Enforces a test program to hold an absolute path.
281*b0d29bc4SBrooks Davis ///
282*b0d29bc4SBrooks Davis /// TODO(jmmv): This function (which is a pretty ugly hack) exists because we
283*b0d29bc4SBrooks Davis /// want the interface hooks to receive a test_program as their argument.
284*b0d29bc4SBrooks Davis /// However, those hooks run after the test program has been isolated, which
285*b0d29bc4SBrooks Davis /// means that the current directory has changed since when the test_program
286*b0d29bc4SBrooks Davis /// objects were created.  This causes the absolute_path() method of
287*b0d29bc4SBrooks Davis /// test_program to return bogus values if the internal representation of their
288*b0d29bc4SBrooks Davis /// path is relative.  We should fix somehow: maybe making the fs module grab
289*b0d29bc4SBrooks Davis /// its "current_path" view at program startup time; or maybe by grabbing the
290*b0d29bc4SBrooks Davis /// current path at test_program creation time; or maybe something else.
291*b0d29bc4SBrooks Davis ///
292*b0d29bc4SBrooks Davis /// \param program The test program to modify.
293*b0d29bc4SBrooks Davis ///
294*b0d29bc4SBrooks Davis /// \return A new test program whose internal paths are absolute.
295*b0d29bc4SBrooks Davis static model::test_program
296*b0d29bc4SBrooks Davis force_absolute_paths(const model::test_program program)
297*b0d29bc4SBrooks Davis {
298*b0d29bc4SBrooks Davis     const std::string& relative = program.relative_path().str();
299*b0d29bc4SBrooks Davis     const std::string absolute = program.absolute_path().str();
300*b0d29bc4SBrooks Davis 
301*b0d29bc4SBrooks Davis     const std::string root = absolute.substr(
302*b0d29bc4SBrooks Davis         0, absolute.length() - relative.length());
303*b0d29bc4SBrooks Davis 
304*b0d29bc4SBrooks Davis     return model::test_program(
305*b0d29bc4SBrooks Davis         program.interface_name(),
306*b0d29bc4SBrooks Davis         program.relative_path(), fs::path(root),
307*b0d29bc4SBrooks Davis         program.test_suite_name(),
308*b0d29bc4SBrooks Davis         program.get_metadata(), program.test_cases());
309*b0d29bc4SBrooks Davis }
310*b0d29bc4SBrooks Davis 
311*b0d29bc4SBrooks Davis 
312*b0d29bc4SBrooks Davis /// Functor to list the test cases of a test program.
313*b0d29bc4SBrooks Davis class list_test_cases {
314*b0d29bc4SBrooks Davis     /// Interface of the test program to execute.
315*b0d29bc4SBrooks Davis     std::shared_ptr< scheduler::interface > _interface;
316*b0d29bc4SBrooks Davis 
317*b0d29bc4SBrooks Davis     /// Test program to execute.
318*b0d29bc4SBrooks Davis     const model::test_program _test_program;
319*b0d29bc4SBrooks Davis 
320*b0d29bc4SBrooks Davis     /// User-provided configuration variables.
321*b0d29bc4SBrooks Davis     const config::tree& _user_config;
322*b0d29bc4SBrooks Davis 
323*b0d29bc4SBrooks Davis public:
324*b0d29bc4SBrooks Davis     /// Constructor.
325*b0d29bc4SBrooks Davis     ///
326*b0d29bc4SBrooks Davis     /// \param interface Interface of the test program to execute.
327*b0d29bc4SBrooks Davis     /// \param test_program Test program to execute.
328*b0d29bc4SBrooks Davis     /// \param user_config User-provided configuration variables.
329*b0d29bc4SBrooks Davis     list_test_cases(
330*b0d29bc4SBrooks Davis         const std::shared_ptr< scheduler::interface > interface,
331*b0d29bc4SBrooks Davis         const model::test_program* test_program,
332*b0d29bc4SBrooks Davis         const config::tree& user_config) :
333*b0d29bc4SBrooks Davis         _interface(interface),
334*b0d29bc4SBrooks Davis         _test_program(force_absolute_paths(*test_program)),
335*b0d29bc4SBrooks Davis         _user_config(user_config)
336*b0d29bc4SBrooks Davis     {
337*b0d29bc4SBrooks Davis     }
338*b0d29bc4SBrooks Davis 
339*b0d29bc4SBrooks Davis     /// Body of the subprocess.
340*b0d29bc4SBrooks Davis     void
341*b0d29bc4SBrooks Davis     operator()(const fs::path& /* control_directory */)
342*b0d29bc4SBrooks Davis     {
343*b0d29bc4SBrooks Davis         const config::properties_map vars = scheduler::generate_config(
344*b0d29bc4SBrooks Davis             _user_config, _test_program.test_suite_name());
345*b0d29bc4SBrooks Davis         _interface->exec_list(_test_program, vars);
346*b0d29bc4SBrooks Davis     }
347*b0d29bc4SBrooks Davis };
348*b0d29bc4SBrooks Davis 
349*b0d29bc4SBrooks Davis 
350*b0d29bc4SBrooks Davis /// Functor to execute a test program in a child process.
351*b0d29bc4SBrooks Davis class run_test_program {
352*b0d29bc4SBrooks Davis     /// Interface of the test program to execute.
353*b0d29bc4SBrooks Davis     std::shared_ptr< scheduler::interface > _interface;
354*b0d29bc4SBrooks Davis 
355*b0d29bc4SBrooks Davis     /// Test program to execute.
356*b0d29bc4SBrooks Davis     const model::test_program _test_program;
357*b0d29bc4SBrooks Davis 
358*b0d29bc4SBrooks Davis     /// Name of the test case to execute.
359*b0d29bc4SBrooks Davis     const std::string& _test_case_name;
360*b0d29bc4SBrooks Davis 
361*b0d29bc4SBrooks Davis     /// User-provided configuration variables.
362*b0d29bc4SBrooks Davis     const config::tree& _user_config;
363*b0d29bc4SBrooks Davis 
364*b0d29bc4SBrooks Davis     /// Verifies if the test case needs to be skipped or not.
365*b0d29bc4SBrooks Davis     ///
366*b0d29bc4SBrooks Davis     /// We could very well run this on the scheduler parent process before
367*b0d29bc4SBrooks Davis     /// issuing the fork.  However, doing this here in the child process is
368*b0d29bc4SBrooks Davis     /// better for two reasons: first, it allows us to continue using the simple
369*b0d29bc4SBrooks Davis     /// spawn/wait abstraction of the scheduler; and, second, we parallelize the
370*b0d29bc4SBrooks Davis     /// requirements checks among tests.
371*b0d29bc4SBrooks Davis     ///
372*b0d29bc4SBrooks Davis     /// \post If the test's preconditions are not met, the caller process is
373*b0d29bc4SBrooks Davis     /// terminated with a special exit code and a "skipped cookie" is written to
374*b0d29bc4SBrooks Davis     /// the disk with the reason for the failure.
375*b0d29bc4SBrooks Davis     ///
376*b0d29bc4SBrooks Davis     /// \param skipped_cookie_path File to create with the skip reason details
377*b0d29bc4SBrooks Davis     ///     if this test is skipped.
378*b0d29bc4SBrooks Davis     void
379*b0d29bc4SBrooks Davis     do_requirements_check(const fs::path& skipped_cookie_path)
380*b0d29bc4SBrooks Davis     {
381*b0d29bc4SBrooks Davis         const model::test_case& test_case = _test_program.find(
382*b0d29bc4SBrooks Davis             _test_case_name);
383*b0d29bc4SBrooks Davis 
384*b0d29bc4SBrooks Davis         const std::string skip_reason = engine::check_reqs(
385*b0d29bc4SBrooks Davis             test_case.get_metadata(), _user_config,
386*b0d29bc4SBrooks Davis             _test_program.test_suite_name(),
387*b0d29bc4SBrooks Davis             fs::current_path());
388*b0d29bc4SBrooks Davis         if (skip_reason.empty())
389*b0d29bc4SBrooks Davis             return;
390*b0d29bc4SBrooks Davis 
391*b0d29bc4SBrooks Davis         std::ofstream output(skipped_cookie_path.c_str());
392*b0d29bc4SBrooks Davis         if (!output) {
393*b0d29bc4SBrooks Davis             std::perror((F("Failed to open %s for write") %
394*b0d29bc4SBrooks Davis                          skipped_cookie_path).str().c_str());
395*b0d29bc4SBrooks Davis             std::abort();
396*b0d29bc4SBrooks Davis         }
397*b0d29bc4SBrooks Davis         output << skip_reason;
398*b0d29bc4SBrooks Davis         output.close();
399*b0d29bc4SBrooks Davis 
400*b0d29bc4SBrooks Davis         // Abruptly terminate the process.  We don't want to run any destructors
401*b0d29bc4SBrooks Davis         // inherited from the parent process by mistake, which could, for
402*b0d29bc4SBrooks Davis         // example, delete our own control files!
403*b0d29bc4SBrooks Davis         ::_exit(exit_skipped);
404*b0d29bc4SBrooks Davis     }
405*b0d29bc4SBrooks Davis 
406*b0d29bc4SBrooks Davis public:
407*b0d29bc4SBrooks Davis     /// Constructor.
408*b0d29bc4SBrooks Davis     ///
409*b0d29bc4SBrooks Davis     /// \param interface Interface of the test program to execute.
410*b0d29bc4SBrooks Davis     /// \param test_program Test program to execute.
411*b0d29bc4SBrooks Davis     /// \param test_case_name Name of the test case to execute.
412*b0d29bc4SBrooks Davis     /// \param user_config User-provided configuration variables.
413*b0d29bc4SBrooks Davis     run_test_program(
414*b0d29bc4SBrooks Davis         const std::shared_ptr< scheduler::interface > interface,
415*b0d29bc4SBrooks Davis         const model::test_program_ptr test_program,
416*b0d29bc4SBrooks Davis         const std::string& test_case_name,
417*b0d29bc4SBrooks Davis         const config::tree& user_config) :
418*b0d29bc4SBrooks Davis         _interface(interface),
419*b0d29bc4SBrooks Davis         _test_program(force_absolute_paths(*test_program)),
420*b0d29bc4SBrooks Davis         _test_case_name(test_case_name),
421*b0d29bc4SBrooks Davis         _user_config(user_config)
422*b0d29bc4SBrooks Davis     {
423*b0d29bc4SBrooks Davis     }
424*b0d29bc4SBrooks Davis 
425*b0d29bc4SBrooks Davis     /// Body of the subprocess.
426*b0d29bc4SBrooks Davis     ///
427*b0d29bc4SBrooks Davis     /// \param control_directory The testcase directory where files will be
428*b0d29bc4SBrooks Davis     ///     read from.
429*b0d29bc4SBrooks Davis     void
430*b0d29bc4SBrooks Davis     operator()(const fs::path& control_directory)
431*b0d29bc4SBrooks Davis     {
432*b0d29bc4SBrooks Davis         const model::test_case& test_case = _test_program.find(
433*b0d29bc4SBrooks Davis             _test_case_name);
434*b0d29bc4SBrooks Davis         if (test_case.fake_result())
435*b0d29bc4SBrooks Davis             ::_exit(EXIT_SUCCESS);
436*b0d29bc4SBrooks Davis 
437*b0d29bc4SBrooks Davis         do_requirements_check(control_directory / skipped_cookie);
438*b0d29bc4SBrooks Davis 
439*b0d29bc4SBrooks Davis         const config::properties_map vars = scheduler::generate_config(
440*b0d29bc4SBrooks Davis             _user_config, _test_program.test_suite_name());
441*b0d29bc4SBrooks Davis         _interface->exec_test(_test_program, _test_case_name, vars,
442*b0d29bc4SBrooks Davis                               control_directory);
443*b0d29bc4SBrooks Davis     }
444*b0d29bc4SBrooks Davis };
445*b0d29bc4SBrooks Davis 
446*b0d29bc4SBrooks Davis 
447*b0d29bc4SBrooks Davis /// Functor to execute a test program in a child process.
448*b0d29bc4SBrooks Davis class run_test_cleanup {
449*b0d29bc4SBrooks Davis     /// Interface of the test program to execute.
450*b0d29bc4SBrooks Davis     std::shared_ptr< scheduler::interface > _interface;
451*b0d29bc4SBrooks Davis 
452*b0d29bc4SBrooks Davis     /// Test program to execute.
453*b0d29bc4SBrooks Davis     const model::test_program _test_program;
454*b0d29bc4SBrooks Davis 
455*b0d29bc4SBrooks Davis     /// Name of the test case to execute.
456*b0d29bc4SBrooks Davis     const std::string& _test_case_name;
457*b0d29bc4SBrooks Davis 
458*b0d29bc4SBrooks Davis     /// User-provided configuration variables.
459*b0d29bc4SBrooks Davis     const config::tree& _user_config;
460*b0d29bc4SBrooks Davis 
461*b0d29bc4SBrooks Davis public:
462*b0d29bc4SBrooks Davis     /// Constructor.
463*b0d29bc4SBrooks Davis     ///
464*b0d29bc4SBrooks Davis     /// \param interface Interface of the test program to execute.
465*b0d29bc4SBrooks Davis     /// \param test_program Test program to execute.
466*b0d29bc4SBrooks Davis     /// \param test_case_name Name of the test case to execute.
467*b0d29bc4SBrooks Davis     /// \param user_config User-provided configuration variables.
468*b0d29bc4SBrooks Davis     run_test_cleanup(
469*b0d29bc4SBrooks Davis         const std::shared_ptr< scheduler::interface > interface,
470*b0d29bc4SBrooks Davis         const model::test_program_ptr test_program,
471*b0d29bc4SBrooks Davis         const std::string& test_case_name,
472*b0d29bc4SBrooks Davis         const config::tree& user_config) :
473*b0d29bc4SBrooks Davis         _interface(interface),
474*b0d29bc4SBrooks Davis         _test_program(force_absolute_paths(*test_program)),
475*b0d29bc4SBrooks Davis         _test_case_name(test_case_name),
476*b0d29bc4SBrooks Davis         _user_config(user_config)
477*b0d29bc4SBrooks Davis     {
478*b0d29bc4SBrooks Davis     }
479*b0d29bc4SBrooks Davis 
480*b0d29bc4SBrooks Davis     /// Body of the subprocess.
481*b0d29bc4SBrooks Davis     ///
482*b0d29bc4SBrooks Davis     /// \param control_directory The testcase directory where cleanup will be
483*b0d29bc4SBrooks Davis     ///     run from.
484*b0d29bc4SBrooks Davis     void
485*b0d29bc4SBrooks Davis     operator()(const fs::path& control_directory)
486*b0d29bc4SBrooks Davis     {
487*b0d29bc4SBrooks Davis         const config::properties_map vars = scheduler::generate_config(
488*b0d29bc4SBrooks Davis             _user_config, _test_program.test_suite_name());
489*b0d29bc4SBrooks Davis         _interface->exec_cleanup(_test_program, _test_case_name, vars,
490*b0d29bc4SBrooks Davis                                  control_directory);
491*b0d29bc4SBrooks Davis     }
492*b0d29bc4SBrooks Davis };
493*b0d29bc4SBrooks Davis 
494*b0d29bc4SBrooks Davis 
495*b0d29bc4SBrooks Davis /// Obtains the right scheduler interface for a given test program.
496*b0d29bc4SBrooks Davis ///
497*b0d29bc4SBrooks Davis /// \param name The name of the interface of the test program.
498*b0d29bc4SBrooks Davis ///
499*b0d29bc4SBrooks Davis /// \return An scheduler interface.
500*b0d29bc4SBrooks Davis std::shared_ptr< scheduler::interface >
501*b0d29bc4SBrooks Davis find_interface(const std::string& name)
502*b0d29bc4SBrooks Davis {
503*b0d29bc4SBrooks Davis     const interfaces_map::const_iterator iter = interfaces.find(name);
504*b0d29bc4SBrooks Davis     PRE(interfaces.find(name) != interfaces.end());
505*b0d29bc4SBrooks Davis     return (*iter).second;
506*b0d29bc4SBrooks Davis }
507*b0d29bc4SBrooks Davis 
508*b0d29bc4SBrooks Davis 
509*b0d29bc4SBrooks Davis }  // anonymous namespace
510*b0d29bc4SBrooks Davis 
511*b0d29bc4SBrooks Davis 
512*b0d29bc4SBrooks Davis void
513*b0d29bc4SBrooks Davis scheduler::interface::exec_cleanup(
514*b0d29bc4SBrooks Davis     const model::test_program& /* test_program */,
515*b0d29bc4SBrooks Davis     const std::string& /* test_case_name */,
516*b0d29bc4SBrooks Davis     const config::properties_map& /* vars */,
517*b0d29bc4SBrooks Davis     const utils::fs::path& /* control_directory */) const
518*b0d29bc4SBrooks Davis {
519*b0d29bc4SBrooks Davis     // Most test interfaces do not support standalone cleanup routines so
520*b0d29bc4SBrooks Davis     // provide a default implementation that does nothing.
521*b0d29bc4SBrooks Davis     UNREACHABLE_MSG("exec_cleanup not implemented for an interface that "
522*b0d29bc4SBrooks Davis                     "supports standalone cleanup routines");
523*b0d29bc4SBrooks Davis }
524*b0d29bc4SBrooks Davis 
525*b0d29bc4SBrooks Davis 
526*b0d29bc4SBrooks Davis /// Internal implementation of a lazy_test_program.
527*b0d29bc4SBrooks Davis struct engine::scheduler::lazy_test_program::impl : utils::noncopyable {
528*b0d29bc4SBrooks Davis     /// Whether the test cases list has been yet loaded or not.
529*b0d29bc4SBrooks Davis     bool _loaded;
530*b0d29bc4SBrooks Davis 
531*b0d29bc4SBrooks Davis     /// User configuration to pass to the test program list operation.
532*b0d29bc4SBrooks Davis     config::tree _user_config;
533*b0d29bc4SBrooks Davis 
534*b0d29bc4SBrooks Davis     /// Scheduler context to use to load test cases.
535*b0d29bc4SBrooks Davis     scheduler::scheduler_handle& _scheduler_handle;
536*b0d29bc4SBrooks Davis 
537*b0d29bc4SBrooks Davis     /// Constructor.
538*b0d29bc4SBrooks Davis     ///
539*b0d29bc4SBrooks Davis     /// \param user_config_ User configuration to pass to the test program list
540*b0d29bc4SBrooks Davis     ///     operation.
541*b0d29bc4SBrooks Davis     /// \param scheduler_handle_ Scheduler context to use when loading test
542*b0d29bc4SBrooks Davis     ///     cases.
543*b0d29bc4SBrooks Davis     impl(const config::tree& user_config_,
544*b0d29bc4SBrooks Davis          scheduler::scheduler_handle& scheduler_handle_) :
545*b0d29bc4SBrooks Davis         _loaded(false), _user_config(user_config_),
546*b0d29bc4SBrooks Davis         _scheduler_handle(scheduler_handle_)
547*b0d29bc4SBrooks Davis     {
548*b0d29bc4SBrooks Davis     }
549*b0d29bc4SBrooks Davis };
550*b0d29bc4SBrooks Davis 
551*b0d29bc4SBrooks Davis 
552*b0d29bc4SBrooks Davis /// Constructs a new test program.
553*b0d29bc4SBrooks Davis ///
554*b0d29bc4SBrooks Davis /// \param interface_name_ Name of the test program interface.
555*b0d29bc4SBrooks Davis /// \param binary_ The name of the test program binary relative to root_.
556*b0d29bc4SBrooks Davis /// \param root_ The root of the test suite containing the test program.
557*b0d29bc4SBrooks Davis /// \param test_suite_name_ The name of the test suite this program belongs to.
558*b0d29bc4SBrooks Davis /// \param md_ Metadata of the test program.
559*b0d29bc4SBrooks Davis /// \param user_config_ User configuration to pass to the scheduler.
560*b0d29bc4SBrooks Davis /// \param scheduler_handle_ Scheduler context to use to load test cases.
561*b0d29bc4SBrooks Davis scheduler::lazy_test_program::lazy_test_program(
562*b0d29bc4SBrooks Davis     const std::string& interface_name_,
563*b0d29bc4SBrooks Davis     const fs::path& binary_,
564*b0d29bc4SBrooks Davis     const fs::path& root_,
565*b0d29bc4SBrooks Davis     const std::string& test_suite_name_,
566*b0d29bc4SBrooks Davis     const model::metadata& md_,
567*b0d29bc4SBrooks Davis     const config::tree& user_config_,
568*b0d29bc4SBrooks Davis     scheduler::scheduler_handle& scheduler_handle_) :
569*b0d29bc4SBrooks Davis     test_program(interface_name_, binary_, root_, test_suite_name_, md_,
570*b0d29bc4SBrooks Davis                  model::test_cases_map()),
571*b0d29bc4SBrooks Davis     _pimpl(new impl(user_config_, scheduler_handle_))
572*b0d29bc4SBrooks Davis {
573*b0d29bc4SBrooks Davis }
574*b0d29bc4SBrooks Davis 
575*b0d29bc4SBrooks Davis 
576*b0d29bc4SBrooks Davis /// Gets or loads the list of test cases from the test program.
577*b0d29bc4SBrooks Davis ///
578*b0d29bc4SBrooks Davis /// \return The list of test cases provided by the test program.
579*b0d29bc4SBrooks Davis const model::test_cases_map&
580*b0d29bc4SBrooks Davis scheduler::lazy_test_program::test_cases(void) const
581*b0d29bc4SBrooks Davis {
582*b0d29bc4SBrooks Davis     _pimpl->_scheduler_handle.check_interrupt();
583*b0d29bc4SBrooks Davis 
584*b0d29bc4SBrooks Davis     if (!_pimpl->_loaded) {
585*b0d29bc4SBrooks Davis         const model::test_cases_map tcs = _pimpl->_scheduler_handle.list_tests(
586*b0d29bc4SBrooks Davis             this, _pimpl->_user_config);
587*b0d29bc4SBrooks Davis 
588*b0d29bc4SBrooks Davis         // Due to the restrictions on when set_test_cases() may be called (as a
589*b0d29bc4SBrooks Davis         // way to lazily initialize the test cases list before it is ever
590*b0d29bc4SBrooks Davis         // returned), this cast is valid.
591*b0d29bc4SBrooks Davis         const_cast< scheduler::lazy_test_program* >(this)->set_test_cases(tcs);
592*b0d29bc4SBrooks Davis 
593*b0d29bc4SBrooks Davis         _pimpl->_loaded = true;
594*b0d29bc4SBrooks Davis 
595*b0d29bc4SBrooks Davis         _pimpl->_scheduler_handle.check_interrupt();
596*b0d29bc4SBrooks Davis     }
597*b0d29bc4SBrooks Davis 
598*b0d29bc4SBrooks Davis     INV(_pimpl->_loaded);
599*b0d29bc4SBrooks Davis     return test_program::test_cases();
600*b0d29bc4SBrooks Davis }
601*b0d29bc4SBrooks Davis 
602*b0d29bc4SBrooks Davis 
603*b0d29bc4SBrooks Davis /// Internal implementation for the result_handle class.
604*b0d29bc4SBrooks Davis struct engine::scheduler::result_handle::bimpl : utils::noncopyable {
605*b0d29bc4SBrooks Davis     /// Generic executor exit handle for this result handle.
606*b0d29bc4SBrooks Davis     executor::exit_handle generic;
607*b0d29bc4SBrooks Davis 
608*b0d29bc4SBrooks Davis     /// Mutable pointer to the corresponding scheduler state.
609*b0d29bc4SBrooks Davis     ///
610*b0d29bc4SBrooks Davis     /// This object references a member of the scheduler_handle that yielded
611*b0d29bc4SBrooks Davis     /// this result_handle instance.  We need this direct access to clean up
612*b0d29bc4SBrooks Davis     /// after ourselves when the result is destroyed.
613*b0d29bc4SBrooks Davis     exec_data_map& all_exec_data;
614*b0d29bc4SBrooks Davis 
615*b0d29bc4SBrooks Davis     /// Constructor.
616*b0d29bc4SBrooks Davis     ///
617*b0d29bc4SBrooks Davis     /// \param generic_ Generic executor exit handle for this result handle.
618*b0d29bc4SBrooks Davis     /// \param [in,out] all_exec_data_ Global object keeping track of all active
619*b0d29bc4SBrooks Davis     ///     executions for an scheduler.  This is a pointer to a member of the
620*b0d29bc4SBrooks Davis     ///     scheduler_handle object.
621*b0d29bc4SBrooks Davis     bimpl(const executor::exit_handle generic_, exec_data_map& all_exec_data_) :
622*b0d29bc4SBrooks Davis         generic(generic_), all_exec_data(all_exec_data_)
623*b0d29bc4SBrooks Davis     {
624*b0d29bc4SBrooks Davis     }
625*b0d29bc4SBrooks Davis 
626*b0d29bc4SBrooks Davis     /// Destructor.
627*b0d29bc4SBrooks Davis     ~bimpl(void)
628*b0d29bc4SBrooks Davis     {
629*b0d29bc4SBrooks Davis         LD(F("Removing %s from all_exec_data") % generic.original_pid());
630*b0d29bc4SBrooks Davis         all_exec_data.erase(generic.original_pid());
631*b0d29bc4SBrooks Davis     }
632*b0d29bc4SBrooks Davis };
633*b0d29bc4SBrooks Davis 
634*b0d29bc4SBrooks Davis 
635*b0d29bc4SBrooks Davis /// Constructor.
636*b0d29bc4SBrooks Davis ///
637*b0d29bc4SBrooks Davis /// \param pbimpl Constructed internal implementation.
638*b0d29bc4SBrooks Davis scheduler::result_handle::result_handle(std::shared_ptr< bimpl > pbimpl) :
639*b0d29bc4SBrooks Davis     _pbimpl(pbimpl)
640*b0d29bc4SBrooks Davis {
641*b0d29bc4SBrooks Davis }
642*b0d29bc4SBrooks Davis 
643*b0d29bc4SBrooks Davis 
644*b0d29bc4SBrooks Davis /// Destructor.
645*b0d29bc4SBrooks Davis scheduler::result_handle::~result_handle(void)
646*b0d29bc4SBrooks Davis {
647*b0d29bc4SBrooks Davis }
648*b0d29bc4SBrooks Davis 
649*b0d29bc4SBrooks Davis 
650*b0d29bc4SBrooks Davis /// Cleans up the test case results.
651*b0d29bc4SBrooks Davis ///
652*b0d29bc4SBrooks Davis /// This function should be called explicitly as it provides the means to
653*b0d29bc4SBrooks Davis /// control any exceptions raised during cleanup.  Do not rely on the destructor
654*b0d29bc4SBrooks Davis /// to clean things up.
655*b0d29bc4SBrooks Davis ///
656*b0d29bc4SBrooks Davis /// \throw engine::error If the cleanup fails, especially due to the inability
657*b0d29bc4SBrooks Davis ///     to remove the work directory.
658*b0d29bc4SBrooks Davis void
659*b0d29bc4SBrooks Davis scheduler::result_handle::cleanup(void)
660*b0d29bc4SBrooks Davis {
661*b0d29bc4SBrooks Davis     _pbimpl->generic.cleanup();
662*b0d29bc4SBrooks Davis }
663*b0d29bc4SBrooks Davis 
664*b0d29bc4SBrooks Davis 
665*b0d29bc4SBrooks Davis /// Returns the original PID corresponding to this result.
666*b0d29bc4SBrooks Davis ///
667*b0d29bc4SBrooks Davis /// \return An exec_handle.
668*b0d29bc4SBrooks Davis int
669*b0d29bc4SBrooks Davis scheduler::result_handle::original_pid(void) const
670*b0d29bc4SBrooks Davis {
671*b0d29bc4SBrooks Davis     return _pbimpl->generic.original_pid();
672*b0d29bc4SBrooks Davis }
673*b0d29bc4SBrooks Davis 
674*b0d29bc4SBrooks Davis 
675*b0d29bc4SBrooks Davis /// Returns the timestamp of when spawn_test was called.
676*b0d29bc4SBrooks Davis ///
677*b0d29bc4SBrooks Davis /// \return A timestamp.
678*b0d29bc4SBrooks Davis const datetime::timestamp&
679*b0d29bc4SBrooks Davis scheduler::result_handle::start_time(void) const
680*b0d29bc4SBrooks Davis {
681*b0d29bc4SBrooks Davis     return _pbimpl->generic.start_time();
682*b0d29bc4SBrooks Davis }
683*b0d29bc4SBrooks Davis 
684*b0d29bc4SBrooks Davis 
685*b0d29bc4SBrooks Davis /// Returns the timestamp of when wait_any_test returned this object.
686*b0d29bc4SBrooks Davis ///
687*b0d29bc4SBrooks Davis /// \return A timestamp.
688*b0d29bc4SBrooks Davis const datetime::timestamp&
689*b0d29bc4SBrooks Davis scheduler::result_handle::end_time(void) const
690*b0d29bc4SBrooks Davis {
691*b0d29bc4SBrooks Davis     return _pbimpl->generic.end_time();
692*b0d29bc4SBrooks Davis }
693*b0d29bc4SBrooks Davis 
694*b0d29bc4SBrooks Davis 
695*b0d29bc4SBrooks Davis /// Returns the path to the test-specific work directory.
696*b0d29bc4SBrooks Davis ///
697*b0d29bc4SBrooks Davis /// This is guaranteed to be clear of files created by the scheduler.
698*b0d29bc4SBrooks Davis ///
699*b0d29bc4SBrooks Davis /// \return The path to a directory that exists until cleanup() is called.
700*b0d29bc4SBrooks Davis fs::path
701*b0d29bc4SBrooks Davis scheduler::result_handle::work_directory(void) const
702*b0d29bc4SBrooks Davis {
703*b0d29bc4SBrooks Davis     return _pbimpl->generic.work_directory();
704*b0d29bc4SBrooks Davis }
705*b0d29bc4SBrooks Davis 
706*b0d29bc4SBrooks Davis 
707*b0d29bc4SBrooks Davis /// Returns the path to the test's stdout file.
708*b0d29bc4SBrooks Davis ///
709*b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called.
710*b0d29bc4SBrooks Davis const fs::path&
711*b0d29bc4SBrooks Davis scheduler::result_handle::stdout_file(void) const
712*b0d29bc4SBrooks Davis {
713*b0d29bc4SBrooks Davis     return _pbimpl->generic.stdout_file();
714*b0d29bc4SBrooks Davis }
715*b0d29bc4SBrooks Davis 
716*b0d29bc4SBrooks Davis 
717*b0d29bc4SBrooks Davis /// Returns the path to the test's stderr file.
718*b0d29bc4SBrooks Davis ///
719*b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called.
720*b0d29bc4SBrooks Davis const fs::path&
721*b0d29bc4SBrooks Davis scheduler::result_handle::stderr_file(void) const
722*b0d29bc4SBrooks Davis {
723*b0d29bc4SBrooks Davis     return _pbimpl->generic.stderr_file();
724*b0d29bc4SBrooks Davis }
725*b0d29bc4SBrooks Davis 
726*b0d29bc4SBrooks Davis 
727*b0d29bc4SBrooks Davis /// Internal implementation for the test_result_handle class.
728*b0d29bc4SBrooks Davis struct engine::scheduler::test_result_handle::impl : utils::noncopyable {
729*b0d29bc4SBrooks Davis     /// Test program data for this test case.
730*b0d29bc4SBrooks Davis     model::test_program_ptr test_program;
731*b0d29bc4SBrooks Davis 
732*b0d29bc4SBrooks Davis     /// Name of the test case.
733*b0d29bc4SBrooks Davis     std::string test_case_name;
734*b0d29bc4SBrooks Davis 
735*b0d29bc4SBrooks Davis     /// The actual result of the test execution.
736*b0d29bc4SBrooks Davis     const model::test_result test_result;
737*b0d29bc4SBrooks Davis 
738*b0d29bc4SBrooks Davis     /// Constructor.
739*b0d29bc4SBrooks Davis     ///
740*b0d29bc4SBrooks Davis     /// \param test_program_ Test program data for this test case.
741*b0d29bc4SBrooks Davis     /// \param test_case_name_ Name of the test case.
742*b0d29bc4SBrooks Davis     /// \param test_result_ The actual result of the test execution.
743*b0d29bc4SBrooks Davis     impl(const model::test_program_ptr test_program_,
744*b0d29bc4SBrooks Davis          const std::string& test_case_name_,
745*b0d29bc4SBrooks Davis          const model::test_result& test_result_) :
746*b0d29bc4SBrooks Davis         test_program(test_program_),
747*b0d29bc4SBrooks Davis         test_case_name(test_case_name_),
748*b0d29bc4SBrooks Davis         test_result(test_result_)
749*b0d29bc4SBrooks Davis     {
750*b0d29bc4SBrooks Davis     }
751*b0d29bc4SBrooks Davis };
752*b0d29bc4SBrooks Davis 
753*b0d29bc4SBrooks Davis 
754*b0d29bc4SBrooks Davis /// Constructor.
755*b0d29bc4SBrooks Davis ///
756*b0d29bc4SBrooks Davis /// \param pbimpl Constructed internal implementation for the base object.
757*b0d29bc4SBrooks Davis /// \param pimpl Constructed internal implementation.
758*b0d29bc4SBrooks Davis scheduler::test_result_handle::test_result_handle(
759*b0d29bc4SBrooks Davis     std::shared_ptr< bimpl > pbimpl, std::shared_ptr< impl > pimpl) :
760*b0d29bc4SBrooks Davis     result_handle(pbimpl), _pimpl(pimpl)
761*b0d29bc4SBrooks Davis {
762*b0d29bc4SBrooks Davis }
763*b0d29bc4SBrooks Davis 
764*b0d29bc4SBrooks Davis 
765*b0d29bc4SBrooks Davis /// Destructor.
766*b0d29bc4SBrooks Davis scheduler::test_result_handle::~test_result_handle(void)
767*b0d29bc4SBrooks Davis {
768*b0d29bc4SBrooks Davis }
769*b0d29bc4SBrooks Davis 
770*b0d29bc4SBrooks Davis 
771*b0d29bc4SBrooks Davis /// Returns the test program that yielded this result.
772*b0d29bc4SBrooks Davis ///
773*b0d29bc4SBrooks Davis /// \return A test program.
774*b0d29bc4SBrooks Davis const model::test_program_ptr
775*b0d29bc4SBrooks Davis scheduler::test_result_handle::test_program(void) const
776*b0d29bc4SBrooks Davis {
777*b0d29bc4SBrooks Davis     return _pimpl->test_program;
778*b0d29bc4SBrooks Davis }
779*b0d29bc4SBrooks Davis 
780*b0d29bc4SBrooks Davis 
781*b0d29bc4SBrooks Davis /// Returns the name of the test case that yielded this result.
782*b0d29bc4SBrooks Davis ///
783*b0d29bc4SBrooks Davis /// \return A test case name
784*b0d29bc4SBrooks Davis const std::string&
785*b0d29bc4SBrooks Davis scheduler::test_result_handle::test_case_name(void) const
786*b0d29bc4SBrooks Davis {
787*b0d29bc4SBrooks Davis     return _pimpl->test_case_name;
788*b0d29bc4SBrooks Davis }
789*b0d29bc4SBrooks Davis 
790*b0d29bc4SBrooks Davis 
791*b0d29bc4SBrooks Davis /// Returns the actual result of the test execution.
792*b0d29bc4SBrooks Davis ///
793*b0d29bc4SBrooks Davis /// \return A test result.
794*b0d29bc4SBrooks Davis const model::test_result&
795*b0d29bc4SBrooks Davis scheduler::test_result_handle::test_result(void) const
796*b0d29bc4SBrooks Davis {
797*b0d29bc4SBrooks Davis     return _pimpl->test_result;
798*b0d29bc4SBrooks Davis }
799*b0d29bc4SBrooks Davis 
800*b0d29bc4SBrooks Davis 
801*b0d29bc4SBrooks Davis /// Internal implementation for the scheduler_handle.
802*b0d29bc4SBrooks Davis struct engine::scheduler::scheduler_handle::impl : utils::noncopyable {
803*b0d29bc4SBrooks Davis     /// Generic executor instance encapsulated by this one.
804*b0d29bc4SBrooks Davis     executor::executor_handle generic;
805*b0d29bc4SBrooks Davis 
806*b0d29bc4SBrooks Davis     /// Mapping of exec handles to the data required at run time.
807*b0d29bc4SBrooks Davis     exec_data_map all_exec_data;
808*b0d29bc4SBrooks Davis 
809*b0d29bc4SBrooks Davis     /// Collection of test_exec_data objects.
810*b0d29bc4SBrooks Davis     typedef std::vector< const test_exec_data* > test_exec_data_vector;
811*b0d29bc4SBrooks Davis 
812*b0d29bc4SBrooks Davis     /// Constructor.
813*b0d29bc4SBrooks Davis     impl(void) : generic(executor::setup())
814*b0d29bc4SBrooks Davis     {
815*b0d29bc4SBrooks Davis     }
816*b0d29bc4SBrooks Davis 
817*b0d29bc4SBrooks Davis     /// Destructor.
818*b0d29bc4SBrooks Davis     ///
819*b0d29bc4SBrooks Davis     /// This runs any pending cleanup routines, which should only happen if the
820*b0d29bc4SBrooks Davis     /// scheduler is abruptly terminated (aka if a signal is received).
821*b0d29bc4SBrooks Davis     ~impl(void)
822*b0d29bc4SBrooks Davis     {
823*b0d29bc4SBrooks Davis         const test_exec_data_vector tests_data = tests_needing_cleanup();
824*b0d29bc4SBrooks Davis 
825*b0d29bc4SBrooks Davis         for (test_exec_data_vector::const_iterator iter = tests_data.begin();
826*b0d29bc4SBrooks Davis              iter != tests_data.end(); ++iter) {
827*b0d29bc4SBrooks Davis             const test_exec_data* test_data = *iter;
828*b0d29bc4SBrooks Davis 
829*b0d29bc4SBrooks Davis             try {
830*b0d29bc4SBrooks Davis                 sync_cleanup(test_data);
831*b0d29bc4SBrooks Davis             } catch (const std::runtime_error& e) {
832*b0d29bc4SBrooks Davis                 LW(F("Failed to run cleanup routine for %s:%s on abrupt "
833*b0d29bc4SBrooks Davis                      "termination")
834*b0d29bc4SBrooks Davis                    % test_data->test_program->relative_path()
835*b0d29bc4SBrooks Davis                    % test_data->test_case_name);
836*b0d29bc4SBrooks Davis             }
837*b0d29bc4SBrooks Davis         }
838*b0d29bc4SBrooks Davis     }
839*b0d29bc4SBrooks Davis 
840*b0d29bc4SBrooks Davis     /// Finds any pending exec_datas that correspond to tests needing cleanup.
841*b0d29bc4SBrooks Davis     ///
842*b0d29bc4SBrooks Davis     /// \return The collection of test_exec_data objects that have their
843*b0d29bc4SBrooks Davis     /// needs_cleanup property set to true.
844*b0d29bc4SBrooks Davis     test_exec_data_vector
845*b0d29bc4SBrooks Davis     tests_needing_cleanup(void)
846*b0d29bc4SBrooks Davis     {
847*b0d29bc4SBrooks Davis         test_exec_data_vector tests_data;
848*b0d29bc4SBrooks Davis 
849*b0d29bc4SBrooks Davis         for (exec_data_map::const_iterator iter = all_exec_data.begin();
850*b0d29bc4SBrooks Davis              iter != all_exec_data.end(); ++iter) {
851*b0d29bc4SBrooks Davis             const exec_data_ptr data = (*iter).second;
852*b0d29bc4SBrooks Davis 
853*b0d29bc4SBrooks Davis             try {
854*b0d29bc4SBrooks Davis                 test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
855*b0d29bc4SBrooks Davis                     *data.get());
856*b0d29bc4SBrooks Davis                 if (test_data->needs_cleanup) {
857*b0d29bc4SBrooks Davis                     tests_data.push_back(test_data);
858*b0d29bc4SBrooks Davis                     test_data->needs_cleanup = false;
859*b0d29bc4SBrooks Davis                 }
860*b0d29bc4SBrooks Davis             } catch (const std::bad_cast& e) {
861*b0d29bc4SBrooks Davis                 // Do nothing for cleanup_exec_data objects.
862*b0d29bc4SBrooks Davis             }
863*b0d29bc4SBrooks Davis         }
864*b0d29bc4SBrooks Davis 
865*b0d29bc4SBrooks Davis         return tests_data;
866*b0d29bc4SBrooks Davis     }
867*b0d29bc4SBrooks Davis 
868*b0d29bc4SBrooks Davis     /// Cleans up a single test case synchronously.
869*b0d29bc4SBrooks Davis     ///
870*b0d29bc4SBrooks Davis     /// \param test_data The data of the previously executed test case to be
871*b0d29bc4SBrooks Davis     ///     cleaned up.
872*b0d29bc4SBrooks Davis     void
873*b0d29bc4SBrooks Davis     sync_cleanup(const test_exec_data* test_data)
874*b0d29bc4SBrooks Davis     {
875*b0d29bc4SBrooks Davis         // The message in this result should never be seen by the user, but use
876*b0d29bc4SBrooks Davis         // something reasonable just in case it leaks and we need to pinpoint
877*b0d29bc4SBrooks Davis         // the call site.
878*b0d29bc4SBrooks Davis         model::test_result result(model::test_result_broken,
879*b0d29bc4SBrooks Davis                                   "Test case died abruptly");
880*b0d29bc4SBrooks Davis 
881*b0d29bc4SBrooks Davis         const executor::exec_handle cleanup_handle = spawn_cleanup(
882*b0d29bc4SBrooks Davis             test_data->test_program, test_data->test_case_name,
883*b0d29bc4SBrooks Davis             test_data->user_config, test_data->exit_handle.get(),
884*b0d29bc4SBrooks Davis             result);
885*b0d29bc4SBrooks Davis         generic.wait(cleanup_handle);
886*b0d29bc4SBrooks Davis     }
887*b0d29bc4SBrooks Davis 
888*b0d29bc4SBrooks Davis     /// Forks and executes a test case cleanup routine asynchronously.
889*b0d29bc4SBrooks Davis     ///
890*b0d29bc4SBrooks Davis     /// \param test_program The container test program.
891*b0d29bc4SBrooks Davis     /// \param test_case_name The name of the test case to run.
892*b0d29bc4SBrooks Davis     /// \param user_config User-provided configuration variables.
893*b0d29bc4SBrooks Davis     /// \param body_handle The exit handle of the test case's corresponding
894*b0d29bc4SBrooks Davis     ///     body.  The cleanup will be executed in the same context.
895*b0d29bc4SBrooks Davis     /// \param body_result The result of the test case's corresponding body.
896*b0d29bc4SBrooks Davis     ///
897*b0d29bc4SBrooks Davis     /// \return A handle for the background operation.  Used to match the result
898*b0d29bc4SBrooks Davis     /// of the execution returned by wait_any() with this invocation.
899*b0d29bc4SBrooks Davis     executor::exec_handle
900*b0d29bc4SBrooks Davis     spawn_cleanup(const model::test_program_ptr test_program,
901*b0d29bc4SBrooks Davis                   const std::string& test_case_name,
902*b0d29bc4SBrooks Davis                   const config::tree& user_config,
903*b0d29bc4SBrooks Davis                   const executor::exit_handle& body_handle,
904*b0d29bc4SBrooks Davis                   const model::test_result& body_result)
905*b0d29bc4SBrooks Davis     {
906*b0d29bc4SBrooks Davis         generic.check_interrupt();
907*b0d29bc4SBrooks Davis 
908*b0d29bc4SBrooks Davis         const std::shared_ptr< scheduler::interface > interface =
909*b0d29bc4SBrooks Davis             find_interface(test_program->interface_name());
910*b0d29bc4SBrooks Davis 
911*b0d29bc4SBrooks Davis         LI(F("Spawning %s:%s (cleanup)") % test_program->absolute_path() %
912*b0d29bc4SBrooks Davis            test_case_name);
913*b0d29bc4SBrooks Davis 
914*b0d29bc4SBrooks Davis         const executor::exec_handle handle = generic.spawn_followup(
915*b0d29bc4SBrooks Davis             run_test_cleanup(interface, test_program, test_case_name,
916*b0d29bc4SBrooks Davis                              user_config),
917*b0d29bc4SBrooks Davis             body_handle, cleanup_timeout);
918*b0d29bc4SBrooks Davis 
919*b0d29bc4SBrooks Davis         const exec_data_ptr data(new cleanup_exec_data(
920*b0d29bc4SBrooks Davis             test_program, test_case_name, body_handle, body_result));
921*b0d29bc4SBrooks Davis         LD(F("Inserting %s into all_exec_data (cleanup)") % handle.pid());
922*b0d29bc4SBrooks Davis         INV_MSG(all_exec_data.find(handle.pid()) == all_exec_data.end(),
923*b0d29bc4SBrooks Davis                 F("PID %s already in all_exec_data; not properly cleaned "
924*b0d29bc4SBrooks Davis                   "up or reused too fast") % handle.pid());;
925*b0d29bc4SBrooks Davis         all_exec_data.insert(exec_data_map::value_type(handle.pid(), data));
926*b0d29bc4SBrooks Davis 
927*b0d29bc4SBrooks Davis         return handle;
928*b0d29bc4SBrooks Davis     }
929*b0d29bc4SBrooks Davis };
930*b0d29bc4SBrooks Davis 
931*b0d29bc4SBrooks Davis 
932*b0d29bc4SBrooks Davis /// Constructor.
933*b0d29bc4SBrooks Davis scheduler::scheduler_handle::scheduler_handle(void) : _pimpl(new impl())
934*b0d29bc4SBrooks Davis {
935*b0d29bc4SBrooks Davis }
936*b0d29bc4SBrooks Davis 
937*b0d29bc4SBrooks Davis 
938*b0d29bc4SBrooks Davis /// Destructor.
939*b0d29bc4SBrooks Davis scheduler::scheduler_handle::~scheduler_handle(void)
940*b0d29bc4SBrooks Davis {
941*b0d29bc4SBrooks Davis }
942*b0d29bc4SBrooks Davis 
943*b0d29bc4SBrooks Davis 
944*b0d29bc4SBrooks Davis /// Queries the path to the root of the work directory for all tests.
945*b0d29bc4SBrooks Davis ///
946*b0d29bc4SBrooks Davis /// \return A path.
947*b0d29bc4SBrooks Davis const fs::path&
948*b0d29bc4SBrooks Davis scheduler::scheduler_handle::root_work_directory(void) const
949*b0d29bc4SBrooks Davis {
950*b0d29bc4SBrooks Davis     return _pimpl->generic.root_work_directory();
951*b0d29bc4SBrooks Davis }
952*b0d29bc4SBrooks Davis 
953*b0d29bc4SBrooks Davis 
954*b0d29bc4SBrooks Davis /// Cleans up the scheduler state.
955*b0d29bc4SBrooks Davis ///
956*b0d29bc4SBrooks Davis /// This function should be called explicitly as it provides the means to
957*b0d29bc4SBrooks Davis /// control any exceptions raised during cleanup.  Do not rely on the destructor
958*b0d29bc4SBrooks Davis /// to clean things up.
959*b0d29bc4SBrooks Davis ///
960*b0d29bc4SBrooks Davis /// \throw engine::error If there are problems cleaning up the scheduler.
961*b0d29bc4SBrooks Davis void
962*b0d29bc4SBrooks Davis scheduler::scheduler_handle::cleanup(void)
963*b0d29bc4SBrooks Davis {
964*b0d29bc4SBrooks Davis     _pimpl->generic.cleanup();
965*b0d29bc4SBrooks Davis }
966*b0d29bc4SBrooks Davis 
967*b0d29bc4SBrooks Davis 
968*b0d29bc4SBrooks Davis /// Checks if the given interface name is valid.
969*b0d29bc4SBrooks Davis ///
970*b0d29bc4SBrooks Davis /// \param name The name of the interface to validate.
971*b0d29bc4SBrooks Davis ///
972*b0d29bc4SBrooks Davis /// \throw engine::error If the given interface is not supported.
973*b0d29bc4SBrooks Davis void
974*b0d29bc4SBrooks Davis scheduler::ensure_valid_interface(const std::string& name)
975*b0d29bc4SBrooks Davis {
976*b0d29bc4SBrooks Davis     if (interfaces.find(name) == interfaces.end())
977*b0d29bc4SBrooks Davis         throw engine::error(F("Unsupported test interface '%s'") % name);
978*b0d29bc4SBrooks Davis }
979*b0d29bc4SBrooks Davis 
980*b0d29bc4SBrooks Davis 
981*b0d29bc4SBrooks Davis /// Registers a new interface.
982*b0d29bc4SBrooks Davis ///
983*b0d29bc4SBrooks Davis /// \param name The name of the interface.  Must not have yet been registered.
984*b0d29bc4SBrooks Davis /// \param spec Interface specification.
985*b0d29bc4SBrooks Davis void
986*b0d29bc4SBrooks Davis scheduler::register_interface(const std::string& name,
987*b0d29bc4SBrooks Davis                               const std::shared_ptr< interface > spec)
988*b0d29bc4SBrooks Davis {
989*b0d29bc4SBrooks Davis     PRE(interfaces.find(name) == interfaces.end());
990*b0d29bc4SBrooks Davis     interfaces.insert(interfaces_map::value_type(name, spec));
991*b0d29bc4SBrooks Davis }
992*b0d29bc4SBrooks Davis 
993*b0d29bc4SBrooks Davis 
994*b0d29bc4SBrooks Davis /// Returns the names of all registered interfaces.
995*b0d29bc4SBrooks Davis ///
996*b0d29bc4SBrooks Davis /// \return A collection of interface names.
997*b0d29bc4SBrooks Davis std::set< std::string >
998*b0d29bc4SBrooks Davis scheduler::registered_interface_names(void)
999*b0d29bc4SBrooks Davis {
1000*b0d29bc4SBrooks Davis     std::set< std::string > names;
1001*b0d29bc4SBrooks Davis     for (interfaces_map::const_iterator iter = interfaces.begin();
1002*b0d29bc4SBrooks Davis          iter != interfaces.end(); ++iter) {
1003*b0d29bc4SBrooks Davis         names.insert((*iter).first);
1004*b0d29bc4SBrooks Davis     }
1005*b0d29bc4SBrooks Davis     return names;
1006*b0d29bc4SBrooks Davis }
1007*b0d29bc4SBrooks Davis 
1008*b0d29bc4SBrooks Davis 
1009*b0d29bc4SBrooks Davis /// Initializes the scheduler.
1010*b0d29bc4SBrooks Davis ///
1011*b0d29bc4SBrooks Davis /// \pre This function can only be called if there is no other scheduler_handle
1012*b0d29bc4SBrooks Davis /// object alive.
1013*b0d29bc4SBrooks Davis ///
1014*b0d29bc4SBrooks Davis /// \return A handle to the operations of the scheduler.
1015*b0d29bc4SBrooks Davis scheduler::scheduler_handle
1016*b0d29bc4SBrooks Davis scheduler::setup(void)
1017*b0d29bc4SBrooks Davis {
1018*b0d29bc4SBrooks Davis     return scheduler_handle();
1019*b0d29bc4SBrooks Davis }
1020*b0d29bc4SBrooks Davis 
1021*b0d29bc4SBrooks Davis 
1022*b0d29bc4SBrooks Davis /// Retrieves the list of test cases from a test program.
1023*b0d29bc4SBrooks Davis ///
1024*b0d29bc4SBrooks Davis /// This operation is currently synchronous.
1025*b0d29bc4SBrooks Davis ///
1026*b0d29bc4SBrooks Davis /// This operation should never throw.  Any errors during the processing of the
1027*b0d29bc4SBrooks Davis /// test case list are subsumed into a single test case in the return value that
1028*b0d29bc4SBrooks Davis /// represents the failed retrieval.
1029*b0d29bc4SBrooks Davis ///
1030*b0d29bc4SBrooks Davis /// \param test_program The test program from which to obtain the list of test
1031*b0d29bc4SBrooks Davis /// cases.
1032*b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
1033*b0d29bc4SBrooks Davis ///
1034*b0d29bc4SBrooks Davis /// \return The list of test cases.
1035*b0d29bc4SBrooks Davis model::test_cases_map
1036*b0d29bc4SBrooks Davis scheduler::scheduler_handle::list_tests(
1037*b0d29bc4SBrooks Davis     const model::test_program* test_program,
1038*b0d29bc4SBrooks Davis     const config::tree& user_config)
1039*b0d29bc4SBrooks Davis {
1040*b0d29bc4SBrooks Davis     _pimpl->generic.check_interrupt();
1041*b0d29bc4SBrooks Davis 
1042*b0d29bc4SBrooks Davis     const std::shared_ptr< scheduler::interface > interface = find_interface(
1043*b0d29bc4SBrooks Davis         test_program->interface_name());
1044*b0d29bc4SBrooks Davis 
1045*b0d29bc4SBrooks Davis     try {
1046*b0d29bc4SBrooks Davis         const executor::exec_handle exec_handle = _pimpl->generic.spawn(
1047*b0d29bc4SBrooks Davis             list_test_cases(interface, test_program, user_config),
1048*b0d29bc4SBrooks Davis             list_timeout, none);
1049*b0d29bc4SBrooks Davis         executor::exit_handle exit_handle = _pimpl->generic.wait(exec_handle);
1050*b0d29bc4SBrooks Davis 
1051*b0d29bc4SBrooks Davis         const model::test_cases_map test_cases = interface->parse_list(
1052*b0d29bc4SBrooks Davis             exit_handle.status(),
1053*b0d29bc4SBrooks Davis             exit_handle.stdout_file(),
1054*b0d29bc4SBrooks Davis             exit_handle.stderr_file());
1055*b0d29bc4SBrooks Davis 
1056*b0d29bc4SBrooks Davis         exit_handle.cleanup();
1057*b0d29bc4SBrooks Davis 
1058*b0d29bc4SBrooks Davis         if (test_cases.empty())
1059*b0d29bc4SBrooks Davis             throw std::runtime_error("Empty test cases list");
1060*b0d29bc4SBrooks Davis 
1061*b0d29bc4SBrooks Davis         return test_cases;
1062*b0d29bc4SBrooks Davis     } catch (const std::runtime_error& e) {
1063*b0d29bc4SBrooks Davis         // TODO(jmmv): This is a very ugly workaround for the fact that we
1064*b0d29bc4SBrooks Davis         // cannot report failures at the test-program level.
1065*b0d29bc4SBrooks Davis         LW(F("Failed to load test cases list: %s") % e.what());
1066*b0d29bc4SBrooks Davis         model::test_cases_map fake_test_cases;
1067*b0d29bc4SBrooks Davis         fake_test_cases.insert(model::test_cases_map::value_type(
1068*b0d29bc4SBrooks Davis             "__test_cases_list__",
1069*b0d29bc4SBrooks Davis             model::test_case(
1070*b0d29bc4SBrooks Davis                 "__test_cases_list__",
1071*b0d29bc4SBrooks Davis                 "Represents the correct processing of the test cases list",
1072*b0d29bc4SBrooks Davis                 model::test_result(model::test_result_broken, e.what()))));
1073*b0d29bc4SBrooks Davis         return fake_test_cases;
1074*b0d29bc4SBrooks Davis     }
1075*b0d29bc4SBrooks Davis }
1076*b0d29bc4SBrooks Davis 
1077*b0d29bc4SBrooks Davis 
1078*b0d29bc4SBrooks Davis /// Forks and executes a test case asynchronously.
1079*b0d29bc4SBrooks Davis ///
1080*b0d29bc4SBrooks Davis /// Note that the caller needn't know if the test has a cleanup routine or not.
1081*b0d29bc4SBrooks Davis /// If there indeed is a cleanup routine, we trigger it at wait_any() time.
1082*b0d29bc4SBrooks Davis ///
1083*b0d29bc4SBrooks Davis /// \param test_program The container test program.
1084*b0d29bc4SBrooks Davis /// \param test_case_name The name of the test case to run.
1085*b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
1086*b0d29bc4SBrooks Davis ///
1087*b0d29bc4SBrooks Davis /// \return A handle for the background operation.  Used to match the result of
1088*b0d29bc4SBrooks Davis /// the execution returned by wait_any() with this invocation.
1089*b0d29bc4SBrooks Davis scheduler::exec_handle
1090*b0d29bc4SBrooks Davis scheduler::scheduler_handle::spawn_test(
1091*b0d29bc4SBrooks Davis     const model::test_program_ptr test_program,
1092*b0d29bc4SBrooks Davis     const std::string& test_case_name,
1093*b0d29bc4SBrooks Davis     const config::tree& user_config)
1094*b0d29bc4SBrooks Davis {
1095*b0d29bc4SBrooks Davis     _pimpl->generic.check_interrupt();
1096*b0d29bc4SBrooks Davis 
1097*b0d29bc4SBrooks Davis     const std::shared_ptr< scheduler::interface > interface = find_interface(
1098*b0d29bc4SBrooks Davis         test_program->interface_name());
1099*b0d29bc4SBrooks Davis 
1100*b0d29bc4SBrooks Davis     LI(F("Spawning %s:%s") % test_program->absolute_path() % test_case_name);
1101*b0d29bc4SBrooks Davis 
1102*b0d29bc4SBrooks Davis     const model::test_case& test_case = test_program->find(test_case_name);
1103*b0d29bc4SBrooks Davis 
1104*b0d29bc4SBrooks Davis     optional< passwd::user > unprivileged_user;
1105*b0d29bc4SBrooks Davis     if (user_config.is_set("unprivileged_user") &&
1106*b0d29bc4SBrooks Davis         test_case.get_metadata().required_user() == "unprivileged") {
1107*b0d29bc4SBrooks Davis         unprivileged_user = user_config.lookup< engine::user_node >(
1108*b0d29bc4SBrooks Davis             "unprivileged_user");
1109*b0d29bc4SBrooks Davis     }
1110*b0d29bc4SBrooks Davis 
1111*b0d29bc4SBrooks Davis     const executor::exec_handle handle = _pimpl->generic.spawn(
1112*b0d29bc4SBrooks Davis         run_test_program(interface, test_program, test_case_name,
1113*b0d29bc4SBrooks Davis                          user_config),
1114*b0d29bc4SBrooks Davis         test_case.get_metadata().timeout(),
1115*b0d29bc4SBrooks Davis         unprivileged_user);
1116*b0d29bc4SBrooks Davis 
1117*b0d29bc4SBrooks Davis     const exec_data_ptr data(new test_exec_data(
1118*b0d29bc4SBrooks Davis         test_program, test_case_name, interface, user_config));
1119*b0d29bc4SBrooks Davis     LD(F("Inserting %s into all_exec_data") % handle.pid());
1120*b0d29bc4SBrooks Davis     INV_MSG(
1121*b0d29bc4SBrooks Davis         _pimpl->all_exec_data.find(handle.pid()) == _pimpl->all_exec_data.end(),
1122*b0d29bc4SBrooks Davis         F("PID %s already in all_exec_data; not cleaned up or reused too fast")
1123*b0d29bc4SBrooks Davis         % handle.pid());;
1124*b0d29bc4SBrooks Davis     _pimpl->all_exec_data.insert(exec_data_map::value_type(handle.pid(), data));
1125*b0d29bc4SBrooks Davis 
1126*b0d29bc4SBrooks Davis     return handle.pid();
1127*b0d29bc4SBrooks Davis }
1128*b0d29bc4SBrooks Davis 
1129*b0d29bc4SBrooks Davis 
1130*b0d29bc4SBrooks Davis /// Waits for completion of any forked test case.
1131*b0d29bc4SBrooks Davis ///
1132*b0d29bc4SBrooks Davis /// Note that if the terminated test case has a cleanup routine, this function
1133*b0d29bc4SBrooks Davis /// is the one in charge of spawning the cleanup routine asynchronously.
1134*b0d29bc4SBrooks Davis ///
1135*b0d29bc4SBrooks Davis /// \return The result of the execution of a subprocess.  This is a dynamically
1136*b0d29bc4SBrooks Davis /// allocated object because the scheduler can spawn subprocesses of various
1137*b0d29bc4SBrooks Davis /// types and, at wait time, we don't know upfront what we are going to get.
1138*b0d29bc4SBrooks Davis scheduler::result_handle_ptr
1139*b0d29bc4SBrooks Davis scheduler::scheduler_handle::wait_any(void)
1140*b0d29bc4SBrooks Davis {
1141*b0d29bc4SBrooks Davis     _pimpl->generic.check_interrupt();
1142*b0d29bc4SBrooks Davis 
1143*b0d29bc4SBrooks Davis     executor::exit_handle handle = _pimpl->generic.wait_any();
1144*b0d29bc4SBrooks Davis 
1145*b0d29bc4SBrooks Davis     const exec_data_map::iterator iter = _pimpl->all_exec_data.find(
1146*b0d29bc4SBrooks Davis         handle.original_pid());
1147*b0d29bc4SBrooks Davis     exec_data_ptr data = (*iter).second;
1148*b0d29bc4SBrooks Davis 
1149*b0d29bc4SBrooks Davis     utils::dump_stacktrace_if_available(data->test_program->absolute_path(),
1150*b0d29bc4SBrooks Davis                                         _pimpl->generic, handle);
1151*b0d29bc4SBrooks Davis 
1152*b0d29bc4SBrooks Davis     optional< model::test_result > result;
1153*b0d29bc4SBrooks Davis     try {
1154*b0d29bc4SBrooks Davis         test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
1155*b0d29bc4SBrooks Davis             *data.get());
1156*b0d29bc4SBrooks Davis         LD(F("Got %s from all_exec_data") % handle.original_pid());
1157*b0d29bc4SBrooks Davis 
1158*b0d29bc4SBrooks Davis         test_data->exit_handle = handle;
1159*b0d29bc4SBrooks Davis 
1160*b0d29bc4SBrooks Davis         const model::test_case& test_case = test_data->test_program->find(
1161*b0d29bc4SBrooks Davis             test_data->test_case_name);
1162*b0d29bc4SBrooks Davis 
1163*b0d29bc4SBrooks Davis         result = test_case.fake_result();
1164*b0d29bc4SBrooks Davis 
1165*b0d29bc4SBrooks Davis         if (!result && handle.status() && handle.status().get().exited() &&
1166*b0d29bc4SBrooks Davis             handle.status().get().exitstatus() == exit_skipped) {
1167*b0d29bc4SBrooks Davis             // If the test's process terminated with our magic "exit_skipped"
1168*b0d29bc4SBrooks Davis             // status, there are two cases to handle.  The first is the case
1169*b0d29bc4SBrooks Davis             // where the "skipped cookie" exists, in which case we never got to
1170*b0d29bc4SBrooks Davis             // actually invoke the test program; if that's the case, handle it
1171*b0d29bc4SBrooks Davis             // here.  The second case is where the test case actually decided to
1172*b0d29bc4SBrooks Davis             // exit with the "exit_skipped" status; in that case, just fall back
1173*b0d29bc4SBrooks Davis             // to the regular status handling.
1174*b0d29bc4SBrooks Davis             const fs::path skipped_cookie_path = handle.control_directory() /
1175*b0d29bc4SBrooks Davis                 skipped_cookie;
1176*b0d29bc4SBrooks Davis             std::ifstream input(skipped_cookie_path.c_str());
1177*b0d29bc4SBrooks Davis             if (input) {
1178*b0d29bc4SBrooks Davis                 result = model::test_result(model::test_result_skipped,
1179*b0d29bc4SBrooks Davis                                             utils::read_stream(input));
1180*b0d29bc4SBrooks Davis                 input.close();
1181*b0d29bc4SBrooks Davis 
1182*b0d29bc4SBrooks Davis                 // If we determined that the test needs to be skipped, we do not
1183*b0d29bc4SBrooks Davis                 // want to run the cleanup routine because doing so could result
1184*b0d29bc4SBrooks Davis                 // in errors.  However, we still want to run the cleanup routine
1185*b0d29bc4SBrooks Davis                 // if the test's body reports a skip (because actions could have
1186*b0d29bc4SBrooks Davis                 // already been taken).
1187*b0d29bc4SBrooks Davis                 test_data->needs_cleanup = false;
1188*b0d29bc4SBrooks Davis             }
1189*b0d29bc4SBrooks Davis         }
1190*b0d29bc4SBrooks Davis         if (!result) {
1191*b0d29bc4SBrooks Davis             result = test_data->interface->compute_result(
1192*b0d29bc4SBrooks Davis                 handle.status(),
1193*b0d29bc4SBrooks Davis                 handle.control_directory(),
1194*b0d29bc4SBrooks Davis                 handle.stdout_file(),
1195*b0d29bc4SBrooks Davis                 handle.stderr_file());
1196*b0d29bc4SBrooks Davis         }
1197*b0d29bc4SBrooks Davis         INV(result);
1198*b0d29bc4SBrooks Davis 
1199*b0d29bc4SBrooks Davis         if (!result.get().good()) {
1200*b0d29bc4SBrooks Davis             append_files_listing(handle.work_directory(),
1201*b0d29bc4SBrooks Davis                                  handle.stderr_file());
1202*b0d29bc4SBrooks Davis         }
1203*b0d29bc4SBrooks Davis 
1204*b0d29bc4SBrooks Davis         if (test_data->needs_cleanup) {
1205*b0d29bc4SBrooks Davis             INV(test_case.get_metadata().has_cleanup());
1206*b0d29bc4SBrooks Davis             // The test body has completed and we have processed it.  If there
1207*b0d29bc4SBrooks Davis             // is a cleanup routine, trigger it now and wait for any other test
1208*b0d29bc4SBrooks Davis             // completion.  The caller never knows about cleanup routines.
1209*b0d29bc4SBrooks Davis             _pimpl->spawn_cleanup(test_data->test_program,
1210*b0d29bc4SBrooks Davis                                   test_data->test_case_name,
1211*b0d29bc4SBrooks Davis                                   test_data->user_config, handle, result.get());
1212*b0d29bc4SBrooks Davis             test_data->needs_cleanup = false;
1213*b0d29bc4SBrooks Davis 
1214*b0d29bc4SBrooks Davis             // TODO(jmmv): Chaining this call is ugly.  We'd be better off by
1215*b0d29bc4SBrooks Davis             // looping over terminated processes until we got a result suitable
1216*b0d29bc4SBrooks Davis             // for user consumption.  For the time being this is good enough and
1217*b0d29bc4SBrooks Davis             // not a problem because the call chain won't get big: the majority
1218*b0d29bc4SBrooks Davis             // of test cases do not have cleanup routines.
1219*b0d29bc4SBrooks Davis             return wait_any();
1220*b0d29bc4SBrooks Davis         }
1221*b0d29bc4SBrooks Davis     } catch (const std::bad_cast& e) {
1222*b0d29bc4SBrooks Davis         const cleanup_exec_data* cleanup_data =
1223*b0d29bc4SBrooks Davis             &dynamic_cast< const cleanup_exec_data& >(*data.get());
1224*b0d29bc4SBrooks Davis         LD(F("Got %s from all_exec_data (cleanup)") % handle.original_pid());
1225*b0d29bc4SBrooks Davis 
1226*b0d29bc4SBrooks Davis         // Handle the completion of cleanup subprocesses internally: the caller
1227*b0d29bc4SBrooks Davis         // is not aware that these exist so, when we return, we must return the
1228*b0d29bc4SBrooks Davis         // data for the original test that triggered this routine.  For example,
1229*b0d29bc4SBrooks Davis         // because the caller wants to see the exact same exec_handle that was
1230*b0d29bc4SBrooks Davis         // returned by spawn_test.
1231*b0d29bc4SBrooks Davis 
1232*b0d29bc4SBrooks Davis         const model::test_result& body_result = cleanup_data->body_result;
1233*b0d29bc4SBrooks Davis         if (body_result.good()) {
1234*b0d29bc4SBrooks Davis             if (!handle.status()) {
1235*b0d29bc4SBrooks Davis                 result = model::test_result(model::test_result_broken,
1236*b0d29bc4SBrooks Davis                                             "Test case cleanup timed out");
1237*b0d29bc4SBrooks Davis             } else {
1238*b0d29bc4SBrooks Davis                 if (!handle.status().get().exited() ||
1239*b0d29bc4SBrooks Davis                     handle.status().get().exitstatus() != EXIT_SUCCESS) {
1240*b0d29bc4SBrooks Davis                     result = model::test_result(
1241*b0d29bc4SBrooks Davis                         model::test_result_broken,
1242*b0d29bc4SBrooks Davis                         "Test case cleanup did not terminate successfully");
1243*b0d29bc4SBrooks Davis                 } else {
1244*b0d29bc4SBrooks Davis                     result = body_result;
1245*b0d29bc4SBrooks Davis                 }
1246*b0d29bc4SBrooks Davis             }
1247*b0d29bc4SBrooks Davis         } else {
1248*b0d29bc4SBrooks Davis             result = body_result;
1249*b0d29bc4SBrooks Davis         }
1250*b0d29bc4SBrooks Davis 
1251*b0d29bc4SBrooks Davis         // Untrack the cleanup process.  This must be done explicitly because we
1252*b0d29bc4SBrooks Davis         // do not create a result_handle object for the cleanup, and that is the
1253*b0d29bc4SBrooks Davis         // one in charge of doing so in the regular (non-cleanup) case.
1254*b0d29bc4SBrooks Davis         LD(F("Removing %s from all_exec_data (cleanup) in favor of %s")
1255*b0d29bc4SBrooks Davis            % handle.original_pid()
1256*b0d29bc4SBrooks Davis            % cleanup_data->body_exit_handle.original_pid());
1257*b0d29bc4SBrooks Davis         _pimpl->all_exec_data.erase(handle.original_pid());
1258*b0d29bc4SBrooks Davis 
1259*b0d29bc4SBrooks Davis         handle = cleanup_data->body_exit_handle;
1260*b0d29bc4SBrooks Davis     }
1261*b0d29bc4SBrooks Davis     INV(result);
1262*b0d29bc4SBrooks Davis 
1263*b0d29bc4SBrooks Davis     std::shared_ptr< result_handle::bimpl > result_handle_bimpl(
1264*b0d29bc4SBrooks Davis         new result_handle::bimpl(handle, _pimpl->all_exec_data));
1265*b0d29bc4SBrooks Davis     std::shared_ptr< test_result_handle::impl > test_result_handle_impl(
1266*b0d29bc4SBrooks Davis         new test_result_handle::impl(
1267*b0d29bc4SBrooks Davis             data->test_program, data->test_case_name, result.get()));
1268*b0d29bc4SBrooks Davis     return result_handle_ptr(new test_result_handle(result_handle_bimpl,
1269*b0d29bc4SBrooks Davis                                                     test_result_handle_impl));
1270*b0d29bc4SBrooks Davis }
1271*b0d29bc4SBrooks Davis 
1272*b0d29bc4SBrooks Davis 
1273*b0d29bc4SBrooks Davis /// Forks and executes a test case synchronously for debugging.
1274*b0d29bc4SBrooks Davis ///
1275*b0d29bc4SBrooks Davis /// \pre No other processes should be in execution by the scheduler.
1276*b0d29bc4SBrooks Davis ///
1277*b0d29bc4SBrooks Davis /// \param test_program The container test program.
1278*b0d29bc4SBrooks Davis /// \param test_case_name The name of the test case to run.
1279*b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
1280*b0d29bc4SBrooks Davis /// \param stdout_target File to which to write the stdout of the test case.
1281*b0d29bc4SBrooks Davis /// \param stderr_target File to which to write the stderr of the test case.
1282*b0d29bc4SBrooks Davis ///
1283*b0d29bc4SBrooks Davis /// \return The result of the execution of the test.
1284*b0d29bc4SBrooks Davis scheduler::result_handle_ptr
1285*b0d29bc4SBrooks Davis scheduler::scheduler_handle::debug_test(
1286*b0d29bc4SBrooks Davis     const model::test_program_ptr test_program,
1287*b0d29bc4SBrooks Davis     const std::string& test_case_name,
1288*b0d29bc4SBrooks Davis     const config::tree& user_config,
1289*b0d29bc4SBrooks Davis     const fs::path& stdout_target,
1290*b0d29bc4SBrooks Davis     const fs::path& stderr_target)
1291*b0d29bc4SBrooks Davis {
1292*b0d29bc4SBrooks Davis     const exec_handle exec_handle = spawn_test(
1293*b0d29bc4SBrooks Davis         test_program, test_case_name, user_config);
1294*b0d29bc4SBrooks Davis     result_handle_ptr result_handle = wait_any();
1295*b0d29bc4SBrooks Davis 
1296*b0d29bc4SBrooks Davis     // TODO(jmmv): We need to do this while the subprocess is alive.  This is
1297*b0d29bc4SBrooks Davis     // important for debugging purposes, as we should see the contents of stdout
1298*b0d29bc4SBrooks Davis     // or stderr as they come in.
1299*b0d29bc4SBrooks Davis     //
1300*b0d29bc4SBrooks Davis     // Unfortunately, we cannot do so.  We cannot just read and block from a
1301*b0d29bc4SBrooks Davis     // file, waiting for further output to appear... as this only works on pipes
1302*b0d29bc4SBrooks Davis     // or sockets.  We need a better interface for this whole thing.
1303*b0d29bc4SBrooks Davis     {
1304*b0d29bc4SBrooks Davis         std::auto_ptr< std::ostream > output = utils::open_ostream(
1305*b0d29bc4SBrooks Davis             stdout_target);
1306*b0d29bc4SBrooks Davis         *output << utils::read_file(result_handle->stdout_file());
1307*b0d29bc4SBrooks Davis     }
1308*b0d29bc4SBrooks Davis     {
1309*b0d29bc4SBrooks Davis         std::auto_ptr< std::ostream > output = utils::open_ostream(
1310*b0d29bc4SBrooks Davis             stderr_target);
1311*b0d29bc4SBrooks Davis         *output << utils::read_file(result_handle->stderr_file());
1312*b0d29bc4SBrooks Davis     }
1313*b0d29bc4SBrooks Davis 
1314*b0d29bc4SBrooks Davis     INV(result_handle->original_pid() == exec_handle);
1315*b0d29bc4SBrooks Davis     return result_handle;
1316*b0d29bc4SBrooks Davis }
1317*b0d29bc4SBrooks Davis 
1318*b0d29bc4SBrooks Davis 
1319*b0d29bc4SBrooks Davis /// Checks if an interrupt has fired.
1320*b0d29bc4SBrooks Davis ///
1321*b0d29bc4SBrooks Davis /// Calls to this function should be sprinkled in strategic places through the
1322*b0d29bc4SBrooks Davis /// code protected by an interrupts_handler object.
1323*b0d29bc4SBrooks Davis ///
1324*b0d29bc4SBrooks Davis /// This is just a wrapper over signals::check_interrupt() to avoid leaking this
1325*b0d29bc4SBrooks Davis /// dependency to the caller.
1326*b0d29bc4SBrooks Davis ///
1327*b0d29bc4SBrooks Davis /// \throw signals::interrupted_error If there has been an interrupt.
1328*b0d29bc4SBrooks Davis void
1329*b0d29bc4SBrooks Davis scheduler::scheduler_handle::check_interrupt(void) const
1330*b0d29bc4SBrooks Davis {
1331*b0d29bc4SBrooks Davis     _pimpl->generic.check_interrupt();
1332*b0d29bc4SBrooks Davis }
1333*b0d29bc4SBrooks Davis 
1334*b0d29bc4SBrooks Davis 
1335*b0d29bc4SBrooks Davis /// Queries the current execution context.
1336*b0d29bc4SBrooks Davis ///
1337*b0d29bc4SBrooks Davis /// \return The queried context.
1338*b0d29bc4SBrooks Davis model::context
1339*b0d29bc4SBrooks Davis scheduler::current_context(void)
1340*b0d29bc4SBrooks Davis {
1341*b0d29bc4SBrooks Davis     return model::context(fs::current_path(), utils::getallenv());
1342*b0d29bc4SBrooks Davis }
1343*b0d29bc4SBrooks Davis 
1344*b0d29bc4SBrooks Davis 
1345*b0d29bc4SBrooks Davis /// Generates the set of configuration variables for a test program.
1346*b0d29bc4SBrooks Davis ///
1347*b0d29bc4SBrooks Davis /// \param user_config The configuration variables provided by the user.
1348*b0d29bc4SBrooks Davis /// \param test_suite The name of the test suite.
1349*b0d29bc4SBrooks Davis ///
1350*b0d29bc4SBrooks Davis /// \return The mapping of configuration variables for the test program.
1351*b0d29bc4SBrooks Davis config::properties_map
1352*b0d29bc4SBrooks Davis scheduler::generate_config(const config::tree& user_config,
1353*b0d29bc4SBrooks Davis                            const std::string& test_suite)
1354*b0d29bc4SBrooks Davis {
1355*b0d29bc4SBrooks Davis     config::properties_map props;
1356*b0d29bc4SBrooks Davis 
1357*b0d29bc4SBrooks Davis     try {
1358*b0d29bc4SBrooks Davis         props = user_config.all_properties(F("test_suites.%s") % test_suite,
1359*b0d29bc4SBrooks Davis                                            true);
1360*b0d29bc4SBrooks Davis     } catch (const config::unknown_key_error& unused_error) {
1361*b0d29bc4SBrooks Davis         // Ignore: not all test suites have entries in the configuration.
1362*b0d29bc4SBrooks Davis     }
1363*b0d29bc4SBrooks Davis 
1364*b0d29bc4SBrooks Davis     // TODO(jmmv): This is a hack that exists for the ATF interface only, so it
1365*b0d29bc4SBrooks Davis     // should be moved there.
1366*b0d29bc4SBrooks Davis     if (user_config.is_set("unprivileged_user")) {
1367*b0d29bc4SBrooks Davis         const passwd::user& user =
1368*b0d29bc4SBrooks Davis             user_config.lookup< engine::user_node >("unprivileged_user");
1369*b0d29bc4SBrooks Davis         props["unprivileged-user"] = user.name;
1370*b0d29bc4SBrooks Davis     }
1371*b0d29bc4SBrooks Davis 
1372*b0d29bc4SBrooks Davis     return props;
1373*b0d29bc4SBrooks Davis }
1374