terom@40: /** terom@40: * The main test code entry point terom@40: */ terom@40: #include "sock_test.h" terom@41: #include "line_proto.h" terom@90: #include "irc_queue.h" terom@40: #include "irc_conn.h" terom@44: #include "irc_net.h" terom@40: #include "log.h" terom@125: #include "str.h" terom@41: #include "error.h" terom@40: terom@41: #include terom@40: #include terom@73: #include terom@40: #include terom@50: #include terom@50: terom@50: #define DUMP_STR_BUF 1024 terom@50: #define DUMP_STR_COUNT 8 terom@50: #define DUMP_STR_TAIL 10 terom@50: terom@90: /** terom@90: * Global test-running state terom@90: */ terom@90: struct test_ctx { terom@90: /** The event_base that we have setup */ terom@90: struct event_base *ev_base; terom@90: terom@90: } _test_ctx; terom@90: terom@50: terom@50: /** terom@50: * This re-formats the given string to escape values, and returns a pointer to an internal static buffer. terom@50: * terom@52: * If len is given as >= 0, only the given number of chars will be dumped from str. terom@50: * terom@52: * The buffer cycles a bit, so the returned pointers remain valid across DUMP_STR_COUNT calls. terom@51: * terom@52: * The resulting string is truncated to (DUMP_STR_BUF - DUMP_STR_TAIL) bytes, not including the ending "...'\0". terom@52: * terom@52: * @param str the string to dump, should be NUL-terminated unless len is given terom@52: * @param len if negative, ignored, otherwise, only this many bytes are dumped from str terom@52: * @param return a pointer to a static buffer that remains valid across DUMP_STR_COUNT calls to this function terom@50: */ terom@50: const char *dump_strn (const char *str, ssize_t len) terom@50: { terom@50: static char dump_buf[DUMP_STR_COUNT][DUMP_STR_BUF]; terom@50: static size_t dump_idx = 0; terom@50: terom@50: // pick a buffer to use terom@125: char *buf = dump_buf[dump_idx++]; terom@50: terom@50: // cycle terom@50: if (dump_idx >= DUMP_STR_COUNT) terom@50: dump_idx = 0; terom@50: terom@125: str_quote(buf, DUMP_STR_BUF, str, len); terom@50: terom@50: // ok terom@125: return buf; terom@50: } terom@50: terom@50: const char *dump_str (const char *str) terom@50: { terom@50: return dump_strn(str, -1); terom@50: } terom@40: terom@75: void assert_null (const void *ptr) terom@75: { terom@75: if (ptr) terom@75: FATAL("%p != NULL", ptr); terom@75: } terom@75: terom@42: void assert_strcmp (const char *is, const char *should_be) terom@41: { terom@52: if (!is || strcmp(is, should_be)) terom@50: FATAL("%s != %s", dump_str(is), dump_str(should_be)); terom@41: } terom@41: terom@42: void assert_strncmp (const char *is, const char *should_be, size_t n) terom@50: { terom@52: if (!is || strncmp(is, should_be, n)) terom@50: FATAL("%s:%u != %s", dump_strn(is, n), (unsigned) n, dump_strn(should_be, n)); terom@41: } terom@41: terom@41: void assert_strlen (const char *str, size_t n) terom@41: { terom@52: if (!str || strlen(str) != n) terom@50: FATAL("strlen(%s) != %u", dump_str(str), (unsigned) n); terom@41: } terom@41: terom@42: void assert_strnull (const char *str) terom@41: { terom@41: if (str != NULL) terom@50: FATAL("%s != NULL", dump_str(str)); terom@41: } terom@41: terom@41: void assert_success (err_t err) terom@41: { terom@41: if (err != SUCCESS) terom@41: FATAL("error: %s", error_name(err)); terom@41: } terom@41: terom@41: void assert_err (err_t err, err_t target) terom@41: { terom@41: if (err != target) terom@42: FATAL("error: <%s> != <%s>", error_name(err), error_name(target)); terom@42: } terom@42: terom@42: void assert_error_info (struct error_info *is, struct error_info *should_be) terom@42: { terom@42: if (ERROR_CODE(is) != ERROR_CODE(should_be) || ERROR_EXTRA(is) != ERROR_EXTRA(should_be)) terom@42: FATAL("error: <%s> != <%s>", error_msg(is), error_msg(should_be)); terom@41: } terom@41: terom@40: void assert_sock_read (struct sock_stream *sock, const char *str) terom@40: { terom@40: char buf[strlen(str)]; terom@40: terom@50: log_debug("read: %p: %s", sock, dump_str(str)); terom@40: terom@40: // read it terom@40: assert(sock_stream_read(sock, buf, strlen(str)) == (int) strlen(str)); terom@40: terom@40: // cmp terom@41: assert_strncmp(buf, str, strlen(str)); terom@40: } terom@40: terom@40: void assert_sock_write (struct sock_stream *sock, const char *str) terom@40: { terom@50: log_debug("write: %p: %s", sock, dump_str(str)); terom@40: terom@40: // write it terom@40: assert(sock_stream_write(sock, str, strlen(str)) == (int) strlen(str)); terom@40: } terom@40: terom@41: void assert_sock_eof (struct sock_stream *sock) terom@41: { terom@41: char buf; terom@41: terom@41: log_debug("eof: %p", sock); terom@41: terom@41: assert_err(-sock_stream_read(sock, &buf, 1), ERR_READ_EOF); terom@41: } terom@41: terom@77: /** terom@77: * Maximum amount that can be pushed using test_sock_push terom@77: */ terom@77: #define TEST_SOCK_FMT_MAX 1024 terom@77: terom@77: void assert_sock_data (struct sock_test *sock, const char *fmt, ...) terom@43: { terom@77: char buf[TEST_SOCK_FMT_MAX]; terom@77: va_list vargs; terom@43: size_t len; terom@43: terom@77: va_start(vargs, fmt); terom@77: terom@77: if ((len = vsnprintf(buf, TEST_SOCK_FMT_MAX, fmt, vargs)) >= TEST_SOCK_FMT_MAX) terom@77: FATAL("input too long: %zu bytes", len); terom@77: terom@77: va_end(vargs); terom@77: terom@77: // get the data out terom@77: char *out; terom@43: terom@77: sock_test_get_send_data(sock, &out, &len); terom@77: terom@77: log_debug("get_send_data: %s", dump_strn(out, len)); terom@43: terom@43: // should be the same terom@77: assert_strncmp(out, buf, len); terom@77: assert_strlen(buf, len); terom@43: terom@43: // cleanup terom@77: free(out); terom@43: } terom@43: terom@52: /** terom@77: * Nicer name for test_sock_add_recv_str, also supports formatted data. terom@77: * terom@77: * The formatted result is limited to TEST_SOCK_PUSH_MAX bytes terom@52: */ terom@77: void test_sock_push (struct sock_test *sock, const char *fmt, ...) terom@52: { terom@77: char buf[TEST_SOCK_FMT_MAX]; terom@77: va_list vargs; terom@77: size_t len; terom@77: terom@77: va_start(vargs, fmt); terom@77: terom@77: if ((len = vsnprintf(buf, TEST_SOCK_FMT_MAX, fmt, vargs)) >= TEST_SOCK_FMT_MAX) terom@77: FATAL("output too long: %zu bytes", len); terom@77: terom@77: va_end(vargs); terom@77: terom@77: return sock_test_add_recv_str(sock, buf); terom@52: } terom@52: terom@72: /** terom@90: * Setup the global sock_stream state terom@90: */ terom@90: struct event_base* setup_sock (void) terom@90: { terom@90: struct event_base *ev_base; terom@90: struct error_info err; terom@90: terom@90: assert((ev_base = event_base_new())); terom@90: assert_success(sock_init(ev_base, &err)); terom@90: terom@90: return ev_base; terom@90: } terom@90: terom@90: /** terom@72: * Create an empty sock_test terom@72: */ terom@72: struct sock_test* setup_sock_test (void) terom@72: { terom@72: struct sock_test *sock; terom@72: terom@72: assert ((sock = sock_test_create()) != NULL); terom@72: terom@72: return sock; terom@72: } terom@72: terom@125: void assert_str_quote (size_t buf_size, const char *data, ssize_t len, const char *target, size_t out) terom@125: { terom@125: char buf[buf_size]; terom@125: terom@125: size_t ret = str_quote(buf, buf_size, data, len); terom@125: terom@125: log_debug("str_quote(%zu, %zd) -> %s:%zu / %s:%zu", buf_size, len, buf, ret, target, out); terom@125: terom@125: assert_strcmp(buf, target); terom@125: assert(ret == out); terom@125: } terom@125: terom@125: void test_str_quote (void) terom@125: { terom@125: log_info("testing str_quote()"); terom@125: terom@125: assert_str_quote(5, NULL, -1, "NULL", 4 ); terom@125: assert_str_quote(16, "foo", -1, "'foo'", 5 ); terom@125: assert_str_quote(16, "foobar", 3, "'foo'", 5 ); terom@125: assert_str_quote(16, "\r\n", -1, "'\\r\\n'", 6 ); terom@125: assert_str_quote(16, "\x13", -1, "'\\x13'", 6 ); terom@125: assert_str_quote(16, "x'y", -1, "'x\\'y'", 6 ); terom@125: assert_str_quote(7, "1234567890", -1, "'1'...", 12 ); terom@125: assert_str_quote(9, "1234567890", -1, "'123'...", 12 ); terom@125: } terom@125: terom@129: struct str_format_ctx { terom@129: const char *name; terom@129: terom@129: const char *value; terom@129: }; terom@129: terom@129: err_t test_str_format_cb (const char *name, const char **value, ssize_t *value_len, void *arg) terom@129: { terom@129: struct str_format_ctx *ctx = arg; terom@129: terom@129: assert_strcmp(name, ctx->name); terom@129: terom@129: *value = ctx->value; terom@129: *value_len = -1; terom@129: terom@129: return SUCCESS; terom@129: } terom@129: terom@129: void assert_str_format (const char *format, const char *name, const char *value, const char *out) terom@129: { terom@129: struct str_format_ctx ctx = { name, value }; terom@129: char buf[512]; terom@129: terom@129: assert_success(str_format(buf, sizeof(buf), format, test_str_format_cb, &ctx)); terom@129: terom@129: log_debug("str_format(%s), { %s:%s } -> %s / %s", format, name, value, buf, out); terom@129: terom@129: assert_strcmp(buf, out); terom@129: } terom@129: terom@129: void test_str_format (void) terom@129: { terom@129: log_info("test str_format()"); terom@129: terom@129: assert_str_format("foo", NULL, NULL, "foo"); terom@129: assert_str_format("foo {bar} quux", "bar", "XXX", "foo XXX quux"); terom@129: } terom@129: terom@50: void test_dump_str (void) terom@50: { terom@50: log_info("dumping example strings on stdout:"); terom@50: terom@50: log_debug("normal: %s", dump_str("Hello World")); terom@50: log_debug("escapes: %s", dump_str("foo\r\nbar\a\001")); terom@50: log_debug("length: %s", dump_strn("<-->**", 4)); terom@50: log_debug("overflow: %s", dump_str( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); terom@50: log_debug("null: %s", dump_str(NULL)); terom@51: log_debug("quote: %s", dump_str("foo\\bar'quux")); terom@50: } terom@50: terom@40: void test_sock_test (void) terom@40: { terom@40: struct sock_test *sock = sock_test_create(); terom@40: struct io_vec _read_data[] = { terom@40: { "foo", 3 }, terom@40: { "barx", 4 } terom@40: }; terom@40: const char *_write_data = "test data"; terom@40: terom@40: // put the read data terom@40: log_debug("set_recv_buffer: %p, %d", _read_data, 2); terom@41: sock_test_set_recv_buffer(sock, _read_data, 2, true); terom@41: terom@41: // read it out terom@41: log_info("test sock_test_read"); terom@40: terom@40: assert_sock_read(SOCK_TEST_BASE(sock), "foo"); terom@40: assert_sock_read(SOCK_TEST_BASE(sock), "ba"); terom@40: assert_sock_read(SOCK_TEST_BASE(sock), "rx"); terom@41: assert_sock_eof(SOCK_TEST_BASE(sock)); terom@40: terom@40: // write the data in terom@41: log_info("test sock_test_write"); terom@41: terom@40: assert_sock_write(SOCK_TEST_BASE(sock), "test "); terom@40: assert_sock_write(SOCK_TEST_BASE(sock), "data"); terom@43: terom@43: // check output terom@44: assert_sock_data(sock, _write_data); terom@40: terom@43: // check output terom@44: assert_sock_data(sock, ""); terom@40: terom@41: // cleanup terom@41: sock_test_destroy(sock); terom@41: } terom@41: terom@41: void assert_read_line (struct line_proto *lp, const char *line_str) terom@41: { terom@41: char *line_buf; terom@41: terom@50: log_debug("expect: %s", dump_str(line_str)); terom@41: terom@41: assert_success(line_proto_recv(lp, &line_buf)); terom@41: terom@41: if (line_str) { terom@41: assert_strcmp(line_buf, line_str); terom@41: terom@41: } else { terom@42: assert_strnull(line_buf); terom@41: terom@41: } terom@41: } terom@41: terom@42: /** terom@42: * Context info for test_line_proto callbacks terom@42: */ terom@42: struct _lp_test_ctx { terom@42: /** Expected line */ terom@42: const char *line; terom@42: terom@42: /** Expected error */ terom@42: struct error_info err; terom@42: }; terom@42: terom@42: static void _lp_on_line (char *line, void *arg) terom@42: { terom@42: struct _lp_test_ctx *ctx = arg; terom@42: terom@50: log_debug("%s", dump_str(line)); terom@42: terom@42: assert_strcmp(line, ctx->line); terom@42: terom@42: ctx->line = NULL; terom@42: } terom@42: terom@42: static void _lp_on_error (struct error_info *err, void *arg) terom@42: { terom@42: struct _lp_test_ctx *ctx = arg; terom@42: terom@42: assert_error_info(err, &ctx->err); terom@42: } terom@42: terom@41: static struct line_proto_callbacks _lp_callbacks = { terom@42: .on_line = &_lp_on_line, terom@42: .on_error = &_lp_on_error, terom@41: }; terom@41: terom@41: void test_line_proto (void) terom@41: { terom@41: struct sock_test *sock = sock_test_create(); terom@41: struct io_vec _read_data[] = { terom@41: { "hello\r\n", 7 }, terom@41: { "world\n", 6 }, terom@41: { "this ", 5 }, terom@41: { "is a line\r", 10 }, terom@41: { "\nfragment", 9 }, terom@41: }, _trailing_data = { "\r\n", 2 }; terom@41: struct line_proto *lp; terom@42: struct _lp_test_ctx ctx; terom@41: struct error_info err; terom@41: terom@41: // put the read data terom@41: log_debug("set_recv_buffer: %p, %d", _read_data, 5); terom@41: sock_test_set_recv_buffer(sock, _read_data, 5, false); terom@41: terom@41: // create the lp terom@42: assert_success(line_proto_create(&lp, SOCK_TEST_BASE(sock), 128, &_lp_callbacks, &ctx, &err)); terom@41: terom@41: log_info("test line_proto_recv"); terom@41: terom@41: // then read some lines from it terom@41: assert_read_line(lp, "hello"); terom@41: assert_read_line(lp, "world"); terom@41: assert_read_line(lp, "this is a line"); terom@41: assert_read_line(lp, NULL); terom@41: terom@42: // then add a final bit to trigger on_line terom@42: log_info("test on_line"); terom@42: terom@42: ctx.line = "fragment"; terom@41: sock_test_add_recv_vec(sock, _trailing_data); terom@42: assert_strnull(ctx.line); terom@41: terom@43: // test writing terom@43: log_info("test line_proto_send"); terom@43: assert_success(-line_proto_send(lp, "foobar\r\n")); terom@43: assert_success(-line_proto_send(lp, "quux\r\n")); terom@44: assert_sock_data(sock, "foobar\r\nquux\r\n"); terom@43: terom@43: // XXX: test partial writes terom@43: terom@41: // cleanup terom@41: line_proto_release(lp); terom@40: } terom@40: terom@90: void test_irc_queue (void) terom@90: { terom@90: struct sock_test *sock = sock_test_create(); terom@90: struct line_proto *lp; terom@90: struct irc_queue *queue; terom@90: struct irc_queue_entry *queue_entry; terom@90: struct error_info err; terom@90: terom@90: // create the lp terom@90: assert_success(line_proto_create(&lp, SOCK_TEST_BASE(sock), 128, &_lp_callbacks, NULL, &err)); terom@90: terom@90: // create the queue terom@90: assert_success(irc_queue_create(&queue, lp, &err)); terom@90: terom@90: struct irc_line line = { terom@90: NULL, "TEST", { "fooX" } terom@90: }; terom@90: terom@90: // then test simple writes, we should be able to push five lines directly terom@90: log_info("test irc_queue_process (irc_queue_send_direct)"); terom@90: line.args[0] = "foo0"; assert_success(irc_queue_process(queue, &line)); terom@90: line.args[0] = "foo1"; assert_success(irc_queue_process(queue, &line)); terom@90: line.args[0] = "foo2"; assert_success(irc_queue_process(queue, &line)); terom@90: line.args[0] = "foo3"; assert_success(irc_queue_process(queue, &line)); terom@90: line.args[0] = "foo4"; assert_success(irc_queue_process(queue, &line)); terom@90: terom@90: // they should all be output terom@90: assert_sock_data(sock, terom@90: "TEST foo0\r\n" terom@90: "TEST foo1\r\n" terom@90: "TEST foo2\r\n" terom@90: "TEST foo3\r\n" terom@90: "TEST foo4\r\n" terom@90: ); terom@90: terom@90: // then enqueue terom@90: log_info("test irc_queue_process (irc_queue_put)"); terom@90: line.args[0] = "foo5"; assert_success(irc_queue_process(queue, &line)); terom@90: terom@90: // ensure it was enqueued terom@90: assert((queue_entry = TAILQ_FIRST(&queue->list)) != NULL); terom@90: assert_strcmp(queue_entry->line_buf, "TEST foo5\r\n"); terom@90: terom@90: // ensure timer is set terom@90: assert(event_pending(queue->ev, EV_TIMEOUT, NULL)); terom@90: terom@90: // run the event loop to let the timer run terom@90: log_info("running the event loop once..."); terom@90: assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0); terom@90: terom@90: // test to check that the line was now sent terom@90: log_info("checking that the delayed line was sent..."); terom@90: assert_sock_data(sock, "TEST foo5\r\n"); terom@90: assert(TAILQ_EMPTY(&queue->list)); terom@90: assert(!event_pending(queue->ev, EV_TIMEOUT, NULL)); terom@91: terom@91: // cleanup terom@91: irc_queue_destroy(queue); terom@90: } terom@90: terom@76: struct test_conn_ctx { terom@76: /** Callback flags */ terom@49: bool on_registered, on_TEST, on_error, on_quit; terom@43: }; terom@43: terom@43: static void _conn_on_registered (struct irc_conn *conn, void *arg) terom@43: { terom@76: struct test_conn_ctx *ctx = arg; terom@43: terom@43: (void) conn; terom@43: terom@44: if (ctx) ctx->on_registered = true; terom@43: terom@43: log_debug("registered"); terom@43: } terom@43: terom@49: static void _conn_on_error (struct irc_conn *conn, struct error_info *err, void *arg) terom@49: { terom@76: struct test_conn_ctx *ctx = arg; terom@49: terom@49: (void) conn; terom@49: (void) err; terom@49: terom@49: if (ctx) ctx->on_error = true; terom@49: terom@49: log_debug("on_error"); terom@49: } terom@49: terom@49: static void _conn_on_quit (struct irc_conn *conn, void *arg) terom@49: { terom@76: struct test_conn_ctx *ctx = arg; terom@49: terom@49: (void) conn; terom@49: terom@49: if (ctx) ctx->on_quit = true; terom@49: terom@49: log_debug("on_quit"); terom@49: } terom@49: terom@43: static void _conn_on_TEST (const struct irc_line *line, void *arg) terom@43: { terom@76: struct test_conn_ctx *ctx = arg; terom@43: terom@75: assert_null(line->source); terom@43: assert_strcmp(line->command, "TEST"); terom@43: assert_strcmp(line->args[0], "arg0"); terom@43: assert_strnull(line->args[1]); terom@43: terom@44: if (ctx) ctx->on_TEST = true; terom@43: terom@43: log_debug("on_TEST"); terom@43: } terom@43: terom@40: static struct irc_conn_callbacks _conn_callbacks = { terom@43: .on_registered = &_conn_on_registered, terom@49: .on_error = &_conn_on_error, terom@49: .on_quit = &_conn_on_quit, terom@43: }; terom@43: terom@43: static struct irc_cmd_handler _conn_handlers[] = { terom@43: { "TEST", &_conn_on_TEST }, terom@43: { NULL, NULL } terom@40: }; terom@40: terom@76: /** terom@76: * Create and return a new irc_conn with the given ctx (will be initialized to zero). terom@76: */ terom@76: struct irc_conn* setup_irc_conn (struct sock_test *sock, bool noisy, struct test_conn_ctx *ctx) terom@40: { terom@40: struct irc_conn *conn; terom@40: struct error_info err; terom@43: struct irc_conn_register_info register_info = { terom@43: "nick", "user", "realname" terom@43: }; terom@44: terom@76: // init the ctx terom@76: memset(ctx, 0, sizeof(*ctx)); terom@76: terom@44: // create the irc_conn terom@44: assert_success(irc_conn_create(&conn, SOCK_TEST_BASE(sock), &_conn_callbacks, ctx, &err)); terom@44: terom@44: // test register terom@44: if (noisy) log_info("test irc_conn_register"); terom@44: assert_success(irc_conn_register(conn, ®ister_info)); terom@44: assert_sock_data(sock, "NICK nick\r\nUSER user 0 * realname\r\n"); terom@44: terom@44: // test on_register callback terom@44: if (noisy) log_info("test irc_conn_callbacks.on_register"); terom@52: test_sock_push(sock, "001 mynick :Blaa blaa blaa\r\n"); terom@44: if (ctx) assert(ctx->on_registered); terom@44: assert_strcmp(conn->nickname, "mynick"); terom@44: terom@44: // ok terom@44: return conn; terom@44: } terom@44: terom@44: void test_irc_conn (void) terom@44: { terom@76: struct test_conn_ctx ctx; terom@76: struct sock_test *sock = setup_sock_test(); terom@76: struct irc_conn *conn = setup_irc_conn(sock, true, &ctx); terom@40: terom@44: // add our test handlers terom@44: assert_success(irc_conn_add_cmd_handlers(conn, _conn_handlers, &ctx)); terom@43: terom@43: // test on_TEST handler terom@75: // XXX: come up with a better prefix terom@43: log_info("test irc_conn.handlers"); terom@52: test_sock_push(sock, ":foobar-prefix TEST arg0\r\n"); terom@43: assert(ctx.on_TEST); terom@40: terom@44: // test PING/PONG terom@44: log_info("test PING/PONG"); terom@52: test_sock_push(sock, "PING foo\r\n"); terom@44: assert_sock_data(sock, "PONG foo\r\n"); terom@44: terom@49: // quit nicely terom@49: log_info("test QUIT"); terom@49: assert_success(irc_conn_QUIT(conn, "bye now")); terom@49: assert_sock_data(sock, "QUIT :bye now\r\n"); terom@49: assert(conn->quitting); terom@49: terom@52: test_sock_push(sock, "ERROR :Closing Link: Quit\r\n"); terom@49: sock_test_set_recv_eof(sock); terom@49: assert(conn->quit && !conn->quitting && !conn->registered); terom@49: assert(ctx.on_quit); terom@49: assert(!ctx.on_error); terom@49: terom@40: // destroy it terom@40: irc_conn_destroy(conn); terom@40: } terom@40: terom@76: void test_irc_conn_self_nick (void) terom@76: { terom@76: struct test_conn_ctx ctx; terom@76: struct sock_test *sock = setup_sock_test(); terom@76: struct irc_conn *conn = setup_irc_conn(sock, false, &ctx); terom@76: terom@76: log_info("test irc_conn_on_NICK"); terom@76: test_sock_push(sock, ":mynick!user@somehost NICK mynick2\r\n"); terom@76: assert_strcmp(conn->nickname, "mynick2"); terom@76: terom@76: // cleanup terom@76: irc_conn_destroy(conn); terom@76: } terom@76: terom@72: struct test_chan_ctx { terom@77: /** The channel name */ terom@77: const char *channel; terom@77: terom@72: /** The channel we're supposed to be testing */ terom@44: struct irc_chan *chan; terom@72: terom@73: /** Flags for callbacks called*/ terom@74: bool on_chan_self_join, on_chan_self_part, on_chan_join, on_chan_part; terom@73: terom@44: }; terom@44: terom@44: void _on_chan_self_join (struct irc_chan *chan, void *arg) terom@44: { terom@72: struct test_chan_ctx *ctx = arg; terom@44: terom@44: assert(chan == ctx->chan); terom@44: terom@44: ctx->on_chan_self_join = true; terom@44: terom@44: log_debug("on_self_join"); terom@44: } terom@44: terom@73: void _on_chan_join (struct irc_chan *chan, const struct irc_nm *source, void *arg) terom@73: { terom@73: struct test_chan_ctx *ctx = arg; terom@73: terom@73: assert(chan == ctx->chan); terom@73: terom@73: // XXX: verify source terom@73: terom@73: ctx->on_chan_join = true; terom@73: terom@73: log_debug("on_join"); terom@73: } terom@73: terom@74: void _on_chan_part (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg) terom@74: { terom@74: struct test_chan_ctx *ctx = arg; terom@74: terom@74: assert(chan == ctx->chan); terom@74: terom@74: // XXX: verify source terom@74: // XXX: verify msg terom@74: terom@74: ctx->on_chan_part = true; terom@74: terom@74: log_debug("on_part"); terom@74: } terom@74: terom@74: terom@44: struct irc_chan_callbacks _chan_callbacks = { terom@44: .on_self_join = &_on_chan_self_join, terom@73: .on_join = &_on_chan_join, terom@74: .on_part = &_on_chan_part, terom@44: }; terom@44: terom@72: /** terom@72: * Setup an irc_net using the given socket, and consume the register request output, but do not push the RPL_WELCOME terom@72: */ terom@72: struct irc_net* setup_irc_net_unregistered (struct sock_test *sock) terom@44: { terom@44: struct irc_net *net; terom@44: struct irc_net_info net_info = { terom@44: .register_info = { terom@44: "nick", "user", "realname" terom@44: }, terom@44: }; terom@72: struct error_info err; terom@72: terom@72: // create the irc_net terom@72: net_info.raw_sock = SOCK_TEST_BASE(sock); terom@72: assert_success(irc_net_create(&net, &net_info, &err)); terom@72: terom@72: // test register output terom@72: assert_sock_data(sock, "NICK nick\r\nUSER user 0 * realname\r\n"); terom@72: terom@72: // ok terom@72: return net; terom@72: } terom@72: terom@72: /** terom@72: * Push to RPL_WELCOME reply, and test state terom@72: */ terom@72: void do_irc_net_welcome (struct sock_test *sock, struct irc_net *net) terom@72: { terom@72: // registration reply terom@72: test_sock_push(sock, "001 mynick :Blaa blaa blaa\r\n"); terom@72: assert(net->conn->registered); terom@72: assert_strcmp(net->conn->nickname, "mynick"); terom@72: terom@72: } terom@72: terom@72: /** terom@72: * Creates an irc_net and puts it into the registered state terom@72: */ terom@72: struct irc_net* setup_irc_net (struct sock_test *sock) terom@72: { terom@72: struct irc_net *net; terom@72: terom@72: net = setup_irc_net_unregistered(sock); terom@72: do_irc_net_welcome(sock, net); terom@72: terom@72: // ok terom@72: return net; terom@72: } terom@72: terom@72: /** terom@72: * General test for irc_net to handle startup terom@72: */ terom@72: void test_irc_net (void) terom@72: { terom@72: struct sock_test *sock = setup_sock_test(); terom@72: terom@72: // create the network terom@72: log_info("test irc_net_create"); terom@72: struct irc_net *net = setup_irc_net_unregistered(sock); terom@72: terom@72: // send the registration reply terom@72: log_info("test irc_conn_on_RPL_WELCOME"); terom@72: do_irc_net_welcome(sock, net); terom@72: terom@72: // test errors by setting EOF terom@72: log_info("test irc_net_error"); terom@72: sock_test_set_recv_eof(sock); terom@72: assert(net->conn == NULL); terom@72: terom@72: // cleanup terom@72: irc_net_destroy(net); terom@72: } terom@72: terom@72: /** terom@74: * Ensure that an irc_chan_user exists/doesn't exist for the given channel/nickname, and return it terom@74: */ terom@74: struct irc_chan_user* check_chan_user (struct irc_chan *chan, const char *nickname, bool exists) terom@74: { terom@74: struct irc_chan_user *chan_user = irc_chan_get_user(chan, nickname); terom@74: terom@74: if (exists && chan_user == NULL) terom@74: FATAL("user %s not found in channel %s", dump_str(nickname), dump_str(irc_chan_name(chan))); terom@74: terom@74: if (!exists && chan_user) terom@74: FATAL("user %s should not be on channel %s anymore", dump_str(nickname), dump_str(irc_chan_name(chan))); terom@74: terom@74: log_debug("%s, exists=%d -> %p: user=%p, nickname=%s", terom@74: nickname, exists, chan_user, chan_user ? chan_user->user : NULL, chan_user ? chan_user->user->nickname : NULL); terom@74: terom@74: if (chan_user) terom@74: assert_strcmp(chan_user->user->nickname, nickname); terom@74: terom@74: return chan_user; terom@74: } terom@74: terom@74: /** terom@72: * Creates an irc_chan on the given irc_net, but does not check any output (useful for testing offline add). terom@72: * terom@72: * You must pass a test_chan_ctx for use with later operations, this will be initialized for you. terom@72: */ terom@77: struct irc_chan* setup_irc_chan_raw (struct irc_net *net, const char *channel, struct test_chan_ctx *ctx) terom@72: { terom@44: struct irc_chan *chan; terom@44: struct irc_chan_info chan_info = { terom@77: .channel = channel, terom@44: }; terom@44: struct error_info err; terom@44: terom@72: // initialize the given ctx terom@72: memset(ctx, 0, sizeof(*ctx)); terom@77: ctx->channel = channel; terom@44: terom@44: // add a channel terom@72: assert_success(irc_net_add_chan(net, &chan, &chan_info, &err)); terom@72: assert(!chan->joined); terom@72: assert_success(irc_chan_add_callbacks(chan, &_chan_callbacks, ctx)); terom@72: ctx->chan = chan; terom@44: terom@72: // ok terom@72: return chan; terom@72: } terom@72: terom@72: /** terom@72: * Checks that the JOIN request for a channel was sent, and sends the basic JOIN reply terom@72: */ terom@72: void do_irc_chan_join (struct sock_test *sock, struct test_chan_ctx *ctx) terom@72: { terom@44: // JOIN request terom@72: assert(ctx->chan->joining); terom@77: assert_sock_data(sock, "JOIN %s\r\n", ctx->channel); terom@44: terom@44: // JOIN reply terom@77: test_sock_push(sock, ":mynick!user@host JOIN %s\r\n", ctx->channel); terom@72: assert(!ctx->chan->joining && ctx->chan->joined); terom@72: assert(ctx->on_chan_self_join); terom@72: } terom@46: terom@72: /** terom@74: * Sends a short RPL_NAMREPLY/RPL_ENDOFNAMES reply and checks that the users list matches terom@74: */ terom@74: void do_irc_chan_namreply (struct sock_test *sock, struct test_chan_ctx *ctx) terom@74: { terom@74: // RPL_NAMREPLY terom@77: test_sock_push(sock, "353 mynick = %s :mynick userA +userB @userC\r\n", ctx->channel); terom@82: test_sock_push(sock, "353 mynick = %s :trailingspace \r\n", ctx->channel); terom@77: test_sock_push(sock, "366 mynick %s :End of NAMES\r\n", ctx->channel); terom@74: terom@74: // XXX: this should be an exclusive test, i.e. these should be the only ones... terom@74: check_chan_user(ctx->chan, "mynick", true); terom@74: check_chan_user(ctx->chan, "userA", true); terom@74: check_chan_user(ctx->chan, "userB", true); terom@74: check_chan_user(ctx->chan, "userC", true); terom@74: } terom@74: terom@74: /** terom@72: * Creates an irc_chan on the given irc_net, and checks up to the JOIN reply terom@72: */ terom@77: struct irc_chan* setup_irc_chan_join (struct sock_test *sock, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx) terom@72: { terom@77: setup_irc_chan_raw(net, channel, ctx); terom@72: do_irc_chan_join(sock, ctx); terom@72: terom@72: // ok terom@72: return ctx->chan; terom@72: } terom@72: terom@72: /** terom@74: * Creates an irc_chan on the given irc_net, sends the JOIN stuff plus RPL_NAMREPLY terom@74: */ terom@77: struct irc_chan* setup_irc_chan (struct sock_test *sock, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx) terom@74: { terom@77: setup_irc_chan_raw(net, channel, ctx); terom@74: do_irc_chan_join(sock, ctx); terom@74: do_irc_chan_namreply(sock, ctx); terom@74: terom@74: // ok terom@74: return ctx->chan; terom@74: } terom@74: terom@74: terom@74: /** terom@72: * Call irc_net_add_chan while offline, and ensure that we send the JOIN request after RPL_WELCOME, and handle the join terom@72: * reply OK. terom@72: */ terom@72: void test_irc_chan_add_offline (void) terom@72: { terom@72: struct test_chan_ctx ctx; terom@72: terom@72: struct sock_test *sock = setup_sock_test(); terom@72: terom@72: log_info("test irc_net_create"); terom@72: struct irc_net *net = setup_irc_net_unregistered(sock); terom@72: terom@72: // add an offline channel terom@72: log_info("test offline irc_net_add_chan"); terom@77: struct irc_chan *chan = setup_irc_chan_raw(net, "#test", &ctx); terom@72: assert(!chan->joining && !chan->joined); terom@72: terom@72: // send the registration reply terom@72: log_info("test irc_conn_on_RPL_WELCOME"); terom@72: do_irc_net_welcome(sock, net); terom@72: terom@72: // test the join sequence terom@72: log_info("test irc_chan_join/irc_chan_on_JOIN"); terom@72: do_irc_chan_join(sock, &ctx); terom@72: terom@72: // cleanup terom@72: irc_net_destroy(net); terom@72: } terom@72: terom@72: void test_irc_chan_namreply (void) terom@72: { terom@72: struct test_chan_ctx ctx; terom@72: struct sock_test *sock = setup_sock_test(); terom@72: struct irc_net *net = setup_irc_net(sock); terom@77: setup_irc_chan_join(sock, net, "#test", &ctx); terom@72: terom@72: log_info("test irc_chan_on_RPL_NAMREPLY"); terom@74: do_irc_chan_namreply(sock, &ctx); terom@47: terom@47: // cleanup terom@47: irc_net_destroy(net); terom@44: } terom@44: terom@73: void test_irc_chan_user_join (void) terom@73: { terom@73: struct test_chan_ctx ctx; terom@73: struct sock_test *sock = setup_sock_test(); terom@73: struct irc_net *net = setup_irc_net(sock); terom@77: struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx); terom@73: terom@73: // have a user join terom@73: log_info("test irc_chan_on_JOIN"); terom@77: test_sock_push(sock, ":newuser!someone@somewhere JOIN %s\r\n", "#test"); terom@73: assert(ctx.on_chan_join); terom@74: check_chan_user(chan, "newuser", true); terom@74: terom@74: // cleanup terom@74: irc_net_destroy(net); terom@74: } terom@74: terom@74: void test_irc_chan_user_part (void) terom@74: { terom@74: struct test_chan_ctx ctx; terom@74: struct sock_test *sock = setup_sock_test(); terom@74: struct irc_net *net = setup_irc_net(sock); terom@77: struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx); terom@74: terom@74: // have a user join terom@74: log_info("test irc_chan_on_PART"); terom@77: test_sock_push(sock, ":userA!someone@somewhere PART %s\r\n", "#test"); terom@74: assert(ctx.on_chan_part); ctx.on_chan_part = NULL; terom@74: check_chan_user(chan, "userA", false); terom@73: terom@73: // cleanup terom@73: irc_net_destroy(net); terom@73: } terom@73: terom@89: void test_irc_chan_user_kick (void) terom@89: { terom@89: struct test_chan_ctx ctx; terom@89: struct sock_test *sock = setup_sock_test(); terom@89: struct irc_net *net = setup_irc_net(sock); terom@89: struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx); terom@89: terom@89: // kick a user terom@89: log_info("test irc_chan_on_KICK (other)"); terom@89: test_sock_push(sock, ":userA!someone@somewhere KICK %s userB\r\n", "#test"); terom@89: check_chan_user(chan, "userA", true); terom@89: check_chan_user(chan, "userB", false); terom@89: terom@89: // cleanup terom@89: irc_net_destroy(net); terom@89: } terom@89: terom@89: void test_irc_chan_self_kick (void) terom@89: { terom@89: struct test_chan_ctx ctx; terom@89: struct sock_test *sock = setup_sock_test(); terom@89: struct irc_net *net = setup_irc_net(sock); terom@89: struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx); terom@89: terom@89: // kick a user terom@89: log_info("test irc_chan_on_KICK (self)"); terom@89: test_sock_push(sock, ":userA!someone@somewhere KICK %s mynick foobar\r\n", "#test"); terom@89: assert(!chan->joined); terom@89: assert(chan->kicked); terom@89: terom@89: // cleanup terom@89: irc_net_destroy(net); terom@89: } terom@89: terom@78: void test_irc_chan_user_nick (void) terom@78: { terom@78: struct test_chan_ctx ctx; terom@78: struct sock_test *sock = setup_sock_test(); terom@78: struct irc_net *net = setup_irc_net(sock); terom@78: struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx); terom@78: terom@78: // rename one of the users terom@78: log_info("test irc_net_on_chanuser"); terom@78: test_sock_push(sock, ":userA!someone@somewhere NICK userA2\r\n"); terom@78: check_chan_user(chan, "userA", false); terom@78: check_chan_user(chan, "userB", true); terom@81: terom@81: // cleanup terom@81: irc_net_destroy(net); terom@78: } terom@78: terom@78: void test_irc_chan_user_quit (void) terom@78: { terom@78: struct test_chan_ctx ctx; terom@78: struct sock_test *sock = setup_sock_test(); terom@78: struct irc_net *net = setup_irc_net(sock); terom@78: struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx); terom@78: terom@78: // rename one of the users terom@78: log_info("test irc_net_on_chanuser"); terom@78: test_sock_push(sock, ":userA!someone@somewhere QUIT foo\r\n"); terom@78: check_chan_user(chan, "userA", false); terom@81: terom@81: // cleanup terom@81: irc_net_destroy(net); terom@78: } terom@78: terom@88: void _test_irc_chan_on_CTCP_ACTION (const struct irc_line *line, void *arg) terom@88: { terom@88: bool *flag = arg; terom@88: terom@88: log_debug("CTCP ACTION"); terom@88: terom@88: *flag = true; terom@88: } terom@88: terom@88: static struct irc_cmd_handler _test_irc_chan_handlers[] = { terom@88: { "CTCP ACTION", &_test_irc_chan_on_CTCP_ACTION }, terom@88: { NULL, NULL } terom@88: }; terom@88: terom@88: void test_irc_chan_CTCP_ACTION (void) terom@88: { terom@88: struct test_chan_ctx ctx; terom@88: struct sock_test *sock = setup_sock_test(); terom@88: struct irc_net *net = setup_irc_net(sock); terom@88: struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx); terom@88: bool cb_ok = false; terom@88: terom@88: // add our handler terom@88: assert_success(irc_cmd_add(&chan->handlers, _test_irc_chan_handlers, &cb_ok)); terom@88: terom@88: // rename one of the users terom@88: log_info("test irc_conn_on_CTCP_ACTION"); terom@88: test_sock_push(sock, ":userA!someone@somewhere PRIVMSG #test \001ACTION hello world\001\r\n"); terom@88: assert(cb_ok); terom@88: terom@88: // cleanup terom@88: irc_net_destroy(net); terom@88: } terom@88: terom@97: void test_irc_chan_privmsg (void) terom@97: { terom@97: struct test_chan_ctx ctx; terom@97: struct sock_test *sock = setup_sock_test(); terom@97: struct irc_net *net = setup_irc_net(sock); terom@97: struct irc_chan *chan = setup_irc_chan(sock, net, "#test", &ctx); terom@97: terom@97: // rename one of the users terom@97: log_info("test irc_chan_PRIVMSG"); terom@97: assert_success(irc_chan_PRIVMSG(chan, "foobar quux")); terom@97: assert_sock_data(sock, "PRIVMSG #test :foobar quux\r\n"); terom@97: terom@97: // cleanup terom@97: irc_net_destroy(net); terom@97: } terom@88: terom@118: // XXX: needs to be split off into its own test_fifo.c terom@118: #include terom@118: #include terom@118: #include terom@118: #include terom@118: struct test_fifo_ctx { terom@118: /** Path to the fifo */ terom@118: const char *path; terom@118: terom@118: /** The write end */ terom@118: int fd; terom@118: terom@118: /** callback invoked? */ terom@118: bool on_read; terom@118: terom@118: /** Still running? */ terom@118: bool run; terom@118: }; terom@118: terom@118: /** terom@118: * Open the FIFO and write the test string to it terom@118: */ terom@118: static void test_fifo_open_write (struct test_fifo_ctx *ctx) terom@118: { terom@118: // ...raw, for writing terom@118: if ((ctx->fd = open(ctx->path, O_WRONLY)) < 0) terom@118: FATAL_PERROR("open"); terom@118: terom@118: // write something into it terom@118: if (write(ctx->fd, "test", 4) != 4) terom@118: FATAL_PERROR("write"); terom@118: terom@118: } terom@118: terom@118: static void test_fifo_close (struct test_fifo_ctx *ctx) terom@118: { terom@118: close(ctx->fd); terom@118: ctx->fd = -1; terom@118: } terom@118: terom@118: static void test_fifo_on_read (struct sock_stream *fifo, void *arg) terom@118: { terom@118: int ret; terom@118: char buf[16]; terom@118: struct test_fifo_ctx *ctx = arg; terom@118: terom@118: // read it back out terom@118: log_info("test fifo_read"); terom@118: if ((ret = sock_stream_read(fifo, buf, 16)) < 0) terom@118: assert_success(-ret); terom@118: terom@118: assert(ret == 4); terom@118: assert_strncmp(buf, "test", 4); terom@118: terom@118: if (ctx->on_read) { terom@118: test_fifo_close(ctx); terom@118: ctx->run = false; terom@118: return; terom@118: } terom@118: terom@118: // re-open the fifo terom@118: log_info("test fifo-re-open"); terom@118: test_fifo_close(ctx); terom@118: test_fifo_open_write(ctx); terom@118: terom@118: assert_success(sock_stream_event_enable(fifo, EV_READ)); terom@118: terom@118: ctx->on_read = true; terom@118: } terom@118: terom@118: static struct sock_stream_callbacks test_fifo_callbacks = { terom@118: .on_read = test_fifo_on_read, terom@118: }; terom@118: terom@118: void test_fifo (void) terom@118: { terom@118: struct sock_stream *fifo; terom@118: struct error_info err; terom@118: struct test_fifo_ctx _ctx, *ctx = &_ctx; memset(ctx, 0, sizeof(*ctx)); terom@118: terom@118: // XXX: requires that this be run in a suitable CWD terom@118: ctx->path = "test.fifo"; terom@118: terom@118: // create the fifo terom@118: if ((mkfifo(ctx->path, 0600) < 0) && (errno != EEXIST)) terom@118: FATAL_PERROR("mkfifo"); terom@118: terom@118: // open it terom@118: log_info("test fifo_open_read"); terom@118: assert_success(fifo_open_read(&fifo, ctx->path, &err)); terom@118: assert_success(sock_stream_event_init(fifo, &test_fifo_callbacks, ctx)); terom@118: assert_success(sock_stream_event_enable(fifo, EV_READ)); terom@118: terom@118: // put some data into it terom@118: test_fifo_open_write(ctx); terom@118: terom@118: // run the event loop terom@118: log_debug("running the event loop..."); terom@118: ctx->run = true; terom@118: terom@118: while (ctx->run) terom@118: assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0); terom@118: terom@118: // check terom@118: assert(ctx->fd < 0); terom@118: terom@118: // cleanup terom@118: sock_stream_release(fifo); terom@118: } terom@118: terom@40: /** terom@40: * Test definition terom@40: */ terom@118: struct test { terom@40: /** Test name */ terom@40: const char *name; terom@40: terom@40: /** Test func */ terom@40: void (*func) (void); terom@118: terom@118: bool optional; terom@118: }; terom@40: terom@118: #define DEF_TEST(name) { #name, &test_ ## name, false } terom@118: #define DEF_TEST_OPTIONAL(name) { #name, &test_ ## name, true } terom@118: #define DEF_TEST_END { NULL, NULL, false } terom@118: terom@118: static struct test _tests[] = { terom@125: DEF_TEST( str_quote ), terom@129: DEF_TEST( str_format ), terom@125: DEF_TEST( dump_str ), terom@125: DEF_TEST( sock_test ), terom@125: DEF_TEST( line_proto ), terom@125: DEF_TEST( irc_queue ), terom@75: // XXX: irc_line_parse_invalid_prefix terom@125: DEF_TEST( irc_conn ), terom@125: DEF_TEST( irc_conn_self_nick ), terom@125: DEF_TEST( irc_net ), terom@125: DEF_TEST( irc_chan_add_offline ), terom@125: DEF_TEST( irc_chan_namreply ), terom@125: DEF_TEST( irc_chan_user_join ), terom@125: DEF_TEST( irc_chan_user_part ), terom@125: DEF_TEST( irc_chan_user_kick ), terom@125: DEF_TEST( irc_chan_self_kick ), terom@125: DEF_TEST( irc_chan_user_nick ), terom@125: DEF_TEST( irc_chan_user_quit ), terom@125: DEF_TEST( irc_chan_CTCP_ACTION ), terom@125: DEF_TEST( irc_chan_privmsg ), terom@125: DEF_TEST_OPTIONAL( fifo ), terom@118: DEF_TEST_END terom@40: }; terom@40: terom@73: /** terom@73: * Command-line option codes terom@73: */ terom@73: enum option_code { terom@73: OPT_HELP = 'h', terom@73: OPT_DEBUG = 'd', terom@73: OPT_QUIET = 'q', terom@90: OPT_LIST = 'l', terom@73: terom@73: /** Options without short names */ terom@73: _OPT_EXT_BEGIN = 0x00ff, terom@73: }; terom@73: terom@73: /** terom@73: * Command-line option definitions terom@73: */ terom@73: static struct option options[] = { terom@73: {"help", 0, NULL, OPT_HELP }, terom@73: {"debug", 0, NULL, OPT_DEBUG }, terom@73: {"quiet", 0, NULL, OPT_QUIET }, terom@90: {"list", 0, NULL, OPT_LIST }, terom@73: {0, 0, 0, 0 }, terom@73: }; terom@73: terom@73: /** terom@73: * Display --help output on stdout terom@73: */ terom@73: static void usage (const char *exe) terom@73: { terom@73: printf("Usage: %s [OPTIONS]\n", exe); terom@73: printf("\n"); terom@73: printf(" --help / -h display this message\n"); terom@73: printf(" --debug / -d display DEBUG log messages\n"); terom@73: printf(" --quiet / -q supress INFO log messages\n"); terom@90: printf(" --list / -l list all tests\n"); terom@90: } terom@90: terom@90: static void list_tests (struct test *tests) terom@90: { terom@90: struct test *test; terom@90: terom@90: printf("Available tests:\n"); terom@90: terom@90: for (test = tests; test->name; test++) { terom@90: printf("\t%s\n", test->name); terom@90: } terom@73: } terom@73: terom@40: int main (int argc, char **argv) terom@40: { terom@40: struct test *test; terom@73: size_t test_count = 0; terom@52: terom@73: int opt, option_index; terom@73: const char *filter = NULL; terom@40: terom@73: // parse options terom@90: while ((opt = getopt_long(argc, argv, "hdql", options, &option_index)) != -1) { terom@73: switch (opt) { terom@73: case OPT_HELP: terom@73: usage(argv[0]); terom@73: exit(EXIT_SUCCESS); terom@73: terom@73: case OPT_DEBUG: terom@73: set_log_level(LOG_DEBUG); terom@73: break; terom@52: terom@73: case OPT_QUIET: terom@73: set_log_level(LOG_WARN); terom@73: break; terom@90: terom@90: case OPT_LIST: terom@90: list_tests(_tests); terom@90: exit(EXIT_SUCCESS); terom@90: terom@73: case '?': terom@73: usage(argv[0]); terom@73: exit(EXIT_FAILURE); terom@73: } terom@73: } terom@73: terom@73: if (optind < argc) { terom@73: if (optind == argc - 1) { terom@73: // filter terom@73: filter = argv[optind]; terom@73: terom@73: log_info("only running tests: %s", filter); terom@73: } else { terom@73: FATAL("too many arguments"); terom@73: } terom@52: } terom@40: terom@90: // setup the sockets stuff terom@90: _test_ctx.ev_base = setup_sock(); terom@90: terom@40: // run tests terom@40: for (test = _tests; test->name; test++) { terom@118: if ((filter && strcmp(test->name, filter)) || (!filter && test->optional)) terom@52: continue; terom@52: terom@40: log_info("Running test: %s", test->name); terom@73: terom@73: test_count++; terom@40: test->func(); terom@40: } terom@46: terom@73: // no tests run? terom@73: if (test_count == 0) terom@73: FATAL("no tests run"); terom@73: terom@73: log_info("done, ran %zu tests", test_count); terom@40: }