mirror of
https://github.com/rustfs/rustfs.git
synced 2026-01-17 01:30:33 +00:00
feat: enhance test coverage and fix compilation errors
This commit is contained in:
333
.cursorrules
Normal file
333
.cursorrules
Normal file
@@ -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<ObjectInfo>;
|
||||
}
|
||||
```
|
||||
|
||||
### 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::<StorageError>() {
|
||||
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<u8>` 进行零拷贝操作
|
||||
- 避免不必要的克隆,使用引用传递
|
||||
- 大对象使用 `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<HealthStatus> {
|
||||
// 检查各个组件状态
|
||||
}
|
||||
```
|
||||
|
||||
## 代码审查清单
|
||||
|
||||
### 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<Config>,
|
||||
storage: Arc<dyn StorageAPI>,
|
||||
}
|
||||
```
|
||||
|
||||
### 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 项目时的指导原则,确保代码质量、性能和可维护性。
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
integration = [] # integration test features
|
||||
full = ["ip", "tls", "net", "integration"] # all features
|
||||
|
||||
@@ -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<IpAddr, _> = 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<IpAddr, _> = 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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user