refactor test.c into tests/* new-transport
authorTero Marttila <terom@fixme.fi>
Mon, 04 May 2009 20:55:04 +0300
branchnew-transport
changeset 168 a58ad50911fc
parent 167 0d2d8ca879d8
child 169 e6a1ce44aecc
refactor test.c into tests/*
src/CMakeLists.txt
src/line_proto.c
src/test.c
src/test/assert.c
src/test/assert.h
src/test/fifo.c
src/test/irc_chan.c
src/test/irc_chan.h
src/test/irc_conn.c
src/test/irc_conn.h
src/test/irc_net.c
src/test/irc_net.h
src/test/irc_queue.c
src/test/line_proto.c
src/test/str.c
src/test/test.c
src/test/test.h
src/test/test_list.c
src/test/test_list.inc
src/test/transport.c
src/test/transport.h
src/test/util.c
src/test/util.h
--- a/src/CMakeLists.txt	Sun May 03 17:19:36 2009 +0300
+++ b/src/CMakeLists.txt	Mon May 04 20:55:04 2009 +0300
@@ -15,9 +15,10 @@
 set (IRC_SOURCES irc_line.c irc_conn.c irc_net.c irc_chan.c chain.c irc_cmd.c irc_proto.c irc_client.c irc_user.c irc_queue.c irc_net_connect.c)
 set (LUA_SOURCES nexus_lua.c lua_objs.c lua_config.c lua_irc.c lua_func.c lua_type.c)
 set (CONSOLE_SOURCES console.c lua_console.c)
+file (GLOB _TEST_SOURCES "test/*.c")
 
 set (NEXUS_SOURCES nexus.c ${CORE_SOURCES} ${IO_SOURCES} ${IRC_SOURCES} ${LUA_SOURCES} ${CONSOLE_SOURCES} signals.c module.c config.c)
-set (TEST_SOURCES test.c ${CORE_SOURCES} ${IO_SOURCES} transport_test.c ${IRC_SOURCES})
+set (TEST_SOURCES ${_TEST_SOURCES} ${CORE_SOURCES} ${IO_SOURCES} transport_test.c ${IRC_SOURCES})
 set (IRC_LOG_SOURCES modules/irc_log.c)
 set (LOGWATCH_SOURCES modules/logwatch.c modules/logwatch_source.c modules/logwatch_filter.c modules/logwatch_chan.c)
 
@@ -26,7 +27,7 @@
 set (NEXUS_LIBRARIES ${LibEvent_LIBRARIES} ${GnuTLS_LIBRARIES} ${MODULE_LIBRARIES} "readline" ${Lua51_LIBRARIES})
 
 # compiler flags
-set (CFLAGS "-Wall -Wextra -std=gnu99")
+set (CMAKE_C_FLAGS "-Wall -Wextra -std=gnu99")
 
 # add our binaries
 add_executable (nexus ${NEXUS_SOURCES})
@@ -42,11 +43,6 @@
 target_link_libraries (irc_log ${Evsql_LIBRARIES})
 target_link_libraries (logwatch ${PCRE_LIBRARIES})
 
-# global target properties
-set_target_properties (nexus test irc_log logwatch PROPERTIES
-    COMPILE_FLAGS   ${CFLAGS}
-)
-
 # nexus needs to export its symbols to be able to load modules
 set_target_properties (nexus PROPERTIES
     LINK_FLAGS      "--export-dynamic"
--- a/src/line_proto.c	Sun May 03 17:19:36 2009 +0300
+++ b/src/line_proto.c	Mon May 04 20:55:04 2009 +0300
@@ -92,6 +92,7 @@
         return line_proto_set_error(lp);
 }
 
+// XXX: implement on_error!
 static const struct transport_callbacks line_proto_transport_callbacks = {
     .on_read    = &line_proto_on_read,
     .on_write   = &line_proto_on_write,
--- a/src/test.c	Sun May 03 17:19:36 2009 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1321 +0,0 @@
-/**
- * The main test code entry point
- */
-#include "transport_test.h"
-#include "line_proto.h"
-#include "irc_queue.h"
-#include "irc_conn.h"
-#include "irc_net.h"
-#include "fifo.h"
-#include "log.h"
-#include "str.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
-
-/**
- * Global test-running state
- */
-struct test_ctx {
-    /** The event_base that we have setup */
-    struct event_base *ev_base;
-
-} _test_ctx;
-
-
-/**
- * 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
-    char *buf = dump_buf[dump_idx++];
-    
-    // cycle
-    if (dump_idx >= DUMP_STR_COUNT)
-        dump_idx = 0;
-    
-    str_quote(buf, DUMP_STR_BUF, str, len);
-
-    // ok
-    return buf;
-}
-
-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_transport_read (transport_t *transport, const char *str)
-{
-    size_t len = strlen(str);
-    char buf[len];
-    error_t err;
-
-    log_debug("read: %p: %s", transport, dump_str(str));
-    
-    // read it
-    assert(transport_read(transport, buf, len, &err) == (int) len);
-
-    // cmp
-    assert_strncmp(buf, str, len);
-}
-
-void assert_transport_write (transport_t *transport, const char *str)
-{
-    size_t len = strlen(str);
-    error_t err;
-
-    log_debug("write: %p: %s", transport, dump_str(str));
-
-    // write it
-    assert(transport_write(transport, str, len, &err) == (int) len);
-}
-
-void assert_transport_eof (transport_t *transport)
-{
-    char buf;
-    error_t err;
-
-    log_debug("eof: %p", transport);
-
-    assert_err(-transport_read(transport, &buf, 1, &err), ERR_EOF);
-}
-
-void assert_transport_data (struct transport_test *tp, const char *fmt, ...)
-{
-    char buf[TRANSPORT_TEST_FMT_MAX];
-    va_list vargs;
-    size_t len;
-    
-    va_start(vargs, fmt);
-
-    if ((len = vsnprintf(buf, TRANSPORT_TEST_FMT_MAX, fmt, vargs)) >= TRANSPORT_TEST_FMT_MAX)
-        FATAL("input too long: %zu bytes", len);
-
-    va_end(vargs);
-
-    // get the data out
-    char *out;
-    
-    transport_test_pull_buf(tp, &out, &len);
-    
-    log_debug("pull_buf: %s", dump_strn(out, len));
-    
-    // should be the same
-    assert_strncmp(out, buf, len);
-    assert_strlen(buf, len);
-
-    // cleanup
-    free(out);
-}
-
-/**
- * Setup the global sock_stream state
- */
-struct event_base* setup_sock (void)
-{
-    struct event_base *ev_base;
-    struct error_info err;
-
-    assert((ev_base = event_base_new()));
-    assert_success(sock_init(ev_base, &err));
-
-    return ev_base;
-}
-
-/**
- * Create an empty transport_test
- */
-struct transport_test* setup_transport_test ()
-{
-    struct transport_test *tp;
-   
-    assert ((tp = transport_test_create(NULL)) != NULL);
-
-    return tp;
-}
-
-void assert_str_quote (size_t buf_size, const char *data, ssize_t len, const char *target, size_t out)
-{
-    char buf[buf_size];
-    
-    size_t ret = str_quote(buf, buf_size, data, len);
-
-    log_debug("str_quote(%zu, %zd) -> %s:%zu / %s:%zu", buf_size, len, buf, ret, target, out);
-
-    assert_strcmp(buf, target);
-    assert(ret == out);
-}
-
-void test_str_quote (void)
-{
-    log_info("testing str_quote()");
-
-    assert_str_quote(5,     NULL,           -1,     "NULL",         4   );
-    assert_str_quote(16,    "foo",          -1,     "'foo'",        5   );
-    assert_str_quote(16,    "foobar",       3,      "'foo'",        5   );
-    assert_str_quote(16,    "\r\n",         -1,     "'\\r\\n'",     6   );
-    assert_str_quote(16,    "\x13",         -1,     "'\\x13'",      6   );
-    assert_str_quote(16,    "x'y",          -1,     "'x\\'y'",      6   );
-    assert_str_quote(7,     "1234567890",   -1,     "'1'...",       12  );
-    assert_str_quote(9,     "1234567890",   -1,     "'123'...",     12  );
-}
-
-struct str_format_ctx {
-    const char *name;
-
-    const char *value;
-};
-
-err_t test_str_format_cb (const char *name, const char **value, ssize_t *value_len, void *arg)
-{
-    struct str_format_ctx *ctx = arg;
-
-    assert_strcmp(name, ctx->name);
-
-    *value = ctx->value;
-    *value_len = -1;
-
-    return SUCCESS;
-}
-
-void assert_str_format (const char *format, const char *name, const char *value, const char *out)
-{
-    struct str_format_ctx ctx = { name, value };
-    char buf[512];
-
-    assert_success(str_format(buf, sizeof(buf), format, test_str_format_cb, &ctx));
-    
-    log_debug("str_format(%s), { %s:%s } -> %s / %s", format, name, value, buf, out);
-
-    assert_strcmp(buf, out);
-}
-
-void test_str_format (void)
-{
-    log_info("test str_format()");
-
-    assert_str_format("foo", NULL, NULL, "foo");
-    assert_str_format("foo {bar} quux", "bar", "XXX", "foo XXX quux");
-}
-
-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_transport_test (void)
-{
-    struct transport_info info = { NULL, NULL, 0 };
-    struct transport_test *tp = transport_test_create(&info);
-    transport_t *transport = transport_test_cast(tp);
-
-    // put the read data
-    log_info("test transport_test_push_*");
-    transport_test_push_buf(tp, "foo", 3);
-    transport_test_push_str(tp, "barx");
-    transport_test_push_fmt(tp, "xx %s xx", "quux");
-    transport_test_push_eof(tp);
-    
-    // read it out
-    log_info("test transport_test_read");
-
-    assert_transport_read(transport, "foo");
-    assert_transport_read(transport, "ba");
-    assert_transport_read(transport, "rx");
-    assert_transport_read(transport, "xx quux xx");
-    assert_transport_eof(transport);
-
-    // write some data in
-    log_info("test transport_test_write");
-
-    assert_transport_write(transport, "test ");
-    assert_transport_write(transport, "data");
-    
-    // check output
-    log_info("test transport_test_pull_*");
-
-    assert_transport_data(tp, "test data");
-    assert_transport_data(tp, "");
-
-    // cleanup
-    transport_test_destroy(tp);
-}
-
-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 transport_test *tp = transport_test_create(NULL);
-    transport_t *transport = transport_test_cast(tp);
-    struct line_proto *lp;
-    struct _lp_test_ctx ctx;
-    struct error_info err;
-    
-    // put the read data
-    log_debug("transport_test_push_*");
-    transport_test_push_str(tp, "hello\r\n");
-    transport_test_push_str(tp, "world\n");
-    transport_test_push_str(tp, "this ");
-    transport_test_push_str(tp, "is a line\r");
-    transport_test_push_str(tp, "\nfragment");
-
-    // create the lp
-    assert_success(line_proto_create(&lp, transport, 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";
-    transport_test_push_str(tp, "\r\n");
-    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_transport_data(tp, "foobar\r\nquux\r\n");
-
-    // XXX: test partial writes
-
-    // cleanup
-    line_proto_destroy(lp);
-}
-
-void test_irc_queue (void)
-{
-    struct transport_test *tp = transport_test_create(NULL);
-    transport_t *transport = transport_test_cast(tp);
-    struct line_proto *lp;
-    struct irc_queue *queue;
-    struct irc_queue_entry *queue_entry;
-    struct error_info err;
-
-    // create the lp
-    assert_success(line_proto_create(&lp, transport, 128, &_lp_callbacks, NULL, &err));
-
-    // create the queue
-    assert_success(irc_queue_create(&queue, _test_ctx.ev_base, lp, &err));
-
-    struct irc_line line = {
-        NULL, "TEST", { "fooX" }
-    };
-
-    // then test simple writes, we should be able to push five lines directly
-    log_info("test irc_queue_process (irc_queue_send_direct)");
-    line.args[0] = "foo0"; assert_success(irc_queue_process(queue, &line));
-    line.args[0] = "foo1"; assert_success(irc_queue_process(queue, &line));
-    line.args[0] = "foo2"; assert_success(irc_queue_process(queue, &line));
-    line.args[0] = "foo3"; assert_success(irc_queue_process(queue, &line));
-    line.args[0] = "foo4"; assert_success(irc_queue_process(queue, &line));
-
-    // they should all be output
-    assert_transport_data(tp,
-            "TEST foo0\r\n"
-            "TEST foo1\r\n"
-            "TEST foo2\r\n"
-            "TEST foo3\r\n"
-            "TEST foo4\r\n"
-    );
-
-    // then enqueue
-    log_info("test irc_queue_process (irc_queue_put)");
-    line.args[0] = "foo5"; assert_success(irc_queue_process(queue, &line));
-
-    // ensure it was enqueued
-    assert((queue_entry = TAILQ_FIRST(&queue->list)) != NULL);
-    assert_strcmp(queue_entry->line_buf, "TEST foo5\r\n");
-
-    // ensure timer is set
-    assert(event_pending(queue->ev, EV_TIMEOUT, NULL));
-
-    // run the event loop to let the timer run
-    log_info("running the event loop once...");
-    assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0);
-
-    // test to check that the line was now sent
-    log_info("checking that the delayed line was sent...");
-    assert_transport_data(tp, "TEST foo5\r\n");
-    assert(TAILQ_EMPTY(&queue->list));
-    assert(!event_pending(queue->ev, EV_TIMEOUT, NULL));
-
-    // cleanup
-    irc_queue_destroy(queue);
-}
-
-struct test_conn_ctx {
-    /** Callback flags */
-    bool on_registered, on_TEST, on_error, on_quit;
-};
-
-static void _conn_on_registered (struct irc_conn *conn, void *arg)
-{
-    struct test_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_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_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_conn_ctx *ctx = arg;
-
-    assert(line->source);
-    assert(!line->source->nickname && !line->source->username && line->source->hostname);
-    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            }
-};
-
-/**
- * Create and return a new irc_conn with the given ctx (will be initialized to zero).
- */
-struct irc_conn* setup_irc_conn (struct transport_test *tp, bool noisy, struct test_conn_ctx *ctx)
-{
-    struct irc_conn *conn;
-    struct error_info err;
-    struct irc_conn_register_info register_info = {
-        "nick", "user", "realname"
-    };
-
-    // init the ctx
-    memset(ctx, 0, sizeof(*ctx));
-
-    // create the irc_conn
-    assert_success(irc_conn_create(&conn, transport_test_cast(tp), &_conn_callbacks, ctx, &err));
-
-    // test register
-    if (noisy) log_info("test irc_conn_register");
-    assert_success(irc_conn_register(conn, &register_info));
-    assert_transport_data(tp, "NICK nick\r\nUSER user 0 * realname\r\n");
- 
-    // test on_register callback    
-    if (noisy) log_info("test irc_conn_callbacks.on_register");
-    transport_test_push_str(tp, "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 test_conn_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_conn *conn = setup_irc_conn(tp, 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");
-    transport_test_push_str(tp, ":foobar-prefix TEST arg0\r\n");
-    assert(ctx.on_TEST);
-
-    // test PING/PONG
-    log_info("test PING/PONG");
-    transport_test_push_str(tp, "PING foo\r\n");
-    assert_transport_data(tp, "PONG foo\r\n");
-
-    // quit nicely
-    log_info("test QUIT");
-    assert_success(irc_conn_QUIT(conn, "bye now"));
-    assert_transport_data(tp, "QUIT :bye now\r\n");
-    assert(conn->quitting);
-
-    transport_test_push_str(tp, "ERROR :Closing Link: Quit\r\n");
-    transport_test_push_eof(tp);
-    assert(conn->quit && !conn->quitting && !conn->registered);
-    assert(ctx.on_quit);
-    assert(!ctx.on_error);
-
-    // destroy it
-    irc_conn_destroy(conn);
-}
-
-void test_irc_conn_self_nick (void)
-{
-    struct test_conn_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_conn *conn = setup_irc_conn(tp, false, &ctx);
-    
-    log_info("test irc_conn_on_NICK");
-    transport_test_push_fmt(tp, ":mynick!user@somehost NICK mynick2\r\n");
-    assert_strcmp(conn->nickname, "mynick2");
-
-    // cleanup
-    irc_conn_destroy(conn);
-}
-
-struct test_chan_ctx {
-    /** The channel name */
-    const char *channel;
-
-    /** 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 transport_test *tp)
-{
-    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.transport = transport_test_cast(tp);
-    assert_success(irc_net_create(&net, &net_info, &err));
-
-    // test register output
-    assert_transport_data(tp, "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 transport_test *tp, struct irc_net *net)
-{
-    // registration reply
-    transport_test_push_fmt(tp, "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 transport_test *tp)
-{
-    struct irc_net *net;   
-
-    net = setup_irc_net_unregistered(tp);
-    do_irc_net_welcome(tp, net);
-    
-    // ok
-    return net;
-}
-
-/**
- * General test for irc_net to handle startup
- */
-void test_irc_net (void)
-{
-    struct transport_test *tp = setup_transport_test();
-    
-    // create the network
-    log_info("test irc_net_create");
-    struct irc_net *net = setup_irc_net_unregistered(tp);
-
-    // send the registration reply
-    log_info("test irc_conn_on_RPL_WELCOME");
-    do_irc_net_welcome(tp, net);
-
-    // test errors by setting EOF
-    log_info("test irc_net_error");
-    transport_test_push_eof(tp);
-    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, const char *channel, struct test_chan_ctx *ctx)
-{
-    struct irc_chan *chan;
-    struct irc_chan_info chan_info = {
-        .channel        = channel,
-    };
-    struct error_info err;
-    
-    // initialize the given ctx
-    memset(ctx, 0, sizeof(*ctx));
-    ctx->channel = channel;
-
-    // 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 transport_test *tp, struct test_chan_ctx *ctx)
-{
-    // JOIN request
-    assert(ctx->chan->joining);
-    assert_transport_data(tp, "JOIN %s\r\n", ctx->channel);
-
-    // JOIN reply
-    transport_test_push_fmt(tp, ":mynick!user@host JOIN %s\r\n", ctx->channel);
-    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 transport_test *tp, struct test_chan_ctx *ctx)
-{
-    // RPL_NAMREPLY
-    transport_test_push_fmt(tp, "353 mynick = %s :mynick userA +userB @userC\r\n", ctx->channel);
-    transport_test_push_fmt(tp, "353 mynick = %s :trailingspace \r\n", ctx->channel);
-    transport_test_push_fmt(tp, "366 mynick %s :End of NAMES\r\n", ctx->channel);
-    
-    // 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 transport_test *tp, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
-{
-    setup_irc_chan_raw(net, channel, ctx);
-    do_irc_chan_join(tp, 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 transport_test *tp, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
-{
-    setup_irc_chan_raw(net, channel, ctx);
-    do_irc_chan_join(tp, ctx);
-    do_irc_chan_namreply(tp, 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 transport_test *tp = setup_transport_test();
-
-    log_info("test irc_net_create");
-    struct irc_net *net = setup_irc_net_unregistered(tp);
-
-    // add an offline channel
-    log_info("test offline irc_net_add_chan");
-    struct irc_chan *chan = setup_irc_chan_raw(net, "#test", &ctx);
-    assert(!chan->joining && !chan->joined);
-
-    // send the registration reply
-    log_info("test irc_conn_on_RPL_WELCOME");
-    do_irc_net_welcome(tp, net);
-    
-    // test the join sequence
-    log_info("test irc_chan_join/irc_chan_on_JOIN");
-    do_irc_chan_join(tp, &ctx);
-    
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_namreply (void)
-{
-    struct test_chan_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_net *net = setup_irc_net(tp);
-    setup_irc_chan_join(tp, net, "#test", &ctx);
-
-    log_info("test irc_chan_on_RPL_NAMREPLY");
-    do_irc_chan_namreply(tp, &ctx);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_user_join (void)
-{
-    struct test_chan_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_net *net = setup_irc_net(tp);
-    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
-
-    // have a user join
-    log_info("test irc_chan_on_JOIN");
-    transport_test_push_fmt(tp, ":newuser!someone@somewhere JOIN %s\r\n", "#test");
-    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 transport_test *tp = setup_transport_test();
-    struct irc_net *net = setup_irc_net(tp);
-    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
-
-    // have a user join
-    log_info("test irc_chan_on_PART");
-    transport_test_push_fmt(tp, ":userA!someone@somewhere PART %s\r\n", "#test");
-    assert(ctx.on_chan_part); ctx.on_chan_part = NULL;
-    check_chan_user(chan, "userA", false);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_user_kick (void)
-{
-    struct test_chan_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_net *net = setup_irc_net(tp);
-    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
-
-    // kick a user
-    log_info("test irc_chan_on_KICK (other)");
-    transport_test_push_fmt(tp, ":userA!someone@somewhere KICK %s userB\r\n", "#test");
-    check_chan_user(chan, "userA", true);
-    check_chan_user(chan, "userB", false);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_self_kick (void)
-{
-    struct test_chan_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_net *net = setup_irc_net(tp);
-    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
-
-    // kick a user
-    log_info("test irc_chan_on_KICK (self)");
-    transport_test_push_fmt(tp, ":userA!someone@somewhere KICK %s mynick foobar\r\n", "#test");
-    assert(!chan->joined);
-    assert(chan->kicked);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_user_nick (void)
-{
-    struct test_chan_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_net *net = setup_irc_net(tp);
-    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
-
-    // rename one of the users
-    log_info("test irc_net_on_chanuser");
-    transport_test_push_fmt(tp, ":userA!someone@somewhere NICK userA2\r\n");
-    check_chan_user(chan, "userA", false);
-    check_chan_user(chan, "userB", true);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_user_quit (void)
-{
-    struct test_chan_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_net *net = setup_irc_net(tp);
-    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
-
-    // rename one of the users
-    log_info("test irc_net_on_chanuser");
-    transport_test_push_fmt(tp, ":userA!someone@somewhere QUIT foo\r\n");
-    check_chan_user(chan, "userA", false);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void _test_irc_chan_on_CTCP_ACTION (const struct irc_line *line, void *arg)
-{
-    bool *flag = arg;
-
-    log_debug("CTCP ACTION");
-
-    *flag = true;
-}
-
-static struct irc_cmd_handler _test_irc_chan_handlers[] = {
-    {   "CTCP ACTION",      &_test_irc_chan_on_CTCP_ACTION  },
-    {   NULL,               NULL                            }
-};
-
-void test_irc_chan_CTCP_ACTION (void)
-{
-    struct test_chan_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_net *net = setup_irc_net(tp);
-    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
-    bool cb_ok = false;
-
-    // add our handler
-    assert_success(irc_cmd_add(&chan->handlers, _test_irc_chan_handlers, &cb_ok));
-
-    // rename one of the users
-    log_info("test irc_conn_on_CTCP_ACTION");
-    transport_test_push_fmt(tp, ":userA!someone@somewhere PRIVMSG #test \001ACTION hello  world\001\r\n");
-    assert(cb_ok);
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-void test_irc_chan_privmsg (void)
-{
-    struct test_chan_ctx ctx;
-    struct transport_test *tp = setup_transport_test();
-    struct irc_net *net = setup_irc_net(tp);
-    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
-
-    // rename one of the users
-    log_info("test irc_chan_PRIVMSG");
-    assert_success(irc_chan_PRIVMSG(chan, "foobar quux"));
-    assert_transport_data(tp, "PRIVMSG #test :foobar quux\r\n");
-
-    // cleanup
-    irc_net_destroy(net);
-}
-
-// XXX: needs to be split off into its own test_fifo.c
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <unistd.h>
-struct test_fifo_ctx {
-    /** Path to the fifo */
-    const char *path;
-
-    /** The write end */
-    int fd;
-
-    /** callback invoked? */
-    bool on_read;
-
-    /** Still running? */
-    bool run;
-};
-
-/**
- * Open the FIFO and write the test string to it
- */
-static void test_fifo_open_write (struct test_fifo_ctx *ctx)
-{
-    // ...raw, for writing
-    if ((ctx->fd = open(ctx->path, O_WRONLY)) < 0)
-        FATAL_PERROR("open");
-
-    // write something into it
-    if (write(ctx->fd, "test", 4) != 4)
-        FATAL_PERROR("write");
- 
-}
-
-static void test_fifo_close (struct test_fifo_ctx *ctx)
-{
-    close(ctx->fd);
-    ctx->fd = -1;
-}
-
-static void test_fifo_on_read (transport_t *fifo, void *arg)
-{
-    int ret;
-    char buf[16];
-    struct test_fifo_ctx *ctx = arg;
-    error_t err;
-
-    // read it back out
-    log_info("test fifo_read");
-    if ((ret = transport_read(fifo, buf, 16, &err)) < 0)
-        assert_success(-ret);
-
-    assert(ret == 4);
-    assert_strncmp(buf, "test", 4);
- 
-    if (ctx->on_read) {
-        test_fifo_close(ctx);
-        ctx->run = false;
-        return;
-    }
-   
-    // re-open the fifo
-    log_info("test fifo-re-open");
-    test_fifo_close(ctx);
-    test_fifo_open_write(ctx);
-
-    ctx->on_read = true;
-}
-
-static struct transport_callbacks test_fifo_callbacks = {
-    .on_read = test_fifo_on_read,
-};
-
-void test_fifo (void)
-{
-    transport_t *fifo;
-    struct error_info err;
-    struct test_fifo_ctx _ctx, *ctx = &_ctx; memset(ctx, 0, sizeof(*ctx));
-    struct transport_info info = { &test_fifo_callbacks, ctx, TRANSPORT_READ };
-
-    // XXX: requires that this be run in a suitable CWD
-    ctx->path = "test.fifo";
-
-    // create the fifo
-    if ((mkfifo(ctx->path, 0600) < 0) && (errno != EEXIST))
-        FATAL_PERROR("mkfifo");
-
-    // open it
-    log_info("test fifo_open_read");
-    assert_success(fifo_open_read(&info, &fifo, _test_ctx.ev_base, ctx->path, &err));
-    
-    // put some data into it
-    test_fifo_open_write(ctx);
-   
-    // run the event loop
-    log_debug("running the event loop...");
-    ctx->run = true;
-
-    while (ctx->run)
-        assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0);
-
-    // check
-    assert(ctx->fd < 0);
-    
-    // cleanup
-    transport_destroy(fifo);
-}
-
-/**
- * Test definition
- */
-struct test {
-    /** Test name */
-    const char *name;
-
-    /** Test func */
-    void (*func) (void);
-    
-    bool optional;
-};
-
-#define DEF_TEST(name) { #name, &test_ ## name, false }
-#define DEF_TEST_OPTIONAL(name) { #name, &test_ ## name, true }
-#define DEF_TEST_END { NULL, NULL, false }
-
-static struct test _tests[] = {
-    DEF_TEST(           str_quote                   ),
-    DEF_TEST(           str_format                  ),
-    DEF_TEST(           dump_str                    ),
-    DEF_TEST(           transport_test              ),
-    DEF_TEST(           line_proto                  ),
-    DEF_TEST(           irc_queue                   ),
-    // XXX: irc_line_parse_invalid_prefix
-    DEF_TEST(           irc_conn                    ),
-    DEF_TEST(           irc_conn_self_nick          ),
-    DEF_TEST(           irc_net                     ),
-    DEF_TEST(           irc_chan_add_offline        ),
-    DEF_TEST(           irc_chan_namreply           ),
-    DEF_TEST(           irc_chan_user_join          ),
-    DEF_TEST(           irc_chan_user_part          ),
-    DEF_TEST(           irc_chan_user_kick          ),
-    DEF_TEST(           irc_chan_self_kick          ),
-    DEF_TEST(           irc_chan_user_nick          ),
-    DEF_TEST(           irc_chan_user_quit          ),
-    DEF_TEST(           irc_chan_CTCP_ACTION        ),
-    DEF_TEST(           irc_chan_privmsg            ),
-    DEF_TEST_OPTIONAL(  fifo                        ),
-    DEF_TEST_END
-};
-
-/**
- * Command-line option codes
- */
-enum option_code {
-    OPT_HELP            = 'h',
-    OPT_DEBUG           = 'd',
-    OPT_QUIET           = 'q',
-    OPT_LIST            = 'l',
-    
-    /** 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       },
-    {"list",            0,  NULL,   OPT_LIST        },
-    {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");
-    printf(" --list / -l            list all tests\n");
-}
-
-static void list_tests (struct test *tests)
-{
-    struct test *test;
-    
-    printf("Available tests:\n");
-
-    for (test = tests; test->name; test++) {
-        printf("\t%s\n", test->name);
-    }
-}
-
-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, "hdql", 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 OPT_LIST:
-                list_tests(_tests);
-                exit(EXIT_SUCCESS);
-
-            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");
-        }
-    }
-
-    // setup the sockets stuff
-    _test_ctx.ev_base = setup_sock();
-
-    // run tests
-    for (test = _tests; test->name; test++) {
-        if ((filter && strcmp(test->name, filter)) || (!filter && test->optional))
-            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);
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/assert.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,63 @@
+#include "assert.h"
+#include "util.h"
+
+#include <string.h>
+
+#define ASSERT_FAIL(...) do { log_fatal(__VA_ARGS__); abort(); } while (0)
+
+void assert_true (bool cond, const char *msg)
+{
+    if (!cond)
+        ASSERT_FAIL("%s", msg);
+}
+
+void assert_null (const void *ptr)
+{
+    if (ptr)
+        ASSERT_FAIL("%p != NULL", ptr);
+}
+
+void assert_strcmp (const char *is, const char *should_be)
+{
+    if (!is || strcmp(is, should_be))
+        ASSERT_FAIL("%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))
+        ASSERT_FAIL("%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)
+        ASSERT_FAIL("strlen(%s) != %u", dump_str(str), (unsigned) n);
+}
+
+void assert_strnull (const char *str)
+{
+    if (str != NULL)
+        ASSERT_FAIL("%s != NULL", dump_str(str));
+}
+
+void assert_success (err_t err)
+{
+    if (err != SUCCESS)
+        ASSERT_FAIL("error: %s", error_name(err));
+}
+
+void assert_err (err_t err, err_t target)
+{
+    if (err != target)
+        ASSERT_FAIL("error: <%s> != <%s>", error_name(err), error_name(target));
+}
+
+void assert_error (error_t *is, error_t *should_be)
+{
+    if (ERROR_CODE(is) != ERROR_CODE(should_be) || ERROR_EXTRA(is) != ERROR_EXTRA(should_be))
+        // XXX: dual use of error_msg
+        ASSERT_FAIL("error: <%s> != <%s>", error_msg(is), error_msg(should_be));
+}
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/assert.h	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,63 @@
+#ifndef TEST_ASSERT_H
+#define TEST_ASSERT_H
+
+/**
+ * @file
+ *
+ * Various general assert-condition tests used to fail tests
+ */
+#include "../error.h"
+#include "../log.h"
+
+/*
+ * Also accept the existance of the system assert() function
+ */
+#include <assert.h>
+#include <stdbool.h>
+
+/**
+ * Assert that the given condition is true, and fail with the given error if not
+ */
+void assert_true (bool cond, const char *msg);
+
+/**
+ * Assert that the given pointer value is NULL.
+ */
+void assert_null (const void *ptr);
+
+/**
+ * Assert that the given NUL-terminated string matches the given target string exactly.
+ */
+void assert_strcmp (const char *is, const char *should_be);
+
+/**
+ * Assert that the first \a n chars of the first string matches the second string exactly.
+ */
+void assert_strncmp (const char *is, const char *should_be, size_t n);
+
+/**
+ * Assert that the given \a str is \a n chars long.
+ */
+void assert_strlen (const char *str, size_t n);
+
+/**
+ * Assert that the given \a str is NULL.
+ */
+void assert_strnull (const char *str);
+
+/**
+ * Assert that the given error code is SUCCESS.
+ */
+void assert_success (err_t err);
+
+/**
+ * Assert that the given actual error code \a err matches the expected error code \target.
+ */
+void assert_err (err_t err, err_t target);
+
+/**
+ * Assert that the given actual error \a is matches the expected error \a should_be
+ */ 
+void assert_error (error_t *is, error_t *should_be);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/fifo.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,111 @@
+#include "test.h"
+#include "../fifo.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+struct test_fifo_ctx {
+    /** Path to the fifo */
+    const char *path;
+
+    /** The write end */
+    int fd;
+
+    /** callback invoked? */
+    bool on_read;
+
+    /** Still running? */
+    bool run;
+};
+
+/**
+ * Open the FIFO and write the test string to it
+ */
+static void test_fifo_open_write (struct test_fifo_ctx *ctx)
+{
+    // ...raw, for writing
+    if ((ctx->fd = open(ctx->path, O_WRONLY)) < 0)
+        FATAL_PERROR("open");
+
+    // write something into it
+    if (write(ctx->fd, "test", 4) != 4)
+        FATAL_PERROR("write");
+ 
+}
+
+static void test_fifo_close (struct test_fifo_ctx *ctx)
+{
+    close(ctx->fd);
+    ctx->fd = -1;
+}
+
+static void test_fifo_on_read (transport_t *fifo, void *arg)
+{
+    int ret;
+    char buf[16];
+    struct test_fifo_ctx *ctx = arg;
+    error_t err;
+
+    // read it back out
+    log_info("test fifo_read");
+    if ((ret = transport_read(fifo, buf, 16, &err)) < 0)
+        assert_success(-ret);
+
+    assert(ret == 4);
+    assert_strncmp(buf, "test", 4);
+ 
+    if (ctx->on_read) {
+        test_fifo_close(ctx);
+        ctx->run = false;
+        return;
+    }
+   
+    // re-open the fifo
+    log_info("test fifo-re-open");
+    test_fifo_close(ctx);
+    test_fifo_open_write(ctx);
+
+    ctx->on_read = true;
+}
+
+static struct transport_callbacks test_fifo_callbacks = {
+    .on_read = test_fifo_on_read,
+};
+
+void test_fifo (void)
+{
+    transport_t *fifo;
+    struct error_info err;
+    struct test_fifo_ctx _ctx, *ctx = &_ctx; memset(ctx, 0, sizeof(*ctx));
+    struct transport_info info = { &test_fifo_callbacks, ctx, TRANSPORT_READ };
+
+    // XXX: requires that this be run in a suitable CWD
+    ctx->path = "test.fifo";
+
+    // create the fifo
+    if ((mkfifo(ctx->path, 0600) < 0) && (errno != EEXIST))
+        FATAL_PERROR("mkfifo");
+
+    // open it
+    log_info("test fifo_open_read");
+    assert_success(fifo_open_read(&info, &fifo, _test_ctx.ev_base, ctx->path, &err));
+    
+    // put some data into it
+    test_fifo_open_write(ctx);
+   
+    // run the event loop
+    log_debug("running the event loop...");
+    ctx->run = true;
+
+    while (ctx->run)
+        assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0);
+
+    // check
+    assert(ctx->fd < 0);
+    
+    // cleanup
+    transport_destroy(fifo);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_chan.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,340 @@
+#include "irc_chan.h"
+#include "irc_net.h"
+#include "test.h"
+
+
+static 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");
+}
+
+static 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");
+}
+
+static 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,
+};
+
+
+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.
+ */
+static struct irc_chan* setup_irc_chan_raw (struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
+{
+    struct irc_chan *chan;
+    struct irc_chan_info chan_info = {
+        .channel        = channel,
+    };
+    struct error_info err;
+    
+    // initialize the given ctx
+    memset(ctx, 0, sizeof(*ctx));
+    ctx->channel = channel;
+
+    // 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
+ */
+static void do_irc_chan_join (struct transport_test *tp, struct test_chan_ctx *ctx)
+{
+    // JOIN request
+    assert(ctx->chan->joining);
+    assert_transport_data(tp, "JOIN %s\r\n", ctx->channel);
+
+    // JOIN reply
+    transport_test_push_fmt(tp, ":mynick!user@host JOIN %s\r\n", ctx->channel);
+    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
+ */
+static void do_irc_chan_namreply (struct transport_test *tp, struct test_chan_ctx *ctx)
+{
+    // RPL_NAMREPLY
+    transport_test_push_fmt(tp, "353 mynick = %s :mynick userA +userB @userC\r\n", ctx->channel);
+    transport_test_push_fmt(tp, "353 mynick = %s :trailingspace \r\n", ctx->channel);
+    transport_test_push_fmt(tp, "366 mynick %s :End of NAMES\r\n", ctx->channel);
+    
+    // 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
+ */
+static struct irc_chan* setup_irc_chan_join (struct transport_test *tp, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
+{
+    setup_irc_chan_raw(net, channel, ctx);
+    do_irc_chan_join(tp, ctx);
+
+    // ok
+    return ctx->chan;
+}
+
+struct irc_chan* setup_irc_chan (struct transport_test *tp, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx)
+{
+    setup_irc_chan_raw(net, channel, ctx);
+    do_irc_chan_join(tp, ctx);
+    do_irc_chan_namreply(tp, ctx);
+
+    // ok
+    return ctx->chan;
+}
+
+
+void test_irc_chan_add_offline (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net_unregistered(tp);
+
+    // add an offline channel
+    log_info("test offline irc_net_add_chan");
+    struct irc_chan *chan = setup_irc_chan_raw(net, "#test", &ctx);
+    assert(!chan->joining && !chan->joined);
+
+    // send the registration reply
+    log_info("test irc_conn_on_RPL_WELCOME");
+    test_irc_net_welcome(tp, net);
+    
+    // test the join sequence
+    log_info("test irc_chan_join/irc_chan_on_JOIN");
+    do_irc_chan_join(tp, &ctx);
+    
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_namreply (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    setup_irc_chan_join(tp, net, "#test", &ctx);
+
+    log_info("test irc_chan_on_RPL_NAMREPLY");
+    do_irc_chan_namreply(tp, &ctx);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_user_join (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // have a user join
+    log_info("test irc_chan_on_JOIN");
+    transport_test_push_fmt(tp, ":newuser!someone@somewhere JOIN %s\r\n", "#test");
+    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 transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // have a user join
+    log_info("test irc_chan_on_PART");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere PART %s\r\n", "#test");
+    assert(ctx.on_chan_part); ctx.on_chan_part = NULL;
+    check_chan_user(chan, "userA", false);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_user_kick (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // kick a user
+    log_info("test irc_chan_on_KICK (other)");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere KICK %s userB\r\n", "#test");
+    check_chan_user(chan, "userA", true);
+    check_chan_user(chan, "userB", false);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_self_kick (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // kick a user
+    log_info("test irc_chan_on_KICK (self)");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere KICK %s mynick foobar\r\n", "#test");
+    assert(!chan->joined);
+    assert(chan->kicked);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_user_nick (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // rename one of the users
+    log_info("test irc_net_on_chanuser");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere NICK userA2\r\n");
+    check_chan_user(chan, "userA", false);
+    check_chan_user(chan, "userB", true);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_user_quit (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // rename one of the users
+    log_info("test irc_net_on_chanuser");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere QUIT foo\r\n");
+    check_chan_user(chan, "userA", false);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void _test_irc_chan_on_CTCP_ACTION (const struct irc_line *line, void *arg)
+{
+    bool *flag = arg;
+
+    log_debug("CTCP ACTION");
+
+    *flag = true;
+}
+
+static struct irc_cmd_handler _test_irc_chan_handlers[] = {
+    {   "CTCP ACTION",      &_test_irc_chan_on_CTCP_ACTION  },
+    {   NULL,               NULL                            }
+};
+
+void test_irc_chan_CTCP_ACTION (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+    bool cb_ok = false;
+
+    // add our handler
+    assert_success(irc_cmd_add(&chan->handlers, _test_irc_chan_handlers, &cb_ok));
+
+    // rename one of the users
+    log_info("test irc_conn_on_CTCP_ACTION");
+    transport_test_push_fmt(tp, ":userA!someone@somewhere PRIVMSG #test \001ACTION hello  world\001\r\n");
+    assert(cb_ok);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
+void test_irc_chan_privmsg (void)
+{
+    struct test_chan_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_net *net = setup_irc_net(tp);
+    struct irc_chan *chan = setup_irc_chan(tp, net, "#test", &ctx);
+
+    // rename one of the users
+    log_info("test irc_chan_PRIVMSG");
+    assert_success(irc_chan_PRIVMSG(chan, "foobar quux"));
+    assert_transport_data(tp, "PRIVMSG #test :foobar quux\r\n");
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_chan.h	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,38 @@
+#ifndef TEST_IRC_CHAN_H
+#define TEST_IRC_CHAN_H
+
+/**
+ * @file
+ *
+ * Utility functions for testing irc_chan
+ */
+#include "../irc_chan.h"
+#include "transport.h"
+
+/**
+ * Callback context
+ */
+struct test_chan_ctx {
+    /** The channel name */
+    const char *channel;
+
+    /** 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;
+
+};
+
+/**
+ * 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);
+
+/**
+ * Creates an irc_chan on the given irc_net, sends the JOIN stuff plus RPL_NAMREPLY
+ */
+struct irc_chan* setup_irc_chan (struct transport_test *tp, struct irc_net *net, const char *channel, struct test_chan_ctx *ctx);
+
+
+#endif /* TEST_IRC_CHAN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_conn.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,142 @@
+#include "irc_conn.h"
+#include "test.h"
+
+static void _conn_on_registered (struct irc_conn *conn, void *arg)
+{
+    struct test_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_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_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_conn_ctx *ctx = arg;
+
+    assert(line->source);
+    assert(!line->source->nickname && !line->source->username && line->source->hostname);
+    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 transport_test *tp, bool noisy, struct test_conn_ctx *ctx)
+{
+    struct irc_conn *conn;
+    struct error_info err;
+    struct irc_conn_register_info register_info = {
+        "nick", "user", "realname"
+    };
+
+    // init the ctx
+    memset(ctx, 0, sizeof(*ctx));
+
+    // create the irc_conn
+    assert_success(irc_conn_create(&conn, transport_test_cast(tp), &_conn_callbacks, ctx, &err));
+
+    // test register
+    if (noisy) log_info("test irc_conn_register");
+    assert_success(irc_conn_register(conn, &register_info));
+    assert_transport_data(tp, "NICK nick\r\nUSER user 0 * realname\r\n");
+ 
+    // test on_register callback    
+    if (noisy) log_info("test irc_conn_callbacks.on_register");
+    transport_test_push_str(tp, "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 test_conn_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_conn *conn = setup_irc_conn(tp, 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");
+    transport_test_push_str(tp, ":foobar-prefix TEST arg0\r\n");
+    assert(ctx.on_TEST);
+
+    // test PING/PONG
+    log_info("test PING/PONG");
+    transport_test_push_str(tp, "PING foo\r\n");
+    assert_transport_data(tp, "PONG foo\r\n");
+
+    // quit nicely
+    log_info("test QUIT");
+    assert_success(irc_conn_QUIT(conn, "bye now"));
+    assert_transport_data(tp, "QUIT :bye now\r\n");
+    assert(conn->quitting);
+
+    transport_test_push_str(tp, "ERROR :Closing Link: Quit\r\n");
+    transport_test_push_eof(tp);
+    assert(conn->quit && !conn->quitting && !conn->registered);
+    assert(ctx.on_quit);
+    assert(!ctx.on_error);
+
+    // destroy it
+    irc_conn_destroy(conn);
+}
+
+void test_irc_conn_self_nick (void)
+{
+    struct test_conn_ctx ctx;
+    struct transport_test *tp = setup_transport_test();
+    struct irc_conn *conn = setup_irc_conn(tp, false, &ctx);
+    
+    log_info("test irc_conn_on_NICK");
+    transport_test_push_fmt(tp, ":mynick!user@somehost NICK mynick2\r\n");
+    assert_strcmp(conn->nickname, "mynick2");
+
+    // cleanup
+    irc_conn_destroy(conn);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_conn.h	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,29 @@
+#ifndef TEST_IRC_CONN_H
+#define TEST_IRC_CONN_H
+
+/**
+ * @file
+ *
+ * Utility test functions for irc_conn related tests
+ */
+#include "../irc_conn.h"
+#include "transport.h"
+
+/**
+ * Callback flags
+ */
+struct test_conn_ctx {
+    /** Callback flags */
+    bool on_registered, on_TEST, on_error, on_quit;
+};
+
+/**
+ * Create and return a new irc_conn with the given ctx (will be initialized to zero).
+ *
+ * The returned irc_conn will be in the registered state.
+ *
+ * Use irc_conn_destroy to clean up the returned irc_conn.
+ */
+struct irc_conn* setup_irc_conn (struct transport_test *tp, bool noisy, struct test_conn_ctx *ctx);
+
+#endif /* TEST_IRC_CONN_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_net.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,67 @@
+#include "irc_net.h"
+#include "test.h"
+
+struct irc_net* setup_irc_net_unregistered (struct transport_test *tp)
+{
+    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.transport = transport_test_cast(tp);
+    assert_success(irc_net_create(&net, &net_info, &err));
+
+    // test register output
+    assert_transport_data(tp, "NICK nick\r\nUSER user 0 * realname\r\n");
+    
+    // ok
+    return net; 
+}
+
+void test_irc_net_welcome (struct transport_test *tp, struct irc_net *net)
+{
+    // registration reply
+    transport_test_push_fmt(tp, "001 mynick :Blaa blaa blaa\r\n");
+    assert(net->conn->registered);
+    assert_strcmp(net->conn->nickname, "mynick");
+
+}
+
+struct irc_net* setup_irc_net (struct transport_test *tp)
+{
+    struct irc_net *net;   
+
+    net = setup_irc_net_unregistered(tp);
+    test_irc_net_welcome(tp, net);
+    
+    // ok
+    return net;
+}
+
+void test_irc_net (void)
+{
+    struct transport_test *tp = setup_transport_test();
+
+    // XXX: test connected/connecting/disconnected/etc stuff
+    
+    // create the network
+    log_info("test irc_net_create");
+    struct irc_net *net = setup_irc_net_unregistered(tp);
+
+    // send the registration reply
+    log_info("test irc_conn_on_RPL_WELCOME");
+    test_irc_net_welcome(tp, net);
+
+    // test errors by setting EOF
+    log_info("test irc_net_error");
+    transport_test_push_eof(tp);
+    assert(net->conn == NULL);
+
+    // cleanup
+    irc_net_destroy(net);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_net.h	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,28 @@
+#ifndef TEST_IRC_NET_H
+#define TEST_IRC_NET_H
+
+/**
+ * @file
+ *
+ * Functionality for testing irc_net
+ */
+#include "../irc_net.h"
+#include "transport.h"
+
+/**
+ * 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 transport_test *tp);
+
+/**
+ * Push the RPL_WELCOME reply.
+ */
+void test_irc_net_welcome (struct transport_test *tp, struct irc_net *net);
+
+/**
+ * Creates an irc_net and puts it into the registered state
+ */
+struct irc_net* setup_irc_net (struct transport_test *tp);
+
+
+#endif /* TEST_IRC_NET_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/irc_queue.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,76 @@
+/**
+ * @file
+ *
+ * Test functions for the irc_queue module
+ */
+#include "test.h"
+#include "transport.h"
+
+#include "../irc_queue.h"
+
+static struct line_proto_callbacks _lp_callbacks = {
+    .on_line    = NULL,
+    .on_error   = NULL
+};
+
+void test_irc_queue (void)
+{
+    struct transport_test *tp = transport_test_create(NULL);
+    transport_t *transport = transport_test_cast(tp);
+    struct line_proto *lp;
+    struct irc_queue *queue;
+    struct irc_queue_entry *queue_entry;
+    struct error_info err;
+
+    // create the lp
+    assert_success(line_proto_create(&lp, transport, 128, &_lp_callbacks, NULL, &err));
+
+    // create the queue
+    assert_success(irc_queue_create(&queue, _test_ctx.ev_base, lp, &err));
+
+    struct irc_line line = {
+        NULL, "TEST", { "fooX" }
+    };
+
+    // then test simple writes, we should be able to push five lines directly
+    log_info("test irc_queue_process (irc_queue_send_direct)");
+    line.args[0] = "foo0"; assert_success(irc_queue_process(queue, &line));
+    line.args[0] = "foo1"; assert_success(irc_queue_process(queue, &line));
+    line.args[0] = "foo2"; assert_success(irc_queue_process(queue, &line));
+    line.args[0] = "foo3"; assert_success(irc_queue_process(queue, &line));
+    line.args[0] = "foo4"; assert_success(irc_queue_process(queue, &line));
+
+    // they should all be output
+    assert_transport_data(tp,
+            "TEST foo0\r\n"
+            "TEST foo1\r\n"
+            "TEST foo2\r\n"
+            "TEST foo3\r\n"
+            "TEST foo4\r\n"
+    );
+
+    // then enqueue
+    log_info("test irc_queue_process (irc_queue_put)");
+    line.args[0] = "foo5"; assert_success(irc_queue_process(queue, &line));
+
+    // ensure it was enqueued
+    assert((queue_entry = TAILQ_FIRST(&queue->list)) != NULL);
+    assert_strcmp(queue_entry->line_buf, "TEST foo5\r\n");
+
+    // ensure timer is set
+    assert(event_pending(queue->ev, EV_TIMEOUT, NULL));
+
+    // run the event loop to let the timer run
+    log_info("running the event loop once...");
+    assert(event_base_loop(_test_ctx.ev_base, EVLOOP_ONCE) == 0);
+
+    // test to check that the line was now sent
+    log_info("checking that the delayed line was sent...");
+    assert_transport_data(tp, "TEST foo5\r\n");
+    assert(TAILQ_EMPTY(&queue->list));
+    assert(!event_pending(queue->ev, EV_TIMEOUT, NULL));
+
+    // cleanup
+    irc_queue_destroy(queue);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/line_proto.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,107 @@
+/**
+ * @file
+ *
+ * Test functions for the line_proto module
+ */
+#include "test.h"
+#include "transport.h"
+
+#include "../line_proto.h"
+
+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(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 transport_test *tp = transport_test_create(NULL);
+    transport_t *transport = transport_test_cast(tp);
+    struct line_proto *lp;
+    struct _lp_test_ctx ctx;
+    struct error_info err;
+    
+    // put the read data
+    log_debug("transport_test_push_*");
+    transport_test_push_str(tp, "hello\r\n");
+    transport_test_push_str(tp, "world\n");
+    transport_test_push_str(tp, "this ");
+    transport_test_push_str(tp, "is a line\r");
+    transport_test_push_str(tp, "\nfragment");
+
+    // create the lp
+    assert_success(line_proto_create(&lp, transport, 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";
+    transport_test_push_str(tp, "\r\n");
+    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_transport_data(tp, "foobar\r\nquux\r\n");
+
+    // XXX: test partial writes
+
+    // cleanup
+    line_proto_destroy(lp);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/str.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,83 @@
+/**
+ * @file
+ *
+ * Test functions for the str module.
+ */
+#include "assert.h"
+
+void assert_str_quote (size_t buf_size, const char *data, ssize_t len, const char *target, size_t out)
+{
+    char buf[buf_size];
+    
+    size_t ret = str_quote(buf, buf_size, data, len);
+
+    log_debug("str_quote(%zu, %zd) -> %s:%zu / %s:%zu", buf_size, len, buf, ret, target, out);
+
+    assert_strcmp(buf, target);
+    assert(ret == out);
+}
+
+void test_str_quote (void)
+{
+    log_info("testing str_quote()");
+
+    assert_str_quote(5,     NULL,           -1,     "NULL",         4   );
+    assert_str_quote(16,    "foo",          -1,     "'foo'",        5   );
+    assert_str_quote(16,    "foobar",       3,      "'foo'",        5   );
+    assert_str_quote(16,    "\r\n",         -1,     "'\\r\\n'",     6   );
+    assert_str_quote(16,    "\x13",         -1,     "'\\x13'",      6   );
+    assert_str_quote(16,    "x'y",          -1,     "'x\\'y'",      6   );
+    assert_str_quote(7,     "1234567890",   -1,     "'1'...",       12  );
+    assert_str_quote(9,     "1234567890",   -1,     "'123'...",     12  );
+}
+
+struct str_format_ctx {
+    const char *name;
+
+    const char *value;
+};
+
+err_t test_str_format_cb (const char *name, const char **value, ssize_t *value_len, void *arg)
+{
+    struct str_format_ctx *ctx = arg;
+
+    assert_strcmp(name, ctx->name);
+
+    *value = ctx->value;
+    *value_len = -1;
+
+    return SUCCESS;
+}
+
+void assert_str_format (const char *format, const char *name, const char *value, const char *out)
+{
+    struct str_format_ctx ctx = { name, value };
+    char buf[512];
+
+    assert_success(str_format(buf, sizeof(buf), format, test_str_format_cb, &ctx));
+    
+    log_debug("str_format(%s), { %s:%s } -> %s / %s", format, name, value, buf, out);
+
+    assert_strcmp(buf, out);
+}
+
+void test_str_format (void)
+{
+    log_info("test str_format()");
+
+    assert_str_format("foo", NULL, NULL, "foo");
+    assert_str_format("foo {bar} quux", "bar", "XXX", "foo XXX quux");
+}
+
+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"));
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/test.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,160 @@
+#include "test.h"
+
+#include "../sock.h"
+
+#include <getopt.h>
+
+/**
+ * The global state
+ */
+struct test_ctx _test_ctx;
+
+/**
+ * Setup the global sock_stream state
+ */
+static struct event_base* setup_sock (void)
+{
+    struct event_base *ev_base;
+    struct error_info err;
+
+    assert((ev_base = event_base_new()));
+    assert_success(sock_init(ev_base, &err));
+
+    return ev_base;
+}
+
+/**
+ * Command-line option codes
+ */
+enum option_code {
+    OPT_HELP            = 'h',
+    OPT_DEBUG           = 'd',
+    OPT_QUIET           = 'q',
+    OPT_LIST            = 'l',
+    
+    /** 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       },
+    {"list",            0,  NULL,   OPT_LIST        },
+    {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");
+    printf(" --list / -l            list all tests\n");
+}
+
+/**
+ * Output the given list of tests on stdout
+ */
+static void list_tests (const struct test *tests)
+{
+    const struct test *test;
+    
+    printf("Available tests:\n");
+
+    for (test = tests; test->name; test++) {
+        printf("\t%s\n", test->name);
+    }
+}
+
+/**
+ * Run the given NULL-terminated list of tests, optionally filtering against the given filter.
+ *
+ * Returns the number of tests run, which may be zero.
+ */
+static size_t run_tests (const struct test tests[], const char *filter)
+{
+    size_t test_count = 0;
+    const struct test *test;
+
+    // run each test in turn
+    for (test = tests; test->name; test++) {
+        // filter out if given
+        if ((filter && strcmp(test->name, filter)) || (!filter && test->optional))
+            continue;
+
+        log_info("Running test: %s", test->name);
+        
+        // count and run
+        test_count++;
+        test->func();
+    }
+    
+    return test_count;
+}
+
+int main (int argc, char **argv)
+{
+    int opt, option_index;
+    const char *filter = NULL;
+
+    size_t test_count;
+
+    // parse options
+    while ((opt = getopt_long(argc, argv, "hdql", 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 OPT_LIST:
+                list_tests(_tests);
+                exit(EXIT_SUCCESS);
+
+            case '?':
+                usage(argv[0]);
+                exit(EXIT_FAILURE);
+        }
+    }
+
+    // parse positional arguments
+    if (optind < argc) {
+        if (optind == argc - 1) {
+            // filter
+            filter = argv[optind];
+            
+            log_info("only running tests: %s", filter);
+        } else {
+            FATAL("too many arguments");
+        }
+    }
+
+    // setup the sockets stuff
+    _test_ctx.ev_base = setup_sock();
+
+    // run tests
+    if ((test_count = run_tests(_tests, filter)) == 0)
+        FATAL("no tests run");
+    
+    // log
+    log_info("done, ran %zu tests", test_count);
+    
+    // ok
+    return EXIT_SUCCESS;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/test.h	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,41 @@
+#ifndef TEST_TEST_H
+#define TEST_TEST_H
+
+/**
+ * @file
+ *
+ * General test-related functionality
+ */
+#include "assert.h"
+#include "util.h"
+
+#include <event2/event.h>
+#include <string.h>
+
+/**
+ * Global test-running state
+ */
+extern struct test_ctx {
+    /** The event_base that we have setup */
+    struct event_base *ev_base;
+
+} _test_ctx;
+
+
+/**
+ * Global list of test definitions
+ */
+extern const struct test {
+    /** Test name */
+    const char *name;
+
+    /** Test func */
+    void (*func) (void);
+    
+    /** Do not run by default */    
+    bool optional;
+} _tests[];
+
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/test_list.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,21 @@
+#include "test.h"
+
+/**
+ * Test function prototypes
+ */
+#define TEST(name) extern void test_ ##name (void);
+
+    #include "test_list.inc"
+
+
+/**
+ * The array of test structs
+ */
+#define TEST(name) { #name, test_ ## name, false },
+#define TEST_OPTIONAL(name) { #name, test_ ## name, true },
+#define TEST_END { NULL, NULL, false }
+
+const struct test _tests[] = {
+    #include "test_list.inc"
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/test_list.inc	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,59 @@
+/**
+ * This include file acts as a "supermacro", calling a macro (TEST) for each defined test.
+ *
+ * All tests must be added to this list.
+ *
+ * TEST() macro signature:
+ *  #define TEST(name)
+ *  #define TEST_OPTIONAL(name)
+ *  #define TEST_END
+ */
+
+#ifndef TEST
+    /* Required */
+    #error TEST macro not defined
+#endif
+
+#ifndef TEST_OPTIONAL
+    /* Default to the same value as TEST() */
+    #define TEST_OPTIONAL(name) TEST(name)
+#endif
+
+
+/* Tests*/
+TEST ( str_quote                    )
+TEST ( str_format                   )
+TEST ( dump_str                     )
+TEST ( transport_test               )
+TEST ( line_proto                   )
+TEST ( irc_queue                    )
+TEST ( irc_conn                     )
+TEST ( irc_conn_self_nick           )
+TEST ( irc_net                      )
+TEST ( irc_chan_add_offline         )
+TEST ( irc_chan_namreply            )
+TEST ( irc_chan_user_join           )
+TEST ( irc_chan_user_part           )
+TEST ( irc_chan_user_kick           )
+TEST ( irc_chan_self_kick           )
+TEST ( irc_chan_user_nick           )
+TEST ( irc_chan_user_quit           )
+TEST ( irc_chan_CTCP_ACTION         )
+TEST ( irc_chan_privmsg             )
+
+/* Optional tests */
+TEST_OPTIONAL ( fifo                )
+
+/*
+ * End of list
+ */
+#ifdef TEST_END
+    TEST_END
+#endif    
+
+/*
+ * Cleanup
+ */
+#undef TEST
+#undef TEST_OPTIONAL
+#undef TEST_END
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/transport.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,114 @@
+#include "transport.h"
+#include "test.h"
+
+void assert_transport_read (transport_t *transport, const char *str)
+{
+    size_t len = strlen(str);
+    char buf[len];
+    error_t err;
+
+    log_debug("read: %p: %s", transport, dump_str(str));
+    
+    // read it
+    assert(transport_read(transport, buf, len, &err) == (int) len);
+
+    // cmp
+    assert_strncmp(buf, str, len);
+}
+
+void assert_transport_write (transport_t *transport, const char *str)
+{
+    size_t len = strlen(str);
+    error_t err;
+
+    log_debug("write: %p: %s", transport, dump_str(str));
+
+    // write it
+    assert(transport_write(transport, str, len, &err) == (int) len);
+}
+
+void assert_transport_eof (transport_t *transport)
+{
+    char buf;
+    error_t err;
+
+    log_debug("eof: %p", transport);
+
+    assert_err(-transport_read(transport, &buf, 1, &err), ERR_EOF);
+}
+
+void assert_transport_data (struct transport_test *tp, const char *fmt, ...)
+{
+    char buf[TRANSPORT_TEST_FMT_MAX];
+    va_list vargs;
+    size_t len;
+    
+    va_start(vargs, fmt);
+
+    if ((len = vsnprintf(buf, sizeof(buf), fmt, vargs)) >= sizeof(buf))
+        FATAL("input too long: %zu bytes", len);
+
+    va_end(vargs);
+
+    // get the data out
+    char *out;
+    
+    transport_test_pull_buf(tp, &out, &len);
+    
+    log_debug("pull_buf: %s", dump_strn(out, len));
+    
+    // should be the same
+    assert_strncmp(out, buf, len);
+    assert_strlen(buf, len);
+
+    // cleanup
+    free(out);
+}
+
+struct transport_test* setup_transport_test (void)
+{
+    struct transport_test *tp;
+   
+    assert ((tp = transport_test_create(NULL)) != NULL);
+
+    return tp;
+}
+
+void test_transport_test (void)
+{
+    struct transport_info info = { NULL, NULL, 0 };
+    struct transport_test *tp = transport_test_create(&info);
+    transport_t *transport = transport_test_cast(tp);
+
+    // put the read data
+    log_info("test transport_test_push_*");
+    transport_test_push_buf(tp, "foo", 3);
+    transport_test_push_str(tp, "barx");
+    transport_test_push_fmt(tp, "xx %s xx", "quux");
+    transport_test_push_eof(tp);
+    
+    // read it out
+    log_info("test transport_test_read");
+
+    assert_transport_read(transport, "foo");
+    assert_transport_read(transport, "ba");
+    assert_transport_read(transport, "rx");
+    assert_transport_read(transport, "xx quux xx");
+    assert_transport_eof(transport);
+
+    // write some data in
+    log_info("test transport_test_write");
+
+    assert_transport_write(transport, "test ");
+    assert_transport_write(transport, "data");
+    
+    // check output
+    log_info("test transport_test_pull_*");
+
+    assert_transport_data(tp, "test data");
+    assert_transport_data(tp, "");
+
+    // cleanup
+    transport_test_destroy(tp);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/transport.h	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,44 @@
+#ifndef TEST_TRANSPORT_H
+#define TEST_TRANSPORT_H
+
+/**
+ * @file
+ *
+ * Functions for interacting with transports
+ */
+#include "../transport_test.h"
+
+/**
+ * Read strlen(str) bytes from the given transport, and assert that they match the given string.
+ *
+ * Note that this only performs one transport_read.
+ */
+void assert_transport_read (transport_t *transport, const char *str);
+
+/**
+ * Write strlen(str) bytes to the given transport, and assert that they all get written.
+ *
+ * Note that this only performs one transport_write.
+ */
+void assert_transport_write (transport_t *transport, const char *str);
+
+/**
+ * Attempt to read a single byte from the given transport, and assert that the attempt returns ERR_EOF.
+ */
+void assert_transport_eof (transport_t *transport);
+
+/**
+ * Compare the written data stored in the given transport_test with the string obtained using the given format and args.
+ *
+ * This will pull /all/ of the data in the transport.
+ */
+void assert_transport_data (struct transport_test *tp, const char *fmt, ...);
+
+/**
+ * Create an empty transport_test, with NULL callbacks.
+ *
+ * You must bind a new set of callbacks to the transport before pushing any data to it.
+ */
+struct transport_test* setup_transport_test (void);
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/util.c	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,27 @@
+#include "util.h"
+#include "../str.h"
+
+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
+    char *buf = dump_buf[dump_idx++];
+    
+    // cycle
+    if (dump_idx >= DUMP_STR_COUNT)
+        dump_idx = 0;
+    
+    // write the quoted string into the selected buf
+    str_quote(buf, DUMP_STR_BUF, str, len);
+
+    // ok
+    return buf;
+}
+
+const char *dump_str (const char *str) 
+{
+    return dump_strn(str, -1);
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/test/util.h	Mon May 04 20:55:04 2009 +0300
@@ -0,0 +1,34 @@
+#ifndef TEST_UTIL_H
+#define TEST_UTIL_H
+
+/**
+ * @file
+ *
+ * General utility functions for tests
+ */
+#include <sys/types.h>
+
+#define DUMP_STR_BUF 1024
+#define DUMP_STR_COUNT 8
+
+/**
+ * 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 bytes, 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);
+
+/**
+ * As dump_strn(), with str as a NUL-terminated string
+ */
+const char *dump_str (const char *str); 
+
+#endif