test_child_common.c revision 4e5e846de22407f825fe3b4040d79606818a2419
/*
Authors:
Jakub Hrozek <jhrozek@redhat.com>
Copyright (C) 2014 Red Hat
SSSD tests: Child handlers
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 <talloc.h>
#include <tevent.h>
#include <errno.h>
#include <popt.h>
#include "util/child_common.h"
#include "tests/cmocka/common_mock.h"
#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];
struct sss_test_ctx *test_ctx;
};
static int child_test_setup(void **state)
{
struct child_test_ctx *child_tctx;
errno_t ret;
check_leaks_push(global_talloc_context);
child_tctx = talloc(global_talloc_context, struct child_test_ctx);
assert_non_null(child_tctx);
child_tctx->test_ctx = create_ev_test_ctx(child_tctx);
assert_non_null(child_tctx->test_ctx);
ret = pipe(child_tctx->pipefd_from_child);
assert_int_not_equal(ret, -1);
DEBUG(SSSDBG_TRACE_LIBS, "from_child: %d:%d\n",
child_tctx->pipefd_from_child[0],
child_tctx->pipefd_from_child[1]);
ret = pipe(child_tctx->pipefd_to_child);
assert_int_not_equal(ret, -1);
DEBUG(SSSDBG_TRACE_LIBS, "to_child: %d:%d\n",
child_tctx->pipefd_to_child[0],
child_tctx->pipefd_to_child[1]);
*state = child_tctx;
return 0;
}
static int child_test_teardown(void **state)
{
struct child_test_ctx *child_tctx = talloc_get_type(*state,
struct child_test_ctx);
talloc_free(child_tctx);
check_leaks_pop(global_talloc_context);
return 0;
}
/* Just make sure the exec works. The child does nothing but exits */
void test_exec_child(void **state)
{
errno_t ret;
pid_t child_pid;
int status;
struct child_test_ctx *child_tctx = talloc_get_type(*state,
struct child_test_ctx);
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);
} else {
do {
errno = 0;
ret = waitpid(child_pid, &status, 0);
} while (ret == -1 && errno == EINTR);
if (ret > 0) {
ret = EIO;
if (WIFEXITED(status)) {
ret = WEXITSTATUS(status);
assert_int_equal(ret, 0);
}
} else {
DEBUG(SSSDBG_FUNC_DATA,
"Failed to wait for children %d\n", child_pid);
ret = EIO;
}
}
}
/* Make sure extra arguments are passed correctly */
void test_exec_child_extra_args(void **state)
{
errno_t ret;
pid_t child_pid;
int status;
struct child_test_ctx *child_tctx = talloc_get_type(*state,
struct child_test_ctx);
const char *extra_args[] = { "--guitar=george",
"--drums=ringo",
NULL };
setenv("TEST_CHILD_ACTION", "check_extra_args", 1);
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, extra_args,
STDIN_FILENO, STDOUT_FILENO);
assert_int_equal(ret, EOK);
} else {
do {
errno = 0;
ret = waitpid(child_pid, &status, 0);
} while (ret == -1 && errno == EINTR);
if (ret > 0) {
ret = EIO;
if (WIFEXITED(status)) {
ret = WEXITSTATUS(status);
assert_int_equal(ret, 0);
}
} else {
DEBUG(SSSDBG_FUNC_DATA,
"Failed to wait for children %d\n", child_pid);
ret = EIO;
}
}
}
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]);
sss_fd_nonblocking(io_fds->write_to_child_fd);
sss_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;
poptContext pc;
int opt;
struct poptOption long_options[] = {
POPT_AUTOHELP
SSSD_DEBUG_OPTS
POPT_TABLEEND
};
const struct CMUnitTest tests[] = {
cmocka_unit_test_setup_teardown(test_exec_child,
child_test_setup,
child_test_teardown),
cmocka_unit_test_setup_teardown(test_exec_child_extra_args,
child_test_setup,
child_test_teardown),
cmocka_unit_test_setup_teardown(test_exec_child_io_destruct,
child_test_setup,
child_test_teardown),
cmocka_unit_test_setup_teardown(test_exec_child_handler,
child_test_setup,
child_test_teardown),
cmocka_unit_test_setup_teardown(test_exec_child_echo,
child_test_setup,
child_test_teardown),
cmocka_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. */
debug_level = SSSDBG_INVALID;
pc = poptGetContext(argv[0], argc, argv, long_options, 0);
while((opt = poptGetNextOpt(pc)) != -1) {
switch(opt) {
default:
fprintf(stderr, "\nInvalid option %s: %s\n\n",
poptBadOption(pc, 0), poptStrerror(opt));
poptPrintUsage(pc, stderr, 0);
return 1;
}
}
poptFreeContext(pc);
DEBUG_CLI_INIT(debug_level);
rv = cmocka_run_group_tests(tests, NULL, NULL);
return rv;
}