test_pubsub_build.c (15375B)
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 ex_int_fmt(msg_aux_data_t aux) 29 { 30 int val = (int) aux.u64; 31 char *r=NULL; 32 tor_asprintf(&r, "%d", val); 33 return r; 34 } 35 36 static char * 37 ex_str_fmt(msg_aux_data_t aux) 38 { 39 return esc_for_log(aux.ptr); 40 } 41 42 static void 43 ex_str_free(msg_aux_data_t aux) 44 { 45 tor_free_(aux.ptr); 46 } 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 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 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 seed_dispatch_builder(pubsub_builder_t *b, 83 unsigned fl1, unsigned fl2, unsigned fl3, unsigned fl4) 84 { 85 pubsub_connector_t *c = NULL; 86 87 { 88 c = pubsub_connector_for_subsystem(b, get_subsys_id("sys1")); 89 DISPATCH_REGISTER_TYPE(c, int, &intfns); 90 if (fl1 != FLAG_SKIP) 91 DISPATCH_ADD_PUB_(c, main, bunch_of_coconuts, fl1); 92 if (fl2 != FLAG_SKIP) 93 DISPATCH_ADD_SUB_(c, main, yes_we_have_no, fl2); 94 pubsub_connector_free(c); 95 } 96 97 { 98 c = pubsub_connector_for_subsystem(b, get_subsys_id("sys2")); 99 DISPATCH_REGISTER_TYPE(c, string, &stringfns); 100 if (fl3 != FLAG_SKIP) 101 DISPATCH_ADD_PUB_(c, main, yes_we_have_no, fl3); 102 if (fl4 != FLAG_SKIP) 103 DISPATCH_ADD_SUB_(c, main, bunch_of_coconuts, fl4); 104 pubsub_connector_free(c); 105 } 106 } 107 108 static void 109 seed_pubsub_builder_basic(pubsub_builder_t *b) 110 { 111 seed_dispatch_builder(b, 0, 0, 0, 0); 112 } 113 114 /* Regular builder with valid types and messages. 115 */ 116 static void 117 test_pubsub_build_types_ok(void *arg) 118 { 119 (void)arg; 120 pubsub_builder_t *b = NULL; 121 dispatch_t *dispatcher = NULL; 122 pubsub_connector_t *c = NULL; 123 pubsub_items_t *items = NULL; 124 125 b = pubsub_builder_new(); 126 seed_pubsub_builder_basic(b); 127 128 dispatcher = pubsub_builder_finalize(b, &items); 129 b = NULL; 130 tt_assert(dispatcher); 131 tt_assert(items); 132 tt_int_op(smartlist_len(items->items), OP_EQ, 4); 133 134 // Make sure that the bindings got build correctly. 135 SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, item) { 136 if (item->is_publish) { 137 tt_assert(item->pub_binding); 138 tt_ptr_op(item->pub_binding->dispatch_ptr, OP_EQ, dispatcher); 139 } 140 } SMARTLIST_FOREACH_END(item); 141 142 tt_int_op(dispatcher->n_types, OP_GE, 2); 143 tt_assert(dispatcher->typefns); 144 145 tt_assert(dispatcher->typefns[get_msg_type_id("int")].fmt_fn == ex_int_fmt); 146 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 pubsub_items_clear_bindings(items); 152 SMARTLIST_FOREACH_BEGIN(items->items, pubsub_cfg_t *, item) { 153 if (item->is_publish) { 154 tt_assert(item->pub_binding); 155 tt_ptr_op(item->pub_binding->dispatch_ptr, OP_EQ, NULL); 156 } 157 } SMARTLIST_FOREACH_END(item); 158 159 done: 160 pubsub_connector_free(c); 161 pubsub_builder_free(b); 162 dispatch_free(dispatcher); 163 pubsub_items_free(items); 164 } 165 166 /* We fail if the same type is defined in two places with different functions. 167 */ 168 static void 169 test_pubsub_build_types_decls_conflict(void *arg) 170 { 171 (void)arg; 172 pubsub_builder_t *b = NULL; 173 dispatch_t *dispatcher = NULL; 174 pubsub_connector_t *c = NULL; 175 176 b = pubsub_builder_new(); 177 seed_pubsub_builder_basic(b); 178 { 179 c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3")); 180 // Extra declaration of int: we don't allow this. 181 DISPATCH_REGISTER_TYPE(c, int, &stringfns); 182 pubsub_connector_free(c); 183 } 184 185 setup_full_capture_of_logs(LOG_WARN); 186 dispatcher = pubsub_builder_finalize(b, NULL); 187 b = NULL; 188 tt_assert(dispatcher == NULL); 189 // expect_log_msg_containing("(int) declared twice"); // XXXX 190 191 done: 192 pubsub_connector_free(c); 193 pubsub_builder_free(b); 194 dispatch_free(dispatcher); 195 teardown_capture_of_logs(); 196 } 197 198 /* If a message ID exists but nobody is publishing or subscribing to it, 199 * that's okay. */ 200 static void 201 test_pubsub_build_unused_message(void *arg) 202 { 203 (void)arg; 204 pubsub_builder_t *b = NULL; 205 dispatch_t *dispatcher = NULL; 206 207 b = pubsub_builder_new(); 208 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 get_message_id("unused"); 213 setup_capture_of_logs(LOG_INFO); 214 215 dispatcher = pubsub_builder_finalize(b, NULL); 216 b = NULL; 217 tt_assert(dispatcher); 218 expect_log_msg_containing( 219 "Nobody is publishing or subscribing to message"); 220 221 done: 222 pubsub_builder_free(b); 223 dispatch_free(dispatcher); 224 teardown_capture_of_logs(); 225 } 226 227 /* Publishing or subscribing to a message with no subscribers / publishers 228 * should fail and warn. */ 229 static void 230 test_pubsub_build_missing_pubsub(void *arg) 231 { 232 (void)arg; 233 pubsub_builder_t *b = NULL; 234 dispatch_t *dispatcher = NULL; 235 236 b = pubsub_builder_new(); 237 seed_dispatch_builder(b, 0, 0, FLAG_SKIP, FLAG_SKIP); 238 239 setup_full_capture_of_logs(LOG_WARN); 240 dispatcher = pubsub_builder_finalize(b, NULL); 241 b = NULL; 242 tt_assert(dispatcher == NULL); 243 244 expect_log_msg_containing( 245 "Message \"bunch_of_coconuts\" has publishers, but no subscribers."); 246 expect_log_msg_containing( 247 "Message \"yes_we_have_no\" has subscribers, but no publishers."); 248 249 done: 250 pubsub_builder_free(b); 251 dispatch_free(dispatcher); 252 teardown_capture_of_logs(); 253 } 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 test_pubsub_build_stub_pubsub(void *arg) 260 { 261 (void)arg; 262 pubsub_builder_t *b = NULL; 263 dispatch_t *dispatcher = NULL; 264 265 b = pubsub_builder_new(); 266 seed_dispatch_builder(b, 0, 0, DISP_FLAG_STUB, DISP_FLAG_STUB); 267 268 dispatcher = pubsub_builder_finalize(b, NULL); 269 b = NULL; 270 tt_assert(dispatcher); 271 272 // 1 subscriber. 273 tt_int_op(1, OP_EQ, 274 dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled); 275 // no subscribers 276 tt_ptr_op(NULL, OP_EQ, 277 dispatcher->table[get_message_id("bunch_of_coconuts")]); 278 279 done: 280 pubsub_builder_free(b); 281 dispatch_free(dispatcher); 282 } 283 284 /* Only one channel per msg id. */ 285 static void 286 test_pubsub_build_channels_conflict(void *arg) 287 { 288 (void)arg; 289 pubsub_builder_t *b = NULL; 290 dispatch_t *dispatcher = NULL; 291 pubsub_connector_t *c = NULL; 292 293 b = pubsub_builder_new(); 294 seed_pubsub_builder_basic(b); 295 pub_binding_t btmp; 296 297 { 298 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 pubsub_add_pub_(c, &btmp, get_channel_id("hithere"), 302 get_message_id("bunch_of_coconuts"), 303 get_msg_type_id("int"), 304 0 /* flags */, 305 "somewhere.c", 22); 306 pubsub_connector_free(c); 307 }; 308 309 setup_full_capture_of_logs(LOG_WARN); 310 dispatcher = pubsub_builder_finalize(b, NULL); 311 b = NULL; 312 tt_assert(dispatcher == NULL); 313 314 expect_log_msg_containing("Message \"bunch_of_coconuts\" is associated " 315 "with multiple inconsistent channels."); 316 317 done: 318 pubsub_builder_free(b); 319 dispatch_free(dispatcher); 320 teardown_capture_of_logs(); 321 } 322 323 /* Only one type per msg id. */ 324 static void 325 test_pubsub_build_types_conflict(void *arg) 326 { 327 (void)arg; 328 pubsub_builder_t *b = NULL; 329 dispatch_t *dispatcher = NULL; 330 pubsub_connector_t *c = NULL; 331 332 b = pubsub_builder_new(); 333 seed_pubsub_builder_basic(b); 334 pub_binding_t btmp; 335 336 { 337 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 pubsub_add_pub_(c, &btmp, get_channel_id("hithere"), 341 get_message_id("bunch_of_coconuts"), 342 get_msg_type_id("string"), 343 0 /* flags */, 344 "somewhere.c", 22); 345 pubsub_connector_free(c); 346 }; 347 348 setup_full_capture_of_logs(LOG_WARN); 349 dispatcher = pubsub_builder_finalize(b, NULL); 350 b = NULL; 351 tt_assert(dispatcher == NULL); 352 353 expect_log_msg_containing("Message \"bunch_of_coconuts\" is associated " 354 "with multiple inconsistent message types."); 355 356 done: 357 pubsub_builder_free(b); 358 dispatch_free(dispatcher); 359 teardown_capture_of_logs(); 360 } 361 362 /* The same module can't publish and subscribe the same message */ 363 static void 364 test_pubsub_build_pubsub_same(void *arg) 365 { 366 (void)arg; 367 pubsub_builder_t *b = NULL; 368 dispatch_t *dispatcher = NULL; 369 pubsub_connector_t *c = NULL; 370 371 b = pubsub_builder_new(); 372 seed_pubsub_builder_basic(b); 373 374 { 375 c = pubsub_connector_for_subsystem(b, get_subsys_id("sys1")); 376 // already publishing this. 377 DISPATCH_ADD_SUB(c, main, bunch_of_coconuts); 378 pubsub_connector_free(c); 379 }; 380 381 setup_full_capture_of_logs(LOG_WARN); 382 dispatcher = pubsub_builder_finalize(b, NULL); 383 b = NULL; 384 tt_assert(dispatcher == NULL); 385 386 expect_log_msg_containing("Message \"bunch_of_coconuts\" is published " 387 "and subscribed by the same subsystem \"sys1\"."); 388 389 done: 390 pubsub_builder_free(b); 391 dispatch_free(dispatcher); 392 teardown_capture_of_logs(); 393 } 394 395 /* More than one subsystem may publish or subscribe, and that's okay. */ 396 static void 397 test_pubsub_build_pubsub_multi(void *arg) 398 { 399 (void)arg; 400 pubsub_builder_t *b = NULL; 401 dispatch_t *dispatcher = NULL; 402 pubsub_connector_t *c = NULL; 403 404 b = pubsub_builder_new(); 405 seed_pubsub_builder_basic(b); 406 pub_binding_t btmp; 407 408 { 409 c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3")); 410 DISPATCH_ADD_SUB(c, main, bunch_of_coconuts); 411 pubsub_add_pub_(c, &btmp, get_channel_id("main"), 412 get_message_id("yes_we_have_no"), 413 get_msg_type_id("string"), 414 0 /* flags */, 415 "somewhere.c", 22); 416 pubsub_connector_free(c); 417 }; 418 419 dispatcher = pubsub_builder_finalize(b, NULL); 420 b = NULL; 421 tt_assert(dispatcher); 422 423 // 1 subscribers 424 tt_int_op(1, OP_EQ, 425 dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled); 426 // 2 subscribers. 427 dtbl_entry_t *ent = 428 dispatcher->table[get_message_id("bunch_of_coconuts")]; 429 tt_int_op(2, OP_EQ, ent->n_enabled); 430 tt_int_op(2, OP_EQ, ent->n_fns); 431 tt_ptr_op(ent->rcv[0].fn, OP_EQ, recv_fn__bunch_of_coconuts); 432 tt_ptr_op(ent->rcv[1].fn, OP_EQ, recv_fn__bunch_of_coconuts); 433 434 done: 435 pubsub_builder_free(b); 436 dispatch_free(dispatcher); 437 } 438 439 static void 440 some_other_coconut_hook(const msg_t *m) 441 { 442 (void)m; 443 } 444 445 /* Subscribe hooks should be build correctly when there are a bunch of 446 * them. */ 447 static void 448 test_pubsub_build_sub_many(void *arg) 449 { 450 (void)arg; 451 pubsub_builder_t *b = NULL; 452 dispatch_t *dispatcher = NULL; 453 pubsub_connector_t *c = NULL; 454 char *sysname = NULL; 455 b = pubsub_builder_new(); 456 seed_pubsub_builder_basic(b); 457 458 int i; 459 for (i = 1; i < 100; ++i) { 460 tor_asprintf(&sysname, "system%d",i); 461 c = pubsub_connector_for_subsystem(b, get_subsys_id(sysname)); 462 if (i % 7) { 463 DISPATCH_ADD_SUB(c, main, bunch_of_coconuts); 464 } else { 465 pubsub_add_sub_(c, some_other_coconut_hook, 466 get_channel_id("main"), 467 get_message_id("bunch_of_coconuts"), 468 get_msg_type_id("int"), 469 0 /* flags */, 470 "somewhere.c", 22); 471 } 472 pubsub_connector_free(c); 473 tor_free(sysname); 474 }; 475 476 dispatcher = pubsub_builder_finalize(b, NULL); 477 b = NULL; 478 tt_assert(dispatcher); 479 480 dtbl_entry_t *ent = 481 dispatcher->table[get_message_id("bunch_of_coconuts")]; 482 tt_int_op(100, OP_EQ, ent->n_enabled); 483 tt_int_op(100, OP_EQ, ent->n_fns); 484 tt_ptr_op(ent->rcv[0].fn, OP_EQ, recv_fn__bunch_of_coconuts); 485 tt_ptr_op(ent->rcv[1].fn, OP_EQ, recv_fn__bunch_of_coconuts); 486 tt_ptr_op(ent->rcv[76].fn, OP_EQ, recv_fn__bunch_of_coconuts); 487 tt_ptr_op(ent->rcv[77].fn, OP_EQ, some_other_coconut_hook); 488 tt_ptr_op(ent->rcv[78].fn, OP_EQ, recv_fn__bunch_of_coconuts); 489 490 done: 491 pubsub_builder_free(b); 492 dispatch_free(dispatcher); 493 tor_free(sysname); 494 } 495 496 /* It's fine to declare the excl flag. */ 497 static void 498 test_pubsub_build_excl_ok(void *arg) 499 { 500 (void)arg; 501 pubsub_builder_t *b = NULL; 502 dispatch_t *dispatcher = NULL; 503 504 b = pubsub_builder_new(); 505 // Try one excl/excl pair and one excl/non pair. 506 seed_dispatch_builder(b, DISP_FLAG_EXCL, 0, 507 DISP_FLAG_EXCL, DISP_FLAG_EXCL); 508 509 dispatcher = pubsub_builder_finalize(b, NULL); 510 b = NULL; 511 tt_assert(dispatcher); 512 513 // 1 subscribers 514 tt_int_op(1, OP_EQ, 515 dispatcher->table[get_message_id("yes_we_have_no")]->n_enabled); 516 // 1 subscriber. 517 tt_int_op(1, OP_EQ, 518 dispatcher->table[get_message_id("bunch_of_coconuts")]->n_enabled); 519 520 done: 521 pubsub_builder_free(b); 522 dispatch_free(dispatcher); 523 } 524 525 /* but if you declare the excl flag, you need to mean it. */ 526 static void 527 test_pubsub_build_excl_bad(void *arg) 528 { 529 (void)arg; 530 pubsub_builder_t *b = NULL; 531 dispatch_t *dispatcher = NULL; 532 pubsub_connector_t *c = NULL; 533 534 b = pubsub_builder_new(); 535 seed_dispatch_builder(b, DISP_FLAG_EXCL, DISP_FLAG_EXCL, 536 0, 0); 537 538 { 539 c = pubsub_connector_for_subsystem(b, get_subsys_id("sys3")); 540 DISPATCH_ADD_PUB_(c, main, bunch_of_coconuts, 0); 541 DISPATCH_ADD_SUB_(c, main, yes_we_have_no, 0); 542 pubsub_connector_free(c); 543 }; 544 545 setup_full_capture_of_logs(LOG_WARN); 546 dispatcher = pubsub_builder_finalize(b, NULL); 547 b = NULL; 548 tt_assert(dispatcher == NULL); 549 550 expect_log_msg_containing("has multiple publishers, but at least one is " 551 "marked as exclusive."); 552 expect_log_msg_containing("has multiple subscribers, but at least one is " 553 "marked as exclusive."); 554 555 done: 556 pubsub_builder_free(b); 557 dispatch_free(dispatcher); 558 teardown_capture_of_logs(); 559 } 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 };