Line data Source code
1 : /* Copyright (c) 2018-2021, The Tor Project, Inc. */
2 : /* See LICENSE for licensing information */
3 :
4 : #define DISPATCH_PRIVATE
5 : #define PUBSUB_PRIVATE
6 :
7 : #include "test/test.h"
8 :
9 : #include "lib/cc/torint.h"
10 : #include "lib/dispatch/dispatch.h"
11 : #include "lib/dispatch/dispatch_naming.h"
12 : #include "lib/dispatch/dispatch_st.h"
13 : #include "lib/dispatch/msgtypes.h"
14 : #include "lib/pubsub/pubsub_macros.h"
15 : #include "lib/pubsub/pubsub_build.h"
16 : #include "lib/pubsub/pubsub_builder_st.h"
17 :
18 : #include "lib/log/escape.h"
19 : #include "lib/malloc/malloc.h"
20 : #include "lib/string/printf.h"
21 :
22 : #include "test/log_test_helpers.h"
23 :
24 : #include <stdio.h>
25 : #include <string.h>
26 :
27 : static char *
28 0 : ex_int_fmt(msg_aux_data_t aux)
29 : {
30 0 : int val = (int) aux.u64;
31 0 : char *r=NULL;
32 0 : tor_asprintf(&r, "%d", val);
33 0 : return r;
34 : }
35 :
36 : static char *
37 0 : ex_str_fmt(msg_aux_data_t aux)
38 : {
39 0 : return esc_for_log(aux.ptr);
40 : }
41 :
42 : static void
43 0 : ex_str_free(msg_aux_data_t aux)
44 : {
45 0 : tor_free_(aux.ptr);
46 0 : }
47 :
48 : static dispatch_typefns_t intfns = {
49 : .fmt_fn = ex_int_fmt
50 : };
51 :
52 : static dispatch_typefns_t stringfns = {
53 : .free_fn = ex_str_free,
54 : .fmt_fn = ex_str_fmt
55 : };
56 :
57 : DECLARE_MESSAGE_INT(bunch_of_coconuts, int, int);
58 : DECLARE_PUBLISH(bunch_of_coconuts);
59 0 : DECLARE_SUBSCRIBE(bunch_of_coconuts, coconut_recipient_cb);
60 :
61 : DECLARE_MESSAGE(yes_we_have_no, string, char *);
62 : DECLARE_PUBLISH(yes_we_have_no);
63 0 : DECLARE_SUBSCRIBE(yes_we_have_no, absent_item_cb);
64 :
65 : static void
66 : coconut_recipient_cb(const msg_t *m, int n_coconuts)
67 : {
68 : (void)m;
69 : (void)n_coconuts;
70 : }
71 :
72 : static void
73 : absent_item_cb(const msg_t *m, const char *fruitname)
74 : {
75 : (void)m;
76 : (void)fruitname;
77 : }
78 :
79 : #define FLAG_SKIP 99999
80 :
81 : static void
82 12 : seed_dispatch_builder(pubsub_builder_t *b,
83 : unsigned fl1, unsigned fl2, unsigned fl3, unsigned fl4)
84 : {
85 12 : pubsub_connector_t *c = NULL;
86 :
87 : {
88 12 : c = pubsub_connector_for_subsystem(b, get_subsys_id("sys1"));
89 12 : DISPATCH_REGISTER_TYPE(c, int, &intfns);
90 12 : if (fl1 != FLAG_SKIP)
91 12 : DISPATCH_ADD_PUB_(c, main, bunch_of_coconuts, fl1);
92 12 : if (fl2 != FLAG_SKIP)
93 12 : DISPATCH_ADD_SUB_(c, main, yes_we_have_no, fl2);
94 12 : pubsub_connector_free(c);
95 : }
96 :
97 : {
98 12 : c = pubsub_connector_for_subsystem(b, get_subsys_id("sys2"));
99 12 : DISPATCH_REGISTER_TYPE(c, string, &stringfns);
100 12 : if (fl3 != FLAG_SKIP)
101 11 : DISPATCH_ADD_PUB_(c, main, yes_we_have_no, fl3);
102 12 : if (fl4 != FLAG_SKIP)
103 11 : DISPATCH_ADD_SUB_(c, main, bunch_of_coconuts, fl4);
104 12 : pubsub_connector_free(c);
105 : }
106 12 : }
107 :
108 : static void
109 8 : seed_pubsub_builder_basic(pubsub_builder_t *b)
110 : {
111 8 : seed_dispatch_builder(b, 0, 0, 0, 0);
112 8 : }
113 :
114 : /* Regular builder with valid types and messages.
115 : */
116 : static void
117 1 : test_pubsub_build_types_ok(void *arg)
118 : {
119 1 : (void)arg;
120 1 : pubsub_builder_t *b = NULL;
121 1 : dispatch_t *dispatcher = NULL;
122 1 : pubsub_connector_t *c = NULL;
123 1 : pubsub_items_t *items = NULL;
124 :
125 1 : b = pubsub_builder_new();
126 1 : seed_pubsub_builder_basic(b);
127 :
128 1 : dispatcher = pubsub_builder_finalize(b, &items);
129 1 : b = NULL;
130 1 : tt_assert(dispatcher);
131 1 : tt_assert(items);
132 1 : tt_int_op(smartlist_len(items->items), OP_EQ, 4);
133 :
134 : // Make sure that the bindings got build correctly.
135 5 : SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, item) {
136 4 : if (item->is_publish) {
137 2 : tt_assert(item->pub_binding);
138 2 : tt_ptr_op(item->pub_binding->dispatch_ptr, OP_EQ, dispatcher);
139 : }
140 4 : } SMARTLIST_FOREACH_END(item);
141 :
142 1 : tt_int_op(dispatcher->n_types, OP_GE, 2);
143 1 : tt_assert(dispatcher->typefns);
144 :
145 1 : tt_assert(dispatcher->typefns[get_msg_type_id("int")].fmt_fn == ex_int_fmt);
146 1 : tt_assert(dispatcher->typefns[get_msg_type_id("string")].fmt_fn ==
147 : ex_str_fmt);
148 :
149 : // Now clear the bindings, like we would do before freeing the
150 : // the dispatcher.
151 1 : pubsub_items_clear_bindings(items);
152 5 : SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, item) {
153 4 : if (item->is_publish) {
154 2 : tt_assert(item->pub_binding);
155 2 : tt_ptr_op(item->pub_binding->dispatch_ptr, OP_EQ, NULL);
156 : }
157 4 : } SMARTLIST_FOREACH_END(item);
158 :
159 1 : done:
160 1 : pubsub_connector_free(c);
161 1 : pubsub_builder_free(b);
162 1 : dispatch_free(dispatcher);
163 1 : pubsub_items_free(items);
164 1 : }
165 :
166 : /* We fail if the same type is defined in two places with different functions.
167 : */
168 : static void
169 1 : test_pubsub_build_types_decls_conflict(void *arg)
170 : {
171 1 : (void)arg;
172 1 : pubsub_builder_t *b = NULL;
173 1 : dispatch_t *dispatcher = NULL;
174 1 : pubsub_connector_t *c = NULL;
175 :
176 1 : b = pubsub_builder_new();
177 1 : seed_pubsub_builder_basic(b);
178 : {
179 1 : c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3"));
180 : // Extra declaration of int: we don't allow this.
181 1 : DISPATCH_REGISTER_TYPE(c, int, &stringfns);
182 1 : pubsub_connector_free(c);
183 : }
184 :
185 1 : setup_full_capture_of_logs(LOG_WARN);
186 1 : dispatcher = pubsub_builder_finalize(b, NULL);
187 1 : b = NULL;
188 1 : tt_assert(dispatcher == NULL);
189 : // expect_log_msg_containing("(int) declared twice"); // XXXX
190 :
191 1 : done:
192 1 : pubsub_connector_free(c);
193 1 : pubsub_builder_free(b);
194 1 : dispatch_free(dispatcher);
195 1 : teardown_capture_of_logs();
196 1 : }
197 :
198 : /* If a message ID exists but nobody is publishing or subscribing to it,
199 : * that's okay. */
200 : static void
201 1 : test_pubsub_build_unused_message(void *arg)
202 : {
203 1 : (void)arg;
204 1 : pubsub_builder_t *b = NULL;
205 1 : dispatch_t *dispatcher = NULL;
206 :
207 1 : b = pubsub_builder_new();
208 1 : seed_pubsub_builder_basic(b);
209 :
210 : // This message isn't actually generated by anyone, but that will be fine:
211 : // we just log it at info.
212 1 : get_message_id("unused");
213 1 : setup_capture_of_logs(LOG_INFO);
214 :
215 1 : dispatcher = pubsub_builder_finalize(b, NULL);
216 1 : b = NULL;
217 1 : tt_assert(dispatcher);
218 1 : expect_log_msg_containing(
219 1 : "Nobody is publishing or subscribing to message");
220 :
221 1 : done:
222 1 : pubsub_builder_free(b);
223 1 : dispatch_free(dispatcher);
224 1 : teardown_capture_of_logs();
225 1 : }
226 :
227 : /* Publishing or subscribing to a message with no subscribers / publishers
228 : * should fail and warn. */
229 : static void
230 1 : test_pubsub_build_missing_pubsub(void *arg)
231 : {
232 1 : (void)arg;
233 1 : pubsub_builder_t *b = NULL;
234 1 : dispatch_t *dispatcher = NULL;
235 :
236 1 : b = pubsub_builder_new();
237 1 : seed_dispatch_builder(b, 0, 0, FLAG_SKIP, FLAG_SKIP);
238 :
239 1 : setup_full_capture_of_logs(LOG_WARN);
240 1 : dispatcher = pubsub_builder_finalize(b, NULL);
241 1 : b = NULL;
242 1 : tt_assert(dispatcher == NULL);
243 :
244 1 : expect_log_msg_containing(
245 1 : "Message \"bunch_of_coconuts\" has publishers, but no subscribers.");
246 1 : expect_log_msg_containing(
247 1 : "Message \"yes_we_have_no\" has subscribers, but no publishers.");
248 :
249 1 : done:
250 1 : pubsub_builder_free(b);
251 1 : dispatch_free(dispatcher);
252 1 : teardown_capture_of_logs();
253 1 : }
254 :
255 : /* Make sure that a stub publisher or subscriber prevents an error from
256 : * happening even if there are no other publishers/subscribers for a message
257 : */
258 : static void
259 1 : test_pubsub_build_stub_pubsub(void *arg)
260 : {
261 1 : (void)arg;
262 1 : pubsub_builder_t *b = NULL;
263 1 : dispatch_t *dispatcher = NULL;
264 :
265 1 : b = pubsub_builder_new();
266 1 : seed_dispatch_builder(b, 0, 0, DISP_FLAG_STUB, DISP_FLAG_STUB);
267 :
268 1 : dispatcher = pubsub_builder_finalize(b, NULL);
269 1 : b = NULL;
270 1 : tt_assert(dispatcher);
271 :
272 : // 1 subscriber.
273 1 : tt_int_op(1, OP_EQ,
274 : dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled);
275 : // no subscribers
276 1 : tt_ptr_op(NULL, OP_EQ,
277 : dispatcher->table[get_message_id("bunch_of_coconuts")]);
278 :
279 1 : done:
280 1 : pubsub_builder_free(b);
281 1 : dispatch_free(dispatcher);
282 1 : }
283 :
284 : /* Only one channel per msg id. */
285 : static void
286 1 : test_pubsub_build_channels_conflict(void *arg)
287 : {
288 1 : (void)arg;
289 1 : pubsub_builder_t *b = NULL;
290 1 : dispatch_t *dispatcher = NULL;
291 1 : pubsub_connector_t *c = NULL;
292 :
293 1 : b = pubsub_builder_new();
294 1 : seed_pubsub_builder_basic(b);
295 1 : pub_binding_t btmp;
296 :
297 : {
298 1 : c = pubsub_connector_for_subsystem(b, get_subsys_id("problems"));
299 : /* Usually the DISPATCH_ADD_PUB macro would keep us from using
300 : * the wrong channel */
301 1 : pubsub_add_pub_(c, &btmp, get_channel_id("hithere"),
302 1 : get_message_id("bunch_of_coconuts"),
303 1 : get_msg_type_id("int"),
304 : 0 /* flags */,
305 : "somewhere.c", 22);
306 1 : pubsub_connector_free(c);
307 1 : };
308 :
309 1 : setup_full_capture_of_logs(LOG_WARN);
310 1 : dispatcher = pubsub_builder_finalize(b, NULL);
311 1 : b = NULL;
312 1 : tt_assert(dispatcher == NULL);
313 :
314 1 : expect_log_msg_containing("Message \"bunch_of_coconuts\" is associated "
315 1 : "with multiple inconsistent channels.");
316 :
317 1 : done:
318 1 : pubsub_builder_free(b);
319 1 : dispatch_free(dispatcher);
320 1 : teardown_capture_of_logs();
321 1 : }
322 :
323 : /* Only one type per msg id. */
324 : static void
325 1 : test_pubsub_build_types_conflict(void *arg)
326 : {
327 1 : (void)arg;
328 1 : pubsub_builder_t *b = NULL;
329 1 : dispatch_t *dispatcher = NULL;
330 1 : pubsub_connector_t *c = NULL;
331 :
332 1 : b = pubsub_builder_new();
333 1 : seed_pubsub_builder_basic(b);
334 1 : pub_binding_t btmp;
335 :
336 : {
337 1 : c = pubsub_connector_for_subsystem(b, get_subsys_id("problems"));
338 : /* Usually the DISPATCH_ADD_PUB macro would keep us from using
339 : * the wrong channel */
340 1 : pubsub_add_pub_(c, &btmp, get_channel_id("hithere"),
341 1 : get_message_id("bunch_of_coconuts"),
342 1 : get_msg_type_id("string"),
343 : 0 /* flags */,
344 : "somewhere.c", 22);
345 1 : pubsub_connector_free(c);
346 1 : };
347 :
348 1 : setup_full_capture_of_logs(LOG_WARN);
349 1 : dispatcher = pubsub_builder_finalize(b, NULL);
350 1 : b = NULL;
351 1 : tt_assert(dispatcher == NULL);
352 :
353 1 : expect_log_msg_containing("Message \"bunch_of_coconuts\" is associated "
354 1 : "with multiple inconsistent message types.");
355 :
356 1 : done:
357 1 : pubsub_builder_free(b);
358 1 : dispatch_free(dispatcher);
359 1 : teardown_capture_of_logs();
360 1 : }
361 :
362 : /* The same module can't publish and subscribe the same message */
363 : static void
364 1 : test_pubsub_build_pubsub_same(void *arg)
365 : {
366 1 : (void)arg;
367 1 : pubsub_builder_t *b = NULL;
368 1 : dispatch_t *dispatcher = NULL;
369 1 : pubsub_connector_t *c = NULL;
370 :
371 1 : b = pubsub_builder_new();
372 1 : seed_pubsub_builder_basic(b);
373 :
374 : {
375 1 : c = pubsub_connector_for_subsystem(b, get_subsys_id("sys1"));
376 : // already publishing this.
377 1 : DISPATCH_ADD_SUB(c, main, bunch_of_coconuts);
378 1 : pubsub_connector_free(c);
379 1 : };
380 :
381 1 : setup_full_capture_of_logs(LOG_WARN);
382 1 : dispatcher = pubsub_builder_finalize(b, NULL);
383 1 : b = NULL;
384 1 : tt_assert(dispatcher == NULL);
385 :
386 1 : expect_log_msg_containing("Message \"bunch_of_coconuts\" is published "
387 1 : "and subscribed by the same subsystem \"sys1\".");
388 :
389 1 : done:
390 1 : pubsub_builder_free(b);
391 1 : dispatch_free(dispatcher);
392 1 : teardown_capture_of_logs();
393 1 : }
394 :
395 : /* More than one subsystem may publish or subscribe, and that's okay. */
396 : static void
397 1 : test_pubsub_build_pubsub_multi(void *arg)
398 : {
399 1 : (void)arg;
400 1 : pubsub_builder_t *b = NULL;
401 1 : dispatch_t *dispatcher = NULL;
402 1 : pubsub_connector_t *c = NULL;
403 :
404 1 : b = pubsub_builder_new();
405 1 : seed_pubsub_builder_basic(b);
406 1 : pub_binding_t btmp;
407 :
408 : {
409 1 : c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3"));
410 1 : DISPATCH_ADD_SUB(c, main, bunch_of_coconuts);
411 1 : pubsub_add_pub_(c, &btmp, get_channel_id("main"),
412 1 : get_message_id("yes_we_have_no"),
413 1 : get_msg_type_id("string"),
414 : 0 /* flags */,
415 : "somewhere.c", 22);
416 1 : pubsub_connector_free(c);
417 1 : };
418 :
419 1 : dispatcher = pubsub_builder_finalize(b, NULL);
420 1 : b = NULL;
421 1 : tt_assert(dispatcher);
422 :
423 : // 1 subscribers
424 1 : tt_int_op(1, OP_EQ,
425 : dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled);
426 : // 2 subscribers.
427 2 : dtbl_entry_t *ent =
428 1 : dispatcher->table[get_message_id("bunch_of_coconuts")];
429 1 : tt_int_op(2, OP_EQ, ent->n_enabled);
430 1 : tt_int_op(2, OP_EQ, ent->n_fns);
431 1 : tt_ptr_op(ent->rcv[0].fn, OP_EQ, recv_fn__bunch_of_coconuts);
432 1 : tt_ptr_op(ent->rcv[1].fn, OP_EQ, recv_fn__bunch_of_coconuts);
433 :
434 1 : done:
435 1 : pubsub_builder_free(b);
436 1 : dispatch_free(dispatcher);
437 1 : }
438 :
439 : static void
440 0 : some_other_coconut_hook(const msg_t *m)
441 : {
442 0 : (void)m;
443 0 : }
444 :
445 : /* Subscribe hooks should be build correctly when there are a bunch of
446 : * them. */
447 : static void
448 1 : test_pubsub_build_sub_many(void *arg)
449 : {
450 1 : (void)arg;
451 1 : pubsub_builder_t *b = NULL;
452 1 : dispatch_t *dispatcher = NULL;
453 1 : pubsub_connector_t *c = NULL;
454 1 : char *sysname = NULL;
455 1 : b = pubsub_builder_new();
456 1 : seed_pubsub_builder_basic(b);
457 :
458 1 : int i;
459 101 : for (i = 1; i < 100; ++i) {
460 99 : tor_asprintf(&sysname, "system%d",i);
461 99 : c = pubsub_connector_for_subsystem(b, get_subsys_id(sysname));
462 99 : if (i % 7) {
463 85 : DISPATCH_ADD_SUB(c, main, bunch_of_coconuts);
464 : } else {
465 14 : pubsub_add_sub_(c, some_other_coconut_hook,
466 14 : get_channel_id("main"),
467 14 : get_message_id("bunch_of_coconuts"),
468 14 : get_msg_type_id("int"),
469 : 0 /* flags */,
470 : "somewhere.c", 22);
471 : }
472 99 : pubsub_connector_free(c);
473 99 : tor_free(sysname);
474 1 : };
475 :
476 1 : dispatcher = pubsub_builder_finalize(b, NULL);
477 1 : b = NULL;
478 1 : tt_assert(dispatcher);
479 :
480 2 : dtbl_entry_t *ent =
481 1 : dispatcher->table[get_message_id("bunch_of_coconuts")];
482 1 : tt_int_op(100, OP_EQ, ent->n_enabled);
483 1 : tt_int_op(100, OP_EQ, ent->n_fns);
484 1 : tt_ptr_op(ent->rcv[0].fn, OP_EQ, recv_fn__bunch_of_coconuts);
485 1 : tt_ptr_op(ent->rcv[1].fn, OP_EQ, recv_fn__bunch_of_coconuts);
486 1 : tt_ptr_op(ent->rcv[76].fn, OP_EQ, recv_fn__bunch_of_coconuts);
487 1 : tt_ptr_op(ent->rcv[77].fn, OP_EQ, some_other_coconut_hook);
488 1 : tt_ptr_op(ent->rcv[78].fn, OP_EQ, recv_fn__bunch_of_coconuts);
489 :
490 1 : done:
491 1 : pubsub_builder_free(b);
492 1 : dispatch_free(dispatcher);
493 1 : tor_free(sysname);
494 1 : }
495 :
496 : /* It's fine to declare the excl flag. */
497 : static void
498 1 : test_pubsub_build_excl_ok(void *arg)
499 : {
500 1 : (void)arg;
501 1 : pubsub_builder_t *b = NULL;
502 1 : dispatch_t *dispatcher = NULL;
503 :
504 1 : b = pubsub_builder_new();
505 : // Try one excl/excl pair and one excl/non pair.
506 1 : seed_dispatch_builder(b, DISP_FLAG_EXCL, 0,
507 : DISP_FLAG_EXCL, DISP_FLAG_EXCL);
508 :
509 1 : dispatcher = pubsub_builder_finalize(b, NULL);
510 1 : b = NULL;
511 1 : tt_assert(dispatcher);
512 :
513 : // 1 subscribers
514 1 : tt_int_op(1, OP_EQ,
515 : dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled);
516 : // 1 subscriber.
517 1 : tt_int_op(1, OP_EQ,
518 : dispatcher->table[get_message_id("bunch_of_coconuts")]->n_enabled);
519 :
520 1 : done:
521 1 : pubsub_builder_free(b);
522 1 : dispatch_free(dispatcher);
523 1 : }
524 :
525 : /* but if you declare the excl flag, you need to mean it. */
526 : static void
527 1 : test_pubsub_build_excl_bad(void *arg)
528 : {
529 1 : (void)arg;
530 1 : pubsub_builder_t *b = NULL;
531 1 : dispatch_t *dispatcher = NULL;
532 1 : pubsub_connector_t *c = NULL;
533 :
534 1 : b = pubsub_builder_new();
535 1 : seed_dispatch_builder(b, DISP_FLAG_EXCL, DISP_FLAG_EXCL,
536 : 0, 0);
537 :
538 : {
539 1 : c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3"));
540 1 : DISPATCH_ADD_PUB_(c, main, bunch_of_coconuts, 0);
541 1 : DISPATCH_ADD_SUB_(c, main, yes_we_have_no, 0);
542 1 : pubsub_connector_free(c);
543 1 : };
544 :
545 1 : setup_full_capture_of_logs(LOG_WARN);
546 1 : dispatcher = pubsub_builder_finalize(b, NULL);
547 1 : b = NULL;
548 1 : tt_assert(dispatcher == NULL);
549 :
550 1 : expect_log_msg_containing("has multiple publishers, but at least one is "
551 1 : "marked as exclusive.");
552 1 : expect_log_msg_containing("has multiple subscribers, but at least one is "
553 1 : "marked as exclusive.");
554 :
555 1 : done:
556 1 : pubsub_builder_free(b);
557 1 : dispatch_free(dispatcher);
558 1 : teardown_capture_of_logs();
559 1 : }
560 :
561 : #define T(name, flags) \
562 : { #name, test_pubsub_build_ ## name , (flags), NULL, NULL }
563 :
564 : struct testcase_t pubsub_build_tests[] = {
565 : T(types_ok, TT_FORK),
566 : T(types_decls_conflict, TT_FORK),
567 : T(unused_message, TT_FORK),
568 : T(missing_pubsub, TT_FORK),
569 : T(stub_pubsub, TT_FORK),
570 : T(channels_conflict, TT_FORK),
571 : T(types_conflict, TT_FORK),
572 : T(pubsub_same, TT_FORK),
573 : T(pubsub_multi, TT_FORK),
574 : T(sub_many, TT_FORK),
575 : T(excl_ok, TT_FORK),
576 : T(excl_bad, TT_FORK),
577 : END_OF_TESTCASES
578 : };
|