Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
204
tests/integration/distributed/raft_consensus_tests.rs
Normal file
204
tests/integration/distributed/raft_consensus_tests.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
//! 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);
|
||||
}
|
||||
Reference in New Issue
Block a user