>From b86664a70becd39334abb35955f72953efe674ce Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Tue, 13 Jan 2015 21:44:21 +0100 Subject: [PATCH 2/5] TESTS: Cover child_common.c with unit tests The module wasn't tested properly, which made it harder to patch it --- Makefile.am | 3 + src/tests/cmocka/dummy_child.c | 29 ++++ src/tests/cmocka/test_child_common.c | 302 ++++++++++++++++++++++++++++++++++- 3 files changed, 333 insertions(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 355f8c3d5d8480c035a28eb2abef382b6ceee043..c14a50fc362938164ada3e976c35d2458a7bb6e6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2175,6 +2175,9 @@ test_child_common_CFLAGS = \ $(AM_CFLAGS) \ -DCHILD_DIR=\"$(builddir)\" \ $(NULL) +test_child_common_LDFLAGS = \ + -Wl,-wrap,child_io_destructor \ + $(NULL) test_child_common_LDADD = \ $(CMOCKA_LIBS) \ $(POPT_LIBS) \ diff --git a/src/tests/cmocka/dummy_child.c b/src/tests/cmocka/dummy_child.c index 057fa6450b51cbac589e64553bb0692cbbfac655..3f79ca837b92b8e8ea60ae45eec4c9148f162719 100644 --- a/src/tests/cmocka/dummy_child.c +++ b/src/tests/cmocka/dummy_child.c @@ -35,6 +35,10 @@ int main(int argc, const char *argv[]) int opt; int debug_fd = -1; poptContext pc; + ssize_t len; + ssize_t written; + errno_t ret; + uint8_t buf[IN_BUF_SIZE]; const char *action = NULL; const char *guitar; const char *drums; @@ -80,6 +84,31 @@ int main(int argc, const char *argv[]) DEBUG(SSSDBG_CRIT_FAILURE, "This band sounds weird\n"); _exit(1); } + } else if (strcasecmp(action, "echo") == 0) { + errno = 0; + len = sss_atomic_read_s(STDIN_FILENO, buf, IN_BUF_SIZE); + if (len == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "read failed [%d][%s].\n", ret, strerror(ret)); + _exit(1); + } + close(STDIN_FILENO); + + errno = 0; + written = sss_atomic_write_s(3, buf, len); + if (written == -1) { + ret = errno; + DEBUG(SSSDBG_CRIT_FAILURE, "write failed [%d][%s].\n", ret, + strerror(ret)); + _exit(1); + } + close(STDOUT_FILENO); + + if (written != len) { + DEBUG(SSSDBG_CRIT_FAILURE, "Expected to write %zu bytes, wrote %zu\n", + len, written); + _exit(1); + } } } diff --git a/src/tests/cmocka/test_child_common.c b/src/tests/cmocka/test_child_common.c index 23e14ce49ff75c8c14320be687b456cfcad1982e..7c36abdb5ae2cbc8b1813b549df298a603815af6 100644 --- a/src/tests/cmocka/test_child_common.c +++ b/src/tests/cmocka/test_child_common.c @@ -31,6 +31,8 @@ #define TEST_BIN "dummy-child" #define ECHO_STR "Hello child" +static int destructor_called; + struct child_test_ctx { int pipefd_to_child[2]; int pipefd_from_child[2]; @@ -111,7 +113,7 @@ void test_exec_child(void **state) } } -/* Just make sure the exec works. The child does nothing but exits */ +/* Make sure extra arguments are passed correctly */ void test_exec_child_extra_args(void **state) { errno_t ret; @@ -154,6 +156,292 @@ void test_exec_child_extra_args(void **state) } } +struct tevent_req *echo_child_write_send(TALLOC_CTX *mem_ctx, + struct child_test_ctx *child_tctx, + struct child_io_fds *io_fds, + const char *input); +static void echo_child_write_done(struct tevent_req *subreq); +static void echo_child_read_done(struct tevent_req *subreq); + +int __real_child_io_destructor(void *ptr); + +int __wrap_child_io_destructor(void *ptr) +{ + destructor_called = 1; + return __real_child_io_destructor(ptr); +} + +/* Test that writing to the pipes works as expected */ +void test_exec_child_io_destruct(void **state) +{ + struct child_test_ctx *child_tctx = talloc_get_type(*state, + struct child_test_ctx); + struct child_io_fds *io_fds; + + io_fds = talloc(child_tctx, struct child_io_fds); + io_fds->read_from_child_fd = -1; + io_fds->write_to_child_fd = -1; + assert_non_null(io_fds); + talloc_set_destructor((void *) io_fds, child_io_destructor); + + io_fds->read_from_child_fd = child_tctx->pipefd_from_child[0]; + io_fds->write_to_child_fd = child_tctx->pipefd_to_child[1]; + + destructor_called = 0; + talloc_free(io_fds); + assert_int_equal(destructor_called, 1); + + errno = 0; + close(child_tctx->pipefd_from_child[0]); + assert_int_equal(errno, EBADF); + + errno = 0; + close(child_tctx->pipefd_from_child[1]); + assert_int_equal(errno, 0); + + errno = 0; + close(child_tctx->pipefd_to_child[0]); + assert_int_equal(errno, 0); + + errno = 0; + close(child_tctx->pipefd_to_child[1]); + assert_int_equal(errno, EBADF); +} + +void test_child_cb(int child_status, + struct tevent_signal *sige, + void *pvt); + +/* Test that writing to the pipes works as expected */ +void test_exec_child_handler(void **state) +{ + errno_t ret; + pid_t child_pid; + struct child_test_ctx *child_tctx = talloc_get_type(*state, + struct child_test_ctx); + struct sss_child_ctx_old *child_old_ctx; + + ret = unsetenv("TEST_CHILD_ACTION"); + assert_int_equal(ret, 0); + + child_pid = fork(); + assert_int_not_equal(child_pid, -1); + if (child_pid == 0) { + ret = exec_child(child_tctx, + child_tctx->pipefd_to_child, + child_tctx->pipefd_from_child, + CHILD_DIR"/"TEST_BIN, 2); + assert_int_equal(ret, EOK); + } + + ret = child_handler_setup(child_tctx->test_ctx->ev, child_pid, + test_child_cb, child_tctx, &child_old_ctx); + assert_int_equal(ret, EOK); + + ret = test_ev_loop(child_tctx->test_ctx); + assert_int_equal(ret, EOK); + assert_int_equal(child_tctx->test_ctx->error, 0); +} + +void test_child_cb(int child_status, + struct tevent_signal *sige, + void *pvt) +{ + struct child_test_ctx *child_ctx = talloc_get_type(pvt, struct child_test_ctx); + + child_ctx->test_ctx->error = EIO; + if (WIFEXITED(child_status) && WEXITSTATUS(child_status) == 0) { + child_ctx->test_ctx->error = 0; + } + + child_ctx->test_ctx->done = true; +} + +/* Test that writing to the pipes works as expected */ +void test_exec_child_echo(void **state) +{ + errno_t ret; + pid_t child_pid; + struct child_test_ctx *child_tctx = talloc_get_type(*state, + struct child_test_ctx); + struct tevent_req *req; + struct child_io_fds *io_fds; + + setenv("TEST_CHILD_ACTION", "echo", 1); + + io_fds = talloc(child_tctx, struct child_io_fds); + assert_non_null(io_fds); + io_fds->read_from_child_fd = -1; + io_fds->write_to_child_fd = -1; + talloc_set_destructor((void *) io_fds, child_io_destructor); + + child_pid = fork(); + assert_int_not_equal(child_pid, -1); + if (child_pid == 0) { + ret = exec_child_ex(child_tctx, + child_tctx->pipefd_to_child, + child_tctx->pipefd_from_child, + CHILD_DIR"/"TEST_BIN, 2, NULL, + STDIN_FILENO, 3); + assert_int_equal(ret, EOK); + } + + DEBUG(SSSDBG_FUNC_DATA, "Forked into %d\n", child_pid); + + io_fds->read_from_child_fd = child_tctx->pipefd_from_child[0]; + close(child_tctx->pipefd_from_child[1]); + io_fds->write_to_child_fd = child_tctx->pipefd_to_child[1]; + close(child_tctx->pipefd_to_child[0]); + + fd_nonblocking(io_fds->write_to_child_fd); + fd_nonblocking(io_fds->read_from_child_fd); + + ret = child_handler_setup(child_tctx->test_ctx->ev, child_pid, + NULL, NULL, NULL); + assert_int_equal(ret, EOK); + + req = echo_child_write_send(child_tctx, child_tctx, io_fds, ECHO_STR); + assert_non_null(req); + + ret = test_ev_loop(child_tctx->test_ctx); + talloc_free(io_fds); + assert_int_equal(ret, EOK); +} + +struct test_exec_echo_state { + struct child_io_fds *io_fds; + struct io_buffer buf; + struct child_test_ctx *child_test_ctx; +}; + +struct tevent_req *echo_child_write_send(TALLOC_CTX *mem_ctx, + struct child_test_ctx *child_tctx, + struct child_io_fds *io_fds, + const char *input) +{ + struct tevent_req *req; + struct tevent_req *subreq; + struct test_exec_echo_state *echo_state; + + req = tevent_req_create(mem_ctx, &echo_state, struct test_exec_echo_state); + assert_non_null(req); + + echo_state->child_test_ctx = child_tctx; + + echo_state->buf.data = (unsigned char *) talloc_strdup(echo_state, input); + assert_non_null(echo_state->buf.data); + echo_state->buf.size = strlen(input) + 1; + echo_state->io_fds = io_fds; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Writing..\n"); + subreq = write_pipe_send(child_tctx, child_tctx->test_ctx->ev, + echo_state->buf.data, echo_state->buf.size, + echo_state->io_fds->write_to_child_fd); + assert_non_null(subreq); + tevent_req_set_callback(subreq, echo_child_write_done, req); + + return req; +} + +static void echo_child_write_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct test_exec_echo_state *echo_state; + errno_t ret; + + req = tevent_req_callback_data(subreq, struct tevent_req); + echo_state = tevent_req_data(req, struct test_exec_echo_state); + + ret = write_pipe_recv(subreq); + DEBUG(SSSDBG_TRACE_INTERNAL, "Writing OK\n"); + talloc_zfree(subreq); + assert_int_equal(ret, EOK); + + close(echo_state->io_fds->write_to_child_fd); + echo_state->io_fds->write_to_child_fd = -1; + + DEBUG(SSSDBG_TRACE_INTERNAL, "Reading..\n"); + subreq = read_pipe_send(echo_state, + echo_state->child_test_ctx->test_ctx->ev, + echo_state->io_fds->read_from_child_fd); + assert_non_null(subreq); + tevent_req_set_callback(subreq, echo_child_read_done, req); +} + +static void echo_child_read_done(struct tevent_req *subreq) +{ + struct tevent_req *req; + struct test_exec_echo_state *echo_state; + errno_t ret; + ssize_t len; + uint8_t *buf; + + req = tevent_req_callback_data(subreq, struct tevent_req); + echo_state = tevent_req_data(req, struct test_exec_echo_state); + + ret = read_pipe_recv(subreq, echo_state, &buf, &len); + talloc_zfree(subreq); + DEBUG(SSSDBG_TRACE_INTERNAL, "Reading OK\n"); + assert_int_equal(ret, EOK); + + close(echo_state->io_fds->read_from_child_fd); + echo_state->io_fds->read_from_child_fd = -1; + + assert_string_equal(buf, echo_state->buf.data); + echo_state->child_test_ctx->test_ctx->done = true; +} + +void sss_child_cb(int pid, int wait_status, void *pvt); + +/* Just make sure the exec works. The child does nothing but exits */ +void test_sss_child(void **state) +{ + errno_t ret; + pid_t child_pid; + struct child_test_ctx *child_tctx = talloc_get_type(*state, + struct child_test_ctx); + struct sss_sigchild_ctx *sc_ctx; + struct sss_child_ctx *sss_child; + + ret = unsetenv("TEST_CHILD_ACTION"); + assert_int_equal(ret, 0); + + ret = sss_sigchld_init(child_tctx, child_tctx->test_ctx->ev, &sc_ctx); + assert_int_equal(ret, EOK); + + child_pid = fork(); + assert_int_not_equal(child_pid, -1); + if (child_pid == 0) { + ret = exec_child(child_tctx, + child_tctx->pipefd_to_child, + child_tctx->pipefd_from_child, + CHILD_DIR"/"TEST_BIN, 2); + assert_int_equal(ret, EOK); + } + + ret = sss_child_register(child_tctx, sc_ctx, + child_pid, + sss_child_cb, + child_tctx, &sss_child); + assert_int_equal(ret, EOK); + + ret = test_ev_loop(child_tctx->test_ctx); + assert_int_equal(ret, EOK); + assert_int_equal(child_tctx->test_ctx->error, 0); +} + +void sss_child_cb(int pid, int wait_status, void *pvt) +{ + struct child_test_ctx *child_ctx = talloc_get_type(pvt, struct child_test_ctx); + + child_ctx->test_ctx->error = EIO; + if (WIFEXITED(wait_status) && WEXITSTATUS(wait_status) == 0) { + child_ctx->test_ctx->error = 0; + } + + child_ctx->test_ctx->done = true; +} + int main(int argc, const char *argv[]) { int rv; @@ -172,6 +460,18 @@ int main(int argc, const char *argv[]) unit_test_setup_teardown(test_exec_child_extra_args, child_test_setup, child_test_teardown), + unit_test_setup_teardown(test_exec_child_io_destruct, + child_test_setup, + child_test_teardown), + unit_test_setup_teardown(test_exec_child_handler, + child_test_setup, + child_test_teardown), + unit_test_setup_teardown(test_exec_child_echo, + child_test_setup, + child_test_teardown), + unit_test_setup_teardown(test_sss_child, + child_test_setup, + child_test_teardown), }; /* Set debug level to invalid value so we can deside if -d 0 was used. */ -- 2.1.0