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