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/scheduler.hpp"
30b0d29bc4SBrooks Davis
31b0d29bc4SBrooks Davis extern "C" {
32b0d29bc4SBrooks Davis #include <unistd.h>
33b0d29bc4SBrooks Davis }
34b0d29bc4SBrooks Davis
35b0d29bc4SBrooks Davis #include <cstdio>
36b0d29bc4SBrooks Davis #include <cstdlib>
37b0d29bc4SBrooks Davis #include <fstream>
38b0d29bc4SBrooks Davis #include <memory>
39b0d29bc4SBrooks Davis #include <stdexcept>
40b0d29bc4SBrooks Davis
41b0d29bc4SBrooks Davis #include "engine/config.hpp"
42b0d29bc4SBrooks Davis #include "engine/exceptions.hpp"
43257e70f1SIgor Ostapenko #include "engine/execenv/execenv.hpp"
44b0d29bc4SBrooks Davis #include "engine/requirements.hpp"
45b0d29bc4SBrooks Davis #include "model/context.hpp"
46b0d29bc4SBrooks Davis #include "model/metadata.hpp"
47b0d29bc4SBrooks Davis #include "model/test_case.hpp"
48b0d29bc4SBrooks Davis #include "model/test_program.hpp"
49b0d29bc4SBrooks Davis #include "model/test_result.hpp"
50b0d29bc4SBrooks Davis #include "utils/config/tree.ipp"
51b0d29bc4SBrooks Davis #include "utils/datetime.hpp"
52b0d29bc4SBrooks Davis #include "utils/defs.hpp"
53b0d29bc4SBrooks Davis #include "utils/env.hpp"
54b0d29bc4SBrooks Davis #include "utils/format/macros.hpp"
55b0d29bc4SBrooks Davis #include "utils/fs/directory.hpp"
56b0d29bc4SBrooks Davis #include "utils/fs/exceptions.hpp"
57b0d29bc4SBrooks Davis #include "utils/fs/operations.hpp"
58b0d29bc4SBrooks Davis #include "utils/fs/path.hpp"
59b0d29bc4SBrooks Davis #include "utils/logging/macros.hpp"
60b0d29bc4SBrooks Davis #include "utils/noncopyable.hpp"
61b0d29bc4SBrooks Davis #include "utils/optional.ipp"
62b0d29bc4SBrooks Davis #include "utils/passwd.hpp"
63b0d29bc4SBrooks Davis #include "utils/process/executor.ipp"
64b0d29bc4SBrooks Davis #include "utils/process/status.hpp"
65b0d29bc4SBrooks Davis #include "utils/sanity.hpp"
66b0d29bc4SBrooks Davis #include "utils/stacktrace.hpp"
67b0d29bc4SBrooks Davis #include "utils/stream.hpp"
68b0d29bc4SBrooks Davis #include "utils/text/operations.ipp"
69b0d29bc4SBrooks Davis
70b0d29bc4SBrooks Davis namespace config = utils::config;
71b0d29bc4SBrooks Davis namespace datetime = utils::datetime;
72257e70f1SIgor Ostapenko namespace execenv = engine::execenv;
73b0d29bc4SBrooks Davis namespace executor = utils::process::executor;
74b0d29bc4SBrooks Davis namespace fs = utils::fs;
75b0d29bc4SBrooks Davis namespace logging = utils::logging;
76b0d29bc4SBrooks Davis namespace passwd = utils::passwd;
77b0d29bc4SBrooks Davis namespace process = utils::process;
78b0d29bc4SBrooks Davis namespace scheduler = engine::scheduler;
79b0d29bc4SBrooks Davis namespace text = utils::text;
80b0d29bc4SBrooks Davis
81b0d29bc4SBrooks Davis using utils::none;
82b0d29bc4SBrooks Davis using utils::optional;
83b0d29bc4SBrooks Davis
84b0d29bc4SBrooks Davis
85b0d29bc4SBrooks Davis /// Timeout for the test case cleanup operation.
86b0d29bc4SBrooks Davis ///
87b0d29bc4SBrooks Davis /// TODO(jmmv): This is here only for testing purposes. Maybe we should expose
88b0d29bc4SBrooks Davis /// this setting as part of the user_config.
89b0d29bc4SBrooks Davis datetime::delta scheduler::cleanup_timeout(60, 0);
90b0d29bc4SBrooks Davis
91b0d29bc4SBrooks Davis
92257e70f1SIgor Ostapenko /// Timeout for the test case execenv cleanup operation.
93257e70f1SIgor Ostapenko datetime::delta scheduler::execenv_cleanup_timeout(60, 0);
94257e70f1SIgor Ostapenko
95257e70f1SIgor Ostapenko
96b0d29bc4SBrooks Davis /// Timeout for the test case listing operation.
97b0d29bc4SBrooks Davis ///
98b0d29bc4SBrooks Davis /// TODO(jmmv): This is here only for testing purposes. Maybe we should expose
99b0d29bc4SBrooks Davis /// this setting as part of the user_config.
100b0d29bc4SBrooks Davis datetime::delta scheduler::list_timeout(300, 0);
101b0d29bc4SBrooks Davis
102b0d29bc4SBrooks Davis
103b0d29bc4SBrooks Davis namespace {
104b0d29bc4SBrooks Davis
105b0d29bc4SBrooks Davis
106b0d29bc4SBrooks Davis /// Magic exit status to indicate that the test case was probably skipped.
107b0d29bc4SBrooks Davis ///
108b0d29bc4SBrooks Davis /// The test case was only skipped if and only if we return this exit code and
109b0d29bc4SBrooks Davis /// we find the skipped_cookie file on disk.
110b0d29bc4SBrooks Davis static const int exit_skipped = 84;
111b0d29bc4SBrooks Davis
112b0d29bc4SBrooks Davis
113b0d29bc4SBrooks Davis /// Text file containing the skip reason for the test case.
114b0d29bc4SBrooks Davis ///
115b0d29bc4SBrooks Davis /// This will only be present within unique_work_directory if the test case
116b0d29bc4SBrooks Davis /// exited with the exit_skipped code. However, there is no guarantee that the
117b0d29bc4SBrooks Davis /// file is there (say if the test really decided to exit with code exit_skipped
118b0d29bc4SBrooks Davis /// on its own).
119b0d29bc4SBrooks Davis static const char* skipped_cookie = "skipped.txt";
120b0d29bc4SBrooks Davis
121b0d29bc4SBrooks Davis
122b0d29bc4SBrooks Davis /// Mapping of interface names to interface definitions.
123b0d29bc4SBrooks Davis typedef std::map< std::string, std::shared_ptr< scheduler::interface > >
124b0d29bc4SBrooks Davis interfaces_map;
125b0d29bc4SBrooks Davis
126b0d29bc4SBrooks Davis
127b0d29bc4SBrooks Davis /// Mapping of interface names to interface definitions.
128b0d29bc4SBrooks Davis ///
129b0d29bc4SBrooks Davis /// Use register_interface() to add an entry to this global table.
130b0d29bc4SBrooks Davis static interfaces_map interfaces;
131b0d29bc4SBrooks Davis
132b0d29bc4SBrooks Davis
133b0d29bc4SBrooks Davis /// Scans the contents of a directory and appends the file listing to a file.
134b0d29bc4SBrooks Davis ///
135b0d29bc4SBrooks Davis /// \param dir_path The directory to scan.
136b0d29bc4SBrooks Davis /// \param output_file The file to which to append the listing.
137b0d29bc4SBrooks Davis ///
138b0d29bc4SBrooks Davis /// \throw engine::error If there are problems listing the files.
139b0d29bc4SBrooks Davis static void
append_files_listing(const fs::path & dir_path,const fs::path & output_file)140b0d29bc4SBrooks Davis append_files_listing(const fs::path& dir_path, const fs::path& output_file)
141b0d29bc4SBrooks Davis {
142b0d29bc4SBrooks Davis std::ofstream output(output_file.c_str(), std::ios::app);
143b0d29bc4SBrooks Davis if (!output)
144b0d29bc4SBrooks Davis throw engine::error(F("Failed to open output file %s for append")
145b0d29bc4SBrooks Davis % output_file);
146b0d29bc4SBrooks Davis try {
147b0d29bc4SBrooks Davis std::set < std::string > names;
148b0d29bc4SBrooks Davis
149b0d29bc4SBrooks Davis const fs::directory dir(dir_path);
150b0d29bc4SBrooks Davis for (fs::directory::const_iterator iter = dir.begin();
151b0d29bc4SBrooks Davis iter != dir.end(); ++iter) {
152b0d29bc4SBrooks Davis if (iter->name != "." && iter->name != "..")
153b0d29bc4SBrooks Davis names.insert(iter->name);
154b0d29bc4SBrooks Davis }
155b0d29bc4SBrooks Davis
156b0d29bc4SBrooks Davis if (!names.empty()) {
157b0d29bc4SBrooks Davis output << "Files left in work directory after failure: "
158b0d29bc4SBrooks Davis << text::join(names, ", ") << '\n';
159b0d29bc4SBrooks Davis }
160b0d29bc4SBrooks Davis } catch (const fs::error& e) {
161b0d29bc4SBrooks Davis throw engine::error(F("Cannot append files listing to %s: %s")
162b0d29bc4SBrooks Davis % output_file % e.what());
163b0d29bc4SBrooks Davis }
164b0d29bc4SBrooks Davis }
165b0d29bc4SBrooks Davis
166b0d29bc4SBrooks Davis
167b0d29bc4SBrooks Davis /// Maintenance data held while a test is being executed.
168b0d29bc4SBrooks Davis ///
169b0d29bc4SBrooks Davis /// This data structure exists from the moment when a test is executed via
170b0d29bc4SBrooks Davis /// scheduler::spawn_test() or scheduler::impl::spawn_cleanup() to when it is
171b0d29bc4SBrooks Davis /// cleaned up with result_handle::cleanup().
172b0d29bc4SBrooks Davis ///
173b0d29bc4SBrooks Davis /// This is a base data type intended to be extended for the test and cleanup
174b0d29bc4SBrooks Davis /// cases so that each contains only the relevant data.
175b0d29bc4SBrooks Davis struct exec_data : utils::noncopyable {
176b0d29bc4SBrooks Davis /// Test program data for this test case.
177b0d29bc4SBrooks Davis const model::test_program_ptr test_program;
178b0d29bc4SBrooks Davis
179b0d29bc4SBrooks Davis /// Name of the test case.
180b0d29bc4SBrooks Davis const std::string test_case_name;
181b0d29bc4SBrooks Davis
182b0d29bc4SBrooks Davis /// Constructor.
183b0d29bc4SBrooks Davis ///
184b0d29bc4SBrooks Davis /// \param test_program_ Test program data for this test case.
185b0d29bc4SBrooks Davis /// \param test_case_name_ Name of the test case.
exec_data__anonced074df0111::exec_data186b0d29bc4SBrooks Davis exec_data(const model::test_program_ptr test_program_,
187b0d29bc4SBrooks Davis const std::string& test_case_name_) :
188b0d29bc4SBrooks Davis test_program(test_program_), test_case_name(test_case_name_)
189b0d29bc4SBrooks Davis {
190b0d29bc4SBrooks Davis }
191b0d29bc4SBrooks Davis
192b0d29bc4SBrooks Davis /// Destructor.
~exec_data__anonced074df0111::exec_data193b0d29bc4SBrooks Davis virtual ~exec_data(void)
194b0d29bc4SBrooks Davis {
195b0d29bc4SBrooks Davis }
196b0d29bc4SBrooks Davis };
197b0d29bc4SBrooks Davis
198b0d29bc4SBrooks Davis
199b0d29bc4SBrooks Davis /// Maintenance data held while a test is being executed.
200b0d29bc4SBrooks Davis struct test_exec_data : public exec_data {
201b0d29bc4SBrooks Davis /// Test program-specific execution interface.
202b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface;
203b0d29bc4SBrooks Davis
204b0d29bc4SBrooks Davis /// User configuration passed to the execution of the test. We need this
205b0d29bc4SBrooks Davis /// here to recover it later when chaining the execution of a cleanup
206b0d29bc4SBrooks Davis /// routine (if any).
207b0d29bc4SBrooks Davis const config::tree user_config;
208b0d29bc4SBrooks Davis
209b0d29bc4SBrooks Davis /// Whether this test case still needs to have its cleanup routine executed.
210b0d29bc4SBrooks Davis ///
211b0d29bc4SBrooks Davis /// This is set externally when the cleanup routine is actually invoked to
212b0d29bc4SBrooks Davis /// denote that no further attempts shall be made at cleaning this up.
213b0d29bc4SBrooks Davis bool needs_cleanup;
214b0d29bc4SBrooks Davis
215257e70f1SIgor Ostapenko /// Whether this test case still needs to have its execenv cleanup executed.
216257e70f1SIgor Ostapenko ///
217257e70f1SIgor Ostapenko /// This is set externally when the cleanup routine is actually invoked to
218257e70f1SIgor Ostapenko /// denote that no further attempts shall be made at cleaning this up.
219257e70f1SIgor Ostapenko bool needs_execenv_cleanup;
220257e70f1SIgor Ostapenko
221257e70f1SIgor Ostapenko /// Original PID of the test case subprocess.
222257e70f1SIgor Ostapenko ///
223257e70f1SIgor Ostapenko /// This is used for the cleanup upon termination by a signal, to reap the
224257e70f1SIgor Ostapenko /// leftovers and form missing exit_handle.
225257e70f1SIgor Ostapenko pid_t pid;
226257e70f1SIgor Ostapenko
227b0d29bc4SBrooks Davis /// The exit_handle for this test once it has completed.
228b0d29bc4SBrooks Davis ///
229b0d29bc4SBrooks Davis /// This is set externally when the test case has finished, as we need this
230b0d29bc4SBrooks Davis /// information to invoke the followup cleanup routine in the right context,
231b0d29bc4SBrooks Davis /// as indicated by needs_cleanup.
232b0d29bc4SBrooks Davis optional< executor::exit_handle > exit_handle;
233b0d29bc4SBrooks Davis
234b0d29bc4SBrooks Davis /// Constructor.
235b0d29bc4SBrooks Davis ///
236b0d29bc4SBrooks Davis /// \param test_program_ Test program data for this test case.
237b0d29bc4SBrooks Davis /// \param test_case_name_ Name of the test case.
238b0d29bc4SBrooks Davis /// \param interface_ Test program-specific execution interface.
239b0d29bc4SBrooks Davis /// \param user_config_ User configuration passed to the test.
test_exec_data__anonced074df0111::test_exec_data240b0d29bc4SBrooks Davis test_exec_data(const model::test_program_ptr test_program_,
241b0d29bc4SBrooks Davis const std::string& test_case_name_,
242b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface_,
243257e70f1SIgor Ostapenko const config::tree& user_config_,
244257e70f1SIgor Ostapenko const pid_t pid_) :
245b0d29bc4SBrooks Davis exec_data(test_program_, test_case_name_),
246257e70f1SIgor Ostapenko interface(interface_), user_config(user_config_), pid(pid_)
247b0d29bc4SBrooks Davis {
248b0d29bc4SBrooks Davis const model::test_case& test_case = test_program->find(test_case_name);
249b0d29bc4SBrooks Davis needs_cleanup = test_case.get_metadata().has_cleanup();
250257e70f1SIgor Ostapenko needs_execenv_cleanup = test_case.get_metadata().has_execenv();
251b0d29bc4SBrooks Davis }
252b0d29bc4SBrooks Davis };
253b0d29bc4SBrooks Davis
254b0d29bc4SBrooks Davis
255b0d29bc4SBrooks Davis /// Maintenance data held while a test cleanup routine is being executed.
256b0d29bc4SBrooks Davis ///
257b0d29bc4SBrooks Davis /// Instances of this object are related to a previous test_exec_data, as
258b0d29bc4SBrooks Davis /// cleanup routines can only exist once the test has been run.
259b0d29bc4SBrooks Davis struct cleanup_exec_data : public exec_data {
260b0d29bc4SBrooks Davis /// The exit handle of the test. This is necessary so that we can return
261b0d29bc4SBrooks Davis /// the correct exit_handle to the user of the scheduler.
262b0d29bc4SBrooks Davis executor::exit_handle body_exit_handle;
263b0d29bc4SBrooks Davis
264b0d29bc4SBrooks Davis /// The final result of the test's body. This is necessary to compute the
265b0d29bc4SBrooks Davis /// right return value for a test with a cleanup routine: the body result is
266b0d29bc4SBrooks Davis /// respected if it is a "bad" result; else the result of the cleanup
267b0d29bc4SBrooks Davis /// routine is used if it has failed.
268b0d29bc4SBrooks Davis model::test_result body_result;
269b0d29bc4SBrooks Davis
270b0d29bc4SBrooks Davis /// Constructor.
271b0d29bc4SBrooks Davis ///
272b0d29bc4SBrooks Davis /// \param test_program_ Test program data for this test case.
273b0d29bc4SBrooks Davis /// \param test_case_name_ Name of the test case.
274b0d29bc4SBrooks Davis /// \param body_exit_handle_ If not none, exit handle of the body
275b0d29bc4SBrooks Davis /// corresponding to the cleanup routine represented by this exec_data.
276b0d29bc4SBrooks Davis /// \param body_result_ If not none, result of the body corresponding to the
277b0d29bc4SBrooks Davis /// cleanup routine represented by this exec_data.
cleanup_exec_data__anonced074df0111::cleanup_exec_data278b0d29bc4SBrooks Davis cleanup_exec_data(const model::test_program_ptr test_program_,
279b0d29bc4SBrooks Davis const std::string& test_case_name_,
280b0d29bc4SBrooks Davis const executor::exit_handle& body_exit_handle_,
281b0d29bc4SBrooks Davis const model::test_result& body_result_) :
282b0d29bc4SBrooks Davis exec_data(test_program_, test_case_name_),
283b0d29bc4SBrooks Davis body_exit_handle(body_exit_handle_), body_result(body_result_)
284b0d29bc4SBrooks Davis {
285b0d29bc4SBrooks Davis }
286b0d29bc4SBrooks Davis };
287b0d29bc4SBrooks Davis
288b0d29bc4SBrooks Davis
289257e70f1SIgor Ostapenko /// Maintenance data held while a test execenv cleanup is being executed.
290257e70f1SIgor Ostapenko ///
291257e70f1SIgor Ostapenko /// Instances of this object are related to a previous test_exec_data, as
292257e70f1SIgor Ostapenko /// cleanup routines can only exist once the test has been run.
293257e70f1SIgor Ostapenko struct execenv_exec_data : public exec_data {
294257e70f1SIgor Ostapenko /// The exit handle of the test. This is necessary so that we can return
295257e70f1SIgor Ostapenko /// the correct exit_handle to the user of the scheduler.
296257e70f1SIgor Ostapenko executor::exit_handle body_exit_handle;
297257e70f1SIgor Ostapenko
298257e70f1SIgor Ostapenko /// The final result of the test's body. This is necessary to compute the
299257e70f1SIgor Ostapenko /// right return value for a test with a cleanup routine: the body result is
300257e70f1SIgor Ostapenko /// respected if it is a "bad" result; else the result of the cleanup
301257e70f1SIgor Ostapenko /// routine is used if it has failed.
302257e70f1SIgor Ostapenko model::test_result body_result;
303257e70f1SIgor Ostapenko
304257e70f1SIgor Ostapenko /// Constructor.
305257e70f1SIgor Ostapenko ///
306257e70f1SIgor Ostapenko /// \param test_program_ Test program data for this test case.
307257e70f1SIgor Ostapenko /// \param test_case_name_ Name of the test case.
308257e70f1SIgor Ostapenko /// \param body_exit_handle_ If not none, exit handle of the body
309257e70f1SIgor Ostapenko /// corresponding to the cleanup routine represented by this exec_data.
310257e70f1SIgor Ostapenko /// \param body_result_ If not none, result of the body corresponding to the
311257e70f1SIgor Ostapenko /// cleanup routine represented by this exec_data.
execenv_exec_data__anonced074df0111::execenv_exec_data312257e70f1SIgor Ostapenko execenv_exec_data(const model::test_program_ptr test_program_,
313257e70f1SIgor Ostapenko const std::string& test_case_name_,
314257e70f1SIgor Ostapenko const executor::exit_handle& body_exit_handle_,
315257e70f1SIgor Ostapenko const model::test_result& body_result_) :
316257e70f1SIgor Ostapenko exec_data(test_program_, test_case_name_),
317257e70f1SIgor Ostapenko body_exit_handle(body_exit_handle_), body_result(body_result_)
318257e70f1SIgor Ostapenko {
319257e70f1SIgor Ostapenko }
320257e70f1SIgor Ostapenko };
321257e70f1SIgor Ostapenko
322257e70f1SIgor Ostapenko
323b0d29bc4SBrooks Davis /// Shared pointer to exec_data.
324b0d29bc4SBrooks Davis ///
325b0d29bc4SBrooks Davis /// We require this because we want exec_data to not be copyable, and thus we
326b0d29bc4SBrooks Davis /// cannot just store it in the map without move constructors.
327b0d29bc4SBrooks Davis typedef std::shared_ptr< exec_data > exec_data_ptr;
328b0d29bc4SBrooks Davis
329b0d29bc4SBrooks Davis
330b0d29bc4SBrooks Davis /// Mapping of active PIDs to their maintenance data.
331b0d29bc4SBrooks Davis typedef std::map< int, exec_data_ptr > exec_data_map;
332b0d29bc4SBrooks Davis
333b0d29bc4SBrooks Davis
334b0d29bc4SBrooks Davis /// Enforces a test program to hold an absolute path.
335b0d29bc4SBrooks Davis ///
336b0d29bc4SBrooks Davis /// TODO(jmmv): This function (which is a pretty ugly hack) exists because we
337b0d29bc4SBrooks Davis /// want the interface hooks to receive a test_program as their argument.
338b0d29bc4SBrooks Davis /// However, those hooks run after the test program has been isolated, which
339b0d29bc4SBrooks Davis /// means that the current directory has changed since when the test_program
340b0d29bc4SBrooks Davis /// objects were created. This causes the absolute_path() method of
341b0d29bc4SBrooks Davis /// test_program to return bogus values if the internal representation of their
342b0d29bc4SBrooks Davis /// path is relative. We should fix somehow: maybe making the fs module grab
343b0d29bc4SBrooks Davis /// its "current_path" view at program startup time; or maybe by grabbing the
344b0d29bc4SBrooks Davis /// current path at test_program creation time; or maybe something else.
345b0d29bc4SBrooks Davis ///
346b0d29bc4SBrooks Davis /// \param program The test program to modify.
347b0d29bc4SBrooks Davis ///
348b0d29bc4SBrooks Davis /// \return A new test program whose internal paths are absolute.
349b0d29bc4SBrooks Davis static model::test_program
force_absolute_paths(const model::test_program program)350b0d29bc4SBrooks Davis force_absolute_paths(const model::test_program program)
351b0d29bc4SBrooks Davis {
352b0d29bc4SBrooks Davis const std::string& relative = program.relative_path().str();
353b0d29bc4SBrooks Davis const std::string absolute = program.absolute_path().str();
354b0d29bc4SBrooks Davis
355b0d29bc4SBrooks Davis const std::string root = absolute.substr(
356b0d29bc4SBrooks Davis 0, absolute.length() - relative.length());
357b0d29bc4SBrooks Davis
358b0d29bc4SBrooks Davis return model::test_program(
359b0d29bc4SBrooks Davis program.interface_name(),
360b0d29bc4SBrooks Davis program.relative_path(), fs::path(root),
361b0d29bc4SBrooks Davis program.test_suite_name(),
362b0d29bc4SBrooks Davis program.get_metadata(), program.test_cases());
363b0d29bc4SBrooks Davis }
364b0d29bc4SBrooks Davis
365b0d29bc4SBrooks Davis
366b0d29bc4SBrooks Davis /// Functor to list the test cases of a test program.
367b0d29bc4SBrooks Davis class list_test_cases {
368b0d29bc4SBrooks Davis /// Interface of the test program to execute.
369b0d29bc4SBrooks Davis std::shared_ptr< scheduler::interface > _interface;
370b0d29bc4SBrooks Davis
371b0d29bc4SBrooks Davis /// Test program to execute.
372b0d29bc4SBrooks Davis const model::test_program _test_program;
373b0d29bc4SBrooks Davis
374b0d29bc4SBrooks Davis /// User-provided configuration variables.
375b0d29bc4SBrooks Davis const config::tree& _user_config;
376b0d29bc4SBrooks Davis
377b0d29bc4SBrooks Davis public:
378b0d29bc4SBrooks Davis /// Constructor.
379b0d29bc4SBrooks Davis ///
380b0d29bc4SBrooks Davis /// \param interface Interface of the test program to execute.
381b0d29bc4SBrooks Davis /// \param test_program Test program to execute.
382b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
list_test_cases(const std::shared_ptr<scheduler::interface> interface,const model::test_program * test_program,const config::tree & user_config)383b0d29bc4SBrooks Davis list_test_cases(
384b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface,
385b0d29bc4SBrooks Davis const model::test_program* test_program,
386b0d29bc4SBrooks Davis const config::tree& user_config) :
387b0d29bc4SBrooks Davis _interface(interface),
388b0d29bc4SBrooks Davis _test_program(force_absolute_paths(*test_program)),
389b0d29bc4SBrooks Davis _user_config(user_config)
390b0d29bc4SBrooks Davis {
391b0d29bc4SBrooks Davis }
392b0d29bc4SBrooks Davis
393b0d29bc4SBrooks Davis /// Body of the subprocess.
394b0d29bc4SBrooks Davis void
operator ()(const fs::path &)395b0d29bc4SBrooks Davis operator()(const fs::path& /* control_directory */)
396b0d29bc4SBrooks Davis {
397b0d29bc4SBrooks Davis const config::properties_map vars = scheduler::generate_config(
398b0d29bc4SBrooks Davis _user_config, _test_program.test_suite_name());
399b0d29bc4SBrooks Davis _interface->exec_list(_test_program, vars);
400b0d29bc4SBrooks Davis }
401b0d29bc4SBrooks Davis };
402b0d29bc4SBrooks Davis
403b0d29bc4SBrooks Davis
404b0d29bc4SBrooks Davis /// Functor to execute a test program in a child process.
405b0d29bc4SBrooks Davis class run_test_program {
406b0d29bc4SBrooks Davis /// Interface of the test program to execute.
407b0d29bc4SBrooks Davis std::shared_ptr< scheduler::interface > _interface;
408b0d29bc4SBrooks Davis
409b0d29bc4SBrooks Davis /// Test program to execute.
410b0d29bc4SBrooks Davis const model::test_program _test_program;
411b0d29bc4SBrooks Davis
412b0d29bc4SBrooks Davis /// Name of the test case to execute.
413b0d29bc4SBrooks Davis const std::string& _test_case_name;
414b0d29bc4SBrooks Davis
415b0d29bc4SBrooks Davis /// User-provided configuration variables.
416b0d29bc4SBrooks Davis const config::tree& _user_config;
417b0d29bc4SBrooks Davis
418b0d29bc4SBrooks Davis /// Verifies if the test case needs to be skipped or not.
419b0d29bc4SBrooks Davis ///
420b0d29bc4SBrooks Davis /// We could very well run this on the scheduler parent process before
421b0d29bc4SBrooks Davis /// issuing the fork. However, doing this here in the child process is
422b0d29bc4SBrooks Davis /// better for two reasons: first, it allows us to continue using the simple
423b0d29bc4SBrooks Davis /// spawn/wait abstraction of the scheduler; and, second, we parallelize the
424b0d29bc4SBrooks Davis /// requirements checks among tests.
425b0d29bc4SBrooks Davis ///
426b0d29bc4SBrooks Davis /// \post If the test's preconditions are not met, the caller process is
427b0d29bc4SBrooks Davis /// terminated with a special exit code and a "skipped cookie" is written to
428b0d29bc4SBrooks Davis /// the disk with the reason for the failure.
429b0d29bc4SBrooks Davis ///
430b0d29bc4SBrooks Davis /// \param skipped_cookie_path File to create with the skip reason details
431b0d29bc4SBrooks Davis /// if this test is skipped.
432b0d29bc4SBrooks Davis void
do_requirements_check(const fs::path & skipped_cookie_path)433b0d29bc4SBrooks Davis do_requirements_check(const fs::path& skipped_cookie_path)
434b0d29bc4SBrooks Davis {
435b0d29bc4SBrooks Davis const model::test_case& test_case = _test_program.find(
436b0d29bc4SBrooks Davis _test_case_name);
437b0d29bc4SBrooks Davis
438b0d29bc4SBrooks Davis const std::string skip_reason = engine::check_reqs(
439b0d29bc4SBrooks Davis test_case.get_metadata(), _user_config,
440b0d29bc4SBrooks Davis _test_program.test_suite_name(),
441b0d29bc4SBrooks Davis fs::current_path());
442b0d29bc4SBrooks Davis if (skip_reason.empty())
443b0d29bc4SBrooks Davis return;
444b0d29bc4SBrooks Davis
445b0d29bc4SBrooks Davis std::ofstream output(skipped_cookie_path.c_str());
446b0d29bc4SBrooks Davis if (!output) {
447b0d29bc4SBrooks Davis std::perror((F("Failed to open %s for write") %
448b0d29bc4SBrooks Davis skipped_cookie_path).str().c_str());
449b0d29bc4SBrooks Davis std::abort();
450b0d29bc4SBrooks Davis }
451b0d29bc4SBrooks Davis output << skip_reason;
452b0d29bc4SBrooks Davis output.close();
453b0d29bc4SBrooks Davis
454b0d29bc4SBrooks Davis // Abruptly terminate the process. We don't want to run any destructors
455b0d29bc4SBrooks Davis // inherited from the parent process by mistake, which could, for
456b0d29bc4SBrooks Davis // example, delete our own control files!
457b0d29bc4SBrooks Davis ::_exit(exit_skipped);
458b0d29bc4SBrooks Davis }
459b0d29bc4SBrooks Davis
460b0d29bc4SBrooks Davis public:
461b0d29bc4SBrooks Davis /// Constructor.
462b0d29bc4SBrooks Davis ///
463b0d29bc4SBrooks Davis /// \param interface Interface of the test program to execute.
464b0d29bc4SBrooks Davis /// \param test_program Test program to execute.
465b0d29bc4SBrooks Davis /// \param test_case_name Name of the test case to execute.
466b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
run_test_program(const std::shared_ptr<scheduler::interface> interface,const model::test_program_ptr test_program,const std::string & test_case_name,const config::tree & user_config)467b0d29bc4SBrooks Davis run_test_program(
468b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface,
469b0d29bc4SBrooks Davis const model::test_program_ptr test_program,
470b0d29bc4SBrooks Davis const std::string& test_case_name,
471b0d29bc4SBrooks Davis const config::tree& user_config) :
472b0d29bc4SBrooks Davis _interface(interface),
473b0d29bc4SBrooks Davis _test_program(force_absolute_paths(*test_program)),
474b0d29bc4SBrooks Davis _test_case_name(test_case_name),
475b0d29bc4SBrooks Davis _user_config(user_config)
476b0d29bc4SBrooks Davis {
477b0d29bc4SBrooks Davis }
478b0d29bc4SBrooks Davis
479b0d29bc4SBrooks Davis /// Body of the subprocess.
480b0d29bc4SBrooks Davis ///
481b0d29bc4SBrooks Davis /// \param control_directory The testcase directory where files will be
482b0d29bc4SBrooks Davis /// read from.
483b0d29bc4SBrooks Davis void
operator ()(const fs::path & control_directory)484b0d29bc4SBrooks Davis operator()(const fs::path& control_directory)
485b0d29bc4SBrooks Davis {
486b0d29bc4SBrooks Davis const model::test_case& test_case = _test_program.find(
487b0d29bc4SBrooks Davis _test_case_name);
488b0d29bc4SBrooks Davis if (test_case.fake_result())
489b0d29bc4SBrooks Davis ::_exit(EXIT_SUCCESS);
490b0d29bc4SBrooks Davis
491b0d29bc4SBrooks Davis do_requirements_check(control_directory / skipped_cookie);
492b0d29bc4SBrooks Davis
493b0d29bc4SBrooks Davis const config::properties_map vars = scheduler::generate_config(
494b0d29bc4SBrooks Davis _user_config, _test_program.test_suite_name());
495b0d29bc4SBrooks Davis _interface->exec_test(_test_program, _test_case_name, vars,
496b0d29bc4SBrooks Davis control_directory);
497b0d29bc4SBrooks Davis }
498b0d29bc4SBrooks Davis };
499b0d29bc4SBrooks Davis
500b0d29bc4SBrooks Davis
501b0d29bc4SBrooks Davis /// Functor to execute a test program in a child process.
502b0d29bc4SBrooks Davis class run_test_cleanup {
503b0d29bc4SBrooks Davis /// Interface of the test program to execute.
504b0d29bc4SBrooks Davis std::shared_ptr< scheduler::interface > _interface;
505b0d29bc4SBrooks Davis
506b0d29bc4SBrooks Davis /// Test program to execute.
507b0d29bc4SBrooks Davis const model::test_program _test_program;
508b0d29bc4SBrooks Davis
509b0d29bc4SBrooks Davis /// Name of the test case to execute.
510b0d29bc4SBrooks Davis const std::string& _test_case_name;
511b0d29bc4SBrooks Davis
512b0d29bc4SBrooks Davis /// User-provided configuration variables.
513b0d29bc4SBrooks Davis const config::tree& _user_config;
514b0d29bc4SBrooks Davis
515b0d29bc4SBrooks Davis public:
516b0d29bc4SBrooks Davis /// Constructor.
517b0d29bc4SBrooks Davis ///
518b0d29bc4SBrooks Davis /// \param interface Interface of the test program to execute.
519b0d29bc4SBrooks Davis /// \param test_program Test program to execute.
520b0d29bc4SBrooks Davis /// \param test_case_name Name of the test case to execute.
521b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
run_test_cleanup(const std::shared_ptr<scheduler::interface> interface,const model::test_program_ptr test_program,const std::string & test_case_name,const config::tree & user_config)522b0d29bc4SBrooks Davis run_test_cleanup(
523b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface,
524b0d29bc4SBrooks Davis const model::test_program_ptr test_program,
525b0d29bc4SBrooks Davis const std::string& test_case_name,
526b0d29bc4SBrooks Davis const config::tree& user_config) :
527b0d29bc4SBrooks Davis _interface(interface),
528b0d29bc4SBrooks Davis _test_program(force_absolute_paths(*test_program)),
529b0d29bc4SBrooks Davis _test_case_name(test_case_name),
530b0d29bc4SBrooks Davis _user_config(user_config)
531b0d29bc4SBrooks Davis {
532b0d29bc4SBrooks Davis }
533b0d29bc4SBrooks Davis
534b0d29bc4SBrooks Davis /// Body of the subprocess.
535b0d29bc4SBrooks Davis ///
536b0d29bc4SBrooks Davis /// \param control_directory The testcase directory where cleanup will be
537b0d29bc4SBrooks Davis /// run from.
538b0d29bc4SBrooks Davis void
operator ()(const fs::path & control_directory)539b0d29bc4SBrooks Davis operator()(const fs::path& control_directory)
540b0d29bc4SBrooks Davis {
541b0d29bc4SBrooks Davis const config::properties_map vars = scheduler::generate_config(
542b0d29bc4SBrooks Davis _user_config, _test_program.test_suite_name());
543b0d29bc4SBrooks Davis _interface->exec_cleanup(_test_program, _test_case_name, vars,
544b0d29bc4SBrooks Davis control_directory);
545b0d29bc4SBrooks Davis }
546b0d29bc4SBrooks Davis };
547b0d29bc4SBrooks Davis
548b0d29bc4SBrooks Davis
549257e70f1SIgor Ostapenko /// Functor to execute a test execenv cleanup in a child process.
550257e70f1SIgor Ostapenko class run_execenv_cleanup {
551257e70f1SIgor Ostapenko /// Test program to execute.
552257e70f1SIgor Ostapenko const model::test_program _test_program;
553257e70f1SIgor Ostapenko
554257e70f1SIgor Ostapenko /// Name of the test case to execute.
555257e70f1SIgor Ostapenko const std::string& _test_case_name;
556257e70f1SIgor Ostapenko
557257e70f1SIgor Ostapenko public:
558257e70f1SIgor Ostapenko /// Constructor.
559257e70f1SIgor Ostapenko ///
560257e70f1SIgor Ostapenko /// \param test_program Test program to execute.
561257e70f1SIgor Ostapenko /// \param test_case_name Name of the test case to execute.
run_execenv_cleanup(const model::test_program_ptr test_program,const std::string & test_case_name)562257e70f1SIgor Ostapenko run_execenv_cleanup(
563257e70f1SIgor Ostapenko const model::test_program_ptr test_program,
564257e70f1SIgor Ostapenko const std::string& test_case_name) :
565257e70f1SIgor Ostapenko _test_program(force_absolute_paths(*test_program)),
566257e70f1SIgor Ostapenko _test_case_name(test_case_name)
567257e70f1SIgor Ostapenko {
568257e70f1SIgor Ostapenko }
569257e70f1SIgor Ostapenko
570257e70f1SIgor Ostapenko /// Body of the subprocess.
571257e70f1SIgor Ostapenko ///
572257e70f1SIgor Ostapenko /// \param control_directory The testcase directory where cleanup will be
573257e70f1SIgor Ostapenko /// run from.
574257e70f1SIgor Ostapenko void
operator ()(const fs::path &)575257e70f1SIgor Ostapenko operator()(const fs::path& /* control_directory */)
576257e70f1SIgor Ostapenko {
577257e70f1SIgor Ostapenko auto e = execenv::get(_test_program, _test_case_name);
578257e70f1SIgor Ostapenko e->cleanup();
579257e70f1SIgor Ostapenko }
580257e70f1SIgor Ostapenko };
581257e70f1SIgor Ostapenko
582257e70f1SIgor Ostapenko
583b0d29bc4SBrooks Davis /// Obtains the right scheduler interface for a given test program.
584b0d29bc4SBrooks Davis ///
585b0d29bc4SBrooks Davis /// \param name The name of the interface of the test program.
586b0d29bc4SBrooks Davis ///
587b0d29bc4SBrooks Davis /// \return An scheduler interface.
588b0d29bc4SBrooks Davis std::shared_ptr< scheduler::interface >
find_interface(const std::string & name)589b0d29bc4SBrooks Davis find_interface(const std::string& name)
590b0d29bc4SBrooks Davis {
591b0d29bc4SBrooks Davis const interfaces_map::const_iterator iter = interfaces.find(name);
592b0d29bc4SBrooks Davis PRE(interfaces.find(name) != interfaces.end());
593b0d29bc4SBrooks Davis return (*iter).second;
594b0d29bc4SBrooks Davis }
595b0d29bc4SBrooks Davis
596b0d29bc4SBrooks Davis
597b0d29bc4SBrooks Davis } // anonymous namespace
598b0d29bc4SBrooks Davis
599b0d29bc4SBrooks Davis
600b0d29bc4SBrooks Davis void
exec_cleanup(const model::test_program &,const std::string &,const config::properties_map &,const utils::fs::path &) const601b0d29bc4SBrooks Davis scheduler::interface::exec_cleanup(
602b0d29bc4SBrooks Davis const model::test_program& /* test_program */,
603b0d29bc4SBrooks Davis const std::string& /* test_case_name */,
604b0d29bc4SBrooks Davis const config::properties_map& /* vars */,
605b0d29bc4SBrooks Davis const utils::fs::path& /* control_directory */) const
606b0d29bc4SBrooks Davis {
607b0d29bc4SBrooks Davis // Most test interfaces do not support standalone cleanup routines so
608b0d29bc4SBrooks Davis // provide a default implementation that does nothing.
609b0d29bc4SBrooks Davis UNREACHABLE_MSG("exec_cleanup not implemented for an interface that "
610b0d29bc4SBrooks Davis "supports standalone cleanup routines");
611b0d29bc4SBrooks Davis }
612b0d29bc4SBrooks Davis
613b0d29bc4SBrooks Davis
614b0d29bc4SBrooks Davis /// Internal implementation of a lazy_test_program.
615b0d29bc4SBrooks Davis struct engine::scheduler::lazy_test_program::impl : utils::noncopyable {
616b0d29bc4SBrooks Davis /// Whether the test cases list has been yet loaded or not.
617b0d29bc4SBrooks Davis bool _loaded;
618b0d29bc4SBrooks Davis
619b0d29bc4SBrooks Davis /// User configuration to pass to the test program list operation.
620b0d29bc4SBrooks Davis config::tree _user_config;
621b0d29bc4SBrooks Davis
622b0d29bc4SBrooks Davis /// Scheduler context to use to load test cases.
623b0d29bc4SBrooks Davis scheduler::scheduler_handle& _scheduler_handle;
624b0d29bc4SBrooks Davis
625b0d29bc4SBrooks Davis /// Constructor.
626b0d29bc4SBrooks Davis ///
627b0d29bc4SBrooks Davis /// \param user_config_ User configuration to pass to the test program list
628b0d29bc4SBrooks Davis /// operation.
629b0d29bc4SBrooks Davis /// \param scheduler_handle_ Scheduler context to use when loading test
630b0d29bc4SBrooks Davis /// cases.
implengine::scheduler::lazy_test_program::impl631b0d29bc4SBrooks Davis impl(const config::tree& user_config_,
632b0d29bc4SBrooks Davis scheduler::scheduler_handle& scheduler_handle_) :
633b0d29bc4SBrooks Davis _loaded(false), _user_config(user_config_),
634b0d29bc4SBrooks Davis _scheduler_handle(scheduler_handle_)
635b0d29bc4SBrooks Davis {
636b0d29bc4SBrooks Davis }
637b0d29bc4SBrooks Davis };
638b0d29bc4SBrooks Davis
639b0d29bc4SBrooks Davis
640b0d29bc4SBrooks Davis /// Constructs a new test program.
641b0d29bc4SBrooks Davis ///
642b0d29bc4SBrooks Davis /// \param interface_name_ Name of the test program interface.
643b0d29bc4SBrooks Davis /// \param binary_ The name of the test program binary relative to root_.
644b0d29bc4SBrooks Davis /// \param root_ The root of the test suite containing the test program.
645b0d29bc4SBrooks Davis /// \param test_suite_name_ The name of the test suite this program belongs to.
646b0d29bc4SBrooks Davis /// \param md_ Metadata of the test program.
647b0d29bc4SBrooks Davis /// \param user_config_ User configuration to pass to the scheduler.
648b0d29bc4SBrooks Davis /// \param scheduler_handle_ Scheduler context to use to load test cases.
lazy_test_program(const std::string & interface_name_,const fs::path & binary_,const fs::path & root_,const std::string & test_suite_name_,const model::metadata & md_,const config::tree & user_config_,scheduler::scheduler_handle & scheduler_handle_)649b0d29bc4SBrooks Davis scheduler::lazy_test_program::lazy_test_program(
650b0d29bc4SBrooks Davis const std::string& interface_name_,
651b0d29bc4SBrooks Davis const fs::path& binary_,
652b0d29bc4SBrooks Davis const fs::path& root_,
653b0d29bc4SBrooks Davis const std::string& test_suite_name_,
654b0d29bc4SBrooks Davis const model::metadata& md_,
655b0d29bc4SBrooks Davis const config::tree& user_config_,
656b0d29bc4SBrooks Davis scheduler::scheduler_handle& scheduler_handle_) :
657b0d29bc4SBrooks Davis test_program(interface_name_, binary_, root_, test_suite_name_, md_,
658b0d29bc4SBrooks Davis model::test_cases_map()),
659b0d29bc4SBrooks Davis _pimpl(new impl(user_config_, scheduler_handle_))
660b0d29bc4SBrooks Davis {
661b0d29bc4SBrooks Davis }
662b0d29bc4SBrooks Davis
663b0d29bc4SBrooks Davis
664b0d29bc4SBrooks Davis /// Gets or loads the list of test cases from the test program.
665b0d29bc4SBrooks Davis ///
666b0d29bc4SBrooks Davis /// \return The list of test cases provided by the test program.
667b0d29bc4SBrooks Davis const model::test_cases_map&
test_cases(void) const668b0d29bc4SBrooks Davis scheduler::lazy_test_program::test_cases(void) const
669b0d29bc4SBrooks Davis {
670b0d29bc4SBrooks Davis _pimpl->_scheduler_handle.check_interrupt();
671b0d29bc4SBrooks Davis
672b0d29bc4SBrooks Davis if (!_pimpl->_loaded) {
673b0d29bc4SBrooks Davis const model::test_cases_map tcs = _pimpl->_scheduler_handle.list_tests(
674b0d29bc4SBrooks Davis this, _pimpl->_user_config);
675b0d29bc4SBrooks Davis
676b0d29bc4SBrooks Davis // Due to the restrictions on when set_test_cases() may be called (as a
677b0d29bc4SBrooks Davis // way to lazily initialize the test cases list before it is ever
678b0d29bc4SBrooks Davis // returned), this cast is valid.
679b0d29bc4SBrooks Davis const_cast< scheduler::lazy_test_program* >(this)->set_test_cases(tcs);
680b0d29bc4SBrooks Davis
681b0d29bc4SBrooks Davis _pimpl->_loaded = true;
682b0d29bc4SBrooks Davis
683b0d29bc4SBrooks Davis _pimpl->_scheduler_handle.check_interrupt();
684b0d29bc4SBrooks Davis }
685b0d29bc4SBrooks Davis
686b0d29bc4SBrooks Davis INV(_pimpl->_loaded);
687b0d29bc4SBrooks Davis return test_program::test_cases();
688b0d29bc4SBrooks Davis }
689b0d29bc4SBrooks Davis
690b0d29bc4SBrooks Davis
691b0d29bc4SBrooks Davis /// Internal implementation for the result_handle class.
692b0d29bc4SBrooks Davis struct engine::scheduler::result_handle::bimpl : utils::noncopyable {
693b0d29bc4SBrooks Davis /// Generic executor exit handle for this result handle.
694b0d29bc4SBrooks Davis executor::exit_handle generic;
695b0d29bc4SBrooks Davis
696b0d29bc4SBrooks Davis /// Mutable pointer to the corresponding scheduler state.
697b0d29bc4SBrooks Davis ///
698b0d29bc4SBrooks Davis /// This object references a member of the scheduler_handle that yielded
699b0d29bc4SBrooks Davis /// this result_handle instance. We need this direct access to clean up
700b0d29bc4SBrooks Davis /// after ourselves when the result is destroyed.
701b0d29bc4SBrooks Davis exec_data_map& all_exec_data;
702b0d29bc4SBrooks Davis
703b0d29bc4SBrooks Davis /// Constructor.
704b0d29bc4SBrooks Davis ///
705b0d29bc4SBrooks Davis /// \param generic_ Generic executor exit handle for this result handle.
706b0d29bc4SBrooks Davis /// \param [in,out] all_exec_data_ Global object keeping track of all active
707b0d29bc4SBrooks Davis /// executions for an scheduler. This is a pointer to a member of the
708b0d29bc4SBrooks Davis /// scheduler_handle object.
bimplengine::scheduler::result_handle::bimpl709b0d29bc4SBrooks Davis bimpl(const executor::exit_handle generic_, exec_data_map& all_exec_data_) :
710b0d29bc4SBrooks Davis generic(generic_), all_exec_data(all_exec_data_)
711b0d29bc4SBrooks Davis {
712b0d29bc4SBrooks Davis }
713b0d29bc4SBrooks Davis
714b0d29bc4SBrooks Davis /// Destructor.
~bimplengine::scheduler::result_handle::bimpl715b0d29bc4SBrooks Davis ~bimpl(void)
716b0d29bc4SBrooks Davis {
717b0d29bc4SBrooks Davis LD(F("Removing %s from all_exec_data") % generic.original_pid());
718b0d29bc4SBrooks Davis all_exec_data.erase(generic.original_pid());
719b0d29bc4SBrooks Davis }
720b0d29bc4SBrooks Davis };
721b0d29bc4SBrooks Davis
722b0d29bc4SBrooks Davis
723b0d29bc4SBrooks Davis /// Constructor.
724b0d29bc4SBrooks Davis ///
725b0d29bc4SBrooks Davis /// \param pbimpl Constructed internal implementation.
result_handle(std::shared_ptr<bimpl> pbimpl)726b0d29bc4SBrooks Davis scheduler::result_handle::result_handle(std::shared_ptr< bimpl > pbimpl) :
727b0d29bc4SBrooks Davis _pbimpl(pbimpl)
728b0d29bc4SBrooks Davis {
729b0d29bc4SBrooks Davis }
730b0d29bc4SBrooks Davis
731b0d29bc4SBrooks Davis
732b0d29bc4SBrooks Davis /// Destructor.
~result_handle(void)733b0d29bc4SBrooks Davis scheduler::result_handle::~result_handle(void)
734b0d29bc4SBrooks Davis {
735b0d29bc4SBrooks Davis }
736b0d29bc4SBrooks Davis
737b0d29bc4SBrooks Davis
738b0d29bc4SBrooks Davis /// Cleans up the test case results.
739b0d29bc4SBrooks Davis ///
740b0d29bc4SBrooks Davis /// This function should be called explicitly as it provides the means to
741b0d29bc4SBrooks Davis /// control any exceptions raised during cleanup. Do not rely on the destructor
742b0d29bc4SBrooks Davis /// to clean things up.
743b0d29bc4SBrooks Davis ///
744b0d29bc4SBrooks Davis /// \throw engine::error If the cleanup fails, especially due to the inability
745b0d29bc4SBrooks Davis /// to remove the work directory.
746b0d29bc4SBrooks Davis void
cleanup(void)747b0d29bc4SBrooks Davis scheduler::result_handle::cleanup(void)
748b0d29bc4SBrooks Davis {
749b0d29bc4SBrooks Davis _pbimpl->generic.cleanup();
750b0d29bc4SBrooks Davis }
751b0d29bc4SBrooks Davis
752b0d29bc4SBrooks Davis
753b0d29bc4SBrooks Davis /// Returns the original PID corresponding to this result.
754b0d29bc4SBrooks Davis ///
755b0d29bc4SBrooks Davis /// \return An exec_handle.
756b0d29bc4SBrooks Davis int
original_pid(void) const757b0d29bc4SBrooks Davis scheduler::result_handle::original_pid(void) const
758b0d29bc4SBrooks Davis {
759b0d29bc4SBrooks Davis return _pbimpl->generic.original_pid();
760b0d29bc4SBrooks Davis }
761b0d29bc4SBrooks Davis
762b0d29bc4SBrooks Davis
763b0d29bc4SBrooks Davis /// Returns the timestamp of when spawn_test was called.
764b0d29bc4SBrooks Davis ///
765b0d29bc4SBrooks Davis /// \return A timestamp.
766b0d29bc4SBrooks Davis const datetime::timestamp&
start_time(void) const767b0d29bc4SBrooks Davis scheduler::result_handle::start_time(void) const
768b0d29bc4SBrooks Davis {
769b0d29bc4SBrooks Davis return _pbimpl->generic.start_time();
770b0d29bc4SBrooks Davis }
771b0d29bc4SBrooks Davis
772b0d29bc4SBrooks Davis
773b0d29bc4SBrooks Davis /// Returns the timestamp of when wait_any_test returned this object.
774b0d29bc4SBrooks Davis ///
775b0d29bc4SBrooks Davis /// \return A timestamp.
776b0d29bc4SBrooks Davis const datetime::timestamp&
end_time(void) const777b0d29bc4SBrooks Davis scheduler::result_handle::end_time(void) const
778b0d29bc4SBrooks Davis {
779b0d29bc4SBrooks Davis return _pbimpl->generic.end_time();
780b0d29bc4SBrooks Davis }
781b0d29bc4SBrooks Davis
782b0d29bc4SBrooks Davis
783b0d29bc4SBrooks Davis /// Returns the path to the test-specific work directory.
784b0d29bc4SBrooks Davis ///
785b0d29bc4SBrooks Davis /// This is guaranteed to be clear of files created by the scheduler.
786b0d29bc4SBrooks Davis ///
787b0d29bc4SBrooks Davis /// \return The path to a directory that exists until cleanup() is called.
788b0d29bc4SBrooks Davis fs::path
work_directory(void) const789b0d29bc4SBrooks Davis scheduler::result_handle::work_directory(void) const
790b0d29bc4SBrooks Davis {
791b0d29bc4SBrooks Davis return _pbimpl->generic.work_directory();
792b0d29bc4SBrooks Davis }
793b0d29bc4SBrooks Davis
794b0d29bc4SBrooks Davis
795b0d29bc4SBrooks Davis /// Returns the path to the test's stdout file.
796b0d29bc4SBrooks Davis ///
797b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called.
798b0d29bc4SBrooks Davis const fs::path&
stdout_file(void) const799b0d29bc4SBrooks Davis scheduler::result_handle::stdout_file(void) const
800b0d29bc4SBrooks Davis {
801b0d29bc4SBrooks Davis return _pbimpl->generic.stdout_file();
802b0d29bc4SBrooks Davis }
803b0d29bc4SBrooks Davis
804b0d29bc4SBrooks Davis
805b0d29bc4SBrooks Davis /// Returns the path to the test's stderr file.
806b0d29bc4SBrooks Davis ///
807b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called.
808b0d29bc4SBrooks Davis const fs::path&
stderr_file(void) const809b0d29bc4SBrooks Davis scheduler::result_handle::stderr_file(void) const
810b0d29bc4SBrooks Davis {
811b0d29bc4SBrooks Davis return _pbimpl->generic.stderr_file();
812b0d29bc4SBrooks Davis }
813b0d29bc4SBrooks Davis
814b0d29bc4SBrooks Davis
815b0d29bc4SBrooks Davis /// Internal implementation for the test_result_handle class.
816b0d29bc4SBrooks Davis struct engine::scheduler::test_result_handle::impl : utils::noncopyable {
817b0d29bc4SBrooks Davis /// Test program data for this test case.
818b0d29bc4SBrooks Davis model::test_program_ptr test_program;
819b0d29bc4SBrooks Davis
820b0d29bc4SBrooks Davis /// Name of the test case.
821b0d29bc4SBrooks Davis std::string test_case_name;
822b0d29bc4SBrooks Davis
823b0d29bc4SBrooks Davis /// The actual result of the test execution.
824b0d29bc4SBrooks Davis const model::test_result test_result;
825b0d29bc4SBrooks Davis
826b0d29bc4SBrooks Davis /// Constructor.
827b0d29bc4SBrooks Davis ///
828b0d29bc4SBrooks Davis /// \param test_program_ Test program data for this test case.
829b0d29bc4SBrooks Davis /// \param test_case_name_ Name of the test case.
830b0d29bc4SBrooks Davis /// \param test_result_ The actual result of the test execution.
implengine::scheduler::test_result_handle::impl831b0d29bc4SBrooks Davis impl(const model::test_program_ptr test_program_,
832b0d29bc4SBrooks Davis const std::string& test_case_name_,
833b0d29bc4SBrooks Davis const model::test_result& test_result_) :
834b0d29bc4SBrooks Davis test_program(test_program_),
835b0d29bc4SBrooks Davis test_case_name(test_case_name_),
836b0d29bc4SBrooks Davis test_result(test_result_)
837b0d29bc4SBrooks Davis {
838b0d29bc4SBrooks Davis }
839b0d29bc4SBrooks Davis };
840b0d29bc4SBrooks Davis
841b0d29bc4SBrooks Davis
842b0d29bc4SBrooks Davis /// Constructor.
843b0d29bc4SBrooks Davis ///
844b0d29bc4SBrooks Davis /// \param pbimpl Constructed internal implementation for the base object.
845b0d29bc4SBrooks Davis /// \param pimpl Constructed internal implementation.
test_result_handle(std::shared_ptr<bimpl> pbimpl,std::shared_ptr<impl> pimpl)846b0d29bc4SBrooks Davis scheduler::test_result_handle::test_result_handle(
847b0d29bc4SBrooks Davis std::shared_ptr< bimpl > pbimpl, std::shared_ptr< impl > pimpl) :
848b0d29bc4SBrooks Davis result_handle(pbimpl), _pimpl(pimpl)
849b0d29bc4SBrooks Davis {
850b0d29bc4SBrooks Davis }
851b0d29bc4SBrooks Davis
852b0d29bc4SBrooks Davis
853b0d29bc4SBrooks Davis /// Destructor.
~test_result_handle(void)854b0d29bc4SBrooks Davis scheduler::test_result_handle::~test_result_handle(void)
855b0d29bc4SBrooks Davis {
856b0d29bc4SBrooks Davis }
857b0d29bc4SBrooks Davis
858b0d29bc4SBrooks Davis
859b0d29bc4SBrooks Davis /// Returns the test program that yielded this result.
860b0d29bc4SBrooks Davis ///
861b0d29bc4SBrooks Davis /// \return A test program.
862b0d29bc4SBrooks Davis const model::test_program_ptr
test_program(void) const863b0d29bc4SBrooks Davis scheduler::test_result_handle::test_program(void) const
864b0d29bc4SBrooks Davis {
865b0d29bc4SBrooks Davis return _pimpl->test_program;
866b0d29bc4SBrooks Davis }
867b0d29bc4SBrooks Davis
868b0d29bc4SBrooks Davis
869b0d29bc4SBrooks Davis /// Returns the name of the test case that yielded this result.
870b0d29bc4SBrooks Davis ///
871b0d29bc4SBrooks Davis /// \return A test case name
872b0d29bc4SBrooks Davis const std::string&
test_case_name(void) const873b0d29bc4SBrooks Davis scheduler::test_result_handle::test_case_name(void) const
874b0d29bc4SBrooks Davis {
875b0d29bc4SBrooks Davis return _pimpl->test_case_name;
876b0d29bc4SBrooks Davis }
877b0d29bc4SBrooks Davis
878b0d29bc4SBrooks Davis
879b0d29bc4SBrooks Davis /// Returns the actual result of the test execution.
880b0d29bc4SBrooks Davis ///
881b0d29bc4SBrooks Davis /// \return A test result.
882b0d29bc4SBrooks Davis const model::test_result&
test_result(void) const883b0d29bc4SBrooks Davis scheduler::test_result_handle::test_result(void) const
884b0d29bc4SBrooks Davis {
885b0d29bc4SBrooks Davis return _pimpl->test_result;
886b0d29bc4SBrooks Davis }
887b0d29bc4SBrooks Davis
888b0d29bc4SBrooks Davis
889b0d29bc4SBrooks Davis /// Internal implementation for the scheduler_handle.
890b0d29bc4SBrooks Davis struct engine::scheduler::scheduler_handle::impl : utils::noncopyable {
891b0d29bc4SBrooks Davis /// Generic executor instance encapsulated by this one.
892b0d29bc4SBrooks Davis executor::executor_handle generic;
893b0d29bc4SBrooks Davis
894b0d29bc4SBrooks Davis /// Mapping of exec handles to the data required at run time.
895b0d29bc4SBrooks Davis exec_data_map all_exec_data;
896b0d29bc4SBrooks Davis
897b0d29bc4SBrooks Davis /// Collection of test_exec_data objects.
898b0d29bc4SBrooks Davis typedef std::vector< const test_exec_data* > test_exec_data_vector;
899b0d29bc4SBrooks Davis
900b0d29bc4SBrooks Davis /// Constructor.
implengine::scheduler::scheduler_handle::impl901b0d29bc4SBrooks Davis impl(void) : generic(executor::setup())
902b0d29bc4SBrooks Davis {
903b0d29bc4SBrooks Davis }
904b0d29bc4SBrooks Davis
905b0d29bc4SBrooks Davis /// Destructor.
906b0d29bc4SBrooks Davis ///
907b0d29bc4SBrooks Davis /// This runs any pending cleanup routines, which should only happen if the
908b0d29bc4SBrooks Davis /// scheduler is abruptly terminated (aka if a signal is received).
~implengine::scheduler::scheduler_handle::impl909b0d29bc4SBrooks Davis ~impl(void)
910b0d29bc4SBrooks Davis {
911b0d29bc4SBrooks Davis const test_exec_data_vector tests_data = tests_needing_cleanup();
912b0d29bc4SBrooks Davis
913b0d29bc4SBrooks Davis for (test_exec_data_vector::const_iterator iter = tests_data.begin();
914b0d29bc4SBrooks Davis iter != tests_data.end(); ++iter) {
915b0d29bc4SBrooks Davis const test_exec_data* test_data = *iter;
916b0d29bc4SBrooks Davis
917b0d29bc4SBrooks Davis try {
918b0d29bc4SBrooks Davis sync_cleanup(test_data);
919b0d29bc4SBrooks Davis } catch (const std::runtime_error& e) {
920b0d29bc4SBrooks Davis LW(F("Failed to run cleanup routine for %s:%s on abrupt "
921b0d29bc4SBrooks Davis "termination")
922b0d29bc4SBrooks Davis % test_data->test_program->relative_path()
923b0d29bc4SBrooks Davis % test_data->test_case_name);
924b0d29bc4SBrooks Davis }
925b0d29bc4SBrooks Davis }
926257e70f1SIgor Ostapenko
927257e70f1SIgor Ostapenko const test_exec_data_vector td = tests_needing_execenv_cleanup();
928257e70f1SIgor Ostapenko
929257e70f1SIgor Ostapenko for (test_exec_data_vector::const_iterator iter = td.begin();
930257e70f1SIgor Ostapenko iter != td.end(); ++iter) {
931257e70f1SIgor Ostapenko const test_exec_data* test_data = *iter;
932257e70f1SIgor Ostapenko
933257e70f1SIgor Ostapenko try {
934257e70f1SIgor Ostapenko sync_execenv_cleanup(test_data);
935257e70f1SIgor Ostapenko } catch (const std::runtime_error& e) {
936257e70f1SIgor Ostapenko LW(F("Failed to run execenv cleanup routine for %s:%s on abrupt "
937257e70f1SIgor Ostapenko "termination")
938257e70f1SIgor Ostapenko % test_data->test_program->relative_path()
939257e70f1SIgor Ostapenko % test_data->test_case_name);
940257e70f1SIgor Ostapenko }
941257e70f1SIgor Ostapenko }
942b0d29bc4SBrooks Davis }
943b0d29bc4SBrooks Davis
944b0d29bc4SBrooks Davis /// Finds any pending exec_datas that correspond to tests needing cleanup.
945b0d29bc4SBrooks Davis ///
946b0d29bc4SBrooks Davis /// \return The collection of test_exec_data objects that have their
947b0d29bc4SBrooks Davis /// needs_cleanup property set to true.
948b0d29bc4SBrooks Davis test_exec_data_vector
tests_needing_cleanupengine::scheduler::scheduler_handle::impl949b0d29bc4SBrooks Davis tests_needing_cleanup(void)
950b0d29bc4SBrooks Davis {
951b0d29bc4SBrooks Davis test_exec_data_vector tests_data;
952b0d29bc4SBrooks Davis
953b0d29bc4SBrooks Davis for (exec_data_map::const_iterator iter = all_exec_data.begin();
954b0d29bc4SBrooks Davis iter != all_exec_data.end(); ++iter) {
955b0d29bc4SBrooks Davis const exec_data_ptr data = (*iter).second;
956b0d29bc4SBrooks Davis
957b0d29bc4SBrooks Davis try {
958b0d29bc4SBrooks Davis test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
959b0d29bc4SBrooks Davis *data.get());
960b0d29bc4SBrooks Davis if (test_data->needs_cleanup) {
961b0d29bc4SBrooks Davis tests_data.push_back(test_data);
962b0d29bc4SBrooks Davis test_data->needs_cleanup = false;
963257e70f1SIgor Ostapenko if (!test_data->exit_handle)
964257e70f1SIgor Ostapenko test_data->exit_handle = generic.reap(test_data->pid);
965b0d29bc4SBrooks Davis }
966b0d29bc4SBrooks Davis } catch (const std::bad_cast& e) {
967b0d29bc4SBrooks Davis // Do nothing for cleanup_exec_data objects.
968b0d29bc4SBrooks Davis }
969b0d29bc4SBrooks Davis }
970b0d29bc4SBrooks Davis
971b0d29bc4SBrooks Davis return tests_data;
972b0d29bc4SBrooks Davis }
973b0d29bc4SBrooks Davis
974257e70f1SIgor Ostapenko /// Finds any pending exec_datas that correspond to tests needing execenv
975257e70f1SIgor Ostapenko /// cleanup.
976257e70f1SIgor Ostapenko ///
977257e70f1SIgor Ostapenko /// \return The collection of test_exec_data objects that have their
978257e70f1SIgor Ostapenko /// specific execenv property set.
979257e70f1SIgor Ostapenko test_exec_data_vector
tests_needing_execenv_cleanupengine::scheduler::scheduler_handle::impl980257e70f1SIgor Ostapenko tests_needing_execenv_cleanup(void)
981257e70f1SIgor Ostapenko {
982257e70f1SIgor Ostapenko test_exec_data_vector tests_data;
983257e70f1SIgor Ostapenko
984257e70f1SIgor Ostapenko for (exec_data_map::const_iterator iter = all_exec_data.begin();
985257e70f1SIgor Ostapenko iter != all_exec_data.end(); ++iter) {
986257e70f1SIgor Ostapenko const exec_data_ptr data = (*iter).second;
987257e70f1SIgor Ostapenko
988257e70f1SIgor Ostapenko try {
989257e70f1SIgor Ostapenko test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
990257e70f1SIgor Ostapenko *data.get());
991257e70f1SIgor Ostapenko if (test_data->needs_execenv_cleanup) {
992257e70f1SIgor Ostapenko tests_data.push_back(test_data);
993257e70f1SIgor Ostapenko test_data->needs_execenv_cleanup = false;
994257e70f1SIgor Ostapenko if (!test_data->exit_handle)
995257e70f1SIgor Ostapenko test_data->exit_handle = generic.reap(test_data->pid);
996257e70f1SIgor Ostapenko }
997257e70f1SIgor Ostapenko } catch (const std::bad_cast& e) {
998257e70f1SIgor Ostapenko // Do nothing for other objects.
999257e70f1SIgor Ostapenko }
1000257e70f1SIgor Ostapenko }
1001257e70f1SIgor Ostapenko
1002257e70f1SIgor Ostapenko return tests_data;
1003257e70f1SIgor Ostapenko }
1004257e70f1SIgor Ostapenko
1005b0d29bc4SBrooks Davis /// Cleans up a single test case synchronously.
1006b0d29bc4SBrooks Davis ///
1007b0d29bc4SBrooks Davis /// \param test_data The data of the previously executed test case to be
1008b0d29bc4SBrooks Davis /// cleaned up.
1009b0d29bc4SBrooks Davis void
sync_cleanupengine::scheduler::scheduler_handle::impl1010b0d29bc4SBrooks Davis sync_cleanup(const test_exec_data* test_data)
1011b0d29bc4SBrooks Davis {
1012b0d29bc4SBrooks Davis // The message in this result should never be seen by the user, but use
1013b0d29bc4SBrooks Davis // something reasonable just in case it leaks and we need to pinpoint
1014b0d29bc4SBrooks Davis // the call site.
1015b0d29bc4SBrooks Davis model::test_result result(model::test_result_broken,
1016b0d29bc4SBrooks Davis "Test case died abruptly");
1017b0d29bc4SBrooks Davis
1018b0d29bc4SBrooks Davis const executor::exec_handle cleanup_handle = spawn_cleanup(
1019b0d29bc4SBrooks Davis test_data->test_program, test_data->test_case_name,
1020b0d29bc4SBrooks Davis test_data->user_config, test_data->exit_handle.get(),
1021b0d29bc4SBrooks Davis result);
1022b0d29bc4SBrooks Davis generic.wait(cleanup_handle);
1023b0d29bc4SBrooks Davis }
1024b0d29bc4SBrooks Davis
1025b0d29bc4SBrooks Davis /// Forks and executes a test case cleanup routine asynchronously.
1026b0d29bc4SBrooks Davis ///
1027b0d29bc4SBrooks Davis /// \param test_program The container test program.
1028b0d29bc4SBrooks Davis /// \param test_case_name The name of the test case to run.
1029b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
1030b0d29bc4SBrooks Davis /// \param body_handle The exit handle of the test case's corresponding
1031b0d29bc4SBrooks Davis /// body. The cleanup will be executed in the same context.
1032b0d29bc4SBrooks Davis /// \param body_result The result of the test case's corresponding body.
1033b0d29bc4SBrooks Davis ///
1034b0d29bc4SBrooks Davis /// \return A handle for the background operation. Used to match the result
1035b0d29bc4SBrooks Davis /// of the execution returned by wait_any() with this invocation.
1036b0d29bc4SBrooks Davis executor::exec_handle
spawn_cleanupengine::scheduler::scheduler_handle::impl1037b0d29bc4SBrooks Davis spawn_cleanup(const model::test_program_ptr test_program,
1038b0d29bc4SBrooks Davis const std::string& test_case_name,
1039b0d29bc4SBrooks Davis const config::tree& user_config,
1040b0d29bc4SBrooks Davis const executor::exit_handle& body_handle,
1041b0d29bc4SBrooks Davis const model::test_result& body_result)
1042b0d29bc4SBrooks Davis {
1043b0d29bc4SBrooks Davis generic.check_interrupt();
1044b0d29bc4SBrooks Davis
1045b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface =
1046b0d29bc4SBrooks Davis find_interface(test_program->interface_name());
1047b0d29bc4SBrooks Davis
1048b0d29bc4SBrooks Davis LI(F("Spawning %s:%s (cleanup)") % test_program->absolute_path() %
1049b0d29bc4SBrooks Davis test_case_name);
1050b0d29bc4SBrooks Davis
1051b0d29bc4SBrooks Davis const executor::exec_handle handle = generic.spawn_followup(
1052b0d29bc4SBrooks Davis run_test_cleanup(interface, test_program, test_case_name,
1053b0d29bc4SBrooks Davis user_config),
1054b0d29bc4SBrooks Davis body_handle, cleanup_timeout);
1055b0d29bc4SBrooks Davis
1056b0d29bc4SBrooks Davis const exec_data_ptr data(new cleanup_exec_data(
1057b0d29bc4SBrooks Davis test_program, test_case_name, body_handle, body_result));
1058b0d29bc4SBrooks Davis LD(F("Inserting %s into all_exec_data (cleanup)") % handle.pid());
1059b0d29bc4SBrooks Davis INV_MSG(all_exec_data.find(handle.pid()) == all_exec_data.end(),
1060b0d29bc4SBrooks Davis F("PID %s already in all_exec_data; not properly cleaned "
1061b0d29bc4SBrooks Davis "up or reused too fast") % handle.pid());;
1062b0d29bc4SBrooks Davis all_exec_data.insert(exec_data_map::value_type(handle.pid(), data));
1063b0d29bc4SBrooks Davis
1064b0d29bc4SBrooks Davis return handle;
1065b0d29bc4SBrooks Davis }
1066257e70f1SIgor Ostapenko
1067257e70f1SIgor Ostapenko /// Cleans up a single test case execenv synchronously.
1068257e70f1SIgor Ostapenko ///
1069257e70f1SIgor Ostapenko /// \param test_data The data of the previously executed test case to be
1070257e70f1SIgor Ostapenko /// cleaned up.
1071257e70f1SIgor Ostapenko void
sync_execenv_cleanupengine::scheduler::scheduler_handle::impl1072257e70f1SIgor Ostapenko sync_execenv_cleanup(const test_exec_data* test_data)
1073257e70f1SIgor Ostapenko {
1074257e70f1SIgor Ostapenko // The message in this result should never be seen by the user, but use
1075257e70f1SIgor Ostapenko // something reasonable just in case it leaks and we need to pinpoint
1076257e70f1SIgor Ostapenko // the call site.
1077257e70f1SIgor Ostapenko model::test_result result(model::test_result_broken,
1078257e70f1SIgor Ostapenko "Test case died abruptly");
1079257e70f1SIgor Ostapenko
1080257e70f1SIgor Ostapenko const executor::exec_handle cleanup_handle = spawn_execenv_cleanup(
1081257e70f1SIgor Ostapenko test_data->test_program, test_data->test_case_name,
1082257e70f1SIgor Ostapenko test_data->exit_handle.get(), result);
1083257e70f1SIgor Ostapenko generic.wait(cleanup_handle);
1084257e70f1SIgor Ostapenko }
1085257e70f1SIgor Ostapenko
1086257e70f1SIgor Ostapenko /// Forks and executes a test case execenv cleanup asynchronously.
1087257e70f1SIgor Ostapenko ///
1088257e70f1SIgor Ostapenko /// \param test_program The container test program.
1089257e70f1SIgor Ostapenko /// \param test_case_name The name of the test case to run.
1090257e70f1SIgor Ostapenko /// \param body_handle The exit handle of the test case's corresponding
1091257e70f1SIgor Ostapenko /// body. The cleanup will be executed in the same context.
1092257e70f1SIgor Ostapenko /// \param body_result The result of the test case's corresponding body.
1093257e70f1SIgor Ostapenko ///
1094257e70f1SIgor Ostapenko /// \return A handle for the background operation. Used to match the result
1095257e70f1SIgor Ostapenko /// of the execution returned by wait_any() with this invocation.
1096257e70f1SIgor Ostapenko executor::exec_handle
spawn_execenv_cleanupengine::scheduler::scheduler_handle::impl1097257e70f1SIgor Ostapenko spawn_execenv_cleanup(const model::test_program_ptr test_program,
1098257e70f1SIgor Ostapenko const std::string& test_case_name,
1099257e70f1SIgor Ostapenko const executor::exit_handle& body_handle,
1100257e70f1SIgor Ostapenko const model::test_result& body_result)
1101257e70f1SIgor Ostapenko {
1102257e70f1SIgor Ostapenko generic.check_interrupt();
1103257e70f1SIgor Ostapenko
1104257e70f1SIgor Ostapenko LI(F("Spawning %s:%s (execenv cleanup)")
1105257e70f1SIgor Ostapenko % test_program->absolute_path() % test_case_name);
1106257e70f1SIgor Ostapenko
1107257e70f1SIgor Ostapenko const executor::exec_handle handle = generic.spawn_followup(
1108257e70f1SIgor Ostapenko run_execenv_cleanup(test_program, test_case_name),
1109257e70f1SIgor Ostapenko body_handle, execenv_cleanup_timeout);
1110257e70f1SIgor Ostapenko
1111257e70f1SIgor Ostapenko const exec_data_ptr data(new execenv_exec_data(
1112257e70f1SIgor Ostapenko test_program, test_case_name, body_handle, body_result));
1113257e70f1SIgor Ostapenko LD(F("Inserting %s into all_exec_data (execenv cleanup)") % handle.pid());
1114257e70f1SIgor Ostapenko INV_MSG(all_exec_data.find(handle.pid()) == all_exec_data.end(),
1115257e70f1SIgor Ostapenko F("PID %s already in all_exec_data; not properly cleaned "
1116257e70f1SIgor Ostapenko "up or reused too fast") % handle.pid());;
1117257e70f1SIgor Ostapenko all_exec_data.insert(exec_data_map::value_type(handle.pid(), data));
1118257e70f1SIgor Ostapenko
1119257e70f1SIgor Ostapenko return handle;
1120257e70f1SIgor Ostapenko }
1121b0d29bc4SBrooks Davis };
1122b0d29bc4SBrooks Davis
1123b0d29bc4SBrooks Davis
1124b0d29bc4SBrooks Davis /// Constructor.
scheduler_handle(void)1125b0d29bc4SBrooks Davis scheduler::scheduler_handle::scheduler_handle(void) : _pimpl(new impl())
1126b0d29bc4SBrooks Davis {
1127b0d29bc4SBrooks Davis }
1128b0d29bc4SBrooks Davis
1129b0d29bc4SBrooks Davis
1130b0d29bc4SBrooks Davis /// Destructor.
~scheduler_handle(void)1131b0d29bc4SBrooks Davis scheduler::scheduler_handle::~scheduler_handle(void)
1132b0d29bc4SBrooks Davis {
1133b0d29bc4SBrooks Davis }
1134b0d29bc4SBrooks Davis
1135b0d29bc4SBrooks Davis
1136b0d29bc4SBrooks Davis /// Queries the path to the root of the work directory for all tests.
1137b0d29bc4SBrooks Davis ///
1138b0d29bc4SBrooks Davis /// \return A path.
1139b0d29bc4SBrooks Davis const fs::path&
root_work_directory(void) const1140b0d29bc4SBrooks Davis scheduler::scheduler_handle::root_work_directory(void) const
1141b0d29bc4SBrooks Davis {
1142b0d29bc4SBrooks Davis return _pimpl->generic.root_work_directory();
1143b0d29bc4SBrooks Davis }
1144b0d29bc4SBrooks Davis
1145b0d29bc4SBrooks Davis
1146b0d29bc4SBrooks Davis /// Cleans up the scheduler state.
1147b0d29bc4SBrooks Davis ///
1148b0d29bc4SBrooks Davis /// This function should be called explicitly as it provides the means to
1149b0d29bc4SBrooks Davis /// control any exceptions raised during cleanup. Do not rely on the destructor
1150b0d29bc4SBrooks Davis /// to clean things up.
1151b0d29bc4SBrooks Davis ///
1152b0d29bc4SBrooks Davis /// \throw engine::error If there are problems cleaning up the scheduler.
1153b0d29bc4SBrooks Davis void
cleanup(void)1154b0d29bc4SBrooks Davis scheduler::scheduler_handle::cleanup(void)
1155b0d29bc4SBrooks Davis {
1156b0d29bc4SBrooks Davis _pimpl->generic.cleanup();
1157b0d29bc4SBrooks Davis }
1158b0d29bc4SBrooks Davis
1159b0d29bc4SBrooks Davis
1160b0d29bc4SBrooks Davis /// Checks if the given interface name is valid.
1161b0d29bc4SBrooks Davis ///
1162b0d29bc4SBrooks Davis /// \param name The name of the interface to validate.
1163b0d29bc4SBrooks Davis ///
1164b0d29bc4SBrooks Davis /// \throw engine::error If the given interface is not supported.
1165b0d29bc4SBrooks Davis void
ensure_valid_interface(const std::string & name)1166b0d29bc4SBrooks Davis scheduler::ensure_valid_interface(const std::string& name)
1167b0d29bc4SBrooks Davis {
1168b0d29bc4SBrooks Davis if (interfaces.find(name) == interfaces.end())
1169b0d29bc4SBrooks Davis throw engine::error(F("Unsupported test interface '%s'") % name);
1170b0d29bc4SBrooks Davis }
1171b0d29bc4SBrooks Davis
1172b0d29bc4SBrooks Davis
1173b0d29bc4SBrooks Davis /// Registers a new interface.
1174b0d29bc4SBrooks Davis ///
1175b0d29bc4SBrooks Davis /// \param name The name of the interface. Must not have yet been registered.
1176b0d29bc4SBrooks Davis /// \param spec Interface specification.
1177b0d29bc4SBrooks Davis void
register_interface(const std::string & name,const std::shared_ptr<interface> spec)1178b0d29bc4SBrooks Davis scheduler::register_interface(const std::string& name,
1179b0d29bc4SBrooks Davis const std::shared_ptr< interface > spec)
1180b0d29bc4SBrooks Davis {
1181b0d29bc4SBrooks Davis PRE(interfaces.find(name) == interfaces.end());
1182b0d29bc4SBrooks Davis interfaces.insert(interfaces_map::value_type(name, spec));
1183b0d29bc4SBrooks Davis }
1184b0d29bc4SBrooks Davis
1185b0d29bc4SBrooks Davis
1186b0d29bc4SBrooks Davis /// Returns the names of all registered interfaces.
1187b0d29bc4SBrooks Davis ///
1188b0d29bc4SBrooks Davis /// \return A collection of interface names.
1189b0d29bc4SBrooks Davis std::set< std::string >
registered_interface_names(void)1190b0d29bc4SBrooks Davis scheduler::registered_interface_names(void)
1191b0d29bc4SBrooks Davis {
1192b0d29bc4SBrooks Davis std::set< std::string > names;
1193b0d29bc4SBrooks Davis for (interfaces_map::const_iterator iter = interfaces.begin();
1194b0d29bc4SBrooks Davis iter != interfaces.end(); ++iter) {
1195b0d29bc4SBrooks Davis names.insert((*iter).first);
1196b0d29bc4SBrooks Davis }
1197b0d29bc4SBrooks Davis return names;
1198b0d29bc4SBrooks Davis }
1199b0d29bc4SBrooks Davis
1200b0d29bc4SBrooks Davis
1201b0d29bc4SBrooks Davis /// Initializes the scheduler.
1202b0d29bc4SBrooks Davis ///
1203b0d29bc4SBrooks Davis /// \pre This function can only be called if there is no other scheduler_handle
1204b0d29bc4SBrooks Davis /// object alive.
1205b0d29bc4SBrooks Davis ///
1206b0d29bc4SBrooks Davis /// \return A handle to the operations of the scheduler.
1207b0d29bc4SBrooks Davis scheduler::scheduler_handle
setup(void)1208b0d29bc4SBrooks Davis scheduler::setup(void)
1209b0d29bc4SBrooks Davis {
1210b0d29bc4SBrooks Davis return scheduler_handle();
1211b0d29bc4SBrooks Davis }
1212b0d29bc4SBrooks Davis
1213b0d29bc4SBrooks Davis
1214b0d29bc4SBrooks Davis /// Retrieves the list of test cases from a test program.
1215b0d29bc4SBrooks Davis ///
1216b0d29bc4SBrooks Davis /// This operation is currently synchronous.
1217b0d29bc4SBrooks Davis ///
1218b0d29bc4SBrooks Davis /// This operation should never throw. Any errors during the processing of the
1219b0d29bc4SBrooks Davis /// test case list are subsumed into a single test case in the return value that
1220b0d29bc4SBrooks Davis /// represents the failed retrieval.
1221b0d29bc4SBrooks Davis ///
1222b0d29bc4SBrooks Davis /// \param test_program The test program from which to obtain the list of test
1223b0d29bc4SBrooks Davis /// cases.
1224b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
1225b0d29bc4SBrooks Davis ///
1226b0d29bc4SBrooks Davis /// \return The list of test cases.
1227b0d29bc4SBrooks Davis model::test_cases_map
list_tests(const model::test_program * test_program,const config::tree & user_config)1228b0d29bc4SBrooks Davis scheduler::scheduler_handle::list_tests(
1229b0d29bc4SBrooks Davis const model::test_program* test_program,
1230b0d29bc4SBrooks Davis const config::tree& user_config)
1231b0d29bc4SBrooks Davis {
1232b0d29bc4SBrooks Davis _pimpl->generic.check_interrupt();
1233b0d29bc4SBrooks Davis
1234b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface = find_interface(
1235b0d29bc4SBrooks Davis test_program->interface_name());
1236b0d29bc4SBrooks Davis
1237b0d29bc4SBrooks Davis try {
1238b0d29bc4SBrooks Davis const executor::exec_handle exec_handle = _pimpl->generic.spawn(
1239b0d29bc4SBrooks Davis list_test_cases(interface, test_program, user_config),
1240b0d29bc4SBrooks Davis list_timeout, none);
1241b0d29bc4SBrooks Davis executor::exit_handle exit_handle = _pimpl->generic.wait(exec_handle);
1242b0d29bc4SBrooks Davis
1243b0d29bc4SBrooks Davis const model::test_cases_map test_cases = interface->parse_list(
1244b0d29bc4SBrooks Davis exit_handle.status(),
1245b0d29bc4SBrooks Davis exit_handle.stdout_file(),
1246b0d29bc4SBrooks Davis exit_handle.stderr_file());
1247b0d29bc4SBrooks Davis
1248b0d29bc4SBrooks Davis exit_handle.cleanup();
1249b0d29bc4SBrooks Davis
1250b0d29bc4SBrooks Davis if (test_cases.empty())
1251b0d29bc4SBrooks Davis throw std::runtime_error("Empty test cases list");
1252b0d29bc4SBrooks Davis
1253b0d29bc4SBrooks Davis return test_cases;
1254b0d29bc4SBrooks Davis } catch (const std::runtime_error& e) {
1255b0d29bc4SBrooks Davis // TODO(jmmv): This is a very ugly workaround for the fact that we
1256b0d29bc4SBrooks Davis // cannot report failures at the test-program level.
1257b0d29bc4SBrooks Davis LW(F("Failed to load test cases list: %s") % e.what());
1258b0d29bc4SBrooks Davis model::test_cases_map fake_test_cases;
1259b0d29bc4SBrooks Davis fake_test_cases.insert(model::test_cases_map::value_type(
1260b0d29bc4SBrooks Davis "__test_cases_list__",
1261b0d29bc4SBrooks Davis model::test_case(
1262b0d29bc4SBrooks Davis "__test_cases_list__",
1263b0d29bc4SBrooks Davis "Represents the correct processing of the test cases list",
1264b0d29bc4SBrooks Davis model::test_result(model::test_result_broken, e.what()))));
1265b0d29bc4SBrooks Davis return fake_test_cases;
1266b0d29bc4SBrooks Davis }
1267b0d29bc4SBrooks Davis }
1268b0d29bc4SBrooks Davis
1269b0d29bc4SBrooks Davis
1270b0d29bc4SBrooks Davis /// Forks and executes a test case asynchronously.
1271b0d29bc4SBrooks Davis ///
1272b0d29bc4SBrooks Davis /// Note that the caller needn't know if the test has a cleanup routine or not.
1273b0d29bc4SBrooks Davis /// If there indeed is a cleanup routine, we trigger it at wait_any() time.
1274b0d29bc4SBrooks Davis ///
1275b0d29bc4SBrooks Davis /// \param test_program The container test program.
1276b0d29bc4SBrooks Davis /// \param test_case_name The name of the test case to run.
1277b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
1278b0d29bc4SBrooks Davis ///
1279b0d29bc4SBrooks Davis /// \return A handle for the background operation. Used to match the result of
1280b0d29bc4SBrooks Davis /// the execution returned by wait_any() with this invocation.
1281b0d29bc4SBrooks Davis scheduler::exec_handle
spawn_test(const model::test_program_ptr test_program,const std::string & test_case_name,const config::tree & user_config)1282b0d29bc4SBrooks Davis scheduler::scheduler_handle::spawn_test(
1283b0d29bc4SBrooks Davis const model::test_program_ptr test_program,
1284b0d29bc4SBrooks Davis const std::string& test_case_name,
1285b0d29bc4SBrooks Davis const config::tree& user_config)
1286b0d29bc4SBrooks Davis {
1287b0d29bc4SBrooks Davis _pimpl->generic.check_interrupt();
1288b0d29bc4SBrooks Davis
1289b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface = find_interface(
1290b0d29bc4SBrooks Davis test_program->interface_name());
1291b0d29bc4SBrooks Davis
1292b0d29bc4SBrooks Davis LI(F("Spawning %s:%s") % test_program->absolute_path() % test_case_name);
1293b0d29bc4SBrooks Davis
1294b0d29bc4SBrooks Davis const model::test_case& test_case = test_program->find(test_case_name);
1295b0d29bc4SBrooks Davis
1296b0d29bc4SBrooks Davis optional< passwd::user > unprivileged_user;
1297b0d29bc4SBrooks Davis if (user_config.is_set("unprivileged_user") &&
1298b0d29bc4SBrooks Davis test_case.get_metadata().required_user() == "unprivileged") {
1299b0d29bc4SBrooks Davis unprivileged_user = user_config.lookup< engine::user_node >(
1300b0d29bc4SBrooks Davis "unprivileged_user");
1301b0d29bc4SBrooks Davis }
1302b0d29bc4SBrooks Davis
1303b0d29bc4SBrooks Davis const executor::exec_handle handle = _pimpl->generic.spawn(
1304b0d29bc4SBrooks Davis run_test_program(interface, test_program, test_case_name,
1305b0d29bc4SBrooks Davis user_config),
1306b0d29bc4SBrooks Davis test_case.get_metadata().timeout(),
1307b0d29bc4SBrooks Davis unprivileged_user);
1308b0d29bc4SBrooks Davis
1309b0d29bc4SBrooks Davis const exec_data_ptr data(new test_exec_data(
1310257e70f1SIgor Ostapenko test_program, test_case_name, interface, user_config, handle.pid()));
1311b0d29bc4SBrooks Davis LD(F("Inserting %s into all_exec_data") % handle.pid());
1312b0d29bc4SBrooks Davis INV_MSG(
1313b0d29bc4SBrooks Davis _pimpl->all_exec_data.find(handle.pid()) == _pimpl->all_exec_data.end(),
1314b0d29bc4SBrooks Davis F("PID %s already in all_exec_data; not cleaned up or reused too fast")
1315b0d29bc4SBrooks Davis % handle.pid());;
1316b0d29bc4SBrooks Davis _pimpl->all_exec_data.insert(exec_data_map::value_type(handle.pid(), data));
1317b0d29bc4SBrooks Davis
1318b0d29bc4SBrooks Davis return handle.pid();
1319b0d29bc4SBrooks Davis }
1320b0d29bc4SBrooks Davis
1321b0d29bc4SBrooks Davis
1322b0d29bc4SBrooks Davis /// Waits for completion of any forked test case.
1323b0d29bc4SBrooks Davis ///
1324b0d29bc4SBrooks Davis /// Note that if the terminated test case has a cleanup routine, this function
1325b0d29bc4SBrooks Davis /// is the one in charge of spawning the cleanup routine asynchronously.
1326b0d29bc4SBrooks Davis ///
1327b0d29bc4SBrooks Davis /// \return The result of the execution of a subprocess. This is a dynamically
1328b0d29bc4SBrooks Davis /// allocated object because the scheduler can spawn subprocesses of various
1329b0d29bc4SBrooks Davis /// types and, at wait time, we don't know upfront what we are going to get.
1330b0d29bc4SBrooks Davis scheduler::result_handle_ptr
wait_any(void)1331b0d29bc4SBrooks Davis scheduler::scheduler_handle::wait_any(void)
1332b0d29bc4SBrooks Davis {
1333b0d29bc4SBrooks Davis _pimpl->generic.check_interrupt();
1334b0d29bc4SBrooks Davis
1335b0d29bc4SBrooks Davis executor::exit_handle handle = _pimpl->generic.wait_any();
1336b0d29bc4SBrooks Davis
1337b0d29bc4SBrooks Davis const exec_data_map::iterator iter = _pimpl->all_exec_data.find(
1338b0d29bc4SBrooks Davis handle.original_pid());
1339b0d29bc4SBrooks Davis exec_data_ptr data = (*iter).second;
1340b0d29bc4SBrooks Davis
1341b0d29bc4SBrooks Davis utils::dump_stacktrace_if_available(data->test_program->absolute_path(),
1342b0d29bc4SBrooks Davis _pimpl->generic, handle);
1343b0d29bc4SBrooks Davis
1344b0d29bc4SBrooks Davis optional< model::test_result > result;
1345257e70f1SIgor Ostapenko
1346257e70f1SIgor Ostapenko // test itself
1347b0d29bc4SBrooks Davis try {
1348b0d29bc4SBrooks Davis test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
1349b0d29bc4SBrooks Davis *data.get());
1350b0d29bc4SBrooks Davis LD(F("Got %s from all_exec_data") % handle.original_pid());
1351b0d29bc4SBrooks Davis
1352b0d29bc4SBrooks Davis test_data->exit_handle = handle;
1353b0d29bc4SBrooks Davis
1354b0d29bc4SBrooks Davis const model::test_case& test_case = test_data->test_program->find(
1355b0d29bc4SBrooks Davis test_data->test_case_name);
1356b0d29bc4SBrooks Davis
1357b0d29bc4SBrooks Davis result = test_case.fake_result();
1358b0d29bc4SBrooks Davis
1359b0d29bc4SBrooks Davis if (!result && handle.status() && handle.status().get().exited() &&
1360b0d29bc4SBrooks Davis handle.status().get().exitstatus() == exit_skipped) {
1361b0d29bc4SBrooks Davis // If the test's process terminated with our magic "exit_skipped"
1362b0d29bc4SBrooks Davis // status, there are two cases to handle. The first is the case
1363b0d29bc4SBrooks Davis // where the "skipped cookie" exists, in which case we never got to
1364b0d29bc4SBrooks Davis // actually invoke the test program; if that's the case, handle it
1365b0d29bc4SBrooks Davis // here. The second case is where the test case actually decided to
1366b0d29bc4SBrooks Davis // exit with the "exit_skipped" status; in that case, just fall back
1367b0d29bc4SBrooks Davis // to the regular status handling.
1368b0d29bc4SBrooks Davis const fs::path skipped_cookie_path = handle.control_directory() /
1369b0d29bc4SBrooks Davis skipped_cookie;
1370b0d29bc4SBrooks Davis std::ifstream input(skipped_cookie_path.c_str());
1371b0d29bc4SBrooks Davis if (input) {
1372b0d29bc4SBrooks Davis result = model::test_result(model::test_result_skipped,
1373b0d29bc4SBrooks Davis utils::read_stream(input));
1374b0d29bc4SBrooks Davis input.close();
1375b0d29bc4SBrooks Davis
1376b0d29bc4SBrooks Davis // If we determined that the test needs to be skipped, we do not
1377b0d29bc4SBrooks Davis // want to run the cleanup routine because doing so could result
1378b0d29bc4SBrooks Davis // in errors. However, we still want to run the cleanup routine
1379b0d29bc4SBrooks Davis // if the test's body reports a skip (because actions could have
1380b0d29bc4SBrooks Davis // already been taken).
1381b0d29bc4SBrooks Davis test_data->needs_cleanup = false;
1382257e70f1SIgor Ostapenko test_data->needs_execenv_cleanup = false;
1383b0d29bc4SBrooks Davis }
1384b0d29bc4SBrooks Davis }
1385b0d29bc4SBrooks Davis if (!result) {
1386b0d29bc4SBrooks Davis result = test_data->interface->compute_result(
1387b0d29bc4SBrooks Davis handle.status(),
1388b0d29bc4SBrooks Davis handle.control_directory(),
1389b0d29bc4SBrooks Davis handle.stdout_file(),
1390b0d29bc4SBrooks Davis handle.stderr_file());
1391b0d29bc4SBrooks Davis }
1392b0d29bc4SBrooks Davis INV(result);
1393b0d29bc4SBrooks Davis
1394b0d29bc4SBrooks Davis if (!result.get().good()) {
1395b0d29bc4SBrooks Davis append_files_listing(handle.work_directory(),
1396b0d29bc4SBrooks Davis handle.stderr_file());
1397b0d29bc4SBrooks Davis }
1398b0d29bc4SBrooks Davis
1399b0d29bc4SBrooks Davis if (test_data->needs_cleanup) {
1400b0d29bc4SBrooks Davis INV(test_case.get_metadata().has_cleanup());
1401b0d29bc4SBrooks Davis // The test body has completed and we have processed it. If there
1402b0d29bc4SBrooks Davis // is a cleanup routine, trigger it now and wait for any other test
1403b0d29bc4SBrooks Davis // completion. The caller never knows about cleanup routines.
1404b0d29bc4SBrooks Davis _pimpl->spawn_cleanup(test_data->test_program,
1405b0d29bc4SBrooks Davis test_data->test_case_name,
1406b0d29bc4SBrooks Davis test_data->user_config, handle, result.get());
1407b0d29bc4SBrooks Davis
1408b0d29bc4SBrooks Davis // TODO(jmmv): Chaining this call is ugly. We'd be better off by
1409b0d29bc4SBrooks Davis // looping over terminated processes until we got a result suitable
1410b0d29bc4SBrooks Davis // for user consumption. For the time being this is good enough and
1411b0d29bc4SBrooks Davis // not a problem because the call chain won't get big: the majority
1412b0d29bc4SBrooks Davis // of test cases do not have cleanup routines.
1413b0d29bc4SBrooks Davis return wait_any();
1414b0d29bc4SBrooks Davis }
1415257e70f1SIgor Ostapenko
1416257e70f1SIgor Ostapenko if (test_data->needs_execenv_cleanup) {
1417257e70f1SIgor Ostapenko INV(test_case.get_metadata().has_execenv());
1418257e70f1SIgor Ostapenko _pimpl->spawn_execenv_cleanup(test_data->test_program,
1419257e70f1SIgor Ostapenko test_data->test_case_name,
1420257e70f1SIgor Ostapenko handle, result.get());
1421257e70f1SIgor Ostapenko test_data->needs_execenv_cleanup = false;
1422257e70f1SIgor Ostapenko return wait_any();
1423257e70f1SIgor Ostapenko }
1424b0d29bc4SBrooks Davis } catch (const std::bad_cast& e) {
1425257e70f1SIgor Ostapenko // ok, let's check for another type
1426257e70f1SIgor Ostapenko }
1427257e70f1SIgor Ostapenko
1428257e70f1SIgor Ostapenko // test cleanup
1429257e70f1SIgor Ostapenko try {
1430b0d29bc4SBrooks Davis const cleanup_exec_data* cleanup_data =
1431b0d29bc4SBrooks Davis &dynamic_cast< const cleanup_exec_data& >(*data.get());
1432b0d29bc4SBrooks Davis LD(F("Got %s from all_exec_data (cleanup)") % handle.original_pid());
1433b0d29bc4SBrooks Davis
1434b0d29bc4SBrooks Davis // Handle the completion of cleanup subprocesses internally: the caller
1435b0d29bc4SBrooks Davis // is not aware that these exist so, when we return, we must return the
1436b0d29bc4SBrooks Davis // data for the original test that triggered this routine. For example,
1437b0d29bc4SBrooks Davis // because the caller wants to see the exact same exec_handle that was
1438b0d29bc4SBrooks Davis // returned by spawn_test.
1439b0d29bc4SBrooks Davis
1440b0d29bc4SBrooks Davis const model::test_result& body_result = cleanup_data->body_result;
1441b0d29bc4SBrooks Davis if (body_result.good()) {
1442b0d29bc4SBrooks Davis if (!handle.status()) {
1443b0d29bc4SBrooks Davis result = model::test_result(model::test_result_broken,
1444b0d29bc4SBrooks Davis "Test case cleanup timed out");
1445b0d29bc4SBrooks Davis } else {
1446b0d29bc4SBrooks Davis if (!handle.status().get().exited() ||
1447b0d29bc4SBrooks Davis handle.status().get().exitstatus() != EXIT_SUCCESS) {
1448b0d29bc4SBrooks Davis result = model::test_result(
1449b0d29bc4SBrooks Davis model::test_result_broken,
1450b0d29bc4SBrooks Davis "Test case cleanup did not terminate successfully");
1451b0d29bc4SBrooks Davis } else {
1452b0d29bc4SBrooks Davis result = body_result;
1453b0d29bc4SBrooks Davis }
1454b0d29bc4SBrooks Davis }
1455b0d29bc4SBrooks Davis } else {
1456b0d29bc4SBrooks Davis result = body_result;
1457b0d29bc4SBrooks Davis }
1458b0d29bc4SBrooks Davis
1459b0d29bc4SBrooks Davis // Untrack the cleanup process. This must be done explicitly because we
1460b0d29bc4SBrooks Davis // do not create a result_handle object for the cleanup, and that is the
1461b0d29bc4SBrooks Davis // one in charge of doing so in the regular (non-cleanup) case.
1462b0d29bc4SBrooks Davis LD(F("Removing %s from all_exec_data (cleanup) in favor of %s")
1463b0d29bc4SBrooks Davis % handle.original_pid()
1464b0d29bc4SBrooks Davis % cleanup_data->body_exit_handle.original_pid());
1465b0d29bc4SBrooks Davis _pimpl->all_exec_data.erase(handle.original_pid());
1466b0d29bc4SBrooks Davis
1467b0d29bc4SBrooks Davis handle = cleanup_data->body_exit_handle;
1468257e70f1SIgor Ostapenko
1469257e70f1SIgor Ostapenko const exec_data_map::iterator it = _pimpl->all_exec_data.find(
1470257e70f1SIgor Ostapenko handle.original_pid());
1471257e70f1SIgor Ostapenko if (it != _pimpl->all_exec_data.end()) {
1472257e70f1SIgor Ostapenko exec_data_ptr d = (*it).second;
1473257e70f1SIgor Ostapenko test_exec_data* test_data = &dynamic_cast< test_exec_data& >(
1474257e70f1SIgor Ostapenko *d.get());
1475257e70f1SIgor Ostapenko const model::test_case& test_case =
1476257e70f1SIgor Ostapenko cleanup_data->test_program->find(cleanup_data->test_case_name);
1477257e70f1SIgor Ostapenko test_data->needs_cleanup = false;
1478257e70f1SIgor Ostapenko
1479257e70f1SIgor Ostapenko if (test_data->needs_execenv_cleanup) {
1480257e70f1SIgor Ostapenko INV(test_case.get_metadata().has_execenv());
1481257e70f1SIgor Ostapenko _pimpl->spawn_execenv_cleanup(cleanup_data->test_program,
1482257e70f1SIgor Ostapenko cleanup_data->test_case_name,
1483257e70f1SIgor Ostapenko handle, result.get());
1484257e70f1SIgor Ostapenko test_data->needs_execenv_cleanup = false;
1485257e70f1SIgor Ostapenko return wait_any();
1486b0d29bc4SBrooks Davis }
1487257e70f1SIgor Ostapenko }
1488257e70f1SIgor Ostapenko } catch (const std::bad_cast& e) {
1489257e70f1SIgor Ostapenko // ok, let's check for another type
1490257e70f1SIgor Ostapenko }
1491257e70f1SIgor Ostapenko
1492257e70f1SIgor Ostapenko // execenv cleanup
1493257e70f1SIgor Ostapenko try {
1494257e70f1SIgor Ostapenko const execenv_exec_data* execenv_data =
1495257e70f1SIgor Ostapenko &dynamic_cast< const execenv_exec_data& >(*data.get());
1496257e70f1SIgor Ostapenko LD(F("Got %s from all_exec_data (execenv cleanup)") % handle.original_pid());
1497257e70f1SIgor Ostapenko
1498257e70f1SIgor Ostapenko const model::test_result& body_result = execenv_data->body_result;
1499257e70f1SIgor Ostapenko if (body_result.good()) {
1500257e70f1SIgor Ostapenko if (!handle.status()) {
1501257e70f1SIgor Ostapenko result = model::test_result(model::test_result_broken,
1502257e70f1SIgor Ostapenko "Test case execenv cleanup timed out");
1503257e70f1SIgor Ostapenko } else {
1504257e70f1SIgor Ostapenko if (!handle.status().get().exited() ||
1505257e70f1SIgor Ostapenko handle.status().get().exitstatus() != EXIT_SUCCESS) {
1506257e70f1SIgor Ostapenko result = model::test_result(
1507257e70f1SIgor Ostapenko model::test_result_broken,
1508257e70f1SIgor Ostapenko "Test case execenv cleanup did not terminate successfully"); // ?
1509257e70f1SIgor Ostapenko } else {
1510257e70f1SIgor Ostapenko result = body_result;
1511257e70f1SIgor Ostapenko }
1512257e70f1SIgor Ostapenko }
1513257e70f1SIgor Ostapenko } else {
1514257e70f1SIgor Ostapenko result = body_result;
1515257e70f1SIgor Ostapenko }
1516257e70f1SIgor Ostapenko
1517257e70f1SIgor Ostapenko LD(F("Removing %s from all_exec_data (execenv cleanup) in favor of %s")
1518257e70f1SIgor Ostapenko % handle.original_pid()
1519257e70f1SIgor Ostapenko % execenv_data->body_exit_handle.original_pid());
1520257e70f1SIgor Ostapenko _pimpl->all_exec_data.erase(handle.original_pid());
1521257e70f1SIgor Ostapenko
1522257e70f1SIgor Ostapenko handle = execenv_data->body_exit_handle;
1523257e70f1SIgor Ostapenko } catch (const std::bad_cast& e) {
1524257e70f1SIgor Ostapenko // ok, it was one of the types above
1525257e70f1SIgor Ostapenko }
1526257e70f1SIgor Ostapenko
1527b0d29bc4SBrooks Davis INV(result);
1528b0d29bc4SBrooks Davis
1529b0d29bc4SBrooks Davis std::shared_ptr< result_handle::bimpl > result_handle_bimpl(
1530b0d29bc4SBrooks Davis new result_handle::bimpl(handle, _pimpl->all_exec_data));
1531b0d29bc4SBrooks Davis std::shared_ptr< test_result_handle::impl > test_result_handle_impl(
1532b0d29bc4SBrooks Davis new test_result_handle::impl(
1533b0d29bc4SBrooks Davis data->test_program, data->test_case_name, result.get()));
1534b0d29bc4SBrooks Davis return result_handle_ptr(new test_result_handle(result_handle_bimpl,
1535b0d29bc4SBrooks Davis test_result_handle_impl));
1536b0d29bc4SBrooks Davis }
1537b0d29bc4SBrooks Davis
1538b0d29bc4SBrooks Davis
1539b0d29bc4SBrooks Davis /// Forks and executes a test case synchronously for debugging.
1540b0d29bc4SBrooks Davis ///
1541b0d29bc4SBrooks Davis /// \pre No other processes should be in execution by the scheduler.
1542b0d29bc4SBrooks Davis ///
1543b0d29bc4SBrooks Davis /// \param test_program The container test program.
1544b0d29bc4SBrooks Davis /// \param test_case_name The name of the test case to run.
1545b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables.
1546b0d29bc4SBrooks Davis /// \param stdout_target File to which to write the stdout of the test case.
1547b0d29bc4SBrooks Davis /// \param stderr_target File to which to write the stderr of the test case.
1548b0d29bc4SBrooks Davis ///
1549b0d29bc4SBrooks Davis /// \return The result of the execution of the test.
1550b0d29bc4SBrooks Davis scheduler::result_handle_ptr
debug_test(const model::test_program_ptr test_program,const std::string & test_case_name,const config::tree & user_config,const fs::path & stdout_target,const fs::path & stderr_target)1551b0d29bc4SBrooks Davis scheduler::scheduler_handle::debug_test(
1552b0d29bc4SBrooks Davis const model::test_program_ptr test_program,
1553b0d29bc4SBrooks Davis const std::string& test_case_name,
1554b0d29bc4SBrooks Davis const config::tree& user_config,
1555b0d29bc4SBrooks Davis const fs::path& stdout_target,
1556b0d29bc4SBrooks Davis const fs::path& stderr_target)
1557b0d29bc4SBrooks Davis {
1558b0d29bc4SBrooks Davis const exec_handle exec_handle = spawn_test(
1559b0d29bc4SBrooks Davis test_program, test_case_name, user_config);
1560b0d29bc4SBrooks Davis result_handle_ptr result_handle = wait_any();
1561b0d29bc4SBrooks Davis
1562b0d29bc4SBrooks Davis // TODO(jmmv): We need to do this while the subprocess is alive. This is
1563b0d29bc4SBrooks Davis // important for debugging purposes, as we should see the contents of stdout
1564b0d29bc4SBrooks Davis // or stderr as they come in.
1565b0d29bc4SBrooks Davis //
1566b0d29bc4SBrooks Davis // Unfortunately, we cannot do so. We cannot just read and block from a
1567b0d29bc4SBrooks Davis // file, waiting for further output to appear... as this only works on pipes
1568b0d29bc4SBrooks Davis // or sockets. We need a better interface for this whole thing.
1569b0d29bc4SBrooks Davis {
1570b0d29bc4SBrooks Davis std::auto_ptr< std::ostream > output = utils::open_ostream(
1571b0d29bc4SBrooks Davis stdout_target);
1572b0d29bc4SBrooks Davis *output << utils::read_file(result_handle->stdout_file());
1573b0d29bc4SBrooks Davis }
1574b0d29bc4SBrooks Davis {
1575b0d29bc4SBrooks Davis std::auto_ptr< std::ostream > output = utils::open_ostream(
1576b0d29bc4SBrooks Davis stderr_target);
1577b0d29bc4SBrooks Davis *output << utils::read_file(result_handle->stderr_file());
1578b0d29bc4SBrooks Davis }
1579b0d29bc4SBrooks Davis
1580b0d29bc4SBrooks Davis INV(result_handle->original_pid() == exec_handle);
1581b0d29bc4SBrooks Davis return result_handle;
1582b0d29bc4SBrooks Davis }
1583b0d29bc4SBrooks Davis
1584b0d29bc4SBrooks Davis
1585b0d29bc4SBrooks Davis /// Checks if an interrupt has fired.
1586b0d29bc4SBrooks Davis ///
1587b0d29bc4SBrooks Davis /// Calls to this function should be sprinkled in strategic places through the
1588b0d29bc4SBrooks Davis /// code protected by an interrupts_handler object.
1589b0d29bc4SBrooks Davis ///
1590b0d29bc4SBrooks Davis /// This is just a wrapper over signals::check_interrupt() to avoid leaking this
1591b0d29bc4SBrooks Davis /// dependency to the caller.
1592b0d29bc4SBrooks Davis ///
1593b0d29bc4SBrooks Davis /// \throw signals::interrupted_error If there has been an interrupt.
1594b0d29bc4SBrooks Davis void
check_interrupt(void) const1595b0d29bc4SBrooks Davis scheduler::scheduler_handle::check_interrupt(void) const
1596b0d29bc4SBrooks Davis {
1597b0d29bc4SBrooks Davis _pimpl->generic.check_interrupt();
1598b0d29bc4SBrooks Davis }
1599b0d29bc4SBrooks Davis
1600b0d29bc4SBrooks Davis
1601b0d29bc4SBrooks Davis /// Queries the current execution context.
1602b0d29bc4SBrooks Davis ///
1603b0d29bc4SBrooks Davis /// \return The queried context.
1604b0d29bc4SBrooks Davis model::context
current_context(void)1605b0d29bc4SBrooks Davis scheduler::current_context(void)
1606b0d29bc4SBrooks Davis {
1607b0d29bc4SBrooks Davis return model::context(fs::current_path(), utils::getallenv());
1608b0d29bc4SBrooks Davis }
1609b0d29bc4SBrooks Davis
1610b0d29bc4SBrooks Davis
1611b0d29bc4SBrooks Davis /// Generates the set of configuration variables for a test program.
1612b0d29bc4SBrooks Davis ///
1613b0d29bc4SBrooks Davis /// \param user_config The configuration variables provided by the user.
1614b0d29bc4SBrooks Davis /// \param test_suite The name of the test suite.
1615b0d29bc4SBrooks Davis ///
1616b0d29bc4SBrooks Davis /// \return The mapping of configuration variables for the test program.
1617b0d29bc4SBrooks Davis config::properties_map
generate_config(const config::tree & user_config,const std::string & test_suite)1618b0d29bc4SBrooks Davis scheduler::generate_config(const config::tree& user_config,
1619b0d29bc4SBrooks Davis const std::string& test_suite)
1620b0d29bc4SBrooks Davis {
1621b0d29bc4SBrooks Davis config::properties_map props;
1622b0d29bc4SBrooks Davis
1623b0d29bc4SBrooks Davis try {
1624b0d29bc4SBrooks Davis props = user_config.all_properties(F("test_suites.%s") % test_suite,
1625b0d29bc4SBrooks Davis true);
1626b0d29bc4SBrooks Davis } catch (const config::unknown_key_error& unused_error) {
1627b0d29bc4SBrooks Davis // Ignore: not all test suites have entries in the configuration.
1628b0d29bc4SBrooks Davis }
1629b0d29bc4SBrooks Davis
1630b0d29bc4SBrooks Davis // TODO(jmmv): This is a hack that exists for the ATF interface only, so it
1631b0d29bc4SBrooks Davis // should be moved there.
1632b0d29bc4SBrooks Davis if (user_config.is_set("unprivileged_user")) {
1633b0d29bc4SBrooks Davis const passwd::user& user =
1634b0d29bc4SBrooks Davis user_config.lookup< engine::user_node >("unprivileged_user");
1635*51a8eb64SIgor Ostapenko // The property is duplicated using both ATF and Kyua naming styles
1636*51a8eb64SIgor Ostapenko // for better UX.
1637b0d29bc4SBrooks Davis props["unprivileged-user"] = user.name;
1638*51a8eb64SIgor Ostapenko props["unprivileged_user"] = user.name;
1639b0d29bc4SBrooks Davis }
1640b0d29bc4SBrooks Davis
1641b0d29bc4SBrooks Davis return props;
1642b0d29bc4SBrooks Davis }
1643