feat: enhance test coverage and fix compilation errors

This commit is contained in:
overtrue
2025-05-25 12:56:43 +08:00
parent 136118ed21
commit a6c3b122bd
4 changed files with 711 additions and 6 deletions

333
.cursorrules Normal file
View 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 项目时的指导原则,确保代码质量、性能和可维护性。

View File

@@ -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));
}
}

View File

@@ -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

View File

@@ -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());
}
}