123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- /* Copyright (c) 2018-2019, The Tor Project, Inc. */
- /* See LICENSE for licensing information */
- /**
- * \file test_process.c
- * \brief Test cases for the Process API.
- */
- #include "orconfig.h"
- #include "core/or/or.h"
- #include "test/test.h"
- #include "lib/process/env.h"
- #define PROCESS_PRIVATE
- #include "lib/process/process.h"
- #define PROCESS_UNIX_PRIVATE
- #include "lib/process/process_unix.h"
- #define PROCESS_WIN32_PRIVATE
- #include "lib/process/process_win32.h"
- static const char *stdout_read_buffer;
- static const char *stderr_read_buffer;
- struct process_data_t {
- smartlist_t *stdout_data;
- smartlist_t *stderr_data;
- smartlist_t *stdin_data;
- process_exit_code_t exit_code;
- };
- typedef struct process_data_t process_data_t;
- static process_data_t *
- process_data_new(void)
- {
- process_data_t *process_data = tor_malloc_zero(sizeof(process_data_t));
- process_data->stdout_data = smartlist_new();
- process_data->stderr_data = smartlist_new();
- process_data->stdin_data = smartlist_new();
- return process_data;
- }
- static void
- process_data_free(process_data_t *process_data)
- {
- if (process_data == NULL)
- return;
- SMARTLIST_FOREACH(process_data->stdout_data, char *, x, tor_free(x));
- SMARTLIST_FOREACH(process_data->stderr_data, char *, x, tor_free(x));
- SMARTLIST_FOREACH(process_data->stdin_data, char *, x, tor_free(x));
- smartlist_free(process_data->stdout_data);
- smartlist_free(process_data->stderr_data);
- smartlist_free(process_data->stdin_data);
- tor_free(process_data);
- }
- static int
- process_mocked_read_stdout(process_t *process, buf_t *buffer)
- {
- (void)process;
- if (stdout_read_buffer != NULL) {
- buf_add_string(buffer, stdout_read_buffer);
- stdout_read_buffer = NULL;
- }
- return (int)buf_datalen(buffer);
- }
- static int
- process_mocked_read_stderr(process_t *process, buf_t *buffer)
- {
- (void)process;
- if (stderr_read_buffer != NULL) {
- buf_add_string(buffer, stderr_read_buffer);
- stderr_read_buffer = NULL;
- }
- return (int)buf_datalen(buffer);
- }
- static void
- process_mocked_write_stdin(process_t *process, buf_t *buffer)
- {
- const size_t size = buf_datalen(buffer);
- if (size == 0)
- return;
- char *data = tor_malloc_zero(size + 1);
- process_data_t *process_data = process_get_data(process);
- buf_get_bytes(buffer, data, size);
- smartlist_add(process_data->stdin_data, data);
- }
- static void
- process_stdout_callback(process_t *process, const char *data, size_t size)
- {
- tt_ptr_op(process, OP_NE, NULL);
- tt_ptr_op(data, OP_NE, NULL);
- tt_int_op(strlen(data), OP_EQ, size);
- process_data_t *process_data = process_get_data(process);
- smartlist_add(process_data->stdout_data, tor_strdup(data));
- done:
- return;
- }
- static void
- process_stderr_callback(process_t *process, const char *data, size_t size)
- {
- tt_ptr_op(process, OP_NE, NULL);
- tt_ptr_op(data, OP_NE, NULL);
- tt_int_op(strlen(data), OP_EQ, size);
- process_data_t *process_data = process_get_data(process);
- smartlist_add(process_data->stderr_data, tor_strdup(data));
- done:
- return;
- }
- static bool
- process_exit_callback(process_t *process, process_exit_code_t exit_code)
- {
- tt_ptr_op(process, OP_NE, NULL);
- process_data_t *process_data = process_get_data(process);
- process_data->exit_code = exit_code;
- done:
- /* Do not free up our process_t. */
- return false;
- }
- static void
- test_default_values(void *arg)
- {
- (void)arg;
- process_t *process = process_new("/path/to/nothing");
- /* We are not running by default. */
- tt_int_op(PROCESS_STATUS_NOT_RUNNING, OP_EQ, process_get_status(process));
- /* We use the line protocol by default. */
- tt_int_op(PROCESS_PROTOCOL_LINE, OP_EQ, process_get_protocol(process));
- /* We don't set any custom data by default. */
- tt_ptr_op(NULL, OP_EQ, process_get_data(process));
- /* Our command was given to the process_t's constructor in process_new(). */
- tt_str_op("/path/to/nothing", OP_EQ, process_get_command(process));
- /* Make sure we are listed in the list of proccesses. */
- tt_assert(smartlist_contains(process_get_all_processes(),
- process));
- /* Default PID is 0. */
- tt_u64_op(0, OP_EQ, process_get_pid(process));
- /* Our arguments should be empty. */
- tt_int_op(0, OP_EQ,
- smartlist_len(process_get_arguments(process)));
- done:
- process_free(process);
- }
- static void
- test_environment(void *arg)
- {
- (void)arg;
- process_t *process = process_new("");
- process_environment_t *env = NULL;
- process_set_environment(process, "E", "F");
- process_set_environment(process, "C", "D");
- process_set_environment(process, "A", "B");
- env = process_get_environment(process);
- tt_mem_op(env->windows_environment_block, OP_EQ,
- "A=B\0C=D\0E=F\0", 12);
- tt_str_op(env->unixoid_environment_block[0], OP_EQ,
- "A=B");
- tt_str_op(env->unixoid_environment_block[1], OP_EQ,
- "C=D");
- tt_str_op(env->unixoid_environment_block[2], OP_EQ,
- "E=F");
- tt_ptr_op(env->unixoid_environment_block[3], OP_EQ,
- NULL);
- process_environment_free(env);
- /* Reset our environment. */
- smartlist_t *new_env = smartlist_new();
- smartlist_add(new_env, (char *)"FOO=bar");
- smartlist_add(new_env, (char *)"HELLO=world");
- process_reset_environment(process, new_env);
- smartlist_free(new_env);
- env = process_get_environment(process);
- tt_mem_op(env->windows_environment_block, OP_EQ,
- "FOO=bar\0HELLO=world\0", 20);
- tt_str_op(env->unixoid_environment_block[0], OP_EQ,
- "FOO=bar");
- tt_str_op(env->unixoid_environment_block[1], OP_EQ,
- "HELLO=world");
- tt_ptr_op(env->unixoid_environment_block[2], OP_EQ,
- NULL);
- done:
- process_environment_free(env);
- process_free(process);
- }
- static void
- test_stringified_types(void *arg)
- {
- (void)arg;
- /* process_protocol_t values. */
- tt_str_op("Raw", OP_EQ, process_protocol_to_string(PROCESS_PROTOCOL_RAW));
- tt_str_op("Line", OP_EQ, process_protocol_to_string(PROCESS_PROTOCOL_LINE));
- /* process_status_t values. */
- tt_str_op("not running", OP_EQ,
- process_status_to_string(PROCESS_STATUS_NOT_RUNNING));
- tt_str_op("running", OP_EQ,
- process_status_to_string(PROCESS_STATUS_RUNNING));
- tt_str_op("error", OP_EQ,
- process_status_to_string(PROCESS_STATUS_ERROR));
- done:
- return;
- }
- static void
- test_line_protocol_simple(void *arg)
- {
- (void)arg;
- process_data_t *process_data = process_data_new();
- process_t *process = process_new("");
- process_set_data(process, process_data);
- process_set_stdout_read_callback(process, process_stdout_callback);
- process_set_stderr_read_callback(process, process_stderr_callback);
- MOCK(process_read_stdout, process_mocked_read_stdout);
- MOCK(process_read_stderr, process_mocked_read_stderr);
- /* Make sure we are running with the line protocol. */
- tt_int_op(PROCESS_PROTOCOL_LINE, OP_EQ, process_get_protocol(process));
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
- stdout_read_buffer = "Hello stdout\n";
- process_notify_event_stdout(process);
- tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
- stderr_read_buffer = "Hello stderr\r\n";
- process_notify_event_stderr(process);
- tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
- /* Data should be ready. */
- tt_int_op(1, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(1, OP_EQ, smartlist_len(process_data->stderr_data));
- /* Check if the data is correct. */
- tt_str_op(smartlist_get(process_data->stdout_data, 0), OP_EQ,
- "Hello stdout");
- tt_str_op(smartlist_get(process_data->stderr_data, 0), OP_EQ,
- "Hello stderr");
- done:
- process_data_free(process_data);
- process_free(process);
- UNMOCK(process_read_stdout);
- UNMOCK(process_read_stderr);
- }
- static void
- test_line_protocol_multi(void *arg)
- {
- (void)arg;
- process_data_t *process_data = process_data_new();
- process_t *process = process_new("");
- process_set_data(process, process_data);
- process_set_stdout_read_callback(process, process_stdout_callback);
- process_set_stderr_read_callback(process, process_stderr_callback);
- MOCK(process_read_stdout, process_mocked_read_stdout);
- MOCK(process_read_stderr, process_mocked_read_stderr);
- /* Make sure we are running with the line protocol. */
- tt_int_op(PROCESS_PROTOCOL_LINE, OP_EQ, process_get_protocol(process));
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
- stdout_read_buffer = "Hello stdout\r\nOnion Onion Onion\nA B C D\r\n\r\n";
- process_notify_event_stdout(process);
- tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
- stderr_read_buffer = "Hello stderr\nFoo bar baz\nOnion Onion Onion\n";
- process_notify_event_stderr(process);
- tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
- /* Data should be ready. */
- tt_int_op(4, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(3, OP_EQ, smartlist_len(process_data->stderr_data));
- /* Check if the data is correct. */
- tt_str_op(smartlist_get(process_data->stdout_data, 0), OP_EQ,
- "Hello stdout");
- tt_str_op(smartlist_get(process_data->stdout_data, 1), OP_EQ,
- "Onion Onion Onion");
- tt_str_op(smartlist_get(process_data->stdout_data, 2), OP_EQ,
- "A B C D");
- tt_str_op(smartlist_get(process_data->stdout_data, 3), OP_EQ,
- "");
- tt_str_op(smartlist_get(process_data->stderr_data, 0), OP_EQ,
- "Hello stderr");
- tt_str_op(smartlist_get(process_data->stderr_data, 1), OP_EQ,
- "Foo bar baz");
- tt_str_op(smartlist_get(process_data->stderr_data, 2), OP_EQ,
- "Onion Onion Onion");
- done:
- process_data_free(process_data);
- process_free(process);
- UNMOCK(process_read_stdout);
- UNMOCK(process_read_stderr);
- }
- static void
- test_line_protocol_partial(void *arg)
- {
- (void)arg;
- process_data_t *process_data = process_data_new();
- process_t *process = process_new("");
- process_set_data(process, process_data);
- process_set_stdout_read_callback(process, process_stdout_callback);
- process_set_stderr_read_callback(process, process_stderr_callback);
- MOCK(process_read_stdout, process_mocked_read_stdout);
- MOCK(process_read_stderr, process_mocked_read_stderr);
- /* Make sure we are running with the line protocol. */
- tt_int_op(PROCESS_PROTOCOL_LINE, OP_EQ, process_get_protocol(process));
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
- stdout_read_buffer = "Hello stdout this is a partial line ...";
- process_notify_event_stdout(process);
- tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
- stderr_read_buffer = "Hello stderr this is a partial line ...";
- process_notify_event_stderr(process);
- tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
- /* Data should NOT be ready. */
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
- stdout_read_buffer = " the end\nAnother partial string goes here ...";
- process_notify_event_stdout(process);
- tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
- stderr_read_buffer = " the end\nAnother partial string goes here ...";
- process_notify_event_stderr(process);
- tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
- /* Some data should be ready. */
- tt_int_op(1, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(1, OP_EQ, smartlist_len(process_data->stderr_data));
- stdout_read_buffer = " the end\nFoo bar baz\n";
- process_notify_event_stdout(process);
- tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
- stderr_read_buffer = " the end\nFoo bar baz\n";
- process_notify_event_stderr(process);
- tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
- /* Some data should be ready. */
- tt_int_op(3, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(3, OP_EQ, smartlist_len(process_data->stderr_data));
- /* Check if the data is correct. */
- tt_str_op(smartlist_get(process_data->stdout_data, 0), OP_EQ,
- "Hello stdout this is a partial line ... the end");
- tt_str_op(smartlist_get(process_data->stdout_data, 1), OP_EQ,
- "Another partial string goes here ... the end");
- tt_str_op(smartlist_get(process_data->stdout_data, 2), OP_EQ,
- "Foo bar baz");
- tt_str_op(smartlist_get(process_data->stderr_data, 0), OP_EQ,
- "Hello stderr this is a partial line ... the end");
- tt_str_op(smartlist_get(process_data->stderr_data, 1), OP_EQ,
- "Another partial string goes here ... the end");
- tt_str_op(smartlist_get(process_data->stderr_data, 2), OP_EQ,
- "Foo bar baz");
- done:
- process_data_free(process_data);
- process_free(process);
- UNMOCK(process_read_stdout);
- UNMOCK(process_read_stderr);
- }
- static void
- test_raw_protocol_simple(void *arg)
- {
- (void)arg;
- process_data_t *process_data = process_data_new();
- process_t *process = process_new("");
- process_set_data(process, process_data);
- process_set_protocol(process, PROCESS_PROTOCOL_RAW);
- process_set_stdout_read_callback(process, process_stdout_callback);
- process_set_stderr_read_callback(process, process_stderr_callback);
- MOCK(process_read_stdout, process_mocked_read_stdout);
- MOCK(process_read_stderr, process_mocked_read_stderr);
- /* Make sure we are running with the raw protocol. */
- tt_int_op(PROCESS_PROTOCOL_RAW, OP_EQ, process_get_protocol(process));
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(0, OP_EQ, smartlist_len(process_data->stderr_data));
- stdout_read_buffer = "Hello stdout\n";
- process_notify_event_stdout(process);
- tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
- stderr_read_buffer = "Hello stderr\n";
- process_notify_event_stderr(process);
- tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
- /* Data should be ready. */
- tt_int_op(1, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(1, OP_EQ, smartlist_len(process_data->stderr_data));
- stdout_read_buffer = "Hello, again, stdout\nThis contains multiple lines";
- process_notify_event_stdout(process);
- tt_ptr_op(NULL, OP_EQ, stdout_read_buffer);
- stderr_read_buffer = "Hello, again, stderr\nThis contains multiple lines";
- process_notify_event_stderr(process);
- tt_ptr_op(NULL, OP_EQ, stderr_read_buffer);
- /* Data should be ready. */
- tt_int_op(2, OP_EQ, smartlist_len(process_data->stdout_data));
- tt_int_op(2, OP_EQ, smartlist_len(process_data->stderr_data));
- /* Check if the data is correct. */
- tt_str_op(smartlist_get(process_data->stdout_data, 0), OP_EQ,
- "Hello stdout\n");
- tt_str_op(smartlist_get(process_data->stdout_data, 1), OP_EQ,
- "Hello, again, stdout\nThis contains multiple lines");
- tt_str_op(smartlist_get(process_data->stderr_data, 0), OP_EQ,
- "Hello stderr\n");
- tt_str_op(smartlist_get(process_data->stderr_data, 1), OP_EQ,
- "Hello, again, stderr\nThis contains multiple lines");
- done:
- process_data_free(process_data);
- process_free(process);
- UNMOCK(process_read_stdout);
- UNMOCK(process_read_stderr);
- }
- static void
- test_write_simple(void *arg)
- {
- (void)arg;
- process_data_t *process_data = process_data_new();
- process_t *process = process_new("");
- process_set_data(process, process_data);
- MOCK(process_write_stdin, process_mocked_write_stdin);
- process_write(process, (uint8_t *)"Hello world\n", 12);
- process_notify_event_stdin(process);
- tt_int_op(1, OP_EQ, smartlist_len(process_data->stdin_data));
- process_printf(process, "Hello %s !\n", "moon");
- process_notify_event_stdin(process);
- tt_int_op(2, OP_EQ, smartlist_len(process_data->stdin_data));
- done:
- process_data_free(process_data);
- process_free(process);
- UNMOCK(process_write_stdin);
- }
- static void
- test_exit_simple(void *arg)
- {
- (void)arg;
- process_data_t *process_data = process_data_new();
- process_t *process = process_new("");
- process_set_data(process, process_data);
- process_set_exit_callback(process, process_exit_callback);
- /* Our default is 0. */
- tt_u64_op(0, OP_EQ, process_data->exit_code);
- /* Fake that we are a running process. */
- process_set_status(process, PROCESS_STATUS_RUNNING);
- tt_int_op(process_get_status(process), OP_EQ, PROCESS_STATUS_RUNNING);
- /* Fake an exit. */
- process_notify_event_exit(process, 1337);
- /* Check if our state changed and if our callback fired. */
- tt_int_op(process_get_status(process), OP_EQ, PROCESS_STATUS_NOT_RUNNING);
- tt_u64_op(1337, OP_EQ, process_data->exit_code);
- done:
- process_set_data(process, process_data);
- process_data_free(process_data);
- process_free(process);
- }
- static void
- test_argv_simple(void *arg)
- {
- (void)arg;
- process_t *process = process_new("/bin/cat");
- char **argv = NULL;
- /* Setup some arguments. */
- process_append_argument(process, "foo");
- process_append_argument(process, "bar");
- process_append_argument(process, "baz");
- /* Check the number of elements. */
- tt_int_op(3, OP_EQ,
- smartlist_len(process_get_arguments(process)));
- /* Let's try to convert it into a Unix style char **argv. */
- argv = process_get_argv(process);
- /* Check our values. */
- tt_str_op(argv[0], OP_EQ, "/bin/cat");
- tt_str_op(argv[1], OP_EQ, "foo");
- tt_str_op(argv[2], OP_EQ, "bar");
- tt_str_op(argv[3], OP_EQ, "baz");
- tt_ptr_op(argv[4], OP_EQ, NULL);
- done:
- tor_free(argv);
- process_free(process);
- }
- static void
- test_unix(void *arg)
- {
- (void)arg;
- #ifndef _WIN32
- process_t *process = process_new("");
- /* On Unix all processes should have a Unix process handle. */
- tt_ptr_op(NULL, OP_NE, process_get_unix_process(process));
- done:
- process_free(process);
- #endif
- }
- static void
- test_win32(void *arg)
- {
- (void)arg;
- #ifdef _WIN32
- process_t *process = process_new("");
- char *joined_argv = NULL;
- /* On Win32 all processes should have a Win32 process handle. */
- tt_ptr_op(NULL, OP_NE, process_get_win32_process(process));
- /* Based on some test cases from "Parsing C++ Command-Line Arguments" in
- * MSDN but we don't exercise all quoting rules because tor_join_win_cmdline
- * will try to only generate simple cases for the child process to parse;
- * i.e. we never embed quoted strings in arguments. */
- const char *argvs[][4] = {
- {"a", "bb", "CCC", NULL}, // Normal
- {NULL, NULL, NULL, NULL}, // Empty argument list
- {"", NULL, NULL, NULL}, // Empty argument
- {"\"a", "b\"b", "CCC\"", NULL}, // Quotes
- {"a\tbc", "dd dd", "E", NULL}, // Whitespace
- {"a\\\\\\b", "de fg", "H", NULL}, // Backslashes
- {"a\\\"b", "\\c", "D\\", NULL}, // Backslashes before quote
- {"a\\\\b c", "d", "E", NULL}, // Backslashes not before quote
- { NULL } // Terminator
- };
- const char *cmdlines[] = {
- "a bb CCC",
- "",
- "\"\"",
- "\\\"a b\\\"b CCC\\\"",
- "\"a\tbc\" \"dd dd\" E",
- "a\\\\\\b \"de fg\" H",
- "a\\\\\\\"b \\c D\\",
- "\"a\\\\b c\" d E",
- NULL // Terminator
- };
- int i;
- for (i=0; cmdlines[i]!=NULL; i++) {
- log_info(LD_GENERAL, "Joining argvs[%d], expecting <%s>", i, cmdlines[i]);
- joined_argv = tor_join_win_cmdline(argvs[i]);
- tt_str_op(cmdlines[i],OP_EQ, joined_argv);
- tor_free(joined_argv);
- }
- done:
- tor_free(joined_argv);
- process_free(process);
- #endif
- }
- struct testcase_t process_tests[] = {
- { "default_values", test_default_values, TT_FORK, NULL, NULL },
- { "environment", test_environment, TT_FORK, NULL, NULL },
- { "stringified_types", test_stringified_types, TT_FORK, NULL, NULL },
- { "line_protocol_simple", test_line_protocol_simple, TT_FORK, NULL, NULL },
- { "line_protocol_multi", test_line_protocol_multi, TT_FORK, NULL, NULL },
- { "line_protocol_partial", test_line_protocol_partial, TT_FORK, NULL, NULL },
- { "raw_protocol_simple", test_raw_protocol_simple, TT_FORK, NULL, NULL },
- { "write_simple", test_write_simple, TT_FORK, NULL, NULL },
- { "exit_simple", test_exit_simple, TT_FORK, NULL, NULL },
- { "argv_simple", test_argv_simple, TT_FORK, NULL, NULL },
- { "unix", test_unix, TT_FORK, NULL, NULL },
- { "win32", test_win32, TT_FORK, NULL, NULL },
- END_OF_TESTCASES
- };
|