From e197ae6fae73719266fd4747f499cd6106fbff4e Mon Sep 17 00:00:00 2001 From: Karen Arutyunov Date: Thu, 8 Oct 2020 21:24:00 +0300 Subject: Add process::term() and implement process::kill() on Windows --- tests/process-term/buildfile | 6 + tests/process-term/driver.cxx | 413 ++++++++++++++++++++++++++++++++++++++++++ tests/process-term/testscript | 4 + 3 files changed, 423 insertions(+) create mode 100644 tests/process-term/buildfile create mode 100644 tests/process-term/driver.cxx create mode 100644 tests/process-term/testscript (limited to 'tests/process-term') diff --git a/tests/process-term/buildfile b/tests/process-term/buildfile new file mode 100644 index 0000000..e710179 --- /dev/null +++ b/tests/process-term/buildfile @@ -0,0 +1,6 @@ +# file : tests/process-term/buildfile +# license : MIT; see accompanying LICENSE file + +import libs = libbutl%lib{butl} + +exe{driver}: {hxx cxx}{*} $libs testscript diff --git a/tests/process-term/driver.cxx b/tests/process-term/driver.cxx new file mode 100644 index 0000000..0e92c2b --- /dev/null +++ b/tests/process-term/driver.cxx @@ -0,0 +1,413 @@ +// file : tests/process-term/driver.cxx -*- C++ -*- +// license : MIT; see accompanying LICENSE file + +#ifndef _WIN32 +# include +# include +# include +# include +#else +# include +#endif + +#include + +#ifndef __cpp_lib_modules_ts +#include +#include // ERANGE +#include // move() +#include // atexit(), exit(), strtoull() +#include // memset() +#include // uint64_t +#include +#ifndef _WIN32 +# include +#endif +#endif + +// Other includes. + +#ifdef __cpp_modules_ts +#ifdef __cpp_lib_modules_ts +import std.core; +import std.io; +#endif +import butl.process; +import butl.optional; +import butl.fdstream; +#else +#include +#include +#include +#endif + +using namespace std; +using namespace butl; + +void +atexit_func () +{ + cout << "exiting"; +} + +#ifndef _WIN32 + +volatile sig_atomic_t term_sig = 0; + +static void +term (int sig) +{ + term_sig = sig; +} +#endif + +// Usages: +// +// argv[0] +// argv[0] -s [-t (ignore|exit|default)] [-e] [-c ] +// +// In the first form run some basic process termination tests, running its +// child in the second form. +// +// In the second form optionally register the SIGTERM signal handler +// (POSIX-only) and the atexit function, then sleep for the requested number +// of seconds and exit with the specified status. +// +// -s +// Sleep for the specified timeout. +// +// -t (ignore|exit|default) +// Register the SIGTERM signal handler. If the signal is received than +// either ignore it, interrupt the sleep and exit, or call the default +// handler. +// +// -e +// Register the function with atexit() that prints the 'exiting' string to +// stdout. +// +// -c +// Exit with the specified status (zero by default). +// +int +main (int argc, const char* argv[]) +{ + using butl::optional; + + auto num = [] (const string& s) + { + assert (!s.empty ()); + + char* e (nullptr); + uint64_t r (strtoull (s.c_str (), &e, 10)); // Can't throw. + assert (errno != ERANGE && e == s.c_str () + s.size ()); + + return r; + }; + + int ec (0); + optional sec; + +#ifndef _WIN32 + enum class sig_action + { + ignore, + exit, + default_ + }; + + optional term_action; + + struct sigaction def_handler; +#endif + + for (int i (1); i != argc; ++i) + { + string o (argv[i]); + + if (o == "-s") + { + assert (++i != argc); + sec = num (argv[i]); + } + else if (o == "-c") + { + assert (++i != argc); + ec = static_cast (num (argv[i])); + } + else if (o == "-e") + { + assert (atexit (atexit_func) == 0); + } + else if (o == "-t") + { + assert (++i != argc); + +#ifndef _WIN32 + string v (argv[i]); + + if (v == "ignore") + term_action = sig_action::ignore; + else if (v == "exit") + term_action = sig_action::exit; + else if (v == "default") + term_action = sig_action::default_; + else + assert (false); + + struct sigaction action; + memset (&action, 0, sizeof (action)); + action.sa_handler = term; + assert (sigaction (SIGTERM, &action, &def_handler) == 0); +#endif + } + else + assert (false); + } + +#ifndef _WIN32 + auto sleep = [&term_action, &def_handler] (uint64_t sec) + { + // Wait until timeout expires or SIGTERM is received and is not ignored. + // + for (timespec tm {static_cast (sec), 0}; + nanosleep (&tm, &tm) == -1; ) + { + assert (term_action && errno == EINTR && term_sig == SIGTERM); + + if (*term_action == sig_action::ignore) + continue; + + if (*term_action == sig_action::default_) + { + assert (sigaction (term_sig, &def_handler, nullptr) == 0); + kill (getpid (), term_sig); + } + + break; + } + }; +#else + auto sleep = [] (uint64_t sec) + { + Sleep (static_cast (sec) * 1000); + }; +#endif + + // Child process. + // + if (sec) + { + if (*sec != 0) + sleep (*sec); + + return ec; + } + + // Main process. + // + + // Return true if the child process has written the specified string to + // stdout, represented by the reading end of the specified pipe. + // + auto test_out = [] (fdpipe&& pipe, const char* out) + { + pipe.out.close (); + + ifdstream is (move (pipe.in)); + bool r (is.read_text () == out); + is.close (); + return r; + }; + +#ifndef _WIN32 + // Terminate a process with the default SIGTERM handler. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, argv[0], "-s", 10, "-e")); + + sleep (2); // Give the child some time to initialize. + p.term (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (!p.exit->normal ()); + assert (p.exit->signal () == SIGTERM); + } + + // Terminate a process that exits on SIGTERM. Make sure it exits normally + // and atexit function is called. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, + argv[0], "-s", 10, "-t", "exit", "-e", "-c", 5)); + + sleep (2); // Give the child some time to initialize. + p.term (); + + assert (test_out (move (pipe), "exiting")); + + assert (!p.wait ()); + assert (p.exit); + assert (p.exit->normal ()); + assert (p.exit->code () == 5); + } + + // Terminate a process that calls the default handler on SIGTERM. + // + { + fdpipe pipe (fdopen_pipe ()); + process p ( + process_start (0, pipe, 2, + argv[0], "-s", 10, "-t", "default", "-e", "-c", 5)); + + sleep (2); // Give the child some time to initialize. + p.term (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (!p.exit->normal ()); + assert (p.exit->signal () == SIGTERM); + } + + // Terminate and then kill still running process. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, + argv[0], "-s", 10, "-t", "ignore", "-e")); + + sleep (2); // Give the child some time to initialize. + p.term (); + + assert (!p.timed_wait (chrono::seconds (1))); + + p.kill (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (!p.exit->normal ()); + assert (p.exit->signal () == SIGKILL); + } + + // Terminate an already terminated process. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, argv[0], "-s", 0, "-c", 5)); + + sleep (4); + p.term (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (p.exit->normal ()); + assert (p.exit->code () == 5); + } + + // Terminate a process being terminated. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, argv[0], "-s", 10)); + + p.term (); + p.term (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (!p.exit->normal ()); + } + + // Kill a process being terminated. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, argv[0], "-s", 10)); + + p.term (); + p.kill (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (!p.exit->normal ()); + assert (p.exit->signal () == SIGTERM || p.exit->signal () == SIGKILL); + } + + // Kill a process being killed. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, argv[0], "-s", 10)); + + p.kill (); + p.kill (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (!p.exit->normal ()); + } +#endif + + // Terminate and wait a process. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, argv[0], "-s", 10, "-e")); + + p.term (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (!p.exit->normal ()); + } + + // Kill and wait a process. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, argv[0], "-s", 10, "-e")); + + p.kill (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (!p.exit->normal ()); + } + + // Kill a terminated process. + // + { + fdpipe pipe (fdopen_pipe ()); + process p (process_start (0, pipe, 2, argv[0], "-s", 0, "-c", 5)); + + sleep (4); + p.kill (); + + assert (test_out (move (pipe), "")); + + assert (!p.wait ()); + assert (p.exit); + assert (p.exit->normal ()); + assert (p.exit->code () == 5); + } +} diff --git a/tests/process-term/testscript b/tests/process-term/testscript new file mode 100644 index 0000000..f61899c --- /dev/null +++ b/tests/process-term/testscript @@ -0,0 +1,4 @@ +# file : tests/process-term/testscript +# license : MIT; see accompanying LICENSE file + +$* -- cgit v1.1