Files
rustytorch/crates/production/rtx-streaming/tests/integration_tests.rs
T
osobhandClaude Fable 5 a0bf29461b
Documentation / Build User Guide (push) Successful in 6s
Documentation / Build API Documentation (push) Failing after 6s
CI / Build (macos-latest) (push) Failing after 11s
CI / Format Check (push) Failing after 12s
Performance Benchmarks / Run Benchmarks (push) Successful in 45s
CI / Build (ubuntu-latest) (push) Successful in 2m42s
CI / Test (macos-latest) (push) Has been skipped
CI / Test (ubuntu-latest) (push) Has been skipped
CI / Python Bindings (maturin) (macos-latest) (push) Has been skipped
CI / Python Bindings (maturin) (ubuntu-latest) (push) Has been skipped
CI / WASM Build + Size Check (push) Has been skipped
CI / Distributed Training Tests (push) Has been skipped
CI / Build CPU-Only (Explicit) (push) Failing after 2m58s
CI / Clippy Check (push) Failing after 2m59s
CI / CI Success (push) Failing after 0s
fix(streaming): real inference backend wiring and lifecycle fixes; full suite green
- token_generator: backend is now an optional real rtx-inference engine
  (RwLock<Option<Arc<InferenceEngine>>>) with ServingTokenizer support;
  set_backend/set_tokenizer plumbing through StreamingServer
- connection_manager: ConnectionPool::acquire no longer errors when the
  idle cache is full — creates fresh connections up to max_connections
- streaming_server: ServerState::Running on construction; stream_inference
  generates one token per step (chunk_size semantics)
- lifecycle bugs surfaced by the newly-compiling integration tests:
  * start(): broadcast control-channel send with zero subscribers was
    treated as fatal ("channel closed") in RealtimePipeline,
    EdgeComputingManager, MonitoringSystem — now tolerated
  * stop(): AdaptiveProcessor/EdgeComputingManager/MonitoringSystem
    awaited worker interval loops that never exit (test hung 5h) —
    workers are now aborted with cancellation-aware join
- integration_tests: removed stale .await on now-synchronous methods

cargo test -p rtx-streaming: 55 lib + 8 integration + 6 aux, all passing.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-10 15:48:33 -07:00

791 lines
28 KiB
Rust

//! # Comprehensive Integration Tests
//!
//! Production-grade integration tests for the rtx-streaming crate covering
//! all major components: real-time inference, stream processing, message queues,
//! adaptive processing, state management, edge computing, and monitoring.
use rtx_streaming::stream_processing::{StreamProcessor, StreamProcessorConfig};
use rtx_streaming::*;
use std::{
collections::HashMap,
sync::Arc,
time::{Duration, Instant, SystemTime},
};
use tokio::{
sync::RwLock,
time::{sleep, timeout},
};
use uuid::Uuid;
/// Test suite for comprehensive streaming functionality
#[cfg(test)]
mod integration_tests {
use super::*;
/// Integration test 1: End-to-end streaming pipeline with sub-millisecond latency
#[tokio::test]
async fn test_end_to_end_streaming_pipeline() {
// Setup comprehensive streaming configuration
let config = create_test_streaming_config();
// Initialize all major components
let _streaming_server = StreamingServer::new(config.clone())
.await
.expect("Failed to create streaming server");
let realtime_pipeline = RealtimePipeline::new(PipelineConfig::default())
.expect("Failed to create realtime pipeline");
let monitoring_system = MonitoringSystem::new(MonitoringConfig::default())
.await
.expect("Failed to create monitoring system");
// Start the complete pipeline
let start_time = Instant::now();
// Create test events for processing
let test_events = create_test_events(100);
// Process events through the complete pipeline
for event in test_events {
let pipeline_start = Instant::now();
// Process through realtime pipeline
let _pipeline_results = realtime_pipeline
.process_event(event.clone())
.expect("Pipeline processing failed");
// Record monitoring metrics
monitoring_system
.record_stream_metrics(&[event])
.await
.expect("Failed to record metrics");
// Verify sub-millisecond processing
let processing_latency = pipeline_start.elapsed();
assert!(
processing_latency < Duration::from_millis(1),
"Processing latency {} exceeds 1ms requirement",
processing_latency.as_micros()
);
}
let total_time = start_time.elapsed();
println!(
"End-to-end pipeline processed 100 events in {:?}",
total_time
);
// Verify overall performance meets requirements
assert!(
total_time < Duration::from_millis(100),
"Total processing time too high"
);
}
/// Integration test 2: High-throughput concurrent streaming with backpressure
#[tokio::test]
async fn test_high_throughput_concurrent_streaming() {
let config = create_test_streaming_config();
let mut streaming_server = StreamingServer::new(config).await.unwrap();
let adaptive_processor = Arc::new(
AdaptiveProcessor::new(AdaptiveProcessingConfig::default())
.await
.unwrap(),
);
// Start systems
streaming_server
.start()
.await
.expect("Failed to start streaming server");
adaptive_processor
.start()
.await
.expect("Failed to start adaptive processor");
let start_time = Instant::now();
let mut handles = Vec::new();
// Create 1000 concurrent streams
for stream_id in 0..1000 {
let _server = streaming_server.clone();
let processor = adaptive_processor.clone();
let handle = tokio::spawn(async move {
let events = create_test_events(10);
// Test concurrent processing with backpressure handling
for event in events {
let result = timeout(
Duration::from_millis(10),
processor
.process_adaptive_batch(vec![event], &format!("stream_{}", stream_id)),
)
.await;
match result {
Ok(Ok(_)) => {} // Success
Ok(Err(_)) => {} // Expected backpressure
Err(_) => panic!("Processing timeout"),
}
}
stream_id
});
handles.push(handle);
}
// Wait for all concurrent streams to complete
let mut completed_streams = 0;
for handle in handles {
match handle.await {
Ok(_) => completed_streams += 1,
Err(e) => println!("Stream failed: {}", e),
}
}
let elapsed = start_time.elapsed();
let throughput = (completed_streams as f64 * 10.0) / elapsed.as_secs_f64();
println!(
"Processed {} streams with throughput: {} events/sec",
completed_streams, throughput
);
// Verify high throughput achievement
assert!(
throughput > 1000.0,
"Throughput {} below requirement",
throughput
);
assert!(
completed_streams >= 950,
"Too many streams failed: {}",
completed_streams
);
// Stop systems
adaptive_processor
.stop()
.await
.expect("Failed to stop adaptive processor");
// streaming_server.shutdown().await.expect("Failed to shutdown server");
}
/// Integration test 3: Stream processing with windowing and watermarks
#[tokio::test]
async fn test_stream_processing_windowing_watermarks() {
let stream_processor = StreamProcessor::new(StreamProcessorConfig::default())
.await
.expect("Failed to create stream processor");
// Create time-series events for windowing
let mut events = Vec::new();
let base_time = SystemTime::now();
for i in 0..50 {
let event_time = base_time + Duration::from_millis(i * 100);
let event_id = Uuid::new_v4();
events.push(StreamEvent {
event_id,
event_time,
processing_time: SystemTime::now(),
stream_id: "windowing_test".to_string(),
data: EventData::TimeSeries {
values: vec![i as f64],
timestamp: event_time,
},
metadata: HashMap::new(),
watermark: if i % 10 == 9 {
Some(Watermark {
timestamp: event_time,
stream_id: "windowing_test".to_string(),
})
} else {
None
},
id: event_id.to_string(),
timestamp: event_time,
partition_key: Some("windowing_test".to_string()),
sequence_number: i,
});
}
// Process events with windowing
let start_time = Instant::now();
let windowed_results = stream_processor
.process_windowed_events(events)
.await
.expect("Windowed processing failed");
let processing_time = start_time.elapsed();
// Verify windowing performance
assert!(
processing_time < Duration::from_millis(50),
"Windowing too slow"
);
assert!(!windowed_results.is_empty(), "No windowed results produced");
println!("Windowed processing completed in {:?}", processing_time);
}
/// Integration test 4: Message queue integration with Kafka/Redis simulation
#[tokio::test]
async fn test_message_queue_integration() {
let mq_manager = MessageQueueManager::new(MessageQueueConfig::default())
.expect("Failed to create message queue manager");
// Test message production
let mut sent_messages = Vec::new();
for i in 0..100 {
let message = OutgoingMessage {
message_id: Uuid::new_v4().to_string(),
topic: "test_topic".to_string(),
key: Some(format!("key_{}", i).as_bytes().to_vec()),
value: format!("test_message_{}", i).as_bytes().to_vec(),
headers: HashMap::new(),
timestamp: SystemTime::now(),
};
let start_time = Instant::now();
match mq_manager.send_message(message.clone()).await {
Ok(_) => {
let send_latency = start_time.elapsed();
assert!(
send_latency < Duration::from_millis(10),
"Send latency too high"
);
sent_messages.push(message);
}
Err(e) => {
// In real test with actual message queues, this might fail
// For now, we accept simulated behavior
println!("Message send simulation: {:?}", e);
}
}
}
// Test message consumption simulation
let receive_request = ReceiveRequest {
consumer_id: "test_consumer".to_string(),
timeout_ms: 1000,
max_messages: 10,
};
let start_time = Instant::now();
let _received_messages = mq_manager
.receive_messages(receive_request)
.expect("Failed to receive messages");
let receive_time = start_time.elapsed();
println!("Message queue operations completed in {:?}", receive_time);
assert!(
receive_time < Duration::from_millis(100),
"Message queue operations too slow"
);
}
/// Integration test 5: State management with exactly-once processing
#[tokio::test]
async fn test_state_management_exactly_once() {
let mut state_config = StateManagementConfig::default();
state_config.exactly_once_config.enabled = true;
let state_manager = StreamStateManager::new(state_config)
.await
.expect("Failed to create state manager");
// Test state storage and retrieval
let test_key = "test_state_key";
let test_value = b"test_state_value";
let stream_id = "exactly_once_test";
// Store state multiple times (should be idempotent)
for i in 0..5 {
let start_time = Instant::now();
let store_result = state_manager
.store_state(test_key, test_value, stream_id)
.await
.expect("Failed to store state");
let store_latency = start_time.elapsed();
assert!(
store_latency < Duration::from_millis(10),
"State store too slow"
);
assert!(store_result.success, "State store should succeed");
if i > 0 {
// Should detect duplicates for exactly-once processing
println!(
"Store result {}: duplicate_detected={}",
i, store_result.duplicate_detected
);
}
}
// Retrieve and verify state
let start_time = Instant::now();
let retrieved_value = state_manager
.retrieve_state(test_key)
.await
.expect("Failed to retrieve state");
let retrieve_latency = start_time.elapsed();
assert!(
retrieve_latency < Duration::from_millis(5),
"State retrieve too slow"
);
assert_eq!(
retrieved_value,
Some(test_value.to_vec()),
"Retrieved value mismatch"
);
// Test exactly-once event processing
let test_events = create_duplicate_test_events();
let start_time = Instant::now();
let _processed_events = state_manager
.process_exactly_once(test_events)
.await
.expect("Exactly-once processing failed");
let processing_time = start_time.elapsed();
assert!(
processing_time < Duration::from_millis(50),
"Exactly-once processing too slow"
);
// Should have fewer processed events due to deduplication
println!("Exactly-once processing completed in {:?}", processing_time);
}
/// Integration test 6: Edge computing with resource constraints
#[tokio::test]
async fn test_edge_computing_resource_constraints() {
let mut edge_config = EdgeComputingConfig::default();
edge_config
.inference_config
.resource_constraints
.max_latency_ms = 5; // Strict constraint
edge_config
.inference_config
.resource_constraints
.max_memory_bytes = 10 * 1024 * 1024; // 10MB
let edge_manager = EdgeComputingManager::new(edge_config)
.await
.expect("Failed to create edge manager");
edge_manager
.start()
.await
.expect("Failed to start edge manager");
// Test lightweight inference under constraints
let test_events = create_test_events(10);
let start_time = Instant::now();
match timeout(
Duration::from_millis(100),
edge_manager.lightweight_inference(test_events, "test_model"),
)
.await
{
Ok(Ok(_results)) => {
let inference_time = start_time.elapsed();
assert!(
inference_time < Duration::from_millis(50),
"Edge inference too slow"
);
println!("Edge inference completed in {:?}", inference_time);
}
Ok(Err(e)) => {
// Resource constraints might prevent processing
println!("Edge inference failed due to constraints: {:?}", e);
}
Err(_) => {
panic!("Edge inference timeout");
}
}
// Test device failure handling
let start_time = Instant::now();
let failure_result = edge_manager
.handle_device_failure(DeviceFailureType::ConnectivityFailure)
.await;
let failure_handling_time = start_time.elapsed();
assert!(failure_result.is_ok(), "Device failure handling failed");
assert!(
failure_handling_time < Duration::from_millis(20),
"Failure handling too slow"
);
edge_manager
.stop()
.await
.expect("Failed to stop edge manager");
}
/// Integration test 7: Monitoring and observability system
#[tokio::test]
async fn test_monitoring_observability() {
let mut monitoring_config = MonitoringConfig::default();
monitoring_config.metrics_config.collection_interval_ms = 100;
monitoring_config.tracing_config.enabled = true;
let monitoring_system = MonitoringSystem::new(monitoring_config)
.await
.expect("Failed to create monitoring system");
monitoring_system
.start()
.await
.expect("Failed to start monitoring");
// Test metrics recording
let test_events = create_test_events(50);
let start_time = Instant::now();
for chunk in test_events.chunks(10) {
monitoring_system
.record_stream_metrics(chunk)
.await
.expect("Failed to record metrics");
}
let metrics_time = start_time.elapsed();
assert!(
metrics_time < Duration::from_millis(100),
"Metrics recording too slow"
);
// Test distributed tracing
let start_time = Instant::now();
let trace_context = monitoring_system
.create_trace("integration_test")
.await
.expect("Failed to create trace");
let trace_time = start_time.elapsed();
assert!(
trace_time < Duration::from_millis(10),
"Trace creation too slow"
);
assert!(!trace_context.trace_id.is_empty(), "Invalid trace context");
// Test health checking
let start_time = Instant::now();
let _health_status = monitoring_system
.check_system_health()
.await
.expect("Failed to check health");
let health_time = start_time.elapsed();
assert!(
health_time < Duration::from_millis(50),
"Health check too slow"
);
// Test metrics export
let start_time = Instant::now();
let metrics_export = monitoring_system
.export_metrics(ExportFormat::Json)
.await
.expect("Failed to export metrics");
let export_time = start_time.elapsed();
assert!(
export_time < Duration::from_millis(100),
"Metrics export too slow"
);
assert!(!metrics_export.is_empty(), "Empty metrics export");
monitoring_system
.stop()
.await
.expect("Failed to stop monitoring");
println!("Monitoring system tests completed successfully");
}
/// Integration test 8: Full system stress test
#[tokio::test]
async fn test_full_system_stress_test() {
// Create comprehensive system configuration
let streaming_config = create_test_streaming_config();
let pipeline_config = PipelineConfig::default();
let adaptive_config = AdaptiveProcessingConfig::default();
let state_config = StateManagementConfig::default();
let monitoring_config = MonitoringConfig::default();
// Initialize all systems
let mut streaming_server = StreamingServer::new(streaming_config).await.unwrap();
let pipeline = Arc::new(RealtimePipeline::new(pipeline_config).unwrap());
let adaptive_processor = Arc::new(AdaptiveProcessor::new(adaptive_config).await.unwrap());
let state_manager = Arc::new(StreamStateManager::new(state_config).await.unwrap());
let monitoring = Arc::new(MonitoringSystem::new(monitoring_config).await.unwrap());
// Start all systems
streaming_server
.start()
.await
.expect("Failed to start streaming server");
pipeline.start().expect("Failed to start pipeline");
adaptive_processor
.start()
.await
.expect("Failed to start adaptive processor");
monitoring
.start()
.await
.expect("Failed to start monitoring");
println!("All systems started for stress test");
let stress_start = Instant::now();
let mut handles = Vec::new();
// Create 100 concurrent stress test workers
for worker_id in 0..100 {
let pipeline = pipeline.clone();
let adaptive_processor = adaptive_processor.clone();
let state_manager = state_manager.clone();
let monitoring = monitoring.clone();
let handle = tokio::spawn(async move {
let mut worker_stats = WorkerStats::default();
for iteration in 0..10 {
let events = create_test_events(5);
let iteration_start = Instant::now();
// Process through pipeline
for event in &events {
if let Ok(_) = pipeline.process_event(event.clone()) {
worker_stats.successful_pipeline_operations += 1;
} else {
worker_stats.failed_operations += 1;
}
}
// Process through adaptive system
let stream_id = format!("stress_worker_{}", worker_id);
if let Ok(_) = adaptive_processor
.process_adaptive_batch(events.clone(), &stream_id)
.await
{
worker_stats.successful_adaptive_operations += 1;
} else {
worker_stats.failed_operations += 1;
}
// State operations
let state_key = format!("stress_key_{}_{}", worker_id, iteration);
let state_value = format!("value_{}", iteration).as_bytes().to_vec();
if let Ok(_) = state_manager
.store_state(&state_key, &state_value, &stream_id)
.await
{
worker_stats.successful_state_operations += 1;
} else {
worker_stats.failed_operations += 1;
}
// Record metrics
if let Ok(_) = monitoring.record_stream_metrics(&events).await {
worker_stats.successful_monitoring_operations += 1;
} else {
worker_stats.failed_operations += 1;
}
worker_stats.total_iterations += 1;
worker_stats.total_processing_time += iteration_start.elapsed();
// Small delay to prevent overwhelming
sleep(Duration::from_millis(10)).await;
}
worker_stats
});
handles.push(handle);
}
// Collect results from all workers
let mut total_stats = WorkerStats::default();
for handle in handles {
match handle.await {
Ok(worker_stats) => {
total_stats.combine(worker_stats);
}
Err(e) => {
println!("Worker failed: {:?}", e);
total_stats.failed_operations += 1;
}
}
}
let stress_duration = stress_start.elapsed();
// Calculate and verify stress test results
let total_operations = total_stats.successful_pipeline_operations
+ total_stats.successful_adaptive_operations
+ total_stats.successful_state_operations
+ total_stats.successful_monitoring_operations
+ total_stats.failed_operations;
let success_rate = if total_operations > 0 {
((total_operations - total_stats.failed_operations) as f64 / total_operations as f64)
* 100.0
} else {
0.0
};
let operations_per_second = total_operations as f64 / stress_duration.as_secs_f64();
println!("Stress Test Results:");
println!(" Duration: {:?}", stress_duration);
println!(" Total Operations: {}", total_operations);
println!(" Failed Operations: {}", total_stats.failed_operations);
println!(" Success Rate: {:.2}%", success_rate);
println!(" Operations/Second: {:.2}", operations_per_second);
println!(
" Pipeline Operations: {}",
total_stats.successful_pipeline_operations
);
println!(
" Adaptive Operations: {}",
total_stats.successful_adaptive_operations
);
println!(
" State Operations: {}",
total_stats.successful_state_operations
);
println!(
" Monitoring Operations: {}",
total_stats.successful_monitoring_operations
);
// Verify stress test requirements
assert!(
success_rate >= 95.0,
"Success rate {} below 95% requirement",
success_rate
);
assert!(
operations_per_second >= 100.0,
"Operations/sec {} below requirement",
operations_per_second
);
assert!(
stress_duration < Duration::from_secs(60),
"Stress test took too long"
);
// Stop all systems
monitoring.stop().await.expect("Failed to stop monitoring");
adaptive_processor
.stop()
.await
.expect("Failed to stop adaptive processor");
// pipeline.stop().await.expect("Failed to stop pipeline"); // stop() method not available
// streaming_server.shutdown().await.expect("Failed to shutdown server");
println!("Full system stress test completed successfully!");
}
// Helper functions for test setup and data creation
fn create_test_streaming_config() -> StreamingConfig {
StreamingConfig {
max_connections: 1000,
target_latency: Duration::from_micros(900), // <1ms
connection_pool_size: 100,
backpressure_threshold: 0.8,
websocket_addr: "127.0.0.1:8080".to_string(),
grpc_addr: "127.0.0.1:50051".to_string(),
memory_pool_size: 1024 * 1024 * 100, // 100MB
metrics_enabled: true,
}
}
fn create_test_events(count: usize) -> Vec<StreamEvent> {
(0..count)
.map(|i| {
let event_id = Uuid::new_v4();
let event_time = SystemTime::now();
StreamEvent {
event_id,
event_time,
processing_time: SystemTime::now(),
stream_id: format!("test_stream_{}", i),
data: EventData::Text {
content: format!("test_content_{}", i),
tokens: Some(vec![format!("token_{}", i)]),
},
metadata: {
let mut metadata = HashMap::new();
metadata.insert("test_key".to_string(), format!("test_value_{}", i));
metadata
},
watermark: None,
id: event_id.to_string(),
timestamp: event_time,
partition_key: Some(format!("partition_{}", i % 4)),
sequence_number: i as u64,
}
})
.collect()
}
fn create_duplicate_test_events() -> Vec<StreamEvent> {
let event_id = Uuid::new_v4();
let event_time = SystemTime::now();
let base_event = StreamEvent {
event_id,
event_time,
processing_time: SystemTime::now(),
stream_id: "duplicate_test".to_string(),
data: EventData::Text {
content: "duplicate_content".to_string(),
tokens: None,
},
metadata: HashMap::new(),
watermark: None,
id: event_id.to_string(),
timestamp: event_time,
partition_key: Some("duplicate_partition".to_string()),
sequence_number: 0,
};
// Create duplicates with same event ID
vec![base_event.clone(), base_event.clone(), base_event.clone()]
}
#[derive(Debug, Default, Clone)]
struct WorkerStats {
successful_pipeline_operations: u64,
successful_adaptive_operations: u64,
successful_state_operations: u64,
successful_monitoring_operations: u64,
failed_operations: u64,
total_iterations: u64,
total_processing_time: Duration,
}
impl WorkerStats {
fn combine(&mut self, other: WorkerStats) {
self.successful_pipeline_operations += other.successful_pipeline_operations;
self.successful_adaptive_operations += other.successful_adaptive_operations;
self.successful_state_operations += other.successful_state_operations;
self.successful_monitoring_operations += other.successful_monitoring_operations;
self.failed_operations += other.failed_operations;
self.total_iterations += other.total_iterations;
self.total_processing_time += other.total_processing_time;
}
}
}