diff --git a/contrib/kyua/cli/cmd_report.cpp b/contrib/kyua/cli/cmd_report.cpp index 27827e893de7..1bf2b425236f 100644 --- a/contrib/kyua/cli/cmd_report.cpp +++ b/contrib/kyua/cli/cmd_report.cpp @@ -1,421 +1,421 @@ // Copyright 2011 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "cli/cmd_report.hpp" #include #include #include #include #include #include #include #include "cli/common.ipp" #include "drivers/scan_results.hpp" #include "model/context.hpp" #include "model/metadata.hpp" #include "model/test_case.hpp" #include "model/test_program.hpp" #include "model/test_result.hpp" #include "model/types.hpp" #include "store/layout.hpp" #include "store/read_transaction.hpp" #include "utils/cmdline/exceptions.hpp" #include "utils/cmdline/options.hpp" #include "utils/cmdline/parser.ipp" #include "utils/cmdline/ui.hpp" #include "utils/datetime.hpp" #include "utils/defs.hpp" #include "utils/format/macros.hpp" #include "utils/fs/path.hpp" #include "utils/optional.ipp" #include "utils/sanity.hpp" #include "utils/stream.hpp" #include "utils/text/operations.ipp" namespace cmdline = utils::cmdline; namespace config = utils::config; namespace datetime = utils::datetime; namespace fs = utils::fs; namespace layout = store::layout; namespace text = utils::text; using cli::cmd_report; using utils::optional; namespace { /// Generates a plain-text report intended to be printed to the console. class report_console_hooks : public drivers::scan_results::base_hooks { /// Stream to which to write the report. std::ostream& _output; /// Whether to include details in the report or not. const bool _verbose; /// Collection of result types to include in the report. const cli::result_types& _results_filters; /// Path to the results file being read. const fs::path& _results_file; /// The start time of the first test. optional< utils::datetime::timestamp > _start_time; /// The end time of the last test. optional< utils::datetime::timestamp > _end_time; /// The total run time of the tests. Note that we cannot subtract _end_time /// from _start_time to compute this due to parallel execution. utils::datetime::delta _runtime; /// Representation of a single result. struct result_data { /// The relative path to the test program. utils::fs::path binary_path; /// The name of the test case. std::string test_case_name; /// The result of the test case. model::test_result result; /// The duration of the test case execution. utils::datetime::delta duration; /// Constructs a new results data. /// /// \param binary_path_ The relative path to the test program. /// \param test_case_name_ The name of the test case. /// \param result_ The result of the test case. /// \param duration_ The duration of the test case execution. result_data(const utils::fs::path& binary_path_, const std::string& test_case_name_, const model::test_result& result_, const utils::datetime::delta& duration_) : binary_path(binary_path_), test_case_name(test_case_name_), result(result_), duration(duration_) { } }; /// Results received, broken down by their type. /// /// Note that this may not include all results, as keeping the whole list in /// memory may be too much. std::map< model::test_result_type, std::vector< result_data > > _results; /// Pretty-prints the value of an environment variable. /// /// \param indent Prefix for the lines to print. Continuation lines /// use this indentation twice. /// \param name Name of the variable. /// \param value Value of the variable. Can have newlines. void print_env_var(const char* indent, const std::string& name, const std::string& value) { const std::vector< std::string > lines = text::split(value, '\n'); if (lines.size() == 0) { _output << F("%s%s=\n") % indent % name;; } else { _output << F("%s%s=%s\n") % indent % name % lines[0]; for (std::vector< std::string >::size_type i = 1; i < lines.size(); ++i) { _output << F("%s%s%s\n") % indent % indent % lines[i]; } } } /// Prints the execution context to the output. /// /// \param context The context to dump. void print_context(const model::context& context) { _output << "===> Execution context\n"; _output << F("Current directory: %s\n") % context.cwd(); const std::map< std::string, std::string >& env = context.env(); if (env.empty()) _output << "No environment variables recorded\n"; else { _output << "Environment variables:\n"; for (std::map< std::string, std::string >::const_iterator iter = env.begin(); iter != env.end(); iter++) { print_env_var(" ", (*iter).first, (*iter).second); } } } /// Dumps a detailed view of the test case. /// /// \param result_iter Results iterator pointing at the test case to be /// dumped. void print_test_case_and_result(const store::results_iterator& result_iter) { const model::test_case& test_case = result_iter.test_program()->find(result_iter.test_case_name()); const model::properties_map props = test_case.get_metadata().to_properties(); _output << F("===> %s:%s\n") % result_iter.test_program()->relative_path() % result_iter.test_case_name(); _output << F("Result: %s\n") % cli::format_result(result_iter.result()); _output << F("Start time: %s\n") % result_iter.start_time().to_iso8601_in_utc(); _output << F("End time: %s\n") % result_iter.end_time().to_iso8601_in_utc(); _output << F("Duration: %s\n") % cli::format_delta(result_iter.end_time() - result_iter.start_time()); _output << "\n"; _output << "Metadata:\n"; for (model::properties_map::const_iterator iter = props.begin(); iter != props.end(); ++iter) { if ((*iter).second.empty()) { _output << F(" %s is empty\n") % (*iter).first; } else { _output << F(" %s = %s\n") % (*iter).first % (*iter).second; } } const std::string stdout_contents = result_iter.stdout_contents(); if (!stdout_contents.empty()) { _output << "\n" << "Standard output:\n" << stdout_contents; } const std::string stderr_contents = result_iter.stderr_contents(); if (!stderr_contents.empty()) { _output << "\n" << "Standard error:\n" << stderr_contents; } } /// Counts how many results of a given type have been received. /// /// \param type Test result type to count results for. /// /// \return The number of test results with \p type. std::size_t count_results(const model::test_result_type type) { const std::map< model::test_result_type, std::vector< result_data > >::const_iterator iter = _results.find(type); if (iter == _results.end()) return 0; else return (*iter).second.size(); } /// Prints a set of results. /// /// \param type Test result type to print results for. /// \param title Title used when printing results. void print_results(const model::test_result_type type, const char* title) { const std::map< model::test_result_type, std::vector< result_data > >::const_iterator iter2 = _results.find(type); if (iter2 == _results.end()) return; const std::vector< result_data >& all = (*iter2).second; _output << F("===> %s\n") % title; for (std::vector< result_data >::const_iterator iter = all.begin(); iter != all.end(); iter++) { _output << F("%s:%s -> %s [%s]\n") % (*iter).binary_path % (*iter).test_case_name % cli::format_result((*iter).result) % cli::format_delta((*iter).duration); } } public: /// Constructor for the hooks. /// /// \param [out] output_ Stream to which to write the report. /// \param verbose_ Whether to include details in the output or not. /// \param results_filters_ The result types to include in the report. /// Cannot be empty. /// \param results_file_ Path to the results file being read. report_console_hooks(std::ostream& output_, const bool verbose_, const cli::result_types& results_filters_, const fs::path& results_file_) : _output(output_), _verbose(verbose_), _results_filters(results_filters_), _results_file(results_file_) { PRE(!results_filters_.empty()); } /// Callback executed when the context is loaded. /// /// \param context The context loaded from the database. void got_context(const model::context& context) { if (_verbose) print_context(context); } /// Callback executed when a test results is found. /// /// \param iter Container for the test result's data. void got_result(store::results_iterator& iter) { if (!_start_time || _start_time.get() > iter.start_time()) _start_time = iter.start_time(); if (!_end_time || _end_time.get() < iter.end_time()) _end_time = iter.end_time(); const datetime::delta duration = iter.end_time() - iter.start_time(); _runtime += duration; const model::test_result result = iter.result(); _results[result.type()].push_back( result_data(iter.test_program()->relative_path(), iter.test_case_name(), iter.result(), duration)); if (_verbose) { // TODO(jmmv): _results_filters is a list and is small enough for // std::find to not be an expensive operation here (probably). But // we should be using a std::set instead. if (std::find(_results_filters.begin(), _results_filters.end(), iter.result().type()) != _results_filters.end()) { print_test_case_and_result(iter); } } } /// Prints the tests summary. void end(const drivers::scan_results::result& /* r */) { typedef std::map< model::test_result_type, const char* > types_map; types_map titles; titles[model::test_result_broken] = "Broken tests"; titles[model::test_result_expected_failure] = "Expected failures"; titles[model::test_result_failed] = "Failed tests"; titles[model::test_result_passed] = "Passed tests"; titles[model::test_result_skipped] = "Skipped tests"; for (cli::result_types::const_iterator iter = _results_filters.begin(); iter != _results_filters.end(); ++iter) { const types_map::const_iterator match = titles.find(*iter); INV_MSG(match != titles.end(), "Conditional does not match user " "input validation in parse_types()"); print_results((*match).first, (*match).second); } const std::size_t broken = count_results(model::test_result_broken); const std::size_t failed = count_results(model::test_result_failed); const std::size_t passed = count_results(model::test_result_passed); const std::size_t skipped = count_results(model::test_result_skipped); const std::size_t xfail = count_results( model::test_result_expected_failure); const std::size_t total = broken + failed + passed + skipped + xfail; _output << "===> Summary\n"; _output << F("Results read from %s\n") % _results_file; _output << F("Test cases: %s total, %s skipped, %s expected failures, " "%s broken, %s failed\n") % total % skipped % xfail % broken % failed; if (_verbose && _start_time) { INV(_end_time); _output << F("Start time: %s\n") % _start_time.get().to_iso8601_in_utc(); _output << F("End time: %s\n") % _end_time.get().to_iso8601_in_utc(); } _output << F("Total time: %s\n") % cli::format_delta(_runtime); } }; } // anonymous namespace /// Default constructor for cmd_report. cmd_report::cmd_report(void) : cli_command( "report", "", 0, -1, "Generates a report with the results of a test suite run") { add_option(results_file_open_option); add_option(cmdline::bool_option( "verbose", "Include the execution context and the details of each test " "case in the report")); add_option(cmdline::path_option("output", "Path to the output file", "path", "/dev/stdout")); add_option(results_filter_option); } /// Entry point for the "report" subcommand. /// /// \param ui Object to interact with the I/O of the program. /// \param cmdline Representation of the command line to the subcommand. /// /// \return 0 if everything is OK, 1 if the statement is invalid or if there is /// any other problem. int cmd_report::run(cmdline::ui* ui, const cmdline::parsed_cmdline& cmdline, const config::tree& /* user_config */) { - std::auto_ptr< std::ostream > output = utils::open_ostream( + std::unique_ptr< std::ostream > output = utils::open_ostream( cmdline.get_option< cmdline::path_option >("output")); const fs::path results_file = layout::find_results( results_file_open(cmdline)); const result_types types = get_result_types(cmdline); report_console_hooks hooks(*output.get(), cmdline.has_option("verbose"), types, results_file); const drivers::scan_results::result result = drivers::scan_results::drive( results_file, parse_filters(cmdline.arguments()), hooks); return report_unused_filters(result.unused_filters, ui) ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/contrib/kyua/cli/cmd_report_junit.cpp b/contrib/kyua/cli/cmd_report_junit.cpp index c4846c8795f2..acbda414d2e7 100644 --- a/contrib/kyua/cli/cmd_report_junit.cpp +++ b/contrib/kyua/cli/cmd_report_junit.cpp @@ -1,89 +1,89 @@ // Copyright 2014 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "cli/cmd_report_junit.hpp" #include #include #include #include "cli/common.ipp" #include "drivers/report_junit.hpp" #include "drivers/scan_results.hpp" #include "engine/filters.hpp" #include "store/layout.hpp" #include "utils/cmdline/options.hpp" #include "utils/cmdline/parser.ipp" #include "utils/defs.hpp" #include "utils/optional.ipp" #include "utils/stream.hpp" namespace cmdline = utils::cmdline; namespace config = utils::config; namespace fs = utils::fs; namespace layout = store::layout; using cli::cmd_report_junit; using utils::optional; /// Default constructor for cmd_report. cmd_report_junit::cmd_report_junit(void) : cli_command( "report-junit", "", 0, 0, "Generates a JUnit report with the result of a test suite run") { add_option(results_file_open_option); add_option(cmdline::path_option("output", "Path to the output file", "path", "/dev/stdout")); } /// Entry point for the "report" subcommand. /// /// \param cmdline Representation of the command line to the subcommand. /// /// \return 0 if everything is OK, 1 if the statement is invalid or if there is /// any other problem. int cmd_report_junit::run(cmdline::ui* /* ui */, const cmdline::parsed_cmdline& cmdline, const config::tree& /* user_config */) { const fs::path results_file = layout::find_results( results_file_open(cmdline)); - std::auto_ptr< std::ostream > output = utils::open_ostream( + std::unique_ptr< std::ostream > output = utils::open_ostream( cmdline.get_option< cmdline::path_option >("output")); drivers::report_junit_hooks hooks(*output.get()); drivers::scan_results::drive(results_file, std::set< engine::test_filter >(), hooks); return EXIT_SUCCESS; } diff --git a/contrib/kyua/cli/common.hpp b/contrib/kyua/cli/common.hpp index 15a7e9fa3344..65aadcf326b6 100644 --- a/contrib/kyua/cli/common.hpp +++ b/contrib/kyua/cli/common.hpp @@ -1,104 +1,104 @@ // Copyright 2011 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file cli/common.hpp /// Utility functions to implement CLI subcommands. #if !defined(CLI_COMMON_HPP) #define CLI_COMMON_HPP #include #include #include #include "engine/filters_fwd.hpp" #include "model/test_program_fwd.hpp" #include "model/test_result.hpp" #include "utils/cmdline/base_command.hpp" #include "utils/cmdline/options_fwd.hpp" #include "utils/cmdline/parser_fwd.hpp" #include "utils/cmdline/ui_fwd.hpp" #include "utils/config/tree_fwd.hpp" #include "utils/datetime_fwd.hpp" #include "utils/fs/path_fwd.hpp" #include "utils/optional_fwd.hpp" namespace cli { extern const utils::cmdline::path_option build_root_option; extern const utils::cmdline::path_option kyuafile_option; extern const utils::cmdline::string_option results_file_create_option; extern const utils::cmdline::string_option results_file_open_option; extern const utils::cmdline::list_option results_filter_option; extern const utils::cmdline::property_option variable_option; /// Base type for commands defined in the cli module. /// /// All commands in Kyua receive a configuration object as their runtime /// data parameter because the configuration file applies to all the /// commands. typedef utils::cmdline::base_command< utils::config::tree > cli_command; /// Scoped, strictly owned pointer to a cli_command. -typedef std::auto_ptr< cli_command > cli_command_ptr; +typedef std::unique_ptr< cli_command > cli_command_ptr; /// Collection of result types. /// /// This is a vector rather than a set because we want to respect the order in /// which the user provided the types. typedef std::vector< model::test_result_type > result_types; utils::optional< utils::fs::path > build_root_path( const utils::cmdline::parsed_cmdline&); utils::fs::path kyuafile_path(const utils::cmdline::parsed_cmdline&); std::string results_file_create(const utils::cmdline::parsed_cmdline&); std::string results_file_open(const utils::cmdline::parsed_cmdline&); result_types get_result_types(const utils::cmdline::parsed_cmdline&); std::set< engine::test_filter > parse_filters( const utils::cmdline::args_vector&); bool report_unused_filters(const std::set< engine::test_filter >&, utils::cmdline::ui*); std::string format_delta(const utils::datetime::delta&); std::string format_result(const model::test_result&); std::string format_test_case_id(const model::test_program&, const std::string&); std::string format_test_case_id(const engine::test_filter&); void write_version_header(utils::cmdline::ui*); } // namespace cli #endif // !defined(CLI_COMMON_HPP) diff --git a/contrib/kyua/cli/main.cpp b/contrib/kyua/cli/main.cpp index 531c252b0a75..dd7ce939e162 100644 --- a/contrib/kyua/cli/main.cpp +++ b/contrib/kyua/cli/main.cpp @@ -1,356 +1,356 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "cli/main.hpp" #if defined(HAVE_CONFIG_H) # include "config.h" #endif extern "C" { #include #include } #include #include #include #include #include "cli/cmd_about.hpp" #include "cli/cmd_config.hpp" #include "cli/cmd_db_exec.hpp" #include "cli/cmd_db_migrate.hpp" #include "cli/cmd_debug.hpp" #include "cli/cmd_help.hpp" #include "cli/cmd_list.hpp" #include "cli/cmd_report.hpp" #include "cli/cmd_report_html.hpp" #include "cli/cmd_report_junit.hpp" #include "cli/cmd_test.hpp" #include "cli/common.ipp" #include "cli/config.hpp" #include "engine/atf.hpp" #include "engine/plain.hpp" #include "engine/scheduler.hpp" #include "engine/tap.hpp" #include "store/exceptions.hpp" #include "utils/cmdline/commands_map.ipp" #include "utils/cmdline/exceptions.hpp" #include "utils/cmdline/globals.hpp" #include "utils/cmdline/options.hpp" #include "utils/cmdline/parser.ipp" #include "utils/cmdline/ui.hpp" #include "utils/config/tree.ipp" #include "utils/env.hpp" #include "utils/format/macros.hpp" #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" #include "utils/logging/macros.hpp" #include "utils/logging/operations.hpp" #include "utils/optional.ipp" #include "utils/sanity.hpp" #include "utils/signals/exceptions.hpp" namespace cmdline = utils::cmdline; namespace config = utils::config; namespace fs = utils::fs; namespace logging = utils::logging; namespace signals = utils::signals; namespace scheduler = engine::scheduler; using utils::none; using utils::optional; namespace { /// Registers all valid scheduler interfaces. /// /// This is part of Kyua's setup but it is a bit strange to find it here. I am /// not sure what a better location would be though, so for now this is good /// enough. static void register_scheduler_interfaces(void) { scheduler::register_interface( "atf", std::shared_ptr< scheduler::interface >( new engine::atf_interface())); scheduler::register_interface( "plain", std::shared_ptr< scheduler::interface >( new engine::plain_interface())); scheduler::register_interface( "tap", std::shared_ptr< scheduler::interface >( new engine::tap_interface())); } /// Executes the given subcommand with proper usage_error reporting. /// /// \param ui Object to interact with the I/O of the program. /// \param command The subcommand to execute. /// \param args The part of the command line passed to the subcommand. The /// first item of this collection must match the command name. /// \param user_config The runtime configuration to pass to the subcommand. /// /// \return The exit code of the command. Typically 0 on success, some other /// integer otherwise. /// /// \throw cmdline::usage_error If the user input to the subcommand is invalid. /// This error does not encode the command name within it, so this function /// extends the message in the error to specify which subcommand was /// affected. /// \throw std::exception This propagates any uncaught exception. Such /// exceptions are bugs, but we let them propagate so that the runtime will /// abort and dump core. static int run_subcommand(cmdline::ui* ui, cli::cli_command* command, const cmdline::args_vector& args, const config::tree& user_config) { try { PRE(command->name() == args[0]); return command->main(ui, args, user_config); } catch (const cmdline::usage_error& e) { throw std::pair< std::string, cmdline::usage_error >( command->name(), e); } } /// Exception-safe version of main. /// /// This function provides the real meat of the entry point of the program. It /// is allowed to throw some known exceptions which are parsed by the caller. /// Doing so keeps this function simpler and allow tests to actually validate /// that the errors reported are accurate. /// /// \return The exit code of the program. Should be EXIT_SUCCESS on success and /// EXIT_FAILURE on failure. The caller extends this to additional integers for /// errors reported through exceptions. /// /// \param ui Object to interact with the I/O of the program. /// \param argc The number of arguments passed on the command line. /// \param argv NULL-terminated array containing the command line arguments. /// \param mock_command An extra command provided for testing purposes; should /// just be NULL other than for tests. /// /// \throw cmdline::usage_error If the user ran the program with invalid /// arguments. /// \throw std::exception This propagates any uncaught exception. Such /// exceptions are bugs, but we let them propagate so that the runtime will /// abort and dump core. static int safe_main(cmdline::ui* ui, int argc, const char* const argv[], cli::cli_command_ptr mock_command) { cmdline::options_vector options; options.push_back(&cli::config_option); options.push_back(&cli::variable_option); const cmdline::string_option loglevel_option( "loglevel", "Level of the messages to log", "level", "info"); options.push_back(&loglevel_option); const cmdline::path_option logfile_option( "logfile", "Path to the log file", "file", cli::detail::default_log_name().c_str()); options.push_back(&logfile_option); cmdline::commands_map< cli::cli_command > commands; commands.insert(new cli::cmd_about()); commands.insert(new cli::cmd_config()); commands.insert(new cli::cmd_db_exec()); commands.insert(new cli::cmd_db_migrate()); commands.insert(new cli::cmd_help(&options, &commands)); commands.insert(new cli::cmd_debug(), "Workspace"); commands.insert(new cli::cmd_list(), "Workspace"); commands.insert(new cli::cmd_test(), "Workspace"); commands.insert(new cli::cmd_report(), "Reporting"); commands.insert(new cli::cmd_report_html(), "Reporting"); commands.insert(new cli::cmd_report_junit(), "Reporting"); if (mock_command.get() != NULL) - commands.insert(mock_command); + commands.insert(std::move(mock_command)); const cmdline::parsed_cmdline cmdline = cmdline::parse(argc, argv, options); const fs::path logfile(cmdline.get_option< cmdline::path_option >( "logfile")); fs::mkdir_p(logfile.branch_path(), 0755); LD(F("Log file is %s") % logfile); utils::install_crash_handlers(logfile.str()); try { logging::set_persistency(cmdline.get_option< cmdline::string_option >( "loglevel"), logfile); } catch (const std::range_error& e) { throw cmdline::usage_error(e.what()); } if (cmdline.arguments().empty()) throw cmdline::usage_error("No command provided"); const std::string cmdname = cmdline.arguments()[0]; const config::tree user_config = cli::load_config(cmdline, cmdname != "help"); cli::cli_command* command = commands.find(cmdname); if (command == NULL) throw cmdline::usage_error(F("Unknown command '%s'") % cmdname); register_scheduler_interfaces(); return run_subcommand(ui, command, cmdline.arguments(), user_config); } } // anonymous namespace /// Gets the name of the default log file. /// /// \return The path to the log file. fs::path cli::detail::default_log_name(void) { // Update doc/troubleshooting.texi if you change this algorithm. const optional< std::string > home(utils::getenv("HOME")); if (home) { return logging::generate_log_name(fs::path(home.get()) / ".kyua" / "logs", cmdline::progname()); } else { const optional< std::string > tmpdir(utils::getenv("TMPDIR")); if (tmpdir) { return logging::generate_log_name(fs::path(tmpdir.get()), cmdline::progname()); } else { return logging::generate_log_name(fs::path("/tmp"), cmdline::progname()); } } } /// Testable entry point, with catch-all exception handlers. /// /// This entry point does not perform any initialization of global state; it is /// provided to allow unit-testing of the utility's entry point. /// /// \param ui Object to interact with the I/O of the program. /// \param argc The number of arguments passed on the command line. /// \param argv NULL-terminated array containing the command line arguments. /// \param mock_command An extra command provided for testing purposes; should /// just be NULL other than for tests. /// /// \return 0 on success, some other integer on error. /// /// \throw std::exception This propagates any uncaught exception. Such /// exceptions are bugs, but we let them propagate so that the runtime will /// abort and dump core. int cli::main(cmdline::ui* ui, const int argc, const char* const* const argv, cli_command_ptr mock_command) { try { - const int exit_code = safe_main(ui, argc, argv, mock_command); + const int exit_code = safe_main(ui, argc, argv, std::move(mock_command)); // Codes above 1 are reserved to report conditions captured as // exceptions below. INV(exit_code == EXIT_SUCCESS || exit_code == EXIT_FAILURE); return exit_code; } catch (const signals::interrupted_error& e) { cmdline::print_error(ui, F("%s.") % e.what()); // Re-deliver the interruption signal to self so that we terminate with // the right status. At this point we should NOT have any custom signal // handlers in place. ::kill(getpid(), e.signo()); LD("Interrupt signal re-delivery did not terminate program"); // If we reach this, something went wrong because we did not exit as // intended. Return an internal error instead. (Would be nicer to // abort in principle, but it wouldn't be a nice experience if it ever // happened.) return 2; } catch (const std::pair< std::string, cmdline::usage_error >& e) { const std::string message = F("Usage error for command %s: %s.") % e.first % e.second.what(); LE(message); ui->err(message); ui->err(F("Type '%s help %s' for usage information.") % cmdline::progname() % e.first); return 3; } catch (const cmdline::usage_error& e) { const std::string message = F("Usage error: %s.") % e.what(); LE(message); ui->err(message); ui->err(F("Type '%s help' for usage information.") % cmdline::progname()); return 3; } catch (const store::old_schema_error& e) { const std::string message = F("The database has schema version %s, " "which is too old; please use db-migrate " "to upgrade it.") % e.old_version(); cmdline::print_error(ui, message); return 2; } catch (const std::runtime_error& e) { cmdline::print_error(ui, F("%s.") % e.what()); return 2; } } /// Delegate for ::main(). /// /// This function is supposed to be called directly from the top-level ::main() /// function. It takes care of initializing internal libraries and then calls /// main(ui, argc, argv). /// /// \pre This function can only be called once. /// /// \throw std::exception This propagates any uncaught exception. Such /// exceptions are bugs, but we let them propagate so that the runtime will /// abort and dump core. int cli::main(const int argc, const char* const* const argv) { logging::set_inmemory(); LI(F("%s %s") % PACKAGE % VERSION); std::string plain_args; for (const char* const* arg = argv; *arg != NULL; arg++) plain_args += F(" %s") % *arg; LI(F("Command line:%s") % plain_args); cmdline::init(argv[0]); cmdline::ui ui; const int exit_code = main(&ui, argc, argv); LI(F("Clean exit with code %s") % exit_code); return exit_code; } diff --git a/contrib/kyua/engine/config.cpp b/contrib/kyua/engine/config.cpp index a7c418e3164c..d53e9936ba91 100644 --- a/contrib/kyua/engine/config.cpp +++ b/contrib/kyua/engine/config.cpp @@ -1,272 +1,272 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "engine/config.hpp" #if defined(HAVE_CONFIG_H) # include "config.h" #endif #include #include "engine/exceptions.hpp" #include "engine/execenv/execenv.hpp" #include "utils/config/exceptions.hpp" #include "utils/config/parser.hpp" #include "utils/config/tree.ipp" #include "utils/passwd.hpp" #include "utils/text/exceptions.hpp" #include "utils/text/operations.ipp" namespace config = utils::config; namespace execenv = engine::execenv; namespace fs = utils::fs; namespace passwd = utils::passwd; namespace text = utils::text; namespace { /// Defines the schema of a configuration tree. /// /// \param [in,out] tree The tree to populate. The tree should be empty on /// entry to prevent collisions with the keys defined in here. static void init_tree(config::tree& tree) { tree.define< config::string_node >("architecture"); tree.define< config::strings_set_node >("execenvs"); tree.define< config::positive_int_node >("parallelism"); tree.define< config::string_node >("platform"); tree.define< engine::user_node >("unprivileged_user"); tree.define_dynamic("test_suites"); } /// Fills in a configuration tree with default values. /// /// \param [in,out] tree The tree to populate. init_tree() must have been /// called on it beforehand. static void set_defaults(config::tree& tree) { tree.set< config::string_node >("architecture", KYUA_ARCHITECTURE); std::set< std::string > supported; for (auto em : execenv::execenvs()) if (em->is_supported()) supported.insert(em->name()); supported.insert(execenv::default_execenv_name); tree.set< config::strings_set_node >("execenvs", supported); // TODO(jmmv): Automatically derive this from the number of CPUs in the // machine and forcibly set to a value greater than 1. Still testing // the new parallel implementation as of 2015-02-27 though. tree.set< config::positive_int_node >("parallelism", 1); tree.set< config::string_node >("platform", KYUA_PLATFORM); } /// Configuration parser specialization for Kyua configuration files. class config_parser : public config::parser { /// Initializes the configuration tree. /// /// This is a callback executed when the configuration script invokes the /// syntax() method. We populate the configuration tree from here with the /// schema version requested by the file. /// /// \param [in,out] tree The tree to populate. /// \param syntax_version The version of the file format as specified in the /// configuration file. /// /// \throw config::syntax_error If the syntax_format/syntax_version /// combination is not supported. void setup(config::tree& tree, const int syntax_version) { if (syntax_version < 1 || syntax_version > 2) throw config::syntax_error(F("Unsupported config version %s") % syntax_version); init_tree(tree); set_defaults(tree); } public: /// Initializes the parser. /// /// \param [out] tree_ The tree in which the results of the parsing will be /// stored when parse() is called. Should be empty on entry. Because /// we grab a reference to this object, the tree must remain valid for /// the existence of the parser object. explicit config_parser(config::tree& tree_) : config::parser(tree_) { } }; } // anonymous namespace /// Copies the node. /// /// \return A dynamically-allocated node. config::detail::base_node* engine::user_node::deep_copy(void) const { - std::auto_ptr< user_node > new_node(new user_node()); + std::unique_ptr< user_node > new_node(new user_node()); new_node->_value = _value; return new_node.release(); } /// Pushes the node's value onto the Lua stack. /// /// \param state The Lua state onto which to push the value. void engine::user_node::push_lua(lutok::state& state) const { state.push_string(value().name); } /// Sets the value of the node from an entry in the Lua stack. /// /// \param state The Lua state from which to get the value. /// \param value_index The stack index in which the value resides. /// /// \throw value_error If the value in state(value_index) cannot be /// processed by this node. void engine::user_node::set_lua(lutok::state& state, const int value_index) { if (state.is_number(value_index)) { config::typed_leaf_node< passwd::user >::set( passwd::find_user_by_uid(state.to_integer(-1))); } else if (state.is_string(value_index)) { config::typed_leaf_node< passwd::user >::set( passwd::find_user_by_name(state.to_string(-1))); } else throw config::value_error("Invalid user identifier"); } /// Sets the value of the node from a raw string representation. /// /// \param raw_value The value to set the node to. /// /// \throw value_error If the value is invalid. void engine::user_node::set_string(const std::string& raw_value) { try { config::typed_leaf_node< passwd::user >::set( passwd::find_user_by_name(raw_value)); } catch (const std::runtime_error& e) { int uid; try { uid = text::to_type< int >(raw_value); } catch (const text::value_error& e2) { throw error(F("Cannot find user with name '%s'") % raw_value); } try { config::typed_leaf_node< passwd::user >::set( passwd::find_user_by_uid(uid)); } catch (const std::runtime_error& e2) { throw error(F("Cannot find user with UID %s") % uid); } } } /// Converts the contents of the node to a string. /// /// \pre The node must have a value. /// /// \return A string representation of the value held by the node. std::string engine::user_node::to_string(void) const { return config::typed_leaf_node< passwd::user >::value().name; } /// Constructs a config with the built-in settings. /// /// \return A default test suite configuration. config::tree engine::default_config(void) { config::tree tree(false); init_tree(tree); set_defaults(tree); return tree; } /// Constructs a config with the built-in settings. /// /// \return An empty test suite configuration. config::tree engine::empty_config(void) { config::tree tree(false); init_tree(tree); // Tests of Kyua itself tend to use an empty config, i.e. default // execution environment is used. Let's allow it. std::set< std::string > supported; supported.insert(engine::execenv::default_execenv_name); tree.set< config::strings_set_node >("execenvs", supported); return tree; } /// Parses a test suite configuration file. /// /// \param file The file to parse. /// /// \return High-level representation of the configuration file. /// /// \throw load_error If there is any problem loading the file. This includes /// file access errors and syntax errors. config::tree engine::load_config(const utils::fs::path& file) { config::tree tree(false); try { config_parser(tree).parse(file); } catch (const config::error& e) { throw load_error(file, e.what()); } return tree; } diff --git a/contrib/kyua/engine/scheduler.cpp b/contrib/kyua/engine/scheduler.cpp index d4507a247323..5179436073b8 100644 --- a/contrib/kyua/engine/scheduler.cpp +++ b/contrib/kyua/engine/scheduler.cpp @@ -1,1642 +1,1642 @@ // Copyright 2014 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "engine/scheduler.hpp" extern "C" { #include } #include #include #include #include #include #include "engine/config.hpp" #include "engine/exceptions.hpp" #include "engine/execenv/execenv.hpp" #include "engine/requirements.hpp" #include "model/context.hpp" #include "model/metadata.hpp" #include "model/test_case.hpp" #include "model/test_program.hpp" #include "model/test_result.hpp" #include "utils/config/tree.ipp" #include "utils/datetime.hpp" #include "utils/defs.hpp" #include "utils/env.hpp" #include "utils/format/macros.hpp" #include "utils/fs/directory.hpp" #include "utils/fs/exceptions.hpp" #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" #include "utils/logging/macros.hpp" #include "utils/noncopyable.hpp" #include "utils/optional.ipp" #include "utils/passwd.hpp" #include "utils/process/executor.ipp" #include "utils/process/status.hpp" #include "utils/sanity.hpp" #include "utils/stacktrace.hpp" #include "utils/stream.hpp" #include "utils/text/operations.ipp" namespace config = utils::config; namespace datetime = utils::datetime; namespace execenv = engine::execenv; namespace executor = utils::process::executor; namespace fs = utils::fs; namespace logging = utils::logging; namespace passwd = utils::passwd; namespace process = utils::process; namespace scheduler = engine::scheduler; namespace text = utils::text; using utils::none; using utils::optional; /// Timeout for the test case cleanup operation. /// /// TODO(jmmv): This is here only for testing purposes. Maybe we should expose /// this setting as part of the user_config. datetime::delta scheduler::cleanup_timeout(60, 0); /// Timeout for the test case execenv cleanup operation. datetime::delta scheduler::execenv_cleanup_timeout(60, 0); /// Timeout for the test case listing operation. /// /// TODO(jmmv): This is here only for testing purposes. Maybe we should expose /// this setting as part of the user_config. datetime::delta scheduler::list_timeout(300, 0); namespace { /// Magic exit status to indicate that the test case was probably skipped. /// /// The test case was only skipped if and only if we return this exit code and /// we find the skipped_cookie file on disk. static const int exit_skipped = 84; /// Text file containing the skip reason for the test case. /// /// This will only be present within unique_work_directory if the test case /// exited with the exit_skipped code. However, there is no guarantee that the /// file is there (say if the test really decided to exit with code exit_skipped /// on its own). static const char* skipped_cookie = "skipped.txt"; /// Mapping of interface names to interface definitions. typedef std::map< std::string, std::shared_ptr< scheduler::interface > > interfaces_map; /// Mapping of interface names to interface definitions. /// /// Use register_interface() to add an entry to this global table. static interfaces_map interfaces; /// Scans the contents of a directory and appends the file listing to a file. /// /// \param dir_path The directory to scan. /// \param output_file The file to which to append the listing. /// /// \throw engine::error If there are problems listing the files. static void append_files_listing(const fs::path& dir_path, const fs::path& output_file) { std::ofstream output(output_file.c_str(), std::ios::app); if (!output) throw engine::error(F("Failed to open output file %s for append") % output_file); try { std::set < std::string > names; const fs::directory dir(dir_path); for (fs::directory::const_iterator iter = dir.begin(); iter != dir.end(); ++iter) { if (iter->name != "." && iter->name != "..") names.insert(iter->name); } if (!names.empty()) { output << "Files left in work directory after failure: " << text::join(names, ", ") << '\n'; } } catch (const fs::error& e) { throw engine::error(F("Cannot append files listing to %s: %s") % output_file % e.what()); } } /// Maintenance data held while a test is being executed. /// /// This data structure exists from the moment when a test is executed via /// scheduler::spawn_test() or scheduler::impl::spawn_cleanup() to when it is /// cleaned up with result_handle::cleanup(). /// /// This is a base data type intended to be extended for the test and cleanup /// cases so that each contains only the relevant data. struct exec_data : utils::noncopyable { /// Test program data for this test case. const model::test_program_ptr test_program; /// Name of the test case. const std::string test_case_name; /// Constructor. /// /// \param test_program_ Test program data for this test case. /// \param test_case_name_ Name of the test case. exec_data(const model::test_program_ptr test_program_, const std::string& test_case_name_) : test_program(test_program_), test_case_name(test_case_name_) { } /// Destructor. virtual ~exec_data(void) { } }; /// Maintenance data held while a test is being executed. struct test_exec_data : public exec_data { /// Test program-specific execution interface. const std::shared_ptr< scheduler::interface > interface; /// User configuration passed to the execution of the test. We need this /// here to recover it later when chaining the execution of a cleanup /// routine (if any). const config::tree user_config; /// Whether this test case still needs to have its cleanup routine executed. /// /// This is set externally when the cleanup routine is actually invoked to /// denote that no further attempts shall be made at cleaning this up. bool needs_cleanup; /// Whether this test case still needs to have its execenv cleanup executed. /// /// This is set externally when the cleanup routine is actually invoked to /// denote that no further attempts shall be made at cleaning this up. bool needs_execenv_cleanup; /// Original PID of the test case subprocess. /// /// This is used for the cleanup upon termination by a signal, to reap the /// leftovers and form missing exit_handle. pid_t pid; /// The exit_handle for this test once it has completed. /// /// This is set externally when the test case has finished, as we need this /// information to invoke the followup cleanup routine in the right context, /// as indicated by needs_cleanup. optional< executor::exit_handle > exit_handle; /// Constructor. /// /// \param test_program_ Test program data for this test case. /// \param test_case_name_ Name of the test case. /// \param interface_ Test program-specific execution interface. /// \param user_config_ User configuration passed to the test. test_exec_data(const model::test_program_ptr test_program_, const std::string& test_case_name_, const std::shared_ptr< scheduler::interface > interface_, const config::tree& user_config_, const pid_t pid_) : exec_data(test_program_, test_case_name_), interface(interface_), user_config(user_config_), pid(pid_) { const model::test_case& test_case = test_program->find(test_case_name); needs_cleanup = test_case.get_metadata().has_cleanup(); needs_execenv_cleanup = test_case.get_metadata().has_execenv(); } }; /// Maintenance data held while a test cleanup routine is being executed. /// /// Instances of this object are related to a previous test_exec_data, as /// cleanup routines can only exist once the test has been run. struct cleanup_exec_data : public exec_data { /// The exit handle of the test. This is necessary so that we can return /// the correct exit_handle to the user of the scheduler. executor::exit_handle body_exit_handle; /// The final result of the test's body. This is necessary to compute the /// right return value for a test with a cleanup routine: the body result is /// respected if it is a "bad" result; else the result of the cleanup /// routine is used if it has failed. model::test_result body_result; /// Constructor. /// /// \param test_program_ Test program data for this test case. /// \param test_case_name_ Name of the test case. /// \param body_exit_handle_ If not none, exit handle of the body /// corresponding to the cleanup routine represented by this exec_data. /// \param body_result_ If not none, result of the body corresponding to the /// cleanup routine represented by this exec_data. cleanup_exec_data(const model::test_program_ptr test_program_, const std::string& test_case_name_, const executor::exit_handle& body_exit_handle_, const model::test_result& body_result_) : exec_data(test_program_, test_case_name_), body_exit_handle(body_exit_handle_), body_result(body_result_) { } }; /// Maintenance data held while a test execenv cleanup is being executed. /// /// Instances of this object are related to a previous test_exec_data, as /// cleanup routines can only exist once the test has been run. struct execenv_exec_data : public exec_data { /// The exit handle of the test. This is necessary so that we can return /// the correct exit_handle to the user of the scheduler. executor::exit_handle body_exit_handle; /// The final result of the test's body. This is necessary to compute the /// right return value for a test with a cleanup routine: the body result is /// respected if it is a "bad" result; else the result of the cleanup /// routine is used if it has failed. model::test_result body_result; /// Constructor. /// /// \param test_program_ Test program data for this test case. /// \param test_case_name_ Name of the test case. /// \param body_exit_handle_ If not none, exit handle of the body /// corresponding to the cleanup routine represented by this exec_data. /// \param body_result_ If not none, result of the body corresponding to the /// cleanup routine represented by this exec_data. execenv_exec_data(const model::test_program_ptr test_program_, const std::string& test_case_name_, const executor::exit_handle& body_exit_handle_, const model::test_result& body_result_) : exec_data(test_program_, test_case_name_), body_exit_handle(body_exit_handle_), body_result(body_result_) { } }; /// Shared pointer to exec_data. /// /// We require this because we want exec_data to not be copyable, and thus we /// cannot just store it in the map without move constructors. typedef std::shared_ptr< exec_data > exec_data_ptr; /// Mapping of active PIDs to their maintenance data. typedef std::map< int, exec_data_ptr > exec_data_map; /// Enforces a test program to hold an absolute path. /// /// TODO(jmmv): This function (which is a pretty ugly hack) exists because we /// want the interface hooks to receive a test_program as their argument. /// However, those hooks run after the test program has been isolated, which /// means that the current directory has changed since when the test_program /// objects were created. This causes the absolute_path() method of /// test_program to return bogus values if the internal representation of their /// path is relative. We should fix somehow: maybe making the fs module grab /// its "current_path" view at program startup time; or maybe by grabbing the /// current path at test_program creation time; or maybe something else. /// /// \param program The test program to modify. /// /// \return A new test program whose internal paths are absolute. static model::test_program force_absolute_paths(const model::test_program program) { const std::string& relative = program.relative_path().str(); const std::string absolute = program.absolute_path().str(); const std::string root = absolute.substr( 0, absolute.length() - relative.length()); return model::test_program( program.interface_name(), program.relative_path(), fs::path(root), program.test_suite_name(), program.get_metadata(), program.test_cases()); } /// Functor to list the test cases of a test program. class list_test_cases { /// Interface of the test program to execute. std::shared_ptr< scheduler::interface > _interface; /// Test program to execute. const model::test_program _test_program; /// User-provided configuration variables. const config::tree& _user_config; public: /// Constructor. /// /// \param interface Interface of the test program to execute. /// \param test_program Test program to execute. /// \param user_config User-provided configuration variables. list_test_cases( const std::shared_ptr< scheduler::interface > interface, const model::test_program* test_program, const config::tree& user_config) : _interface(interface), _test_program(force_absolute_paths(*test_program)), _user_config(user_config) { } /// Body of the subprocess. void operator()(const fs::path& /* control_directory */) { const config::properties_map vars = scheduler::generate_config( _user_config, _test_program.test_suite_name()); _interface->exec_list(_test_program, vars); } }; /// Functor to execute a test program in a child process. class run_test_program { /// Interface of the test program to execute. std::shared_ptr< scheduler::interface > _interface; /// Test program to execute. const model::test_program _test_program; /// Name of the test case to execute. const std::string& _test_case_name; /// User-provided configuration variables. const config::tree& _user_config; /// Verifies if the test case needs to be skipped or not. /// /// We could very well run this on the scheduler parent process before /// issuing the fork. However, doing this here in the child process is /// better for two reasons: first, it allows us to continue using the simple /// spawn/wait abstraction of the scheduler; and, second, we parallelize the /// requirements checks among tests. /// /// \post If the test's preconditions are not met, the caller process is /// terminated with a special exit code and a "skipped cookie" is written to /// the disk with the reason for the failure. /// /// \param skipped_cookie_path File to create with the skip reason details /// if this test is skipped. void do_requirements_check(const fs::path& skipped_cookie_path) { const model::test_case& test_case = _test_program.find( _test_case_name); const std::string skip_reason = engine::check_reqs( test_case.get_metadata(), _user_config, _test_program.test_suite_name(), fs::current_path()); if (skip_reason.empty()) return; std::ofstream output(skipped_cookie_path.c_str()); if (!output) { std::perror((F("Failed to open %s for write") % skipped_cookie_path).str().c_str()); std::abort(); } output << skip_reason; output.close(); // Abruptly terminate the process. We don't want to run any destructors // inherited from the parent process by mistake, which could, for // example, delete our own control files! ::_exit(exit_skipped); } public: /// Constructor. /// /// \param interface Interface of the test program to execute. /// \param test_program Test program to execute. /// \param test_case_name Name of the test case to execute. /// \param user_config User-provided configuration variables. run_test_program( const std::shared_ptr< scheduler::interface > interface, const model::test_program_ptr test_program, const std::string& test_case_name, const config::tree& user_config) : _interface(interface), _test_program(force_absolute_paths(*test_program)), _test_case_name(test_case_name), _user_config(user_config) { } /// Body of the subprocess. /// /// \param control_directory The testcase directory where files will be /// read from. void operator()(const fs::path& control_directory) { const model::test_case& test_case = _test_program.find( _test_case_name); if (test_case.fake_result()) ::_exit(EXIT_SUCCESS); do_requirements_check(control_directory / skipped_cookie); const config::properties_map vars = scheduler::generate_config( _user_config, _test_program.test_suite_name()); _interface->exec_test(_test_program, _test_case_name, vars, control_directory); } }; /// Functor to execute a test program in a child process. class run_test_cleanup { /// Interface of the test program to execute. std::shared_ptr< scheduler::interface > _interface; /// Test program to execute. const model::test_program _test_program; /// Name of the test case to execute. const std::string& _test_case_name; /// User-provided configuration variables. const config::tree& _user_config; public: /// Constructor. /// /// \param interface Interface of the test program to execute. /// \param test_program Test program to execute. /// \param test_case_name Name of the test case to execute. /// \param user_config User-provided configuration variables. run_test_cleanup( const std::shared_ptr< scheduler::interface > interface, const model::test_program_ptr test_program, const std::string& test_case_name, const config::tree& user_config) : _interface(interface), _test_program(force_absolute_paths(*test_program)), _test_case_name(test_case_name), _user_config(user_config) { } /// Body of the subprocess. /// /// \param control_directory The testcase directory where cleanup will be /// run from. void operator()(const fs::path& control_directory) { const config::properties_map vars = scheduler::generate_config( _user_config, _test_program.test_suite_name()); _interface->exec_cleanup(_test_program, _test_case_name, vars, control_directory); } }; /// Functor to execute a test execenv cleanup in a child process. class run_execenv_cleanup { /// Test program to execute. const model::test_program _test_program; /// Name of the test case to execute. const std::string& _test_case_name; public: /// Constructor. /// /// \param test_program Test program to execute. /// \param test_case_name Name of the test case to execute. run_execenv_cleanup( const model::test_program_ptr test_program, const std::string& test_case_name) : _test_program(force_absolute_paths(*test_program)), _test_case_name(test_case_name) { } /// Body of the subprocess. /// /// \param control_directory The testcase directory where cleanup will be /// run from. void operator()(const fs::path& /* control_directory */) { auto e = execenv::get(_test_program, _test_case_name); e->cleanup(); } }; /// Obtains the right scheduler interface for a given test program. /// /// \param name The name of the interface of the test program. /// /// \return An scheduler interface. std::shared_ptr< scheduler::interface > find_interface(const std::string& name) { const interfaces_map::const_iterator iter = interfaces.find(name); PRE(interfaces.find(name) != interfaces.end()); return (*iter).second; } } // anonymous namespace void scheduler::interface::exec_cleanup( const model::test_program& /* test_program */, const std::string& /* test_case_name */, const config::properties_map& /* vars */, const utils::fs::path& /* control_directory */) const { // Most test interfaces do not support standalone cleanup routines so // provide a default implementation that does nothing. UNREACHABLE_MSG("exec_cleanup not implemented for an interface that " "supports standalone cleanup routines"); } /// Internal implementation of a lazy_test_program. struct engine::scheduler::lazy_test_program::impl : utils::noncopyable { /// Whether the test cases list has been yet loaded or not. bool _loaded; /// User configuration to pass to the test program list operation. config::tree _user_config; /// Scheduler context to use to load test cases. scheduler::scheduler_handle& _scheduler_handle; /// Constructor. /// /// \param user_config_ User configuration to pass to the test program list /// operation. /// \param scheduler_handle_ Scheduler context to use when loading test /// cases. impl(const config::tree& user_config_, scheduler::scheduler_handle& scheduler_handle_) : _loaded(false), _user_config(user_config_), _scheduler_handle(scheduler_handle_) { } }; /// Constructs a new test program. /// /// \param interface_name_ Name of the test program interface. /// \param binary_ The name of the test program binary relative to root_. /// \param root_ The root of the test suite containing the test program. /// \param test_suite_name_ The name of the test suite this program belongs to. /// \param md_ Metadata of the test program. /// \param user_config_ User configuration to pass to the scheduler. /// \param scheduler_handle_ Scheduler context to use to load test cases. scheduler::lazy_test_program::lazy_test_program( const std::string& interface_name_, const fs::path& binary_, const fs::path& root_, const std::string& test_suite_name_, const model::metadata& md_, const config::tree& user_config_, scheduler::scheduler_handle& scheduler_handle_) : test_program(interface_name_, binary_, root_, test_suite_name_, md_, model::test_cases_map()), _pimpl(new impl(user_config_, scheduler_handle_)) { } /// Gets or loads the list of test cases from the test program. /// /// \return The list of test cases provided by the test program. const model::test_cases_map& scheduler::lazy_test_program::test_cases(void) const { _pimpl->_scheduler_handle.check_interrupt(); if (!_pimpl->_loaded) { const model::test_cases_map tcs = _pimpl->_scheduler_handle.list_tests( this, _pimpl->_user_config); // Due to the restrictions on when set_test_cases() may be called (as a // way to lazily initialize the test cases list before it is ever // returned), this cast is valid. const_cast< scheduler::lazy_test_program* >(this)->set_test_cases(tcs); _pimpl->_loaded = true; _pimpl->_scheduler_handle.check_interrupt(); } INV(_pimpl->_loaded); return test_program::test_cases(); } /// Internal implementation for the result_handle class. struct engine::scheduler::result_handle::bimpl : utils::noncopyable { /// Generic executor exit handle for this result handle. executor::exit_handle generic; /// Mutable pointer to the corresponding scheduler state. /// /// This object references a member of the scheduler_handle that yielded /// this result_handle instance. We need this direct access to clean up /// after ourselves when the result is destroyed. exec_data_map& all_exec_data; /// Constructor. /// /// \param generic_ Generic executor exit handle for this result handle. /// \param [in,out] all_exec_data_ Global object keeping track of all active /// executions for an scheduler. This is a pointer to a member of the /// scheduler_handle object. bimpl(const executor::exit_handle generic_, exec_data_map& all_exec_data_) : generic(generic_), all_exec_data(all_exec_data_) { } /// Destructor. ~bimpl(void) { LD(F("Removing %s from all_exec_data") % generic.original_pid()); all_exec_data.erase(generic.original_pid()); } }; /// Constructor. /// /// \param pbimpl Constructed internal implementation. scheduler::result_handle::result_handle(std::shared_ptr< bimpl > pbimpl) : _pbimpl(pbimpl) { } /// Destructor. scheduler::result_handle::~result_handle(void) { } /// Cleans up the test case results. /// /// This function should be called explicitly as it provides the means to /// control any exceptions raised during cleanup. Do not rely on the destructor /// to clean things up. /// /// \throw engine::error If the cleanup fails, especially due to the inability /// to remove the work directory. void scheduler::result_handle::cleanup(void) { _pbimpl->generic.cleanup(); } /// Returns the original PID corresponding to this result. /// /// \return An exec_handle. int scheduler::result_handle::original_pid(void) const { return _pbimpl->generic.original_pid(); } /// Returns the timestamp of when spawn_test was called. /// /// \return A timestamp. const datetime::timestamp& scheduler::result_handle::start_time(void) const { return _pbimpl->generic.start_time(); } /// Returns the timestamp of when wait_any_test returned this object. /// /// \return A timestamp. const datetime::timestamp& scheduler::result_handle::end_time(void) const { return _pbimpl->generic.end_time(); } /// Returns the path to the test-specific work directory. /// /// This is guaranteed to be clear of files created by the scheduler. /// /// \return The path to a directory that exists until cleanup() is called. fs::path scheduler::result_handle::work_directory(void) const { return _pbimpl->generic.work_directory(); } /// Returns the path to the test's stdout file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& scheduler::result_handle::stdout_file(void) const { return _pbimpl->generic.stdout_file(); } /// Returns the path to the test's stderr file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& scheduler::result_handle::stderr_file(void) const { return _pbimpl->generic.stderr_file(); } /// Internal implementation for the test_result_handle class. struct engine::scheduler::test_result_handle::impl : utils::noncopyable { /// Test program data for this test case. model::test_program_ptr test_program; /// Name of the test case. std::string test_case_name; /// The actual result of the test execution. const model::test_result test_result; /// Constructor. /// /// \param test_program_ Test program data for this test case. /// \param test_case_name_ Name of the test case. /// \param test_result_ The actual result of the test execution. impl(const model::test_program_ptr test_program_, const std::string& test_case_name_, const model::test_result& test_result_) : test_program(test_program_), test_case_name(test_case_name_), test_result(test_result_) { } }; /// Constructor. /// /// \param pbimpl Constructed internal implementation for the base object. /// \param pimpl Constructed internal implementation. scheduler::test_result_handle::test_result_handle( std::shared_ptr< bimpl > pbimpl, std::shared_ptr< impl > pimpl) : result_handle(pbimpl), _pimpl(pimpl) { } /// Destructor. scheduler::test_result_handle::~test_result_handle(void) { } /// Returns the test program that yielded this result. /// /// \return A test program. const model::test_program_ptr scheduler::test_result_handle::test_program(void) const { return _pimpl->test_program; } /// Returns the name of the test case that yielded this result. /// /// \return A test case name const std::string& scheduler::test_result_handle::test_case_name(void) const { return _pimpl->test_case_name; } /// Returns the actual result of the test execution. /// /// \return A test result. const model::test_result& scheduler::test_result_handle::test_result(void) const { return _pimpl->test_result; } /// Internal implementation for the scheduler_handle. struct engine::scheduler::scheduler_handle::impl : utils::noncopyable { /// Generic executor instance encapsulated by this one. executor::executor_handle generic; /// Mapping of exec handles to the data required at run time. exec_data_map all_exec_data; /// Collection of test_exec_data objects. typedef std::vector< const test_exec_data* > test_exec_data_vector; /// Constructor. impl(void) : generic(executor::setup()) { } /// Destructor. /// /// This runs any pending cleanup routines, which should only happen if the /// scheduler is abruptly terminated (aka if a signal is received). ~impl(void) { const test_exec_data_vector tests_data = tests_needing_cleanup(); for (test_exec_data_vector::const_iterator iter = tests_data.begin(); iter != tests_data.end(); ++iter) { const test_exec_data* test_data = *iter; try { sync_cleanup(test_data); } catch (const std::runtime_error& e) { LW(F("Failed to run cleanup routine for %s:%s on abrupt " "termination") % test_data->test_program->relative_path() % test_data->test_case_name); } } const test_exec_data_vector td = tests_needing_execenv_cleanup(); for (test_exec_data_vector::const_iterator iter = td.begin(); iter != td.end(); ++iter) { const test_exec_data* test_data = *iter; try { sync_execenv_cleanup(test_data); } catch (const std::runtime_error& e) { LW(F("Failed to run execenv cleanup routine for %s:%s on abrupt " "termination") % test_data->test_program->relative_path() % test_data->test_case_name); } } } /// Finds any pending exec_datas that correspond to tests needing cleanup. /// /// \return The collection of test_exec_data objects that have their /// needs_cleanup property set to true. test_exec_data_vector tests_needing_cleanup(void) { test_exec_data_vector tests_data; for (exec_data_map::const_iterator iter = all_exec_data.begin(); iter != all_exec_data.end(); ++iter) { const exec_data_ptr data = (*iter).second; try { test_exec_data* test_data = &dynamic_cast< test_exec_data& >( *data.get()); if (test_data->needs_cleanup) { tests_data.push_back(test_data); test_data->needs_cleanup = false; if (!test_data->exit_handle) test_data->exit_handle = generic.reap(test_data->pid); } } catch (const std::bad_cast& e) { // Do nothing for cleanup_exec_data objects. } } return tests_data; } /// Finds any pending exec_datas that correspond to tests needing execenv /// cleanup. /// /// \return The collection of test_exec_data objects that have their /// specific execenv property set. test_exec_data_vector tests_needing_execenv_cleanup(void) { test_exec_data_vector tests_data; for (exec_data_map::const_iterator iter = all_exec_data.begin(); iter != all_exec_data.end(); ++iter) { const exec_data_ptr data = (*iter).second; try { test_exec_data* test_data = &dynamic_cast< test_exec_data& >( *data.get()); if (test_data->needs_execenv_cleanup) { tests_data.push_back(test_data); test_data->needs_execenv_cleanup = false; if (!test_data->exit_handle) test_data->exit_handle = generic.reap(test_data->pid); } } catch (const std::bad_cast& e) { // Do nothing for other objects. } } return tests_data; } /// Cleans up a single test case synchronously. /// /// \param test_data The data of the previously executed test case to be /// cleaned up. void sync_cleanup(const test_exec_data* test_data) { // The message in this result should never be seen by the user, but use // something reasonable just in case it leaks and we need to pinpoint // the call site. model::test_result result(model::test_result_broken, "Test case died abruptly"); const executor::exec_handle cleanup_handle = spawn_cleanup( test_data->test_program, test_data->test_case_name, test_data->user_config, test_data->exit_handle.get(), result); generic.wait(cleanup_handle); } /// Forks and executes a test case cleanup routine asynchronously. /// /// \param test_program The container test program. /// \param test_case_name The name of the test case to run. /// \param user_config User-provided configuration variables. /// \param body_handle The exit handle of the test case's corresponding /// body. The cleanup will be executed in the same context. /// \param body_result The result of the test case's corresponding body. /// /// \return A handle for the background operation. Used to match the result /// of the execution returned by wait_any() with this invocation. executor::exec_handle spawn_cleanup(const model::test_program_ptr test_program, const std::string& test_case_name, const config::tree& user_config, const executor::exit_handle& body_handle, const model::test_result& body_result) { generic.check_interrupt(); const std::shared_ptr< scheduler::interface > interface = find_interface(test_program->interface_name()); LI(F("Spawning %s:%s (cleanup)") % test_program->absolute_path() % test_case_name); const executor::exec_handle handle = generic.spawn_followup( run_test_cleanup(interface, test_program, test_case_name, user_config), body_handle, cleanup_timeout); const exec_data_ptr data(new cleanup_exec_data( test_program, test_case_name, body_handle, body_result)); LD(F("Inserting %s into all_exec_data (cleanup)") % handle.pid()); INV_MSG(all_exec_data.find(handle.pid()) == all_exec_data.end(), F("PID %s already in all_exec_data; not properly cleaned " "up or reused too fast") % handle.pid());; all_exec_data.insert(exec_data_map::value_type(handle.pid(), data)); return handle; } /// Cleans up a single test case execenv synchronously. /// /// \param test_data The data of the previously executed test case to be /// cleaned up. void sync_execenv_cleanup(const test_exec_data* test_data) { // The message in this result should never be seen by the user, but use // something reasonable just in case it leaks and we need to pinpoint // the call site. model::test_result result(model::test_result_broken, "Test case died abruptly"); const executor::exec_handle cleanup_handle = spawn_execenv_cleanup( test_data->test_program, test_data->test_case_name, test_data->exit_handle.get(), result); generic.wait(cleanup_handle); } /// Forks and executes a test case execenv cleanup asynchronously. /// /// \param test_program The container test program. /// \param test_case_name The name of the test case to run. /// \param body_handle The exit handle of the test case's corresponding /// body. The cleanup will be executed in the same context. /// \param body_result The result of the test case's corresponding body. /// /// \return A handle for the background operation. Used to match the result /// of the execution returned by wait_any() with this invocation. executor::exec_handle spawn_execenv_cleanup(const model::test_program_ptr test_program, const std::string& test_case_name, const executor::exit_handle& body_handle, const model::test_result& body_result) { generic.check_interrupt(); LI(F("Spawning %s:%s (execenv cleanup)") % test_program->absolute_path() % test_case_name); const executor::exec_handle handle = generic.spawn_followup( run_execenv_cleanup(test_program, test_case_name), body_handle, execenv_cleanup_timeout); const exec_data_ptr data(new execenv_exec_data( test_program, test_case_name, body_handle, body_result)); LD(F("Inserting %s into all_exec_data (execenv cleanup)") % handle.pid()); INV_MSG(all_exec_data.find(handle.pid()) == all_exec_data.end(), F("PID %s already in all_exec_data; not properly cleaned " "up or reused too fast") % handle.pid());; all_exec_data.insert(exec_data_map::value_type(handle.pid(), data)); return handle; } }; /// Constructor. scheduler::scheduler_handle::scheduler_handle(void) : _pimpl(new impl()) { } /// Destructor. scheduler::scheduler_handle::~scheduler_handle(void) { } /// Queries the path to the root of the work directory for all tests. /// /// \return A path. const fs::path& scheduler::scheduler_handle::root_work_directory(void) const { return _pimpl->generic.root_work_directory(); } /// Cleans up the scheduler state. /// /// This function should be called explicitly as it provides the means to /// control any exceptions raised during cleanup. Do not rely on the destructor /// to clean things up. /// /// \throw engine::error If there are problems cleaning up the scheduler. void scheduler::scheduler_handle::cleanup(void) { _pimpl->generic.cleanup(); } /// Checks if the given interface name is valid. /// /// \param name The name of the interface to validate. /// /// \throw engine::error If the given interface is not supported. void scheduler::ensure_valid_interface(const std::string& name) { if (interfaces.find(name) == interfaces.end()) throw engine::error(F("Unsupported test interface '%s'") % name); } /// Registers a new interface. /// /// \param name The name of the interface. Must not have yet been registered. /// \param spec Interface specification. void scheduler::register_interface(const std::string& name, const std::shared_ptr< interface > spec) { PRE(interfaces.find(name) == interfaces.end()); interfaces.insert(interfaces_map::value_type(name, spec)); } /// Returns the names of all registered interfaces. /// /// \return A collection of interface names. std::set< std::string > scheduler::registered_interface_names(void) { std::set< std::string > names; for (interfaces_map::const_iterator iter = interfaces.begin(); iter != interfaces.end(); ++iter) { names.insert((*iter).first); } return names; } /// Initializes the scheduler. /// /// \pre This function can only be called if there is no other scheduler_handle /// object alive. /// /// \return A handle to the operations of the scheduler. scheduler::scheduler_handle scheduler::setup(void) { return scheduler_handle(); } /// Retrieves the list of test cases from a test program. /// /// This operation is currently synchronous. /// /// This operation should never throw. Any errors during the processing of the /// test case list are subsumed into a single test case in the return value that /// represents the failed retrieval. /// /// \param test_program The test program from which to obtain the list of test /// cases. /// \param user_config User-provided configuration variables. /// /// \return The list of test cases. model::test_cases_map scheduler::scheduler_handle::list_tests( const model::test_program* test_program, const config::tree& user_config) { _pimpl->generic.check_interrupt(); const std::shared_ptr< scheduler::interface > interface = find_interface( test_program->interface_name()); try { const executor::exec_handle exec_handle = _pimpl->generic.spawn( list_test_cases(interface, test_program, user_config), list_timeout, none); executor::exit_handle exit_handle = _pimpl->generic.wait(exec_handle); const model::test_cases_map test_cases = interface->parse_list( exit_handle.status(), exit_handle.stdout_file(), exit_handle.stderr_file()); exit_handle.cleanup(); if (test_cases.empty()) throw std::runtime_error("Empty test cases list"); return test_cases; } catch (const std::runtime_error& e) { // TODO(jmmv): This is a very ugly workaround for the fact that we // cannot report failures at the test-program level. LW(F("Failed to load test cases list: %s") % e.what()); model::test_cases_map fake_test_cases; fake_test_cases.insert(model::test_cases_map::value_type( "__test_cases_list__", model::test_case( "__test_cases_list__", "Represents the correct processing of the test cases list", model::test_result(model::test_result_broken, e.what())))); return fake_test_cases; } } /// Forks and executes a test case asynchronously. /// /// Note that the caller needn't know if the test has a cleanup routine or not. /// If there indeed is a cleanup routine, we trigger it at wait_any() time. /// /// \param test_program The container test program. /// \param test_case_name The name of the test case to run. /// \param user_config User-provided configuration variables. /// /// \return A handle for the background operation. Used to match the result of /// the execution returned by wait_any() with this invocation. scheduler::exec_handle scheduler::scheduler_handle::spawn_test( const model::test_program_ptr test_program, const std::string& test_case_name, const config::tree& user_config) { _pimpl->generic.check_interrupt(); const std::shared_ptr< scheduler::interface > interface = find_interface( test_program->interface_name()); LI(F("Spawning %s:%s") % test_program->absolute_path() % test_case_name); const model::test_case& test_case = test_program->find(test_case_name); optional< passwd::user > unprivileged_user; if (user_config.is_set("unprivileged_user") && test_case.get_metadata().required_user() == "unprivileged") { unprivileged_user = user_config.lookup< engine::user_node >( "unprivileged_user"); } const executor::exec_handle handle = _pimpl->generic.spawn( run_test_program(interface, test_program, test_case_name, user_config), test_case.get_metadata().timeout(), unprivileged_user); const exec_data_ptr data(new test_exec_data( test_program, test_case_name, interface, user_config, handle.pid())); LD(F("Inserting %s into all_exec_data") % handle.pid()); INV_MSG( _pimpl->all_exec_data.find(handle.pid()) == _pimpl->all_exec_data.end(), F("PID %s already in all_exec_data; not cleaned up or reused too fast") % handle.pid());; _pimpl->all_exec_data.insert(exec_data_map::value_type(handle.pid(), data)); return handle.pid(); } /// Waits for completion of any forked test case. /// /// Note that if the terminated test case has a cleanup routine, this function /// is the one in charge of spawning the cleanup routine asynchronously. /// /// \return The result of the execution of a subprocess. This is a dynamically /// allocated object because the scheduler can spawn subprocesses of various /// types and, at wait time, we don't know upfront what we are going to get. scheduler::result_handle_ptr scheduler::scheduler_handle::wait_any(void) { _pimpl->generic.check_interrupt(); executor::exit_handle handle = _pimpl->generic.wait_any(); const exec_data_map::iterator iter = _pimpl->all_exec_data.find( handle.original_pid()); exec_data_ptr data = (*iter).second; utils::dump_stacktrace_if_available(data->test_program->absolute_path(), _pimpl->generic, handle); optional< model::test_result > result; // test itself try { test_exec_data* test_data = &dynamic_cast< test_exec_data& >( *data.get()); LD(F("Got %s from all_exec_data") % handle.original_pid()); test_data->exit_handle = handle; const model::test_case& test_case = test_data->test_program->find( test_data->test_case_name); result = test_case.fake_result(); if (!result && handle.status() && handle.status().get().exited() && handle.status().get().exitstatus() == exit_skipped) { // If the test's process terminated with our magic "exit_skipped" // status, there are two cases to handle. The first is the case // where the "skipped cookie" exists, in which case we never got to // actually invoke the test program; if that's the case, handle it // here. The second case is where the test case actually decided to // exit with the "exit_skipped" status; in that case, just fall back // to the regular status handling. const fs::path skipped_cookie_path = handle.control_directory() / skipped_cookie; std::ifstream input(skipped_cookie_path.c_str()); if (input) { result = model::test_result(model::test_result_skipped, utils::read_stream(input)); input.close(); // If we determined that the test needs to be skipped, we do not // want to run the cleanup routine because doing so could result // in errors. However, we still want to run the cleanup routine // if the test's body reports a skip (because actions could have // already been taken). test_data->needs_cleanup = false; test_data->needs_execenv_cleanup = false; } } if (!result) { result = test_data->interface->compute_result( handle.status(), handle.control_directory(), handle.stdout_file(), handle.stderr_file()); } INV(result); if (!result.get().good()) { append_files_listing(handle.work_directory(), handle.stderr_file()); } if (test_data->needs_cleanup) { INV(test_case.get_metadata().has_cleanup()); // The test body has completed and we have processed it. If there // is a cleanup routine, trigger it now and wait for any other test // completion. The caller never knows about cleanup routines. _pimpl->spawn_cleanup(test_data->test_program, test_data->test_case_name, test_data->user_config, handle, result.get()); // TODO(jmmv): Chaining this call is ugly. We'd be better off by // looping over terminated processes until we got a result suitable // for user consumption. For the time being this is good enough and // not a problem because the call chain won't get big: the majority // of test cases do not have cleanup routines. return wait_any(); } if (test_data->needs_execenv_cleanup) { INV(test_case.get_metadata().has_execenv()); _pimpl->spawn_execenv_cleanup(test_data->test_program, test_data->test_case_name, handle, result.get()); test_data->needs_execenv_cleanup = false; return wait_any(); } } catch (const std::bad_cast& e) { // ok, let's check for another type } // test cleanup try { const cleanup_exec_data* cleanup_data = &dynamic_cast< const cleanup_exec_data& >(*data.get()); LD(F("Got %s from all_exec_data (cleanup)") % handle.original_pid()); // Handle the completion of cleanup subprocesses internally: the caller // is not aware that these exist so, when we return, we must return the // data for the original test that triggered this routine. For example, // because the caller wants to see the exact same exec_handle that was // returned by spawn_test. const model::test_result& body_result = cleanup_data->body_result; if (body_result.good()) { if (!handle.status()) { result = model::test_result(model::test_result_broken, "Test case cleanup timed out"); } else { if (!handle.status().get().exited() || handle.status().get().exitstatus() != EXIT_SUCCESS) { result = model::test_result( model::test_result_broken, "Test case cleanup did not terminate successfully"); } else { result = body_result; } } } else { result = body_result; } // Untrack the cleanup process. This must be done explicitly because we // do not create a result_handle object for the cleanup, and that is the // one in charge of doing so in the regular (non-cleanup) case. LD(F("Removing %s from all_exec_data (cleanup) in favor of %s") % handle.original_pid() % cleanup_data->body_exit_handle.original_pid()); _pimpl->all_exec_data.erase(handle.original_pid()); handle = cleanup_data->body_exit_handle; const exec_data_map::iterator it = _pimpl->all_exec_data.find( handle.original_pid()); if (it != _pimpl->all_exec_data.end()) { exec_data_ptr d = (*it).second; test_exec_data* test_data = &dynamic_cast< test_exec_data& >( *d.get()); const model::test_case& test_case = cleanup_data->test_program->find(cleanup_data->test_case_name); test_data->needs_cleanup = false; if (test_data->needs_execenv_cleanup) { INV(test_case.get_metadata().has_execenv()); _pimpl->spawn_execenv_cleanup(cleanup_data->test_program, cleanup_data->test_case_name, handle, result.get()); test_data->needs_execenv_cleanup = false; return wait_any(); } } } catch (const std::bad_cast& e) { // ok, let's check for another type } // execenv cleanup try { const execenv_exec_data* execenv_data = &dynamic_cast< const execenv_exec_data& >(*data.get()); LD(F("Got %s from all_exec_data (execenv cleanup)") % handle.original_pid()); const model::test_result& body_result = execenv_data->body_result; if (body_result.good()) { if (!handle.status()) { result = model::test_result(model::test_result_broken, "Test case execenv cleanup timed out"); } else { if (!handle.status().get().exited() || handle.status().get().exitstatus() != EXIT_SUCCESS) { result = model::test_result( model::test_result_broken, "Test case execenv cleanup did not terminate successfully"); // ? } else { result = body_result; } } } else { result = body_result; } LD(F("Removing %s from all_exec_data (execenv cleanup) in favor of %s") % handle.original_pid() % execenv_data->body_exit_handle.original_pid()); _pimpl->all_exec_data.erase(handle.original_pid()); handle = execenv_data->body_exit_handle; } catch (const std::bad_cast& e) { // ok, it was one of the types above } INV(result); std::shared_ptr< result_handle::bimpl > result_handle_bimpl( new result_handle::bimpl(handle, _pimpl->all_exec_data)); std::shared_ptr< test_result_handle::impl > test_result_handle_impl( new test_result_handle::impl( data->test_program, data->test_case_name, result.get())); return result_handle_ptr(new test_result_handle(result_handle_bimpl, test_result_handle_impl)); } /// Forks and executes a test case synchronously for debugging. /// /// \pre No other processes should be in execution by the scheduler. /// /// \param test_program The container test program. /// \param test_case_name The name of the test case to run. /// \param user_config User-provided configuration variables. /// \param stdout_target File to which to write the stdout of the test case. /// \param stderr_target File to which to write the stderr of the test case. /// /// \return The result of the execution of the test. scheduler::result_handle_ptr scheduler::scheduler_handle::debug_test( const model::test_program_ptr test_program, const std::string& test_case_name, const config::tree& user_config, const fs::path& stdout_target, const fs::path& stderr_target) { const exec_handle exec_handle = spawn_test( test_program, test_case_name, user_config); result_handle_ptr result_handle = wait_any(); // TODO(jmmv): We need to do this while the subprocess is alive. This is // important for debugging purposes, as we should see the contents of stdout // or stderr as they come in. // // Unfortunately, we cannot do so. We cannot just read and block from a // file, waiting for further output to appear... as this only works on pipes // or sockets. We need a better interface for this whole thing. { - std::auto_ptr< std::ostream > output = utils::open_ostream( + std::unique_ptr< std::ostream > output = utils::open_ostream( stdout_target); *output << utils::read_file(result_handle->stdout_file()); } { - std::auto_ptr< std::ostream > output = utils::open_ostream( + std::unique_ptr< std::ostream > output = utils::open_ostream( stderr_target); *output << utils::read_file(result_handle->stderr_file()); } INV(result_handle->original_pid() == exec_handle); return result_handle; } /// Checks if an interrupt has fired. /// /// Calls to this function should be sprinkled in strategic places through the /// code protected by an interrupts_handler object. /// /// This is just a wrapper over signals::check_interrupt() to avoid leaking this /// dependency to the caller. /// /// \throw signals::interrupted_error If there has been an interrupt. void scheduler::scheduler_handle::check_interrupt(void) const { _pimpl->generic.check_interrupt(); } /// Queries the current execution context. /// /// \return The queried context. model::context scheduler::current_context(void) { return model::context(fs::current_path(), utils::getallenv()); } /// Generates the set of configuration variables for a test program. /// /// \param user_config The configuration variables provided by the user. /// \param test_suite The name of the test suite. /// /// \return The mapping of configuration variables for the test program. config::properties_map scheduler::generate_config(const config::tree& user_config, const std::string& test_suite) { config::properties_map props; try { props = user_config.all_properties(F("test_suites.%s") % test_suite, true); } catch (const config::unknown_key_error& unused_error) { // Ignore: not all test suites have entries in the configuration. } // TODO(jmmv): This is a hack that exists for the ATF interface only, so it // should be moved there. if (user_config.is_set("unprivileged_user")) { const passwd::user& user = user_config.lookup< engine::user_node >("unprivileged_user"); // The property is duplicated using both ATF and Kyua naming styles // for better UX. props["unprivileged-user"] = user.name; props["unprivileged_user"] = user.name; } return props; } diff --git a/contrib/kyua/model/metadata.cpp b/contrib/kyua/model/metadata.cpp index 26b7f7322c6e..85248d596727 100644 --- a/contrib/kyua/model/metadata.cpp +++ b/contrib/kyua/model/metadata.cpp @@ -1,1144 +1,1144 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "model/metadata.hpp" #include #include "engine/execenv/execenv.hpp" #include "model/exceptions.hpp" #include "model/types.hpp" #include "utils/config/exceptions.hpp" #include "utils/config/nodes.ipp" #include "utils/config/tree.ipp" #include "utils/datetime.hpp" #include "utils/defs.hpp" #include "utils/format/macros.hpp" #include "utils/fs/exceptions.hpp" #include "utils/fs/path.hpp" #include "utils/noncopyable.hpp" #include "utils/optional.ipp" #include "utils/sanity.hpp" #include "utils/text/exceptions.hpp" #include "utils/text/operations.hpp" #include "utils/units.hpp" namespace config = utils::config; namespace datetime = utils::datetime; namespace fs = utils::fs; namespace text = utils::text; namespace units = utils::units; using utils::optional; namespace { /// Global instance of defaults. /// /// This exists so that the getters in metadata can return references instead /// of object copies. Use get_defaults() to query. static optional< config::tree > defaults; /// A leaf node that holds a bytes quantity. class bytes_node : public config::native_leaf_node< units::bytes > { public: /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { - std::auto_ptr< bytes_node > new_node(new bytes_node()); + std::unique_ptr< bytes_node > new_node(new bytes_node()); new_node->_value = _value; return new_node.release(); } /// Pushes the node's value onto the Lua stack. void push_lua(lutok::state& /* state */) const { UNREACHABLE; } /// Sets the value of the node from an entry in the Lua stack. void set_lua(lutok::state& /* state */, const int /* index */) { UNREACHABLE; } }; /// A leaf node that holds a time delta. class delta_node : public config::typed_leaf_node< datetime::delta > { public: /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { - std::auto_ptr< delta_node > new_node(new delta_node()); + std::unique_ptr< delta_node > new_node(new delta_node()); new_node->_value = _value; return new_node.release(); } /// Sets the value of the node from a raw string representation. /// /// \param raw_value The value to set the node to. /// /// \throw value_error If the value is invalid. void set_string(const std::string& raw_value) { unsigned int seconds; try { seconds = text::to_type< unsigned int >(raw_value); } catch (const text::error& e) { throw config::value_error(F("Invalid time delta %s") % raw_value); } set(datetime::delta(seconds, 0)); } /// Converts the contents of the node to a string. /// /// \pre The node must have a value. /// /// \return A string representation of the value held by the node. std::string to_string(void) const { return F("%s") % value().seconds; } /// Pushes the node's value onto the Lua stack. void push_lua(lutok::state& /* state */) const { UNREACHABLE; } /// Sets the value of the node from an entry in the Lua stack. void set_lua(lutok::state& /* state */, const int /* index */) { UNREACHABLE; } }; /// A leaf node that holds a "required user" property. /// /// This node is just a string, but it provides validation of the only allowed /// values. class user_node : public config::string_node { /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { - std::auto_ptr< user_node > new_node(new user_node()); + std::unique_ptr< user_node > new_node(new user_node()); new_node->_value = _value; return new_node.release(); } /// Checks a given user textual representation for validity. /// /// \param user The value to validate. /// /// \throw config::value_error If the value is not valid. void validate(const value_type& user) const { if (!user.empty() && user != "root" && user != "unprivileged") throw config::value_error("Invalid required user value"); } }; /// A leaf node that holds a set of paths. /// /// This node type is used to represent the value of the required files and /// required programs, for example, and these do not allow relative paths. We /// check this here. class paths_set_node : public config::base_set_node< fs::path > { /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { - std::auto_ptr< paths_set_node > new_node(new paths_set_node()); + std::unique_ptr< paths_set_node > new_node(new paths_set_node()); new_node->_value = _value; return new_node.release(); } /// Converts a single path to the native type. /// /// \param raw_value The value to parse. /// /// \return The parsed value. /// /// \throw config::value_error If the value is invalid. fs::path parse_one(const std::string& raw_value) const { try { return fs::path(raw_value); } catch (const fs::error& e) { throw config::value_error(e.what()); } } /// Checks a collection of paths for validity. /// /// \param paths The value to validate. /// /// \throw config::value_error If the value is not valid. void validate(const value_type& paths) const { for (value_type::const_iterator iter = paths.begin(); iter != paths.end(); ++iter) { const fs::path& path = *iter; if (!path.is_absolute() && path.ncomponents() > 1) throw config::value_error(F("Relative path '%s' not allowed") % *iter); } } }; /// Initializes a tree to hold test case requirements. /// /// \param [in,out] tree The tree to initialize. static void init_tree(config::tree& tree) { tree.define< config::strings_set_node >("allowed_architectures"); tree.define< config::strings_set_node >("allowed_platforms"); tree.define_dynamic("custom"); tree.define< config::string_node >("description"); tree.define< config::string_node >("execenv"); tree.define< config::string_node >("execenv_jail_params"); tree.define< config::bool_node >("has_cleanup"); tree.define< config::bool_node >("is_exclusive"); tree.define< config::strings_set_node >("required_configs"); tree.define< bytes_node >("required_disk_space"); tree.define< paths_set_node >("required_files"); tree.define< bytes_node >("required_memory"); tree.define< paths_set_node >("required_programs"); tree.define< user_node >("required_user"); tree.define< delta_node >("timeout"); } /// Sets default values on a tree object. /// /// \param [in,out] tree The tree to configure. static void set_defaults(config::tree& tree) { tree.set< config::strings_set_node >("allowed_architectures", model::strings_set()); tree.set< config::strings_set_node >("allowed_platforms", model::strings_set()); tree.set< config::string_node >("description", ""); tree.set< config::string_node >("execenv", ""); tree.set< config::string_node >("execenv_jail_params", ""); tree.set< config::bool_node >("has_cleanup", false); tree.set< config::bool_node >("is_exclusive", false); tree.set< config::strings_set_node >("required_configs", model::strings_set()); tree.set< bytes_node >("required_disk_space", units::bytes(0)); tree.set< paths_set_node >("required_files", model::paths_set()); tree.set< bytes_node >("required_memory", units::bytes(0)); tree.set< paths_set_node >("required_programs", model::paths_set()); tree.set< user_node >("required_user", ""); // TODO(jmmv): We shouldn't be setting a default timeout like this. See // Issue 5 for details. tree.set< delta_node >("timeout", datetime::delta(300, 0)); } /// Queries the global defaults tree object with lazy initialization. /// /// \return A metadata tree. This object is statically allocated so it is /// acceptable to obtain references to it and its members. const config::tree& get_defaults(void) { if (!defaults) { config::tree props; init_tree(props); set_defaults(props); defaults = props; } return defaults.get(); } /// Looks up a value in a tree with error rewriting. /// /// \tparam NodeType The type of the node. /// \param tree The tree in which to insert the value. /// \param key The key to set. /// /// \return A read-write reference to the value in the node. /// /// \throw model::error If the key is not known or if the value is not valid. template< class NodeType > typename NodeType::value_type& lookup_rw(config::tree& tree, const std::string& key) { try { return tree.lookup_rw< NodeType >(key); } catch (const config::unknown_key_error& e) { throw model::error(F("Unknown metadata property %s") % key); } catch (const config::value_error& e) { throw model::error(F("Invalid value for metadata property %s: %s") % key % e.what()); } } /// Sets a value in a tree with error rewriting. /// /// \tparam NodeType The type of the node. /// \param tree The tree in which to insert the value. /// \param key The key to set. /// \param value The value to set the node to. /// /// \throw model::error If the key is not known or if the value is not valid. template< class NodeType > void set(config::tree& tree, const std::string& key, const typename NodeType::value_type& value) { try { tree.set< NodeType >(key, value); } catch (const config::unknown_key_error& e) { throw model::error(F("Unknown metadata property %s") % key); } catch (const config::value_error& e) { throw model::error(F("Invalid value for metadata property %s: %s") % key % e.what()); } } } // anonymous namespace /// Internal implementation of the metadata class. struct model::metadata::impl : utils::noncopyable { /// Metadata properties. config::tree props; /// Constructor. /// /// \param props_ Metadata properties of the test. impl(const utils::config::tree& props_) : props(props_) { } /// Equality comparator. /// /// \param other The other object to compare this one to. /// /// \return True if this object and other are equal; false otherwise. bool operator==(const impl& other) const { return (get_defaults().combine(props) == get_defaults().combine(other.props)); } }; /// Constructor. /// /// \param props Metadata properties of the test. model::metadata::metadata(const utils::config::tree& props) : _pimpl(new impl(props)) { } /// Destructor. model::metadata::~metadata(void) { } /// Applies a set of overrides to this metadata object. /// /// \param overrides The overrides to apply. Any values explicitly set in this /// other object will override any possible values set in this object. /// /// \return A new metadata object with the combination. model::metadata model::metadata::apply_overrides(const metadata& overrides) const { return metadata(_pimpl->props.combine(overrides._pimpl->props)); } /// Returns the architectures allowed by the test. /// /// \return Set of architectures, or empty if this does not apply. const model::strings_set& model::metadata::allowed_architectures(void) const { if (_pimpl->props.is_set("allowed_architectures")) { return _pimpl->props.lookup< config::strings_set_node >( "allowed_architectures"); } else { return get_defaults().lookup< config::strings_set_node >( "allowed_architectures"); } } /// Returns the platforms allowed by the test. /// /// \return Set of platforms, or empty if this does not apply. const model::strings_set& model::metadata::allowed_platforms(void) const { if (_pimpl->props.is_set("allowed_platforms")) { return _pimpl->props.lookup< config::strings_set_node >( "allowed_platforms"); } else { return get_defaults().lookup< config::strings_set_node >( "allowed_platforms"); } } /// Returns all the user-defined metadata properties. /// /// \return A key/value map of properties. model::properties_map model::metadata::custom(void) const { return _pimpl->props.all_properties("custom", true); } /// Returns the description of the test. /// /// \return Textual description; may be empty. const std::string& model::metadata::description(void) const { if (_pimpl->props.is_set("description")) { return _pimpl->props.lookup< config::string_node >("description"); } else { return get_defaults().lookup< config::string_node >("description"); } } /// Returns execution environment name. /// /// \return Name of configured execution environment. const std::string& model::metadata::execenv(void) const { if (_pimpl->props.is_set("execenv")) { return _pimpl->props.lookup< config::string_node >("execenv"); } else { return get_defaults().lookup< config::string_node >("execenv"); } } /// Returns execenv jail(8) parameters string to run a test with. /// /// \return String of jail parameters. const std::string& model::metadata::execenv_jail_params(void) const { if (_pimpl->props.is_set("execenv_jail_params")) { return _pimpl->props.lookup< config::string_node >( "execenv_jail_params"); } else { return get_defaults().lookup< config::string_node >( "execenv_jail_params"); } } /// Returns whether the test has a cleanup part or not. /// /// \return True if there is a cleanup part; false otherwise. bool model::metadata::has_cleanup(void) const { if (_pimpl->props.is_set("has_cleanup")) { return _pimpl->props.lookup< config::bool_node >("has_cleanup"); } else { return get_defaults().lookup< config::bool_node >("has_cleanup"); } } /// Returns whether the test has a specific execenv apart from default one. /// /// \return True if there is a non-host execenv configured; false otherwise. bool model::metadata::has_execenv(void) const { const std::string& name = execenv(); return !name.empty() && name != engine::execenv::default_execenv_name; } /// Returns whether the test is exclusive or not. /// /// \return True if the test has to be run on its own, not concurrently with any /// other tests; false otherwise. bool model::metadata::is_exclusive(void) const { if (_pimpl->props.is_set("is_exclusive")) { return _pimpl->props.lookup< config::bool_node >("is_exclusive"); } else { return get_defaults().lookup< config::bool_node >("is_exclusive"); } } /// Returns the list of configuration variables needed by the test. /// /// \return Set of configuration variables. const model::strings_set& model::metadata::required_configs(void) const { if (_pimpl->props.is_set("required_configs")) { return _pimpl->props.lookup< config::strings_set_node >( "required_configs"); } else { return get_defaults().lookup< config::strings_set_node >( "required_configs"); } } /// Returns the amount of free disk space required by the test. /// /// \return Number of bytes, or 0 if this does not apply. const units::bytes& model::metadata::required_disk_space(void) const { if (_pimpl->props.is_set("required_disk_space")) { return _pimpl->props.lookup< bytes_node >("required_disk_space"); } else { return get_defaults().lookup< bytes_node >("required_disk_space"); } } /// Returns the list of files needed by the test. /// /// \return Set of paths. const model::paths_set& model::metadata::required_files(void) const { if (_pimpl->props.is_set("required_files")) { return _pimpl->props.lookup< paths_set_node >("required_files"); } else { return get_defaults().lookup< paths_set_node >("required_files"); } } /// Returns the amount of memory required by the test. /// /// \return Number of bytes, or 0 if this does not apply. const units::bytes& model::metadata::required_memory(void) const { if (_pimpl->props.is_set("required_memory")) { return _pimpl->props.lookup< bytes_node >("required_memory"); } else { return get_defaults().lookup< bytes_node >("required_memory"); } } /// Returns the list of programs needed by the test. /// /// \return Set of paths. const model::paths_set& model::metadata::required_programs(void) const { if (_pimpl->props.is_set("required_programs")) { return _pimpl->props.lookup< paths_set_node >("required_programs"); } else { return get_defaults().lookup< paths_set_node >("required_programs"); } } /// Returns the user required by the test. /// /// \return One of unprivileged, root or empty. const std::string& model::metadata::required_user(void) const { if (_pimpl->props.is_set("required_user")) { return _pimpl->props.lookup< user_node >("required_user"); } else { return get_defaults().lookup< user_node >("required_user"); } } /// Returns the timeout of the test. /// /// \return A time delta; should be compared to default_timeout to see if it has /// been overriden. const datetime::delta& model::metadata::timeout(void) const { if (_pimpl->props.is_set("timeout")) { return _pimpl->props.lookup< delta_node >("timeout"); } else { return get_defaults().lookup< delta_node >("timeout"); } } /// Externalizes the metadata to a set of key/value textual pairs. /// /// \return A key/value representation of the metadata. model::properties_map model::metadata::to_properties(void) const { const config::tree fully_specified = get_defaults().combine(_pimpl->props); return fully_specified.all_properties(); } /// Equality comparator. /// /// \param other The other object to compare this one to. /// /// \return True if this object and other are equal; false otherwise. bool model::metadata::operator==(const metadata& other) const { return _pimpl == other._pimpl || *_pimpl == *other._pimpl; } /// Inequality comparator. /// /// \param other The other object to compare this one to. /// /// \return True if this object and other are different; false otherwise. bool model::metadata::operator!=(const metadata& other) const { return !(*this == other); } /// Injects the object into a stream. /// /// \param output The stream into which to inject the object. /// \param object The object to format. /// /// \return The output stream. std::ostream& model::operator<<(std::ostream& output, const metadata& object) { output << "metadata{"; bool first = true; const model::properties_map props = object.to_properties(); for (model::properties_map::const_iterator iter = props.begin(); iter != props.end(); ++iter) { if (!first) output << ", "; output << F("%s=%s") % (*iter).first % text::quote((*iter).second, '\''); first = false; } output << "}"; return output; } /// Internal implementation of the metadata_builder class. struct model::metadata_builder::impl : utils::noncopyable { /// Collection of requirements. config::tree props; /// Whether we have created a metadata object or not. bool built; /// Constructor. impl(void) : built(false) { init_tree(props); } /// Constructor. /// /// \param base The base model to construct a copy from. impl(const model::metadata& base) : props(base._pimpl->props.deep_copy()), built(false) { } }; /// Constructor. model::metadata_builder::metadata_builder(void) : _pimpl(new impl()) { } /// Constructor. model::metadata_builder::metadata_builder(const model::metadata& base) : _pimpl(new impl(base)) { } /// Destructor. model::metadata_builder::~metadata_builder(void) { } /// Accumulates an additional allowed architecture. /// /// \param arch The architecture. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_allowed_architecture(const std::string& arch) { if (!_pimpl->props.is_set("allowed_architectures")) { _pimpl->props.set< config::strings_set_node >( "allowed_architectures", get_defaults().lookup< config::strings_set_node >( "allowed_architectures")); } lookup_rw< config::strings_set_node >( _pimpl->props, "allowed_architectures").insert(arch); return *this; } /// Accumulates an additional allowed platform. /// /// \param platform The platform. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_allowed_platform(const std::string& platform) { if (!_pimpl->props.is_set("allowed_platforms")) { _pimpl->props.set< config::strings_set_node >( "allowed_platforms", get_defaults().lookup< config::strings_set_node >( "allowed_platforms")); } lookup_rw< config::strings_set_node >( _pimpl->props, "allowed_platforms").insert(platform); return *this; } /// Accumulates a single user-defined property. /// /// \param key Name of the property to define. /// \param value Value of the property. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_custom(const std::string& key, const std::string& value) { _pimpl->props.set_string(F("custom.%s") % key, value); return *this; } /// Accumulates an additional required configuration variable. /// /// \param var The name of the configuration variable. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_required_config(const std::string& var) { if (!_pimpl->props.is_set("required_configs")) { _pimpl->props.set< config::strings_set_node >( "required_configs", get_defaults().lookup< config::strings_set_node >( "required_configs")); } lookup_rw< config::strings_set_node >( _pimpl->props, "required_configs").insert(var); return *this; } /// Accumulates an additional required file. /// /// \param path The path to the file. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_required_file(const fs::path& path) { if (!_pimpl->props.is_set("required_files")) { _pimpl->props.set< paths_set_node >( "required_files", get_defaults().lookup< paths_set_node >("required_files")); } lookup_rw< paths_set_node >(_pimpl->props, "required_files").insert(path); return *this; } /// Accumulates an additional required program. /// /// \param path The path to the program. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::add_required_program(const fs::path& path) { if (!_pimpl->props.is_set("required_programs")) { _pimpl->props.set< paths_set_node >( "required_programs", get_defaults().lookup< paths_set_node >("required_programs")); } lookup_rw< paths_set_node >(_pimpl->props, "required_programs").insert(path); return *this; } /// Sets the architectures allowed by the test. /// /// \param as Set of architectures. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_allowed_architectures( const model::strings_set& as) { set< config::strings_set_node >(_pimpl->props, "allowed_architectures", as); return *this; } /// Sets the platforms allowed by the test. /// /// \return ps Set of platforms. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_allowed_platforms(const model::strings_set& ps) { set< config::strings_set_node >(_pimpl->props, "allowed_platforms", ps); return *this; } /// Sets the user-defined properties. /// /// \param props The custom properties to set. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_custom(const model::properties_map& props) { for (model::properties_map::const_iterator iter = props.begin(); iter != props.end(); ++iter) _pimpl->props.set_string(F("custom.%s") % (*iter).first, (*iter).second); return *this; } /// Sets the description of the test. /// /// \param description Textual description of the test. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_description(const std::string& description) { set< config::string_node >(_pimpl->props, "description", description); return *this; } /// Sets execution environment name. /// /// \param name Execution environment name. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_execenv(const std::string& name) { set< config::string_node >(_pimpl->props, "execenv", name); return *this; } /// Sets execenv jail(8) parameters string to run the test with. /// /// \param params String of jail parameters. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_execenv_jail_params(const std::string& params) { set< config::string_node >(_pimpl->props, "execenv_jail_params", params); return *this; } /// Sets whether the test has a cleanup part or not. /// /// \param cleanup True if the test has a cleanup part; false otherwise. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_has_cleanup(const bool cleanup) { set< config::bool_node >(_pimpl->props, "has_cleanup", cleanup); return *this; } /// Sets whether the test is exclusive or not. /// /// \param exclusive True if the test is exclusive; false otherwise. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_is_exclusive(const bool exclusive) { set< config::bool_node >(_pimpl->props, "is_exclusive", exclusive); return *this; } /// Sets the list of configuration variables needed by the test. /// /// \param vars Set of configuration variables. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_configs(const model::strings_set& vars) { set< config::strings_set_node >(_pimpl->props, "required_configs", vars); return *this; } /// Sets the amount of free disk space required by the test. /// /// \param bytes Number of bytes. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_disk_space(const units::bytes& bytes) { set< bytes_node >(_pimpl->props, "required_disk_space", bytes); return *this; } /// Sets the list of files needed by the test. /// /// \param files Set of paths. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_files(const model::paths_set& files) { set< paths_set_node >(_pimpl->props, "required_files", files); return *this; } /// Sets the amount of memory required by the test. /// /// \param bytes Number of bytes. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_memory(const units::bytes& bytes) { set< bytes_node >(_pimpl->props, "required_memory", bytes); return *this; } /// Sets the list of programs needed by the test. /// /// \param progs Set of paths. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_programs(const model::paths_set& progs) { set< paths_set_node >(_pimpl->props, "required_programs", progs); return *this; } /// Sets the user required by the test. /// /// \param user One of unprivileged, root or empty. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_required_user(const std::string& user) { set< user_node >(_pimpl->props, "required_user", user); return *this; } /// Sets a metadata property by name from its textual representation. /// /// \param key The property to set. /// \param value The value to set the property to. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid or the key does not exist. model::metadata_builder& model::metadata_builder::set_string(const std::string& key, const std::string& value) { try { _pimpl->props.set_string(key, value); } catch (const config::unknown_key_error& e) { throw model::format_error(F("Unknown metadata property %s") % key); } catch (const config::value_error& e) { throw model::format_error( F("Invalid value for metadata property %s: %s") % key % e.what()); } return *this; } /// Sets the timeout of the test. /// /// \param timeout The timeout to set. /// /// \return A reference to this builder. /// /// \throw model::error If the value is invalid. model::metadata_builder& model::metadata_builder::set_timeout(const datetime::delta& timeout) { set< delta_node >(_pimpl->props, "timeout", timeout); return *this; } /// Creates a new metadata object. /// /// \pre This has not yet been called. We only support calling this function /// once due to the way the internal tree works: we pass around references, not /// deep copies, so if we allowed a second build, we'd encourage reusing the /// same builder to construct different metadata objects, and this could have /// unintended consequences. /// /// \return The constructed metadata object. model::metadata model::metadata_builder::build(void) const { PRE(!_pimpl->built); _pimpl->built = true; return metadata(_pimpl->props); } diff --git a/contrib/kyua/model/metadata.hpp b/contrib/kyua/model/metadata.hpp index 83bc5348774a..263ecc86106d 100644 --- a/contrib/kyua/model/metadata.hpp +++ b/contrib/kyua/model/metadata.hpp @@ -1,135 +1,135 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file model/metadata.hpp /// Definition of the "test metadata" concept. #if !defined(MODEL_METADATA_HPP) #define MODEL_METADATA_HPP #include "model/metadata_fwd.hpp" #include #include #include #include "model/types.hpp" #include "utils/config/tree_fwd.hpp" #include "utils/datetime_fwd.hpp" #include "utils/fs/path_fwd.hpp" #include "utils/noncopyable.hpp" #include "utils/units_fwd.hpp" namespace model { /// Collection of metadata properties of a test. class metadata { struct impl; /// Pointer to the shared internal implementation. std::shared_ptr< impl > _pimpl; friend class metadata_builder; public: metadata(const utils::config::tree&); ~metadata(void); metadata apply_overrides(const metadata&) const; const strings_set& allowed_architectures(void) const; const strings_set& allowed_platforms(void) const; model::properties_map custom(void) const; const std::string& description(void) const; const std::string& execenv(void) const; const std::string& execenv_jail_params(void) const; bool has_cleanup(void) const; bool has_execenv(void) const; bool is_exclusive(void) const; const strings_set& required_configs(void) const; const utils::units::bytes& required_disk_space(void) const; const paths_set& required_files(void) const; const utils::units::bytes& required_memory(void) const; const paths_set& required_programs(void) const; const std::string& required_user(void) const; const utils::datetime::delta& timeout(void) const; model::properties_map to_properties(void) const; bool operator==(const metadata&) const; bool operator!=(const metadata&) const; }; std::ostream& operator<<(std::ostream&, const metadata&); /// Builder for a metadata object. class metadata_builder : utils::noncopyable { struct impl; /// Pointer to the shared internal implementation. - std::auto_ptr< impl > _pimpl; + std::unique_ptr< impl > _pimpl; public: metadata_builder(void); explicit metadata_builder(const metadata&); ~metadata_builder(void); metadata_builder& add_allowed_architecture(const std::string&); metadata_builder& add_allowed_platform(const std::string&); metadata_builder& add_custom(const std::string&, const std::string&); metadata_builder& add_required_config(const std::string&); metadata_builder& add_required_file(const utils::fs::path&); metadata_builder& add_required_program(const utils::fs::path&); metadata_builder& set_allowed_architectures(const strings_set&); metadata_builder& set_allowed_platforms(const strings_set&); metadata_builder& set_custom(const model::properties_map&); metadata_builder& set_description(const std::string&); metadata_builder& set_execenv(const std::string&); metadata_builder& set_execenv_jail_params(const std::string&); metadata_builder& set_has_cleanup(const bool); metadata_builder& set_is_exclusive(const bool); metadata_builder& set_required_configs(const strings_set&); metadata_builder& set_required_disk_space(const utils::units::bytes&); metadata_builder& set_required_files(const paths_set&); metadata_builder& set_required_memory(const utils::units::bytes&); metadata_builder& set_required_programs(const paths_set&); metadata_builder& set_required_user(const std::string&); metadata_builder& set_string(const std::string&, const std::string&); metadata_builder& set_timeout(const utils::datetime::delta&); metadata build(void) const; }; } // namespace model #endif // !defined(MODEL_METADATA_HPP) diff --git a/contrib/kyua/model/test_program.hpp b/contrib/kyua/model/test_program.hpp index 974ec2a12d19..ab00dbeb44e4 100644 --- a/contrib/kyua/model/test_program.hpp +++ b/contrib/kyua/model/test_program.hpp @@ -1,110 +1,110 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file model/test_program.hpp /// Definition of the "test program" concept. #if !defined(MODEL_TEST_PROGRAM_HPP) #define MODEL_TEST_PROGRAM_HPP #include "model/test_program_fwd.hpp" #include #include #include #include #include "model/metadata_fwd.hpp" #include "model/test_case_fwd.hpp" #include "utils/fs/path_fwd.hpp" #include "utils/noncopyable.hpp" namespace model { /// Representation of a test program. class test_program { struct impl; /// Pointer to the shared internal implementation. std::shared_ptr< impl > _pimpl; protected: void set_test_cases(const model::test_cases_map&); public: test_program(const std::string&, const utils::fs::path&, const utils::fs::path&, const std::string&, const model::metadata&, const model::test_cases_map&); virtual ~test_program(void); const std::string& interface_name(void) const; const utils::fs::path& root(void) const; const utils::fs::path& relative_path(void) const; const utils::fs::path absolute_path(void) const; const std::string& test_suite_name(void) const; const model::metadata& get_metadata(void) const; const model::test_case& find(const std::string&) const; virtual const model::test_cases_map& test_cases(void) const; bool operator==(const test_program&) const; bool operator!=(const test_program&) const; bool operator<(const test_program&) const; }; std::ostream& operator<<(std::ostream&, const test_program&); /// Builder for a test_program object. class test_program_builder : utils::noncopyable { struct impl; /// Pointer to the shared internal implementation. - std::auto_ptr< impl > _pimpl; + std::unique_ptr< impl > _pimpl; public: test_program_builder(const std::string&, const utils::fs::path&, const utils::fs::path&, const std::string&); ~test_program_builder(void); test_program_builder& add_test_case(const std::string&); test_program_builder& add_test_case(const std::string&, const model::metadata&); test_program_builder& set_metadata(const model::metadata&); test_program build(void) const; test_program_ptr build_ptr(void) const; }; } // namespace model #endif // !defined(MODEL_TEST_PROGRAM_HPP) diff --git a/contrib/kyua/os/freebsd/utils/jail.cpp b/contrib/kyua/os/freebsd/utils/jail.cpp index b39761f28e51..d2f320d1f460 100644 --- a/contrib/kyua/os/freebsd/utils/jail.cpp +++ b/contrib/kyua/os/freebsd/utils/jail.cpp @@ -1,306 +1,306 @@ // Copyright 2024 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "os/freebsd/utils/jail.hpp" extern "C" { #include #include #include // FreeBSD sysctl facility #include // FreeBSD Jail syscalls #include #include // FreeBSD Jail library #include } #include #include #include #include "model/metadata.hpp" #include "model/test_case.hpp" #include "model/test_program.hpp" #include "utils/fs/path.hpp" #include "utils/process/child.ipp" #include "utils/format/macros.hpp" #include "utils/process/operations.hpp" #include "utils/process/status.hpp" namespace process = utils::process; namespace fs = utils::fs; using utils::process::args_vector; using utils::process::child; static const size_t jail_name_max_len = MAXHOSTNAMELEN - 1; static const char* jail_name_prefix = "kyua"; /// Functor to run a program. class run { /// Program binary absolute path. const utils::fs::path& _program; /// Program arguments. const args_vector& _args; public: /// Constructor. /// /// \param program Program binary absolute path. /// \param args Program arguments. run( const utils::fs::path& program, const args_vector& args) : _program(program), _args(args) { } /// Body of the subprocess. void operator()(void) { process::exec(_program, _args); } }; namespace freebsd { namespace utils { std::vector< std::string > jail::parse_params_string(const std::string& str) { std::vector< std::string > params; std::string p; char quote = 0; std::istringstream iss(str); while (iss >> p) { if (p.front() == '"' || p.front() == '\'') { quote = p.front(); p.erase(p.begin()); if (p.find(quote) == std::string::npos) { std::string rest; std::getline(iss, rest, quote); p += rest; iss.ignore(); } if (p.back() == quote) p.erase(p.end() - 1); } params.push_back(p); } return params; } /// Constructs a jail name based on program and test case. /// /// The formula is "kyua" + + "_" + . /// All non-alphanumeric chars are replaced with "_". /// /// If a resulting string exceeds maximum allowed length of a jail name, /// then it's shortened from the left side keeping the "kyua" prefix. /// /// \param program The test program. /// \param test_case_name Name of the test case. /// /// \return A jail name string. std::string jail::make_name(const fs::path& program, const std::string& test_case_name) { std::string name = std::regex_replace( program.str() + "_" + test_case_name, std::regex(R"([^A-Za-z0-9_])"), "_"); const std::string::size_type limit = jail_name_max_len - strlen(jail_name_prefix); if (name.length() > limit) name.erase(0, name.length() - limit); return jail_name_prefix + name; } /// Create a jail with a given name and params string. /// /// A new jail will always be 'persist', thus the caller is expected to remove /// the jail eventually via remove(). /// /// It's expected to be run in a subprocess. /// /// \param jail_name Name of a new jail. /// \param jail_params String of jail parameters. void jail::create(const std::string& jail_name, const std::string& jail_params) { args_vector av; // creation flag av.push_back("-qc"); // jail name av.push_back("name=" + jail_name); // determine maximum allowed children.max const char* const oid = "security.jail.children.max"; int max; size_t len = sizeof(max); if (::sysctlbyname(oid, &max, &len, NULL, 0) != 0) { std::cerr << "sysctlbyname(" << oid << ") errors: " << strerror(errno) << ".\n"; std::exit(EXIT_FAILURE); } if (len < sizeof(max)) { std::cerr << "sysctlbyname(" << oid << ") provides less " "data (" << len << ") than expected (" << sizeof(max) << ").\n"; std::exit(EXIT_FAILURE); } if (max < 0) { std::cerr << "sysctlbyname(" << oid << ") yields " "abnormal " << max << ".\n"; std::exit(EXIT_FAILURE); } if (max > 0) max--; // a child jail must have less than parent's children.max av.push_back("children.max=" + std::to_string(max)); // test defined jail params const std::vector< std::string > params = parse_params_string(jail_params); for (const std::string& p : params) av.push_back(p); // it must be persist av.push_back("persist"); // invoke jail - std::auto_ptr< process::child > child = child::fork_capture( + std::unique_ptr< process::child > child = child::fork_capture( run(fs::path("/usr/sbin/jail"), av)); process::status status = child->wait(); // expect success if (status.exited() && status.exitstatus() == EXIT_SUCCESS) return; // otherwise, let us know what jail thinks and fail fast std::cerr << child->output().rdbuf(); std::exit(EXIT_FAILURE); } /// Executes an external binary in a jail and replaces the current process. /// /// \param jail_name Name of the jail to run within. /// \param program The test program binary absolute path. /// \param args The arguments to pass to the binary, without the program name. void jail::exec(const std::string& jail_name, const fs::path& program, const args_vector& args) throw() { // get work dir prepared by kyua char cwd[PATH_MAX]; if (::getcwd(cwd, sizeof(cwd)) == NULL) { std::cerr << "jail::exec: getcwd() errors: " << strerror(errno) << ".\n"; std::exit(EXIT_FAILURE); } // get jail id by its name int jid = ::jail_getid(jail_name.c_str()); if (jid == -1) { std::cerr << "jail::exec: jail_getid() errors: " << strerror(errno) << ": " << jail_errmsg << ".\n"; std::exit(EXIT_FAILURE); } // attach to the jail if (::jail_attach(jid) == -1) { std::cerr << "jail::exec: jail_attach() errors: " << strerror(errno) << ".\n"; std::exit(EXIT_FAILURE); } // set back the expected work dir if (::chdir(cwd) == -1) { std::cerr << "jail::exec: chdir() errors: " << strerror(errno) << ".\n"; std::exit(EXIT_FAILURE); } process::exec(program, args); } /// Removes a jail with a given name. /// /// It's expected to be run in a subprocess. /// /// \param jail_name Name of a jail to remove. void jail::remove(const std::string& jail_name) { args_vector av; // removal flag av.push_back("-r"); // jail name av.push_back(jail_name); // invoke jail - std::auto_ptr< process::child > child = child::fork_capture( + std::unique_ptr< process::child > child = child::fork_capture( run(fs::path("/usr/sbin/jail"), av)); process::status status = child->wait(); // expect success if (status.exited() && status.exitstatus() == EXIT_SUCCESS) std::exit(EXIT_SUCCESS); // otherwise, let us know what jail thinks and fail fast std::cerr << child->output().rdbuf(); std::exit(EXIT_FAILURE); } } // namespace utils } // namespace freebsd diff --git a/contrib/kyua/utils/auto_array.hpp b/contrib/kyua/utils/auto_array.hpp index 0cc3d0e0afd5..93b3e20223cf 100644 --- a/contrib/kyua/utils/auto_array.hpp +++ b/contrib/kyua/utils/auto_array.hpp @@ -1,102 +1,102 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/auto_array.hpp /// Provides the utils::auto_array class. /// /// The class is provided as a separate module on its own to minimize /// header-inclusion side-effects. #if !defined(UTILS_AUTO_ARRAY_HPP) #define UTILS_AUTO_ARRAY_HPP #include "utils/auto_array_fwd.hpp" #include namespace utils { namespace detail { /// Wrapper class to provide reference semantics for utils::auto_array. /// /// This class is internally used, for example, to allow returning a /// utils::auto_array from a function. template< class T > class auto_array_ref { /// Internal pointer to the dynamically-allocated array. T* _ptr; template< class > friend class utils::auto_array; public: explicit auto_array_ref(T*); }; } // namespace detail /// A simple smart pointer for arrays providing strict ownership semantics. /// -/// This class is the counterpart of std::auto_ptr for arrays. The semantics of -/// the API of this class are the same as those of std::auto_ptr. +/// This class is the counterpart of std::unique_ptr for arrays. The semantics of +/// the API of this class are the same as those of std::unique_ptr. /// /// The wrapped pointer must be NULL or must have been allocated using operator /// new[]. template< class T > class auto_array { /// Internal pointer to the dynamically-allocated array. T* _ptr; public: auto_array(T* = NULL) throw(); auto_array(auto_array< T >&) throw(); auto_array(detail::auto_array_ref< T >) throw(); ~auto_array(void) throw(); T* get(void) throw(); const T* get(void) const throw(); T* release(void) throw(); void reset(T* = NULL) throw(); auto_array< T >& operator=(auto_array< T >&) throw(); auto_array< T >& operator=(detail::auto_array_ref< T >) throw(); T& operator[](int) throw(); const T& operator[](int) const throw(); operator detail::auto_array_ref< T >(void) throw(); }; } // namespace utils #endif // !defined(UTILS_AUTO_ARRAY_HPP) diff --git a/contrib/kyua/utils/cmdline/commands_map.hpp b/contrib/kyua/utils/cmdline/commands_map.hpp index 5378a6f2c471..d5933cc31e33 100644 --- a/contrib/kyua/utils/cmdline/commands_map.hpp +++ b/contrib/kyua/utils/cmdline/commands_map.hpp @@ -1,96 +1,96 @@ // Copyright 2011 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/cmdline/commands_map.hpp /// Maintains a collection of dynamically-instantiated commands. /// /// Commands need to be dynamically-instantiated because they are often /// complex data structures. Instantiating them as static variables causes /// problems with the order of construction of globals. The commands_map class /// provided by this module provides a mechanism to maintain these instantiated /// objects. #if !defined(UTILS_CMDLINE_COMMANDS_MAP_HPP) #define UTILS_CMDLINE_COMMANDS_MAP_HPP #include "utils/cmdline/commands_map_fwd.hpp" #include #include #include #include #include "utils/noncopyable.hpp" namespace utils { namespace cmdline { /// Collection of dynamically-instantiated commands. template< typename BaseCommand > class commands_map : noncopyable { /// Map of command names to their implementations. typedef std::map< std::string, BaseCommand* > impl_map; /// Map of category names to the command names they contain. typedef std::map< std::string, std::set< std::string > > categories_map; /// Collection of all available commands. impl_map _commands; /// Collection of defined categories and their commands. categories_map _categories; public: commands_map(void); ~commands_map(void); /// Scoped, strictly-owned pointer to a command from this map. - typedef typename std::auto_ptr< BaseCommand > command_ptr; + typedef typename std::unique_ptr< BaseCommand > command_ptr; void insert(command_ptr, const std::string& = ""); void insert(BaseCommand*, const std::string& = ""); /// Type for a constant iterator. typedef typename categories_map::const_iterator const_iterator; bool empty(void) const; const_iterator begin(void) const; const_iterator end(void) const; BaseCommand* find(const std::string&); const BaseCommand* find(const std::string&) const; }; } // namespace cmdline } // namespace utils #endif // !defined(UTILS_CMDLINE_BASE_COMMAND_HPP) diff --git a/contrib/kyua/utils/config/lua_module_test.cpp b/contrib/kyua/utils/config/lua_module_test.cpp index 484d129c4021..d5d0bfc1a1f2 100644 --- a/contrib/kyua/utils/config/lua_module_test.cpp +++ b/contrib/kyua/utils/config/lua_module_test.cpp @@ -1,474 +1,474 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/config/lua_module.hpp" #include #include #include #include #include "utils/config/tree.ipp" #include "utils/defs.hpp" namespace config = utils::config; namespace { /// Non-native type to use as a leaf node. struct custom_type { /// The value recorded in the object. int value; /// Constructs a new object. /// /// \param value_ The value to store in the object. explicit custom_type(const int value_) : value(value_) { } }; /// Custom implementation of a node type for testing purposes. class custom_node : public config::typed_leaf_node< custom_type > { public: /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { - std::auto_ptr< custom_node > new_node(new custom_node()); + std::unique_ptr< custom_node > new_node(new custom_node()); new_node->_value = _value; return new_node.release(); } /// Pushes the node's value onto the Lua stack. /// /// \param state The Lua state onto which to push the value. void push_lua(lutok::state& state) const { state.push_integer(value().value * 5); } /// Sets the value of the node from an entry in the Lua stack. /// /// \param state The Lua state from which to get the value. /// \param value_index The stack index in which the value resides. void set_lua(lutok::state& state, const int value_index) { ATF_REQUIRE(state.is_number(value_index)); set(custom_type(state.to_integer(value_index) * 2)); } /// Sets the value of the node from a raw string representation. /// /// \post The test case is marked as failed, as this function is not /// supposed to be invoked by the lua_module code. void set_string(const std::string& /* raw_value */) { ATF_FAIL("Should not be used"); } /// Converts the contents of the node to a string. /// /// \post The test case is marked as failed, as this function is not /// supposed to be invoked by the lua_module code. /// /// \return Nothing. std::string to_string(void) const { ATF_FAIL("Should not be used"); } }; } // anonymous namespace ATF_TEST_CASE_WITHOUT_HEAD(top__valid_types); ATF_TEST_CASE_BODY(top__valid_types) { config::tree tree; tree.define< config::bool_node >("top_boolean"); tree.define< config::int_node >("top_integer"); tree.define< config::string_node >("top_string"); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "top_boolean = true\n" "top_integer = 12345\n" "top_string = 'a foo'\n", 0, 0, 0); } ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("top_boolean")); ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("top_integer")); ATF_REQUIRE_EQ("a foo", tree.lookup< config::string_node >("top_string")); } ATF_TEST_CASE_WITHOUT_HEAD(top__invalid_types); ATF_TEST_CASE_BODY(top__invalid_types) { config::tree tree; tree.define< config::bool_node >("top_boolean"); tree.define< config::int_node >("top_integer"); { lutok::state state; config::redirect(state, tree); ATF_REQUIRE_THROW_RE( lutok::error, "Invalid value for property 'top_boolean': Not a boolean", lutok::do_string(state, "top_boolean = true\n" "top_integer = 8\n" "top_boolean = 'foo'\n", 0, 0, 0)); } ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("top_boolean")); ATF_REQUIRE_EQ(8, tree.lookup< config::int_node >("top_integer")); } ATF_TEST_CASE_WITHOUT_HEAD(top__reuse); ATF_TEST_CASE_BODY(top__reuse) { config::tree tree; tree.define< config::int_node >("first"); tree.define< config::int_node >("second"); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "first = 100; second = first * 2", 0, 0, 0); } ATF_REQUIRE_EQ(100, tree.lookup< config::int_node >("first")); ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("second")); } ATF_TEST_CASE_WITHOUT_HEAD(top__reset); ATF_TEST_CASE_BODY(top__reset) { config::tree tree; tree.define< config::int_node >("first"); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "first = 100; first = 200", 0, 0, 0); } ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("first")); } ATF_TEST_CASE_WITHOUT_HEAD(top__already_set_on_entry); ATF_TEST_CASE_BODY(top__already_set_on_entry) { config::tree tree; tree.define< config::int_node >("first"); tree.set< config::int_node >("first", 100); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "first = first * 15", 0, 0, 0); } ATF_REQUIRE_EQ(1500, tree.lookup< config::int_node >("first")); } ATF_TEST_CASE_WITHOUT_HEAD(subtree__valid_types); ATF_TEST_CASE_BODY(subtree__valid_types) { config::tree tree; tree.define< config::bool_node >("root.boolean"); tree.define< config::int_node >("root.a.integer"); tree.define< config::string_node >("root.string"); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "root.boolean = true\n" "root.a.integer = 12345\n" "root.string = 'a foo'\n", 0, 0, 0); } ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("root.boolean")); ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("root.a.integer")); ATF_REQUIRE_EQ("a foo", tree.lookup< config::string_node >("root.string")); } ATF_TEST_CASE_WITHOUT_HEAD(subtree__reuse); ATF_TEST_CASE_BODY(subtree__reuse) { config::tree tree; tree.define< config::int_node >("a.first"); tree.define< config::int_node >("a.second"); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "a.first = 100; a.second = a.first * 2", 0, 0, 0); } ATF_REQUIRE_EQ(100, tree.lookup< config::int_node >("a.first")); ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("a.second")); } ATF_TEST_CASE_WITHOUT_HEAD(subtree__reset); ATF_TEST_CASE_BODY(subtree__reset) { config::tree tree; tree.define< config::int_node >("a.first"); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "a.first = 100; a.first = 200", 0, 0, 0); } ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("a.first")); } ATF_TEST_CASE_WITHOUT_HEAD(subtree__already_set_on_entry); ATF_TEST_CASE_BODY(subtree__already_set_on_entry) { config::tree tree; tree.define< config::int_node >("a.first"); tree.set< config::int_node >("a.first", 100); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "a.first = a.first * 15", 0, 0, 0); } ATF_REQUIRE_EQ(1500, tree.lookup< config::int_node >("a.first")); } ATF_TEST_CASE_WITHOUT_HEAD(subtree__override_inner); ATF_TEST_CASE_BODY(subtree__override_inner) { config::tree tree; tree.define_dynamic("root"); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "root.test = 'a'", 0, 0, 0); ATF_REQUIRE_THROW_RE(lutok::error, "Invalid value for property 'root'", lutok::do_string(state, "root = 'b'", 0, 0, 0)); // Ensure that the previous assignment to 'root' did not cause any // inconsistencies in the environment that would prevent a new // assignment from working. lutok::do_string(state, "root.test2 = 'c'", 0, 0, 0); } ATF_REQUIRE_EQ("a", tree.lookup< config::string_node >("root.test")); ATF_REQUIRE_EQ("c", tree.lookup< config::string_node >("root.test2")); } ATF_TEST_CASE_WITHOUT_HEAD(dynamic_subtree__strings); ATF_TEST_CASE_BODY(dynamic_subtree__strings) { config::tree tree; tree.define_dynamic("root"); lutok::state state; config::redirect(state, tree); lutok::do_string(state, "root.key1 = 1234\n" "root.a.b.key2 = 'foo bar'\n", 0, 0, 0); ATF_REQUIRE_EQ("1234", tree.lookup< config::string_node >("root.key1")); ATF_REQUIRE_EQ("foo bar", tree.lookup< config::string_node >("root.a.b.key2")); } ATF_TEST_CASE_WITHOUT_HEAD(dynamic_subtree__invalid_types); ATF_TEST_CASE_BODY(dynamic_subtree__invalid_types) { config::tree tree; tree.define_dynamic("root"); lutok::state state; config::redirect(state, tree); ATF_REQUIRE_THROW_RE(lutok::error, "Invalid value for property 'root.boolean': " "Not a string", lutok::do_string(state, "root.boolean = true", 0, 0, 0)); ATF_REQUIRE_THROW_RE(lutok::error, "Invalid value for property 'root.table': " "Not a string", lutok::do_string(state, "root.table = {}", 0, 0, 0)); ATF_REQUIRE(!tree.is_set("root.boolean")); ATF_REQUIRE(!tree.is_set("root.table")); } ATF_TEST_CASE_WITHOUT_HEAD(locals); ATF_TEST_CASE_BODY(locals) { config::tree tree; tree.define< config::int_node >("the_key"); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "local function generate()\n" " return 15\n" "end\n" "local test_var = 20\n" "the_key = generate() + test_var\n", 0, 0, 0); } ATF_REQUIRE_EQ(35, tree.lookup< config::int_node >("the_key")); } ATF_TEST_CASE_WITHOUT_HEAD(custom_node); ATF_TEST_CASE_BODY(custom_node) { config::tree tree; tree.define< custom_node >("key1"); tree.define< custom_node >("key2"); tree.set< custom_node >("key2", custom_type(10)); { lutok::state state; config::redirect(state, tree); lutok::do_string(state, "key1 = 512\n", 0, 0, 0); lutok::do_string(state, "key2 = key2 * 2\n", 0, 0, 0); } ATF_REQUIRE_EQ(1024, tree.lookup< custom_node >("key1").value); ATF_REQUIRE_EQ(200, tree.lookup< custom_node >("key2").value); } ATF_TEST_CASE_WITHOUT_HEAD(invalid_key); ATF_TEST_CASE_BODY(invalid_key) { config::tree tree; lutok::state state; config::redirect(state, tree); ATF_REQUIRE_THROW_RE(lutok::error, "Empty component in key 'root.'", lutok::do_string(state, "root['']['a'] = 12345\n", 0, 0, 0)); } ATF_TEST_CASE_WITHOUT_HEAD(unknown_key); ATF_TEST_CASE_BODY(unknown_key) { config::tree tree; tree.define< config::bool_node >("static.bool"); lutok::state state; config::redirect(state, tree); ATF_REQUIRE_THROW_RE(lutok::error, "Unknown configuration property 'static.int'", lutok::do_string(state, "static.int = 12345\n", 0, 0, 0)); } ATF_TEST_CASE_WITHOUT_HEAD(value_error); ATF_TEST_CASE_BODY(value_error) { config::tree tree; tree.define< config::bool_node >("a.b"); lutok::state state; config::redirect(state, tree); ATF_REQUIRE_THROW_RE(lutok::error, "Invalid value for property 'a.b': Not a boolean", lutok::do_string(state, "a.b = 12345\n", 0, 0, 0)); ATF_REQUIRE_THROW_RE(lutok::error, "Invalid value for property 'a': ", lutok::do_string(state, "a = 1\n", 0, 0, 0)); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, top__valid_types); ATF_ADD_TEST_CASE(tcs, top__invalid_types); ATF_ADD_TEST_CASE(tcs, top__reuse); ATF_ADD_TEST_CASE(tcs, top__reset); ATF_ADD_TEST_CASE(tcs, top__already_set_on_entry); ATF_ADD_TEST_CASE(tcs, subtree__valid_types); ATF_ADD_TEST_CASE(tcs, subtree__reuse); ATF_ADD_TEST_CASE(tcs, subtree__reset); ATF_ADD_TEST_CASE(tcs, subtree__already_set_on_entry); ATF_ADD_TEST_CASE(tcs, subtree__override_inner); ATF_ADD_TEST_CASE(tcs, dynamic_subtree__strings); ATF_ADD_TEST_CASE(tcs, dynamic_subtree__invalid_types); ATF_ADD_TEST_CASE(tcs, locals); ATF_ADD_TEST_CASE(tcs, custom_node); ATF_ADD_TEST_CASE(tcs, invalid_key); ATF_ADD_TEST_CASE(tcs, unknown_key); ATF_ADD_TEST_CASE(tcs, value_error); } diff --git a/contrib/kyua/utils/config/nodes.cpp b/contrib/kyua/utils/config/nodes.cpp index 1c6e848daf07..2747c11989ad 100644 --- a/contrib/kyua/utils/config/nodes.cpp +++ b/contrib/kyua/utils/config/nodes.cpp @@ -1,589 +1,589 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/config/nodes.ipp" #include #include #include "utils/config/exceptions.hpp" #include "utils/config/keys.hpp" #include "utils/format/macros.hpp" namespace config = utils::config; /// Destructor. config::detail::base_node::~base_node(void) { } /// Constructor. /// /// \param dynamic_ Whether the node is dynamic or not. config::detail::inner_node::inner_node(const bool dynamic_) : _dynamic(dynamic_) { } /// Destructor. config::detail::inner_node::~inner_node(void) { for (children_map::const_iterator iter = _children.begin(); iter != _children.end(); ++iter) delete (*iter).second; } /// Fills the given node with a copy of this node's data. /// /// \param node The node to fill. Should be the fresh return value of a /// deep_copy() operation. void config::detail::inner_node::copy_into(inner_node* node) const { node->_dynamic = _dynamic; for (children_map::const_iterator iter = _children.begin(); iter != _children.end(); ++iter) { base_node* new_node = (*iter).second->deep_copy(); try { node->_children[(*iter).first] = new_node; } catch (...) { delete new_node; throw; } } } /// Combines two children sets, preferring the keys in the first set only. /// /// This operation is not symmetrical on c1 and c2. The caller is responsible /// for invoking this twice so that the two key sets are combined if they happen /// to differ. /// /// \param key Key to this node. /// \param c1 First children set. /// \param c2 First children set. /// \param [in,out] node The node to combine into. /// /// \throw bad_combination_error If the two nodes cannot be combined. void config::detail::inner_node::combine_children_into( const tree_key& key, const children_map& c1, const children_map& c2, inner_node* node) const { for (children_map::const_iterator iter1 = c1.begin(); iter1 != c1.end(); ++iter1) { const std::string& name = (*iter1).first; if (node->_children.find(name) != node->_children.end()) { continue; } - std::auto_ptr< base_node > new_node; + std::unique_ptr< base_node > new_node; children_map::const_iterator iter2 = c2.find(name); if (iter2 == c2.end()) { new_node.reset((*iter1).second->deep_copy()); } else { tree_key child_key = key; child_key.push_back(name); new_node.reset((*iter1).second->combine(child_key, (*iter2).second)); } node->_children[name] = new_node.release(); } } /// Combines this inner node with another inner node onto a new node. /// /// The "dynamic" property is inherited by the new node if either of the two /// nodes are dynamic. /// /// \param key Key to this node. /// \param other_base The node to combine with. /// \param [in,out] node The node to combine into. /// /// \throw bad_combination_error If the two nodes cannot be combined. void config::detail::inner_node::combine_into(const tree_key& key, const base_node* other_base, inner_node* node) const { try { const inner_node& other = dynamic_cast< const inner_node& >( *other_base); node->_dynamic = _dynamic || other._dynamic; combine_children_into(key, _children, other._children, node); combine_children_into(key, other._children, _children, node); } catch (const std::bad_cast& unused_e) { throw config::bad_combination_error( key, "'%s' is an inner node in the base tree but a leaf node in " "the overrides treee"); } } /// Finds a node without creating it if not found. /// /// This recursive algorithm traverses the tree searching for a particular key. /// The returned node is constant, so this can only be used for querying /// purposes. For this reason, this algorithm does not create intermediate /// nodes if they don't exist (as would be necessary to set a new node). /// /// \param key The key to be queried. /// \param key_pos The current level within the key to be examined. /// /// \return A reference to the located node, if successful. /// /// \throw unknown_key_error If the provided key is unknown. const config::detail::base_node* config::detail::inner_node::lookup_ro(const tree_key& key, const tree_key::size_type key_pos) const { PRE(key_pos < key.size()); const children_map::const_iterator child_iter = _children.find( key[key_pos]); if (child_iter == _children.end()) throw unknown_key_error(key); if (key_pos == key.size() - 1) { return (*child_iter).second; } else { PRE(key_pos < key.size() - 1); try { const inner_node& child = dynamic_cast< const inner_node& >( *(*child_iter).second); return child.lookup_ro(key, key_pos + 1); } catch (const std::bad_cast& e) { throw unknown_key_error( key, "Cannot address incomplete configuration property '%s'"); } } } /// Finds a node and creates it if not found. /// /// This recursive algorithm traverses the tree searching for a particular key, /// creating any intermediate nodes if they do not already exist (for the case /// of dynamic inner nodes). The returned node is non-constant, so this can be /// used by the algorithms that set key values. /// /// \param key The key to be queried. /// \param key_pos The current level within the key to be examined. /// \param new_node A function that returns a new leaf node of the desired /// type. This is only called if the leaf cannot be found, but it has /// already been defined. /// /// \return A reference to the located node, if successful. /// /// \throw invalid_key_value If the resulting node of the search would be an /// inner node. /// \throw unknown_key_error If the provided key is unknown. config::leaf_node* config::detail::inner_node::lookup_rw(const tree_key& key, const tree_key::size_type key_pos, new_node_hook new_node) { PRE(key_pos < key.size()); children_map::const_iterator child_iter = _children.find(key[key_pos]); if (child_iter == _children.end()) { if (_dynamic) { base_node* const child = (key_pos == key.size() - 1) ? static_cast< base_node* >(new_node()) : static_cast< base_node* >(new dynamic_inner_node()); _children.insert(children_map::value_type(key[key_pos], child)); child_iter = _children.find(key[key_pos]); } else { throw unknown_key_error(key); } } if (key_pos == key.size() - 1) { try { leaf_node& child = dynamic_cast< leaf_node& >( *(*child_iter).second); return &child; } catch (const std::bad_cast& unused_error) { throw invalid_key_value(key, "Type mismatch"); } } else { PRE(key_pos < key.size() - 1); try { inner_node& child = dynamic_cast< inner_node& >( *(*child_iter).second); return child.lookup_rw(key, key_pos + 1, new_node); } catch (const std::bad_cast& e) { throw unknown_key_error( key, "Cannot address incomplete configuration property '%s'"); } } } /// Converts the subtree to a collection of key/value string pairs. /// /// \param [out] properties The accumulator for the generated properties. The /// contents of the map are only extended. /// \param key The path to the current node. void config::detail::inner_node::all_properties(properties_map& properties, const tree_key& key) const { for (children_map::const_iterator iter = _children.begin(); iter != _children.end(); ++iter) { tree_key child_key = key; child_key.push_back((*iter).first); try { leaf_node& child = dynamic_cast< leaf_node& >(*(*iter).second); if (child.is_set()) properties[flatten_key(child_key)] = child.to_string(); } catch (const std::bad_cast& unused_error) { inner_node& child = dynamic_cast< inner_node& >(*(*iter).second); child.all_properties(properties, child_key); } } } /// Constructor. config::detail::static_inner_node::static_inner_node(void) : inner_node(false) { } /// Copies the node. /// /// \return A dynamically-allocated node. config::detail::base_node* config::detail::static_inner_node::deep_copy(void) const { - std::auto_ptr< inner_node > new_node(new static_inner_node()); + std::unique_ptr< inner_node > new_node(new static_inner_node()); copy_into(new_node.get()); return new_node.release(); } /// Combines this node with another one. /// /// \param key Key to this node. /// \param other The node to combine with. /// /// \return A new node representing the combination. /// /// \throw bad_combination_error If the two nodes cannot be combined. config::detail::base_node* config::detail::static_inner_node::combine(const tree_key& key, const base_node* other) const { - std::auto_ptr< inner_node > new_node(new static_inner_node()); + std::unique_ptr< inner_node > new_node(new static_inner_node()); combine_into(key, other, new_node.get()); return new_node.release(); } /// Registers a key as valid and having a specific type. /// /// This method does not raise errors on invalid/unknown keys or other /// tree-related issues. The reasons is that define() is a method that does not /// depend on user input: it is intended to pre-populate the tree with a /// specific structure, and that happens once at coding time. /// /// \param key The key to be registered. /// \param key_pos The current level within the key to be examined. /// \param new_node A function that returns a new leaf node of the desired /// type. void config::detail::static_inner_node::define(const tree_key& key, const tree_key::size_type key_pos, new_node_hook new_node) { PRE(key_pos < key.size()); if (key_pos == key.size() - 1) { PRE_MSG(_children.find(key[key_pos]) == _children.end(), "Key already defined"); _children.insert(children_map::value_type(key[key_pos], new_node())); } else { PRE(key_pos < key.size() - 1); const children_map::const_iterator child_iter = _children.find( key[key_pos]); if (child_iter == _children.end()) { static_inner_node* const child_ptr = new static_inner_node(); _children.insert(children_map::value_type(key[key_pos], child_ptr)); child_ptr->define(key, key_pos + 1, new_node); } else { try { static_inner_node& child = dynamic_cast< static_inner_node& >( *(*child_iter).second); child.define(key, key_pos + 1, new_node); } catch (const std::bad_cast& e) { UNREACHABLE; } } } } /// Constructor. config::detail::dynamic_inner_node::dynamic_inner_node(void) : inner_node(true) { } /// Copies the node. /// /// \return A dynamically-allocated node. config::detail::base_node* config::detail::dynamic_inner_node::deep_copy(void) const { - std::auto_ptr< inner_node > new_node(new dynamic_inner_node()); + std::unique_ptr< inner_node > new_node(new dynamic_inner_node()); copy_into(new_node.get()); return new_node.release(); } /// Combines this node with another one. /// /// \param key Key to this node. /// \param other The node to combine with. /// /// \return A new node representing the combination. /// /// \throw bad_combination_error If the two nodes cannot be combined. config::detail::base_node* config::detail::dynamic_inner_node::combine(const tree_key& key, const base_node* other) const { - std::auto_ptr< inner_node > new_node(new dynamic_inner_node()); + std::unique_ptr< inner_node > new_node(new dynamic_inner_node()); combine_into(key, other, new_node.get()); return new_node.release(); } /// Destructor. config::leaf_node::~leaf_node(void) { } /// Combines this node with another one. /// /// \param key Key to this node. /// \param other_base The node to combine with. /// /// \return A new node representing the combination. /// /// \throw bad_combination_error If the two nodes cannot be combined. config::detail::base_node* config::leaf_node::combine(const detail::tree_key& key, const base_node* other_base) const { try { const leaf_node& other = dynamic_cast< const leaf_node& >(*other_base); if (other.is_set()) { return other.deep_copy(); } else { return deep_copy(); } } catch (const std::bad_cast& unused_e) { throw config::bad_combination_error( key, "'%s' is a leaf node in the base tree but an inner node in " "the overrides treee"); } } /// Copies the node. /// /// \return A dynamically-allocated node. config::detail::base_node* config::bool_node::deep_copy(void) const { - std::auto_ptr< bool_node > new_node(new bool_node()); + std::unique_ptr< bool_node > new_node(new bool_node()); new_node->_value = _value; return new_node.release(); } /// Pushes the node's value onto the Lua stack. /// /// \param state The Lua state onto which to push the value. void config::bool_node::push_lua(lutok::state& state) const { state.push_boolean(value()); } /// Sets the value of the node from an entry in the Lua stack. /// /// \param state The Lua state from which to get the value. /// \param value_index The stack index in which the value resides. /// /// \throw value_error If the value in state(value_index) cannot be /// processed by this node. void config::bool_node::set_lua(lutok::state& state, const int value_index) { if (state.is_boolean(value_index)) set(state.to_boolean(value_index)); else throw value_error("Not a boolean"); } /// Copies the node. /// /// \return A dynamically-allocated node. config::detail::base_node* config::int_node::deep_copy(void) const { - std::auto_ptr< int_node > new_node(new int_node()); + std::unique_ptr< int_node > new_node(new int_node()); new_node->_value = _value; return new_node.release(); } /// Pushes the node's value onto the Lua stack. /// /// \param state The Lua state onto which to push the value. void config::int_node::push_lua(lutok::state& state) const { state.push_integer(value()); } /// Sets the value of the node from an entry in the Lua stack. /// /// \param state The Lua state from which to get the value. /// \param value_index The stack index in which the value resides. /// /// \throw value_error If the value in state(value_index) cannot be /// processed by this node. void config::int_node::set_lua(lutok::state& state, const int value_index) { if (state.is_number(value_index)) set(state.to_integer(value_index)); else throw value_error("Not an integer"); } /// Checks a given value for validity. /// /// \param new_value The value to validate. /// /// \throw value_error If the value is not valid. void config::positive_int_node::validate(const value_type& new_value) const { if (new_value <= 0) throw value_error("Must be a positive integer"); } /// Copies the node. /// /// \return A dynamically-allocated node. config::detail::base_node* config::string_node::deep_copy(void) const { - std::auto_ptr< string_node > new_node(new string_node()); + std::unique_ptr< string_node > new_node(new string_node()); new_node->_value = _value; return new_node.release(); } /// Pushes the node's value onto the Lua stack. /// /// \param state The Lua state onto which to push the value. void config::string_node::push_lua(lutok::state& state) const { state.push_string(value()); } /// Sets the value of the node from an entry in the Lua stack. /// /// \param state The Lua state from which to get the value. /// \param value_index The stack index in which the value resides. /// /// \throw value_error If the value in state(value_index) cannot be /// processed by this node. void config::string_node::set_lua(lutok::state& state, const int value_index) { if (state.is_string(value_index)) set(state.to_string(value_index)); else throw value_error("Not a string"); } /// Copies the node. /// /// \return A dynamically-allocated node. config::detail::base_node* config::strings_set_node::deep_copy(void) const { - std::auto_ptr< strings_set_node > new_node(new strings_set_node()); + std::unique_ptr< strings_set_node > new_node(new strings_set_node()); new_node->_value = _value; return new_node.release(); } /// Converts a single word to the native type. /// /// \param raw_value The value to parse. /// /// \return The parsed value. std::string config::strings_set_node::parse_one(const std::string& raw_value) const { return raw_value; } diff --git a/contrib/kyua/utils/config/parser.hpp b/contrib/kyua/utils/config/parser.hpp index cb69e756cbe8..a0496f0bda40 100644 --- a/contrib/kyua/utils/config/parser.hpp +++ b/contrib/kyua/utils/config/parser.hpp @@ -1,95 +1,95 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/config/parser.hpp /// Utilities to read a configuration file into memory. #if !defined(UTILS_CONFIG_PARSER_HPP) #define UTILS_CONFIG_PARSER_HPP #include "utils/config/parser_fwd.hpp" #include #include "utils/config/tree_fwd.hpp" #include "utils/fs/path_fwd.hpp" #include "utils/noncopyable.hpp" namespace utils { namespace config { /// A configuration parser. /// /// This parser is a class rather than a function because we need to support /// callbacks to perform the initialization of the config file schema. The /// configuration files always start with a call to syntax(), which define the /// particular version of the schema being used. Depending on such version, the /// layout of the internal tree representation needs to be different. /// /// A parser implementation must provide a setup() method to set up the /// configuration schema based on the particular combination of syntax format /// and version specified on the file. /// /// Parser objects are not supposed to be reused, and specific trees are not /// supposed to be passed to multiple parsers (even if sequentially). Doing so /// will cause all kinds of inconsistencies in the managed tree itself or in the /// Lua state. class parser : noncopyable { public: struct impl; private: /// Pointer to the internal implementation. - std::auto_ptr< impl > _pimpl; + std::unique_ptr< impl > _pimpl; /// Hook to initialize the tree keys before reading the file. /// /// This hook gets called when the configuration file defines its specific /// format by calling the syntax() function. We have to delay the tree /// initialization until this point because, before we know what version of /// a configuration file we are parsing, we cannot know what keys are valid. /// /// \param [in,out] config_tree The tree in which to define the key /// structure. /// \param syntax_version The version of the file format as specified in the /// configuration file. virtual void setup(tree& config_tree, const int syntax_version) = 0; public: explicit parser(tree&); virtual ~parser(void); void parse(const fs::path&); }; } // namespace config } // namespace utils #endif // !defined(UTILS_CONFIG_PARSER_HPP) diff --git a/contrib/kyua/utils/config/tree_test.cpp b/contrib/kyua/utils/config/tree_test.cpp index b6efd64a84a6..328b8b59a785 100644 --- a/contrib/kyua/utils/config/tree_test.cpp +++ b/contrib/kyua/utils/config/tree_test.cpp @@ -1,1086 +1,1086 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/config/tree.ipp" #include #include "utils/config/nodes.ipp" #include "utils/format/macros.hpp" #include "utils/text/operations.ipp" namespace config = utils::config; namespace text = utils::text; namespace { /// Simple wrapper around an integer value without default constructors. /// /// The purpose of this type is to have a simple class without default /// constructors to validate that we can use it as a leaf of a tree. class int_wrapper { /// The wrapped integer value. int _value; public: /// Constructs a new wrapped integer. /// /// \param value_ The value to store in the object. explicit int_wrapper(int value_) : _value(value_) { } /// \return The integer value stored by the object. int value(void) const { return _value; } }; /// Custom tree leaf type for an object without defualt constructors. class wrapped_int_node : public config::typed_leaf_node< int_wrapper > { public: /// Copies the node. /// /// \return A dynamically-allocated node. virtual base_node* deep_copy(void) const { - std::auto_ptr< wrapped_int_node > new_node(new wrapped_int_node()); + std::unique_ptr< wrapped_int_node > new_node(new wrapped_int_node()); new_node->_value = _value; return new_node.release(); } /// Pushes the node's value onto the Lua stack. /// /// \param state The Lua state onto which to push the value. void push_lua(lutok::state& state) const { state.push_integer( config::typed_leaf_node< int_wrapper >::value().value()); } /// Sets the value of the node from an entry in the Lua stack. /// /// \param state The Lua state from which to get the value. /// \param value_index The stack index in which the value resides. void set_lua(lutok::state& state, const int value_index) { ATF_REQUIRE(state.is_number(value_index)); int_wrapper new_value(state.to_integer(value_index)); config::typed_leaf_node< int_wrapper >::set(new_value); } /// Sets the value of the node from a raw string representation. /// /// \param raw_value The value to set the node to. void set_string(const std::string& raw_value) { int_wrapper new_value(text::to_type< int >(raw_value)); config::typed_leaf_node< int_wrapper >::set(new_value); } /// Converts the contents of the node to a string. /// /// \return A string representation of the value held by the node. std::string to_string(void) const { return F("%s") % config::typed_leaf_node< int_wrapper >::value().value(); } }; } // anonymous namespace ATF_TEST_CASE_WITHOUT_HEAD(define_set_lookup__one_level); ATF_TEST_CASE_BODY(define_set_lookup__one_level) { config::tree tree; tree.define< config::int_node >("var1"); tree.define< config::string_node >("var2"); tree.define< config::bool_node >("var3"); tree.set< config::int_node >("var1", 42); tree.set< config::string_node >("var2", "hello"); tree.set< config::bool_node >("var3", false); ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("var1")); ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("var2")); ATF_REQUIRE(!tree.lookup< config::bool_node >("var3")); } ATF_TEST_CASE_WITHOUT_HEAD(define_set_lookup__multiple_levels); ATF_TEST_CASE_BODY(define_set_lookup__multiple_levels) { config::tree tree; tree.define< config::int_node >("foo.bar.1"); tree.define< config::string_node >("foo.bar.2"); tree.define< config::bool_node >("foo.3"); tree.define_dynamic("sub.tree"); tree.set< config::int_node >("foo.bar.1", 42); tree.set< config::string_node >("foo.bar.2", "hello"); tree.set< config::bool_node >("foo.3", true); tree.set< config::string_node >("sub.tree.1", "bye"); tree.set< config::int_node >("sub.tree.2", 4); tree.set< config::int_node >("sub.tree.3.4", 123); ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("foo.bar.1")); ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("foo.bar.2")); ATF_REQUIRE(tree.lookup< config::bool_node >("foo.3")); ATF_REQUIRE_EQ(4, tree.lookup< config::int_node >("sub.tree.2")); ATF_REQUIRE_EQ(123, tree.lookup< config::int_node >("sub.tree.3.4")); } ATF_TEST_CASE_WITHOUT_HEAD(deep_copy__empty); ATF_TEST_CASE_BODY(deep_copy__empty) { config::tree tree1; config::tree tree2 = tree1.deep_copy(); tree1.define< config::bool_node >("var1"); // This would crash if the copy shared the internal data. tree2.define< config::int_node >("var1"); } ATF_TEST_CASE_WITHOUT_HEAD(deep_copy__some); ATF_TEST_CASE_BODY(deep_copy__some) { config::tree tree1; tree1.define< config::bool_node >("this.is.a.var"); tree1.set< config::bool_node >("this.is.a.var", true); tree1.define< config::int_node >("this.is.another.var"); tree1.set< config::int_node >("this.is.another.var", 34); tree1.define< config::int_node >("and.another"); tree1.set< config::int_node >("and.another", 123); config::tree tree2 = tree1.deep_copy(); tree2.set< config::bool_node >("this.is.a.var", false); tree2.set< config::int_node >("this.is.another.var", 43); ATF_REQUIRE( tree1.lookup< config::bool_node >("this.is.a.var")); ATF_REQUIRE(!tree2.lookup< config::bool_node >("this.is.a.var")); ATF_REQUIRE_EQ(34, tree1.lookup< config::int_node >("this.is.another.var")); ATF_REQUIRE_EQ(43, tree2.lookup< config::int_node >("this.is.another.var")); ATF_REQUIRE_EQ(123, tree1.lookup< config::int_node >("and.another")); ATF_REQUIRE_EQ(123, tree2.lookup< config::int_node >("and.another")); } ATF_TEST_CASE_WITHOUT_HEAD(combine__empty); ATF_TEST_CASE_BODY(combine__empty) { const config::tree t1, t2; const config::tree combined = t1.combine(t2); const config::tree expected; ATF_REQUIRE(expected == combined); } static void init_tree_for_combine_test(config::tree& tree) { tree.define< config::int_node >("int-node"); tree.define< config::string_node >("string-node"); tree.define< config::int_node >("unused.node"); tree.define< config::int_node >("deeper.int.node"); tree.define_dynamic("deeper.dynamic"); } ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__no_overrides); ATF_TEST_CASE_BODY(combine__same_layout__no_overrides) { config::tree t1, t2; init_tree_for_combine_test(t1); init_tree_for_combine_test(t2); t1.set< config::int_node >("int-node", 3); t1.set< config::string_node >("string-node", "foo"); t1.set< config::int_node >("deeper.int.node", 15); t1.set_string("deeper.dynamic.first", "value1"); t1.set_string("deeper.dynamic.second", "value2"); const config::tree combined = t1.combine(t2); ATF_REQUIRE(t1 == combined); } ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__no_base); ATF_TEST_CASE_BODY(combine__same_layout__no_base) { config::tree t1, t2; init_tree_for_combine_test(t1); init_tree_for_combine_test(t2); t2.set< config::int_node >("int-node", 3); t2.set< config::string_node >("string-node", "foo"); t2.set< config::int_node >("deeper.int.node", 15); t2.set_string("deeper.dynamic.first", "value1"); t2.set_string("deeper.dynamic.second", "value2"); const config::tree combined = t1.combine(t2); ATF_REQUIRE(t2 == combined); } ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__mix); ATF_TEST_CASE_BODY(combine__same_layout__mix) { config::tree t1, t2; init_tree_for_combine_test(t1); init_tree_for_combine_test(t2); t1.set< config::int_node >("int-node", 3); t2.set< config::int_node >("int-node", 5); t1.set< config::string_node >("string-node", "foo"); t2.set< config::int_node >("deeper.int.node", 15); t1.set_string("deeper.dynamic.first", "value1"); t1.set_string("deeper.dynamic.second", "value2.1"); t2.set_string("deeper.dynamic.second", "value2.2"); t2.set_string("deeper.dynamic.third", "value3"); const config::tree combined = t1.combine(t2); config::tree expected; init_tree_for_combine_test(expected); expected.set< config::int_node >("int-node", 5); expected.set< config::string_node >("string-node", "foo"); expected.set< config::int_node >("deeper.int.node", 15); expected.set_string("deeper.dynamic.first", "value1"); expected.set_string("deeper.dynamic.second", "value2.2"); expected.set_string("deeper.dynamic.third", "value3"); ATF_REQUIRE(expected == combined); } ATF_TEST_CASE_WITHOUT_HEAD(combine__different_layout); ATF_TEST_CASE_BODY(combine__different_layout) { config::tree t1; t1.define< config::int_node >("common.base1"); t1.define< config::int_node >("common.base2"); t1.define_dynamic("dynamic.base"); t1.define< config::int_node >("unset.base"); config::tree t2; t2.define< config::int_node >("common.base2"); t2.define< config::int_node >("common.base3"); t2.define_dynamic("dynamic.other"); t2.define< config::int_node >("unset.other"); t1.set< config::int_node >("common.base1", 1); t1.set< config::int_node >("common.base2", 2); t1.set_string("dynamic.base.first", "foo"); t1.set_string("dynamic.base.second", "bar"); t2.set< config::int_node >("common.base2", 4); t2.set< config::int_node >("common.base3", 3); t2.set_string("dynamic.other.first", "FOO"); t2.set_string("dynamic.other.second", "BAR"); config::tree combined = t1.combine(t2); config::tree expected; expected.define< config::int_node >("common.base1"); expected.define< config::int_node >("common.base2"); expected.define< config::int_node >("common.base3"); expected.define_dynamic("dynamic.base"); expected.define_dynamic("dynamic.other"); expected.define< config::int_node >("unset.base"); expected.define< config::int_node >("unset.other"); expected.set< config::int_node >("common.base1", 1); expected.set< config::int_node >("common.base2", 4); expected.set< config::int_node >("common.base3", 3); expected.set_string("dynamic.base.first", "foo"); expected.set_string("dynamic.base.second", "bar"); expected.set_string("dynamic.other.first", "FOO"); expected.set_string("dynamic.other.second", "BAR"); ATF_REQUIRE(expected == combined); // The combined tree should have respected existing but unset nodes. Check // that these calls do not crash. combined.set< config::int_node >("unset.base", 5); combined.set< config::int_node >("unset.other", 5); } ATF_TEST_CASE_WITHOUT_HEAD(combine__dynamic_wins); ATF_TEST_CASE_BODY(combine__dynamic_wins) { config::tree t1; t1.define< config::int_node >("inner.leaf1"); t1.set< config::int_node >("inner.leaf1", 3); config::tree t2; t2.define_dynamic("inner"); t2.set_string("inner.leaf2", "4"); config::tree combined = t1.combine(t2); config::tree expected; expected.define_dynamic("inner"); expected.set_string("inner.leaf1", "3"); expected.set_string("inner.leaf2", "4"); ATF_REQUIRE(expected == combined); // The combined inner node should have become dynamic so this call should // not fail. combined.set_string("inner.leaf3", "5"); } ATF_TEST_CASE_WITHOUT_HEAD(combine__inner_leaf_mismatch); ATF_TEST_CASE_BODY(combine__inner_leaf_mismatch) { config::tree t1; t1.define< config::int_node >("top.foo.bar"); config::tree t2; t2.define< config::int_node >("top.foo"); ATF_REQUIRE_THROW_RE(config::bad_combination_error, "'top.foo' is an inner node in the base tree but a " "leaf node in the overrides tree", t1.combine(t2)); ATF_REQUIRE_THROW_RE(config::bad_combination_error, "'top.foo' is a leaf node in the base tree but an " "inner node in the overrides tree", t2.combine(t1)); } ATF_TEST_CASE_WITHOUT_HEAD(lookup__invalid_key); ATF_TEST_CASE_BODY(lookup__invalid_key) { config::tree tree; ATF_REQUIRE_THROW(config::invalid_key_error, tree.lookup< config::int_node >(".")); } ATF_TEST_CASE_WITHOUT_HEAD(lookup__unknown_key); ATF_TEST_CASE_BODY(lookup__unknown_key) { config::tree tree; tree.define< config::int_node >("foo.bar"); tree.define< config::int_node >("a.b.c"); tree.define_dynamic("a.d"); tree.set< config::int_node >("a.b.c", 123); tree.set< config::int_node >("a.d.100", 0); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("abc")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("foo")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("foo.bar")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("foo.bar.baz")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("a")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("a.b")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("a.c")); (void)tree.lookup< config::int_node >("a.b.c"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("a.b.c.d")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("a.d")); (void)tree.lookup< config::int_node >("a.d.100"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("a.d.101")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("a.d.100.3")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup< config::int_node >("a.d.e")); } ATF_TEST_CASE_WITHOUT_HEAD(is_set__one_level); ATF_TEST_CASE_BODY(is_set__one_level) { config::tree tree; tree.define< config::int_node >("var1"); tree.define< config::string_node >("var2"); tree.define< config::bool_node >("var3"); tree.set< config::int_node >("var1", 42); tree.set< config::bool_node >("var3", false); ATF_REQUIRE( tree.is_set("var1")); ATF_REQUIRE(!tree.is_set("var2")); ATF_REQUIRE( tree.is_set("var3")); } ATF_TEST_CASE_WITHOUT_HEAD(is_set__multiple_levels); ATF_TEST_CASE_BODY(is_set__multiple_levels) { config::tree tree; tree.define< config::int_node >("a.b.var1"); tree.define< config::string_node >("a.b.var2"); tree.define< config::bool_node >("e.var3"); tree.set< config::int_node >("a.b.var1", 42); tree.set< config::bool_node >("e.var3", false); ATF_REQUIRE(!tree.is_set("a")); ATF_REQUIRE(!tree.is_set("a.b")); ATF_REQUIRE( tree.is_set("a.b.var1")); ATF_REQUIRE(!tree.is_set("a.b.var1.trailing")); ATF_REQUIRE(!tree.is_set("a")); ATF_REQUIRE(!tree.is_set("a.b")); ATF_REQUIRE(!tree.is_set("a.b.var2")); ATF_REQUIRE(!tree.is_set("a.b.var2.trailing")); ATF_REQUIRE(!tree.is_set("e")); ATF_REQUIRE( tree.is_set("e.var3")); ATF_REQUIRE(!tree.is_set("e.var3.trailing")); } ATF_TEST_CASE_WITHOUT_HEAD(is_set__invalid_key); ATF_TEST_CASE_BODY(is_set__invalid_key) { config::tree tree; ATF_REQUIRE_THROW(config::invalid_key_error, tree.is_set(".abc")); } ATF_TEST_CASE_WITHOUT_HEAD(set__invalid_key); ATF_TEST_CASE_BODY(set__invalid_key) { config::tree tree; ATF_REQUIRE_THROW(config::invalid_key_error, tree.set< config::int_node >("foo.", 54)); } ATF_TEST_CASE_WITHOUT_HEAD(set__invalid_key_value); ATF_TEST_CASE_BODY(set__invalid_key_value) { config::tree tree; tree.define< config::int_node >("foo.bar"); tree.define_dynamic("a.d"); ATF_REQUIRE_THROW(config::invalid_key_value, tree.set< config::int_node >("foo", 3)); ATF_REQUIRE_THROW(config::invalid_key_value, tree.set< config::int_node >("a", -10)); } ATF_TEST_CASE_WITHOUT_HEAD(set__unknown_key); ATF_TEST_CASE_BODY(set__unknown_key) { config::tree tree; tree.define< config::int_node >("foo.bar"); tree.define< config::int_node >("a.b.c"); tree.define_dynamic("a.d"); tree.set< config::int_node >("a.b.c", 123); tree.set< config::string_node >("a.d.3", "foo"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set< config::int_node >("abc", 2)); tree.set< config::int_node >("foo.bar", 15); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set< config::int_node >("foo.bar.baz", 0)); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set< config::int_node >("a.c", 100)); tree.set< config::int_node >("a.b.c", -3); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set< config::int_node >("a.b.c.d", 82)); tree.set< config::string_node >("a.d.3", "bar"); tree.set< config::string_node >("a.d.4", "bar"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set< config::int_node >("a.d.4.5", 82)); tree.set< config::int_node >("a.d.5.6", 82); } ATF_TEST_CASE_WITHOUT_HEAD(set__unknown_key_not_strict); ATF_TEST_CASE_BODY(set__unknown_key_not_strict) { config::tree tree(false); tree.define< config::int_node >("foo.bar"); tree.define< config::int_node >("a.b.c"); tree.define_dynamic("a.d"); tree.set< config::int_node >("a.b.c", 123); tree.set< config::string_node >("a.d.3", "foo"); tree.set< config::int_node >("abc", 2); ATF_REQUIRE(!tree.is_set("abc")); tree.set< config::int_node >("foo.bar", 15); tree.set< config::int_node >("foo.bar.baz", 0); ATF_REQUIRE(!tree.is_set("foo.bar.baz")); tree.set< config::int_node >("a.c", 100); ATF_REQUIRE(!tree.is_set("a.c")); } ATF_TEST_CASE_WITHOUT_HEAD(push_lua__ok); ATF_TEST_CASE_BODY(push_lua__ok) { config::tree tree; tree.define< config::int_node >("top.integer"); tree.define< wrapped_int_node >("top.custom"); tree.define_dynamic("dynamic"); tree.set< config::int_node >("top.integer", 5); tree.set< wrapped_int_node >("top.custom", int_wrapper(10)); tree.set_string("dynamic.first", "foo"); lutok::state state; tree.push_lua("top.integer", state); tree.push_lua("top.custom", state); tree.push_lua("dynamic.first", state); ATF_REQUIRE(state.is_number(-3)); ATF_REQUIRE_EQ(5, state.to_integer(-3)); ATF_REQUIRE(state.is_number(-2)); ATF_REQUIRE_EQ(10, state.to_integer(-2)); ATF_REQUIRE(state.is_string(-1)); ATF_REQUIRE_EQ("foo", state.to_string(-1)); state.pop(3); } ATF_TEST_CASE_WITHOUT_HEAD(set_lua__ok); ATF_TEST_CASE_BODY(set_lua__ok) { config::tree tree; tree.define< config::int_node >("top.integer"); tree.define< wrapped_int_node >("top.custom"); tree.define_dynamic("dynamic"); { lutok::state state; state.push_integer(5); state.push_integer(10); state.push_string("foo"); tree.set_lua("top.integer", state, -3); tree.set_lua("top.custom", state, -2); tree.set_lua("dynamic.first", state, -1); state.pop(3); } ATF_REQUIRE_EQ(5, tree.lookup< config::int_node >("top.integer")); ATF_REQUIRE_EQ(10, tree.lookup< wrapped_int_node >("top.custom").value()); ATF_REQUIRE_EQ("foo", tree.lookup< config::string_node >("dynamic.first")); } ATF_TEST_CASE_WITHOUT_HEAD(lookup_rw); ATF_TEST_CASE_BODY(lookup_rw) { config::tree tree; tree.define< config::int_node >("var1"); tree.define< config::bool_node >("var3"); tree.set< config::int_node >("var1", 42); tree.set< config::bool_node >("var3", false); tree.lookup_rw< config::int_node >("var1") += 10; ATF_REQUIRE_EQ(52, tree.lookup< config::int_node >("var1")); ATF_REQUIRE(!tree.lookup< config::bool_node >("var3")); } ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__ok); ATF_TEST_CASE_BODY(lookup_string__ok) { config::tree tree; tree.define< config::int_node >("var1"); tree.define< config::string_node >("b.var2"); tree.define< config::bool_node >("c.d.var3"); tree.set< config::int_node >("var1", 42); tree.set< config::string_node >("b.var2", "hello"); tree.set< config::bool_node >("c.d.var3", false); ATF_REQUIRE_EQ("42", tree.lookup_string("var1")); ATF_REQUIRE_EQ("hello", tree.lookup_string("b.var2")); ATF_REQUIRE_EQ("false", tree.lookup_string("c.d.var3")); } ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__invalid_key); ATF_TEST_CASE_BODY(lookup_string__invalid_key) { config::tree tree; ATF_REQUIRE_THROW(config::invalid_key_error, tree.lookup_string("")); } ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__unknown_key); ATF_TEST_CASE_BODY(lookup_string__unknown_key) { config::tree tree; tree.define< config::int_node >("a.b.c"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup_string("a.b")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup_string("a.b.c.d")); } ATF_TEST_CASE_WITHOUT_HEAD(set_string__ok); ATF_TEST_CASE_BODY(set_string__ok) { config::tree tree; tree.define< config::int_node >("foo.bar.1"); tree.define< config::string_node >("foo.bar.2"); tree.define_dynamic("sub.tree"); tree.set_string("foo.bar.1", "42"); tree.set_string("foo.bar.2", "hello"); tree.set_string("sub.tree.2", "15"); tree.set_string("sub.tree.3.4", "bye"); ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("foo.bar.1")); ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("foo.bar.2")); ATF_REQUIRE_EQ("15", tree.lookup< config::string_node >("sub.tree.2")); ATF_REQUIRE_EQ("bye", tree.lookup< config::string_node >("sub.tree.3.4")); } ATF_TEST_CASE_WITHOUT_HEAD(set_string__invalid_key); ATF_TEST_CASE_BODY(set_string__invalid_key) { config::tree tree; ATF_REQUIRE_THROW(config::invalid_key_error, tree.set_string(".", "foo")); } ATF_TEST_CASE_WITHOUT_HEAD(set_string__invalid_key_value); ATF_TEST_CASE_BODY(set_string__invalid_key_value) { config::tree tree; tree.define< config::int_node >("foo.bar"); ATF_REQUIRE_THROW(config::invalid_key_value, tree.set_string("foo", "abc")); ATF_REQUIRE_THROW(config::invalid_key_value, tree.set_string("foo.bar", " -3")); ATF_REQUIRE_THROW(config::invalid_key_value, tree.set_string("foo.bar", "3 ")); } ATF_TEST_CASE_WITHOUT_HEAD(set_string__unknown_key); ATF_TEST_CASE_BODY(set_string__unknown_key) { config::tree tree; tree.define< config::int_node >("foo.bar"); tree.define< config::int_node >("a.b.c"); tree.define_dynamic("a.d"); tree.set_string("a.b.c", "123"); tree.set_string("a.d.3", "foo"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set_string("abc", "2")); tree.set_string("foo.bar", "15"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set_string("foo.bar.baz", "0")); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set_string("a.c", "100")); tree.set_string("a.b.c", "-3"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set_string("a.b.c.d", "82")); tree.set_string("a.d.3", "bar"); tree.set_string("a.d.4", "bar"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.set_string("a.d.4.5", "82")); tree.set_string("a.d.5.6", "82"); } ATF_TEST_CASE_WITHOUT_HEAD(set_string__unknown_key_not_strict); ATF_TEST_CASE_BODY(set_string__unknown_key_not_strict) { config::tree tree(false); tree.define< config::int_node >("foo.bar"); tree.define< config::int_node >("a.b.c"); tree.define_dynamic("a.d"); tree.set_string("a.b.c", "123"); tree.set_string("a.d.3", "foo"); tree.set_string("abc", "2"); ATF_REQUIRE(!tree.is_set("abc")); tree.set_string("foo.bar", "15"); tree.set_string("foo.bar.baz", "0"); ATF_REQUIRE(!tree.is_set("foo.bar.baz")); tree.set_string("a.c", "100"); ATF_REQUIRE(!tree.is_set("a.c")); } ATF_TEST_CASE_WITHOUT_HEAD(all_properties__none); ATF_TEST_CASE_BODY(all_properties__none) { const config::tree tree; ATF_REQUIRE(tree.all_properties().empty()); } ATF_TEST_CASE_WITHOUT_HEAD(all_properties__all_set); ATF_TEST_CASE_BODY(all_properties__all_set) { config::tree tree; tree.define< config::int_node >("plain"); tree.set< config::int_node >("plain", 1234); tree.define< config::int_node >("static.first"); tree.set< config::int_node >("static.first", -3); tree.define< config::string_node >("static.second"); tree.set< config::string_node >("static.second", "some text"); tree.define_dynamic("dynamic"); tree.set< config::string_node >("dynamic.first", "hello"); tree.set< config::string_node >("dynamic.second", "bye"); config::properties_map exp_properties; exp_properties["plain"] = "1234"; exp_properties["static.first"] = "-3"; exp_properties["static.second"] = "some text"; exp_properties["dynamic.first"] = "hello"; exp_properties["dynamic.second"] = "bye"; const config::properties_map properties = tree.all_properties(); ATF_REQUIRE(exp_properties == properties); } ATF_TEST_CASE_WITHOUT_HEAD(all_properties__some_unset); ATF_TEST_CASE_BODY(all_properties__some_unset) { config::tree tree; tree.define< config::int_node >("static.first"); tree.set< config::int_node >("static.first", -3); tree.define< config::string_node >("static.second"); tree.define_dynamic("dynamic"); config::properties_map exp_properties; exp_properties["static.first"] = "-3"; const config::properties_map properties = tree.all_properties(); ATF_REQUIRE(exp_properties == properties); } ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__inner); ATF_TEST_CASE_BODY(all_properties__subtree__inner) { config::tree tree; tree.define< config::int_node >("root.a.b.c.first"); tree.define< config::int_node >("root.a.b.c.second"); tree.define< config::int_node >("root.a.d.first"); tree.set< config::int_node >("root.a.b.c.first", 1); tree.set< config::int_node >("root.a.b.c.second", 2); tree.set< config::int_node >("root.a.d.first", 3); { config::properties_map exp_properties; exp_properties["root.a.b.c.first"] = "1"; exp_properties["root.a.b.c.second"] = "2"; exp_properties["root.a.d.first"] = "3"; ATF_REQUIRE(exp_properties == tree.all_properties("root")); ATF_REQUIRE(exp_properties == tree.all_properties("root.a")); } { config::properties_map exp_properties; exp_properties["root.a.b.c.first"] = "1"; exp_properties["root.a.b.c.second"] = "2"; ATF_REQUIRE(exp_properties == tree.all_properties("root.a.b")); ATF_REQUIRE(exp_properties == tree.all_properties("root.a.b.c")); } { config::properties_map exp_properties; exp_properties["root.a.d.first"] = "3"; ATF_REQUIRE(exp_properties == tree.all_properties("root.a.d")); } } ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__leaf); ATF_TEST_CASE_BODY(all_properties__subtree__leaf) { config::tree tree; tree.define< config::int_node >("root.a.b.c.first"); tree.set< config::int_node >("root.a.b.c.first", 1); ATF_REQUIRE_THROW_RE(config::value_error, "Cannot export.*leaf", tree.all_properties("root.a.b.c.first")); } ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__strip_key); ATF_TEST_CASE_BODY(all_properties__subtree__strip_key) { config::tree tree; tree.define< config::int_node >("root.a.b.c.first"); tree.define< config::int_node >("root.a.b.c.second"); tree.define< config::int_node >("root.a.d.first"); tree.set< config::int_node >("root.a.b.c.first", 1); tree.set< config::int_node >("root.a.b.c.second", 2); tree.set< config::int_node >("root.a.d.first", 3); config::properties_map exp_properties; exp_properties["b.c.first"] = "1"; exp_properties["b.c.second"] = "2"; exp_properties["d.first"] = "3"; ATF_REQUIRE(exp_properties == tree.all_properties("root.a", true)); } ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__invalid_key); ATF_TEST_CASE_BODY(all_properties__subtree__invalid_key) { config::tree tree; ATF_REQUIRE_THROW(config::invalid_key_error, tree.all_properties(".")); } ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__unknown_key); ATF_TEST_CASE_BODY(all_properties__subtree__unknown_key) { config::tree tree; tree.define< config::int_node >("root.a.b.c.first"); tree.set< config::int_node >("root.a.b.c.first", 1); tree.define< config::int_node >("root.a.b.c.unset"); ATF_REQUIRE_THROW(config::unknown_key_error, tree.all_properties("root.a.b.c.first.foo")); ATF_REQUIRE_THROW_RE(config::value_error, "Cannot export.*leaf", tree.all_properties("root.a.b.c.unset")); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__empty); ATF_TEST_CASE_BODY(operators_eq_and_ne__empty) { config::tree t1; config::tree t2; ATF_REQUIRE( t1 == t2); ATF_REQUIRE(!(t1 != t2)); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__shallow_copy); ATF_TEST_CASE_BODY(operators_eq_and_ne__shallow_copy) { config::tree t1; t1.define< config::int_node >("root.a.b.c.first"); t1.set< config::int_node >("root.a.b.c.first", 1); config::tree t2 = t1; ATF_REQUIRE( t1 == t2); ATF_REQUIRE(!(t1 != t2)); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__deep_copy); ATF_TEST_CASE_BODY(operators_eq_and_ne__deep_copy) { config::tree t1; t1.define< config::int_node >("root.a.b.c.first"); t1.set< config::int_node >("root.a.b.c.first", 1); config::tree t2 = t1.deep_copy(); ATF_REQUIRE( t1 == t2); ATF_REQUIRE(!(t1 != t2)); } ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__some_contents); ATF_TEST_CASE_BODY(operators_eq_and_ne__some_contents) { config::tree t1, t2; t1.define< config::int_node >("root.a.b.c.first"); t1.set< config::int_node >("root.a.b.c.first", 1); ATF_REQUIRE(!(t1 == t2)); ATF_REQUIRE( t1 != t2); t2.define< config::int_node >("root.a.b.c.first"); t2.set< config::int_node >("root.a.b.c.first", 1); ATF_REQUIRE( t1 == t2); ATF_REQUIRE(!(t1 != t2)); t1.set< config::int_node >("root.a.b.c.first", 2); ATF_REQUIRE(!(t1 == t2)); ATF_REQUIRE( t1 != t2); t2.set< config::int_node >("root.a.b.c.first", 2); ATF_REQUIRE( t1 == t2); ATF_REQUIRE(!(t1 != t2)); t1.define< config::string_node >("another.key"); t1.set< config::string_node >("another.key", "some text"); ATF_REQUIRE(!(t1 == t2)); ATF_REQUIRE( t1 != t2); t2.define< config::string_node >("another.key"); t2.set< config::string_node >("another.key", "some text"); ATF_REQUIRE( t1 == t2); ATF_REQUIRE(!(t1 != t2)); } ATF_TEST_CASE_WITHOUT_HEAD(custom_leaf__no_default_ctor); ATF_TEST_CASE_BODY(custom_leaf__no_default_ctor) { config::tree tree; tree.define< wrapped_int_node >("test1"); tree.define< wrapped_int_node >("test2"); tree.set< wrapped_int_node >("test1", int_wrapper(5)); tree.set< wrapped_int_node >("test2", int_wrapper(10)); const int_wrapper& test1 = tree.lookup< wrapped_int_node >("test1"); ATF_REQUIRE_EQ(5, test1.value()); const int_wrapper& test2 = tree.lookup< wrapped_int_node >("test2"); ATF_REQUIRE_EQ(10, test2.value()); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, define_set_lookup__one_level); ATF_ADD_TEST_CASE(tcs, define_set_lookup__multiple_levels); ATF_ADD_TEST_CASE(tcs, deep_copy__empty); ATF_ADD_TEST_CASE(tcs, deep_copy__some); ATF_ADD_TEST_CASE(tcs, combine__empty); ATF_ADD_TEST_CASE(tcs, combine__same_layout__no_overrides); ATF_ADD_TEST_CASE(tcs, combine__same_layout__no_base); ATF_ADD_TEST_CASE(tcs, combine__same_layout__mix); ATF_ADD_TEST_CASE(tcs, combine__different_layout); ATF_ADD_TEST_CASE(tcs, combine__dynamic_wins); ATF_ADD_TEST_CASE(tcs, combine__inner_leaf_mismatch); ATF_ADD_TEST_CASE(tcs, lookup__invalid_key); ATF_ADD_TEST_CASE(tcs, lookup__unknown_key); ATF_ADD_TEST_CASE(tcs, is_set__one_level); ATF_ADD_TEST_CASE(tcs, is_set__multiple_levels); ATF_ADD_TEST_CASE(tcs, is_set__invalid_key); ATF_ADD_TEST_CASE(tcs, set__invalid_key); ATF_ADD_TEST_CASE(tcs, set__invalid_key_value); ATF_ADD_TEST_CASE(tcs, set__unknown_key); ATF_ADD_TEST_CASE(tcs, set__unknown_key_not_strict); ATF_ADD_TEST_CASE(tcs, push_lua__ok); ATF_ADD_TEST_CASE(tcs, set_lua__ok); ATF_ADD_TEST_CASE(tcs, lookup_rw); ATF_ADD_TEST_CASE(tcs, lookup_string__ok); ATF_ADD_TEST_CASE(tcs, lookup_string__invalid_key); ATF_ADD_TEST_CASE(tcs, lookup_string__unknown_key); ATF_ADD_TEST_CASE(tcs, set_string__ok); ATF_ADD_TEST_CASE(tcs, set_string__invalid_key); ATF_ADD_TEST_CASE(tcs, set_string__invalid_key_value); ATF_ADD_TEST_CASE(tcs, set_string__unknown_key); ATF_ADD_TEST_CASE(tcs, set_string__unknown_key_not_strict); ATF_ADD_TEST_CASE(tcs, all_properties__none); ATF_ADD_TEST_CASE(tcs, all_properties__all_set); ATF_ADD_TEST_CASE(tcs, all_properties__some_unset); ATF_ADD_TEST_CASE(tcs, all_properties__subtree__inner); ATF_ADD_TEST_CASE(tcs, all_properties__subtree__leaf); ATF_ADD_TEST_CASE(tcs, all_properties__subtree__strip_key); ATF_ADD_TEST_CASE(tcs, all_properties__subtree__invalid_key); ATF_ADD_TEST_CASE(tcs, all_properties__subtree__unknown_key); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__empty); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__shallow_copy); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__deep_copy); ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__some_contents); ATF_ADD_TEST_CASE(tcs, custom_leaf__no_default_ctor); } diff --git a/contrib/kyua/utils/format/formatter.cpp b/contrib/kyua/utils/format/formatter.cpp index 99cfd40f03ab..1746f6cbd332 100644 --- a/contrib/kyua/utils/format/formatter.cpp +++ b/contrib/kyua/utils/format/formatter.cpp @@ -1,293 +1,293 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/format/formatter.hpp" #include #include #include #include "utils/format/exceptions.hpp" #include "utils/sanity.hpp" #include "utils/text/exceptions.hpp" #include "utils/text/operations.ipp" namespace format = utils::format; namespace text = utils::text; namespace { /// Finds the next placeholder in a string. /// /// \param format The original format string provided by the user; needed for /// error reporting purposes only. /// \param expansion The string containing the placeholder to look for. Any /// '%%' in the string will be skipped, and they must be stripped later by /// strip_double_percent(). /// \param begin The position from which to start looking for the next /// placeholder. /// /// \return The position in the string in which the placeholder is located and /// the placeholder itself. If there are no placeholders left, this returns /// the length of the string and an empty string. /// /// \throw bad_format_error If the input string contains a trailing formatting /// character. We cannot detect any other kind of invalid formatter because /// we do not implement a full parser for them. static std::pair< std::string::size_type, std::string > find_next_placeholder(const std::string& format, const std::string& expansion, std::string::size_type begin) { begin = expansion.find('%', begin); while (begin != std::string::npos && expansion[begin + 1] == '%') begin = expansion.find('%', begin + 2); if (begin == std::string::npos) return std::make_pair(expansion.length(), ""); if (begin == expansion.length() - 1) throw format::bad_format_error(format, "Trailing %"); std::string::size_type end = begin + 1; while (end < expansion.length() && expansion[end] != 's') end++; const std::string placeholder = expansion.substr(begin, end - begin + 1); if (end == expansion.length() || placeholder.find('%', 1) != std::string::npos) throw format::bad_format_error(format, "Unterminated placeholder '" + placeholder + "'"); return std::make_pair(begin, placeholder); } /// Converts a string to an integer. /// /// \param format The format string; for error reporting purposes only. /// \param str The string to conver. /// \param what The name of the field this integer belongs to; for error /// reporting purposes only. /// /// \return An integer representing the input string. inline int to_int(const std::string& format, const std::string& str, const char* what) { try { return text::to_type< int >(str); } catch (const text::value_error& e) { throw format::bad_format_error(format, "Invalid " + std::string(what) + "specifier"); } } /// Constructs an std::ostringstream based on a formatting placeholder. /// /// \param format The format placeholder; may be empty. /// /// \return A new std::ostringstream that is prepared to format a single /// object in the manner specified by the format placeholder. /// /// \throw bad_format_error If the format string is bad. We do minimal /// validation on this string though. static std::ostringstream* new_ostringstream(const std::string& format) { - std::auto_ptr< std::ostringstream > output(new std::ostringstream()); + std::unique_ptr< std::ostringstream > output(new std::ostringstream()); if (format.length() <= 2) { // If the format is empty, we create a new stream so that we don't have // to check for NULLs later on. We rarely should hit this condition // (and when we do it's a bug in the caller), so this is not a big deal. // // Otherwise, if the format is a regular '%s', then we don't have to do // any processing for additional formatters. So this is just a "fast // path". } else { std::string partial = format.substr(1, format.length() - 2); if (partial[0] == '0') { output->fill('0'); partial.erase(0, 1); } if (!partial.empty()) { const std::string::size_type dot = partial.find('.'); if (dot != 0) output->width(to_int(format, partial.substr(0, dot), "width")); if (dot != std::string::npos) { output->setf(std::ios::fixed, std::ios::floatfield); output->precision(to_int(format, partial.substr(dot + 1), "precision")); } } } return output.release(); } /// Replaces '%%' by '%' in a given string range. /// /// \param in The input string to be rewritten. /// \param begin The position at which to start the replacement. /// \param end The position at which to end the replacement. /// /// \return The modified string and the amount of characters removed. static std::pair< std::string, int > strip_double_percent(const std::string& in, const std::string::size_type begin, std::string::size_type end) { std::string part = in.substr(begin, end - begin); int removed = 0; std::string::size_type pos = part.find("%%"); while (pos != std::string::npos) { part.erase(pos, 1); ++removed; pos = part.find("%%", pos + 1); } return std::make_pair(in.substr(0, begin) + part + in.substr(end), removed); } } // anonymous namespace /// Performs internal initialization of the formatter. /// /// This is separate from the constructor just because it is shared by different /// overloaded constructors. void format::formatter::init(void) { const std::pair< std::string::size_type, std::string > placeholder = find_next_placeholder(_format, _expansion, _last_pos); const std::pair< std::string, int > no_percents = strip_double_percent(_expansion, _last_pos, placeholder.first); _oss = new_ostringstream(placeholder.second); _expansion = no_percents.first; _placeholder_pos = placeholder.first - no_percents.second; _placeholder = placeholder.second; } /// Constructs a new formatter object (internal). /// /// \param format The format string. /// \param expansion The format string with any replacements performed so far. /// \param last_pos The position from which to start looking for formatting /// placeholders. This must be maintained in case one of the replacements /// introduced a new placeholder, which must be ignored. Think, for /// example, replacing a "%s" string with "foo %s". format::formatter::formatter(const std::string& format, const std::string& expansion, const std::string::size_type last_pos) : _format(format), _expansion(expansion), _last_pos(last_pos), _oss(NULL) { init(); } /// Constructs a new formatter object. /// /// \param format The format string. The formatters in the string are not /// validated during construction, but will cause errors when used later if /// they are invalid. format::formatter::formatter(const std::string& format) : _format(format), _expansion(format), _last_pos(0), _oss(NULL) { init(); } format::formatter::~formatter(void) { delete _oss; } /// Returns the formatted string. /// /// \return A string representation of the formatted string. const std::string& format::formatter::str(void) const { return _expansion; } /// Automatic conversion of formatter objects to strings. /// /// This is provided to allow painless injection of formatter objects into /// streams, without having to manually call the str() method. format::formatter::operator const std::string&(void) const { return _expansion; } /// Specialization of operator% for booleans. /// /// \param value The boolean to inject into the format string. /// /// \return A new formatter that has one less format placeholder. format::formatter format::formatter::operator%(const bool& value) const { (*_oss) << (value ? "true" : "false"); return replace(_oss->str()); } /// Replaces the first formatting placeholder with a value. /// /// \param arg The replacement string. /// /// \return A new formatter in which the first formatting placeholder has been /// replaced by arg and is ready to replace the next item. /// /// \throw utils::format::extra_args_error If there are no more formatting /// placeholders in the input string, or if the placeholder is invalid. format::formatter format::formatter::replace(const std::string& arg) const { if (_placeholder_pos == _expansion.length()) throw format::extra_args_error(_format, arg); const std::string expansion = _expansion.substr(0, _placeholder_pos) + arg + _expansion.substr(_placeholder_pos + _placeholder.length()); return formatter(_format, expansion, _placeholder_pos + arg.length()); } diff --git a/contrib/kyua/utils/fs/directory.cpp b/contrib/kyua/utils/fs/directory.cpp index ff7ad5e34357..c8cd5e008490 100644 --- a/contrib/kyua/utils/fs/directory.cpp +++ b/contrib/kyua/utils/fs/directory.cpp @@ -1,360 +1,360 @@ // Copyright 2015 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/fs/directory.hpp" extern "C" { #include #include } #include #include #include "utils/format/macros.hpp" #include "utils/fs/exceptions.hpp" #include "utils/fs/path.hpp" #include "utils/noncopyable.hpp" #include "utils/sanity.hpp" #include "utils/text/operations.ipp" namespace detail = utils::fs::detail; namespace fs = utils::fs; namespace text = utils::text; /// Constructs a new directory entry. /// /// \param name_ Name of the directory entry. fs::directory_entry::directory_entry(const std::string& name_) : name(name_) { } /// Checks if two directory entries are equal. /// /// \param other The entry to compare to. /// /// \return True if the two entries are equal; false otherwise. bool fs::directory_entry::operator==(const directory_entry& other) const { return name == other.name; } /// Checks if two directory entries are different. /// /// \param other The entry to compare to. /// /// \return True if the two entries are different; false otherwise. bool fs::directory_entry::operator!=(const directory_entry& other) const { return !(*this == other); } /// Checks if this entry sorts before another entry. /// /// \param other The entry to compare to. /// /// \return True if this entry sorts before the other entry; false otherwise. bool fs::directory_entry::operator<(const directory_entry& other) const { return name < other.name; } /// Formats a directory entry. /// /// \param output Stream into which to inject the formatted entry. /// \param entry The entry to format. /// /// \return A reference to output. std::ostream& fs::operator<<(std::ostream& output, const directory_entry& entry) { output << F("directory_entry{name=%s}") % text::quote(entry.name, '\''); return output; } /// Internal implementation details for the directory_iterator. /// /// In order to support multiple concurrent iterators over the same directory /// object, this class is the one that performs all directory-level accesses. /// In particular, even if it may seem surprising, this is the class that /// handles the DIR object for the directory. /// /// Note that iterators implemented by this class do not rely on the container /// directory class at all. This should not be relied on for object lifecycle /// purposes. struct utils::fs::detail::directory_iterator::impl : utils::noncopyable { /// Path of the directory accessed by this iterator. const fs::path _path; /// Raw pointer to the system representation of the directory. /// /// We also use this to determine if the iterator is valid (at the end) or /// not. A null pointer means an invalid iterator. ::DIR* _dirp; /// Raw representation of the system directory entry. /// /// We need to keep this at the class level so that we can use the /// readdir_r(3) function. ::dirent _dirent; /// Custom representation of the directory entry. /// /// This is separate from _dirent because this is the type we return to the /// user. We must keep this as a pointer so that we can support the common /// operators (* and ->) over iterators. - std::auto_ptr< directory_entry > _entry; + std::unique_ptr< directory_entry > _entry; /// Constructs an iterator pointing to the "end" of the directory. impl(void) : _path("invalid-directory-entry"), _dirp(NULL) { } /// Constructs a new iterator to start scanning a directory. /// /// \param path The directory that will be scanned. /// /// \throw system_error If there is a problem opening the directory. explicit impl(const path& path) : _path(path) { DIR* dirp = ::opendir(_path.c_str()); if (dirp == NULL) { const int original_errno = errno; throw fs::system_error(F("opendir(%s) failed") % _path, original_errno); } _dirp = dirp; // Initialize our first directory entry. Note that this may actually // close the directory we just opened if the directory happens to be // empty -- but directories are never empty because they at least have // '.' and '..' entries. next(); } /// Destructor. /// /// This closes the directory if still open. ~impl(void) { if (_dirp != NULL) close(); } /// Closes the directory and invalidates the iterator. void close(void) { PRE(_dirp != NULL); if (::closedir(_dirp) == -1) { UNREACHABLE_MSG("Invalid dirp provided to closedir(3)"); } _dirp = NULL; } /// Advances the directory entry to the next one. /// /// It is possible to use this function on a new directory_entry object to /// initialize the first entry. /// /// \throw system_error If the call to readdir_r fails. void next(void) { ::dirent* result; if (::readdir_r(_dirp, &_dirent, &result) == -1) { const int original_errno = errno; throw fs::system_error(F("readdir_r(%s) failed") % _path, original_errno); } if (result == NULL) { _entry.reset(NULL); close(); } else { _entry.reset(new directory_entry(_dirent.d_name)); } } }; /// Constructs a new directory iterator. /// /// \param pimpl The constructed internal implementation structure to use. detail::directory_iterator::directory_iterator(std::shared_ptr< impl > pimpl) : _pimpl(pimpl) { } /// Destructor. detail::directory_iterator::~directory_iterator(void) { } /// Creates a new directory iterator for a directory. /// /// \return The directory iterator. Note that the result may be invalid. /// /// \throw system_error If opening the directory or reading its first entry /// fails. detail::directory_iterator detail::directory_iterator::new_begin(const path& path) { return directory_iterator(std::shared_ptr< impl >(new impl(path))); } /// Creates a new invalid directory iterator. /// /// \return The invalid directory iterator. detail::directory_iterator detail::directory_iterator::new_end(void) { return directory_iterator(std::shared_ptr< impl >(new impl())); } /// Checks if two iterators are equal. /// /// We consider two iterators to be equal if both of them are invalid or, /// otherwise, if they have the exact same internal representation (as given by /// equality of the pimpl pointers). /// /// \param other The object to compare to. /// /// \return True if the two iterators are equal; false otherwise. bool detail::directory_iterator::operator==(const directory_iterator& other) const { return (_pimpl->_dirp == NULL && other._pimpl->_dirp == NULL) || _pimpl == other._pimpl; } /// Checks if two iterators are different. /// /// \param other The object to compare to. /// /// \return True if the two iterators are different; false otherwise. bool detail::directory_iterator::operator!=(const directory_iterator& other) const { return !(*this == other); } /// Moves the iterator one element forward. /// /// \return A reference to the iterator. /// /// \throw system_error If advancing the iterator fails. detail::directory_iterator& detail::directory_iterator::operator++(void) { _pimpl->next(); return *this; } /// Dereferences the iterator to its contents. /// /// \return A reference to the directory entry pointed to by the iterator. const fs::directory_entry& detail::directory_iterator::operator*(void) const { PRE(_pimpl->_entry.get() != NULL); return *_pimpl->_entry; } /// Dereferences the iterator to its contents. /// /// \return A pointer to the directory entry pointed to by the iterator. const fs::directory_entry* detail::directory_iterator::operator->(void) const { PRE(_pimpl->_entry.get() != NULL); return _pimpl->_entry.get(); } /// Internal implementation details for the directory. struct utils::fs::directory::impl : utils::noncopyable { /// Path to the directory to scan. fs::path _path; /// Constructs a new directory. /// /// \param path_ Path to the directory to scan. impl(const fs::path& path_) : _path(path_) { } }; /// Constructs a new directory. /// /// \param path_ Path to the directory to scan. fs::directory::directory(const path& path_) : _pimpl(new impl(path_)) { } /// Returns an iterator to start scanning the directory. /// /// \return An iterator on the directory. /// /// \throw system_error If the directory cannot be opened to obtain its first /// entry. fs::directory::const_iterator fs::directory::begin(void) const { return const_iterator::new_begin(_pimpl->_path); } /// Returns an invalid iterator to check for the end of an scan. /// /// \return An invalid iterator. fs::directory::const_iterator fs::directory::end(void) const { return const_iterator::new_end(); } diff --git a/contrib/kyua/utils/logging/operations.cpp b/contrib/kyua/utils/logging/operations.cpp index 88f25361fa18..ce86183910b2 100644 --- a/contrib/kyua/utils/logging/operations.cpp +++ b/contrib/kyua/utils/logging/operations.cpp @@ -1,303 +1,303 @@ // Copyright 2011 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/logging/operations.hpp" extern "C" { #include } #include #include #include #include #include "utils/datetime.hpp" #include "utils/format/macros.hpp" #include "utils/fs/path.hpp" #include "utils/optional.ipp" #include "utils/sanity.hpp" #include "utils/stream.hpp" namespace datetime = utils::datetime; namespace fs = utils::fs; namespace logging = utils::logging; using utils::none; using utils::optional; /// The general idea for the application-wide logging goes like this: /// /// 1. The application starts. Logging is initialized to capture _all_ log /// messages into memory regardless of their level by issuing a call to the /// set_inmemory() function. /// /// 2. The application offers the user a way to select the logging level and a /// file into which to store the log. /// /// 3. The application calls set_persistency providing a new log level and a log /// file. This must be done as early as possible, to minimize the chances of an /// early crash not capturing any logs. /// /// 4. At this point, any log messages stored into memory are flushed to disk /// respecting the provided log level. /// /// 5. The internal state of the logging module is updated to only capture /// messages that are of the provided log level (or below) and is configured to /// directly send messages to disk. /// /// 6. The user may choose to call set_inmemory() again at a later stage, which /// will cause the log to be flushed and messages to be recorded in memory /// again. This is useful in case the logs are being sent to either stdout or /// stderr and the process forks and wants to keep those child channels /// unpolluted. /// /// The call to set_inmemory() should only be performed by the user-facing /// application. Tests should skip this call so that the logging messages go to /// stderr by default, thus generating a useful log to debug the tests. namespace { /// Constant string to strftime to format timestamps. static const char* timestamp_format = "%Y%m%d-%H%M%S"; /// Mutable global state. struct global_state { /// Current log level. logging::level log_level; /// Indicates whether set_persistency() will be called automatically or not. bool auto_set_persistency; /// First time recorded by the logging module. optional< datetime::timestamp > first_timestamp; /// In-memory record of log entries before persistency is enabled. std::vector< std::pair< logging::level, std::string > > backlog; /// Stream to the currently open log file. - std::auto_ptr< std::ostream > logfile; + std::unique_ptr< std::ostream > logfile; global_state() : log_level(logging::level_debug), auto_set_persistency(true) { } }; /// Single instance of the mutable global state. /// /// Note that this is a raw pointer that we intentionally leak. We must do /// this, instead of making all of the singleton's members static values, /// because we want other destructors in the program to be able to log critical /// conditions. If we use complex types in this translation unit, they may be /// destroyed before the logging methods in the destructors get a chance to run /// thus resulting in a premature crash. By using a plain pointer, we ensure /// this state never gets cleaned up. static struct global_state* globals_singleton = NULL; /// Gets the singleton instance of global_state. /// /// \return A pointer to the unique global_state instance. static struct global_state* get_globals(void) { if (globals_singleton == NULL) { globals_singleton = new global_state(); } return globals_singleton; } /// Converts a level to a printable character. /// /// \param level The level to convert. /// /// \return The printable character, to be used in log messages. static char level_to_char(const logging::level level) { switch (level) { case logging::level_error: return 'E'; case logging::level_warning: return 'W'; case logging::level_info: return 'I'; case logging::level_debug: return 'D'; default: UNREACHABLE; } } } // anonymous namespace /// Generates a standard log name. /// /// This always adds the same timestamp to the log name for a particular run. /// Also, the timestamp added to the file name corresponds to the first /// timestamp recorded by the module; it does not necessarily contain the /// current value of "now". /// /// \param logdir The path to the directory in which to place the log. /// \param progname The name of the program that is generating the log. /// /// \return A string representation of the log name based on \p logdir and /// \p progname. fs::path logging::generate_log_name(const fs::path& logdir, const std::string& progname) { struct global_state* globals = get_globals(); if (!globals->first_timestamp) globals->first_timestamp = datetime::timestamp::now(); // Update kyua(1) if you change the name format. return logdir / (F("%s.%s.log") % progname % globals->first_timestamp.get().strftime(timestamp_format)); } /// Logs an entry to the log file. /// /// If the log is not yet set to persistent mode, the entry is recorded in the /// in-memory backlog. Otherwise, it is just written to disk. /// /// \param message_level The level of the entry. /// \param file The file from which the log message is generated. /// \param line The line from which the log message is generated. /// \param user_message The raw message to store. void logging::log(const level message_level, const char* file, const int line, const std::string& user_message) { struct global_state* globals = get_globals(); const datetime::timestamp now = datetime::timestamp::now(); if (!globals->first_timestamp) globals->first_timestamp = now; if (globals->auto_set_persistency) { // These values are hardcoded here for testing purposes. The // application should call set_inmemory() by itself during // initialization to avoid this, so that it has explicit control on how // the call to set_persistency() happens. set_persistency("debug", fs::path("/dev/stderr")); globals->auto_set_persistency = false; } if (message_level > globals->log_level) return; // Update doc/troubleshooting.texi if you change the log format. const std::string message = F("%s %s %s %s:%s: %s") % now.strftime(timestamp_format) % level_to_char(message_level) % ::getpid() % file % line % user_message; if (globals->logfile.get() == NULL) globals->backlog.push_back(std::make_pair(message_level, message)); else { INV(globals->backlog.empty()); (*globals->logfile) << message << '\n'; globals->logfile->flush(); } } /// Sets the logging to record messages in memory for later flushing. /// /// Can be called after set_persistency to flush logs and set recording to be /// in-memory again. void logging::set_inmemory(void) { struct global_state* globals = get_globals(); globals->auto_set_persistency = false; if (globals->logfile.get() != NULL) { INV(globals->backlog.empty()); globals->logfile->flush(); globals->logfile.reset(NULL); } } /// Makes the log persistent. /// /// Calling this function flushes the in-memory log, if any, to disk and sets /// the logging module to send log entries to disk from this point onwards. /// There is no way back, and the caller program should execute this function as /// early as possible to ensure that a crash at startup does not discard too /// many useful log entries. /// /// Any log entries above the provided new_level are discarded. /// /// \param new_level The new log level. /// \param path The file to write the logs to. /// /// \throw std::range_error If the given log level is invalid. /// \throw std::runtime_error If the given file cannot be created. void logging::set_persistency(const std::string& new_level, const fs::path& path) { struct global_state* globals = get_globals(); globals->auto_set_persistency = false; PRE(globals->logfile.get() == NULL); // Update doc/troubleshooting.info if you change the log levels. if (new_level == "debug") globals->log_level = level_debug; else if (new_level == "error") globals->log_level = level_error; else if (new_level == "info") globals->log_level = level_info; else if (new_level == "warning") globals->log_level = level_warning; else throw std::range_error(F("Unrecognized log level '%s'") % new_level); try { globals->logfile = utils::open_ostream(path); } catch (const std::runtime_error& unused_error) { throw std::runtime_error(F("Failed to create log file %s") % path); } for (std::vector< std::pair< logging::level, std::string > >::const_iterator iter = globals->backlog.begin(); iter != globals->backlog.end(); ++iter) { if ((*iter).first <= globals->log_level) (*globals->logfile) << (*iter).second << '\n'; } globals->logfile->flush(); globals->backlog.clear(); } diff --git a/contrib/kyua/utils/process/child.cpp b/contrib/kyua/utils/process/child.cpp index fef09ccaad3b..bfde8159d5c8 100644 --- a/contrib/kyua/utils/process/child.cpp +++ b/contrib/kyua/utils/process/child.cpp @@ -1,385 +1,385 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/process/child.ipp" extern "C" { #include #include #include #include #include } #include #include #include #include "utils/defs.hpp" #include "utils/format/macros.hpp" #include "utils/fs/path.hpp" #include "utils/logging/macros.hpp" #include "utils/noncopyable.hpp" #include "utils/process/exceptions.hpp" #include "utils/process/fdstream.hpp" #include "utils/process/operations.hpp" #include "utils/process/system.hpp" #include "utils/process/status.hpp" #include "utils/sanity.hpp" #include "utils/signals/interrupts.hpp" namespace utils { namespace process { /// Private implementation fields for child objects. struct child::impl : utils::noncopyable { /// The process identifier. pid_t _pid; /// The input stream for the process' stdout and stderr. May be NULL. - std::auto_ptr< process::ifdstream > _output; + std::unique_ptr< process::ifdstream > _output; /// Initializes private implementation data. /// /// \param pid The process identifier. /// \param output The input stream. Grabs ownership of the pointer. impl(const pid_t pid, process::ifdstream* output) : _pid(pid), _output(output) {} }; } // namespace process } // namespace utils namespace fs = utils::fs; namespace process = utils::process; namespace signals = utils::signals; namespace { /// Exception-based version of dup(2). /// /// \param old_fd The file descriptor to duplicate. /// \param new_fd The file descriptor to use as the duplicate. This is /// closed if it was open before the copy happens. /// /// \throw process::system_error If the call to dup2(2) fails. static void safe_dup(const int old_fd, const int new_fd) { if (process::detail::syscall_dup2(old_fd, new_fd) == -1) { const int original_errno = errno; throw process::system_error(F("dup2(%s, %s) failed") % old_fd % new_fd, original_errno); } } /// Exception-based version of open(2) to open (or create) a file for append. /// /// \param filename The file to open in append mode. /// /// \return The file descriptor for the opened or created file. /// /// \throw process::system_error If the call to open(2) fails. static int open_for_append(const fs::path& filename) { const int fd = process::detail::syscall_open( filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (fd == -1) { const int original_errno = errno; throw process::system_error(F("Failed to create %s because open(2) " "failed") % filename, original_errno); } return fd; } /// Logs the execution of another program. /// /// \param program The binary to execute. /// \param args The arguments to pass to the binary, without the program name. static void log_exec(const fs::path& program, const process::args_vector& args) { std::string plain_command = program.str(); for (process::args_vector::const_iterator iter = args.begin(); iter != args.end(); ++iter) plain_command += F(" %s") % *iter; LD(F("Executing %s") % plain_command); } } // anonymous namespace /// Prints out a fatal error and aborts. void utils::process::detail::report_error_and_abort(void) { std::cerr << "Caught unknown exception\n"; std::abort(); } /// Prints out a fatal error and aborts. /// /// \param error The error to display. void utils::process::detail::report_error_and_abort(const std::runtime_error& error) { std::cerr << "Caught runtime_error: " << error.what() << '\n'; std::abort(); } /// Creates a new child. /// /// \param implptr A dynamically-allocated impl object with the contents of the /// new child. process::child::child(impl *implptr) : _pimpl(implptr) { } /// Destructor for child. process::child::~child(void) { } /// Helper function for fork(). /// /// Please note: if you update this function to change the return type or to /// raise different errors, do not forget to update fork() accordingly. /// /// \return In the case of the parent, a new child object returned as a /// dynamically-allocated object because children classes are unique and thus /// noncopyable. In the case of the child, a NULL pointer. /// /// \throw process::system_error If the calls to pipe(2) or fork(2) fail. -std::auto_ptr< process::child > +std::unique_ptr< process::child > process::child::fork_capture_aux(void) { std::cout.flush(); std::cerr.flush(); int fds[2]; if (detail::syscall_pipe(fds) == -1) throw process::system_error("pipe(2) failed", errno); - std::auto_ptr< signals::interrupts_inhibiter > inhibiter( + std::unique_ptr< signals::interrupts_inhibiter > inhibiter( new signals::interrupts_inhibiter); pid_t pid = detail::syscall_fork(); if (pid == -1) { inhibiter.reset(NULL); // Unblock signals. ::close(fds[0]); ::close(fds[1]); throw process::system_error("fork(2) failed", errno); } else if (pid == 0) { inhibiter.reset(NULL); // Unblock signals. ::setsid(); try { ::close(fds[0]); safe_dup(fds[1], STDOUT_FILENO); safe_dup(fds[1], STDERR_FILENO); ::close(fds[1]); } catch (const system_error& e) { std::cerr << F("Failed to set up subprocess: %s\n") % e.what(); std::abort(); } - return std::auto_ptr< process::child >(NULL); + return std::unique_ptr< process::child >(NULL); } else { ::close(fds[1]); LD(F("Spawned process %s: stdout and stderr inherited") % pid); signals::add_pid_to_kill(pid); inhibiter.reset(NULL); // Unblock signals. - return std::auto_ptr< process::child >( + return std::unique_ptr< process::child >( new process::child(new impl(pid, new process::ifdstream(fds[0])))); } } /// Helper function for fork(). /// /// Please note: if you update this function to change the return type or to /// raise different errors, do not forget to update fork() accordingly. /// /// \param stdout_file The name of the file in which to store the stdout. /// If this has the magic value /dev/stdout, then the parent's stdout is /// reused without applying any redirection. /// \param stderr_file The name of the file in which to store the stderr. /// If this has the magic value /dev/stderr, then the parent's stderr is /// reused without applying any redirection. /// /// \return In the case of the parent, a new child object returned as a /// dynamically-allocated object because children classes are unique and thus /// noncopyable. In the case of the child, a NULL pointer. /// /// \throw process::system_error If the call to fork(2) fails. -std::auto_ptr< process::child > +std::unique_ptr< process::child > process::child::fork_files_aux(const fs::path& stdout_file, const fs::path& stderr_file) { std::cout.flush(); std::cerr.flush(); - std::auto_ptr< signals::interrupts_inhibiter > inhibiter( + std::unique_ptr< signals::interrupts_inhibiter > inhibiter( new signals::interrupts_inhibiter); pid_t pid = detail::syscall_fork(); if (pid == -1) { inhibiter.reset(NULL); // Unblock signals. throw process::system_error("fork(2) failed", errno); } else if (pid == 0) { inhibiter.reset(NULL); // Unblock signals. ::setsid(); try { if (stdout_file != fs::path("/dev/stdout")) { const int stdout_fd = open_for_append(stdout_file); safe_dup(stdout_fd, STDOUT_FILENO); ::close(stdout_fd); } if (stderr_file != fs::path("/dev/stderr")) { const int stderr_fd = open_for_append(stderr_file); safe_dup(stderr_fd, STDERR_FILENO); ::close(stderr_fd); } } catch (const system_error& e) { std::cerr << F("Failed to set up subprocess: %s\n") % e.what(); std::abort(); } - return std::auto_ptr< process::child >(NULL); + return std::unique_ptr< process::child >(NULL); } else { LD(F("Spawned process %s: stdout=%s, stderr=%s") % pid % stdout_file % stderr_file); signals::add_pid_to_kill(pid); inhibiter.reset(NULL); // Unblock signals. - return std::auto_ptr< process::child >( + return std::unique_ptr< process::child >( new process::child(new impl(pid, NULL))); } } /// Spawns a new binary and multiplexes and captures its stdout and stderr. /// /// If the subprocess cannot be completely set up for any reason, it attempts to /// dump an error message to its stderr channel and it then calls std::abort(). /// /// \param program The binary to execute. /// \param args The arguments to pass to the binary, without the program name. /// /// \return A new child object, returned as a dynamically-allocated object /// because children classes are unique and thus noncopyable. /// /// \throw process::system_error If the process cannot be spawned due to a /// system call error. -std::auto_ptr< process::child > +std::unique_ptr< process::child > process::child::spawn_capture(const fs::path& program, const args_vector& args) { - std::auto_ptr< child > child = fork_capture_aux(); + std::unique_ptr< child > child = fork_capture_aux(); if (child.get() == NULL) exec(program, args); log_exec(program, args); return child; } /// Spawns a new binary and redirects its stdout and stderr to files. /// /// If the subprocess cannot be completely set up for any reason, it attempts to /// dump an error message to its stderr channel and it then calls std::abort(). /// /// \param program The binary to execute. /// \param args The arguments to pass to the binary, without the program name. /// \param stdout_file The name of the file in which to store the stdout. /// \param stderr_file The name of the file in which to store the stderr. /// /// \return A new child object, returned as a dynamically-allocated object /// because children classes are unique and thus noncopyable. /// /// \throw process::system_error If the process cannot be spawned due to a /// system call error. -std::auto_ptr< process::child > +std::unique_ptr< process::child > process::child::spawn_files(const fs::path& program, const args_vector& args, const fs::path& stdout_file, const fs::path& stderr_file) { - std::auto_ptr< child > child = fork_files_aux(stdout_file, stderr_file); + std::unique_ptr< child > child = fork_files_aux(stdout_file, stderr_file); if (child.get() == NULL) exec(program, args); log_exec(program, args); return child; } /// Returns the process identifier of this child. /// /// \return A process identifier. int process::child::pid(void) const { return _pimpl->_pid; } /// Gets the input stream corresponding to the stdout and stderr of the child. /// /// \pre The child must have been started by fork_capture(). /// /// \return A reference to the input stream connected to the output of the test /// case. std::istream& process::child::output(void) { PRE(_pimpl->_output.get() != NULL); return *_pimpl->_output; } /// Blocks to wait for completion. /// /// \return The termination status of the child process. /// /// \throw process::system_error If the call to waitpid(2) fails. process::status process::child::wait(void) { return process::wait(_pimpl->_pid); } diff --git a/contrib/kyua/utils/process/child.hpp b/contrib/kyua/utils/process/child.hpp index 2c9450f6500a..3e00cea8752c 100644 --- a/contrib/kyua/utils/process/child.hpp +++ b/contrib/kyua/utils/process/child.hpp @@ -1,113 +1,113 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/process/child.hpp /// Spawning and manipulation of children processes. /// /// The child module provides a set of functions to spawn subprocesses with /// different settings, and the corresponding set of classes to interact with /// said subprocesses. The interfaces to fork subprocesses are very simplified /// and only provide the minimum functionality required by the rest of the /// project. /// /// Be aware that the semantics of the fork and wait methods exposed by this /// module are slightly different from that of the native calls. Any process /// spawned by fork here will be isolated in its own session; once any of /// such children processes is awaited for, its whole process group will be /// terminated. This is the semantics we want in the above layers to ensure /// that test programs (and, for that matter, external utilities) do not leak /// subprocesses on the system. #if !defined(UTILS_PROCESS_CHILD_HPP) #define UTILS_PROCESS_CHILD_HPP #include "utils/process/child_fwd.hpp" #include #include #include #include "utils/defs.hpp" #include "utils/fs/path_fwd.hpp" #include "utils/noncopyable.hpp" #include "utils/process/operations_fwd.hpp" #include "utils/process/status_fwd.hpp" namespace utils { namespace process { namespace detail { void report_error_and_abort(void) UTILS_NORETURN; void report_error_and_abort(const std::runtime_error&) UTILS_NORETURN; } // namespace detail /// Child process spawner and controller. class child : noncopyable { struct impl; /// Pointer to the shared internal implementation. - std::auto_ptr< impl > _pimpl; + std::unique_ptr< impl > _pimpl; - static std::auto_ptr< child > fork_capture_aux(void); + static std::unique_ptr< child > fork_capture_aux(void); - static std::auto_ptr< child > fork_files_aux(const fs::path&, + static std::unique_ptr< child > fork_files_aux(const fs::path&, const fs::path&); explicit child(impl *); public: ~child(void); template< typename Hook > - static std::auto_ptr< child > fork_capture(Hook); + static std::unique_ptr< child > fork_capture(Hook); std::istream& output(void); template< typename Hook > - static std::auto_ptr< child > fork_files(Hook, const fs::path&, + static std::unique_ptr< child > fork_files(Hook, const fs::path&, const fs::path&); - static std::auto_ptr< child > spawn_capture( + static std::unique_ptr< child > spawn_capture( const fs::path&, const args_vector&); - static std::auto_ptr< child > spawn_files( + static std::unique_ptr< child > spawn_files( const fs::path&, const args_vector&, const fs::path&, const fs::path&); int pid(void) const; status wait(void); }; } // namespace process } // namespace utils #endif // !defined(UTILS_PROCESS_CHILD_HPP) diff --git a/contrib/kyua/utils/process/child.ipp b/contrib/kyua/utils/process/child.ipp index aa90373652fd..beb2ea3b0b0a 100644 --- a/contrib/kyua/utils/process/child.ipp +++ b/contrib/kyua/utils/process/child.ipp @@ -1,110 +1,110 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #if !defined(UTILS_PROCESS_CHILD_IPP) #define UTILS_PROCESS_CHILD_IPP #include #include "utils/process/child.hpp" namespace utils { namespace process { /// Spawns a new subprocess and redirects its stdout and stderr to files. /// /// If the subprocess cannot be completely set up for any reason, it attempts to /// dump an error message to its stderr channel and it then calls std::abort(). /// /// \param hook The function to execute in the subprocess. Must not return. /// \param stdout_file The name of the file in which to store the stdout. /// \param stderr_file The name of the file in which to store the stderr. /// /// \return A new child object, returned as a dynamically-allocated object /// because children classes are unique and thus noncopyable. /// /// \throw process::system_error If the process cannot be spawned due to a /// system call error. template< typename Hook > -std::auto_ptr< child > +std::unique_ptr< child > child::fork_files(Hook hook, const fs::path& stdout_file, const fs::path& stderr_file) { - std::auto_ptr< child > child = fork_files_aux(stdout_file, stderr_file); + std::unique_ptr< child > child = fork_files_aux(stdout_file, stderr_file); if (child.get() == NULL) { try { hook(); std::abort(); } catch (const std::runtime_error& e) { detail::report_error_and_abort(e); } catch (...) { detail::report_error_and_abort(); } } return child; } /// Spawns a new subprocess and multiplexes and captures its stdout and stderr. /// /// If the subprocess cannot be completely set up for any reason, it attempts to /// dump an error message to its stderr channel and it then calls std::abort(). /// /// \param hook The function to execute in the subprocess. Must not return. /// /// \return A new child object, returned as a dynamically-allocated object /// because children classes are unique and thus noncopyable. /// /// \throw process::system_error If the process cannot be spawned due to a /// system call error. template< typename Hook > -std::auto_ptr< child > +std::unique_ptr< child > child::fork_capture(Hook hook) { - std::auto_ptr< child > child = fork_capture_aux(); + std::unique_ptr< child > child = fork_capture_aux(); if (child.get() == NULL) { try { hook(); std::abort(); } catch (const std::runtime_error& e) { detail::report_error_and_abort(e); } catch (...) { detail::report_error_and_abort(); } } return child; } } // namespace process } // namespace utils #endif // !defined(UTILS_PROCESS_CHILD_IPP) diff --git a/contrib/kyua/utils/process/child_test.cpp b/contrib/kyua/utils/process/child_test.cpp index 69de9991ae13..68911ae04660 100644 --- a/contrib/kyua/utils/process/child_test.cpp +++ b/contrib/kyua/utils/process/child_test.cpp @@ -1,846 +1,846 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/process/child.ipp" extern "C" { #include #include #include #include #include } #include #include #include #include #include #include #include #include #include "utils/defs.hpp" #include "utils/env.hpp" #include "utils/format/macros.hpp" #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" #include "utils/logging/macros.hpp" #include "utils/process/exceptions.hpp" #include "utils/process/status.hpp" #include "utils/process/system.hpp" #include "utils/sanity.hpp" #include "utils/test_utils.ipp" namespace fs = utils::fs; namespace logging = utils::logging; namespace process = utils::process; namespace { /// Checks if the current subprocess is in its own session. static void child_check_own_session(void) { std::exit((::getsid(::getpid()) == ::getpid()) ? EXIT_SUCCESS : EXIT_FAILURE); } /// Body for a process that prints a simple message and exits. /// /// \tparam ExitStatus The exit status for the subprocess. /// \tparam Message A single character that will be prepended to the printed /// messages. This would ideally be a string, but we cannot templatize a /// function with an object nor a pointer. template< int ExitStatus, char Message > static void child_simple_function(void) { std::cout << "To stdout: " << Message << "\n"; std::cerr << "To stderr: " << Message << "\n"; std::exit(ExitStatus); } /// Functor for the body of a process that prints a simple message and exits. class child_simple_functor { /// The exit status that the subprocess will yield. int _exitstatus; /// The message to print on stdout and stderr. std::string _message; public: /// Constructs a new functor. /// /// \param exitstatus The exit status that the subprocess will yield. /// \param message The message to print on stdout and stderr. child_simple_functor(const int exitstatus, const std::string& message) : _exitstatus(exitstatus), _message(message) { } /// Body for the subprocess. void operator()(void) { std::cout << "To stdout: " << _message << "\n"; std::cerr << "To stderr: " << _message << "\n"; std::exit(_exitstatus); } }; /// Body for a process that prints many messages to stdout and exits. /// /// The goal of this body is to validate that any buffering performed on the /// parent process to read the output of the subprocess works correctly. static void child_printer_function(void) { for (std::size_t i = 0; i < 100; i++) std::cout << "This is a message to stdout, sequence " << i << "\n"; std::cout.flush(); std::cerr << "Exiting\n"; std::exit(EXIT_SUCCESS); } /// Functor for the body of a process that runs child_printer_function. class child_printer_functor { public: /// Body for the subprocess. void operator()(void) { child_printer_function(); } }; /// Body for a child process that throws an exception. static void child_throw_exception(void) { throw std::runtime_error("A loose exception"); } /// Body for a child process that creates a pidfile. static void child_write_pid(void) { std::ofstream output("pidfile"); output << ::getpid() << "\n"; output.close(); std::exit(EXIT_SUCCESS); } /// A child process that returns. /// /// The fork() wrappers are supposed to capture this condition and terminate the /// child before the code returns to the fork() call point. static void child_return(void) { } /// A child process that raises an exception. /// /// The fork() wrappers are supposed to capture this condition and terminate the /// child before the code returns to the fork() call point. /// /// \tparam Type The type of the exception to raise. /// \tparam Value The value passed to the constructor of the exception type. In /// general, this only makes sense if Type is a primitive type so that, in /// the end, the code becomes "throw int(123)". /// /// \throw Type An exception of the provided type. template< class Type, Type Value > void child_raise_exception(void) { throw Type(Value); } /// Calculates the path to the test helpers binary. /// /// \param tc A pointer to the caller test case, needed to extract the value of /// the "srcdir" property. /// /// \return The path to the helpers binary. static fs::path get_helpers(const atf::tests::tc* tc) { return fs::path(tc->get_config_var("srcdir")) / "helpers"; } /// Mock fork(2) that just returns an error. /// /// \tparam Errno The value to set as the errno of the failed call. /// /// \return Always -1. template< int Errno > static pid_t fork_fail(void) throw() { errno = Errno; return -1; } /// Mock open(2) that fails if the 'raise-error' file is opened. /// /// \tparam Errno The value to set as the errno if the known failure triggers. /// \param path The path to the file to be opened. /// \param flags The open flags. /// \param ... The file mode creation, if flags contains O_CREAT. /// /// \return The opened file handle or -1 on error. template< int Errno > static int open_fail(const char* path, const int flags, ...) throw() { if (std::strcmp(path, "raise-error") == 0) { errno = Errno; return -1; } else { va_list ap; va_start(ap, flags); const int mode = va_arg(ap, int); va_end(ap); return ::open(path, flags, mode); } } /// Mock pipe(2) that just returns an error. /// /// \tparam Errno The value to set as the errno of the failed call. /// /// \return Always -1. template< int Errno > static pid_t pipe_fail(int* /* fildes */) throw() { errno = Errno; return -1; } /// Helper for child tests to validate inheritance of stdout/stderr. /// /// This function ensures that passing one of /dev/stdout or /dev/stderr to /// the child__fork_files fork method does the right thing. The idea is that we /// call fork with the given parameters and then make our child redirect one of /// its file descriptors to a specific file without going through the process /// library. We then validate if this redirection worked and got the expected /// output. /// /// \param fork_stdout The path to pass to the fork call as the stdout file. /// \param fork_stderr The path to pass to the fork call as the stderr file. /// \param child_file The file to explicitly in the subchild. /// \param child_fd The file descriptor to which to attach child_file. static void do_inherit_test(const char* fork_stdout, const char* fork_stderr, const char* child_file, const int child_fd) { const pid_t pid = ::fork(); ATF_REQUIRE(pid != -1); if (pid == 0) { logging::set_inmemory(); const int fd = ::open(child_file, O_CREAT | O_WRONLY | O_TRUNC, 0644); if (fd != child_fd) { if (::dup2(fd, child_fd) == -1) std::abort(); ::close(fd); } - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_simple_function< 123, 'Z' >, fs::path(fork_stdout), fs::path(fork_stderr)); const process::status status = child->wait(); if (!status.exited() || status.exitstatus() != 123) std::abort(); std::exit(EXIT_SUCCESS); } else { int status; ATF_REQUIRE(::waitpid(pid, &status, 0) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status)); ATF_REQUIRE(atf::utils::grep_file("stdout: Z", "stdout.txt")); ATF_REQUIRE(atf::utils::grep_file("stderr: Z", "stderr.txt")); } } /// Performs a "child__fork_capture__ok_*" test. /// /// This test basically ensures that the child__fork_capture class spawns a /// process whose output is captured in an input stream. /// /// \tparam Hook The type of the fork hook to use. /// \param hook The hook to the fork call. template< class Hook > static void child__fork_capture__ok(Hook hook) { std::cout << "This unflushed message should not propagate to the child"; std::cerr << "This unflushed message should not propagate to the child"; - std::auto_ptr< process::child > child = process::child::fork_capture(hook); + std::unique_ptr< process::child > child = process::child::fork_capture(hook); std::cout.flush(); std::cerr.flush(); std::istream& output = child->output(); for (std::size_t i = 0; i < 100; i++) { std::string line; ATF_REQUIRE(std::getline(output, line).good()); ATF_REQUIRE_EQ((F("This is a message to stdout, " "sequence %s") % i).str(), line); } std::string line; ATF_REQUIRE(std::getline(output, line).good()); ATF_REQUIRE_EQ("Exiting", line); process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } } // anonymous namespace ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__ok_function); ATF_TEST_CASE_BODY(child__fork_capture__ok_function) { child__fork_capture__ok(child_printer_function); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__ok_functor); ATF_TEST_CASE_BODY(child__fork_capture__ok_functor) { child__fork_capture__ok(child_printer_functor()); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__catch_exceptions); ATF_TEST_CASE_BODY(child__fork_capture__catch_exceptions) { - std::auto_ptr< process::child > child = process::child::fork_capture( + std::unique_ptr< process::child > child = process::child::fork_capture( child_throw_exception); std::string message; std::istream& output = child->output(); ATF_REQUIRE(std::getline(output, message).good()); const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE_EQ(SIGABRT, status.termsig()); ATF_REQUIRE_MATCH("Caught.*A loose exception", message); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__new_session); ATF_TEST_CASE_BODY(child__fork_capture__new_session) { - std::auto_ptr< process::child > child = process::child::fork_capture( + std::unique_ptr< process::child > child = process::child::fork_capture( child_check_own_session); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__pipe_fail); ATF_TEST_CASE_BODY(child__fork_capture__pipe_fail) { process::detail::syscall_pipe = pipe_fail< 23 >; try { process::child::fork_capture(child_simple_function< 1, 'A' >); fail("Expected exception but none raised"); } catch (const process::system_error& e) { ATF_REQUIRE(atf::utils::grep_string("pipe.*failed", e.what())); ATF_REQUIRE_EQ(23, e.original_errno()); } } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__fork_cannot_exit); ATF_TEST_CASE_BODY(child__fork_capture__fork_cannot_exit) { const pid_t parent_pid = ::getpid(); atf::utils::create_file("to-not-be-deleted", ""); - std::auto_ptr< process::child > child = process::child::fork_capture( + std::unique_ptr< process::child > child = process::child::fork_capture( child_return); if (::getpid() != parent_pid) { // If we enter this clause, it is because the hook returned. ::unlink("to-not-be-deleted"); std::exit(EXIT_SUCCESS); } const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted"))); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__fork_cannot_unwind); ATF_TEST_CASE_BODY(child__fork_capture__fork_cannot_unwind) { const pid_t parent_pid = ::getpid(); atf::utils::create_file("to-not-be-deleted", ""); try { - std::auto_ptr< process::child > child = process::child::fork_capture( + std::unique_ptr< process::child > child = process::child::fork_capture( child_raise_exception< int, 123 >); const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted"))); } catch (const int i) { // If we enter this clause, it is because an exception leaked from the // hook. INV(parent_pid != ::getpid()); INV(i == 123); ::unlink("to-not-be-deleted"); std::exit(EXIT_SUCCESS); } } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_capture__fork_fail); ATF_TEST_CASE_BODY(child__fork_capture__fork_fail) { process::detail::syscall_fork = fork_fail< 89 >; try { process::child::fork_capture(child_simple_function< 1, 'A' >); fail("Expected exception but none raised"); } catch (const process::system_error& e) { ATF_REQUIRE(atf::utils::grep_string("fork.*failed", e.what())); ATF_REQUIRE_EQ(89, e.original_errno()); } } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__ok_function); ATF_TEST_CASE_BODY(child__fork_files__ok_function) { const fs::path file1("file1.txt"); const fs::path file2("file2.txt"); - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_simple_function< 15, 'Z' >, file1, file2); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(15, status.exitstatus()); ATF_REQUIRE( atf::utils::grep_file("^To stdout: Z$", file1.str())); ATF_REQUIRE(!atf::utils::grep_file("^To stdout: Z$", file2.str())); ATF_REQUIRE( atf::utils::grep_file("^To stderr: Z$", file2.str())); ATF_REQUIRE(!atf::utils::grep_file("^To stderr: Z$", file1.str())); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__ok_functor); ATF_TEST_CASE_BODY(child__fork_files__ok_functor) { const fs::path filea("fileA.txt"); const fs::path fileb("fileB.txt"); atf::utils::create_file(filea.str(), "Initial stdout\n"); atf::utils::create_file(fileb.str(), "Initial stderr\n"); - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_simple_functor(16, "a functor"), filea, fileb); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(16, status.exitstatus()); ATF_REQUIRE( atf::utils::grep_file("^Initial stdout$", filea.str())); ATF_REQUIRE(!atf::utils::grep_file("^Initial stdout$", fileb.str())); ATF_REQUIRE( atf::utils::grep_file("^To stdout: a functor$", filea.str())); ATF_REQUIRE(!atf::utils::grep_file("^To stdout: a functor$", fileb.str())); ATF_REQUIRE( atf::utils::grep_file("^Initial stderr$", fileb.str())); ATF_REQUIRE(!atf::utils::grep_file("^Initial stderr$", filea.str())); ATF_REQUIRE( atf::utils::grep_file("^To stderr: a functor$", fileb.str())); ATF_REQUIRE(!atf::utils::grep_file("^To stderr: a functor$", filea.str())); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__catch_exceptions); ATF_TEST_CASE_BODY(child__fork_files__catch_exceptions) { - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_throw_exception, fs::path("unused.out"), fs::path("stderr")); const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE_EQ(SIGABRT, status.termsig()); ATF_REQUIRE(atf::utils::grep_file("Caught.*A loose exception", "stderr")); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__new_session); ATF_TEST_CASE_BODY(child__fork_files__new_session) { - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_check_own_session, fs::path("unused.out"), fs::path("unused.err")); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__inherit_stdout); ATF_TEST_CASE_BODY(child__fork_files__inherit_stdout) { do_inherit_test("/dev/stdout", "stderr.txt", "stdout.txt", STDOUT_FILENO); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__inherit_stderr); ATF_TEST_CASE_BODY(child__fork_files__inherit_stderr) { do_inherit_test("stdout.txt", "/dev/stderr", "stderr.txt", STDERR_FILENO); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__fork_cannot_exit); ATF_TEST_CASE_BODY(child__fork_files__fork_cannot_exit) { const pid_t parent_pid = ::getpid(); atf::utils::create_file("to-not-be-deleted", ""); - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_return, fs::path("out"), fs::path("err")); if (::getpid() != parent_pid) { // If we enter this clause, it is because the hook returned. ::unlink("to-not-be-deleted"); std::exit(EXIT_SUCCESS); } const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted"))); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__fork_cannot_unwind); ATF_TEST_CASE_BODY(child__fork_files__fork_cannot_unwind) { const pid_t parent_pid = ::getpid(); atf::utils::create_file("to-not-be-deleted", ""); try { - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_raise_exception< int, 123 >, fs::path("out"), fs::path("err")); const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE(fs::exists(fs::path("to-not-be-deleted"))); } catch (const int i) { // If we enter this clause, it is because an exception leaked from the // hook. INV(parent_pid != ::getpid()); INV(i == 123); ::unlink("to-not-be-deleted"); std::exit(EXIT_SUCCESS); } } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__fork_fail); ATF_TEST_CASE_BODY(child__fork_files__fork_fail) { process::detail::syscall_fork = fork_fail< 1234 >; try { process::child::fork_files(child_simple_function< 1, 'A' >, fs::path("a.txt"), fs::path("b.txt")); fail("Expected exception but none raised"); } catch (const process::system_error& e) { ATF_REQUIRE(atf::utils::grep_string("fork.*failed", e.what())); ATF_REQUIRE_EQ(1234, e.original_errno()); } ATF_REQUIRE(!fs::exists(fs::path("a.txt"))); ATF_REQUIRE(!fs::exists(fs::path("b.txt"))); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__create_stdout_fail); ATF_TEST_CASE_BODY(child__fork_files__create_stdout_fail) { process::detail::syscall_open = open_fail< ENOENT >; - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_simple_function< 1, 'A' >, fs::path("raise-error"), fs::path("created")); const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE_EQ(SIGABRT, status.termsig()); ATF_REQUIRE(!fs::exists(fs::path("raise-error"))); ATF_REQUIRE(!fs::exists(fs::path("created"))); } ATF_TEST_CASE_WITHOUT_HEAD(child__fork_files__create_stderr_fail); ATF_TEST_CASE_BODY(child__fork_files__create_stderr_fail) { process::detail::syscall_open = open_fail< ENOENT >; - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_simple_function< 1, 'A' >, fs::path("created"), fs::path("raise-error")); const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE_EQ(SIGABRT, status.termsig()); ATF_REQUIRE(fs::exists(fs::path("created"))); ATF_REQUIRE(!fs::exists(fs::path("raise-error"))); } ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__absolute_path); ATF_TEST_CASE_BODY(child__spawn__absolute_path) { std::vector< std::string > args; args.push_back("return-code"); args.push_back("12"); const fs::path program = get_helpers(this); INV(program.is_absolute()); - std::auto_ptr< process::child > child = process::child::spawn_files( + std::unique_ptr< process::child > child = process::child::spawn_files( program, args, fs::path("out"), fs::path("err")); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(12, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__relative_path); ATF_TEST_CASE_BODY(child__spawn__relative_path) { std::vector< std::string > args; args.push_back("return-code"); args.push_back("13"); ATF_REQUIRE(::mkdir("root", 0755) != -1); ATF_REQUIRE(::symlink(get_helpers(this).c_str(), "root/helpers") != -1); - std::auto_ptr< process::child > child = process::child::spawn_files( + std::unique_ptr< process::child > child = process::child::spawn_files( fs::path("root/helpers"), args, fs::path("out"), fs::path("err")); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(13, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__basename_only); ATF_TEST_CASE_BODY(child__spawn__basename_only) { std::vector< std::string > args; args.push_back("return-code"); args.push_back("14"); ATF_REQUIRE(::symlink(get_helpers(this).c_str(), "helpers") != -1); - std::auto_ptr< process::child > child = process::child::spawn_files( + std::unique_ptr< process::child > child = process::child::spawn_files( fs::path("helpers"), args, fs::path("out"), fs::path("err")); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(14, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__no_path); ATF_TEST_CASE_BODY(child__spawn__no_path) { logging::set_inmemory(); std::vector< std::string > args; args.push_back("return-code"); args.push_back("14"); const fs::path helpers = get_helpers(this); utils::setenv("PATH", helpers.branch_path().c_str()); - std::auto_ptr< process::child > child = process::child::spawn_capture( + std::unique_ptr< process::child > child = process::child::spawn_capture( fs::path(helpers.leaf_name()), args); std::string line; ATF_REQUIRE(std::getline(child->output(), line).good()); ATF_REQUIRE_MATCH("Failed to execute", line); ATF_REQUIRE(!std::getline(child->output(), line)); const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE_EQ(SIGABRT, status.termsig()); } ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__no_args); ATF_TEST_CASE_BODY(child__spawn__no_args) { std::vector< std::string > args; - std::auto_ptr< process::child > child = process::child::spawn_capture( + std::unique_ptr< process::child > child = process::child::spawn_capture( get_helpers(this), args); std::string line; ATF_REQUIRE(std::getline(child->output(), line).good()); ATF_REQUIRE_EQ("Must provide a helper name", line); ATF_REQUIRE(!std::getline(child->output(), line)); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_FAILURE, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__some_args); ATF_TEST_CASE_BODY(child__spawn__some_args) { std::vector< std::string > args; args.push_back("print-args"); args.push_back("foo"); args.push_back(" bar baz "); - std::auto_ptr< process::child > child = process::child::spawn_capture( + std::unique_ptr< process::child > child = process::child::spawn_capture( get_helpers(this), args); std::string line; ATF_REQUIRE(std::getline(child->output(), line).good()); ATF_REQUIRE_EQ("argv[0] = " + get_helpers(this).str(), line); ATF_REQUIRE(std::getline(child->output(), line).good()); ATF_REQUIRE_EQ("argv[1] = print-args", line); ATF_REQUIRE(std::getline(child->output(), line)); ATF_REQUIRE_EQ("argv[2] = foo", line); ATF_REQUIRE(std::getline(child->output(), line)); ATF_REQUIRE_EQ("argv[3] = bar baz ", line); ATF_REQUIRE(std::getline(child->output(), line)); ATF_REQUIRE_EQ("argv[4] = NULL", line); ATF_REQUIRE(!std::getline(child->output(), line)); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(child__spawn__missing_program); ATF_TEST_CASE_BODY(child__spawn__missing_program) { std::vector< std::string > args; - std::auto_ptr< process::child > child = process::child::spawn_capture( + std::unique_ptr< process::child > child = process::child::spawn_capture( fs::path("a/b/c"), args); std::string line; ATF_REQUIRE(std::getline(child->output(), line).good()); const std::string exp = "Failed to execute a/b/c: "; ATF_REQUIRE_EQ(exp, line.substr(0, exp.length())); ATF_REQUIRE(!std::getline(child->output(), line)); const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE_EQ(SIGABRT, status.termsig()); } ATF_TEST_CASE_WITHOUT_HEAD(child__pid); ATF_TEST_CASE_BODY(child__pid) { - std::auto_ptr< process::child > child = process::child::fork_capture( + std::unique_ptr< process::child > child = process::child::fork_capture( child_write_pid); const int pid = child->pid(); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); std::ifstream input("pidfile"); ATF_REQUIRE(input); int read_pid; input >> read_pid; input.close(); ATF_REQUIRE_EQ(read_pid, pid); } ATF_INIT_TEST_CASES(tcs) { utils::avoid_coredump_on_crash(); ATF_ADD_TEST_CASE(tcs, child__fork_capture__ok_function); ATF_ADD_TEST_CASE(tcs, child__fork_capture__ok_functor); ATF_ADD_TEST_CASE(tcs, child__fork_capture__catch_exceptions); ATF_ADD_TEST_CASE(tcs, child__fork_capture__new_session); ATF_ADD_TEST_CASE(tcs, child__fork_capture__pipe_fail); ATF_ADD_TEST_CASE(tcs, child__fork_capture__fork_cannot_exit); ATF_ADD_TEST_CASE(tcs, child__fork_capture__fork_cannot_unwind); ATF_ADD_TEST_CASE(tcs, child__fork_capture__fork_fail); ATF_ADD_TEST_CASE(tcs, child__fork_files__ok_function); ATF_ADD_TEST_CASE(tcs, child__fork_files__ok_functor); ATF_ADD_TEST_CASE(tcs, child__fork_files__catch_exceptions); ATF_ADD_TEST_CASE(tcs, child__fork_files__new_session); ATF_ADD_TEST_CASE(tcs, child__fork_files__inherit_stdout); ATF_ADD_TEST_CASE(tcs, child__fork_files__inherit_stderr); ATF_ADD_TEST_CASE(tcs, child__fork_files__fork_cannot_exit); ATF_ADD_TEST_CASE(tcs, child__fork_files__fork_cannot_unwind); ATF_ADD_TEST_CASE(tcs, child__fork_files__fork_fail); ATF_ADD_TEST_CASE(tcs, child__fork_files__create_stdout_fail); ATF_ADD_TEST_CASE(tcs, child__fork_files__create_stderr_fail); ATF_ADD_TEST_CASE(tcs, child__spawn__absolute_path); ATF_ADD_TEST_CASE(tcs, child__spawn__relative_path); ATF_ADD_TEST_CASE(tcs, child__spawn__basename_only); ATF_ADD_TEST_CASE(tcs, child__spawn__no_path); ATF_ADD_TEST_CASE(tcs, child__spawn__no_args); ATF_ADD_TEST_CASE(tcs, child__spawn__some_args); ATF_ADD_TEST_CASE(tcs, child__spawn__missing_program); ATF_ADD_TEST_CASE(tcs, child__pid); } diff --git a/contrib/kyua/utils/process/deadline_killer_test.cpp b/contrib/kyua/utils/process/deadline_killer_test.cpp index 06c89660ac31..56ac31d3dda0 100644 --- a/contrib/kyua/utils/process/deadline_killer_test.cpp +++ b/contrib/kyua/utils/process/deadline_killer_test.cpp @@ -1,108 +1,108 @@ // Copyright 2015 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/process/deadline_killer.hpp" extern "C" { #include #include } #include #include #include "utils/datetime.hpp" #include "utils/process/child.ipp" #include "utils/process/status.hpp" namespace datetime = utils::datetime; namespace process = utils::process; namespace { /// Body of a child process that sleeps and then exits. /// /// \tparam Seconds The delay the subprocess has to sleep for. template< int Seconds > static void child_sleep(void) { ::sleep(Seconds); std::exit(EXIT_SUCCESS); } } // anonymous namespace ATF_TEST_CASE_WITHOUT_HEAD(activation); ATF_TEST_CASE_BODY(activation) { - std::auto_ptr< process::child > child = process::child::fork_capture( + std::unique_ptr< process::child > child = process::child::fork_capture( child_sleep< 60 >); datetime::timestamp start = datetime::timestamp::now(); process::deadline_killer killer(datetime::delta(1, 0), child->pid()); const process::status status = child->wait(); killer.unprogram(); datetime::timestamp end = datetime::timestamp::now(); ATF_REQUIRE(killer.fired()); ATF_REQUIRE(end - start <= datetime::delta(10, 0)); ATF_REQUIRE(status.signaled()); ATF_REQUIRE_EQ(SIGKILL, status.termsig()); } ATF_TEST_CASE_WITHOUT_HEAD(no_activation); ATF_TEST_CASE_BODY(no_activation) { - std::auto_ptr< process::child > child = process::child::fork_capture( + std::unique_ptr< process::child > child = process::child::fork_capture( child_sleep< 1 >); datetime::timestamp start = datetime::timestamp::now(); process::deadline_killer killer(datetime::delta(60, 0), child->pid()); const process::status status = child->wait(); killer.unprogram(); datetime::timestamp end = datetime::timestamp::now(); ATF_REQUIRE(!killer.fired()); ATF_REQUIRE(end - start <= datetime::delta(10, 0)); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, activation); ATF_ADD_TEST_CASE(tcs, no_activation); } diff --git a/contrib/kyua/utils/process/executor.cpp b/contrib/kyua/utils/process/executor.cpp index b73a86b9c1b9..843c9d862304 100644 --- a/contrib/kyua/utils/process/executor.cpp +++ b/contrib/kyua/utils/process/executor.cpp @@ -1,937 +1,937 @@ // Copyright 2015 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/process/executor.ipp" #if defined(HAVE_CONFIG_H) #include "config.h" #endif extern "C" { #include #include #include } #include #include #include #include #include #include #include "utils/datetime.hpp" #include "utils/format/macros.hpp" #include "utils/fs/auto_cleaners.hpp" #include "utils/fs/exceptions.hpp" #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" #include "utils/logging/macros.hpp" #include "utils/logging/operations.hpp" #include "utils/noncopyable.hpp" #include "utils/optional.ipp" #include "utils/passwd.hpp" #include "utils/process/child.ipp" #include "utils/process/deadline_killer.hpp" #include "utils/process/isolation.hpp" #include "utils/process/operations.hpp" #include "utils/process/status.hpp" #include "utils/sanity.hpp" #include "utils/signals/interrupts.hpp" #include "utils/signals/timer.hpp" namespace datetime = utils::datetime; namespace executor = utils::process::executor; namespace fs = utils::fs; namespace logging = utils::logging; namespace passwd = utils::passwd; namespace process = utils::process; namespace signals = utils::signals; using utils::none; using utils::optional; namespace { /// Template for temporary directories created by the executor. static const char* work_directory_template = PACKAGE_TARNAME ".XXXXXX"; /// Mapping of active subprocess PIDs to their execution data. typedef std::map< int, executor::exec_handle > exec_handles_map; } // anonymous namespace /// Basename of the file containing the stdout of the subprocess. const char* utils::process::executor::detail::stdout_name = "stdout.txt"; /// Basename of the file containing the stderr of the subprocess. const char* utils::process::executor::detail::stderr_name = "stderr.txt"; /// Basename of the subdirectory in which the subprocess is actually executed. /// /// This is a subdirectory of the "unique work directory" generated for the /// subprocess so that our code can create control files on disk and not /// get them clobbered by the subprocess's activity. const char* utils::process::executor::detail::work_subdir = "work"; /// Prepares a subprocess to run a user-provided hook in a controlled manner. /// /// \param unprivileged_user User to switch to if not none. /// \param control_directory Path to the subprocess-specific control directory. /// \param work_directory Path to the subprocess-specific work directory. void utils::process::executor::detail::setup_child( const optional< passwd::user > unprivileged_user, const fs::path& control_directory, const fs::path& work_directory) { logging::set_inmemory(); process::isolate_path(unprivileged_user, control_directory); process::isolate_child(unprivileged_user, work_directory); } /// Internal implementation for the exec_handle class. struct utils::process::executor::exec_handle::impl : utils::noncopyable { /// PID of the process being run. int pid; /// Path to the subprocess-specific work directory. fs::path control_directory; /// Path to the subprocess's stdout file. const fs::path stdout_file; /// Path to the subprocess's stderr file. const fs::path stderr_file; /// Start time. datetime::timestamp start_time; /// User the subprocess is running as if different than the current one. const optional< passwd::user > unprivileged_user; /// Timer to kill the subprocess on activation. process::deadline_killer timer; /// Number of owners of the on-disk state. executor::detail::refcnt_t state_owners; /// Constructor. /// /// \param pid_ PID of the forked process. /// \param control_directory_ Path to the subprocess-specific work /// directory. /// \param stdout_file_ Path to the subprocess's stdout file. /// \param stderr_file_ Path to the subprocess's stderr file. /// \param start_time_ Timestamp of when this object was constructed. /// \param timeout Maximum amount of time the subprocess can run for. /// \param unprivileged_user_ User the subprocess is running as if /// different than the current one. /// \param [in,out] state_owners_ Number of owners of the on-disk state. /// For first-time processes, this should be a new counter set to 0; /// for followup processes, this should point to the same counter used /// by the preceding process. impl(const int pid_, const fs::path& control_directory_, const fs::path& stdout_file_, const fs::path& stderr_file_, const datetime::timestamp& start_time_, const datetime::delta& timeout, const optional< passwd::user > unprivileged_user_, executor::detail::refcnt_t state_owners_) : pid(pid_), control_directory(control_directory_), stdout_file(stdout_file_), stderr_file(stderr_file_), start_time(start_time_), unprivileged_user(unprivileged_user_), timer(timeout, pid_), state_owners(state_owners_) { (*state_owners)++; POST(*state_owners > 0); } }; /// Constructor. /// /// \param pimpl Constructed internal implementation. executor::exec_handle::exec_handle(std::shared_ptr< impl > pimpl) : _pimpl(pimpl) { } /// Destructor. executor::exec_handle::~exec_handle(void) { } /// Returns the PID of the process being run. /// /// \return A PID. int executor::exec_handle::pid(void) const { return _pimpl->pid; } /// Returns the path to the subprocess-specific control directory. /// /// This is where the executor may store control files. /// /// \return The path to a directory that exists until cleanup() is called. fs::path executor::exec_handle::control_directory(void) const { return _pimpl->control_directory; } /// Returns the path to the subprocess-specific work directory. /// /// This is guaranteed to be clear of files created by the executor. /// /// \return The path to a directory that exists until cleanup() is called. fs::path executor::exec_handle::work_directory(void) const { return _pimpl->control_directory / detail::work_subdir; } /// Returns the path to the subprocess's stdout file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& executor::exec_handle::stdout_file(void) const { return _pimpl->stdout_file; } /// Returns the path to the subprocess's stderr file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& executor::exec_handle::stderr_file(void) const { return _pimpl->stderr_file; } /// Internal implementation for the exit_handle class. struct utils::process::executor::exit_handle::impl : utils::noncopyable { /// Original PID of the terminated subprocess. /// /// Note that this PID is no longer valid and cannot be used on system /// tables! const int original_pid; /// Termination status of the subprocess, or none if it timed out. const optional< process::status > status; /// The user the process ran as, if different than the current one. const optional< passwd::user > unprivileged_user; /// Timestamp of when the subprocess was spawned. const datetime::timestamp start_time; /// Timestamp of when wait() or wait_any() returned this object. const datetime::timestamp end_time; /// Path to the subprocess-specific work directory. const fs::path control_directory; /// Path to the subprocess's stdout file. const fs::path stdout_file; /// Path to the subprocess's stderr file. const fs::path stderr_file; /// Number of owners of the on-disk state. /// /// This will be 1 if this exit_handle is the last holder of the on-disk /// state, in which case cleanup() invocations will wipe the disk state. /// For all other cases, this will hold a higher value. detail::refcnt_t state_owners; /// Mutable pointer to the corresponding executor state. /// /// This object references a member of the executor_handle that yielded this /// exit_handle instance. We need this direct access to clean up after /// ourselves when the handle is destroyed. exec_handles_map& all_exec_handles; /// Whether the subprocess state has been cleaned yet or not. /// /// Used to keep track of explicit calls to the public cleanup(). bool cleaned; /// Constructor. /// /// \param original_pid_ Original PID of the terminated subprocess. /// \param status_ Termination status of the subprocess, or none if /// timed out. /// \param unprivileged_user_ The user the process ran as, if different than /// the current one. /// \param start_time_ Timestamp of when the subprocess was spawned. /// \param end_time_ Timestamp of when wait() or wait_any() returned this /// object. /// \param control_directory_ Path to the subprocess-specific work /// directory. /// \param stdout_file_ Path to the subprocess's stdout file. /// \param stderr_file_ Path to the subprocess's stderr file. /// \param [in,out] state_owners_ Number of owners of the on-disk state. /// \param [in,out] all_exec_handles_ Global object keeping track of all /// active executions for an executor. This is a pointer to a member of /// the executor_handle object. impl(const int original_pid_, const optional< process::status > status_, const optional< passwd::user > unprivileged_user_, const datetime::timestamp& start_time_, const datetime::timestamp& end_time_, const fs::path& control_directory_, const fs::path& stdout_file_, const fs::path& stderr_file_, detail::refcnt_t state_owners_, exec_handles_map& all_exec_handles_) : original_pid(original_pid_), status(status_), unprivileged_user(unprivileged_user_), start_time(start_time_), end_time(end_time_), control_directory(control_directory_), stdout_file(stdout_file_), stderr_file(stderr_file_), state_owners(state_owners_), all_exec_handles(all_exec_handles_), cleaned(false) { } /// Destructor. ~impl(void) { if (!cleaned) { LW(F("Implicitly cleaning up exit_handle for exec_handle %s; " "ignoring errors!") % original_pid); try { cleanup(); } catch (const std::runtime_error& error) { LE(F("Subprocess cleanup failed: %s") % error.what()); } } } /// Cleans up the subprocess on-disk state. /// /// \throw engine::error If the cleanup fails, especially due to the /// inability to remove the work directory. void cleanup(void) { PRE(*state_owners > 0); if (*state_owners == 1) { LI(F("Cleaning up exit_handle for exec_handle %s") % original_pid); fs::rm_r(control_directory); } else { LI(F("Not cleaning up exit_handle for exec_handle %s; " "%s owners left") % original_pid % (*state_owners - 1)); } // We must decrease our reference only after we have successfully // cleaned up the control directory. Otherwise, the rm_r call would // throw an exception, which would in turn invoke the implicit cleanup // from the destructor, which would make us crash due to an invalid // reference count. (*state_owners)--; // Marking this object as clean here, even if we did not do actually the // cleaning above, is fine (albeit a bit confusing). Note that "another // owner" refers to a handle for a different PID, so that handle will be // the one issuing the cleanup. all_exec_handles.erase(original_pid); cleaned = true; } }; /// Constructor. /// /// \param pimpl Constructed internal implementation. executor::exit_handle::exit_handle(std::shared_ptr< impl > pimpl) : _pimpl(pimpl) { } /// Destructor. executor::exit_handle::~exit_handle(void) { } /// Cleans up the subprocess status. /// /// This function should be called explicitly as it provides the means to /// control any exceptions raised during cleanup. Do not rely on the destructor /// to clean things up. /// /// \throw engine::error If the cleanup fails, especially due to the inability /// to remove the work directory. void executor::exit_handle::cleanup(void) { PRE(!_pimpl->cleaned); _pimpl->cleanup(); POST(_pimpl->cleaned); } /// Gets the current number of owners of the on-disk data. /// /// \return A shared reference counter. Even though this function is marked as /// const, the return value is intentionally mutable because we need to update /// reference counts from different but related processes. This is why this /// method is not public. std::shared_ptr< std::size_t > executor::exit_handle::state_owners(void) const { return _pimpl->state_owners; } /// Returns the original PID corresponding to the terminated subprocess. /// /// \return An exec_handle. int executor::exit_handle::original_pid(void) const { return _pimpl->original_pid; } /// Returns the process termination status of the subprocess. /// /// \return A process termination status, or none if the subprocess timed out. const optional< process::status >& executor::exit_handle::status(void) const { return _pimpl->status; } /// Returns the user the process ran as if different than the current one. /// /// \return None if the credentials of the process were the same as the current /// one, or else a user. const optional< passwd::user >& executor::exit_handle::unprivileged_user(void) const { return _pimpl->unprivileged_user; } /// Returns the timestamp of when the subprocess was spawned. /// /// \return A timestamp. const datetime::timestamp& executor::exit_handle::start_time(void) const { return _pimpl->start_time; } /// Returns the timestamp of when wait() or wait_any() returned this object. /// /// \return A timestamp. const datetime::timestamp& executor::exit_handle::end_time(void) const { return _pimpl->end_time; } /// Returns the path to the subprocess-specific control directory. /// /// This is where the executor may store control files. /// /// \return The path to a directory that exists until cleanup() is called. fs::path executor::exit_handle::control_directory(void) const { return _pimpl->control_directory; } /// Returns the path to the subprocess-specific work directory. /// /// This is guaranteed to be clear of files created by the executor. /// /// \return The path to a directory that exists until cleanup() is called. fs::path executor::exit_handle::work_directory(void) const { return _pimpl->control_directory / detail::work_subdir; } /// Returns the path to the subprocess's stdout file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& executor::exit_handle::stdout_file(void) const { return _pimpl->stdout_file; } /// Returns the path to the subprocess's stderr file. /// /// \return The path to a file that exists until cleanup() is called. const fs::path& executor::exit_handle::stderr_file(void) const { return _pimpl->stderr_file; } /// Internal implementation for the executor_handle. /// /// Because the executor is a singleton, these essentially is a container for /// global variables. struct utils::process::executor::executor_handle::impl : utils::noncopyable { /// Numeric counter of executed subprocesses. /// /// This is used to generate a unique identifier for each subprocess as an /// easy mechanism to discern their unique work directories. size_t last_subprocess; /// Interrupts handler. - std::auto_ptr< signals::interrupts_handler > interrupts_handler; + std::unique_ptr< signals::interrupts_handler > interrupts_handler; /// Root work directory for all executed subprocesses. - std::auto_ptr< fs::auto_directory > root_work_directory; + std::unique_ptr< fs::auto_directory > root_work_directory; /// Mapping of PIDs to the data required at run time. exec_handles_map all_exec_handles; /// Former members of all_exec_handles removed due to PID reuse. std::forward_list stale_exec_handles; /// Whether the executor state has been cleaned yet or not. /// /// Used to keep track of explicit calls to the public cleanup(). bool cleaned; /// Constructor. impl(void) : last_subprocess(0), interrupts_handler(new signals::interrupts_handler()), root_work_directory(new fs::auto_directory( fs::auto_directory::mkdtemp_public(work_directory_template))), all_exec_handles(), stale_exec_handles(), cleaned(false) { } /// Destructor. ~impl(void) { if (!cleaned) { LW("Implicitly cleaning up executor; ignoring errors!"); try { cleanup(); cleaned = true; } catch (const std::runtime_error& error) { LE(F("Executor global cleanup failed: %s") % error.what()); } } } /// Cleans up the executor state. void cleanup(void) { PRE(!cleaned); for (exec_handles_map::const_iterator iter = all_exec_handles.begin(); iter != all_exec_handles.end(); ++iter) { const int& pid = (*iter).first; const exec_handle& data = (*iter).second; process::terminate_group(pid); int status; if (::waitpid(pid, &status, 0) == -1) { // Should not happen. LW(F("Failed to wait for PID %s") % pid); } try { fs::rm_r(data.control_directory()); } catch (const fs::error& e) { LE(F("Failed to clean up subprocess work directory %s: %s") % data.control_directory() % e.what()); } } all_exec_handles.clear(); for (auto iter : stale_exec_handles) { // The process already exited, so no need to kill and wait. try { fs::rm_r(iter.control_directory()); } catch (const fs::error& e) { LE(F("Failed to clean up stale subprocess work directory " "%s: %s") % iter.control_directory() % e.what()); } } stale_exec_handles.clear(); try { // The following only causes the work directory to be deleted, not // any of its contents, so we expect this to always succeed. This // *should* be sufficient because, in the loop above, we have // individually wiped the subdirectories of any still-unclean // subprocesses. root_work_directory->cleanup(); } catch (const fs::error& e) { LE(F("Failed to clean up executor work directory %s: %s; " "this could be an internal error or a buggy test") % root_work_directory->directory() % e.what()); } root_work_directory.reset(NULL); interrupts_handler->unprogram(); interrupts_handler.reset(NULL); } /// Common code to run after any of the wait calls. /// /// \param original_pid The PID of the terminated subprocess. /// \param status The exit status of the terminated subprocess. /// /// \return A pointer to an object describing the waited-for subprocess. executor::exit_handle post_wait(const int original_pid, const process::status& status) { PRE(original_pid == status.dead_pid()); LI(F("Waited for subprocess with exec_handle %s") % original_pid); process::terminate_group(status.dead_pid()); const exec_handles_map::iterator iter = all_exec_handles.find( original_pid); exec_handle& data = (*iter).second; data._pimpl->timer.unprogram(); // It is tempting to assert here (and old code did) that, if the timer // has fired, the process has been forcibly killed by us. This is not // always the case though: for short-lived processes and with very short // timeouts (think 1ms), it is possible for scheduling decisions to // allow the subprocess to finish while at the same time cause the timer // to fire. So we do not assert this any longer and just rely on the // timer expiration to check if the process timed out or not. If the // process did finish but the timer expired... oh well, we do not detect // this correctly but we don't care because this should not really // happen. if (!fs::exists(data.stdout_file())) { std::ofstream new_stdout(data.stdout_file().c_str()); } if (!fs::exists(data.stderr_file())) { std::ofstream new_stderr(data.stderr_file().c_str()); } return exit_handle(std::shared_ptr< exit_handle::impl >( new exit_handle::impl( data.pid(), data._pimpl->timer.fired() ? none : utils::make_optional(status), data._pimpl->unprivileged_user, data._pimpl->start_time, datetime::timestamp::now(), data.control_directory(), data.stdout_file(), data.stderr_file(), data._pimpl->state_owners, all_exec_handles))); } executor::exit_handle reap(const pid_t original_pid) { const exec_handles_map::iterator iter = all_exec_handles.find( original_pid); exec_handle& data = (*iter).second; data._pimpl->timer.unprogram(); if (!fs::exists(data.stdout_file())) { std::ofstream new_stdout(data.stdout_file().c_str()); } if (!fs::exists(data.stderr_file())) { std::ofstream new_stderr(data.stderr_file().c_str()); } return exit_handle(std::shared_ptr< exit_handle::impl >( new exit_handle::impl( data.pid(), none, data._pimpl->unprivileged_user, data._pimpl->start_time, datetime::timestamp::now(), data.control_directory(), data.stdout_file(), data.stderr_file(), data._pimpl->state_owners, all_exec_handles))); } }; /// Constructor. executor::executor_handle::executor_handle(void) throw() : _pimpl(new impl()) { } /// Destructor. executor::executor_handle::~executor_handle(void) { } /// Queries the path to the root of the work directory for all subprocesses. /// /// \return A path. const fs::path& executor::executor_handle::root_work_directory(void) const { return _pimpl->root_work_directory->directory(); } /// Cleans up the executor state. /// /// This function should be called explicitly as it provides the means to /// control any exceptions raised during cleanup. Do not rely on the destructor /// to clean things up. /// /// \throw engine::error If there are problems cleaning up the executor. void executor::executor_handle::cleanup(void) { PRE(!_pimpl->cleaned); _pimpl->cleanup(); _pimpl->cleaned = true; } /// Initializes the executor. /// /// \pre This function can only be called if there is no other executor_handle /// object alive. /// /// \return A handle to the operations of the executor. executor::executor_handle executor::setup(void) { return executor_handle(); } /// Pre-helper for the spawn() method. /// /// \return The created control directory for the subprocess. fs::path executor::executor_handle::spawn_pre(void) { signals::check_interrupt(); ++_pimpl->last_subprocess; const fs::path control_directory = _pimpl->root_work_directory->directory() / (F("%s") % _pimpl->last_subprocess); fs::mkdir_p(control_directory / detail::work_subdir, 0755); return control_directory; } /// Post-helper for the spawn() method. /// /// \param control_directory Control directory as returned by spawn_pre(). /// \param stdout_file Path to the subprocess' stdout. /// \param stderr_file Path to the subprocess' stderr. /// \param timeout Maximum amount of time the subprocess can run for. /// \param unprivileged_user If not none, user to switch to before execution. /// \param child The process created by spawn(). /// /// \return The execution handle of the started subprocess. executor::exec_handle executor::executor_handle::spawn_post( const fs::path& control_directory, const fs::path& stdout_file, const fs::path& stderr_file, const datetime::delta& timeout, const optional< passwd::user > unprivileged_user, - std::auto_ptr< process::child > child) + std::unique_ptr< process::child > child) { const exec_handle handle(std::shared_ptr< exec_handle::impl >( new exec_handle::impl( child->pid(), control_directory, stdout_file, stderr_file, datetime::timestamp::now(), timeout, unprivileged_user, detail::refcnt_t(new detail::refcnt_t::element_type(0))))); const auto value = exec_handles_map::value_type(handle.pid(), handle); auto insert_pair = _pimpl->all_exec_handles.insert(value); if (!insert_pair.second) { LI(F("PID %s already in all_exec_handles") % handle.pid()); _pimpl->stale_exec_handles.push_front(insert_pair.first->second); _pimpl->all_exec_handles.erase(insert_pair.first); insert_pair = _pimpl->all_exec_handles.insert(value); INV_MSG(insert_pair.second, F("PID %s still in all_exec_handles") % handle.pid()); } LI(F("Spawned subprocess with exec_handle %s") % handle.pid()); return handle; } /// Pre-helper for the spawn_followup() method. void executor::executor_handle::spawn_followup_pre(void) { signals::check_interrupt(); } /// Post-helper for the spawn_followup() method. /// /// \param base Exit handle of the subprocess to use as context. /// \param timeout Maximum amount of time the subprocess can run for. /// \param child The process created by spawn_followup(). /// /// \return The execution handle of the started subprocess. executor::exec_handle executor::executor_handle::spawn_followup_post( const exit_handle& base, const datetime::delta& timeout, - std::auto_ptr< process::child > child) + std::unique_ptr< process::child > child) { INV(*base.state_owners() > 0); const exec_handle handle(std::shared_ptr< exec_handle::impl >( new exec_handle::impl( child->pid(), base.control_directory(), base.stdout_file(), base.stderr_file(), datetime::timestamp::now(), timeout, base.unprivileged_user(), base.state_owners()))); const auto value = exec_handles_map::value_type(handle.pid(), handle); auto insert_pair = _pimpl->all_exec_handles.insert(value); if (!insert_pair.second) { LI(F("PID %s already in all_exec_handles") % handle.pid()); _pimpl->stale_exec_handles.push_front(insert_pair.first->second); _pimpl->all_exec_handles.erase(insert_pair.first); insert_pair = _pimpl->all_exec_handles.insert(value); INV_MSG(insert_pair.second, F("PID %s still in all_exec_handles") % handle.pid()); } LI(F("Spawned subprocess with exec_handle %s") % handle.pid()); return handle; } /// Waits for completion of any forked process. /// /// \param exec_handle The handle of the process to wait for. /// /// \return A pointer to an object describing the waited-for subprocess. executor::exit_handle executor::executor_handle::wait(const exec_handle exec_handle) { signals::check_interrupt(); const process::status status = process::wait(exec_handle.pid()); return _pimpl->post_wait(exec_handle.pid(), status); } /// Waits for completion of any forked process. /// /// \return A pointer to an object describing the waited-for subprocess. executor::exit_handle executor::executor_handle::wait_any(void) { signals::check_interrupt(); const process::status status = process::wait_any(); return _pimpl->post_wait(status.dead_pid(), status); } /// Forms exit_handle for the given PID subprocess. /// /// Can be used in the cases when we want to do cleanup(s) of a killed test /// subprocess, but we do not have exit handle as we usually do after normal /// wait mechanism. /// /// \return A pointer to an object describing the subprocess. executor::exit_handle executor::executor_handle::reap(const int pid) { return _pimpl->reap(pid); } /// Checks if an interrupt has fired. /// /// Calls to this function should be sprinkled in strategic places through the /// code protected by an interrupts_handler object. /// /// This is just a wrapper over signals::check_interrupt() to avoid leaking this /// dependency to the caller. /// /// \throw signals::interrupted_error If there has been an interrupt. void executor::executor_handle::check_interrupt(void) const { signals::check_interrupt(); } diff --git a/contrib/kyua/utils/process/executor.hpp b/contrib/kyua/utils/process/executor.hpp index 01a17ff8c681..efc541d304b3 100644 --- a/contrib/kyua/utils/process/executor.hpp +++ b/contrib/kyua/utils/process/executor.hpp @@ -1,232 +1,232 @@ // Copyright 2015 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/process/executor.hpp /// Multiprogrammed process executor with isolation guarantees. /// /// This module provides a mechanism to invoke more than one process /// concurrently while at the same time ensuring that each process is run /// in a clean container and in a "safe" work directory that gets cleaned /// up automatically on termination. /// /// The intended workflow for using this module is the following: /// /// 1) Initialize the executor using setup(). Keep the returned object /// around through the lifetime of the next operations. Only one /// instance of the executor can be alive at once. /// 2) Spawn one or more processes with spawn(). On the caller side, keep /// track of any per-process data you may need using the returned /// exec_handle, which is unique among the set of active processes. /// 3) Call wait() or wait_any() to wait for completion of a process started /// in the previous step. Repeat as desired. /// 4) Use the returned exit_handle object by wait() or wait_any() to query /// the status of the terminated process and/or to access any of its /// data files. /// 5) Invoke cleanup() on the exit_handle to wipe any stale data. /// 6) Invoke cleanup() on the object returned by setup(). /// /// It is the responsibility of the caller to ensure that calls to /// spawn() and spawn_followup() are balanced with wait() and wait_any() calls. /// /// Processes executed in this manner have access to two different "unique" /// directories: the first is the "work directory", which is an empty directory /// that acts as the subprocess' work directory; the second is the "control /// directory", which is the location where the in-process code may place files /// that are not clobbered by activities in the work directory. #if !defined(UTILS_PROCESS_EXECUTOR_HPP) #define UTILS_PROCESS_EXECUTOR_HPP #include "utils/process/executor_fwd.hpp" #include #include #include "utils/datetime_fwd.hpp" #include "utils/fs/path_fwd.hpp" #include "utils/optional.hpp" #include "utils/passwd_fwd.hpp" #include "utils/process/child_fwd.hpp" #include "utils/process/status_fwd.hpp" namespace utils { namespace process { namespace executor { namespace detail { extern const char* stdout_name; extern const char* stderr_name; extern const char* work_subdir; /// Shared reference counter. typedef std::shared_ptr< std::size_t > refcnt_t; void setup_child(const utils::optional< utils::passwd::user >, const utils::fs::path&, const utils::fs::path&); } // namespace detail /// Maintenance data held while a subprocess is being executed. /// /// This data structure exists from the moment a subprocess is executed via /// executor::spawn() to when it is cleaned up with exit_handle::cleanup(). /// /// The caller NEED NOT maintain this object alive for the execution of the /// subprocess. However, the PID contained in here can be used to match /// exec_handle objects with corresponding exit_handle objects via their /// original_pid() method. /// /// Objects of this type can be copied around but their implementation is /// shared. The implication of this is that only the last copy of a given exit /// handle will execute the automatic cleanup() on destruction. class exec_handle { struct impl; /// Pointer to internal implementation. std::shared_ptr< impl > _pimpl; friend class executor_handle; exec_handle(std::shared_ptr< impl >); public: ~exec_handle(void); int pid(void) const; utils::fs::path control_directory(void) const; utils::fs::path work_directory(void) const; const utils::fs::path& stdout_file(void) const; const utils::fs::path& stderr_file(void) const; }; /// Container for the data of a process termination. /// /// This handle provides access to the details of the process that terminated /// and serves as the owner of the remaining on-disk files. The caller is /// expected to call cleanup() before destruction to remove the on-disk state. /// /// Objects of this type can be copied around but their implementation is /// shared. The implication of this is that only the last copy of a given exit /// handle will execute the automatic cleanup() on destruction. class exit_handle { struct impl; /// Pointer to internal implementation. std::shared_ptr< impl > _pimpl; friend class executor_handle; exit_handle(std::shared_ptr< impl >); detail::refcnt_t state_owners(void) const; public: ~exit_handle(void); void cleanup(void); int original_pid(void) const; const utils::optional< utils::process::status >& status(void) const; const utils::optional< utils::passwd::user >& unprivileged_user(void) const; const utils::datetime::timestamp& start_time() const; const utils::datetime::timestamp& end_time() const; utils::fs::path control_directory(void) const; utils::fs::path work_directory(void) const; const utils::fs::path& stdout_file(void) const; const utils::fs::path& stderr_file(void) const; }; /// Handler for the livelihood of the executor. /// /// Objects of this type can be copied around (because we do not have move /// semantics...) but their implementation is shared. Only one instance of the /// executor can exist at any point in time. class executor_handle { struct impl; /// Pointer to internal implementation. std::shared_ptr< impl > _pimpl; friend executor_handle setup(void); executor_handle(void) throw(); utils::fs::path spawn_pre(void); exec_handle spawn_post(const utils::fs::path&, const utils::fs::path&, const utils::fs::path&, const utils::datetime::delta&, const utils::optional< utils::passwd::user >, - std::auto_ptr< utils::process::child >); + std::unique_ptr< utils::process::child >); void spawn_followup_pre(void); exec_handle spawn_followup_post(const exit_handle&, const utils::datetime::delta&, - std::auto_ptr< utils::process::child >); + std::unique_ptr< utils::process::child >); public: ~executor_handle(void); const utils::fs::path& root_work_directory(void) const; void cleanup(void); template< class Hook > exec_handle spawn(Hook, const datetime::delta&, const utils::optional< utils::passwd::user >, const utils::optional< utils::fs::path > = utils::none, const utils::optional< utils::fs::path > = utils::none); template< class Hook > exec_handle spawn_followup(Hook, const exit_handle&, const datetime::delta&); exit_handle wait(const exec_handle); exit_handle wait_any(void); exit_handle reap(const pid_t); void check_interrupt(void) const; }; executor_handle setup(void); } // namespace executor } // namespace process } // namespace utils #endif // !defined(UTILS_PROCESS_EXECUTOR_HPP) diff --git a/contrib/kyua/utils/process/executor.ipp b/contrib/kyua/utils/process/executor.ipp index e91f994673d7..0fc8cd943da5 100644 --- a/contrib/kyua/utils/process/executor.ipp +++ b/contrib/kyua/utils/process/executor.ipp @@ -1,182 +1,182 @@ // Copyright 2015 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #if !defined(UTILS_PROCESS_EXECUTOR_IPP) #define UTILS_PROCESS_EXECUTOR_IPP #include "utils/process/executor.hpp" #include "utils/fs/path.hpp" #include "utils/optional.ipp" #include "utils/passwd.hpp" #include "utils/process/child.ipp" namespace utils { namespace process { namespace executor { namespace detail { /// Functor to execute a hook in a child process. /// /// The hook is executed after the process has been isolated per the logic in /// utils::process::isolation based on the input parameters at construction /// time. template< class Hook > class run_child { /// Function or functor to invoke in the child. Hook _hook; /// Directory where the hook may place control files. const fs::path& _control_directory; /// Directory to enter when running the subprocess. /// /// This is a subdirectory of _control_directory but is separate so that /// subprocess operations do not inadvertently affect our files. const fs::path& _work_directory; /// User to switch to when running the subprocess. /// /// If not none, the subprocess will be executed as the provided user and /// the control and work directories will be writable by this user. const optional< passwd::user > _unprivileged_user; public: /// Constructor. /// /// \param hook Function or functor to invoke in the child. /// \param control_directory Directory where control files can be placed. /// \param work_directory Directory to enter when running the subprocess. /// \param unprivileged_user If set, user to switch to before execution. run_child(Hook hook, const fs::path& control_directory, const fs::path& work_directory, const optional< passwd::user > unprivileged_user) : _hook(hook), _control_directory(control_directory), _work_directory(work_directory), _unprivileged_user(unprivileged_user) { } /// Body of the subprocess. void operator()(void) { executor::detail::setup_child(_unprivileged_user, _control_directory, _work_directory); _hook(_control_directory); } }; } // namespace detail } // namespace executor /// Forks and executes a subprocess asynchronously. /// /// \tparam Hook Type of the hook. /// \param hook Function or functor to run in the subprocess. /// \param timeout Maximum amount of time the subprocess can run for. /// \param unprivileged_user If not none, user to switch to before execution. /// \param stdout_target If not none, file to which to write the stdout of the /// test case. /// \param stderr_target If not none, file to which to write the stderr of the /// test case. /// /// \return A handle for the background operation. Used to match the result of /// the execution returned by wait_any() with this invocation. template< class Hook > executor::exec_handle executor::executor_handle::spawn( Hook hook, const datetime::delta& timeout, const optional< passwd::user > unprivileged_user, const optional< fs::path > stdout_target, const optional< fs::path > stderr_target) { const fs::path unique_work_directory = spawn_pre(); const fs::path stdout_path = stdout_target ? stdout_target.get() : (unique_work_directory / detail::stdout_name); const fs::path stderr_path = stderr_target ? stderr_target.get() : (unique_work_directory / detail::stderr_name); - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( detail::run_child< Hook >(hook, unique_work_directory, unique_work_directory / detail::work_subdir, unprivileged_user), stdout_path, stderr_path); return spawn_post(unique_work_directory, stdout_path, stderr_path, - timeout, unprivileged_user, child); + timeout, unprivileged_user, std::move(child)); } /// Forks and executes a subprocess asynchronously in the context of another. /// /// By context we understand the on-disk state of a previously-executed process, /// thus the new subprocess spawned by this function will run with the same /// control and work directories as another process. /// /// \tparam Hook Type of the hook. /// \param hook Function or functor to run in the subprocess. /// \param base Context of the subprocess in which to run this one. The /// exit_handle provided here must remain alive throughout the existence of /// this other object because the original exit_handle is the one that owns /// the on-disk state. /// \param timeout Maximum amount of time the subprocess can run for. /// /// \return A handle for the background operation. Used to match the result of /// the execution returned by wait_any() with this invocation. template< class Hook > executor::exec_handle executor::executor_handle::spawn_followup(Hook hook, const exit_handle& base, const datetime::delta& timeout) { spawn_followup_pre(); - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( detail::run_child< Hook >(hook, base.control_directory(), base.work_directory(), base.unprivileged_user()), base.stdout_file(), base.stderr_file()); - return spawn_followup_post(base, timeout, child); + return spawn_followup_post(base, timeout, std::move(child)); } } // namespace process } // namespace utils #endif // !defined(UTILS_PROCESS_EXECUTOR_IPP) diff --git a/contrib/kyua/utils/process/fdstream.hpp b/contrib/kyua/utils/process/fdstream.hpp index e785b0ac4282..bfcb16ec439e 100644 --- a/contrib/kyua/utils/process/fdstream.hpp +++ b/contrib/kyua/utils/process/fdstream.hpp @@ -1,66 +1,66 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/process/fdstream.hpp /// Provides the utils::process::ifdstream class. #if !defined(UTILS_PROCESS_FDSTREAM_HPP) #define UTILS_PROCESS_FDSTREAM_HPP #include "utils/process/fdstream_fwd.hpp" #include #include #include "utils/noncopyable.hpp" namespace utils { namespace process { /// An input stream backed by a file descriptor. /// /// This class grabs ownership of the file descriptor. I.e. when the class is /// destroyed, the file descriptor is closed unconditionally. class ifdstream : public std::istream, noncopyable { struct impl; /// Pointer to the shared internal implementation. - std::auto_ptr< impl > _pimpl; + std::unique_ptr< impl > _pimpl; public: explicit ifdstream(const int); ~ifdstream(void); }; } // namespace process } // namespace utils #endif // !defined(UTILS_PROCESS_FDSTREAM_HPP) diff --git a/contrib/kyua/utils/process/isolation_test.cpp b/contrib/kyua/utils/process/isolation_test.cpp index dc723cc65c88..44aa4729d51c 100644 --- a/contrib/kyua/utils/process/isolation_test.cpp +++ b/contrib/kyua/utils/process/isolation_test.cpp @@ -1,622 +1,622 @@ // Copyright 2014 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/process/isolation.hpp" extern "C" { #include #include #include #include } #include #include #include #include #include #include "utils/defs.hpp" #include "utils/env.hpp" #include "utils/format/macros.hpp" #include "utils/fs/operations.hpp" #include "utils/fs/path.hpp" #include "utils/optional.ipp" #include "utils/passwd.hpp" #include "utils/process/child.ipp" #include "utils/process/status.hpp" #include "utils/sanity.hpp" #include "utils/test_utils.ipp" namespace fs = utils::fs; namespace passwd = utils::passwd; namespace process = utils::process; using utils::none; using utils::optional; namespace { /// Runs the given hook in a subprocess. /// /// \param hook The code to run in the subprocess. /// /// \return The status of the subprocess for further validation. /// /// \post The subprocess.stdout and subprocess.stderr files, created in the /// current directory, contain the output of the subprocess. template< typename Hook > static process::status fork_and_run(Hook hook) { - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( hook, fs::path("subprocess.stdout"), fs::path("subprocess.stderr")); const process::status status = child->wait(); atf::utils::cat_file("subprocess.stdout", "isolated child stdout: "); atf::utils::cat_file("subprocess.stderr", "isolated child stderr: "); return status; } /// Subprocess that validates the cleanliness of the environment. /// /// \post Exits with success if the environment is clean; failure otherwise. static void check_clean_environment(void) { fs::mkdir(fs::path("some-directory"), 0755); process::isolate_child(none, fs::path("some-directory")); bool failed = false; const char* empty[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", NULL }; const char** iter; for (iter = empty; *iter != NULL; ++iter) { if (utils::getenv(*iter)) { failed = true; std::cout << F("%s was not unset\n") % *iter; } } if (utils::getenv_with_default("HOME", "") != "some-directory") { failed = true; std::cout << "HOME was not set to the work directory\n"; } if (utils::getenv_with_default("TMPDIR", "") != "some-directory") { failed = true; std::cout << "TMPDIR was not set to the work directory\n"; } if (utils::getenv_with_default("TZ", "") != "UTC") { failed = true; std::cout << "TZ was not set to UTC\n"; } if (utils::getenv_with_default("LEAVE_ME_ALONE", "") != "kill-some-day") { failed = true; std::cout << "LEAVE_ME_ALONE was modified while it should not have " "been\n"; } std::exit(failed ? EXIT_FAILURE : EXIT_SUCCESS); } /// Subprocess that checks if user privileges are dropped. class check_drop_privileges { /// The user to drop the privileges to. const passwd::user _unprivileged_user; public: /// Constructor. /// /// \param unprivileged_user The user to drop the privileges to. check_drop_privileges(const passwd::user& unprivileged_user) : _unprivileged_user(unprivileged_user) { } /// Body of the subprocess. /// /// \post Exits with success if the process has dropped privileges as /// expected. void operator()(void) const { fs::mkdir(fs::path("subdir"), 0755); process::isolate_child(utils::make_optional(_unprivileged_user), fs::path("subdir")); if (::getuid() == 0) { std::cout << "UID is still 0\n"; std::exit(EXIT_FAILURE); } if (::getgid() == 0) { std::cout << "GID is still 0\n"; std::exit(EXIT_FAILURE); } ::gid_t groups[1]; if (::getgroups(1, groups) == -1) { // Should only fail if we get more than one group notifying about // not enough space in the groups variable to store the whole // result. INV(errno == EINVAL); std::exit(EXIT_FAILURE); } if (groups[0] == 0) { std::cout << "Primary group is still 0\n"; std::exit(EXIT_FAILURE); } std::ofstream output("file.txt"); if (!output) { std::cout << "Cannot write to isolated directory; owner not " "changed?\n"; std::exit(EXIT_FAILURE); } std::exit(EXIT_SUCCESS); } }; /// Subprocess that dumps core to validate core dumping abilities. static void check_enable_core_dumps(void) { process::isolate_child(none, fs::path(".")); std::abort(); } /// Subprocess that checks if the work directory is entered. class check_enter_work_directory { /// Directory to enter. May be releative. const fs::path _directory; public: /// Constructor. /// /// \param directory Directory to enter. check_enter_work_directory(const fs::path& directory) : _directory(directory) { } /// Body of the subprocess. /// /// \post Exits with success if the process has entered the given work /// directory; false otherwise. void operator()(void) const { const fs::path exp_subdir = fs::current_path() / _directory; process::isolate_child(none, _directory); std::exit(fs::current_path() == exp_subdir ? EXIT_SUCCESS : EXIT_FAILURE); } }; /// Subprocess that validates that it owns a session. /// /// \post Exits with success if the process lives in its own session; /// failure otherwise. static void check_new_session(void) { process::isolate_child(none, fs::path(".")); std::exit(::getsid(::getpid()) == ::getpid() ? EXIT_SUCCESS : EXIT_FAILURE); } /// Subprocess that validates the disconnection from any terminal. /// /// \post Exits with success if the environment is clean; failure otherwise. static void check_no_terminal(void) { process::isolate_child(none, fs::path(".")); const char* const args[] = { "/bin/sh", "-i", "-c", "echo success", NULL }; ::execv("/bin/sh", UTILS_UNCONST(char*, args)); std::abort(); } /// Subprocess that validates that it has become the leader of a process group. /// /// \post Exits with success if the process lives in its own process group; /// failure otherwise. static void check_process_group(void) { process::isolate_child(none, fs::path(".")); std::exit(::getpgid(::getpid()) == ::getpid() ? EXIT_SUCCESS : EXIT_FAILURE); } /// Subprocess that validates that the umask has been reset. /// /// \post Exits with success if the umask matches the expected value; failure /// otherwise. static void check_umask(void) { process::isolate_child(none, fs::path(".")); std::exit(::umask(0) == 0022 ? EXIT_SUCCESS : EXIT_FAILURE); } } // anonymous namespace ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__clean_environment); ATF_TEST_CASE_BODY(isolate_child__clean_environment) { utils::setenv("HOME", "/non-existent/directory"); utils::setenv("TMPDIR", "/non-existent/directory"); utils::setenv("LANG", "C"); utils::setenv("LC_ALL", "C"); utils::setenv("LC_COLLATE", "C"); utils::setenv("LC_CTYPE", "C"); utils::setenv("LC_MESSAGES", "C"); utils::setenv("LC_MONETARY", "C"); utils::setenv("LC_NUMERIC", "C"); utils::setenv("LC_TIME", "C"); utils::setenv("LEAVE_ME_ALONE", "kill-some-day"); utils::setenv("TZ", "EST+5"); const process::status status = fork_and_run(check_clean_environment); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_TEST_CASE(isolate_child__other_user_when_unprivileged); ATF_TEST_CASE_HEAD(isolate_child__other_user_when_unprivileged) { set_md_var("require.user", "unprivileged"); } ATF_TEST_CASE_BODY(isolate_child__other_user_when_unprivileged) { const passwd::user user = passwd::current_user(); passwd::user other_user = user; other_user.uid += 1; other_user.gid += 1; process::isolate_child(utils::make_optional(other_user), fs::path(".")); ATF_REQUIRE_EQ(user.uid, ::getuid()); ATF_REQUIRE_EQ(user.gid, ::getgid()); } ATF_TEST_CASE(isolate_child__drop_privileges); ATF_TEST_CASE_HEAD(isolate_child__drop_privileges) { set_md_var("require.config", "unprivileged-user"); set_md_var("require.user", "root"); } ATF_TEST_CASE_BODY(isolate_child__drop_privileges) { const passwd::user unprivileged_user = passwd::find_user_by_name( get_config_var("unprivileged-user")); const process::status status = fork_and_run(check_drop_privileges( unprivileged_user)); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_TEST_CASE(isolate_child__drop_privileges_fail_uid); ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_uid) { set_md_var("require.user", "unprivileged"); } ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_uid) { // Fake the current user as root so that we bypass the protections in // isolate_child that prevent us from attempting a user switch when we are // not root. We do this so we can trigger the setuid failure. passwd::user root = passwd::user("root", 0, 0); ATF_REQUIRE(root.is_root()); passwd::set_current_user_for_testing(root); passwd::user unprivileged_user = passwd::current_user(); unprivileged_user.uid += 1; const process::status status = fork_and_run(check_drop_privileges( unprivileged_user)); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus()); ATF_REQUIRE(atf::utils::grep_file("(chown|setuid).*failed", "subprocess.stderr")); } ATF_TEST_CASE(isolate_child__drop_privileges_fail_gid); ATF_TEST_CASE_HEAD(isolate_child__drop_privileges_fail_gid) { set_md_var("require.user", "unprivileged"); } ATF_TEST_CASE_BODY(isolate_child__drop_privileges_fail_gid) { // Fake the current user as root so that we bypass the protections in // isolate_child that prevent us from attempting a user switch when we are // not root. We do this so we can trigger the setgid failure. passwd::user root = passwd::user("root", 0, 0); ATF_REQUIRE(root.is_root()); passwd::set_current_user_for_testing(root); passwd::user unprivileged_user = passwd::current_user(); unprivileged_user.gid += 1; const process::status status = fork_and_run(check_drop_privileges( unprivileged_user)); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus()); ATF_REQUIRE(atf::utils::grep_file("(chown|setgid).*failed", "subprocess.stderr")); } ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enable_core_dumps); ATF_TEST_CASE_BODY(isolate_child__enable_core_dumps) { utils::require_run_coredump_tests(this); struct ::rlimit rl; if (::getrlimit(RLIMIT_CORE, &rl) == -1) fail("Failed to query the core size limit"); if (rl.rlim_cur == 0 || rl.rlim_max == 0) skip("Maximum core size is zero; cannot run test"); rl.rlim_cur = 0; if (::setrlimit(RLIMIT_CORE, &rl) == -1) fail("Failed to lower the core size limit"); const process::status status = fork_and_run(check_enable_core_dumps); ATF_REQUIRE(status.signaled()); ATF_REQUIRE(status.coredump()); } ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enter_work_directory); ATF_TEST_CASE_BODY(isolate_child__enter_work_directory) { const fs::path directory("some/sub/directory"); fs::mkdir_p(directory, 0755); const process::status status = fork_and_run( check_enter_work_directory(directory)); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__enter_work_directory_failure); ATF_TEST_CASE_BODY(isolate_child__enter_work_directory_failure) { const fs::path directory("some/sub/directory"); const process::status status = fork_and_run( check_enter_work_directory(directory)); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(process::exit_isolation_failure, status.exitstatus()); ATF_REQUIRE(atf::utils::grep_file("chdir\\(some/sub/directory\\) failed", "subprocess.stderr")); } ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__new_session); ATF_TEST_CASE_BODY(isolate_child__new_session) { const process::status status = fork_and_run(check_new_session); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__no_terminal); ATF_TEST_CASE_BODY(isolate_child__no_terminal) { const process::status status = fork_and_run(check_no_terminal); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__process_group); ATF_TEST_CASE_BODY(isolate_child__process_group) { const process::status status = fork_and_run(check_process_group); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(isolate_child__reset_umask); ATF_TEST_CASE_BODY(isolate_child__reset_umask) { const process::status status = fork_and_run(check_umask); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } /// Executes isolate_path() and compares the on-disk changes to expected values. /// /// \param unprivileged_user The user to pass to isolate_path; may be none. /// \param exp_uid Expected UID or none to expect the old value. /// \param exp_gid Expected GID or none to expect the old value. static void do_isolate_path_test(const optional< passwd::user >& unprivileged_user, const optional< uid_t >& exp_uid, const optional< gid_t >& exp_gid) { const fs::path dir("dir"); fs::mkdir(dir, 0755); struct ::stat old_sb; ATF_REQUIRE(::stat(dir.c_str(), &old_sb) != -1); process::isolate_path(unprivileged_user, dir); struct ::stat new_sb; ATF_REQUIRE(::stat(dir.c_str(), &new_sb) != -1); if (exp_uid) ATF_REQUIRE_EQ(exp_uid.get(), new_sb.st_uid); else ATF_REQUIRE_EQ(old_sb.st_uid, new_sb.st_uid); if (exp_gid) ATF_REQUIRE_EQ(exp_gid.get(), new_sb.st_gid); else ATF_REQUIRE_EQ(old_sb.st_gid, new_sb.st_gid); } ATF_TEST_CASE_WITHOUT_HEAD(isolate_path__no_user); ATF_TEST_CASE_BODY(isolate_path__no_user) { do_isolate_path_test(none, none, none); } ATF_TEST_CASE_WITHOUT_HEAD(isolate_path__same_user); ATF_TEST_CASE_BODY(isolate_path__same_user) { do_isolate_path_test(utils::make_optional(passwd::current_user()), none, none); } ATF_TEST_CASE(isolate_path__other_user_when_unprivileged); ATF_TEST_CASE_HEAD(isolate_path__other_user_when_unprivileged) { set_md_var("require.user", "unprivileged"); } ATF_TEST_CASE_BODY(isolate_path__other_user_when_unprivileged) { passwd::user user = passwd::current_user(); user.uid += 1; user.gid += 1; do_isolate_path_test(utils::make_optional(user), none, none); } ATF_TEST_CASE(isolate_path__drop_privileges); ATF_TEST_CASE_HEAD(isolate_path__drop_privileges) { set_md_var("require.config", "unprivileged-user"); set_md_var("require.user", "root"); } ATF_TEST_CASE_BODY(isolate_path__drop_privileges) { const passwd::user unprivileged_user = passwd::find_user_by_name( get_config_var("unprivileged-user")); do_isolate_path_test(utils::make_optional(unprivileged_user), utils::make_optional(unprivileged_user.uid), utils::make_optional(unprivileged_user.gid)); } ATF_TEST_CASE(isolate_path__drop_privileges_only_uid); ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_uid) { set_md_var("require.config", "unprivileged-user"); set_md_var("require.user", "root"); } ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_uid) { passwd::user unprivileged_user = passwd::find_user_by_name( get_config_var("unprivileged-user")); unprivileged_user.gid = ::getgid(); do_isolate_path_test(utils::make_optional(unprivileged_user), utils::make_optional(unprivileged_user.uid), none); } ATF_TEST_CASE(isolate_path__drop_privileges_only_gid); ATF_TEST_CASE_HEAD(isolate_path__drop_privileges_only_gid) { set_md_var("require.config", "unprivileged-user"); set_md_var("require.user", "root"); } ATF_TEST_CASE_BODY(isolate_path__drop_privileges_only_gid) { passwd::user unprivileged_user = passwd::find_user_by_name( get_config_var("unprivileged-user")); unprivileged_user.uid = ::getuid(); do_isolate_path_test(utils::make_optional(unprivileged_user), none, utils::make_optional(unprivileged_user.gid)); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, isolate_child__clean_environment); ATF_ADD_TEST_CASE(tcs, isolate_child__other_user_when_unprivileged); ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges); ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges_fail_uid); ATF_ADD_TEST_CASE(tcs, isolate_child__drop_privileges_fail_gid); ATF_ADD_TEST_CASE(tcs, isolate_child__enable_core_dumps); ATF_ADD_TEST_CASE(tcs, isolate_child__enter_work_directory); ATF_ADD_TEST_CASE(tcs, isolate_child__enter_work_directory_failure); ATF_ADD_TEST_CASE(tcs, isolate_child__new_session); ATF_ADD_TEST_CASE(tcs, isolate_child__no_terminal); ATF_ADD_TEST_CASE(tcs, isolate_child__process_group); ATF_ADD_TEST_CASE(tcs, isolate_child__reset_umask); ATF_ADD_TEST_CASE(tcs, isolate_path__no_user); ATF_ADD_TEST_CASE(tcs, isolate_path__same_user); ATF_ADD_TEST_CASE(tcs, isolate_path__other_user_when_unprivileged); ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges); ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges_only_uid); ATF_ADD_TEST_CASE(tcs, isolate_path__drop_privileges_only_gid); } diff --git a/contrib/kyua/utils/process/operations_test.cpp b/contrib/kyua/utils/process/operations_test.cpp index e9c1ebb65a3d..d30dc890abd2 100644 --- a/contrib/kyua/utils/process/operations_test.cpp +++ b/contrib/kyua/utils/process/operations_test.cpp @@ -1,471 +1,471 @@ // Copyright 2014 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/process/operations.hpp" extern "C" { #include #include #include #include } #include #include #include #include "utils/defs.hpp" #include "utils/format/containers.ipp" #include "utils/fs/path.hpp" #include "utils/process/child.ipp" #include "utils/process/exceptions.hpp" #include "utils/process/status.hpp" #include "utils/stacktrace.hpp" #include "utils/test_utils.ipp" namespace fs = utils::fs; namespace process = utils::process; namespace { /// Type of the process::exec() and process::exec_unsafe() functions. typedef void (*exec_function)(const fs::path&, const process::args_vector&); /// Calculates the path to the test helpers binary. /// /// \param tc A pointer to the caller test case, needed to extract the value of /// the "srcdir" property. /// /// \return The path to the helpers binary. static fs::path get_helpers(const atf::tests::tc* tc) { return fs::path(tc->get_config_var("srcdir")) / "helpers"; } /// Body for a subprocess that runs exec(). class child_exec { /// Function to do the exec. const exec_function _do_exec; /// Path to the binary to exec. const fs::path& _program; /// Arguments to the binary, not including argv[0]. const process::args_vector& _args; public: /// Constructor. /// /// \param do_exec Function to do the exec. /// \param program Path to the binary to exec. /// \param args Arguments to the binary, not including argv[0]. child_exec(const exec_function do_exec, const fs::path& program, const process::args_vector& args) : _do_exec(do_exec), _program(program), _args(args) { } /// Body for the subprocess. void operator()(void) { _do_exec(_program, _args); } }; /// Body for a process that returns a specific exit code. /// /// \tparam ExitStatus The exit status for the subprocess. template< int ExitStatus > static void child_exit(void) { std::exit(ExitStatus); } static void suspend(void) UTILS_NORETURN; /// Blocks a subprocess from running indefinitely. static void suspend(void) { sigset_t mask; sigemptyset(&mask); for (;;) { ::sigsuspend(&mask); } } static void write_loop(const int) UTILS_NORETURN; /// Provides an infinite stream of data in a subprocess. /// /// \param fd Descriptor into which to write. static void write_loop(const int fd) { const int cookie = 0x12345678; for (;;) { std::cerr << "Still alive in PID " << ::getpid() << '\n'; if (::write(fd, &cookie, sizeof(cookie)) != sizeof(cookie)) std::exit(EXIT_FAILURE); ::sleep(1); } } } // anonymous namespace /// Tests an exec function with no arguments. /// /// \param tc The calling test case. /// \param do_exec The exec function to test. static void check_exec_no_args(const atf::tests::tc* tc, const exec_function do_exec) { - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_exec(do_exec, get_helpers(tc), process::args_vector()), fs::path("stdout"), fs::path("stderr")); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_FAILURE, status.exitstatus()); ATF_REQUIRE(atf::utils::grep_file("Must provide a helper name", "stderr")); } /// Tests an exec function with some arguments. /// /// \param tc The calling test case. /// \param do_exec The exec function to test. static void check_exec_some_args(const atf::tests::tc* tc, const exec_function do_exec) { process::args_vector args; args.push_back("print-args"); args.push_back("foo"); args.push_back("bar"); - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_exec(do_exec, get_helpers(tc), args), fs::path("stdout"), fs::path("stderr")); const process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); ATF_REQUIRE(atf::utils::grep_file("argv\\[1\\] = print-args", "stdout")); ATF_REQUIRE(atf::utils::grep_file("argv\\[2\\] = foo", "stdout")); ATF_REQUIRE(atf::utils::grep_file("argv\\[3\\] = bar", "stdout")); } ATF_TEST_CASE_WITHOUT_HEAD(exec__no_args); ATF_TEST_CASE_BODY(exec__no_args) { check_exec_no_args(this, process::exec); } ATF_TEST_CASE_WITHOUT_HEAD(exec__some_args); ATF_TEST_CASE_BODY(exec__some_args) { check_exec_some_args(this, process::exec); } ATF_TEST_CASE_WITHOUT_HEAD(exec__fail); ATF_TEST_CASE_BODY(exec__fail) { utils::avoid_coredump_on_crash(); - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( child_exec(process::exec, fs::path("non-existent"), process::args_vector()), fs::path("stdout"), fs::path("stderr")); const process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE_EQ(SIGABRT, status.termsig()); ATF_REQUIRE(atf::utils::grep_file("Failed to execute non-existent", "stderr")); } ATF_TEST_CASE_WITHOUT_HEAD(exec_unsafe__no_args); ATF_TEST_CASE_BODY(exec_unsafe__no_args) { check_exec_no_args(this, process::exec_unsafe); } ATF_TEST_CASE_WITHOUT_HEAD(exec_unsafe__some_args); ATF_TEST_CASE_BODY(exec_unsafe__some_args) { check_exec_some_args(this, process::exec_unsafe); } ATF_TEST_CASE_WITHOUT_HEAD(exec_unsafe__fail); ATF_TEST_CASE_BODY(exec_unsafe__fail) { ATF_REQUIRE_THROW_RE( process::system_error, "Failed to execute missing-program", process::exec_unsafe(fs::path("missing-program"), process::args_vector())); } ATF_TEST_CASE_WITHOUT_HEAD(terminate_group__setpgrp_executed); ATF_TEST_CASE_BODY(terminate_group__setpgrp_executed) { int first_fds[2], second_fds[2]; ATF_REQUIRE(::pipe(first_fds) != -1); ATF_REQUIRE(::pipe(second_fds) != -1); const pid_t pid = ::fork(); ATF_REQUIRE(pid != -1); if (pid == 0) { ::setpgid(::getpid(), ::getpid()); const pid_t pid2 = ::fork(); if (pid2 == -1) { std::exit(EXIT_FAILURE); } else if (pid2 == 0) { ::close(first_fds[0]); ::close(first_fds[1]); ::close(second_fds[0]); write_loop(second_fds[1]); } ::close(first_fds[0]); ::close(second_fds[0]); ::close(second_fds[1]); write_loop(first_fds[1]); } ::close(first_fds[1]); ::close(second_fds[1]); int dummy; std::cerr << "Waiting for children to start\n"; while (::read(first_fds[0], &dummy, sizeof(dummy)) <= 0 || ::read(second_fds[0], &dummy, sizeof(dummy)) <= 0) { // Wait for children to come up. } process::terminate_group(pid); std::cerr << "Waiting for children to die\n"; while (::read(first_fds[0], &dummy, sizeof(dummy)) > 0 || ::read(second_fds[0], &dummy, sizeof(dummy)) > 0) { // Wait for children to terminate. If they don't, then the test case // will time out. } int status; ATF_REQUIRE(::wait(&status) != -1); ATF_REQUIRE(WIFSIGNALED(status)); ATF_REQUIRE(WTERMSIG(status) == SIGKILL); } ATF_TEST_CASE_WITHOUT_HEAD(terminate_group__setpgrp_not_executed); ATF_TEST_CASE_BODY(terminate_group__setpgrp_not_executed) { const pid_t pid = ::fork(); ATF_REQUIRE(pid != -1); if (pid == 0) { // We do not call setgprp() here to simulate the race that happens when // we invoke terminate_group on a process that has not yet had a chance // to run the setpgrp() call. suspend(); } process::terminate_group(pid); int status; ATF_REQUIRE(::wait(&status) != -1); ATF_REQUIRE(WIFSIGNALED(status)); ATF_REQUIRE(WTERMSIG(status) == SIGKILL); } ATF_TEST_CASE_WITHOUT_HEAD(terminate_self_with__exitstatus); ATF_TEST_CASE_BODY(terminate_self_with__exitstatus) { const pid_t pid = ::fork(); ATF_REQUIRE(pid != -1); if (pid == 0) { const process::status status = process::status::fake_exited(123); process::terminate_self_with(status); } int status; ATF_REQUIRE(::wait(&status) != -1); ATF_REQUIRE(WIFEXITED(status)); ATF_REQUIRE(WEXITSTATUS(status) == 123); } ATF_TEST_CASE_WITHOUT_HEAD(terminate_self_with__termsig); ATF_TEST_CASE_BODY(terminate_self_with__termsig) { const pid_t pid = ::fork(); ATF_REQUIRE(pid != -1); if (pid == 0) { const process::status status = process::status::fake_signaled( SIGKILL, false); process::terminate_self_with(status); } int status; ATF_REQUIRE(::wait(&status) != -1); ATF_REQUIRE(WIFSIGNALED(status)); ATF_REQUIRE(WTERMSIG(status) == SIGKILL); ATF_REQUIRE(!WCOREDUMP(status)); } ATF_TEST_CASE_WITHOUT_HEAD(terminate_self_with__termsig_and_core); ATF_TEST_CASE_BODY(terminate_self_with__termsig_and_core) { utils::prepare_coredump_test(this); const pid_t pid = ::fork(); ATF_REQUIRE(pid != -1); if (pid == 0) { const process::status status = process::status::fake_signaled( SIGABRT, true); process::terminate_self_with(status); } int status; ATF_REQUIRE(::wait(&status) != -1); ATF_REQUIRE(WIFSIGNALED(status)); ATF_REQUIRE(WTERMSIG(status) == SIGABRT); ATF_REQUIRE(WCOREDUMP(status)); } ATF_TEST_CASE_WITHOUT_HEAD(wait__ok); ATF_TEST_CASE_BODY(wait__ok) { - std::auto_ptr< process::child > child = process::child::fork_capture( + std::unique_ptr< process::child > child = process::child::fork_capture( child_exit< 15 >); const pid_t pid = child->pid(); child.reset(); // Ensure there is no conflict between destructor and wait. const process::status status = process::wait(pid); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(15, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(wait__fail); ATF_TEST_CASE_BODY(wait__fail) { ATF_REQUIRE_THROW(process::system_error, process::wait(1)); } ATF_TEST_CASE_WITHOUT_HEAD(wait_any__one); ATF_TEST_CASE_BODY(wait_any__one) { process::child::fork_capture(child_exit< 15 >); const process::status status = process::wait_any(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(15, status.exitstatus()); } ATF_TEST_CASE_WITHOUT_HEAD(wait_any__many); ATF_TEST_CASE_BODY(wait_any__many) { process::child::fork_capture(child_exit< 15 >); process::child::fork_capture(child_exit< 30 >); process::child::fork_capture(child_exit< 45 >); std::set< int > exit_codes; for (int i = 0; i < 3; i++) { const process::status status = process::wait_any(); ATF_REQUIRE(status.exited()); exit_codes.insert(status.exitstatus()); } std::set< int > exp_exit_codes; exp_exit_codes.insert(15); exp_exit_codes.insert(30); exp_exit_codes.insert(45); ATF_REQUIRE_EQ(exp_exit_codes, exit_codes); } ATF_TEST_CASE_WITHOUT_HEAD(wait_any__none_is_failure); ATF_TEST_CASE_BODY(wait_any__none_is_failure) { try { const process::status status = process::wait_any(); fail("Expected exception but none raised"); } catch (const process::system_error& e) { ATF_REQUIRE(atf::utils::grep_string("Failed to wait", e.what())); ATF_REQUIRE_EQ(ECHILD, e.original_errno()); } } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, exec__no_args); ATF_ADD_TEST_CASE(tcs, exec__some_args); ATF_ADD_TEST_CASE(tcs, exec__fail); ATF_ADD_TEST_CASE(tcs, exec_unsafe__no_args); ATF_ADD_TEST_CASE(tcs, exec_unsafe__some_args); ATF_ADD_TEST_CASE(tcs, exec_unsafe__fail); ATF_ADD_TEST_CASE(tcs, terminate_group__setpgrp_executed); ATF_ADD_TEST_CASE(tcs, terminate_group__setpgrp_not_executed); ATF_ADD_TEST_CASE(tcs, terminate_self_with__exitstatus); ATF_ADD_TEST_CASE(tcs, terminate_self_with__termsig); ATF_ADD_TEST_CASE(tcs, terminate_self_with__termsig_and_core); ATF_ADD_TEST_CASE(tcs, wait__ok); ATF_ADD_TEST_CASE(tcs, wait__fail); ATF_ADD_TEST_CASE(tcs, wait_any__one); ATF_ADD_TEST_CASE(tcs, wait_any__many); ATF_ADD_TEST_CASE(tcs, wait_any__none_is_failure); } diff --git a/contrib/kyua/utils/process/systembuf.hpp b/contrib/kyua/utils/process/systembuf.hpp index c89c9108dc4b..7378fb8afbbc 100644 --- a/contrib/kyua/utils/process/systembuf.hpp +++ b/contrib/kyua/utils/process/systembuf.hpp @@ -1,71 +1,71 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/process/systembuf.hpp /// Provides the utils::process::systembuf class. #if !defined(UTILS_PROCESS_SYSTEMBUF_HPP) #define UTILS_PROCESS_SYSTEMBUF_HPP #include "utils/process/systembuf_fwd.hpp" #include #include #include #include "utils/noncopyable.hpp" namespace utils { namespace process { /// A std::streambuf implementation for raw file descriptors. /// /// This class grabs ownership of the file descriptor. I.e. when the class is /// destroyed, the file descriptor is closed unconditionally. class systembuf : public std::streambuf, noncopyable { struct impl; /// Pointer to the shared internal implementation. - std::auto_ptr< impl > _pimpl; + std::unique_ptr< impl > _pimpl; protected: int_type underflow(void); int_type overflow(int); int sync(void); public: explicit systembuf(const int, std::size_t = 8192); ~systembuf(void); }; } // namespace process } // namespace utils #endif // !defined(UTILS_PROCESS_SYSTEMBUF_HPP) diff --git a/contrib/kyua/utils/signals/interrupts.cpp b/contrib/kyua/utils/signals/interrupts.cpp index 956a83c66802..829df7cfbf1e 100644 --- a/contrib/kyua/utils/signals/interrupts.cpp +++ b/contrib/kyua/utils/signals/interrupts.cpp @@ -1,309 +1,309 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/signals/interrupts.hpp" extern "C" { #include #include #include } #include #include #include #include "utils/logging/macros.hpp" #include "utils/process/operations.hpp" #include "utils/sanity.hpp" #include "utils/signals/exceptions.hpp" #include "utils/signals/programmer.hpp" namespace signals = utils::signals; namespace process = utils::process; namespace { /// The interrupt signal that fired, or -1 if none. static volatile int fired_signal = -1; /// Collection of PIDs. typedef std::set< pid_t > pids_set; /// List of processes to kill upon reception of a signal. static pids_set pids_to_kill; /// Programmer status for the SIGHUP signal. -static std::auto_ptr< signals::programmer > sighup_handler; +static std::unique_ptr< signals::programmer > sighup_handler; /// Programmer status for the SIGINT signal. -static std::auto_ptr< signals::programmer > sigint_handler; +static std::unique_ptr< signals::programmer > sigint_handler; /// Programmer status for the SIGTERM signal. -static std::auto_ptr< signals::programmer > sigterm_handler; +static std::unique_ptr< signals::programmer > sigterm_handler; /// Signal mask to restore after exiting a signal inhibited section. static sigset_t global_old_sigmask; /// Whether there is an interrupts_handler object in existence or not. static bool interrupts_handler_active = false; /// Whether there is an interrupts_inhibiter object in existence or not. static std::size_t interrupts_inhibiter_active = 0; /// Generic handler to capture interrupt signals. /// /// From this handler, we record that an interrupt has happened so that /// check_interrupt() can know whether there execution has to be stopped or not. /// We also terminate any of our child processes (started by the /// utils::process::children class) so that any ongoing wait(2) system calls /// terminate. /// /// \param signo The signal that caused this handler to be called. static void signal_handler(const int signo) { static const char* message = "[-- Signal caught; please wait for " "cleanup --]\n"; if (::write(STDERR_FILENO, message, std::strlen(message)) == -1) { // We are exiting: the message printed here is only for informational // purposes. If we fail to print it (which probably means something // is really bad), there is not much we can do within the signal // handler, so just ignore this. } fired_signal = signo; for (pids_set::const_iterator iter = pids_to_kill.begin(); iter != pids_to_kill.end(); ++iter) { process::terminate_group(*iter); } } /// Installs signal handlers for potential interrupts. /// /// \pre Must not have been called before. /// \post The various sig*_handler global variables are atomically updated. static void setup_handlers(void) { PRE(sighup_handler.get() == NULL); PRE(sigint_handler.get() == NULL); PRE(sigterm_handler.get() == NULL); // Create the handlers on the stack first so that, if any of them fails, the // stack unwinding cleans things up. - std::auto_ptr< signals::programmer > tmp_sighup_handler( + std::unique_ptr< signals::programmer > tmp_sighup_handler( new signals::programmer(SIGHUP, signal_handler)); - std::auto_ptr< signals::programmer > tmp_sigint_handler( + std::unique_ptr< signals::programmer > tmp_sigint_handler( new signals::programmer(SIGINT, signal_handler)); - std::auto_ptr< signals::programmer > tmp_sigterm_handler( + std::unique_ptr< signals::programmer > tmp_sigterm_handler( new signals::programmer(SIGTERM, signal_handler)); // Now, update the global pointers, which is an operation that cannot fail. - sighup_handler = tmp_sighup_handler; - sigint_handler = tmp_sigint_handler; - sigterm_handler = tmp_sigterm_handler; + sighup_handler = std::move(tmp_sighup_handler); + sigint_handler = std::move(tmp_sigint_handler); + sigterm_handler = std::move(tmp_sigterm_handler); } /// Uninstalls the signal handlers installed by setup_handlers(). static void cleanup_handlers(void) { sighup_handler->unprogram(); sighup_handler.reset(NULL); sigint_handler->unprogram(); sigint_handler.reset(NULL); sigterm_handler->unprogram(); sigterm_handler.reset(NULL); } /// Masks the signals installed by setup_handlers(). /// /// \param[out] old_sigmask The old signal mask to save via the /// \code oset \endcode argument with sigprocmask(2). static void mask_signals(sigset_t* old_sigmask) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGALRM); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); const int ret = ::sigprocmask(SIG_BLOCK, &mask, old_sigmask); INV(ret != -1); } /// Resets the signal masking put in place by mask_signals(). /// /// \param[in] old_sigmask The old signal mask to restore via the /// \code set \endcode argument with sigprocmask(2). static void unmask_signals(sigset_t* old_sigmask) { const int ret = ::sigprocmask(SIG_SETMASK, old_sigmask, NULL); INV(ret != -1); } } // anonymous namespace /// Constructor that sets up the signal handlers. signals::interrupts_handler::interrupts_handler(void) : _programmed(false) { PRE(!interrupts_handler_active); setup_handlers(); _programmed = true; interrupts_handler_active = true; } /// Destructor that removes the signal handlers. /// /// Given that this is a destructor and it can't report errors back to the /// caller, the caller must attempt to call unprogram() on its own. signals::interrupts_handler::~interrupts_handler(void) { if (_programmed) { LW("Destroying still-programmed signals::interrupts_handler object"); try { unprogram(); } catch (const error& e) { UNREACHABLE; } } } /// Unprograms all signals captured by the interrupts handler. /// /// \throw system_error If the unprogramming of any signal fails. void signals::interrupts_handler::unprogram(void) { PRE(_programmed); // Modify the control variables first before unprogramming the handlers. If // we fail to do the latter, we do not want to try again because we will not // succeed (and we'll cause a crash due to failed preconditions). _programmed = false; interrupts_handler_active = false; cleanup_handlers(); fired_signal = -1; } /// Constructor that sets up signal masking. signals::interrupts_inhibiter::interrupts_inhibiter(void) { sigset_t old_sigmask; mask_signals(&old_sigmask); if (interrupts_inhibiter_active == 0) { global_old_sigmask = old_sigmask; } ++interrupts_inhibiter_active; } /// Destructor that removes signal masking. signals::interrupts_inhibiter::~interrupts_inhibiter(void) { if (interrupts_inhibiter_active > 1) { --interrupts_inhibiter_active; } else { interrupts_inhibiter_active = false; unmask_signals(&global_old_sigmask); } } /// Checks if an interrupt has fired. /// /// Calls to this function should be sprinkled in strategic places through the /// code protected by an interrupts_handler object. /// /// Only one call to this function will raise an exception per signal received. /// This is to allow executing cleanup actions without reraising interrupt /// exceptions unless the user has fired another interrupt. /// /// \throw interrupted_error If there has been an interrupt. void signals::check_interrupt(void) { if (fired_signal != -1) { const int original_fired_signal = fired_signal; fired_signal = -1; throw interrupted_error(original_fired_signal); } } /// Registers a child process to be killed upon reception of an interrupt. /// /// \pre Must be called with interrupts being inhibited. The caller must ensure /// that the call call to fork() and the addition of the PID happen atomically. /// /// \param pid The PID of the child process. Must not have been yet regsitered. void signals::add_pid_to_kill(const pid_t pid) { PRE(interrupts_inhibiter_active); PRE(pids_to_kill.find(pid) == pids_to_kill.end()); pids_to_kill.insert(pid); } /// Unregisters a child process previously registered via add_pid_to_kill(). /// /// \pre Must be called with interrupts being inhibited. This is not necessary, /// but pushing this to the caller simplifies our logic and provides consistency /// with the add_pid_to_kill() call. /// /// \param pid The PID of the child process. Must have been registered /// previously, and the process must have already been awaited for. void signals::remove_pid_to_kill(const pid_t pid) { PRE(interrupts_inhibiter_active); PRE(pids_to_kill.find(pid) != pids_to_kill.end()); pids_to_kill.erase(pid); } diff --git a/contrib/kyua/utils/signals/interrupts_test.cpp b/contrib/kyua/utils/signals/interrupts_test.cpp index ef8758d8d5f1..2342de06f9af 100644 --- a/contrib/kyua/utils/signals/interrupts_test.cpp +++ b/contrib/kyua/utils/signals/interrupts_test.cpp @@ -1,266 +1,266 @@ // Copyright 2012 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/signals/interrupts.hpp" extern "C" { #include #include } #include #include #include #include "utils/format/macros.hpp" #include "utils/fs/path.hpp" #include "utils/process/child.ipp" #include "utils/process/status.hpp" #include "utils/signals/exceptions.hpp" #include "utils/signals/programmer.hpp" namespace fs = utils::fs; namespace process = utils::process; namespace signals = utils::signals; namespace { /// Set to the signal that fired; -1 if none. static volatile int fired_signal = -1; /// Test handler for signals. /// /// \post fired_signal is set to the signal that triggered the handler. /// /// \param signo The signal that triggered the handler. static void signal_handler(const int signo) { PRE(fired_signal == -1 || fired_signal == signo); fired_signal = signo; } /// Child process that pauses waiting to be killed. static void pause_child(void) { sigset_t mask; sigemptyset(&mask); // We loop waiting for signals because we want the parent process to send us // a SIGKILL that we cannot handle, not just any non-deadly signal. for (;;) { std::cerr << F("Waiting for any signal; pid=%s\n") % ::getpid(); ::sigsuspend(&mask); std::cerr << F("Signal received; pid=%s\n") % ::getpid(); } } /// Checks that interrupts_handler() handles a particular signal. /// /// This indirectly checks the check_interrupt() function, which is not part of /// the class but is tightly related. /// /// \param signo The signal to check. /// \param explicit_unprogram Whether to call interrupts_handler::unprogram() /// explicitly before letting the object go out of scope. static void check_interrupts_handler(const int signo, const bool explicit_unprogram) { fired_signal = -1; signals::programmer test_handler(signo, signal_handler); { signals::interrupts_handler interrupts; // No pending interrupts at first. signals::check_interrupt(); // Send us an interrupt and check for it. ::kill(getpid(), signo); ATF_REQUIRE_THROW_RE(signals::interrupted_error, F("Interrupted by signal %s") % signo, signals::check_interrupt()); // Interrupts should have been cleared now, so this should not throw. signals::check_interrupt(); // Check to see if a second interrupt is detected. ::kill(getpid(), signo); ATF_REQUIRE_THROW_RE(signals::interrupted_error, F("Interrupted by signal %s") % signo, signals::check_interrupt()); // And ensure the interrupt was cleared again. signals::check_interrupt(); if (explicit_unprogram) { interrupts.unprogram(); } } ATF_REQUIRE_EQ(-1, fired_signal); ::kill(getpid(), signo); ATF_REQUIRE_EQ(signo, fired_signal); test_handler.unprogram(); } /// Checks that interrupts_inhibiter() handles a particular signal. /// /// \param signo The signal to check. static void check_interrupts_inhibiter(const int signo) { signals::programmer test_handler(signo, signal_handler); { signals::interrupts_inhibiter inhibiter; { signals::interrupts_inhibiter nested_inhibiter; ::kill(::getpid(), signo); ATF_REQUIRE_EQ(-1, fired_signal); } ::kill(::getpid(), signo); ATF_REQUIRE_EQ(-1, fired_signal); } ATF_REQUIRE_EQ(signo, fired_signal); test_handler.unprogram(); } } // anonymous namespace ATF_TEST_CASE_WITHOUT_HEAD(interrupts_handler__sighup); ATF_TEST_CASE_BODY(interrupts_handler__sighup) { // We run this twice in sequence to ensure that we can actually program two // interrupts handlers in a row. check_interrupts_handler(SIGHUP, true); check_interrupts_handler(SIGHUP, false); } ATF_TEST_CASE_WITHOUT_HEAD(interrupts_handler__sigint); ATF_TEST_CASE_BODY(interrupts_handler__sigint) { // We run this twice in sequence to ensure that we can actually program two // interrupts handlers in a row. check_interrupts_handler(SIGINT, true); check_interrupts_handler(SIGINT, false); } ATF_TEST_CASE_WITHOUT_HEAD(interrupts_handler__sigterm); ATF_TEST_CASE_BODY(interrupts_handler__sigterm) { // We run this twice in sequence to ensure that we can actually program two // interrupts handlers in a row. check_interrupts_handler(SIGTERM, true); check_interrupts_handler(SIGTERM, false); } ATF_TEST_CASE(interrupts_handler__kill_children); ATF_TEST_CASE_HEAD(interrupts_handler__kill_children) { set_md_var("timeout", "10"); } ATF_TEST_CASE_BODY(interrupts_handler__kill_children) { - std::auto_ptr< process::child > child1(process::child::fork_files( + std::unique_ptr< process::child > child1(process::child::fork_files( pause_child, fs::path("/dev/stdout"), fs::path("/dev/stderr"))); - std::auto_ptr< process::child > child2(process::child::fork_files( + std::unique_ptr< process::child > child2(process::child::fork_files( pause_child, fs::path("/dev/stdout"), fs::path("/dev/stderr"))); signals::interrupts_handler interrupts; // Our children pause until the reception of a signal. Interrupting // ourselves will cause the signal to be re-delivered to our children due to // the interrupts_handler semantics. If this does not happen, the wait // calls below would block indefinitely and cause our test to time out. ::kill(::getpid(), SIGHUP); const process::status status1 = child1->wait(); ATF_REQUIRE(status1.signaled()); ATF_REQUIRE_EQ(SIGKILL, status1.termsig()); const process::status status2 = child2->wait(); ATF_REQUIRE(status2.signaled()); ATF_REQUIRE_EQ(SIGKILL, status2.termsig()); } ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sigalrm); ATF_TEST_CASE_BODY(interrupts_inhibiter__sigalrm) { check_interrupts_inhibiter(SIGALRM); } ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sighup); ATF_TEST_CASE_BODY(interrupts_inhibiter__sighup) { check_interrupts_inhibiter(SIGHUP); } ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sigint); ATF_TEST_CASE_BODY(interrupts_inhibiter__sigint) { check_interrupts_inhibiter(SIGINT); } ATF_TEST_CASE_WITHOUT_HEAD(interrupts_inhibiter__sigterm); ATF_TEST_CASE_BODY(interrupts_inhibiter__sigterm) { check_interrupts_inhibiter(SIGTERM); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, interrupts_handler__sighup); ATF_ADD_TEST_CASE(tcs, interrupts_handler__sigint); ATF_ADD_TEST_CASE(tcs, interrupts_handler__sigterm); ATF_ADD_TEST_CASE(tcs, interrupts_handler__kill_children); ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sigalrm); ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sighup); ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sigint); ATF_ADD_TEST_CASE(tcs, interrupts_inhibiter__sigterm); } diff --git a/contrib/kyua/utils/signals/misc_test.cpp b/contrib/kyua/utils/signals/misc_test.cpp index 76f36b0e5082..3b6d57325ee1 100644 --- a/contrib/kyua/utils/signals/misc_test.cpp +++ b/contrib/kyua/utils/signals/misc_test.cpp @@ -1,133 +1,133 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/signals/misc.hpp" extern "C" { #include #include } #include #include #include "utils/defs.hpp" #include "utils/fs/path.hpp" #include "utils/process/child.ipp" #include "utils/process/status.hpp" #include "utils/signals/exceptions.hpp" namespace fs = utils::fs; namespace process = utils::process; namespace signals = utils::signals; namespace { static void program_reset_raise(void) UTILS_NORETURN; /// Body of a subprocess that tests the signals::reset function. /// /// This function programs a signal to be ignored, then uses signal::reset to /// bring it back to its default handler and then delivers the signal to self. /// The default behavior of the signal is for the process to die, so this /// function should never return correctly (and thus the child process should /// always die due to a signal if all goes well). static void program_reset_raise(void) { struct ::sigaction sa; sa.sa_handler = SIG_IGN; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; if (::sigaction(SIGUSR1, &sa, NULL) == -1) std::exit(EXIT_FAILURE); signals::reset(SIGUSR1); ::kill(::getpid(), SIGUSR1); // Should not be reached, but we do not assert this condition because we // want to exit cleanly if the signal does not abort our execution to let // the parent easily know what happened. std::exit(EXIT_SUCCESS); } /// Body of a subprocess that executes the signals::reset_all function. /// /// The process exits with success if the function worked, or with a failure if /// an error is reported. No signals are tested. static void run_reset_all(void) { const bool ok = signals::reset_all(); std::exit(ok ? EXIT_SUCCESS : EXIT_FAILURE); } } // anonymous namespace ATF_TEST_CASE_WITHOUT_HEAD(reset__ok); ATF_TEST_CASE_BODY(reset__ok) { - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( program_reset_raise, fs::path("/dev/stdout"), fs::path("/dev/stderr")); process::status status = child->wait(); ATF_REQUIRE(status.signaled()); ATF_REQUIRE_EQ(SIGUSR1, status.termsig()); } ATF_TEST_CASE_WITHOUT_HEAD(reset__invalid); ATF_TEST_CASE_BODY(reset__invalid) { ATF_REQUIRE_THROW(signals::system_error, signals::reset(-1)); } ATF_TEST_CASE_WITHOUT_HEAD(reset_all); ATF_TEST_CASE_BODY(reset_all) { - std::auto_ptr< process::child > child = process::child::fork_files( + std::unique_ptr< process::child > child = process::child::fork_files( run_reset_all, fs::path("/dev/stdout"), fs::path("/dev/stderr")); process::status status = child->wait(); ATF_REQUIRE(status.exited()); ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus()); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, reset__ok); ATF_ADD_TEST_CASE(tcs, reset__invalid); ATF_ADD_TEST_CASE(tcs, reset_all); } diff --git a/contrib/kyua/utils/signals/programmer.hpp b/contrib/kyua/utils/signals/programmer.hpp index 5ac5318f0bb9..9f00e5ed8502 100644 --- a/contrib/kyua/utils/signals/programmer.hpp +++ b/contrib/kyua/utils/signals/programmer.hpp @@ -1,63 +1,63 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/signals/programmer.hpp /// Provides the signals::programmer class. #if !defined(UTILS_SIGNALS_PROGRAMMER_HPP) #define UTILS_SIGNALS_PROGRAMMER_HPP #include "utils/signals/programmer_fwd.hpp" #include #include "utils/noncopyable.hpp" namespace utils { namespace signals { /// A RAII class to program signal handlers. class programmer : noncopyable { struct impl; /// Pointer to the shared internal implementation. - std::auto_ptr< impl > _pimpl; + std::unique_ptr< impl > _pimpl; public: programmer(const int, const handler_type); ~programmer(void); void unprogram(void); }; } // namespace signals } // namespace utils #endif // !defined(UTILS_SIGNALS_PROGRAMMER_HPP) diff --git a/contrib/kyua/utils/signals/timer.cpp b/contrib/kyua/utils/signals/timer.cpp index 698b9835dc10..eceaa7fcd4ff 100644 --- a/contrib/kyua/utils/signals/timer.cpp +++ b/contrib/kyua/utils/signals/timer.cpp @@ -1,547 +1,547 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/signals/timer.hpp" extern "C" { #include #include } #include #include #include #include #include "utils/datetime.hpp" #include "utils/format/macros.hpp" #include "utils/logging/macros.hpp" #include "utils/noncopyable.hpp" #include "utils/optional.ipp" #include "utils/sanity.hpp" #include "utils/signals/exceptions.hpp" #include "utils/signals/interrupts.hpp" #include "utils/signals/programmer.hpp" namespace datetime = utils::datetime; namespace signals = utils::signals; using utils::none; using utils::optional; namespace { static void sigalrm_handler(const int); /// Calls setitimer(2) with exception-based error reporting. /// /// This does not currently support intervals. /// /// \param delta The time to the first activation of the programmed timer. /// \param old_timeval If not NULL, pointer to a timeval into which to store the /// existing system timer. /// /// \throw system_error If the call to setitimer(2) fails. static void safe_setitimer(const datetime::delta& delta, itimerval* old_timeval) { ::itimerval timeval; timerclear(&timeval.it_interval); timeval.it_value.tv_sec = delta.seconds; timeval.it_value.tv_usec = delta.useconds; if (::setitimer(ITIMER_REAL, &timeval, old_timeval) == -1) { const int original_errno = errno; throw signals::system_error("Failed to program system's interval timer", original_errno); } } /// Deadline scheduler for all user timers on top of the unique system timer. class global_state : utils::noncopyable { /// Collection of active timers. /// /// Because this is a collection of pointers, all timers are guaranteed to /// be unique, and we want all of these pointers to be valid. typedef std::set< signals::timer* > timers_set; /// Sequence of ordered timers. typedef std::vector< signals::timer* > timers_vector; /// Collection of active timestamps by their activation timestamp. /// /// This collection is ordered intentionally so that it can be scanned /// sequentially to find either expired or expiring-now timers. typedef std::map< datetime::timestamp, timers_set > timers_by_timestamp_map; /// The original timer before any timer was programmed. ::itimerval _old_timeval; /// Programmer for the SIGALRM handler. - std::auto_ptr< signals::programmer > _sigalrm_programmer; + std::unique_ptr< signals::programmer > _sigalrm_programmer; /// Time of the current activation of the timer. datetime::timestamp _timer_activation; /// Mapping of all active timers using their timestamp as the key. timers_by_timestamp_map _all_timers; /// Adds a timer to the _all_timers map. /// /// \param timer The timer to add. void add_to_all_timers(signals::timer* timer) { timers_set& timers = _all_timers[timer->when()]; INV(timers.find(timer) == timers.end()); timers.insert(timer); } /// Removes a timer from the _all_timers map. /// /// This ensures that empty vectors are removed from _all_timers if the /// removal of the timer causes its bucket to be emptied. /// /// \param timer The timer to remove. void remove_from_all_timers(signals::timer* timer) { // We may not find the timer in _all_timers if the timer has fired, // because fire() took it out from the map. timers_by_timestamp_map::iterator iter = _all_timers.find( timer->when()); if (iter != _all_timers.end()) { timers_set& timers = (*iter).second; INV(timers.find(timer) != timers.end()); timers.erase(timer); if (timers.empty()) { _all_timers.erase(iter); } } } /// Calculates all timers to execute at this timestamp. /// /// \param now The current timestamp. /// /// \post _all_timers is updated to contain only the timers that are /// strictly in the future. /// /// \return A sequence of valid timers that need to be invoked in the order /// of activation. These are all previously registered timers with /// activations in the past. timers_vector compute_timers_to_run_and_prune_old( const datetime::timestamp& now, const signals::interrupts_inhibiter& /* inhibiter */) { timers_vector to_run; timers_by_timestamp_map::iterator iter = _all_timers.begin(); while (iter != _all_timers.end() && (*iter).first <= now) { const timers_set& timers = (*iter).second; to_run.insert(to_run.end(), timers.begin(), timers.end()); // Remove expired entries here so that we can always assume that // the first entry in all_timers corresponds to the next // activation. const timers_by_timestamp_map::iterator previous_iter = iter; ++iter; _all_timers.erase(previous_iter); } return to_run; } /// Adjusts the global system timer to point to the next activation. /// /// \param now The current timestamp. /// /// \throw system_error If the programming fails. void reprogram_system_timer( const datetime::timestamp& now, const signals::interrupts_inhibiter& /* inhibiter */) { if (_all_timers.empty()) { // Nothing to do. We can reach this case if all the existing timers // are in the past and they all fired. Just ignore the request and // leave the global timer as is. return; } // While fire() prunes old entries from the list of timers, it is // possible for this routine to run with "expired" timers (i.e. timers // whose deadline lies in the past but that have not yet fired for // whatever reason that is out of our control) in the list. We have to // iterate until we find the next activation instead of assuming that // the first entry represents the desired value. timers_by_timestamp_map::const_iterator iter = _all_timers.begin(); PRE(!(*iter).second.empty()); datetime::timestamp next = (*iter).first; while (next < now) { ++iter; if (iter == _all_timers.end()) { // Nothing to do. We can reach this case if all the existing // timers are in the past but they have not yet fired. return; } PRE(!(*iter).second.empty()); next = (*iter).first; } if (next < _timer_activation || now > _timer_activation) { INV(next >= now); const datetime::delta delta = next - now; LD(F("Reprogramming timer; firing on %s; now is %s") % next % now); safe_setitimer(delta, NULL); _timer_activation = next; } } public: /// Programs the first timer. /// /// The programming of the first timer involves setting up the SIGALRM /// handler and installing a timer handler for the first time, which in turn /// involves keeping track of the old handlers so that we can restore them. /// /// \param timer The timer being programmed. /// \param now The current timestamp. /// /// \throw system_error If the programming fails. global_state(signals::timer* timer, const datetime::timestamp& now) : _timer_activation(timer->when()) { PRE(now < timer->when()); signals::interrupts_inhibiter inhibiter; const datetime::delta delta = timer->when() - now; LD(F("Installing first timer; firing on %s; now is %s") % timer->when() % now); _sigalrm_programmer.reset( new signals::programmer(SIGALRM, sigalrm_handler)); try { safe_setitimer(delta, &_old_timeval); _timer_activation = timer->when(); add_to_all_timers(timer); } catch (...) { _sigalrm_programmer.reset(NULL); throw; } } /// Unprograms all timers. /// /// This clears the global system timer and unsets the SIGALRM handler. ~global_state(void) { signals::interrupts_inhibiter inhibiter; LD("Unprogramming all timers"); if (::setitimer(ITIMER_REAL, &_old_timeval, NULL) == -1) { UNREACHABLE_MSG("Failed to restore original timer"); } _sigalrm_programmer->unprogram(); _sigalrm_programmer.reset(NULL); } /// Programs a new timer, possibly adjusting the global system timer. /// /// Programming any timer other than the first one only involves reloading /// the existing timer, not backing up the previous handler nor installing a /// handler for SIGALRM. /// /// \param timer The timer being programmed. /// \param now The current timestamp. /// /// \throw system_error If the programming fails. void program_new(signals::timer* timer, const datetime::timestamp& now) { signals::interrupts_inhibiter inhibiter; add_to_all_timers(timer); reprogram_system_timer(now, inhibiter); } /// Unprograms a timer. /// /// This removes the timer from the global state and reprograms the global /// system timer if necessary. /// /// \param timer The timer to unprogram. /// /// \return True if the system interval timer has been reprogrammed to /// another future timer; false if there are no more active timers. bool unprogram(signals::timer* timer) { signals::interrupts_inhibiter inhibiter; LD(F("Unprogramming timer; previously firing on %s") % timer->when()); remove_from_all_timers(timer); if (_all_timers.empty()) { return false; } else { reprogram_system_timer(datetime::timestamp::now(), inhibiter); return true; } } /// Executes active timers. /// /// Active timers are all those that fire on or before 'now'. /// /// \param now The current time. void fire(const datetime::timestamp& now) { timers_vector to_run; { signals::interrupts_inhibiter inhibiter; to_run = compute_timers_to_run_and_prune_old(now, inhibiter); reprogram_system_timer(now, inhibiter); } for (timers_vector::iterator iter = to_run.begin(); iter != to_run.end(); ++iter) { signals::detail::invoke_do_fired(*iter); } } }; /// Unique instance of the global state. -static std::auto_ptr< global_state > globals; +static std::unique_ptr< global_state > globals; /// SIGALRM handler for the timer implementation. /// /// \param signo The signal received; must be SIGALRM. static void sigalrm_handler(const int signo) { PRE(signo == SIGALRM); globals->fire(datetime::timestamp::now()); } } // anonymous namespace /// Indirection to invoke the private do_fired() method of a timer. /// /// \param timer The timer on which to run do_fired(). void utils::signals::detail::invoke_do_fired(timer* timer) { timer->do_fired(); } /// Internal implementation for the timer. /// /// We assume that there is a 1-1 mapping between timer objects and impl /// objects. If this assumption breaks, then the rest of the code in this /// module breaks as well because we use pointers to the parent timer as the /// identifier of the timer. struct utils::signals::timer::impl : utils::noncopyable { /// Timestamp when this timer is expected to fire. /// /// Note that the timer might be processed after this timestamp, so users of /// this field need to check for timers that fire on or before the /// activation time. datetime::timestamp when; /// True until unprogram() is called. bool programmed; /// Whether this timer has fired already or not. /// /// This is updated from an interrupt context, hence why it is marked /// volatile. volatile bool fired; /// Constructor. /// /// \param when_ Timestamp when this timer is expected to fire. impl(const datetime::timestamp& when_) : when(when_), programmed(true), fired(false) { } /// Destructor. ~impl(void) { } }; /// Constructor; programs a run-once timer. /// /// This programs the global timer and signal handler if this is the first timer /// being installed. Otherwise, reprograms the global timer if this timer /// expires earlier than all other active timers. /// /// \param delta The time until the timer fires. signals::timer::timer(const datetime::delta& delta) { signals::interrupts_inhibiter inhibiter; const datetime::timestamp now = datetime::timestamp::now(); _pimpl.reset(new impl(now + delta)); if (globals.get() == NULL) { globals.reset(new global_state(this, now)); } else { globals->program_new(this, now); } } /// Destructor; unprograms the timer if still programmed. /// /// Given that this is a destructor and it can't report errors back to the /// caller, the caller must attempt to call unprogram() on its own. This is /// extremely important because, otherwise, expired timers will never run! signals::timer::~timer(void) { signals::interrupts_inhibiter inhibiter; if (_pimpl->programmed) { LW("Auto-destroying still-programmed signals::timer object"); try { unprogram(); } catch (const system_error& e) { UNREACHABLE; } } if (!_pimpl->fired) { const datetime::timestamp now = datetime::timestamp::now(); if (now > _pimpl->when) { LW("Expired timer never fired; the code never called unprogram()!"); } } } /// Returns the time of the timer activation. /// /// \return A timestamp that has no relation to the current time (i.e. can be in /// the future or in the past) nor the timer's activation status. const datetime::timestamp& signals::timer::when(void) const { return _pimpl->when; } /// Callback for the SIGALRM handler when this timer expires. /// /// \warning This is executed from a signal handler context without signals /// inhibited. See signal(7) for acceptable system calls. void signals::timer::do_fired(void) { PRE(!_pimpl->fired); _pimpl->fired = true; callback(); } /// User-provided callback to run when the timer expires. /// /// The default callback does nothing. We record the activation of the timer /// separately, which may be appropriate in the majority of the cases. /// /// \warning This is executed from a signal handler context without signals /// inhibited. See signal(7) for acceptable system calls. void signals::timer::callback(void) { // Intentionally left blank. } /// Checks whether the timer has fired already or not. /// /// \return Returns true if the timer has fired. bool signals::timer::fired(void) const { return _pimpl->fired; } /// Unprograms the timer. /// /// \pre The timer is programmed (i.e. this can only be called once). /// /// \post If the timer never fired asynchronously because the signal delivery /// did not arrive on time, make sure we invoke the timer's callback here. /// /// \throw system_error If unprogramming the timer failed. void signals::timer::unprogram(void) { signals::interrupts_inhibiter inhibiter; if (!_pimpl->programmed) { // We cannot assert that the timer is not programmed because it might // have been unprogrammed asynchronously between the time we called // unprogram() and the time we reach this. Simply return in this case. LD("Called unprogram on already-unprogrammed timer; possibly just " "a race"); return; } if (!globals->unprogram(this)) { globals.reset(NULL); } _pimpl->programmed = false; // Handle the case where the timer has expired before we ever got its // corresponding signal. Do so by invoking its callback now. if (!_pimpl->fired) { const datetime::timestamp now = datetime::timestamp::now(); if (now > _pimpl->when) { LW(F("Firing expired timer on destruction (was to fire on %s)") % _pimpl->when); do_fired(); } } } diff --git a/contrib/kyua/utils/signals/timer.hpp b/contrib/kyua/utils/signals/timer.hpp index 1174effe2b48..65a43e19de6f 100644 --- a/contrib/kyua/utils/signals/timer.hpp +++ b/contrib/kyua/utils/signals/timer.hpp @@ -1,86 +1,86 @@ // Copyright 2010 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/signals/timer.hpp /// Multiprogrammed support for timers. /// /// The timer module and class implement a mechanism to program multiple timers /// concurrently by using a deadline scheduler and leveraging the "single timer" /// features of the underlying operating system. #if !defined(UTILS_SIGNALS_TIMER_HPP) #define UTILS_SIGNALS_TIMER_HPP #include "utils/signals/timer_fwd.hpp" #include #include "utils/datetime_fwd.hpp" #include "utils/noncopyable.hpp" namespace utils { namespace signals { namespace detail { void invoke_do_fired(timer*); } // namespace detail /// Individual timer. /// /// Asynchronously executes its callback() method, which can be overridden by /// subclasses, when the timeout given at construction expires. class timer : noncopyable { struct impl; /// Pointer to the shared internal implementation. - std::auto_ptr< impl > _pimpl; + std::unique_ptr< impl > _pimpl; friend void detail::invoke_do_fired(timer*); void do_fired(void); protected: virtual void callback(void); public: timer(const utils::datetime::delta&); virtual ~timer(void); const utils::datetime::timestamp& when(void) const; bool fired(void) const; void unprogram(void); }; } // namespace signals } // namespace utils #endif // !defined(UTILS_SIGNALS_TIMER_HPP) diff --git a/contrib/kyua/utils/stream.cpp b/contrib/kyua/utils/stream.cpp index ee3ab417f753..04aafb6da5a5 100644 --- a/contrib/kyua/utils/stream.cpp +++ b/contrib/kyua/utils/stream.cpp @@ -1,149 +1,149 @@ // Copyright 2011 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/stream.hpp" #include #include #include #include #include "utils/format/macros.hpp" #include "utils/fs/path.hpp" #include "utils/sanity.hpp" namespace fs = utils::fs; namespace { /// Constant that represents the path to stdout. static const fs::path stdout_path("/dev/stdout"); /// Constant that represents the path to stderr. static const fs::path stderr_path("/dev/stderr"); } // anonymous namespace /// Opens a new file for output, respecting the stdout and stderr streams. /// /// \param path The path to the output file to be created. /// /// \return A pointer to a new output stream. -std::auto_ptr< std::ostream > +std::unique_ptr< std::ostream > utils::open_ostream(const fs::path& path) { - std::auto_ptr< std::ostream > out; + std::unique_ptr< std::ostream > out; if (path == stdout_path) { out.reset(new std::ofstream()); out->copyfmt(std::cout); out->clear(std::cout.rdstate()); out->rdbuf(std::cout.rdbuf()); } else if (path == stderr_path) { out.reset(new std::ofstream()); out->copyfmt(std::cerr); out->clear(std::cerr.rdstate()); out->rdbuf(std::cerr.rdbuf()); } else { out.reset(new std::ofstream(path.c_str())); if (!(*out)) { throw std::runtime_error(F("Cannot open output file %s") % path); } } INV(out.get() != NULL); return out; } /// Gets the length of a stream. /// /// \param is The input stream for which to calculate its length. /// /// \return The length of the stream. This is of size_t type instead of /// directly std::streampos to simplify the caller. Some systems do not /// support comparing a std::streampos directly to an integer (see /// NetBSD 1.5.x), which is what we often want to do. /// /// \throw std::exception If calculating the length fails due to a stream error. std::size_t utils::stream_length(std::istream& is) { const std::streampos current_pos = is.tellg(); try { is.seekg(0, std::ios::end); const std::streampos length = is.tellg(); is.seekg(current_pos, std::ios::beg); return static_cast< std::size_t >(length); } catch (...) { is.seekg(current_pos, std::ios::beg); throw; } } /// Reads a whole file into memory. /// /// \param path The file to read. /// /// \return A plain string containing the raw contents of the file. /// /// \throw std::runtime_error If the file cannot be opened. std::string utils::read_file(const fs::path& path) { std::ifstream input(path.c_str()); if (!input) throw std::runtime_error(F("Failed to open '%s' for read") % path); return read_stream(input); } /// Reads the whole contents of a stream into memory. /// /// \param input The input stream from which to read. /// /// \return A plain string containing the raw contents of the file. std::string utils::read_stream(std::istream& input) { std::ostringstream buffer; char tmp[1024]; while (input.good()) { input.read(tmp, sizeof(tmp)); if (input.good() || input.eof()) { buffer.write(tmp, input.gcount()); } } return buffer.str(); } diff --git a/contrib/kyua/utils/stream.hpp b/contrib/kyua/utils/stream.hpp index 5c9316e72810..48213b8930f1 100644 --- a/contrib/kyua/utils/stream.hpp +++ b/contrib/kyua/utils/stream.hpp @@ -1,57 +1,57 @@ // Copyright 2011 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// \file utils/stream.hpp /// Stream manipulation utilities. /// /// Note that file-manipulation utilities live in utils::fs instead. The /// utilities here deal with already-open streams. #if !defined(UTILS_STREAM_HPP) #define UTILS_STREAM_HPP #include #include #include #include #include #include "utils/fs/path_fwd.hpp" namespace utils { -std::auto_ptr< std::ostream > open_ostream(const utils::fs::path&); +std::unique_ptr< std::ostream > open_ostream(const utils::fs::path&); std::size_t stream_length(std::istream&); std::string read_file(const utils::fs::path&); std::string read_stream(std::istream&); } // namespace utils #endif // !defined(UTILS_STREAM_HPP) diff --git a/contrib/kyua/utils/stream_test.cpp b/contrib/kyua/utils/stream_test.cpp index 7c4f3b5c6b4a..69c934f95ba3 100644 --- a/contrib/kyua/utils/stream_test.cpp +++ b/contrib/kyua/utils/stream_test.cpp @@ -1,157 +1,157 @@ // Copyright 2011 The Kyua Authors. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of Google Inc. nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "utils/stream.hpp" #include #include #include #include "utils/fs/path.hpp" namespace fs = utils::fs; ATF_TEST_CASE_WITHOUT_HEAD(open_ostream__stdout); ATF_TEST_CASE_BODY(open_ostream__stdout) { const pid_t pid = atf::utils::fork(); if (pid == 0) { - std::auto_ptr< std::ostream > output = utils::open_ostream( + std::unique_ptr< std::ostream > output = utils::open_ostream( fs::path("/dev/stdout")); (*output) << "Message to stdout\n"; output.reset(); std::exit(EXIT_SUCCESS); } atf::utils::wait(pid, EXIT_SUCCESS, "Message to stdout\n", ""); } ATF_TEST_CASE_WITHOUT_HEAD(open_ostream__stderr); ATF_TEST_CASE_BODY(open_ostream__stderr) { const pid_t pid = atf::utils::fork(); if (pid == 0) { - std::auto_ptr< std::ostream > output = utils::open_ostream( + std::unique_ptr< std::ostream > output = utils::open_ostream( fs::path("/dev/stderr")); (*output) << "Message to stderr\n"; output.reset(); std::exit(EXIT_SUCCESS); } atf::utils::wait(pid, EXIT_SUCCESS, "", "Message to stderr\n"); } ATF_TEST_CASE_WITHOUT_HEAD(open_ostream__other); ATF_TEST_CASE_BODY(open_ostream__other) { const pid_t pid = atf::utils::fork(); if (pid == 0) { - std::auto_ptr< std::ostream > output = utils::open_ostream( + std::unique_ptr< std::ostream > output = utils::open_ostream( fs::path("some-file.txt")); (*output) << "Message to other file\n"; output.reset(); std::exit(EXIT_SUCCESS); } atf::utils::wait(pid, EXIT_SUCCESS, "", ""); atf::utils::compare_file("some-file.txt", "Message to other file\n"); } ATF_TEST_CASE_WITHOUT_HEAD(stream_length__empty); ATF_TEST_CASE_BODY(stream_length__empty) { std::istringstream input(""); ATF_REQUIRE_EQ(0, utils::stream_length(input)); } ATF_TEST_CASE_WITHOUT_HEAD(stream_length__some); ATF_TEST_CASE_BODY(stream_length__some) { const std::string contents(8192, 'x'); std::istringstream input(contents); ATF_REQUIRE_EQ( contents.length(), static_cast< std::string::size_type >(utils::stream_length(input))); } ATF_TEST_CASE_WITHOUT_HEAD(read_file__ok); ATF_TEST_CASE_BODY(read_file__ok) { const char* contents = "These are\nsome file contents"; atf::utils::create_file("input.txt", contents); ATF_REQUIRE_EQ(contents, utils::read_file(fs::path("input.txt"))); } ATF_TEST_CASE_WITHOUT_HEAD(read_file__missing_file); ATF_TEST_CASE_BODY(read_file__missing_file) { ATF_REQUIRE_THROW_RE(std::runtime_error, "Failed to open 'foo.txt' for read", utils::read_file(fs::path("foo.txt"))); } ATF_TEST_CASE_WITHOUT_HEAD(read_stream__empty); ATF_TEST_CASE_BODY(read_stream__empty) { std::istringstream input(""); ATF_REQUIRE_EQ("", utils::read_stream(input)); } ATF_TEST_CASE_WITHOUT_HEAD(read_stream__some); ATF_TEST_CASE_BODY(read_stream__some) { std::string contents; for (int i = 0; i < 1000; i++) contents += "abcdef"; std::istringstream input(contents); ATF_REQUIRE_EQ(contents, utils::read_stream(input)); } ATF_INIT_TEST_CASES(tcs) { ATF_ADD_TEST_CASE(tcs, open_ostream__stdout); ATF_ADD_TEST_CASE(tcs, open_ostream__stderr); ATF_ADD_TEST_CASE(tcs, open_ostream__other); ATF_ADD_TEST_CASE(tcs, stream_length__empty); ATF_ADD_TEST_CASE(tcs, stream_length__some); ATF_ADD_TEST_CASE(tcs, read_file__ok); ATF_ADD_TEST_CASE(tcs, read_file__missing_file); ATF_ADD_TEST_CASE(tcs, read_stream__empty); ATF_ADD_TEST_CASE(tcs, read_stream__some); } diff --git a/usr.bin/kyua/Makefile b/usr.bin/kyua/Makefile index f9ac12bb0f9d..24ceba841ba4 100644 --- a/usr.bin/kyua/Makefile +++ b/usr.bin/kyua/Makefile @@ -1,224 +1,221 @@ .include KYUA_CONFDIR= /etc/kyua KYUA_DOCDIR= /usr/share/doc/kyua KYUA_EGDIR= /usr/share/examples/kyua KYUA_MISCDIR= /usr/share/kyua/misc KYUA_STOREDIR= /usr/share/kyua/store KYUA_VERSION= 0.13 KYUA_SRCDIR= ${SRCTOP}/contrib/kyua .PATH: ${KYUA_SRCDIR} PACKAGE= tests PROG_CXX= kyua SRCS= main.cpp LIBADD= lutok sqlite3 MAN= kyua-about.1 \ kyua-config.1 \ kyua-db-exec.1 \ kyua-db-migrate.1 \ kyua-debug.1 \ kyua-help.1 \ kyua-list.1 \ kyua-report-html.1 \ kyua-report-junit.1 \ kyua-report.1 \ kyua-test.1 \ kyua.1 \ kyua.conf.5 \ kyuafile.5 CFLAGS+= -I${KYUA_SRCDIR} -I${.CURDIR} CFLAGS+= -I${SRCTOP}/contrib/lutok/include CFLAGS+= -I${SRCTOP}/contrib/sqlite3 -# kyua uses auto_ptr -CFLAGS+= -Wno-deprecated-declarations -CXXSTD= c++11 CFLAGS+= -DHAVE_CONFIG_H # We compile the kyua libraries as part of the main executable as this saves # compile time and we don't install them anyway. CFLAGS+= -DGDB=\"/usr/local/bin/gdb\" \ -DKYUA_ARCHITECTURE=\"${MACHINE_ARCH}\" \ -DKYUA_CONFDIR=\"${KYUA_CONFDIR}\" \ -DKYUA_DOCDIR=\"${KYUA_DOCDIR}\" \ -DKYUA_MISCDIR=\"${KYUA_MISCDIR}\" \ -DKYUA_PLATFORM=\"${MACHINE}\" \ -DKYUA_STOREDIR=\"${KYUA_STOREDIR}\" \ -DPACKAGE=\"kyua\" \ -DPACKAGE_NAME=\"Kyua\" \ -DPACKAGE_VERSION=\"${KYUA_VERSION}\" \ -DVERSION=\"${KYUA_VERSION}\" SRCS+= utils/datetime.cpp \ utils/env.cpp \ utils/memory.cpp \ utils/passwd.cpp \ utils/sanity.cpp \ utils/stacktrace.cpp \ utils/stream.cpp \ utils/units.cpp \ utils/cmdline/base_command.cpp \ utils/cmdline/exceptions.cpp \ utils/cmdline/globals.cpp \ utils/cmdline/options.cpp \ utils/cmdline/parser.cpp \ utils/cmdline/ui.cpp \ utils/cmdline/ui_mock.cpp \ utils/config/exceptions.cpp \ utils/config/keys.cpp \ utils/config/lua_module.cpp \ utils/config/nodes.cpp \ utils/config/parser.cpp \ utils/config/tree.cpp \ utils/format/exceptions.cpp \ utils/format/formatter.cpp \ utils/fs/auto_cleaners.cpp \ utils/fs/directory.cpp \ utils/fs/exceptions.cpp \ utils/fs/lua_module.cpp \ utils/fs/operations.cpp \ utils/fs/path.cpp \ utils/logging/operations.cpp \ utils/process/child.cpp \ utils/process/deadline_killer.cpp \ utils/process/exceptions.cpp \ utils/process/executor.cpp \ utils/process/fdstream.cpp \ utils/process/isolation.cpp \ utils/process/operations.cpp \ utils/process/status.cpp \ utils/process/system.cpp \ utils/process/systembuf.cpp \ utils/signals/exceptions.cpp \ utils/signals/interrupts.cpp \ utils/signals/misc.cpp \ utils/signals/programmer.cpp \ utils/signals/timer.cpp \ utils/sqlite/c_gate.cpp \ utils/sqlite/database.cpp \ utils/sqlite/exceptions.cpp \ utils/sqlite/statement.cpp \ utils/sqlite/transaction.cpp \ utils/text/exceptions.cpp \ utils/text/operations.cpp \ utils/text/regex.cpp \ utils/text/table.cpp \ utils/text/templates.cpp SRCS+= model/context.cpp \ model/exceptions.cpp \ model/metadata.cpp \ model/test_case.cpp \ model/test_program.cpp \ model/test_result.cpp SRCS+= engine/atf.cpp \ engine/atf_list.cpp \ engine/atf_result.cpp \ engine/config.cpp \ engine/exceptions.cpp \ engine/filters.cpp \ engine/kyuafile.cpp \ engine/plain.cpp \ engine/requirements.cpp \ engine/scanner.cpp \ engine/tap.cpp \ engine/tap_parser.cpp \ engine/scheduler.cpp \ engine/execenv/execenv.cpp \ engine/execenv/execenv_host.cpp SRCS+= os/freebsd/execenv_jail_manager.cpp \ os/freebsd/main.cpp SRCS+= store/dbtypes.cpp \ store/exceptions.cpp \ store/layout.cpp \ store/metadata.cpp \ store/migrate.cpp \ store/read_backend.cpp \ store/read_transaction.cpp \ store/write_backend.cpp \ store/write_transaction.cpp SRCS+= drivers/debug_test.cpp \ drivers/list_tests.cpp \ drivers/report_junit.cpp \ drivers/run_tests.cpp \ drivers/scan_results.cpp SRCS+= cli/cmd_about.cpp \ cli/cmd_config.cpp \ cli/cmd_db_exec.cpp \ cli/cmd_db_migrate.cpp \ cli/cmd_debug.cpp \ cli/cmd_help.cpp \ cli/cmd_list.cpp \ cli/cmd_report.cpp \ cli/cmd_report_html.cpp \ cli/cmd_report_junit.cpp \ cli/cmd_test.cpp \ cli/common.cpp \ cli/config.cpp \ cli/main.cpp .if ${MK_JAIL} == "no" SRCS+= os/freebsd/execenv_jail_stub.cpp .else SRCS+= os/freebsd/execenv_jail.cpp \ os/freebsd/utils/jail.cpp LIBADD+= jail .endif FILESGROUPS= DOCS MISC STORE .if ${MK_EXAMPLES} != "no" FILESGROUPS+= EXAMPLES .endif # Install a minimal default config that uses the 'tests' user. # The examples config is not appropriate for general use. CONFS= kyua.conf-default CONFSDIR= ${KYUA_CONFDIR} CONFSNAME= kyua.conf CONFSDIRTAGS= package=tests DOCS= AUTHORS CONTRIBUTORS LICENSE DOCSDIR= ${KYUA_DOCDIR} DOCSTAGS= package=tests EXAMPLES= Kyuafile.top kyua.conf EXAMPLESDIR= ${KYUA_EGDIR} EXAMPLESTAGS= package=tests .PATH: ${KYUA_SRCDIR}/examples MISC= context.html index.html report.css test_result.html MISCDIR= ${KYUA_MISCDIR} MISCTAGS= package=tests .PATH: ${KYUA_SRCDIR}/misc STORE= migrate_v1_v2.sql migrate_v2_v3.sql schema_v3.sql STOREDIR= ${KYUA_STOREDIR} STORETAGS= package=tests .PATH: ${KYUA_SRCDIR}/store CLEANFILES+= ${MAN} .PATH: ${KYUA_SRCDIR}/doc .for man in ${MAN} ${man}: ${man}.in sh ${KYUA_SRCDIR}/doc/manbuild.sh \ -v "CONFDIR=${KYUA_CONFDIR}" \ -v "DOCDIR=${KYUA_DOCDIR}" \ -v "EGDIR=${KYUA_EGDIR}" \ -v "MISCDIR=${KYUA_MISCDIR}" \ -v "PACKAGE=kyua" \ -v "STOREDIR=${KYUA_STOREDIR}" \ -v "TESTSDIR=${TESTSBASE}" \ -v "VERSION=${KYUA_VERSION}" \ ${.ALLSRC} ${.TARGET} .endfor .include