xref: /freebsd/contrib/kyua/engine/scheduler.cpp (revision 257e70f1d5ee61037c8c59b116538d3b6b1427a2)
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