diff options
Diffstat (limited to 'utils/process/operations_test.cpp')
-rw-r--r-- | utils/process/operations_test.cpp | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/utils/process/operations_test.cpp b/utils/process/operations_test.cpp new file mode 100644 index 0000000000000..e9c1ebb65a3df --- /dev/null +++ b/utils/process/operations_test.cpp @@ -0,0 +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 <sys/types.h> +#include <sys/wait.h> + +#include <signal.h> +#include <unistd.h> +} + +#include <cerrno> +#include <iostream> + +#include <atf-c++.hpp> + +#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( + 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( + 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( + 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( + 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); +} |