Skip to main content

elara_loadtest/
test_node.rs

1//! Test node infrastructure for load testing
2//!
3//! This module provides the `TestNode` struct which simulates an ELARA Protocol node
4//! for load testing purposes. Test nodes can spawn, connect to each other, and
5//! generate realistic message patterns.
6
7use elara_runtime::{Node, NodeConfig};
8use elara_core::{NodeId, SessionId, Event, EventType, StateId, MutationOp};
9use std::time::Instant;
10use std::collections::HashMap;
11
12/// A simulated ELARA Protocol node for load testing
13pub struct TestNode {
14    /// The underlying ELARA node
15    node: Node,
16    /// Node identifier
17    node_id: NodeId,
18    /// Connected peer nodes (for message routing)
19    pub(crate) peers: HashMap<NodeId, usize>,
20    /// Message counter for tracking
21    messages_sent: u64,
22    /// Message counter for tracking
23    messages_received: u64,
24}
25
26impl TestNode {
27    /// Spawn a new test node with the given configuration
28    pub fn spawn(config: NodeConfig) -> Result<Self, String> {
29        let node = Node::with_config(config);
30        let node_id = node.node_id();
31        
32        Ok(Self {
33            node,
34            node_id,
35            peers: HashMap::new(),
36            messages_sent: 0,
37            messages_received: 0,
38        })
39    }
40    
41    /// Spawn a new test node with default configuration
42    pub fn spawn_default() -> Result<Self, String> {
43        Self::spawn(NodeConfig::default())
44    }
45    
46    /// Get the node ID
47    pub fn node_id(&self) -> NodeId {
48        self.node_id
49    }
50    
51    /// Join a session (required for message exchange)
52    pub fn join_session(&mut self, session_id: SessionId, session_key: [u8; 32]) {
53        self.node.join_session(session_id, session_key);
54    }
55    
56    /// Join a session without encryption (for testing)
57    pub fn join_session_unsecured(&mut self, session_id: SessionId) {
58        self.node.join_session_unsecured(session_id);
59    }
60    
61    /// Connect to another test node
62    pub fn connect_to(&mut self, peer: &TestNode) -> Result<(), String> {
63        let peer_id = peer.node_id();
64        let peer_index = self.peers.len();
65        self.peers.insert(peer_id, peer_index);
66        Ok(())
67    }
68    
69    /// Generate and send a test message
70    ///
71    /// Returns the time taken to queue the message (for latency measurement)
72    pub fn send_message(&mut self, payload: Vec<u8>) -> Result<Instant, String> {
73        let start = Instant::now();
74        
75        let seq = self.node.next_event_seq();
76        
77        // Create a test event with the payload
78        let event = Event::new(
79            self.node_id,
80            seq,
81            EventType::TextAppend,
82            StateId::new(0),
83            MutationOp::Append(payload),
84        );
85        
86        // Queue the event
87        self.node.queue_local_event(event);
88        
89        // Process the node tick to generate outgoing frames
90        self.node.tick();
91        
92        self.messages_sent += 1;
93        
94        Ok(start)
95    }
96    
97    /// Receive and process incoming frames from another node
98    pub fn receive_from(&mut self, peer: &mut TestNode) -> usize {
99        let mut received_count = 0;
100        
101        // Pop all outgoing frames from the peer
102        while let Some(frame) = peer.node.pop_outgoing() {
103            // Queue as incoming on this node
104            self.node.queue_incoming(frame);
105            received_count += 1;
106        }
107        
108        // Process incoming frames
109        if received_count > 0 {
110            self.node.tick();
111            self.messages_received += received_count as u64;
112        }
113        
114        received_count
115    }
116    
117    /// Tick the node to process queued events
118    pub fn tick(&mut self) {
119        self.node.tick();
120    }
121    
122    /// Get the number of messages sent by this node
123    pub fn messages_sent(&self) -> u64 {
124        self.messages_sent
125    }
126    
127    /// Get the number of messages received by this node
128    pub fn messages_received(&self) -> u64 {
129        self.messages_received
130    }
131    
132    /// Get a reference to the underlying node
133    pub fn node(&self) -> &Node {
134        &self.node
135    }
136    
137    /// Get a mutable reference to the underlying node
138    pub fn node_mut(&mut self) -> &mut Node {
139        &mut self.node
140    }
141    
142    /// Shutdown the test node and cleanup resources
143    pub fn shutdown(self) {
144        // Node will be dropped, cleaning up resources
145        drop(self);
146    }
147}
148
149/// Generate a test message payload with the given size
150pub fn generate_test_message(size: usize) -> Vec<u8> {
151    vec![0u8; size]
152}
153
154/// Generate a test message payload with random data
155#[allow(dead_code)]
156pub fn generate_random_message(size: usize) -> Vec<u8> {
157    use std::collections::hash_map::RandomState;
158    use std::hash::{BuildHasher, Hash, Hasher};
159    
160    let mut data = Vec::with_capacity(size);
161    let hasher_builder = RandomState::new();
162    
163    for i in 0..size {
164        let mut hasher = hasher_builder.build_hasher();
165        i.hash(&mut hasher);
166        data.push((hasher.finish() & 0xFF) as u8);
167    }
168    
169    data
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175
176    #[test]
177    fn test_node_spawn() {
178        let node = TestNode::spawn_default();
179        assert!(node.is_ok());
180    }
181
182    #[test]
183    fn test_node_connection() {
184        let mut node1 = TestNode::spawn_default().unwrap();
185        let node2 = TestNode::spawn_default().unwrap();
186        
187        let result = node1.connect_to(&node2);
188        assert!(result.is_ok());
189        assert_eq!(node1.peers.len(), 1);
190    }
191
192    #[test]
193    fn test_message_generation() {
194        let msg = generate_test_message(100);
195        assert_eq!(msg.len(), 100);
196        
197        let random_msg = generate_random_message(100);
198        assert_eq!(random_msg.len(), 100);
199    }
200
201    #[test]
202    fn test_send_message() {
203        let mut node = TestNode::spawn_default().unwrap();
204        let session_id = SessionId::new(1);
205        node.join_session_unsecured(session_id);
206        
207        let payload = generate_test_message(64);
208        let result = node.send_message(payload);
209        
210        assert!(result.is_ok());
211        assert_eq!(node.messages_sent(), 1);
212    }
213
214    #[test]
215    fn test_message_exchange() {
216        let mut node1 = TestNode::spawn_default().unwrap();
217        let mut node2 = TestNode::spawn_default().unwrap();
218        
219        let session_id = SessionId::new(1);
220        node1.join_session_unsecured(session_id);
221        node2.join_session_unsecured(session_id);
222        
223        node1.connect_to(&node2).unwrap();
224        
225        // Send message from node1
226        let payload = generate_test_message(64);
227        node1.send_message(payload).unwrap();
228        
229        // Receive on node2
230        let _received = node2.receive_from(&mut node1);
231        
232        assert_eq!(node1.messages_sent(), 1);
233        // Note: received count depends on frame generation
234    }
235}