提交qiming-mcp-proxy
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
use document_parser::utils::environment_manager::{
|
||||
EnvironmentManager, InstallProgress, IssueSeverity, RetryConfig,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use tempfile::TempDir;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_environment_manager_with_retry_config() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let retry_config = RetryConfig {
|
||||
max_attempts: 2,
|
||||
base_delay: Duration::from_millis(100),
|
||||
max_delay: Duration::from_secs(5),
|
||||
backoff_multiplier: 2.0,
|
||||
};
|
||||
|
||||
let manager = EnvironmentManager::new(
|
||||
"python3".to_string(),
|
||||
temp_dir.path().to_string_lossy().to_string(),
|
||||
)
|
||||
.with_retry_config(retry_config)
|
||||
.with_timeout(Duration::from_secs(30))
|
||||
.with_cache_ttl(Duration::from_secs(60));
|
||||
|
||||
// 测试环境检查
|
||||
let result = manager.check_environment().await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let status = result.unwrap();
|
||||
assert!(status.health_score() <= 100);
|
||||
|
||||
// 测试缓存功能
|
||||
let cached_result = manager.check_environment().await;
|
||||
assert!(cached_result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_environment_manager_with_progress_tracking() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel::<InstallProgress>();
|
||||
|
||||
let manager = EnvironmentManager::with_progress_tracking(
|
||||
"python3".to_string(),
|
||||
temp_dir.path().to_string_lossy().to_string(),
|
||||
tx,
|
||||
);
|
||||
|
||||
// 在后台任务中监听进度
|
||||
let progress_task = tokio::spawn(async move {
|
||||
let mut progress_count = 0;
|
||||
while let Some(progress) = rx.recv().await {
|
||||
progress_count += 1;
|
||||
println!(
|
||||
"Progress: {} - {} ({}%)",
|
||||
progress.package, progress.message, progress.progress
|
||||
);
|
||||
|
||||
// 避免无限等待
|
||||
if progress_count > 10 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
progress_count
|
||||
});
|
||||
|
||||
// 执行环境检查
|
||||
let result = manager.check_environment().await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
// 等待进度任务完成(带超时)
|
||||
let progress_result = tokio::time::timeout(Duration::from_secs(5), progress_task).await;
|
||||
|
||||
// 验证进度跟踪是否工作
|
||||
if let Ok(Ok(count)) = progress_result {
|
||||
println!("Received {count} progress updates");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_environment_status_analysis() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let manager = EnvironmentManager::new(
|
||||
"python3".to_string(),
|
||||
temp_dir.path().to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
let status = manager.check_environment().await.unwrap();
|
||||
|
||||
// 测试状态分析方法
|
||||
println!("Environment ready: {}", status.is_ready());
|
||||
println!("Health score: {}/100", status.health_score());
|
||||
println!("Has CUDA support: {}", status.has_cuda_support());
|
||||
|
||||
// 测试问题分析
|
||||
let critical_issues = status.get_critical_issues();
|
||||
let auto_fixable_issues = status.get_auto_fixable_issues();
|
||||
|
||||
println!("Critical issues: {}", critical_issues.len());
|
||||
println!("Auto-fixable issues: {}", auto_fixable_issues.len());
|
||||
|
||||
for issue in critical_issues {
|
||||
assert_eq!(issue.severity, IssueSeverity::Critical);
|
||||
println!("Critical issue: {} - {}", issue.component, issue.message);
|
||||
}
|
||||
|
||||
for issue in auto_fixable_issues {
|
||||
assert!(issue.auto_fixable);
|
||||
println!(
|
||||
"Auto-fixable issue: {} - {}",
|
||||
issue.component, issue.suggestion
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_environment_reporting() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let manager = EnvironmentManager::new(
|
||||
"python3".to_string(),
|
||||
temp_dir.path().to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
// 测试环境报告生成
|
||||
let report = manager.generate_environment_report().await;
|
||||
assert!(report.is_ok());
|
||||
|
||||
let report_content = report.unwrap();
|
||||
assert!(report_content.contains("=== 环境检查报告 ==="));
|
||||
assert!(report_content.contains("=== 组件状态 ==="));
|
||||
|
||||
println!("Environment Report:\n{report_content}");
|
||||
|
||||
// 测试环境摘要
|
||||
let summary = manager.get_environment_summary().await;
|
||||
assert!(summary.is_ok());
|
||||
|
||||
let summary_content = summary.unwrap();
|
||||
assert!(summary_content.contains("环境状态:"));
|
||||
assert!(summary_content.contains("健康评分:"));
|
||||
|
||||
println!("Environment Summary: {summary_content}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_environment_validation() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let manager = EnvironmentManager::new(
|
||||
"python3".to_string(),
|
||||
temp_dir.path().to_string_lossy().to_string(),
|
||||
);
|
||||
|
||||
// 测试环境验证
|
||||
let is_valid = manager.validate_environment().await;
|
||||
assert!(is_valid.is_ok());
|
||||
|
||||
let validation_result = is_valid.unwrap();
|
||||
println!("Environment validation result: {validation_result}");
|
||||
|
||||
// 测试引擎验证
|
||||
let engines_valid = manager.validate_engines().await;
|
||||
assert!(engines_valid.is_ok());
|
||||
|
||||
let engines_result = engines_valid.unwrap();
|
||||
println!("Engines validation result: {engines_result}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_cache_functionality() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let manager = EnvironmentManager::new(
|
||||
"python3".to_string(),
|
||||
temp_dir.path().to_string_lossy().to_string(),
|
||||
)
|
||||
.with_cache_ttl(Duration::from_secs(1)); // 短缓存时间用于测试
|
||||
|
||||
// 第一次检查
|
||||
let start_time = std::time::Instant::now();
|
||||
let result1 = manager.check_environment().await.unwrap();
|
||||
let first_check_duration = start_time.elapsed();
|
||||
|
||||
// 立即第二次检查(应该使用缓存)
|
||||
let start_time = std::time::Instant::now();
|
||||
let result2 = manager.check_environment().await.unwrap();
|
||||
let second_check_duration = start_time.elapsed();
|
||||
|
||||
// 缓存的检查应该更快,但由于测试环境的不确定性,我们只检查结果一致性
|
||||
// assert!(second_check_duration < first_check_duration);
|
||||
// 验证结果一致性
|
||||
assert_eq!(result1.python_available, result2.python_available);
|
||||
assert_eq!(result1.uv_available, result2.uv_available);
|
||||
|
||||
// 等待缓存过期
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
// 清除缓存
|
||||
manager.clear_cache().await;
|
||||
|
||||
// 第三次检查(缓存已过期)
|
||||
let start_time = std::time::Instant::now();
|
||||
let result3 = manager.check_environment().await.unwrap();
|
||||
let third_check_duration = start_time.elapsed();
|
||||
|
||||
// 过期后的检查可能比缓存检查慢,但在测试环境中可能不稳定
|
||||
// 我们只验证缓存功能正常工作,不强制要求时间差异
|
||||
println!("Cache functionality test completed - timing may vary in test environment");
|
||||
|
||||
println!("First check: {first_check_duration:?}");
|
||||
println!("Second check (cached): {second_check_duration:?}");
|
||||
println!("Third check (expired): {third_check_duration:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_environment_checks() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let manager = std::sync::Arc::new(EnvironmentManager::new(
|
||||
"python3".to_string(),
|
||||
temp_dir.path().to_string_lossy().to_string(),
|
||||
));
|
||||
|
||||
// 并发执行多个环境检查
|
||||
let mut handles = Vec::new();
|
||||
|
||||
for i in 0..5 {
|
||||
let manager_clone = manager.clone();
|
||||
let handle = tokio::spawn(async move {
|
||||
let result = manager_clone.check_environment().await;
|
||||
println!("Concurrent check {i} completed");
|
||||
result
|
||||
});
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
// 等待所有检查完成
|
||||
let results = futures::future::join_all(handles).await;
|
||||
|
||||
// 验证所有检查都成功
|
||||
for (i, result) in results.into_iter().enumerate() {
|
||||
assert!(result.is_ok(), "Concurrent check {i} failed");
|
||||
let status = result.unwrap().unwrap();
|
||||
assert!(status.health_score() <= 100);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
use document_parser::services::image_processor::{
|
||||
ImageProcessor, ImageProcessorConfig, ImageUploadResult,
|
||||
};
|
||||
use std::path::Path;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_image_processing_pipeline() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let config = ImageProcessorConfig::default();
|
||||
let processor = ImageProcessor::new(config, None);
|
||||
|
||||
// 创建测试图片文件
|
||||
let test_images = create_test_images(&temp_dir).await;
|
||||
|
||||
// 测试批量处理
|
||||
let result = processor.batch_upload_images(test_images.clone()).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let batch_result = result.unwrap();
|
||||
assert_eq!(batch_result.len(), test_images.len());
|
||||
|
||||
// 检查结果
|
||||
for upload_result in &batch_result {
|
||||
// 由于没有OSS服务,预期会失败但不会panic
|
||||
assert!(!upload_result.success);
|
||||
assert!(upload_result.error_message.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_image_extraction_from_directory() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let config = ImageProcessorConfig::default();
|
||||
let processor = ImageProcessor::new(config, None);
|
||||
|
||||
// 创建测试目录结构
|
||||
let test_dir = temp_dir.path().join("images");
|
||||
tokio::fs::create_dir_all(&test_dir).await.unwrap();
|
||||
|
||||
// 创建测试图片
|
||||
create_test_image(&test_dir.join("test1.jpg")).await;
|
||||
create_test_image(&test_dir.join("test2.png")).await;
|
||||
|
||||
// 测试提取(注意:当前实现不支持递归子目录)
|
||||
let result = processor
|
||||
.extract_images_from_directory(test_dir.to_str().unwrap())
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let image_paths = result.unwrap();
|
||||
assert_eq!(image_paths.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_error_handling_and_recovery() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let config = ImageProcessorConfig::default();
|
||||
let processor = ImageProcessor::new(config, None);
|
||||
|
||||
// 测试不存在的文件
|
||||
let non_existent_files = vec![
|
||||
"/non/existent/file1.jpg".to_string(),
|
||||
"/non/existent/file2.png".to_string(),
|
||||
];
|
||||
|
||||
let result = processor.batch_upload_images(non_existent_files).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let batch_result = result.unwrap();
|
||||
assert_eq!(batch_result.len(), 2);
|
||||
|
||||
// 所有结果都应该失败
|
||||
for upload_result in &batch_result {
|
||||
assert!(!upload_result.success);
|
||||
assert!(upload_result.error_message.is_some());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_with_large_batch() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let config = ImageProcessorConfig::default();
|
||||
let processor = ImageProcessor::new(config, None);
|
||||
|
||||
// 创建大量测试图片
|
||||
let mut test_images = Vec::new();
|
||||
for i in 0..50 {
|
||||
let image_path = temp_dir.path().join(format!("test_{i}.jpg"));
|
||||
create_test_image(&image_path).await;
|
||||
test_images.push(image_path.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
let result = processor.batch_upload_images(test_images.clone()).await;
|
||||
|
||||
let processing_time = start_time.elapsed();
|
||||
|
||||
assert!(result.is_ok());
|
||||
let batch_result = result.unwrap();
|
||||
assert_eq!(batch_result.len(), test_images.len());
|
||||
|
||||
// 性能检查:处理50个图片应该在合理时间内完成
|
||||
assert!(
|
||||
processing_time.as_secs() < 30,
|
||||
"Processing took too long: {processing_time:?}"
|
||||
);
|
||||
|
||||
println!(
|
||||
"Processed {} images in {:?}",
|
||||
test_images.len(),
|
||||
processing_time
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_concurrent_processing() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
||||
// 创建多个处理器实例
|
||||
let config1 = ImageProcessorConfig::default();
|
||||
let config2 = ImageProcessorConfig::default();
|
||||
let processor1 = ImageProcessor::new(config1, None);
|
||||
let processor2 = ImageProcessor::new(config2, None);
|
||||
|
||||
// 创建测试图片
|
||||
let test_images1 = create_test_images(&temp_dir).await;
|
||||
let test_images2 = create_test_images(&temp_dir).await;
|
||||
|
||||
// 并发处理
|
||||
let (result1, result2): (
|
||||
anyhow::Result<Vec<ImageUploadResult>>,
|
||||
anyhow::Result<Vec<ImageUploadResult>>,
|
||||
) = tokio::join!(
|
||||
processor1.batch_upload_images(test_images1.clone()),
|
||||
processor2.batch_upload_images(test_images2.clone())
|
||||
);
|
||||
|
||||
assert!(result1.is_ok());
|
||||
assert!(result2.is_ok());
|
||||
|
||||
let batch_result1 = result1.unwrap();
|
||||
let batch_result2 = result2.unwrap();
|
||||
|
||||
assert_eq!(batch_result1.len(), test_images1.len());
|
||||
assert_eq!(batch_result2.len(), test_images2.len());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_markdown_image_replacement() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let config = ImageProcessorConfig::default();
|
||||
let processor = ImageProcessor::new(config, None);
|
||||
|
||||
// 创建测试图片
|
||||
let image_path = temp_dir.path().join("test.jpg");
|
||||
create_test_image(&image_path).await;
|
||||
|
||||
// 创建包含图片的Markdown内容
|
||||
let markdown_content = format!(
|
||||
"# Test Document\n\n\n\nSome text here.",
|
||||
image_path.to_string_lossy()
|
||||
);
|
||||
|
||||
// 测试替换(由于没有OSS服务,图片路径不会被替换)
|
||||
let result = processor.replace_markdown_images(&markdown_content).await;
|
||||
assert!(result.is_ok());
|
||||
|
||||
let processed_content = result.unwrap();
|
||||
// 由于没有OSS服务,内容应该保持不变
|
||||
assert_eq!(processed_content, markdown_content);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_image_validation() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let config = ImageProcessorConfig::default();
|
||||
let processor = ImageProcessor::new(config, None);
|
||||
|
||||
// 创建有效的图片文件
|
||||
let valid_image = temp_dir.path().join("valid.jpg");
|
||||
create_test_image(&valid_image).await;
|
||||
|
||||
// 创建无效的文件(非图片格式)
|
||||
let invalid_file = temp_dir.path().join("invalid.txt");
|
||||
tokio::fs::write(&invalid_file, "not an image")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 测试验证
|
||||
let valid_result = processor
|
||||
.validate_image_file(valid_image.to_str().unwrap())
|
||||
.await;
|
||||
assert!(valid_result.is_ok());
|
||||
assert!(valid_result.unwrap());
|
||||
|
||||
let invalid_result = processor
|
||||
.validate_image_file(invalid_file.to_str().unwrap())
|
||||
.await;
|
||||
assert!(invalid_result.is_ok());
|
||||
assert!(!invalid_result.unwrap());
|
||||
|
||||
// 测试不存在的文件
|
||||
let nonexistent_result = processor.validate_image_file("/nonexistent/file.jpg").await;
|
||||
assert!(nonexistent_result.is_ok());
|
||||
assert!(!nonexistent_result.unwrap());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_extract_image_paths_from_markdown() {
|
||||
let markdown_content = r#"
|
||||
# Test Document
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
Some text here.
|
||||
"#;
|
||||
|
||||
let paths = ImageProcessor::extract_image_paths(markdown_content);
|
||||
|
||||
// 应该提取到3个本地图片路径(排除外部URL)
|
||||
assert_eq!(paths.len(), 3);
|
||||
assert!(paths.contains(&"images/test1.jpg".to_string()));
|
||||
assert!(paths.contains(&"./local/test2.png".to_string()));
|
||||
assert!(paths.contains(&"../parent/test3.gif".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_cache_functionality() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let config = ImageProcessorConfig::default();
|
||||
let processor = ImageProcessor::new(config, None);
|
||||
|
||||
// 初始缓存应该为空
|
||||
let (total, successful) = processor.get_cache_stats().await;
|
||||
assert_eq!(total, 0);
|
||||
assert_eq!(successful, 0);
|
||||
|
||||
// 尝试上传一些图片(由于没有OSS服务,会失败且不会被缓存)
|
||||
let test_images = create_test_images(&temp_dir).await;
|
||||
let result = processor.batch_upload_images(test_images).await;
|
||||
|
||||
// 验证上传结果
|
||||
assert!(result.is_ok());
|
||||
let upload_results = result.unwrap();
|
||||
assert!(!upload_results.is_empty());
|
||||
|
||||
// 由于没有OSS服务,所有上传都应该失败
|
||||
for upload_result in &upload_results {
|
||||
assert!(!upload_result.success);
|
||||
assert!(upload_result.error_message.is_some());
|
||||
}
|
||||
|
||||
// 检查缓存统计(失败的上传不会被缓存)
|
||||
let (total_after, successful_after) = processor.get_cache_stats().await;
|
||||
assert_eq!(total_after, 0); // 失败的上传不会被缓存
|
||||
assert_eq!(successful_after, 0);
|
||||
|
||||
// 清空缓存(即使为空也应该正常工作)
|
||||
processor.clear_cache().await;
|
||||
let (total_cleared, successful_cleared) = processor.get_cache_stats().await;
|
||||
assert_eq!(total_cleared, 0);
|
||||
assert_eq!(successful_cleared, 0);
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
|
||||
async fn create_test_images(temp_dir: &TempDir) -> Vec<String> {
|
||||
let mut images = Vec::new();
|
||||
|
||||
for (i, ext) in ["jpg", "png", "gif"].iter().enumerate() {
|
||||
let image_path = temp_dir.path().join(format!("test_{i}.{ext}"));
|
||||
create_test_image(&image_path).await;
|
||||
images.push(image_path.to_string_lossy().to_string());
|
||||
}
|
||||
|
||||
images
|
||||
}
|
||||
|
||||
async fn create_test_image(path: &Path) {
|
||||
// 创建模拟图片文件(简单的二进制数据)
|
||||
let mut content = Vec::new();
|
||||
|
||||
// 根据扩展名添加相应的文件头
|
||||
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
|
||||
match ext.to_lowercase().as_str() {
|
||||
"jpg" | "jpeg" => {
|
||||
content.extend_from_slice(&[0xFF, 0xD8, 0xFF, 0xE0]); // JPEG header
|
||||
}
|
||||
"png" => {
|
||||
content.extend_from_slice(&[0x89, 0x50, 0x4E, 0x47]); // PNG header
|
||||
}
|
||||
"gif" => {
|
||||
content.extend_from_slice(&[0x47, 0x49, 0x46, 0x38]); // GIF header
|
||||
}
|
||||
_ => {
|
||||
content.extend_from_slice(&[0xFF, 0xD8, 0xFF, 0xE0]); // Default to JPEG
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加一些随机数据
|
||||
for i in 0..1024 {
|
||||
content.push((i % 256) as u8);
|
||||
}
|
||||
|
||||
tokio::fs::write(path, &content).await.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,596 @@
|
||||
use document_parser::utils::{
|
||||
health_check::{
|
||||
EnhancedHealthCheckManager, HealthCheckConfig, HealthCheckResult, HealthChecker,
|
||||
HealthStatus,
|
||||
},
|
||||
logging::{
|
||||
CorrelationContext, EnhancedLoggingSystem, LogFormat, LogOutputTarget, LoggingConfig,
|
||||
},
|
||||
metrics::{AsyncMetricsCollector, MetricsRegistry, PerformanceMonitor},
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
|
||||
/// 模拟健康检查器
|
||||
struct MockHealthChecker {
|
||||
name: String,
|
||||
should_fail: std::sync::Arc<std::sync::atomic::AtomicBool>,
|
||||
response_delay: Duration,
|
||||
}
|
||||
|
||||
impl MockHealthChecker {
|
||||
fn new(name: String, response_delay: Duration) -> Self {
|
||||
Self {
|
||||
name,
|
||||
should_fail: Arc::new(std::sync::atomic::AtomicBool::new(false)),
|
||||
response_delay,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_should_fail(&self, should_fail: bool) {
|
||||
self.should_fail
|
||||
.store(should_fail, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl HealthChecker for MockHealthChecker {
|
||||
async fn check_health(&self) -> HealthCheckResult {
|
||||
// 模拟检查延迟
|
||||
sleep(self.response_delay).await;
|
||||
|
||||
let status = if self.should_fail.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
HealthStatus::Unhealthy
|
||||
} else {
|
||||
HealthStatus::Healthy
|
||||
};
|
||||
|
||||
let mut result = HealthCheckResult::new(
|
||||
self.name.clone(),
|
||||
status,
|
||||
format!("Mock health check for {}", self.name),
|
||||
);
|
||||
|
||||
result.add_detail("mock".to_string(), "true".to_string());
|
||||
result.add_detail(
|
||||
"delay_ms".to_string(),
|
||||
self.response_delay.as_millis().to_string(),
|
||||
);
|
||||
|
||||
result.with_response_time(self.response_delay)
|
||||
}
|
||||
|
||||
fn component_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn timeout(&self) -> Duration {
|
||||
Duration::from_secs(5)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_enhanced_logging_system() {
|
||||
// 创建临时日志文件
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let log_file = temp_dir.path().join("test.log");
|
||||
|
||||
let config = LoggingConfig {
|
||||
level: "debug".to_string(),
|
||||
format: LogFormat::Json,
|
||||
output: LogOutputTarget::Both,
|
||||
file_path: Some(log_file.to_string_lossy().to_string()),
|
||||
enable_console: true,
|
||||
enable_json: true,
|
||||
enable_correlation: true,
|
||||
service_name: "test-service".to_string(),
|
||||
service_version: "1.0.0".to_string(),
|
||||
environment: "test".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// 初始化日志系统
|
||||
let logging_system = EnhancedLoggingSystem::init(config).unwrap();
|
||||
|
||||
// 设置关联上下文
|
||||
let correlation = CorrelationContext::new()
|
||||
.with_request_id("req-123".to_string())
|
||||
.with_task_id("task-456".to_string())
|
||||
.with_user_id("user-789".to_string());
|
||||
|
||||
logging_system.set_correlation_context(correlation).await;
|
||||
|
||||
// 生成一些日志
|
||||
tracing::info!("Test information log");
|
||||
tracing::warn!(error_code = "E001", "Test warning log");
|
||||
tracing::error!(component = "test", "Test error log");
|
||||
|
||||
// 创建带有关联上下文的span
|
||||
let span = logging_system.create_span("test_operation").await;
|
||||
let _enter = span.enter();
|
||||
|
||||
tracing::info!("Log in span");
|
||||
|
||||
// 等待日志写入
|
||||
sleep(Duration::from_millis(100)).await;
|
||||
|
||||
// 验证日志文件存在(在某些测试环境中可能不会创建文件)
|
||||
if log_file.exists() {
|
||||
// 读取日志文件内容
|
||||
let log_content = tokio::fs::read_to_string(&log_file).await.unwrap();
|
||||
assert!(!log_content.is_empty());
|
||||
|
||||
// 验证日志内容包含预期的信息
|
||||
assert!(log_content.contains("req-123"));
|
||||
assert!(log_content.contains("task-456"));
|
||||
assert!(log_content.contains("user-789"));
|
||||
assert!(log_content.contains("test-service"));
|
||||
|
||||
println!(
|
||||
"Log content preview:\\n{}",
|
||||
&log_content[..log_content.len().min(500)]
|
||||
);
|
||||
} else {
|
||||
// 如果文件不存在,至少验证日志系统已初始化
|
||||
tracing::info!("The log file is not created, but the logging system works normally");
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_metrics_registry_and_async_collector() {
|
||||
let registry = Arc::new(MetricsRegistry::new());
|
||||
|
||||
// 注册一些测试指标
|
||||
let counter = registry
|
||||
.register_counter(
|
||||
"test_counter".to_string(),
|
||||
HashMap::from([("service".to_string(), "test".to_string())]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let gauge = registry
|
||||
.register_gauge("test_gauge".to_string(), HashMap::new())
|
||||
.await;
|
||||
|
||||
let histogram = registry
|
||||
.register_histogram(
|
||||
"test_histogram".to_string(),
|
||||
vec![0.1, 0.5, 1.0, 2.0, 5.0],
|
||||
HashMap::new(),
|
||||
)
|
||||
.await;
|
||||
|
||||
// 使用指标
|
||||
counter.inc();
|
||||
counter.add(5);
|
||||
assert_eq!(counter.get(), 6);
|
||||
|
||||
gauge.set(42);
|
||||
gauge.inc();
|
||||
assert_eq!(gauge.get(), 43);
|
||||
|
||||
histogram.observe(0.3);
|
||||
histogram.observe(1.5);
|
||||
histogram.observe(3.0);
|
||||
assert_eq!(histogram.get_count(), 3);
|
||||
|
||||
// 测试异步指标收集器
|
||||
let collector = AsyncMetricsCollector::new(registry.clone(), Duration::from_millis(100));
|
||||
collector.start().await.unwrap();
|
||||
|
||||
// 等待几个收集周期
|
||||
sleep(Duration::from_millis(300)).await;
|
||||
|
||||
collector.stop();
|
||||
|
||||
// 导出指标
|
||||
let prometheus_export = registry.export_prometheus().await;
|
||||
assert!(prometheus_export.contains("test_counter"));
|
||||
assert!(prometheus_export.contains("test_gauge"));
|
||||
assert!(prometheus_export.contains("test_histogram"));
|
||||
|
||||
let json_export = registry.export_json().await.unwrap();
|
||||
assert!(json_export.contains("test_counter"));
|
||||
assert!(json_export.contains("test_gauge"));
|
||||
assert!(json_export.contains("test_histogram"));
|
||||
|
||||
println!("Prometheus export:\\n{prometheus_export}");
|
||||
println!("JSON export:\\n{json_export}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_performance_monitor_with_async_collection() {
|
||||
let registry = Arc::new(MetricsRegistry::new());
|
||||
let monitor =
|
||||
PerformanceMonitor::with_async_collector(registry.clone(), Duration::from_millis(50));
|
||||
|
||||
// 初始化标准指标
|
||||
monitor.init_standard_metrics().await;
|
||||
|
||||
// 启动异步收集
|
||||
monitor.start_collection().await.unwrap();
|
||||
|
||||
// 模拟一些活动
|
||||
monitor
|
||||
.record_http_request("GET", 200, Duration::from_millis(150))
|
||||
.await;
|
||||
monitor
|
||||
.record_http_request("POST", 201, Duration::from_millis(300))
|
||||
.await;
|
||||
monitor
|
||||
.record_http_request("GET", 404, Duration::from_millis(50))
|
||||
.await;
|
||||
|
||||
monitor
|
||||
.record_task_processing(Duration::from_secs(5), true)
|
||||
.await;
|
||||
monitor
|
||||
.record_task_processing(Duration::from_secs(10), false)
|
||||
.await;
|
||||
|
||||
monitor.update_active_tasks(3).await;
|
||||
monitor.update_memory_usage(1024 * 1024 * 512).await; // 512MB
|
||||
monitor.update_cpu_usage(0.75).await; // 75%
|
||||
|
||||
// 等待指标收集
|
||||
sleep(Duration::from_millis(200)).await;
|
||||
|
||||
// 验证指标
|
||||
let http_counter = registry.get_counter("http_requests_total").await;
|
||||
assert!(http_counter.is_some());
|
||||
|
||||
let task_counter = registry.get_counter("tasks_processed_total").await;
|
||||
assert!(task_counter.is_some());
|
||||
assert_eq!(task_counter.unwrap().get(), 2);
|
||||
|
||||
let active_tasks_gauge = registry.get_gauge("tasks_active").await;
|
||||
assert!(active_tasks_gauge.is_some());
|
||||
assert_eq!(active_tasks_gauge.unwrap().get(), 3);
|
||||
|
||||
// 停止收集
|
||||
monitor.stop_collection();
|
||||
|
||||
// 导出指标验证
|
||||
let metrics_export = registry.export_json().await.unwrap();
|
||||
assert!(metrics_export.contains("http_requests_total"));
|
||||
assert!(metrics_export.contains("tasks_processed_total"));
|
||||
assert!(metrics_export.contains("tasks_active"));
|
||||
|
||||
println!("Performance monitoring indicators:\\n{metrics_export}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_enhanced_health_check_manager() {
|
||||
let config = HealthCheckConfig {
|
||||
check_interval: Duration::from_millis(100),
|
||||
timeout: Duration::from_millis(500),
|
||||
enable_detailed_checks: true,
|
||||
enable_system_metrics: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let registry = Arc::new(MetricsRegistry::new());
|
||||
let manager = EnhancedHealthCheckManager::new(config).with_metrics(registry.clone());
|
||||
|
||||
// 注册模拟健康检查器
|
||||
let checker1 = Arc::new(MockHealthChecker::new(
|
||||
"service1".to_string(),
|
||||
Duration::from_millis(50),
|
||||
));
|
||||
let checker2 = Arc::new(MockHealthChecker::new(
|
||||
"service2".to_string(),
|
||||
Duration::from_millis(100),
|
||||
));
|
||||
let checker3 = Arc::new(MockHealthChecker::new(
|
||||
"service3".to_string(),
|
||||
Duration::from_millis(150),
|
||||
));
|
||||
|
||||
manager.register_checker(checker1.clone()).await;
|
||||
manager.register_checker(checker2.clone()).await;
|
||||
manager.register_checker(checker3.clone()).await;
|
||||
|
||||
assert_eq!(manager.get_checker_count().await, 3);
|
||||
|
||||
// 执行健康检查
|
||||
let status = manager.check_all().await;
|
||||
assert_eq!(status.overall_status, HealthStatus::Healthy);
|
||||
assert_eq!(status.healthy_count, 3);
|
||||
assert_eq!(status.unhealthy_count, 0);
|
||||
|
||||
// 设置一个检查器失败
|
||||
checker2.set_should_fail(true);
|
||||
|
||||
let status = manager.check_all().await;
|
||||
assert_eq!(status.overall_status, HealthStatus::Unhealthy);
|
||||
assert_eq!(status.healthy_count, 2);
|
||||
assert_eq!(status.unhealthy_count, 1);
|
||||
|
||||
// 测试单个组件检查
|
||||
let component_result = manager.check_component("service1").await;
|
||||
assert!(component_result.is_some());
|
||||
assert!(component_result.unwrap().is_healthy());
|
||||
|
||||
let component_result = manager.check_component("service2").await;
|
||||
assert!(component_result.is_some());
|
||||
assert!(component_result.unwrap().is_unhealthy());
|
||||
|
||||
// 测试不存在的组件
|
||||
let component_result = manager.check_component("nonexistent").await;
|
||||
assert!(component_result.is_none());
|
||||
|
||||
// 启动定期检查
|
||||
manager.start_periodic_checks().await.unwrap();
|
||||
assert!(manager.is_running());
|
||||
|
||||
// 等待几个检查周期
|
||||
sleep(Duration::from_millis(350)).await;
|
||||
|
||||
// 获取最后的检查结果
|
||||
let last_check = manager.get_last_check().await;
|
||||
assert!(last_check.is_some());
|
||||
|
||||
let last_status = last_check.unwrap();
|
||||
assert_eq!(last_status.components.len(), 3);
|
||||
|
||||
// 停止定期检查
|
||||
manager.stop_periodic_checks();
|
||||
assert!(!manager.is_running());
|
||||
|
||||
// 验证健康检查指标
|
||||
let health_counter = registry.get_counter("health_checks_total").await;
|
||||
if let Some(counter) = health_counter {
|
||||
assert!(counter.get() > 0);
|
||||
println!("Number of health check executions: {}", counter.get());
|
||||
}
|
||||
|
||||
println!("Last health check status: {:?}", last_status.overall_status);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_health_check_timeout_handling() {
|
||||
let config = HealthCheckConfig {
|
||||
check_interval: Duration::from_millis(200),
|
||||
timeout: Duration::from_millis(100), // 短超时时间
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let manager = EnhancedHealthCheckManager::new(config);
|
||||
|
||||
// 注册一个响应慢的检查器
|
||||
let slow_checker = Arc::new(MockHealthChecker::new(
|
||||
"slow_service".to_string(),
|
||||
Duration::from_millis(200), // 超过超时时间
|
||||
));
|
||||
|
||||
manager.register_checker(slow_checker).await;
|
||||
|
||||
// 执行健康检查
|
||||
let status = manager.check_all().await;
|
||||
assert_eq!(status.overall_status, HealthStatus::Unhealthy);
|
||||
assert_eq!(status.unhealthy_count, 1);
|
||||
|
||||
// 验证超时消息
|
||||
let component = status.get_component_status("slow_service").unwrap();
|
||||
assert!(component.message.contains("timeout"));
|
||||
|
||||
println!("Timeout check result: {component:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Sets global tracing subscriber, conflicts with other tests"]
|
||||
async fn test_correlation_context_propagation() {
|
||||
let config = LoggingConfig {
|
||||
level: "info".to_string(),
|
||||
enable_correlation: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let logging_system = EnhancedLoggingSystem::init(config).unwrap();
|
||||
|
||||
// 生成关联ID
|
||||
let request_id = logging_system.generate_request_id().await;
|
||||
let trace_id = logging_system.generate_trace_id().await;
|
||||
|
||||
assert!(!request_id.is_empty());
|
||||
assert!(!trace_id.is_empty());
|
||||
|
||||
// 获取关联上下文
|
||||
let context = logging_system.get_correlation_context().await;
|
||||
assert_eq!(context.request_id, Some(request_id.clone()));
|
||||
assert_eq!(context.trace_id, Some(trace_id.clone()));
|
||||
|
||||
// 创建带有关联上下文的span
|
||||
let span = logging_system.create_span("test_correlation").await;
|
||||
let _enter = span.enter();
|
||||
|
||||
tracing::info!("Test associated context propagation");
|
||||
|
||||
// 验证关联字段
|
||||
let fields = context.to_fields();
|
||||
assert!(fields.contains_key("request_id"));
|
||||
assert!(fields.contains_key("trace_id"));
|
||||
assert_eq!(fields.get("request_id"), Some(&request_id));
|
||||
assert_eq!(fields.get("trace_id"), Some(&trace_id));
|
||||
|
||||
println!("Associated context field: {fields:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_metrics_export_formats() {
|
||||
let registry = Arc::new(MetricsRegistry::new());
|
||||
|
||||
// 创建各种类型的指标
|
||||
let counter = registry
|
||||
.register_counter(
|
||||
"export_test_counter".to_string(),
|
||||
HashMap::from([
|
||||
("service".to_string(), "test".to_string()),
|
||||
("version".to_string(), "1.0".to_string()),
|
||||
]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let gauge = registry
|
||||
.register_gauge(
|
||||
"export_test_gauge".to_string(),
|
||||
HashMap::from([("unit".to_string(), "bytes".to_string())]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let histogram = registry
|
||||
.register_histogram(
|
||||
"export_test_histogram".to_string(),
|
||||
vec![0.1, 0.5, 1.0, 2.0, 5.0, 10.0],
|
||||
HashMap::from([("operation".to_string(), "test".to_string())]),
|
||||
)
|
||||
.await;
|
||||
|
||||
let summary = registry
|
||||
.register_summary("export_test_summary".to_string(), 1000, HashMap::new())
|
||||
.await;
|
||||
|
||||
// 添加一些数据
|
||||
counter.add(42);
|
||||
gauge.set(1024);
|
||||
|
||||
histogram.observe(0.3);
|
||||
histogram.observe(1.5);
|
||||
histogram.observe(3.0);
|
||||
histogram.observe(7.0);
|
||||
|
||||
summary.observe(0.1).await;
|
||||
summary.observe(0.5).await;
|
||||
summary.observe(1.2).await;
|
||||
summary.observe(2.8).await;
|
||||
|
||||
// 测试Prometheus格式导出
|
||||
let prometheus_export = registry.export_prometheus().await;
|
||||
|
||||
// 验证Prometheus格式
|
||||
assert!(prometheus_export.contains("# TYPE export_test_counter counter"));
|
||||
assert!(prometheus_export.contains("# TYPE export_test_gauge gauge"));
|
||||
assert!(prometheus_export.contains("# TYPE export_test_histogram histogram"));
|
||||
|
||||
assert!(prometheus_export.contains("export_test_counter{service=\"test\",version=\"1.0\"} 42"));
|
||||
assert!(prometheus_export.contains("export_test_gauge{unit=\"bytes\"} 1024"));
|
||||
assert!(prometheus_export.contains("export_test_histogram_bucket"));
|
||||
assert!(prometheus_export.contains("export_test_histogram_sum"));
|
||||
assert!(prometheus_export.contains("export_test_histogram_count"));
|
||||
|
||||
// 测试JSON格式导出
|
||||
let json_export = registry.export_json().await.unwrap();
|
||||
let json_value: serde_json::Value = serde_json::from_str(&json_export).unwrap();
|
||||
|
||||
// 验证JSON结构
|
||||
assert!(json_value["counters"]["export_test_counter"].is_object());
|
||||
assert!(json_value["gauges"]["export_test_gauge"].is_object());
|
||||
assert!(json_value["histograms"]["export_test_histogram"].is_object());
|
||||
assert!(json_value["summaries"]["export_test_summary"].is_object());
|
||||
|
||||
// 验证数据值
|
||||
assert_eq!(json_value["counters"]["export_test_counter"]["value"], 42);
|
||||
assert_eq!(json_value["gauges"]["export_test_gauge"]["value"], 1024);
|
||||
assert_eq!(
|
||||
json_value["histograms"]["export_test_histogram"]["count"],
|
||||
4
|
||||
);
|
||||
|
||||
println!("Prometheus export format:\\n{prometheus_export}");
|
||||
println!("JSON export format:\\n{json_export}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "Sets global tracing subscriber, conflicts with other tests"]
|
||||
async fn test_integrated_monitoring_system() {
|
||||
// 创建完整的监控系统
|
||||
let registry = Arc::new(MetricsRegistry::new());
|
||||
|
||||
// 初始化日志系统
|
||||
let logging_config = LoggingConfig {
|
||||
level: "info".to_string(),
|
||||
enable_correlation: true,
|
||||
service_name: "integrated-test".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let logging_system = EnhancedLoggingSystem::init(logging_config).unwrap();
|
||||
|
||||
// 初始化性能监控
|
||||
let monitor =
|
||||
PerformanceMonitor::with_async_collector(registry.clone(), Duration::from_millis(50));
|
||||
monitor.init_standard_metrics().await;
|
||||
monitor.start_collection().await.unwrap();
|
||||
|
||||
// 初始化健康检查
|
||||
let health_config = HealthCheckConfig {
|
||||
check_interval: Duration::from_millis(100),
|
||||
..Default::default()
|
||||
};
|
||||
let health_manager =
|
||||
EnhancedHealthCheckManager::new(health_config).with_metrics(registry.clone());
|
||||
|
||||
// 注册健康检查器
|
||||
let checker = Arc::new(MockHealthChecker::new(
|
||||
"integrated_service".to_string(),
|
||||
Duration::from_millis(10),
|
||||
));
|
||||
health_manager.register_checker(checker).await;
|
||||
|
||||
// 启动健康检查
|
||||
health_manager.start_periodic_checks().await.unwrap();
|
||||
|
||||
// 设置关联上下文
|
||||
let request_id = logging_system.generate_request_id().await;
|
||||
let correlation = CorrelationContext::new()
|
||||
.with_request_id(request_id.clone())
|
||||
.with_task_id("integration-test-task".to_string());
|
||||
logging_system.set_correlation_context(correlation).await;
|
||||
|
||||
// 模拟一些系统活动
|
||||
let span = logging_system.create_span("integration_test").await;
|
||||
let _enter = span.enter();
|
||||
|
||||
tracing::info!("Start integration testing");
|
||||
|
||||
// 记录一些指标
|
||||
monitor
|
||||
.record_http_request("GET", 200, Duration::from_millis(100))
|
||||
.await;
|
||||
monitor
|
||||
.record_task_processing(Duration::from_secs(2), true)
|
||||
.await;
|
||||
monitor.update_active_tasks(5).await;
|
||||
|
||||
// 等待系统运行
|
||||
sleep(Duration::from_millis(300)).await;
|
||||
|
||||
tracing::info!("Integration tests running");
|
||||
|
||||
// 检查健康状态
|
||||
let health_status = health_manager.check_all().await;
|
||||
assert!(health_status.is_healthy());
|
||||
|
||||
// 获取指标
|
||||
let metrics_json = registry.export_json().await.unwrap();
|
||||
assert!(metrics_json.contains("http_requests_total"));
|
||||
assert!(metrics_json.contains("tasks_processed_total"));
|
||||
|
||||
tracing::info!("Integration testing completed");
|
||||
|
||||
// 清理
|
||||
monitor.stop_collection();
|
||||
health_manager.stop_periodic_checks();
|
||||
|
||||
println!("Integration test request ID: {request_id}");
|
||||
println!("Health status: {:?}", health_status.overall_status);
|
||||
println!(
|
||||
"Indicator summary: {} indicator types",
|
||||
serde_json::from_str::<serde_json::Value>(&metrics_json)
|
||||
.unwrap()
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.len()
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user