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