diff --git a/crates/simulation/tests/distribution_properties.rs b/crates/simulation/tests/distribution_properties.rs index 760018a..dfaba63 100644 --- a/crates/simulation/tests/distribution_properties.rs +++ b/crates/simulation/tests/distribution_properties.rs @@ -484,3 +484,73 @@ fn large_cluster_registry_converges() { result.actual ); } + +// ──────────────────────────────────────────────────────────────────────────── +// 10. Three-way partition: cluster splits into 3 groups, heals, converges +// ──────────────────────────────────────────────────────────────────────────── + +#[test] +fn three_way_partition_heals_and_converges() { + // Given: 9 nodes split into 3 groups {0,1,2}, {3,4,5}, {6,7,8}. + // Each group registers a name during the partition. After healing, + // all 9 nodes should converge on all 3 names. + let config = DistributionSimConfig { + name: "three-way-partition".into(), + num_nodes: 9, + num_rounds: 140, + ticks_per_round: 3, + actors_per_node: 0, + network_faults: vec![ + // Partition A vs B + NetworkFault::Partition { + round: 10, + partition: Partition { + side_a: vec![0, 1, 2], + side_b: vec![3, 4, 5, 6, 7, 8], + asymmetric: false, + }, + }, + // Partition B vs C (stacks with the above: now 3 groups isolated) + NetworkFault::Partition { + round: 10, + partition: Partition { + side_a: vec![3, 4, 5], + side_b: vec![6, 7, 8], + asymmetric: false, + }, + }, + NetworkFault::Heal { round: 60 }, + ], + action_schedule: vec![ + (20, SimAction::RegisterName { node_idx: 0, name: "group-a-svc".into() }), + (20, SimAction::RegisterName { node_idx: 3, name: "group-b-svc".into() }), + (20, SimAction::RegisterName { node_idx: 6, name: "group-c-svc".into() }), + ], + swim: distribution::swim::probe::SwimConfig { + probe_interval: 1, + probe_timeout: 3, + indirect_probes: 1, + // Must exceed partition duration (50 rounds × 3 ticks = 150 ticks) + suspicion_timeout: 200, + dead_reprobe_interval: 10, + }, + ..DistributionSimConfig::default() + }; + + let (_trace, nodes) = run_simulation_with_nodes(config); + + let alive_count = nodes.iter().filter(|n| n.is_some()).count(); + assert_eq!(alive_count, 9, "all 9 nodes should survive the three-way partition"); + + // After healing + gossip, all 3 names should be resolvable from every node + for (i, node) in nodes.iter().enumerate() { + if let Some(node) = node { + for name in &["group-a-svc", "group-b-svc", "group-c-svc"] { + assert!( + node.resolve_name(name).is_some(), + "node {i} should resolve '{name}' after three-way partition heals" + ); + } + } + } +}