use std::{sync::Arc, thread};
use d_engine_proto::server::election::VotedFor;
use super::super::*;
#[test]
fn test_voted_for_backward_compatibility() {
let old_vote = VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: false, };
assert_eq!(old_vote.voted_for_id, 3);
assert_eq!(old_vote.voted_for_term, 5);
assert!(!old_vote.committed);
}
#[test]
fn test_voted_for_committed_flag() {
let leader_vote = VotedFor {
voted_for_id: 1,
voted_for_term: 10,
committed: true,
};
assert!(leader_vote.committed);
let candidate_vote = VotedFor {
voted_for_id: 2,
voted_for_term: 10,
committed: false,
};
assert!(!candidate_vote.committed);
}
#[test]
fn test_hard_state_with_voted_for() {
let hs = HardState {
current_term: 5,
voted_for: Some(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: false, }),
};
assert_eq!(hs.current_term, 5);
assert!(hs.voted_for.is_some());
let vote = hs.voted_for.unwrap();
assert_eq!(vote.voted_for_id, 3);
assert_eq!(vote.voted_for_term, 5);
assert!(!vote.committed);
}
#[test]
fn test_candidate_to_leader_committed_vote() {
let leader_vote = VotedFor {
voted_for_id: 1,
voted_for_term: 10,
committed: true,
};
assert!(leader_vote.committed);
assert_eq!(leader_vote.voted_for_id, 1);
}
#[test]
fn test_step_down_resets_vote() {
let mut shared = SharedState::new(1, None, None);
shared
.update_voted_for(VotedFor {
voted_for_id: 2,
voted_for_term: 5,
committed: true,
})
.unwrap();
assert!(shared.voted_for().unwrap().is_some());
shared.reset_voted_for().unwrap();
assert!(shared.voted_for().unwrap().is_none());
}
#[test]
fn test_committed_vote_represents_leader() {
let leader_vote = VotedFor {
voted_for_id: 1,
voted_for_term: 10,
committed: true,
};
assert!(leader_vote.committed);
let candidate_vote = VotedFor {
voted_for_id: 1,
voted_for_term: 10,
committed: false,
};
assert!(!candidate_vote.committed);
}
#[test]
fn test_follower_learns_leader_from_append_entries() {
let mut shared = SharedState::new(2, None, None);
assert!(shared.voted_for().unwrap().is_none());
shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: true, })
.unwrap();
let vote = shared.voted_for().unwrap().unwrap();
assert_eq!(vote.voted_for_id, 3);
assert!(vote.committed);
}
#[test]
fn test_vote_lifecycle() {
let mut shared = SharedState::new(1, None, None);
assert!(shared.voted_for().unwrap().is_none());
shared
.update_voted_for(VotedFor {
voted_for_id: 1,
voted_for_term: 5,
committed: false,
})
.unwrap();
let vote = shared.voted_for().unwrap().unwrap();
assert!(!vote.committed);
shared
.update_voted_for(VotedFor {
voted_for_id: 1,
voted_for_term: 5,
committed: true,
})
.unwrap();
let vote = shared.voted_for().unwrap().unwrap();
assert!(vote.committed);
shared.update_current_term(6);
shared.reset_voted_for().unwrap();
assert!(shared.voted_for().unwrap().is_none());
}
#[test]
fn test_committed_vote_persistence() {
let hs = HardState {
current_term: 10,
voted_for: Some(VotedFor {
voted_for_id: 2,
voted_for_term: 10,
committed: true,
}),
};
assert!(hs.voted_for.unwrap().committed);
let hs2 = HardState {
current_term: 10,
voted_for: Some(VotedFor {
voted_for_id: 2,
voted_for_term: 10,
committed: false,
}),
};
assert!(!hs2.voted_for.unwrap().committed);
}
#[test]
fn test_shared_state_current_leader_default() {
let shared = SharedState::new(1, None, None);
assert_eq!(shared.current_leader(), None);
}
#[test]
fn test_shared_state_set_current_leader() {
let shared = SharedState::new(1, None, None);
shared.set_current_leader(5);
assert_eq!(shared.current_leader(), Some(5));
shared.set_current_leader(3);
assert_eq!(shared.current_leader(), Some(3));
}
#[test]
fn test_shared_state_clear_current_leader() {
let shared = SharedState::new(1, None, None);
shared.set_current_leader(5);
assert_eq!(shared.current_leader(), Some(5));
shared.clear_current_leader();
assert_eq!(shared.current_leader(), None);
}
#[test]
fn test_shared_state_leader_zero_means_none() {
let shared = SharedState::new(1, None, None);
shared.set_current_leader(0);
assert_eq!(shared.current_leader(), None);
shared.set_current_leader(2);
assert_eq!(shared.current_leader(), Some(2));
shared.set_current_leader(0);
assert_eq!(shared.current_leader(), None);
}
#[test]
fn test_shared_state_leader_clone() {
let shared1 = SharedState::new(1, None, None);
shared1.set_current_leader(10);
let shared2 = shared1.clone();
assert_eq!(shared2.current_leader(), Some(10));
shared2.set_current_leader(20);
assert_eq!(shared1.current_leader(), Some(10)); assert_eq!(shared2.current_leader(), Some(20));
}
#[test]
fn test_shared_state_leader_debug() {
let shared = SharedState::new(1, None, None);
shared.set_current_leader(7);
let debug_str = format!("{shared:?}");
assert!(debug_str.contains("current_leader"));
assert!(debug_str.contains("7"));
}
#[test]
fn test_shared_state_concurrent_updates() {
let shared = Arc::new(SharedState::new(1, None, None));
let handles: Vec<_> = (0..10)
.map(|i| {
let shared = Arc::clone(&shared);
thread::spawn(move || {
shared.set_current_leader(i as u32);
})
})
.collect();
for h in handles {
h.join().unwrap();
}
let final_leader = shared.current_leader();
assert!(final_leader.is_some());
assert!(final_leader.unwrap() < 10);
}
#[test]
fn test_shared_state_leader_lifecycle() {
let shared = SharedState::new(1, None, None);
assert_eq!(shared.current_leader(), None);
shared.set_current_leader(3);
assert_eq!(shared.current_leader(), Some(3));
shared.clear_current_leader();
assert_eq!(shared.current_leader(), None);
shared.set_current_leader(5);
assert_eq!(shared.current_leader(), Some(5));
}
#[test]
fn test_update_voted_for_first_leader_discovery() {
let mut shared = SharedState::new(2, None, None);
assert!(shared.voted_for().unwrap().is_none());
assert_eq!(shared.current_leader(), None);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: true,
})
.unwrap();
assert!(is_new, "First leader discovery should return true");
assert_eq!(shared.voted_for().unwrap().unwrap().voted_for_id, 3);
assert_eq!(shared.voted_for().unwrap().unwrap().voted_for_term, 5);
}
#[test]
fn test_update_voted_for_same_leader_heartbeat() {
let mut shared = SharedState::new(2, None, None);
shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: true,
})
.unwrap();
shared.set_current_leader(3);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: true,
})
.unwrap();
assert!(!is_new, "Same leader heartbeat should return false");
}
#[test]
fn test_update_voted_for_leader_change() {
let mut shared = SharedState::new(2, None, None);
shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: true,
})
.unwrap();
shared.set_current_leader(3);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 5,
voted_for_term: 6,
committed: true,
})
.unwrap();
assert!(is_new, "Leader change should return true");
assert_eq!(shared.voted_for().unwrap().unwrap().voted_for_id, 5);
assert_eq!(shared.voted_for().unwrap().unwrap().voted_for_term, 6);
}
#[test]
fn test_update_voted_for_candidate_vote() {
let mut shared = SharedState::new(1, None, None);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 1,
voted_for_term: 5,
committed: false,
})
.unwrap();
assert!(!is_new, "Uncommitted candidate vote should return false");
assert!(!shared.voted_for().unwrap().unwrap().committed);
}
#[test]
fn test_update_voted_for_candidate_to_leader() {
let mut shared = SharedState::new(1, None, None);
shared
.update_voted_for(VotedFor {
voted_for_id: 1,
voted_for_term: 5,
committed: false,
})
.unwrap();
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 1,
voted_for_term: 5,
committed: true,
})
.unwrap();
assert!(is_new, "Candidate to leader transition should return true");
assert!(shared.voted_for().unwrap().unwrap().committed);
}
#[test]
fn test_update_voted_for_node_restart_same_leader() {
let hard_state_before_shutdown = HardState {
current_term: 4,
voted_for: Some(VotedFor {
voted_for_id: 3,
voted_for_term: 4,
committed: true,
}),
};
let mut shared = SharedState::new(1, Some(hard_state_before_shutdown), None);
assert_eq!(
shared.current_leader(),
None,
"After restart, current_leader should be None"
);
assert!(
shared.voted_for().unwrap().is_some(),
"voted_for loaded from disk"
);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 4,
committed: true,
})
.unwrap();
assert!(
is_new,
"Node restart: should rediscover leader even if voted_for unchanged"
);
}
#[test]
fn test_update_voted_for_node_restart_leader_changed() {
let hard_state_before_shutdown = HardState {
current_term: 4,
voted_for: Some(VotedFor {
voted_for_id: 3,
voted_for_term: 4,
committed: true,
}),
};
let mut shared = SharedState::new(1, Some(hard_state_before_shutdown), None);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 5,
voted_for_term: 6,
committed: true,
})
.unwrap();
assert!(is_new, "Node restart with new leader should return true");
assert_eq!(shared.voted_for().unwrap().unwrap().voted_for_id, 5);
assert_eq!(shared.voted_for().unwrap().unwrap().voted_for_term, 6);
}
#[test]
fn test_update_voted_for_term_advance_no_leader() {
let mut shared = SharedState::new(2, None, None);
shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: true,
})
.unwrap();
shared.set_current_leader(3);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 5,
voted_for_term: 6,
committed: false, })
.unwrap();
assert!(
!is_new,
"Term advance without committed leader should return false"
);
}
#[test]
fn test_update_voted_for_leader_step_down() {
let mut shared = SharedState::new(3, None, None);
shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: true,
})
.unwrap();
shared.set_current_leader(3);
shared.update_current_term(6);
shared.reset_voted_for().unwrap();
shared.clear_current_leader();
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 5,
voted_for_term: 6,
committed: true,
})
.unwrap();
assert!(is_new, "New leader after step down should return true");
}
#[test]
fn test_update_voted_for_multiple_leader_changes() {
let mut shared = SharedState::new(2, None, None);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 1,
voted_for_term: 3,
committed: true,
})
.unwrap();
assert!(is_new);
shared.set_current_leader(1);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 2,
voted_for_term: 4,
committed: true,
})
.unwrap();
assert!(is_new, "First leader change");
shared.set_current_leader(2);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: true,
})
.unwrap();
assert!(is_new, "Second leader change");
}
#[test]
fn test_update_voted_for_term_regression() {
let mut shared = SharedState::new(2, None, None);
shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 6,
committed: true,
})
.unwrap();
shared.set_current_leader(3);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 5,
committed: true,
})
.unwrap();
assert!(is_new, "Term regression should return true");
}
#[test]
fn test_update_voted_for_learner_discovers_leader() {
let mut shared = SharedState::new(10, None, None);
let is_new = shared
.update_voted_for(VotedFor {
voted_for_id: 3,
voted_for_term: 8,
committed: true,
})
.unwrap();
assert!(is_new, "Learner first leader discovery should return true");
}