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