xref: /freebsd/contrib/kyua/engine/scheduler.hpp (revision dc318a4ffabcbfa23bb56a33403aad36e6de30af)
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 namespace engine {
78 namespace scheduler {
79 
80 
81 /// Abstract interface of a test program scheduler interface.
82 ///
83 /// This interface defines the test program-specific operations that need to be
84 /// invoked at different points during the execution of a given test case.  The
85 /// scheduler internally instantiates one of these for every test case.
86 class interface {
87 public:
88     /// Destructor.
89     virtual ~interface() {}
90 
91     /// Executes a test program's list operation.
92     ///
93     /// This method is intended to be called within a subprocess and is expected
94     /// to terminate execution either by exec(2)ing the test program or by
95     /// exiting with a failure.
96     ///
97     /// \param test_program The test program to execute.
98     /// \param vars User-provided variables to pass to the test program.
99     virtual void exec_list(const model::test_program& test_program,
100                            const utils::config::properties_map& vars)
101         const UTILS_NORETURN = 0;
102 
103     /// Computes the test cases list of a test program.
104     ///
105     /// \param status The termination status of the subprocess used to execute
106     ///     the exec_test() method or none if the test timed out.
107     /// \param stdout_path Path to the file containing the stdout of the test.
108     /// \param stderr_path Path to the file containing the stderr of the test.
109     ///
110     /// \return A list of test cases.
111     virtual model::test_cases_map parse_list(
112         const utils::optional< utils::process::status >& status,
113         const utils::fs::path& stdout_path,
114         const utils::fs::path& stderr_path) const = 0;
115 
116     /// Executes a test case of the test program.
117     ///
118     /// This method is intended to be called within a subprocess and is expected
119     /// to terminate execution either by exec(2)ing the test program or by
120     /// exiting with a failure.
121     ///
122     /// \param test_program The test program to execute.
123     /// \param test_case_name Name of the test case to invoke.
124     /// \param vars User-provided variables to pass to the test program.
125     /// \param control_directory Directory where the interface may place control
126     ///     files.
127     virtual void exec_test(const model::test_program& test_program,
128                            const std::string& test_case_name,
129                            const utils::config::properties_map& vars,
130                            const utils::fs::path& control_directory)
131         const UTILS_NORETURN = 0;
132 
133     /// Executes a test cleanup routine of the test program.
134     ///
135     /// This method is intended to be called within a subprocess and is expected
136     /// to terminate execution either by exec(2)ing the test program or by
137     /// exiting with a failure.
138     ///
139     /// \param test_program The test program to execute.
140     /// \param test_case_name Name of the test case to invoke.
141     /// \param vars User-provided variables to pass to the test program.
142     /// \param control_directory Directory where the interface may place control
143     ///     files.
144     virtual void exec_cleanup(const model::test_program& test_program,
145                               const std::string& test_case_name,
146                               const utils::config::properties_map& vars,
147                               const utils::fs::path& control_directory)
148         const UTILS_NORETURN;
149 
150     /// Computes the result of a test case based on its termination status.
151     ///
152     /// \param status The termination status of the subprocess used to execute
153     ///     the exec_test() method or none if the test timed out.
154     /// \param control_directory Directory where the interface may have placed
155     ///     control files.
156     /// \param stdout_path Path to the file containing the stdout of the test.
157     /// \param stderr_path Path to the file containing the stderr of the test.
158     ///
159     /// \return A test result.
160     virtual model::test_result compute_result(
161         const utils::optional< utils::process::status >& status,
162         const utils::fs::path& control_directory,
163         const utils::fs::path& stdout_path,
164         const utils::fs::path& stderr_path) const = 0;
165 };
166 
167 
168 /// Implementation of a test program with lazy loading of test cases.
169 class lazy_test_program : public model::test_program {
170     struct impl;
171 
172     /// Pointer to the shared internal implementation.
173     std::shared_ptr< impl > _pimpl;
174 
175 public:
176     lazy_test_program(const std::string&, const utils::fs::path&,
177                       const utils::fs::path&, const std::string&,
178                       const model::metadata&,
179                       const utils::config::tree&,
180                       scheduler_handle&);
181 
182     const model::test_cases_map& test_cases(void) const;
183 };
184 
185 
186 /// Base type containing the results of the execution of a subprocess.
187 class result_handle {
188 protected:
189     struct bimpl;
190 
191 private:
192     /// Pointer to internal implementation of the base type.
193     std::shared_ptr< bimpl > _pbimpl;
194 
195 protected:
196     friend class scheduler_handle;
197     result_handle(std::shared_ptr< bimpl >);
198 
199 public:
200     virtual ~result_handle(void) = 0;
201 
202     void cleanup(void);
203 
204     int original_pid(void) const;
205     const utils::datetime::timestamp& start_time() const;
206     const utils::datetime::timestamp& end_time() const;
207     utils::fs::path work_directory(void) const;
208     const utils::fs::path& stdout_file(void) const;
209     const utils::fs::path& stderr_file(void) const;
210 };
211 
212 
213 /// Container for all test termination data and accessor to cleanup operations.
214 class test_result_handle : public result_handle {
215     struct impl;
216     /// Pointer to internal implementation.
217     std::shared_ptr< impl > _pimpl;
218 
219     friend class scheduler_handle;
220     test_result_handle(std::shared_ptr< bimpl >, std::shared_ptr< impl >);
221 
222 public:
223     ~test_result_handle(void);
224 
225     const model::test_program_ptr test_program(void) const;
226     const std::string& test_case_name(void) const;
227     const model::test_result& test_result(void) const;
228 };
229 
230 
231 /// Stateful interface to the multiprogrammed execution of tests.
232 class scheduler_handle {
233     struct impl;
234     /// Pointer to internal implementation.
235     std::shared_ptr< impl > _pimpl;
236 
237     friend scheduler_handle setup(void);
238     scheduler_handle(void);
239 
240 public:
241     ~scheduler_handle(void);
242 
243     const utils::fs::path& root_work_directory(void) const;
244 
245     void cleanup(void);
246 
247     model::test_cases_map list_tests(const model::test_program*,
248                                      const utils::config::tree&);
249     exec_handle spawn_test(const model::test_program_ptr,
250                            const std::string&,
251                            const utils::config::tree&);
252     result_handle_ptr wait_any(void);
253 
254     result_handle_ptr debug_test(const model::test_program_ptr,
255                                  const std::string&,
256                                  const utils::config::tree&,
257                                  const utils::fs::path&,
258                                  const utils::fs::path&);
259 
260     void check_interrupt(void) const;
261 };
262 
263 
264 extern utils::datetime::delta cleanup_timeout;
265 extern utils::datetime::delta list_timeout;
266 
267 
268 void ensure_valid_interface(const std::string&);
269 void register_interface(const std::string&, const std::shared_ptr< interface >);
270 std::set< std::string > registered_interface_names(void);
271 scheduler_handle setup(void);
272 
273 model::context current_context(void);
274 utils::config::properties_map generate_config(const utils::config::tree&,
275                                               const std::string&);
276 
277 
278 }  // namespace scheduler
279 }  // namespace engine
280 
281 
282 #endif  // !defined(ENGINE_SCHEDULER_HPP)
283