diff options
Diffstat (limited to 'src/test')
-rw-r--r-- | src/test/Makefile.am | 17 | ||||
-rw-r--r-- | src/test/test-child.c | 40 | ||||
-rw-r--r-- | src/test/test.c | 787 | ||||
-rw-r--r-- | src/test/test.h | 3 | ||||
-rw-r--r-- | src/test/test_addr.c | 4 | ||||
-rw-r--r-- | src/test/test_crypto.c | 24 | ||||
-rw-r--r-- | src/test/test_dir.c | 18 | ||||
-rw-r--r-- | src/test/test_microdesc.c | 233 | ||||
-rw-r--r-- | src/test/test_util.c | 474 | ||||
-rw-r--r-- | src/test/tinytest.c | 17 | ||||
-rw-r--r-- | src/test/tinytest_demo.c | 14 | ||||
-rw-r--r-- | src/test/tinytest_macros.h | 39 |
12 files changed, 1587 insertions, 83 deletions
diff --git a/src/test/Makefile.am b/src/test/Makefile.am index 904719d94b..852715079d 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -1,6 +1,6 @@ TESTS = test -noinst_PROGRAMS = test +noinst_PROGRAMS = test test-child AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ -DLOCALSTATEDIR="\"$(localstatedir)\"" \ @@ -12,12 +12,13 @@ AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \ # matters a lot there, and is quite hard to debug if you forget to do it. test_SOURCES = \ - test_data.c \ test.c \ test_addr.c \ + test_containers.c \ test_crypto.c \ + test_data.c \ test_dir.c \ - test_containers.c \ + test_microdesc.c \ test_util.c \ tinytest.c @@ -25,6 +26,12 @@ test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \ @TOR_LDFLAGS_libevent@ test_LDADD = ../or/libtor.a ../common/libor.a ../common/libor-crypto.a \ ../common/libor-event.a \ - @TOR_ZLIB_LIBS@ -lm @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ @TOR_LIB_WS32@ @TOR_LIB_GDI@ + @TOR_ZLIB_LIBS@ -lm @TOR_LIBEVENT_LIBS@ @TOR_OPENSSL_LIBS@ \ + @TOR_LIB_WS32@ @TOR_LIB_GDI@ + +noinst_HEADERS = \ + tinytest.h \ + tinytest_macros.h \ + test.h + -noinst_HEADERS = tinytest.h tinytest_macros.h test.h diff --git a/src/test/test-child.c b/src/test/test-child.c new file mode 100644 index 0000000000..1b9c5e3d57 --- /dev/null +++ b/src/test/test-child.c @@ -0,0 +1,40 @@ +#include <stdio.h> +#include "orconfig.h" +#ifdef MS_WINDOWS +#define WINDOWS_LEAN_AND_MEAN +#include <windows.h> +#else +#include <unistd.h> +#endif + +/** Trivial test program which prints out its command line arguments so we can + * check if tor_spawn_background() works */ +int +main(int argc, char **argv) +{ + int i; + + fprintf(stdout, "OUT\n"); + fprintf(stderr, "ERR\n"); + for (i = 1; i < argc; i++) + fprintf(stdout, "%s\n", argv[i]); + 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 MS_WINDOWS + Sleep(1000); +#else + sleep(1); +#endif + fprintf(stdout, "DONE\n"); +#ifdef MS_WINDOWS + Sleep(1000); +#else + sleep(1); +#endif + + return 0; +} + diff --git a/src/test/test.c b/src/test/test.c index b5b744eba7..04e7e02863 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -119,30 +119,46 @@ get_fname(const char *name) return buf; } -/** Remove all files stored under the temporary directory, and the directory - * itself. Called by atexit(). */ +/* Remove a directory and all of its subdirectories */ static void -remove_directory(void) +rm_rf(const char *dir) { + struct stat st; smartlist_t *elements; - if (getpid() != temp_dir_setup_in_pid) { - /* Only clean out the tempdir when the main process is exiting. */ - return; - } - elements = tor_listdir(temp_dir); + + elements = tor_listdir(dir); if (elements) { SMARTLIST_FOREACH(elements, const char *, cp, { - size_t len = strlen(cp)+strlen(temp_dir)+16; - char *tmp = tor_malloc(len); - tor_snprintf(tmp, len, "%s"PATH_SEPARATOR"%s", temp_dir, cp); - unlink(tmp); + char *tmp = NULL; + tor_asprintf(&tmp, "%s"PATH_SEPARATOR"%s", dir, cp); + if (0 == stat(tmp,&st) && (st.st_mode & S_IFDIR)) { + rm_rf(tmp); + } else { + if (unlink(tmp)) { + fprintf(stderr, "Error removing %s: %s\n", tmp, strerror(errno)); + } + } tor_free(tmp); }); SMARTLIST_FOREACH(elements, char *, cp, tor_free(cp)); smartlist_free(elements); } - rmdir(temp_dir); + if (rmdir(dir)) + fprintf(stderr, "Error removing directory %s: %s\n", dir, strerror(errno)); +} + +/** Remove all files stored under the temporary directory, and the directory + * itself. Called by atexit(). */ +static void +remove_directory(void) +{ + if (getpid() != temp_dir_setup_in_pid) { + /* Only clean out the tempdir when the main process is exiting. */ + return; + } + + rm_rf(temp_dir); } /** Define this if unit tests spend too much time generating public keys*/ @@ -187,6 +203,437 @@ free_pregenerated_keys(void) } } +typedef struct socks_test_data_t { + socks_request_t *req; + buf_t *buf; +} socks_test_data_t; + +static void * +socks_test_setup(const struct testcase_t *testcase) +{ + socks_test_data_t *data = tor_malloc(sizeof(socks_test_data_t)); + (void)testcase; + data->buf = buf_new_with_capacity(256); + data->req = socks_request_new(); + config_register_addressmaps(get_options()); + return data; +} +static int +socks_test_cleanup(const struct testcase_t *testcase, void *ptr) +{ + socks_test_data_t *data = ptr; + (void)testcase; + buf_free(data->buf); + socks_request_free(data->req); + tor_free(data); + return 1; +} + +const struct testcase_setup_t socks_setup = { + socks_test_setup, socks_test_cleanup +}; + +#define SOCKS_TEST_INIT() \ + socks_test_data_t *testdata = ptr; \ + buf_t *buf = testdata->buf; \ + socks_request_t *socks = testdata->req; +#define ADD_DATA(buf, s) \ + write_to_buf(s, sizeof(s)-1, buf) + +static void +socks_request_clear(socks_request_t *socks) +{ + tor_free(socks->username); + tor_free(socks->password); + memset(socks, 0, sizeof(socks_request_t)); +} + +/** Perform unsupported SOCKS 4 commands */ +static void +test_socks_4_unsupported_commands(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 4 Send BIND [02] to IP address 2.2.2.2:4369 */ + ADD_DATA(buf, "\x04\x02\x11\x11\x02\x02\x02\x02\x00"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == -1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + + done: + ; +} + +/** Perform supported SOCKS 4 commands */ +static void +test_socks_4_supported_commands(void *ptr) +{ + SOCKS_TEST_INIT(); + + test_eq(0, buf_datalen(buf)); + + /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4370 */ + ADD_DATA(buf, "\x04\x01\x11\x12\x02\x02\x02\x03\x00"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + test_eq(SOCKS_COMMAND_CONNECT, socks->command); + test_streq("2.2.2.3", socks->address); + test_eq(4370, socks->port); + test_assert(socks->got_auth == 0); + test_assert(! socks->username); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 4 Send CONNECT [01] to IP address 2.2.2.2:4369 with userid*/ + ADD_DATA(buf, "\x04\x01\x11\x12\x02\x02\x02\x04me\x00"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + test_eq(SOCKS_COMMAND_CONNECT, socks->command); + test_streq("2.2.2.4", socks->address); + test_eq(4370, socks->port); + test_assert(socks->got_auth == 1); + test_assert(socks->username); + test_eq(2, socks->usernamelen); + test_memeq("me", socks->username, 2); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 4a Send RESOLVE [F0] request for torproject.org */ + ADD_DATA(buf, "\x04\xF0\x01\x01\x00\x00\x00\x02me\x00torproject.org\x00"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(4, socks->socks_version); + test_eq(0, socks->replylen); /* XXX: shouldn't tor reply? */ + test_streq("torproject.org", socks->address); + + test_eq(0, buf_datalen(buf)); + + done: + ; +} + +/** Perform unsupported SOCKS 5 commands */ +static void +test_socks_5_unsupported_commands(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Send unsupported BIND [02] command */ + ADD_DATA(buf, "\x05\x02\x00\x01"); + + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 0); + test_eq(0, buf_datalen(buf)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + ADD_DATA(buf, "\x05\x02\x00\x01\x02\x02\x02\x01\x01\x01"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), -1); + /* XXX: shouldn't tor reply 'command not supported' [07]? */ + + buf_clear(buf); + socks_request_clear(socks); + + /* SOCKS 5 Send unsupported UDP_ASSOCIATE [03] command */ + ADD_DATA(buf, "\x05\x03\x00\x01\x02"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 0); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + ADD_DATA(buf, "\x05\x03\x00\x01\x02\x02\x02\x01\x01\x01"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), -1); + /* XXX: shouldn't tor reply 'command not supported' [07]? */ + + done: + ; +} + +/** Perform supported SOCKS 5 commands */ +static void +test_socks_5_supported_commands(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Send CONNECT [01] to IP address 2.2.2.2:4369 */ + ADD_DATA(buf, "\x05\x01\x00"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 0); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + + ADD_DATA(buf, "\x05\x01\x00\x01\x02\x02\x02\x02\x11\x11"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 1); + test_streq("2.2.2.2", socks->address); + test_eq(4369, socks->port); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 5 Send CONNECT [01] to FQDN torproject.org:4369 */ + ADD_DATA(buf, "\x05\x01\x00"); + ADD_DATA(buf, "\x05\x01\x00\x03\x0Etorproject.org\x11\x11"); + test_eq(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks), 1); + + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("torproject.org", socks->address); + test_eq(4369, socks->port); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 5 Send RESOLVE [F0] request for torproject.org:4369 */ + ADD_DATA(buf, "\x05\x01\x00"); + ADD_DATA(buf, "\x05\xF0\x00\x03\x0Etorproject.org\x01\x02"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("torproject.org", socks->address); + + test_eq(0, buf_datalen(buf)); + socks_request_clear(socks); + + /* SOCKS 5 Send RESOLVE_PTR [F1] for IP address 2.2.2.5 */ + ADD_DATA(buf, "\x05\x01\x00"); + ADD_DATA(buf, "\x05\xF1\x00\x01\x02\x02\x02\x05\x01\x03"); + test_assert(fetch_from_buf_socks(buf, socks, get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + test_streq("2.2.2.5", socks->address); + + test_eq(0, buf_datalen(buf)); + + done: + ; +} + +/** Perform SOCKS 5 authentication */ +static void +test_socks_5_no_authenticate(void *ptr) +{ + SOCKS_TEST_INIT(); + + /*SOCKS 5 No Authentication */ + ADD_DATA(buf,"\x05\x01\x00"); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(SOCKS_NO_AUTH, socks->reply[1]); + + test_eq(0, buf_datalen(buf)); + + /*SOCKS 5 Send username/password anyway - pretend to be broken */ + ADD_DATA(buf,"\x01\x02\x01\x01\x02\x01\x01"); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + + test_eq(2, socks->usernamelen); + test_eq(2, socks->passwordlen); + + test_memeq("\x01\x01", socks->username, 2); + test_memeq("\x01\x01", socks->password, 2); + + done: + ; +} + +/** Perform SOCKS 5 authentication */ +static void +test_socks_5_authenticate(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Negotiate username/password authentication */ + ADD_DATA(buf, "\x05\x01\x02"); + + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(SOCKS_USER_PASS, socks->reply[1]); + test_eq(5, socks->socks_version); + + test_eq(0, buf_datalen(buf)); + + /* SOCKS 5 Send username/password */ + ADD_DATA(buf, "\x01\x02me\x08mypasswd"); + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + + test_eq(2, socks->usernamelen); + test_eq(8, socks->passwordlen); + + test_memeq("me", socks->username, 2); + test_memeq("mypasswd", socks->password, 8); + + done: + ; +} + +/** Perform SOCKS 5 authentication and send data all in one go */ +static void +test_socks_5_authenticate_with_data(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Negotiate username/password authentication */ + ADD_DATA(buf, "\x05\x01\x02"); + + test_assert(!fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks)); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(SOCKS_USER_PASS, socks->reply[1]); + test_eq(5, socks->socks_version); + + test_eq(0, buf_datalen(buf)); + + /* SOCKS 5 Send username/password */ + /* SOCKS 5 Send CONNECT [01] to IP address 2.2.2.2:4369 */ + ADD_DATA(buf, "\x01\x02me\x03you\x05\x01\x00\x01\x02\x02\x02\x02\x11\x11"); + test_assert(fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks) == 1); + test_eq(5, socks->socks_version); + test_eq(2, socks->replylen); + test_eq(5, socks->reply[0]); + test_eq(0, socks->reply[1]); + + test_streq("2.2.2.2", socks->address); + test_eq(4369, socks->port); + + test_eq(2, socks->usernamelen); + test_eq(3, socks->passwordlen); + test_memeq("me", socks->username, 2); + test_memeq("you", socks->password, 3); + + done: + ; +} + +/** Perform SOCKS 5 authentication before method negotiated */ +static void +test_socks_5_auth_before_negotiation(void *ptr) +{ + SOCKS_TEST_INIT(); + + /* SOCKS 5 Send username/password */ + ADD_DATA(buf, "\x01\x02me\x02me"); + test_assert(fetch_from_buf_socks(buf, socks, + get_options()->TestSocks, + get_options()->SafeSocks) == -1); + test_eq(0, socks->socks_version); + test_eq(0, socks->replylen); + test_eq(0, socks->reply[0]); + test_eq(0, socks->reply[1]); + + done: + ; +} + +static void +test_buffer_copy(void *arg) +{ + generic_buffer_t *buf=NULL, *buf2=NULL; + const char *s; + size_t len; + char b[256]; + int i; + (void)arg; + + buf = generic_buffer_new(); + tt_assert(buf); + + /* Copy an empty buffer. */ + tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); + tt_assert(buf2); + tt_int_op(0, ==, generic_buffer_len(buf2)); + + /* Now try with a short buffer. */ + s = "And now comes an act of enormous enormance!"; + len = strlen(s); + generic_buffer_add(buf, s, len); + tt_int_op(len, ==, generic_buffer_len(buf)); + /* Add junk to buf2 so we can test replacing.*/ + generic_buffer_add(buf2, "BLARG", 5); + tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); + tt_int_op(len, ==, generic_buffer_len(buf2)); + generic_buffer_get(buf2, b, len); + test_mem_op(b, ==, s, len); + /* Now free buf2 and retry so we can test allocating */ + generic_buffer_free(buf2); + buf2 = NULL; + tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); + tt_int_op(len, ==, generic_buffer_len(buf2)); + generic_buffer_get(buf2, b, len); + test_mem_op(b, ==, s, len); + /* Clear buf for next test */ + generic_buffer_get(buf, b, len); + tt_int_op(generic_buffer_len(buf),==,0); + + /* Okay, now let's try a bigger buffer. */ + s = "Quis autem vel eum iure reprehenderit qui in ea voluptate velit " + "esse quam nihil molestiae consequatur, vel illum qui dolorem eum " + "fugiat quo voluptas nulla pariatur?"; + len = strlen(s); + for (i = 0; i < 256; ++i) { + b[0]=i; + generic_buffer_add(buf, b, 1); + generic_buffer_add(buf, s, len); + } + tt_int_op(0, ==, generic_buffer_set_to_copy(&buf2, buf)); + tt_int_op(generic_buffer_len(buf2), ==, generic_buffer_len(buf)); + for (i = 0; i < 256; ++i) { + generic_buffer_get(buf2, b, len+1); + tt_int_op((unsigned char)b[0],==,i); + test_mem_op(b+1, ==, s, len); + } + + done: + if (buf) + generic_buffer_free(buf); + if (buf2) + generic_buffer_free(buf2); +} + /** Run unit tests for buffers.c */ static void test_buffers(void) @@ -566,6 +1013,7 @@ test_policy_summary_helper(const char *policy_str, smartlist_t *policy = smartlist_create(); char *summary = NULL; int r; + short_policy_t *short_policy = NULL; line.key = (char*)"foo"; line.value = (char *)policy_str; @@ -578,10 +1026,14 @@ test_policy_summary_helper(const char *policy_str, test_assert(summary != NULL); test_streq(summary, expected_summary); + short_policy = parse_short_policy(summary); + tt_assert(short_policy); + done: tor_free(summary); if (policy) addr_policy_list_free(policy); + short_policy_free(short_policy); } /** Run unit tests for generating summary lines of exit policies */ @@ -611,12 +1063,15 @@ test_policies(void) smartlist_add(policy, p); + tor_addr_from_ipv4h(&tar, 0x01020304u); test_assert(ADDR_POLICY_ACCEPTED == - compare_addr_to_addr_policy(0x01020304u, 2, policy)); + compare_tor_addr_to_addr_policy(&tar, 2, policy)); + tor_addr_make_unspec(&tar); test_assert(ADDR_POLICY_PROBABLY_ACCEPTED == - compare_addr_to_addr_policy(0, 2, policy)); + compare_tor_addr_to_addr_policy(&tar, 2, policy)); + tor_addr_from_ipv4h(&tar, 0xc0a80102); test_assert(ADDR_POLICY_REJECTED == - compare_addr_to_addr_policy(0xc0a80102, 2, policy)); + compare_tor_addr_to_addr_policy(&tar, 2, policy)); test_assert(0 == policies_parse_exit_policy(NULL, &policy2, 1, NULL, 1)); test_assert(policy2); @@ -1026,8 +1481,73 @@ static void test_geoip(void) { int i, j; - time_t now = time(NULL); + time_t now = 1281533250; /* 2010-08-11 13:27:30 UTC */ char *s = NULL; + const char *bridge_stats_1 = + "bridge-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "bridge-ips zz=24,xy=8\n", + *dirreq_stats_1 = + "dirreq-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "dirreq-v3-ips ab=8\n" + "dirreq-v2-ips \n" + "dirreq-v3-reqs ab=8\n" + "dirreq-v2-reqs \n" + "dirreq-v3-resp ok=0,not-enough-sigs=0,unavailable=0,not-found=0," + "not-modified=0,busy=0\n" + "dirreq-v2-resp ok=0,unavailable=0,not-found=0,not-modified=0," + "busy=0\n" + "dirreq-v3-direct-dl complete=0,timeout=0,running=0\n" + "dirreq-v2-direct-dl complete=0,timeout=0,running=0\n" + "dirreq-v3-tunneled-dl complete=0,timeout=0,running=0\n" + "dirreq-v2-tunneled-dl complete=0,timeout=0,running=0\n", + *dirreq_stats_2 = + "dirreq-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "dirreq-v3-ips \n" + "dirreq-v2-ips \n" + "dirreq-v3-reqs \n" + "dirreq-v2-reqs \n" + "dirreq-v3-resp ok=0,not-enough-sigs=0,unavailable=0,not-found=0," + "not-modified=0,busy=0\n" + "dirreq-v2-resp ok=0,unavailable=0,not-found=0,not-modified=0," + "busy=0\n" + "dirreq-v3-direct-dl complete=0,timeout=0,running=0\n" + "dirreq-v2-direct-dl complete=0,timeout=0,running=0\n" + "dirreq-v3-tunneled-dl complete=0,timeout=0,running=0\n" + "dirreq-v2-tunneled-dl complete=0,timeout=0,running=0\n", + *dirreq_stats_3 = + "dirreq-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "dirreq-v3-ips \n" + "dirreq-v2-ips \n" + "dirreq-v3-reqs \n" + "dirreq-v2-reqs \n" + "dirreq-v3-resp ok=8,not-enough-sigs=0,unavailable=0,not-found=0," + "not-modified=0,busy=0\n" + "dirreq-v2-resp ok=0,unavailable=0,not-found=0,not-modified=0," + "busy=0\n" + "dirreq-v3-direct-dl complete=0,timeout=0,running=0\n" + "dirreq-v2-direct-dl complete=0,timeout=0,running=0\n" + "dirreq-v3-tunneled-dl complete=0,timeout=0,running=0\n" + "dirreq-v2-tunneled-dl complete=0,timeout=0,running=0\n", + *dirreq_stats_4 = + "dirreq-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "dirreq-v3-ips \n" + "dirreq-v2-ips \n" + "dirreq-v3-reqs \n" + "dirreq-v2-reqs \n" + "dirreq-v3-resp ok=8,not-enough-sigs=0,unavailable=0,not-found=0," + "not-modified=0,busy=0\n" + "dirreq-v2-resp ok=0,unavailable=0,not-found=0,not-modified=0," + "busy=0\n" + "dirreq-v3-direct-dl complete=0,timeout=0,running=0\n" + "dirreq-v2-direct-dl complete=0,timeout=0,running=0\n" + "dirreq-v3-tunneled-dl complete=0,timeout=0,running=4\n" + "dirreq-v2-tunneled-dl complete=0,timeout=0,running=0\n", + *entry_stats_1 = + "entry-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "entry-ips ab=8\n", + *entry_stats_2 = + "entry-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "entry-ips \n"; /* Populate the DB a bit. Add these in order, since we can't do the final * 'sort' step. These aren't very good IP addresses, but they're perfectly @@ -1053,8 +1573,8 @@ test_geoip(void) test_streq("??", NAMEFOR(2000)); #undef NAMEFOR - get_options()->BridgeRelay = 1; - get_options()->BridgeRecordUsageByCountry = 1; + get_options_mutable()->BridgeRelay = 1; + get_options_mutable()->BridgeRecordUsageByCountry = 1; /* Put 9 observations in AB... */ for (i=32; i < 40; ++i) geoip_note_client_seen(GEOIP_CLIENT_CONNECT, i, now-7200); @@ -1077,6 +1597,114 @@ test_geoip(void) test_assert(s); test_streq("zz=24,xy=8", s); + /* Start testing bridge statistics by making sure that we don't output + * bridge stats without initializing them. */ + s = geoip_format_bridge_stats(now + 86400); + test_assert(!s); + + /* Initialize stats and generate the bridge-stats history string out of + * the connecting clients added above. */ + geoip_bridge_stats_init(now); + s = geoip_format_bridge_stats(now + 86400); + test_streq(bridge_stats_1, s); + tor_free(s); + + /* Stop collecting bridge stats and make sure we don't write a history + * string anymore. */ + geoip_bridge_stats_term(); + s = geoip_format_bridge_stats(now + 86400); + test_assert(!s); + + /* Stop being a bridge and start being a directory mirror that gathers + * directory request statistics. */ + geoip_bridge_stats_term(); + get_options_mutable()->BridgeRelay = 0; + get_options_mutable()->BridgeRecordUsageByCountry = 0; + get_options_mutable()->DirReqStatistics = 1; + + /* Start testing dirreq statistics by making sure that we don't collect + * dirreq stats without initializing them. */ + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, 100, now); + s = geoip_format_dirreq_stats(now + 86400); + test_assert(!s); + + /* Initialize stats, note one connecting client, and generate the + * dirreq-stats history string. */ + geoip_dirreq_stats_init(now); + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, 100, now); + s = geoip_format_dirreq_stats(now + 86400); + test_streq(dirreq_stats_1, s); + tor_free(s); + + /* Stop collecting stats, add another connecting client, and ensure we + * don't generate a history string. */ + geoip_dirreq_stats_term(); + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, 101, now); + s = geoip_format_dirreq_stats(now + 86400); + test_assert(!s); + + /* Re-start stats, add a connecting client, reset stats, and make sure + * that we get an all empty history string. */ + geoip_dirreq_stats_init(now); + geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS, 100, now); + geoip_reset_dirreq_stats(now); + s = geoip_format_dirreq_stats(now + 86400); + test_streq(dirreq_stats_2, s); + tor_free(s); + + /* Note a successful network status response and make sure that it + * appears in the history string. */ + geoip_note_ns_response(GEOIP_CLIENT_NETWORKSTATUS, GEOIP_SUCCESS); + s = geoip_format_dirreq_stats(now + 86400); + test_streq(dirreq_stats_3, s); + tor_free(s); + + /* Start a tunneled directory request. */ + geoip_start_dirreq((uint64_t) 1, 1024, GEOIP_CLIENT_NETWORKSTATUS, + DIRREQ_TUNNELED); + s = geoip_format_dirreq_stats(now + 86400); + test_streq(dirreq_stats_4, s); + + /* Stop collecting directory request statistics and start gathering + * entry stats. */ + geoip_dirreq_stats_term(); + get_options_mutable()->DirReqStatistics = 0; + get_options_mutable()->EntryStatistics = 1; + + /* Start testing entry statistics by making sure that we don't collect + * anything without initializing entry stats. */ + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, 100, now); + s = geoip_format_entry_stats(now + 86400); + test_assert(!s); + + /* Initialize stats, note one connecting client, and generate the + * entry-stats history string. */ + geoip_entry_stats_init(now); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, 100, now); + s = geoip_format_entry_stats(now + 86400); + test_streq(entry_stats_1, s); + tor_free(s); + + /* Stop collecting stats, add another connecting client, and ensure we + * don't generate a history string. */ + geoip_entry_stats_term(); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, 101, now); + s = geoip_format_entry_stats(now + 86400); + test_assert(!s); + + /* Re-start stats, add a connecting client, reset stats, and make sure + * that we get an all empty history string. */ + geoip_entry_stats_init(now); + geoip_note_client_seen(GEOIP_CLIENT_CONNECT, 100, now); + geoip_reset_entry_stats(now); + s = geoip_format_entry_stats(now + 86400); + test_streq(entry_stats_2, s); + tor_free(s); + + /* Stop collecting entry statistics. */ + geoip_entry_stats_term(); + get_options_mutable()->EntryStatistics = 0; + done: tor_free(s); } @@ -1089,7 +1717,8 @@ test_stats(void) char *s = NULL; int i; - /* We shouldn't collect exit stats without initializing them. */ + /* Start with testing exit port statistics; we shouldn't collect exit + * stats without initializing them. */ rep_hist_note_exit_stream_opened(80); rep_hist_note_exit_bytes(80, 100, 10000); s = rep_hist_format_exit_stats(now + 86400); @@ -1134,7 +1763,7 @@ test_stats(void) test_assert(!s); /* Re-start stats, add some bytes, reset stats, and see what history we - * get when observing no streams or bytes at all. */ + * get when observing no streams or bytes at all. */ rep_hist_exit_stats_init(now); rep_hist_note_exit_stream_opened(80); rep_hist_note_exit_bytes(80, 100, 10000); @@ -1144,6 +1773,96 @@ test_stats(void) "exit-kibibytes-written other=0\n" "exit-kibibytes-read other=0\n" "exit-streams-opened other=0\n", s); + tor_free(s); + + /* Continue with testing connection statistics; we shouldn't collect + * conn stats without initializing them. */ + rep_hist_note_or_conn_bytes(1, 20, 400, now); + s = rep_hist_format_conn_stats(now + 86400); + test_assert(!s); + + /* Initialize stats, note bytes, and generate history string. */ + rep_hist_conn_stats_init(now); + rep_hist_note_or_conn_bytes(1, 30000, 400000, now); + rep_hist_note_or_conn_bytes(1, 30000, 400000, now + 5); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 10); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 15); + s = rep_hist_format_conn_stats(now + 86400); + test_streq("conn-bi-direct 2010-08-12 13:27:30 (86400 s) 0,0,1,0\n", s); + tor_free(s); + + /* Stop collecting stats, add some bytes, and ensure we don't generate + * a history string. */ + rep_hist_conn_stats_term(); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 15); + s = rep_hist_format_conn_stats(now + 86400); + test_assert(!s); + + /* Re-start stats, add some bytes, reset stats, and see what history we + * get when observing no bytes at all. */ + rep_hist_conn_stats_init(now); + rep_hist_note_or_conn_bytes(1, 30000, 400000, now); + rep_hist_note_or_conn_bytes(1, 30000, 400000, now + 5); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 10); + rep_hist_note_or_conn_bytes(2, 400000, 30000, now + 15); + rep_hist_reset_conn_stats(now); + s = rep_hist_format_conn_stats(now + 86400); + test_streq("conn-bi-direct 2010-08-12 13:27:30 (86400 s) 0,0,0,0\n", s); + tor_free(s); + + /* Continue with testing buffer statistics; we shouldn't collect buffer + * stats without initializing them. */ + rep_hist_add_buffer_stats(2.0, 2.0, 20); + s = rep_hist_format_buffer_stats(now + 86400); + test_assert(!s); + + /* Initialize stats, add statistics for a single circuit, and generate + * the history string. */ + rep_hist_buffer_stats_init(now); + rep_hist_add_buffer_stats(2.0, 2.0, 20); + s = rep_hist_format_buffer_stats(now + 86400); + test_streq("cell-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "cell-processed-cells 20,0,0,0,0,0,0,0,0,0\n" + "cell-queued-cells 2.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00," + "0.00,0.00\n" + "cell-time-in-queue 2,0,0,0,0,0,0,0,0,0\n" + "cell-circuits-per-decile 1\n", s); + tor_free(s); + + /* Add nineteen more circuit statistics to the one that's already in the + * history to see that the math works correctly. */ + for (i = 21; i < 30; i++) + rep_hist_add_buffer_stats(2.0, 2.0, i); + for (i = 20; i < 30; i++) + rep_hist_add_buffer_stats(3.5, 3.5, i); + s = rep_hist_format_buffer_stats(now + 86400); + test_streq("cell-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "cell-processed-cells 29,28,27,26,25,24,23,22,21,20\n" + "cell-queued-cells 2.75,2.75,2.75,2.75,2.75,2.75,2.75,2.75," + "2.75,2.75\n" + "cell-time-in-queue 3,3,3,3,3,3,3,3,3,3\n" + "cell-circuits-per-decile 2\n", s); + tor_free(s); + + /* Stop collecting stats, add statistics for one circuit, and ensure we + * don't generate a history string. */ + rep_hist_buffer_stats_term(); + rep_hist_add_buffer_stats(2.0, 2.0, 20); + s = rep_hist_format_buffer_stats(now + 86400); + test_assert(!s); + + /* Re-start stats, add statistics for one circuit, reset stats, and make + * sure that the history has all zeros. */ + rep_hist_buffer_stats_init(now); + rep_hist_add_buffer_stats(2.0, 2.0, 20); + rep_hist_reset_buffer_stats(now); + s = rep_hist_format_buffer_stats(now + 86400); + test_streq("cell-stats-end 2010-08-12 13:27:30 (86400 s)\n" + "cell-processed-cells 0,0,0,0,0,0,0,0,0,0\n" + "cell-queued-cells 0.00,0.00,0.00,0.00,0.00,0.00,0.00,0.00," + "0.00,0.00\n" + "cell-time-in-queue 0,0,0,0,0,0,0,0,0,0\n" + "cell-circuits-per-decile 0\n", s); done: tor_free(s); @@ -1186,6 +1905,7 @@ const struct testcase_setup_t legacy_setup = { static struct testcase_t test_array[] = { ENT(buffers), + { "buffer_copy", test_buffer_copy, 0, NULL, NULL }, ENT(onion_handshake), ENT(circuit_timeout), ENT(policies), @@ -1198,19 +1918,39 @@ static struct testcase_t test_array[] = { END_OF_TESTCASES }; +#define SOCKSENT(name) \ + { #name, test_socks_##name, TT_FORK, &socks_setup, NULL } + +static struct testcase_t socks_tests[] = { + SOCKSENT(4_unsupported_commands), + SOCKSENT(4_supported_commands), + + SOCKSENT(5_unsupported_commands), + SOCKSENT(5_supported_commands), + SOCKSENT(5_no_authenticate), + SOCKSENT(5_auth_before_negotiation), + SOCKSENT(5_authenticate), + SOCKSENT(5_authenticate_with_data), + + END_OF_TESTCASES +}; + extern struct testcase_t addr_tests[]; extern struct testcase_t crypto_tests[]; extern struct testcase_t container_tests[]; extern struct testcase_t util_tests[]; extern struct testcase_t dir_tests[]; +extern struct testcase_t microdesc_tests[]; static struct testgroup_t testgroups[] = { { "", test_array }, + { "socks/", socks_tests }, { "addr/", addr_tests }, { "crypto/", crypto_tests }, { "container/", container_tests }, { "util/", util_tests }, { "dir/", dir_tests }, + { "dir/md/", microdesc_tests }, END_OF_GROUPS }; @@ -1259,7 +1999,10 @@ main(int c, const char **v) } options->command = CMD_RUN_UNITTESTS; - crypto_global_init(0, NULL, NULL); + if (crypto_global_init(0, NULL, NULL)) { + printf("Can't initialize crypto subsystem; exiting.\n"); + return 1; + } rep_hist_init(); network_init(); setup_directory(); diff --git a/src/test/test.h b/src/test/test.h index f7ae46ce6d..a053a7ac43 100644 --- a/src/test/test.h +++ b/src/test/test.h @@ -45,7 +45,8 @@ _print = tor_malloc(printlen); \ base16_encode(_print, printlen, _value, \ (len)); }, \ - { tor_free(_print); } \ + { tor_free(_print); }, \ + TT_EXIT_TEST_FUNCTION \ ); #define test_memeq(expr1, expr2, len) test_mem_op((expr1), ==, (expr2), len) diff --git a/src/test/test_addr.c b/src/test/test_addr.c index 1dab0e0112..ec0c508978 100644 --- a/src/test/test_addr.c +++ b/src/test/test_addr.c @@ -74,7 +74,9 @@ test_addr_basic(void) cp += 2; \ if (i != 15) *cp++ = ':'; \ } \ - }, { tor_free(_print); } \ + }, \ + { tor_free(_print); }, \ + TT_EXIT_TEST_FUNCTION \ ); \ STMT_END diff --git a/src/test/test_crypto.c b/src/test/test_crypto.c index 121af279c7..dca0d3a28f 100644 --- a/src/test/test_crypto.c +++ b/src/test/test_crypto.c @@ -630,7 +630,7 @@ test_crypto_aes_iv(void) crypto_free_cipher_env(cipher); cipher = NULL; test_eq(encrypted_size, 16 + 4095); - tor_assert(encrypted_size > 0); /* This is obviously true, since 4111 is + tt_assert(encrypted_size > 0); /* This is obviously true, since 4111 is * greater than 0, but its truth is not * obvious to all analysis tools. */ cipher = crypto_create_init_cipher(key1, 0); @@ -639,7 +639,7 @@ test_crypto_aes_iv(void) crypto_free_cipher_env(cipher); cipher = NULL; test_eq(decrypted_size, 4095); - tor_assert(decrypted_size > 0); + tt_assert(decrypted_size > 0); test_memeq(plain, decrypted1, 4095); /* Encrypt a second time (with a new random initialization vector). */ cipher = crypto_create_init_cipher(key1, 1); @@ -648,14 +648,14 @@ test_crypto_aes_iv(void) crypto_free_cipher_env(cipher); cipher = NULL; test_eq(encrypted_size, 16 + 4095); - tor_assert(encrypted_size > 0); + tt_assert(encrypted_size > 0); cipher = crypto_create_init_cipher(key1, 0); decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted2, 4095, encrypted2, encrypted_size); crypto_free_cipher_env(cipher); cipher = NULL; test_eq(decrypted_size, 4095); - tor_assert(decrypted_size > 0); + tt_assert(decrypted_size > 0); test_memeq(plain, decrypted2, 4095); test_memneq(encrypted1, encrypted2, encrypted_size); /* Decrypt with the wrong key. */ @@ -680,14 +680,14 @@ test_crypto_aes_iv(void) crypto_free_cipher_env(cipher); cipher = NULL; test_eq(encrypted_size, 16 + 1); - tor_assert(encrypted_size > 0); + tt_assert(encrypted_size > 0); cipher = crypto_create_init_cipher(key1, 0); decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 1, encrypted1, encrypted_size); crypto_free_cipher_env(cipher); cipher = NULL; test_eq(decrypted_size, 1); - tor_assert(decrypted_size > 0); + tt_assert(decrypted_size > 0); test_memeq(plain_1, decrypted1, 1); /* Special length case: 15. */ cipher = crypto_create_init_cipher(key1, 1); @@ -696,14 +696,14 @@ test_crypto_aes_iv(void) crypto_free_cipher_env(cipher); cipher = NULL; test_eq(encrypted_size, 16 + 15); - tor_assert(encrypted_size > 0); + tt_assert(encrypted_size > 0); cipher = crypto_create_init_cipher(key1, 0); decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 15, encrypted1, encrypted_size); crypto_free_cipher_env(cipher); cipher = NULL; test_eq(decrypted_size, 15); - tor_assert(decrypted_size > 0); + tt_assert(decrypted_size > 0); test_memeq(plain_15, decrypted1, 15); /* Special length case: 16. */ cipher = crypto_create_init_cipher(key1, 1); @@ -712,14 +712,14 @@ test_crypto_aes_iv(void) crypto_free_cipher_env(cipher); cipher = NULL; test_eq(encrypted_size, 16 + 16); - tor_assert(encrypted_size > 0); + tt_assert(encrypted_size > 0); cipher = crypto_create_init_cipher(key1, 0); decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 16, encrypted1, encrypted_size); crypto_free_cipher_env(cipher); cipher = NULL; test_eq(decrypted_size, 16); - tor_assert(decrypted_size > 0); + tt_assert(decrypted_size > 0); test_memeq(plain_16, decrypted1, 16); /* Special length case: 17. */ cipher = crypto_create_init_cipher(key1, 1); @@ -728,12 +728,12 @@ test_crypto_aes_iv(void) crypto_free_cipher_env(cipher); cipher = NULL; test_eq(encrypted_size, 16 + 17); - tor_assert(encrypted_size > 0); + tt_assert(encrypted_size > 0); cipher = crypto_create_init_cipher(key1, 0); decrypted_size = crypto_cipher_decrypt_with_iv(cipher, decrypted1, 17, encrypted1, encrypted_size); test_eq(decrypted_size, 17); - tor_assert(decrypted_size > 0); + tt_assert(decrypted_size > 0); test_memeq(plain_17, decrypted1, 17); done: diff --git a/src/test/test_dir.c b/src/test/test_dir.c index 8fd94289a9..04ca29a9fb 100644 --- a/src/test/test_dir.c +++ b/src/test/test_dir.c @@ -298,7 +298,7 @@ test_dir_versions(void) #define tt_versionstatus_op(vs1, op, vs2) \ tt_assert_test_type(vs1,vs2,#vs1" "#op" "#vs2,version_status_t, \ - (_val1 op _val2),"%d") + (_val1 op _val2),"%d",TT_EXIT_TEST_FUNCTION) #define test_v_i_o(val, ver, lst) \ tt_versionstatus_op(val, ==, tor_version_is_obsolete(ver, lst)) @@ -803,7 +803,7 @@ test_dir_v3_networkstatus(void) rs->or_port = 443; rs->dir_port = 8000; /* all flags but running cleared */ - rs->is_running = 1; + rs->is_flagged_running = 1; smartlist_add(vote->routerstatus_list, vrs); test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); @@ -818,7 +818,7 @@ test_dir_v3_networkstatus(void) rs->addr = 0x99009901; rs->or_port = 443; rs->dir_port = 0; - rs->is_exit = rs->is_stable = rs->is_fast = rs->is_running = + rs->is_exit = rs->is_stable = rs->is_fast = rs->is_flagged_running = rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1; smartlist_add(vote->routerstatus_list, vrs); test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); @@ -835,7 +835,8 @@ test_dir_v3_networkstatus(void) rs->or_port = 400; rs->dir_port = 9999; rs->is_authority = rs->is_exit = rs->is_stable = rs->is_fast = - rs->is_running = rs->is_valid = rs->is_v2_dir = rs->is_possible_guard = 1; + rs->is_flagged_running = rs->is_valid = rs->is_v2_dir = + rs->is_possible_guard = 1; smartlist_add(vote->routerstatus_list, vrs); test_assert(router_add_to_routerlist(generate_ri_from_rs(vrs), &msg,0,0)>=0); @@ -1075,7 +1076,8 @@ test_dir_v3_networkstatus(void) test_assert(!rs->is_fast); test_assert(!rs->is_possible_guard); test_assert(!rs->is_stable); - test_assert(rs->is_running); /* If it wasn't running it wouldn't be here */ + /* (If it wasn't running it wouldn't be here) */ + test_assert(rs->is_flagged_running); test_assert(!rs->is_v2_dir); test_assert(!rs->is_valid); test_assert(!rs->is_named); @@ -1097,7 +1099,7 @@ test_dir_v3_networkstatus(void) test_assert(rs->is_fast); test_assert(rs->is_possible_guard); test_assert(rs->is_stable); - test_assert(rs->is_running); + test_assert(rs->is_flagged_running); test_assert(rs->is_v2_dir); test_assert(rs->is_valid); test_assert(!rs->is_named); @@ -1167,10 +1169,10 @@ test_dir_v3_networkstatus(void) /* Extract a detached signature from con3. */ detached_text1 = get_detached_sigs(con3, con_md3); - tor_assert(detached_text1); + tt_assert(detached_text1); /* Try to parse it. */ dsig1 = networkstatus_parse_detached_signatures(detached_text1, NULL); - tor_assert(dsig1); + tt_assert(dsig1); /* Are parsed values as expected? */ test_eq(dsig1->valid_after, con3->valid_after); diff --git a/src/test/test_microdesc.c b/src/test/test_microdesc.c new file mode 100644 index 0000000000..b807265c84 --- /dev/null +++ b/src/test/test_microdesc.c @@ -0,0 +1,233 @@ +/* Copyright (c) 2010-2011, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "or.h" + +#include "config.h" +#include "microdesc.h" + +#include "test.h" + +#ifdef MS_WINDOWS +/* For mkdir() */ +#include <direct.h> +#else +#include <dirent.h> +#endif + +static const char test_md1[] = + "onion-key\n" + "-----BEGIN RSA PUBLIC KEY-----\n" + "MIGJAoGBAMjlHH/daN43cSVRaHBwgUfnszzAhg98EvivJ9Qxfv51mvQUxPjQ07es\n" + "gV/3n8fyh3Kqr/ehi9jxkdgSRfSnmF7giaHL1SLZ29kA7KtST+pBvmTpDtHa3ykX\n" + "Xorc7hJvIyTZoc1HU+5XSynj3gsBE5IGK1ZRzrNS688LnuZMVp1tAgMBAAE=\n" + "-----END RSA PUBLIC KEY-----\n"; + +static const char test_md2[] = + "onion-key\n" + "-----BEGIN RSA PUBLIC KEY-----\n" + "MIGJAoGBAMIixIowh2DyPmDNMDwBX2DHcYcqdcH1zdIQJZkyV6c6rQHnvbcaDoSg\n" + "jgFSLJKpnGmh71FVRqep+yVB0zI1JY43kuEnXry2HbZCD9UDo3d3n7t015X5S7ON\n" + "bSSYtQGPwOr6Epf96IF6DoQxy4iDnPUAlejuhAG51s1y6/rZQ3zxAgMBAAE=\n" + "-----END RSA PUBLIC KEY-----\n"; + +static const char test_md3[] = + "@last-listed 2009-06-22\n" + "onion-key\n" + "-----BEGIN RSA PUBLIC KEY-----\n" + "MIGJAoGBAMH3340d4ENNGrqx7UxT+lB7x6DNUKOdPEOn4teceE11xlMyZ9TPv41c\n" + "qj2fRZzfxlc88G/tmiaHshmdtEpklZ740OFqaaJVj4LjPMKFNE+J7Xc1142BE9Ci\n" + "KgsbjGYe2RY261aADRWLetJ8T9QDMm+JngL4288hc8pq1uB/3TAbAgMBAAE=\n" + "-----END RSA PUBLIC KEY-----\n" + "p accept 1-700,800-1000\n" + "family nodeX nodeY nodeZ\n"; + +static void +test_md_cache(void *data) +{ + or_options_t *options = NULL; + microdesc_cache_t *mc = NULL ; + smartlist_t *added = NULL, *wanted = NULL; + microdesc_t *md1, *md2, *md3; + char d1[DIGEST256_LEN], d2[DIGEST256_LEN], d3[DIGEST256_LEN]; + const char *test_md3_noannotation = strchr(test_md3, '\n')+1; + time_t time1, time2, time3; + char *fn = NULL, *s = NULL; + (void)data; + + options = get_options_mutable(); + tt_assert(options); + + time1 = time(NULL); + time2 = time(NULL) - 2*24*60*60; + time3 = time(NULL) - 15*24*60*60; + + /* Possibly, turn this into a test setup/cleanup pair */ + tor_free(options->DataDirectory); + options->DataDirectory = tor_strdup(get_fname("md_datadir_test")); +#ifdef MS_WINDOWS + tt_int_op(0, ==, mkdir(options->DataDirectory)); +#else + tt_int_op(0, ==, mkdir(options->DataDirectory, 0700)); +#endif + + tt_assert(!strcmpstart(test_md3_noannotation, "onion-key")); + + crypto_digest256(d1, test_md1, strlen(test_md1), DIGEST_SHA256); + crypto_digest256(d2, test_md2, strlen(test_md1), DIGEST_SHA256); + crypto_digest256(d3, test_md3_noannotation, strlen(test_md3_noannotation), + DIGEST_SHA256); + + mc = get_microdesc_cache(); + + added = microdescs_add_to_cache(mc, test_md1, NULL, SAVED_NOWHERE, 0, + time1, NULL); + tt_int_op(1, ==, smartlist_len(added)); + md1 = smartlist_get(added, 0); + smartlist_free(added); + added = NULL; + + wanted = smartlist_create(); + added = microdescs_add_to_cache(mc, test_md2, NULL, SAVED_NOWHERE, 0, + time2, wanted); + /* Should fail, since we didn't list test_md2's digest in wanted */ + tt_int_op(0, ==, smartlist_len(added)); + smartlist_free(added); + added = NULL; + + smartlist_add(wanted, tor_memdup(d2, DIGEST256_LEN)); + smartlist_add(wanted, tor_memdup(d3, DIGEST256_LEN)); + added = microdescs_add_to_cache(mc, test_md2, NULL, SAVED_NOWHERE, 0, + time2, wanted); + /* Now it can work. md2 should have been added */ + tt_int_op(1, ==, smartlist_len(added)); + md2 = smartlist_get(added, 0); + /* And it should have gotten removed from 'wanted' */ + tt_int_op(smartlist_len(wanted), ==, 1); + test_mem_op(smartlist_get(wanted, 0), ==, d3, DIGEST256_LEN); + smartlist_free(added); + added = NULL; + + added = microdescs_add_to_cache(mc, test_md3, NULL, + SAVED_NOWHERE, 0, -1, NULL); + /* Must fail, since SAVED_NOWHERE precludes annotations */ + tt_int_op(0, ==, smartlist_len(added)); + smartlist_free(added); + added = NULL; + + added = microdescs_add_to_cache(mc, test_md3_noannotation, NULL, + SAVED_NOWHERE, 0, time3, NULL); + /* Now it can work */ + tt_int_op(1, ==, smartlist_len(added)); + md3 = smartlist_get(added, 0); + smartlist_free(added); + added = NULL; + + /* Okay. We added 1...3. Let's poke them to see how they look, and make + * sure they're really in the journal. */ + tt_ptr_op(md1, ==, microdesc_cache_lookup_by_digest256(mc, d1)); + tt_ptr_op(md2, ==, microdesc_cache_lookup_by_digest256(mc, d2)); + tt_ptr_op(md3, ==, microdesc_cache_lookup_by_digest256(mc, d3)); + + tt_int_op(md1->last_listed, ==, time1); + tt_int_op(md2->last_listed, ==, time2); + tt_int_op(md3->last_listed, ==, time3); + + tt_int_op(md1->saved_location, ==, SAVED_IN_JOURNAL); + tt_int_op(md2->saved_location, ==, SAVED_IN_JOURNAL); + tt_int_op(md3->saved_location, ==, SAVED_IN_JOURNAL); + + tt_int_op(md1->bodylen, ==, strlen(test_md1)); + tt_int_op(md2->bodylen, ==, strlen(test_md2)); + tt_int_op(md3->bodylen, ==, strlen(test_md3_noannotation)); + test_mem_op(md1->body, ==, test_md1, strlen(test_md1)); + test_mem_op(md2->body, ==, test_md2, strlen(test_md2)); + test_mem_op(md3->body, ==, test_md3_noannotation, + strlen(test_md3_noannotation)); + + tor_asprintf(&fn, "%s"PATH_SEPARATOR"cached-microdescs.new", + options->DataDirectory); + s = read_file_to_str(fn, RFTS_BIN, NULL); + tt_assert(s); + test_mem_op(md1->body, ==, s + md1->off, md1->bodylen); + test_mem_op(md2->body, ==, s + md2->off, md2->bodylen); + test_mem_op(md3->body, ==, s + md3->off, md3->bodylen); + + tt_ptr_op(md1->family, ==, NULL); + tt_ptr_op(md3->family, !=, NULL); + tt_int_op(smartlist_len(md3->family), ==, 3); + tt_str_op(smartlist_get(md3->family, 0), ==, "nodeX"); + + /* Now rebuild the cache! */ + tt_int_op(microdesc_cache_rebuild(mc, 1), ==, 0); + + tt_int_op(md1->saved_location, ==, SAVED_IN_CACHE); + tt_int_op(md2->saved_location, ==, SAVED_IN_CACHE); + tt_int_op(md3->saved_location, ==, SAVED_IN_CACHE); + + /* The journal should be empty now */ + tor_free(s); + s = read_file_to_str(fn, RFTS_BIN, NULL); + tt_str_op(s, ==, ""); + tor_free(s); + tor_free(fn); + + /* read the cache. */ + tor_asprintf(&fn, "%s"PATH_SEPARATOR"cached-microdescs", + options->DataDirectory); + s = read_file_to_str(fn, RFTS_BIN, NULL); + test_mem_op(md1->body, ==, s + md1->off, strlen(test_md1)); + test_mem_op(md2->body, ==, s + md2->off, strlen(test_md2)); + test_mem_op(md3->body, ==, s + md3->off, strlen(test_md3_noannotation)); + + /* Okay, now we are going to forget about the cache entirely, and reload it + * from the disk. */ + microdesc_free_all(); + mc = get_microdesc_cache(); + md1 = microdesc_cache_lookup_by_digest256(mc, d1); + md2 = microdesc_cache_lookup_by_digest256(mc, d2); + md3 = microdesc_cache_lookup_by_digest256(mc, d3); + test_assert(md1); + test_assert(md2); + test_assert(md3); + test_mem_op(md1->body, ==, s + md1->off, strlen(test_md1)); + test_mem_op(md2->body, ==, s + md2->off, strlen(test_md2)); + test_mem_op(md3->body, ==, s + md3->off, strlen(test_md3_noannotation)); + + tt_int_op(md1->last_listed, ==, time1); + tt_int_op(md2->last_listed, ==, time2); + tt_int_op(md3->last_listed, ==, time3); + + /* Okay, now we are going to clear out everything older than a week old. + * In practice, that means md3 */ + microdesc_cache_clean(mc, time(NULL)-7*24*60*60, 1/*force*/); + tt_ptr_op(md1, ==, microdesc_cache_lookup_by_digest256(mc, d1)); + tt_ptr_op(md2, ==, microdesc_cache_lookup_by_digest256(mc, d2)); + tt_ptr_op(NULL, ==, microdesc_cache_lookup_by_digest256(mc, d3)); + md3 = NULL; /* it's history now! */ + + /* rebuild again, make sure it stays gone. */ + microdesc_cache_rebuild(mc, 1); + tt_ptr_op(md1, ==, microdesc_cache_lookup_by_digest256(mc, d1)); + tt_ptr_op(md2, ==, microdesc_cache_lookup_by_digest256(mc, d2)); + tt_ptr_op(NULL, ==, microdesc_cache_lookup_by_digest256(mc, d3)); + + done: + if (options) + tor_free(options->DataDirectory); + microdesc_free_all(); + + smartlist_free(added); + if (wanted) + SMARTLIST_FOREACH(wanted, char *, cp, tor_free(cp)); + smartlist_free(wanted); + tor_free(s); + tor_free(fn); +} + +struct testcase_t microdesc_tests[] = { + { "cache", test_md_cache, TT_FORK, NULL, NULL }, + END_OF_TESTCASES +}; + diff --git a/src/test/test_util.c b/src/test/test_util.c index 23cd059cf7..f9672c100b 100644 --- a/src/test/test_util.c +++ b/src/test/test_util.c @@ -6,6 +6,7 @@ #include "orconfig.h" #define CONTROL_PRIVATE #define MEMPOOL_PRIVATE +#define UTIL_PRIVATE #include "or.h" #include "config.h" #include "control.h" @@ -375,7 +376,7 @@ test_util_strmisc(void) /* Test memmem and memstr */ { const char *haystack = "abcde"; - tor_assert(!tor_memmem(haystack, 5, "ef", 2)); + tt_assert(!tor_memmem(haystack, 5, "ef", 2)); test_eq_ptr(tor_memmem(haystack, 5, "cd", 2), haystack + 2); test_eq_ptr(tor_memmem(haystack, 5, "cde", 3), haystack + 2); haystack = "ababcad"; @@ -639,26 +640,26 @@ test_util_gzip(void) tor_strdup("String with low redundancy that won't be compressed much."); test_assert(!tor_gzip_compress(&buf2, &len1, buf1, strlen(buf1)+1, ZLIB_METHOD)); - tor_assert(len1>16); + tt_assert(len1>16); /* when we allow an incomplete string, we should succeed.*/ - tor_assert(!tor_gzip_uncompress(&buf3, &len2, buf2, len1-16, + tt_assert(!tor_gzip_uncompress(&buf3, &len2, buf2, len1-16, ZLIB_METHOD, 0, LOG_INFO)); buf3[len2]='\0'; - tor_assert(len2 > 5); - tor_assert(!strcmpstart(buf1, buf3)); + tt_assert(len2 > 5); + tt_assert(!strcmpstart(buf1, buf3)); /* when we demand a complete string, this must fail. */ tor_free(buf3); - tor_assert(tor_gzip_uncompress(&buf3, &len2, buf2, len1-16, + tt_assert(tor_gzip_uncompress(&buf3, &len2, buf2, len1-16, ZLIB_METHOD, 1, LOG_INFO)); - tor_assert(!buf3); + tt_assert(!buf3); /* Now, try streaming compression. */ tor_free(buf1); tor_free(buf2); tor_free(buf3); state = tor_zlib_new(1, ZLIB_METHOD); - tor_assert(state); + tt_assert(state); cp1 = buf1 = tor_malloc(1024); len1 = 1024; ccp2 = "ABCDEFGHIJABCDEFGHIJ"; @@ -675,7 +676,7 @@ test_util_gzip(void) test_eq(len2, 0); test_assert(cp1 > cp2); /* Make sure we really added something. */ - tor_assert(!tor_gzip_uncompress(&buf3, &len2, buf1, 1024-len1, + tt_assert(!tor_gzip_uncompress(&buf3, &len2, buf1, 1024-len1, ZLIB_METHOD, 1, LOG_WARN)); test_streq(buf3, "ABCDEFGHIJABCDEFGHIJ"); /*Make sure it compressed right.*/ @@ -832,6 +833,18 @@ test_util_sscanf(void) test_eq(u2, 3u); test_eq(u3, 99u); + /* %x should work. */ + r = tor_sscanf("1234 02aBcdEf", "%x %x", &u1, &u2); + test_eq(r, 2); + test_eq(u1, 0x1234); + test_eq(u2, 0x2ABCDEF); + /* Width works on %x */ + r = tor_sscanf("f00dcafe444", "%4x%4x%u", &u1, &u2, &u3); + test_eq(r, 3); + test_eq(u1, 0xf00d); + test_eq(u2, 0xcafe); + test_eq(u3, 444); + r = tor_sscanf("99% fresh", "%3u%% fresh", &u1); /* percents are scannable.*/ test_eq(r, 1); test_eq(u1, 99); @@ -1239,6 +1252,440 @@ test_util_load_win_lib(void *ptr) #endif static void +clear_hex_errno(char *hex_errno) +{ + memset(hex_errno, '\0', HEX_ERRNO_SIZE + 1); +} + +static void +test_util_exit_status(void *ptr) +{ + /* Leave an extra byte for a \0 so we can do string comparison */ + char hex_errno[HEX_ERRNO_SIZE + 1]; + + (void)ptr; + + clear_hex_errno(hex_errno); + format_helper_exit_status(0, 0, hex_errno); + tt_str_op(hex_errno, ==, " 0/0\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0, 0x7FFFFFFF, hex_errno); + tt_str_op(hex_errno, ==, " 0/7FFFFFFF\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0xFF, -0x80000000, hex_errno); + tt_str_op(hex_errno, ==, "FF/-80000000\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0x7F, 0, hex_errno); + tt_str_op(hex_errno, ==, " 7F/0\n"); + + clear_hex_errno(hex_errno); + format_helper_exit_status(0x08, -0x242, hex_errno); + tt_str_op(hex_errno, ==, " 8/-242\n"); + + done: + ; +} + +#ifndef MS_WINDOWS +/** Check that fgets waits until a full line, and not return a partial line, on + * a EAGAIN with a non-blocking pipe */ +static void +test_util_fgets_eagain(void *ptr) +{ + int test_pipe[2] = {-1, -1}; + int retval; + ssize_t retlen; + char *retptr; + FILE *test_stream = NULL; + char buf[10]; + + (void)ptr; + + /* Set up a pipe to test on */ + retval = pipe(test_pipe); + tt_int_op(retval, >=, 0); + + /* Set up the read-end to be non-blocking */ + retval = fcntl(test_pipe[0], F_SETFL, O_NONBLOCK); + tt_int_op(retval, >=, 0); + + /* Open it as a stdio stream */ + test_stream = fdopen(test_pipe[0], "r"); + tt_ptr_op(test_stream, !=, NULL); + + /* Send in a partial line */ + retlen = write(test_pipe[1], "A", 1); + tt_int_op(retlen, ==, 1); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_want(retptr == NULL); + tt_int_op(errno, ==, EAGAIN); + + /* Send in the rest */ + retlen = write(test_pipe[1], "B\n", 2); + tt_int_op(retlen, ==, 2); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, buf); + tt_str_op(buf, ==, "AB\n"); + + /* Send in a full line */ + retlen = write(test_pipe[1], "CD\n", 3); + tt_int_op(retlen, ==, 3); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, buf); + tt_str_op(buf, ==, "CD\n"); + + /* Send in a partial line */ + retlen = write(test_pipe[1], "E", 1); + tt_int_op(retlen, ==, 1); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, NULL); + tt_int_op(errno, ==, EAGAIN); + + /* Send in the rest */ + retlen = write(test_pipe[1], "F\n", 2); + tt_int_op(retlen, ==, 2); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, buf); + tt_str_op(buf, ==, "EF\n"); + + /* Send in a full line and close */ + retlen = write(test_pipe[1], "GH", 2); + tt_int_op(retlen, ==, 2); + retval = close(test_pipe[1]); + test_pipe[1] = -1; + tt_int_op(retval, ==, 0); + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, buf); + tt_str_op(buf, ==, "GH"); + + /* Check for EOF */ + retptr = fgets(buf, sizeof(buf), test_stream); + tt_ptr_op(retptr, ==, NULL); + tt_int_op(feof(test_stream), >, 0); + + done: + if (test_stream != NULL) + fclose(test_stream); + if (test_pipe[0] != -1) + close(test_pipe[0]); + if (test_pipe[1] != -1) + close(test_pipe[1]); +} +#endif + +/** Helper function for testing tor_spawn_background */ +static void +run_util_spawn_background(const char *argv[], const char *expected_out, + const char *expected_err, int expected_exit, + int expected_status) +{ + int retval, exit_code; + ssize_t pos; + process_handle_t process_handle; + char stdout_buf[100], stderr_buf[100]; + + /* Start the program */ +#ifdef MS_WINDOWS + tor_spawn_background(NULL, argv, &process_handle); +#else + tor_spawn_background(argv[0], argv, &process_handle); +#endif + + tt_int_op(process_handle.status, ==, expected_status); + + /* If the process failed to start, don't bother continuing */ + if (process_handle.status == PROCESS_STATUS_ERROR) + return; + + tt_int_op(process_handle.stdout_pipe, >, 0); + tt_int_op(process_handle.stderr_pipe, >, 0); + + /* Check stdout */ + pos = tor_read_all_from_process_stdout(&process_handle, stdout_buf, + sizeof(stdout_buf) - 1); + tt_assert(pos >= 0); + stdout_buf[pos] = '\0'; + tt_str_op(stdout_buf, ==, expected_out); + tt_int_op(pos, ==, strlen(expected_out)); + + /* Check it terminated correctly */ + retval = tor_get_exit_code(process_handle, 1, &exit_code); + tt_int_op(retval, ==, PROCESS_EXIT_EXITED); + tt_int_op(exit_code, ==, expected_exit); + // TODO: Make test-child exit with something other than 0 + + /* Check stderr */ + pos = tor_read_all_from_process_stderr(&process_handle, stderr_buf, + sizeof(stderr_buf) - 1); + tt_assert(pos >= 0); + stderr_buf[pos] = '\0'; + tt_str_op(stderr_buf, ==, expected_err); + tt_int_op(pos, ==, strlen(expected_err)); + + done: + ; +} + +/** Check that we can launch a process and read the output */ +static void +test_util_spawn_background_ok(void *ptr) +{ +#ifdef MS_WINDOWS + const char *argv[] = {"test-child.exe", "--test", NULL}; + const char *expected_out = "OUT\r\n--test\r\nSLEEPING\r\nDONE\r\n"; + const char *expected_err = "ERR\r\n"; +#else + const char *argv[] = {BUILDDIR "/src/test/test-child", "--test", NULL}; + const char *expected_out = "OUT\n--test\nSLEEPING\nDONE\n"; + const char *expected_err = "ERR\n"; +#endif + + (void)ptr; + + run_util_spawn_background(argv, expected_out, expected_err, 0, + PROCESS_STATUS_RUNNING); +} + +/** Check that failing to find the executable works as expected */ +static void +test_util_spawn_background_fail(void *ptr) +{ +#ifdef MS_WINDOWS + const char *argv[] = {BUILDDIR "/src/test/no-such-file", "--test", NULL}; + const char *expected_out = "ERR: Failed to spawn background process " + "- code 9/2\n"; + const char *expected_err = ""; + const int expected_status = PROCESS_STATUS_ERROR; +#else + const char *argv[] = {BUILDDIR "/src/test/no-such-file", "--test", NULL}; + const char *expected_out = "ERR: Failed to spawn background process " + "- code 9/2\n"; + const char *expected_err = ""; + /* TODO: Once we can signal failure to exec, set this to be + * PROCESS_STATUS_ERROR */ + const int expected_status = PROCESS_STATUS_RUNNING; +#endif + + (void)ptr; + + run_util_spawn_background(argv, expected_out, expected_err, 255, + 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) +{ + const int expected_exit = 0; + const int expected_status = PROCESS_STATUS_RUNNING; + + int retval, exit_code; + ssize_t pos = -1; + process_handle_t process_handle; + char stdout_buf[100], stderr_buf[100]; +#ifdef MS_WINDOWS + const char *argv[] = {"test-child.exe", "--test", NULL}; + const char *expected_out[] = { "OUT\r\n--test\r\nSLEEPING\r\n", + "DONE\r\n", + NULL }; + const char *expected_err = "ERR\r\n"; +#else + const char *argv[] = {BUILDDIR "/src/test/test-child", "--test", NULL}; + const char *expected_out[] = { "OUT\n--test\nSLEEPING\n", + "DONE\n", + NULL }; + const char *expected_err = "ERR\n"; + int eof = 0; +#endif + int expected_out_ctr; + (void)ptr; + + /* Start the program */ +#ifdef MS_WINDOWS + tor_spawn_background(NULL, argv, &process_handle); +#else + tor_spawn_background(argv[0], argv, &process_handle); +#endif + tt_int_op(process_handle.status, ==, expected_status); + + /* Check stdout */ + for (expected_out_ctr =0; expected_out[expected_out_ctr] != NULL;) { +#ifdef MS_WINDOWS + pos = tor_read_all_handle(process_handle.stdout_pipe, stdout_buf, + sizeof(stdout_buf) - 1, NULL); +#else + /* Check that we didn't read the end of file last time */ + tt_assert(!eof); + pos = tor_read_all_handle(process_handle.stdout_handle, stdout_buf, + sizeof(stdout_buf) - 1, NULL, &eof); +#endif + log_info(LD_GENERAL, "tor_read_all_handle() returned %d", (int)pos); + + /* We would have blocked, keep on trying */ + if (0 == pos) + continue; + + tt_int_op(pos, >, 0); + stdout_buf[pos] = '\0'; + tt_str_op(stdout_buf, ==, expected_out[expected_out_ctr]); + tt_int_op(pos, ==, strlen(expected_out[expected_out_ctr])); + expected_out_ctr++; + } + + /* The process should have exited without writing more */ +#ifdef MS_WINDOWS + pos = tor_read_all_handle(process_handle.stdout_pipe, stdout_buf, + sizeof(stdout_buf) - 1, + &process_handle); + tt_int_op(pos, ==, 0); +#else + if (!eof) { + /* We should have got all the data, but maybe not the EOF flag */ + pos = tor_read_all_handle(process_handle.stdout_handle, stdout_buf, + sizeof(stdout_buf) - 1, + &process_handle, &eof); + tt_int_op(pos, ==, 0); + tt_assert(eof); + } + /* Otherwise, we got the EOF on the last read */ +#endif + + /* Check it terminated correctly */ + retval = tor_get_exit_code(process_handle, 1, &exit_code); + tt_int_op(retval, ==, PROCESS_EXIT_EXITED); + tt_int_op(exit_code, ==, expected_exit); + + // TODO: Make test-child exit with something other than 0 + + /* Check stderr */ + pos = tor_read_all_from_process_stderr(&process_handle, stderr_buf, + sizeof(stderr_buf) - 1); + tt_assert(pos >= 0); + stderr_buf[pos] = '\0'; + tt_str_op(stderr_buf, ==, expected_err); + tt_int_op(pos, ==, strlen(expected_err)); + + done: + ; +} + +/** + * Test that we can properly format q Windows command line + */ +static void +test_util_join_win_cmdline(void *ptr) +{ + /* 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 + {} // 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; + char *joined_argv; + + (void)ptr; + + 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(joined_argv, ==, cmdlines[i]); + tor_free(joined_argv); + } + + done: + ; +} + +#define MAX_SPLIT_LINE_COUNT 3 +struct split_lines_test_t { + const char *orig_line; // Line to be split (may contain \0's) + int orig_length; // Length of orig_line + const char *split_line[MAX_SPLIT_LINE_COUNT]; // Split lines +}; + +/** + * Test that we properly split a buffer into lines + */ +static void +test_util_split_lines(void *ptr) +{ + /* Test cases. orig_line of last test case must be NULL. + * The last element of split_line[i] must be NULL. */ + struct split_lines_test_t tests[] = { + {"", 0, {NULL}}, + {"foo", 3, {"foo", NULL}}, + {"\n\rfoo\n\rbar\r\n", 12, {"foo", "bar", NULL}}, + {"fo o\r\nb\tar", 10, {"fo o", "b.ar", NULL}}, + {"\x0f""f\0o\0\n\x01""b\0r\0\r", 12, {".f.o.", ".b.r.", NULL}}, + {NULL, 0, {}} + }; + + int i, j; + char *orig_line; + smartlist_t *sl; + + (void)ptr; + + for (i=0; tests[i].orig_line; i++) { + sl = smartlist_create(); + /* Allocate space for string and trailing NULL */ + orig_line = tor_memdup(tests[i].orig_line, tests[i].orig_length + 1); + tor_split_lines(sl, orig_line, tests[i].orig_length); + + j = 0; + log_info(LD_GENERAL, "Splitting test %d of length %d", + i, tests[i].orig_length); + SMARTLIST_FOREACH(sl, const char *, line, + { + /* Check we have not got too many lines */ + tt_int_op(j, <, MAX_SPLIT_LINE_COUNT); + /* Check that there actually should be a line here */ + tt_assert(tests[i].split_line[j] != NULL); + log_info(LD_GENERAL, "Line %d of test %d, should be <%s>", + j, i, tests[i].split_line[j]); + /* Check that the line is as expected */ + tt_str_op(tests[i].split_line[j], ==, line); + j++; + }); + /* Check that we didn't miss some lines */ + tt_assert(tests[i].split_line[j] == NULL); + tor_free(orig_line); + smartlist_free(sl); + } + + done: + ; +} + +static void test_util_di_ops(void) { #define LT -1 @@ -1319,6 +1766,15 @@ struct testcase_t util_tests[] = { #ifdef MS_WINDOWS UTIL_TEST(load_win_lib, 0), #endif + UTIL_TEST(exit_status, 0), +#ifndef MS_WINDOWS + UTIL_TEST(fgets_eagain, TT_SKIP), +#endif + UTIL_TEST(spawn_background_ok, 0), + UTIL_TEST(spawn_background_fail, 0), + UTIL_TEST(spawn_background_partial_read, 0), + UTIL_TEST(join_win_cmdline, 0), + UTIL_TEST(split_lines, 0), END_OF_TESTCASES }; diff --git a/src/test/tinytest.c b/src/test/tinytest.c index 11ffc2fe56..8caa4f5453 100644 --- a/src/test/tinytest.c +++ b/src/test/tinytest.c @@ -28,7 +28,11 @@ #include <string.h> #include <assert.h> -#ifdef WIN32 +#ifdef TINYTEST_LOCAL +#include "tinytest_local.h" +#endif + +#ifdef _WIN32 #include <windows.h> #else #include <sys/types.h> @@ -40,9 +44,6 @@ #define __attribute__(x) #endif -#ifdef TINYTEST_LOCAL -#include "tinytest_local.h" -#endif #include "tinytest.h" #include "tinytest_macros.h" @@ -64,7 +65,7 @@ const char *cur_test_prefix = NULL; /**< prefix of the current test group */ /** Name of the current test, if we haven't logged is yet. Used for --quiet */ const char *cur_test_name = NULL; -#ifdef WIN32 +#ifdef _WIN32 /** Pointer to argv[0] for win32. */ static const char *commandname = NULL; #endif @@ -103,7 +104,7 @@ static enum outcome _testcase_run_forked(const struct testgroup_t *group, const struct testcase_t *testcase) { -#ifdef WIN32 +#ifdef _WIN32 /* Fork? On Win32? How primitive! We'll do what the smart kids do: we'll invoke our own exe (whose name we recall from the command line) with a command line that tells it to run just the test we @@ -174,6 +175,7 @@ _testcase_run_forked(const struct testgroup_t *group, exit(1); } exit(0); + return FAIL; /* unreachable */ } else { /* parent */ int status, r; @@ -239,6 +241,7 @@ testcase_run_one(const struct testgroup_t *group, if (opt_forked) { exit(outcome==OK ? 0 : (outcome==SKIP?MAGIC_EXITCODE : 1)); + return 1; /* unreachable */ } else { return (int)outcome; } @@ -287,7 +290,7 @@ tinytest_main(int c, const char **v, struct testgroup_t *groups) { int i, j, n=0; -#ifdef WIN32 +#ifdef _WIN32 commandname = v[0]; #endif for (i=1; i<c; ++i) { diff --git a/src/test/tinytest_demo.c b/src/test/tinytest_demo.c index 4d2f588435..98cb773d1a 100644 --- a/src/test/tinytest_demo.c +++ b/src/test/tinytest_demo.c @@ -1,4 +1,4 @@ -/* tinytest_demo.c -- Copyright 2009 Nick Mathewson +/* tinytest_demo.c -- Copyright 2009-2010 Nick Mathewson * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -53,7 +53,7 @@ test_strcmp(void *data) } /* Pretty often, calling tt_abort_msg to indicate failure is more - heavy-weight than you want. Instead, just say: */ + heavy-weight than you want. Instead, just say: */ tt_assert(strcmp("testcase", "testcase") == 0); /* Occasionally, you don't want to stop the current testcase just @@ -91,7 +91,7 @@ test_strcmp(void *data) /* First you declare a type to hold the environment info, and functions to set it up and tear it down. */ struct data_buffer { - /* We're just going to have couple of character buffer. Using + /* We're just going to have couple of character buffer. Using setup/teardown functions is probably overkill for this case. You could also do file descriptors, complicated handles, temporary @@ -164,7 +164,7 @@ test_memcpy(void *ptr) /* ============================================================ */ -/* Now we need to make sure that our tests get invoked. First, you take +/* Now we need to make sure that our tests get invoked. First, you take a bunch of related tests and put them into an array of struct testcase_t. */ @@ -189,15 +189,15 @@ struct testgroup_t groups[] = { /* Every group has a 'prefix', and an array of tests. That's it. */ { "demo/", demo_tests }, - END_OF_GROUPS + END_OF_GROUPS }; int main(int c, const char **v) { - /* Finally, just call tinytest_main(). It lets you specify verbose - or quiet output with --verbose and --quiet. You can list + /* Finally, just call tinytest_main(). It lets you specify verbose + or quiet output with --verbose and --quiet. You can list specific tests: tinytest-demo demo/memcpy diff --git a/src/test/tinytest_macros.h b/src/test/tinytest_macros.h index a7fa64a824..032393ccf7 100644 --- a/src/test/tinytest_macros.h +++ b/src/test/tinytest_macros.h @@ -90,10 +90,10 @@ TT_STMT_BEGIN \ if (!(b)) { \ _tinytest_set_test_failed(); \ - TT_GRIPE((msg)); \ + TT_GRIPE(("%s",msg)); \ fail; \ } else { \ - TT_BLATHER((msg)); \ + TT_BLATHER(("%s",msg)); \ } \ TT_STMT_END @@ -111,7 +111,7 @@ #define tt_assert(b) tt_assert_msg((b), "assert("#b")") #define tt_assert_test_fmt_type(a,b,str_test,type,test,printf_type,printf_fmt, \ - setup_block,cleanup_block) \ + setup_block,cleanup_block,die_on_fail) \ TT_STMT_BEGIN \ type _val1 = (type)(a); \ type _val2 = (type)(b); \ @@ -135,33 +135,50 @@ cleanup_block; \ if (!_tt_status) { \ _tinytest_set_test_failed(); \ - TT_EXIT_TEST_FUNCTION; \ + die_on_fail ; \ } \ } \ TT_STMT_END -#define tt_assert_test_type(a,b,str_test,type,test,fmt) \ +#define tt_assert_test_type(a,b,str_test,type,test,fmt,die_on_fail) \ tt_assert_test_fmt_type(a,b,str_test,type,test,type,fmt, \ - {_print=_value;},{}) + {_print=_value;},{},die_on_fail) /* Helper: assert that a op b, when cast to type. Format the values with * printf format fmt on failure. */ #define tt_assert_op_type(a,op,b,type,fmt) \ - tt_assert_test_type(a,b,#a" "#op" "#b,type,(_val1 op _val2),fmt) + tt_assert_test_type(a,b,#a" "#op" "#b,type,(_val1 op _val2),fmt, \ + TT_EXIT_TEST_FUNCTION) #define tt_int_op(a,op,b) \ - tt_assert_test_type(a,b,#a" "#op" "#b,long,(_val1 op _val2),"%ld") + tt_assert_test_type(a,b,#a" "#op" "#b,long,(_val1 op _val2), \ + "%ld",TT_EXIT_TEST_FUNCTION) #define tt_uint_op(a,op,b) \ tt_assert_test_type(a,b,#a" "#op" "#b,unsigned long, \ - (_val1 op _val2),"%lu") + (_val1 op _val2),"%lu",TT_EXIT_TEST_FUNCTION) #define tt_ptr_op(a,op,b) \ tt_assert_test_type(a,b,#a" "#op" "#b,void*, \ - (_val1 op _val2),"%p") + (_val1 op _val2),"%p",TT_EXIT_TEST_FUNCTION) #define tt_str_op(a,op,b) \ tt_assert_test_type(a,b,#a" "#op" "#b,const char *, \ - (strcmp(_val1,_val2) op 0),"<%s>") + (strcmp(_val1,_val2) op 0),"<%s>",TT_EXIT_TEST_FUNCTION) + +#define tt_want_int_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,long,(_val1 op _val2),"%ld",(void)0) + +#define tt_want_uint_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,unsigned long, \ + (_val1 op _val2),"%lu",(void)0) + +#define tt_want_ptr_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,void*, \ + (_val1 op _val2),"%p",(void)0) + +#define tt_want_str_op(a,op,b) \ + tt_assert_test_type(a,b,#a" "#op" "#b,const char *, \ + (strcmp(_val1,_val2) op 0),"<%s>",(void)0) #endif |