]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
ctdb-tests: Add tests for trivial FD monitoring
authorMartin Schwenke <martin@meltin.net>
Wed, 2 Feb 2022 10:47:59 +0000 (21:47 +1100)
committerMartin Schwenke <martins@samba.org>
Thu, 28 Jul 2022 10:09:34 +0000 (10:09 +0000)
tmon_ping_test covers complex 2-way interaction between processes
using tmon_ping_send(), including via a socketpair().  tmon_test
covers the more general functionality of tmon_send() but uses a
simpler 1-way harness with wide coverage.

Signed-off-by: Martin Schwenke <martin@meltin.net>
Reviewed-by: Amitay Isaacs <amitay@gmail.com>
ctdb/tests/UNIT/cunit/tmon_test_001.sh [new file with mode: 0755]
ctdb/tests/UNIT/cunit/tmon_test_002.sh [new file with mode: 0755]
ctdb/tests/src/tmon_ping_test.c [new file with mode: 0644]
ctdb/tests/src/tmon_test.c [new file with mode: 0644]
ctdb/wscript

diff --git a/ctdb/tests/UNIT/cunit/tmon_test_001.sh b/ctdb/tests/UNIT/cunit/tmon_test_001.sh
new file mode 100755 (executable)
index 0000000..96f706c
--- /dev/null
@@ -0,0 +1,195 @@
+#!/bin/sh
+
+. "${TEST_SCRIPTS_DIR}/unit.sh"
+
+epipe=$(errcode EPIPE)
+eio=$(errcode EIO)
+etimedout=$(errcode ETIMEDOUT)
+
+test_case "No pings, only child monitors, so gets EPIPE"
+ok <<EOF
+parent: async wait start 5
+child: async wait start 10
+parent: async wait end
+child: pipe closed
+EOF
+unit_test tmon_ping_test false 0 5 0 0 false 0 10 0 "$epipe"
+
+test_case "No pings, only parent monitors, so gets EPIPE"
+ok <<EOF
+parent: async wait start 10
+child: async wait start 5
+child: async wait end
+parent: pipe closed
+EOF
+unit_test tmon_ping_test false 0 10 0 "$epipe" false 0 5 0 0
+
+test_case "No pings, Child exits first, parent notices"
+ok <<EOF
+parent: async wait start 10
+child: async wait start 1
+child: async wait end
+parent: pipe closed
+EOF
+unit_test tmon_ping_test false 0 10 0 "$epipe" false 0 1 0 0
+
+test_case "No pings, parent exits first, child notices"
+ok <<EOF
+parent: async wait start 1
+child: async wait start 10
+parent: async wait end
+child: pipe closed
+EOF
+unit_test tmon_ping_test false 0 1 0 0 false 0 10 0 "$epipe"
+
+test_case "Parent pings, child doesn't expect them, EIO"
+ok <<EOF
+parent: async wait start 5
+child: async wait start 5
+child: error ($eio)
+parent: pipe closed
+EOF
+unit_test tmon_ping_test true 0 5 0 "$epipe" false 0 5 0 "$eio"
+
+test_case "Child pings, parent doesn't expect them, EIO"
+ok <<EOF
+parent: async wait start 5
+child: async wait start 5
+parent: error ($eio)
+child: pipe closed
+EOF
+unit_test tmon_ping_test false 0 5 0 "$eio" true 0 5 0 "$epipe"
+
+test_case "Both ping, child doesn't expect them, EIO"
+ok <<EOF
+parent: async wait start 5
+child: async wait start 5
+child: error ($eio)
+parent: pipe closed
+EOF
+unit_test tmon_ping_test true 3 5 0 "$epipe" true 0 5 0 "$eio"
+
+test_case "Both ping, parent doesn't expect them, EIO"
+ok <<EOF
+parent: async wait start 5
+child: async wait start 5
+parent: error ($eio)
+child: pipe closed
+EOF
+unit_test tmon_ping_test true 0 5 0 "$eio" true 3 5 0 "$epipe"
+
+test_case "Child pings, no ping timeout error, child exits first"
+ok <<EOF
+parent: async wait start 10
+child: async wait start 5
+child: async wait end
+parent: pipe closed
+EOF
+unit_test tmon_ping_test false 3 10 0 "$epipe" true 0 5 0 0
+
+test_case "Parent pings, no ping timeout error, parent exits first"
+ok <<EOF
+parent: async wait start 5
+child: async wait start 10
+parent: async wait end
+child: pipe closed
+EOF
+unit_test tmon_ping_test true 0 5 0 0 false 3 10 0 "$epipe"
+
+test_case "Both ping, no ping timeout error, parent exits first"
+ok <<EOF
+parent: async wait start 5
+child: async wait start 10
+parent: async wait end
+child: pipe closed
+EOF
+unit_test tmon_ping_test true 3 5 0 0 true 3 10 0 "$epipe"
+
+test_case "Both ping, no ping timeout error, child exits first"
+ok <<EOF
+parent: async wait start 10
+child: async wait start 5
+child: async wait end
+parent: pipe closed
+EOF
+unit_test tmon_ping_test true 3 10 0 "$epipe" true 3 5 0 0
+
+test_case "Both ping, child blocks, parent ping timeout error"
+ok <<EOF
+parent: async wait start 20
+child: blocking sleep start 7
+parent: ping timeout
+child: blocking sleep end
+EOF
+unit_test tmon_ping_test true 3 20 0 "$etimedout" true 3 0 7 0
+
+test_case "Both ping, parent blocks, child ping timeout error"
+ok <<EOF
+parent: blocking sleep start 7
+child: async wait start 20
+child: ping timeout
+parent: blocking sleep end
+EOF
+unit_test tmon_ping_test true 3 0 7 0 true 3 20 0 "$etimedout"
+
+test_case "Both ping, child waits, child blocks, parent ping timeout error"
+ok <<EOF
+parent: async wait start 20
+child: async wait start 2
+child: async wait end
+child: blocking sleep start 7
+parent: ping timeout
+child: blocking sleep end
+EOF
+unit_test tmon_ping_test true 3 20 0 "$etimedout" true 3 2 7 0
+
+test_case "Both ping, parent waits, parent blocks, child ping timeout error"
+ok <<EOF
+parent: async wait start 2
+child: async wait start 20
+parent: async wait end
+parent: blocking sleep start 7
+child: ping timeout
+parent: blocking sleep end
+EOF
+unit_test tmon_ping_test true 3 2 7 0 true 3 20 0 "$etimedout"
+
+test_case "Both ping, child blocks for less than ping timeout"
+ok <<EOF
+parent: async wait start 20
+child: blocking sleep start 3
+child: blocking sleep end
+parent: pipe closed
+EOF
+unit_test tmon_ping_test true 7 20 0 "$epipe" true 7 0 3 0
+
+test_case "Both ping, parent blocks for less than ping timeout"
+ok <<EOF
+parent: blocking sleep start 3
+child: async wait start 20
+parent: blocking sleep end
+child: pipe closed
+EOF
+unit_test tmon_ping_test true 7 0 3 0 true 7 20 3 "$epipe"
+
+test_case "Both ping, child waits, child blocks for less than ping timeout"
+ok <<EOF
+parent: async wait start 20
+child: async wait start 2
+child: async wait end
+child: blocking sleep start 3
+child: blocking sleep end
+parent: pipe closed
+EOF
+unit_test tmon_ping_test true 7 20 0 "$epipe" true 7 2 3 0
+
+test_case "Both ping, parent waits, parent blocks for less than ping timeout"
+ok <<EOF
+parent: async wait start 2
+child: async wait start 20
+parent: async wait end
+parent: blocking sleep start 3
+parent: blocking sleep end
+child: pipe closed
+EOF
+unit_test tmon_ping_test true 7 2 3 0 true 7 20 0 "$epipe"
diff --git a/ctdb/tests/UNIT/cunit/tmon_test_002.sh b/ctdb/tests/UNIT/cunit/tmon_test_002.sh
new file mode 100755 (executable)
index 0000000..97a17a1
--- /dev/null
@@ -0,0 +1,113 @@
+#!/bin/sh
+
+. "${TEST_SCRIPTS_DIR}/unit.sh"
+
+epipe=$(errcode EPIPE)
+etimedout=$(errcode ETIMEDOUT)
+
+test_cases()
+{
+       test_case "no packets, sender exits, 3s timeout"
+       ok <<EOF
+WRITER OK
+READER ERR=$epipe
+EOF
+       unit_test tmon_test "" false 3 false
+
+       test_case "no packets, sender exits, 3s timeout, close ok"
+       ok <<EOF
+WRITER OK
+READER OK
+EOF
+       unit_test tmon_test "" true 3 false
+
+       test_case "Exit packet @ 1s, no timeout"
+       ok <<EOF
+READER OK
+WRITER OK
+EOF
+       unit_test tmon_test "0" false 0 false
+
+       test_case "errno 7 packet @ 1s, no timeout"
+       ok <<EOF
+READER ERR=7
+WRITER OK
+EOF
+       unit_test tmon_test "7" false 0 false
+
+       test_case "Exit packet @ 3s, no timeout"
+       ok <<EOF
+READER OK
+WRITER OK
+EOF
+       unit_test tmon_test "..0" false 0 false
+
+       test_case "errno 7 packet @ 3s, no timeout"
+       ok <<EOF
+READER ERR=7
+WRITER OK
+EOF
+       unit_test tmon_test "..7" false 0 false
+
+       test_case "no packets for 5s, 3s timeout"
+       ok <<EOF
+READER ERR=$etimedout
+WRITER OK
+EOF
+       unit_test tmon_test "....." false 3 false
+
+       test_case "no packets for 5s, 3s timeout, timeout ok"
+       ok <<EOF
+READER OK
+WRITER OK
+EOF
+       unit_test tmon_test "....." false 3 true
+
+       test_case "4 pings then exit, 3s timeout"
+       ok <<EOF
+PING
+PING
+PING
+PING
+READER OK
+WRITER OK
+EOF
+       unit_test tmon_test "!!!!0" false 3 false
+
+       test_case "ASCII Hello, errno 7, 3s timeout"
+       ok <<EOF
+ASCII H
+ASCII e
+ASCII l
+ASCII l
+ASCII o
+READER ERR=7
+WRITER OK
+EOF
+       unit_test tmon_test "Hello7" false 3 false
+
+       test_case "Hi there! 3s timeout"
+       ok <<EOF
+ASCII H
+ASCII i
+CUSTOM 0x20
+ASCII t
+ASCII h
+ASCII e
+ASCII r
+ASCII e
+PING
+WRITER OK
+READER ERR=$epipe
+EOF
+       unit_test tmon_test "Hi there!" false 3 false
+}
+
+echo "PASS #1: Run test cases in default mode"
+test_cases
+
+echo
+echo "=================================================="
+
+echo "PASS #2: Run test cases in write-skip mode"
+CTDB_TEST_TMON_WRITE_SKIP_MODE=1 test_cases
diff --git a/ctdb/tests/src/tmon_ping_test.c b/ctdb/tests/src/tmon_ping_test.c
new file mode 100644 (file)
index 0000000..491792d
--- /dev/null
@@ -0,0 +1,381 @@
+/*
+   Test trivial FD monitoring
+
+   Copyright (C) Martin Schwenke, DataDirect Networks  2022
+
+   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 "system/filesys.h"
+#include "system/network.h"
+#include "system/wait.h"
+
+#include <talloc.h>
+#include <tevent.h>
+#include <assert.h>
+
+#include "lib/util/tevent_unix.h"
+
+#include "common/tmon.h"
+
+#include "tests/src/test_backtrace.h"
+
+struct test_state {
+       const char *label;
+       unsigned long async_wait_time;
+       unsigned long blocking_sleep_time;
+};
+
+static void test_tmon_ping_done(struct tevent_req *subreq);
+static void test_async_wait_done(struct tevent_req *subreq);
+
+static struct tevent_req *test_send(TALLOC_CTX *mem_ctx,
+                                   struct tevent_context *ev,
+                                   const char *label,
+                                   int fd,
+                                   int direction,
+                                   unsigned long timeout,
+                                   unsigned long interval,
+                                   unsigned long async_wait_time,
+                                   unsigned long blocking_sleep_time)
+{
+       struct tevent_req *req, *subreq;
+       struct test_state *state;
+
+       req = tevent_req_create(mem_ctx, &state, struct test_state);
+       if (req == NULL) {
+               return NULL;
+       }
+
+       state->label = label;
+       state->async_wait_time = async_wait_time;
+       state->blocking_sleep_time = blocking_sleep_time;
+
+       subreq = tmon_ping_send(state,
+                               ev,
+                               fd,
+                               direction,
+                               timeout,
+                               interval);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, test_tmon_ping_done, req);
+
+       if (state->async_wait_time != 0) {
+               fprintf(stderr,
+                       "%s: async wait start %lu\n",
+                       state->label,
+                       state->async_wait_time);
+       }
+       subreq = tevent_wakeup_send(state,
+                                   ev,
+                                   tevent_timeval_current_ofs(
+                                           (uint32_t)async_wait_time, 0));
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, test_async_wait_done, req);
+
+       return req;
+}
+
+static void test_tmon_ping_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct test_state *state = tevent_req_data(req, struct test_state);
+       bool status;
+       int err;
+
+       status = tmon_ping_recv(subreq, &err);
+       TALLOC_FREE(subreq);
+       if (!status) {
+               switch(err) {
+               case EPIPE:
+                       fprintf(stderr, "%s: pipe closed\n", state->label);
+                       break;
+               case ETIMEDOUT:
+                       fprintf(stderr, "%s: ping timeout\n", state->label);
+                       break;
+               default:
+                       fprintf(stderr, "%s: error (%d)\n", state->label, err);
+               }
+               tevent_req_error(req, err);
+               return;
+       }
+
+       fprintf(stderr, "%s: done\n", state->label);
+       tevent_req_done(req);
+}
+
+static void test_async_wait_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       struct test_state *state = tevent_req_data(req, struct test_state);
+       unsigned int left;
+       bool status;
+
+       status = tevent_wakeup_recv(subreq);
+       TALLOC_FREE(subreq);
+       if (!status) {
+               fprintf(stderr,
+                       "%s: tevent_wakeup_recv() failed\n",
+                       state->label);
+               /* Ignore error */
+       }
+       if (state->async_wait_time != 0) {
+               fprintf(stderr, "%s: async wait end\n", state->label);
+       }
+
+       if (state->blocking_sleep_time == 0) {
+               goto done;
+       }
+
+       fprintf(stderr,
+               "%s: blocking sleep start %lu\n",
+               state->label,
+               state->blocking_sleep_time);
+       left = sleep((unsigned int)state->blocking_sleep_time);
+       fprintf(stderr,
+               "%s: blocking sleep end\n",
+               state->label);
+       if (left != 0) {
+               tevent_req_error(req, EINTR);
+               return;
+       }
+
+done:
+       tevent_req_done(req);
+}
+
+static bool test_recv(struct tevent_req *req, int *perr)
+{
+       if (tevent_req_is_unix_error(req, perr)) {
+               return false;
+       }
+
+       return true;
+}
+
+static int test_one(bool is_parent,
+                   int sync_fd,
+                   int fd,
+                   int direction,
+                   unsigned long timeout,
+                   unsigned long interval,
+                   unsigned long async_wait_time,
+                   unsigned long blocking_sleep_time)
+{
+       TALLOC_CTX *mem_ctx;
+       struct tevent_context *ev;
+       struct tevent_req *req;
+       bool status;
+       char buf[1] = "";
+       ssize_t count;
+       int err;
+       int ret;
+
+       if (!is_parent) {
+               count = read(sync_fd, buf, sizeof(buf));
+               assert(count == 1);
+               assert(buf[0] == '\0');
+               close(sync_fd);
+       }
+
+       mem_ctx = talloc_new(NULL);
+       if (mem_ctx == NULL) {
+               ret = ENOMEM;
+               goto done;
+       }
+
+       ev = tevent_context_init(mem_ctx);
+       if (ev == NULL) {
+               ret = ENOMEM;
+               goto done;
+       }
+
+       req = test_send(mem_ctx,
+                       ev,
+                       is_parent ? "parent" : "child",
+                       fd,
+                       direction,
+                       timeout,
+                       interval,
+                       async_wait_time,
+                       blocking_sleep_time);
+       if (req == NULL) {
+               ret = ENOMEM;
+               goto done;
+       }
+
+       if (is_parent) {
+               count = write(sync_fd, buf, sizeof(buf));
+               assert(count == 1);
+       }
+
+       status = tevent_req_poll(req, ev);
+       if (!status) {
+               ret = EIO;
+               goto done;
+       }
+
+       status = test_recv(req, &err);
+       ret = status ? 0 : err;
+
+done:
+       return ret;
+}
+
+static void test(unsigned long parent_timeout,
+                unsigned long parent_interval,
+                unsigned long parent_async_wait_time,
+                unsigned long parent_blocking_sleep_time,
+                int parent_result,
+                unsigned long child_timeout,
+                unsigned long child_interval,
+                unsigned long child_async_wait_time,
+                unsigned long child_blocking_sleep_time,
+                int child_result)
+{
+       int sync[2];
+       int fd[2];
+       pid_t pid;
+       int wstatus;
+       int ret;
+
+       /* Pipe for synchronisation */
+       ret = pipe(sync);
+       assert(ret == 0);
+
+       ret = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
+       assert(ret == 0);
+
+       pid = fork();
+       assert(pid != -1);
+
+       if (pid == 0) {
+               /* child */
+               close(sync[1]);
+               close(fd[0]);
+
+               ret = test_one(false,
+                              sync[0],
+                              fd[1],
+                              TMON_FD_BOTH,
+                              child_timeout,
+                              child_interval,
+                              child_async_wait_time,
+                              child_blocking_sleep_time);
+               _exit(ret);
+       }
+
+       /* Parent */
+       close(sync[0]);
+       close(fd[1]);
+
+       ret = test_one(true,
+                      sync[1],
+                      fd[0],
+                      TMON_FD_BOTH,
+                      parent_timeout,
+                      parent_interval,
+                      parent_async_wait_time,
+                      parent_blocking_sleep_time);
+       assert(ret == parent_result);
+
+       /* Close to mimick exit, so child status can be checked below */
+       close(fd[0]);
+
+       /* Abort if child failed */
+       waitpid(pid, &wstatus, 0);
+       if (WIFEXITED(wstatus)) {
+               assert(WEXITSTATUS(wstatus) == child_result);
+       }
+}
+
+struct test_inputs {
+       unsigned int timeout;
+       unsigned int interval;
+       unsigned int async_wait_time;
+       unsigned int blocking_sleep_time;
+       int expected_result;
+};
+
+static void get_test_inputs(const char **args, struct test_inputs *inputs)
+{
+       if (strcmp(args[0], "false") == 0) {
+               inputs->interval = 0;
+       } else if (strcmp(args[0], "true") == 0) {
+               inputs->interval = 1;
+       } else {
+               inputs->interval = strtoul(args[0], NULL, 0);
+       }
+
+       inputs->timeout = strtoul(args[1], NULL, 0);
+       inputs->async_wait_time = (unsigned int)strtoul(args[2], NULL, 0);
+       inputs->blocking_sleep_time = (unsigned int)strtoul(args[3], NULL, 0);
+       inputs->expected_result = (int)strtoul(args[4], NULL, 0);
+}
+
+static void usage(const char *prog)
+{
+       fprintf(stderr,
+               "usage: %s "
+               "\\\n\t"
+               "<parent_send_pings> "
+               "<parent_ping_timeout> "
+               "<parent_async_wait_time> "
+               "<parent_blocking_sleep_time> "
+               "<parent_expected_result> "
+               "\\\n\t"
+               "<child_send_pings> "
+               "<child_ping_timeout> "
+               "<child_async_wait_time> "
+               "<child_blocking_sleep_time> "
+               "<child_expected_result> "
+               "\n",
+               prog);
+       exit(1);
+}
+
+int main(int argc, const char **argv)
+{
+       struct test_inputs parent;
+       struct test_inputs child;
+
+       if (argc != 11) {
+               usage(argv[0]);
+       }
+
+       test_backtrace_setup();
+
+       get_test_inputs(&argv[1], &parent);
+       get_test_inputs(&argv[6], &child);
+
+       test(parent.timeout,
+            parent.interval,
+            parent.async_wait_time,
+            parent.blocking_sleep_time,
+            parent.expected_result,
+            child.timeout,
+            child.interval,
+            child.async_wait_time,
+            child.blocking_sleep_time,
+            child.expected_result);
+
+       return 0;
+}
diff --git a/ctdb/tests/src/tmon_test.c b/ctdb/tests/src/tmon_test.c
new file mode 100644 (file)
index 0000000..651c24b
--- /dev/null
@@ -0,0 +1,395 @@
+/*
+   Test trivial FD monitoring
+
+   Copyright (C) Martin Schwenke, DataDirect Networks  2022
+
+   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 "system/filesys.h"
+#include "system/wait.h"
+
+#include <talloc.h>
+#include <tevent.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "lib/util/tevent_unix.h"
+
+#include "common/tmon.h"
+
+#include "tests/src/test_backtrace.h"
+
+struct test_write_state {
+       const char *write_data;
+       size_t write_data_len;
+       unsigned int offset;
+       struct tevent_req *req;
+};
+
+static int test_write_callback(void *private_data, struct tmon_pkt *pkt)
+{
+       struct test_write_state *state = talloc_get_type_abort(
+               private_data, struct test_write_state);
+       bool status;
+       size_t len;
+       int err;
+       char c;
+       const char *t;
+
+       assert(state->write_data != NULL);
+
+       len = strlen(state->write_data);
+       if (state->offset >= len) {
+               return TMON_STATUS_EXIT;
+       }
+
+       c = state->write_data[state->offset];
+       state->offset++;
+
+       if (isdigit(c)) {
+               err = c - '0';
+
+               if (err == 0) {
+                       status = tmon_set_exit(pkt);
+               } else {
+                       status = tmon_set_errno(pkt, err);
+               }
+       } else if (ispunct(c)) {
+               switch (c) {
+               case '.':
+                       return TMON_STATUS_SKIP;
+                       break;
+               case '!':
+                       status = tmon_set_ping(pkt);
+                       break;
+               default:
+                       status = false;
+               }
+       } else if (isascii(c) && !isspace(c)) {
+               status = tmon_set_ascii(pkt, c);
+       } else {
+               status = tmon_set_custom(pkt, (uint16_t)c);
+       }
+
+       if (!status) {
+               return EDOM;
+       }
+
+       t = getenv("CTDB_TEST_TMON_WRITE_SKIP_MODE");
+       if (t == NULL) {
+               return 0;
+       }
+
+       /*
+        * This is write-skip mode: tmon_write() is called directly
+        * here in the callback and TMON_WRITE_SKIP is returned.  This
+        * allows tmon_write() to be exercised by reusing test cases
+        * rather than writing extra test code and test cases.
+        */
+
+       status = tmon_write(state->req, pkt);
+       if (!status) {
+               return EIO;
+       }
+
+       return TMON_STATUS_SKIP;
+}
+
+static void test_tmon_done(struct tevent_req *subreq);
+
+static struct tevent_req *test_write_send(TALLOC_CTX *mem_ctx,
+                                         struct tevent_context *ev,
+                                         int fd,
+                                         const char *write_data)
+{
+       struct tevent_req *req, *subreq;
+       struct test_write_state *state;
+       struct tmon_actions actions = {
+               .write_callback = test_write_callback,
+       };
+
+       req = tevent_req_create(mem_ctx, &state, struct test_write_state);
+       if (req == NULL) {
+               return NULL;
+       }
+
+       state->write_data = write_data;
+       state->offset = 0;
+
+       subreq = tmon_send(state,
+                          ev,
+                          fd,
+                          TMON_FD_WRITE,
+                          0,
+                          1,
+                          &actions,
+                          state);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, test_tmon_done, req);
+
+       /* Nasty hack, but OK to cheapen testing - see test_write_callback() */
+       state->req = subreq;
+
+       return req;
+}
+
+static void test_tmon_done(struct tevent_req *subreq)
+{
+       struct tevent_req *req = tevent_req_callback_data(
+               subreq, struct tevent_req);
+       bool status;
+       int err;
+
+       status = tmon_recv(subreq, &err);
+       TALLOC_FREE(subreq);
+       if (!status) {
+               tevent_req_error(req, err);
+               return;
+       }
+
+       tevent_req_done(req);
+}
+
+static bool test_write_recv(struct tevent_req *req, int *perr)
+{
+       if (tevent_req_is_unix_error(req, perr)) {
+               return false;
+       }
+
+       return true;
+}
+
+static int test_timeout_ok_callback(void *private_data)
+{
+       return 0;
+}
+
+static int test_read_callback(void *private_data, struct tmon_pkt *pkt)
+{
+       bool status;
+       char c;
+       uint16_t val;
+
+       status = tmon_parse_ping(pkt);
+       if (status) {
+               printf("PING\n");
+               fflush(stdout);
+               return 0;
+       }
+
+       status = tmon_parse_ascii(pkt, &c);
+       if (status) {
+               printf("ASCII %c\n", c);
+               fflush(stdout);
+               return 0;
+       }
+
+       status = tmon_parse_custom(pkt, &val);
+       if (status) {
+               printf("CUSTOM 0x%"PRIx16"\n", val);
+               fflush(stdout);
+               return 0;
+       }
+
+       return 0;
+}
+
+static int test_close_ok_callback(void *private_data)
+{
+       return 0;
+}
+
+struct test_read_state {
+};
+
+static struct tevent_req *test_read_send(TALLOC_CTX *mem_ctx,
+                                        struct tevent_context *ev,
+                                        int fd,
+                                        bool close_ok,
+                                        unsigned long timeout,
+                                        bool timeout_ok)
+{
+       struct tevent_req *req, *subreq;
+       struct test_read_state *state;
+       struct tmon_actions actions = {
+               .read_callback = test_read_callback,
+       };
+
+       req = tevent_req_create(mem_ctx, &state, struct test_read_state);
+       if (req == NULL) {
+               return NULL;
+       }
+
+       if (timeout_ok) {
+               actions.timeout_callback = test_timeout_ok_callback;
+       }
+       if (close_ok) {
+               actions.close_callback = test_close_ok_callback;
+       }
+
+       subreq = tmon_send(state,
+                          ev,
+                          fd,
+                          TMON_FD_READ,
+                          timeout,
+                          0,
+                          &actions,
+                          state);
+       if (tevent_req_nomem(subreq, req)) {
+               return tevent_req_post(req, ev);
+       }
+       tevent_req_set_callback(subreq, test_tmon_done, req);
+
+       return req;
+}
+
+static bool test_read_recv(struct tevent_req *req, int *perr)
+{
+       if (tevent_req_is_unix_error(req, perr)) {
+               return false;
+       }
+
+       return true;
+}
+
+static void test(const char *write_data,
+                bool close_ok,
+                unsigned long timeout,
+                bool timeout_ok)
+{
+       TALLOC_CTX *mem_ctx;
+       struct tevent_context *ev;
+       struct tevent_req *req;
+       int fd[2];
+       pid_t pid;
+       int wstatus;
+       bool status;
+       int err;
+       int ret;
+
+       mem_ctx = talloc_new(NULL);
+       assert(mem_ctx != NULL);
+
+       ev = tevent_context_init(mem_ctx);
+       assert(ev != NULL);
+
+       ret = pipe(fd);
+       assert(ret == 0);
+
+       pid = fork();
+       assert(pid != -1);
+
+       if (pid == 0) {
+               /* child */
+               close(fd[1]);
+
+               req = test_read_send(mem_ctx,
+                                    ev,
+                                    fd[0],
+                                    close_ok,
+                                    timeout,
+                                    timeout_ok);
+               assert(req != NULL);
+
+               status = tevent_req_poll(req, ev);
+               assert(status);
+
+               status = test_read_recv(req, &err);
+               if (status) {
+                       err = 0;
+                       printf("READER OK\n");
+               } else {
+                       printf("READER ERR=%d\n", err);
+               }
+               fflush(stdout);
+
+               _exit(ret);
+       }
+
+       /* Parent */
+       close(fd[0]);
+
+       req = test_write_send(mem_ctx,
+                             ev,
+                             fd[1],
+                             write_data);
+       assert(req != NULL);
+
+       status = tevent_req_poll(req, ev);
+       assert(status);
+
+       status = test_write_recv(req, &err);
+       if (status) {
+               err = 0;
+               printf("WRITER OK\n");
+       } else {
+               printf("WRITER ERR=%d\n", err);
+       }
+       fflush(stdout);
+
+       /* Close to mimick exit, so child status can be checked below */
+       close(fd[1]);
+
+       waitpid(pid, &wstatus, 0);
+}
+
+static void usage(const char *prog)
+{
+       fprintf(stderr,
+               "usage: %s <write_data> <close_ok> <timeout> <timeout_ok>\n\n"
+               "  <write_data> is processed by test_write_callback(), "
+               "1 character per second:\n"
+               "                0: write EXIT\n"
+               "              1-9: write ERRNO 1-9\n"
+               "                .: skip write\n"
+               "          <space>: write CUSTOM containing <space>\n"
+               "    other <ascii>: write ASCII containing <ascii>\n"
+               "            other: write CUSTOM\n"
+               "  See test_write_callback() for more details\n"
+               ,
+               prog);
+       exit(1);
+}
+
+int main(int argc, const char **argv)
+{
+       bool close_ok, timeout_ok;
+       unsigned long timeout;
+
+       if (argc != 5) {
+               usage(argv[0]);
+       }
+
+       test_backtrace_setup();
+
+       close_ok = (strcmp(argv[2], "true") == 0);
+       timeout = strtoul(argv[3], NULL, 0);
+       if (timeout == 0) {
+               /*
+                * Default timeout that should not come into play but
+                * will cause tests to fail after a reasonable amount
+                * of time, if something unexpected happens.
+                */
+               timeout = 20;
+       }
+       timeout_ok = (strcmp(argv[4], "true") == 0);
+
+       test(argv[1], close_ok, timeout, timeout_ok);
+
+       return 0;
+}
index a0d0935b1c44a738b21e9bf9a29c591aa25930bd..3b865e9dabd6c4c75dfd5d814c92edb5151ecd86 100644 (file)
@@ -1003,6 +1003,24 @@ def build(bld):
                              ctdb-util samba-util talloc tevent replace''',
                      install_path='${CTDB_TEST_LIBEXECDIR}')
 
+    for target in ['tmon_ping_test', 'tmon_test']:
+        src = 'tests/src/' + target + '.c'
+
+        bld.SAMBA_BINARY(target,
+                         source=src,
+                         deps='''ctdb-util
+                                 ctdb-tests-backtrace
+                                 LIBASYNC_REQ
+                                 samba-util
+                                 sys_rw
+                                 tevent-util
+                                 talloc
+                                 tevent
+                                 tdb
+                                 popt
+                              ''',
+                         install_path='${CTDB_TEST_LIBEXECDIR}')
+
     bld.SAMBA_SUBSYSTEM('ctdb-protocol-tests-basic',
                         source=bld.SUBDIR('tests/src',
                                           'protocol_common_basic.c'),