Wave 64-65 cleanup: Proto regeneration and build system updates from Tonic 0.12→0.14 upgrade Files updated: - Cargo.lock: Dependency resolution for Tonic 0.14.2 - All build.rs: Updated for tonic-prost-build - Proto files: Regenerated with tonic-prost 0.14 - Examples/tests: Updated for new gRPC API 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
675 lines
26 KiB
Rust
675 lines
26 KiB
Rust
//! Trading Flow Integration Tests
|
|
//!
|
|
//! Comprehensive integration tests for TLI Client ↔ Trading Service communication.
|
|
//! Tests end-to-end trading workflows including order submission, risk validation,
|
|
//! execution, and monitoring with performance benchmarks.
|
|
#![allow(unused_crate_dependencies)]
|
|
|
|
use std::collections::HashMap;
|
|
use std::sync::{Arc, atomic::{AtomicU64, Ordering}};
|
|
use std::time::{Duration, Instant};
|
|
|
|
use tokio::sync::{mpsc, RwLock, Mutex};
|
|
use tokio::time::timeout;
|
|
use uuid::Uuid;
|
|
use serde_json::json;
|
|
|
|
use tli::prelude::*;
|
|
// use risk::prelude::*; // REMOVED - prelude does not exist
|
|
use crate::fixtures::*;
|
|
use crate::mocks::*;
|
|
|
|
/// Trading flow integration test suite
|
|
pub struct TradingFlowTests {
|
|
/// TLI client suite for testing
|
|
client_suite: TliClientSuite,
|
|
/// Mock trading service
|
|
mock_trading_service: MockTradingService,
|
|
/// PostgreSQL test database
|
|
test_db: TestDatabase,
|
|
/// Performance metrics collector
|
|
metrics: Arc<PerformanceMetrics>,
|
|
/// Test configuration
|
|
config: IntegrationTestConfig,
|
|
}
|
|
|
|
/// Performance metrics for trading operations
|
|
#[derive(Debug, Default)]
|
|
pub struct PerformanceMetrics {
|
|
/// Order submission latency measurements (nanoseconds)
|
|
pub order_submission_latencies: RwLock<Vec<u64>>,
|
|
/// Risk validation latency measurements (nanoseconds)
|
|
pub risk_validation_latencies: RwLock<Vec<u64>>,
|
|
/// Database persistence latency measurements (nanoseconds)
|
|
pub database_latencies: RwLock<Vec<u64>>,
|
|
/// Total throughput counter
|
|
pub total_operations: AtomicU64,
|
|
/// Error counter
|
|
pub error_count: AtomicU64,
|
|
/// Memory usage tracking
|
|
pub memory_usage_mb: AtomicU64,
|
|
}
|
|
|
|
impl TradingFlowTests {
|
|
/// Create new trading flow test suite
|
|
pub async fn new(config: IntegrationTestConfig) -> TliResult<Self> {
|
|
// Initialize test database
|
|
let test_db = TestDatabase::new().await?;
|
|
|
|
// Initialize mock trading service
|
|
let mock_trading_service = MockTradingService::new().await?;
|
|
|
|
// Create TLI client suite with test endpoints
|
|
let client_suite = TliClientBuilder::new()
|
|
.with_service_endpoint(
|
|
"trading_service".to_string(),
|
|
format!("http://localhost:{}", mock_trading_service.port())
|
|
)
|
|
.with_trading_config(TradingClientConfig {
|
|
timeout_ms: config.request_timeout_ms,
|
|
max_retry_attempts: config.max_retry_attempts,
|
|
circuit_breaker_threshold: config.circuit_breaker_threshold,
|
|
..Default::default()
|
|
})
|
|
.build()
|
|
.await?;
|
|
|
|
Ok(Self {
|
|
client_suite,
|
|
mock_trading_service,
|
|
test_db,
|
|
metrics: Arc::new(PerformanceMetrics::default()),
|
|
config,
|
|
})
|
|
}
|
|
|
|
/// Test basic order submission flow
|
|
pub async fn test_basic_order_submission(&self) -> TliResult<TestResult> {
|
|
let mut test_result = TestResult::new("basic_order_submission");
|
|
let start_time = Instant::now();
|
|
|
|
// Create test order
|
|
let order_request = SubmitOrderRequest {
|
|
symbol: "AAPL".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
order_type: OrderType::Market as i32,
|
|
quantity: 100.0,
|
|
client_order_id: format!("test_order_{}", Uuid::new_v4()),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
// Configure mock response
|
|
self.mock_trading_service.configure_order_response(
|
|
&order_request.client_order_id,
|
|
SubmitOrderResponse {
|
|
success: true,
|
|
order_id: format!("order_{}", Uuid::new_v4()),
|
|
message: "Order submitted successfully".to_string(),
|
|
execution_time_ns: 15_000, // 15µs simulated execution time
|
|
}
|
|
).await;
|
|
|
|
// Measure order submission latency
|
|
let submission_start = Instant::now();
|
|
|
|
let response = match self.client_suite.trading_client {
|
|
Some(ref client) => {
|
|
timeout(
|
|
Duration::from_millis(self.config.request_timeout_ms),
|
|
client.submit_order(order_request.clone())
|
|
).await
|
|
}
|
|
None => {
|
|
test_result.add_error("Trading client not available".to_string());
|
|
return Ok(test_result);
|
|
}
|
|
};
|
|
|
|
let submission_latency = submission_start.elapsed().as_nanos() as u64;
|
|
|
|
// Record performance metrics
|
|
self.metrics.order_submission_latencies.write().await.push(submission_latency);
|
|
self.metrics.total_operations.fetch_add(1, Ordering::Relaxed);
|
|
|
|
// Validate response
|
|
match response {
|
|
Ok(Ok(response)) => {
|
|
let resp = response.into_inner();
|
|
test_result.add_assertion("Order submission successful", resp.success);
|
|
test_result.add_assertion("Order ID provided", !resp.order_id.is_empty());
|
|
test_result.add_assertion(
|
|
"Latency within HFT requirements",
|
|
submission_latency < self.config.max_latency_ns
|
|
);
|
|
|
|
// Verify database persistence
|
|
let db_start = Instant::now();
|
|
let order_persisted = self.test_db.verify_order_persisted(
|
|
&resp.order_id
|
|
).await.unwrap_or(false);
|
|
let db_latency = db_start.elapsed().as_nanos() as u64;
|
|
|
|
self.metrics.database_latencies.write().await.push(db_latency);
|
|
test_result.add_assertion("Order persisted to database", order_persisted);
|
|
test_result.add_assertion(
|
|
"Database latency acceptable",
|
|
db_latency < self.config.max_db_latency_ns
|
|
);
|
|
}
|
|
Ok(Err(e)) => {
|
|
test_result.add_error(format!("Order submission failed: {:?}", e));
|
|
self.metrics.error_count.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
Err(_) => {
|
|
test_result.add_error("Order submission timeout".to_string());
|
|
self.metrics.error_count.fetch_add(1, Ordering::Relaxed);
|
|
}
|
|
}
|
|
|
|
test_result.execution_time = start_time.elapsed();
|
|
test_result.set_passed(test_result.errors.is_empty());
|
|
Ok(test_result)
|
|
}
|
|
|
|
/// Test risk-integrated order submission with validation
|
|
pub async fn test_risk_integrated_order_submission(&self) -> TliResult<TestResult> {
|
|
let mut test_result = TestResult::new("risk_integrated_order_submission");
|
|
let start_time = Instant::now();
|
|
|
|
// Create high-risk order that should trigger risk checks
|
|
let risky_order = SubmitOrderRequest {
|
|
symbol: "AAPL".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
order_type: OrderType::Market as i32,
|
|
quantity: 100_000.0, // Large quantity to trigger risk limits
|
|
price: Some(200.0), // High price
|
|
client_order_id: format!("risky_order_{}", Uuid::new_v4()),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
// Configure mock risk service to reject this order
|
|
self.mock_trading_service.configure_risk_rejection(
|
|
&risky_order.client_order_id,
|
|
"Position limit exceeded: would result in 150% of max allowed position".to_string()
|
|
).await;
|
|
|
|
// Submit order and measure risk validation latency
|
|
let risk_start = Instant::now();
|
|
|
|
let response = match self.client_suite.trading_client {
|
|
Some(ref client) => {
|
|
timeout(
|
|
Duration::from_millis(self.config.request_timeout_ms),
|
|
client.submit_order(risky_order.clone())
|
|
).await
|
|
}
|
|
None => {
|
|
test_result.add_error("Trading client not available".to_string());
|
|
return Ok(test_result);
|
|
}
|
|
};
|
|
|
|
let risk_latency = risk_start.elapsed().as_nanos() as u64;
|
|
self.metrics.risk_validation_latencies.write().await.push(risk_latency);
|
|
|
|
// Validate risk rejection
|
|
match response {
|
|
Ok(Ok(response)) => {
|
|
let resp = response.into_inner();
|
|
test_result.add_assertion("Order correctly rejected", !resp.success);
|
|
test_result.add_assertion("Risk reason provided", resp.message.contains("Position limit"));
|
|
test_result.add_assertion(
|
|
"Risk validation latency acceptable",
|
|
risk_latency < self.config.max_risk_latency_ns
|
|
);
|
|
}
|
|
Ok(Err(e)) => {
|
|
test_result.add_error(format!("Unexpected error: {:?}", e));
|
|
}
|
|
Err(_) => {
|
|
test_result.add_error("Risk validation timeout".to_string());
|
|
}
|
|
}
|
|
|
|
test_result.execution_time = start_time.elapsed();
|
|
test_result.set_passed(test_result.errors.is_empty());
|
|
Ok(test_result)
|
|
}
|
|
|
|
/// Test order lifecycle with position tracking
|
|
pub async fn test_order_lifecycle_with_position_tracking(&self) -> TliResult<TestResult> {
|
|
let mut test_result = TestResult::new("order_lifecycle_position_tracking");
|
|
let start_time = Instant::now();
|
|
|
|
let order_id = format!("lifecycle_order_{}", Uuid::new_v4());
|
|
|
|
// Create order
|
|
let order_request = SubmitOrderRequest {
|
|
symbol: "AAPL".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
order_type: OrderType::Limit as i32,
|
|
quantity: 500.0,
|
|
price: Some(150.0),
|
|
client_order_id: order_id.clone(),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
// Configure mock to simulate full order lifecycle
|
|
self.mock_trading_service.configure_lifecycle_simulation(
|
|
&order_id,
|
|
vec![
|
|
OrderStatus::Pending,
|
|
OrderStatus::PartiallyFilled,
|
|
OrderStatus::Filled
|
|
]
|
|
).await;
|
|
|
|
// Submit order
|
|
let submit_response = match self.client_suite.trading_client {
|
|
Some(ref client) => {
|
|
let response = client.submit_order(order_request).await?;
|
|
response.into_inner()
|
|
},
|
|
None => {
|
|
test_result.add_error("Trading client not available".to_string());
|
|
return Ok(test_result);
|
|
}
|
|
};
|
|
|
|
test_result.add_assertion("Order submission successful", submit_response.success);
|
|
|
|
// Monitor order status changes
|
|
let mut status_updates = Vec::new();
|
|
let mut position_updates = Vec::new();
|
|
|
|
// Start monitoring streams
|
|
if let Some(ref client) = self.client_suite.trading_client {
|
|
let order_stream = client.subscribe_to_order_updates().await?;
|
|
let position_stream = client.subscribe_to_position_updates().await?;
|
|
|
|
// Collect updates for 5 seconds
|
|
let monitor_duration = Duration::from_secs(5);
|
|
let monitor_start = Instant::now();
|
|
|
|
while monitor_start.elapsed() < monitor_duration {
|
|
tokio::select! {
|
|
order_update = order_stream.recv() => {
|
|
if let Some(update) = order_update {
|
|
if update.order_id == submit_response.order_id {
|
|
status_updates.push(update);
|
|
}
|
|
}
|
|
}
|
|
position_update = position_stream.recv() => {
|
|
if let Some(update) = position_update {
|
|
if update.symbol == "AAPL" {
|
|
position_updates.push(update);
|
|
}
|
|
}
|
|
}
|
|
_ = tokio::time::sleep(Duration::from_millis(100)) => {
|
|
// Continue monitoring
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate lifecycle
|
|
test_result.add_assertion("Received status updates", !status_updates.is_empty());
|
|
test_result.add_assertion("Received position updates", !position_updates.is_empty());
|
|
|
|
// Verify final state
|
|
if let Some(final_status) = status_updates.last() {
|
|
test_result.add_assertion("Order reached filled state", final_status.status == OrderStatus::Filled);
|
|
}
|
|
|
|
// Verify position tracking
|
|
if let Some(final_position) = position_updates.last() {
|
|
test_result.add_assertion("Position updated correctly", final_position.quantity == 500.0);
|
|
}
|
|
|
|
test_result.execution_time = start_time.elapsed();
|
|
test_result.set_passed(test_result.errors.is_empty());
|
|
Ok(test_result)
|
|
}
|
|
|
|
/// Test concurrent order submissions for throughput validation
|
|
pub async fn test_concurrent_order_throughput(&self) -> TliResult<TestResult> {
|
|
let mut test_result = TestResult::new("concurrent_order_throughput");
|
|
let start_time = Instant::now();
|
|
|
|
let num_concurrent_orders = self.config.concurrent_order_count;
|
|
let mut handles = Vec::new();
|
|
let metrics = Arc::clone(&self.metrics);
|
|
|
|
// Submit multiple orders concurrently
|
|
for i in 0..num_concurrent_orders {
|
|
let client = match self.client_suite.trading_client {
|
|
Some(ref c) => c.clone(),
|
|
None => {
|
|
test_result.add_error("Trading client not available".to_string());
|
|
return Ok(test_result);
|
|
}
|
|
};
|
|
|
|
let order_request = SubmitOrderRequest {
|
|
symbol: "AAPL".to_string(),
|
|
side: if i % 2 == 0 { OrderSide::Buy } else { OrderSide::Sell } as i32,
|
|
order_type: OrderType::Market as i32,
|
|
quantity: 100.0,
|
|
client_order_id: format!("concurrent_order_{}_{}", i, Uuid::new_v4()),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
let metrics_clone = Arc::clone(&metrics);
|
|
|
|
let handle = tokio::spawn(async move {
|
|
let start = Instant::now();
|
|
let result = client.submit_order(order_request).await;
|
|
let latency = start.elapsed().as_nanos() as u64;
|
|
|
|
metrics_clone.order_submission_latencies.write().await.push(latency);
|
|
metrics_clone.total_operations.fetch_add(1, Ordering::Relaxed);
|
|
|
|
match result {
|
|
Ok(response) => {
|
|
let resp = response.into_inner();
|
|
resp.success
|
|
},
|
|
Err(_) => {
|
|
metrics_clone.error_count.fetch_add(1, Ordering::Relaxed);
|
|
false
|
|
}
|
|
}
|
|
});
|
|
|
|
handles.push(handle);
|
|
}
|
|
|
|
// Wait for all orders to complete
|
|
let mut successful_orders = 0;
|
|
for handle in handles {
|
|
if let Ok(success) = handle.await {
|
|
if success {
|
|
successful_orders += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
let total_time = start_time.elapsed();
|
|
let throughput = successful_orders as f64 / total_time.as_secs_f64();
|
|
|
|
// Validate throughput requirements
|
|
test_result.add_assertion(
|
|
"Minimum successful orders",
|
|
successful_orders >= (num_concurrent_orders * 9 / 10) // 90% success rate
|
|
);
|
|
|
|
test_result.add_assertion(
|
|
"Throughput meets HFT requirements",
|
|
throughput >= self.config.min_throughput_ops_per_sec
|
|
);
|
|
|
|
test_result.metadata.insert("throughput_ops_per_sec".to_string(), json!(throughput));
|
|
test_result.metadata.insert("successful_orders".to_string(), json!(successful_orders));
|
|
test_result.metadata.insert("total_orders".to_string(), json!(num_concurrent_orders));
|
|
|
|
test_result.execution_time = total_time;
|
|
test_result.set_passed(test_result.errors.is_empty());
|
|
Ok(test_result)
|
|
}
|
|
|
|
/// Test circuit breaker functionality
|
|
pub async fn test_circuit_breaker_activation(&self) -> TliResult<TestResult> {
|
|
let mut test_result = TestResult::new("circuit_breaker_activation");
|
|
let start_time = Instant::now();
|
|
|
|
// Configure mock to fail multiple consecutive requests
|
|
self.mock_trading_service.configure_failure_sequence(10).await;
|
|
|
|
let mut consecutive_failures = 0;
|
|
let max_attempts = 15;
|
|
|
|
// Submit orders until circuit breaker activates
|
|
for i in 0..max_attempts {
|
|
let order_request = SubmitOrderRequest {
|
|
symbol: "AAPL".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
order_type: OrderType::Market as i32,
|
|
quantity: 100.0,
|
|
client_order_id: format!("cb_test_order_{}", i),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
let response = match self.client_suite.trading_client {
|
|
Some(ref client) => client.submit_order(order_request).await,
|
|
None => {
|
|
test_result.add_error("Trading client not available".to_string());
|
|
return Ok(test_result);
|
|
}
|
|
};
|
|
|
|
match response {
|
|
Ok(response) => {
|
|
let resp = response.into_inner();
|
|
if !resp.success {
|
|
consecutive_failures += 1;
|
|
if consecutive_failures >= self.config.circuit_breaker_threshold {
|
|
test_result.add_assertion("Circuit breaker activated", true);
|
|
break;
|
|
}
|
|
} else {
|
|
consecutive_failures = 0; // Reset on success
|
|
}
|
|
}
|
|
Err(TliError::CircuitBreakerOpen) => {
|
|
test_result.add_assertion("Circuit breaker properly triggered", true);
|
|
break;
|
|
}
|
|
Err(_) => {
|
|
// Handle other errors
|
|
consecutive_failures += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Verify circuit breaker status
|
|
if let Some(ref client) = self.client_suite.trading_client {
|
|
let response = client.get_circuit_breaker_status().await?;
|
|
let status = response.into_inner();
|
|
test_result.add_assertion("Circuit breaker status available", status.is_open);
|
|
}
|
|
|
|
test_result.execution_time = start_time.elapsed();
|
|
test_result.set_passed(test_result.errors.is_empty());
|
|
Ok(test_result)
|
|
}
|
|
|
|
/// Test emergency stop functionality
|
|
pub async fn test_emergency_stop(&self) -> TliResult<TestResult> {
|
|
let mut test_result = TestResult::new("emergency_stop");
|
|
let start_time = Instant::now();
|
|
|
|
// Submit initial order to ensure system is active
|
|
let order_request = SubmitOrderRequest {
|
|
symbol: "AAPL".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
order_type: OrderType::Market as i32,
|
|
quantity: 100.0,
|
|
client_order_id: format!("pre_stop_order_{}", Uuid::new_v4()),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
if let Some(ref client) = self.client_suite.trading_client {
|
|
let response = client.submit_order(order_request).await?;
|
|
let initial_response = response.into_inner();
|
|
test_result.add_assertion("Initial order successful", initial_response.success);
|
|
|
|
// Trigger emergency stop
|
|
let response = client.trigger_emergency_stop("Integration test emergency stop").await?;
|
|
let stop_result = response.into_inner();
|
|
test_result.add_assertion("Emergency stop triggered successfully", stop_result.success);
|
|
|
|
// Wait for stop to propagate
|
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
|
|
// Attempt to submit order after emergency stop
|
|
let post_stop_order = SubmitOrderRequest {
|
|
symbol: "AAPL".to_string(),
|
|
side: OrderSide::Buy as i32,
|
|
order_type: OrderType::Market as i32,
|
|
quantity: 100.0,
|
|
client_order_id: format!("post_stop_order_{}", Uuid::new_v4()),
|
|
metadata: HashMap::new(),
|
|
};
|
|
|
|
let post_stop_response = client.submit_order(post_stop_order).await;
|
|
|
|
match post_stop_response {
|
|
Ok(response) => {
|
|
let resp = response.into_inner();
|
|
test_result.add_assertion("Order rejected after emergency stop", !resp.success);
|
|
test_result.add_assertion("Emergency stop message provided",
|
|
resp.message.contains("emergency") || resp.message.contains("stopped"));
|
|
}
|
|
Err(TliError::TradingHalted) => {
|
|
test_result.add_assertion("Trading halted error received", true);
|
|
}
|
|
Err(e) => {
|
|
test_result.add_error(format!("Unexpected error after emergency stop: {:?}", e));
|
|
}
|
|
}
|
|
|
|
// Verify emergency stop status
|
|
let response = client.get_trading_status().await?;
|
|
let status = response.into_inner();
|
|
test_result.add_assertion("Trading status shows emergency stop", status.emergency_stop_active);
|
|
}
|
|
|
|
test_result.execution_time = start_time.elapsed();
|
|
test_result.set_passed(test_result.errors.is_empty());
|
|
Ok(test_result)
|
|
}
|
|
|
|
/// Run complete trading flow test suite
|
|
pub async fn run_complete_suite(&self) -> TliResult<TestSuite> {
|
|
let mut suite = TestSuite::new("trading_flow_integration");
|
|
let suite_start = Instant::now();
|
|
|
|
// Run all test cases
|
|
let tests = vec![
|
|
self.test_basic_order_submission().await?,
|
|
self.test_risk_integrated_order_submission().await?,
|
|
self.test_order_lifecycle_with_position_tracking().await?,
|
|
self.test_concurrent_order_throughput().await?,
|
|
self.test_circuit_breaker_activation().await?,
|
|
self.test_emergency_stop().await?,
|
|
];
|
|
|
|
for test in tests {
|
|
suite.add_test_result(test);
|
|
}
|
|
|
|
// Generate performance summary
|
|
let metrics = self.generate_performance_summary().await;
|
|
suite.metadata.insert("performance_metrics".to_string(), json!(metrics));
|
|
|
|
suite.execution_time = suite_start.elapsed();
|
|
suite.set_passed(suite.passed_tests >= suite.total_tests * 80 / 100); // 80% pass rate
|
|
|
|
Ok(suite)
|
|
}
|
|
|
|
/// Generate comprehensive performance summary
|
|
async fn generate_performance_summary(&self) -> serde_json::Value {
|
|
let order_latencies = self.metrics.order_submission_latencies.read().await;
|
|
let risk_latencies = self.metrics.risk_validation_latencies.read().await;
|
|
let db_latencies = self.metrics.database_latencies.read().await;
|
|
|
|
let order_stats = calculate_latency_stats(&order_latencies);
|
|
let risk_stats = calculate_latency_stats(&risk_latencies);
|
|
let db_stats = calculate_latency_stats(&db_latencies);
|
|
|
|
json!({
|
|
"order_submission": {
|
|
"count": order_latencies.len(),
|
|
"avg_ns": order_stats.avg,
|
|
"p50_ns": order_stats.p50,
|
|
"p95_ns": order_stats.p95,
|
|
"p99_ns": order_stats.p99,
|
|
"max_ns": order_stats.max
|
|
},
|
|
"risk_validation": {
|
|
"count": risk_latencies.len(),
|
|
"avg_ns": risk_stats.avg,
|
|
"p50_ns": risk_stats.p50,
|
|
"p95_ns": risk_stats.p95,
|
|
"p99_ns": risk_stats.p99,
|
|
"max_ns": risk_stats.max
|
|
},
|
|
"database_operations": {
|
|
"count": db_latencies.len(),
|
|
"avg_ns": db_stats.avg,
|
|
"p50_ns": db_stats.p50,
|
|
"p95_ns": db_stats.p95,
|
|
"p99_ns": db_stats.p99,
|
|
"max_ns": db_stats.max
|
|
},
|
|
"total_operations": self.metrics.total_operations.load(Ordering::Relaxed),
|
|
"error_count": self.metrics.error_count.load(Ordering::Relaxed),
|
|
"memory_usage_mb": self.metrics.memory_usage_mb.load(Ordering::Relaxed)
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Calculate latency statistics from measurements
|
|
fn calculate_latency_stats(latencies: &[u64]) -> LatencyStats {
|
|
if latencies.is_empty() {
|
|
return LatencyStats::default();
|
|
}
|
|
|
|
let mut sorted = latencies.to_vec();
|
|
sorted.sort_unstable();
|
|
|
|
let len = sorted.len();
|
|
let avg = sorted.iter().sum::<u64>() / len as u64;
|
|
let p50 = sorted[len * 50 / 100];
|
|
let p95 = sorted[len * 95 / 100];
|
|
let p99 = sorted[len * 99 / 100];
|
|
let max = sorted[len - 1];
|
|
|
|
LatencyStats { avg, p50, p95, p99, max }
|
|
}
|
|
|
|
/// Latency statistics structure
|
|
#[derive(Debug, Default)]
|
|
struct LatencyStats {
|
|
avg: u64,
|
|
p50: u64,
|
|
p95: u64,
|
|
p99: u64,
|
|
max: u64,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[tokio::test]
|
|
async fn test_trading_flow_integration() {
|
|
let config = IntegrationTestConfig::default();
|
|
let tests = TradingFlowTests::new(config).await.unwrap();
|
|
let results = tests.run_complete_suite().await.unwrap();
|
|
|
|
println!("Trading Flow Integration Test Results:");
|
|
println!("Passed: {}/{}", results.passed_tests, results.total_tests);
|
|
println!("Execution time: {:?}", results.execution_time);
|
|
|
|
// Print performance metrics
|
|
if let Some(metrics) = results.metadata.get("performance_metrics") {
|
|
println!("Performance Metrics: {}", serde_json::to_string_pretty(metrics).unwrap());
|
|
}
|
|
|
|
assert!(results.passed, "Trading flow integration tests should pass");
|
|
}
|
|
} |