Path: blob/main/contrib/kyua/utils/process/operations_test.cpp
48178 views
// Copyright 2014 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/process/operations.hpp"2930extern "C" {31#include <sys/types.h>32#include <sys/wait.h>3334#include <signal.h>35#include <unistd.h>36}3738#include <cerrno>39#include <iostream>4041#include <atf-c++.hpp>4243#include "utils/defs.hpp"44#include "utils/format/containers.ipp"45#include "utils/fs/path.hpp"46#include "utils/process/child.ipp"47#include "utils/process/exceptions.hpp"48#include "utils/process/status.hpp"49#include "utils/stacktrace.hpp"50#include "utils/test_utils.ipp"5152namespace fs = utils::fs;53namespace process = utils::process;545556namespace {575859/// Type of the process::exec() and process::exec_unsafe() functions.60typedef void (*exec_function)(const fs::path&, const process::args_vector&);616263/// Calculates the path to the test helpers binary.64///65/// \param tc A pointer to the caller test case, needed to extract the value of66/// the "srcdir" property.67///68/// \return The path to the helpers binary.69static fs::path70get_helpers(const atf::tests::tc* tc)71{72return fs::path(tc->get_config_var("srcdir")) / "helpers";73}747576/// Body for a subprocess that runs exec().77class child_exec {78/// Function to do the exec.79const exec_function _do_exec;8081/// Path to the binary to exec.82const fs::path& _program;8384/// Arguments to the binary, not including argv[0].85const process::args_vector& _args;8687public:88/// Constructor.89///90/// \param do_exec Function to do the exec.91/// \param program Path to the binary to exec.92/// \param args Arguments to the binary, not including argv[0].93child_exec(const exec_function do_exec, const fs::path& program,94const process::args_vector& args) :95_do_exec(do_exec), _program(program), _args(args)96{97}9899/// Body for the subprocess.100void101operator()(void)102{103_do_exec(_program, _args);104}105};106107108/// Body for a process that returns a specific exit code.109///110/// \tparam ExitStatus The exit status for the subprocess.111template< int ExitStatus >112static void113child_exit(void)114{115std::exit(ExitStatus);116}117118119static void suspend(void) UTILS_NORETURN;120121122/// Blocks a subprocess from running indefinitely.123static void124suspend(void)125{126sigset_t mask;127sigemptyset(&mask);128for (;;) {129::sigsuspend(&mask);130}131}132133134static void write_loop(const int) UTILS_NORETURN;135136137/// Provides an infinite stream of data in a subprocess.138///139/// \param fd Descriptor into which to write.140static void141write_loop(const int fd)142{143const int cookie = 0x12345678;144for (;;) {145std::cerr << "Still alive in PID " << ::getpid() << '\n';146if (::write(fd, &cookie, sizeof(cookie)) != sizeof(cookie))147std::exit(EXIT_FAILURE);148::sleep(1);149}150}151152153} // anonymous namespace154155156/// Tests an exec function with no arguments.157///158/// \param tc The calling test case.159/// \param do_exec The exec function to test.160static void161check_exec_no_args(const atf::tests::tc* tc, const exec_function do_exec)162{163std::unique_ptr< process::child > child = process::child::fork_files(164child_exec(do_exec, get_helpers(tc), process::args_vector()),165fs::path("stdout"), fs::path("stderr"));166const process::status status = child->wait();167ATF_REQUIRE(status.exited());168ATF_REQUIRE_EQ(EXIT_FAILURE, status.exitstatus());169ATF_REQUIRE(atf::utils::grep_file("Must provide a helper name", "stderr"));170}171172173/// Tests an exec function with some arguments.174///175/// \param tc The calling test case.176/// \param do_exec The exec function to test.177static void178check_exec_some_args(const atf::tests::tc* tc, const exec_function do_exec)179{180process::args_vector args;181args.push_back("print-args");182args.push_back("foo");183args.push_back("bar");184185std::unique_ptr< process::child > child = process::child::fork_files(186child_exec(do_exec, get_helpers(tc), args),187fs::path("stdout"), fs::path("stderr"));188const process::status status = child->wait();189ATF_REQUIRE(status.exited());190ATF_REQUIRE_EQ(EXIT_SUCCESS, status.exitstatus());191ATF_REQUIRE(atf::utils::grep_file("argv\\[1\\] = print-args", "stdout"));192ATF_REQUIRE(atf::utils::grep_file("argv\\[2\\] = foo", "stdout"));193ATF_REQUIRE(atf::utils::grep_file("argv\\[3\\] = bar", "stdout"));194}195196197ATF_TEST_CASE_WITHOUT_HEAD(exec__no_args);198ATF_TEST_CASE_BODY(exec__no_args)199{200check_exec_no_args(this, process::exec);201}202203204ATF_TEST_CASE_WITHOUT_HEAD(exec__some_args);205ATF_TEST_CASE_BODY(exec__some_args)206{207check_exec_some_args(this, process::exec);208}209210211ATF_TEST_CASE_WITHOUT_HEAD(exec__fail);212ATF_TEST_CASE_BODY(exec__fail)213{214utils::avoid_coredump_on_crash();215216std::unique_ptr< process::child > child = process::child::fork_files(217child_exec(process::exec, fs::path("non-existent"),218process::args_vector()),219fs::path("stdout"), fs::path("stderr"));220const process::status status = child->wait();221ATF_REQUIRE(status.signaled());222ATF_REQUIRE_EQ(SIGABRT, status.termsig());223ATF_REQUIRE(atf::utils::grep_file("Failed to execute non-existent",224"stderr"));225}226227228ATF_TEST_CASE_WITHOUT_HEAD(exec_unsafe__no_args);229ATF_TEST_CASE_BODY(exec_unsafe__no_args)230{231check_exec_no_args(this, process::exec_unsafe);232}233234235ATF_TEST_CASE_WITHOUT_HEAD(exec_unsafe__some_args);236ATF_TEST_CASE_BODY(exec_unsafe__some_args)237{238check_exec_some_args(this, process::exec_unsafe);239}240241242ATF_TEST_CASE_WITHOUT_HEAD(exec_unsafe__fail);243ATF_TEST_CASE_BODY(exec_unsafe__fail)244{245ATF_REQUIRE_THROW_RE(246process::system_error, "Failed to execute missing-program",247process::exec_unsafe(fs::path("missing-program"),248process::args_vector()));249}250251252ATF_TEST_CASE_WITHOUT_HEAD(terminate_group__setpgrp_executed);253ATF_TEST_CASE_BODY(terminate_group__setpgrp_executed)254{255int first_fds[2], second_fds[2];256ATF_REQUIRE(::pipe(first_fds) != -1);257ATF_REQUIRE(::pipe(second_fds) != -1);258259const pid_t pid = ::fork();260ATF_REQUIRE(pid != -1);261if (pid == 0) {262::setpgid(::getpid(), ::getpid());263const pid_t pid2 = ::fork();264if (pid2 == -1) {265std::exit(EXIT_FAILURE);266} else if (pid2 == 0) {267::close(first_fds[0]);268::close(first_fds[1]);269::close(second_fds[0]);270write_loop(second_fds[1]);271}272::close(first_fds[0]);273::close(second_fds[0]);274::close(second_fds[1]);275write_loop(first_fds[1]);276}277::close(first_fds[1]);278::close(second_fds[1]);279280int dummy;281std::cerr << "Waiting for children to start\n";282while (::read(first_fds[0], &dummy, sizeof(dummy)) <= 0 ||283::read(second_fds[0], &dummy, sizeof(dummy)) <= 0) {284// Wait for children to come up.285}286287process::terminate_group(pid);288std::cerr << "Waiting for children to die\n";289while (::read(first_fds[0], &dummy, sizeof(dummy)) > 0 ||290::read(second_fds[0], &dummy, sizeof(dummy)) > 0) {291// Wait for children to terminate. If they don't, then the test case292// will time out.293}294295int status;296ATF_REQUIRE(::wait(&status) != -1);297ATF_REQUIRE(WIFSIGNALED(status));298ATF_REQUIRE(WTERMSIG(status) == SIGKILL);299}300301302ATF_TEST_CASE_WITHOUT_HEAD(terminate_group__setpgrp_not_executed);303ATF_TEST_CASE_BODY(terminate_group__setpgrp_not_executed)304{305const pid_t pid = ::fork();306ATF_REQUIRE(pid != -1);307if (pid == 0) {308// We do not call setgprp() here to simulate the race that happens when309// we invoke terminate_group on a process that has not yet had a chance310// to run the setpgrp() call.311suspend();312}313314process::terminate_group(pid);315316int status;317ATF_REQUIRE(::wait(&status) != -1);318ATF_REQUIRE(WIFSIGNALED(status));319ATF_REQUIRE(WTERMSIG(status) == SIGKILL);320}321322323ATF_TEST_CASE_WITHOUT_HEAD(terminate_self_with__exitstatus);324ATF_TEST_CASE_BODY(terminate_self_with__exitstatus)325{326const pid_t pid = ::fork();327ATF_REQUIRE(pid != -1);328if (pid == 0) {329const process::status status = process::status::fake_exited(123);330process::terminate_self_with(status);331}332333int status;334ATF_REQUIRE(::wait(&status) != -1);335ATF_REQUIRE(WIFEXITED(status));336ATF_REQUIRE(WEXITSTATUS(status) == 123);337}338339340ATF_TEST_CASE_WITHOUT_HEAD(terminate_self_with__termsig);341ATF_TEST_CASE_BODY(terminate_self_with__termsig)342{343const pid_t pid = ::fork();344ATF_REQUIRE(pid != -1);345if (pid == 0) {346const process::status status = process::status::fake_signaled(347SIGKILL, false);348process::terminate_self_with(status);349}350351int status;352ATF_REQUIRE(::wait(&status) != -1);353ATF_REQUIRE(WIFSIGNALED(status));354ATF_REQUIRE(WTERMSIG(status) == SIGKILL);355ATF_REQUIRE(!WCOREDUMP(status));356}357358359ATF_TEST_CASE_WITHOUT_HEAD(terminate_self_with__termsig_and_core);360ATF_TEST_CASE_BODY(terminate_self_with__termsig_and_core)361{362utils::prepare_coredump_test(this);363364const pid_t pid = ::fork();365ATF_REQUIRE(pid != -1);366if (pid == 0) {367const process::status status = process::status::fake_signaled(368SIGABRT, true);369process::terminate_self_with(status);370}371372int status;373ATF_REQUIRE(::wait(&status) != -1);374ATF_REQUIRE(WIFSIGNALED(status));375ATF_REQUIRE(WTERMSIG(status) == SIGABRT);376ATF_REQUIRE(WCOREDUMP(status));377}378379380ATF_TEST_CASE_WITHOUT_HEAD(wait__ok);381ATF_TEST_CASE_BODY(wait__ok)382{383std::unique_ptr< process::child > child = process::child::fork_capture(384child_exit< 15 >);385const pid_t pid = child->pid();386child.reset(); // Ensure there is no conflict between destructor and wait.387388const process::status status = process::wait(pid);389ATF_REQUIRE(status.exited());390ATF_REQUIRE_EQ(15, status.exitstatus());391}392393394ATF_TEST_CASE_WITHOUT_HEAD(wait__fail);395ATF_TEST_CASE_BODY(wait__fail)396{397ATF_REQUIRE_THROW(process::system_error, process::wait(1));398}399400401ATF_TEST_CASE_WITHOUT_HEAD(wait_any__one);402ATF_TEST_CASE_BODY(wait_any__one)403{404process::child::fork_capture(child_exit< 15 >);405406const process::status status = process::wait_any();407ATF_REQUIRE(status.exited());408ATF_REQUIRE_EQ(15, status.exitstatus());409}410411412ATF_TEST_CASE_WITHOUT_HEAD(wait_any__many);413ATF_TEST_CASE_BODY(wait_any__many)414{415process::child::fork_capture(child_exit< 15 >);416process::child::fork_capture(child_exit< 30 >);417process::child::fork_capture(child_exit< 45 >);418419std::set< int > exit_codes;420for (int i = 0; i < 3; i++) {421const process::status status = process::wait_any();422ATF_REQUIRE(status.exited());423exit_codes.insert(status.exitstatus());424}425426std::set< int > exp_exit_codes;427exp_exit_codes.insert(15);428exp_exit_codes.insert(30);429exp_exit_codes.insert(45);430ATF_REQUIRE_EQ(exp_exit_codes, exit_codes);431}432433434ATF_TEST_CASE_WITHOUT_HEAD(wait_any__none_is_failure);435ATF_TEST_CASE_BODY(wait_any__none_is_failure)436{437try {438const process::status status = process::wait_any();439fail("Expected exception but none raised");440} catch (const process::system_error& e) {441ATF_REQUIRE(atf::utils::grep_string("Failed to wait", e.what()));442ATF_REQUIRE_EQ(ECHILD, e.original_errno());443}444}445446447ATF_INIT_TEST_CASES(tcs)448{449ATF_ADD_TEST_CASE(tcs, exec__no_args);450ATF_ADD_TEST_CASE(tcs, exec__some_args);451ATF_ADD_TEST_CASE(tcs, exec__fail);452453ATF_ADD_TEST_CASE(tcs, exec_unsafe__no_args);454ATF_ADD_TEST_CASE(tcs, exec_unsafe__some_args);455ATF_ADD_TEST_CASE(tcs, exec_unsafe__fail);456457ATF_ADD_TEST_CASE(tcs, terminate_group__setpgrp_executed);458ATF_ADD_TEST_CASE(tcs, terminate_group__setpgrp_not_executed);459460ATF_ADD_TEST_CASE(tcs, terminate_self_with__exitstatus);461ATF_ADD_TEST_CASE(tcs, terminate_self_with__termsig);462ATF_ADD_TEST_CASE(tcs, terminate_self_with__termsig_and_core);463464ATF_ADD_TEST_CASE(tcs, wait__ok);465ATF_ADD_TEST_CASE(tcs, wait__fail);466467ATF_ADD_TEST_CASE(tcs, wait_any__one);468ATF_ADD_TEST_CASE(tcs, wait_any__many);469ATF_ADD_TEST_CASE(tcs, wait_any__none_is_failure);470}471472473