From e3833193af11dc4252e90565c3919889cc4130b3 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 30 Apr 2014 12:50:00 -0400 Subject: [PATCH] More unit tests for process spawning Try killing a running process; try noticing that a process has exited without checking its output; verify that waitpid_cb (when present) is set to NULL when you would expect it to be. --- src/test/test-child.c | 40 ++++++++++---- src/test/test_util.c | 124 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 146 insertions(+), 18 deletions(-) diff --git a/src/test/test-child.c b/src/test/test-child.c index ef10fbb922..756782e70b 100644 --- a/src/test/test-child.c +++ b/src/test/test-child.c @@ -9,6 +9,13 @@ #else #include #endif +#include + +#ifdef _WIN32 +#define SLEEP(sec) Sleep((sec)*1000) +#else +#define SLEEP(sec) sleep(sec) +#endif /** Trivial test program which prints out its command line arguments so we can * check if tor_spawn_background() works */ @@ -16,27 +23,38 @@ int main(int argc, char **argv) { int i; + int delay = 1; + int fast = 0; + + if (argc > 1) { + if (!strcmp(argv[1], "--hang")) { + delay = 60; + } else if (!strcmp(argv[1], "--fast")) { + fast = 1; + delay = 0; + } + } fprintf(stdout, "OUT\n"); fprintf(stderr, "ERR\n"); for (i = 1; i < argc; i++) fprintf(stdout, "%s\n", argv[i]); - fprintf(stdout, "SLEEPING\n"); + if (!fast) + fprintf(stdout, "SLEEPING\n"); /* We need to flush stdout so that test_util_spawn_background_partial_read() succeed. Otherwise ReadFile() will get the entire output in one */ // XXX: Can we make stdio flush on newline? fflush(stdout); -#ifdef _WIN32 - Sleep(1000); -#else - sleep(1); -#endif + if (!fast) + SLEEP(1); fprintf(stdout, "DONE\n"); -#ifdef _WIN32 - Sleep(1000); -#else - sleep(1); -#endif + fflush(stdout); + if (fast) + return 0; + + while (--delay) { + SLEEP(1); + } return 0; } diff --git a/src/test/test_util.c b/src/test/test_util.c index 6d6b6dbdf8..029e88eb69 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -14,6 +14,7 @@ #include "test.h" #include "mempool.h" #include "memarea.h" +#include "util_process.h" #ifdef _WIN32 #include @@ -2529,6 +2530,10 @@ test_util_fgets_eagain(void *ptr) } #endif +#ifdef _WIN32 +#define notify_pending_waitpid_callbacks() STMT_NIL +#endif + /** Helper function for testing tor_spawn_background */ static void run_util_spawn_background(const char *argv[], const char *expected_out, @@ -2548,19 +2553,28 @@ run_util_spawn_background(const char *argv[], const char *expected_out, status = tor_spawn_background(argv[0], argv, NULL, &process_handle); #endif + notify_pending_waitpid_callbacks(); + test_eq(expected_status, status); - if (status == PROCESS_STATUS_ERROR) + if (status == PROCESS_STATUS_ERROR) { + tt_ptr_op(process_handle, ==, NULL); return; + } test_assert(process_handle != NULL); test_eq(expected_status, process_handle->status); +#ifndef _WIN32 + notify_pending_waitpid_callbacks(); + tt_ptr_op(process_handle->waitpid_cb, !=, NULL); +#endif + #ifdef _WIN32 test_assert(process_handle->stdout_pipe != INVALID_HANDLE_VALUE); test_assert(process_handle->stderr_pipe != INVALID_HANDLE_VALUE); #else - test_assert(process_handle->stdout_pipe > 0); - test_assert(process_handle->stderr_pipe > 0); + test_assert(process_handle->stdout_pipe >= 0); + test_assert(process_handle->stderr_pipe >= 0); #endif /* Check stdout */ @@ -2571,12 +2585,19 @@ run_util_spawn_background(const char *argv[], const char *expected_out, test_eq(strlen(expected_out), pos); test_streq(expected_out, stdout_buf); + notify_pending_waitpid_callbacks(); + /* Check it terminated correctly */ retval = tor_get_exit_code(process_handle, 1, &exit_code); test_eq(PROCESS_EXIT_EXITED, retval); test_eq(expected_exit, exit_code); // TODO: Make test-child exit with something other than 0 +#ifndef _WIN32 + notify_pending_waitpid_callbacks(); + tt_ptr_op(process_handle->waitpid_cb, ==, NULL); +#endif + /* Check stderr */ pos = tor_read_all_from_process_stderr(process_handle, stderr_buf, sizeof(stderr_buf) - 1); @@ -2585,6 +2606,8 @@ run_util_spawn_background(const char *argv[], const char *expected_out, test_streq(expected_err, stderr_buf); test_eq(strlen(expected_err), pos); + notify_pending_waitpid_callbacks(); + done: if (process_handle) tor_process_handle_destroy(process_handle, 1); @@ -2607,7 +2630,7 @@ test_util_spawn_background_ok(void *ptr) (void)ptr; run_util_spawn_background(argv, expected_out, expected_err, 0, - PROCESS_STATUS_RUNNING); + PROCESS_STATUS_RUNNING); } /** Check that failing to find the executable works as expected */ @@ -2637,13 +2660,13 @@ test_util_spawn_background_fail(void *ptr) "ERR: Failed to spawn background process - code %s\n", code); run_util_spawn_background(argv, expected_out, expected_err, 255, - expected_status); + expected_status); } /** Test that reading from a handle returns a partial read rather than * blocking */ static void -test_util_spawn_background_partial_read(void *ptr) +test_util_spawn_background_partial_read_impl(int exit_early) { const int expected_exit = 0; const int expected_status = PROCESS_STATUS_RUNNING; @@ -2668,7 +2691,16 @@ test_util_spawn_background_partial_read(void *ptr) int eof = 0; #endif int expected_out_ctr; - (void)ptr; + + + if (exit_early) { + argv[1] = "--hang"; +#ifdef _WIN32 + expected_out[0] = "OUT\r\n--hang\r\nSLEEPING\r\n"; +#else + expected_out[0] = "OUT\n--hang\nSLEEPING\n"; +#endif + } /* Start the program */ #ifdef _WIN32 @@ -2704,6 +2736,12 @@ test_util_spawn_background_partial_read(void *ptr) expected_out_ctr++; } + if (exit_early) { + tor_process_handle_destroy(process_handle, 1); + process_handle = NULL; + goto done; + } + /* The process should have exited without writing more */ #ifdef _WIN32 pos = tor_read_all_handle(process_handle->stdout_pipe, stdout_buf, @@ -2741,6 +2779,76 @@ test_util_spawn_background_partial_read(void *ptr) tor_process_handle_destroy(process_handle, 1); } +static void +test_util_spawn_background_partial_read(void *arg) +{ + (void)arg; + test_util_spawn_background_partial_read_impl(0); +} + +static void +test_util_spawn_background_exit_early(void *arg) +{ + (void)arg; + test_util_spawn_background_partial_read_impl(1); +} + +static void +test_util_spawn_background_waitpid_notify(void *arg) +{ + int retval, exit_code; + process_handle_t *process_handle=NULL; + int status; + int ms_timer; + +#ifdef _WIN32 + const char *argv[] = {"test-child.exe", "--fast", NULL}; +#else + const char *argv[] = {BUILDDIR "/src/test/test-child", "--fast", NULL}; +#endif + + (void) arg; + +#ifdef _WIN32 + status = tor_spawn_background(NULL, argv, NULL, &process_handle); +#else + status = tor_spawn_background(argv[0], argv, NULL, &process_handle); +#endif + + tt_int_op(status, ==, PROCESS_STATUS_RUNNING); + tt_ptr_op(process_handle, !=, NULL); + + /* We're not going to look at the stdout/stderr output this time. Instead, + * we're testing whether notify_pending_waitpid_calbacks() can report the + * process exit (on unix) and/or whether tor_get_exit_code() can notice it + * (on windows) */ + +#ifndef _WIN32 + ms_timer = 30*1000; + tt_ptr_op(process_handle->waitpid_cb, !=, NULL); + while (process_handle->waitpid_cb && ms_timer > 0) { + tor_sleep_msec(100); + ms_timer -= 100; + notify_pending_waitpid_callbacks(); + } + tt_int_op(ms_timer, >, 0); + tt_ptr_op(process_handle->waitpid_cb, ==, NULL); +#endif + + ms_timer = 30*1000; + while (((retval = tor_get_exit_code(process_handle, 0, &exit_code)) + == PROCESS_EXIT_RUNNING) && ms_timer > 0) { + tor_sleep_msec(100); + ms_timer -= 100; + } + tt_int_op(ms_timer, >, 0); + + tt_int_op(retval, ==, PROCESS_EXIT_EXITED); + + done: + tor_process_handle_destroy(process_handle, 1); +} + /** * Test for format_hex_number_sigsafe() */ @@ -3682,6 +3790,8 @@ struct testcase_t util_tests[] = { UTIL_TEST(spawn_background_ok, 0), UTIL_TEST(spawn_background_fail, 0), UTIL_TEST(spawn_background_partial_read, 0), + UTIL_TEST(spawn_background_exit_early, 0), + UTIL_TEST(spawn_background_waitpid_notify, 0), UTIL_TEST(format_hex_number, 0), UTIL_TEST(format_dec_number, 0), UTIL_TEST(join_win_cmdline, 0),