/**
* The main test code entry point
*/
#include "sock_test.h"
#include "line_proto.h"
#include "irc_conn.h"
#include "irc_net.h"
#include "log.h"
#include "error.h"
#include <stdlib.h>
#include <string.h>
#include <getopt.h>
#include <assert.h>
#include <ctype.h>
#define DUMP_STR_BUF 1024
#define DUMP_STR_COUNT 8
#define DUMP_STR_TAIL 10
char *dump_str_append (char *buf, const char *str)
{
while (*str)
*buf++ = *str++;
return buf;
}
/**
* This re-formats the given string to escape values, and returns a pointer to an internal static buffer.
*
* If len is given as >= 0, only the given number of chars will be dumped from str.
*
* The buffer cycles a bit, so the returned pointers remain valid across DUMP_STR_COUNT calls.
*
* The resulting string is truncated to (DUMP_STR_BUF - DUMP_STR_TAIL) bytes, not including the ending "...'\0".
*
* @param str the string to dump, should be NUL-terminated unless len is given
* @param len if negative, ignored, otherwise, only this many bytes are dumped from str
* @param return a pointer to a static buffer that remains valid across DUMP_STR_COUNT calls to this function
*/
const char *dump_strn (const char *str, ssize_t len)
{
static char dump_buf[DUMP_STR_COUNT][DUMP_STR_BUF];
static size_t dump_idx = 0;
// pick a buffer to use
const char *str_ptr = str;
char *buf_ptr = dump_buf[dump_idx++], *buf = buf_ptr;
// cycle
if (dump_idx >= DUMP_STR_COUNT)
dump_idx = 0;
// NULL?
if (str == NULL) {
buf = dump_str_append(buf, "NULL");
*buf = '\0';
return buf_ptr;
}
// quote
*buf++ = '\'';
// dump each char
for (; (
// ...stop on NUL
*str
// ...leave DUMP_STR_TAIL bytes room at the end of buf
&& (size_t) (buf - buf_ptr) < DUMP_STR_BUF - DUMP_STR_TAIL
// ...don't consume more than len bytes of str, unless len < 0
&& (len < 0 || (size_t) (str - str_ptr) < (size_t) len)
); str++
) {
if (*str == '\'') {
// escape quotes
buf = dump_str_append(buf, "\\'");
} else if (*str == '\\') {
// escape escapes
buf = dump_str_append(buf, "\\\\");
} else if (isprint(*str)) {
// normal char
*buf++ = *str;
} else {
// something more special
switch (*str) {
case '\r':
buf = dump_str_append(buf, "\\r"); break;
case '\n':
buf = dump_str_append(buf, "\\n"); break;
default:
// format as "\xFF"
buf += snprintf(buf, (DUMP_STR_BUF - (buf - buf_ptr)), "\\x%02x", *str);
break;
}
}
}
// end quote
*buf++ = '\'';
// overflow?
if ((size_t)(buf - buf_ptr) == DUMP_STR_BUF - DUMP_STR_TAIL)
buf = dump_str_append(buf, "...");
// terminate
*buf = '\0';
// ok
return buf_ptr;
}
const char *dump_str (const char *str)
{
return dump_strn(str, -1);
}
void assert_null (const void *ptr)
{
if (ptr)
FATAL("%p != NULL", ptr);
}
void assert_strcmp (const char *is, const char *should_be)
{
if (!is || strcmp(is, should_be))
FATAL("%s != %s", dump_str(is), dump_str(should_be));
}
void assert_strncmp (const char *is, const char *should_be, size_t n)
{
if (!is || strncmp(is, should_be, n))
FATAL("%s:%u != %s", dump_strn(is, n), (unsigned) n, dump_strn(should_be, n));
}
void assert_strlen (const char *str, size_t n)
{
if (!str || strlen(str) != n)
FATAL("strlen(%s) != %u", dump_str(str), (unsigned) n);
}
void assert_strnull (const char *str)
{
if (str != NULL)
FATAL("%s != NULL", dump_str(str));
}
void assert_success (err_t err)
{
if (err != SUCCESS)
FATAL("error: %s", error_name(err));
}
void assert_err (err_t err, err_t target)
{
if (err != target)
FATAL("error: <%s> != <%s>", error_name(err), error_name(target));
}
void assert_error_info (struct error_info *is, struct error_info *should_be)
{
if (ERROR_CODE(is) != ERROR_CODE(should_be) || ERROR_EXTRA(is) != ERROR_EXTRA(should_be))
FATAL("error: <%s> != <%s>", error_msg(is), error_msg(should_be));
}
void assert_sock_read (struct sock_stream *sock, const char *str)
{
char buf[strlen(str)];
log_debug("read: %p: %s", sock, dump_str(str));
// read it
assert(sock_stream_read(sock, buf, strlen(str)) == (int) strlen(str));
// cmp
assert_strncmp(buf, str, strlen(str));
}
void assert_sock_write (struct sock_stream *sock, const char *str)
{
log_debug("write: %p: %s", sock, dump_str(str));
// write it
assert(sock_stream_write(sock, str, strlen(str)) == (int) strlen(str));
}
void assert_sock_eof (struct sock_stream *sock)
{
char buf;
log_debug("eof: %p", sock);
assert_err(-sock_stream_read(sock, &buf, 1), ERR_READ_EOF);
}
void assert_sock_data (struct sock_test *sock, const char *data)
{
// get the data out
char *buf;
size_t len;
sock_test_get_send_data(sock, &buf, &len);
log_debug("get_send_data: %s", dump_strn(buf, len));
// should be the same
assert_strncmp(buf, data, len);
assert_strlen(data, len);
// cleanup
free(buf);
}
/**
* Nicer name for test_sock_add_recv_str
*/
void test_sock_push (struct sock_test *sock, const char *str)
{
return sock_test_add_recv_str(sock, str);
}
/**
* Create an empty sock_test
*/
struct sock_test* setup_sock_test (void)
{
struct sock_test *sock;
assert ((sock = sock_test_create()) != NULL);
return sock;
}
void test_dump_str (void)
{
log_info("dumping example strings on stdout:");
log_debug("normal: %s", dump_str("Hello World"));
log_debug("escapes: %s", dump_str("foo\r\nbar\a\001"));
log_debug("length: %s", dump_strn("<-->**", 4));
log_debug("overflow: %s", dump_str( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"));
log_debug("null: %s", dump_str(NULL));
log_debug("quote: %s", dump_str("foo\\bar'quux"));
}
void test_sock_test (void)
{
struct sock_test *sock = sock_test_create();
struct io_vec _read_data[] = {
{ "foo", 3 },
{ "barx", 4 }
};
const char *_write_data = "test data";
// put the read data
log_debug("set_recv_buffer: %p, %d", _read_data, 2);
sock_test_set_recv_buffer(sock, _read_data, 2, true);
// read it out
log_info("test sock_test_read");
assert_sock_read(SOCK_TEST_BASE(sock), "foo");
assert_sock_read(SOCK_TEST_BASE(sock), "ba");
assert_sock_read(SOCK_TEST_BASE(sock), "rx");
assert_sock_eof(SOCK_TEST_BASE(sock));
// write the data in
log_info("test sock_test_write");
assert_sock_write(SOCK_TEST_BASE(sock), "test ");
assert_sock_write(SOCK_TEST_BASE(sock), "data");
// check output
assert_sock_data(sock, _write_data);
// check output
assert_sock_data(sock, "");
// cleanup
sock_test_destroy(sock);
}
void assert_read_line (struct line_proto *lp, const char *line_str)
{
char *line_buf;
log_debug("expect: %s", dump_str(line_str));
assert_success(line_proto_recv(lp, &line_buf));
if (line_str) {
assert_strcmp(line_buf, line_str);
} else {
assert_strnull(line_buf);
}
}
/**
* Context info for test_line_proto callbacks
*/
struct _lp_test_ctx {
/** Expected line */
const char *line;
/** Expected error */
struct error_info err;
};
static void _lp_on_line (char *line, void *arg)
{
struct _lp_test_ctx *ctx = arg;
log_debug("%s", dump_str(line));
assert_strcmp(line, ctx->line);
ctx->line = NULL;
}
static void _lp_on_error (struct error_info *err, void *arg)
{
struct _lp_test_ctx *ctx = arg;
assert_error_info(err, &ctx->err);
}
static struct line_proto_callbacks _lp_callbacks = {
.on_line = &_lp_on_line,
.on_error = &_lp_on_error,
};
void test_line_proto (void)
{
struct sock_test *sock = sock_test_create();
struct io_vec _read_data[] = {
{ "hello\r\n", 7 },
{ "world\n", 6 },
{ "this ", 5 },
{ "is a line\r", 10 },
{ "\nfragment", 9 },
}, _trailing_data = { "\r\n", 2 };
struct line_proto *lp;
struct _lp_test_ctx ctx;
struct error_info err;
// put the read data
log_debug("set_recv_buffer: %p, %d", _read_data, 5);
sock_test_set_recv_buffer(sock, _read_data, 5, false);
// create the lp
assert_success(line_proto_create(&lp, SOCK_TEST_BASE(sock), 128, &_lp_callbacks, &ctx, &err));
log_info("test line_proto_recv");
// then read some lines from it
assert_read_line(lp, "hello");
assert_read_line(lp, "world");
assert_read_line(lp, "this is a line");
assert_read_line(lp, NULL);
// then add a final bit to trigger on_line
log_info("test on_line");
ctx.line = "fragment";
sock_test_add_recv_vec(sock, _trailing_data);
assert_strnull(ctx.line);
// test writing
log_info("test line_proto_send");
assert_success(-line_proto_send(lp, "foobar\r\n"));
assert_success(-line_proto_send(lp, "quux\r\n"));
assert_sock_data(sock, "foobar\r\nquux\r\n");
// XXX: test partial writes
// cleanup
line_proto_release(lp);
}
struct _test_irc_conn_ctx {
bool on_registered, on_TEST, on_error, on_quit;
};
static void _conn_on_registered (struct irc_conn *conn, void *arg)
{
struct _test_irc_conn_ctx *ctx = arg;
(void) conn;
if (ctx) ctx->on_registered = true;
log_debug("registered");
}
static void _conn_on_error (struct irc_conn *conn, struct error_info *err, void *arg)
{
struct _test_irc_conn_ctx *ctx = arg;
(void) conn;
(void) err;
if (ctx) ctx->on_error = true;
log_debug("on_error");
}
static void _conn_on_quit (struct irc_conn *conn, void *arg)
{
struct _test_irc_conn_ctx *ctx = arg;
(void) conn;
if (ctx) ctx->on_quit = true;
log_debug("on_quit");
}
static void _conn_on_TEST (const struct irc_line *line, void *arg)
{
struct _test_irc_conn_ctx *ctx = arg;
assert_null(line->source);
assert_strcmp(line->command, "TEST");
assert_strcmp(line->args[0], "arg0");
assert_strnull(line->args[1]);
if (ctx) ctx->on_TEST = true;
log_debug("on_TEST");
}
static struct irc_conn_callbacks _conn_callbacks = {
.on_registered = &_conn_on_registered,
.on_error = &_conn_on_error,
.on_quit = &_conn_on_quit,
};
static struct irc_cmd_handler _conn_handlers[] = {
{ "TEST", &_conn_on_TEST },
{ NULL, NULL }
};
struct irc_conn* setup_irc_conn (struct sock_test *sock, bool noisy, struct _test_irc_conn_ctx *ctx)
{
struct irc_conn *conn;
struct error_info err;
struct irc_conn_register_info register_info = {
"nick", "user", "realname"
};
// create the irc_conn
assert_success(irc_conn_create(&conn, SOCK_TEST_BASE(sock), &_conn_callbacks, ctx, &err));
// test register
if (noisy) log_info("test irc_conn_register");
assert_success(irc_conn_register(conn, ®ister_info));
assert_sock_data(sock, "NICK nick\r\nUSER user 0 * realname\r\n");
// test on_register callback
if (noisy) log_info("test irc_conn_callbacks.on_register");
test_sock_push(sock, "001 mynick :Blaa blaa blaa\r\n");
if (ctx) assert(ctx->on_registered);
assert_strcmp(conn->nickname, "mynick");
// ok
return conn;
}
void test_irc_conn (void)
{
struct sock_test *sock;
struct irc_conn *conn;
struct _test_irc_conn_ctx ctx = { false, false, false, false };
// create the test socket
assert((sock = sock_test_create()));
// setup the basic irc_conn
conn = setup_irc_conn(sock, true, &ctx);
// add our test handlers
assert_success(irc_conn_add_cmd_handlers(conn, _conn_handlers, &ctx));
// test on_TEST handler
// XXX: come up with a better prefix
log_info("test irc_conn.handlers");
test_sock_push(sock, ":foobar-prefix TEST arg0\r\n");
assert(ctx.on_TEST);
// test PING/PONG
log_info("test PING/PONG");
test_sock_push(sock, "PING foo\r\n");
assert_sock_data(sock, "PONG foo\r\n");
// quit nicely
log_info("test QUIT");
assert_success(irc_conn_QUIT(conn, "bye now"));
assert_sock_data(sock, "QUIT :bye now\r\n");
assert(conn->quitting);
test_sock_push(sock, "ERROR :Closing Link: Quit\r\n");
sock_test_set_recv_eof(sock);
assert(conn->quit && !conn->quitting && !conn->registered);
assert(ctx.on_quit);
assert(!ctx.on_error);
// destroy it
irc_conn_destroy(conn);
}
struct test_chan_ctx {
/** The channel we're supposed to be testing */
struct irc_chan *chan;
/** Flags for callbacks called*/
bool on_chan_self_join, on_chan_self_part, on_chan_join, on_chan_part;
};
void _on_chan_self_join (struct irc_chan *chan, void *arg)
{
struct test_chan_ctx *ctx = arg;
assert(chan == ctx->chan);
ctx->on_chan_self_join = true;
log_debug("on_self_join");
}
void _on_chan_join (struct irc_chan *chan, const struct irc_nm *source, void *arg)
{
struct test_chan_ctx *ctx = arg;
assert(chan == ctx->chan);
// XXX: verify source
ctx->on_chan_join = true;
log_debug("on_join");
}
void _on_chan_part (struct irc_chan *chan, const struct irc_nm *source, const char *msg, void *arg)
{
struct test_chan_ctx *ctx = arg;
assert(chan == ctx->chan);
// XXX: verify source
// XXX: verify msg
ctx->on_chan_part = true;
log_debug("on_part");
}
struct irc_chan_callbacks _chan_callbacks = {
.on_self_join = &_on_chan_self_join,
.on_join = &_on_chan_join,
.on_part = &_on_chan_part,
};
/**
* Setup an irc_net using the given socket, and consume the register request output, but do not push the RPL_WELCOME
*/
struct irc_net* setup_irc_net_unregistered (struct sock_test *sock)
{
struct irc_net *net;
struct irc_net_info net_info = {
.register_info = {
"nick", "user", "realname"
},
};
struct error_info err;
// create the irc_net
net_info.raw_sock = SOCK_TEST_BASE(sock);
assert_success(irc_net_create(&net, &net_info, &err));
// test register output
assert_sock_data(sock, "NICK nick\r\nUSER user 0 * realname\r\n");
// ok
return net;
}
/**
* Push to RPL_WELCOME reply, and test state
*/
void do_irc_net_welcome (struct sock_test *sock, struct irc_net *net)
{
// registration reply
test_sock_push(sock, "001 mynick :Blaa blaa blaa\r\n");
assert(net->conn->registered);
assert_strcmp(net->conn->nickname, "mynick");
}
/**
* Creates an irc_net and puts it into the registered state
*/
struct irc_net* setup_irc_net (struct sock_test *sock)
{
struct irc_net *net;
net = setup_irc_net_unregistered(sock);
do_irc_net_welcome(sock, net);
// ok
return net;
}
/**
* General test for irc_net to handle startup
*/
void test_irc_net (void)
{
struct sock_test *sock = setup_sock_test();
// create the network
log_info("test irc_net_create");
struct irc_net *net = setup_irc_net_unregistered(sock);
// send the registration reply
log_info("test irc_conn_on_RPL_WELCOME");
do_irc_net_welcome(sock, net);
// test errors by setting EOF
log_info("test irc_net_error");
sock_test_set_recv_eof(sock);
assert(net->conn == NULL);
// cleanup
irc_net_destroy(net);
}
/**
* Ensure that an irc_chan_user exists/doesn't exist for the given channel/nickname, and return it
*/
struct irc_chan_user* check_chan_user (struct irc_chan *chan, const char *nickname, bool exists)
{
struct irc_chan_user *chan_user = irc_chan_get_user(chan, nickname);
if (exists && chan_user == NULL)
FATAL("user %s not found in channel %s", dump_str(nickname), dump_str(irc_chan_name(chan)));
if (!exists && chan_user)
FATAL("user %s should not be on channel %s anymore", dump_str(nickname), dump_str(irc_chan_name(chan)));
log_debug("%s, exists=%d -> %p: user=%p, nickname=%s",
nickname, exists, chan_user, chan_user ? chan_user->user : NULL, chan_user ? chan_user->user->nickname : NULL);
if (chan_user)
assert_strcmp(chan_user->user->nickname, nickname);
return chan_user;
}
/**
* Creates an irc_chan on the given irc_net, but does not check any output (useful for testing offline add).
*
* You must pass a test_chan_ctx for use with later operations, this will be initialized for you.
*/
struct irc_chan* setup_irc_chan_raw (struct irc_net *net, struct test_chan_ctx *ctx)
{
struct irc_chan *chan;
struct irc_chan_info chan_info = {
.channel = "#test",
};
struct error_info err;
// initialize the given ctx
memset(ctx, 0, sizeof(*ctx));
// add a channel
assert_success(irc_net_add_chan(net, &chan, &chan_info, &err));
assert(!chan->joined);
assert_success(irc_chan_add_callbacks(chan, &_chan_callbacks, ctx));
ctx->chan = chan;
// ok
return chan;
}
/**
* Checks that the JOIN request for a channel was sent, and sends the basic JOIN reply
*/
void do_irc_chan_join (struct sock_test *sock, struct test_chan_ctx *ctx)
{
// JOIN request
assert(ctx->chan->joining);
assert_sock_data(sock, "JOIN #test\r\n");
// JOIN reply
test_sock_push(sock, ":mynick!user@host JOIN #test\r\n");
assert(!ctx->chan->joining && ctx->chan->joined);
assert(ctx->on_chan_self_join);
}
/**
* Sends a short RPL_NAMREPLY/RPL_ENDOFNAMES reply and checks that the users list matches
*/
void do_irc_chan_namreply (struct sock_test *sock, struct test_chan_ctx *ctx)
{
// RPL_NAMREPLY
test_sock_push(sock, "353 mynick = #test :mynick userA +userB @userC\r\n");
test_sock_push(sock, "366 mynick #test :End of NAMES\r\n");
// XXX: this should be an exclusive test, i.e. these should be the only ones...
check_chan_user(ctx->chan, "mynick", true);
check_chan_user(ctx->chan, "userA", true);
check_chan_user(ctx->chan, "userB", true);
check_chan_user(ctx->chan, "userC", true);
}
/**
* Creates an irc_chan on the given irc_net, and checks up to the JOIN reply
*/
struct irc_chan* setup_irc_chan_join (struct sock_test *sock, struct irc_net *net, struct test_chan_ctx *ctx)
{
setup_irc_chan_raw(net, ctx);
do_irc_chan_join(sock, ctx);
// ok
return ctx->chan;
}
/**
* Creates an irc_chan on the given irc_net, sends the JOIN stuff plus RPL_NAMREPLY
*/
struct irc_chan* setup_irc_chan (struct sock_test *sock, struct irc_net *net, struct test_chan_ctx *ctx)
{
setup_irc_chan_raw(net, ctx);
do_irc_chan_join(sock, ctx);
do_irc_chan_namreply(sock, ctx);
// ok
return ctx->chan;
}
/**
* Call irc_net_add_chan while offline, and ensure that we send the JOIN request after RPL_WELCOME, and handle the join
* reply OK.
*/
void test_irc_chan_add_offline (void)
{
struct test_chan_ctx ctx;
struct sock_test *sock = setup_sock_test();
log_info("test irc_net_create");
struct irc_net *net = setup_irc_net_unregistered(sock);
// add an offline channel
log_info("test offline irc_net_add_chan");
struct irc_chan *chan = setup_irc_chan_raw(net, &ctx);
assert(!chan->joining && !chan->joined);
// send the registration reply
log_info("test irc_conn_on_RPL_WELCOME");
do_irc_net_welcome(sock, net);
// test the join sequence
log_info("test irc_chan_join/irc_chan_on_JOIN");
do_irc_chan_join(sock, &ctx);
// cleanup
irc_net_destroy(net);
}
void test_irc_chan_namreply (void)
{
struct test_chan_ctx ctx;
struct sock_test *sock = setup_sock_test();
struct irc_net *net = setup_irc_net(sock);
setup_irc_chan_join(sock, net, &ctx);
log_info("test irc_chan_on_RPL_NAMREPLY");
do_irc_chan_namreply(sock, &ctx);
// cleanup
irc_net_destroy(net);
}
void test_irc_chan_user_join (void)
{
struct test_chan_ctx ctx;
struct sock_test *sock = setup_sock_test();
struct irc_net *net = setup_irc_net(sock);
struct irc_chan *chan = setup_irc_chan(sock, net, &ctx);
// have a user join
log_info("test irc_chan_on_JOIN");
test_sock_push(sock, ":newuser!someone@somewhere JOIN #test\r\n");
assert(ctx.on_chan_join);
check_chan_user(chan, "newuser", true);
// cleanup
irc_net_destroy(net);
}
void test_irc_chan_user_part (void)
{
struct test_chan_ctx ctx;
struct sock_test *sock = setup_sock_test();
struct irc_net *net = setup_irc_net(sock);
struct irc_chan *chan = setup_irc_chan(sock, net, &ctx);
// have a user join
log_info("test irc_chan_on_PART");
test_sock_push(sock, ":userA!someone@somewhere PART #test\r\n");
assert(ctx.on_chan_part); ctx.on_chan_part = NULL;
check_chan_user(chan, "userA", false);
// cleanup
irc_net_destroy(net);
}
/**
* Test definition
*/
static struct test {
/** Test name */
const char *name;
/** Test func */
void (*func) (void);
} _tests[] = {
{ "dump_str", &test_dump_str },
{ "sock_test", &test_sock_test },
{ "line_proto", &test_line_proto },
// XXX: irc_line_parse_invalid_prefix
{ "irc_conn", &test_irc_conn },
// XXX: irc_conn_self_nick
{ "irc_net", &test_irc_net },
{ "irc_chan_add_offline", &test_irc_chan_add_offline },
{ "irc_chan_namreply", &test_irc_chan_namreply },
{ "irc_chan_user_join", &test_irc_chan_user_join },
{ "irc_chan_user_part", &test_irc_chan_user_part },
{ NULL, NULL }
};
/**
* Command-line option codes
*/
enum option_code {
OPT_HELP = 'h',
OPT_DEBUG = 'd',
OPT_QUIET = 'q',
/** Options without short names */
_OPT_EXT_BEGIN = 0x00ff,
};
/**
* Command-line option definitions
*/
static struct option options[] = {
{"help", 0, NULL, OPT_HELP },
{"debug", 0, NULL, OPT_DEBUG },
{"quiet", 0, NULL, OPT_QUIET },
{0, 0, 0, 0 },
};
/**
* Display --help output on stdout
*/
static void usage (const char *exe)
{
printf("Usage: %s [OPTIONS]\n", exe);
printf("\n");
printf(" --help / -h display this message\n");
printf(" --debug / -d display DEBUG log messages\n");
printf(" --quiet / -q supress INFO log messages\n");
}
int main (int argc, char **argv)
{
struct test *test;
size_t test_count = 0;
int opt, option_index;
const char *filter = NULL;
// parse options
while ((opt = getopt_long(argc, argv, "hdq", options, &option_index)) != -1) {
switch (opt) {
case OPT_HELP:
usage(argv[0]);
exit(EXIT_SUCCESS);
case OPT_DEBUG:
set_log_level(LOG_DEBUG);
break;
case OPT_QUIET:
set_log_level(LOG_WARN);
break;
case '?':
usage(argv[0]);
exit(EXIT_FAILURE);
}
}
if (optind < argc) {
if (optind == argc - 1) {
// filter
filter = argv[optind];
log_info("only running tests: %s", filter);
} else {
FATAL("too many arguments");
}
}
// run tests
for (test = _tests; test->name; test++) {
if (filter && strcmp(test->name, filter))
continue;
log_info("Running test: %s", test->name);
test_count++;
test->func();
}
// no tests run?
if (test_count == 0)
FATAL("no tests run");
log_info("done, ran %zu tests", test_count);
}