# HG changeset patch # User David Demelier # Date 1610833710 -3600 # Node ID b4e8551e2064ce400e6245812e0b0e9604070841 # Parent 21a91311c8eaaf73fb0807a40b5f5a56dc15fb48 server: several improvements diff -r 21a91311c8ea -r b4e8551e2064 .hgignore --- a/.hgignore Sat Jan 16 17:58:46 2021 +0100 +++ b/.hgignore Sat Jan 16 22:48:30 2021 +0100 @@ -11,32 +11,10 @@ \.swp$ \.swo$ -# libcompat. -^extern/libcompat/src/.*\.h$ -^extern/libcompat/trycompile$ -^extern/libcompat/tryinclude$ -^extern/libcompat/trylib$ - # executables. ^irccd/irccd$ ^irccdctl/irccdctl$ -# temporary files. -^lib/irccd/config\.h$ -\.a$ -\.o$ -\.d$ -\.so$ -\.dylib$ - -# tests. -^tests/test-channel$ -^tests/test-dl-plugin$ -^tests/test-log$ -^tests/test-rule$ -^tests/test-subst$ -^tests/test-util$ - # macOS specific. \.DS_Store$ \.dSYM diff -r 21a91311c8ea -r b4e8551e2064 irccd/main.c --- a/irccd/main.c Sat Jan 16 17:58:46 2021 +0100 +++ b/irccd/main.c Sat Jan 16 22:48:30 2021 +0100 @@ -45,9 +45,9 @@ irc_bot_init(); irc_transport_bind("/tmp/irccd.sock"); - irc_server_join(&s, "#test", NULL); - irc_js_plugin_open(&p, "test.js"); - irc_bot_add_server(irc_util_memdup(&s, sizeof (s))); + //irc_server_join(&s, "#test", NULL); + irc_js_plugin_open(&p, "/Users/markand/test.js"); + //irc_bot_add_server(irc_util_memdup(&s, sizeof (s))); irc_bot_add_plugin(&p); irc_bot_run(); } diff -r 21a91311c8ea -r b4e8551e2064 lib/CMakeLists.txt --- a/lib/CMakeLists.txt Sat Jan 16 17:58:46 2021 +0100 +++ b/lib/CMakeLists.txt Sat Jan 16 22:48:30 2021 +0100 @@ -41,6 +41,7 @@ irccd/server.c irccd/server.h irccd/set.h + irccd/list.h irccd/subst.c irccd/subst.h irccd/transport.c diff -r 21a91311c8ea -r b4e8551e2064 lib/irccd/irccd.c --- a/lib/irccd/irccd.c Sat Jan 16 17:58:46 2021 +0100 +++ b/lib/irccd/irccd.c Sat Jan 16 22:48:30 2021 +0100 @@ -26,6 +26,7 @@ #include "event.h" #include "irccd.h" +#include "list.h" #include "log.h" #include "peer.h" #include "plugin.h" @@ -113,6 +114,9 @@ static void process(struct pkg *pkg) { + struct irc_server *s; + struct irc_peer peer; + struct irc_event ev; if (poll(pkg->fds, pkg->fdsz, 1000) < 0) err(1, "poll"); @@ -123,18 +127,11 @@ * not. */ for (size_t i = 0; i < pkg->fdsz; ++i) { - struct irc_peer peer; - pipe_flush(&pkg->fds[i]); -#if 0 - for (size_t s = 0; s < irc.serversz; ++s) - irc_server_flush(irc.servers[s], &pkg->fds[i]); -#endif - for (struct irc_server *s = irc.servers; s; s = s->next) + IRC_LIST_FOREACH(irc.servers, s) irc_server_flush(s, &pkg->fds[i]); - /* Accept new transport client. */ if (irc_transport_flush(&pkg->fds[i], &peer)) IRC_SET_ALLOC_PUSH(&irc.peers, &irc.peersz, &peer, cmp_peer); @@ -153,12 +150,9 @@ * For every server, poll any kind of new event and pass them to the * plugin unless the rules explicitly disallow us to do so. */ - for (struct irc_server *s = irc.servers; s; s = s->next) { - struct irc_event ev; - + IRC_LIST_FOREACH(irc.servers, s) while (irc_server_poll(s, &ev)) invoke(&ev); - } } static void @@ -184,8 +178,8 @@ irc_server_incref(s); irc_server_connect(s); - s->next = irc.servers; - irc.servers = s; + IRC_LIST_ADD(irc.servers, s); + irc.serversz++; } @@ -204,7 +198,7 @@ void irc_bot_remove_server(const char *name) { - struct irc_server *s, *p; + struct irc_server *s; if (!(s = irc_bot_find_server(name))) return; @@ -217,17 +211,7 @@ .server = s }); - if (s == irc.servers) - irc.servers = irc.servers->next; - else { - /* x -> y -> z */ - /* ^ */ - /* s */ - for (p = irc.servers->next; p->next != s; p = p->next) - continue; - - p->next = s->next; - } + IRC_LIST_REMOVE(irc.servers, s); irc_server_decref(s); irc.serversz--; @@ -238,14 +222,8 @@ { struct irc_server *s, *next; - if (!(s = irc.servers)) - return; - - while (s) { - next = s->next; + IRC_LIST_FOREACH_SAFE(irc.servers, s, next) irc_bot_remove_server(s->name); - s = next; - } } void diff -r 21a91311c8ea -r b4e8551e2064 lib/irccd/jsapi-server.c --- a/lib/irccd/jsapi-server.c Sat Jan 16 17:58:46 2021 +0100 +++ b/lib/irccd/jsapi-server.c Sat Jan 16 22:48:30 2021 +0100 @@ -23,6 +23,7 @@ #include "channel.h" #include "irccd.h" #include "jsapi-server.h" +#include "list.h" #include "server.h" #include "util.h" @@ -60,6 +61,79 @@ return sv; } +static inline void +get_port(duk_context *ctx, struct irc_server *s) +{ + duk_get_prop_string(ctx, 0, "port"); + + if (!duk_is_number(ctx, -1)) + duk_error(ctx, DUK_ERR_ERROR, "invalid 'port' property"); + + s->port = duk_to_int(ctx, -1); + duk_pop(ctx); +} + +static inline void +get_ip(duk_context *ctx, struct irc_server *s) +{ + enum irc_server_flags flags = IRC_SERVER_FLAGS_IPV4 | + IRC_SERVER_FLAGS_IPV6; + + duk_get_prop_string(ctx, 0, "ipv4"); + duk_get_prop_string(ctx, 0, "ipv6"); + + if (duk_is_boolean(ctx, -1) && !duk_to_boolean(ctx, -1)) + flags &= ~(IRC_SERVER_FLAGS_IPV4); + if (duk_is_boolean(ctx, -2) && !duk_to_boolean(ctx, -2)) + flags &= ~(IRC_SERVER_FLAGS_IPV6); + + s->flags |= flags; + duk_pop_n(ctx, 2); +} + +static inline void +get_ssl(duk_context *ctx, struct irc_server *s) +{ + duk_get_prop_string(ctx, 0, "ssl"); + + if (duk_is_boolean(ctx, -1) && duk_to_boolean(ctx, -1)) + s->flags |= IRC_SERVER_FLAGS_SSL; + + duk_pop(ctx); +} + +static inline void +get_string(duk_context *ctx, const char *n, bool required, char *dst, size_t dstsz) +{ + duk_get_prop_string(ctx, 0, n); + + if (duk_is_string(ctx, -1)) + strlcpy(dst, duk_to_string(ctx, -1), dstsz); + else if (required) + duk_error(ctx, DUK_ERR_ERROR, "invalid or missing '%s' property", n); + + duk_pop(ctx); +} + +static inline void +get_channels(duk_context *ctx, struct irc_server *s) +{ + duk_get_prop_string(ctx, 0, "channels"); + + for (duk_enum(ctx, -1, 0); duk_next(ctx, -1, true); ) { + duk_get_prop_string(ctx, -1, "name"); + duk_get_prop_string(ctx, -2, "password"); + + if (!duk_is_string(ctx, -2)) + duk_error(ctx, DUK_ERR_ERROR, "invalid channel 'name' property"); + + irc_server_join(s, duk_to_string(ctx, -2), duk_opt_string(ctx, -1, NULL)); + duk_pop_n(ctx, 4); + } + + duk_pop_n(ctx, 2); +} + static duk_ret_t Server_prototype_info(duk_context *ctx) { @@ -362,89 +436,6 @@ return 1; } -static inline void -get_name(duk_context *ctx, struct irc_server *s) -{ - duk_get_prop_string(ctx, 0, "name"); - - if (!duk_is_string(ctx, -1)) - duk_error(ctx, DUK_ERR_ERROR, "invalid 'name' property"); - - strlcpy(s->name, duk_to_string(ctx, -1), sizeof (s->name)); - duk_pop(ctx); -} - -static inline void -get_port(duk_context *ctx, struct irc_server *s) -{ - duk_get_prop_string(ctx, 0, "port"); - - if (!duk_is_number(ctx, -1)) - duk_error(ctx, DUK_ERR_ERROR, "invalid 'port' property"); - - s->port = duk_to_int(ctx, -1); - duk_pop(ctx); -} - -static inline void -get_ip(duk_context *ctx, struct irc_server *s) -{ - enum irc_server_flags flags = IRC_SERVER_FLAGS_IPV4 | - IRC_SERVER_FLAGS_IPV6; - - duk_get_prop_string(ctx, 0, "ipv4"); - duk_get_prop_string(ctx, 0, "ipv6"); - - if (duk_is_boolean(ctx, -1) && !duk_to_boolean(ctx, -1)) - flags &= ~(IRC_SERVER_FLAGS_IPV4); - if (duk_is_boolean(ctx, -2) && !duk_to_boolean(ctx, -2)) - flags &= ~(IRC_SERVER_FLAGS_IPV6); - - s->flags |= flags; - duk_pop_n(ctx, 2); -} - -static inline void -get_ssl(duk_context *ctx, struct irc_server *s) -{ - duk_get_prop_string(ctx, 0, "ssl"); - - if (duk_is_boolean(ctx, -1) && duk_to_boolean(ctx, -1)) - s->flags |= IRC_SERVER_FLAGS_SSL; - - duk_pop(ctx); -} - -static inline void -get_string(duk_context *ctx, const char *n, char *dst, size_t dstsz) -{ - duk_get_prop_string(ctx, 0, n); - - if (duk_is_string(ctx, -1) && duk_is_string(ctx ,-1)) - strlcpy(dst, duk_to_string(ctx, -1), dstsz); - - duk_pop(ctx); -} - -static inline void -get_channels(duk_context *ctx, struct irc_server *s) -{ - duk_get_prop_string(ctx, 0, "channels"); - - for (duk_enum(ctx, -1, 0); duk_next(ctx, -1, true); ) { - duk_get_prop_string(ctx, -1, "name"); - duk_get_prop_string(ctx, -2, "password"); - - if (!duk_is_string(ctx, -2)) - duk_error(ctx, DUK_ERR_ERROR, "invalid channel 'name' property"); - - irc_server_join(s, duk_to_string(ctx, -2), duk_opt_string(ctx, -1, NULL)); - duk_pop_n(ctx, 4); - } - - duk_pop_n(ctx, 2); -} - static duk_ret_t Server_constructor(duk_context *ctx) { @@ -452,14 +443,15 @@ duk_require_object(ctx, 0); - get_name(ctx, &s); + get_string(ctx, "name", true, s.name, sizeof (s.name)); + get_string(ctx, "hostname", true, s.hostname, sizeof (s.hostname)); get_port(ctx, &s); get_ip(ctx, &s); get_ssl(ctx, &s); - get_string(ctx, "nickname", s.nickname, sizeof (s.nickname)); - get_string(ctx, "username", s.username, sizeof (s.username)); - get_string(ctx, "realname", s.realname, sizeof (s.realname)); - get_string(ctx, "commandChar", s.commandchar, sizeof (s.commandchar)); + get_string(ctx, "nickname", false, s.nickname, sizeof (s.nickname)); + get_string(ctx, "username", false, s.username, sizeof (s.username)); + get_string(ctx, "realname", false, s.realname, sizeof (s.realname)); + get_string(ctx, "commandChar", false, s.commandchar, sizeof (s.commandchar)); get_channels(ctx, &s); p = irc_util_memdup(&s, sizeof (s)); @@ -473,8 +465,6 @@ return 0; } -#if 0 - static duk_ret_t Server_destructor(duk_context *ctx) { @@ -491,8 +481,6 @@ return 0; } -#endif - static duk_ret_t Server_add(duk_context *ctx) { @@ -520,9 +508,11 @@ static duk_ret_t Server_list(duk_context *ctx) { + struct irc_server *s; + duk_push_object(ctx); - for (struct irc_server *s = irc.servers; s; s = s->next) { + IRC_LIST_FOREACH(irc.servers, s) { irc_jsapi_server_push(ctx, s); duk_put_prop_string(ctx, -2, s->name); } @@ -577,6 +567,8 @@ duk_put_function_list(ctx, -1, functions); duk_push_object(ctx); duk_put_function_list(ctx, -1, methods); + duk_push_c_function(ctx, Server_destructor, 1); + duk_set_finalizer(ctx, -2); duk_dup_top(ctx); duk_put_global_string(ctx, PROTOTYPE); duk_put_prop_string(ctx, -2, "prototype"); diff -r 21a91311c8ea -r b4e8551e2064 lib/irccd/list.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/irccd/list.h Sat Jan 16 22:48:30 2021 +0100 @@ -0,0 +1,48 @@ +/* + * list.h -- generic macros to manipulate linked lists + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCD_LIST_H +#define IRCD_LIST_H + +#define IRC_LIST_ADD(h, o) \ +do { \ + if ((h)) \ + (h)->prev = (o); \ + (o)->next = (h); \ + (h) = (o); \ +} while (0) + +#define IRC_LIST_REMOVE(h, o) \ +do { \ + if ((o)->prev) \ + (o)->prev->next = (o)->next; \ + if ((o)->next) \ + (o)->next->prev = (o)->prev; \ + if ((o) == (h)) \ + (h) = (o)->next; \ + (o)->next = (o)->prev = NULL; \ +} while (0) + + +#define IRC_LIST_FOREACH(h, s) \ + for ((s) = (h); (s); (s) = (s)->next) + +#define IRC_LIST_FOREACH_SAFE(h, s, tmp) \ + for ((s) = (h); s && ((tmp) = (s)->next, 1); (s) = (tmp)) + +#endif /* IRC_LIST_H */ diff -r 21a91311c8ea -r b4e8551e2064 lib/irccd/server.c --- a/lib/irccd/server.c Sat Jan 16 17:58:46 2021 +0100 +++ b/lib/irccd/server.c Sat Jan 16 22:48:30 2021 +0100 @@ -573,6 +573,26 @@ } } +static bool +set_nonblock(struct irc_server *s) +{ + int cflags = 0; + + if ((cflags = fcntl(s->fd, F_GETFL)) < 0 || fcntl(s->fd, F_SETFL, cflags | O_NONBLOCK) < 0) + return false; + + return true; +} + +static bool +create(struct irc_server *s) +{ + s->fd = socket(s->aip->ai_family, s->aip->ai_socktype, + s->aip->ai_protocol); + + return set_nonblock(s); +} + static void dial(struct irc_server *s) { @@ -583,27 +603,15 @@ } for (; s->aip; s->aip = s->aip->ai_next) { - int cflags; - /* We may need to close a socket that was open earlier. */ if (s->fd != 0) close(s->fd); - s->fd = socket(s->aip->ai_family, s->aip->ai_socktype, - s->aip->ai_protocol); - - if (s->fd < 0) { + if (!create(s)) { irc_log_warn("server %s: %s", s->name, strerror(errno)); continue; } - if ((cflags = fcntl(s->fd, F_GETFL)) < 0) { - irc_log_warn("server %s: %s", s->name, strerror(errno)); - continue; - } - - fcntl(s->fd, F_SETFL, cflags | O_NONBLOCK); - /* * With some luck, the connection completes immediately, * otherwise we will need to wait until the socket is writable. diff -r 21a91311c8ea -r b4e8551e2064 lib/irccd/server.h --- a/lib/irccd/server.h Sat Jan 16 17:58:46 2021 +0100 +++ b/lib/irccd/server.h Sat Jan 16 22:48:30 2021 +0100 @@ -102,6 +102,7 @@ /* Reference count. */ size_t refc; struct irc_server *next; + struct irc_server *prev; /* IRC server settings. */ char chantypes[8]; diff -r 21a91311c8ea -r b4e8551e2064 tests/CMakeLists.txt --- a/tests/CMakeLists.txt Sat Jan 16 17:58:46 2021 +0100 +++ b/tests/CMakeLists.txt Sat Jan 16 22:48:30 2021 +0100 @@ -20,6 +20,7 @@ set( TESTS + test-bot test-channel #test-dl-plugin test-log diff -r 21a91311c8ea -r b4e8551e2064 tests/test-bot.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-bot.c Sat Jan 16 22:48:30 2021 +0100 @@ -0,0 +1,207 @@ +/* + * test-bot.c -- test bot.h functions + * + * Copyright (c) 2013-2021 David Demelier + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#define GREATEST_USE_ABBREVS 0 +#include + +#include +#include +#include + +static struct irc_server * +server_new(const char *name) +{ + struct irc_server *s; + + s = irc_util_calloc(1, sizeof (*s)); + strlcpy(s->name, name, sizeof (s->name)); + + return s; +} + +static void +clean(void *udata) +{ + (void)udata; + + irc_bot_clear_servers(); +} + +GREATEST_TEST +servers_add(void) +{ + struct irc_server *s1, *s2, *s3; + + s1 = server_new("malikania"); + s2 = server_new("freenode"); + s3 = server_new("oftc"); + + /* irc.servers -> s1 */ + irc_bot_add_server(s1); + GREATEST_ASSERT_EQ(1, irc.serversz); + GREATEST_ASSERT_EQ(1, s1->refc); + GREATEST_ASSERT_EQ(s1, irc.servers); + GREATEST_ASSERT_EQ(NULL, s1->prev); + GREATEST_ASSERT_EQ(NULL, s1->next); + + /* irc.servers -> s2 -> s1 */ + irc_bot_add_server(s2); + GREATEST_ASSERT_EQ(2, irc.serversz); + GREATEST_ASSERT_EQ(1, s1->refc); + GREATEST_ASSERT_EQ(1, s2->refc); + GREATEST_ASSERT_EQ(s2, irc.servers); + GREATEST_ASSERT_EQ(s1, s2->next); + GREATEST_ASSERT_EQ(NULL, s2->prev); + GREATEST_ASSERT_EQ(NULL, s1->next); + GREATEST_ASSERT_EQ(s2, s1->prev); + + /* irc.servers -> s3 -> s2 -> s1 */ + irc_bot_add_server(s3); + GREATEST_ASSERT_EQ(3, irc.serversz); + GREATEST_ASSERT_EQ(1, s1->refc); + GREATEST_ASSERT_EQ(1, s2->refc); + GREATEST_ASSERT_EQ(1, s3->refc); + GREATEST_ASSERT_EQ(s3, irc.servers); + GREATEST_ASSERT_EQ(s2, s3->next); + GREATEST_ASSERT_EQ(NULL, s3->prev); + GREATEST_ASSERT_EQ(s1, s2->next); + GREATEST_ASSERT_EQ(s3, s2->prev); + GREATEST_ASSERT_EQ(NULL, s1->next); + GREATEST_ASSERT_EQ(s2, s1->prev); + + GREATEST_PASS(); +} + +GREATEST_TEST +servers_remove(void) +{ + struct irc_server *s1, *s2, *s3; + + s1 = server_new("1"); + s2 = server_new("2"); + s3 = server_new("3"); + + /* Protect deletion from irc_bot_remove_server. */ + irc_server_incref(s1); + irc_server_incref(s2); + irc_server_incref(s3); + + /* irc.servers -> s3 -> s2 -> s1 */ + irc_bot_add_server(s1); + irc_bot_add_server(s2); + irc_bot_add_server(s3); + + /* irc.servers -> s3 -> [s2] -> s1 */ + /* irc.servers -> s3 -> s1 */ + irc_bot_remove_server(s2->name); + GREATEST_ASSERT_EQ(2, irc.serversz); + GREATEST_ASSERT_EQ(2, s1->refc); + GREATEST_ASSERT_EQ(1, s2->refc); + GREATEST_ASSERT_EQ(2, s3->refc); + GREATEST_ASSERT_EQ(NULL, s2->next); + GREATEST_ASSERT_EQ(NULL, s2->prev); + GREATEST_ASSERT_EQ(s1, s3->next); + GREATEST_ASSERT_EQ(NULL, s3->prev); + GREATEST_ASSERT_EQ(NULL, s1->next); + GREATEST_ASSERT_EQ(s3, s1->prev); + + /* irc.servers -> s3 -> [s1] */ + /* irc.servers -> s3 */ + irc_bot_remove_server(s1->name); + GREATEST_ASSERT_EQ(1, irc.serversz); + GREATEST_ASSERT_EQ(1, s1->refc); + GREATEST_ASSERT_EQ(1, s2->refc); + GREATEST_ASSERT_EQ(2, s3->refc); + GREATEST_ASSERT_EQ(NULL, s1->next); + GREATEST_ASSERT_EQ(NULL, s1->prev); + GREATEST_ASSERT_EQ(NULL, s3->next); + GREATEST_ASSERT_EQ(NULL, s3->prev); + + /* irc.servers -> [s3] */ + /* irc.servers -> NULL */ + irc_bot_remove_server(s3->name); + GREATEST_ASSERT_EQ(0, irc.serversz); + GREATEST_ASSERT_EQ(NULL, irc.servers); + GREATEST_ASSERT_EQ(1, s1->refc); + GREATEST_ASSERT_EQ(1, s2->refc); + GREATEST_ASSERT_EQ(1, s3->refc); + GREATEST_ASSERT_EQ(NULL, s3->next); + GREATEST_ASSERT_EQ(NULL, s3->prev); + + irc_server_decref(s1); + irc_server_decref(s2); + irc_server_decref(s3); + + GREATEST_PASS(); +} + +GREATEST_TEST +servers_clear(void) +{ + struct irc_server *s1, *s2, *s3; + + s1 = server_new("1"); + s2 = server_new("2"); + s3 = server_new("3"); + + /* Protect deletion from irc_bot_remove_server. */ + irc_server_incref(s1); + irc_server_incref(s2); + irc_server_incref(s3); + + irc_bot_add_server(s1); + irc_bot_add_server(s2); + irc_bot_add_server(s3); + irc_bot_clear_servers(); + + GREATEST_ASSERT_EQ(0, irc.serversz); + GREATEST_ASSERT_EQ(NULL, irc.servers); + GREATEST_ASSERT_EQ(1, s1->refc); + GREATEST_ASSERT_EQ(NULL, s1->next); + GREATEST_ASSERT_EQ(NULL, s1->prev); + GREATEST_ASSERT_EQ(1, s2->refc); + GREATEST_ASSERT_EQ(NULL, s2->next); + GREATEST_ASSERT_EQ(NULL, s2->prev); + GREATEST_ASSERT_EQ(1, s3->refc); + GREATEST_ASSERT_EQ(NULL, s3->next); + GREATEST_ASSERT_EQ(NULL, s3->prev); + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_servers) +{ + GREATEST_SET_SETUP_CB(clean, NULL); + GREATEST_SET_TEARDOWN_CB(clean, NULL); + GREATEST_RUN_TEST(servers_add); + GREATEST_RUN_TEST(servers_remove); + GREATEST_RUN_TEST(servers_clear); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_servers); + GREATEST_MAIN_END(); + + return 0; +}