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 execenv_cleanup_timeout; 266 extern utils::datetime::delta list_timeout; 267 268 269 void ensure_valid_interface(const std::string&); 270 void register_interface(const std::string&, const std::shared_ptr< interface >); 271 std::set< std::string > registered_interface_names(void); 272 scheduler_handle setup(void); 273 274 model::context current_context(void); 275 utils::config::properties_map generate_config(const utils::config::tree&, 276 const std::string&); 277 278 279 } // namespace scheduler 280 } // namespace engine 281 282 283 #endif // !defined(ENGINE_SCHEDULER_HPP) 284