Files
tor/src/test/test_pubsub_build.c
T
Nick Mathewson 203e9138d1 Remove message/subsystem numbers from log messages
Having the numbers in those messages makes some of the unit test
unstable, by causing them to depend on the initialization order of
the naming objects.
2019-03-26 19:56:39 -04:00

622 lines
16 KiB
C

/* Copyright (c) 2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */
#define DISPATCH_PRIVATE
#define PUBSUB_PRIVATE
#include "test/test.h"
#include "lib/cc/torint.h"
#include "lib/dispatch/dispatch.h"
#include "lib/dispatch/dispatch_naming.h"
#include "lib/dispatch/dispatch_st.h"
#include "lib/dispatch/msgtypes.h"
#include "lib/pubsub/pubsub_macros.h"
#include "lib/pubsub/pubsub_build.h"
#include "lib/pubsub/pubsub_builder_st.h"
#include "lib/log/escape.h"
#include "lib/malloc/malloc.h"
#include "lib/string/printf.h"
#include "test/log_test_helpers.h"
#include <stdio.h>
#include <string.h>
static char *
ex_int_fmt(msg_aux_data_t aux)
{
int val = (int) aux.u64;
char *r=NULL;
tor_asprintf(&r, "%d", val);
return r;
}
static char *
ex_str_fmt(msg_aux_data_t aux)
{
return esc_for_log(aux.ptr);
}
static void
ex_str_free(msg_aux_data_t aux)
{
tor_free_(aux.ptr);
}
static dispatch_typefns_t intfns = {
.fmt_fn = ex_int_fmt
};
static dispatch_typefns_t stringfns = {
.free_fn = ex_str_free,
.fmt_fn = ex_str_fmt
};
DECLARE_MESSAGE_INT(bunch_of_coconuts, int, int);
DECLARE_PUBLISH(bunch_of_coconuts);
DECLARE_SUBSCRIBE(bunch_of_coconuts, coconut_recipient_cb);
DECLARE_MESSAGE(yes_we_have_no, string, char *);
DECLARE_PUBLISH(yes_we_have_no);
DECLARE_SUBSCRIBE(yes_we_have_no, absent_item_cb);
static void
coconut_recipient_cb(const msg_t *m, int n_coconuts)
{
(void)m;
(void)n_coconuts;
}
static void
absent_item_cb(const msg_t *m, const char *fruitname)
{
(void)m;
(void)fruitname;
}
#define FLAG_SKIP 99999
static void
seed_dispatch_builder(pubsub_builder_t *b,
unsigned fl1, unsigned fl2, unsigned fl3, unsigned fl4)
{
pubsub_connector_t *c = NULL;
{
c = pubsub_connector_for_subsystem(b, get_subsys_id("sys1"));
DISPATCH_REGISTER_TYPE(c, int, &intfns);
if (fl1 != FLAG_SKIP)
DISPATCH_ADD_PUB_(c, main, bunch_of_coconuts, fl1);
if (fl2 != FLAG_SKIP)
DISPATCH_ADD_SUB_(c, main, yes_we_have_no, fl2);
pubsub_connector_free(c);
}
{
c = pubsub_connector_for_subsystem(b, get_subsys_id("sys2"));
DISPATCH_REGISTER_TYPE(c, string, &stringfns);
if (fl3 != FLAG_SKIP)
DISPATCH_ADD_PUB_(c, main, yes_we_have_no, fl3);
if (fl4 != FLAG_SKIP)
DISPATCH_ADD_SUB_(c, main, bunch_of_coconuts, fl4);
pubsub_connector_free(c);
}
}
static void
seed_pubsub_builder_basic(pubsub_builder_t *b)
{
seed_dispatch_builder(b, 0, 0, 0, 0);
}
/* Regular builder with valid types and messages.
*/
static void
test_pubsub_build_types_ok(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
pubsub_connector_t *c = NULL;
pubsub_items_t *items = NULL;
b = pubsub_builder_new();
seed_pubsub_builder_basic(b);
dispatcher = pubsub_builder_finalize(b, &items);
b = NULL;
tt_assert(dispatcher);
tt_assert(items);
tt_int_op(smartlist_len(items->items), OP_EQ, 4);
// Make sure that the bindings got build correctly.
SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, item) {
if (item->is_publish) {
tt_assert(item->pub_binding);
tt_ptr_op(item->pub_binding->dispatch_ptr, OP_EQ, dispatcher);
}
} SMARTLIST_FOREACH_END(item);
tt_int_op(dispatcher->n_types, OP_GE, 2);
tt_assert(dispatcher->typefns);
tt_assert(dispatcher->typefns[get_msg_type_id("int")].fmt_fn == ex_int_fmt);
tt_assert(dispatcher->typefns[get_msg_type_id("string")].fmt_fn ==
ex_str_fmt);
// Now clear the bindings, like we would do before freeing the
// the dispatcher.
pubsub_items_clear_bindings(items);
SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, item) {
if (item->is_publish) {
tt_assert(item->pub_binding);
tt_ptr_op(item->pub_binding->dispatch_ptr, OP_EQ, NULL);
}
} SMARTLIST_FOREACH_END(item);
done:
pubsub_connector_free(c);
pubsub_builder_free(b);
dispatch_free(dispatcher);
pubsub_items_free(items);
}
/* We fail if the same type is defined in two places with different functions.
*/
static void
test_pubsub_build_types_decls_conflict(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
pubsub_connector_t *c = NULL;
b = pubsub_builder_new();
seed_pubsub_builder_basic(b);
{
c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3"));
// Extra declaration of int: we don't allow this.
DISPATCH_REGISTER_TYPE(c, int, &stringfns);
pubsub_connector_free(c);
}
setup_full_capture_of_logs(LOG_WARN);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher == NULL);
// expect_log_msg_containing("(int) declared twice"); // XXXX
done:
pubsub_connector_free(c);
pubsub_builder_free(b);
dispatch_free(dispatcher);
teardown_capture_of_logs();
}
/* If a message ID exists but nobody is publishing or subscribing to it,
* that's okay. */
static void
test_pubsub_build_unused_message(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
b = pubsub_builder_new();
seed_pubsub_builder_basic(b);
// This message isn't actually generated by anyone, but that will be fine:
// we just log it at info.
get_message_id("unused");
setup_capture_of_logs(LOG_INFO);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher);
expect_log_msg_containing(
"Nobody is publishing or subscribing to message");
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
teardown_capture_of_logs();
}
/* Publishing or subscribing to a message with no subscribers / publishers
* should fail and warn. */
static void
test_pubsub_build_missing_pubsub(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
b = pubsub_builder_new();
seed_dispatch_builder(b, 0, 0, FLAG_SKIP, FLAG_SKIP);
setup_full_capture_of_logs(LOG_WARN);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher == NULL);
expect_log_msg_containing(
"Message \"bunch_of_coconuts\" has publishers, but no subscribers.");
expect_log_msg_containing(
"Message \"yes_we_have_no\" has subscribers, but no publishers.");
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
teardown_capture_of_logs();
}
/* Make sure that a stub publisher or subscriber prevents an error from
* happening even if there are no other publishers/subscribers for a message
*/
static void
test_pubsub_build_stub_pubsub(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
b = pubsub_builder_new();
seed_dispatch_builder(b, 0, 0, DISP_FLAG_STUB, DISP_FLAG_STUB);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher);
// 1 subscriber.
tt_int_op(1, OP_EQ,
dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled);
// no subscribers
tt_ptr_op(NULL, OP_EQ,
dispatcher->table[get_message_id("bunch_of_coconuts")]);
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
}
/* Only one channel per msg id. */
static void
test_pubsub_build_channels_conflict(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
pubsub_connector_t *c = NULL;
b = pubsub_builder_new();
seed_pubsub_builder_basic(b);
pub_binding_t btmp;
{
c = pubsub_connector_for_subsystem(b, get_subsys_id("problems"));
/* Usually the DISPATCH_ADD_PUB macro would keep us from using
* the wrong channel */
pubsub_add_pub_(c, &btmp, get_channel_id("hithere"),
get_message_id("bunch_of_coconuts"),
get_msg_type_id("int"),
0 /* flags */,
"somewhere.c", 22);
pubsub_connector_free(c);
};
setup_full_capture_of_logs(LOG_WARN);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher == NULL);
expect_log_msg_containing("Message \"bunch_of_coconuts\" is associated "
"with multiple inconsistent channels.");
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
teardown_capture_of_logs();
}
/* Only one type per msg id. */
static void
test_pubsub_build_types_conflict(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
pubsub_connector_t *c = NULL;
b = pubsub_builder_new();
seed_pubsub_builder_basic(b);
pub_binding_t btmp;
{
c = pubsub_connector_for_subsystem(b, get_subsys_id("problems"));
/* Usually the DISPATCH_ADD_PUB macro would keep us from using
* the wrong channel */
pubsub_add_pub_(c, &btmp, get_channel_id("hithere"),
get_message_id("bunch_of_coconuts"),
get_msg_type_id("string"),
0 /* flags */,
"somewhere.c", 22);
pubsub_connector_free(c);
};
setup_full_capture_of_logs(LOG_WARN);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher == NULL);
expect_log_msg_containing("Message \"bunch_of_coconuts\" is associated "
"with multiple inconsistent message types.");
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
teardown_capture_of_logs();
}
/* The same module can't publish and subscribe the same message */
static void
test_pubsub_build_pubsub_same(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
pubsub_connector_t *c = NULL;
b = pubsub_builder_new();
seed_pubsub_builder_basic(b);
{
c = pubsub_connector_for_subsystem(b, get_subsys_id("sys1"));
// already publishing this.
DISPATCH_ADD_SUB(c, main, bunch_of_coconuts);
pubsub_connector_free(c);
};
setup_full_capture_of_logs(LOG_WARN);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher == NULL);
expect_log_msg_containing("Message \"bunch_of_coconuts\" is published "
"and subscribed by the same subsystem \"sys1\".");
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
teardown_capture_of_logs();
}
/* More than one subsystem may publish or subscribe, and that's okay. */
static void
test_pubsub_build_pubsub_multi(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
pubsub_connector_t *c = NULL;
b = pubsub_builder_new();
seed_pubsub_builder_basic(b);
pub_binding_t btmp;
{
c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3"));
DISPATCH_ADD_SUB(c, main, bunch_of_coconuts);
pubsub_add_pub_(c, &btmp, get_channel_id("main"),
get_message_id("yes_we_have_no"),
get_msg_type_id("string"),
0 /* flags */,
"somewhere.c", 22);
pubsub_connector_free(c);
};
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher);
// 1 subscribers
tt_int_op(1, OP_EQ,
dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled);
// 2 subscribers.
dtbl_entry_t *ent =
dispatcher->table[get_message_id("bunch_of_coconuts")];
tt_int_op(2, OP_EQ, ent->n_enabled);
tt_int_op(2, OP_EQ, ent->n_fns);
tt_ptr_op(ent->rcv[0].fn, OP_EQ, recv_fn__bunch_of_coconuts);
tt_ptr_op(ent->rcv[1].fn, OP_EQ, recv_fn__bunch_of_coconuts);
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
}
static void
some_other_coconut_hook(const msg_t *m)
{
(void)m;
}
/* Subscribe hooks should be build correctly when there are a bunch of
* them. */
static void
test_pubsub_build_sub_many(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
pubsub_connector_t *c = NULL;
char *sysname = NULL;
b = pubsub_builder_new();
seed_pubsub_builder_basic(b);
int i;
for (i = 1; i < 100; ++i) {
tor_asprintf(&sysname, "system%d",i);
c = pubsub_connector_for_subsystem(b, get_subsys_id(sysname));
if (i % 7) {
DISPATCH_ADD_SUB(c, main, bunch_of_coconuts);
} else {
pubsub_add_sub_(c, some_other_coconut_hook,
get_channel_id("main"),
get_message_id("bunch_of_coconuts"),
get_msg_type_id("int"),
0 /* flags */,
"somewhere.c", 22);
}
pubsub_connector_free(c);
tor_free(sysname);
};
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher);
dtbl_entry_t *ent =
dispatcher->table[get_message_id("bunch_of_coconuts")];
tt_int_op(100, OP_EQ, ent->n_enabled);
tt_int_op(100, OP_EQ, ent->n_fns);
tt_ptr_op(ent->rcv[0].fn, OP_EQ, recv_fn__bunch_of_coconuts);
tt_ptr_op(ent->rcv[1].fn, OP_EQ, recv_fn__bunch_of_coconuts);
tt_ptr_op(ent->rcv[76].fn, OP_EQ, recv_fn__bunch_of_coconuts);
tt_ptr_op(ent->rcv[77].fn, OP_EQ, some_other_coconut_hook);
tt_ptr_op(ent->rcv[78].fn, OP_EQ, recv_fn__bunch_of_coconuts);
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
tor_free(sysname);
}
/* The same subsystem can only declare one publish or subscribe. */
static void
test_pubsub_build_pubsub_redundant(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
pubsub_connector_t *c = NULL;
b = pubsub_builder_new();
seed_pubsub_builder_basic(b);
pub_binding_t btmp;
{
c = pubsub_connector_for_subsystem(b, get_subsys_id("sys2"));
DISPATCH_ADD_SUB(c, main, bunch_of_coconuts);
pubsub_add_pub_(c, &btmp, get_channel_id("main"),
get_message_id("yes_we_have_no"),
get_msg_type_id("string"),
0 /* flags */,
"somewhere.c", 22);
pubsub_connector_free(c);
};
setup_full_capture_of_logs(LOG_WARN);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher == NULL);
expect_log_msg_containing(
"Message \"yes_we_have_no\" is configured to be published by "
"subsystem \"sys2\" more than once.");
expect_log_msg_containing(
"Message \"bunch_of_coconuts\" is configured to be subscribed by "
"subsystem \"sys2\" more than once.");
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
teardown_capture_of_logs();
}
/* It's fine to declare the excl flag. */
static void
test_pubsub_build_excl_ok(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
b = pubsub_builder_new();
// Try one excl/excl pair and one excl/non pair.
seed_dispatch_builder(b, DISP_FLAG_EXCL, 0,
DISP_FLAG_EXCL, DISP_FLAG_EXCL);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher);
// 1 subscribers
tt_int_op(1, OP_EQ,
dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled);
// 1 subscriber.
tt_int_op(1, OP_EQ,
dispatcher->table[get_message_id("bunch_of_coconuts")]->n_enabled);
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
}
/* but if you declare the excl flag, you need to mean it. */
static void
test_pubsub_build_excl_bad(void *arg)
{
(void)arg;
pubsub_builder_t *b = NULL;
dispatch_t *dispatcher = NULL;
pubsub_connector_t *c = NULL;
b = pubsub_builder_new();
seed_dispatch_builder(b, DISP_FLAG_EXCL, DISP_FLAG_EXCL,
0, 0);
{
c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3"));
DISPATCH_ADD_PUB_(c, main, bunch_of_coconuts, 0);
DISPATCH_ADD_SUB_(c, main, yes_we_have_no, 0);
pubsub_connector_free(c);
};
setup_full_capture_of_logs(LOG_WARN);
dispatcher = pubsub_builder_finalize(b, NULL);
b = NULL;
tt_assert(dispatcher == NULL);
expect_log_msg_containing("has multiple publishers, but at least one is "
"marked as exclusive.");
expect_log_msg_containing("has multiple subscribers, but at least one is "
"marked as exclusive.");
done:
pubsub_builder_free(b);
dispatch_free(dispatcher);
teardown_capture_of_logs();
}
#define T(name, flags) \
{ #name, test_pubsub_build_ ## name , (flags), NULL, NULL }
struct testcase_t pubsub_build_tests[] = {
T(types_ok, TT_FORK),
T(types_decls_conflict, TT_FORK),
T(unused_message, TT_FORK),
T(missing_pubsub, TT_FORK),
T(stub_pubsub, TT_FORK),
T(channels_conflict, TT_FORK),
T(types_conflict, TT_FORK),
T(pubsub_same, TT_FORK),
T(pubsub_multi, TT_FORK),
T(sub_many, TT_FORK),
T(pubsub_redundant, TT_FORK),
T(excl_ok, TT_FORK),
T(excl_bad, TT_FORK),
END_OF_TESTCASES
};