diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..86146eaf --- /dev/null +++ b/.cursorrules @@ -0,0 +1,333 @@ +# RustFS 项目 Cursor 规则 + +## 项目概述 +RustFS 是一个用 Rust 编写的高性能分布式对象存储系统,兼容 S3 API。项目采用模块化架构,支持纠删码存储、多租户管理、可观测性等企业级功能。 + +## 核心架构原则 + +### 1. 模块化设计 +- 项目采用 Cargo workspace 结构,包含多个独立的 crate +- 核心模块:`rustfs`(主服务)、`ecstore`(纠删码存储)、`common`(共享组件) +- 功能模块:`iam`(身份管理)、`madmin`(管理接口)、`crypto`(加密)等 +- 工具模块:`cli`(命令行工具)、`crates/*`(工具库) + +### 2. 异步编程模式 +- 全面使用 `tokio` 异步运行时 +- 优先使用 `async/await` 语法 +- 使用 `async-trait` 处理 trait 中的异步方法 +- 避免阻塞操作,必要时使用 `spawn_blocking` + +### 3. 错误处理策略 +- 使用统一的错误类型 `common::error::Error` +- 支持错误链和上下文信息 +- 使用 `thiserror` 定义具体错误类型 +- 错误转换使用 `downcast_ref` 进行类型检查 + +## 代码风格规范 + +### 1. 格式化配置 +```toml +max_width = 130 +fn_call_width = 90 +single_line_let_else_max_width = 100 +``` + +### 2. 命名约定 +- 使用 `snake_case` 命名函数、变量、模块 +- 使用 `PascalCase` 命名类型、trait、枚举 +- 常量使用 `SCREAMING_SNAKE_CASE` +- 全局变量前缀 `GLOBAL_`,如 `GLOBAL_Endpoints` + +### 3. 文档注释 +- 公共 API 必须有文档注释 +- 使用 `///` 进行文档注释 +- 复杂函数添加 `# Examples` 和 `# Parameters` 说明 +- 错误情况使用 `# Errors` 说明 + +### 4. 导入规范 +- 标准库导入在最前面 +- 第三方 crate 导入在中间 +- 本项目内部导入在最后 +- 使用 `use` 语句分组,组间空行分隔 + +## 异步编程规范 + +### 1. Trait 定义 +```rust +#[async_trait::async_trait] +pub trait StorageAPI: Send + Sync { + async fn get_object(&self, bucket: &str, object: &str) -> Result; +} +``` + +### 2. 错误处理 +```rust +// 使用 ? 操作符传播错误 +async fn example_function() -> Result<()> { + let data = read_file("path").await?; + process_data(data).await?; + Ok(()) +} +``` + +### 3. 并发控制 +- 使用 `Arc` 和 `Mutex`/`RwLock` 进行共享状态管理 +- 优先使用 `tokio::sync` 中的异步锁 +- 避免长时间持有锁 + +## 日志和追踪规范 + +### 1. Tracing 使用 +```rust +#[tracing::instrument(skip(self, data))] +async fn process_data(&self, data: &[u8]) -> Result<()> { + info!("Processing {} bytes", data.len()); + // 实现逻辑 +} +``` + +### 2. 日志级别 +- `error!`: 系统错误,需要立即关注 +- `warn!`: 警告信息,可能影响功能 +- `info!`: 重要的业务信息 +- `debug!`: 调试信息,开发时使用 +- `trace!`: 详细的执行路径 + +### 3. 结构化日志 +```rust +info!( + counter.rustfs_api_requests_total = 1_u64, + key_request_method = %request.method(), + key_request_uri_path = %request.uri().path(), + "API request processed" +); +``` + +## 错误处理规范 + +### 1. 错误类型定义 +```rust +#[derive(Debug, thiserror::Error)] +pub enum MyError { + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + #[error("Custom error: {message}")] + Custom { message: String }, +} +``` + +### 2. 错误转换 +```rust +pub fn to_s3_error(err: Error) -> S3Error { + if let Some(storage_err) = err.downcast_ref::() { + match storage_err { + StorageError::ObjectNotFound(bucket, object) => { + s3_error!(NoSuchKey, "{}/{}", bucket, object) + } + // 其他错误类型... + } + } + // 默认错误处理 +} +``` + +### 3. 错误上下文 +```rust +// 添加错误上下文 +.map_err(|e| Error::from_string(format!("Failed to process {}: {}", path, e)))? +``` + +## 性能优化规范 + +### 1. 内存管理 +- 使用 `Bytes` 而不是 `Vec` 进行零拷贝操作 +- 避免不必要的克隆,使用引用传递 +- 大对象使用 `Arc` 共享 + +### 2. 并发优化 +```rust +// 使用 join_all 进行并发操作 +let futures = disks.iter().map(|disk| disk.operation()); +let results = join_all(futures).await; +``` + +### 3. 缓存策略 +- 使用 `lazy_static` 或 `OnceCell` 进行全局缓存 +- 实现 LRU 缓存避免内存泄漏 + +## 测试规范 + +### 1. 单元测试 +```rust +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[tokio::test] + async fn test_async_function() { + let result = async_function().await; + assert!(result.is_ok()); + } + + #[test_case("input1", "expected1")] + #[test_case("input2", "expected2")] + fn test_with_cases(input: &str, expected: &str) { + assert_eq!(function(input), expected); + } +} +``` + +### 2. 集成测试 +- 使用 `e2e_test` 模块进行端到端测试 +- 模拟真实的存储环境 + +## 安全规范 + +### 1. 内存安全 +- 禁用 `unsafe` 代码(workspace.lints.rust.unsafe_code = "deny") +- 使用 `rustls` 而不是 `openssl` + +### 2. 认证授权 +```rust +// 使用 IAM 系统进行权限检查 +let identity = iam.authenticate(&access_key, &secret_key).await?; +iam.authorize(&identity, &action, &resource).await?; +``` + +## 配置管理规范 + +### 1. 环境变量 +- 使用 `RUSTFS_` 前缀 +- 支持配置文件和环境变量两种方式 +- 提供合理的默认值 + +### 2. 配置结构 +```rust +#[derive(Debug, Deserialize, Clone)] +pub struct Config { + pub address: String, + pub volumes: String, + #[serde(default)] + pub console_enable: bool, +} +``` + +## 依赖管理规范 + +### 1. Workspace 依赖 +- 在 workspace 级别统一管理版本 +- 使用 `workspace = true` 继承配置 + +### 2. 功能特性 +```rust +[features] +default = ["file"] +gpu = ["dep:nvml-wrapper"] +kafka = ["dep:rdkafka"] +``` + +## 部署和运维规范 + +### 1. 容器化 +- 提供 Dockerfile 和 docker-compose 配置 +- 支持多阶段构建优化镜像大小 + +### 2. 可观测性 +- 集成 OpenTelemetry 进行分布式追踪 +- 支持 Prometheus 指标收集 +- 提供 Grafana 仪表板 + +### 3. 健康检查 +```rust +// 实现健康检查端点 +async fn health_check() -> Result { + // 检查各个组件状态 +} +``` + +## 代码审查清单 + +### 1. 功能性 +- [ ] 是否正确处理所有错误情况 +- [ ] 是否有适当的日志记录 +- [ ] 是否有必要的测试覆盖 + +### 2. 性能 +- [ ] 是否避免了不必要的内存分配 +- [ ] 是否正确使用了异步操作 +- [ ] 是否有潜在的死锁风险 + +### 3. 安全性 +- [ ] 是否正确验证输入参数 +- [ ] 是否有适当的权限检查 +- [ ] 是否避免了信息泄露 + +### 4. 可维护性 +- [ ] 代码是否清晰易懂 +- [ ] 是否遵循项目的架构模式 +- [ ] 是否有适当的文档 + +### 5. 代码提交 +- [ ] 是否符合[代码提交规范](https://www.conventionalcommits.org/en/v1.0.0/) +- [ ] 提交的标题要精简,以英文为主,不要使用中文 + +## 常用模式和最佳实践 + +### 1. 资源管理 +```rust +// 使用 RAII 模式管理资源 +pub struct ResourceGuard { + resource: Resource, +} + +impl Drop for ResourceGuard { + fn drop(&mut self) { + // 清理资源 + } +} +``` + +### 2. 配置注入 +```rust +// 使用依赖注入模式 +pub struct Service { + config: Arc, + storage: Arc, +} +``` + +### 3. 优雅关闭 +```rust +// 实现优雅关闭 +async fn shutdown_gracefully(shutdown_rx: &mut Receiver<()>) { + tokio::select! { + _ = shutdown_rx.recv() => { + info!("Received shutdown signal"); + // 执行清理操作 + } + _ = tokio::time::sleep(SHUTDOWN_TIMEOUT) => { + warn!("Shutdown timeout reached"); + } + } +} +``` + +## 特定领域规范 + +### 1. 存储操作 +- 所有存储操作必须支持纠删码 +- 实现读写仲裁机制 +- 支持数据完整性校验 + +### 2. 网络通信 +- 使用 gRPC 进行内部服务通信 +- HTTP/HTTPS 支持 S3 兼容 API +- 实现连接池和重试机制 + +### 3. 元数据管理 +- 使用 FlatBuffers 进行序列化 +- 支持版本控制和迁移 +- 实现元数据缓存 + +这些规则应该作为开发 RustFS 项目时的指导原则,确保代码质量、性能和可维护性。 diff --git a/crates/config/src/constants/app.rs b/crates/config/src/constants/app.rs index c5ad125a..8b52e08a 100644 --- a/crates/config/src/constants/app.rs +++ b/crates/config/src/constants/app.rs @@ -89,3 +89,212 @@ pub const DEFAULT_CONSOLE_PORT: u16 = 9002; /// Default address for rustfs console /// This is the default address for rustfs console. pub const DEFAULT_CONSOLE_ADDRESS: &str = concat!(":", DEFAULT_CONSOLE_PORT); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_app_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"); + } + + #[test] + fn test_logging_constants() { + // 测试日志相关常量 + assert_eq!(DEFAULT_LOG_LEVEL, "info"); + assert!(["trace", "debug", "info", "warn", "error"].contains(&DEFAULT_LOG_LEVEL), + "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] + fn test_environment_constants() { + // 测试环境相关常量 + assert_eq!(ENVIRONMENT, "production"); + assert!(["development", "staging", "production", "test"].contains(&ENVIRONMENT), + "Environment should be a standard environment name"); + } + + #[test] + fn test_connection_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() { + // 测试安全相关常量 + 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"); + + // 在生产环境中,访问密钥和秘密密钥应该不同 + // 这里是默认值,所以相同是可以接受的,但应该在文档中警告 + println!("Warning: Default access key and secret key are the same. Change them in production!"); + } + + #[test] + fn test_file_path_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"); + + assert_eq!(RUSTFS_TLS_CERT, "rustfs_cert.pem"); + assert!(RUSTFS_TLS_CERT.ends_with(".pem"), "TLS cert should be PEM format"); + } + + #[test] + fn test_port_constants() { + // 测试端口相关常量 + assert_eq!(DEFAULT_PORT, 9000); + assert!(DEFAULT_PORT > 1024, "Default port should be above reserved range"); + // u16类型自动保证端口在有效范围内(0-65535) + + assert_eq!(DEFAULT_CONSOLE_PORT, 9002); + assert!(DEFAULT_CONSOLE_PORT > 1024, "Console port should be above reserved range"); + // u16类型自动保证端口在有效范围内(0-65535) + + assert_ne!(DEFAULT_PORT, DEFAULT_CONSOLE_PORT, + "Main port and console port should be different"); + } + + #[test] + fn test_address_constants() { + // 测试地址相关常量 + assert_eq!(DEFAULT_ADDRESS, ":9000"); + assert!(DEFAULT_ADDRESS.starts_with(':'), "Address should start with colon"); + assert!(DEFAULT_ADDRESS.contains(&DEFAULT_PORT.to_string()), + "Address should contain the default port"); + + assert_eq!(DEFAULT_CONSOLE_ADDRESS, ":9002"); + assert!(DEFAULT_CONSOLE_ADDRESS.starts_with(':'), "Console address should start with colon"); + assert!(DEFAULT_CONSOLE_ADDRESS.contains(&DEFAULT_CONSOLE_PORT.to_string()), + "Console address should contain the console port"); + + assert_ne!(DEFAULT_ADDRESS, DEFAULT_CONSOLE_ADDRESS, + "Main address and console address should be different"); + } + + #[test] + fn test_const_str_concat_functionality() { + // 测试const_str::concat宏的功能 + let expected_address = format!(":{}", DEFAULT_PORT); + assert_eq!(DEFAULT_ADDRESS, expected_address); + + let expected_console_address = format!(":{}", DEFAULT_CONSOLE_PORT); + assert_eq!(DEFAULT_CONSOLE_ADDRESS, expected_console_address); + } + + #[test] + fn test_string_constants_validity() { + // 测试字符串常量的有效性 + let string_constants = [ + APP_NAME, + VERSION, + DEFAULT_LOG_LEVEL, + SERVICE_VERSION, + ENVIRONMENT, + DEFAULT_ACCESS_KEY, + DEFAULT_SECRET_KEY, + DEFAULT_OBS_CONFIG, + RUSTFS_TLS_KEY, + RUSTFS_TLS_CERT, + DEFAULT_ADDRESS, + DEFAULT_CONSOLE_ADDRESS, + ]; + + for constant in &string_constants { + assert!(!constant.is_empty(), "String constant should not be empty: {}", constant); + assert!(!constant.starts_with(' '), "String constant should not start with space: {}", constant); + assert!(!constant.ends_with(' '), "String constant should not end with space: {}", constant); + } + } + + #[test] + fn test_numeric_constants_validity() { + // 测试数值常量的有效性 + 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"); + + assert!(DEFAULT_PORT != 0, "Default port should not be zero"); + assert!(DEFAULT_CONSOLE_PORT != 0, "Console port should not be zero"); + } + + #[test] + fn test_security_best_practices() { + // 测试安全最佳实践 + + // 这些是默认值,在生产环境中应该被更改 + println!("Security Warning: Default credentials detected!"); + println!("Access Key: {}", DEFAULT_ACCESS_KEY); + println!("Secret Key: {}", DEFAULT_SECRET_KEY); + println!("These should be changed in production environments!"); + + // 验证密钥长度符合最低安全要求 + assert!(DEFAULT_ACCESS_KEY.len() >= 8, "Access key should be at least 8 characters"); + assert!(DEFAULT_SECRET_KEY.len() >= 8, "Secret key should be at least 8 characters"); + + // 检查默认凭据是否包含常见的不安全模式 + let _insecure_patterns = ["admin", "password", "123456", "default"]; + let _access_key_lower = DEFAULT_ACCESS_KEY.to_lowercase(); + let _secret_key_lower = DEFAULT_SECRET_KEY.to_lowercase(); + + // 注意:这里可以添加更多的安全检查逻辑 + // 例如检查密钥是否包含不安全的模式 + } + + #[test] + fn test_configuration_consistency() { + // 测试配置的一致性 + + // 版本一致性 + assert_eq!(VERSION, SERVICE_VERSION, "Application version should match service version"); + + // 端口不冲突 + let ports = [DEFAULT_PORT, DEFAULT_CONSOLE_PORT]; + let mut unique_ports = std::collections::HashSet::new(); + for port in &ports { + assert!(unique_ports.insert(port), "Port {} is duplicated", port); + } + + // 地址格式一致性 + assert_eq!(DEFAULT_ADDRESS, format!(":{}", DEFAULT_PORT)); + assert_eq!(DEFAULT_CONSOLE_ADDRESS, format!(":{}", DEFAULT_CONSOLE_PORT)); + } +} diff --git a/crates/utils/Cargo.toml b/crates/utils/Cargo.toml index 76cac05c..6641a2a1 100644 --- a/crates/utils/Cargo.toml +++ b/crates/utils/Cargo.toml @@ -22,4 +22,5 @@ default = ["ip"] # features that are enabled by default ip = ["dep:local-ip-address"] # ip characteristics and their dependencies tls = ["dep:rustls", "dep:rustls-pemfile", "dep:rustls-pki-types"] # tls characteristics and their dependencies net = ["ip"] # empty network features -full = ["ip", "tls", "net"] # all features \ No newline at end of file +integration = [] # integration test features +full = ["ip", "tls", "net", "integration"] # all features diff --git a/crates/utils/src/ip.rs b/crates/utils/src/ip.rs index 3b63b12f..f461a849 100644 --- a/crates/utils/src/ip.rs +++ b/crates/utils/src/ip.rs @@ -31,13 +31,175 @@ pub fn get_local_ip_with_default() -> String { #[cfg(test)] mod tests { use super::*; + use std::net::Ipv4Addr; #[test] - fn test_get_local_ip() { - match get_local_ip() { - Some(ip) => println!("the ip address of this machine:{}", ip), - None => println!("Unable to obtain the IP address of the machine"), + fn test_get_local_ip_returns_some_ip() { + // 测试获取本地IP地址,应该返回Some值 + let ip = get_local_ip(); + assert!(ip.is_some(), "Should be able to get local IP address"); + + if let Some(ip_addr) = ip { + println!("Local IP address: {}", ip_addr); + // 验证返回的是有效的IP地址 + match ip_addr { + IpAddr::V4(ipv4) => { + assert!(!ipv4.is_unspecified(), "IPv4 should not be unspecified (0.0.0.0)"); + println!("Got IPv4 address: {}", ipv4); + } + IpAddr::V6(ipv6) => { + assert!(!ipv6.is_unspecified(), "IPv6 should not be unspecified (::)"); + println!("Got IPv6 address: {}", ipv6); + } + } + } + } + + #[test] + fn test_get_local_ip_with_default_never_empty() { + // 测试带默认值的函数永远不会返回空字符串 + let ip_string = get_local_ip_with_default(); + assert!(!ip_string.is_empty(), "IP string should never be empty"); + + // 验证返回的字符串可以解析为有效的IP地址 + let parsed_ip: Result = ip_string.parse(); + assert!(parsed_ip.is_ok(), "Returned string should be a valid IP address: {}", ip_string); + + println!("Local IP with default: {}", ip_string); + } + + #[test] + fn test_get_local_ip_with_default_fallback() { + // 测试默认值是否为127.0.0.1 + let default_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let ip_string = get_local_ip_with_default(); + + // 如果无法获取真实IP,应该返回默认值 + if get_local_ip().is_none() { + assert_eq!(ip_string, default_ip.to_string()); + } + + // 无论如何,返回的都应该是有效的IP地址字符串 + let parsed: Result = ip_string.parse(); + assert!(parsed.is_ok(), "Should always return a valid IP string"); + } + + #[test] + fn test_ip_address_types() { + // 测试IP地址类型的识别 + if let Some(ip) = get_local_ip() { + match ip { + IpAddr::V4(ipv4) => { + // 测试IPv4地址的属性 + println!("IPv4 address: {}", ipv4); + assert!(!ipv4.is_multicast(), "Local IP should not be multicast"); + assert!(!ipv4.is_broadcast(), "Local IP should not be broadcast"); + + // 检查是否为私有地址(通常本地IP是私有的) + let is_private = ipv4.is_private(); + let is_loopback = ipv4.is_loopback(); + println!("IPv4 is private: {}, is loopback: {}", is_private, is_loopback); + } + IpAddr::V6(ipv6) => { + // 测试IPv6地址的属性 + println!("IPv6 address: {}", ipv6); + assert!(!ipv6.is_multicast(), "Local IP should not be multicast"); + + let is_loopback = ipv6.is_loopback(); + println!("IPv6 is loopback: {}", is_loopback); + } + } + } + } + + #[test] + fn test_ip_string_format() { + // 测试IP地址字符串格式 + let ip_string = get_local_ip_with_default(); + + // 验证字符串格式 + assert!(!ip_string.contains(' '), "IP string should not contain spaces"); + assert!(!ip_string.is_empty(), "IP string should not be empty"); + + // 验证可以往返转换 + let parsed_ip: IpAddr = ip_string.parse().expect("Should parse as valid IP"); + let back_to_string = parsed_ip.to_string(); + + // 对于标准IP地址,往返转换应该保持一致 + println!("Original: {}, Parsed back: {}", ip_string, back_to_string); + } + + #[test] + fn test_default_fallback_value() { + // 测试默认回退值的正确性 + let default_ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + assert_eq!(default_ip.to_string(), "127.0.0.1"); + + // 验证默认IP的属性 + if let IpAddr::V4(ipv4) = default_ip { + assert!(ipv4.is_loopback(), "Default IP should be loopback"); + assert!(!ipv4.is_unspecified(), "Default IP should not be unspecified"); + assert!(!ipv4.is_multicast(), "Default IP should not be multicast"); + } + } + + #[test] + fn test_consistency_between_functions() { + // 测试两个函数之间的一致性 + let ip_option = get_local_ip(); + let ip_string = get_local_ip_with_default(); + + match ip_option { + Some(ip) => { + // 如果get_local_ip返回Some,那么get_local_ip_with_default应该返回相同的IP + assert_eq!(ip.to_string(), ip_string, + "Both functions should return the same IP when available"); + } + None => { + // 如果get_local_ip返回None,那么get_local_ip_with_default应该返回默认值 + assert_eq!(ip_string, "127.0.0.1", + "Should return default value when no IP is available"); + } + } + } + + #[test] + fn test_multiple_calls_consistency() { + // 测试多次调用的一致性 + let ip1 = get_local_ip(); + let ip2 = get_local_ip(); + let ip_str1 = get_local_ip_with_default(); + let ip_str2 = get_local_ip_with_default(); + + // 多次调用应该返回相同的结果 + assert_eq!(ip1, ip2, "Multiple calls to get_local_ip should return same result"); + assert_eq!(ip_str1, ip_str2, "Multiple calls to get_local_ip_with_default should return same result"); + } + + #[cfg(feature = "integration")] + #[test] + fn test_network_connectivity() { + // 集成测试:验证获取的IP地址是否可用于网络连接 + if let Some(ip) = get_local_ip() { + match ip { + IpAddr::V4(ipv4) => { + // 对于IPv4,检查是否为有效的网络地址 + assert!(!ipv4.is_unspecified(), "Should not be 0.0.0.0"); + + // 如果不是回环地址,应该是可路由的 + if !ipv4.is_loopback() { + println!("Got routable IPv4: {}", ipv4); + } + } + IpAddr::V6(ipv6) => { + // 对于IPv6,检查是否为有效的网络地址 + assert!(!ipv6.is_unspecified(), "Should not be ::"); + + if !ipv6.is_loopback() { + println!("Got routable IPv6: {}", ipv6); + } + } + } } - assert!(get_local_ip().is_some()); } }