merge main

This commit is contained in:
houseme
2025-05-28 17:26:31 +08:00
100 changed files with 10881 additions and 2464 deletions
+177
View File
@@ -21,3 +21,180 @@ impl Default for RustFsConfig {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rustfs_config_new() {
let config = RustFsConfig::new();
// Verify that observability config is properly initialized
assert!(!config.observability.sinks.is_empty(), "Observability sinks should not be empty");
assert!(config.observability.logger.is_some(), "Logger config should be present");
// Verify that event config is properly initialized
assert!(!config.event.store_path.is_empty(), "Event store path should not be empty");
assert!(config.event.channel_capacity > 0, "Channel capacity should be positive");
assert!(!config.event.adapters.is_empty(), "Event adapters should not be empty");
}
#[test]
fn test_rustfs_config_default() {
let config = RustFsConfig::default();
// Default should be equivalent to new()
let new_config = RustFsConfig::new();
// Compare observability config
assert_eq!(config.observability.sinks.len(), new_config.observability.sinks.len());
assert_eq!(config.observability.logger.is_some(), new_config.observability.logger.is_some());
// Compare event config
assert_eq!(config.event.store_path, new_config.event.store_path);
assert_eq!(config.event.channel_capacity, new_config.event.channel_capacity);
assert_eq!(config.event.adapters.len(), new_config.event.adapters.len());
}
#[test]
fn test_rustfs_config_components_independence() {
let mut config = RustFsConfig::new();
// Modify observability config
config.observability.sinks.clear();
// Event config should remain unchanged
assert!(!config.event.adapters.is_empty(), "Event adapters should remain unchanged");
assert!(config.event.channel_capacity > 0, "Channel capacity should remain unchanged");
// Create new config to verify independence
let new_config = RustFsConfig::new();
assert!(!new_config.observability.sinks.is_empty(), "New config should have default sinks");
}
#[test]
fn test_rustfs_config_observability_integration() {
let config = RustFsConfig::new();
// Test observability config properties
assert!(config.observability.otel.endpoint.is_empty() || !config.observability.otel.endpoint.is_empty());
assert!(config.observability.otel.use_stdout.is_some());
assert!(config.observability.otel.sample_ratio.is_some());
assert!(config.observability.otel.meter_interval.is_some());
assert!(config.observability.otel.service_name.is_some());
assert!(config.observability.otel.service_version.is_some());
assert!(config.observability.otel.environment.is_some());
assert!(config.observability.otel.logger_level.is_some());
}
#[test]
fn test_rustfs_config_event_integration() {
let config = RustFsConfig::new();
// Test event config properties
assert!(!config.event.store_path.is_empty(), "Store path should not be empty");
assert!(
config.event.channel_capacity >= 1000,
"Channel capacity should be reasonable for production"
);
// Test that store path is a valid path format
let store_path = &config.event.store_path;
assert!(!store_path.contains('\0'), "Store path should not contain null characters");
// Test adapters configuration
for adapter in &config.event.adapters {
// Each adapter should have a valid configuration
match adapter {
crate::event::adapters::AdapterConfig::Webhook(_) => {
// Webhook adapter should be properly configured
}
crate::event::adapters::AdapterConfig::Kafka(_) => {
// Kafka adapter should be properly configured
}
crate::event::adapters::AdapterConfig::Mqtt(_) => {
// MQTT adapter should be properly configured
}
}
}
}
#[test]
fn test_rustfs_config_memory_usage() {
// Test that config doesn't use excessive memory
let config = RustFsConfig::new();
// Basic memory usage checks
assert!(std::mem::size_of_val(&config) < 10000, "Config should not use excessive memory");
// Test that strings are not excessively long
assert!(config.event.store_path.len() < 1000, "Store path should not be excessively long");
// Test that collections are reasonably sized
assert!(config.observability.sinks.len() < 100, "Sinks collection should be reasonably sized");
assert!(config.event.adapters.len() < 100, "Adapters collection should be reasonably sized");
}
#[test]
fn test_rustfs_config_serialization_compatibility() {
let config = RustFsConfig::new();
// Test that observability config can be serialized (it has Serialize trait)
let observability_json = serde_json::to_string(&config.observability);
assert!(observability_json.is_ok(), "Observability config should be serializable");
// Test that event config can be serialized (it has Serialize trait)
let event_json = serde_json::to_string(&config.event);
assert!(event_json.is_ok(), "Event config should be serializable");
}
#[test]
fn test_rustfs_config_debug_format() {
let config = RustFsConfig::new();
// Test that observability config has Debug trait
let observability_debug = format!("{:?}", config.observability);
assert!(!observability_debug.is_empty(), "Observability config should have debug output");
assert!(
observability_debug.contains("ObservabilityConfig"),
"Debug output should contain type name"
);
// Test that event config has Debug trait
let event_debug = format!("{:?}", config.event);
assert!(!event_debug.is_empty(), "Event config should have debug output");
assert!(event_debug.contains("NotifierConfig"), "Debug output should contain type name");
}
#[test]
fn test_rustfs_config_clone_behavior() {
let config = RustFsConfig::new();
// Test that observability config can be cloned
let observability_clone = config.observability.clone();
assert_eq!(observability_clone.sinks.len(), config.observability.sinks.len());
// Test that event config can be cloned
let event_clone = config.event.clone();
assert_eq!(event_clone.store_path, config.event.store_path);
assert_eq!(event_clone.channel_capacity, config.event.channel_capacity);
}
#[test]
fn test_rustfs_config_environment_independence() {
// Test that config creation doesn't depend on specific environment variables
// This test ensures the config can be created in any environment
let config1 = RustFsConfig::new();
let config2 = RustFsConfig::new();
// Both configs should have the same structure
assert_eq!(config1.observability.sinks.len(), config2.observability.sinks.len());
assert_eq!(config1.event.adapters.len(), config2.event.adapters.len());
// Store paths should be consistent
assert_eq!(config1.event.store_path, config2.event.store_path);
assert_eq!(config1.event.channel_capacity, config2.event.channel_capacity);
}
}
+10 -25
View File
@@ -15,8 +15,8 @@ pub const VERSION: &str = "0.0.1";
pub const DEFAULT_LOG_LEVEL: &str = "info";
/// Default configuration use stdout
/// Default value: true
pub const USE_STDOUT: bool = true;
/// Default value: false
pub const USE_STDOUT: bool = false;
/// Default configuration sample ratio
/// Default value: 1.0
@@ -84,7 +84,7 @@ pub const DEFAULT_ADDRESS: &str = concat!(":", DEFAULT_PORT);
/// Default port for rustfs console
/// This is the default port for rustfs console.
pub const DEFAULT_CONSOLE_PORT: u16 = 9002;
pub const DEFAULT_CONSOLE_PORT: u16 = 9001;
/// Default address for rustfs console
/// This is the default address for rustfs console.
@@ -98,11 +98,9 @@ mod tests {
fn test_app_basic_constants() {
// Test application basic constants
assert_eq!(APP_NAME, "RustFs");
assert!(!APP_NAME.is_empty(), "App name should not be empty");
assert!(!APP_NAME.contains(' '), "App name should not contain spaces");
assert_eq!(VERSION, "0.0.1");
assert!(!VERSION.is_empty(), "Version should not be empty");
assert_eq!(SERVICE_VERSION, "0.0.1");
assert_eq!(VERSION, SERVICE_VERSION, "Version and service version should be consistent");
@@ -117,13 +115,9 @@ mod tests {
"Log level should be a valid tracing level"
);
assert_eq!(USE_STDOUT, true);
assert_eq!(SAMPLE_RATIO, 1.0);
assert!(SAMPLE_RATIO >= 0.0 && SAMPLE_RATIO <= 1.0, "Sample ratio should be between 0.0 and 1.0");
assert_eq!(METER_INTERVAL, 30);
assert!(METER_INTERVAL > 0, "Meter interval should be positive");
}
#[test]
@@ -140,23 +134,17 @@ mod tests {
fn test_connection_constants() {
// Test connection related constants
assert_eq!(MAX_CONNECTIONS, 100);
assert!(MAX_CONNECTIONS > 0, "Max connections should be positive");
assert!(MAX_CONNECTIONS <= 10000, "Max connections should be reasonable");
assert_eq!(DEFAULT_TIMEOUT_MS, 3000);
assert!(DEFAULT_TIMEOUT_MS > 0, "Timeout should be positive");
assert!(DEFAULT_TIMEOUT_MS >= 1000, "Timeout should be at least 1 second");
}
#[test]
fn test_security_constants() {
// Test security related constants
assert_eq!(DEFAULT_ACCESS_KEY, "rustfsadmin");
assert!(!DEFAULT_ACCESS_KEY.is_empty(), "Access key should not be empty");
assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters");
assert_eq!(DEFAULT_SECRET_KEY, "rustfsadmin");
assert!(!DEFAULT_SECRET_KEY.is_empty(), "Secret key should not be empty");
assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters");
// In production environment, access key and secret key should be different
@@ -169,7 +157,6 @@ mod tests {
// Test file path related constants
assert_eq!(DEFAULT_OBS_CONFIG, "./deploy/config/obs.toml");
assert!(DEFAULT_OBS_CONFIG.ends_with(".toml"), "Config file should be TOML format");
assert!(!DEFAULT_OBS_CONFIG.is_empty(), "Config path should not be empty");
assert_eq!(RUSTFS_TLS_KEY, "rustfs_key.pem");
assert!(RUSTFS_TLS_KEY.ends_with(".pem"), "TLS key should be PEM format");
@@ -182,12 +169,8 @@ mod tests {
fn test_port_constants() {
// Test port related constants
assert_eq!(DEFAULT_PORT, 9000);
assert!(DEFAULT_PORT > 1024, "Default port should be above reserved range");
// u16 type automatically ensures port is in valid range (0-65535)
assert_eq!(DEFAULT_CONSOLE_PORT, 9002);
assert!(DEFAULT_CONSOLE_PORT > 1024, "Console port should be above reserved range");
// u16 type automatically ensures port is in valid range (0-65535)
assert_ne!(DEFAULT_PORT, DEFAULT_CONSOLE_PORT, "Main port and console port should be different");
}
@@ -256,12 +239,14 @@ mod tests {
assert!(SAMPLE_RATIO.is_finite(), "Sample ratio should be finite");
assert!(!SAMPLE_RATIO.is_nan(), "Sample ratio should not be NaN");
assert!(METER_INTERVAL < u64::MAX, "Meter interval should be reasonable");
assert!(MAX_CONNECTIONS < usize::MAX, "Max connections should be reasonable");
assert!(DEFAULT_TIMEOUT_MS < u64::MAX, "Timeout should be reasonable");
// All these are const values, so range checks are redundant
// assert!(METER_INTERVAL < u64::MAX, "Meter interval should be reasonable");
// assert!(MAX_CONNECTIONS < usize::MAX, "Max connections should be reasonable");
// assert!(DEFAULT_TIMEOUT_MS < u64::MAX, "Timeout should be reasonable");
assert!(DEFAULT_PORT != 0, "Default port should not be zero");
assert!(DEFAULT_CONSOLE_PORT != 0, "Console port should not be zero");
// These are const non-zero values, so zero checks are redundant
// assert!(DEFAULT_PORT != 0, "Default port should not be zero");
// assert!(DEFAULT_CONSOLE_PORT != 0, "Console port should not be zero");
}
#[test]
+291
View File
@@ -41,3 +41,294 @@ fn default_store_path() -> String {
fn default_channel_capacity() -> usize {
10000 // Reasonable default values for high concurrency systems
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_notifier_config_new() {
let config = NotifierConfig::new();
// Verify store path is set
assert!(!config.store_path.is_empty(), "Store path should not be empty");
assert!(
config.store_path.contains("event-notification"),
"Store path should contain event-notification"
);
// Verify channel capacity is reasonable
assert_eq!(config.channel_capacity, 10000, "Channel capacity should be 10000");
assert!(config.channel_capacity > 0, "Channel capacity should be positive");
// Verify adapters are initialized
assert!(!config.adapters.is_empty(), "Adapters should not be empty");
assert_eq!(config.adapters.len(), 1, "Should have exactly one default adapter");
}
#[test]
fn test_notifier_config_default() {
let config = NotifierConfig::default();
let new_config = NotifierConfig::new();
// Default should be equivalent to new()
assert_eq!(config.store_path, new_config.store_path);
assert_eq!(config.channel_capacity, new_config.channel_capacity);
assert_eq!(config.adapters.len(), new_config.adapters.len());
}
#[test]
fn test_default_store_path() {
let store_path = default_store_path();
// Verify store path properties
assert!(!store_path.is_empty(), "Store path should not be empty");
assert!(store_path.contains("event-notification"), "Store path should contain event-notification");
// Verify it's a valid path format
let path = Path::new(&store_path);
assert!(path.is_absolute() || path.is_relative(), "Store path should be a valid path");
// Verify it doesn't contain invalid characters
assert!(!store_path.contains('\0'), "Store path should not contain null characters");
// Verify it's based on temp directory
let temp_dir = env::temp_dir();
let expected_path = temp_dir.join("event-notification");
assert_eq!(store_path, expected_path.to_string_lossy().to_string());
}
#[test]
fn test_default_channel_capacity() {
let capacity = default_channel_capacity();
// Verify capacity is reasonable
assert_eq!(capacity, 10000, "Default capacity should be 10000");
assert!(capacity > 0, "Capacity should be positive");
assert!(capacity >= 1000, "Capacity should be at least 1000 for production use");
assert!(capacity <= 1_000_000, "Capacity should not be excessively large");
}
#[test]
fn test_notifier_config_serialization() {
let config = NotifierConfig::new();
// Test serialization to JSON
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config should be serializable to JSON");
let json_str = json_result.unwrap();
assert!(!json_str.is_empty(), "Serialized JSON should not be empty");
assert!(json_str.contains("store_path"), "JSON should contain store_path");
assert!(json_str.contains("channel_capacity"), "JSON should contain channel_capacity");
assert!(json_str.contains("adapters"), "JSON should contain adapters");
// Test deserialization from JSON
let deserialized_result: Result<NotifierConfig, _> = serde_json::from_str(&json_str);
assert!(deserialized_result.is_ok(), "Config should be deserializable from JSON");
let deserialized_config = deserialized_result.unwrap();
assert_eq!(deserialized_config.store_path, config.store_path);
assert_eq!(deserialized_config.channel_capacity, config.channel_capacity);
assert_eq!(deserialized_config.adapters.len(), config.adapters.len());
}
#[test]
fn test_notifier_config_serialization_with_defaults() {
// Test serialization with minimal JSON (using serde defaults)
let minimal_json = r#"{"adapters": []}"#;
let deserialized_result: Result<NotifierConfig, _> = serde_json::from_str(minimal_json);
assert!(deserialized_result.is_ok(), "Config should deserialize with defaults");
let config = deserialized_result.unwrap();
assert_eq!(config.store_path, default_store_path(), "Should use default store path");
assert_eq!(config.channel_capacity, default_channel_capacity(), "Should use default channel capacity");
assert!(config.adapters.is_empty(), "Should have empty adapters as specified");
}
#[test]
fn test_notifier_config_debug_format() {
let config = NotifierConfig::new();
let debug_str = format!("{:?}", config);
assert!(!debug_str.is_empty(), "Debug output should not be empty");
assert!(debug_str.contains("NotifierConfig"), "Debug output should contain struct name");
assert!(debug_str.contains("store_path"), "Debug output should contain store_path field");
assert!(
debug_str.contains("channel_capacity"),
"Debug output should contain channel_capacity field"
);
assert!(debug_str.contains("adapters"), "Debug output should contain adapters field");
}
#[test]
fn test_notifier_config_clone() {
let config = NotifierConfig::new();
let cloned_config = config.clone();
// Test that clone creates an independent copy
assert_eq!(cloned_config.store_path, config.store_path);
assert_eq!(cloned_config.channel_capacity, config.channel_capacity);
assert_eq!(cloned_config.adapters.len(), config.adapters.len());
// Verify they are independent (modifying one doesn't affect the other)
let mut modified_config = config.clone();
modified_config.channel_capacity = 5000;
assert_ne!(modified_config.channel_capacity, config.channel_capacity);
assert_eq!(cloned_config.channel_capacity, config.channel_capacity);
}
#[test]
fn test_notifier_config_modification() {
let mut config = NotifierConfig::new();
// Test modifying store path
let original_store_path = config.store_path.clone();
config.store_path = "/custom/path".to_string();
assert_ne!(config.store_path, original_store_path);
assert_eq!(config.store_path, "/custom/path");
// Test modifying channel capacity
let original_capacity = config.channel_capacity;
config.channel_capacity = 5000;
assert_ne!(config.channel_capacity, original_capacity);
assert_eq!(config.channel_capacity, 5000);
// Test modifying adapters
let original_adapters_len = config.adapters.len();
config.adapters.push(AdapterConfig::new());
assert_eq!(config.adapters.len(), original_adapters_len + 1);
// Test clearing adapters
config.adapters.clear();
assert!(config.adapters.is_empty());
}
#[test]
fn test_notifier_config_adapters() {
let config = NotifierConfig::new();
// Test default adapter configuration
assert_eq!(config.adapters.len(), 1, "Should have exactly one default adapter");
// Test that we can add more adapters
let mut config_mut = config.clone();
config_mut.adapters.push(AdapterConfig::new());
assert_eq!(config_mut.adapters.len(), 2, "Should be able to add more adapters");
// Test adapter types
for adapter in &config.adapters {
match adapter {
AdapterConfig::Webhook(_) => {
// Webhook adapter should be properly configured
}
AdapterConfig::Kafka(_) => {
// Kafka adapter should be properly configured
}
AdapterConfig::Mqtt(_) => {
// MQTT adapter should be properly configured
}
}
}
}
#[test]
fn test_notifier_config_edge_cases() {
// Test with empty adapters
let mut config = NotifierConfig::new();
config.adapters.clear();
assert!(config.adapters.is_empty(), "Adapters should be empty after clearing");
// Test serialization with empty adapters
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config with empty adapters should be serializable");
// Test with very large channel capacity
config.channel_capacity = 1_000_000;
assert_eq!(config.channel_capacity, 1_000_000);
// Test with minimum channel capacity
config.channel_capacity = 1;
assert_eq!(config.channel_capacity, 1);
// Test with empty store path
config.store_path = String::new();
assert!(config.store_path.is_empty());
}
#[test]
fn test_notifier_config_memory_efficiency() {
let config = NotifierConfig::new();
// Test that config doesn't use excessive memory
let config_size = std::mem::size_of_val(&config);
assert!(config_size < 5000, "Config should not use excessive memory");
// Test that store path is not excessively long
assert!(config.store_path.len() < 1000, "Store path should not be excessively long");
// Test that adapters collection is reasonably sized
assert!(config.adapters.len() < 100, "Adapters collection should be reasonably sized");
}
#[test]
fn test_notifier_config_consistency() {
// Create multiple configs and ensure they're consistent
let config1 = NotifierConfig::new();
let config2 = NotifierConfig::new();
// Both configs should have the same default values
assert_eq!(config1.store_path, config2.store_path);
assert_eq!(config1.channel_capacity, config2.channel_capacity);
assert_eq!(config1.adapters.len(), config2.adapters.len());
}
#[test]
fn test_notifier_config_path_validation() {
let config = NotifierConfig::new();
// Test that store path is a valid path
let path = Path::new(&config.store_path);
// Path should be valid
assert!(path.components().count() > 0, "Path should have components");
// Path should not contain invalid characters for most filesystems
assert!(!config.store_path.contains('\0'), "Path should not contain null characters");
assert!(!config.store_path.contains('\x01'), "Path should not contain control characters");
// Path should be reasonable length
assert!(config.store_path.len() < 260, "Path should be shorter than Windows MAX_PATH");
}
#[test]
fn test_notifier_config_production_readiness() {
let config = NotifierConfig::new();
// Test production readiness criteria
assert!(config.channel_capacity >= 1000, "Channel capacity should be sufficient for production");
assert!(!config.store_path.is_empty(), "Store path should be configured");
assert!(!config.adapters.is_empty(), "At least one adapter should be configured");
// Test that configuration is reasonable for high-load scenarios
assert!(config.channel_capacity <= 10_000_000, "Channel capacity should not be excessive");
// Test that store path is in a reasonable location (temp directory)
assert!(config.store_path.contains("event-notification"), "Store path should be identifiable");
}
#[test]
fn test_default_config_file_constant() {
// Test that the constant is properly defined
assert_eq!(DEFAULT_CONFIG_FILE, "event");
// DEFAULT_CONFIG_FILE is a const, so is_empty() check is redundant
// assert!(!DEFAULT_CONFIG_FILE.is_empty(), "Config file name should not be empty");
assert!(!DEFAULT_CONFIG_FILE.contains('/'), "Config file name should not contain path separators");
assert!(
!DEFAULT_CONFIG_FILE.contains('\\'),
"Config file name should not contain Windows path separators"
);
}
}
+248
View File
@@ -26,3 +26,251 @@ impl Default for ObservabilityConfig {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_observability_config_new() {
let config = ObservabilityConfig::new();
// Verify OTEL config is initialized
assert!(config.otel.use_stdout.is_some(), "OTEL use_stdout should be configured");
assert!(config.otel.sample_ratio.is_some(), "OTEL sample_ratio should be configured");
assert!(config.otel.meter_interval.is_some(), "OTEL meter_interval should be configured");
assert!(config.otel.service_name.is_some(), "OTEL service_name should be configured");
assert!(config.otel.service_version.is_some(), "OTEL service_version should be configured");
assert!(config.otel.environment.is_some(), "OTEL environment should be configured");
assert!(config.otel.logger_level.is_some(), "OTEL logger_level should be configured");
// Verify sinks are initialized
assert!(!config.sinks.is_empty(), "Sinks should not be empty");
assert_eq!(config.sinks.len(), 1, "Should have exactly one default sink");
// Verify logger is initialized
assert!(config.logger.is_some(), "Logger should be configured");
}
#[test]
fn test_observability_config_default() {
let config = ObservabilityConfig::default();
let new_config = ObservabilityConfig::new();
// Default should be equivalent to new()
assert_eq!(config.sinks.len(), new_config.sinks.len());
assert_eq!(config.logger.is_some(), new_config.logger.is_some());
// OTEL configs should be equivalent
assert_eq!(config.otel.use_stdout, new_config.otel.use_stdout);
assert_eq!(config.otel.sample_ratio, new_config.otel.sample_ratio);
assert_eq!(config.otel.meter_interval, new_config.otel.meter_interval);
assert_eq!(config.otel.service_name, new_config.otel.service_name);
assert_eq!(config.otel.service_version, new_config.otel.service_version);
assert_eq!(config.otel.environment, new_config.otel.environment);
assert_eq!(config.otel.logger_level, new_config.otel.logger_level);
}
#[test]
fn test_observability_config_otel_defaults() {
let config = ObservabilityConfig::new();
// Test OTEL default values
if let Some(_use_stdout) = config.otel.use_stdout {
// Test boolean values - any boolean value is valid
}
if let Some(sample_ratio) = config.otel.sample_ratio {
assert!((0.0..=1.0).contains(&sample_ratio), "Sample ratio should be between 0.0 and 1.0");
}
if let Some(meter_interval) = config.otel.meter_interval {
assert!(meter_interval > 0, "Meter interval should be positive");
assert!(meter_interval <= 3600, "Meter interval should be reasonable (≤ 1 hour)");
}
if let Some(service_name) = &config.otel.service_name {
assert!(!service_name.is_empty(), "Service name should not be empty");
assert!(!service_name.contains(' '), "Service name should not contain spaces");
}
if let Some(service_version) = &config.otel.service_version {
assert!(!service_version.is_empty(), "Service version should not be empty");
}
if let Some(environment) = &config.otel.environment {
assert!(!environment.is_empty(), "Environment should not be empty");
assert!(
["development", "staging", "production", "test"].contains(&environment.as_str()),
"Environment should be a standard environment name"
);
}
if let Some(logger_level) = &config.otel.logger_level {
assert!(
["trace", "debug", "info", "warn", "error"].contains(&logger_level.as_str()),
"Logger level should be a valid tracing level"
);
}
}
#[test]
fn test_observability_config_sinks() {
let config = ObservabilityConfig::new();
// Test default sink configuration
assert_eq!(config.sinks.len(), 1, "Should have exactly one default sink");
let _default_sink = &config.sinks[0];
// Test that the sink has valid configuration
// Note: We can't test specific values without knowing SinkConfig implementation
// but we can test that it's properly initialized
// Test that we can add more sinks
let mut config_mut = config.clone();
config_mut.sinks.push(SinkConfig::new());
assert_eq!(config_mut.sinks.len(), 2, "Should be able to add more sinks");
}
#[test]
fn test_observability_config_logger() {
let config = ObservabilityConfig::new();
// Test logger configuration
assert!(config.logger.is_some(), "Logger should be configured by default");
if let Some(_logger) = &config.logger {
// Test that logger has valid configuration
// Note: We can't test specific values without knowing LoggerConfig implementation
// but we can test that it's properly initialized
}
// Test that logger can be disabled
let mut config_mut = config.clone();
config_mut.logger = None;
assert!(config_mut.logger.is_none(), "Logger should be able to be disabled");
}
#[test]
fn test_observability_config_serialization() {
let config = ObservabilityConfig::new();
// Test serialization to JSON
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config should be serializable to JSON");
let json_str = json_result.unwrap();
assert!(!json_str.is_empty(), "Serialized JSON should not be empty");
assert!(json_str.contains("otel"), "JSON should contain otel configuration");
assert!(json_str.contains("sinks"), "JSON should contain sinks configuration");
assert!(json_str.contains("logger"), "JSON should contain logger configuration");
// Test deserialization from JSON
let deserialized_result: Result<ObservabilityConfig, _> = serde_json::from_str(&json_str);
assert!(deserialized_result.is_ok(), "Config should be deserializable from JSON");
let deserialized_config = deserialized_result.unwrap();
assert_eq!(deserialized_config.sinks.len(), config.sinks.len());
assert_eq!(deserialized_config.logger.is_some(), config.logger.is_some());
}
#[test]
fn test_observability_config_debug_format() {
let config = ObservabilityConfig::new();
let debug_str = format!("{:?}", config);
assert!(!debug_str.is_empty(), "Debug output should not be empty");
assert!(debug_str.contains("ObservabilityConfig"), "Debug output should contain struct name");
assert!(debug_str.contains("otel"), "Debug output should contain otel field");
assert!(debug_str.contains("sinks"), "Debug output should contain sinks field");
assert!(debug_str.contains("logger"), "Debug output should contain logger field");
}
#[test]
fn test_observability_config_clone() {
let config = ObservabilityConfig::new();
let cloned_config = config.clone();
// Test that clone creates an independent copy
assert_eq!(cloned_config.sinks.len(), config.sinks.len());
assert_eq!(cloned_config.logger.is_some(), config.logger.is_some());
assert_eq!(cloned_config.otel.endpoint, config.otel.endpoint);
assert_eq!(cloned_config.otel.use_stdout, config.otel.use_stdout);
assert_eq!(cloned_config.otel.sample_ratio, config.otel.sample_ratio);
assert_eq!(cloned_config.otel.meter_interval, config.otel.meter_interval);
assert_eq!(cloned_config.otel.service_name, config.otel.service_name);
assert_eq!(cloned_config.otel.service_version, config.otel.service_version);
assert_eq!(cloned_config.otel.environment, config.otel.environment);
assert_eq!(cloned_config.otel.logger_level, config.otel.logger_level);
}
#[test]
fn test_observability_config_modification() {
let mut config = ObservabilityConfig::new();
// Test modifying OTEL endpoint
let original_endpoint = config.otel.endpoint.clone();
config.otel.endpoint = "http://localhost:4317".to_string();
assert_ne!(config.otel.endpoint, original_endpoint);
assert_eq!(config.otel.endpoint, "http://localhost:4317");
// Test modifying sinks
let original_sinks_len = config.sinks.len();
config.sinks.push(SinkConfig::new());
assert_eq!(config.sinks.len(), original_sinks_len + 1);
// Test disabling logger
config.logger = None;
assert!(config.logger.is_none());
}
#[test]
fn test_observability_config_edge_cases() {
// Test with empty sinks
let mut config = ObservabilityConfig::new();
config.sinks.clear();
assert!(config.sinks.is_empty(), "Sinks should be empty after clearing");
// Test serialization with empty sinks
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config with empty sinks should be serializable");
// Test with no logger
config.logger = None;
let json_result = serde_json::to_string(&config);
assert!(json_result.is_ok(), "Config with no logger should be serializable");
}
#[test]
fn test_observability_config_memory_efficiency() {
let config = ObservabilityConfig::new();
// Test that config doesn't use excessive memory
let config_size = std::mem::size_of_val(&config);
assert!(config_size < 5000, "Config should not use excessive memory");
// Test that endpoint string is not excessively long
assert!(config.otel.endpoint.len() < 1000, "Endpoint should not be excessively long");
// Test that collections are reasonably sized
assert!(config.sinks.len() < 100, "Sinks collection should be reasonably sized");
}
#[test]
fn test_observability_config_consistency() {
// Create multiple configs and ensure they're consistent
let config1 = ObservabilityConfig::new();
let config2 = ObservabilityConfig::new();
// Both configs should have the same default structure
assert_eq!(config1.sinks.len(), config2.sinks.len());
assert_eq!(config1.logger.is_some(), config2.logger.is_some());
assert_eq!(config1.otel.use_stdout, config2.otel.use_stdout);
assert_eq!(config1.otel.sample_ratio, config2.otel.sample_ratio);
assert_eq!(config1.otel.meter_interval, config2.otel.meter_interval);
assert_eq!(config1.otel.service_name, config2.otel.service_name);
assert_eq!(config1.otel.service_version, config2.otel.service_version);
assert_eq!(config1.otel.environment, config2.otel.environment);
assert_eq!(config1.otel.logger_level, config2.otel.logger_level);
}
}
+9 -9
View File
@@ -288,10 +288,10 @@ mod tests {
use std::mem;
let size = mem::size_of::<Error>();
// 错误类型应该相对紧凑,考虑到包含多种错误类型,96字节是合理的
// 错误类型应该相对紧凑,考虑到包含多种错误类型,96 字节是合理的
assert!(size <= 128, "Error size should be reasonable, got {} bytes", size);
// 测试Option<Error>的大小
// 测试 Option<Error>的大小
let option_size = mem::size_of::<Option<Error>>();
assert!(option_size <= 136, "Option<Error> should be efficient, got {} bytes", option_size);
}
@@ -323,7 +323,7 @@ mod tests {
_ => panic!("Expected Custom error variant"),
}
// 测试包含Unicode字符的消息
// 测试包含 Unicode 字符的消息
let unicode_error = Error::custom("🚀 Unicode test 测试 🎉");
match unicode_error {
Error::Custom(msg) => assert!(msg.contains('🚀')),
@@ -345,7 +345,7 @@ mod tests {
#[test]
fn test_error_downcast() {
// 测试错误的向下转型
let io_error = io::Error::new(io::ErrorKind::Other, "test error");
let io_error = io::Error::other("test error");
let converted: Error = io_error.into();
// 验证可以获取源错误
@@ -360,7 +360,7 @@ mod tests {
#[test]
fn test_error_chain_depth() {
// 测试错误链的深度
let root_cause = io::Error::new(io::ErrorKind::Other, "root cause");
let root_cause = io::Error::other("root cause");
let converted: Error = root_cause.into();
let mut depth = 0;
@@ -407,14 +407,14 @@ mod tests {
let display_str = error.to_string();
let debug_str = format!("{:?}", error);
// DisplayDebug都不应该为空
// DisplayDebug 都不应该为空
assert!(!display_str.is_empty());
assert!(!debug_str.is_empty());
// Debug输出通常包含更多信息,但不是绝对的
// Debug 输出通常包含更多信息,但不是绝对的
// 这里我们只验证两者都有内容即可
assert!(debug_str.len() > 0);
assert!(display_str.len() > 0);
assert!(!debug_str.is_empty());
assert!(!display_str.is_empty());
}
}
}
+31 -40
View File
@@ -1,5 +1,6 @@
use std::cell::OnceCell;
use crate::{create_adapters, Error, Event, NotifierConfig, NotifierSystem};
use std::sync::{atomic, Arc};
use std::sync::{atomic, Arc, Mutex};
use tokio::sync::{Mutex, OnceCell};
use tracing::instrument;
@@ -173,15 +174,18 @@ async fn get_system() -> Result<Arc<Mutex<NotifierSystem>>, Error> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{AdapterCommon, AdapterConfig, NotifierConfig, WebhookConfig};
use std::collections::HashMap;
use crate::NotifierConfig;
fn init_tracing() {
// Use try_init to avoid panic if already initialized
let _ = tracing_subscriber::fmt::try_init();
}
#[tokio::test]
async fn test_initialize_success() {
tracing_subscriber::fmt::init();
init_tracing();
let config = NotifierConfig::default(); // assume there is a default configuration
let result = initialize(&config).await;
let result = initialize(config).await;
assert!(result.is_err(), "Initialization should not succeed");
assert!(!is_initialized(), "System should not be marked as initialized");
assert!(!is_ready(), "System should not be marked as ready");
@@ -189,56 +193,43 @@ mod tests {
#[tokio::test]
async fn test_initialize_twice() {
tracing_subscriber::fmt::init();
init_tracing();
let config = NotifierConfig::default();
let _ = initialize(&config.clone()).await; // first initialization
let result = initialize(&config).await; // second initialization
let _ = initialize(config.clone()).await; // first initialization
let result = initialize(config).await; // second initialization
assert!(result.is_err(), "Initialization should succeed");
assert!(result.is_err(), "Re-initialization should fail");
}
#[tokio::test]
async fn test_initialize_failure_resets_state() {
tracing_subscriber::fmt::init();
// simulate wrong configuration
init_tracing();
// Test with empty adapters to force failure
let config = NotifierConfig {
adapters: vec![
// assuming that the empty adapter will cause failure
AdapterConfig::Webhook(WebhookConfig {
common: AdapterCommon {
identifier: "empty".to_string(),
comment: "empty".to_string(),
enable: true,
queue_dir: "".to_string(),
queue_limit: 10,
},
endpoint: "http://localhost:8080/webhook".to_string(),
auth_token: Some("secret-token".to_string()),
custom_headers: Some(HashMap::from([("X-Custom".to_string(), "value".to_string())])),
max_retries: 3,
timeout: Some(10),
retry_interval: Some(5),
client_cert: None,
client_key: None,
}),
], // assuming that the empty adapter will cause failure
adapters: Vec::new(),
..Default::default()
};
let result = initialize(&config).await;
assert!(result.is_ok(), "Initialization with invalid config should fail");
assert!(is_initialized(), "System should not be marked as initialized after failure");
assert!(is_ready(), "System should not be marked as ready after failure");
let result = initialize(config).await;
assert!(result.is_err(), "Initialization should fail with empty adapters");
assert!(!is_initialized(), "System should not be marked as initialized after failure");
assert!(!is_ready(), "System should not be marked as ready after failure");
}
#[tokio::test]
async fn test_is_initialized_and_is_ready() {
tracing_subscriber::fmt::init();
init_tracing();
// Initially, the system should not be initialized or ready
assert!(!is_initialized(), "System should not be initialized initially");
assert!(!is_ready(), "System should not be ready initially");
let config = NotifierConfig::default();
let _ = initialize(&config).await;
assert!(!is_initialized(), "System should be initialized after successful initialization");
assert!(!is_ready(), "System should be ready after successful initialization");
// Test with empty adapters to ensure failure
let config = NotifierConfig {
adapters: Vec::new(),
..Default::default()
};
let result = initialize(config).await;
assert!(result.is_err(), "Initialization should fail with empty adapters");
assert!(!is_initialized(), "System should not be initialized after failed init");
assert!(!is_ready(), "System should not be ready after failed init");
}
}
+2 -2
View File
@@ -44,9 +44,9 @@ pub enum GlobalError {
/// ```rust
/// use rustfs_obs::{init_telemetry, load_config, set_global_guard};
///
/// async fn init() -> Result<(), Box<dyn std::error::Error>> {
/// fn init() -> Result<(), Box<dyn std::error::Error>> {
/// let config = load_config(None);
/// let guard = init_telemetry(&config.observability).await?;
/// let guard = init_telemetry(&config.observability);
/// set_global_guard(guard)?;
/// Ok(())
/// }
+11 -5
View File
@@ -22,11 +22,14 @@
///
/// ## Usage
///
/// ```rust
/// ```no_run
/// use rustfs_obs::{AppConfig, init_obs};
///
/// # #[tokio::main]
/// # async fn main() {
/// let config = AppConfig::default();
/// let (logger, guard) = init_obs(config);
/// let (logger, guard) = init_obs(config).await;
/// # }
/// ```
mod config;
mod entry;
@@ -67,11 +70,14 @@ pub use metrics::request::*;
/// A tuple containing the logger and the telemetry guard
///
/// # Example
/// ```
/// ```no_run
/// use rustfs_obs::{AppConfig, init_obs};
///
/// # #[tokio::main]
/// # async fn main() {
/// let config = AppConfig::default();
/// let (logger, guard) = init_obs(config);
/// let (logger, guard) = init_obs(config).await;
/// # }
/// ```
pub async fn init_obs(config: AppConfig) -> (Arc<Mutex<Logger>>, telemetry::OtelGuard) {
let guard = init_telemetry(&config.observability);
@@ -100,7 +106,7 @@ pub async fn init_obs(config: AppConfig) -> (Arc<Mutex<Logger>>, telemetry::Otel
/// A reference to the global logger instance
///
/// # Example
/// ```
/// ```no_run
/// use rustfs_obs::get_logger;
///
/// let logger = get_logger();
+8 -8
View File
@@ -224,7 +224,7 @@ impl Logger {
/// # Returns
/// The global logger instance
/// # Example
/// ```
/// ```no_run
/// use rustfs_obs::{AppConfig, start_logger};
///
/// let config = AppConfig::default();
@@ -270,7 +270,7 @@ pub async fn init_global_logger(config: &AppConfig, sinks: Vec<Arc<dyn Sink>>) -
/// A reference to the global logger instance
///
/// # Example
/// ```
/// ```no_run
/// use rustfs_obs::get_global_logger;
///
/// let logger = get_global_logger();
@@ -290,7 +290,7 @@ pub fn get_global_logger() -> &'static Arc<Mutex<Logger>> {
/// Result indicating whether the operation was successful
///
/// # Example
/// ```
/// ```no_run
/// use rustfs_obs::log_info;
///
/// async fn example() {
@@ -309,7 +309,7 @@ pub async fn log_info(message: &str, source: &str) -> Result<(), GlobalError> {
/// # Returns
/// Result indicating whether the operation was successful
/// # Example
/// ```
/// ```no_run
/// use rustfs_obs::log_error;
///
/// async fn example() {
@@ -328,7 +328,7 @@ pub async fn log_error(message: &str, source: &str) -> Result<(), GlobalError> {
/// Result indicating whether the operation was successful
///
/// # Example
/// ```
/// ```no_run
/// use rustfs_obs::log_warn;
///
/// async fn example() {
@@ -348,7 +348,7 @@ pub async fn log_warn(message: &str, source: &str) -> Result<(), GlobalError> {
/// Result indicating whether the operation was successful
///
/// # Example
/// ```
/// ```no_run
/// use rustfs_obs::log_debug;
///
/// async fn example() {
@@ -369,7 +369,7 @@ pub async fn log_debug(message: &str, source: &str) -> Result<(), GlobalError> {
/// Result indicating whether the operation was successful
///
/// # Example
/// ```
/// ```no_run
/// use rustfs_obs::log_trace;
///
/// async fn example() {
@@ -392,7 +392,7 @@ pub async fn log_trace(message: &str, source: &str) -> Result<(), GlobalError> {
/// # Returns
/// Result indicating whether the operation was successful
/// # Example
/// ```
/// ```no_run
/// use tracing_core::Level;
/// use rustfs_obs::log_with_context;
///
+3
View File
@@ -14,6 +14,9 @@ rustls-pemfile = { workspace = true, optional = true }
rustls-pki-types = { workspace = true, optional = true }
tracing = { workspace = true }
[dev-dependencies]
tempfile = { workspace = true }
[lints]
workspace = true
+270
View File
@@ -184,3 +184,273 @@ pub fn create_multi_cert_resolver(
default_cert,
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_certs_error_function() {
let error_msg = "Test error message";
let error = certs_error(error_msg.to_string());
assert_eq!(error.kind(), std::io::ErrorKind::Other);
assert_eq!(error.to_string(), error_msg);
}
#[test]
fn test_load_certs_file_not_found() {
let result = load_certs("non_existent_file.pem");
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.kind(), std::io::ErrorKind::Other);
assert!(error.to_string().contains("failed to open"));
}
#[test]
fn test_load_private_key_file_not_found() {
let result = load_private_key("non_existent_key.pem");
assert!(result.is_err());
let error = result.unwrap_err();
assert_eq!(error.kind(), std::io::ErrorKind::Other);
assert!(error.to_string().contains("failed to open"));
}
#[test]
fn test_load_certs_empty_file() {
let temp_dir = TempDir::new().unwrap();
let cert_path = temp_dir.path().join("empty.pem");
fs::write(&cert_path, "").unwrap();
let result = load_certs(cert_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("No valid certificate was found"));
}
#[test]
fn test_load_certs_invalid_format() {
let temp_dir = TempDir::new().unwrap();
let cert_path = temp_dir.path().join("invalid.pem");
fs::write(&cert_path, "invalid certificate content").unwrap();
let result = load_certs(cert_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("No valid certificate was found"));
}
#[test]
fn test_load_private_key_empty_file() {
let temp_dir = TempDir::new().unwrap();
let key_path = temp_dir.path().join("empty_key.pem");
fs::write(&key_path, "").unwrap();
let result = load_private_key(key_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("no private key found"));
}
#[test]
fn test_load_private_key_invalid_format() {
let temp_dir = TempDir::new().unwrap();
let key_path = temp_dir.path().join("invalid_key.pem");
fs::write(&key_path, "invalid private key content").unwrap();
let result = load_private_key(key_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("no private key found"));
}
#[test]
fn test_load_all_certs_from_directory_not_exists() {
let result = load_all_certs_from_directory("/non/existent/directory");
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("does not exist or is not a directory"));
}
#[test]
fn test_load_all_certs_from_directory_empty() {
let temp_dir = TempDir::new().unwrap();
let result = load_all_certs_from_directory(temp_dir.path().to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("No valid certificate/private key pair found"));
}
#[test]
fn test_load_all_certs_from_directory_file_instead_of_dir() {
let temp_dir = TempDir::new().unwrap();
let file_path = temp_dir.path().join("not_a_directory.txt");
fs::write(&file_path, "content").unwrap();
let result = load_all_certs_from_directory(file_path.to_str().unwrap());
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("does not exist or is not a directory"));
}
#[test]
fn test_load_cert_key_pair_missing_cert() {
let temp_dir = TempDir::new().unwrap();
let key_path = temp_dir.path().join("test_key.pem");
fs::write(&key_path, "dummy key content").unwrap();
let result = load_cert_key_pair("non_existent_cert.pem", key_path.to_str().unwrap());
assert!(result.is_err());
}
#[test]
fn test_load_cert_key_pair_missing_key() {
let temp_dir = TempDir::new().unwrap();
let cert_path = temp_dir.path().join("test_cert.pem");
fs::write(&cert_path, "dummy cert content").unwrap();
let result = load_cert_key_pair(cert_path.to_str().unwrap(), "non_existent_key.pem");
assert!(result.is_err());
}
#[test]
fn test_create_multi_cert_resolver_empty_map() {
let empty_map = HashMap::new();
let result = create_multi_cert_resolver(empty_map);
// Should succeed even with empty map
assert!(result.is_ok());
}
#[test]
fn test_error_message_formatting() {
let test_cases = vec![
("file not found", "failed to open test.pem: file not found"),
("permission denied", "failed to open key.pem: permission denied"),
("invalid format", "certificate file cert.pem format error:invalid format"),
];
for (input, _expected_pattern) in test_cases {
let error1 = certs_error(format!("failed to open test.pem: {}", input));
assert!(error1.to_string().contains(input));
let error2 = certs_error(format!("failed to open key.pem: {}", input));
assert!(error2.to_string().contains(input));
}
}
#[test]
fn test_path_handling_edge_cases() {
// Test with various path formats
let path_cases = vec![
"", // Empty path
".", // Current directory
"..", // Parent directory
"/", // Root directory (Unix)
"relative/path", // Relative path
"/absolute/path", // Absolute path
];
for path in path_cases {
let result = load_all_certs_from_directory(path);
// All should fail since these are not valid cert directories
assert!(result.is_err());
}
}
#[test]
fn test_filename_constants_consistency() {
// Test that the constants match expected values
assert_eq!(RUSTFS_TLS_CERT, "rustfs_cert.pem");
assert_eq!(RUSTFS_TLS_KEY, "rustfs_key.pem");
// Test that constants are not empty
assert!(!RUSTFS_TLS_CERT.is_empty());
assert!(!RUSTFS_TLS_KEY.is_empty());
// Test that constants have proper extensions
assert!(RUSTFS_TLS_CERT.ends_with(".pem"));
assert!(RUSTFS_TLS_KEY.ends_with(".pem"));
}
#[test]
fn test_directory_structure_validation() {
let temp_dir = TempDir::new().unwrap();
// Create a subdirectory without certificates
let sub_dir = temp_dir.path().join("example.com");
fs::create_dir(&sub_dir).unwrap();
// Should fail because no certificates found
let result = load_all_certs_from_directory(temp_dir.path().to_str().unwrap());
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("No valid certificate/private key pair found"));
}
#[test]
fn test_unicode_path_handling() {
let temp_dir = TempDir::new().unwrap();
// Create directory with Unicode characters
let unicode_dir = temp_dir.path().join("测试目录");
fs::create_dir(&unicode_dir).unwrap();
let result = load_all_certs_from_directory(unicode_dir.to_str().unwrap());
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("No valid certificate/private key pair found"));
}
#[test]
fn test_concurrent_access_safety() {
use std::sync::Arc;
use std::thread;
let temp_dir = TempDir::new().unwrap();
let dir_path = Arc::new(temp_dir.path().to_string_lossy().to_string());
let handles: Vec<_> = (0..5)
.map(|_| {
let path = Arc::clone(&dir_path);
thread::spawn(move || {
let result = load_all_certs_from_directory(&path);
// All should fail since directory is empty
assert!(result.is_err());
})
})
.collect();
for handle in handles {
handle.join().expect("Thread should complete successfully");
}
}
#[test]
fn test_memory_efficiency() {
// Test that error types are reasonably sized
use std::mem;
let error = certs_error("test".to_string());
let error_size = mem::size_of_val(&error);
// Error should not be excessively large
assert!(error_size < 1024, "Error size should be reasonable, got {} bytes", error_size);
}
}
+10 -16
View File
@@ -21,20 +21,15 @@ pub enum CompressionFormat {
Unknown,
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CompressionLevel {
Fastest,
Best,
#[default]
Default,
Level(u32),
}
impl Default for CompressionLevel {
fn default() -> Self {
CompressionLevel::Default
}
}
impl CompressionFormat {
/// Identify compression format from file extension
pub fn from_extension(ext: &str) -> Self {
@@ -679,7 +674,7 @@ mod tests {
async move {
if invocation_number == 0 {
// First invocation returns an error
Err(io::Error::new(io::ErrorKind::Other, "Simulated callback error"))
Err(io::Error::other("Simulated callback error"))
} else {
Ok(())
}
@@ -716,7 +711,7 @@ mod tests {
#[test]
fn test_compression_format_clone_and_copy() {
// 测试CompressionFormat是否可以被复制
// 测试 CompressionFormat 是否可以被复制
let format = CompressionFormat::Gzip;
let format_copy = format;
@@ -729,7 +724,7 @@ mod tests {
#[test]
fn test_compression_format_match_exhaustiveness() {
// 测试match语句的完整性
// 测试 match 语句的完整性
fn handle_format(format: CompressionFormat) -> &'static str {
match format {
CompressionFormat::Gzip => "gzip",
@@ -765,8 +760,7 @@ mod tests {
}
}
// 如果能执行到这里,说明性能是可接受的
assert!(true, "Extension parsing performance test completed");
// Extension parsing performance test completed
}
#[test]
@@ -906,7 +900,7 @@ mod tests {
#[test]
fn test_zip_entry_creation() {
// 测试ZIP条目信息创建
// 测试 ZIP 条目信息创建
let entry = ZipEntry {
name: "test.txt".to_string(),
size: 1024,
@@ -934,7 +928,7 @@ mod tests {
];
for level in levels {
// 验证每个级别都有对应的Debug实现
// 验证每个级别都有对应的 Debug 实现
let _debug_str = format!("{:?}", level);
}
}
@@ -960,7 +954,7 @@ mod tests {
// 验证支持状态检查
let _supported = format.is_supported();
// 验证Debug实现
// 验证 Debug 实现
let _debug = format!("{:?}", format);
}
}
@@ -991,7 +985,7 @@ mod tests {
// .await
// {
// Ok(_) => println!("解压成功!"),
// Err(e) => println!("解压失败: {}", e),
// Err(e) => println!("解压失败{}", e),
// }
// Ok(())