Path: blob/main/contrib/kyua/utils/process/executor_test.cpp
48199 views
// Copyright 2015 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/executor.ipp"2930extern "C" {31#include <sys/types.h>32#include <sys/time.h>33#include <sys/wait.h>3435#include <signal.h>36#include <unistd.h>37}3839#include <cerrno>40#include <cstdlib>41#include <fstream>42#include <iostream>43#include <vector>4445#include <atf-c++.hpp>4647#include "utils/datetime.hpp"48#include "utils/defs.hpp"49#include "utils/env.hpp"50#include "utils/format/containers.ipp"51#include "utils/format/macros.hpp"52#include "utils/fs/operations.hpp"53#include "utils/fs/path.hpp"54#include "utils/optional.ipp"55#include "utils/passwd.hpp"56#include "utils/process/status.hpp"57#include "utils/sanity.hpp"58#include "utils/signals/exceptions.hpp"59#include "utils/stacktrace.hpp"60#include "utils/text/exceptions.hpp"61#include "utils/text/operations.ipp"6263namespace datetime = utils::datetime;64namespace executor = utils::process::executor;65namespace fs = utils::fs;66namespace passwd = utils::passwd;67namespace process = utils::process;68namespace signals = utils::signals;69namespace text = utils::text;7071using utils::none;72using utils::optional;737475/// Large timeout for the processes we spawn.76///77/// This number is supposed to be (much) larger than the timeout of the test78/// cases that use it so that children processes are not killed spuriously.79static const datetime::delta infinite_timeout(1000000, 0);808182static void do_exit(const int) UTILS_NORETURN;838485/// Terminates a subprocess without invoking destructors.86///87/// This is just a simple wrapper over _exit(2) because we cannot use std::exit88/// on exit from a subprocess. The reason is that we do not want to invoke any89/// destructors as otherwise we'd clear up the global executor state by mistake.90/// This wouldn't be a major problem if it wasn't because doing so deletes91/// on-disk files and we want to leave them in place so that the parent process92/// can test for them!93///94/// \param exit_code Code to exit with.95static void96do_exit(const int exit_code)97{98std::cout.flush();99std::cerr.flush();100::_exit(exit_code);101}102103104/// Subprocess that creates a cookie file in its work directory.105class child_create_cookie {106/// Name of the cookie to create.107const std::string _cookie_name;108109public:110/// Constructor.111///112/// \param cookie_name Name of the cookie to create.113child_create_cookie(const std::string& cookie_name) :114_cookie_name(cookie_name)115{116}117118/// Runs the subprocess.119void120operator()(const fs::path& /* control_directory */)121UTILS_NORETURN122{123std::cout << "Creating cookie: " << _cookie_name << " (stdout)\n";124std::cerr << "Creating cookie: " << _cookie_name << " (stderr)\n";125atf::utils::create_file(_cookie_name, "");126do_exit(EXIT_SUCCESS);127}128};129130131static void child_delete_all(const fs::path&) UTILS_NORETURN;132133134/// Subprocess that deletes all files in the current directory.135///136/// This is intended to validate that the test runs in an empty directory,137/// separate from any control files that the executor may have created.138///139/// \param control_directory Directory where control files separate from the140/// work directory can be placed.141static void142child_delete_all(const fs::path& control_directory)143{144const fs::path cookie = control_directory / "exec_was_called";145std::ofstream control_file(cookie.c_str());146if (!control_file) {147std::cerr << "Failed to create " << cookie << '\n';148std::abort();149}150151const int exit_code = ::system("rm *") == -1152? EXIT_FAILURE : EXIT_SUCCESS;153do_exit(exit_code);154}155156157static void child_dump_unprivileged_user(const fs::path&) UTILS_NORETURN;158159160/// Subprocess that dumps user configuration.161static void162child_dump_unprivileged_user(const fs::path& /* control_directory */)163{164const passwd::user current_user = passwd::current_user();165std::cout << F("UID = %s\n") % current_user.uid;166do_exit(EXIT_SUCCESS);167}168169170/// Subprocess that returns a specific exit code.171class child_exit {172/// Exit code to return.173int _exit_code;174175public:176/// Constructor.177///178/// \param exit_code Exit code to return.179child_exit(const int exit_code) : _exit_code(exit_code)180{181}182183/// Runs the subprocess.184void185operator()(const fs::path& /* control_directory */)186UTILS_NORETURN187{188do_exit(_exit_code);189}190};191192193static void child_pause(const fs::path&) UTILS_NORETURN;194195196/// Subprocess that just blocks.197static void198child_pause(const fs::path& /* control_directory */)199{200sigset_t mask;201sigemptyset(&mask);202for (;;) {203::sigsuspend(&mask);204}205std::abort();206}207208209static void child_print(const fs::path&) UTILS_NORETURN;210211212/// Subprocess that writes to stdout and stderr.213static void214child_print(const fs::path& /* control_directory */)215{216std::cout << "stdout: some text\n";217std::cerr << "stderr: some other text\n";218219do_exit(EXIT_SUCCESS);220}221222223/// Subprocess that sleeps for a period of time before exiting.224class child_sleep {225/// Seconds to sleep for before termination.226int _seconds;227228public:229/// Construtor.230///231/// \param seconds Seconds to sleep for before termination.232child_sleep(const int seconds) : _seconds(seconds)233{234}235236/// Runs the subprocess.237void238operator()(const fs::path& /* control_directory */)239UTILS_NORETURN240{241::sleep(_seconds);242do_exit(EXIT_SUCCESS);243}244};245246247static void child_spawn_blocking_child(const fs::path&) UTILS_NORETURN;248249250/// Subprocess that spawns a subchild that gets stuck.251///252/// Used by the caller to validate that the whole process tree is terminated253/// when this subprocess is killed.254static void255child_spawn_blocking_child(256const fs::path& /* control_directory */)257{258pid_t pid = ::fork();259if (pid == -1) {260std::cerr << "Cannot fork subprocess\n";261do_exit(EXIT_FAILURE);262} else if (pid == 0) {263for (;;)264::pause();265} else {266const fs::path name = fs::path(utils::getenv("CONTROL_DIR").get()) /267"pid";268std::ofstream pidfile(name.c_str());269if (!pidfile) {270std::cerr << "Failed to create the pidfile\n";271do_exit(EXIT_FAILURE);272}273pidfile << pid;274pidfile.close();275do_exit(EXIT_SUCCESS);276}277}278279280static void child_validate_isolation(const fs::path&) UTILS_NORETURN;281282283/// Subprocess that checks if isolate_child() has been called.284static void285child_validate_isolation(const fs::path& /* control_directory */)286{287if (utils::getenv("HOME").get() == "fake-value") {288std::cerr << "HOME not reset\n";289do_exit(EXIT_FAILURE);290}291if (utils::getenv("LANG")) {292std::cerr << "LANG not unset\n";293do_exit(EXIT_FAILURE);294}295do_exit(EXIT_SUCCESS);296}297298299/// Invokes executor::spawn() with default arguments.300///301/// \param handle The executor on which to invoke spawn().302/// \param args Arguments to the binary.303/// \param timeout Maximum time the program can run for.304/// \param unprivileged_user If set, user to switch to when running the child305/// program.306/// \param stdout_target If not none, file to which to write the stdout of the307/// test case.308/// \param stderr_target If not none, file to which to write the stderr of the309/// test case.310///311/// \return The exec handle for the spawned binary.312template< class Hook >313static executor::exec_handle314do_spawn(executor::executor_handle& handle, Hook hook,315const datetime::delta& timeout = infinite_timeout,316const optional< passwd::user > unprivileged_user = none,317const optional< fs::path > stdout_target = none,318const optional< fs::path > stderr_target = none)319{320const executor::exec_handle exec_handle = handle.spawn< Hook >(321hook, timeout, unprivileged_user, stdout_target, stderr_target);322return exec_handle;323}324325326/// Checks for a specific exit status in the status of a exit_handle.327///328/// \param exit_status The expected exit status.329/// \param status The value of exit_handle.status().330///331/// \post Terminates the calling test case if the status does not match the332/// required value.333static void334require_exit(const int exit_status, const optional< process::status > status)335{336ATF_REQUIRE(status);337ATF_REQUIRE(status.get().exited());338ATF_REQUIRE_EQ(exit_status, status.get().exitstatus());339}340341342/// Ensures that a killed process is gone.343///344/// The way we do this is by sending an idempotent signal to the given PID345/// and checking if the signal was delivered. If it was, the process is346/// still alive; if it was not, then it is gone.347///348/// Note that this might be inaccurate for two reasons:349///350/// 1) The system may have spawned a new process with the same pid as351/// our subchild... but in practice, this does not happen because352/// most systems do not immediately reuse pid numbers. If that353/// happens... well, we get a false test failure.354///355/// 2) We ran so fast that even if the process was sent a signal to356/// die, it has not had enough time to process it yet. This is why357/// we retry this a few times.358///359/// \param pid PID of the process to check.360static void361ensure_dead(const pid_t pid)362{363int attempts = 30;364retry:365if (::kill(pid, SIGCONT) != -1 || errno != ESRCH) {366if (attempts > 0) {367std::cout << "Subprocess not dead yet; retrying wait\n";368--attempts;369::usleep(100000);370goto retry;371}372ATF_FAIL(F("The subprocess %s of our child was not killed") % pid);373}374}375376377ATF_TEST_CASE_WITHOUT_HEAD(integration__run_one);378ATF_TEST_CASE_BODY(integration__run_one)379{380executor::executor_handle handle = executor::setup();381382const executor::exec_handle exec_handle = do_spawn(handle, child_exit(41));383384executor::exit_handle exit_handle = handle.wait_any();385ATF_REQUIRE_EQ(exec_handle.pid(), exit_handle.original_pid());386require_exit(41, exit_handle.status());387exit_handle.cleanup();388389handle.cleanup();390}391392393ATF_TEST_CASE_WITHOUT_HEAD(integration__run_many);394ATF_TEST_CASE_BODY(integration__run_many)395{396static const std::size_t num_children = 30;397398executor::executor_handle handle = executor::setup();399400std::size_t total_children = 0;401std::map< int, int > exp_exit_statuses;402std::map< int, datetime::timestamp > exp_start_times;403for (std::size_t i = 0; i < num_children; ++i) {404const datetime::timestamp start_time = datetime::timestamp::from_values(4052014, 12, 8, 9, 40, 0, i);406407for (std::size_t j = 0; j < 3; j++) {408const std::size_t id = i * 3 + j;409410datetime::set_mock_now(start_time);411const int pid = do_spawn(handle, child_exit(id)).pid();412exp_exit_statuses.insert(std::make_pair(pid, id));413exp_start_times.insert(std::make_pair(pid, start_time));414++total_children;415}416}417418for (std::size_t i = 0; i < total_children; ++i) {419const datetime::timestamp end_time = datetime::timestamp::from_values(4202014, 12, 8, 9, 50, 10, i);421datetime::set_mock_now(end_time);422executor::exit_handle exit_handle = handle.wait_any();423const int original_pid = exit_handle.original_pid();424425const int exit_status = exp_exit_statuses.find(original_pid)->second;426const datetime::timestamp& start_time = exp_start_times.find(427original_pid)->second;428429require_exit(exit_status, exit_handle.status());430431ATF_REQUIRE_EQ(start_time, exit_handle.start_time());432ATF_REQUIRE_EQ(end_time, exit_handle.end_time());433434exit_handle.cleanup();435436ATF_REQUIRE(!atf::utils::file_exists(437exit_handle.stdout_file().str()));438ATF_REQUIRE(!atf::utils::file_exists(439exit_handle.stderr_file().str()));440ATF_REQUIRE(!atf::utils::file_exists(441exit_handle.work_directory().str()));442}443444handle.cleanup();445}446447448ATF_TEST_CASE_WITHOUT_HEAD(integration__parameters_and_output);449ATF_TEST_CASE_BODY(integration__parameters_and_output)450{451executor::executor_handle handle = executor::setup();452453const executor::exec_handle exec_handle = do_spawn(handle, child_print);454455executor::exit_handle exit_handle = handle.wait_any();456457ATF_REQUIRE_EQ(exec_handle.pid(), exit_handle.original_pid());458459require_exit(EXIT_SUCCESS, exit_handle.status());460461const fs::path stdout_file = exit_handle.stdout_file();462ATF_REQUIRE(atf::utils::compare_file(463stdout_file.str(), "stdout: some text\n"));464const fs::path stderr_file = exit_handle.stderr_file();465ATF_REQUIRE(atf::utils::compare_file(466stderr_file.str(), "stderr: some other text\n"));467468exit_handle.cleanup();469ATF_REQUIRE(!fs::exists(stdout_file));470ATF_REQUIRE(!fs::exists(stderr_file));471472handle.cleanup();473}474475476ATF_TEST_CASE_WITHOUT_HEAD(integration__custom_output_files);477ATF_TEST_CASE_BODY(integration__custom_output_files)478{479executor::executor_handle handle = executor::setup();480481const fs::path stdout_file("custom-stdout.txt");482const fs::path stderr_file("custom-stderr.txt");483484const executor::exec_handle exec_handle = do_spawn(485handle, child_print, infinite_timeout, none,486utils::make_optional(stdout_file),487utils::make_optional(stderr_file));488489executor::exit_handle exit_handle = handle.wait_any();490491ATF_REQUIRE_EQ(exec_handle.pid(), exit_handle.original_pid());492493require_exit(EXIT_SUCCESS, exit_handle.status());494495ATF_REQUIRE_EQ(stdout_file, exit_handle.stdout_file());496ATF_REQUIRE_EQ(stderr_file, exit_handle.stderr_file());497498exit_handle.cleanup();499500handle.cleanup();501502// Must compare after cleanup to ensure the files did not get deleted.503ATF_REQUIRE(atf::utils::compare_file(504stdout_file.str(), "stdout: some text\n"));505ATF_REQUIRE(atf::utils::compare_file(506stderr_file.str(), "stderr: some other text\n"));507}508509510ATF_TEST_CASE_WITHOUT_HEAD(integration__timestamps);511ATF_TEST_CASE_BODY(integration__timestamps)512{513executor::executor_handle handle = executor::setup();514515const datetime::timestamp start_time = datetime::timestamp::from_values(5162014, 12, 8, 9, 35, 10, 1000);517const datetime::timestamp end_time = datetime::timestamp::from_values(5182014, 12, 8, 9, 35, 20, 2000);519520datetime::set_mock_now(start_time);521do_spawn(handle, child_exit(70));522523datetime::set_mock_now(end_time);524executor::exit_handle exit_handle = handle.wait_any();525526require_exit(70, exit_handle.status());527528ATF_REQUIRE_EQ(start_time, exit_handle.start_time());529ATF_REQUIRE_EQ(end_time, exit_handle.end_time());530exit_handle.cleanup();531532handle.cleanup();533}534535536ATF_TEST_CASE_WITHOUT_HEAD(integration__files);537ATF_TEST_CASE_BODY(integration__files)538{539executor::executor_handle handle = executor::setup();540541do_spawn(handle, child_create_cookie("cookie.12345"));542543executor::exit_handle exit_handle = handle.wait_any();544545ATF_REQUIRE(atf::utils::file_exists(546(exit_handle.work_directory() / "cookie.12345").str()));547548exit_handle.cleanup();549550ATF_REQUIRE(!atf::utils::file_exists(exit_handle.stdout_file().str()));551ATF_REQUIRE(!atf::utils::file_exists(exit_handle.stderr_file().str()));552ATF_REQUIRE(!atf::utils::file_exists(exit_handle.work_directory().str()));553554handle.cleanup();555}556557558ATF_TEST_CASE_WITHOUT_HEAD(integration__followup);559ATF_TEST_CASE_BODY(integration__followup)560{561executor::executor_handle handle = executor::setup();562563(void)handle.spawn(child_create_cookie("cookie.1"), infinite_timeout, none);564executor::exit_handle exit_1_handle = handle.wait_any();565566(void)handle.spawn_followup(child_create_cookie("cookie.2"), exit_1_handle,567infinite_timeout);568executor::exit_handle exit_2_handle = handle.wait_any();569570ATF_REQUIRE_EQ(exit_1_handle.stdout_file(), exit_2_handle.stdout_file());571ATF_REQUIRE_EQ(exit_1_handle.stderr_file(), exit_2_handle.stderr_file());572ATF_REQUIRE_EQ(exit_1_handle.control_directory(),573exit_2_handle.control_directory());574ATF_REQUIRE_EQ(exit_1_handle.work_directory(),575exit_2_handle.work_directory());576577(void)handle.spawn_followup(child_create_cookie("cookie.3"), exit_2_handle,578infinite_timeout);579exit_2_handle.cleanup();580exit_1_handle.cleanup();581executor::exit_handle exit_3_handle = handle.wait_any();582583ATF_REQUIRE_EQ(exit_1_handle.stdout_file(), exit_3_handle.stdout_file());584ATF_REQUIRE_EQ(exit_1_handle.stderr_file(), exit_3_handle.stderr_file());585ATF_REQUIRE_EQ(exit_1_handle.control_directory(),586exit_3_handle.control_directory());587ATF_REQUIRE_EQ(exit_1_handle.work_directory(),588exit_3_handle.work_directory());589590ATF_REQUIRE(atf::utils::file_exists(591(exit_1_handle.work_directory() / "cookie.1").str()));592ATF_REQUIRE(atf::utils::file_exists(593(exit_1_handle.work_directory() / "cookie.2").str()));594ATF_REQUIRE(atf::utils::file_exists(595(exit_1_handle.work_directory() / "cookie.3").str()));596597ATF_REQUIRE(atf::utils::compare_file(598exit_1_handle.stdout_file().str(),599"Creating cookie: cookie.1 (stdout)\n"600"Creating cookie: cookie.2 (stdout)\n"601"Creating cookie: cookie.3 (stdout)\n"));602603ATF_REQUIRE(atf::utils::compare_file(604exit_1_handle.stderr_file().str(),605"Creating cookie: cookie.1 (stderr)\n"606"Creating cookie: cookie.2 (stderr)\n"607"Creating cookie: cookie.3 (stderr)\n"));608609exit_3_handle.cleanup();610611ATF_REQUIRE(!atf::utils::file_exists(exit_1_handle.stdout_file().str()));612ATF_REQUIRE(!atf::utils::file_exists(exit_1_handle.stderr_file().str()));613ATF_REQUIRE(!atf::utils::file_exists(exit_1_handle.work_directory().str()));614615handle.cleanup();616}617618619ATF_TEST_CASE_WITHOUT_HEAD(integration__output_files_always_exist);620ATF_TEST_CASE_BODY(integration__output_files_always_exist)621{622executor::executor_handle handle = executor::setup();623624// This test is racy: we specify a very short timeout for the subprocess so625// that we cause the subprocess to exit before it has had time to set up the626// output files. However, for scheduling reasons, the subprocess may627// actually run to completion before the timer triggers. Retry this a few628// times to attempt to catch a "good test".629for (int i = 0; i < 50; i++) {630const executor::exec_handle exec_handle =631do_spawn(handle, child_exit(0), datetime::delta(0, 100000));632executor::exit_handle exit_handle = handle.wait(exec_handle);633ATF_REQUIRE(fs::exists(exit_handle.stdout_file()));634ATF_REQUIRE(fs::exists(exit_handle.stderr_file()));635exit_handle.cleanup();636}637638handle.cleanup();639}640641642ATF_TEST_CASE(integration__timeouts);643ATF_TEST_CASE_HEAD(integration__timeouts)644{645set_md_var("timeout", "60");646}647ATF_TEST_CASE_BODY(integration__timeouts)648{649executor::executor_handle handle = executor::setup();650651const executor::exec_handle exec_handle1 =652do_spawn(handle, child_sleep(30), datetime::delta(2, 0));653const executor::exec_handle exec_handle2 =654do_spawn(handle, child_sleep(40), datetime::delta(5, 0));655const executor::exec_handle exec_handle3 =656do_spawn(handle, child_exit(15));657658{659executor::exit_handle exit_handle = handle.wait_any();660ATF_REQUIRE_EQ(exec_handle3.pid(), exit_handle.original_pid());661require_exit(15, exit_handle.status());662exit_handle.cleanup();663}664665{666executor::exit_handle exit_handle = handle.wait_any();667ATF_REQUIRE_EQ(exec_handle1.pid(), exit_handle.original_pid());668ATF_REQUIRE(!exit_handle.status());669const datetime::delta duration =670exit_handle.end_time() - exit_handle.start_time();671ATF_REQUIRE(duration < datetime::delta(10, 0));672ATF_REQUIRE(duration >= datetime::delta(2, 0));673exit_handle.cleanup();674}675676{677executor::exit_handle exit_handle = handle.wait_any();678ATF_REQUIRE_EQ(exec_handle2.pid(), exit_handle.original_pid());679ATF_REQUIRE(!exit_handle.status());680const datetime::delta duration =681exit_handle.end_time() - exit_handle.start_time();682ATF_REQUIRE(duration < datetime::delta(10, 0));683ATF_REQUIRE(duration >= datetime::delta(4, 0));684exit_handle.cleanup();685}686687handle.cleanup();688}689690691ATF_TEST_CASE(integration__unprivileged_user);692ATF_TEST_CASE_HEAD(integration__unprivileged_user)693{694set_md_var("require.config", "unprivileged-user");695set_md_var("require.user", "root");696}697ATF_TEST_CASE_BODY(integration__unprivileged_user)698{699executor::executor_handle handle = executor::setup();700701const passwd::user unprivileged_user = passwd::find_user_by_name(702get_config_var("unprivileged-user"));703704do_spawn(handle, child_dump_unprivileged_user,705infinite_timeout, utils::make_optional(unprivileged_user));706707executor::exit_handle exit_handle = handle.wait_any();708ATF_REQUIRE(atf::utils::compare_file(709exit_handle.stdout_file().str(),710F("UID = %s\n") % unprivileged_user.uid));711exit_handle.cleanup();712713handle.cleanup();714}715716717ATF_TEST_CASE_WITHOUT_HEAD(integration__auto_cleanup);718ATF_TEST_CASE_BODY(integration__auto_cleanup)719{720std::vector< int > pids;721std::vector< fs::path > paths;722{723executor::executor_handle handle = executor::setup();724725pids.push_back(do_spawn(handle, child_exit(10)).pid());726pids.push_back(do_spawn(handle, child_exit(20)).pid());727728// This invocation is never waited for below. This is intentional: we729// want the destructor to clean the "leaked" test automatically so that730// the clean up of the parent work directory also happens correctly.731pids.push_back(do_spawn(handle, child_pause).pid());732733executor::exit_handle exit_handle1 = handle.wait_any();734paths.push_back(exit_handle1.stdout_file());735paths.push_back(exit_handle1.stderr_file());736paths.push_back(exit_handle1.work_directory());737738executor::exit_handle exit_handle2 = handle.wait_any();739paths.push_back(exit_handle2.stdout_file());740paths.push_back(exit_handle2.stderr_file());741paths.push_back(exit_handle2.work_directory());742}743for (std::vector< int >::const_iterator iter = pids.begin();744iter != pids.end(); ++iter) {745ensure_dead(*iter);746}747for (std::vector< fs::path >::const_iterator iter = paths.begin();748iter != paths.end(); ++iter) {749ATF_REQUIRE(!atf::utils::file_exists((*iter).str()));750}751}752753754/// Ensures that interrupting an executor cleans things up correctly.755///756/// This test scenario is tricky. We spawn a master child process that runs the757/// executor code and we send a signal to it externally. The child process758/// spawns a bunch of tests that block indefinitely and tries to wait for their759/// results. When the signal is received, we expect an interrupt_error to be760/// raised, which in turn should clean up all test resources and exit the master761/// child process successfully.762///763/// \param signo Signal to deliver to the executor.764static void765do_signal_handling_test(const int signo)766{767static const char* cookie = "spawned.txt";768769const pid_t pid = ::fork();770ATF_REQUIRE(pid != -1);771if (pid == 0) {772static const std::size_t num_children = 3;773774optional< fs::path > root_work_directory;775try {776executor::executor_handle handle = executor::setup();777root_work_directory = handle.root_work_directory();778779for (std::size_t i = 0; i < num_children; ++i) {780std::cout << "Spawned child number " << i << '\n';781do_spawn(handle, child_pause);782}783784std::cout << "Creating " << cookie << " cookie\n";785atf::utils::create_file(cookie, "");786787std::cout << "Waiting for subprocess termination\n";788for (std::size_t i = 0; i < num_children; ++i) {789executor::exit_handle exit_handle = handle.wait_any();790// We may never reach this point in the test, but if we do let's791// make sure the subprocess was terminated as expected.792if (exit_handle.status()) {793if (exit_handle.status().get().signaled() &&794exit_handle.status().get().termsig() == SIGKILL) {795// OK.796} else {797std::cerr << "Child exited with unexpected code: "798<< exit_handle.status().get();799std::exit(EXIT_FAILURE);800}801} else {802std::cerr << "Child timed out\n";803std::exit(EXIT_FAILURE);804}805exit_handle.cleanup();806}807std::cerr << "Terminating without reception of signal\n";808std::exit(EXIT_FAILURE);809} catch (const signals::interrupted_error& unused_error) {810std::cerr << "Terminating due to interrupted_error\n";811// We never kill ourselves until the cookie is created, so it is812// guaranteed that the optional root_work_directory has been813// initialized at this point.814if (atf::utils::file_exists(root_work_directory.get().str())) {815// Some cleanup did not happen; error out.816std::exit(EXIT_FAILURE);817} else {818std::exit(EXIT_SUCCESS);819}820}821std::abort();822}823824std::cout << "Waiting for " << cookie << " cookie creation\n";825while (!atf::utils::file_exists(cookie)) {826// Wait for processes.827}828ATF_REQUIRE(::unlink(cookie) != -1);829std::cout << "Killing process\n";830ATF_REQUIRE(::kill(pid, signo) != -1);831832int status;833std::cout << "Waiting for process termination\n";834ATF_REQUIRE(::waitpid(pid, &status, 0) != -1);835ATF_REQUIRE(WIFEXITED(status));836ATF_REQUIRE_EQ(EXIT_SUCCESS, WEXITSTATUS(status));837}838839840ATF_TEST_CASE_WITHOUT_HEAD(integration__signal_handling);841ATF_TEST_CASE_BODY(integration__signal_handling)842{843// This test scenario is racy so run it multiple times to have higher844// chances of exposing problems.845const std::size_t rounds = 20;846847for (std::size_t i = 0; i < rounds; ++i) {848std::cout << F("Testing round %s\n") % i;849do_signal_handling_test(SIGHUP);850do_signal_handling_test(SIGINT);851do_signal_handling_test(SIGTERM);852}853}854855856ATF_TEST_CASE_WITHOUT_HEAD(integration__isolate_child_is_called);857ATF_TEST_CASE_BODY(integration__isolate_child_is_called)858{859executor::executor_handle handle = executor::setup();860861utils::setenv("HOME", "fake-value");862utils::setenv("LANG", "es_ES");863do_spawn(handle, child_validate_isolation);864865executor::exit_handle exit_handle = handle.wait_any();866require_exit(EXIT_SUCCESS, exit_handle.status());867exit_handle.cleanup();868869handle.cleanup();870}871872873ATF_TEST_CASE_WITHOUT_HEAD(integration__process_group_is_terminated);874ATF_TEST_CASE_BODY(integration__process_group_is_terminated)875{876utils::setenv("CONTROL_DIR", fs::current_path().str());877878executor::executor_handle handle = executor::setup();879do_spawn(handle, child_spawn_blocking_child);880881executor::exit_handle exit_handle = handle.wait_any();882require_exit(EXIT_SUCCESS, exit_handle.status());883exit_handle.cleanup();884885handle.cleanup();886887if (!fs::exists(fs::path("pid")))888fail("The pid file was not created");889890std::ifstream pidfile("pid");891ATF_REQUIRE(pidfile);892pid_t pid;893pidfile >> pid;894pidfile.close();895896ensure_dead(pid);897}898899900ATF_TEST_CASE_WITHOUT_HEAD(integration__prevent_clobbering_control_files);901ATF_TEST_CASE_BODY(integration__prevent_clobbering_control_files)902{903executor::executor_handle handle = executor::setup();904905do_spawn(handle, child_delete_all);906907executor::exit_handle exit_handle = handle.wait_any();908require_exit(EXIT_SUCCESS, exit_handle.status());909ATF_REQUIRE(atf::utils::file_exists(910(exit_handle.control_directory() / "exec_was_called").str()));911ATF_REQUIRE(!atf::utils::file_exists(912(exit_handle.work_directory() / "exec_was_called").str()));913exit_handle.cleanup();914915handle.cleanup();916}917918919ATF_INIT_TEST_CASES(tcs)920{921ATF_ADD_TEST_CASE(tcs, integration__run_one);922ATF_ADD_TEST_CASE(tcs, integration__run_many);923924ATF_ADD_TEST_CASE(tcs, integration__parameters_and_output);925ATF_ADD_TEST_CASE(tcs, integration__custom_output_files);926ATF_ADD_TEST_CASE(tcs, integration__timestamps);927ATF_ADD_TEST_CASE(tcs, integration__files);928929ATF_ADD_TEST_CASE(tcs, integration__followup);930931ATF_ADD_TEST_CASE(tcs, integration__output_files_always_exist);932ATF_ADD_TEST_CASE(tcs, integration__timeouts);933ATF_ADD_TEST_CASE(tcs, integration__unprivileged_user);934ATF_ADD_TEST_CASE(tcs, integration__auto_cleanup);935ATF_ADD_TEST_CASE(tcs, integration__signal_handling);936ATF_ADD_TEST_CASE(tcs, integration__isolate_child_is_called);937ATF_ADD_TEST_CASE(tcs, integration__process_group_is_terminated);938ATF_ADD_TEST_CASE(tcs, integration__prevent_clobbering_control_files);939}940941942