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
77namespace engine {
78namespace 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.
86class interface {
87public:
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.
169class 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
175public:
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.
187class result_handle {
188protected:
189    struct bimpl;
190
191private:
192    /// Pointer to internal implementation of the base type.
193    std::shared_ptr< bimpl > _pbimpl;
194
195protected:
196    friend class scheduler_handle;
197    result_handle(std::shared_ptr< bimpl >);
198
199public:
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.
214class 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
222public:
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.
232class 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
240public:
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
264extern utils::datetime::delta cleanup_timeout;
265extern utils::datetime::delta list_timeout;
266
267
268void ensure_valid_interface(const std::string&);
269void register_interface(const std::string&, const std::shared_ptr< interface >);
270std::set< std::string > registered_interface_names(void);
271scheduler_handle setup(void);
272
273model::context current_context(void);
274utils::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