summaryrefslogtreecommitdiffstats
path: root/lib/util/tests/tfork.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--lib/util/tests/tfork.c855
1 files changed, 855 insertions, 0 deletions
diff --git a/lib/util/tests/tfork.c b/lib/util/tests/tfork.c
new file mode 100644
index 0000000..70ae975
--- /dev/null
+++ b/lib/util/tests/tfork.c
@@ -0,0 +1,855 @@
+/*
+ * Tests for tfork
+ *
+ * Copyright Ralph Boehme <slow@samba.org> 2017
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "replace.h"
+#include <talloc.h>
+#include <tevent.h>
+#include "system/filesys.h"
+#include "system/wait.h"
+#include "system/select.h"
+#include "libcli/util/ntstatus.h"
+#include "torture/torture.h"
+#include "lib/util/data_blob.h"
+#include "torture/local/proto.h"
+#include "lib/util/tfork.h"
+#include "lib/util/samba_util.h"
+#include "lib/util/sys_rw.h"
+#ifdef HAVE_PTHREAD
+#include <pthread.h>
+#endif
+
+static bool test_tfork_simple(struct torture_context *tctx)
+{
+ pid_t parent = getpid();
+ struct tfork *t = NULL;
+ pid_t child;
+ int ret;
+
+ t = tfork_create();
+ if (t == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child = tfork_child_pid(t);
+ if (child == 0) {
+ torture_comment(tctx, "my parent pid is %d\n", parent);
+ torture_assert(tctx, getpid() != parent, "tfork failed\n");
+ _exit(0);
+ }
+
+ ret = tfork_destroy(&t);
+ torture_assert(tctx, ret == 0, "tfork_destroy failed\n");
+
+ return true;
+}
+
+static bool test_tfork_status(struct torture_context *tctx)
+{
+ struct tfork *t = NULL;
+ int status;
+ pid_t child;
+ bool ok = true;
+
+ t = tfork_create();
+ if (t == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child = tfork_child_pid(t);
+ if (child == 0) {
+ _exit(123);
+ }
+
+ status = tfork_status(&t, true);
+ if (status == -1) {
+ torture_fail(tctx, "tfork_status failed\n");
+ }
+
+ torture_assert_goto(tctx, WIFEXITED(status) == true, ok, done,
+ "tfork failed\n");
+ torture_assert_goto(tctx, WEXITSTATUS(status) == 123, ok, done,
+ "tfork failed\n");
+
+ torture_comment(tctx, "exit status [%d]\n", WEXITSTATUS(status));
+
+done:
+ return ok;
+}
+
+static bool test_tfork_sigign(struct torture_context *tctx)
+{
+ struct tfork *t = NULL;
+ struct sigaction act;
+ pid_t child;
+ int status;
+ bool ok = true;
+ int ret;
+
+ act = (struct sigaction) {
+ .sa_flags = SA_NOCLDWAIT,
+ .sa_handler = SIG_IGN,
+ };
+
+ ret = sigaction(SIGCHLD, &act, NULL);
+ torture_assert_goto(tctx, ret == 0, ok, done, "sigaction failed\n");
+
+ t = tfork_create();
+ if (t == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child = tfork_child_pid(t);
+ if (child == 0) {
+ sleep(1);
+ _exit(123);
+ }
+
+ child = fork();
+ if (child == -1) {
+ torture_fail(tctx, "fork failed\n");
+ return false;
+ }
+ if (child == 0) {
+ _exit(0);
+ }
+
+ status = tfork_status(&t, true);
+ if (status == -1) {
+ torture_fail(tctx, "tfork_status failed\n");
+ }
+
+ torture_assert_goto(tctx, WIFEXITED(status) == true, ok, done,
+ "tfork failed\n");
+ torture_assert_goto(tctx, WEXITSTATUS(status) == 123, ok, done,
+ "tfork failed\n");
+ torture_comment(tctx, "exit status [%d]\n", WEXITSTATUS(status));
+
+done:
+ return ok;
+}
+
+static void sigchld_handler1(int signum, siginfo_t *si, void *u)
+{
+ pid_t pid;
+ int status;
+
+ if (signum != SIGCHLD) {
+ abort();
+ }
+
+ pid = waitpid(si->si_pid, &status, 0);
+ if (pid != si->si_pid) {
+ abort();
+ }
+}
+
+static bool test_tfork_sighandler(struct torture_context *tctx)
+{
+ struct tfork *t = NULL;
+ struct sigaction act;
+ struct sigaction oldact;
+ pid_t child;
+ int status;
+ bool ok = true;
+ int ret;
+
+ act = (struct sigaction) {
+ .sa_flags = SA_SIGINFO,
+ .sa_sigaction = sigchld_handler1,
+ };
+
+ ret = sigaction(SIGCHLD, &act, &oldact);
+ torture_assert_goto(tctx, ret == 0, ok, done, "sigaction failed\n");
+
+ t = tfork_create();
+ if (t == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child = tfork_child_pid(t);
+ if (child == 0) {
+ sleep(1);
+ _exit(123);
+ }
+
+ child = fork();
+ if (child == -1) {
+ torture_fail(tctx, "fork failed\n");
+ return false;
+ }
+ if (child == 0) {
+ _exit(0);
+ }
+
+ status = tfork_status(&t, true);
+ if (status == -1) {
+ torture_fail(tctx, "tfork_status failed\n");
+ }
+
+ torture_assert_goto(tctx, WIFEXITED(status) == true, ok, done,
+ "tfork failed\n");
+ torture_assert_goto(tctx, WEXITSTATUS(status) == 123, ok, done,
+ "tfork failed\n");
+ torture_comment(tctx, "exit status [%d]\n", WEXITSTATUS(status));
+
+done:
+ sigaction(SIGCHLD, &oldact, NULL);
+
+ return ok;
+}
+
+static bool test_tfork_process_hierarchy(struct torture_context *tctx)
+{
+ struct tfork *t = NULL;
+ pid_t pid = getpid();
+ pid_t child;
+ pid_t pgid = getpgid(0);
+ pid_t sid = getsid(0);
+ char *procpath = NULL;
+ int status;
+ struct stat st;
+ int ret;
+ bool ok = true;
+
+ procpath = talloc_asprintf(tctx, "/proc/%d/status", getpid());
+ torture_assert_not_null(tctx, procpath, "talloc_asprintf failed\n");
+
+ ret = stat(procpath, &st);
+ TALLOC_FREE(procpath);
+ if (ret != 0) {
+ if (errno == ENOENT) {
+ torture_skip(tctx, "/proc missing\n");
+ }
+ torture_fail(tctx, "stat failed\n");
+ }
+
+ t = tfork_create();
+ if (t == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child = tfork_child_pid(t);
+ if (child == 0) {
+ char *cmd = NULL;
+ FILE *fp = NULL;
+ char line[64];
+ char *p;
+ pid_t ppid;
+
+ torture_assert_goto(tctx, pgid == getpgid(0), ok, child_fail, "tfork failed\n");
+ torture_assert_goto(tctx, sid == getsid(0), ok, child_fail, "tfork failed\n");
+
+ cmd = talloc_asprintf(tctx, "cat /proc/%d/status | awk '/^PPid:/ {print $2}'", getppid());
+ torture_assert_goto(tctx, cmd != NULL, ok, child_fail, "talloc_asprintf failed\n");
+
+ fp = popen(cmd, "r");
+ torture_assert_goto(tctx, fp != NULL, ok, child_fail, "popen failed\n");
+
+ p = fgets(line, sizeof(line) - 1, fp);
+ pclose(fp);
+ torture_assert_goto(tctx, p != NULL, ok, child_fail, "popen failed\n");
+
+ ret = sscanf(line, "%d", &ppid);
+ torture_assert_goto(tctx, ret == 1, ok, child_fail, "sscanf failed\n");
+ torture_assert_goto(tctx, ppid == pid, ok, child_fail, "process hierarchy not rooted at caller\n");
+
+ _exit(0);
+
+ child_fail:
+ _exit(1);
+ }
+
+ status = tfork_status(&t, true);
+ if (status == -1) {
+ torture_fail(tctx, "tfork_status failed\n");
+ }
+
+ torture_assert_goto(tctx, WIFEXITED(status) == true, ok, done,
+ "tfork failed\n");
+ torture_assert_goto(tctx, WEXITSTATUS(status) == 0, ok, done,
+ "tfork failed\n");
+ torture_comment(tctx, "exit status [%d]\n", WEXITSTATUS(status));
+
+done:
+ return ok;
+}
+
+static bool test_tfork_pipe(struct torture_context *tctx)
+{
+ struct tfork *t = NULL;
+ int status;
+ pid_t child;
+ int up[2];
+ int down[2];
+ char c;
+ int ret;
+ bool ok = true;
+
+ ret = pipe(&up[0]);
+ torture_assert(tctx, ret == 0, "pipe failed\n");
+
+ ret = pipe(&down[0]);
+ torture_assert(tctx, ret == 0, "pipe failed\n");
+
+ t = tfork_create();
+ if (t == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child = tfork_child_pid(t);
+ if (child == 0) {
+ close(up[0]);
+ close(down[1]);
+
+ ret = read(down[0], &c, 1);
+ torture_assert_goto(tctx, ret == 1, ok, child_fail, "read failed\n");
+ torture_assert_goto(tctx, c == 1, ok, child_fail, "read failed\n");
+
+ ret = write(up[1], &(char){2}, 1);
+ torture_assert_goto(tctx, ret == 1, ok, child_fail, "write failed\n");
+
+ _exit(0);
+
+ child_fail:
+ _exit(1);
+ }
+
+ close(up[1]);
+ close(down[0]);
+
+ ret = write(down[1], &(char){1}, 1);
+ torture_assert(tctx, ret == 1, "read failed\n");
+
+ ret = read(up[0], &c, 1);
+ torture_assert(tctx, ret == 1, "read failed\n");
+ torture_assert(tctx, c == 2, "read failed\n");
+
+ status = tfork_status(&t, true);
+ if (status == -1) {
+ torture_fail(tctx, "tfork_status failed\n");
+ }
+
+ torture_assert_goto(tctx, WIFEXITED(status) == true, ok, done,
+ "tfork failed\n");
+ torture_assert_goto(tctx, WEXITSTATUS(status) == 0, ok, done,
+ "tfork failed\n");
+done:
+ return ok;
+}
+
+static bool test_tfork_twice(struct torture_context *tctx)
+{
+ struct tfork *t = NULL;
+ int status;
+ pid_t child;
+ pid_t pid;
+ int up[2];
+ int ret;
+ bool ok = true;
+
+ ret = pipe(&up[0]);
+ torture_assert(tctx, ret == 0, "pipe failed\n");
+
+ t = tfork_create();
+ if (t == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child = tfork_child_pid(t);
+ if (child == 0) {
+ close(up[0]);
+
+ t = tfork_create();
+ if (t == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child = tfork_child_pid(t);
+ if (child == 0) {
+ sleep(1);
+ pid = getpid();
+ ret = write(up[1], &pid, sizeof(pid_t));
+ torture_assert_goto(tctx, ret == sizeof(pid_t), ok, child_fail, "write failed\n");
+
+ _exit(0);
+
+ child_fail:
+ _exit(1);
+ }
+
+ _exit(0);
+ }
+
+ close(up[1]);
+
+ ret = read(up[0], &pid, sizeof(pid_t));
+ torture_assert(tctx, ret == sizeof(pid_t), "read failed\n");
+
+ status = tfork_status(&t, true);
+ torture_assert_goto(tctx, status != -1, ok, done, "tfork_status failed\n");
+
+ torture_assert_goto(tctx, WIFEXITED(status) == true, ok, done,
+ "tfork failed\n");
+ torture_assert_goto(tctx, WEXITSTATUS(status) == 0, ok, done,
+ "tfork failed\n");
+done:
+ return ok;
+}
+
+static void *tfork_thread(void *p)
+{
+ struct tfork *t = NULL;
+ int status;
+ pid_t child;
+ uint64_t tid = (uint64_t)pthread_self();
+ uint64_t *result = NULL;
+ int up[2];
+ ssize_t nread;
+ int ret;
+
+ ret = pipe(up);
+ if (ret != 0) {
+ pthread_exit(NULL);
+ }
+
+ t = tfork_create();
+ if (t == NULL) {
+ pthread_exit(NULL);
+ }
+ child = tfork_child_pid(t);
+ if (child == 0) {
+ ssize_t nwritten;
+
+ close(up[0]);
+ tid++;
+ nwritten = sys_write(up[1], &tid, sizeof(uint64_t));
+ if (nwritten != sizeof(uint64_t)) {
+ _exit(1);
+ }
+ _exit(0);
+ }
+ close(up[1]);
+
+ result = malloc(sizeof(uint64_t));
+ if (result == NULL) {
+ pthread_exit(NULL);
+ }
+
+ nread = sys_read(up[0], result, sizeof(uint64_t));
+ if (nread != sizeof(uint64_t)) {
+ pthread_exit(NULL);
+ }
+
+ status = tfork_status(&t, true);
+ if (status == -1) {
+ pthread_exit(NULL);
+ }
+
+ pthread_exit(result);
+}
+
+static bool test_tfork_threads(struct torture_context *tctx)
+{
+ int ret;
+ bool ok = true;
+ const int num_threads = 64;
+ pthread_t threads[num_threads];
+ sigset_t set;
+ int i;
+
+#ifndef HAVE_PTHREAD
+ torture_skip(tctx, "no pthread support\n");
+#endif
+
+ /*
+ * Be nasty and taste for the worst case: ensure all threads start with
+ * SIGCHLD unblocked so we have the most fun with SIGCHLD being
+ * delivered to a random thread. :)
+ */
+ sigemptyset(&set);
+ sigaddset(&set, SIGCHLD);
+#ifdef HAVE_PTHREAD
+ ret = pthread_sigmask(SIG_UNBLOCK, &set, NULL);
+#else
+ ret = sigprocmask(SIG_UNBLOCK, &set, NULL);
+#endif
+ if (ret != 0) {
+ return false;
+ }
+
+ for (i = 0; i < num_threads; i++) {
+ ret = pthread_create(&threads[i], NULL, tfork_thread, NULL);
+ torture_assert_goto(tctx, ret == 0, ok, done,
+ "pthread_create failed\n");
+ }
+
+ for (i = 0; i < num_threads; i++) {
+ void *p;
+ uint64_t *result;
+
+ ret = pthread_join(threads[i], &p);
+ torture_assert_goto(tctx, ret == 0, ok, done,
+ "pthread_join failed\n");
+ result = (uint64_t *)p;
+ torture_assert_goto(tctx, *result == (uint64_t)threads[i] + 1,
+ ok, done, "thread failed\n");
+ free(p);
+ }
+
+done:
+ return ok;
+}
+
+static bool test_tfork_cmd_send(struct torture_context *tctx)
+{
+ struct tevent_context *ev = NULL;
+ struct tevent_req *req = NULL;
+ const char *cmd[2] = { NULL, NULL };
+ bool ok = true;
+
+ ev = tevent_context_init(tctx);
+ torture_assert_goto(tctx, ev != NULL, ok, done,
+ "tevent_context_init failed\n");
+
+ cmd[0] = talloc_asprintf(tctx, "%s/testprogs/blackbox/tfork.sh", SRCDIR);
+ torture_assert_goto(tctx, cmd[0] != NULL, ok, done,
+ "talloc_asprintf failed\n");
+
+ req = samba_runcmd_send(tctx, ev, timeval_zero(), 0, 0,
+ cmd, "foo", NULL);
+ torture_assert_goto(tctx, req != NULL, ok, done,
+ "samba_runcmd_send failed\n");
+
+ ok = tevent_req_poll(req, ev);
+ torture_assert_goto(tctx, ok, ok, done, "tevent_req_poll failed\n");
+
+ torture_comment(tctx, "samba_runcmd_send test finished\n");
+
+done:
+ TALLOC_FREE(ev);
+
+ return ok;
+}
+
+/*
+ * Test to ensure that the event_fd becomes readable after
+ * a tfork_process terminates.
+ */
+static bool test_tfork_event_file_handle(struct torture_context *tctx)
+{
+ bool ok = true;
+
+ struct tfork *t1 = NULL;
+ pid_t child1;
+ struct pollfd poll1[] = {
+ {
+ .fd = -1,
+ .events = POLLIN,
+ },
+ };
+
+ struct tfork *t2 = NULL;
+ pid_t child2;
+ struct pollfd poll2[] = {
+ {
+ .fd = -1,
+ .events = POLLIN,
+ },
+ };
+
+
+ t1 = tfork_create();
+ if (t1 == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+
+ child1 = tfork_child_pid(t1);
+ if (child1 == 0) {
+ /*
+ * Parent process will kill this with a SIGTERM
+ * so 10 seconds should be plenty
+ */
+ sleep(10);
+ exit(1);
+ }
+ poll1[0].fd = tfork_event_fd(t1);
+
+ t2 = tfork_create();
+ if (t2 == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child2 = tfork_child_pid(t2);
+ if (child2 == 0) {
+ /*
+ * Parent process will kill this with a SIGTERM
+ * so 10 seconds should be plenty
+ */
+ sleep(10);
+ exit(2);
+ }
+ poll2[0].fd = tfork_event_fd(t2);
+
+ /*
+ * Have forked two process and are in the master process
+ * Expect that both event_fds are unreadable
+ */
+ poll(poll1, 1, 0);
+ ok = !(poll1[0].revents & POLLIN);
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 1 event fd readable\n");
+ poll(poll2, 1, 0);
+ ok = !(poll2[0].revents & POLLIN);
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 1 event fd readable\n");
+
+ /* Kill the first child process */
+ kill(child1, SIGKILL);
+ sleep(1);
+
+ /*
+ * Have killed the first child, so expect it's event_fd to have gone
+ * readable.
+ *
+ */
+ poll(poll1, 1, 0);
+ ok = (poll1[0].revents & POLLIN);
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 1 event fd not readable\n");
+ poll(poll2, 1, 0);
+ ok = !(poll2[0].revents & POLLIN);
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 2 event fd readable\n");
+
+ /* Kill the secind child process */
+ kill(child2, SIGKILL);
+ sleep(1);
+ /*
+ * Have killed the children, so expect their event_fd's to have gone
+ * readable.
+ *
+ */
+ poll(poll1, 1, 0);
+ ok = (poll1[0].revents & POLLIN);
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 1 event fd not readable\n");
+ poll(poll2, 1, 0);
+ ok = (poll2[0].revents & POLLIN);
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 2 event fd not readable\n");
+
+done:
+ free(t1);
+ free(t2);
+
+ return ok;
+}
+
+/*
+ * Test to ensure that the status calls behave as expected after a process
+ * terminates.
+ *
+ * As the parent process owns the status fd's they get passed to all
+ * subsequent children after a tfork. So it's possible for another
+ * child process to hold the status pipe open.
+ *
+ * The event fd needs to be left open by tfork, as a close in the status
+ * code can cause issues in tevent code.
+ *
+ */
+static bool test_tfork_status_handle(struct torture_context *tctx)
+{
+ bool ok = true;
+
+ struct tfork *t1 = NULL;
+ pid_t child1;
+
+ struct tfork *t2 = NULL;
+ pid_t child2;
+
+ int status;
+ int fd;
+ int ev1_fd;
+ int ev2_fd;
+
+
+ t1 = tfork_create();
+ if (t1 == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+
+ child1 = tfork_child_pid(t1);
+ if (child1 == 0) {
+ /*
+ * Parent process will kill this with a SIGTERM
+ * so 10 seconds should be plenty
+ */
+ sleep(10);
+ exit(1);
+ }
+ ev1_fd = tfork_event_fd(t1);
+
+ t2 = tfork_create();
+ if (t2 == NULL) {
+ torture_fail(tctx, "tfork failed\n");
+ return false;
+ }
+ child2 = tfork_child_pid(t2);
+ if (child2 == 0) {
+ /*
+ * Parent process will kill this with a SIGTERM
+ * so 10 seconds should be plenty
+ */
+ sleep(10);
+ exit(2);
+ }
+ ev2_fd = tfork_event_fd(t2);
+
+ /*
+ * Have forked two process and are in the master process
+ * expect that the status call will block, and hence return -1
+ * as the processes are still running
+ * The event fd's should be open.
+ */
+ status = tfork_status(&t1, false);
+ ok = status == -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork status available for non terminated "
+ "process 1\n");
+ /* Is the event fd open? */
+ fd = dup(ev1_fd);
+ ok = fd != -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 1 event fd is not open");
+
+ status = tfork_status(&t2, false);
+ ok = status == -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork status available for non terminated "
+ "process 2\n");
+ /* Is the event fd open? */
+ fd = dup(ev2_fd);
+ ok = fd != -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 2 event fd is not open");
+
+ /*
+ * Kill the first process, it's status should be readable
+ * and it's event_fd should be open
+ * The second process's status should be unreadable.
+ */
+ kill(child1, SIGTERM);
+ sleep(1);
+ status = tfork_status(&t1, false);
+ ok = status != -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork status for child 1 not available after "
+ "termination\n");
+ /* Is the event fd open? */
+ fd = dup(ev2_fd);
+ ok = fd != -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 1 event fd is not open");
+
+ status = tfork_status(&t2, false);
+ ok = status == -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork status available for child 2 after "
+ "termination of child 1\n");
+
+ /*
+ * Kill the second process, it's status should be readable
+ */
+ kill(child2, SIGTERM);
+ sleep(1);
+ status = tfork_status(&t2, false);
+ ok = status != -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork status for child 2 not available after "
+ "termination\n");
+
+ /* Check that the event fd's are still open */
+ /* Is the event fd open? */
+ fd = dup(ev1_fd);
+ ok = fd != -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 1 event fd is not open");
+ /* Is the event fd open? */
+ fd = dup(ev2_fd);
+ ok = fd != -1;
+ torture_assert_goto(tctx, ok, ok, done,
+ "tfork process 2 event fd is not open");
+
+done:
+ return ok;
+}
+
+struct torture_suite *torture_local_tfork(TALLOC_CTX *mem_ctx)
+{
+ struct torture_suite *suite =
+ torture_suite_create(mem_ctx, "tfork");
+
+ torture_suite_add_simple_test(suite,
+ "tfork_simple",
+ test_tfork_simple);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_status",
+ test_tfork_status);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_sigign",
+ test_tfork_sigign);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_sighandler",
+ test_tfork_sighandler);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_process_hierarchy",
+ test_tfork_process_hierarchy);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_pipe",
+ test_tfork_pipe);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_twice",
+ test_tfork_twice);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_threads",
+ test_tfork_threads);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_cmd_send",
+ test_tfork_cmd_send);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_event_file_handle",
+ test_tfork_event_file_handle);
+
+ torture_suite_add_simple_test(suite,
+ "tfork_status_handle",
+ test_tfork_status_handle);
+
+ return suite;
+}