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