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