xref: /freebsd/contrib/kyua/engine/scheduler.hpp (revision 350f319731897f741b516db99636e7ced071dfa4)
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 /// \file engine/scheduler.hpp
30 /// Multiprogrammed executor of test related operations.
31 ///
32 /// The scheduler's public interface exposes test cases as "black boxes".  The
33 /// handling of cleanup routines is completely hidden from the caller and
34 /// happens in two cases: first, once a test case completes; and, second, in the
35 /// case of abrupt termination due to the reception of a signal.
36 ///
37 /// Hiding cleanup routines from the caller is an attempt to keep the logic of
38 /// execution and results handling in a single place.  Otherwise, the various
39 /// drivers (say run_tests and debug_test) would need to replicate the handling
40 /// of this logic, which is tricky in itself (particularly due to signal
41 /// handling) and would lead to inconsistencies.
42 ///
43 /// Handling cleanup routines in the manner described above is *incredibly
44 /// complicated* (insane, actually) as you will see from the code.  The
45 /// complexity will bite us in the future (today is 2015-06-26).  Switching to a
46 /// threads-based implementation would probably simplify the code flow
47 /// significantly and allow parallelization of the test case listings in a
48 /// reasonable manner, though it depends on whether we can get clean handling of
49 /// signals and on whether we could use C++11's std::thread.  (Is this a to-do?
50 /// Maybe.  Maybe not.)
51 ///
52 /// See the documentation in utils/process/executor.hpp for details on
53 /// the expected workflow of these classes.
54 
55 #if !defined(ENGINE_SCHEDULER_HPP)
56 #define ENGINE_SCHEDULER_HPP
57 
58 #include "engine/scheduler_fwd.hpp"
59 
60 #include <memory>
61 #include <set>
62 #include <string>
63 
64 #include "model/context_fwd.hpp"
65 #include "model/metadata_fwd.hpp"
66 #include "model/test_case_fwd.hpp"
67 #include "model/test_program.hpp"
68 #include "model/test_result_fwd.hpp"
69 #include "utils/config/tree_fwd.hpp"
70 #include "utils/datetime_fwd.hpp"
71 #include "utils/defs.hpp"
72 #include "utils/fs/path_fwd.hpp"
73 #include "utils/optional.hpp"
74 #include "utils/process/executor_fwd.hpp"
75 #include "utils/process/status_fwd.hpp"
76 
77 using utils::none;
78 
79 namespace engine {
80 namespace scheduler {
81 
82 
83 /// Abstract interface of a test program scheduler interface.
84 ///
85 /// This interface defines the test program-specific operations that need to be
86 /// invoked at different points during the execution of a given test case.  The
87 /// scheduler internally instantiates one of these for every test case.
88 class interface {
89 public:
90     /// Destructor.
~interface()91     virtual ~interface() {}
92 
93     /// Executes a test program's list operation.
94     ///
95     /// This method is intended to be called within a subprocess and is expected
96     /// to terminate execution either by exec(2)ing the test program or by
97     /// exiting with a failure.
98     ///
99     /// \param test_program The test program to execute.
100     /// \param vars User-provided variables to pass to the test program.
101     virtual void exec_list(const model::test_program& test_program,
102                            const utils::config::properties_map& vars)
103         const UTILS_NORETURN = 0;
104 
105     /// Computes the test cases list of a test program.
106     ///
107     /// \param status The termination status of the subprocess used to execute
108     ///     the exec_test() method or none if the test timed out.
109     /// \param stdout_path Path to the file containing the stdout of the test.
110     /// \param stderr_path Path to the file containing the stderr of the test.
111     ///
112     /// \return A list of test cases.
113     virtual model::test_cases_map parse_list(
114         const utils::optional< utils::process::status >& status,
115         const utils::fs::path& stdout_path,
116         const utils::fs::path& stderr_path) const = 0;
117 
118     /// Executes a test case of the test program.
119     ///
120     /// This method is intended to be called within a subprocess and is expected
121     /// to terminate execution either by exec(2)ing the test program or by
122     /// exiting with a failure.
123     ///
124     /// \param test_program The test program to execute.
125     /// \param test_case_name Name of the test case to invoke.
126     /// \param vars User-provided variables to pass to the test program.
127     /// \param control_directory Directory where the interface may place control
128     ///     files.
129     virtual void exec_test(const model::test_program& test_program,
130                            const std::string& test_case_name,
131                            const utils::config::properties_map& vars,
132                            const utils::fs::path& control_directory)
133         const UTILS_NORETURN = 0;
134 
135     /// Executes a test cleanup routine of the test program.
136     ///
137     /// This method is intended to be called within a subprocess and is expected
138     /// to terminate execution either by exec(2)ing the test program or by
139     /// exiting with a failure.
140     ///
141     /// \param test_program The test program to execute.
142     /// \param test_case_name Name of the test case to invoke.
143     /// \param vars User-provided variables to pass to the test program.
144     /// \param control_directory Directory where the interface may place control
145     ///     files.
146     virtual void exec_cleanup(const model::test_program& test_program,
147                               const std::string& test_case_name,
148                               const utils::config::properties_map& vars,
149                               const utils::fs::path& control_directory)
150         const UTILS_NORETURN;
151 
152     /// Computes the result of a test case based on its termination status.
153     ///
154     /// \param status The termination status of the subprocess used to execute
155     ///     the exec_test() method or none if the test timed out.
156     /// \param control_directory Directory where the interface may have placed
157     ///     control files.
158     /// \param stdout_path Path to the file containing the stdout of the test.
159     /// \param stderr_path Path to the file containing the stderr of the test.
160     ///
161     /// \return A test result.
162     virtual model::test_result compute_result(
163         const utils::optional< utils::process::status >& status,
164         const utils::fs::path& control_directory,
165         const utils::fs::path& stdout_path,
166         const utils::fs::path& stderr_path) const = 0;
167 };
168 
169 
170 /// Implementation of a test program with lazy loading of test cases.
171 class lazy_test_program : public model::test_program {
172     struct impl;
173 
174     /// Pointer to the shared internal implementation.
175     std::shared_ptr< impl > _pimpl;
176 
177 public:
178     lazy_test_program(const std::string&, const utils::fs::path&,
179                       const utils::fs::path&, const std::string&,
180                       const model::metadata&,
181                       const utils::config::tree&,
182                       scheduler_handle&);
183 
184     const model::test_cases_map& test_cases(void) const;
185 };
186 
187 
188 /// Base type containing the results of the execution of a subprocess.
189 class result_handle {
190 protected:
191     struct bimpl;
192 
193 private:
194     /// Pointer to internal implementation of the base type.
195     std::shared_ptr< bimpl > _pbimpl;
196 
197 protected:
198     friend class scheduler_handle;
199     result_handle(std::shared_ptr< bimpl >);
200 
201 public:
202     virtual ~result_handle(void) = 0;
203 
204     void cleanup(void);
205 
206     int original_pid(void) const;
207     const utils::datetime::timestamp& start_time() const;
208     const utils::datetime::timestamp& end_time() const;
209     utils::fs::path work_directory(void) const;
210     const utils::fs::path& stdout_file(void) const;
211     const utils::fs::path& stderr_file(void) const;
212 };
213 
214 
215 /// Container for all test termination data and accessor to cleanup operations.
216 class test_result_handle : public result_handle {
217     struct impl;
218     /// Pointer to internal implementation.
219     std::shared_ptr< impl > _pimpl;
220 
221     friend class scheduler_handle;
222     test_result_handle(std::shared_ptr< bimpl >, std::shared_ptr< impl >);
223 
224 public:
225     ~test_result_handle(void);
226 
227     const model::test_program_ptr test_program(void) const;
228     const std::string& test_case_name(void) const;
229     const model::test_result& test_result(void) const;
230 };
231 
232 
233 /// Stateful interface to the multiprogrammed execution of tests.
234 class scheduler_handle {
235     struct impl;
236     /// Pointer to internal implementation.
237     std::shared_ptr< impl > _pimpl;
238 
239     friend scheduler_handle setup(void);
240     scheduler_handle(void);
241 
242 public:
243     ~scheduler_handle(void);
244 
245     const utils::fs::path& root_work_directory(void) const;
246 
247     void cleanup(void);
248 
249     model::test_cases_map list_tests(const model::test_program*,
250                                      const utils::config::tree&);
251     exec_handle spawn_test(const model::test_program_ptr,
252                            const std::string&,
253                            const utils::config::tree&,
254                            const utils::optional<utils::fs::path>& = none,
255                            const utils::optional<utils::fs::path>& = none);
256     result_handle_ptr wait_any(void);
257 
258     result_handle_ptr debug_test(const model::test_program_ptr,
259                                  const std::string&,
260                                  const utils::config::tree&,
261                                  const utils::fs::path&,
262                                  const utils::fs::path&);
263 
264     void check_interrupt(void) const;
265 };
266 
267 
268 extern utils::datetime::delta cleanup_timeout;
269 extern utils::datetime::delta execenv_cleanup_timeout;
270 extern utils::datetime::delta list_timeout;
271 
272 
273 void ensure_valid_interface(const std::string&);
274 void register_interface(const std::string&, const std::shared_ptr< interface >);
275 std::set< std::string > registered_interface_names(void);
276 scheduler_handle setup(void);
277 
278 model::context current_context(void);
279 utils::config::properties_map generate_config(const utils::config::tree&,
280                                               const std::string&);
281 
282 
283 }  // namespace scheduler
284 }  // namespace engine
285 
286 
287 #endif  // !defined(ENGINE_SCHEDULER_HPP)
288