1b0d29bc4SBrooks Davis // Copyright 2014 The Kyua Authors. 2b0d29bc4SBrooks Davis // All rights reserved. 3b0d29bc4SBrooks Davis // 4b0d29bc4SBrooks Davis // Redistribution and use in source and binary forms, with or without 5b0d29bc4SBrooks Davis // modification, are permitted provided that the following conditions are 6b0d29bc4SBrooks Davis // met: 7b0d29bc4SBrooks Davis // 8b0d29bc4SBrooks Davis // * Redistributions of source code must retain the above copyright 9b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer. 10b0d29bc4SBrooks Davis // * Redistributions in binary form must reproduce the above copyright 11b0d29bc4SBrooks Davis // notice, this list of conditions and the following disclaimer in the 12b0d29bc4SBrooks Davis // documentation and/or other materials provided with the distribution. 13b0d29bc4SBrooks Davis // * Neither the name of Google Inc. nor the names of its contributors 14b0d29bc4SBrooks Davis // may be used to endorse or promote products derived from this software 15b0d29bc4SBrooks Davis // without specific prior written permission. 16b0d29bc4SBrooks Davis // 17b0d29bc4SBrooks Davis // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18b0d29bc4SBrooks Davis // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19b0d29bc4SBrooks Davis // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20b0d29bc4SBrooks Davis // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21b0d29bc4SBrooks Davis // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22b0d29bc4SBrooks Davis // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23b0d29bc4SBrooks Davis // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24b0d29bc4SBrooks Davis // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25b0d29bc4SBrooks Davis // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26b0d29bc4SBrooks Davis // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27b0d29bc4SBrooks Davis // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28b0d29bc4SBrooks Davis 29b0d29bc4SBrooks Davis #include "engine/scheduler.hpp" 30b0d29bc4SBrooks Davis 31b0d29bc4SBrooks Davis extern "C" { 32b0d29bc4SBrooks Davis #include <unistd.h> 33b0d29bc4SBrooks Davis } 34b0d29bc4SBrooks Davis 35b0d29bc4SBrooks Davis #include <cstdio> 36b0d29bc4SBrooks Davis #include <cstdlib> 37b0d29bc4SBrooks Davis #include <fstream> 38b0d29bc4SBrooks Davis #include <memory> 39b0d29bc4SBrooks Davis #include <stdexcept> 40b0d29bc4SBrooks Davis 41b0d29bc4SBrooks Davis #include "engine/config.hpp" 42b0d29bc4SBrooks Davis #include "engine/exceptions.hpp" 43*257e70f1SIgor Ostapenko #include "engine/execenv/execenv.hpp" 44b0d29bc4SBrooks Davis #include "engine/requirements.hpp" 45b0d29bc4SBrooks Davis #include "model/context.hpp" 46b0d29bc4SBrooks Davis #include "model/metadata.hpp" 47b0d29bc4SBrooks Davis #include "model/test_case.hpp" 48b0d29bc4SBrooks Davis #include "model/test_program.hpp" 49b0d29bc4SBrooks Davis #include "model/test_result.hpp" 50b0d29bc4SBrooks Davis #include "utils/config/tree.ipp" 51b0d29bc4SBrooks Davis #include "utils/datetime.hpp" 52b0d29bc4SBrooks Davis #include "utils/defs.hpp" 53b0d29bc4SBrooks Davis #include "utils/env.hpp" 54b0d29bc4SBrooks Davis #include "utils/format/macros.hpp" 55b0d29bc4SBrooks Davis #include "utils/fs/directory.hpp" 56b0d29bc4SBrooks Davis #include "utils/fs/exceptions.hpp" 57b0d29bc4SBrooks Davis #include "utils/fs/operations.hpp" 58b0d29bc4SBrooks Davis #include "utils/fs/path.hpp" 59b0d29bc4SBrooks Davis #include "utils/logging/macros.hpp" 60b0d29bc4SBrooks Davis #include "utils/noncopyable.hpp" 61b0d29bc4SBrooks Davis #include "utils/optional.ipp" 62b0d29bc4SBrooks Davis #include "utils/passwd.hpp" 63b0d29bc4SBrooks Davis #include "utils/process/executor.ipp" 64b0d29bc4SBrooks Davis #include "utils/process/status.hpp" 65b0d29bc4SBrooks Davis #include "utils/sanity.hpp" 66b0d29bc4SBrooks Davis #include "utils/stacktrace.hpp" 67b0d29bc4SBrooks Davis #include "utils/stream.hpp" 68b0d29bc4SBrooks Davis #include "utils/text/operations.ipp" 69b0d29bc4SBrooks Davis 70b0d29bc4SBrooks Davis namespace config = utils::config; 71b0d29bc4SBrooks Davis namespace datetime = utils::datetime; 72*257e70f1SIgor Ostapenko namespace execenv = engine::execenv; 73b0d29bc4SBrooks Davis namespace executor = utils::process::executor; 74b0d29bc4SBrooks Davis namespace fs = utils::fs; 75b0d29bc4SBrooks Davis namespace logging = utils::logging; 76b0d29bc4SBrooks Davis namespace passwd = utils::passwd; 77b0d29bc4SBrooks Davis namespace process = utils::process; 78b0d29bc4SBrooks Davis namespace scheduler = engine::scheduler; 79b0d29bc4SBrooks Davis namespace text = utils::text; 80b0d29bc4SBrooks Davis 81b0d29bc4SBrooks Davis using utils::none; 82b0d29bc4SBrooks Davis using utils::optional; 83b0d29bc4SBrooks Davis 84b0d29bc4SBrooks Davis 85b0d29bc4SBrooks Davis /// Timeout for the test case cleanup operation. 86b0d29bc4SBrooks Davis /// 87b0d29bc4SBrooks Davis /// TODO(jmmv): This is here only for testing purposes. Maybe we should expose 88b0d29bc4SBrooks Davis /// this setting as part of the user_config. 89b0d29bc4SBrooks Davis datetime::delta scheduler::cleanup_timeout(60, 0); 90b0d29bc4SBrooks Davis 91b0d29bc4SBrooks Davis 92*257e70f1SIgor Ostapenko /// Timeout for the test case execenv cleanup operation. 93*257e70f1SIgor Ostapenko datetime::delta scheduler::execenv_cleanup_timeout(60, 0); 94*257e70f1SIgor Ostapenko 95*257e70f1SIgor Ostapenko 96b0d29bc4SBrooks Davis /// Timeout for the test case listing operation. 97b0d29bc4SBrooks Davis /// 98b0d29bc4SBrooks Davis /// TODO(jmmv): This is here only for testing purposes. Maybe we should expose 99b0d29bc4SBrooks Davis /// this setting as part of the user_config. 100b0d29bc4SBrooks Davis datetime::delta scheduler::list_timeout(300, 0); 101b0d29bc4SBrooks Davis 102b0d29bc4SBrooks Davis 103b0d29bc4SBrooks Davis namespace { 104b0d29bc4SBrooks Davis 105b0d29bc4SBrooks Davis 106b0d29bc4SBrooks Davis /// Magic exit status to indicate that the test case was probably skipped. 107b0d29bc4SBrooks Davis /// 108b0d29bc4SBrooks Davis /// The test case was only skipped if and only if we return this exit code and 109b0d29bc4SBrooks Davis /// we find the skipped_cookie file on disk. 110b0d29bc4SBrooks Davis static const int exit_skipped = 84; 111b0d29bc4SBrooks Davis 112b0d29bc4SBrooks Davis 113b0d29bc4SBrooks Davis /// Text file containing the skip reason for the test case. 114b0d29bc4SBrooks Davis /// 115b0d29bc4SBrooks Davis /// This will only be present within unique_work_directory if the test case 116b0d29bc4SBrooks Davis /// exited with the exit_skipped code. However, there is no guarantee that the 117b0d29bc4SBrooks Davis /// file is there (say if the test really decided to exit with code exit_skipped 118b0d29bc4SBrooks Davis /// on its own). 119b0d29bc4SBrooks Davis static const char* skipped_cookie = "skipped.txt"; 120b0d29bc4SBrooks Davis 121b0d29bc4SBrooks Davis 122b0d29bc4SBrooks Davis /// Mapping of interface names to interface definitions. 123b0d29bc4SBrooks Davis typedef std::map< std::string, std::shared_ptr< scheduler::interface > > 124b0d29bc4SBrooks Davis interfaces_map; 125b0d29bc4SBrooks Davis 126b0d29bc4SBrooks Davis 127b0d29bc4SBrooks Davis /// Mapping of interface names to interface definitions. 128b0d29bc4SBrooks Davis /// 129b0d29bc4SBrooks Davis /// Use register_interface() to add an entry to this global table. 130b0d29bc4SBrooks Davis static interfaces_map interfaces; 131b0d29bc4SBrooks Davis 132b0d29bc4SBrooks Davis 133b0d29bc4SBrooks Davis /// Scans the contents of a directory and appends the file listing to a file. 134b0d29bc4SBrooks Davis /// 135b0d29bc4SBrooks Davis /// \param dir_path The directory to scan. 136b0d29bc4SBrooks Davis /// \param output_file The file to which to append the listing. 137b0d29bc4SBrooks Davis /// 138b0d29bc4SBrooks Davis /// \throw engine::error If there are problems listing the files. 139b0d29bc4SBrooks Davis static void 140b0d29bc4SBrooks Davis append_files_listing(const fs::path& dir_path, const fs::path& output_file) 141b0d29bc4SBrooks Davis { 142b0d29bc4SBrooks Davis std::ofstream output(output_file.c_str(), std::ios::app); 143b0d29bc4SBrooks Davis if (!output) 144b0d29bc4SBrooks Davis throw engine::error(F("Failed to open output file %s for append") 145b0d29bc4SBrooks Davis % output_file); 146b0d29bc4SBrooks Davis try { 147b0d29bc4SBrooks Davis std::set < std::string > names; 148b0d29bc4SBrooks Davis 149b0d29bc4SBrooks Davis const fs::directory dir(dir_path); 150b0d29bc4SBrooks Davis for (fs::directory::const_iterator iter = dir.begin(); 151b0d29bc4SBrooks Davis iter != dir.end(); ++iter) { 152b0d29bc4SBrooks Davis if (iter->name != "." && iter->name != "..") 153b0d29bc4SBrooks Davis names.insert(iter->name); 154b0d29bc4SBrooks Davis } 155b0d29bc4SBrooks Davis 156b0d29bc4SBrooks Davis if (!names.empty()) { 157b0d29bc4SBrooks Davis output << "Files left in work directory after failure: " 158b0d29bc4SBrooks Davis << text::join(names, ", ") << '\n'; 159b0d29bc4SBrooks Davis } 160b0d29bc4SBrooks Davis } catch (const fs::error& e) { 161b0d29bc4SBrooks Davis throw engine::error(F("Cannot append files listing to %s: %s") 162b0d29bc4SBrooks Davis % output_file % e.what()); 163b0d29bc4SBrooks Davis } 164b0d29bc4SBrooks Davis } 165b0d29bc4SBrooks Davis 166b0d29bc4SBrooks Davis 167b0d29bc4SBrooks Davis /// Maintenance data held while a test is being executed. 168b0d29bc4SBrooks Davis /// 169b0d29bc4SBrooks Davis /// This data structure exists from the moment when a test is executed via 170b0d29bc4SBrooks Davis /// scheduler::spawn_test() or scheduler::impl::spawn_cleanup() to when it is 171b0d29bc4SBrooks Davis /// cleaned up with result_handle::cleanup(). 172b0d29bc4SBrooks Davis /// 173b0d29bc4SBrooks Davis /// This is a base data type intended to be extended for the test and cleanup 174b0d29bc4SBrooks Davis /// cases so that each contains only the relevant data. 175b0d29bc4SBrooks Davis struct exec_data : utils::noncopyable { 176b0d29bc4SBrooks Davis /// Test program data for this test case. 177b0d29bc4SBrooks Davis const model::test_program_ptr test_program; 178b0d29bc4SBrooks Davis 179b0d29bc4SBrooks Davis /// Name of the test case. 180b0d29bc4SBrooks Davis const std::string test_case_name; 181b0d29bc4SBrooks Davis 182b0d29bc4SBrooks Davis /// Constructor. 183b0d29bc4SBrooks Davis /// 184b0d29bc4SBrooks Davis /// \param test_program_ Test program data for this test case. 185b0d29bc4SBrooks Davis /// \param test_case_name_ Name of the test case. 186b0d29bc4SBrooks Davis exec_data(const model::test_program_ptr test_program_, 187b0d29bc4SBrooks Davis const std::string& test_case_name_) : 188b0d29bc4SBrooks Davis test_program(test_program_), test_case_name(test_case_name_) 189b0d29bc4SBrooks Davis { 190b0d29bc4SBrooks Davis } 191b0d29bc4SBrooks Davis 192b0d29bc4SBrooks Davis /// Destructor. 193b0d29bc4SBrooks Davis virtual ~exec_data(void) 194b0d29bc4SBrooks Davis { 195b0d29bc4SBrooks Davis } 196b0d29bc4SBrooks Davis }; 197b0d29bc4SBrooks Davis 198b0d29bc4SBrooks Davis 199b0d29bc4SBrooks Davis /// Maintenance data held while a test is being executed. 200b0d29bc4SBrooks Davis struct test_exec_data : public exec_data { 201b0d29bc4SBrooks Davis /// Test program-specific execution interface. 202b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface; 203b0d29bc4SBrooks Davis 204b0d29bc4SBrooks Davis /// User configuration passed to the execution of the test. We need this 205b0d29bc4SBrooks Davis /// here to recover it later when chaining the execution of a cleanup 206b0d29bc4SBrooks Davis /// routine (if any). 207b0d29bc4SBrooks Davis const config::tree user_config; 208b0d29bc4SBrooks Davis 209b0d29bc4SBrooks Davis /// Whether this test case still needs to have its cleanup routine executed. 210b0d29bc4SBrooks Davis /// 211b0d29bc4SBrooks Davis /// This is set externally when the cleanup routine is actually invoked to 212b0d29bc4SBrooks Davis /// denote that no further attempts shall be made at cleaning this up. 213b0d29bc4SBrooks Davis bool needs_cleanup; 214b0d29bc4SBrooks Davis 215*257e70f1SIgor Ostapenko /// Whether this test case still needs to have its execenv cleanup executed. 216*257e70f1SIgor Ostapenko /// 217*257e70f1SIgor Ostapenko /// This is set externally when the cleanup routine is actually invoked to 218*257e70f1SIgor Ostapenko /// denote that no further attempts shall be made at cleaning this up. 219*257e70f1SIgor Ostapenko bool needs_execenv_cleanup; 220*257e70f1SIgor Ostapenko 221*257e70f1SIgor Ostapenko /// Original PID of the test case subprocess. 222*257e70f1SIgor Ostapenko /// 223*257e70f1SIgor Ostapenko /// This is used for the cleanup upon termination by a signal, to reap the 224*257e70f1SIgor Ostapenko /// leftovers and form missing exit_handle. 225*257e70f1SIgor Ostapenko pid_t pid; 226*257e70f1SIgor Ostapenko 227b0d29bc4SBrooks Davis /// The exit_handle for this test once it has completed. 228b0d29bc4SBrooks Davis /// 229b0d29bc4SBrooks Davis /// This is set externally when the test case has finished, as we need this 230b0d29bc4SBrooks Davis /// information to invoke the followup cleanup routine in the right context, 231b0d29bc4SBrooks Davis /// as indicated by needs_cleanup. 232b0d29bc4SBrooks Davis optional< executor::exit_handle > exit_handle; 233b0d29bc4SBrooks Davis 234b0d29bc4SBrooks Davis /// Constructor. 235b0d29bc4SBrooks Davis /// 236b0d29bc4SBrooks Davis /// \param test_program_ Test program data for this test case. 237b0d29bc4SBrooks Davis /// \param test_case_name_ Name of the test case. 238b0d29bc4SBrooks Davis /// \param interface_ Test program-specific execution interface. 239b0d29bc4SBrooks Davis /// \param user_config_ User configuration passed to the test. 240b0d29bc4SBrooks Davis test_exec_data(const model::test_program_ptr test_program_, 241b0d29bc4SBrooks Davis const std::string& test_case_name_, 242b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface_, 243*257e70f1SIgor Ostapenko const config::tree& user_config_, 244*257e70f1SIgor Ostapenko const pid_t pid_) : 245b0d29bc4SBrooks Davis exec_data(test_program_, test_case_name_), 246*257e70f1SIgor Ostapenko interface(interface_), user_config(user_config_), pid(pid_) 247b0d29bc4SBrooks Davis { 248b0d29bc4SBrooks Davis const model::test_case& test_case = test_program->find(test_case_name); 249b0d29bc4SBrooks Davis needs_cleanup = test_case.get_metadata().has_cleanup(); 250*257e70f1SIgor Ostapenko needs_execenv_cleanup = test_case.get_metadata().has_execenv(); 251b0d29bc4SBrooks Davis } 252b0d29bc4SBrooks Davis }; 253b0d29bc4SBrooks Davis 254b0d29bc4SBrooks Davis 255b0d29bc4SBrooks Davis /// Maintenance data held while a test cleanup routine is being executed. 256b0d29bc4SBrooks Davis /// 257b0d29bc4SBrooks Davis /// Instances of this object are related to a previous test_exec_data, as 258b0d29bc4SBrooks Davis /// cleanup routines can only exist once the test has been run. 259b0d29bc4SBrooks Davis struct cleanup_exec_data : public exec_data { 260b0d29bc4SBrooks Davis /// The exit handle of the test. This is necessary so that we can return 261b0d29bc4SBrooks Davis /// the correct exit_handle to the user of the scheduler. 262b0d29bc4SBrooks Davis executor::exit_handle body_exit_handle; 263b0d29bc4SBrooks Davis 264b0d29bc4SBrooks Davis /// The final result of the test's body. This is necessary to compute the 265b0d29bc4SBrooks Davis /// right return value for a test with a cleanup routine: the body result is 266b0d29bc4SBrooks Davis /// respected if it is a "bad" result; else the result of the cleanup 267b0d29bc4SBrooks Davis /// routine is used if it has failed. 268b0d29bc4SBrooks Davis model::test_result body_result; 269b0d29bc4SBrooks Davis 270b0d29bc4SBrooks Davis /// Constructor. 271b0d29bc4SBrooks Davis /// 272b0d29bc4SBrooks Davis /// \param test_program_ Test program data for this test case. 273b0d29bc4SBrooks Davis /// \param test_case_name_ Name of the test case. 274b0d29bc4SBrooks Davis /// \param body_exit_handle_ If not none, exit handle of the body 275b0d29bc4SBrooks Davis /// corresponding to the cleanup routine represented by this exec_data. 276b0d29bc4SBrooks Davis /// \param body_result_ If not none, result of the body corresponding to the 277b0d29bc4SBrooks Davis /// cleanup routine represented by this exec_data. 278b0d29bc4SBrooks Davis cleanup_exec_data(const model::test_program_ptr test_program_, 279b0d29bc4SBrooks Davis const std::string& test_case_name_, 280b0d29bc4SBrooks Davis const executor::exit_handle& body_exit_handle_, 281b0d29bc4SBrooks Davis const model::test_result& body_result_) : 282b0d29bc4SBrooks Davis exec_data(test_program_, test_case_name_), 283b0d29bc4SBrooks Davis body_exit_handle(body_exit_handle_), body_result(body_result_) 284b0d29bc4SBrooks Davis { 285b0d29bc4SBrooks Davis } 286b0d29bc4SBrooks Davis }; 287b0d29bc4SBrooks Davis 288b0d29bc4SBrooks Davis 289*257e70f1SIgor Ostapenko /// Maintenance data held while a test execenv cleanup is being executed. 290*257e70f1SIgor Ostapenko /// 291*257e70f1SIgor Ostapenko /// Instances of this object are related to a previous test_exec_data, as 292*257e70f1SIgor Ostapenko /// cleanup routines can only exist once the test has been run. 293*257e70f1SIgor Ostapenko struct execenv_exec_data : public exec_data { 294*257e70f1SIgor Ostapenko /// The exit handle of the test. This is necessary so that we can return 295*257e70f1SIgor Ostapenko /// the correct exit_handle to the user of the scheduler. 296*257e70f1SIgor Ostapenko executor::exit_handle body_exit_handle; 297*257e70f1SIgor Ostapenko 298*257e70f1SIgor Ostapenko /// The final result of the test's body. This is necessary to compute the 299*257e70f1SIgor Ostapenko /// right return value for a test with a cleanup routine: the body result is 300*257e70f1SIgor Ostapenko /// respected if it is a "bad" result; else the result of the cleanup 301*257e70f1SIgor Ostapenko /// routine is used if it has failed. 302*257e70f1SIgor Ostapenko model::test_result body_result; 303*257e70f1SIgor Ostapenko 304*257e70f1SIgor Ostapenko /// Constructor. 305*257e70f1SIgor Ostapenko /// 306*257e70f1SIgor Ostapenko /// \param test_program_ Test program data for this test case. 307*257e70f1SIgor Ostapenko /// \param test_case_name_ Name of the test case. 308*257e70f1SIgor Ostapenko /// \param body_exit_handle_ If not none, exit handle of the body 309*257e70f1SIgor Ostapenko /// corresponding to the cleanup routine represented by this exec_data. 310*257e70f1SIgor Ostapenko /// \param body_result_ If not none, result of the body corresponding to the 311*257e70f1SIgor Ostapenko /// cleanup routine represented by this exec_data. 312*257e70f1SIgor Ostapenko execenv_exec_data(const model::test_program_ptr test_program_, 313*257e70f1SIgor Ostapenko const std::string& test_case_name_, 314*257e70f1SIgor Ostapenko const executor::exit_handle& body_exit_handle_, 315*257e70f1SIgor Ostapenko const model::test_result& body_result_) : 316*257e70f1SIgor Ostapenko exec_data(test_program_, test_case_name_), 317*257e70f1SIgor Ostapenko body_exit_handle(body_exit_handle_), body_result(body_result_) 318*257e70f1SIgor Ostapenko { 319*257e70f1SIgor Ostapenko } 320*257e70f1SIgor Ostapenko }; 321*257e70f1SIgor Ostapenko 322*257e70f1SIgor Ostapenko 323b0d29bc4SBrooks Davis /// Shared pointer to exec_data. 324b0d29bc4SBrooks Davis /// 325b0d29bc4SBrooks Davis /// We require this because we want exec_data to not be copyable, and thus we 326b0d29bc4SBrooks Davis /// cannot just store it in the map without move constructors. 327b0d29bc4SBrooks Davis typedef std::shared_ptr< exec_data > exec_data_ptr; 328b0d29bc4SBrooks Davis 329b0d29bc4SBrooks Davis 330b0d29bc4SBrooks Davis /// Mapping of active PIDs to their maintenance data. 331b0d29bc4SBrooks Davis typedef std::map< int, exec_data_ptr > exec_data_map; 332b0d29bc4SBrooks Davis 333b0d29bc4SBrooks Davis 334b0d29bc4SBrooks Davis /// Enforces a test program to hold an absolute path. 335b0d29bc4SBrooks Davis /// 336b0d29bc4SBrooks Davis /// TODO(jmmv): This function (which is a pretty ugly hack) exists because we 337b0d29bc4SBrooks Davis /// want the interface hooks to receive a test_program as their argument. 338b0d29bc4SBrooks Davis /// However, those hooks run after the test program has been isolated, which 339b0d29bc4SBrooks Davis /// means that the current directory has changed since when the test_program 340b0d29bc4SBrooks Davis /// objects were created. This causes the absolute_path() method of 341b0d29bc4SBrooks Davis /// test_program to return bogus values if the internal representation of their 342b0d29bc4SBrooks Davis /// path is relative. We should fix somehow: maybe making the fs module grab 343b0d29bc4SBrooks Davis /// its "current_path" view at program startup time; or maybe by grabbing the 344b0d29bc4SBrooks Davis /// current path at test_program creation time; or maybe something else. 345b0d29bc4SBrooks Davis /// 346b0d29bc4SBrooks Davis /// \param program The test program to modify. 347b0d29bc4SBrooks Davis /// 348b0d29bc4SBrooks Davis /// \return A new test program whose internal paths are absolute. 349b0d29bc4SBrooks Davis static model::test_program 350b0d29bc4SBrooks Davis force_absolute_paths(const model::test_program program) 351b0d29bc4SBrooks Davis { 352b0d29bc4SBrooks Davis const std::string& relative = program.relative_path().str(); 353b0d29bc4SBrooks Davis const std::string absolute = program.absolute_path().str(); 354b0d29bc4SBrooks Davis 355b0d29bc4SBrooks Davis const std::string root = absolute.substr( 356b0d29bc4SBrooks Davis 0, absolute.length() - relative.length()); 357b0d29bc4SBrooks Davis 358b0d29bc4SBrooks Davis return model::test_program( 359b0d29bc4SBrooks Davis program.interface_name(), 360b0d29bc4SBrooks Davis program.relative_path(), fs::path(root), 361b0d29bc4SBrooks Davis program.test_suite_name(), 362b0d29bc4SBrooks Davis program.get_metadata(), program.test_cases()); 363b0d29bc4SBrooks Davis } 364b0d29bc4SBrooks Davis 365b0d29bc4SBrooks Davis 366b0d29bc4SBrooks Davis /// Functor to list the test cases of a test program. 367b0d29bc4SBrooks Davis class list_test_cases { 368b0d29bc4SBrooks Davis /// Interface of the test program to execute. 369b0d29bc4SBrooks Davis std::shared_ptr< scheduler::interface > _interface; 370b0d29bc4SBrooks Davis 371b0d29bc4SBrooks Davis /// Test program to execute. 372b0d29bc4SBrooks Davis const model::test_program _test_program; 373b0d29bc4SBrooks Davis 374b0d29bc4SBrooks Davis /// User-provided configuration variables. 375b0d29bc4SBrooks Davis const config::tree& _user_config; 376b0d29bc4SBrooks Davis 377b0d29bc4SBrooks Davis public: 378b0d29bc4SBrooks Davis /// Constructor. 379b0d29bc4SBrooks Davis /// 380b0d29bc4SBrooks Davis /// \param interface Interface of the test program to execute. 381b0d29bc4SBrooks Davis /// \param test_program Test program to execute. 382b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables. 383b0d29bc4SBrooks Davis list_test_cases( 384b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface, 385b0d29bc4SBrooks Davis const model::test_program* test_program, 386b0d29bc4SBrooks Davis const config::tree& user_config) : 387b0d29bc4SBrooks Davis _interface(interface), 388b0d29bc4SBrooks Davis _test_program(force_absolute_paths(*test_program)), 389b0d29bc4SBrooks Davis _user_config(user_config) 390b0d29bc4SBrooks Davis { 391b0d29bc4SBrooks Davis } 392b0d29bc4SBrooks Davis 393b0d29bc4SBrooks Davis /// Body of the subprocess. 394b0d29bc4SBrooks Davis void 395b0d29bc4SBrooks Davis operator()(const fs::path& /* control_directory */) 396b0d29bc4SBrooks Davis { 397b0d29bc4SBrooks Davis const config::properties_map vars = scheduler::generate_config( 398b0d29bc4SBrooks Davis _user_config, _test_program.test_suite_name()); 399b0d29bc4SBrooks Davis _interface->exec_list(_test_program, vars); 400b0d29bc4SBrooks Davis } 401b0d29bc4SBrooks Davis }; 402b0d29bc4SBrooks Davis 403b0d29bc4SBrooks Davis 404b0d29bc4SBrooks Davis /// Functor to execute a test program in a child process. 405b0d29bc4SBrooks Davis class run_test_program { 406b0d29bc4SBrooks Davis /// Interface of the test program to execute. 407b0d29bc4SBrooks Davis std::shared_ptr< scheduler::interface > _interface; 408b0d29bc4SBrooks Davis 409b0d29bc4SBrooks Davis /// Test program to execute. 410b0d29bc4SBrooks Davis const model::test_program _test_program; 411b0d29bc4SBrooks Davis 412b0d29bc4SBrooks Davis /// Name of the test case to execute. 413b0d29bc4SBrooks Davis const std::string& _test_case_name; 414b0d29bc4SBrooks Davis 415b0d29bc4SBrooks Davis /// User-provided configuration variables. 416b0d29bc4SBrooks Davis const config::tree& _user_config; 417b0d29bc4SBrooks Davis 418b0d29bc4SBrooks Davis /// Verifies if the test case needs to be skipped or not. 419b0d29bc4SBrooks Davis /// 420b0d29bc4SBrooks Davis /// We could very well run this on the scheduler parent process before 421b0d29bc4SBrooks Davis /// issuing the fork. However, doing this here in the child process is 422b0d29bc4SBrooks Davis /// better for two reasons: first, it allows us to continue using the simple 423b0d29bc4SBrooks Davis /// spawn/wait abstraction of the scheduler; and, second, we parallelize the 424b0d29bc4SBrooks Davis /// requirements checks among tests. 425b0d29bc4SBrooks Davis /// 426b0d29bc4SBrooks Davis /// \post If the test's preconditions are not met, the caller process is 427b0d29bc4SBrooks Davis /// terminated with a special exit code and a "skipped cookie" is written to 428b0d29bc4SBrooks Davis /// the disk with the reason for the failure. 429b0d29bc4SBrooks Davis /// 430b0d29bc4SBrooks Davis /// \param skipped_cookie_path File to create with the skip reason details 431b0d29bc4SBrooks Davis /// if this test is skipped. 432b0d29bc4SBrooks Davis void 433b0d29bc4SBrooks Davis do_requirements_check(const fs::path& skipped_cookie_path) 434b0d29bc4SBrooks Davis { 435b0d29bc4SBrooks Davis const model::test_case& test_case = _test_program.find( 436b0d29bc4SBrooks Davis _test_case_name); 437b0d29bc4SBrooks Davis 438b0d29bc4SBrooks Davis const std::string skip_reason = engine::check_reqs( 439b0d29bc4SBrooks Davis test_case.get_metadata(), _user_config, 440b0d29bc4SBrooks Davis _test_program.test_suite_name(), 441b0d29bc4SBrooks Davis fs::current_path()); 442b0d29bc4SBrooks Davis if (skip_reason.empty()) 443b0d29bc4SBrooks Davis return; 444b0d29bc4SBrooks Davis 445b0d29bc4SBrooks Davis std::ofstream output(skipped_cookie_path.c_str()); 446b0d29bc4SBrooks Davis if (!output) { 447b0d29bc4SBrooks Davis std::perror((F("Failed to open %s for write") % 448b0d29bc4SBrooks Davis skipped_cookie_path).str().c_str()); 449b0d29bc4SBrooks Davis std::abort(); 450b0d29bc4SBrooks Davis } 451b0d29bc4SBrooks Davis output << skip_reason; 452b0d29bc4SBrooks Davis output.close(); 453b0d29bc4SBrooks Davis 454b0d29bc4SBrooks Davis // Abruptly terminate the process. We don't want to run any destructors 455b0d29bc4SBrooks Davis // inherited from the parent process by mistake, which could, for 456b0d29bc4SBrooks Davis // example, delete our own control files! 457b0d29bc4SBrooks Davis ::_exit(exit_skipped); 458b0d29bc4SBrooks Davis } 459b0d29bc4SBrooks Davis 460b0d29bc4SBrooks Davis public: 461b0d29bc4SBrooks Davis /// Constructor. 462b0d29bc4SBrooks Davis /// 463b0d29bc4SBrooks Davis /// \param interface Interface of the test program to execute. 464b0d29bc4SBrooks Davis /// \param test_program Test program to execute. 465b0d29bc4SBrooks Davis /// \param test_case_name Name of the test case to execute. 466b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables. 467b0d29bc4SBrooks Davis run_test_program( 468b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface, 469b0d29bc4SBrooks Davis const model::test_program_ptr test_program, 470b0d29bc4SBrooks Davis const std::string& test_case_name, 471b0d29bc4SBrooks Davis const config::tree& user_config) : 472b0d29bc4SBrooks Davis _interface(interface), 473b0d29bc4SBrooks Davis _test_program(force_absolute_paths(*test_program)), 474b0d29bc4SBrooks Davis _test_case_name(test_case_name), 475b0d29bc4SBrooks Davis _user_config(user_config) 476b0d29bc4SBrooks Davis { 477b0d29bc4SBrooks Davis } 478b0d29bc4SBrooks Davis 479b0d29bc4SBrooks Davis /// Body of the subprocess. 480b0d29bc4SBrooks Davis /// 481b0d29bc4SBrooks Davis /// \param control_directory The testcase directory where files will be 482b0d29bc4SBrooks Davis /// read from. 483b0d29bc4SBrooks Davis void 484b0d29bc4SBrooks Davis operator()(const fs::path& control_directory) 485b0d29bc4SBrooks Davis { 486b0d29bc4SBrooks Davis const model::test_case& test_case = _test_program.find( 487b0d29bc4SBrooks Davis _test_case_name); 488b0d29bc4SBrooks Davis if (test_case.fake_result()) 489b0d29bc4SBrooks Davis ::_exit(EXIT_SUCCESS); 490b0d29bc4SBrooks Davis 491b0d29bc4SBrooks Davis do_requirements_check(control_directory / skipped_cookie); 492b0d29bc4SBrooks Davis 493b0d29bc4SBrooks Davis const config::properties_map vars = scheduler::generate_config( 494b0d29bc4SBrooks Davis _user_config, _test_program.test_suite_name()); 495b0d29bc4SBrooks Davis _interface->exec_test(_test_program, _test_case_name, vars, 496b0d29bc4SBrooks Davis control_directory); 497b0d29bc4SBrooks Davis } 498b0d29bc4SBrooks Davis }; 499b0d29bc4SBrooks Davis 500b0d29bc4SBrooks Davis 501b0d29bc4SBrooks Davis /// Functor to execute a test program in a child process. 502b0d29bc4SBrooks Davis class run_test_cleanup { 503b0d29bc4SBrooks Davis /// Interface of the test program to execute. 504b0d29bc4SBrooks Davis std::shared_ptr< scheduler::interface > _interface; 505b0d29bc4SBrooks Davis 506b0d29bc4SBrooks Davis /// Test program to execute. 507b0d29bc4SBrooks Davis const model::test_program _test_program; 508b0d29bc4SBrooks Davis 509b0d29bc4SBrooks Davis /// Name of the test case to execute. 510b0d29bc4SBrooks Davis const std::string& _test_case_name; 511b0d29bc4SBrooks Davis 512b0d29bc4SBrooks Davis /// User-provided configuration variables. 513b0d29bc4SBrooks Davis const config::tree& _user_config; 514b0d29bc4SBrooks Davis 515b0d29bc4SBrooks Davis public: 516b0d29bc4SBrooks Davis /// Constructor. 517b0d29bc4SBrooks Davis /// 518b0d29bc4SBrooks Davis /// \param interface Interface of the test program to execute. 519b0d29bc4SBrooks Davis /// \param test_program Test program to execute. 520b0d29bc4SBrooks Davis /// \param test_case_name Name of the test case to execute. 521b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables. 522b0d29bc4SBrooks Davis run_test_cleanup( 523b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface, 524b0d29bc4SBrooks Davis const model::test_program_ptr test_program, 525b0d29bc4SBrooks Davis const std::string& test_case_name, 526b0d29bc4SBrooks Davis const config::tree& user_config) : 527b0d29bc4SBrooks Davis _interface(interface), 528b0d29bc4SBrooks Davis _test_program(force_absolute_paths(*test_program)), 529b0d29bc4SBrooks Davis _test_case_name(test_case_name), 530b0d29bc4SBrooks Davis _user_config(user_config) 531b0d29bc4SBrooks Davis { 532b0d29bc4SBrooks Davis } 533b0d29bc4SBrooks Davis 534b0d29bc4SBrooks Davis /// Body of the subprocess. 535b0d29bc4SBrooks Davis /// 536b0d29bc4SBrooks Davis /// \param control_directory The testcase directory where cleanup will be 537b0d29bc4SBrooks Davis /// run from. 538b0d29bc4SBrooks Davis void 539b0d29bc4SBrooks Davis operator()(const fs::path& control_directory) 540b0d29bc4SBrooks Davis { 541b0d29bc4SBrooks Davis const config::properties_map vars = scheduler::generate_config( 542b0d29bc4SBrooks Davis _user_config, _test_program.test_suite_name()); 543b0d29bc4SBrooks Davis _interface->exec_cleanup(_test_program, _test_case_name, vars, 544b0d29bc4SBrooks Davis control_directory); 545b0d29bc4SBrooks Davis } 546b0d29bc4SBrooks Davis }; 547b0d29bc4SBrooks Davis 548b0d29bc4SBrooks Davis 549*257e70f1SIgor Ostapenko /// Functor to execute a test execenv cleanup in a child process. 550*257e70f1SIgor Ostapenko class run_execenv_cleanup { 551*257e70f1SIgor Ostapenko /// Test program to execute. 552*257e70f1SIgor Ostapenko const model::test_program _test_program; 553*257e70f1SIgor Ostapenko 554*257e70f1SIgor Ostapenko /// Name of the test case to execute. 555*257e70f1SIgor Ostapenko const std::string& _test_case_name; 556*257e70f1SIgor Ostapenko 557*257e70f1SIgor Ostapenko public: 558*257e70f1SIgor Ostapenko /// Constructor. 559*257e70f1SIgor Ostapenko /// 560*257e70f1SIgor Ostapenko /// \param test_program Test program to execute. 561*257e70f1SIgor Ostapenko /// \param test_case_name Name of the test case to execute. 562*257e70f1SIgor Ostapenko run_execenv_cleanup( 563*257e70f1SIgor Ostapenko const model::test_program_ptr test_program, 564*257e70f1SIgor Ostapenko const std::string& test_case_name) : 565*257e70f1SIgor Ostapenko _test_program(force_absolute_paths(*test_program)), 566*257e70f1SIgor Ostapenko _test_case_name(test_case_name) 567*257e70f1SIgor Ostapenko { 568*257e70f1SIgor Ostapenko } 569*257e70f1SIgor Ostapenko 570*257e70f1SIgor Ostapenko /// Body of the subprocess. 571*257e70f1SIgor Ostapenko /// 572*257e70f1SIgor Ostapenko /// \param control_directory The testcase directory where cleanup will be 573*257e70f1SIgor Ostapenko /// run from. 574*257e70f1SIgor Ostapenko void 575*257e70f1SIgor Ostapenko operator()(const fs::path& /* control_directory */) 576*257e70f1SIgor Ostapenko { 577*257e70f1SIgor Ostapenko auto e = execenv::get(_test_program, _test_case_name); 578*257e70f1SIgor Ostapenko e->cleanup(); 579*257e70f1SIgor Ostapenko } 580*257e70f1SIgor Ostapenko }; 581*257e70f1SIgor Ostapenko 582*257e70f1SIgor Ostapenko 583b0d29bc4SBrooks Davis /// Obtains the right scheduler interface for a given test program. 584b0d29bc4SBrooks Davis /// 585b0d29bc4SBrooks Davis /// \param name The name of the interface of the test program. 586b0d29bc4SBrooks Davis /// 587b0d29bc4SBrooks Davis /// \return An scheduler interface. 588b0d29bc4SBrooks Davis std::shared_ptr< scheduler::interface > 589b0d29bc4SBrooks Davis find_interface(const std::string& name) 590b0d29bc4SBrooks Davis { 591b0d29bc4SBrooks Davis const interfaces_map::const_iterator iter = interfaces.find(name); 592b0d29bc4SBrooks Davis PRE(interfaces.find(name) != interfaces.end()); 593b0d29bc4SBrooks Davis return (*iter).second; 594b0d29bc4SBrooks Davis } 595b0d29bc4SBrooks Davis 596b0d29bc4SBrooks Davis 597b0d29bc4SBrooks Davis } // anonymous namespace 598b0d29bc4SBrooks Davis 599b0d29bc4SBrooks Davis 600b0d29bc4SBrooks Davis void 601b0d29bc4SBrooks Davis scheduler::interface::exec_cleanup( 602b0d29bc4SBrooks Davis const model::test_program& /* test_program */, 603b0d29bc4SBrooks Davis const std::string& /* test_case_name */, 604b0d29bc4SBrooks Davis const config::properties_map& /* vars */, 605b0d29bc4SBrooks Davis const utils::fs::path& /* control_directory */) const 606b0d29bc4SBrooks Davis { 607b0d29bc4SBrooks Davis // Most test interfaces do not support standalone cleanup routines so 608b0d29bc4SBrooks Davis // provide a default implementation that does nothing. 609b0d29bc4SBrooks Davis UNREACHABLE_MSG("exec_cleanup not implemented for an interface that " 610b0d29bc4SBrooks Davis "supports standalone cleanup routines"); 611b0d29bc4SBrooks Davis } 612b0d29bc4SBrooks Davis 613b0d29bc4SBrooks Davis 614b0d29bc4SBrooks Davis /// Internal implementation of a lazy_test_program. 615b0d29bc4SBrooks Davis struct engine::scheduler::lazy_test_program::impl : utils::noncopyable { 616b0d29bc4SBrooks Davis /// Whether the test cases list has been yet loaded or not. 617b0d29bc4SBrooks Davis bool _loaded; 618b0d29bc4SBrooks Davis 619b0d29bc4SBrooks Davis /// User configuration to pass to the test program list operation. 620b0d29bc4SBrooks Davis config::tree _user_config; 621b0d29bc4SBrooks Davis 622b0d29bc4SBrooks Davis /// Scheduler context to use to load test cases. 623b0d29bc4SBrooks Davis scheduler::scheduler_handle& _scheduler_handle; 624b0d29bc4SBrooks Davis 625b0d29bc4SBrooks Davis /// Constructor. 626b0d29bc4SBrooks Davis /// 627b0d29bc4SBrooks Davis /// \param user_config_ User configuration to pass to the test program list 628b0d29bc4SBrooks Davis /// operation. 629b0d29bc4SBrooks Davis /// \param scheduler_handle_ Scheduler context to use when loading test 630b0d29bc4SBrooks Davis /// cases. 631b0d29bc4SBrooks Davis impl(const config::tree& user_config_, 632b0d29bc4SBrooks Davis scheduler::scheduler_handle& scheduler_handle_) : 633b0d29bc4SBrooks Davis _loaded(false), _user_config(user_config_), 634b0d29bc4SBrooks Davis _scheduler_handle(scheduler_handle_) 635b0d29bc4SBrooks Davis { 636b0d29bc4SBrooks Davis } 637b0d29bc4SBrooks Davis }; 638b0d29bc4SBrooks Davis 639b0d29bc4SBrooks Davis 640b0d29bc4SBrooks Davis /// Constructs a new test program. 641b0d29bc4SBrooks Davis /// 642b0d29bc4SBrooks Davis /// \param interface_name_ Name of the test program interface. 643b0d29bc4SBrooks Davis /// \param binary_ The name of the test program binary relative to root_. 644b0d29bc4SBrooks Davis /// \param root_ The root of the test suite containing the test program. 645b0d29bc4SBrooks Davis /// \param test_suite_name_ The name of the test suite this program belongs to. 646b0d29bc4SBrooks Davis /// \param md_ Metadata of the test program. 647b0d29bc4SBrooks Davis /// \param user_config_ User configuration to pass to the scheduler. 648b0d29bc4SBrooks Davis /// \param scheduler_handle_ Scheduler context to use to load test cases. 649b0d29bc4SBrooks Davis scheduler::lazy_test_program::lazy_test_program( 650b0d29bc4SBrooks Davis const std::string& interface_name_, 651b0d29bc4SBrooks Davis const fs::path& binary_, 652b0d29bc4SBrooks Davis const fs::path& root_, 653b0d29bc4SBrooks Davis const std::string& test_suite_name_, 654b0d29bc4SBrooks Davis const model::metadata& md_, 655b0d29bc4SBrooks Davis const config::tree& user_config_, 656b0d29bc4SBrooks Davis scheduler::scheduler_handle& scheduler_handle_) : 657b0d29bc4SBrooks Davis test_program(interface_name_, binary_, root_, test_suite_name_, md_, 658b0d29bc4SBrooks Davis model::test_cases_map()), 659b0d29bc4SBrooks Davis _pimpl(new impl(user_config_, scheduler_handle_)) 660b0d29bc4SBrooks Davis { 661b0d29bc4SBrooks Davis } 662b0d29bc4SBrooks Davis 663b0d29bc4SBrooks Davis 664b0d29bc4SBrooks Davis /// Gets or loads the list of test cases from the test program. 665b0d29bc4SBrooks Davis /// 666b0d29bc4SBrooks Davis /// \return The list of test cases provided by the test program. 667b0d29bc4SBrooks Davis const model::test_cases_map& 668b0d29bc4SBrooks Davis scheduler::lazy_test_program::test_cases(void) const 669b0d29bc4SBrooks Davis { 670b0d29bc4SBrooks Davis _pimpl->_scheduler_handle.check_interrupt(); 671b0d29bc4SBrooks Davis 672b0d29bc4SBrooks Davis if (!_pimpl->_loaded) { 673b0d29bc4SBrooks Davis const model::test_cases_map tcs = _pimpl->_scheduler_handle.list_tests( 674b0d29bc4SBrooks Davis this, _pimpl->_user_config); 675b0d29bc4SBrooks Davis 676b0d29bc4SBrooks Davis // Due to the restrictions on when set_test_cases() may be called (as a 677b0d29bc4SBrooks Davis // way to lazily initialize the test cases list before it is ever 678b0d29bc4SBrooks Davis // returned), this cast is valid. 679b0d29bc4SBrooks Davis const_cast< scheduler::lazy_test_program* >(this)->set_test_cases(tcs); 680b0d29bc4SBrooks Davis 681b0d29bc4SBrooks Davis _pimpl->_loaded = true; 682b0d29bc4SBrooks Davis 683b0d29bc4SBrooks Davis _pimpl->_scheduler_handle.check_interrupt(); 684b0d29bc4SBrooks Davis } 685b0d29bc4SBrooks Davis 686b0d29bc4SBrooks Davis INV(_pimpl->_loaded); 687b0d29bc4SBrooks Davis return test_program::test_cases(); 688b0d29bc4SBrooks Davis } 689b0d29bc4SBrooks Davis 690b0d29bc4SBrooks Davis 691b0d29bc4SBrooks Davis /// Internal implementation for the result_handle class. 692b0d29bc4SBrooks Davis struct engine::scheduler::result_handle::bimpl : utils::noncopyable { 693b0d29bc4SBrooks Davis /// Generic executor exit handle for this result handle. 694b0d29bc4SBrooks Davis executor::exit_handle generic; 695b0d29bc4SBrooks Davis 696b0d29bc4SBrooks Davis /// Mutable pointer to the corresponding scheduler state. 697b0d29bc4SBrooks Davis /// 698b0d29bc4SBrooks Davis /// This object references a member of the scheduler_handle that yielded 699b0d29bc4SBrooks Davis /// this result_handle instance. We need this direct access to clean up 700b0d29bc4SBrooks Davis /// after ourselves when the result is destroyed. 701b0d29bc4SBrooks Davis exec_data_map& all_exec_data; 702b0d29bc4SBrooks Davis 703b0d29bc4SBrooks Davis /// Constructor. 704b0d29bc4SBrooks Davis /// 705b0d29bc4SBrooks Davis /// \param generic_ Generic executor exit handle for this result handle. 706b0d29bc4SBrooks Davis /// \param [in,out] all_exec_data_ Global object keeping track of all active 707b0d29bc4SBrooks Davis /// executions for an scheduler. This is a pointer to a member of the 708b0d29bc4SBrooks Davis /// scheduler_handle object. 709b0d29bc4SBrooks Davis bimpl(const executor::exit_handle generic_, exec_data_map& all_exec_data_) : 710b0d29bc4SBrooks Davis generic(generic_), all_exec_data(all_exec_data_) 711b0d29bc4SBrooks Davis { 712b0d29bc4SBrooks Davis } 713b0d29bc4SBrooks Davis 714b0d29bc4SBrooks Davis /// Destructor. 715b0d29bc4SBrooks Davis ~bimpl(void) 716b0d29bc4SBrooks Davis { 717b0d29bc4SBrooks Davis LD(F("Removing %s from all_exec_data") % generic.original_pid()); 718b0d29bc4SBrooks Davis all_exec_data.erase(generic.original_pid()); 719b0d29bc4SBrooks Davis } 720b0d29bc4SBrooks Davis }; 721b0d29bc4SBrooks Davis 722b0d29bc4SBrooks Davis 723b0d29bc4SBrooks Davis /// Constructor. 724b0d29bc4SBrooks Davis /// 725b0d29bc4SBrooks Davis /// \param pbimpl Constructed internal implementation. 726b0d29bc4SBrooks Davis scheduler::result_handle::result_handle(std::shared_ptr< bimpl > pbimpl) : 727b0d29bc4SBrooks Davis _pbimpl(pbimpl) 728b0d29bc4SBrooks Davis { 729b0d29bc4SBrooks Davis } 730b0d29bc4SBrooks Davis 731b0d29bc4SBrooks Davis 732b0d29bc4SBrooks Davis /// Destructor. 733b0d29bc4SBrooks Davis scheduler::result_handle::~result_handle(void) 734b0d29bc4SBrooks Davis { 735b0d29bc4SBrooks Davis } 736b0d29bc4SBrooks Davis 737b0d29bc4SBrooks Davis 738b0d29bc4SBrooks Davis /// Cleans up the test case results. 739b0d29bc4SBrooks Davis /// 740b0d29bc4SBrooks Davis /// This function should be called explicitly as it provides the means to 741b0d29bc4SBrooks Davis /// control any exceptions raised during cleanup. Do not rely on the destructor 742b0d29bc4SBrooks Davis /// to clean things up. 743b0d29bc4SBrooks Davis /// 744b0d29bc4SBrooks Davis /// \throw engine::error If the cleanup fails, especially due to the inability 745b0d29bc4SBrooks Davis /// to remove the work directory. 746b0d29bc4SBrooks Davis void 747b0d29bc4SBrooks Davis scheduler::result_handle::cleanup(void) 748b0d29bc4SBrooks Davis { 749b0d29bc4SBrooks Davis _pbimpl->generic.cleanup(); 750b0d29bc4SBrooks Davis } 751b0d29bc4SBrooks Davis 752b0d29bc4SBrooks Davis 753b0d29bc4SBrooks Davis /// Returns the original PID corresponding to this result. 754b0d29bc4SBrooks Davis /// 755b0d29bc4SBrooks Davis /// \return An exec_handle. 756b0d29bc4SBrooks Davis int 757b0d29bc4SBrooks Davis scheduler::result_handle::original_pid(void) const 758b0d29bc4SBrooks Davis { 759b0d29bc4SBrooks Davis return _pbimpl->generic.original_pid(); 760b0d29bc4SBrooks Davis } 761b0d29bc4SBrooks Davis 762b0d29bc4SBrooks Davis 763b0d29bc4SBrooks Davis /// Returns the timestamp of when spawn_test was called. 764b0d29bc4SBrooks Davis /// 765b0d29bc4SBrooks Davis /// \return A timestamp. 766b0d29bc4SBrooks Davis const datetime::timestamp& 767b0d29bc4SBrooks Davis scheduler::result_handle::start_time(void) const 768b0d29bc4SBrooks Davis { 769b0d29bc4SBrooks Davis return _pbimpl->generic.start_time(); 770b0d29bc4SBrooks Davis } 771b0d29bc4SBrooks Davis 772b0d29bc4SBrooks Davis 773b0d29bc4SBrooks Davis /// Returns the timestamp of when wait_any_test returned this object. 774b0d29bc4SBrooks Davis /// 775b0d29bc4SBrooks Davis /// \return A timestamp. 776b0d29bc4SBrooks Davis const datetime::timestamp& 777b0d29bc4SBrooks Davis scheduler::result_handle::end_time(void) const 778b0d29bc4SBrooks Davis { 779b0d29bc4SBrooks Davis return _pbimpl->generic.end_time(); 780b0d29bc4SBrooks Davis } 781b0d29bc4SBrooks Davis 782b0d29bc4SBrooks Davis 783b0d29bc4SBrooks Davis /// Returns the path to the test-specific work directory. 784b0d29bc4SBrooks Davis /// 785b0d29bc4SBrooks Davis /// This is guaranteed to be clear of files created by the scheduler. 786b0d29bc4SBrooks Davis /// 787b0d29bc4SBrooks Davis /// \return The path to a directory that exists until cleanup() is called. 788b0d29bc4SBrooks Davis fs::path 789b0d29bc4SBrooks Davis scheduler::result_handle::work_directory(void) const 790b0d29bc4SBrooks Davis { 791b0d29bc4SBrooks Davis return _pbimpl->generic.work_directory(); 792b0d29bc4SBrooks Davis } 793b0d29bc4SBrooks Davis 794b0d29bc4SBrooks Davis 795b0d29bc4SBrooks Davis /// Returns the path to the test's stdout file. 796b0d29bc4SBrooks Davis /// 797b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called. 798b0d29bc4SBrooks Davis const fs::path& 799b0d29bc4SBrooks Davis scheduler::result_handle::stdout_file(void) const 800b0d29bc4SBrooks Davis { 801b0d29bc4SBrooks Davis return _pbimpl->generic.stdout_file(); 802b0d29bc4SBrooks Davis } 803b0d29bc4SBrooks Davis 804b0d29bc4SBrooks Davis 805b0d29bc4SBrooks Davis /// Returns the path to the test's stderr file. 806b0d29bc4SBrooks Davis /// 807b0d29bc4SBrooks Davis /// \return The path to a file that exists until cleanup() is called. 808b0d29bc4SBrooks Davis const fs::path& 809b0d29bc4SBrooks Davis scheduler::result_handle::stderr_file(void) const 810b0d29bc4SBrooks Davis { 811b0d29bc4SBrooks Davis return _pbimpl->generic.stderr_file(); 812b0d29bc4SBrooks Davis } 813b0d29bc4SBrooks Davis 814b0d29bc4SBrooks Davis 815b0d29bc4SBrooks Davis /// Internal implementation for the test_result_handle class. 816b0d29bc4SBrooks Davis struct engine::scheduler::test_result_handle::impl : utils::noncopyable { 817b0d29bc4SBrooks Davis /// Test program data for this test case. 818b0d29bc4SBrooks Davis model::test_program_ptr test_program; 819b0d29bc4SBrooks Davis 820b0d29bc4SBrooks Davis /// Name of the test case. 821b0d29bc4SBrooks Davis std::string test_case_name; 822b0d29bc4SBrooks Davis 823b0d29bc4SBrooks Davis /// The actual result of the test execution. 824b0d29bc4SBrooks Davis const model::test_result test_result; 825b0d29bc4SBrooks Davis 826b0d29bc4SBrooks Davis /// Constructor. 827b0d29bc4SBrooks Davis /// 828b0d29bc4SBrooks Davis /// \param test_program_ Test program data for this test case. 829b0d29bc4SBrooks Davis /// \param test_case_name_ Name of the test case. 830b0d29bc4SBrooks Davis /// \param test_result_ The actual result of the test execution. 831b0d29bc4SBrooks Davis impl(const model::test_program_ptr test_program_, 832b0d29bc4SBrooks Davis const std::string& test_case_name_, 833b0d29bc4SBrooks Davis const model::test_result& test_result_) : 834b0d29bc4SBrooks Davis test_program(test_program_), 835b0d29bc4SBrooks Davis test_case_name(test_case_name_), 836b0d29bc4SBrooks Davis test_result(test_result_) 837b0d29bc4SBrooks Davis { 838b0d29bc4SBrooks Davis } 839b0d29bc4SBrooks Davis }; 840b0d29bc4SBrooks Davis 841b0d29bc4SBrooks Davis 842b0d29bc4SBrooks Davis /// Constructor. 843b0d29bc4SBrooks Davis /// 844b0d29bc4SBrooks Davis /// \param pbimpl Constructed internal implementation for the base object. 845b0d29bc4SBrooks Davis /// \param pimpl Constructed internal implementation. 846b0d29bc4SBrooks Davis scheduler::test_result_handle::test_result_handle( 847b0d29bc4SBrooks Davis std::shared_ptr< bimpl > pbimpl, std::shared_ptr< impl > pimpl) : 848b0d29bc4SBrooks Davis result_handle(pbimpl), _pimpl(pimpl) 849b0d29bc4SBrooks Davis { 850b0d29bc4SBrooks Davis } 851b0d29bc4SBrooks Davis 852b0d29bc4SBrooks Davis 853b0d29bc4SBrooks Davis /// Destructor. 854b0d29bc4SBrooks Davis scheduler::test_result_handle::~test_result_handle(void) 855b0d29bc4SBrooks Davis { 856b0d29bc4SBrooks Davis } 857b0d29bc4SBrooks Davis 858b0d29bc4SBrooks Davis 859b0d29bc4SBrooks Davis /// Returns the test program that yielded this result. 860b0d29bc4SBrooks Davis /// 861b0d29bc4SBrooks Davis /// \return A test program. 862b0d29bc4SBrooks Davis const model::test_program_ptr 863b0d29bc4SBrooks Davis scheduler::test_result_handle::test_program(void) const 864b0d29bc4SBrooks Davis { 865b0d29bc4SBrooks Davis return _pimpl->test_program; 866b0d29bc4SBrooks Davis } 867b0d29bc4SBrooks Davis 868b0d29bc4SBrooks Davis 869b0d29bc4SBrooks Davis /// Returns the name of the test case that yielded this result. 870b0d29bc4SBrooks Davis /// 871b0d29bc4SBrooks Davis /// \return A test case name 872b0d29bc4SBrooks Davis const std::string& 873b0d29bc4SBrooks Davis scheduler::test_result_handle::test_case_name(void) const 874b0d29bc4SBrooks Davis { 875b0d29bc4SBrooks Davis return _pimpl->test_case_name; 876b0d29bc4SBrooks Davis } 877b0d29bc4SBrooks Davis 878b0d29bc4SBrooks Davis 879b0d29bc4SBrooks Davis /// Returns the actual result of the test execution. 880b0d29bc4SBrooks Davis /// 881b0d29bc4SBrooks Davis /// \return A test result. 882b0d29bc4SBrooks Davis const model::test_result& 883b0d29bc4SBrooks Davis scheduler::test_result_handle::test_result(void) const 884b0d29bc4SBrooks Davis { 885b0d29bc4SBrooks Davis return _pimpl->test_result; 886b0d29bc4SBrooks Davis } 887b0d29bc4SBrooks Davis 888b0d29bc4SBrooks Davis 889b0d29bc4SBrooks Davis /// Internal implementation for the scheduler_handle. 890b0d29bc4SBrooks Davis struct engine::scheduler::scheduler_handle::impl : utils::noncopyable { 891b0d29bc4SBrooks Davis /// Generic executor instance encapsulated by this one. 892b0d29bc4SBrooks Davis executor::executor_handle generic; 893b0d29bc4SBrooks Davis 894b0d29bc4SBrooks Davis /// Mapping of exec handles to the data required at run time. 895b0d29bc4SBrooks Davis exec_data_map all_exec_data; 896b0d29bc4SBrooks Davis 897b0d29bc4SBrooks Davis /// Collection of test_exec_data objects. 898b0d29bc4SBrooks Davis typedef std::vector< const test_exec_data* > test_exec_data_vector; 899b0d29bc4SBrooks Davis 900b0d29bc4SBrooks Davis /// Constructor. 901b0d29bc4SBrooks Davis impl(void) : generic(executor::setup()) 902b0d29bc4SBrooks Davis { 903b0d29bc4SBrooks Davis } 904b0d29bc4SBrooks Davis 905b0d29bc4SBrooks Davis /// Destructor. 906b0d29bc4SBrooks Davis /// 907b0d29bc4SBrooks Davis /// This runs any pending cleanup routines, which should only happen if the 908b0d29bc4SBrooks Davis /// scheduler is abruptly terminated (aka if a signal is received). 909b0d29bc4SBrooks Davis ~impl(void) 910b0d29bc4SBrooks Davis { 911b0d29bc4SBrooks Davis const test_exec_data_vector tests_data = tests_needing_cleanup(); 912b0d29bc4SBrooks Davis 913b0d29bc4SBrooks Davis for (test_exec_data_vector::const_iterator iter = tests_data.begin(); 914b0d29bc4SBrooks Davis iter != tests_data.end(); ++iter) { 915b0d29bc4SBrooks Davis const test_exec_data* test_data = *iter; 916b0d29bc4SBrooks Davis 917b0d29bc4SBrooks Davis try { 918b0d29bc4SBrooks Davis sync_cleanup(test_data); 919b0d29bc4SBrooks Davis } catch (const std::runtime_error& e) { 920b0d29bc4SBrooks Davis LW(F("Failed to run cleanup routine for %s:%s on abrupt " 921b0d29bc4SBrooks Davis "termination") 922b0d29bc4SBrooks Davis % test_data->test_program->relative_path() 923b0d29bc4SBrooks Davis % test_data->test_case_name); 924b0d29bc4SBrooks Davis } 925b0d29bc4SBrooks Davis } 926*257e70f1SIgor Ostapenko 927*257e70f1SIgor Ostapenko const test_exec_data_vector td = tests_needing_execenv_cleanup(); 928*257e70f1SIgor Ostapenko 929*257e70f1SIgor Ostapenko for (test_exec_data_vector::const_iterator iter = td.begin(); 930*257e70f1SIgor Ostapenko iter != td.end(); ++iter) { 931*257e70f1SIgor Ostapenko const test_exec_data* test_data = *iter; 932*257e70f1SIgor Ostapenko 933*257e70f1SIgor Ostapenko try { 934*257e70f1SIgor Ostapenko sync_execenv_cleanup(test_data); 935*257e70f1SIgor Ostapenko } catch (const std::runtime_error& e) { 936*257e70f1SIgor Ostapenko LW(F("Failed to run execenv cleanup routine for %s:%s on abrupt " 937*257e70f1SIgor Ostapenko "termination") 938*257e70f1SIgor Ostapenko % test_data->test_program->relative_path() 939*257e70f1SIgor Ostapenko % test_data->test_case_name); 940*257e70f1SIgor Ostapenko } 941*257e70f1SIgor Ostapenko } 942b0d29bc4SBrooks Davis } 943b0d29bc4SBrooks Davis 944b0d29bc4SBrooks Davis /// Finds any pending exec_datas that correspond to tests needing cleanup. 945b0d29bc4SBrooks Davis /// 946b0d29bc4SBrooks Davis /// \return The collection of test_exec_data objects that have their 947b0d29bc4SBrooks Davis /// needs_cleanup property set to true. 948b0d29bc4SBrooks Davis test_exec_data_vector 949b0d29bc4SBrooks Davis tests_needing_cleanup(void) 950b0d29bc4SBrooks Davis { 951b0d29bc4SBrooks Davis test_exec_data_vector tests_data; 952b0d29bc4SBrooks Davis 953b0d29bc4SBrooks Davis for (exec_data_map::const_iterator iter = all_exec_data.begin(); 954b0d29bc4SBrooks Davis iter != all_exec_data.end(); ++iter) { 955b0d29bc4SBrooks Davis const exec_data_ptr data = (*iter).second; 956b0d29bc4SBrooks Davis 957b0d29bc4SBrooks Davis try { 958b0d29bc4SBrooks Davis test_exec_data* test_data = &dynamic_cast< test_exec_data& >( 959b0d29bc4SBrooks Davis *data.get()); 960b0d29bc4SBrooks Davis if (test_data->needs_cleanup) { 961b0d29bc4SBrooks Davis tests_data.push_back(test_data); 962b0d29bc4SBrooks Davis test_data->needs_cleanup = false; 963*257e70f1SIgor Ostapenko if (!test_data->exit_handle) 964*257e70f1SIgor Ostapenko test_data->exit_handle = generic.reap(test_data->pid); 965b0d29bc4SBrooks Davis } 966b0d29bc4SBrooks Davis } catch (const std::bad_cast& e) { 967b0d29bc4SBrooks Davis // Do nothing for cleanup_exec_data objects. 968b0d29bc4SBrooks Davis } 969b0d29bc4SBrooks Davis } 970b0d29bc4SBrooks Davis 971b0d29bc4SBrooks Davis return tests_data; 972b0d29bc4SBrooks Davis } 973b0d29bc4SBrooks Davis 974*257e70f1SIgor Ostapenko /// Finds any pending exec_datas that correspond to tests needing execenv 975*257e70f1SIgor Ostapenko /// cleanup. 976*257e70f1SIgor Ostapenko /// 977*257e70f1SIgor Ostapenko /// \return The collection of test_exec_data objects that have their 978*257e70f1SIgor Ostapenko /// specific execenv property set. 979*257e70f1SIgor Ostapenko test_exec_data_vector 980*257e70f1SIgor Ostapenko tests_needing_execenv_cleanup(void) 981*257e70f1SIgor Ostapenko { 982*257e70f1SIgor Ostapenko test_exec_data_vector tests_data; 983*257e70f1SIgor Ostapenko 984*257e70f1SIgor Ostapenko for (exec_data_map::const_iterator iter = all_exec_data.begin(); 985*257e70f1SIgor Ostapenko iter != all_exec_data.end(); ++iter) { 986*257e70f1SIgor Ostapenko const exec_data_ptr data = (*iter).second; 987*257e70f1SIgor Ostapenko 988*257e70f1SIgor Ostapenko try { 989*257e70f1SIgor Ostapenko test_exec_data* test_data = &dynamic_cast< test_exec_data& >( 990*257e70f1SIgor Ostapenko *data.get()); 991*257e70f1SIgor Ostapenko if (test_data->needs_execenv_cleanup) { 992*257e70f1SIgor Ostapenko tests_data.push_back(test_data); 993*257e70f1SIgor Ostapenko test_data->needs_execenv_cleanup = false; 994*257e70f1SIgor Ostapenko if (!test_data->exit_handle) 995*257e70f1SIgor Ostapenko test_data->exit_handle = generic.reap(test_data->pid); 996*257e70f1SIgor Ostapenko } 997*257e70f1SIgor Ostapenko } catch (const std::bad_cast& e) { 998*257e70f1SIgor Ostapenko // Do nothing for other objects. 999*257e70f1SIgor Ostapenko } 1000*257e70f1SIgor Ostapenko } 1001*257e70f1SIgor Ostapenko 1002*257e70f1SIgor Ostapenko return tests_data; 1003*257e70f1SIgor Ostapenko } 1004*257e70f1SIgor Ostapenko 1005b0d29bc4SBrooks Davis /// Cleans up a single test case synchronously. 1006b0d29bc4SBrooks Davis /// 1007b0d29bc4SBrooks Davis /// \param test_data The data of the previously executed test case to be 1008b0d29bc4SBrooks Davis /// cleaned up. 1009b0d29bc4SBrooks Davis void 1010b0d29bc4SBrooks Davis sync_cleanup(const test_exec_data* test_data) 1011b0d29bc4SBrooks Davis { 1012b0d29bc4SBrooks Davis // The message in this result should never be seen by the user, but use 1013b0d29bc4SBrooks Davis // something reasonable just in case it leaks and we need to pinpoint 1014b0d29bc4SBrooks Davis // the call site. 1015b0d29bc4SBrooks Davis model::test_result result(model::test_result_broken, 1016b0d29bc4SBrooks Davis "Test case died abruptly"); 1017b0d29bc4SBrooks Davis 1018b0d29bc4SBrooks Davis const executor::exec_handle cleanup_handle = spawn_cleanup( 1019b0d29bc4SBrooks Davis test_data->test_program, test_data->test_case_name, 1020b0d29bc4SBrooks Davis test_data->user_config, test_data->exit_handle.get(), 1021b0d29bc4SBrooks Davis result); 1022b0d29bc4SBrooks Davis generic.wait(cleanup_handle); 1023b0d29bc4SBrooks Davis } 1024b0d29bc4SBrooks Davis 1025b0d29bc4SBrooks Davis /// Forks and executes a test case cleanup routine asynchronously. 1026b0d29bc4SBrooks Davis /// 1027b0d29bc4SBrooks Davis /// \param test_program The container test program. 1028b0d29bc4SBrooks Davis /// \param test_case_name The name of the test case to run. 1029b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables. 1030b0d29bc4SBrooks Davis /// \param body_handle The exit handle of the test case's corresponding 1031b0d29bc4SBrooks Davis /// body. The cleanup will be executed in the same context. 1032b0d29bc4SBrooks Davis /// \param body_result The result of the test case's corresponding body. 1033b0d29bc4SBrooks Davis /// 1034b0d29bc4SBrooks Davis /// \return A handle for the background operation. Used to match the result 1035b0d29bc4SBrooks Davis /// of the execution returned by wait_any() with this invocation. 1036b0d29bc4SBrooks Davis executor::exec_handle 1037b0d29bc4SBrooks Davis spawn_cleanup(const model::test_program_ptr test_program, 1038b0d29bc4SBrooks Davis const std::string& test_case_name, 1039b0d29bc4SBrooks Davis const config::tree& user_config, 1040b0d29bc4SBrooks Davis const executor::exit_handle& body_handle, 1041b0d29bc4SBrooks Davis const model::test_result& body_result) 1042b0d29bc4SBrooks Davis { 1043b0d29bc4SBrooks Davis generic.check_interrupt(); 1044b0d29bc4SBrooks Davis 1045b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface = 1046b0d29bc4SBrooks Davis find_interface(test_program->interface_name()); 1047b0d29bc4SBrooks Davis 1048b0d29bc4SBrooks Davis LI(F("Spawning %s:%s (cleanup)") % test_program->absolute_path() % 1049b0d29bc4SBrooks Davis test_case_name); 1050b0d29bc4SBrooks Davis 1051b0d29bc4SBrooks Davis const executor::exec_handle handle = generic.spawn_followup( 1052b0d29bc4SBrooks Davis run_test_cleanup(interface, test_program, test_case_name, 1053b0d29bc4SBrooks Davis user_config), 1054b0d29bc4SBrooks Davis body_handle, cleanup_timeout); 1055b0d29bc4SBrooks Davis 1056b0d29bc4SBrooks Davis const exec_data_ptr data(new cleanup_exec_data( 1057b0d29bc4SBrooks Davis test_program, test_case_name, body_handle, body_result)); 1058b0d29bc4SBrooks Davis LD(F("Inserting %s into all_exec_data (cleanup)") % handle.pid()); 1059b0d29bc4SBrooks Davis INV_MSG(all_exec_data.find(handle.pid()) == all_exec_data.end(), 1060b0d29bc4SBrooks Davis F("PID %s already in all_exec_data; not properly cleaned " 1061b0d29bc4SBrooks Davis "up or reused too fast") % handle.pid());; 1062b0d29bc4SBrooks Davis all_exec_data.insert(exec_data_map::value_type(handle.pid(), data)); 1063b0d29bc4SBrooks Davis 1064b0d29bc4SBrooks Davis return handle; 1065b0d29bc4SBrooks Davis } 1066*257e70f1SIgor Ostapenko 1067*257e70f1SIgor Ostapenko /// Cleans up a single test case execenv synchronously. 1068*257e70f1SIgor Ostapenko /// 1069*257e70f1SIgor Ostapenko /// \param test_data The data of the previously executed test case to be 1070*257e70f1SIgor Ostapenko /// cleaned up. 1071*257e70f1SIgor Ostapenko void 1072*257e70f1SIgor Ostapenko sync_execenv_cleanup(const test_exec_data* test_data) 1073*257e70f1SIgor Ostapenko { 1074*257e70f1SIgor Ostapenko // The message in this result should never be seen by the user, but use 1075*257e70f1SIgor Ostapenko // something reasonable just in case it leaks and we need to pinpoint 1076*257e70f1SIgor Ostapenko // the call site. 1077*257e70f1SIgor Ostapenko model::test_result result(model::test_result_broken, 1078*257e70f1SIgor Ostapenko "Test case died abruptly"); 1079*257e70f1SIgor Ostapenko 1080*257e70f1SIgor Ostapenko const executor::exec_handle cleanup_handle = spawn_execenv_cleanup( 1081*257e70f1SIgor Ostapenko test_data->test_program, test_data->test_case_name, 1082*257e70f1SIgor Ostapenko test_data->exit_handle.get(), result); 1083*257e70f1SIgor Ostapenko generic.wait(cleanup_handle); 1084*257e70f1SIgor Ostapenko } 1085*257e70f1SIgor Ostapenko 1086*257e70f1SIgor Ostapenko /// Forks and executes a test case execenv cleanup asynchronously. 1087*257e70f1SIgor Ostapenko /// 1088*257e70f1SIgor Ostapenko /// \param test_program The container test program. 1089*257e70f1SIgor Ostapenko /// \param test_case_name The name of the test case to run. 1090*257e70f1SIgor Ostapenko /// \param body_handle The exit handle of the test case's corresponding 1091*257e70f1SIgor Ostapenko /// body. The cleanup will be executed in the same context. 1092*257e70f1SIgor Ostapenko /// \param body_result The result of the test case's corresponding body. 1093*257e70f1SIgor Ostapenko /// 1094*257e70f1SIgor Ostapenko /// \return A handle for the background operation. Used to match the result 1095*257e70f1SIgor Ostapenko /// of the execution returned by wait_any() with this invocation. 1096*257e70f1SIgor Ostapenko executor::exec_handle 1097*257e70f1SIgor Ostapenko spawn_execenv_cleanup(const model::test_program_ptr test_program, 1098*257e70f1SIgor Ostapenko const std::string& test_case_name, 1099*257e70f1SIgor Ostapenko const executor::exit_handle& body_handle, 1100*257e70f1SIgor Ostapenko const model::test_result& body_result) 1101*257e70f1SIgor Ostapenko { 1102*257e70f1SIgor Ostapenko generic.check_interrupt(); 1103*257e70f1SIgor Ostapenko 1104*257e70f1SIgor Ostapenko LI(F("Spawning %s:%s (execenv cleanup)") 1105*257e70f1SIgor Ostapenko % test_program->absolute_path() % test_case_name); 1106*257e70f1SIgor Ostapenko 1107*257e70f1SIgor Ostapenko const executor::exec_handle handle = generic.spawn_followup( 1108*257e70f1SIgor Ostapenko run_execenv_cleanup(test_program, test_case_name), 1109*257e70f1SIgor Ostapenko body_handle, execenv_cleanup_timeout); 1110*257e70f1SIgor Ostapenko 1111*257e70f1SIgor Ostapenko const exec_data_ptr data(new execenv_exec_data( 1112*257e70f1SIgor Ostapenko test_program, test_case_name, body_handle, body_result)); 1113*257e70f1SIgor Ostapenko LD(F("Inserting %s into all_exec_data (execenv cleanup)") % handle.pid()); 1114*257e70f1SIgor Ostapenko INV_MSG(all_exec_data.find(handle.pid()) == all_exec_data.end(), 1115*257e70f1SIgor Ostapenko F("PID %s already in all_exec_data; not properly cleaned " 1116*257e70f1SIgor Ostapenko "up or reused too fast") % handle.pid());; 1117*257e70f1SIgor Ostapenko all_exec_data.insert(exec_data_map::value_type(handle.pid(), data)); 1118*257e70f1SIgor Ostapenko 1119*257e70f1SIgor Ostapenko return handle; 1120*257e70f1SIgor Ostapenko } 1121b0d29bc4SBrooks Davis }; 1122b0d29bc4SBrooks Davis 1123b0d29bc4SBrooks Davis 1124b0d29bc4SBrooks Davis /// Constructor. 1125b0d29bc4SBrooks Davis scheduler::scheduler_handle::scheduler_handle(void) : _pimpl(new impl()) 1126b0d29bc4SBrooks Davis { 1127b0d29bc4SBrooks Davis } 1128b0d29bc4SBrooks Davis 1129b0d29bc4SBrooks Davis 1130b0d29bc4SBrooks Davis /// Destructor. 1131b0d29bc4SBrooks Davis scheduler::scheduler_handle::~scheduler_handle(void) 1132b0d29bc4SBrooks Davis { 1133b0d29bc4SBrooks Davis } 1134b0d29bc4SBrooks Davis 1135b0d29bc4SBrooks Davis 1136b0d29bc4SBrooks Davis /// Queries the path to the root of the work directory for all tests. 1137b0d29bc4SBrooks Davis /// 1138b0d29bc4SBrooks Davis /// \return A path. 1139b0d29bc4SBrooks Davis const fs::path& 1140b0d29bc4SBrooks Davis scheduler::scheduler_handle::root_work_directory(void) const 1141b0d29bc4SBrooks Davis { 1142b0d29bc4SBrooks Davis return _pimpl->generic.root_work_directory(); 1143b0d29bc4SBrooks Davis } 1144b0d29bc4SBrooks Davis 1145b0d29bc4SBrooks Davis 1146b0d29bc4SBrooks Davis /// Cleans up the scheduler state. 1147b0d29bc4SBrooks Davis /// 1148b0d29bc4SBrooks Davis /// This function should be called explicitly as it provides the means to 1149b0d29bc4SBrooks Davis /// control any exceptions raised during cleanup. Do not rely on the destructor 1150b0d29bc4SBrooks Davis /// to clean things up. 1151b0d29bc4SBrooks Davis /// 1152b0d29bc4SBrooks Davis /// \throw engine::error If there are problems cleaning up the scheduler. 1153b0d29bc4SBrooks Davis void 1154b0d29bc4SBrooks Davis scheduler::scheduler_handle::cleanup(void) 1155b0d29bc4SBrooks Davis { 1156b0d29bc4SBrooks Davis _pimpl->generic.cleanup(); 1157b0d29bc4SBrooks Davis } 1158b0d29bc4SBrooks Davis 1159b0d29bc4SBrooks Davis 1160b0d29bc4SBrooks Davis /// Checks if the given interface name is valid. 1161b0d29bc4SBrooks Davis /// 1162b0d29bc4SBrooks Davis /// \param name The name of the interface to validate. 1163b0d29bc4SBrooks Davis /// 1164b0d29bc4SBrooks Davis /// \throw engine::error If the given interface is not supported. 1165b0d29bc4SBrooks Davis void 1166b0d29bc4SBrooks Davis scheduler::ensure_valid_interface(const std::string& name) 1167b0d29bc4SBrooks Davis { 1168b0d29bc4SBrooks Davis if (interfaces.find(name) == interfaces.end()) 1169b0d29bc4SBrooks Davis throw engine::error(F("Unsupported test interface '%s'") % name); 1170b0d29bc4SBrooks Davis } 1171b0d29bc4SBrooks Davis 1172b0d29bc4SBrooks Davis 1173b0d29bc4SBrooks Davis /// Registers a new interface. 1174b0d29bc4SBrooks Davis /// 1175b0d29bc4SBrooks Davis /// \param name The name of the interface. Must not have yet been registered. 1176b0d29bc4SBrooks Davis /// \param spec Interface specification. 1177b0d29bc4SBrooks Davis void 1178b0d29bc4SBrooks Davis scheduler::register_interface(const std::string& name, 1179b0d29bc4SBrooks Davis const std::shared_ptr< interface > spec) 1180b0d29bc4SBrooks Davis { 1181b0d29bc4SBrooks Davis PRE(interfaces.find(name) == interfaces.end()); 1182b0d29bc4SBrooks Davis interfaces.insert(interfaces_map::value_type(name, spec)); 1183b0d29bc4SBrooks Davis } 1184b0d29bc4SBrooks Davis 1185b0d29bc4SBrooks Davis 1186b0d29bc4SBrooks Davis /// Returns the names of all registered interfaces. 1187b0d29bc4SBrooks Davis /// 1188b0d29bc4SBrooks Davis /// \return A collection of interface names. 1189b0d29bc4SBrooks Davis std::set< std::string > 1190b0d29bc4SBrooks Davis scheduler::registered_interface_names(void) 1191b0d29bc4SBrooks Davis { 1192b0d29bc4SBrooks Davis std::set< std::string > names; 1193b0d29bc4SBrooks Davis for (interfaces_map::const_iterator iter = interfaces.begin(); 1194b0d29bc4SBrooks Davis iter != interfaces.end(); ++iter) { 1195b0d29bc4SBrooks Davis names.insert((*iter).first); 1196b0d29bc4SBrooks Davis } 1197b0d29bc4SBrooks Davis return names; 1198b0d29bc4SBrooks Davis } 1199b0d29bc4SBrooks Davis 1200b0d29bc4SBrooks Davis 1201b0d29bc4SBrooks Davis /// Initializes the scheduler. 1202b0d29bc4SBrooks Davis /// 1203b0d29bc4SBrooks Davis /// \pre This function can only be called if there is no other scheduler_handle 1204b0d29bc4SBrooks Davis /// object alive. 1205b0d29bc4SBrooks Davis /// 1206b0d29bc4SBrooks Davis /// \return A handle to the operations of the scheduler. 1207b0d29bc4SBrooks Davis scheduler::scheduler_handle 1208b0d29bc4SBrooks Davis scheduler::setup(void) 1209b0d29bc4SBrooks Davis { 1210b0d29bc4SBrooks Davis return scheduler_handle(); 1211b0d29bc4SBrooks Davis } 1212b0d29bc4SBrooks Davis 1213b0d29bc4SBrooks Davis 1214b0d29bc4SBrooks Davis /// Retrieves the list of test cases from a test program. 1215b0d29bc4SBrooks Davis /// 1216b0d29bc4SBrooks Davis /// This operation is currently synchronous. 1217b0d29bc4SBrooks Davis /// 1218b0d29bc4SBrooks Davis /// This operation should never throw. Any errors during the processing of the 1219b0d29bc4SBrooks Davis /// test case list are subsumed into a single test case in the return value that 1220b0d29bc4SBrooks Davis /// represents the failed retrieval. 1221b0d29bc4SBrooks Davis /// 1222b0d29bc4SBrooks Davis /// \param test_program The test program from which to obtain the list of test 1223b0d29bc4SBrooks Davis /// cases. 1224b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables. 1225b0d29bc4SBrooks Davis /// 1226b0d29bc4SBrooks Davis /// \return The list of test cases. 1227b0d29bc4SBrooks Davis model::test_cases_map 1228b0d29bc4SBrooks Davis scheduler::scheduler_handle::list_tests( 1229b0d29bc4SBrooks Davis const model::test_program* test_program, 1230b0d29bc4SBrooks Davis const config::tree& user_config) 1231b0d29bc4SBrooks Davis { 1232b0d29bc4SBrooks Davis _pimpl->generic.check_interrupt(); 1233b0d29bc4SBrooks Davis 1234b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface = find_interface( 1235b0d29bc4SBrooks Davis test_program->interface_name()); 1236b0d29bc4SBrooks Davis 1237b0d29bc4SBrooks Davis try { 1238b0d29bc4SBrooks Davis const executor::exec_handle exec_handle = _pimpl->generic.spawn( 1239b0d29bc4SBrooks Davis list_test_cases(interface, test_program, user_config), 1240b0d29bc4SBrooks Davis list_timeout, none); 1241b0d29bc4SBrooks Davis executor::exit_handle exit_handle = _pimpl->generic.wait(exec_handle); 1242b0d29bc4SBrooks Davis 1243b0d29bc4SBrooks Davis const model::test_cases_map test_cases = interface->parse_list( 1244b0d29bc4SBrooks Davis exit_handle.status(), 1245b0d29bc4SBrooks Davis exit_handle.stdout_file(), 1246b0d29bc4SBrooks Davis exit_handle.stderr_file()); 1247b0d29bc4SBrooks Davis 1248b0d29bc4SBrooks Davis exit_handle.cleanup(); 1249b0d29bc4SBrooks Davis 1250b0d29bc4SBrooks Davis if (test_cases.empty()) 1251b0d29bc4SBrooks Davis throw std::runtime_error("Empty test cases list"); 1252b0d29bc4SBrooks Davis 1253b0d29bc4SBrooks Davis return test_cases; 1254b0d29bc4SBrooks Davis } catch (const std::runtime_error& e) { 1255b0d29bc4SBrooks Davis // TODO(jmmv): This is a very ugly workaround for the fact that we 1256b0d29bc4SBrooks Davis // cannot report failures at the test-program level. 1257b0d29bc4SBrooks Davis LW(F("Failed to load test cases list: %s") % e.what()); 1258b0d29bc4SBrooks Davis model::test_cases_map fake_test_cases; 1259b0d29bc4SBrooks Davis fake_test_cases.insert(model::test_cases_map::value_type( 1260b0d29bc4SBrooks Davis "__test_cases_list__", 1261b0d29bc4SBrooks Davis model::test_case( 1262b0d29bc4SBrooks Davis "__test_cases_list__", 1263b0d29bc4SBrooks Davis "Represents the correct processing of the test cases list", 1264b0d29bc4SBrooks Davis model::test_result(model::test_result_broken, e.what())))); 1265b0d29bc4SBrooks Davis return fake_test_cases; 1266b0d29bc4SBrooks Davis } 1267b0d29bc4SBrooks Davis } 1268b0d29bc4SBrooks Davis 1269b0d29bc4SBrooks Davis 1270b0d29bc4SBrooks Davis /// Forks and executes a test case asynchronously. 1271b0d29bc4SBrooks Davis /// 1272b0d29bc4SBrooks Davis /// Note that the caller needn't know if the test has a cleanup routine or not. 1273b0d29bc4SBrooks Davis /// If there indeed is a cleanup routine, we trigger it at wait_any() time. 1274b0d29bc4SBrooks Davis /// 1275b0d29bc4SBrooks Davis /// \param test_program The container test program. 1276b0d29bc4SBrooks Davis /// \param test_case_name The name of the test case to run. 1277b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables. 1278b0d29bc4SBrooks Davis /// 1279b0d29bc4SBrooks Davis /// \return A handle for the background operation. Used to match the result of 1280b0d29bc4SBrooks Davis /// the execution returned by wait_any() with this invocation. 1281b0d29bc4SBrooks Davis scheduler::exec_handle 1282b0d29bc4SBrooks Davis scheduler::scheduler_handle::spawn_test( 1283b0d29bc4SBrooks Davis const model::test_program_ptr test_program, 1284b0d29bc4SBrooks Davis const std::string& test_case_name, 1285b0d29bc4SBrooks Davis const config::tree& user_config) 1286b0d29bc4SBrooks Davis { 1287b0d29bc4SBrooks Davis _pimpl->generic.check_interrupt(); 1288b0d29bc4SBrooks Davis 1289b0d29bc4SBrooks Davis const std::shared_ptr< scheduler::interface > interface = find_interface( 1290b0d29bc4SBrooks Davis test_program->interface_name()); 1291b0d29bc4SBrooks Davis 1292b0d29bc4SBrooks Davis LI(F("Spawning %s:%s") % test_program->absolute_path() % test_case_name); 1293b0d29bc4SBrooks Davis 1294b0d29bc4SBrooks Davis const model::test_case& test_case = test_program->find(test_case_name); 1295b0d29bc4SBrooks Davis 1296b0d29bc4SBrooks Davis optional< passwd::user > unprivileged_user; 1297b0d29bc4SBrooks Davis if (user_config.is_set("unprivileged_user") && 1298b0d29bc4SBrooks Davis test_case.get_metadata().required_user() == "unprivileged") { 1299b0d29bc4SBrooks Davis unprivileged_user = user_config.lookup< engine::user_node >( 1300b0d29bc4SBrooks Davis "unprivileged_user"); 1301b0d29bc4SBrooks Davis } 1302b0d29bc4SBrooks Davis 1303b0d29bc4SBrooks Davis const executor::exec_handle handle = _pimpl->generic.spawn( 1304b0d29bc4SBrooks Davis run_test_program(interface, test_program, test_case_name, 1305b0d29bc4SBrooks Davis user_config), 1306b0d29bc4SBrooks Davis test_case.get_metadata().timeout(), 1307b0d29bc4SBrooks Davis unprivileged_user); 1308b0d29bc4SBrooks Davis 1309b0d29bc4SBrooks Davis const exec_data_ptr data(new test_exec_data( 1310*257e70f1SIgor Ostapenko test_program, test_case_name, interface, user_config, handle.pid())); 1311b0d29bc4SBrooks Davis LD(F("Inserting %s into all_exec_data") % handle.pid()); 1312b0d29bc4SBrooks Davis INV_MSG( 1313b0d29bc4SBrooks Davis _pimpl->all_exec_data.find(handle.pid()) == _pimpl->all_exec_data.end(), 1314b0d29bc4SBrooks Davis F("PID %s already in all_exec_data; not cleaned up or reused too fast") 1315b0d29bc4SBrooks Davis % handle.pid());; 1316b0d29bc4SBrooks Davis _pimpl->all_exec_data.insert(exec_data_map::value_type(handle.pid(), data)); 1317b0d29bc4SBrooks Davis 1318b0d29bc4SBrooks Davis return handle.pid(); 1319b0d29bc4SBrooks Davis } 1320b0d29bc4SBrooks Davis 1321b0d29bc4SBrooks Davis 1322b0d29bc4SBrooks Davis /// Waits for completion of any forked test case. 1323b0d29bc4SBrooks Davis /// 1324b0d29bc4SBrooks Davis /// Note that if the terminated test case has a cleanup routine, this function 1325b0d29bc4SBrooks Davis /// is the one in charge of spawning the cleanup routine asynchronously. 1326b0d29bc4SBrooks Davis /// 1327b0d29bc4SBrooks Davis /// \return The result of the execution of a subprocess. This is a dynamically 1328b0d29bc4SBrooks Davis /// allocated object because the scheduler can spawn subprocesses of various 1329b0d29bc4SBrooks Davis /// types and, at wait time, we don't know upfront what we are going to get. 1330b0d29bc4SBrooks Davis scheduler::result_handle_ptr 1331b0d29bc4SBrooks Davis scheduler::scheduler_handle::wait_any(void) 1332b0d29bc4SBrooks Davis { 1333b0d29bc4SBrooks Davis _pimpl->generic.check_interrupt(); 1334b0d29bc4SBrooks Davis 1335b0d29bc4SBrooks Davis executor::exit_handle handle = _pimpl->generic.wait_any(); 1336b0d29bc4SBrooks Davis 1337b0d29bc4SBrooks Davis const exec_data_map::iterator iter = _pimpl->all_exec_data.find( 1338b0d29bc4SBrooks Davis handle.original_pid()); 1339b0d29bc4SBrooks Davis exec_data_ptr data = (*iter).second; 1340b0d29bc4SBrooks Davis 1341b0d29bc4SBrooks Davis utils::dump_stacktrace_if_available(data->test_program->absolute_path(), 1342b0d29bc4SBrooks Davis _pimpl->generic, handle); 1343b0d29bc4SBrooks Davis 1344b0d29bc4SBrooks Davis optional< model::test_result > result; 1345*257e70f1SIgor Ostapenko 1346*257e70f1SIgor Ostapenko // test itself 1347b0d29bc4SBrooks Davis try { 1348b0d29bc4SBrooks Davis test_exec_data* test_data = &dynamic_cast< test_exec_data& >( 1349b0d29bc4SBrooks Davis *data.get()); 1350b0d29bc4SBrooks Davis LD(F("Got %s from all_exec_data") % handle.original_pid()); 1351b0d29bc4SBrooks Davis 1352b0d29bc4SBrooks Davis test_data->exit_handle = handle; 1353b0d29bc4SBrooks Davis 1354b0d29bc4SBrooks Davis const model::test_case& test_case = test_data->test_program->find( 1355b0d29bc4SBrooks Davis test_data->test_case_name); 1356b0d29bc4SBrooks Davis 1357b0d29bc4SBrooks Davis result = test_case.fake_result(); 1358b0d29bc4SBrooks Davis 1359b0d29bc4SBrooks Davis if (!result && handle.status() && handle.status().get().exited() && 1360b0d29bc4SBrooks Davis handle.status().get().exitstatus() == exit_skipped) { 1361b0d29bc4SBrooks Davis // If the test's process terminated with our magic "exit_skipped" 1362b0d29bc4SBrooks Davis // status, there are two cases to handle. The first is the case 1363b0d29bc4SBrooks Davis // where the "skipped cookie" exists, in which case we never got to 1364b0d29bc4SBrooks Davis // actually invoke the test program; if that's the case, handle it 1365b0d29bc4SBrooks Davis // here. The second case is where the test case actually decided to 1366b0d29bc4SBrooks Davis // exit with the "exit_skipped" status; in that case, just fall back 1367b0d29bc4SBrooks Davis // to the regular status handling. 1368b0d29bc4SBrooks Davis const fs::path skipped_cookie_path = handle.control_directory() / 1369b0d29bc4SBrooks Davis skipped_cookie; 1370b0d29bc4SBrooks Davis std::ifstream input(skipped_cookie_path.c_str()); 1371b0d29bc4SBrooks Davis if (input) { 1372b0d29bc4SBrooks Davis result = model::test_result(model::test_result_skipped, 1373b0d29bc4SBrooks Davis utils::read_stream(input)); 1374b0d29bc4SBrooks Davis input.close(); 1375b0d29bc4SBrooks Davis 1376b0d29bc4SBrooks Davis // If we determined that the test needs to be skipped, we do not 1377b0d29bc4SBrooks Davis // want to run the cleanup routine because doing so could result 1378b0d29bc4SBrooks Davis // in errors. However, we still want to run the cleanup routine 1379b0d29bc4SBrooks Davis // if the test's body reports a skip (because actions could have 1380b0d29bc4SBrooks Davis // already been taken). 1381b0d29bc4SBrooks Davis test_data->needs_cleanup = false; 1382*257e70f1SIgor Ostapenko test_data->needs_execenv_cleanup = false; 1383b0d29bc4SBrooks Davis } 1384b0d29bc4SBrooks Davis } 1385b0d29bc4SBrooks Davis if (!result) { 1386b0d29bc4SBrooks Davis result = test_data->interface->compute_result( 1387b0d29bc4SBrooks Davis handle.status(), 1388b0d29bc4SBrooks Davis handle.control_directory(), 1389b0d29bc4SBrooks Davis handle.stdout_file(), 1390b0d29bc4SBrooks Davis handle.stderr_file()); 1391b0d29bc4SBrooks Davis } 1392b0d29bc4SBrooks Davis INV(result); 1393b0d29bc4SBrooks Davis 1394b0d29bc4SBrooks Davis if (!result.get().good()) { 1395b0d29bc4SBrooks Davis append_files_listing(handle.work_directory(), 1396b0d29bc4SBrooks Davis handle.stderr_file()); 1397b0d29bc4SBrooks Davis } 1398b0d29bc4SBrooks Davis 1399b0d29bc4SBrooks Davis if (test_data->needs_cleanup) { 1400b0d29bc4SBrooks Davis INV(test_case.get_metadata().has_cleanup()); 1401b0d29bc4SBrooks Davis // The test body has completed and we have processed it. If there 1402b0d29bc4SBrooks Davis // is a cleanup routine, trigger it now and wait for any other test 1403b0d29bc4SBrooks Davis // completion. The caller never knows about cleanup routines. 1404b0d29bc4SBrooks Davis _pimpl->spawn_cleanup(test_data->test_program, 1405b0d29bc4SBrooks Davis test_data->test_case_name, 1406b0d29bc4SBrooks Davis test_data->user_config, handle, result.get()); 1407b0d29bc4SBrooks Davis 1408b0d29bc4SBrooks Davis // TODO(jmmv): Chaining this call is ugly. We'd be better off by 1409b0d29bc4SBrooks Davis // looping over terminated processes until we got a result suitable 1410b0d29bc4SBrooks Davis // for user consumption. For the time being this is good enough and 1411b0d29bc4SBrooks Davis // not a problem because the call chain won't get big: the majority 1412b0d29bc4SBrooks Davis // of test cases do not have cleanup routines. 1413b0d29bc4SBrooks Davis return wait_any(); 1414b0d29bc4SBrooks Davis } 1415*257e70f1SIgor Ostapenko 1416*257e70f1SIgor Ostapenko if (test_data->needs_execenv_cleanup) { 1417*257e70f1SIgor Ostapenko INV(test_case.get_metadata().has_execenv()); 1418*257e70f1SIgor Ostapenko _pimpl->spawn_execenv_cleanup(test_data->test_program, 1419*257e70f1SIgor Ostapenko test_data->test_case_name, 1420*257e70f1SIgor Ostapenko handle, result.get()); 1421*257e70f1SIgor Ostapenko test_data->needs_execenv_cleanup = false; 1422*257e70f1SIgor Ostapenko return wait_any(); 1423*257e70f1SIgor Ostapenko } 1424b0d29bc4SBrooks Davis } catch (const std::bad_cast& e) { 1425*257e70f1SIgor Ostapenko // ok, let's check for another type 1426*257e70f1SIgor Ostapenko } 1427*257e70f1SIgor Ostapenko 1428*257e70f1SIgor Ostapenko // test cleanup 1429*257e70f1SIgor Ostapenko try { 1430b0d29bc4SBrooks Davis const cleanup_exec_data* cleanup_data = 1431b0d29bc4SBrooks Davis &dynamic_cast< const cleanup_exec_data& >(*data.get()); 1432b0d29bc4SBrooks Davis LD(F("Got %s from all_exec_data (cleanup)") % handle.original_pid()); 1433b0d29bc4SBrooks Davis 1434b0d29bc4SBrooks Davis // Handle the completion of cleanup subprocesses internally: the caller 1435b0d29bc4SBrooks Davis // is not aware that these exist so, when we return, we must return the 1436b0d29bc4SBrooks Davis // data for the original test that triggered this routine. For example, 1437b0d29bc4SBrooks Davis // because the caller wants to see the exact same exec_handle that was 1438b0d29bc4SBrooks Davis // returned by spawn_test. 1439b0d29bc4SBrooks Davis 1440b0d29bc4SBrooks Davis const model::test_result& body_result = cleanup_data->body_result; 1441b0d29bc4SBrooks Davis if (body_result.good()) { 1442b0d29bc4SBrooks Davis if (!handle.status()) { 1443b0d29bc4SBrooks Davis result = model::test_result(model::test_result_broken, 1444b0d29bc4SBrooks Davis "Test case cleanup timed out"); 1445b0d29bc4SBrooks Davis } else { 1446b0d29bc4SBrooks Davis if (!handle.status().get().exited() || 1447b0d29bc4SBrooks Davis handle.status().get().exitstatus() != EXIT_SUCCESS) { 1448b0d29bc4SBrooks Davis result = model::test_result( 1449b0d29bc4SBrooks Davis model::test_result_broken, 1450b0d29bc4SBrooks Davis "Test case cleanup did not terminate successfully"); 1451b0d29bc4SBrooks Davis } else { 1452b0d29bc4SBrooks Davis result = body_result; 1453b0d29bc4SBrooks Davis } 1454b0d29bc4SBrooks Davis } 1455b0d29bc4SBrooks Davis } else { 1456b0d29bc4SBrooks Davis result = body_result; 1457b0d29bc4SBrooks Davis } 1458b0d29bc4SBrooks Davis 1459b0d29bc4SBrooks Davis // Untrack the cleanup process. This must be done explicitly because we 1460b0d29bc4SBrooks Davis // do not create a result_handle object for the cleanup, and that is the 1461b0d29bc4SBrooks Davis // one in charge of doing so in the regular (non-cleanup) case. 1462b0d29bc4SBrooks Davis LD(F("Removing %s from all_exec_data (cleanup) in favor of %s") 1463b0d29bc4SBrooks Davis % handle.original_pid() 1464b0d29bc4SBrooks Davis % cleanup_data->body_exit_handle.original_pid()); 1465b0d29bc4SBrooks Davis _pimpl->all_exec_data.erase(handle.original_pid()); 1466b0d29bc4SBrooks Davis 1467b0d29bc4SBrooks Davis handle = cleanup_data->body_exit_handle; 1468*257e70f1SIgor Ostapenko 1469*257e70f1SIgor Ostapenko const exec_data_map::iterator it = _pimpl->all_exec_data.find( 1470*257e70f1SIgor Ostapenko handle.original_pid()); 1471*257e70f1SIgor Ostapenko if (it != _pimpl->all_exec_data.end()) { 1472*257e70f1SIgor Ostapenko exec_data_ptr d = (*it).second; 1473*257e70f1SIgor Ostapenko test_exec_data* test_data = &dynamic_cast< test_exec_data& >( 1474*257e70f1SIgor Ostapenko *d.get()); 1475*257e70f1SIgor Ostapenko const model::test_case& test_case = 1476*257e70f1SIgor Ostapenko cleanup_data->test_program->find(cleanup_data->test_case_name); 1477*257e70f1SIgor Ostapenko test_data->needs_cleanup = false; 1478*257e70f1SIgor Ostapenko 1479*257e70f1SIgor Ostapenko if (test_data->needs_execenv_cleanup) { 1480*257e70f1SIgor Ostapenko INV(test_case.get_metadata().has_execenv()); 1481*257e70f1SIgor Ostapenko _pimpl->spawn_execenv_cleanup(cleanup_data->test_program, 1482*257e70f1SIgor Ostapenko cleanup_data->test_case_name, 1483*257e70f1SIgor Ostapenko handle, result.get()); 1484*257e70f1SIgor Ostapenko test_data->needs_execenv_cleanup = false; 1485*257e70f1SIgor Ostapenko return wait_any(); 1486b0d29bc4SBrooks Davis } 1487*257e70f1SIgor Ostapenko } 1488*257e70f1SIgor Ostapenko } catch (const std::bad_cast& e) { 1489*257e70f1SIgor Ostapenko // ok, let's check for another type 1490*257e70f1SIgor Ostapenko } 1491*257e70f1SIgor Ostapenko 1492*257e70f1SIgor Ostapenko // execenv cleanup 1493*257e70f1SIgor Ostapenko try { 1494*257e70f1SIgor Ostapenko const execenv_exec_data* execenv_data = 1495*257e70f1SIgor Ostapenko &dynamic_cast< const execenv_exec_data& >(*data.get()); 1496*257e70f1SIgor Ostapenko LD(F("Got %s from all_exec_data (execenv cleanup)") % handle.original_pid()); 1497*257e70f1SIgor Ostapenko 1498*257e70f1SIgor Ostapenko const model::test_result& body_result = execenv_data->body_result; 1499*257e70f1SIgor Ostapenko if (body_result.good()) { 1500*257e70f1SIgor Ostapenko if (!handle.status()) { 1501*257e70f1SIgor Ostapenko result = model::test_result(model::test_result_broken, 1502*257e70f1SIgor Ostapenko "Test case execenv cleanup timed out"); 1503*257e70f1SIgor Ostapenko } else { 1504*257e70f1SIgor Ostapenko if (!handle.status().get().exited() || 1505*257e70f1SIgor Ostapenko handle.status().get().exitstatus() != EXIT_SUCCESS) { 1506*257e70f1SIgor Ostapenko result = model::test_result( 1507*257e70f1SIgor Ostapenko model::test_result_broken, 1508*257e70f1SIgor Ostapenko "Test case execenv cleanup did not terminate successfully"); // ? 1509*257e70f1SIgor Ostapenko } else { 1510*257e70f1SIgor Ostapenko result = body_result; 1511*257e70f1SIgor Ostapenko } 1512*257e70f1SIgor Ostapenko } 1513*257e70f1SIgor Ostapenko } else { 1514*257e70f1SIgor Ostapenko result = body_result; 1515*257e70f1SIgor Ostapenko } 1516*257e70f1SIgor Ostapenko 1517*257e70f1SIgor Ostapenko LD(F("Removing %s from all_exec_data (execenv cleanup) in favor of %s") 1518*257e70f1SIgor Ostapenko % handle.original_pid() 1519*257e70f1SIgor Ostapenko % execenv_data->body_exit_handle.original_pid()); 1520*257e70f1SIgor Ostapenko _pimpl->all_exec_data.erase(handle.original_pid()); 1521*257e70f1SIgor Ostapenko 1522*257e70f1SIgor Ostapenko handle = execenv_data->body_exit_handle; 1523*257e70f1SIgor Ostapenko } catch (const std::bad_cast& e) { 1524*257e70f1SIgor Ostapenko // ok, it was one of the types above 1525*257e70f1SIgor Ostapenko } 1526*257e70f1SIgor Ostapenko 1527b0d29bc4SBrooks Davis INV(result); 1528b0d29bc4SBrooks Davis 1529b0d29bc4SBrooks Davis std::shared_ptr< result_handle::bimpl > result_handle_bimpl( 1530b0d29bc4SBrooks Davis new result_handle::bimpl(handle, _pimpl->all_exec_data)); 1531b0d29bc4SBrooks Davis std::shared_ptr< test_result_handle::impl > test_result_handle_impl( 1532b0d29bc4SBrooks Davis new test_result_handle::impl( 1533b0d29bc4SBrooks Davis data->test_program, data->test_case_name, result.get())); 1534b0d29bc4SBrooks Davis return result_handle_ptr(new test_result_handle(result_handle_bimpl, 1535b0d29bc4SBrooks Davis test_result_handle_impl)); 1536b0d29bc4SBrooks Davis } 1537b0d29bc4SBrooks Davis 1538b0d29bc4SBrooks Davis 1539b0d29bc4SBrooks Davis /// Forks and executes a test case synchronously for debugging. 1540b0d29bc4SBrooks Davis /// 1541b0d29bc4SBrooks Davis /// \pre No other processes should be in execution by the scheduler. 1542b0d29bc4SBrooks Davis /// 1543b0d29bc4SBrooks Davis /// \param test_program The container test program. 1544b0d29bc4SBrooks Davis /// \param test_case_name The name of the test case to run. 1545b0d29bc4SBrooks Davis /// \param user_config User-provided configuration variables. 1546b0d29bc4SBrooks Davis /// \param stdout_target File to which to write the stdout of the test case. 1547b0d29bc4SBrooks Davis /// \param stderr_target File to which to write the stderr of the test case. 1548b0d29bc4SBrooks Davis /// 1549b0d29bc4SBrooks Davis /// \return The result of the execution of the test. 1550b0d29bc4SBrooks Davis scheduler::result_handle_ptr 1551b0d29bc4SBrooks Davis scheduler::scheduler_handle::debug_test( 1552b0d29bc4SBrooks Davis const model::test_program_ptr test_program, 1553b0d29bc4SBrooks Davis const std::string& test_case_name, 1554b0d29bc4SBrooks Davis const config::tree& user_config, 1555b0d29bc4SBrooks Davis const fs::path& stdout_target, 1556b0d29bc4SBrooks Davis const fs::path& stderr_target) 1557b0d29bc4SBrooks Davis { 1558b0d29bc4SBrooks Davis const exec_handle exec_handle = spawn_test( 1559b0d29bc4SBrooks Davis test_program, test_case_name, user_config); 1560b0d29bc4SBrooks Davis result_handle_ptr result_handle = wait_any(); 1561b0d29bc4SBrooks Davis 1562b0d29bc4SBrooks Davis // TODO(jmmv): We need to do this while the subprocess is alive. This is 1563b0d29bc4SBrooks Davis // important for debugging purposes, as we should see the contents of stdout 1564b0d29bc4SBrooks Davis // or stderr as they come in. 1565b0d29bc4SBrooks Davis // 1566b0d29bc4SBrooks Davis // Unfortunately, we cannot do so. We cannot just read and block from a 1567b0d29bc4SBrooks Davis // file, waiting for further output to appear... as this only works on pipes 1568b0d29bc4SBrooks Davis // or sockets. We need a better interface for this whole thing. 1569b0d29bc4SBrooks Davis { 1570b0d29bc4SBrooks Davis std::auto_ptr< std::ostream > output = utils::open_ostream( 1571b0d29bc4SBrooks Davis stdout_target); 1572b0d29bc4SBrooks Davis *output << utils::read_file(result_handle->stdout_file()); 1573b0d29bc4SBrooks Davis } 1574b0d29bc4SBrooks Davis { 1575b0d29bc4SBrooks Davis std::auto_ptr< std::ostream > output = utils::open_ostream( 1576b0d29bc4SBrooks Davis stderr_target); 1577b0d29bc4SBrooks Davis *output << utils::read_file(result_handle->stderr_file()); 1578b0d29bc4SBrooks Davis } 1579b0d29bc4SBrooks Davis 1580b0d29bc4SBrooks Davis INV(result_handle->original_pid() == exec_handle); 1581b0d29bc4SBrooks Davis return result_handle; 1582b0d29bc4SBrooks Davis } 1583b0d29bc4SBrooks Davis 1584b0d29bc4SBrooks Davis 1585b0d29bc4SBrooks Davis /// Checks if an interrupt has fired. 1586b0d29bc4SBrooks Davis /// 1587b0d29bc4SBrooks Davis /// Calls to this function should be sprinkled in strategic places through the 1588b0d29bc4SBrooks Davis /// code protected by an interrupts_handler object. 1589b0d29bc4SBrooks Davis /// 1590b0d29bc4SBrooks Davis /// This is just a wrapper over signals::check_interrupt() to avoid leaking this 1591b0d29bc4SBrooks Davis /// dependency to the caller. 1592b0d29bc4SBrooks Davis /// 1593b0d29bc4SBrooks Davis /// \throw signals::interrupted_error If there has been an interrupt. 1594b0d29bc4SBrooks Davis void 1595b0d29bc4SBrooks Davis scheduler::scheduler_handle::check_interrupt(void) const 1596b0d29bc4SBrooks Davis { 1597b0d29bc4SBrooks Davis _pimpl->generic.check_interrupt(); 1598b0d29bc4SBrooks Davis } 1599b0d29bc4SBrooks Davis 1600b0d29bc4SBrooks Davis 1601b0d29bc4SBrooks Davis /// Queries the current execution context. 1602b0d29bc4SBrooks Davis /// 1603b0d29bc4SBrooks Davis /// \return The queried context. 1604b0d29bc4SBrooks Davis model::context 1605b0d29bc4SBrooks Davis scheduler::current_context(void) 1606b0d29bc4SBrooks Davis { 1607b0d29bc4SBrooks Davis return model::context(fs::current_path(), utils::getallenv()); 1608b0d29bc4SBrooks Davis } 1609b0d29bc4SBrooks Davis 1610b0d29bc4SBrooks Davis 1611b0d29bc4SBrooks Davis /// Generates the set of configuration variables for a test program. 1612b0d29bc4SBrooks Davis /// 1613b0d29bc4SBrooks Davis /// \param user_config The configuration variables provided by the user. 1614b0d29bc4SBrooks Davis /// \param test_suite The name of the test suite. 1615b0d29bc4SBrooks Davis /// 1616b0d29bc4SBrooks Davis /// \return The mapping of configuration variables for the test program. 1617b0d29bc4SBrooks Davis config::properties_map 1618b0d29bc4SBrooks Davis scheduler::generate_config(const config::tree& user_config, 1619b0d29bc4SBrooks Davis const std::string& test_suite) 1620b0d29bc4SBrooks Davis { 1621b0d29bc4SBrooks Davis config::properties_map props; 1622b0d29bc4SBrooks Davis 1623b0d29bc4SBrooks Davis try { 1624b0d29bc4SBrooks Davis props = user_config.all_properties(F("test_suites.%s") % test_suite, 1625b0d29bc4SBrooks Davis true); 1626b0d29bc4SBrooks Davis } catch (const config::unknown_key_error& unused_error) { 1627b0d29bc4SBrooks Davis // Ignore: not all test suites have entries in the configuration. 1628b0d29bc4SBrooks Davis } 1629b0d29bc4SBrooks Davis 1630b0d29bc4SBrooks Davis // TODO(jmmv): This is a hack that exists for the ATF interface only, so it 1631b0d29bc4SBrooks Davis // should be moved there. 1632b0d29bc4SBrooks Davis if (user_config.is_set("unprivileged_user")) { 1633b0d29bc4SBrooks Davis const passwd::user& user = 1634b0d29bc4SBrooks Davis user_config.lookup< engine::user_node >("unprivileged_user"); 1635b0d29bc4SBrooks Davis props["unprivileged-user"] = user.name; 1636b0d29bc4SBrooks Davis } 1637b0d29bc4SBrooks Davis 1638b0d29bc4SBrooks Davis return props; 1639b0d29bc4SBrooks Davis } 1640