use std::collections::HashMap;
use super::*;
fn make_node_addr(val: u8) -> NodeAddr {
let mut bytes = [0u8; 16];
bytes[0] = val;
NodeAddr::from_bytes(bytes)
}
fn make_coords(ids: &[u8]) -> TreeCoordinate {
TreeCoordinate::from_addrs(ids.iter().map(|&v| make_node_addr(v)).collect()).unwrap()
}
#[test]
fn test_tree_coordinate_root() {
let root_id = make_node_addr(1);
let coord = TreeCoordinate::root(root_id);
assert!(coord.is_root());
assert_eq!(coord.depth(), 0);
assert_eq!(coord.node_addr(), &root_id);
assert_eq!(coord.root_id(), &root_id);
assert_eq!(coord.parent_id(), &root_id);
}
#[test]
fn test_tree_coordinate_path() {
let node = make_node_addr(1);
let parent = make_node_addr(2);
let root = make_node_addr(3);
let coord = make_coords(&[1, 2, 3]);
assert!(!coord.is_root());
assert_eq!(coord.depth(), 2);
assert_eq!(coord.node_addr(), &node);
assert_eq!(coord.parent_id(), &parent);
assert_eq!(coord.root_id(), &root);
}
#[test]
fn test_tree_coordinate_empty_fails() {
let result = TreeCoordinate::from_addrs(vec![]);
assert!(matches!(result, Err(TreeError::EmptyCoordinate)));
}
#[test]
fn test_tree_coordinate_entries_metadata() {
let node = make_node_addr(1);
let root = make_node_addr(0);
let coord = TreeCoordinate::new(vec![
CoordEntry::new(node, 5, 1000),
CoordEntry::new(root, 1, 500),
])
.unwrap();
assert_eq!(coord.entries()[0].sequence, 5);
assert_eq!(coord.entries()[0].timestamp, 1000);
assert_eq!(coord.entries()[1].sequence, 1);
assert_eq!(coord.entries()[1].timestamp, 500);
}
#[test]
fn test_tree_distance_same_node() {
let node = make_node_addr(1);
let coord = TreeCoordinate::root(node);
assert_eq!(coord.distance_to(&coord), 0);
}
#[test]
fn test_tree_distance_siblings() {
let coord_a = make_coords(&[1, 0]);
let coord_b = make_coords(&[2, 0]);
assert_eq!(coord_a.distance_to(&coord_b), 2);
}
#[test]
fn test_tree_distance_ancestor() {
let coord_parent = make_coords(&[1, 0]);
let coord_child = make_coords(&[2, 1, 0]);
assert_eq!(coord_child.distance_to(&coord_parent), 1);
}
#[test]
fn test_tree_distance_cousins() {
let coord_c = make_coords(&[3, 1, 0]);
let coord_d = make_coords(&[4, 2, 0]);
assert_eq!(coord_c.distance_to(&coord_d), 4);
}
#[test]
fn test_tree_distance_different_roots() {
let coord1 = TreeCoordinate::root(make_node_addr(1));
let coord2 = TreeCoordinate::root(make_node_addr(2));
assert_eq!(coord1.distance_to(&coord2), usize::MAX);
}
#[test]
fn test_has_ancestor() {
let root = make_node_addr(0);
let parent = make_node_addr(1);
let child = make_node_addr(2);
let coord = make_coords(&[2, 1, 0]);
assert!(coord.has_ancestor(&parent));
assert!(coord.has_ancestor(&root));
assert!(!coord.has_ancestor(&child)); }
#[test]
fn test_contains() {
let root = make_node_addr(0);
let parent = make_node_addr(1);
let child = make_node_addr(2);
let other = make_node_addr(99);
let coord = make_coords(&[2, 1, 0]);
assert!(coord.contains(&child));
assert!(coord.contains(&parent));
assert!(coord.contains(&root));
assert!(!coord.contains(&other));
}
#[test]
fn test_ancestor_at() {
let root = make_node_addr(0);
let parent = make_node_addr(1);
let child = make_node_addr(2);
let coord = make_coords(&[2, 1, 0]);
assert_eq!(coord.ancestor_at(0), Some(&child));
assert_eq!(coord.ancestor_at(1), Some(&parent));
assert_eq!(coord.ancestor_at(2), Some(&root));
assert_eq!(coord.ancestor_at(3), None);
}
#[test]
fn test_lca() {
let root = make_node_addr(0);
let a = make_node_addr(1);
let coord_c = make_coords(&[3, 1, 0]);
let coord_d = make_coords(&[4, 2, 0]);
assert_eq!(coord_c.lca(&coord_d), Some(&root));
let coord_a = make_coords(&[1, 0]);
assert_eq!(coord_c.lca(&coord_a), Some(&a));
}
#[test]
fn test_parent_declaration_new() {
let node = make_node_addr(1);
let parent = make_node_addr(2);
let decl = ParentDeclaration::new(node, parent, 1, 1000);
assert_eq!(decl.node_addr(), &node);
assert_eq!(decl.parent_id(), &parent);
assert_eq!(decl.sequence(), 1);
assert_eq!(decl.timestamp(), 1000);
assert!(!decl.is_root());
assert!(!decl.is_signed());
}
#[test]
fn test_parent_declaration_self_root() {
let node = make_node_addr(1);
let decl = ParentDeclaration::self_root(node, 5, 2000);
assert!(decl.is_root());
assert_eq!(decl.node_addr(), decl.parent_id());
}
#[test]
fn test_parent_declaration_freshness() {
let node = make_node_addr(1);
let parent = make_node_addr(2);
let old_decl = ParentDeclaration::new(node, parent, 1, 1000);
let new_decl = ParentDeclaration::new(node, parent, 2, 2000);
assert!(new_decl.is_fresher_than(&old_decl));
assert!(!old_decl.is_fresher_than(&new_decl));
assert!(!old_decl.is_fresher_than(&old_decl));
}
#[test]
fn test_parent_declaration_signing_bytes() {
let node = make_node_addr(1);
let parent = make_node_addr(2);
let decl = ParentDeclaration::new(node, parent, 100, 1234567890);
let bytes = decl.signing_bytes();
assert_eq!(bytes.len(), 48);
assert_eq!(&bytes[0..16], node.as_bytes());
assert_eq!(&bytes[16..32], parent.as_bytes());
assert_eq!(&bytes[32..40], &100u64.to_le_bytes());
assert_eq!(&bytes[40..48], &1234567890u64.to_le_bytes());
}
#[test]
fn test_parent_declaration_equality() {
let node = make_node_addr(1);
let parent = make_node_addr(2);
let decl1 = ParentDeclaration::new(node, parent, 1, 1000);
let decl2 = ParentDeclaration::new(node, parent, 1, 1000);
let decl3 = ParentDeclaration::new(node, parent, 2, 1000);
assert_eq!(decl1, decl2);
assert_ne!(decl1, decl3);
}
#[test]
fn test_tree_state_new() {
let node = make_node_addr(1);
let state = TreeState::new(node);
assert_eq!(state.my_node_addr(), &node);
assert!(state.is_root());
assert_eq!(state.root(), &node);
assert_eq!(state.my_coords().depth(), 0);
assert_eq!(state.peer_count(), 0);
}
#[test]
fn test_tree_state_update_peer() {
let my_node = make_node_addr(0);
let mut state = TreeState::new(my_node);
let peer = make_node_addr(1);
let root = make_node_addr(2);
let decl = ParentDeclaration::new(peer, root, 1, 1000);
let coords = make_coords(&[1, 2]);
assert!(state.update_peer(decl.clone(), coords.clone()));
assert_eq!(state.peer_count(), 1);
assert!(state.peer_coords(&peer).is_some());
assert!(state.peer_declaration(&peer).is_some());
let decl2 = ParentDeclaration::new(peer, root, 1, 1000);
assert!(!state.update_peer(decl2, coords.clone()));
let decl3 = ParentDeclaration::new(peer, root, 2, 2000);
assert!(state.update_peer(decl3, coords));
}
#[test]
fn test_tree_state_remove_peer() {
let my_node = make_node_addr(0);
let mut state = TreeState::new(my_node);
let peer = make_node_addr(1);
let root = make_node_addr(2);
let decl = ParentDeclaration::new(peer, root, 1, 1000);
let coords = make_coords(&[1, 2]);
state.update_peer(decl, coords);
assert_eq!(state.peer_count(), 1);
state.remove_peer(&peer);
assert_eq!(state.peer_count(), 0);
assert!(state.peer_coords(&peer).is_none());
}
#[test]
fn test_tree_state_distance_to_peer() {
let my_node = make_node_addr(0);
let mut state = TreeState::new(my_node);
let peer = make_node_addr(1);
let peer_coords = TreeCoordinate::root(peer);
let decl = ParentDeclaration::self_root(peer, 1, 1000);
state.update_peer(decl, peer_coords);
assert_eq!(state.distance_to_peer(&peer), Some(usize::MAX));
let shared_root = make_node_addr(99);
state.set_parent(shared_root, 1, 1000);
let my_new_coords = make_coords(&[0, 99]);
state.my_coords = my_new_coords;
state.root = shared_root;
let peer_coords = make_coords(&[1, 99]);
let decl = ParentDeclaration::new(peer, shared_root, 2, 2000);
state.update_peer(decl, peer_coords);
assert_eq!(state.distance_to_peer(&peer), Some(2));
}
#[test]
fn test_tree_state_peer_ids() {
let my_node = make_node_addr(0);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
state.update_peer(
ParentDeclaration::self_root(peer1, 1, 1000),
TreeCoordinate::root(peer1),
);
state.update_peer(
ParentDeclaration::self_root(peer2, 1, 1000),
TreeCoordinate::root(peer2),
);
let ids: Vec<_> = state.peer_ids().collect();
assert_eq!(ids.len(), 2);
assert!(ids.contains(&&peer1));
assert!(ids.contains(&&peer2));
}
#[test]
fn test_evaluate_parent_picks_smallest_root() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer3 = make_node_addr(3);
let peer7 = make_node_addr(7);
state.update_peer(
ParentDeclaration::new(peer3, make_node_addr(1), 1, 1000),
make_coords(&[3, 1]),
);
state.update_peer(
ParentDeclaration::new(peer7, make_node_addr(2), 1, 1000),
make_coords(&[7, 2]),
);
let result = state.evaluate_parent(&HashMap::new());
assert_eq!(result, Some(peer3));
}
#[test]
fn test_evaluate_parent_prefers_shallowest_depth() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer1, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer2, make_node_addr(3), 1, 1000),
make_coords(&[2, 3, 4, 0]),
);
let result = state.evaluate_parent(&HashMap::new());
assert_eq!(result, Some(peer1));
}
#[test]
fn test_evaluate_parent_stays_root_when_smallest() {
let my_node = make_node_addr(0);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
state.update_peer(
ParentDeclaration::new(peer1, my_node, 1, 1000),
make_coords(&[1, 0]),
);
assert_eq!(state.evaluate_parent(&HashMap::new()), None);
}
#[test]
fn test_evaluate_parent_no_switch_when_already_best() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer1, root, 1, 1000),
make_coords(&[1, 0]),
);
state.set_parent(peer1, 1, 1000);
state.recompute_coords();
assert_eq!(state.evaluate_parent(&HashMap::new()), None);
}
#[test]
fn test_evaluate_parent_no_peers() {
let my_node = make_node_addr(5);
let state = TreeState::new(my_node);
assert_eq!(state.evaluate_parent(&HashMap::new()), None);
}
#[test]
fn test_evaluate_parent_depth_threshold() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer2 = make_node_addr(2);
let peer3 = make_node_addr(3);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer2, make_node_addr(6), 1, 1000),
make_coords(&[2, 6, 7, 0]),
);
state.set_parent(peer2, 1, 1000);
state.recompute_coords();
assert_eq!(state.my_coords().depth(), 4);
state.update_peer(
ParentDeclaration::new(peer3, root, 1, 1000),
make_coords(&[3, 0]),
);
let result = state.evaluate_parent(&HashMap::new());
assert_eq!(result, Some(peer3));
}
#[test]
fn test_evaluate_parent_rejects_loop_candidate() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let _root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer1, my_node, 1, 1000),
make_coords(&[1, 5, 0]),
);
assert_eq!(state.evaluate_parent(&HashMap::new()), None);
}
#[test]
fn test_evaluate_parent_picks_loop_free_over_loopy() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
let _root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer1, my_node, 1, 1000),
make_coords(&[1, 5, 0]),
);
state.update_peer(
ParentDeclaration::new(peer2, make_node_addr(3), 1, 1000),
make_coords(&[2, 3, 4, 0]),
);
let result = state.evaluate_parent(&HashMap::new());
assert_eq!(result, Some(peer2));
}
#[test]
fn test_handle_parent_lost_finds_alternative() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer1, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer2, root, 1, 1000),
make_coords(&[2, 0]),
);
state.set_parent(peer1, 1, 1000);
state.recompute_coords();
state.remove_peer(&peer1);
let changed = state.handle_parent_lost(&HashMap::new());
assert!(changed);
assert_eq!(state.my_declaration().parent_id(), &peer2);
assert!(!state.is_root());
}
#[test]
fn test_handle_parent_lost_becomes_root_when_self_smaller_than_remaining() {
let my_node = make_node_addr(1); let mut state = TreeState::new(my_node);
let smaller = make_node_addr(0);
let bigger1 = make_node_addr(2);
let bigger2 = make_node_addr(3);
state.update_peer(
ParentDeclaration::self_root(smaller, 1, 1000),
make_coords(&[0]),
);
state.update_peer(
ParentDeclaration::self_root(bigger1, 1, 1000),
make_coords(&[2]),
);
state.update_peer(
ParentDeclaration::self_root(bigger2, 1, 1000),
make_coords(&[3]),
);
state.set_parent(smaller, 2, 2000);
state.recompute_coords();
assert_eq!(state.my_coords().entries().len(), 2);
assert_eq!(state.root(), &smaller);
state.remove_peer(&smaller);
let changed = state.handle_parent_lost(&HashMap::new());
assert!(changed);
assert!(
state.is_root(),
"must self-root when no smaller peer remains"
);
assert_eq!(state.root(), &my_node);
assert_eq!(state.my_coords().entries().len(), 1);
let entries = state.my_coords().entries();
let min = entries.iter().map(|e| e.node_addr).min().unwrap();
assert_eq!(*state.my_coords().root_id(), min);
}
#[test]
fn test_recompute_coords_demotes_when_self_smaller_than_parent_root() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let bigger_peer = make_node_addr(7);
state.update_peer(
ParentDeclaration::self_root(bigger_peer, 1, 1000),
make_coords(&[7]),
);
state.set_parent(bigger_peer, 2, 2000);
state.recompute_coords();
assert!(state.is_root(), "recompute_coords demoted to self-root");
assert_eq!(state.root(), &my_node);
assert_eq!(state.my_coords().entries().len(), 1);
let entries = state.my_coords().entries();
let min = entries.iter().map(|e| e.node_addr).min().unwrap();
assert_eq!(*state.my_coords().root_id(), min);
}
#[test]
fn test_handle_parent_lost_becomes_root() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer1, root, 1, 1000),
make_coords(&[1, 0]),
);
state.set_parent(peer1, 1, 1000);
state.recompute_coords();
let seq_before = state.my_declaration().sequence();
state.remove_peer(&peer1);
let changed = state.handle_parent_lost(&HashMap::new());
assert!(changed);
assert!(state.is_root());
assert!(state.my_declaration().sequence() > seq_before);
assert_eq!(state.root(), &my_node);
}
fn make_tree_state(my_addr: u8, coord_path: &[u8]) -> TreeState {
let my_node = make_node_addr(my_addr);
let mut state = TreeState::new(my_node);
let coords = make_coords(coord_path);
state.root = *coords.root_id();
state.my_coords = coords;
state
}
fn add_peer(state: &mut TreeState, peer_addr: u8, coord_path: &[u8]) {
let peer = make_node_addr(peer_addr);
let parent = make_node_addr(coord_path[1]);
state.update_peer(
ParentDeclaration::new(peer, parent, 1, 1000),
make_coords(coord_path),
);
}
#[test]
fn test_find_next_hop_chain() {
let mut state = make_tree_state(5, &[5, 0]);
add_peer(&mut state, 1, &[1, 5, 0]);
add_peer(&mut state, 2, &[2, 1, 5, 0]);
let dest = make_coords(&[2, 1, 5, 0]);
assert_eq!(state.find_next_hop(&dest), Some(make_node_addr(2)));
}
#[test]
fn test_find_next_hop_chain_indirect() {
let mut state = make_tree_state(5, &[5, 0]);
add_peer(&mut state, 1, &[1, 5, 0]);
let dest = make_coords(&[2, 1, 5, 0]);
assert_eq!(state.find_next_hop(&dest), Some(make_node_addr(1)));
}
#[test]
fn test_find_next_hop_toward_root() {
let mut state = make_tree_state(5, &[5, 1, 0]);
add_peer(&mut state, 1, &[1, 0]);
let dest = make_coords(&[0]);
assert_eq!(state.find_next_hop(&dest), Some(make_node_addr(1)));
}
#[test]
fn test_find_next_hop_sibling() {
let mut state = make_tree_state(5, &[5, 0]);
add_peer(&mut state, 3, &[3, 0]);
let dest = make_coords(&[3, 0]);
assert_eq!(state.find_next_hop(&dest), Some(make_node_addr(3)));
}
#[test]
fn test_find_next_hop_tie_breaking() {
let mut state = make_tree_state(5, &[5, 0]);
add_peer(&mut state, 3, &[3, 0]);
add_peer(&mut state, 2, &[2, 0]);
let dest = make_coords(&[4, 0]);
assert_eq!(state.find_next_hop(&dest), None);
}
#[test]
fn test_find_next_hop_different_root() {
let mut state = make_tree_state(5, &[5, 0]);
add_peer(&mut state, 1, &[1, 0]);
let dest = make_coords(&[3, 9]);
assert_eq!(state.find_next_hop(&dest), None);
}
#[test]
fn test_find_next_hop_no_peers() {
let state = make_tree_state(5, &[5, 0]);
let dest = make_coords(&[3, 0]);
assert_eq!(state.find_next_hop(&dest), None);
}
#[test]
fn test_find_next_hop_local_minimum() {
let mut state = make_tree_state(5, &[5, 0]);
add_peer(&mut state, 8, &[8, 5, 0]);
let dest = make_coords(&[3, 0]);
assert_eq!(state.find_next_hop(&dest), None);
}
#[test]
fn test_find_next_hop_best_of_multiple() {
let mut state = make_tree_state(5, &[5, 1, 0]);
add_peer(&mut state, 1, &[1, 0]);
add_peer(&mut state, 3, &[3, 1, 0]);
let dest = make_coords(&[7, 3, 1, 0]);
assert_eq!(state.find_next_hop(&dest), Some(make_node_addr(3)));
}
fn make_costs(entries: &[(u8, f64)]) -> HashMap<NodeAddr, f64> {
entries
.iter()
.map(|&(addr, cost)| (make_node_addr(addr), cost))
.collect()
}
#[test]
fn test_effective_depth_selects_lower_cost_deeper_peer() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, make_node_addr(3), 1, 1000),
make_coords(&[2, 3, 0]),
);
let costs = make_costs(&[(1, 6.0), (2, 1.01)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, Some(peer_b));
}
#[test]
fn test_effective_depth_equal_cost_degenerates_to_depth() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer1, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer2, make_node_addr(3), 1, 1000),
make_coords(&[2, 3, 4, 0]),
);
let costs = make_costs(&[(1, 1.0), (2, 1.0)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, Some(peer1));
}
#[test]
fn test_effective_depth_tiebreak_by_node_addr() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer1, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer2, root, 1, 1000),
make_coords(&[2, 0]),
);
let costs = make_costs(&[(1, 1.0), (2, 1.0)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, Some(peer1)); }
#[test]
fn test_hysteresis_prevents_marginal_switch() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_parent_hysteresis(0.2);
let peer_a = make_node_addr(1); let peer_b = make_node_addr(2); let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
let costs = make_costs(&[(1, 2.5), (2, 2.2)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, None); }
#[test]
fn test_hysteresis_allows_significant_switch() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_parent_hysteresis(0.2);
let peer_a = make_node_addr(1); let peer_b = make_node_addr(2); let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, make_node_addr(3), 1, 1000),
make_coords(&[2, 3, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
let costs = make_costs(&[(1, 6.0), (2, 1.01)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, Some(peer_b));
}
#[test]
fn test_cold_start_default_cost() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1);
let peer2 = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer1, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer2, make_node_addr(3), 1, 1000),
make_coords(&[2, 3, 4, 0]),
);
let result = state.evaluate_parent(&HashMap::new());
assert_eq!(result, Some(peer1)); }
#[test]
fn test_hold_down_suppresses_reeval() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_hold_down(60);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
let costs = make_costs(&[(1, 5.0), (2, 1.0)]);
state.set_parent_hysteresis(0.0); let result = state.evaluate_parent(&costs);
assert_eq!(result, None); }
#[test]
fn test_mandatory_switch_bypasses_hold_down() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_hold_down(60);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
state.remove_peer(&peer_a);
let result = state.evaluate_parent(&HashMap::new());
assert_eq!(result, Some(peer_b)); }
#[test]
fn test_heterogeneous_7node_avoids_bottleneck() {
let root = make_node_addr(0);
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer1 = make_node_addr(1); let peer2 = make_node_addr(2);
state.update_peer(
ParentDeclaration::new(peer1, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer2, root, 1, 1000),
make_coords(&[2, 0]),
);
let result_no_cost = state.evaluate_parent(&HashMap::new());
assert_eq!(result_no_cost, Some(peer1));
let costs = make_costs(&[(1, 1.01), (2, 6.0)]);
let result_with_cost = state.evaluate_parent(&costs);
assert_eq!(result_with_cost, Some(peer1));
state.set_parent(peer2, 1, 1000);
state.recompute_coords();
assert_eq!(state.my_coords().depth(), 2);
let result_switch = state.evaluate_parent(&costs);
assert_eq!(result_switch, Some(peer1));
state.set_parent_hysteresis(0.2);
let result_hyst = state.evaluate_parent(&costs);
assert_eq!(result_hyst, Some(peer1));
}
#[test]
fn test_cost_degradation_triggers_switch() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_parent_hysteresis(0.2);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
let initial_costs = make_costs(&[(1, 1.05), (2, 1.08)]);
let result = state.evaluate_parent(&initial_costs);
assert_eq!(result, Some(peer_a));
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
let result = state.evaluate_parent(&initial_costs);
assert_eq!(result, None);
let degraded_costs = make_costs(&[(1, 6.0), (2, 1.08)]);
let result = state.evaluate_parent(°raded_costs);
assert_eq!(result, Some(peer_b));
}
#[test]
fn test_cost_improvement_within_hysteresis_no_switch() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_parent_hysteresis(0.2);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
let costs = make_costs(&[(1, 2.0), (2, 1.5)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, None);
}
#[test]
fn test_single_peer_no_reeval_benefit() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
let peer_a = make_node_addr(1);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
let costs = make_costs(&[(1, 1.05)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, Some(peer_a));
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
let bad_costs = make_costs(&[(1, 50.0)]);
let result = state.evaluate_parent(&bad_costs);
assert_eq!(result, None);
}
#[test]
fn test_flap_dampening_engages_after_threshold() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_flap_dampening(3, 60, 3600);
state.set_hold_down(0);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
assert!(!state.is_flap_dampened());
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
assert!(!state.is_flap_dampened());
state.set_parent(peer_b, 2, 2000);
state.recompute_coords();
assert!(!state.is_flap_dampened());
let dampened = state.set_parent(peer_a, 3, 3000);
state.recompute_coords();
assert!(dampened);
assert!(state.is_flap_dampened());
let costs = make_costs(&[(1, 10.0), (2, 1.0)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, None); }
#[test]
fn test_flap_dampening_allows_mandatory_switches() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_flap_dampening(3, 60, 3600);
state.set_hold_down(0);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
state.set_parent(peer_b, 2, 2000);
state.recompute_coords();
state.set_parent(peer_a, 3, 3000);
state.recompute_coords();
assert!(state.is_flap_dampened());
state.remove_peer(&peer_a);
let result = state.evaluate_parent(&HashMap::new());
assert_eq!(result, Some(peer_b)); }
#[test]
fn test_flap_dampening_expires() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_flap_dampening(3, 60, 0); state.set_hold_down(0);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
state.set_parent(peer_b, 2, 2000);
state.recompute_coords();
let dampened = state.set_parent(peer_a, 3, 3000);
state.recompute_coords();
assert!(dampened);
assert!(!state.is_flap_dampened());
let costs = make_costs(&[(1, 10.0), (2, 1.0)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, Some(peer_b)); }
#[test]
fn test_flap_dampening_below_threshold() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_flap_dampening(4, 60, 3600); state.set_hold_down(0);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
state.set_parent(peer_b, 2, 2000);
state.recompute_coords();
state.set_parent(peer_a, 3, 3000);
state.recompute_coords();
assert!(!state.is_flap_dampened());
let costs = make_costs(&[(1, 10.0), (2, 1.0)]);
let result = state.evaluate_parent(&costs);
assert_eq!(result, Some(peer_b)); }
#[test]
fn test_flap_dampening_window_reset() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_flap_dampening(3, 0, 3600);
state.set_hold_down(0);
let peer_a = make_node_addr(1);
let peer_b = make_node_addr(2);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.update_peer(
ParentDeclaration::new(peer_b, root, 1, 1000),
make_coords(&[2, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
state.set_parent(peer_b, 2, 2000);
state.recompute_coords();
state.set_parent(peer_a, 3, 3000);
state.recompute_coords();
assert!(!state.is_flap_dampened());
}
#[test]
fn test_flap_dampening_same_parent_no_count() {
let my_node = make_node_addr(5);
let mut state = TreeState::new(my_node);
state.set_flap_dampening(3, 60, 3600);
state.set_hold_down(0);
let peer_a = make_node_addr(1);
let root = make_node_addr(0);
state.update_peer(
ParentDeclaration::new(peer_a, root, 1, 1000),
make_coords(&[1, 0]),
);
state.set_parent(peer_a, 1, 1000);
state.recompute_coords();
state.set_parent(peer_a, 2, 2000);
state.recompute_coords();
state.set_parent(peer_a, 3, 3000);
state.recompute_coords();
state.set_parent(peer_a, 4, 4000);
state.recompute_coords();
state.set_parent(peer_a, 5, 5000);
state.recompute_coords();
assert!(!state.is_flap_dampened());
}