this repo has no description
at main 16 kB view raw
1open Swim.Types 2open Swim.Protocol_pure 3 4let node1 = 5 make_node_info 6 ~id:(node_id_of_string "node1") 7 ~addr:(`Udp (Eio.Net.Ipaddr.of_raw "\127\000\000\001", 7946)) 8 ~meta:"" 9 10let node2 = 11 make_node_info 12 ~id:(node_id_of_string "node2") 13 ~addr:(`Udp (Eio.Net.Ipaddr.of_raw "\127\000\000\002", 7946)) 14 ~meta:"" 15 16let node3 = 17 make_node_info 18 ~id:(node_id_of_string "node3") 19 ~addr:(`Udp (Eio.Net.Ipaddr.of_raw "\127\000\000\003", 7946)) 20 ~meta:"" 21 22let now = Mtime.Span.of_uint64_ns 0L 23let alive_state : member_state = Alive 24let suspect_state : member_state = Suspect 25let dead_state : member_state = Dead 26 27let make_member ?(state = alive_state) ?(incarnation = 0) node = 28 { 29 node; 30 state; 31 incarnation = incarnation_of_int incarnation; 32 state_change = now; 33 } 34 35let test_alive_higher_incarnation_wins () = 36 let member = make_member ~incarnation:1 node1 in 37 let msg = Alive { node = node1; incarnation = incarnation_of_int 2 } in 38 let result = handle_alive ~self:(node_id_of_string "self") member msg ~now in 39 Alcotest.(check int) 40 "incarnation" 2 41 (incarnation_to_int result.new_state.incarnation); 42 Alcotest.(check bool) "broadcast" true (List.length result.broadcasts = 1) 43 44let test_alive_lower_incarnation_ignored () = 45 let member = make_member ~incarnation:5 node1 in 46 let msg = Alive { node = node1; incarnation = incarnation_of_int 3 } in 47 let result = handle_alive ~self:(node_id_of_string "self") member msg ~now in 48 Alcotest.(check int) 49 "incarnation unchanged" 5 50 (incarnation_to_int result.new_state.incarnation); 51 Alcotest.(check bool) "no broadcast" true (List.length result.broadcasts = 0) 52 53let test_alive_same_incarnation_unsuspects () = 54 let member = make_member ~state:suspect_state ~incarnation:3 node1 in 55 let msg = Alive { node = node1; incarnation = incarnation_of_int 3 } in 56 let result = handle_alive ~self:(node_id_of_string "self") member msg ~now in 57 Alcotest.(check string) 58 "state alive" "alive" 59 (member_state_to_string result.new_state.state) 60 61let test_alive_revives_dead_node () = 62 let member = make_member ~state:dead_state ~incarnation:1 node1 in 63 let msg = Alive { node = node1; incarnation = incarnation_of_int 5 } in 64 let result = handle_alive ~self:(node_id_of_string "self") member msg ~now in 65 Alcotest.(check string) 66 "state alive" "alive" 67 (member_state_to_string result.new_state.state); 68 match result.events with 69 | [ Join _ ] -> () 70 | _ -> Alcotest.fail "expected Join event" 71 72let test_suspect_triggers_refute_for_self () = 73 let member = make_member ~incarnation:1 node1 in 74 let msg = 75 Suspect 76 { 77 node = node_id_of_string "node1"; 78 incarnation = incarnation_of_int 1; 79 suspector = node_id_of_string "node2"; 80 } 81 in 82 let result = 83 handle_suspect ~self:(node_id_of_string "node1") member msg ~now 84 in 85 Alcotest.(check int) 86 "incarnation incremented" 2 87 (incarnation_to_int result.new_state.incarnation); 88 match result.broadcasts with 89 | [ Alive { incarnation; _ } ] -> 90 Alcotest.(check int) 91 "refute incarnation" 2 92 (incarnation_to_int incarnation) 93 | _ -> Alcotest.fail "expected Alive refute broadcast" 94 95let test_suspect_higher_incarnation_suspects () = 96 let member = make_member ~state:alive_state ~incarnation:1 node1 in 97 let msg = 98 Suspect 99 { 100 node = node_id_of_string "node1"; 101 incarnation = incarnation_of_int 2; 102 suspector = node_id_of_string "node2"; 103 } 104 in 105 let result = 106 handle_suspect ~self:(node_id_of_string "self") member msg ~now 107 in 108 Alcotest.(check string) 109 "state suspect" "suspect" 110 (member_state_to_string result.new_state.state) 111 112let test_suspect_lower_incarnation_ignored () = 113 let member = make_member ~state:alive_state ~incarnation:5 node1 in 114 let msg = 115 Suspect 116 { 117 node = node_id_of_string "node1"; 118 incarnation = incarnation_of_int 3; 119 suspector = node_id_of_string "node2"; 120 } 121 in 122 let result = 123 handle_suspect ~self:(node_id_of_string "self") member msg ~now 124 in 125 Alcotest.(check string) 126 "state unchanged" "alive" 127 (member_state_to_string result.new_state.state); 128 Alcotest.(check bool) "no broadcast" true (List.length result.broadcasts = 0) 129 130let test_suspect_dead_node_ignored () = 131 let member = make_member ~state:dead_state ~incarnation:1 node1 in 132 let msg = 133 Suspect 134 { 135 node = node_id_of_string "node1"; 136 incarnation = incarnation_of_int 5; 137 suspector = node_id_of_string "node2"; 138 } 139 in 140 let result = 141 handle_suspect ~self:(node_id_of_string "self") member msg ~now 142 in 143 Alcotest.(check string) 144 "state dead" "dead" 145 (member_state_to_string result.new_state.state) 146 147let test_dead_marks_node_dead () = 148 let member = make_member ~state:alive_state ~incarnation:1 node1 in 149 let msg = 150 Dead 151 { 152 node = node_id_of_string "node1"; 153 incarnation = incarnation_of_int 2; 154 declarator = node_id_of_string "node2"; 155 } 156 in 157 let result = handle_dead member msg ~now in 158 Alcotest.(check string) 159 "state dead" "dead" 160 (member_state_to_string result.new_state.state); 161 match result.events with 162 | [ Leave _ ] -> () 163 | _ -> Alcotest.fail "expected Leave event" 164 165let test_dead_already_dead_ignored () = 166 let member = make_member ~state:dead_state ~incarnation:5 node1 in 167 let msg = 168 Dead 169 { 170 node = node_id_of_string "node1"; 171 incarnation = incarnation_of_int 10; 172 declarator = node_id_of_string "node2"; 173 } 174 in 175 let result = handle_dead member msg ~now in 176 Alcotest.(check bool) "no events" true (List.length result.events = 0) 177 178let test_dead_lower_incarnation_ignored () = 179 let member = make_member ~state:alive_state ~incarnation:10 node1 in 180 let msg = 181 Dead 182 { 183 node = node_id_of_string "node1"; 184 incarnation = incarnation_of_int 5; 185 declarator = node_id_of_string "node2"; 186 } 187 in 188 let result = handle_dead member msg ~now in 189 Alcotest.(check string) 190 "state alive" "alive" 191 (member_state_to_string result.new_state.state) 192 193let test_invalidates_dead_beats_all () = 194 let dead_msg = 195 Dead 196 { 197 node = node_id_of_string "node1"; 198 incarnation = incarnation_of_int 1; 199 declarator = node_id_of_string "node2"; 200 } 201 in 202 let alive_msg = Alive { node = node1; incarnation = incarnation_of_int 5 } in 203 let suspect_msg = 204 Suspect 205 { 206 node = node_id_of_string "node1"; 207 incarnation = incarnation_of_int 5; 208 suspector = node_id_of_string "node2"; 209 } 210 in 211 Alcotest.(check bool) 212 "dead invalidates alive" true 213 (invalidates ~newer:dead_msg ~older:alive_msg); 214 Alcotest.(check bool) 215 "dead invalidates suspect" true 216 (invalidates ~newer:dead_msg ~older:suspect_msg) 217 218let test_invalidates_alive_beats_suspect_same_inc () = 219 let alive_msg = Alive { node = node1; incarnation = incarnation_of_int 5 } in 220 let suspect_msg = 221 Suspect 222 { 223 node = node_id_of_string "node1"; 224 incarnation = incarnation_of_int 5; 225 suspector = node_id_of_string "node2"; 226 } 227 in 228 Alcotest.(check bool) 229 "alive beats suspect" true 230 (invalidates ~newer:alive_msg ~older:suspect_msg) 231 232let test_invalidates_higher_incarnation_wins () = 233 let alive_old = Alive { node = node1; incarnation = incarnation_of_int 1 } in 234 let alive_new = Alive { node = node1; incarnation = incarnation_of_int 2 } in 235 Alcotest.(check bool) 236 "higher inc wins" true 237 (invalidates ~newer:alive_new ~older:alive_old); 238 Alcotest.(check bool) 239 "lower inc doesnt" false 240 (invalidates ~newer:alive_old ~older:alive_new) 241 242let test_invalidates_different_nodes_false () = 243 let alive1 = Alive { node = node1; incarnation = incarnation_of_int 10 } in 244 let alive2 = Alive { node = node2; incarnation = incarnation_of_int 1 } in 245 Alcotest.(check bool) 246 "different nodes" false 247 (invalidates ~newer:alive1 ~older:alive2) 248 249let test_merge_dead_local_wins () = 250 let local = make_member ~state:dead_state ~incarnation:1 node1 in 251 let remote = make_member ~state:alive_state ~incarnation:10 node1 in 252 let result = merge_member_state ~local ~remote in 253 Alcotest.(check string) 254 "local dead wins" "dead" 255 (member_state_to_string result.state) 256 257let test_merge_remote_dead_higher_inc_wins () = 258 let local = make_member ~state:alive_state ~incarnation:1 node1 in 259 let remote = make_member ~state:dead_state ~incarnation:5 node1 in 260 let result = merge_member_state ~local ~remote in 261 Alcotest.(check string) 262 "remote dead wins" "dead" 263 (member_state_to_string result.state) 264 265let test_merge_higher_incarnation_wins () = 266 let local = make_member ~state:alive_state ~incarnation:1 node1 in 267 let remote = make_member ~state:alive_state ~incarnation:5 node1 in 268 let result = merge_member_state ~local ~remote in 269 Alcotest.(check int) "higher inc" 5 (incarnation_to_int result.incarnation) 270 271let test_merge_suspect_beats_alive_higher_inc () = 272 let local = make_member ~state:alive_state ~incarnation:1 node1 in 273 let remote = make_member ~state:suspect_state ~incarnation:5 node1 in 274 let result = merge_member_state ~local ~remote in 275 Alcotest.(check string) 276 "suspect" "suspect" 277 (member_state_to_string result.state) 278 279let test_merge_alive_beats_suspect_same_or_higher_inc () = 280 let local = make_member ~state:suspect_state ~incarnation:3 node1 in 281 let remote = make_member ~state:alive_state ~incarnation:3 node1 in 282 let result = merge_member_state ~local ~remote in 283 Alcotest.(check string) "alive" "alive" (member_state_to_string result.state) 284 285let test_suspicion_timeout_increases_with_nodes () = 286 let config = default_config in 287 let t1 = suspicion_timeout config ~node_count:10 in 288 let t2 = suspicion_timeout config ~node_count:100 in 289 Alcotest.(check bool) "more nodes = longer timeout" true (t2 > t1) 290 291let test_suspicion_timeout_bounded () = 292 let config = { default_config with suspicion_max_timeout = 30.0 } in 293 let t = suspicion_timeout config ~node_count:1000000 in 294 Alcotest.(check bool) "bounded" true (t <= 30.0) 295 296let test_suspicion_timeout_zero_nodes () = 297 let config = default_config in 298 let t = suspicion_timeout config ~node_count:0 in 299 Alcotest.(check bool) "handles zero" true (t >= 0.0) 300 301let test_retransmit_limit_increases_with_nodes () = 302 let config = default_config in 303 let r1 = retransmit_limit config ~node_count:10 in 304 let r2 = retransmit_limit config ~node_count:100 in 305 Alcotest.(check bool) "more nodes = higher limit" true (r2 > r1) 306 307let test_next_probe_empty_list () = 308 let result = 309 next_probe_target ~self:(node_id_of_string "self") ~probe_index:0 310 ~members:[] 311 in 312 match result with None -> () | Some _ -> Alcotest.fail "expected None" 313 314let test_next_probe_skips_self () = 315 let self = node_id_of_string "node1" in 316 let result = 317 next_probe_target ~self ~probe_index:0 ~members:[ node1; node2 ] 318 in 319 match result with 320 | Some (target, _) -> 321 Alcotest.(check bool) "not self" false (equal_node_id target.id self) 322 | None -> Alcotest.fail "expected Some" 323 324let test_next_probe_wraps_around () = 325 let self = node_id_of_string "self" in 326 let result = 327 next_probe_target ~self ~probe_index:5 ~members:[ node1; node2 ] 328 in 329 match result with Some _ -> () | None -> Alcotest.fail "expected Some" 330 331let test_next_probe_all_self_returns_none () = 332 let self = node_id_of_string "node1" in 333 let result = next_probe_target ~self ~probe_index:0 ~members:[ node1 ] in 334 match result with 335 | None -> () 336 | Some _ -> Alcotest.fail "expected None when only self" 337 338let test_select_indirect_targets_excludes_self_and_target () = 339 let self = node_id_of_string "node1" in 340 let exclude = node_id_of_string "node2" in 341 let result = 342 select_indirect_targets ~self ~exclude ~count:10 343 ~members:[ node1; node2; node3 ] 344 in 345 Alcotest.(check int) "only node3" 1 (List.length result); 346 Alcotest.(check bool) 347 "is node3" true 348 (equal_node_id (List.hd result).id node3.id) 349 350let test_select_indirect_targets_limits_count () = 351 let self = node_id_of_string "self" in 352 let exclude = node_id_of_string "exclude" in 353 let result = 354 select_indirect_targets ~self ~exclude ~count:1 355 ~members:[ node1; node2; node3 ] 356 in 357 Alcotest.(check int) "limited to 1" 1 (List.length result) 358 359let clamp_incarnation inc = 360 incarnation_of_int (incarnation_to_int inc land 0x7FFFFFFF) 361 362let test_merge_converges = 363 QCheck.Test.make ~count:200 364 ~name:"merge converges (applying twice yields same result)" 365 (QCheck.pair Generators.arb_member_snapshot Generators.arb_member_snapshot) 366 (fun (a, b) -> 367 let a = 368 { a with node = node1; incarnation = clamp_incarnation a.incarnation } 369 in 370 let b = 371 { b with node = node1; incarnation = clamp_incarnation b.incarnation } 372 in 373 let ab = merge_member_state ~local:a ~remote:b in 374 let ab2 = merge_member_state ~local:ab ~remote:b in 375 ab.state = ab2.state && ab.incarnation = ab2.incarnation) 376 377let test_merge_idempotent = 378 QCheck.Test.make ~count:200 ~name:"merge is idempotent" 379 Generators.arb_member_snapshot (fun a -> 380 let result = merge_member_state ~local:a ~remote:a in 381 result.state = a.state && result.incarnation = a.incarnation) 382 383let qcheck_tests = 384 List.map QCheck_alcotest.to_alcotest 385 [ test_merge_converges; test_merge_idempotent ] 386 387let unit_tests = 388 [ 389 ("alive_higher_incarnation_wins", `Quick, test_alive_higher_incarnation_wins); 390 ( "alive_lower_incarnation_ignored", 391 `Quick, 392 test_alive_lower_incarnation_ignored ); 393 ( "alive_same_incarnation_unsuspects", 394 `Quick, 395 test_alive_same_incarnation_unsuspects ); 396 ("alive_revives_dead_node", `Quick, test_alive_revives_dead_node); 397 ( "suspect_triggers_refute_for_self", 398 `Quick, 399 test_suspect_triggers_refute_for_self ); 400 ( "suspect_higher_incarnation_suspects", 401 `Quick, 402 test_suspect_higher_incarnation_suspects ); 403 ( "suspect_lower_incarnation_ignored", 404 `Quick, 405 test_suspect_lower_incarnation_ignored ); 406 ("suspect_dead_node_ignored", `Quick, test_suspect_dead_node_ignored); 407 ("dead_marks_node_dead", `Quick, test_dead_marks_node_dead); 408 ("dead_already_dead_ignored", `Quick, test_dead_already_dead_ignored); 409 ( "dead_lower_incarnation_ignored", 410 `Quick, 411 test_dead_lower_incarnation_ignored ); 412 ("invalidates_dead_beats_all", `Quick, test_invalidates_dead_beats_all); 413 ( "invalidates_alive_beats_suspect_same_inc", 414 `Quick, 415 test_invalidates_alive_beats_suspect_same_inc ); 416 ( "invalidates_higher_incarnation_wins", 417 `Quick, 418 test_invalidates_higher_incarnation_wins ); 419 ( "invalidates_different_nodes_false", 420 `Quick, 421 test_invalidates_different_nodes_false ); 422 ("merge_dead_local_wins", `Quick, test_merge_dead_local_wins); 423 ( "merge_remote_dead_higher_inc_wins", 424 `Quick, 425 test_merge_remote_dead_higher_inc_wins ); 426 ("merge_higher_incarnation_wins", `Quick, test_merge_higher_incarnation_wins); 427 ( "merge_suspect_beats_alive_higher_inc", 428 `Quick, 429 test_merge_suspect_beats_alive_higher_inc ); 430 ( "merge_alive_beats_suspect_same_or_higher_inc", 431 `Quick, 432 test_merge_alive_beats_suspect_same_or_higher_inc ); 433 ( "suspicion_timeout_increases_with_nodes", 434 `Quick, 435 test_suspicion_timeout_increases_with_nodes ); 436 ("suspicion_timeout_bounded", `Quick, test_suspicion_timeout_bounded); 437 ("suspicion_timeout_zero_nodes", `Quick, test_suspicion_timeout_zero_nodes); 438 ( "retransmit_limit_increases_with_nodes", 439 `Quick, 440 test_retransmit_limit_increases_with_nodes ); 441 ("next_probe_empty_list", `Quick, test_next_probe_empty_list); 442 ("next_probe_skips_self", `Quick, test_next_probe_skips_self); 443 ("next_probe_wraps_around", `Quick, test_next_probe_wraps_around); 444 ( "next_probe_all_self_returns_none", 445 `Quick, 446 test_next_probe_all_self_returns_none ); 447 ( "select_indirect_targets_excludes", 448 `Quick, 449 test_select_indirect_targets_excludes_self_and_target ); 450 ( "select_indirect_targets_limits", 451 `Quick, 452 test_select_indirect_targets_limits_count ); 453 ] 454 455let () = 456 Alcotest.run "protocol_pure" 457 [ ("property", qcheck_tests); ("unit", unit_tests) ]