aboutsummaryrefslogtreecommitdiff
path: root/tests/process-term
diff options
context:
space:
mode:
authorKaren Arutyunov <karen@codesynthesis.com>2020-10-08 21:24:00 +0300
committerKaren Arutyunov <karen@codesynthesis.com>2020-11-05 18:23:39 +0300
commite197ae6fae73719266fd4747f499cd6106fbff4e (patch)
tree3cf4e18310469492f371045ae83c28cb25fab4e7 /tests/process-term
parent7a8386289b18678c2ee49ffdfcf71e3a2abd3258 (diff)
Add process::term() and implement process::kill() on Windows
Diffstat (limited to 'tests/process-term')
-rw-r--r--tests/process-term/buildfile6
-rw-r--r--tests/process-term/driver.cxx413
-rw-r--r--tests/process-term/testscript4
3 files changed, 423 insertions, 0 deletions
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 <time.h>
+# include <signal.h>
+# include <unistd.h>
+# include <sys/types.h>
+#else
+# include <libbutl/win32-utility.hxx>
+#endif
+
+#include <cassert>
+
+#ifndef __cpp_lib_modules_ts
+#include <string>
+#include <cerrno> // ERANGE
+#include <utility> // move()
+#include <cstdlib> // atexit(), exit(), strtoull()
+#include <cstring> // memset()
+#include <cstdint> // uint64_t
+#include <iostream>
+#ifndef _WIN32
+# include <chrono>
+#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 <libbutl/process.mxx>
+#include <libbutl/optional.mxx>
+#include <libbutl/fdstream.mxx>
+#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 <sec> [-t (ignore|exit|default)] [-e] [-c <num>]
+//
+// 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 <sec>
+// 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 <num>
+// 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<uint64_t> sec;
+
+#ifndef _WIN32
+ enum class sig_action
+ {
+ ignore,
+ exit,
+ default_
+ };
+
+ optional<sig_action> 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<int> (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<time_t> (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<DWORD> (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
+
+$*