Files
wifi-densepose/tests/integration/distributed/raft_consensus_tests.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

205 lines
5.6 KiB
Rust

//! Raft Consensus Protocol Tests
//!
//! Tests for:
//! - Leader election with configurable timeouts
//! - Log replication across cluster nodes
//! - Split-brain prevention
//! - Node failure recovery
use ruvector_raft::{RaftNode, RaftNodeConfig, RaftState, RaftError};
use std::sync::Arc;
use std::time::{Duration, Instant};
use tokio::sync::mpsc;
/// Test basic Raft node creation and initialization
#[tokio::test]
async fn test_raft_node_initialization() {
let config = RaftNodeConfig::new(
"node1".to_string(),
vec!["node1".to_string(), "node2".to_string(), "node3".to_string()],
);
let node = RaftNode::new(config);
// Initial state should be Follower
assert_eq!(node.current_state(), RaftState::Follower);
assert_eq!(node.current_term(), 0);
assert!(node.current_leader().is_none());
}
/// Test Raft cluster with multiple nodes
#[tokio::test]
async fn test_raft_cluster_formation() {
let cluster_members = vec![
"node1".to_string(),
"node2".to_string(),
"node3".to_string(),
];
let mut nodes = Vec::new();
for member in &cluster_members {
let config = RaftNodeConfig::new(member.clone(), cluster_members.clone());
nodes.push(RaftNode::new(config));
}
// All nodes should start as followers
for node in &nodes {
assert_eq!(node.current_state(), RaftState::Follower);
}
assert_eq!(nodes.len(), 3);
}
/// Test election timeout configuration
#[tokio::test]
async fn test_election_timeout_configuration() {
let mut config = RaftNodeConfig::new(
"node1".to_string(),
vec!["node1".to_string(), "node2".to_string(), "node3".to_string()],
);
// Default timeouts
assert_eq!(config.election_timeout_min, 150);
assert_eq!(config.election_timeout_max, 300);
// Custom timeouts for faster testing
config.election_timeout_min = 50;
config.election_timeout_max = 100;
config.heartbeat_interval = 25;
let node = RaftNode::new(config);
assert_eq!(node.current_state(), RaftState::Follower);
}
/// Test that node ID is properly stored
#[tokio::test]
async fn test_node_identity() {
let config = RaftNodeConfig::new(
"test-node-123".to_string(),
vec!["test-node-123".to_string()],
);
let _node = RaftNode::new(config.clone());
assert_eq!(config.node_id, "test-node-123");
}
/// Test snapshot configuration
#[tokio::test]
async fn test_snapshot_configuration() {
let config = RaftNodeConfig::new(
"node1".to_string(),
vec!["node1".to_string()],
);
// Default snapshot chunk size
assert_eq!(config.snapshot_chunk_size, 64 * 1024); // 64KB
assert_eq!(config.max_entries_per_message, 100);
}
/// Simulate leader election scenario (unit test version)
#[tokio::test]
async fn test_leader_election_scenario() {
// This tests the state transitions that would occur during election
let config = RaftNodeConfig::new(
"node1".to_string(),
vec![
"node1".to_string(),
"node2".to_string(),
"node3".to_string(),
],
);
let node = RaftNode::new(config);
// Initially a follower
assert_eq!(node.current_state(), RaftState::Follower);
// Term starts at 0
assert_eq!(node.current_term(), 0);
}
/// Test quorum calculation for different cluster sizes
#[tokio::test]
async fn test_quorum_calculations() {
// 3 node cluster: quorum = 2
let three_node_quorum = (3 / 2) + 1;
assert_eq!(three_node_quorum, 2);
// 5 node cluster: quorum = 3
let five_node_quorum = (5 / 2) + 1;
assert_eq!(five_node_quorum, 3);
// 7 node cluster: quorum = 4
let seven_node_quorum = (7 / 2) + 1;
assert_eq!(seven_node_quorum, 4);
}
/// Test handling of network partition scenarios
#[tokio::test]
async fn test_network_partition_handling() {
// Simulate a 5-node cluster with partition
let cluster_size = 5;
let partition_a_size = 3; // Majority
let partition_b_size = 2; // Minority
// Only partition A can elect a leader (has majority)
let quorum = (cluster_size / 2) + 1;
assert!(partition_a_size >= quorum, "Partition A should have quorum");
assert!(partition_b_size < quorum, "Partition B should not have quorum");
}
/// Test log consistency requirements
#[tokio::test]
async fn test_log_consistency() {
// Test the log matching property
// If two logs contain an entry with the same index and term,
// then the logs are identical in all entries up through that index
let entries = vec![
(1, 1), // (index, term)
(2, 1),
(3, 2),
(4, 2),
(5, 3),
];
// Verify sequential indices
for (i, &(index, _)) in entries.iter().enumerate() {
assert_eq!(index, (i + 1) as u64);
}
}
/// Test term monotonicity
#[tokio::test]
async fn test_term_monotonicity() {
// Terms should never decrease
let terms = vec![0u64, 1, 1, 2, 3, 3, 4];
for i in 1..terms.len() {
assert!(terms[i] >= terms[i-1], "Term should not decrease");
}
}
/// Performance test for node creation
#[tokio::test]
async fn test_node_creation_performance() {
let start = Instant::now();
let iterations = 100;
for i in 0..iterations {
let config = RaftNodeConfig::new(
format!("node{}", i),
vec![format!("node{}", i)],
);
let _node = RaftNode::new(config);
}
let elapsed = start.elapsed();
let avg_ms = elapsed.as_secs_f64() * 1000.0 / iterations as f64;
println!("Average node creation time: {:.3}ms", avg_ms);
// Node creation should be fast
assert!(avg_ms < 10.0, "Node creation too slow: {:.3}ms", avg_ms);
}